diff --git a/Builds/VisualStudio2013/RippleD.vcxproj b/Builds/VisualStudio2013/RippleD.vcxproj index 0cd80d13aa..51c4243aac 100644 --- a/Builds/VisualStudio2013/RippleD.vcxproj +++ b/Builds/VisualStudio2013/RippleD.vcxproj @@ -2598,7 +2598,7 @@ - + True diff --git a/Builds/VisualStudio2013/RippleD.vcxproj.filters b/Builds/VisualStudio2013/RippleD.vcxproj.filters index d338e23361..e51d83de8f 100644 --- a/Builds/VisualStudio2013/RippleD.vcxproj.filters +++ b/Builds/VisualStudio2013/RippleD.vcxproj.filters @@ -3738,7 +3738,7 @@ ripple\module\data\protocol - + ripple\module\data\protocol diff --git a/src/ripple/module/app/book/Quality.h b/src/ripple/module/app/book/Quality.h index ae053c43ca..97bf2a2381 100644 --- a/src/ripple/module/app/book/Quality.h +++ b/src/ripple/module/app/book/Quality.h @@ -80,7 +80,7 @@ public: Amount rate () const { - return Amount::setRate (m_value); + return amountFromQuality (m_value); } /** Returns the scaled amount with in capped. diff --git a/src/ripple/module/app/book/impl/Quality.cpp b/src/ripple/module/app/book/impl/Quality.cpp index 5ec2e68c22..334361a740 100644 --- a/src/ripple/module/app/book/impl/Quality.cpp +++ b/src/ripple/module/app/book/impl/Quality.cpp @@ -31,7 +31,7 @@ Quality::Quality (std::uint64_t value) } Quality::Quality (Amounts const& amount) - : m_value (Amount::getRate (amount.out, amount.in)) + : m_value (getRate (amount.out, amount.in)) { } @@ -72,7 +72,7 @@ Quality::ceil_in (Amounts const& amount, Amount const& limit) const { if (amount.in > limit) { - Amounts result (limit, Amount::divRound ( + Amounts result (limit, divRound ( limit, rate(), amount.out, true)); // Clamp out if (result.out > amount.out) @@ -89,7 +89,7 @@ Quality::ceil_out (Amounts const& amount, Amount const& limit) const { if (amount.out > limit) { - Amounts result (Amount::mulRound ( + Amounts result (mulRound ( limit, rate(), amount.in, true), limit); // Clamp in if (result.in > amount.in) @@ -110,10 +110,10 @@ composed_quality (Quality const& lhs, Quality const& rhs) Amount const rhs_rate (rhs.rate ()); assert (rhs_rate != zero); - Amount const rate (Amount::mulRound (lhs_rate, rhs_rate, true)); + Amount const rate (mulRound (lhs_rate, rhs_rate, true)); - std::uint64_t const stored_exponent (rate.getExponent () + 100); - std::uint64_t const stored_mantissa (rate.getMantissa ()); + std::uint64_t const stored_exponent (rate.exponent () + 100); + std::uint64_t const stored_mantissa (rate.mantissa()); assert ((stored_exponent > 0) && (stored_exponent <= 255)); diff --git a/src/ripple/module/app/book/impl/Taker.cpp b/src/ripple/module/app/book/impl/Taker.cpp index ca7452866c..65023864f9 100644 --- a/src/ripple/module/app/book/impl/Taker.cpp +++ b/src/ripple/module/app/book/impl/Taker.cpp @@ -57,14 +57,14 @@ Taker::remaining_offer () const assert (m_remain.in > zero); // We scale the output based on the remaining input: - return Amounts (m_remain.in, Amount::divRound ( + return Amounts (m_remain.in, divRound ( m_remain.in, m_quality.rate (), m_remain.out, true)); } assert (m_remain.out > zero); // We scale the input based on the remaining output: - return Amounts (Amount::mulRound ( + return Amounts (mulRound ( m_remain.out, m_quality.rate (), m_remain.in, true), m_remain.out); } @@ -94,7 +94,7 @@ Taker::flow (Amounts amount, Offer const& offer, Account const& taker) { Amount const taker_charge (Amount::saFromRate (taker_charge_rate)); amount = offer.quality ().ceil_in (amount, - Amount::divide (taker_funds, taker_charge)); + divide (taker_funds, taker_charge)); } // Best flow the owner can get. @@ -118,7 +118,7 @@ Taker::flow (Amounts amount, Offer const& offer, Account const& taker) { Amount const owner_charge (Amount::saFromRate (owner_charge_rate)); owner_amount = offer.quality ().ceil_out (owner_amount, - Amount::divide (owner_funds, owner_charge)); + divide (owner_funds, owner_charge)); } // Calculate the amount that will flow through the offer diff --git a/src/ripple/module/app/ledger/LedgerEntrySet.cpp b/src/ripple/module/app/ledger/LedgerEntrySet.cpp index 0394a6be55..c8ed632c16 100644 --- a/src/ripple/module/app/ledger/LedgerEntrySet.cpp +++ b/src/ripple/module/app/ledger/LedgerEntrySet.cpp @@ -1278,7 +1278,7 @@ STAmount LedgerEntrySet::rippleTransferFee ( STAmount saTransitRate ( noIssue(), static_cast (uTransitRate), -9); - STAmount saTransferTotal = STAmount::multiply ( + STAmount saTransferTotal = multiply ( saAmount, saTransitRate, saAmount.issue ()); STAmount saTransferFee = saTransferTotal - saAmount; diff --git a/src/ripple/module/app/ledger/OrderBookIterator.h b/src/ripple/module/app/ledger/OrderBookIterator.h index b9a03019e1..11e400252c 100644 --- a/src/ripple/module/app/ledger/OrderBookIterator.h +++ b/src/ripple/module/app/ledger/OrderBookIterator.h @@ -60,7 +60,7 @@ public: */ STAmount getCurrentRate () const { - return STAmount::setRate (getCurrentQuality()); + return amountFromQuality (getCurrentQuality()); } /** Get the current quality diff --git a/src/ripple/module/app/misc/NetworkOPs.cpp b/src/ripple/module/app/misc/NetworkOPs.cpp index 056c49b6e9..7a313dba4e 100644 --- a/src/ripple/module/app/misc/NetworkOPs.cpp +++ b/src/ripple/module/app/misc/NetworkOPs.cpp @@ -3140,7 +3140,7 @@ void NetworkOPsImp::getBookPage ( else { uTipIndex = sleOfferDir->getIndex (); - saDirRate = STAmount::setRate (Ledger::getQuality (uTipIndex)); + saDirRate = amountFromQuality (Ledger::getQuality (uTipIndex)); lesActive.dirFirst ( uTipIndex, sleOfferDir, uBookEntry, offerIndex); @@ -3220,7 +3220,7 @@ void NetworkOPsImp::getBookPage ( { // Need to charge a transfer fee to offer owner. uOfferRate = uTransferRate; - saOwnerFundsLimit = STAmount::divide ( + saOwnerFundsLimit = divide ( saOwnerFunds, STAmount (noIssue(), uOfferRate, -9)); // TODO(tom): why -9? } @@ -3243,7 +3243,7 @@ void NetworkOPsImp::getBookPage ( saTakerGetsFunded.setJson (jvOffer[jss::taker_gets_funded]); std::min ( - saTakerPays, STAmount::multiply ( + saTakerPays, multiply ( saTakerGetsFunded, saDirRate, saTakerPays)).setJson (jvOffer[jss::taker_pays_funded]); } @@ -3252,7 +3252,7 @@ void NetworkOPsImp::getBookPage ( ? saTakerGetsFunded : std::min ( saOwnerFunds, - STAmount::multiply ( + multiply ( saTakerGetsFunded, STAmount (noIssue(), uOfferRate, -9))); @@ -3388,7 +3388,7 @@ void NetworkOPsImp::getBookPage ( uOfferRate = uTransferRate; // TODO(tom): where does -9 come from?! STAmount amount (noIssue(), uOfferRate, -9); - saOwnerFundsLimit = STAmount::divide (saOwnerFunds, amount); + saOwnerFundsLimit = divide (saOwnerFunds, amount); } else { @@ -3410,7 +3410,7 @@ void NetworkOPsImp::getBookPage ( // TOOD(tom): The result of this expression is not used - what's // going on here? - std::min (saTakerPays, STAmount::multiply ( + std::min (saTakerPays, multiply ( saTakerGetsFunded, saDirRate, saTakerPays)).setJson ( jvOffer[jss::taker_pays_funded]); } @@ -3418,7 +3418,7 @@ void NetworkOPsImp::getBookPage ( STAmount saOwnerPays = (uOfferRate == QUALITY_ONE) ? saTakerGetsFunded : std::min (saOwnerFunds, - STAmount::multiply ( + multiply ( saTakerGetsFunded, STAmount ( noIssue(), uOfferRate, -9))); diff --git a/src/ripple/module/app/paths/PathRequest.cpp b/src/ripple/module/app/paths/PathRequest.cpp index f4cac1cac2..015db503a1 100644 --- a/src/ripple/module/app/paths/PathRequest.cpp +++ b/src/ripple/module/app/paths/PathRequest.cpp @@ -268,7 +268,7 @@ int PathRequest::parseJson (Json::Value const& jvParams, bool complete) if (jvParams.isMember ("destination_amount")) { - if (!saDstAmount.bSetJson (jvParams["destination_amount"]) || + if (! amountFromJsonNoThrow (saDstAmount, jvParams["destination_amount"]) || (saDstAmount.getCurrency ().isZero () && saDstAmount.getIssuer ().isNonZero ()) || (saDstAmount.getCurrency () == badCurrency()) || saDstAmount <= zero) diff --git a/src/ripple/module/app/paths/Pathfinder.cpp b/src/ripple/module/app/paths/Pathfinder.cpp index 52cf96e07e..54ef722d85 100644 --- a/src/ripple/module/app/paths/Pathfinder.cpp +++ b/src/ripple/module/app/paths/Pathfinder.cpp @@ -302,7 +302,7 @@ STPathSet Pathfinder::filterPaths(int iMaxPaths, STPath& extraPath) std::vector vMap; // Ignore paths that move only very small amounts - auto saMinDstAmount = STAmount::divide( + auto saMinDstAmount = divide( mDstAmount, STAmount(iMaxPaths + 2), mDstAmount); // Build map of quality to entry. @@ -348,7 +348,7 @@ STPathSet Pathfinder::filterPaths(int iMaxPaths, STPath& extraPath) else { std::uint64_t uQuality ( - STAmount::getRate (rc.actualAmountOut, rc.actualAmountIn)); + getRate (rc.actualAmountOut, rc.actualAmountIn)); WriteLog (lsDEBUG, Pathfinder) << "findPaths: quality: " << uQuality << diff --git a/src/ripple/module/app/paths/RippleCalc.cpp b/src/ripple/module/app/paths/RippleCalc.cpp index ce91cf2c03..d947cdb7b2 100644 --- a/src/ripple/module/app/paths/RippleCalc.cpp +++ b/src/ripple/module/app/paths/RippleCalc.cpp @@ -193,7 +193,7 @@ TER RippleCalc::rippleCalculate () // When processing, we don't want to complicate directory walking with // deletion. const std::uint64_t uQualityLimit = inputFlags.limitQuality ? - STAmount::getRate (saDstAmountReq_, saMaxAmountReq_) : 0; + getRate (saDstAmountReq_, saMaxAmountReq_) : 0; // Offers that became unfunded. OfferSet unfundedOffersFromBestPaths; diff --git a/src/ripple/module/app/paths/cursor/AdvanceNode.cpp b/src/ripple/module/app/paths/cursor/AdvanceNode.cpp index f7290e70fa..6a946073f3 100644 --- a/src/ripple/module/app/paths/cursor/AdvanceNode.cpp +++ b/src/ripple/module/app/paths/cursor/AdvanceNode.cpp @@ -103,7 +103,7 @@ TER PathCursor::advanceNode (bool const bReverse) const { // Our quality changed since last iteration. // Use the rate from the directory. - node().saOfrRate = STAmount::setRate ( + node().saOfrRate = amountFromQuality ( Ledger::getQuality (node().directory.current)); // For correct ratio node().uEntry = 0; diff --git a/src/ripple/module/app/paths/cursor/DeliverNodeForward.cpp b/src/ripple/module/app/paths/cursor/DeliverNodeForward.cpp index 62d6292376..e9f70a78b9 100644 --- a/src/ripple/module/app/paths/cursor/DeliverNodeForward.cpp +++ b/src/ripple/module/app/paths/cursor/DeliverNodeForward.cpp @@ -97,14 +97,14 @@ TER PathCursor::deliverNodeForward ( node().saRevDeliver - node().saFwdDeliver); // Offer maximum in - Limited by by payout. - auto saInFunded = STAmount::mulRound ( + auto saInFunded = mulRound ( saOutPassFunded, node().saOfrRate, node().saTakerPays, true); // Offer maximum in with fees. - auto saInTotal = STAmount::mulRound (saInFunded, saInFeeRate, true); + auto saInTotal = mulRound (saInFunded, saInFeeRate, true); auto saInRemaining = saInReq - saInAct - saInFees; if (saInRemaining < zero) @@ -115,11 +115,11 @@ TER PathCursor::deliverNodeForward ( // In without fees. auto saInPassAct = std::min ( - node().saTakerPays, STAmount::divRound ( + node().saTakerPays, divRound ( saInSum, saInFeeRate, true)); // Out limited by in remaining. - auto outPass = STAmount::divRound ( + auto outPass = divRound ( saInPassAct, node().saOfrRate, node().saTakerGets, true); STAmount saOutPassMax = std::min (saOutPassFunded, outPass); @@ -235,10 +235,10 @@ TER PathCursor::deliverNodeForward ( // previous. assert (saOutPassAct < saOutPassMax); - auto inPassAct = STAmount::mulRound ( + auto inPassAct = mulRound ( saOutPassAct, node().saOfrRate, saInReq, true); saInPassAct = std::min (node().saTakerPays, inPassAct); - auto inPassFees = STAmount::mulRound ( + auto inPassFees = mulRound ( saInPassAct, saInFeeRate, true); saInPassFees = std::min (saInPassFeesMax, inPassFees); } diff --git a/src/ripple/module/app/paths/cursor/DeliverNodeReverse.cpp b/src/ripple/module/app/paths/cursor/DeliverNodeReverse.cpp index 1885166f63..c494445f83 100644 --- a/src/ripple/module/app/paths/cursor/DeliverNodeReverse.cpp +++ b/src/ripple/module/app/paths/cursor/DeliverNodeReverse.cpp @@ -151,7 +151,7 @@ TER PathCursor::deliverNodeReverse ( // as a cost to taker. // // Round down: prefer liquidity rather than microscopic fees. - STAmount saOutPlusFees = STAmount::mulRound ( + STAmount saOutPlusFees = mulRound ( saOutPassAct, saOutFeeRate, false); // Offer out with fees. @@ -172,7 +172,7 @@ TER PathCursor::deliverNodeReverse ( // Round up: prefer liquidity rather than microscopic fees. But, // limit by requested. - auto fee = STAmount::divRound (saOutPlusFees, saOutFeeRate, true); + auto fee = divRound (saOutPlusFees, saOutFeeRate, true); saOutPassAct = std::min (saOutPassReq, fee); WriteLog (lsTRACE, RippleCalc) @@ -183,7 +183,7 @@ TER PathCursor::deliverNodeReverse ( } // Compute portion of input needed to cover actual output. - auto outputFee = STAmount::mulRound ( + auto outputFee = mulRound ( saOutPassAct, node().saOfrRate, node().saTakerPays, true); STAmount saInPassReq = std::min (node().saTakerPays, outputFee); STAmount saInPassAct; @@ -248,10 +248,10 @@ TER PathCursor::deliverNodeReverse ( if (saInPassAct < saInPassReq) { // Adjust output to conform to limited input. - auto outputRequirements = STAmount::divRound ( + auto outputRequirements = divRound ( saInPassAct, node().saOfrRate, node().saTakerGets, true); saOutPassAct = std::min (saOutPassReq, outputRequirements); - auto outputFees = STAmount::mulRound ( + auto outputFees = mulRound ( saOutPassAct, saOutFeeRate, true); saOutPlusFees = std::min (node().saOfferFunds, outputFees); diff --git a/src/ripple/module/app/paths/cursor/ForwardLiquidityForAccount.cpp b/src/ripple/module/app/paths/cursor/ForwardLiquidityForAccount.cpp index 715a257600..3bc3bd9230 100644 --- a/src/ripple/module/app/paths/cursor/ForwardLiquidityForAccount.cpp +++ b/src/ripple/module/app/paths/cursor/ForwardLiquidityForAccount.cpp @@ -152,7 +152,7 @@ TER PathCursor::forwardLiquidityForAccount () const auto& saCurReceive = pathState_.outPass(); STAmount saIssueCrd = uQualityIn >= QUALITY_ONE ? previousNode().saFwdIssue // No fee. - : STAmount::mulRound ( + : mulRound ( previousNode().saFwdIssue, STAmount (noIssue(), uQualityIn, -9), true); // Amount to credit. diff --git a/src/ripple/module/app/paths/cursor/NextIncrement.cpp b/src/ripple/module/app/paths/cursor/NextIncrement.cpp index 053bb6b83e..9808a442a4 100644 --- a/src/ripple/module/app/paths/cursor/NextIncrement.cpp +++ b/src/ripple/module/app/paths/cursor/NextIncrement.cpp @@ -54,7 +54,7 @@ void PathCursor::nextIncrement (LedgerEntrySet const& lesCheckpoint) const throw std::runtime_error ("Made no progress."); // Calculate relative quality. - pathState_.setQuality(STAmount::getRate ( + pathState_.setQuality(getRate ( pathState_.outPass(), pathState_.inPass())); WriteLog (lsTRACE, RippleCalc) diff --git a/src/ripple/module/app/paths/cursor/RippleLiquidity.cpp b/src/ripple/module/app/paths/cursor/RippleLiquidity.cpp index 187807a8c3..611042046c 100644 --- a/src/ripple/module/app/paths/cursor/RippleLiquidity.cpp +++ b/src/ripple/module/app/paths/cursor/RippleLiquidity.cpp @@ -126,7 +126,7 @@ void rippleLiquidity ( // If the quality is worse than the previous WriteLog (lsTRACE, RippleCalc) << "rippleLiquidity: Fee"; - std::uint64_t uRate = STAmount::getRate ( + std::uint64_t uRate = getRate ( STAmount (uQualityOut), STAmount (uQualityIn)); // If the next rate is at least as good as the current rate, process. @@ -136,11 +136,11 @@ void rippleLiquidity ( auto uCurIssuerID = saCur.getIssuer (); // current actual = current request * (quality out / quality in). - auto numerator = STAmount::mulRound ( + auto numerator = mulRound ( saCur, uQualityOut, {currency, uCurIssuerID}, true); // True means "round up" to get best flow. - STAmount saCurIn = STAmount::divRound ( + STAmount saCurIn = divRound ( numerator, uQualityIn, {currency, uCurIssuerID}, true); WriteLog (lsTRACE, RippleCalc) @@ -169,11 +169,11 @@ void rippleLiquidity ( // going the other way Issue issue{currency, uCurIssuerID}; - auto numerator = STAmount::mulRound ( + auto numerator = mulRound ( saPrv, uQualityIn, issue, true); // A part of current. All of previous. (Cur is the driver // variable.) - STAmount saCurOut = STAmount::divRound ( + STAmount saCurOut = divRound ( numerator, uQualityOut, issue, true); WriteLog (lsTRACE, RippleCalc) diff --git a/src/ripple/module/app/transactors/CreateOffer.cpp b/src/ripple/module/app/transactors/CreateOffer.cpp index aa22348de2..e735b34252 100644 --- a/src/ripple/module/app/transactors/CreateOffer.cpp +++ b/src/ripple/module/app/transactors/CreateOffer.cpp @@ -153,7 +153,7 @@ public: STAmount saTakerPays = mTxn.getFieldAmount (sfTakerPays); STAmount saTakerGets = mTxn.getFieldAmount (sfTakerGets); - if (!saTakerPays.isLegalNet () || !saTakerGets.isLegalNet ()) + if (!isLegalNet (saTakerPays) || !isLegalNet (saTakerGets)) return temBAD_AMOUNT; auto const& uPaysIssuerID = saTakerPays.getIssuer (); @@ -192,7 +192,7 @@ public: // This is the original rate of this offer, and is the rate at which it will // be placed, even if crossing offers change the amounts. - std::uint64_t const uRate = STAmount::getRate (saTakerGets, saTakerPays); + std::uint64_t const uRate = getRate (saTakerGets, saTakerPays); TER terResult (tesSUCCESS); diff --git a/src/ripple/module/app/transactors/Payment.cpp b/src/ripple/module/app/transactors/Payment.cpp index a40b5aeef6..56fdf70bf6 100644 --- a/src/ripple/module/app/transactors/Payment.cpp +++ b/src/ripple/module/app/transactors/Payment.cpp @@ -63,7 +63,7 @@ public: else maxSourceAmount = STAmount ( {saDstAmount.getCurrency (), mTxnAccountID}, - saDstAmount.getMantissa (), saDstAmount.getExponent (), + saDstAmount.mantissa(), saDstAmount.exponent (), saDstAmount < zero); auto const& uSrcCurrency = maxSourceAmount.getCurrency (); auto const& uDstCurrency = saDstAmount.getCurrency (); @@ -75,7 +75,7 @@ public: "maxSourceAmount=" << maxSourceAmount.getFullText () << " saDstAmount=" << saDstAmount.getFullText (); - if (!saDstAmount.isLegalNet () || !maxSourceAmount.isLegalNet ()) + if (!isLegalNet (saDstAmount) || !isLegalNet (maxSourceAmount)) return temBAD_AMOUNT; if (uTxFlags & tfPaymentMask) diff --git a/src/ripple/module/app/transactors/SetTrust.cpp b/src/ripple/module/app/transactors/SetTrust.cpp index 6f8a16596f..b5b03f63d9 100644 --- a/src/ripple/module/app/transactors/SetTrust.cpp +++ b/src/ripple/module/app/transactors/SetTrust.cpp @@ -70,7 +70,7 @@ public: std::uint32_t uQualityIn (bQualityIn ? mTxn.getFieldU32 (sfQualityIn) : 0); std::uint32_t uQualityOut (bQualityOut ? mTxn.getFieldU32 (sfQualityOut) : 0); - if (!saLimitAmount.isLegalNet ()) + if (!isLegalNet (saLimitAmount)) return temBAD_AMOUNT; if (bQualityOut && QUALITY_ONE == uQualityOut) diff --git a/src/ripple/module/app/transactors/Transactor.cpp b/src/ripple/module/app/transactors/Transactor.cpp index 05d779d128..a126fced33 100644 --- a/src/ripple/module/app/transactors/Transactor.cpp +++ b/src/ripple/module/app/transactors/Transactor.cpp @@ -106,7 +106,7 @@ TER Transactor::payFee () { STAmount saPaid = mTxn.getTransactionFee (); - if (!saPaid.isLegalNet ()) + if (!isLegalNet (saPaid)) return temBAD_AMOUNT; // Only check fee is sufficient when the ledger is open. diff --git a/src/ripple/module/data/protocol/STAmount.cpp b/src/ripple/module/data/protocol/STAmount.cpp index 4a421c8a31..1738c67247 100644 --- a/src/ripple/module/data/protocol/STAmount.cpp +++ b/src/ripple/module/data/protocol/STAmount.cpp @@ -17,148 +17,366 @@ */ //============================================================================== -#include - #include +#include +#include namespace ripple { +static const std::uint64_t tenTo14 = 100000000000000ull; +static const std::uint64_t tenTo14m1 = tenTo14 - 1; +static const std::uint64_t tenTo17 = tenTo14 * 1000; + +//------------------------------------------------------------------------------ + +STAmount::STAmount (SField::ref name, Issue const& issue, + mantissa_type mantissa, exponent_type exponent, + bool native, bool negative) + : SerializedType (name) + , mIssue (issue) + , mValue (mantissa) + , mOffset (exponent) + , mIsNative (native) + , mIsNegative (negative) +{ + canonicalize(); +} + +STAmount::STAmount (SField::ref name, Issue const& issue, + mantissa_type mantissa, exponent_type exponent, + bool native, bool negative, unchecked) + : SerializedType (name) + , mIssue (issue) + , mValue (mantissa) + , mOffset (exponent) + , mIsNative (native) + , mIsNegative (negative) +{ +} + +STAmount::STAmount (SField::ref name, std::int64_t mantissa) + : SerializedType (name) + , mOffset (0) + , mIsNative (true) +{ + set (mantissa); +} + +STAmount::STAmount (SField::ref name, + std::uint64_t mantissa, bool negative) + : SerializedType (name) + , mValue (mantissa) + , mOffset (0) + , mIsNative (true) + , mIsNegative (negative) +{ +} + +STAmount::STAmount (SField::ref name, Issue const& issue, + std::uint64_t mantissa, int exponent, bool negative) + : SerializedType (name) + , mIssue (issue) + , mValue (mantissa) + , mOffset (exponent) + , mIsNegative (negative) +{ + canonicalize (); +} + +//------------------------------------------------------------------------------ + +STAmount::STAmount (std::uint64_t mantissa, bool negative) + : mValue (mantissa) + , mOffset (0) + , mIsNative (true) + , mIsNegative (mantissa != 0 && negative) +{ +} + +STAmount::STAmount (Issue const& issue, + std::uint64_t mantissa, int exponent, bool negative) + : mIssue (issue) + , mValue (mantissa) + , mOffset (exponent) + , mIsNegative (negative) +{ + canonicalize (); +} + +STAmount::STAmount (Issue const& issue, + std::int64_t mantissa, int exponent) + : mIssue (issue) + , mOffset (exponent) +{ + set (mantissa); + canonicalize (); +} + +STAmount::STAmount (Issue const& issue, + std::uint32_t mantissa, int exponent, bool negative) + : STAmount (issue, static_cast(mantissa), exponent, negative) +{ +} + +STAmount::STAmount (Issue const& issue, + int mantissa, int exponent) + : STAmount (issue, static_cast(mantissa), exponent) +{ +} + +std::unique_ptr +STAmount::construct (SerializerIterator& sit, SField::ref name) +{ + std::uint64_t value = sit.get64 (); + + // native + if ((value & cNotNative) == 0) + { + // positive + if ((value & cPosNative) != 0) + return std::make_unique (name, value & ~cPosNative, false); + + // negative + if (value == 0) + throw std::runtime_error ("negative zero is not canonical"); + + return std::make_unique (name, value, true); + } + + Issue issue; + issue.currency.copyFrom (sit.get160 ()); + + if (isXRP (issue.currency)) + throw std::runtime_error ("invalid native currency"); + + issue.account.copyFrom (sit.get160 ()); + + if (isXRP (issue.account)) + throw std::runtime_error ("invalid native account"); + + // 10 bits for the offset, sign and "not native" flag + int offset = static_cast (value >> (64 - 10)); + + value &= ~ (1023ull << (64 - 10)); + + if (value) + { + bool isNegative = (offset & 256) == 0; + offset = (offset & 255) - 97; // center the range + + if (value < cMinValue || + value > cMaxValue || + offset < cMinOffset || + offset > cMaxOffset) + { + throw std::runtime_error ("invalid currency value"); + } + + return std::make_unique (name, issue, value, offset, isNegative); + } + + if (offset != 512) + throw std::runtime_error ("invalid currency value"); + + return std::make_unique (name, issue); +} + +STAmount +STAmount::createFromInt64 (SField::ref name, std::int64_t value) +{ + return value >= 0 + ? STAmount (name, static_cast (value), false) + : STAmount (name, static_cast (-value), true); +} + +STAmount STAmount::deserialize (SerializerIterator& it) +{ + auto s = construct (it, sfGeneric); + + if (!s) + throw std::runtime_error("Deserialization error"); + + return STAmount (*s); +} + +//------------------------------------------------------------------------------ +// +// Operators +// +//------------------------------------------------------------------------------ + +bool STAmount::isComparable (STAmount const& t) const +{ + // are these two STAmount instances in the same currency + if (mIsNative) return t.mIsNative; + + if (t.mIsNative) return false; + + return mIssue.currency == t.mIssue.currency; +} + +void STAmount::throwComparable (STAmount const& t) const +{ + // throw an exception if these two STAmount instances are incomparable + if (!isComparable (t)) + throw std::runtime_error ("amounts are not comparable"); +} + +STAmount& STAmount::operator+= (STAmount const& a) +{ + *this = *this + a; + return *this; +} + +STAmount& STAmount::operator-= (STAmount const& a) +{ + *this = *this - a; + return *this; +} + +STAmount& STAmount::operator+= (std::uint64_t v) +{ + assert (mIsNative); + if (!mIsNative) + throw std::runtime_error ("not native"); + // VFALCO TODO The cast looks dangerous, is it needed? + setSNValue (getSNValue () + static_cast (v)); + return *this; +} + +STAmount& STAmount::operator-= (std::uint64_t v) +{ + assert (mIsNative); + + if (!mIsNative) + throw std::runtime_error ("not native"); + + // VFALCO TODO The cast looks dangerous, is it needed? + setSNValue (getSNValue () - static_cast (v)); + return *this; +} + +STAmount& STAmount::operator= (std::uint64_t v) +{ + // Does not copy name, does not change currency type. + mOffset = 0; + mValue = v; + mIsNegative = false; + if (!mIsNative) + canonicalize (); + return *this; +} + + + +STAmount operator+ (STAmount const& v1, STAmount const& v2) +{ + v1.throwComparable (v2); + + if (v2 == zero) + return v1; + + if (v1 == zero) + { + // Result must be in terms of v1 currency and issuer. + return STAmount (v1.getFName (), v1.mIssue, + v2.mValue, v2.mOffset, v2.mIsNegative); + } + + if (v1.mIsNative) + return STAmount (v1.getFName (), v1.getSNValue () + v2.getSNValue ()); + + int ov1 = v1.mOffset, ov2 = v2.mOffset; + std::int64_t vv1 = static_cast (v1.mValue); + std::int64_t vv2 = static_cast (v2.mValue); + + if (v1.mIsNegative) + vv1 = -vv1; + + if (v2.mIsNegative) + vv2 = -vv2; + + while (ov1 < ov2) + { + vv1 /= 10; + ++ov1; + } + + while (ov2 < ov1) + { + vv2 /= 10; + ++ov2; + } + + // This addition cannot overflow an std::int64_t. It can overflow an + // STAmount and the constructor will throw. + + std::int64_t fv = vv1 + vv2; + + if ((fv >= -10) && (fv <= 10)) + return STAmount (v1.getFName (), v1.mIssue); + if (fv >= 0) + return STAmount (v1.getFName (), v1.mIssue, fv, ov1, false); + return STAmount (v1.getFName (), v1.mIssue, -fv, ov1, true); +} + +STAmount operator- (STAmount const& v1, STAmount const& v2) +{ + v1.throwComparable (v2); + + if (v2 == zero) + return v1; + + if (v2.mIsNative) + { + // XXX This could be better, check for overflow and that maximum range + // is covered. + return STAmount::createFromInt64 ( + v1.getFName (), v1.getSNValue () - v2.getSNValue ()); + } + + int ov1 = v1.mOffset, ov2 = v2.mOffset; + auto vv1 = static_cast (v1.mValue); + auto vv2 = static_cast (v2.mValue); + + if (v1.mIsNegative) + vv1 = -vv1; + + if (v2.mIsNegative) + vv2 = -vv2; + + while (ov1 < ov2) + { + vv1 /= 10; + ++ov1; + } + + while (ov2 < ov1) + { + vv2 /= 10; + ++ov2; + } + + // this subtraction cannot overflow an std::int64_t, it can overflow an STAmount and the constructor will throw + + std::int64_t fv = vv1 - vv2; + + if ((fv >= -10) && (fv <= 10)) + return STAmount (v1.getFName (), v1.mIssue); + if (fv >= 0) + return STAmount (v1.getFName (), v1.mIssue, fv, ov1, false); + return STAmount (v1.getFName (), v1.mIssue, -fv, ov1, true); +} + +//------------------------------------------------------------------------------ + std::uint64_t STAmount::uRateOne = - STAmount::getRate (STAmount (1), STAmount (1)); + getRate (STAmount (1), STAmount (1)); -std::string STAmount::getHumanCurrency () const +// Note: mIsNative and mIssue.currency must be set already! +bool +STAmount::setValue (std::string const& sAmount) { - return to_string (mIssue.currency); -} - -bool STAmount::bSetJson (Json::Value const& jvSource) -{ - try - { - STAmount saParsed (sfGeneric, jvSource); - - *this = saParsed; - - return true; - } - catch (const std::exception& e) - { - WriteLog (lsINFO, STAmount) << "bSetJson(): caught: " << e.what (); - return false; - } -} - -STAmount::STAmount (SField::ref n, Json::Value const& v) - : SerializedType (n), mValue (0), mOffset (0), mIsNegative (false) -{ - Json::Value value, currency, issuer; - - if (v.isObject ()) - { - WriteLog (lsTRACE, STAmount) << - "value='" << v["value"].asString () << - "', currency='" << v["currency"].asString () << - "', issuer='" << v["issuer"].asString () << - "')"; - - value = v[jss::value]; - currency = v[jss::currency]; - issuer = v[jss::issuer]; - } - else if (v.isArray ()) - { - value = v.get (Json::UInt (0), 0); - currency = v.get (Json::UInt (1), Json::nullValue); - issuer = v.get (Json::UInt (2), Json::nullValue); - } - else if (v.isString ()) - { - std::string val = v.asString (); - std::vector elements; - boost::split (elements, val, boost::is_any_of ("\t\n\r ,/")); - - if (elements.size () > 3) - throw std::runtime_error ("invalid amount string"); - - value = elements[0]; - - if (elements.size () > 1) - currency = elements[1]; - - if (elements.size () > 2) - issuer = elements[2]; - } - else - value = v; - - mIsNative = !currency.isString () || currency.asString ().empty () || (currency.asString () == systemCurrencyCode()); - - if (mIsNative) - { - if (v.isObject ()) - throw std::runtime_error ("XRP may not be specified as an object"); - } - else - { - // non-XRP - if (!to_currency (mIssue.currency, currency.asString ())) - throw std::runtime_error ("invalid currency"); - - if (!issuer.isString () - || !to_issuer (mIssue.account, issuer.asString ())) - throw std::runtime_error ("invalid issuer"); - - if (isXRP (*this)) - throw std::runtime_error ("invalid issuer"); - } - - if (value.isInt ()) - { - if (value.asInt () >= 0) - mValue = value.asInt (); - else - { - mValue = -value.asInt (); - mIsNegative = true; - } - - canonicalize (); - } - else if (value.isUInt ()) - { - mValue = v.asUInt (); - - canonicalize (); - } - else if (value.isString ()) - { - if (mIsNative) - { - std::int64_t val = beast::lexicalCastThrow (value.asString ()); - - if (val >= 0) - mValue = val; - else - { - mValue = -val; - mIsNegative = true; - } - - canonicalize (); - } - else - { - setValue (value.asString ()); - } - } - else - throw std::runtime_error ("invalid amount type"); -} - -bool STAmount::setValue (std::string const& sAmount) -{ - // Note: mIsNative and mIssue.currency must be set already! - static boost::regex reNumber ( "\\`([+-]?)(\\d*)(\\.(\\d*))?([eE]([+-]?)(\\d+))?\\'"); boost::smatch smMatch; @@ -288,214 +506,21 @@ bool STAmount::setFullValue (std::string const& sAmount, std::string const& sCur return setValue (sAmount); } -// amount = value * [10 ^ offset] -// Representation range is 10^80 - 10^(-80). -// On the wire, high 8 bits are (offset+142), low 56 bits are value. -// -// Value is zero if amount is zero, otherwise value is 10^15 to (10^16 - 1) -// inclusive. - -void STAmount::canonicalize () -{ - if (isXRP (*this)) - { - // native currency amounts should always have an offset of zero - mIsNative = true; - - if (mValue == 0) - { - mOffset = 0; - mIsNegative = false; - return; - } - - while (mOffset < 0) - { - mValue /= 10; - ++mOffset; - } - - while (mOffset > 0) - { - mValue *= 10; - --mOffset; - } - - if (mValue > cMaxNative) - throw std::runtime_error ("Native currency amount out of range"); - - return; - } - - mIsNative = false; - - if (mValue == 0) - { - mOffset = -100; - mIsNegative = false; - return; - } - - while ((mValue < cMinValue) && (mOffset > cMinOffset)) - { - mValue *= 10; - --mOffset; - } - - while (mValue > cMaxValue) - { - if (mOffset >= cMaxOffset) - throw std::runtime_error ("value overflow"); - - mValue /= 10; - ++mOffset; - } - - if ((mOffset < cMinOffset) || (mValue < cMinValue)) - { - mValue = 0; - mOffset = 0; - mIsNegative = false; - } - - if (mOffset > cMaxOffset) - throw std::runtime_error ("value overflow"); - - assert ((mValue == 0) || ((mValue >= cMinValue) && (mValue <= cMaxValue))); - assert ((mValue == 0) || ((mOffset >= cMinOffset) && (mOffset <= cMaxOffset))); - assert ((mValue != 0) || (mOffset != -100)); -} - -void STAmount::add (Serializer& s) const -{ - if (mIsNative) - { - assert (mOffset == 0); - - if (!mIsNegative) - s.add64 (mValue | cPosNative); - else - s.add64 (mValue); - } - else - { - if (*this == zero) - s.add64 (cNotNative); - else if (mIsNegative) // 512 = not native - s.add64 (mValue | (static_cast (mOffset + 512 + 97) << (64 - 10))); - else // 256 = positive - s.add64 (mValue | (static_cast (mOffset + 512 + 256 + 97) << (64 - 10))); - - s.add160 (mIssue.currency); - s.add160 (mIssue.account); - } -} - -STAmount STAmount::createFromInt64 (SField::ref name, std::int64_t value) -{ - return value >= 0 - ? STAmount (name, static_cast (value), false) - : STAmount (name, static_cast (-value), true); -} - -void STAmount::setValue (STAmount const& a) -{ - mIssue = a.mIssue; - mValue = a.mValue; - mOffset = a.mOffset; - mIsNative = a.mIsNative; - mIsNegative = a.mIsNegative; -} - void STAmount::setIssue (Issue const& issue) { mIssue = std::move(issue); mIsNative = isXRP (*this); } -int STAmount::compare (STAmount const& a) const +std::uint64_t +STAmount::getNValue () const { - // Compares the value of a to the value of this STAmount, amounts must be comparable - if (mIsNegative != a.mIsNegative) - return mIsNegative ? -1 : 1; - - if (!mValue) - { - if (a.mIsNegative) return 1; - - return a.mValue ? -1 : 0; - } - - if (!a.mValue) return 1; - - if (mOffset > a.mOffset) return mIsNegative ? -1 : 1; - - if (mOffset < a.mOffset) return mIsNegative ? 1 : -1; - - if (mValue > a.mValue) return mIsNegative ? -1 : 1; - - if (mValue < a.mValue) return mIsNegative ? 1 : -1; - - return 0; + if (!mIsNative) + throw std::runtime_error ("not native"); + return mValue; } -std::unique_ptr -STAmount::construct (SerializerIterator& sit, SField::ref name) -{ - std::uint64_t value = sit.get64 (); - - // native - if ((value & cNotNative) == 0) - { - // positive - if ((value & cPosNative) != 0) - return std::make_unique (name, value & ~cPosNative, false); - - // negative - if (value == 0) - throw std::runtime_error ("negative zero is not canonical"); - - return std::make_unique (name, value, true); - } - - Issue issue; - issue.currency.copyFrom (sit.get160 ()); - - if (isXRP (issue.currency)) - throw std::runtime_error ("invalid native currency"); - - issue.account.copyFrom (sit.get160 ()); - - if (isXRP (issue.account)) - throw std::runtime_error ("invalid native account"); - - // 10 bits for the offset, sign and "not native" flag - int offset = static_cast (value >> (64 - 10)); - - value &= ~ (1023ull << (64 - 10)); - - if (value) - { - bool isNegative = (offset & 256) == 0; - offset = (offset & 255) - 97; // center the range - - if (value < cMinValue || - value > cMaxValue || - offset < cMinOffset || - offset > cMaxOffset) - { - throw std::runtime_error ("invalid currency value"); - } - - return std::make_unique (name, issue, value, offset, isNegative); - } - - if (offset != 512) - throw std::runtime_error ("invalid currency value"); - - return std::make_unique (name, issue); -} - -std::int64_t STAmount::getSNValue () const +std::int64_t +STAmount::getSNValue () const { // signed native value if (!mIsNative) @@ -507,7 +532,21 @@ std::int64_t STAmount::getSNValue () const return static_cast (mValue); } -void STAmount::setSNValue (std::int64_t v) +std::string STAmount::getHumanCurrency () const +{ + return to_string (mIssue.currency); +} + +void +STAmount::setNValue (std::uint64_t v) +{ + if (!mIsNative) + throw std::runtime_error ("not native"); + mValue = v; +} + +void +STAmount::setSNValue (std::int64_t v) { if (!mIsNative) throw std::runtime_error ("not native"); @@ -523,7 +562,110 @@ void STAmount::setSNValue (std::int64_t v) } } -std::string STAmount::getText () const +// Convert an offer into an index amount so they sort by rate. +// A taker will take the best, lowest, rate first. +// (e.g. a taker will prefer pay 1 get 3 over pay 1 get 2. +// --> offerOut: takerGets: How much the offerer is selling to the taker. +// --> offerIn: takerPays: How much the offerer is receiving from the taker. +// <-- uRate: normalize(offerIn/offerOut) +// A lower rate is better for the person taking the order. +// The taker gets more for less with a lower rate. +// Zero is returned if the offer is worthless. +std::uint64_t +getRate (STAmount const& offerOut, STAmount const& offerIn) +{ + if (offerOut == zero) + return 0; + try + { + STAmount r = divide (offerIn, offerOut, noIssue()); + if (r == zero) // offer is too good + return 0; + assert ((r.exponent() >= -100) && (r.exponent() <= 155)); + std::uint64_t ret = r.exponent() + 100; + return (ret << (64 - 8)) | r.mantissa(); + } + catch (...) + { + } + + // overflow -- very bad offer + return 0; +} + +void STAmount::setJson (Json::Value& elem) const +{ + elem = Json::objectValue; + + if (!mIsNative) + { + // It is an error for currency or issuer not to be specified for valid + // json. + elem[jss::value] = getText (); + elem[jss::currency] = getHumanCurrency (); + elem[jss::issuer] = to_string (mIssue.account); + } + else + { + elem = getText (); + } +} + +// VFALCO What does this perplexing function do? +void STAmount::roundSelf () +{ + if (mIsNative) + return; + + std::uint64_t valueDigits = mValue % 1000000000ull; + + if (valueDigits == 1) + { + mValue -= 1; + + if (mValue < cMinValue) + canonicalize (); + } + else if (valueDigits == 999999999ull) + { + mValue += 1; + + if (mValue > cMaxValue) + canonicalize (); + } +} + +//------------------------------------------------------------------------------ +// +// SerializedType +// +//------------------------------------------------------------------------------ + +std::string +STAmount::getFullText () const +{ + std::string ret; + + ret.reserve(64); + ret = getText () + "/" + getHumanCurrency (); + + if (!mIsNative) + { + ret += "/"; + + if (isXRP (*this)) + ret += "0"; + else if (mIssue.account == noAccount()) + ret += "1"; + else + ret += to_string (mIssue.account); + } + + return ret; +} + +std::string +STAmount::getText () const { // keep full internal accuracy, but make more human friendly if posible if (*this == zero) @@ -612,272 +754,435 @@ std::string STAmount::getText () const return ret; } -bool STAmount::isComparable (STAmount const& t) const +Json::Value +STAmount::getJson (int) const { - // are these two STAmount instances in the same currency - if (mIsNative) return t.mIsNative; - - if (t.mIsNative) return false; - - return mIssue.currency == t.mIssue.currency; + Json::Value elem; + setJson (elem); + return elem; } -bool STAmount::isEquivalent (const SerializedType& t) const +void +STAmount::add (Serializer& s) const +{ + if (mIsNative) + { + assert (mOffset == 0); + + if (!mIsNegative) + s.add64 (mValue | cPosNative); + else + s.add64 (mValue); + } + else + { + if (*this == zero) + s.add64 (cNotNative); + else if (mIsNegative) // 512 = not native + s.add64 (mValue | (static_cast (mOffset + 512 + 97) << (64 - 10))); + else // 256 = positive + s.add64 (mValue | (static_cast (mOffset + 512 + 256 + 97) << (64 - 10))); + + s.add160 (mIssue.currency); + s.add160 (mIssue.account); + } +} + +bool +STAmount::isEquivalent (const SerializedType& t) const { const STAmount* v = dynamic_cast (&t); return v && (*v == *this); } -void STAmount::throwComparable (STAmount const& t) const +STAmount* +STAmount::duplicate () const { - // throw an exception if these two STAmount instances are incomparable - if (!isComparable (t)) - throw std::runtime_error ("amounts are not comparable"); + return new STAmount (*this); } -bool STAmount::operator== (STAmount const& a) const +//------------------------------------------------------------------------------ + +// amount = value * [10 ^ offset] +// Representation range is 10^80 - 10^(-80). +// On the wire, high 8 bits are (offset+142), low 56 bits are value. +// +// Value is zero if amount is zero, otherwise value is 10^15 to (10^16 - 1) +// inclusive. +void STAmount::canonicalize () { - return isComparable (a) && - mIsNegative == a.mIsNegative && - mOffset == a.mOffset && - mValue == a.mValue; -} - -bool STAmount::operator!= (STAmount const& a) const -{ - return mOffset != a.mOffset || - mValue != a.mValue || - mIsNegative != a.mIsNegative || - !isComparable (a); -} - -bool STAmount::operator< (STAmount const& a) const -{ - throwComparable (a); - return compare (a) < 0; -} - -bool STAmount::operator> (STAmount const& a) const -{ - throwComparable (a); - return compare (a) > 0; -} - -bool STAmount::operator<= (STAmount const& a) const -{ - throwComparable (a); - return compare (a) <= 0; -} - -bool STAmount::operator>= (STAmount const& a) const -{ - throwComparable (a); - return compare (a) >= 0; -} - -STAmount& STAmount::operator+= (STAmount const& a) -{ - *this = *this + a; - return *this; -} - -STAmount& STAmount::operator-= (STAmount const& a) -{ - *this = *this - a; - return *this; -} - -STAmount STAmount::operator- (void) const -{ - if (mValue == 0) return *this; - - return STAmount ( - getFName (), mIssue, mValue, mOffset, mIsNative, !mIsNegative); -} - -STAmount& STAmount::operator= (std::uint64_t v) -{ - // Does not copy name, does not change currency type. - mOffset = 0; - mValue = v; - mIsNegative = false; - - if (!mIsNative) - canonicalize (); - - return *this; -} - -STAmount& STAmount::operator+= (std::uint64_t v) -{ - assert (mIsNative); - - if (!mIsNative) - throw std::runtime_error ("not native"); - - setSNValue (getSNValue () + static_cast (v)); - return *this; -} - -STAmount& STAmount::operator-= (std::uint64_t v) -{ - assert (mIsNative); - - if (!mIsNative) - throw std::runtime_error ("not native"); - - setSNValue (getSNValue () - static_cast (v)); - return *this; -} - -bool STAmount::operator< (std::uint64_t v) const -{ - return getSNValue () < static_cast (v); -} - -bool STAmount::operator> (std::uint64_t v) const -{ - return getSNValue () > static_cast (v); -} - -bool STAmount::operator<= (std::uint64_t v) const -{ - return getSNValue () <= static_cast (v); -} - -bool STAmount::operator>= (std::uint64_t v) const -{ - return getSNValue () >= static_cast (v); -} - -STAmount STAmount::operator+ (std::uint64_t v) const -{ - return STAmount ( - getFName (), getSNValue () + static_cast (v)); -} - -STAmount STAmount::operator- (std::uint64_t v) const -{ - return STAmount ( - getFName (), getSNValue () - static_cast (v)); -} - -STAmount::operator double () const -{ - // Does not keep the precise value. Not recommended - if (!mValue) - return 0.0; - - if (mIsNegative) - return -1.0 * static_cast (mValue) * pow (10.0, mOffset); - - return static_cast (mValue) * pow (10.0, mOffset); -} - -STAmount operator+ (STAmount const& v1, STAmount const& v2) -{ - v1.throwComparable (v2); - - if (v2 == zero) - return v1; - - if (v1 == zero) + if (isXRP (*this)) { - // Result must be in terms of v1 currency and issuer. - return STAmount (v1.getFName (), v1.mIssue, - v2.mValue, v2.mOffset, v2.mIsNegative); + // native currency amounts should always have an offset of zero + mIsNative = true; + + if (mValue == 0) + { + mOffset = 0; + mIsNegative = false; + return; + } + + while (mOffset < 0) + { + mValue /= 10; + ++mOffset; + } + + while (mOffset > 0) + { + mValue *= 10; + --mOffset; + } + + if (mValue > cMaxNative) + throw std::runtime_error ("Native currency amount out of range"); + + return; } - if (v1.mIsNative) - return STAmount (v1.getFName (), v1.getSNValue () + v2.getSNValue ()); + mIsNative = false; - int ov1 = v1.mOffset, ov2 = v2.mOffset; - std::int64_t vv1 = static_cast (v1.mValue); - std::int64_t vv2 = static_cast (v2.mValue); - - if (v1.mIsNegative) - vv1 = -vv1; - - if (v2.mIsNegative) - vv2 = -vv2; - - while (ov1 < ov2) + if (mValue == 0) { - vv1 /= 10; - ++ov1; + mOffset = -100; + mIsNegative = false; + return; } - while (ov2 < ov1) + while ((mValue < cMinValue) && (mOffset > cMinOffset)) { - vv2 /= 10; - ++ov2; + mValue *= 10; + --mOffset; } - // This addition cannot overflow an std::int64_t. It can overflow an - // STAmount and the constructor will throw. + while (mValue > cMaxValue) + { + if (mOffset >= cMaxOffset) + throw std::runtime_error ("value overflow"); - std::int64_t fv = vv1 + vv2; + mValue /= 10; + ++mOffset; + } - if ((fv >= -10) && (fv <= 10)) - return STAmount (v1.getFName (), v1.mIssue); - if (fv >= 0) - return STAmount (v1.getFName (), v1.mIssue, fv, ov1, false); + if ((mOffset < cMinOffset) || (mValue < cMinValue)) + { + mValue = 0; + mOffset = 0; + mIsNegative = false; + } + + if (mOffset > cMaxOffset) + throw std::runtime_error ("value overflow"); + + assert ((mValue == 0) || ((mValue >= cMinValue) && (mValue <= cMaxValue))); + assert ((mValue == 0) || ((mOffset >= cMinOffset) && (mOffset <= cMaxOffset))); + assert ((mValue != 0) || (mOffset != -100)); +} + +void STAmount::set (std::int64_t v) +{ + if (v < 0) + { + mIsNegative = true; + mValue = static_cast (-v); + } else - return STAmount (v1.getFName (), v1.mIssue, -fv, ov1, true); + { + mIsNegative = false; + mValue = static_cast (v); + } } -STAmount operator- (STAmount const& v1, STAmount const& v2) +//------------------------------------------------------------------------------ + +STAmount +amountFromQuality (std::uint64_t rate) { - v1.throwComparable (v2); + if (rate == 0) + return STAmount (noIssue()); - if (v2 == zero) - return v1; + std::uint64_t mantissa = rate & ~ (255ull << (64 - 8)); + int exponent = static_cast (rate >> (64 - 8)) - 100; - if (v2.mIsNative) - { - // XXX This could be better, check for overflow and that maximum range - // is covered. - return STAmount::createFromInt64 ( - v1.getFName (), v1.getSNValue () - v2.getSNValue ()); - } - - int ov1 = v1.mOffset, ov2 = v2.mOffset; - auto vv1 = static_cast (v1.mValue); - auto vv2 = static_cast (v2.mValue); - - if (v1.mIsNegative) - vv1 = -vv1; - - if (v2.mIsNegative) - vv2 = -vv2; - - while (ov1 < ov2) - { - vv1 /= 10; - ++ov1; - } - - while (ov2 < ov1) - { - vv2 /= 10; - ++ov2; - } - - // this subtraction cannot overflow an std::int64_t, it can overflow an STAmount and the constructor will throw - - std::int64_t fv = vv1 - vv2; - - if ((fv >= -10) && (fv <= 10)) - return STAmount (v1.getFName (), v1.mIssue); - if (fv >= 0) - return STAmount (v1.getFName (), v1.mIssue, fv, ov1, false); - else - return STAmount (v1.getFName (), v1.mIssue, -fv, ov1, true); + return STAmount (noIssue(), mantissa, exponent); } +STAmount +amountFromJson (SField::ref name, Json::Value const& v) +{ + STAmount::mantissa_type mantissa = 0; + STAmount::exponent_type exponent = 0; + bool negative = false; + Issue issue; + + Json::Value value; + Json::Value currency; + Json::Value issuer; + + if (v.isObject ()) + { + WriteLog (lsTRACE, STAmount) << + "value='" << v["value"].asString () << + "', currency='" << v["currency"].asString () << + "', issuer='" << v["issuer"].asString () << + "')"; + + value = v[jss::value]; + currency = v[jss::currency]; + issuer = v[jss::issuer]; + } + else if (v.isArray ()) + { + value = v.get (Json::UInt (0), 0); + currency = v.get (Json::UInt (1), Json::nullValue); + issuer = v.get (Json::UInt (2), Json::nullValue); + } + else if (v.isString ()) + { + std::string val = v.asString (); + std::vector elements; + boost::split (elements, val, boost::is_any_of ("\t\n\r ,/")); + + if (elements.size () > 3) + throw std::runtime_error ("invalid amount string"); + + value = elements[0]; + + if (elements.size () > 1) + currency = elements[1]; + + if (elements.size () > 2) + issuer = elements[2]; + } + else + { + value = v; + } + + bool const native = ! currency.isString () || + currency.asString ().empty () || + (currency.asString () == systemCurrencyCode()); + + if (native) + { + if (v.isObject ()) + throw std::runtime_error ("XRP may not be specified as an object"); + } + else + { + // non-XRP + if (! to_currency (issue.currency, currency.asString ())) + throw std::runtime_error ("invalid currency"); + + if (! issuer.isString () + || !to_issuer (issue.account, issuer.asString ())) + throw std::runtime_error ("invalid issuer"); + + if (isXRP (issue.currency)) + throw std::runtime_error ("invalid issuer"); + } + + if (value.isInt ()) + { + if (value.asInt () >= 0) + { + mantissa = value.asInt (); + } + else + { + mantissa = -value.asInt (); + negative = true; + } + } + else if (value.isUInt ()) + { + mantissa = v.asUInt (); + } + else if (value.isString ()) + { + if (native) + { + std::int64_t val = beast::lexicalCastThrow ( + value.asString ()); + + if (val >= 0) + { + mantissa = val; + } + else + { + mantissa = -val; + negative = true; + } + } + else + { + STAmount amount (name, issue, mantissa, exponent, + native, negative, STAmount::unchecked{}); + amount.setValue (value.asString ()); + return amount; + } + } + else + { + throw std::runtime_error ("invalid amount type"); + } + + return STAmount (name, issue, mantissa, exponent, native, negative); +} + +bool +amountFromJsonNoThrow (STAmount& result, Json::Value const& jvSource) +{ + try + { + result = amountFromJson (sfGeneric, jvSource); + return true; + } + catch (const std::exception& e) + { + WriteLog (lsINFO, STAmount) << + "amountFromJsonNoThrow: caught: " << e.what (); + } + return false; +} + +//------------------------------------------------------------------------------ +// +// Operators +// +//------------------------------------------------------------------------------ + +static +int +compare (STAmount const& lhs, STAmount const& rhs) +{ + // Compares the value of a to the value of this STAmount, amounts must be comparable + if (lhs.negative() != rhs.negative()) + return lhs.negative() ? -1 : 1; + if (lhs.mantissa() == 0) + { + if (rhs.negative()) + return 1; + return (rhs.mantissa() != 0) ? -1 : 0; + } + if (rhs.mantissa() == 0) return 1; + if (lhs.exponent() > rhs.exponent()) return lhs.negative() ? -1 : 1; + if (lhs.exponent() < rhs.exponent()) return lhs.negative() ? 1 : -1; + if (lhs.mantissa() > rhs.mantissa()) return lhs.negative() ? -1 : 1; + if (lhs.mantissa() < rhs.mantissa()) return lhs.negative() ? 1 : -1; + return 0; +} + +bool +operator== (STAmount const& lhs, STAmount const& rhs) +{ + return lhs.isComparable (rhs) && lhs.negative() == rhs.negative() && + lhs.exponent() == rhs.exponent() && lhs.mantissa() == rhs.mantissa(); +} + +bool +operator!= (STAmount const& lhs, STAmount const& rhs) +{ + return lhs.exponent() != rhs.exponent() || + lhs.mantissa() != rhs.mantissa() || + lhs.negative() != rhs.negative() || ! lhs.isComparable (rhs); +} + +bool +operator< (STAmount const& lhs, STAmount const& rhs) +{ + lhs.throwComparable (rhs); + return compare (lhs, rhs) < 0; +} + +bool +operator> (STAmount const& lhs, STAmount const& rhs) +{ + lhs.throwComparable (rhs); + return compare (lhs, rhs) > 0; +} + +bool +operator<= (STAmount const& lhs, STAmount const& rhs) +{ + lhs.throwComparable (rhs); + return compare (lhs, rhs) <= 0; +} + +bool +operator>= (STAmount const& lhs, STAmount const& rhs) +{ + lhs.throwComparable (rhs); + return compare (lhs, rhs) >= 0; +} + +// native currency only + +bool +operator< (STAmount const& lhs, std::uint64_t rhs) +{ + // VFALCO Why the cast? + return lhs.getSNValue() < static_cast (rhs); +} + +bool +operator> (STAmount const& lhs, std::uint64_t rhs) +{ + // VFALCO Why the cast? + return lhs.getSNValue() > static_cast (rhs); +} + +bool +operator<= (STAmount const& lhs, std::uint64_t rhs) +{ + // VFALCO TODO The cast looks dangerous, is it needed? + return lhs.getSNValue () <= static_cast (rhs); +} + +bool +operator>= (STAmount const& lhs, std::uint64_t rhs) +{ + // VFALCO TODO The cast looks dangerous, is it needed? + return lhs.getSNValue() >= static_cast (rhs); +} + +STAmount +operator+ (STAmount const& lhs, std::uint64_t rhs) +{ + // VFALCO TODO The cast looks dangerous, is it needed? + return STAmount (lhs.getFName (), + lhs.getSNValue () + static_cast (rhs)); +} + +STAmount +operator- (STAmount const& lhs, std::uint64_t rhs) +{ + // VFALCO TODO The cast looks dangerous, is it needed? + return STAmount (lhs.getFName (), + lhs.getSNValue () - static_cast (rhs)); +} + +STAmount +operator- (STAmount const& value) +{ + if (value.mantissa() == 0) + return value; + return STAmount (value.getFName (), + value.issue(), value.mantissa(), value.exponent(), + value.native(), ! value.negative(), STAmount::unchecked{}); +} + +//------------------------------------------------------------------------------ +// +// Arithmetic +// +//------------------------------------------------------------------------------ + // NIKB TODO Make Amount::divide skip math if den == QUALITY_ONE -STAmount STAmount::divide ( - STAmount const& num, STAmount const& den, Issue const& issue) +STAmount +divide (STAmount const& num, STAmount const& den, Issue const& issue) { if (den == zero) throw std::runtime_error ("division by zero"); @@ -885,23 +1190,29 @@ STAmount STAmount::divide ( if (num == zero) return {issue}; - std::uint64_t numVal = num.mValue, denVal = den.mValue; - int numOffset = num.mOffset, denOffset = den.mOffset; + std::uint64_t numVal = num.mantissa(); + std::uint64_t denVal = den.mantissa(); + int numOffset = num.exponent(); + int denOffset = den.exponent(); - if (num.mIsNative) + if (num.native()) + { while (numVal < STAmount::cMinValue) { // Need to bring into range numVal *= 10; --numOffset; } + } - if (den.mIsNative) + if (den.native()) + { while (denVal < STAmount::cMinValue) { denVal *= 10; --denOffset; } + } // Compute (numerator * 10^17) / denominator CBigNum v; @@ -919,20 +1230,20 @@ STAmount STAmount::divide ( // TODO(tom): where do 5 and 17 come from? return STAmount (issue, v.getuint64 () + 5, numOffset - denOffset - 17, - num.mIsNegative != den.mIsNegative); + num.negative() != den.negative()); } -STAmount STAmount::multiply ( - STAmount const& v1, STAmount const& v2, Issue const& issue) +STAmount +multiply (STAmount const& v1, STAmount const& v2, Issue const& issue) { if (v1 == zero || v2 == zero) return STAmount (issue); - if (v1.mIsNative && v2.mIsNative && isXRP (issue) ) + if (v1.native() && v2.native() && isXRP (issue)) { - std::uint64_t minV = v1.getSNValue () < v2.getSNValue () + std::uint64_t const minV = v1.getSNValue () < v2.getSNValue () ? v1.getSNValue () : v2.getSNValue (); - std::uint64_t maxV = v1.getSNValue () < v2.getSNValue () + std::uint64_t const maxV = v1.getSNValue () < v2.getSNValue () ? v2.getSNValue () : v1.getSNValue (); if (minV > 3000000000ull) // sqrt(cMaxNative) @@ -944,10 +1255,12 @@ STAmount STAmount::multiply ( return STAmount (v1.getFName (), minV * maxV); } - std::uint64_t value1 = v1.mValue, value2 = v2.mValue; - int offset1 = v1.mOffset, offset2 = v2.mOffset; + std::uint64_t value1 = v1.mantissa(); + std::uint64_t value2 = v2.mantissa(); + int offset1 = v1.exponent(); + int offset2 = v2.exponent(); - if (v1.mIsNative) + if (v1.native()) { while (value1 < STAmount::cMinValue) { @@ -956,7 +1269,7 @@ STAmount STAmount::multiply ( } } - if (v2.mIsNative) + if (v2.native()) { while (value2 < STAmount::cMinValue) { @@ -981,810 +1294,318 @@ STAmount STAmount::multiply ( // TODO(tom): where do 7 and 14 come from? return STAmount (issue, v.getuint64 () + 7, - offset1 + offset2 + 14, v1.mIsNegative != v2.mIsNegative); + offset1 + offset2 + 14, v1.negative() != v2.negative()); } -// Convert an offer into an index amount so they sort by rate. -// A taker will take the best, lowest, rate first. -// (e.g. a taker will prefer pay 1 get 3 over pay 1 get 2. -// --> offerOut: takerGets: How much the offerer is selling to the taker. -// --> offerIn: takerPays: How much the offerer is receiving from the taker. -// <-- uRate: normalize(offerIn/offerOut) -// A lower rate is better for the person taking the order. -// The taker gets more for less with a lower rate. -// Zero is returned if the offer is worthless. -std::uint64_t STAmount::getRate (STAmount const& offerOut, STAmount const& offerIn) +void +canonicalizeRound (bool isNative, std::uint64_t& value, + int& offset, bool roundUp) { - if (offerOut == zero) - return 0; - - try - { - STAmount r = divide (offerIn, offerOut, noIssue()); - - if (r == zero) // offer is too good - return 0; - - assert ((r.getExponent () >= -100) && (r.getExponent () <= 155)); - - std::uint64_t ret = r.getExponent () + 100; - - return (ret << (64 - 8)) | r.getMantissa (); - } - catch (...) - { - // overflow -- very bad offer - return 0; - } -} - -STAmount STAmount::setRate (std::uint64_t rate) -{ - if (rate == 0) - return STAmount (noIssue()); - - std::uint64_t mantissa = rate & ~ (255ull << (64 - 8)); - int exponent = static_cast (rate >> (64 - 8)) - 100; - - return STAmount (noIssue(), mantissa, exponent); -} - -STAmount STAmount::getPay ( - STAmount const& offerOut, STAmount const& offerIn, STAmount const& needed) -{ - // Someone wants to get (needed) out of the offer, how much should they pay - // in? - if (offerOut == zero) - return STAmount (offerIn.issue ()); - - if (needed >= offerOut) - // They need more than offered, pay full amount. - return needed; - - - STAmount ret = divide (multiply (needed, offerIn, noIssue()), - offerOut, offerIn.issue()); - - return (ret > offerIn) ? offerIn : ret; -} - -STAmount STAmount::deserialize (SerializerIterator& it) -{ - auto s = construct (it, sfGeneric); - - if (!s) - throw std::runtime_error("Deserialization error"); - - return STAmount (*s); -} - -std::string STAmount::getFullText () const -{ - std::string ret; - - ret.reserve(64); - ret = getText () + "/" + getHumanCurrency (); - - if (!mIsNative) - { - ret += "/"; - - if (isXRP (*this)) - ret += "0"; - else if (mIssue.account == noAccount()) - ret += "1"; - else - ret += to_string (mIssue.account); - } - - return ret; -} - -STAmount STAmount::getRound () const -{ - if (mIsNative) - return *this; - - std::uint64_t valueDigits = mValue % 1000000000ull; - - if (valueDigits == 1) - return STAmount (mIssue, mValue - 1, mOffset, mIsNegative); - else if (valueDigits == 999999999ull) - return STAmount (mIssue, mValue + 1, mOffset, mIsNegative); - - return *this; -} - -void STAmount::roundSelf () -{ - if (mIsNative) + if (!roundUp) // canonicalize already rounds down return; - std::uint64_t valueDigits = mValue % 1000000000ull; + WriteLog (lsTRACE, STAmount) + << "canonicalizeRound< " << value << ":" << offset; - if (valueDigits == 1) + if (isNative) { - mValue -= 1; + if (offset < 0) + { + int loops = 0; - if (mValue < cMinValue) - canonicalize (); + while (offset < -1) + { + value /= 10; + ++offset; + ++loops; + } + + value += (loops >= 2) ? 9 : 10; // add before last divide + value /= 10; + ++offset; + } } - else if (valueDigits == 999999999ull) + else if (value > STAmount::cMaxValue) { - mValue += 1; + while (value > (10 * STAmount::cMaxValue)) + { + value /= 10; + ++offset; + } - if (mValue > cMaxValue) - canonicalize (); + value += 9; // add before last divide + value /= 10; + ++offset; } + + WriteLog (lsTRACE, STAmount) + << "canonicalizeRound> " << value << ":" << offset; } -void STAmount::setJson (Json::Value& elem) const +STAmount +addRound (STAmount const& v1, STAmount const& v2, bool roundUp) { - elem = Json::objectValue; + v1.throwComparable (v2); - if (!mIsNative) + if (v2.mantissa() == 0) + return v1; + + if (v1.mantissa() == 0) + return STAmount (v1.getFName (), v1.issue(), v2.mantissa(), + v2.exponent(), v2.negative()); + + if (v1.native()) + return STAmount (v1.getFName (), v1.getSNValue () + v2.getSNValue ()); + + int ov1 = v1.exponent(), ov2 = v2.exponent(); + auto vv1 = static_cast (v1.mantissa()); + auto vv2 = static_cast (v2.mantissa()); + + if (v1.negative()) + vv1 = -vv1; + + if (v2.negative()) + vv2 = -vv2; + + if (ov1 < ov2) { - // It is an error for currency or issuer not to be specified for valid - // json. - elem[jss::value] = getText (); - elem[jss::currency] = getHumanCurrency (); - elem[jss::issuer] = to_string (mIssue.account); + while (ov1 < (ov2 - 1)) + { + vv1 /= 10; + ++ov1; + } + + if (roundUp) + vv1 += 9; + + vv1 /= 10; + ++ov1; + } + + if (ov2 < ov1) + { + while (ov2 < (ov1 - 1)) + { + vv2 /= 10; + ++ov2; + } + + if (roundUp) + vv2 += 9; + + vv2 /= 10; + ++ov2; + } + + std::int64_t fv = vv1 + vv2; + + if ((fv >= -10) && (fv <= 10)) + return STAmount (v1.getFName (), v1.issue()); + else if (fv >= 0) + { + std::uint64_t v = static_cast (fv); + canonicalizeRound (false, v, ov1, roundUp); + return STAmount (v1.getFName (), v1.issue(), v, ov1, false); } else { - elem = getText (); + std::uint64_t v = static_cast (-fv); + canonicalizeRound (false, v, ov1, !roundUp); + return STAmount (v1.getFName (), v1.issue(), v, ov1, true); } } -Json::Value STAmount::getJson (int) const +STAmount +subRound (STAmount const& v1, STAmount const& v2, bool roundUp) { - Json::Value elem; - setJson (elem); - return elem; + v1.throwComparable (v2); + + if (v2.mantissa() == 0) + return v1; + + if (v1.mantissa() == 0) + return STAmount (v1.getFName (), v1.issue(), v2.mantissa(), + v2.exponent(), !v2.negative()); + + if (v1.native()) + return STAmount (v1.getFName (), v1.getSNValue () - v2.getSNValue ()); + + int ov1 = v1.exponent(), ov2 = v2.exponent(); + auto vv1 = static_cast (v1.mantissa()); + auto vv2 = static_cast (v2.mantissa()); + + if (v1.negative()) + vv1 = -vv1; + + if (!v2.negative()) + vv2 = -vv2; + + if (ov1 < ov2) + { + while (ov1 < (ov2 - 1)) + { + vv1 /= 10; + ++ov1; + } + + if (roundUp) + vv1 += 9; + + vv1 /= 10; + ++ov1; + } + + if (ov2 < ov1) + { + while (ov2 < (ov1 - 1)) + { + vv2 /= 10; + ++ov2; + } + + if (roundUp) + vv2 += 9; + + vv2 /= 10; + ++ov2; + } + + std::int64_t fv = vv1 + vv2; + + if ((fv >= -10) && (fv <= 10)) + return STAmount (v1.getFName (), v1.issue()); + + if (fv >= 0) + { + std::uint64_t v = static_cast (fv); + canonicalizeRound (false, v, ov1, roundUp); + return STAmount (v1.getFName (), v1.issue(), v, ov1, false); + } + else + { + std::uint64_t v = static_cast (-fv); + canonicalizeRound (false, v, ov1, !roundUp); + return STAmount (v1.getFName (), v1.issue(), v, ov1, true); + } } -//------------------------------------------------------------------------------ - -class STAmount_test : public beast::unit_test::suite +STAmount +mulRound (STAmount const& v1, STAmount const& v2, + Issue const& issue, bool roundUp) { -public: - static STAmount serializeAndDeserialize (STAmount const& s) - { - Serializer ser; - s.add (ser); + if (v1 == zero || v2 == zero) + return {issue}; - SerializerIterator sit (ser); - return STAmount::deserialize (sit); + if (v1.native() && v2.native() && isXRP (issue)) + { + std::uint64_t minV = (v1.getSNValue () < v2.getSNValue ()) ? + v1.getSNValue () : v2.getSNValue (); + std::uint64_t maxV = (v1.getSNValue () < v2.getSNValue ()) ? + v2.getSNValue () : v1.getSNValue (); + + if (minV > 3000000000ull) // sqrt(cMaxNative) + throw std::runtime_error ("Native value overflow"); + + if (((maxV >> 32) * minV) > 2095475792ull) // cMaxNative / 2^32 + throw std::runtime_error ("Native value overflow"); + + return STAmount (v1.getFName (), minV * maxV); } - //-------------------------------------------------------------------------- + std::uint64_t value1 = v1.mantissa(), value2 = v2.mantissa(); + int offset1 = v1.exponent(), offset2 = v2.exponent(); - bool roundTest (int n, int d, int m) + if (v1.native()) { - // check STAmount rounding - STAmount num (noIssue(), n); - STAmount den (noIssue(), d); - STAmount mul (noIssue(), m); - STAmount quot = STAmount::divide (n, d, noIssue()); - STAmount res = STAmount::multiply (quot, mul, noIssue()); - - expect (! res.isNative (), "Product should not be native"); - - res.roundSelf (); - - STAmount cmp (noIssue(), (n * m) / d); - - expect (! cmp.isNative (), "Comparison amount should not be native"); - - if (res != cmp) + while (value1 < STAmount::cMinValue) { - cmp.throwComparable (res); - - WriteLog (lsWARNING, STAmount) << "(" << num.getText () << "/" << den.getText () << ") X " << mul.getText () << " = " - << res.getText () << " not " << cmp.getText (); - - fail ("Rounding"); - - return false; - } - else - { - pass (); - } - - return true; - } - - void mulTest (int a, int b) - { - STAmount aa (noIssue(), a); - STAmount bb (noIssue(), b); - STAmount prod1 (STAmount::multiply (aa, bb, noIssue())); - - expect (! prod1.isNative ()); - - STAmount prod2 (noIssue(), static_cast (a) * static_cast (b)); - - if (prod1 != prod2) - { - WriteLog (lsWARNING, STAmount) << "nn(" << aa.getFullText () << " * " << bb.getFullText () << ") = " << prod1.getFullText () - << " not " << prod2.getFullText (); - - fail ("Multiplication result is not exact"); - } - else - { - pass (); - } - - aa = a; - prod1 = STAmount::multiply (aa, bb, noIssue()); - - if (prod1 != prod2) - { - WriteLog (lsWARNING, STAmount) << "n(" << aa.getFullText () << " * " << bb.getFullText () << ") = " << prod1.getFullText () - << " not " << prod2.getFullText (); - fail ("Multiplication result is not exact"); - } - else - { - pass (); + value1 *= 10; + --offset1; } } - //-------------------------------------------------------------------------- - - void testSetValue () + if (v2.native()) { - testcase ("set value"); - - STAmount saTmp; - - #if 0 - // Check native floats - saTmp.setFullValue ("1^0"); - BOOST_CHECK_MESSAGE (SYSTEM_CURRENCY_PARTS == saTmp.getNValue (), "float integer failed"); - saTmp.setFullValue ("0^1"); - BOOST_CHECK_MESSAGE (SYSTEM_CURRENCY_PARTS / 10 == saTmp.getNValue (), "float fraction failed"); - saTmp.setFullValue ("0^12"); - BOOST_CHECK_MESSAGE (12 * SYSTEM_CURRENCY_PARTS / 100 == saTmp.getNValue (), "float fraction failed"); - saTmp.setFullValue ("1^2"); - BOOST_CHECK_MESSAGE (SYSTEM_CURRENCY_PARTS + (2 * SYSTEM_CURRENCY_PARTS / 10) == saTmp.getNValue (), "float combined failed"); - #endif - - // Check native integer - saTmp.setFullValue ("1"); - expect (1 == saTmp.getNValue (), "should be equal"); - } - - //-------------------------------------------------------------------------- - - void testNativeCurrency () - { - testcase ("native currency"); - - STAmount zeroSt, one (1), hundred (100); - - unexpected (serializeAndDeserialize (zeroSt) != zeroSt, "STAmount fail"); - - unexpected (serializeAndDeserialize (one) != one, "STAmount fail"); - - unexpected (serializeAndDeserialize (hundred) != hundred, "STAmount fail"); - - unexpected (!zeroSt.isNative (), "STAmount fail"); - - unexpected (!hundred.isNative (), "STAmount fail"); - - unexpected (zeroSt != zero, "STAmount fail"); - - unexpected (one == zero, "STAmount fail"); - - unexpected (hundred == zero, "STAmount fail"); - - unexpected ((zeroSt < zeroSt), "STAmount fail"); - - unexpected (! (zeroSt < one), "STAmount fail"); - - unexpected (! (zeroSt < hundred), "STAmount fail"); - - unexpected ((one < zeroSt), "STAmount fail"); - - unexpected ((one < one), "STAmount fail"); - - unexpected (! (one < hundred), "STAmount fail"); - - unexpected ((hundred < zeroSt), "STAmount fail"); - - unexpected ((hundred < one), "STAmount fail"); - - unexpected ((hundred < hundred), "STAmount fail"); - - unexpected ((zeroSt > zeroSt), "STAmount fail"); - - unexpected ((zeroSt > one), "STAmount fail"); - - unexpected ((zeroSt > hundred), "STAmount fail"); - - unexpected (! (one > zeroSt), "STAmount fail"); - - unexpected ((one > one), "STAmount fail"); - - unexpected ((one > hundred), "STAmount fail"); - - unexpected (! (hundred > zeroSt), "STAmount fail"); - - unexpected (! (hundred > one), "STAmount fail"); - - unexpected ((hundred > hundred), "STAmount fail"); - - unexpected (! (zeroSt <= zeroSt), "STAmount fail"); - - unexpected (! (zeroSt <= one), "STAmount fail"); - - unexpected (! (zeroSt <= hundred), "STAmount fail"); - - unexpected ((one <= zeroSt), "STAmount fail"); - - unexpected (! (one <= one), "STAmount fail"); - - unexpected (! (one <= hundred), "STAmount fail"); - - unexpected ((hundred <= zeroSt), "STAmount fail"); - - unexpected ((hundred <= one), "STAmount fail"); - - unexpected (! (hundred <= hundred), "STAmount fail"); - - unexpected (! (zeroSt >= zeroSt), "STAmount fail"); - - unexpected ((zeroSt >= one), "STAmount fail"); - - unexpected ((zeroSt >= hundred), "STAmount fail"); - - unexpected (! (one >= zeroSt), "STAmount fail"); - - unexpected (! (one >= one), "STAmount fail"); - - unexpected ((one >= hundred), "STAmount fail"); - - unexpected (! (hundred >= zeroSt), "STAmount fail"); - - unexpected (! (hundred >= one), "STAmount fail"); - - unexpected (! (hundred >= hundred), "STAmount fail"); - - unexpected (! (zeroSt == zeroSt), "STAmount fail"); - - unexpected ((zeroSt == one), "STAmount fail"); - - unexpected ((zeroSt == hundred), "STAmount fail"); - - unexpected ((one == zeroSt), "STAmount fail"); - - unexpected (! (one == one), "STAmount fail"); - - unexpected ((one == hundred), "STAmount fail"); - - unexpected ((hundred == zeroSt), "STAmount fail"); - - unexpected ((hundred == one), "STAmount fail"); - - unexpected (! (hundred == hundred), "STAmount fail"); - - unexpected ((zeroSt != zeroSt), "STAmount fail"); - - unexpected (! (zeroSt != one), "STAmount fail"); - - unexpected (! (zeroSt != hundred), "STAmount fail"); - - unexpected (! (one != zeroSt), "STAmount fail"); - - unexpected ((one != one), "STAmount fail"); - - unexpected (! (one != hundred), "STAmount fail"); - - unexpected (! (hundred != zeroSt), "STAmount fail"); - - unexpected (! (hundred != one), "STAmount fail"); - - unexpected ((hundred != hundred), "STAmount fail"); - - unexpected (STAmount ().getText () != "0", "STAmount fail"); - - unexpected (STAmount (31).getText () != "31", "STAmount fail"); - - unexpected (STAmount (310).getText () != "310", "STAmount fail"); - - unexpected (to_string (Currency ()) != "XRP", "cHC(XRP)"); - - Currency c; - unexpected (!to_currency (c, "USD"), "create USD currency"); - unexpected (to_string (c) != "USD", "check USD currency"); - - const std::string cur = "015841551A748AD2C1F76FF6ECB0CCCD00000000"; - unexpected (!to_currency (c, cur), "create custom currency"); - unexpected (to_string (c) != cur, "check custom currency"); - unexpected (c != Currency (cur), "check custom currency"); - } - - //-------------------------------------------------------------------------- - - void testCustomCurrency () - { - testcase ("custom currency"); - - STAmount zeroSt (noIssue()), one (noIssue(), 1), hundred (noIssue(), 100); - - unexpected (serializeAndDeserialize (zeroSt) != zeroSt, "STAmount fail"); - - unexpected (serializeAndDeserialize (one) != one, "STAmount fail"); - - unexpected (serializeAndDeserialize (hundred) != hundred, "STAmount fail"); - - unexpected (zeroSt.isNative (), "STAmount fail"); - - unexpected (hundred.isNative (), "STAmount fail"); - - unexpected (zeroSt != zero, "STAmount fail"); - - unexpected (one == zero, "STAmount fail"); - - unexpected (hundred == zero, "STAmount fail"); - - unexpected ((zeroSt < zeroSt), "STAmount fail"); - - unexpected (! (zeroSt < one), "STAmount fail"); - - unexpected (! (zeroSt < hundred), "STAmount fail"); - - unexpected ((one < zeroSt), "STAmount fail"); - - unexpected ((one < one), "STAmount fail"); - - unexpected (! (one < hundred), "STAmount fail"); - - unexpected ((hundred < zeroSt), "STAmount fail"); - - unexpected ((hundred < one), "STAmount fail"); - - unexpected ((hundred < hundred), "STAmount fail"); - - unexpected ((zeroSt > zeroSt), "STAmount fail"); - - unexpected ((zeroSt > one), "STAmount fail"); - - unexpected ((zeroSt > hundred), "STAmount fail"); - - unexpected (! (one > zeroSt), "STAmount fail"); - - unexpected ((one > one), "STAmount fail"); - - unexpected ((one > hundred), "STAmount fail"); - - unexpected (! (hundred > zeroSt), "STAmount fail"); - - unexpected (! (hundred > one), "STAmount fail"); - - unexpected ((hundred > hundred), "STAmount fail"); - - unexpected (! (zeroSt <= zeroSt), "STAmount fail"); - - unexpected (! (zeroSt <= one), "STAmount fail"); - - unexpected (! (zeroSt <= hundred), "STAmount fail"); - - unexpected ((one <= zeroSt), "STAmount fail"); - - unexpected (! (one <= one), "STAmount fail"); - - unexpected (! (one <= hundred), "STAmount fail"); - - unexpected ((hundred <= zeroSt), "STAmount fail"); - - unexpected ((hundred <= one), "STAmount fail"); - - unexpected (! (hundred <= hundred), "STAmount fail"); - - unexpected (! (zeroSt >= zeroSt), "STAmount fail"); - - unexpected ((zeroSt >= one), "STAmount fail"); - - unexpected ((zeroSt >= hundred), "STAmount fail"); - - unexpected (! (one >= zeroSt), "STAmount fail"); - - unexpected (! (one >= one), "STAmount fail"); - - unexpected ((one >= hundred), "STAmount fail"); - - unexpected (! (hundred >= zeroSt), "STAmount fail"); - - unexpected (! (hundred >= one), "STAmount fail"); - - unexpected (! (hundred >= hundred), "STAmount fail"); - - unexpected (! (zeroSt == zeroSt), "STAmount fail"); - - unexpected ((zeroSt == one), "STAmount fail"); - - unexpected ((zeroSt == hundred), "STAmount fail"); - - unexpected ((one == zeroSt), "STAmount fail"); - - unexpected (! (one == one), "STAmount fail"); - - unexpected ((one == hundred), "STAmount fail"); - - unexpected ((hundred == zeroSt), "STAmount fail"); - - unexpected ((hundred == one), "STAmount fail"); - - unexpected (! (hundred == hundred), "STAmount fail"); - - unexpected ((zeroSt != zeroSt), "STAmount fail"); - - unexpected (! (zeroSt != one), "STAmount fail"); - - unexpected (! (zeroSt != hundred), "STAmount fail"); - - unexpected (! (one != zeroSt), "STAmount fail"); - - unexpected ((one != one), "STAmount fail"); - - unexpected (! (one != hundred), "STAmount fail"); - - unexpected (! (hundred != zeroSt), "STAmount fail"); - - unexpected (! (hundred != one), "STAmount fail"); - - unexpected ((hundred != hundred), "STAmount fail"); - - unexpected (STAmount (noIssue()).getText () != "0", "STAmount fail"); - - unexpected (STAmount (noIssue(), 31).getText () != "31", "STAmount fail"); - - unexpected (STAmount (noIssue(), 31, 1).getText () != "310", "STAmount fail"); - - unexpected (STAmount (noIssue(), 31, -1).getText () != "3.1", "STAmount fail"); - - unexpected (STAmount (noIssue(), 31, -2).getText () != "0.31", "STAmount fail"); - - unexpected (STAmount::multiply (STAmount (noIssue(), 20), STAmount (3), noIssue()).getText () != "60", - "STAmount multiply fail 1"); - - unexpected (STAmount::multiply (STAmount (noIssue(), 20), STAmount (3), xrpIssue ()).getText () != "60", - "STAmount multiply fail 2"); - - unexpected (STAmount::multiply (STAmount (20), STAmount (3), noIssue()).getText () != "60", - "STAmount multiply fail 3"); - - unexpected (STAmount::multiply (STAmount (20), STAmount (3), xrpIssue ()).getText () != "60", - "STAmount multiply fail 4"); - - if (STAmount::divide (STAmount (noIssue(), 60), STAmount (3), noIssue()).getText () != "20") + while (value2 < STAmount::cMinValue) { - WriteLog (lsFATAL, STAmount) << "60/3 = " << - STAmount::divide (STAmount (noIssue(), 60), - STAmount (3), noIssue()).getText (); - fail ("STAmount divide fail"); + value2 *= 10; + --offset2; } - else + } + + bool resultNegative = v1.negative() != v2.negative(); + // Compute (numerator * denominator) / 10^14 with rounding + // 10^16 <= result <= 10^18 + CBigNum v; + + if ((BN_add_word64 (&v, value1) != 1) || (BN_mul_word64 (&v, value2) != 1)) + throw std::runtime_error ("internal bn error"); + + if (resultNegative != roundUp) // rounding down is automatic when we divide + BN_add_word64 (&v, tenTo14m1); + + if (BN_div_word64 (&v, tenTo14) == ((std::uint64_t) - 1)) + throw std::runtime_error ("internal bn error"); + + // 10^16 <= product <= 10^18 + assert (BN_num_bytes (&v) <= 64); + + std::uint64_t amount = v.getuint64 (); + int offset = offset1 + offset2 + 14; + canonicalizeRound ( + isXRP (issue), amount, offset, resultNegative != roundUp); + return STAmount (issue, amount, offset, resultNegative); +} + +STAmount +divRound (STAmount const& num, STAmount const& den, + Issue const& issue, bool roundUp) +{ + if (den == zero) + throw std::runtime_error ("division by zero"); + + if (num == zero) + return {issue}; + + std::uint64_t numVal = num.mantissa(), denVal = den.mantissa(); + int numOffset = num.exponent(), denOffset = den.exponent(); + + if (num.native()) + while (numVal < STAmount::cMinValue) { - pass (); + // Need to bring into range + numVal *= 10; + --numOffset; } - unexpected (STAmount::divide (STAmount (noIssue(), 60), STAmount (3), xrpIssue ()).getText () != "20", - "STAmount divide fail"); - - unexpected (STAmount::divide (STAmount (noIssue(), 60), STAmount (noIssue(), 3), noIssue()).getText () != "20", - "STAmount divide fail"); - - unexpected (STAmount::divide (STAmount (noIssue(), 60), STAmount (noIssue(), 3), xrpIssue ()).getText () != "20", - "STAmount divide fail"); - - STAmount a1 (noIssue(), 60), a2 (noIssue(), 10, -1); - - unexpected (STAmount::divide (a2, a1, noIssue()) != STAmount::setRate (STAmount::getRate (a1, a2)), - "STAmount setRate(getRate) fail"); - - unexpected (STAmount::divide (a1, a2, noIssue()) != STAmount::setRate (STAmount::getRate (a2, a1)), - "STAmount setRate(getRate) fail"); - } - - //-------------------------------------------------------------------------- - - void testArithmetic () - { - testcase ("arithmetic"); - - CBigNum b; - - for (int i = 0; i < 16; ++i) + if (den.native()) + while (denVal < STAmount::cMinValue) { - std::uint64_t r = rand (); - r <<= 32; - r |= rand (); - b.setuint64 (r); - - if (b.getuint64 () != r) - { - WriteLog (lsFATAL, STAmount) << r << " != " << b.getuint64 () << " " << b.ToString (16); - fail ("setull64/getull64 failure"); - } - else - { - pass (); - } + denVal *= 10; + --denOffset; } - // Test currency multiplication and division operations such as - // convertToDisplayAmount, convertToInternalAmount, getRate, getClaimed, and getNeeded + bool resultNegative = num.negative() != den.negative(); + // Compute (numerator * 10^17) / denominator + CBigNum v; - unexpected (STAmount::getRate (STAmount (1), STAmount (10)) != (((100ull - 14) << (64 - 8)) | 1000000000000000ull), - "STAmount getRate fail 1"); + if ((BN_add_word64 (&v, numVal) != 1) || (BN_mul_word64 (&v, tenTo17) != 1)) + throw std::runtime_error ("internal bn error"); - unexpected (STAmount::getRate (STAmount (10), STAmount (1)) != (((100ull - 16) << (64 - 8)) | 1000000000000000ull), - "STAmount getRate fail 2"); + if (resultNegative != roundUp) // Rounding down is automatic when we divide + BN_add_word64 (&v, denVal - 1); - unexpected (STAmount::getRate (STAmount (noIssue(), 1), STAmount (noIssue(), 10)) != (((100ull - 14) << (64 - 8)) | 1000000000000000ull), - "STAmount getRate fail 3"); + if (BN_div_word64 (&v, denVal) == ((std::uint64_t) - 1)) + throw std::runtime_error ("internal bn error"); - unexpected (STAmount::getRate (STAmount (noIssue(), 10), STAmount (noIssue(), 1)) != (((100ull - 16) << (64 - 8)) | 1000000000000000ull), - "STAmount getRate fail 4"); + // 10^16 <= quotient <= 10^18 + assert (BN_num_bytes (&v) <= 64); - unexpected (STAmount::getRate (STAmount (noIssue(), 1), STAmount (10)) != (((100ull - 14) << (64 - 8)) | 1000000000000000ull), - "STAmount getRate fail 5"); - - unexpected (STAmount::getRate (STAmount (noIssue(), 10), STAmount (1)) != (((100ull - 16) << (64 - 8)) | 1000000000000000ull), - "STAmount getRate fail 6"); - - unexpected (STAmount::getRate (STAmount (1), STAmount (noIssue(), 10)) != (((100ull - 14) << (64 - 8)) | 1000000000000000ull), - "STAmount getRate fail 7"); - - unexpected (STAmount::getRate (STAmount (10), STAmount (noIssue(), 1)) != (((100ull - 16) << (64 - 8)) | 1000000000000000ull), - "STAmount getRate fail 8"); - - roundTest (1, 3, 3); - roundTest (2, 3, 9); - roundTest (1, 7, 21); - roundTest (1, 2, 4); - roundTest (3, 9, 18); - roundTest (7, 11, 44); - - for (int i = 0; i <= 100000; ++i) - mulTest (rand () % 10000000, rand () % 10000000); - } - - //-------------------------------------------------------------------------- - - template - bool - expect (Cond cond, beast::String const& s) - { - return suite::expect (cond, s.toStdString()); - } - - template - bool - expect (Cond cond) - { - return suite::expect (cond); - } - - void testUnderflow () - { - testcase ("underflow"); - - STAmount bigNative (STAmount::cMaxNative / 2); - STAmount bigValue (noIssue(), - (STAmount::cMinValue + STAmount::cMaxValue) / 2, STAmount::cMaxOffset - 1); - STAmount smallValue (noIssue(), - (STAmount::cMinValue + STAmount::cMaxValue) / 2, STAmount::cMinOffset + 1); - STAmount zeroSt (noIssue(), 0); - - STAmount smallXsmall = STAmount::multiply (smallValue, smallValue, noIssue()); - - expect (smallXsmall == zero, "smallXsmall != 0"); - - STAmount bigDsmall = STAmount::divide (smallValue, bigValue, noIssue()); - - expect (bigDsmall == zero, beast::String ("small/big != 0: ") + bigDsmall.getText ()); - -#if 0 - // TODO(tom): this test makes no sense - we should have no way to have - // the currency not be XRP while the account is XRP. - bigDsmall = STAmount::divide (smallValue, bigNative, noCurrency(), xrpAccount ()); -#endif - - expect (bigDsmall == zero, beast::String ("small/bigNative != 0: ") + bigDsmall.getText ()); - - bigDsmall = STAmount::divide (smallValue, bigValue, xrpIssue ()); - - expect (bigDsmall == zero, beast::String ("(small/big)->N != 0: ") + bigDsmall.getText ()); - - bigDsmall = STAmount::divide (smallValue, bigNative, xrpIssue ()); - - expect (bigDsmall == zero, beast::String ("(small/bigNative)->N != 0: ") + bigDsmall.getText ()); - - // very bad offer - std::uint64_t r = STAmount::getRate (smallValue, bigValue); - - expect (r == 0, "getRate(smallOut/bigIn) != 0"); - - // very good offer - r = STAmount::getRate (bigValue, smallValue); - - expect (r == 0, "getRate(smallIn/bigOUt) != 0"); - } - - //-------------------------------------------------------------------------- - - void testRounding () - { - // VFALCO TODO There are no actual tests here, just printed output? - // Change this to actually do something. - -#if 0 - beginTestCase ("rounding "); - - std::uint64_t value = 25000000000000000ull; - int offset = -14; - STAmount::canonicalizeRound (false, value, offset, true); - - STAmount one (noIssue(), 1); - STAmount two (noIssue(), 2); - STAmount three (noIssue(), 3); - - STAmount oneThird1 = STAmount::divRound (one, three, noIssue(), false); - STAmount oneThird2 = STAmount::divide (one, three, noIssue()); - STAmount oneThird3 = STAmount::divRound (one, three, noIssue(), true); - WriteLog (lsINFO, STAmount) << oneThird1; - WriteLog (lsINFO, STAmount) << oneThird2; - WriteLog (lsINFO, STAmount) << oneThird3; - - STAmount twoThird1 = STAmount::divRound (two, three, noIssue(), false); - STAmount twoThird2 = STAmount::divide (two, three, noIssue()); - STAmount twoThird3 = STAmount::divRound (two, three, noIssue(), true); - WriteLog (lsINFO, STAmount) << twoThird1; - WriteLog (lsINFO, STAmount) << twoThird2; - WriteLog (lsINFO, STAmount) << twoThird3; - - STAmount oneA = STAmount::mulRound (oneThird1, three, noIssue(), false); - STAmount oneB = STAmount::multiply (oneThird2, three, noIssue()); - STAmount oneC = STAmount::mulRound (oneThird3, three, noIssue(), true); - WriteLog (lsINFO, STAmount) << oneA; - WriteLog (lsINFO, STAmount) << oneB; - WriteLog (lsINFO, STAmount) << oneC; - - STAmount fourThirdsA = STAmount::addRound (twoThird2, twoThird2, false); - STAmount fourThirdsB = twoThird2 + twoThird2; - STAmount fourThirdsC = STAmount::addRound (twoThird2, twoThird2, true); - WriteLog (lsINFO, STAmount) << fourThirdsA; - WriteLog (lsINFO, STAmount) << fourThirdsB; - WriteLog (lsINFO, STAmount) << fourThirdsC; - - STAmount dripTest1 = STAmount::mulRound (twoThird2, two, xrpIssue (), false); - STAmount dripTest2 = STAmount::multiply (twoThird2, two, xrpIssue ()); - STAmount dripTest3 = STAmount::mulRound (twoThird2, two, xrpIssue (), true); - WriteLog (lsINFO, STAmount) << dripTest1; - WriteLog (lsINFO, STAmount) << dripTest2; - WriteLog (lsINFO, STAmount) << dripTest3; -#endif - } - - //-------------------------------------------------------------------------- - - void run () - { - testSetValue (); - testNativeCurrency (); - testCustomCurrency (); - testArithmetic (); - testUnderflow (); - testRounding (); - } -}; - -BEAST_DEFINE_TESTSUITE(STAmount,ripple_data,ripple); + std::uint64_t amount = v.getuint64 (); + int offset = numOffset - denOffset - 17; + canonicalizeRound ( + isXRP (issue), amount, offset, resultNegative != roundUp); + return STAmount (issue, amount, offset, resultNegative); +} } // ripple diff --git a/src/ripple/module/data/protocol/STAmount.h b/src/ripple/module/data/protocol/STAmount.h index 0efc99da11..032191fed0 100644 --- a/src/ripple/module/data/protocol/STAmount.h +++ b/src/ripple/module/data/protocol/STAmount.h @@ -40,6 +40,18 @@ namespace ripple { // Low 56 bits are value, legal range is 10^15 to (10^16 - 1) inclusive class STAmount : public SerializedType { +public: + typedef std::uint64_t mantissa_type; + typedef int exponent_type; + typedef std::pair rep; + +private: + Issue mIssue; + mantissa_type mValue; + exponent_type mOffset; + bool mIsNative; // A shorthand for isXRP(mIssue). + bool mIsNegative; + public: static const int cMinOffset = -96; static const int cMaxOffset = 80; @@ -55,164 +67,168 @@ public: static std::uint64_t uRateOne; - STAmount (std::uint64_t v = 0, bool negative = false) - : mValue (v), mOffset (0), mIsNative (true), mIsNegative (negative) - { - if (v == 0) mIsNegative = false; - } + //-------------------------------------------------------------------------- - STAmount (SField::ref n, std::uint64_t v = 0, bool negative = false) - : SerializedType (n), mValue (v), mOffset (0), mIsNative (true), - mIsNegative (negative) - { - } + struct unchecked { }; - STAmount (SField::ref n, std::int64_t v) - : SerializedType (n), mOffset (0), mIsNative (true) - { - set (v); - } + // Calls canonicalize + STAmount (SField::ref name, Issue const& issue, + mantissa_type mantissa, exponent_type exponent, + bool native, bool negative); + + // Does not call canonicalize + STAmount (SField::ref name, Issue const& issue, + mantissa_type mantissa, exponent_type exponent, + bool native, bool negative, unchecked); + + STAmount (SField::ref name, std::int64_t mantissa); + + STAmount (SField::ref name, + std::uint64_t mantissa = 0, bool negative = false); + + STAmount (SField::ref name, Issue const& issue, + std::uint64_t mantissa = 0, int exponent = 0, bool negative = false); + + STAmount (std::uint64_t mantissa = 0, bool negative = false); STAmount (Issue const& issue, - std::uint64_t uV = 0, int iOff = 0, bool negative = false) - : mIssue(issue), mValue (uV), mOffset (iOff), mIsNegative (negative) - { - canonicalize (); - } + std::uint64_t mantissa = 0, int exponent = 0, bool negative = false); + // VFALCO Is this needed when we have the previous signature? STAmount (Issue const& issue, - std::uint32_t uV, int iOff = 0, bool negative = false) - : mIssue(issue), mValue (uV), mOffset (iOff), mIsNegative (negative) - { - canonicalize (); - } + std::uint32_t mantissa, int exponent = 0, bool negative = false); - STAmount (SField::ref n, Issue const& issue, - std::uint64_t v = 0, int off = 0, bool negative = false) : - SerializedType (n), mIssue(issue), mValue (v), mOffset (off), - mIsNegative (negative) - { - canonicalize (); - } + STAmount (Issue const& issue, std::int64_t mantissa, int exponent = 0); - STAmount (Issue const& issue, std::int64_t v, int iOff = 0) - : mIssue(issue), mOffset (iOff) - { - set (v); - canonicalize (); - } + STAmount (Issue const& issue, int mantissa, int exponent = 0); - STAmount (SField::ref n, Issue const& issue, std::int64_t v, int off = 0) - : SerializedType (n), mIssue(issue), mOffset (off) - { - set (v); - canonicalize (); - } + //-------------------------------------------------------------------------- - STAmount (Issue const& issue, int v, int iOff = 0) - : mIssue(issue), mOffset (iOff) - { - set (v); - canonicalize (); - } +private: + static + std::unique_ptr + construct (SerializerIterator&, SField::ref name); - STAmount (SField::ref n, Issue const& issue, int v, int off = 0) - : SerializedType (n), mIssue(issue), mOffset (off) - { - set (v); - canonicalize (); - } +public: + static + STAmount + createFromInt64 (SField::ref n, std::int64_t v); - STAmount (SField::ref, Json::Value const&); - - static STAmount createFromInt64 (SField::ref n, std::int64_t v); - - static std::unique_ptr deserialize ( + static + std::unique_ptr + deserialize ( SerializerIterator& sit, SField::ref name) { return construct (sit, name); } - bool bSetJson (Json::Value const& jvSource); - - static STAmount saFromRate (std::uint64_t uRate = 0) + static + STAmount + saFromRate (std::uint64_t uRate = 0) { return STAmount (noIssue(), uRate, -9, false); } - SerializedTypeID getSType () const - { - return STI_AMOUNT; - } - std::string getText () const; - std::string getFullText () const; - void add (Serializer& s) const; + static + STAmount + deserialize (SerializerIterator&); - int getExponent () const - { - return mOffset; - } - std::uint64_t getMantissa () const - { - return mValue; - } + //-------------------------------------------------------------------------- + // + // Observers + // + //-------------------------------------------------------------------------- - int signum () const + int exponent() const noexcept { return mOffset; } + bool native() const noexcept { return mIsNative; } + bool negative() const noexcept { return mIsNegative; } + std::uint64_t mantissa() const noexcept { return mValue; } + Issue const& issue() const { return mIssue; } + + // These three are deprecated + Currency const& getCurrency() const { return mIssue.currency; } + Account const& getIssuer() const { return mIssue.account; } + bool isNative() const { return mIsNative; } + + int + signum() const noexcept { return mValue ? (mIsNegative ? -1 : 1) : 0; } - - // When the currency is XRP, the value in raw units. S=signed - std::uint64_t getNValue () const - { - if (!mIsNative) - throw std::runtime_error ("not native"); - - return mValue; - } - void setNValue (std::uint64_t v) - { - if (!mIsNative) - throw std::runtime_error ("not native"); - - mValue = v; - } - std::int64_t getSNValue () const; - void setSNValue (std::int64_t); - - std::string getHumanCurrency () const; - - bool isNative () const - { - return mIsNative; - } - bool isLegalNet () const - { - return !mIsNative || (mValue <= cMaxNativeN); - } - - explicit - operator bool () const noexcept - { - return *this != zero; - } - - void negate () - { - if (*this != zero) - mIsNegative = !mIsNegative; - } - - /** @return a copy of amount with the same Issuer and Currency but zero - value. */ - STAmount zeroed() const + + /** Returns a zero value with the same issuer and currency. */ + STAmount + zeroed() const { // TODO(tom): what does this next comment mean here? // See https://ripplelabs.atlassian.net/browse/WC-1847?jql= return STAmount (mIssue); } - void clear () + // When the currency is XRP, the value in raw unsigned units. + std::uint64_t + getNValue() const; + + // When the currency is XRP, the value in raw signed units. + std::int64_t + getSNValue() const; + + // VFALCO TODO This can be a free function or just call the + // member on the issue. + std::string + getHumanCurrency() const; + + void + setJson (Json::Value&) const; + + //-------------------------------------------------------------------------- + // + // Operators + // + //-------------------------------------------------------------------------- + + explicit operator bool() const noexcept + { + return *this != zero; + } + + bool isComparable (STAmount const&) const; + void throwComparable (STAmount const&) const; + + STAmount& operator+= (STAmount const&); + STAmount& operator-= (STAmount const&); + STAmount& operator+= (std::uint64_t); + STAmount& operator-= (std::uint64_t); + + STAmount& operator= (std::uint64_t); + STAmount& operator= (beast::Zero) + { + clear(); + return *this; + } + + friend STAmount operator+ (STAmount const& v1, STAmount const& v2); + friend STAmount operator- (STAmount const& v1, STAmount const& v2); + + //-------------------------------------------------------------------------- + // + // Modification + // + //-------------------------------------------------------------------------- + + // VFALCO TODO Remove this, it is only called from the unit test + void roundSelf(); + + void setNValue (std::uint64_t v); + void setSNValue (std::int64_t); + + void negate() + { + if (*this != zero) + mIsNegative = !mIsNegative; + } + + void clear() { // VFALCO: Why -100? mOffset = mIsNative ? 0 : -100; @@ -223,26 +239,13 @@ public: // Zero while copying currency and issuer. void clear (STAmount const& saTmpl) { - clear(saTmpl.mIssue); + clear (saTmpl.mIssue); } void clear (Issue const& issue) { setIssue(issue); - clear (); - } - - STAmount& operator=(beast::Zero) - { - clear (); - return *this; - } - - int compare (STAmount const&) const; - - Account const& getIssuer () const - { - return mIssue.account; + clear(); } void setIssuer (Account const& uIssuer) @@ -254,201 +257,208 @@ public: /** Set the Issue for this amount and update mIsNative. */ void setIssue (Issue const& issue); - Currency const& getCurrency () const - { - return mIssue.currency; - } - - Issue const& issue () const - { - return mIssue; - } - + // VFALCO TODO Rename to setValueOnly (it only sets mantissa and exponent) + // Make this private bool setValue (std::string const& sAmount); - bool setFullValue ( - std::string const& sAmount, std::string const& sCurrency = "", - std::string const& sIssuer = ""); - void setValue (STAmount const&); - virtual bool isEquivalent (const SerializedType& t) const; - virtual bool isDefault () const + bool setFullValue (std::string const& sAmount, + std::string const& sCurrency = "", std::string const& sIssuer = ""); + + //-------------------------------------------------------------------------- + // + // SerializedType + // + //-------------------------------------------------------------------------- + + SerializedTypeID + getSType() const override + { + return STI_AMOUNT; + } + + std::string + getFullText() const override; + + std::string + getText() const override; + + Json::Value + getJson (int) const override; + + void + add (Serializer& s) const override; + + bool + isEquivalent (const SerializedType& t) const override; + + bool + isDefault() const override { return (mValue == 0) && mIsNative; } - bool operator== (STAmount const&) const; - bool operator!= (STAmount const&) const; - bool operator< (STAmount const&) const; - bool operator> (STAmount const&) const; - bool operator<= (STAmount const&) const; - bool operator>= (STAmount const&) const; - bool isComparable (STAmount const&) const; - void throwComparable (STAmount const&) const; - - // native currency only - bool operator< (std::uint64_t) const; - bool operator> (std::uint64_t) const; - bool operator<= (std::uint64_t) const; - bool operator>= (std::uint64_t) const; - STAmount operator+ (std::uint64_t) const; - STAmount operator- (std::uint64_t) const; - STAmount operator- (void) const; - - STAmount& operator+= (STAmount const&); - STAmount& operator-= (STAmount const&); - STAmount& operator+= (std::uint64_t); - STAmount& operator-= (std::uint64_t); - STAmount& operator= (std::uint64_t); - - operator double () const; - - friend STAmount operator+ (STAmount const& v1, STAmount const& v2); - friend STAmount operator- (STAmount const& v1, STAmount const& v2); - - static STAmount divide ( - STAmount const& v1, STAmount const& v2, Issue const& issue); - - static STAmount divide ( - STAmount const& v1, STAmount const& v2, STAmount const& saUnit) - { - return divide (v1, v2, saUnit.issue ()); - } - static STAmount divide (STAmount const& v1, STAmount const& v2) - { - return divide (v1, v2, v1); - } - - static STAmount multiply ( - STAmount const& v1, STAmount const& v2, Issue const& issue); - - static STAmount multiply ( - STAmount const& v1, STAmount const& v2, STAmount const& saUnit) - { - return multiply (v1, v2, saUnit.issue()); - } - static STAmount multiply (STAmount const& v1, STAmount const& v2) - { - return multiply (v1, v2, v1); - } - - /* addRound, subRound can end up rounding if the amount subtracted is too small - to make a change. Consder (X-d) where d is very small relative to X. - If you ask to round down, then (X-d) should not be X unless d is zero. - If you ask to round up, (X+d) should never be X unless d is zero. (Assuming X and d are positive). - */ - // Add, subtract, multiply, or divide rounding result in specified direction - static STAmount addRound ( - STAmount const& v1, STAmount const& v2, bool roundUp); - static STAmount subRound ( - STAmount const& v1, STAmount const& v2, bool roundUp); - static STAmount mulRound ( - STAmount const& v1, STAmount const& v2, Issue const& issue, - bool roundUp); - static STAmount divRound ( - STAmount const& v1, STAmount const& v2, Issue const& issue, - bool roundUp); - - static STAmount mulRound ( - STAmount const& v1, STAmount const& v2, STAmount const& saUnit, - bool roundUp) - { - return mulRound (v1, v2, saUnit.issue (), roundUp); - } - static STAmount mulRound ( - STAmount const& v1, STAmount const& v2, bool roundUp) - { - return mulRound (v1, v2, v1.issue (), roundUp); - } - static STAmount divRound ( - STAmount const& v1, STAmount const& v2, STAmount const& saUnit, - bool roundUp) - { - return divRound (v1, v2, saUnit.issue (), roundUp); - } - static STAmount divRound ( - STAmount const& v1, STAmount const& v2, bool roundUp) - { - return divRound (v1, v2, v1.issue (), roundUp); - } - - // Someone is offering X for Y, what is the rate? - // Rate: smaller is better, the taker wants the most out: in/out - static std::uint64_t getRate ( - STAmount const& offerOut, STAmount const& offerIn); - static STAmount setRate (std::uint64_t rate); - - // Someone is offering X for Y, I need Z, how much do I pay - - // WARNING: most methods in rippled have parameters ordered "in, out" - this - // one is ordered "out, in". - static STAmount getPay ( - STAmount const& out, STAmount const& in, STAmount const& needed); - - static STAmount deserialize (SerializerIterator&); - - Json::Value getJson (int) const; - void setJson (Json::Value&) const; - - STAmount getRound () const; - void roundSelf (); - - static void canonicalizeRound ( - bool isNative, std::uint64_t& value, int& offset, bool roundUp); - private: - Issue mIssue; + STAmount* + duplicate() const override; - std::uint64_t mValue; - int mOffset; - bool mIsNative; // A shorthand for isXRP(mIssue). - bool mIsNegative; - - void canonicalize (); - STAmount* duplicate () const - { - return new STAmount (*this); - } - - static - std::unique_ptr - construct (SerializerIterator&, SField::ref name); - - STAmount (SField::ref name, Issue const& issue, - std::uint64_t val, int off, bool isNat, bool negative) - : SerializedType (name), mIssue(issue), mValue (val), - mOffset (off), mIsNative (isNat), mIsNegative (negative) - { - } - - void set (std::int64_t v) - { - if (v < 0) - { - mIsNegative = true; - mValue = static_cast (-v); - } - else - { - mIsNegative = false; - mValue = static_cast (v); - } - } - - void set (int v) - { - if (v < 0) - { - mIsNegative = true; - mValue = static_cast (-v); - } - else - { - mIsNegative = false; - mValue = static_cast (v); - } - } + void canonicalize(); + void set (std::int64_t v); }; +//------------------------------------------------------------------------------ +// +// Creation +// +//------------------------------------------------------------------------------ + +// VFALCO TODO The parameter type should be Quality not uint64_t +STAmount +amountFromQuality (std::uint64_t rate); + +STAmount +amountFromJson (SField::ref name, Json::Value const& v); + +bool +amountFromJsonNoThrow (STAmount& result, Json::Value const& jvSource); + +//------------------------------------------------------------------------------ +// +// Observers +// +//------------------------------------------------------------------------------ + +inline +bool +isLegalNet (STAmount const& value) +{ + return ! value.native() || (value.mantissa() <= STAmount::cMaxNativeN); +} + +//------------------------------------------------------------------------------ +// +// Operators +// +//------------------------------------------------------------------------------ + +bool operator== (STAmount const& lhs, STAmount const& rhs); +bool operator!= (STAmount const& lhs, STAmount const& rhs); +bool operator< (STAmount const& lhs, STAmount const& rhs); +bool operator> (STAmount const& lhs, STAmount const& rhs); +bool operator<= (STAmount const& lhs, STAmount const& rhs); +bool operator>= (STAmount const& lhs, STAmount const& rhs); + +// native currency only +bool operator< (STAmount const& lhs, std::uint64_t rhs); +bool operator> (STAmount const& lhs, std::uint64_t rhs); +bool operator<= (STAmount const& lhs, std::uint64_t rhs); +bool operator>= (STAmount const& lhs, std::uint64_t rhs); + +STAmount operator+ (STAmount const& lhs, std::uint64_t rhs); +STAmount operator- (STAmount const& lhs, std::uint64_t rhs); +STAmount operator- (STAmount const& value); + +//------------------------------------------------------------------------------ +// +// Arithmetic +// +//------------------------------------------------------------------------------ + +STAmount +divide (STAmount const& v1, STAmount const& v2, Issue const& issue); + +inline +STAmount +divide (STAmount const& v1, STAmount const& v2, STAmount const& saUnit) +{ + return divide (v1, v2, saUnit.issue()); +} + +inline +STAmount +divide (STAmount const& v1, STAmount const& v2) +{ + return divide (v1, v2, v1); +} + +STAmount +multiply (STAmount const& v1, STAmount const& v2, Issue const& issue); + +inline +STAmount +multiply (STAmount const& v1, STAmount const& v2, STAmount const& saUnit) +{ + return multiply (v1, v2, saUnit.issue()); +} + +inline +STAmount +multiply (STAmount const& v1, STAmount const& v2) +{ + return multiply (v1, v2, v1); +} + +void +canonicalizeRound (bool native, std::uint64_t& mantissa, + int& exponent, bool roundUp); + +/* addRound, subRound can end up rounding if the amount subtracted is too small + to make a change. Consder (X-d) where d is very small relative to X. + If you ask to round down, then (X-d) should not be X unless d is zero. + If you ask to round up, (X+d) should never be X unless d is zero. (Assuming X and d are positive). +*/ +// Add, subtract, multiply, or divide rounding result in specified direction +STAmount +addRound (STAmount const& v1, STAmount const& v2, bool roundUp); + +STAmount +subRound (STAmount const& v1, STAmount const& v2, bool roundUp); + +STAmount +mulRound (STAmount const& v1, STAmount const& v2, + Issue const& issue, bool roundUp); + +inline +STAmount +mulRound (STAmount const& v1, STAmount const& v2, + STAmount const& saUnit, bool roundUp) +{ + return mulRound (v1, v2, saUnit.issue(), roundUp); +} + +inline +STAmount +mulRound (STAmount const& v1, STAmount const& v2, bool roundUp) +{ + return mulRound (v1, v2, v1.issue(), roundUp); +} + +STAmount +divRound (STAmount const& v1, STAmount const& v2, + Issue const& issue, bool roundUp); + +inline +STAmount +divRound (STAmount const& v1, STAmount const& v2, + STAmount const& saUnit, bool roundUp) +{ + return divRound (v1, v2, saUnit.issue(), roundUp); +} + +inline +STAmount +divRound (STAmount const& v1, STAmount const& v2, bool roundUp) +{ + return divRound (v1, v2, v1.issue(), roundUp); +} + +// Someone is offering X for Y, what is the rate? +// Rate: smaller is better, the taker wants the most out: in/out +// VFALCO TODO Return a Quality object +std::uint64_t +getRate (STAmount const& offerOut, STAmount const& offerIn); + +//------------------------------------------------------------------------------ + inline bool isXRP(STAmount const& amount) { return isXRP (amount.issue().currency); diff --git a/src/ripple/module/data/protocol/STAmount.test.cpp b/src/ripple/module/data/protocol/STAmount.test.cpp new file mode 100644 index 0000000000..2af9e53a2b --- /dev/null +++ b/src/ripple/module/data/protocol/STAmount.test.cpp @@ -0,0 +1,528 @@ +//------------------------------------------------------------------------------ +/* + 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. +*/ +//============================================================================== + +#include +#include + +namespace ripple { + +class STAmount_test : public beast::unit_test::suite +{ +public: + static STAmount serializeAndDeserialize (STAmount const& s) + { + Serializer ser; + s.add (ser); + + SerializerIterator sit (ser); + return STAmount::deserialize (sit); + } + + //-------------------------------------------------------------------------- + + bool roundTest (int n, int d, int m) + { + // check STAmount rounding + STAmount num (noIssue(), n); + STAmount den (noIssue(), d); + STAmount mul (noIssue(), m); + STAmount quot = divide (n, d, noIssue()); + STAmount res = multiply (quot, mul, noIssue()); + + expect (! res.isNative (), "Product should not be native"); + + res.roundSelf (); + + STAmount cmp (noIssue(), (n * m) / d); + + expect (! cmp.isNative (), "Comparison amount should not be native"); + + if (res != cmp) + { + cmp.throwComparable (res); + + WriteLog (lsWARNING, STAmount) << "(" << num.getText () << "/" << den.getText () << ") X " << mul.getText () << " = " + << res.getText () << " not " << cmp.getText (); + + fail ("Rounding"); + + return false; + } + else + { + pass (); + } + + return true; + } + + void mulTest (int a, int b) + { + STAmount aa (noIssue(), a); + STAmount bb (noIssue(), b); + STAmount prod1 (multiply (aa, bb, noIssue())); + + expect (! prod1.isNative ()); + + STAmount prod2 (noIssue(), static_cast (a) * static_cast (b)); + + if (prod1 != prod2) + { + WriteLog (lsWARNING, STAmount) << "nn(" << aa.getFullText () << " * " << bb.getFullText () << ") = " << prod1.getFullText () + << " not " << prod2.getFullText (); + + fail ("Multiplication result is not exact"); + } + else + { + pass (); + } + + aa = a; + prod1 = multiply (aa, bb, noIssue()); + + if (prod1 != prod2) + { + WriteLog (lsWARNING, STAmount) << "n(" << aa.getFullText () << " * " << bb.getFullText () << ") = " << prod1.getFullText () + << " not " << prod2.getFullText (); + fail ("Multiplication result is not exact"); + } + else + { + pass (); + } + } + + //-------------------------------------------------------------------------- + + void testSetValue () + { + testcase ("set value"); + + STAmount saTmp; + + #if 0 + // Check native floats + saTmp.setFullValue ("1^0"); + BOOST_CHECK_MESSAGE (SYSTEM_CURRENCY_PARTS == saTmp.getNValue (), "float integer failed"); + saTmp.setFullValue ("0^1"); + BOOST_CHECK_MESSAGE (SYSTEM_CURRENCY_PARTS / 10 == saTmp.getNValue (), "float fraction failed"); + saTmp.setFullValue ("0^12"); + BOOST_CHECK_MESSAGE (12 * SYSTEM_CURRENCY_PARTS / 100 == saTmp.getNValue (), "float fraction failed"); + saTmp.setFullValue ("1^2"); + BOOST_CHECK_MESSAGE (SYSTEM_CURRENCY_PARTS + (2 * SYSTEM_CURRENCY_PARTS / 10) == saTmp.getNValue (), "float combined failed"); + #endif + + // Check native integer + saTmp.setFullValue ("1"); + expect (1 == saTmp.getNValue (), "should be equal"); + } + + //-------------------------------------------------------------------------- + + void testNativeCurrency () + { + testcase ("native currency"); + STAmount zeroSt, one (1), hundred (100); + // VFALCO NOTE Why repeat "STAmount fail" so many times?? + unexpected (serializeAndDeserialize (zeroSt) != zeroSt, "STAmount fail"); + unexpected (serializeAndDeserialize (one) != one, "STAmount fail"); + unexpected (serializeAndDeserialize (hundred) != hundred, "STAmount fail"); + unexpected (!zeroSt.isNative (), "STAmount fail"); + unexpected (!hundred.isNative (), "STAmount fail"); + unexpected (zeroSt != zero, "STAmount fail"); + unexpected (one == zero, "STAmount fail"); + unexpected (hundred == zero, "STAmount fail"); + unexpected ((zeroSt < zeroSt), "STAmount fail"); + unexpected (! (zeroSt < one), "STAmount fail"); + unexpected (! (zeroSt < hundred), "STAmount fail"); + unexpected ((one < zeroSt), "STAmount fail"); + unexpected ((one < one), "STAmount fail"); + unexpected (! (one < hundred), "STAmount fail"); + unexpected ((hundred < zeroSt), "STAmount fail"); + unexpected ((hundred < one), "STAmount fail"); + unexpected ((hundred < hundred), "STAmount fail"); + unexpected ((zeroSt > zeroSt), "STAmount fail"); + unexpected ((zeroSt > one), "STAmount fail"); + unexpected ((zeroSt > hundred), "STAmount fail"); + unexpected (! (one > zeroSt), "STAmount fail"); + unexpected ((one > one), "STAmount fail"); + unexpected ((one > hundred), "STAmount fail"); + unexpected (! (hundred > zeroSt), "STAmount fail"); + unexpected (! (hundred > one), "STAmount fail"); + unexpected ((hundred > hundred), "STAmount fail"); + unexpected (! (zeroSt <= zeroSt), "STAmount fail"); + unexpected (! (zeroSt <= one), "STAmount fail"); + unexpected (! (zeroSt <= hundred), "STAmount fail"); + unexpected ((one <= zeroSt), "STAmount fail"); + unexpected (! (one <= one), "STAmount fail"); + unexpected (! (one <= hundred), "STAmount fail"); + unexpected ((hundred <= zeroSt), "STAmount fail"); + unexpected ((hundred <= one), "STAmount fail"); + unexpected (! (hundred <= hundred), "STAmount fail"); + unexpected (! (zeroSt >= zeroSt), "STAmount fail"); + unexpected ((zeroSt >= one), "STAmount fail"); + unexpected ((zeroSt >= hundred), "STAmount fail"); + unexpected (! (one >= zeroSt), "STAmount fail"); + unexpected (! (one >= one), "STAmount fail"); + unexpected ((one >= hundred), "STAmount fail"); + unexpected (! (hundred >= zeroSt), "STAmount fail"); + unexpected (! (hundred >= one), "STAmount fail"); + unexpected (! (hundred >= hundred), "STAmount fail"); + unexpected (! (zeroSt == zeroSt), "STAmount fail"); + unexpected ((zeroSt == one), "STAmount fail"); + unexpected ((zeroSt == hundred), "STAmount fail"); + unexpected ((one == zeroSt), "STAmount fail"); + unexpected (! (one == one), "STAmount fail"); + unexpected ((one == hundred), "STAmount fail"); + unexpected ((hundred == zeroSt), "STAmount fail"); + unexpected ((hundred == one), "STAmount fail"); + unexpected (! (hundred == hundred), "STAmount fail"); + unexpected ((zeroSt != zeroSt), "STAmount fail"); + unexpected (! (zeroSt != one), "STAmount fail"); + unexpected (! (zeroSt != hundred), "STAmount fail"); + unexpected (! (one != zeroSt), "STAmount fail"); + unexpected ((one != one), "STAmount fail"); + unexpected (! (one != hundred), "STAmount fail"); + unexpected (! (hundred != zeroSt), "STAmount fail"); + unexpected (! (hundred != one), "STAmount fail"); + unexpected ((hundred != hundred), "STAmount fail"); + unexpected (STAmount ().getText () != "0", "STAmount fail"); + unexpected (STAmount (31).getText () != "31", "STAmount fail"); + unexpected (STAmount (310).getText () != "310", "STAmount fail"); + unexpected (to_string (Currency ()) != "XRP", "cHC(XRP)"); + Currency c; + unexpected (!to_currency (c, "USD"), "create USD currency"); + unexpected (to_string (c) != "USD", "check USD currency"); + + const std::string cur = "015841551A748AD2C1F76FF6ECB0CCCD00000000"; + unexpected (!to_currency (c, cur), "create custom currency"); + unexpected (to_string (c) != cur, "check custom currency"); + unexpected (c != Currency (cur), "check custom currency"); + } + + //-------------------------------------------------------------------------- + + void testCustomCurrency () + { + testcase ("custom currency"); + STAmount zeroSt (noIssue()), one (noIssue(), 1), hundred (noIssue(), 100); + unexpected (serializeAndDeserialize (zeroSt) != zeroSt, "STAmount fail"); + unexpected (serializeAndDeserialize (one) != one, "STAmount fail"); + unexpected (serializeAndDeserialize (hundred) != hundred, "STAmount fail"); + unexpected (zeroSt.isNative (), "STAmount fail"); + unexpected (hundred.isNative (), "STAmount fail"); + unexpected (zeroSt != zero, "STAmount fail"); + unexpected (one == zero, "STAmount fail"); + unexpected (hundred == zero, "STAmount fail"); + unexpected ((zeroSt < zeroSt), "STAmount fail"); + unexpected (! (zeroSt < one), "STAmount fail"); + unexpected (! (zeroSt < hundred), "STAmount fail"); + unexpected ((one < zeroSt), "STAmount fail"); + unexpected ((one < one), "STAmount fail"); + unexpected (! (one < hundred), "STAmount fail"); + unexpected ((hundred < zeroSt), "STAmount fail"); + unexpected ((hundred < one), "STAmount fail"); + unexpected ((hundred < hundred), "STAmount fail"); + unexpected ((zeroSt > zeroSt), "STAmount fail"); + unexpected ((zeroSt > one), "STAmount fail"); + unexpected ((zeroSt > hundred), "STAmount fail"); + unexpected (! (one > zeroSt), "STAmount fail"); + unexpected ((one > one), "STAmount fail"); + unexpected ((one > hundred), "STAmount fail"); + unexpected (! (hundred > zeroSt), "STAmount fail"); + unexpected (! (hundred > one), "STAmount fail"); + unexpected ((hundred > hundred), "STAmount fail"); + unexpected (! (zeroSt <= zeroSt), "STAmount fail"); + unexpected (! (zeroSt <= one), "STAmount fail"); + unexpected (! (zeroSt <= hundred), "STAmount fail"); + unexpected ((one <= zeroSt), "STAmount fail"); + unexpected (! (one <= one), "STAmount fail"); + unexpected (! (one <= hundred), "STAmount fail"); + unexpected ((hundred <= zeroSt), "STAmount fail"); + unexpected ((hundred <= one), "STAmount fail"); + unexpected (! (hundred <= hundred), "STAmount fail"); + unexpected (! (zeroSt >= zeroSt), "STAmount fail"); + unexpected ((zeroSt >= one), "STAmount fail"); + unexpected ((zeroSt >= hundred), "STAmount fail"); + unexpected (! (one >= zeroSt), "STAmount fail"); + unexpected (! (one >= one), "STAmount fail"); + unexpected ((one >= hundred), "STAmount fail"); + unexpected (! (hundred >= zeroSt), "STAmount fail"); + unexpected (! (hundred >= one), "STAmount fail"); + unexpected (! (hundred >= hundred), "STAmount fail"); + unexpected (! (zeroSt == zeroSt), "STAmount fail"); + unexpected ((zeroSt == one), "STAmount fail"); + unexpected ((zeroSt == hundred), "STAmount fail"); + unexpected ((one == zeroSt), "STAmount fail"); + unexpected (! (one == one), "STAmount fail"); + unexpected ((one == hundred), "STAmount fail"); + unexpected ((hundred == zeroSt), "STAmount fail"); + unexpected ((hundred == one), "STAmount fail"); + unexpected (! (hundred == hundred), "STAmount fail"); + unexpected ((zeroSt != zeroSt), "STAmount fail"); + unexpected (! (zeroSt != one), "STAmount fail"); + unexpected (! (zeroSt != hundred), "STAmount fail"); + unexpected (! (one != zeroSt), "STAmount fail"); + unexpected ((one != one), "STAmount fail"); + unexpected (! (one != hundred), "STAmount fail"); + unexpected (! (hundred != zeroSt), "STAmount fail"); + unexpected (! (hundred != one), "STAmount fail"); + unexpected ((hundred != hundred), "STAmount fail"); + unexpected (STAmount (noIssue()).getText () != "0", "STAmount fail"); + unexpected (STAmount (noIssue(), 31).getText () != "31", "STAmount fail"); + unexpected (STAmount (noIssue(), 31, 1).getText () != "310", "STAmount fail"); + unexpected (STAmount (noIssue(), 31, -1).getText () != "3.1", "STAmount fail"); + unexpected (STAmount (noIssue(), 31, -2).getText () != "0.31", "STAmount fail"); + unexpected (multiply (STAmount (noIssue(), 20), STAmount (3), noIssue()).getText () != "60", + "STAmount multiply fail 1"); + unexpected (multiply (STAmount (noIssue(), 20), STAmount (3), xrpIssue ()).getText () != "60", + "STAmount multiply fail 2"); + unexpected (multiply (STAmount (20), STAmount (3), noIssue()).getText () != "60", + "STAmount multiply fail 3"); + unexpected (multiply (STAmount (20), STAmount (3), xrpIssue ()).getText () != "60", + "STAmount multiply fail 4"); + + if (divide (STAmount (noIssue(), 60), STAmount (3), noIssue()).getText () != "20") + { + WriteLog (lsFATAL, STAmount) << "60/3 = " << + divide (STAmount (noIssue(), 60), + STAmount (3), noIssue()).getText (); + fail ("STAmount divide fail"); + } + else + { + pass (); + } + + unexpected (divide (STAmount (noIssue(), 60), STAmount (3), xrpIssue ()).getText () != "20", + "STAmount divide fail"); + + unexpected (divide (STAmount (noIssue(), 60), STAmount (noIssue(), 3), noIssue()).getText () != "20", + "STAmount divide fail"); + + unexpected (divide (STAmount (noIssue(), 60), STAmount (noIssue(), 3), xrpIssue ()).getText () != "20", + "STAmount divide fail"); + + STAmount a1 (noIssue(), 60), a2 (noIssue(), 10, -1); + + unexpected (divide (a2, a1, noIssue()) != amountFromQuality (getRate (a1, a2)), + "STAmount setRate(getRate) fail"); + + unexpected (divide (a1, a2, noIssue()) != amountFromQuality (getRate (a2, a1)), + "STAmount setRate(getRate) fail"); + } + + //-------------------------------------------------------------------------- + + void testArithmetic () + { + testcase ("arithmetic"); + + CBigNum b; + + for (int i = 0; i < 16; ++i) + { + std::uint64_t r = rand (); + r <<= 32; + r |= rand (); + b.setuint64 (r); + + if (b.getuint64 () != r) + { + WriteLog (lsFATAL, STAmount) << r << " != " << b.getuint64 () << " " << b.ToString (16); + fail ("setull64/getull64 failure"); + } + else + { + pass (); + } + } + + // Test currency multiplication and division operations such as + // convertToDisplayAmount, convertToInternalAmount, getRate, getClaimed, and getNeeded + + unexpected (getRate (STAmount (1), STAmount (10)) != (((100ull - 14) << (64 - 8)) | 1000000000000000ull), + "STAmount getRate fail 1"); + + unexpected (getRate (STAmount (10), STAmount (1)) != (((100ull - 16) << (64 - 8)) | 1000000000000000ull), + "STAmount getRate fail 2"); + + unexpected (getRate (STAmount (noIssue(), 1), STAmount (noIssue(), 10)) != (((100ull - 14) << (64 - 8)) | 1000000000000000ull), + "STAmount getRate fail 3"); + + unexpected (getRate (STAmount (noIssue(), 10), STAmount (noIssue(), 1)) != (((100ull - 16) << (64 - 8)) | 1000000000000000ull), + "STAmount getRate fail 4"); + + unexpected (getRate (STAmount (noIssue(), 1), STAmount (10)) != (((100ull - 14) << (64 - 8)) | 1000000000000000ull), + "STAmount getRate fail 5"); + + unexpected (getRate (STAmount (noIssue(), 10), STAmount (1)) != (((100ull - 16) << (64 - 8)) | 1000000000000000ull), + "STAmount getRate fail 6"); + + unexpected (getRate (STAmount (1), STAmount (noIssue(), 10)) != (((100ull - 14) << (64 - 8)) | 1000000000000000ull), + "STAmount getRate fail 7"); + + unexpected (getRate (STAmount (10), STAmount (noIssue(), 1)) != (((100ull - 16) << (64 - 8)) | 1000000000000000ull), + "STAmount getRate fail 8"); + + roundTest (1, 3, 3); + roundTest (2, 3, 9); + roundTest (1, 7, 21); + roundTest (1, 2, 4); + roundTest (3, 9, 18); + roundTest (7, 11, 44); + + for (int i = 0; i <= 100000; ++i) + mulTest (rand () % 10000000, rand () % 10000000); + } + + //-------------------------------------------------------------------------- + + template + bool + expect (Cond cond, beast::String const& s) + { + return suite::expect (cond, s.toStdString()); + } + + template + bool + expect (Cond cond) + { + return suite::expect (cond); + } + + void testUnderflow () + { + testcase ("underflow"); + + STAmount bigNative (STAmount::cMaxNative / 2); + STAmount bigValue (noIssue(), + (STAmount::cMinValue + STAmount::cMaxValue) / 2, STAmount::cMaxOffset - 1); + STAmount smallValue (noIssue(), + (STAmount::cMinValue + STAmount::cMaxValue) / 2, STAmount::cMinOffset + 1); + STAmount zeroSt (noIssue(), 0); + + STAmount smallXsmall = multiply (smallValue, smallValue, noIssue()); + + expect (smallXsmall == zero, "smallXsmall != 0"); + + STAmount bigDsmall = divide (smallValue, bigValue, noIssue()); + + expect (bigDsmall == zero, beast::String ("small/big != 0: ") + bigDsmall.getText ()); + +#if 0 + // TODO(tom): this test makes no sense - we should have no way to have + // the currency not be XRP while the account is XRP. + bigDsmall = divide (smallValue, bigNative, noCurrency(), xrpAccount ()); +#endif + + expect (bigDsmall == zero, beast::String ("small/bigNative != 0: ") + bigDsmall.getText ()); + + bigDsmall = divide (smallValue, bigValue, xrpIssue ()); + + expect (bigDsmall == zero, beast::String ("(small/big)->N != 0: ") + bigDsmall.getText ()); + + bigDsmall = divide (smallValue, bigNative, xrpIssue ()); + + expect (bigDsmall == zero, beast::String ("(small/bigNative)->N != 0: ") + bigDsmall.getText ()); + + // very bad offer + std::uint64_t r = getRate (smallValue, bigValue); + + expect (r == 0, "getRate(smallOut/bigIn) != 0"); + + // very good offer + r = getRate (bigValue, smallValue); + + expect (r == 0, "getRate(smallIn/bigOUt) != 0"); + } + + //-------------------------------------------------------------------------- + + void testRounding () + { + // VFALCO TODO There are no actual tests here, just printed output? + // Change this to actually do something. + +#if 0 + beginTestCase ("rounding "); + + std::uint64_t value = 25000000000000000ull; + int offset = -14; + canonicalizeRound (false, value, offset, true); + + STAmount one (noIssue(), 1); + STAmount two (noIssue(), 2); + STAmount three (noIssue(), 3); + + STAmount oneThird1 = divRound (one, three, noIssue(), false); + STAmount oneThird2 = divide (one, three, noIssue()); + STAmount oneThird3 = divRound (one, three, noIssue(), true); + WriteLog (lsINFO, STAmount) << oneThird1; + WriteLog (lsINFO, STAmount) << oneThird2; + WriteLog (lsINFO, STAmount) << oneThird3; + + STAmount twoThird1 = divRound (two, three, noIssue(), false); + STAmount twoThird2 = divide (two, three, noIssue()); + STAmount twoThird3 = divRound (two, three, noIssue(), true); + WriteLog (lsINFO, STAmount) << twoThird1; + WriteLog (lsINFO, STAmount) << twoThird2; + WriteLog (lsINFO, STAmount) << twoThird3; + + STAmount oneA = mulRound (oneThird1, three, noIssue(), false); + STAmount oneB = multiply (oneThird2, three, noIssue()); + STAmount oneC = mulRound (oneThird3, three, noIssue(), true); + WriteLog (lsINFO, STAmount) << oneA; + WriteLog (lsINFO, STAmount) << oneB; + WriteLog (lsINFO, STAmount) << oneC; + + STAmount fourThirdsA = addRound (twoThird2, twoThird2, false); + STAmount fourThirdsB = twoThird2 + twoThird2; + STAmount fourThirdsC = addRound (twoThird2, twoThird2, true); + WriteLog (lsINFO, STAmount) << fourThirdsA; + WriteLog (lsINFO, STAmount) << fourThirdsB; + WriteLog (lsINFO, STAmount) << fourThirdsC; + + STAmount dripTest1 = mulRound (twoThird2, two, xrpIssue (), false); + STAmount dripTest2 = multiply (twoThird2, two, xrpIssue ()); + STAmount dripTest3 = mulRound (twoThird2, two, xrpIssue (), true); + WriteLog (lsINFO, STAmount) << dripTest1; + WriteLog (lsINFO, STAmount) << dripTest2; + WriteLog (lsINFO, STAmount) << dripTest3; +#endif + } + + //-------------------------------------------------------------------------- + + void run () + { + testSetValue (); + testNativeCurrency (); + testCustomCurrency (); + testArithmetic (); + testUnderflow (); + testRounding (); + } +}; + +BEAST_DEFINE_TESTSUITE(STAmount,ripple_data,ripple); + +} // ripple diff --git a/src/ripple/module/data/protocol/STAmountRound.cpp b/src/ripple/module/data/protocol/STAmountRound.cpp deleted file mode 100644 index 97c8dc28c7..0000000000 --- a/src/ripple/module/data/protocol/STAmountRound.cpp +++ /dev/null @@ -1,329 +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. -*/ -//============================================================================== - -namespace ripple { - -void STAmount::canonicalizeRound ( - bool isNative, std::uint64_t& value, int& offset, bool roundUp) -{ - if (!roundUp) // canonicalize already rounds down - return; - - WriteLog (lsTRACE, STAmount) - << "canonicalizeRound< " << value << ":" << offset; - - if (isNative) - { - if (offset < 0) - { - int loops = 0; - - while (offset < -1) - { - value /= 10; - ++offset; - ++loops; - } - - value += (loops >= 2) ? 9 : 10; // add before last divide - value /= 10; - ++offset; - } - } - else if (value > STAmount::cMaxValue) - { - while (value > (10 * STAmount::cMaxValue)) - { - value /= 10; - ++offset; - } - - value += 9; // add before last divide - value /= 10; - ++offset; - } - - WriteLog (lsTRACE, STAmount) - << "canonicalizeRound> " << value << ":" << offset; -} - -STAmount STAmount::addRound (STAmount const& v1, STAmount const& v2, bool roundUp) -{ - v1.throwComparable (v2); - - if (v2.mValue == 0) - return v1; - - if (v1.mValue == 0) - return STAmount (v1.getFName (), v1.mIssue, v2.mValue, - v2.mOffset, v2.mIsNegative); - - if (v1.mIsNative) - return STAmount (v1.getFName (), v1.getSNValue () + v2.getSNValue ()); - - int ov1 = v1.mOffset, ov2 = v2.mOffset; - auto vv1 = static_cast (v1.mValue); - auto vv2 = static_cast (v2.mValue); - - if (v1.mIsNegative) - vv1 = -vv1; - - if (v2.mIsNegative) - vv2 = -vv2; - - if (ov1 < ov2) - { - while (ov1 < (ov2 - 1)) - { - vv1 /= 10; - ++ov1; - } - - if (roundUp) - vv1 += 9; - - vv1 /= 10; - ++ov1; - } - - if (ov2 < ov1) - { - while (ov2 < (ov1 - 1)) - { - vv2 /= 10; - ++ov2; - } - - if (roundUp) - vv2 += 9; - - vv2 /= 10; - ++ov2; - } - - std::int64_t fv = vv1 + vv2; - - if ((fv >= -10) && (fv <= 10)) - return STAmount (v1.getFName (), v1.mIssue); - else if (fv >= 0) - { - std::uint64_t v = static_cast (fv); - canonicalizeRound (false, v, ov1, roundUp); - return STAmount (v1.getFName (), v1.mIssue, v, ov1, false); - } - else - { - std::uint64_t v = static_cast (-fv); - canonicalizeRound (false, v, ov1, !roundUp); - return STAmount (v1.getFName (), v1.mIssue, v, ov1, true); - } -} - -STAmount STAmount::subRound (STAmount const& v1, STAmount const& v2, bool roundUp) -{ - v1.throwComparable (v2); - - if (v2.mValue == 0) - return v1; - - if (v1.mValue == 0) - return STAmount (v1.getFName (), v1.mIssue, v2.mValue, - v2.mOffset, !v2.mIsNegative); - - if (v1.mIsNative) - return STAmount (v1.getFName (), v1.getSNValue () - v2.getSNValue ()); - - int ov1 = v1.mOffset, ov2 = v2.mOffset; - auto vv1 = static_cast (v1.mValue); - auto vv2 = static_cast (v2.mValue); - - if (v1.mIsNegative) - vv1 = -vv1; - - if (!v2.mIsNegative) - vv2 = -vv2; - - if (ov1 < ov2) - { - while (ov1 < (ov2 - 1)) - { - vv1 /= 10; - ++ov1; - } - - if (roundUp) - vv1 += 9; - - vv1 /= 10; - ++ov1; - } - - if (ov2 < ov1) - { - while (ov2 < (ov1 - 1)) - { - vv2 /= 10; - ++ov2; - } - - if (roundUp) - vv2 += 9; - - vv2 /= 10; - ++ov2; - } - - std::int64_t fv = vv1 + vv2; - - if ((fv >= -10) && (fv <= 10)) - return STAmount (v1.getFName (), v1.mIssue); - - if (fv >= 0) - { - std::uint64_t v = static_cast (fv); - canonicalizeRound (false, v, ov1, roundUp); - return STAmount (v1.getFName (), v1.mIssue, v, ov1, false); - } - else - { - std::uint64_t v = static_cast (-fv); - canonicalizeRound (false, v, ov1, !roundUp); - return STAmount (v1.getFName (), v1.mIssue, v, ov1, true); - } -} - -STAmount STAmount::mulRound ( - STAmount const& v1, STAmount const& v2, Issue const& issue, bool roundUp) -{ - if (v1 == zero || v2 == zero) - return {issue}; - - if (v1.mIsNative && v2.mIsNative && isXRP (issue)) - { - std::uint64_t minV = (v1.getSNValue () < v2.getSNValue ()) ? - v1.getSNValue () : v2.getSNValue (); - std::uint64_t maxV = (v1.getSNValue () < v2.getSNValue ()) ? - v2.getSNValue () : v1.getSNValue (); - - if (minV > 3000000000ull) // sqrt(cMaxNative) - throw std::runtime_error ("Native value overflow"); - - if (((maxV >> 32) * minV) > 2095475792ull) // cMaxNative / 2^32 - throw std::runtime_error ("Native value overflow"); - - return STAmount (v1.getFName (), minV * maxV); - } - - std::uint64_t value1 = v1.mValue, value2 = v2.mValue; - int offset1 = v1.mOffset, offset2 = v2.mOffset; - - if (v1.mIsNative) - { - while (value1 < STAmount::cMinValue) - { - value1 *= 10; - --offset1; - } - } - - if (v2.mIsNative) - { - while (value2 < STAmount::cMinValue) - { - value2 *= 10; - --offset2; - } - } - - bool resultNegative = v1.mIsNegative != v2.mIsNegative; - // Compute (numerator * denominator) / 10^14 with rounding - // 10^16 <= result <= 10^18 - CBigNum v; - - if ((BN_add_word64 (&v, value1) != 1) || (BN_mul_word64 (&v, value2) != 1)) - throw std::runtime_error ("internal bn error"); - - if (resultNegative != roundUp) // rounding down is automatic when we divide - BN_add_word64 (&v, tenTo14m1); - - if (BN_div_word64 (&v, tenTo14) == ((std::uint64_t) - 1)) - throw std::runtime_error ("internal bn error"); - - // 10^16 <= product <= 10^18 - assert (BN_num_bytes (&v) <= 64); - - std::uint64_t amount = v.getuint64 (); - int offset = offset1 + offset2 + 14; - canonicalizeRound ( - isXRP (issue), amount, offset, resultNegative != roundUp); - return STAmount (issue, amount, offset, resultNegative); -} - -STAmount STAmount::divRound ( - STAmount const& num, STAmount const& den, - Issue const& issue, bool roundUp) -{ - if (den == zero) - throw std::runtime_error ("division by zero"); - - if (num == zero) - return {issue}; - - std::uint64_t numVal = num.mValue, denVal = den.mValue; - int numOffset = num.mOffset, denOffset = den.mOffset; - - if (num.mIsNative) - while (numVal < STAmount::cMinValue) - { - // Need to bring into range - numVal *= 10; - --numOffset; - } - - if (den.mIsNative) - while (denVal < STAmount::cMinValue) - { - denVal *= 10; - --denOffset; - } - - bool resultNegative = num.mIsNegative != den.mIsNegative; - // Compute (numerator * 10^17) / denominator - CBigNum v; - - if ((BN_add_word64 (&v, numVal) != 1) || (BN_mul_word64 (&v, tenTo17) != 1)) - throw std::runtime_error ("internal bn error"); - - if (resultNegative != roundUp) // Rounding down is automatic when we divide - BN_add_word64 (&v, denVal - 1); - - if (BN_div_word64 (&v, denVal) == ((std::uint64_t) - 1)) - throw std::runtime_error ("internal bn error"); - - // 10^16 <= quotient <= 10^18 - assert (BN_num_bytes (&v) <= 64); - - std::uint64_t amount = v.getuint64 (); - int offset = numOffset - denOffset - 17; - canonicalizeRound ( - isXRP (issue), amount, offset, resultNegative != roundUp); - return STAmount (issue, amount, offset, resultNegative); -} - -} // ripple diff --git a/src/ripple/module/data/protocol/STParsedJSON.cpp b/src/ripple/module/data/protocol/STParsedJSON.cpp index 8f0e9ed5e3..60113e4718 100644 --- a/src/ripple/module/data/protocol/STParsedJSON.cpp +++ b/src/ripple/module/data/protocol/STParsedJSON.cpp @@ -416,7 +416,7 @@ bool STParsedJSON::parse (std::string const& json_name, case STI_AMOUNT: try { - data.push_back (new STAmount (field, value)); + data.push_back (new STAmount (amountFromJson (field, value))); } catch (...) { diff --git a/src/ripple/module/data/protocol/SerializedType.h b/src/ripple/module/data/protocol/SerializedType.h index bdf07914b2..cc709f8da8 100644 --- a/src/ripple/module/data/protocol/SerializedType.h +++ b/src/ripple/module/data/protocol/SerializedType.h @@ -83,14 +83,78 @@ public: assert (fName); } - virtual ~SerializedType () { } + virtual ~SerializedType () = default; - static std::unique_ptr deserialize (SField::ref name) + // + // overridables + // + + virtual + SerializedTypeID + getSType () const + { + return STI_NOTPRESENT; + } + + virtual + std::string + getFullText() const; + + // just the value + virtual + std::string + getText() const + { + return std::string(); + } + + virtual + Json::Value getJson (int /*options*/) const + { + return getText(); + } + + virtual + void + add (Serializer& s) const + { + // VFALCO Why not just make this pure virtual? + assert (false); + } + + virtual + bool + isEquivalent (SerializedType const& t) const; + + virtual + bool + isDefault () const + { + return true; + } + +private: + // VFALCO TODO Return std::unique_ptr + virtual + SerializedType* + duplicate () const + { + return new SerializedType (*fName); + } + +public: + // + // members + // + + static + std::unique_ptr + deserialize (SField::ref name) { return std::unique_ptr (new SerializedType (name)); } - /** A SerializeType is a field. + /** A SerializedType is a field. This sets the name. */ void setFName (SField::ref n) @@ -102,31 +166,11 @@ public: { return *fName; } - virtual SerializedTypeID getSType () const - { - return STI_NOTPRESENT; - } std::unique_ptr clone () const { return std::unique_ptr (duplicate ()); } - virtual std::string getFullText () const; - virtual std::string getText () const // just the value - { - return std::string (); - } - virtual Json::Value getJson (int /*options*/) const - { - return getText (); - } - - virtual void add (Serializer& s) const - { - assert (false); - } - - virtual bool isEquivalent (const SerializedType& t) const; void addFieldID (Serializer& s) const { @@ -145,11 +189,6 @@ public: return (getSType () != t.getSType ()) || !isEquivalent (t); } - virtual bool isDefault () const - { - return true; - } - template D& downcast() { @@ -171,12 +210,6 @@ public: protected: // VFALCO TODO make accessors for this SField::ptr fName; - -private: - virtual SerializedType* duplicate () const - { - return new SerializedType (*fName); - } }; //------------------------------------------------------------------------------ diff --git a/src/ripple/module/data/protocol/SerializedTypes.cpp b/src/ripple/module/data/protocol/SerializedTypes.cpp index 920d730096..ea423602d2 100644 --- a/src/ripple/module/data/protocol/SerializedTypes.cpp +++ b/src/ripple/module/data/protocol/SerializedTypes.cpp @@ -19,8 +19,8 @@ namespace ripple { -const STAmount saZero (noIssue(), 0); -const STAmount saOne (noIssue(), 1); +const STAmount saZero (noIssue(), 0u); +const STAmount saOne (noIssue(), 1u); SerializedType& SerializedType::operator= (const SerializedType& t) { diff --git a/src/ripple/module/rpc/handlers/RipplePathFind.cpp b/src/ripple/module/rpc/handlers/RipplePathFind.cpp index dcb6209383..8c19803eb2 100644 --- a/src/ripple/module/rpc/handlers/RipplePathFind.cpp +++ b/src/ripple/module/rpc/handlers/RipplePathFind.cpp @@ -72,7 +72,7 @@ Json::Value doRipplePathFind (RPC::Context& context) else if ( // Parse saDstAmount. !context.params_.isMember ("destination_amount") - || !saDstAmount.bSetJson (context.params_["destination_amount"]) + || ! amountFromJsonNoThrow(saDstAmount, context.params_["destination_amount"]) || saDstAmount <= zero || (!isXRP(saDstAmount.getCurrency ()) && (!saDstAmount.getIssuer () || diff --git a/src/ripple/module/rpc/impl/TransactionSign.cpp b/src/ripple/module/rpc/impl/TransactionSign.cpp index 95596c899e..b9cc821039 100644 --- a/src/ripple/module/rpc/impl/TransactionSign.cpp +++ b/src/ripple/module/rpc/impl/TransactionSign.cpp @@ -105,7 +105,7 @@ static Json::Value signPayment( STAmount amount; - if (!amount.bSetJson (tx_json ["Amount"])) + if (! amountFromJsonNoThrow (amount, tx_json ["Amount"])) return RPC::invalid_field_error ("tx_json.Amount"); if (!tx_json.isMember ("Destination")) @@ -131,7 +131,7 @@ static Json::Value signPayment( if (tx_json.isMember ("SendMax")) { - if (!saSendMax.bSetJson (tx_json ["SendMax"])) + if (! amountFromJsonNoThrow (saSendMax, tx_json ["SendMax"])) return RPC::invalid_field_error ("tx_json.SendMax"); } else diff --git a/src/ripple/unity/data.cpp b/src/ripple/unity/data.cpp index 010e661771..0857df2d37 100644 --- a/src/ripple/unity/data.cpp +++ b/src/ripple/unity/data.cpp @@ -79,13 +79,8 @@ #include #include #include - -// These are for STAmount -static const std::uint64_t tenTo14 = 100000000000000ull; -static const std::uint64_t tenTo14m1 = tenTo14 - 1; -static const std::uint64_t tenTo17 = tenTo14 * 1000; #include -#include +#include #if BEAST_MSVC #pragma warning (pop)