From 28792f0a1815e0bb6cc617b5a9a4f88b7bea95ba Mon Sep 17 00:00:00 2001 From: gregtatcam Date: Fri, 23 Feb 2024 16:40:11 +0000 Subject: [PATCH] Implement direct payment with mAsset in STAmount holding a union of Issue/MPTIssue --- Builds/CMake/RippledCore.cmake | 2 + src/ripple/app/paths/AMMLiquidity.h | 4 +- src/ripple/app/paths/AMMOffer.h | 6 +- src/ripple/app/paths/Credit.cpp | 4 +- src/ripple/app/paths/Flow.cpp | 6 + src/ripple/app/paths/PathRequest.cpp | 14 +- src/ripple/app/paths/Pathfinder.cpp | 7 +- src/ripple/app/paths/impl/AMMLiquidity.cpp | 1 - src/ripple/app/paths/impl/AMMOffer.cpp | 19 +- src/ripple/app/paths/impl/AmountSpec.h | 11 +- src/ripple/app/paths/impl/Steps.h | 6 +- src/ripple/app/paths/impl/StrandFlow.h | 2 +- src/ripple/app/tx/impl/AMMCreate.cpp | 6 + src/ripple/app/tx/impl/CashCheck.cpp | 4 + src/ripple/app/tx/impl/Clawback.cpp | 3 + src/ripple/app/tx/impl/CreateCheck.cpp | 4 + src/ripple/app/tx/impl/CreateOffer.cpp | 3 + src/ripple/app/tx/impl/Escrow.cpp | 3 + src/ripple/app/tx/impl/NFTokenAcceptOffer.cpp | 3 + src/ripple/app/tx/impl/NFTokenCreateOffer.cpp | 3 + src/ripple/app/tx/impl/PayChan.cpp | 3 + src/ripple/app/tx/impl/Payment.cpp | 98 +++++-- src/ripple/app/tx/impl/SetTrust.cpp | 5 +- src/ripple/app/tx/impl/XChainBridge.cpp | 4 + src/ripple/basics/Number.h | 7 + src/ripple/basics/impl/Number.cpp | 5 + src/ripple/ledger/View.h | 37 +++ src/ripple/ledger/impl/View.cpp | 220 +++++++++++++- src/ripple/protocol/AmountConversions.h | 6 + src/ripple/protocol/Asset.h | 130 +++++++++ src/ripple/protocol/ErrorCodes.h | 5 +- src/ripple/protocol/Issue.h | 7 + src/ripple/protocol/MPTIssue.h | 74 +++++ src/ripple/protocol/Rate.h | 4 +- src/ripple/protocol/STAmount.h | 271 +++++++++++++++--- src/ripple/protocol/TER.h | 5 +- src/ripple/protocol/impl/AMMCore.cpp | 2 + src/ripple/protocol/impl/Asset.cpp | 63 ++++ src/ripple/protocol/impl/ErrorCodes.cpp | 1 + src/ripple/protocol/impl/MPTIssue.cpp | 31 ++ src/ripple/protocol/impl/Quality.cpp | 8 +- src/ripple/protocol/impl/Rate2.cpp | 16 +- src/ripple/protocol/impl/STAmount.cpp | 232 ++++++++------- src/ripple/protocol/impl/STTx.cpp | 11 +- src/ripple/protocol/impl/TER.cpp | 2 + src/ripple/rpc/impl/TransactionSign.cpp | 3 + src/test/app/MPToken_test.cpp | 9 - src/test/app/SetAuth_test.cpp | 4 +- src/test/jtx/amount.h | 21 +- src/test/jtx/impl/amount.cpp | 7 +- src/test/jtx/impl/mpt.cpp | 8 +- src/test/protocol/Quality_test.cpp | 2 +- 52 files changed, 1158 insertions(+), 254 deletions(-) create mode 100644 src/ripple/protocol/Asset.h create mode 100644 src/ripple/protocol/MPTIssue.h create mode 100644 src/ripple/protocol/impl/Asset.cpp create mode 100644 src/ripple/protocol/impl/MPTIssue.cpp diff --git a/Builds/CMake/RippledCore.cmake b/Builds/CMake/RippledCore.cmake index 199818b9e84..c379f497c41 100644 --- a/Builds/CMake/RippledCore.cmake +++ b/Builds/CMake/RippledCore.cmake @@ -75,6 +75,7 @@ target_sources (xrpl_core PRIVATE #]===============================] src/ripple/protocol/impl/AccountID.cpp src/ripple/protocol/impl/AMMCore.cpp + src/ripple/protocol/impl/Asset.cpp src/ripple/protocol/impl/Book.cpp src/ripple/protocol/impl/BuildInfo.cpp src/ripple/protocol/impl/ErrorCodes.cpp @@ -86,6 +87,7 @@ target_sources (xrpl_core PRIVATE src/ripple/protocol/impl/Keylet.cpp src/ripple/protocol/impl/LedgerFormats.cpp src/ripple/protocol/impl/LedgerHeader.cpp + src/ripple/protocol/impl/MPTIssue.cpp src/ripple/protocol/impl/PublicKey.cpp src/ripple/protocol/impl/Quality.cpp src/ripple/protocol/impl/QualityFunction.cpp diff --git a/src/ripple/app/paths/AMMLiquidity.h b/src/ripple/app/paths/AMMLiquidity.h index 7757bd4684d..72d5dfd9677 100644 --- a/src/ripple/app/paths/AMMLiquidity.h +++ b/src/ripple/app/paths/AMMLiquidity.h @@ -23,6 +23,7 @@ #include "ripple/app/misc/AMMHelpers.h" #include "ripple/app/misc/AMMUtils.h" #include "ripple/app/paths/AMMContext.h" +#include "ripple/app/paths/AMMOffer.h" #include "ripple/basics/Log.h" #include "ripple/ledger/ReadView.h" #include "ripple/ledger/View.h" @@ -31,9 +32,6 @@ namespace ripple { -template -class AMMOffer; - /** AMMLiquidity class provides AMM offers to BookStep class. * The offers are generated in two ways. If there are multiple * paths specified to the payment transaction then the offers diff --git a/src/ripple/app/paths/AMMOffer.h b/src/ripple/app/paths/AMMOffer.h index 10e6017dd96..64327dfca30 100644 --- a/src/ripple/app/paths/AMMOffer.h +++ b/src/ripple/app/paths/AMMOffer.h @@ -31,11 +31,15 @@ template class AMMLiquidity; class QualityFunction; +template +concept OfferAmount = + !(std::is_same_v || std::is_same_v); + /** Represents synthetic AMM offer in BookStep. AMMOffer mirrors TOffer * methods for use in generic BookStep methods. AMMOffer amounts * are changed indirectly in BookStep limiting steps. */ -template +template class AMMOffer { private: diff --git a/src/ripple/app/paths/Credit.cpp b/src/ripple/app/paths/Credit.cpp index b9b19d29f90..5266a3e3433 100644 --- a/src/ripple/app/paths/Credit.cpp +++ b/src/ripple/app/paths/Credit.cpp @@ -31,7 +31,7 @@ creditLimit( AccountID const& issuer, Currency const& currency) { - STAmount result({currency, account}); + STAmount result(Issue{currency, account}); auto sleRippleState = view.read(keylet::line(account, issuer, currency)); @@ -64,7 +64,7 @@ creditBalance( AccountID const& issuer, Currency const& currency) { - STAmount result({currency, account}); + STAmount result(Issue{currency, account}); auto sleRippleState = view.read(keylet::line(account, issuer, currency)); diff --git a/src/ripple/app/paths/Flow.cpp b/src/ripple/app/paths/Flow.cpp index 83379d34e79..b3d4ecc1c20 100644 --- a/src/ripple/app/paths/Flow.cpp +++ b/src/ripple/app/paths/Flow.cpp @@ -71,6 +71,12 @@ flow( beast::Journal j, path::detail::FlowDebugInfo* flowDebugInfo) { + if (deliver.isMPT() || (sendMax && sendMax->isMPT())) + { + path::RippleCalc::Output result; + result.setResult(tecMPT_NOT_SUPPORTED); + } + Issue const srcIssue = [&] { if (sendMax) return sendMax->issue(); diff --git a/src/ripple/app/paths/PathRequest.cpp b/src/ripple/app/paths/PathRequest.cpp index f2aa363f934..eff909ab9b2 100644 --- a/src/ripple/app/paths/PathRequest.cpp +++ b/src/ripple/app/paths/PathRequest.cpp @@ -313,6 +313,12 @@ PathRequest::parseJson(Json::Value const& jvParams) return PFR_PJ_INVALID; } + if (saDstAmount.isMPT()) + { + jvStatus = rpcError(rpcMPT_NOT_SUPPORTED); + return PFR_PJ_INVALID; + } + convert_all_ = saDstAmount == STAmount(saDstAmount.issue(), 1u, 0, true); if ((saDstAmount.getCurrency().isZero() && @@ -344,6 +350,12 @@ PathRequest::parseJson(Json::Value const& jvParams) jvStatus = rpcError(rpcSENDMAX_MALFORMED); return PFR_PJ_INVALID; } + + if (saSendMax->isMPT()) + { + jvStatus = rpcError(rpcMPT_NOT_SUPPORTED); + return PFR_PJ_INVALID; + } } if (jvParams.isMember(jss::source_currencies)) @@ -562,7 +574,7 @@ PathRequest::findPaths( }(); STAmount saMaxAmount = saSendMax.value_or( - STAmount({issue.currency, sourceAccount}, 1u, 0, true)); + STAmount(Issue{issue.currency, sourceAccount}, 1u, 0, true)); JLOG(m_journal.debug()) << iIdentifier << " Paths found, calling rippleCalc"; diff --git a/src/ripple/app/paths/Pathfinder.cpp b/src/ripple/app/paths/Pathfinder.cpp index 556622ee7bf..217255a47fa 100644 --- a/src/ripple/app/paths/Pathfinder.cpp +++ b/src/ripple/app/paths/Pathfinder.cpp @@ -176,9 +176,10 @@ Pathfinder::Pathfinder( , mSrcCurrency(uSrcCurrency) , mSrcIssuer(uSrcIssuer) , mSrcAmount(srcAmount.value_or(STAmount( - {uSrcCurrency, - uSrcIssuer.value_or( - isXRP(uSrcCurrency) ? xrpAccount() : uSrcAccount)}, + Issue{ + uSrcCurrency, + uSrcIssuer.value_or( + isXRP(uSrcCurrency) ? xrpAccount() : uSrcAccount)}, 1u, 0, true))) diff --git a/src/ripple/app/paths/impl/AMMLiquidity.cpp b/src/ripple/app/paths/impl/AMMLiquidity.cpp index 3f22ebacec5..c4345060842 100644 --- a/src/ripple/app/paths/impl/AMMLiquidity.cpp +++ b/src/ripple/app/paths/impl/AMMLiquidity.cpp @@ -215,7 +215,6 @@ AMMLiquidity::getOffer( return std::nullopt; } -template class AMMLiquidity; template class AMMLiquidity; template class AMMLiquidity; template class AMMLiquidity; diff --git a/src/ripple/app/paths/impl/AMMOffer.cpp b/src/ripple/app/paths/impl/AMMOffer.cpp index 10b75b78565..9c09051340e 100644 --- a/src/ripple/app/paths/impl/AMMOffer.cpp +++ b/src/ripple/app/paths/impl/AMMOffer.cpp @@ -23,7 +23,7 @@ namespace ripple { -template +template AMMOffer::AMMOffer( AMMLiquidity const& ammLiquidity, TAmounts const& amounts, @@ -37,35 +37,35 @@ AMMOffer::AMMOffer( { } -template +template Issue const& AMMOffer::issueIn() const { return ammLiquidity_.issueIn(); } -template +template Issue const& AMMOffer::issueOut() const { return ammLiquidity_.issueOut(); } -template +template AccountID const& AMMOffer::owner() const { return ammLiquidity_.ammAccount(); } -template +template TAmounts const& AMMOffer::amount() const { return amounts_; } -template +template void AMMOffer::consume( ApplyView& view, @@ -83,7 +83,7 @@ AMMOffer::consume( ammLiquidity_.context().setAMMUsed(); } -template +template TAmounts AMMOffer::limitOut( TAmounts const& offrAmt, @@ -113,7 +113,7 @@ AMMOffer::limitOut( return {swapAssetOut(*balances_, limit, ammLiquidity_.tradingFee()), limit}; } -template +template TAmounts AMMOffer::limitIn( TAmounts const& offrAmt, @@ -125,7 +125,7 @@ AMMOffer::limitIn( return {limit, swapAssetIn(*balances_, limit, ammLiquidity_.tradingFee())}; } -template +template QualityFunction AMMOffer::getQualityFunc() const { @@ -135,7 +135,6 @@ AMMOffer::getQualityFunc() const *balances_, ammLiquidity_.tradingFee(), QualityFunction::AMMTag{}}; } -template class AMMOffer; template class AMMOffer; template class AMMOffer; template class AMMOffer; diff --git a/src/ripple/app/paths/impl/AmountSpec.h b/src/ripple/app/paths/impl/AmountSpec.h index ca814c7b3ac..bc1c1844853 100644 --- a/src/ripple/app/paths/impl/AmountSpec.h +++ b/src/ripple/app/paths/impl/AmountSpec.h @@ -37,14 +37,18 @@ struct AmountSpec { XRPAmount xrp; IOUAmount iou = {}; + MPTAmount mpt; }; std::optional issuer; std::optional currency; + std::optional mptid; friend std::ostream& operator<<(std::ostream& stream, AmountSpec const& amt) { - if (amt.native) + if (amt.mptid) + stream << to_string(*amt.mptid); + else if (amt.native) stream << to_string(amt.xrp); else stream << to_string(amt.iou); @@ -175,6 +179,11 @@ toAmountSpec(STAmount const& amt) { result.xrp = XRPAmount(sMant); } + else if (amt.isMPT()) + { + result.mptid = amt.mptIssue().mpt(); + result.mpt = amt.mpt(); + } else { result.iou = IOUAmount(sMant, amt.exponent()); diff --git a/src/ripple/app/paths/impl/Steps.h b/src/ripple/app/paths/impl/Steps.h index 1ae2273929d..a312064d1f3 100644 --- a/src/ripple/app/paths/impl/Steps.h +++ b/src/ripple/app/paths/impl/Steps.h @@ -443,8 +443,12 @@ toStrands( AMMContext& ammContext, beast::Journal j); +template +concept StepAsset = + !(std::is_same_v || std::is_same_v); + /// @cond INTERNAL -template +template struct StepImp : public Step { explicit StepImp() = default; diff --git a/src/ripple/app/paths/impl/StrandFlow.h b/src/ripple/app/paths/impl/StrandFlow.h index 7817251560f..21082169d47 100644 --- a/src/ripple/app/paths/impl/StrandFlow.h +++ b/src/ripple/app/paths/impl/StrandFlow.h @@ -550,7 +550,7 @@ class ActiveStrands @return Actual amount in and out from the strands, errors, and payment sandbox */ -template +template FlowResult flow( PaymentSandbox const& baseView, diff --git a/src/ripple/app/tx/impl/AMMCreate.cpp b/src/ripple/app/tx/impl/AMMCreate.cpp index 55b1126fcd0..cca600c38c0 100644 --- a/src/ripple/app/tx/impl/AMMCreate.cpp +++ b/src/ripple/app/tx/impl/AMMCreate.cpp @@ -50,6 +50,12 @@ AMMCreate::preflight(PreflightContext const& ctx) auto const amount = ctx.tx[sfAmount]; auto const amount2 = ctx.tx[sfAmount2]; + if (amount.isMPT() || amount2.isIssue()) + { + JLOG(ctx.j.debug()) << "AMM Instance: MPT is not supported."; + return temMPT_NOT_SUPPORTED; + } + if (amount.issue() == amount2.issue()) { JLOG(ctx.j.debug()) diff --git a/src/ripple/app/tx/impl/CashCheck.cpp b/src/ripple/app/tx/impl/CashCheck.cpp index bc3d838540b..923004332fb 100644 --- a/src/ripple/app/tx/impl/CashCheck.cpp +++ b/src/ripple/app/tx/impl/CashCheck.cpp @@ -53,6 +53,10 @@ CashCheck::preflight(PreflightContext const& ctx) auto const optAmount = ctx.tx[~sfAmount]; auto const optDeliverMin = ctx.tx[~sfDeliverMin]; + if ((optAmount && optAmount->isMPT()) || + (optDeliverMin && optDeliverMin->isMPT())) + return temMPT_NOT_SUPPORTED; + if (static_cast(optAmount) == static_cast(optDeliverMin)) { JLOG(ctx.j.warn()) diff --git a/src/ripple/app/tx/impl/Clawback.cpp b/src/ripple/app/tx/impl/Clawback.cpp index 58546db5ca7..2b4689bbda1 100644 --- a/src/ripple/app/tx/impl/Clawback.cpp +++ b/src/ripple/app/tx/impl/Clawback.cpp @@ -42,6 +42,9 @@ Clawback::preflight(PreflightContext const& ctx) AccountID const issuer = ctx.tx[sfAccount]; STAmount const clawAmount = ctx.tx[sfAmount]; + if (clawAmount.isMPT()) + return temMPT_NOT_SUPPORTED; + // The issuer field is used for the token holder instead AccountID const& holder = clawAmount.getIssuer(); diff --git a/src/ripple/app/tx/impl/CreateCheck.cpp b/src/ripple/app/tx/impl/CreateCheck.cpp index 77ce4d017a1..01b12bf0dad 100644 --- a/src/ripple/app/tx/impl/CreateCheck.cpp +++ b/src/ripple/app/tx/impl/CreateCheck.cpp @@ -53,6 +53,10 @@ CreateCheck::preflight(PreflightContext const& ctx) { STAmount const sendMax{ctx.tx.getFieldAmount(sfSendMax)}; + + if (sendMax.isMPT()) + return temMPT_NOT_SUPPORTED; + if (!isLegalNet(sendMax) || sendMax.signum() <= 0) { JLOG(ctx.j.warn()) << "Malformed transaction: bad sendMax amount: " diff --git a/src/ripple/app/tx/impl/CreateOffer.cpp b/src/ripple/app/tx/impl/CreateOffer.cpp index 17f7e2853db..44e1f9657ee 100644 --- a/src/ripple/app/tx/impl/CreateOffer.cpp +++ b/src/ripple/app/tx/impl/CreateOffer.cpp @@ -83,6 +83,9 @@ CreateOffer::preflight(PreflightContext const& ctx) STAmount saTakerPays = tx[sfTakerPays]; STAmount saTakerGets = tx[sfTakerGets]; + if (saTakerPays.isMPT() || saTakerGets.isMPT()) + return temMPT_NOT_SUPPORTED; + if (!isLegalNet(saTakerPays) || !isLegalNet(saTakerGets)) return temBAD_AMOUNT; diff --git a/src/ripple/app/tx/impl/Escrow.cpp b/src/ripple/app/tx/impl/Escrow.cpp index 9d8aa6a2289..d1d7f7b5bad 100644 --- a/src/ripple/app/tx/impl/Escrow.cpp +++ b/src/ripple/app/tx/impl/Escrow.cpp @@ -105,6 +105,9 @@ EscrowCreate::preflight(PreflightContext const& ctx) if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) return ret; + if (ctx.tx[sfAmount].isMPT()) + return temMPT_NOT_SUPPORTED; + if (!isXRP(ctx.tx[sfAmount])) return temBAD_AMOUNT; diff --git a/src/ripple/app/tx/impl/NFTokenAcceptOffer.cpp b/src/ripple/app/tx/impl/NFTokenAcceptOffer.cpp index 02471c1d482..80f3cdde6dc 100644 --- a/src/ripple/app/tx/impl/NFTokenAcceptOffer.cpp +++ b/src/ripple/app/tx/impl/NFTokenAcceptOffer.cpp @@ -96,6 +96,9 @@ NFTokenAcceptOffer::preclaim(PreclaimContext const& ctx) if (!isTesSuccess(err2)) return err2; + if ((bo && (*bo)[sfAmount].isMPT()) || (so && (*so)[sfAmount].isMPT())) + return temMPT_NOT_SUPPORTED; + if (bo && so) { // Brokered mode: diff --git a/src/ripple/app/tx/impl/NFTokenCreateOffer.cpp b/src/ripple/app/tx/impl/NFTokenCreateOffer.cpp index 22eca2dffdd..676623db751 100644 --- a/src/ripple/app/tx/impl/NFTokenCreateOffer.cpp +++ b/src/ripple/app/tx/impl/NFTokenCreateOffer.cpp @@ -48,6 +48,9 @@ NFTokenCreateOffer::preflight(PreflightContext const& ctx) { STAmount const amount = ctx.tx[sfAmount]; + if (amount.isMPT()) + return temMPT_NOT_SUPPORTED; + if (amount.negative() && ctx.rules.enabled(fixNFTokenNegOffer)) // An offer for a negative amount makes no sense. return temBAD_AMOUNT; diff --git a/src/ripple/app/tx/impl/PayChan.cpp b/src/ripple/app/tx/impl/PayChan.cpp index 3fe2a28a7cf..2c474501f2c 100644 --- a/src/ripple/app/tx/impl/PayChan.cpp +++ b/src/ripple/app/tx/impl/PayChan.cpp @@ -177,6 +177,9 @@ PayChanCreate::preflight(PreflightContext const& ctx) if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) return ret; + if (ctx.tx[sfAmount].isMPT()) + return temMPT_NOT_SUPPORTED; + if (!isXRP(ctx.tx[sfAmount]) || (ctx.tx[sfAmount] <= beast::zero)) return temBAD_AMOUNT; diff --git a/src/ripple/app/tx/impl/Payment.cpp b/src/ripple/app/tx/impl/Payment.cpp index 3903aa75045..51220f3dabd 100644 --- a/src/ripple/app/tx/impl/Payment.cpp +++ b/src/ripple/app/tx/impl/Payment.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -77,17 +78,26 @@ Payment::preflight(PreflightContext const& ctx) else if (saDstAmount.native()) maxSourceAmount = saDstAmount; else + { + auto const asset = [&]() -> Asset { + if (saDstAmount.isMPT()) + return saDstAmount.asset(); + return Issue{saDstAmount.getCurrency(), account}; + }(); maxSourceAmount = STAmount( - {saDstAmount.getCurrency(), account}, + asset, saDstAmount.mantissa(), saDstAmount.exponent(), saDstAmount < beast::zero); + } - auto const& uSrcCurrency = maxSourceAmount.getCurrency(); - auto const& uDstCurrency = saDstAmount.getCurrency(); + auto const& uSrcAsset = maxSourceAmount.asset(); + auto const& uDstAsset = saDstAmount.asset(); // isZero() is XRP. FIX! - bool const bXRPDirect = uSrcCurrency.isZero() && uDstCurrency.isZero(); + bool const bXRPDirect = isXRP(uSrcAsset) && isXRP(uDstAsset); + bool const bMPTDirect = isMPT(uSrcAsset) && isMPT(uDstAsset); + bool const bDirect = bXRPDirect || bMPTDirect; if (!isLegalNet(saDstAmount) || !isLegalNet(maxSourceAmount)) return temBAD_AMOUNT; @@ -112,55 +122,58 @@ Payment::preflight(PreflightContext const& ctx) << "bad dst amount: " << saDstAmount.getFullText(); return temBAD_AMOUNT; } - if (badCurrency() == uSrcCurrency || badCurrency() == uDstCurrency) + if ((uSrcAsset.isIssue() && badCurrency() == uSrcAsset.issue().currency) || + (uDstAsset.isIssue() && badCurrency() == uDstAsset.issue().currency)) { JLOG(j.trace()) << "Malformed transaction: " << "Bad currency."; return temBAD_CURRENCY; } - if (account == uDstAccountID && uSrcCurrency == uDstCurrency && !bPaths) + if (account == uDstAccountID && uSrcAsset == uDstAsset && !bPaths) { // You're signing yourself a payment. // If bPaths is true, you might be trying some arbitrage. JLOG(j.trace()) << "Malformed transaction: " << "Redundant payment from " << to_string(account) - << " to self without path for " - << to_string(uDstCurrency); + << " to self without path for " << to_string(uDstAsset); return temREDUNDANT; } - if (bXRPDirect && bMax) + if (bDirect && bMax) { // Consistent but redundant transaction. JLOG(j.trace()) << "Malformed transaction: " - << "SendMax specified for XRP to XRP."; - return temBAD_SEND_XRP_MAX; + << "SendMax specified for XRP to XRP or MPT to MPT."; + return temBAD_SEND_XRP_MAX; // TODO MPT new err code here and below } - if (bXRPDirect && bPaths) + if (bDirect && bPaths) { // XRP is sent without paths. JLOG(j.trace()) << "Malformed transaction: " - << "Paths specified for XRP to XRP."; + << "Paths specified for XRP to XRP or MPT to MPT."; return temBAD_SEND_XRP_PATHS; } - if (bXRPDirect && partialPaymentAllowed) + if (bDirect && partialPaymentAllowed) { // Consistent but redundant transaction. - JLOG(j.trace()) << "Malformed transaction: " - << "Partial payment specified for XRP to XRP."; + JLOG(j.trace()) + << "Malformed transaction: " + << "Partial payment specified for XRP to XRP or MPT to MPT."; return temBAD_SEND_XRP_PARTIAL; } - if (bXRPDirect && limitQuality) + if (bDirect && limitQuality) { // Consistent but redundant transaction. - JLOG(j.trace()) << "Malformed transaction: " - << "Limit quality specified for XRP to XRP."; + JLOG(j.trace()) + << "Malformed transaction: " + << "Limit quality specified for XRP to XRP or MPT to MPT."; return temBAD_SEND_XRP_LIMIT; } - if (bXRPDirect && !defaultPathsAllowed) + if (bDirect && !defaultPathsAllowed) { // Consistent but redundant transaction. - JLOG(j.trace()) << "Malformed transaction: " - << "No ripple direct specified for XRP to XRP."; + JLOG(j.trace()) + << "Malformed transaction: " + << "No ripple direct specified for XRP to XRP or MPT to MPT."; return temBAD_SEND_XRP_NO_DIRECT; } @@ -308,11 +321,18 @@ Payment::doApply() else if (saDstAmount.native()) maxSourceAmount = saDstAmount; else + { + auto const asset = [&]() -> Asset { + if (saDstAmount.isMPT()) + return saDstAmount.asset(); + return Issue{saDstAmount.getCurrency(), account_}; + }(); maxSourceAmount = STAmount( - {saDstAmount.getCurrency(), account_}, + asset, saDstAmount.mantissa(), saDstAmount.exponent(), saDstAmount < beast::zero); + } JLOG(j_.trace()) << "maxSourceAmount=" << maxSourceAmount.getFullText() << " saDstAmount=" << saDstAmount.getFullText(); @@ -348,7 +368,8 @@ Payment::doApply() bool const depositPreauth = view().rules().enabled(featureDepositPreauth); - bool const bRipple = paths || sendMax || !saDstAmount.native(); + bool const bRipple = + paths || sendMax || !(saDstAmount.native() || saDstAmount.isMPT()); // If the destination has lsfDepositAuth set, then only direct XRP // payments (no intermediate steps) are allowed to the destination. @@ -420,6 +441,35 @@ Payment::doApply() terResult = tecPATH_DRY; return terResult; } + else if (saDstAmount.isMPT()) + { + if (auto const ter = + requireAuth(view(), saDstAmount.mptIssue(), account_); + ter != tesSUCCESS) + return ter; + + if (auto const ter = + requireAuth(view(), saDstAmount.mptIssue(), uDstAccountID); + ter != tesSUCCESS) + return ter; + + auto const& mpt = saDstAmount.mptIssue(); + auto const& issuer = mpt.account(); + // If globally/individually locked then + // can't send between holders + // holder can send back to issuer + // issuer can send to holder + if (account_ != issuer && uDstAccountID != issuer && + (isFrozen(view(), account_, mpt) || + isFrozen(view(), uDstAccountID, mpt))) + return tecMPT_LOCKED; + + PaymentSandbox pv(&view()); + auto const res = + accountSendMPT(pv, account_, uDstAccountID, saDstAmount, j_); + pv.apply(ctx_.rawView()); + return res; + } assert(saDstAmount.native()); diff --git a/src/ripple/app/tx/impl/SetTrust.cpp b/src/ripple/app/tx/impl/SetTrust.cpp index 00a5165221e..7397f08c3c2 100644 --- a/src/ripple/app/tx/impl/SetTrust.cpp +++ b/src/ripple/app/tx/impl/SetTrust.cpp @@ -47,6 +47,9 @@ SetTrust::preflight(PreflightContext const& ctx) STAmount const saLimitAmount(tx.getFieldAmount(sfLimitAmount)); + if (saLimitAmount.isMPT()) + return temMPT_NOT_SUPPORTED; + if (!isLegalNet(saLimitAmount)) return temBAD_AMOUNT; @@ -537,7 +540,7 @@ SetTrust::doApply() else { // Zero balance in currency. - STAmount saBalance({currency, noAccount()}); + STAmount saBalance(Issue{currency, noAccount()}); auto const k = keylet::line(account_, uDstAccountID, currency); diff --git a/src/ripple/app/tx/impl/XChainBridge.cpp b/src/ripple/app/tx/impl/XChainBridge.cpp index 59450113d2b..900f7b750b4 100644 --- a/src/ripple/app/tx/impl/XChainBridge.cpp +++ b/src/ripple/app/tx/impl/XChainBridge.cpp @@ -1382,6 +1382,10 @@ XChainCreateBridge::preflight(PreflightContext const& ctx) auto const reward = ctx.tx[sfSignatureReward]; auto const minAccountCreate = ctx.tx[~sfMinAccountCreateAmount]; auto const bridgeSpec = ctx.tx[sfXChainBridge]; + + if (reward.isMPT() || (minAccountCreate && minAccountCreate->isMPT())) + return temMPT_NOT_SUPPORTED; + // Doors must be distinct to help prevent transaction replay attacks if (bridgeSpec.lockingChainDoor() == bridgeSpec.issuingChainDoor()) { diff --git a/src/ripple/basics/Number.h b/src/ripple/basics/Number.h index c308abec712..7ca471d5a54 100644 --- a/src/ripple/basics/Number.h +++ b/src/ripple/basics/Number.h @@ -20,6 +20,7 @@ #ifndef RIPPLE_BASICS_NUMBER_H_INCLUDED #define RIPPLE_BASICS_NUMBER_H_INCLUDED +#include #include #include #include @@ -52,6 +53,7 @@ class Number explicit constexpr Number(rep mantissa, int exponent, unchecked) noexcept; Number(XRPAmount const& x); + Number(MPTAmount const& x); constexpr rep mantissa() const noexcept; @@ -89,6 +91,7 @@ class Number lowest() noexcept; explicit operator XRPAmount() const; // round to nearest, even on tie + explicit operator MPTAmount() const; // round to nearest, even on tie explicit operator rep() const; // round to nearest, even on tie friend constexpr bool @@ -203,6 +206,10 @@ inline Number::Number(XRPAmount const& x) : Number{x.drops()} { } +inline Number::Number(MPTAmount const& x) : Number{x.mpt()} +{ +} + inline constexpr Number::rep Number::mantissa() const noexcept { diff --git a/src/ripple/basics/impl/Number.cpp b/src/ripple/basics/impl/Number.cpp index 9b3247536f9..c5ae28075b8 100644 --- a/src/ripple/basics/impl/Number.cpp +++ b/src/ripple/basics/impl/Number.cpp @@ -504,6 +504,11 @@ Number::operator XRPAmount() const return XRPAmount{static_cast(*this)}; } +Number::operator MPTAmount() const +{ + return MPTAmount{static_cast(*this)}; +} + std::string to_string(Number const& amount) { diff --git a/src/ripple/ledger/View.h b/src/ripple/ledger/View.h index 5680114a79c..9e6af0f36d2 100644 --- a/src/ripple/ledger/View.h +++ b/src/ripple/ledger/View.h @@ -81,6 +81,9 @@ enum FreezeHandling { fhIGNORE_FREEZE, fhZERO_IF_FROZEN }; [[nodiscard]] bool isGlobalFrozen(ReadView const& view, AccountID const& issuer); +[[nodiscard]] bool +isGlobalFrozen(ReadView const& view, MPTIssue const& mpt); + [[nodiscard]] bool isIndividualFrozen( ReadView const& view, @@ -97,6 +100,12 @@ isIndividualFrozen( return isIndividualFrozen(view, account, issue.currency, issue.account); } +[[nodiscard]] inline bool +isIndividualFrozen( + ReadView const& view, + AccountID const& account, + MPTIssue const& mpt); + [[nodiscard]] bool isFrozen( ReadView const& view, @@ -110,6 +119,9 @@ isFrozen(ReadView const& view, AccountID const& account, Issue const& issue) return isFrozen(view, account, issue.currency, issue.account); } +[[nodiscard]] bool +isFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mpt); + // Returns the amount an account can spend without going into debt. // // <-- saAmount: amount of currency held by account. May be negative. @@ -209,6 +221,9 @@ forEachItemAfter( [[nodiscard]] Rate transferRate(ReadView const& view, AccountID const& issuer); +[[nodiscard]] Rate +transferRateMPT(ReadView const& view, MPT const& id); + /** Returns `true` if the directory is empty @param key The key of the directory */ @@ -419,6 +434,14 @@ rippleCredit( bool bCheckIssuer, beast::Journal j); +[[nodiscard]] TER +rippleMPTCredit( + ApplyView& view, + AccountID const& uSenderID, + AccountID const& uReceiverID, + STAmount saAmount, + beast::Journal j); + [[nodiscard]] TER accountSend( ApplyView& view, @@ -428,6 +451,15 @@ accountSend( beast::Journal j, WaiveTransferFee waiveFee = WaiveTransferFee::No); +[[nodiscard]] TER +accountSendMPT( + ApplyView& view, + AccountID const& from, + AccountID const& to, + const STAmount& saAmount, + beast::Journal j, + WaiveTransferFee waiveFee = WaiveTransferFee::No); + [[nodiscard]] TER issueIOU( ApplyView& view, @@ -458,6 +490,11 @@ transferXRP( */ [[nodiscard]] TER requireAuth(ReadView const& view, Issue const& issue, AccountID const& account); +[[nodiscard]] TER +requireAuth( + ReadView const& view, + MPTIssue const& mpt, + AccountID const& account); /** Deleter function prototype. Returns the status of the entry deletion * (if should not be skipped) and if the entry should be skipped. The status diff --git a/src/ripple/ledger/impl/View.cpp b/src/ripple/ledger/impl/View.cpp index 5050e8764e9..de14d12f160 100644 --- a/src/ripple/ledger/impl/View.cpp +++ b/src/ripple/ledger/impl/View.cpp @@ -177,6 +177,14 @@ isGlobalFrozen(ReadView const& view, AccountID const& issuer) return false; } +bool +isGlobalFrozen(ReadView const& view, MPTIssue const& mpt) +{ + if (auto const sle = view.read(keylet::mptIssuance(mpt.mpt()))) + return sle->getFlags() & lsfMPTLocked; + return false; +} + bool isIndividualFrozen( ReadView const& view, @@ -197,6 +205,17 @@ isIndividualFrozen( return false; } +bool +isIndividualFrozen( + ReadView const& view, + AccountID const& account, + MPTIssue const& mpt) +{ + if (auto const sle = view.read(keylet::mptoken(mpt.mpt(), account))) + return sle->getFlags() & lsfMPTLocked; + return false; +} + // Can the specified account spend the specified currency issued by // the specified issuer or does the freeze flag prohibit it? bool @@ -222,6 +241,14 @@ isFrozen( return false; } +bool +isFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mpt) +{ + if (isGlobalFrozen(view, mpt)) + return true; + return isIndividualFrozen(view, account, mpt); +} + STAmount accountHolds( ReadView const& view, @@ -495,6 +522,18 @@ transferRate(ReadView const& view, AccountID const& issuer) return parityRate; } +Rate +transferRateMPT(ReadView const& view, MPT const& id) +{ + auto const sle = view.read(keylet::mptIssuance(id)); + + // fee is 0-50,000 (0-50%), rate is 1,000,000,000-2,000,000,000 + if (sle && sle->isFieldPresent(sfTransferFee)) + return Rate{1'000'000'000u + 10'000 * sle->getFieldU16(sfTransferFee)}; + + return parityRate; +} + bool areCompatible( ReadView const& validLedger, @@ -820,9 +859,8 @@ trustCreate( bSetHigh ? sfHighLimit : sfLowLimit, saLimit); sleRippleState->setFieldAmount( bSetHigh ? sfLowLimit : sfHighLimit, - STAmount( - {saBalance.getCurrency(), - bSetDst ? uSrcAccountID : uDstAccountID})); + STAmount(Issue{ + saBalance.getCurrency(), bSetDst ? uSrcAccountID : uDstAccountID})); if (uQualityIn) sleRippleState->setFieldU32( @@ -1055,7 +1093,7 @@ rippleCredit( return tesSUCCESS; } - STAmount const saReceiverLimit({currency, uReceiverID}); + STAmount const saReceiverLimit(Issue{currency, uReceiverID}); STAmount saBalance{saAmount}; saBalance.setIssuer(noAccount()); @@ -1147,7 +1185,7 @@ accountSend( beast::Journal j, WaiveTransferFee waiveFee) { - assert(saAmount >= beast::zero); + assert(saAmount >= beast::zero && !saAmount.isMPT()); /* If we aren't sending anything or if the sender is the same as the * receiver then we don't need to do anything. @@ -1246,6 +1284,82 @@ accountSend( return terResult; } +static TER +rippleSendMPT( + ApplyView& view, + AccountID const& uSenderID, + AccountID const& uReceiverID, + STAmount const& saAmount, + STAmount& saActual, + beast::Journal j, + WaiveTransferFee waiveFee) +{ + assert(uSenderID != uReceiverID); + + // Safe to get MPT since rippleSendMPT is only called by accountSendMPT + auto const issuer = saAmount.mptIssue().account(); + + if (uSenderID == issuer || uReceiverID == issuer || issuer == noAccount()) + { + // Direct send: redeeming IOUs and/or sending own IOUs. + auto const ter = + rippleMPTCredit(view, uSenderID, uReceiverID, saAmount, j); + if (view.rules().enabled(featureDeletableAccounts) && ter != tesSUCCESS) + return ter; + saActual = saAmount; + return tesSUCCESS; + } + + // Sending 3rd party MPTs: transit. + if (auto const sle = + view.read(keylet::mptIssuance(saAmount.mptIssue().mpt()))) + { + saActual = (waiveFee == WaiveTransferFee::Yes) + ? saAmount + : multiply( + saAmount, + transferRateMPT( + view, static_cast(saAmount.mptIssue().mpt()))); + + JLOG(j.debug()) << "rippleSend> " << to_string(uSenderID) << " - > " + << to_string(uReceiverID) + << " : deliver=" << saAmount.getFullText() + << " cost=" << saActual.getFullText(); + + if (auto const terResult = + rippleMPTCredit(view, issuer, uReceiverID, saAmount, j); + terResult != tesSUCCESS) + return terResult; + else + return rippleMPTCredit(view, uSenderID, issuer, saActual, j); + } + + return tecINTERNAL; +} + +TER +accountSendMPT( + ApplyView& view, + AccountID const& uSenderID, + AccountID const& uReceiverID, + STAmount const& saAmount, + beast::Journal j, + WaiveTransferFee waiveFee) +{ + assert(saAmount >= beast::zero && saAmount.isMPT()); + + /* If we aren't sending anything or if the sender is the same as the + * receiver then we don't need to do anything. + */ + if (!saAmount || (uSenderID == uReceiverID)) + return tesSUCCESS; + + STAmount saActual{saAmount.asset()}; + + return rippleSendMPT( + view, uSenderID, uReceiverID, saAmount, saActual, j, waiveFee); +} + static bool updateTrustLine( ApplyView& view, @@ -1367,7 +1481,7 @@ issueIOU( // NIKB TODO: The limit uses the receiver's account as the issuer and // this is unnecessarily inefficient as copying which could be avoided // is now required. Consider available options. - STAmount const limit({issue.currency, account}); + STAmount const limit(Issue{issue.currency, account}); STAmount final_balance = amount; final_balance.setIssuer(noAccount()); @@ -1527,6 +1641,22 @@ requireAuth(ReadView const& view, Issue const& issue, AccountID const& account) return tesSUCCESS; } +TER +requireAuth(ReadView const& view, MPTIssue const& mpt, AccountID const& account) +{ + auto const mptID = keylet::mptIssuance(mpt.mpt()); + if (auto const sle = view.read(mptID); + sle && sle->getFieldU32(sfFlags) & lsfMPTRequireAuth) + { + auto const mptokenID = keylet::mptoken(mptID.key, account); + if (auto const tokSle = view.read(mptokenID); tokSle && + //(sle->getFlags() & lsfMPTRequireAuth) && + !(tokSle->getFlags() & lsfMPTAuthorized)) + return TER{tecNO_AUTH}; + } + return tesSUCCESS; +} + TER cleanupOnAccountDelete( ApplyView& view, @@ -1652,4 +1782,82 @@ deleteAMMTrustLine( return tesSUCCESS; } +TER +rippleMPTCredit( + ApplyView& view, + AccountID const& uSenderID, + AccountID const& uReceiverID, + STAmount saAmount, + beast::Journal j) +{ + auto const mptID = keylet::mptIssuance(saAmount.mptIssue().mpt()); + auto const issuer = saAmount.mptIssue().account(); + if (uSenderID == issuer) + { + if (auto sle = view.peek(mptID)) + { + sle->setFieldU64( + sfOutstandingAmount, + sle->getFieldU64(sfOutstandingAmount) + saAmount.mpt().mpt()); + + if (sle->getFieldU64(sfOutstandingAmount) > + (*sle)[~sfMaximumAmount].value_or(maxMPTokenAmount)) + return tecMPT_MAX_AMOUNT_EXCEEDED; + + view.update(sle); + } + else + return tecINTERNAL; + } + else + { + auto const mptokenID = keylet::mptoken(mptID.key, uSenderID); + if (auto sle = view.peek(mptokenID)) + { + auto const amt = sle->getFieldU64(sfMPTAmount); + auto const pay = saAmount.mpt().mpt(); + if (amt >= pay) + { + if (amt == pay) + sle->makeFieldAbsent(sfMPTAmount); + else + sle->setFieldU64(sfMPTAmount, amt - pay); + view.update(sle); + } + else + return tecINSUFFICIENT_FUNDS; + } + } + + if (uReceiverID == issuer) + { + if (auto sle = view.peek(mptID)) + { + auto const outstanding = sle->getFieldU64(sfOutstandingAmount); + auto const redeem = saAmount.mpt().mpt(); + if (outstanding >= redeem) + { + sle->setFieldU64(sfOutstandingAmount, outstanding - redeem); + view.update(sle); + } + else + return tecINSUFFICIENT_FUNDS; + } + else + return tecINTERNAL; + } + else + { + auto const mptokenID = keylet::mptoken(mptID.key, uReceiverID); + if (auto sle = view.peek(mptokenID)) + { + sle->setFieldU64( + sfMPTAmount, + sle->getFieldU64(sfMPTAmount) + saAmount.mpt().mpt()); + view.update(sle); + } + } + return tesSUCCESS; +} + } // namespace ripple diff --git a/src/ripple/protocol/AmountConversions.h b/src/ripple/protocol/AmountConversions.h index 7403d7b9419..bb01aabc20a 100644 --- a/src/ripple/protocol/AmountConversions.h +++ b/src/ripple/protocol/AmountConversions.h @@ -155,12 +155,16 @@ template Issue getIssue(T const& amt) { + static_assert(!std::is_same_v); if constexpr (std::is_same_v) return noIssue(); if constexpr (std::is_same_v) return xrpIssue(); if constexpr (std::is_same_v) + { + assert(!amt.isMPT()); return amt.issue(); + } } template @@ -171,6 +175,8 @@ get(STAmount const& a) return a.iou(); if constexpr (std::is_same_v) return a.xrp(); + if constexpr (std::is_same_v) + return a.mpt(); if constexpr (std::is_same_v) return a; } diff --git a/src/ripple/protocol/Asset.h b/src/ripple/protocol/Asset.h new file mode 100644 index 00000000000..76b42fd4bce --- /dev/null +++ b/src/ripple/protocol/Asset.h @@ -0,0 +1,130 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2024 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef RIPPLE_PROTOCOL_ASSET_H_INCLUDED +#define RIPPLE_PROTOCOL_ASSET_H_INCLUDED + +#include +#include +#include + +namespace ripple { + +class Asset +{ +private: + std::variant asset_; + +public: + Asset(Issue const& issue) : asset_(issue) + { + } + Asset(MPTIssue const& mpt) : asset_(mpt) + { + } + Asset(MPT const& mpt) : asset_(MPTIssue{mpt}) + { + } + Asset(uint192 const& mptID); + Asset() = default; + + operator Issue() const + { + return issue(); + } + + operator MPTIssue() const + { + return mptIssue(); + } + + Issue const& + issue() const + { + if (!std::holds_alternative(asset_)) + Throw("Asset is not Issue"); + return std::get(asset_); + } + Issue& + issue() + { + if (!std::holds_alternative(asset_)) + Throw("Asset is not Issue"); + return std::get(asset_); + } + + MPTIssue const& + mptIssue() const + { + if (!std::holds_alternative(asset_)) + Throw("Asset is not MPT"); + return std::get(asset_); + } + MPTIssue& + mptIssue() + { + if (!std::holds_alternative(asset_)) + Throw("Asset is not MPT"); + return std::get(asset_); + } + + constexpr bool + isMPT() const + { + return std::holds_alternative(asset_); + } + + constexpr bool + isIssue() const + { + return std::holds_alternative(asset_); + } + + std::string + getText() const; + + friend constexpr bool + operator==(Asset const& lhs, Asset const& rhs) + { + if (lhs.isIssue() != rhs.isIssue()) + Throw("Assets are not comparable"); + if (lhs.isIssue()) + return lhs.issue() == rhs.issue(); + return lhs.mptIssue() == lhs.mptIssue(); + } + + friend constexpr bool + operator!=(Asset const& lhs, Asset const& rhs) + { + return !(lhs == rhs); + } +}; + +std::string +to_string(Asset const& asset); + +std::string +to_string(MPTIssue const& mpt); + +std::string +to_string(MPT const& mpt); + +} // namespace ripple + +#endif // RIPPLE_PROTOCOL_ASSET_H_INCLUDED diff --git a/src/ripple/protocol/ErrorCodes.h b/src/ripple/protocol/ErrorCodes.h index 8319b69c8c2..928f15d6bce 100644 --- a/src/ripple/protocol/ErrorCodes.h +++ b/src/ripple/protocol/ErrorCodes.h @@ -145,7 +145,10 @@ enum error_code_i { // AMM rpcISSUE_MALFORMED = 93, - rpcLAST = rpcISSUE_MALFORMED // rpcLAST should always equal the last code.= + rpcMPT_NOT_SUPPORTED = 94, + + rpcLAST = + rpcMPT_NOT_SUPPORTED // rpcLAST should always equal the last code.= }; /** Codes returned in the `warnings` array of certain RPC commands. diff --git a/src/ripple/protocol/Issue.h b/src/ripple/protocol/Issue.h index 1956b942e2b..2638cc7366e 100644 --- a/src/ripple/protocol/Issue.h +++ b/src/ripple/protocol/Issue.h @@ -116,6 +116,13 @@ noIssue() return issue; } +template +requires std::is_same_v bool +isXRP(T const& issue) +{ + return isXRP(issue.currency); +} + } // namespace ripple #endif diff --git a/src/ripple/protocol/MPTIssue.h b/src/ripple/protocol/MPTIssue.h new file mode 100644 index 00000000000..28e5d492e44 --- /dev/null +++ b/src/ripple/protocol/MPTIssue.h @@ -0,0 +1,74 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2024 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef RIPPLE_PROTOCOL_MPTISSUE_H_INCLUDED +#define RIPPLE_PROTOCOL_MPTISSUE_H_INCLUDED + +#include +#include + +namespace ripple { + +class MPTIssue +{ +private: + MPT mpt_; + +public: + MPTIssue(MPT const& mpt) : mpt_(mpt) + { + } + + AccountID const& + account() const + { + return mpt_.second; + } + + std::uint32_t + sequence() const + { + return mpt_.first; + } + + MPT const& + mpt() const + { + return mpt_; + } + + MPT& + mpt() + { + return mpt_; + } + + uint192 + getMptID() const; + + friend constexpr bool + operator==(MPTIssue const& lhs, MPTIssue const& rhs) + { + return lhs.mpt_ == rhs.mpt_; + } +}; + +} // namespace ripple + +#endif // RIPPLE_PROTOCOL_MPTISSUE_H_INCLUDED diff --git a/src/ripple/protocol/Rate.h b/src/ripple/protocol/Rate.h index 3524eabb627..3a54ec53af7 100644 --- a/src/ripple/protocol/Rate.h +++ b/src/ripple/protocol/Rate.h @@ -74,7 +74,7 @@ STAmount multiplyRound( STAmount const& amount, Rate const& rate, - Issue const& issue, + Asset const& asset, bool roundUp); STAmount @@ -87,7 +87,7 @@ STAmount divideRound( STAmount const& amount, Rate const& rate, - Issue const& issue, + Asset const& issue, bool roundUp); namespace nft { diff --git a/src/ripple/protocol/STAmount.h b/src/ripple/protocol/STAmount.h index 4b60b945291..641083f02a6 100644 --- a/src/ripple/protocol/STAmount.h +++ b/src/ripple/protocol/STAmount.h @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -34,6 +35,11 @@ namespace ripple { +template +concept AssetType = std::is_same_v || std::is_same_v || + std::is_same_v || std::is_convertible_v || + std::is_convertible_v; + // Internal form: // 1: If amount is zero, then value is zero and offset is -100 // 2: Otherwise: @@ -52,7 +58,7 @@ class STAmount final : public STBase, public CountedObject using rep = std::pair; private: - Issue mIssue; + Asset mAsset; mantissa_type mValue; exponent_type mOffset; bool mIsNative; // A shorthand for isXRP(mIssue). @@ -87,17 +93,19 @@ class STAmount final : public STBase, public CountedObject }; // Do not call canonicalize + template STAmount( SField const& name, - Issue const& issue, + A const& asset, mantissa_type mantissa, exponent_type exponent, bool native, bool negative, unchecked); + template STAmount( - Issue const& issue, + A const& asset, mantissa_type mantissa, exponent_type exponent, bool native, @@ -105,9 +113,10 @@ class STAmount final : public STBase, public CountedObject unchecked); // Call canonicalize + template STAmount( SField const& name, - Issue const& issue, + A const& asset, mantissa_type mantissa, exponent_type exponent, bool native, @@ -120,9 +129,10 @@ class STAmount final : public STBase, public CountedObject std::uint64_t mantissa = 0, bool negative = false); + template STAmount( SField const& name, - Issue const& issue, + A const& asset, std::uint64_t mantissa = 0, int exponent = 0, bool negative = false); @@ -131,27 +141,40 @@ class STAmount final : public STBase, public CountedObject explicit STAmount(SField const& name, STAmount const& amt); + template STAmount( - Issue const& issue, + A const& asset, std::uint64_t mantissa = 0, int exponent = 0, - bool negative = false); + bool negative = false) + : mAsset(asset) + , mValue(mantissa) + , mOffset(exponent) + , mIsNegative(negative) + { + canonicalize(); + } // VFALCO Is this needed when we have the previous signature? + template STAmount( - Issue const& issue, + A const& asset, std::uint32_t mantissa, int exponent = 0, bool negative = false); - STAmount(Issue const& issue, std::int64_t mantissa, int exponent = 0); + template + STAmount(A const& asset, std::int64_t mantissa, int exponent = 0); - STAmount(Issue const& issue, int mantissa, int exponent = 0); + template + STAmount(A const& asset, int mantissa, int exponent = 0); // Legacy support for new-style amounts - STAmount(IOUAmount const& amount, Issue const& issue); + template + STAmount(IOUAmount const& amount, A const& asset); STAmount(XRPAmount const& amount); - STAmount(MPTAmount const& amount, Issue const& issue); + template + STAmount(MPTAmount const& amount, A const& asset); operator Number() const; //-------------------------------------------------------------------------- @@ -169,6 +192,9 @@ class STAmount final : public STBase, public CountedObject bool isMPT() const noexcept; + bool + isIssue() const noexcept; + bool isIOU() const noexcept; @@ -181,9 +207,15 @@ class STAmount final : public STBase, public CountedObject std::uint64_t mantissa() const noexcept; + Asset const& + asset() const; + Issue const& issue() const; + MPTIssue const& + mptIssue() const; + // These three are deprecated Currency const& getCurrency() const; @@ -241,6 +273,9 @@ class STAmount final : public STBase, public CountedObject void clear(Issue const& issue); + void + clear(MPT const& mpt); + void setIssuer(AccountID const& uIssuer); @@ -281,6 +316,10 @@ class STAmount final : public STBase, public CountedObject MPTAmount mpt() const; + template + void + setAsset(A const& a, bool native); + private: static std::unique_ptr construct(SerialIter&, SField const& name); @@ -304,6 +343,140 @@ class STAmount final : public STBase, public CountedObject operator+(STAmount const& v1, STAmount const& v2); }; +template +void +STAmount::setAsset(const A& asset, bool native) +{ + if (native) + mAsset = xrpIssue(); + else + mAsset = asset; +} + +template +STAmount::STAmount( + SField const& name, + A const& asset, + mantissa_type mantissa, + exponent_type exponent, + bool native, + bool negative, + unchecked) + : STBase(name) + , mValue(mantissa) + , mOffset(exponent) + , mIsNative(native) + , mIsNegative(negative) +{ + setAsset(asset, native); +} + +template +STAmount::STAmount( + A const& asset, + mantissa_type mantissa, + exponent_type exponent, + bool native, + bool negative, + unchecked) + : mValue(mantissa) + , mOffset(exponent) + , mIsNative(native) + , mIsNegative(negative) +{ + setAsset(asset, native); +} + +template +STAmount::STAmount( + SField const& name, + A const& asset, + mantissa_type mantissa, + exponent_type exponent, + bool native, + bool negative) + : STBase(name) + , mValue(mantissa) + , mOffset(exponent) + , mIsNative(native) + , mIsNegative(negative) +{ + setAsset(asset, native); + canonicalize(); +} + +template +STAmount::STAmount( + SField const& name, + A const& asset, + std::uint64_t mantissa, + int exponent, + bool negative) + : STBase(name) + , mAsset(asset) + , mValue(mantissa) + , mOffset(exponent) + , mIsNegative(negative) +{ + assert(mValue <= std::numeric_limits::max()); + canonicalize(); +} + +template +STAmount::STAmount(A const& asset, std::int64_t mantissa, int exponent) + : mAsset(asset), mOffset(exponent) +{ + set(mantissa); + canonicalize(); +} + +template +STAmount::STAmount( + A const& asset, + std::uint32_t mantissa, + int exponent, + bool negative) + : STAmount(asset, safe_cast(mantissa), exponent, negative) +{ +} + +template +STAmount::STAmount(A const& asset, int mantissa, int exponent) + : STAmount(asset, safe_cast(mantissa), exponent) +{ +} + +// Legacy support for new-style amounts +template +STAmount::STAmount(IOUAmount const& amount, A const& asset) + : mAsset(asset) + , mOffset(amount.exponent()) + , mIsNative(false) + , mIsNegative(amount < beast::zero) +{ + if (mIsNegative) + mValue = static_cast(-amount.mantissa()); + else + mValue = static_cast(amount.mantissa()); + + canonicalize(); +} + +template +STAmount::STAmount(MPTAmount const& amount, A const& asset) + : mAsset(asset) + , mOffset(0) + , mIsNative(false) + , mIsNegative(amount < beast::zero) +{ + if (mIsNegative) + mValue = unsafe_cast(-amount.mpt()); + else + mValue = unsafe_cast(amount.mpt()); + + canonicalize(); +} + //------------------------------------------------------------------------------ // // Creation @@ -315,7 +488,7 @@ STAmount amountFromQuality(std::uint64_t rate); STAmount -amountFromString(Issue const& issue, std::string const& amount); +amountFromString(Asset const& issue, std::string const& amount); STAmount amountFromJson(SField const& name, Json::Value const& v); @@ -352,15 +525,19 @@ STAmount::native() const noexcept inline bool STAmount::isMPT() const noexcept { - // MPT TODO - return false; //! mIsNative && mIssue.isMPT(); + return mAsset.isMPT(); +} + +inline bool +STAmount::isIssue() const noexcept +{ + return mAsset.isIssue(); } inline bool STAmount::isIOU() const noexcept { - // MPT TODO - return !mIsNative; //&& !mIssue.isMPT(); + return mAsset.isIssue() && !mIsNative; } inline bool @@ -375,22 +552,34 @@ STAmount::mantissa() const noexcept return mValue; } +inline Asset const& +STAmount::asset() const +{ + return mAsset; +} + inline Issue const& STAmount::issue() const { - return mIssue; + return mAsset.issue(); +} + +inline MPTIssue const& +STAmount::mptIssue() const +{ + return mAsset.mptIssue(); } inline Currency const& STAmount::getCurrency() const { - return mIssue.currency; + return mAsset.issue().currency; } inline AccountID const& STAmount::getIssuer() const { - return mIssue.account; + return mAsset.issue().account; } inline int @@ -402,7 +591,9 @@ STAmount::signum() const noexcept inline STAmount STAmount::zeroed() const { - return STAmount(mIssue); + if (mAsset.isIssue()) + return STAmount(mAsset.issue()); + return STAmount(mAsset.mptIssue()); } inline STAmount::operator bool() const noexcept @@ -414,9 +605,8 @@ inline STAmount::operator Number() const { if (mIsNative) return xrp(); - // TODO MPT - // if (mIssue.isMPT()) - // return mpt(); + if (mAsset.isMPT()) + return mpt(); return iou(); } @@ -454,7 +644,10 @@ STAmount::clear() inline void STAmount::clear(STAmount const& saTmpl) { - clear(saTmpl.mIssue); + if (saTmpl.isMPT()) + clear(saTmpl.mAsset.mptIssue()); + else + clear(saTmpl.issue()); } inline void @@ -464,11 +657,18 @@ STAmount::clear(Issue const& issue) clear(); } +inline void +STAmount::clear(MPT const& mpt) +{ + mAsset = mpt; + clear(); +} + inline void STAmount::setIssuer(AccountID const& uIssuer) { - mIssue.account = uIssuer; - setIssue(mIssue); + mAsset.issue().account = uIssuer; + setIssue(mAsset.issue()); } inline STAmount const& @@ -533,17 +733,17 @@ STAmount operator-(STAmount const& v1, STAmount const& v2); STAmount -divide(STAmount const& v1, STAmount const& v2, Issue const& issue); +divide(STAmount const& v1, STAmount const& v2, Asset const& asset); STAmount -multiply(STAmount const& v1, STAmount const& v2, Issue const& issue); +multiply(STAmount const& v1, STAmount const& v2, Asset const& asset); // multiply rounding result in specified direction STAmount mulRound( STAmount const& v1, STAmount const& v2, - Issue const& issue, + Asset const& asset, bool roundUp); // multiply following the rounding directions more precisely. @@ -551,7 +751,7 @@ STAmount mulRoundStrict( STAmount const& v1, STAmount const& v2, - Issue const& issue, + Asset const& asset, bool roundUp); // divide rounding result in specified direction @@ -559,7 +759,7 @@ STAmount divRound( STAmount const& v1, STAmount const& v2, - Issue const& issue, + Asset const& asset, bool roundUp); // divide following the rounding directions more precisely. @@ -567,7 +767,7 @@ STAmount divRoundStrict( STAmount const& v1, STAmount const& v2, - Issue const& issue, + Asset const& asset, bool roundUp); // Someone is offering X for Y, what is the rate? @@ -581,14 +781,13 @@ getRate(STAmount const& offerOut, STAmount const& offerIn); inline bool isXRP(STAmount const& amount) { - return isXRP(amount.issue().currency); + return !amount.isMPT() && isXRP(amount.issue().currency); } inline bool isMPT(STAmount const& amount) { - // TODO MPT - return false; // isMPT(amount.issue()); + return amount.isMPT(); } // Since `canonicalize` does not have access to a ledger, this is needed to put diff --git a/src/ripple/protocol/TER.h b/src/ripple/protocol/TER.h index 8e8d8097fc7..a00bc086c57 100644 --- a/src/ripple/protocol/TER.h +++ b/src/ripple/protocol/TER.h @@ -136,6 +136,8 @@ enum TEMcodes : TERUnderlyingType { temXCHAIN_BRIDGE_BAD_REWARD_AMOUNT, temEMPTY_DID, + + temMPT_NOT_SUPPORTED }; //------------------------------------------------------------------------------ @@ -334,7 +336,8 @@ enum TECcodes : TERUnderlyingType { tecEMPTY_DID = 187, tecMPTOKEN_EXISTS = 188, tecMPT_MAX_AMOUNT_EXCEEDED = 189, - tecMPT_LOCKED = 190 + tecMPT_LOCKED = 190, + tecMPT_NOT_SUPPORTED = 191, }; diff --git a/src/ripple/protocol/impl/AMMCore.cpp b/src/ripple/protocol/impl/AMMCore.cpp index b774ee4ee3b..500696ee49e 100644 --- a/src/ripple/protocol/impl/AMMCore.cpp +++ b/src/ripple/protocol/impl/AMMCore.cpp @@ -96,6 +96,8 @@ invalidAMMAmount( std::optional> const& pair, bool validZero) { + if (amount.isMPT()) + return temMPT_NOT_SUPPORTED; if (auto const res = invalidAMMAsset(amount.issue(), pair)) return res; if (amount < beast::zero || (!validZero && amount == beast::zero)) diff --git a/src/ripple/protocol/impl/Asset.cpp b/src/ripple/protocol/impl/Asset.cpp new file mode 100644 index 00000000000..46906b14dc3 --- /dev/null +++ b/src/ripple/protocol/impl/Asset.cpp @@ -0,0 +1,63 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2024 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 { + +Asset::Asset(uint192 const& u) +{ + std::uint32_t sequence; + AccountID account; + memcpy(&sequence, u.data(), sizeof(sequence)); + sequence = boost::endian::big_to_native(sequence); + memcpy(account.data(), u.data() + sizeof(sequence), sizeof(AccountID)); + asset_ = std::make_pair(sequence, account); +} + +std::string +Asset::getText() const +{ + if (isIssue()) + return issue().getText(); + return to_string(mptIssue().getMptID()); +} + +std::string +to_string(Asset const& asset) +{ + if (asset.isIssue()) + return to_string(asset.issue()); + return to_string(asset.mptIssue().getMptID()); +} + +std::string +to_string(MPTIssue const& mptIssue) +{ + return to_string(mptIssue.getMptID()); +} + +std::string +to_string(MPT const& mpt) +{ + return to_string(getMptID(mpt.second, mpt.first)); +} + +} // namespace ripple \ No newline at end of file diff --git a/src/ripple/protocol/impl/ErrorCodes.cpp b/src/ripple/protocol/impl/ErrorCodes.cpp index 319bd8e28c2..5df85c893e3 100644 --- a/src/ripple/protocol/impl/ErrorCodes.cpp +++ b/src/ripple/protocol/impl/ErrorCodes.cpp @@ -84,6 +84,7 @@ constexpr static ErrorInfo unorderedErrorInfos[]{ {rpcLGR_NOT_FOUND, "lgrNotFound", "Ledger not found.", 404}, {rpcLGR_NOT_VALIDATED, "lgrNotValidated", "Ledger not validated.", 202}, {rpcMASTER_DISABLED, "masterDisabled", "Master key is disabled.", 403}, + {rpcMPT_NOT_SUPPORTED, "mptNotSupported", "MPT is not supported.", 403}, {rpcNOT_ENABLED, "notEnabled", "Not enabled in configuration.", 501}, {rpcNOT_IMPL, "notImpl", "Not implemented.", 501}, {rpcNOT_READY, "notReady", "Not ready to handle this request.", 503}, diff --git a/src/ripple/protocol/impl/MPTIssue.cpp b/src/ripple/protocol/impl/MPTIssue.cpp new file mode 100644 index 00000000000..ff19537e4ed --- /dev/null +++ b/src/ripple/protocol/impl/MPTIssue.cpp @@ -0,0 +1,31 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2024 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 { + +uint192 +MPTIssue::getMptID() const +{ + return ripple::getMptID(account(), sequence()); +} + +} // namespace ripple diff --git a/src/ripple/protocol/impl/Quality.cpp b/src/ripple/protocol/impl/Quality.cpp index f7b9d6b3c41..cacaba0b521 100644 --- a/src/ripple/protocol/impl/Quality.cpp +++ b/src/ripple/protocol/impl/Quality.cpp @@ -70,7 +70,7 @@ Quality::ceil_in(Amounts const& amount, STAmount const& limit) const if (amount.in > limit) { Amounts result( - limit, divRound(limit, rate(), amount.out.issue(), true)); + limit, divRound(limit, rate(), amount.out.asset(), true)); // Clamp out if (result.out > amount.out) result.out = amount.out; @@ -82,7 +82,7 @@ Quality::ceil_in(Amounts const& amount, STAmount const& limit) const } template + *MulRoundFunc)(STAmount const&, STAmount const&, Asset const&, bool)> static Amounts ceil_out_impl( Amounts const& amount, @@ -93,7 +93,7 @@ ceil_out_impl( if (amount.out > limit) { Amounts result( - MulRoundFunc(limit, quality.rate(), amount.in.issue(), roundUp), + MulRoundFunc(limit, quality.rate(), amount.in.asset(), roundUp), limit); // Clamp in if (result.in > amount.in) @@ -129,7 +129,7 @@ composed_quality(Quality const& lhs, Quality const& rhs) STAmount const rhs_rate(rhs.rate()); assert(rhs_rate != beast::zero); - STAmount const rate(mulRound(lhs_rate, rhs_rate, lhs_rate.issue(), true)); + STAmount const rate(mulRound(lhs_rate, rhs_rate, lhs_rate.asset(), true)); std::uint64_t const stored_exponent(rate.exponent() + 100); std::uint64_t const stored_mantissa(rate.mantissa()); diff --git a/src/ripple/protocol/impl/Rate2.cpp b/src/ripple/protocol/impl/Rate2.cpp index 340b6719bca..c39f992ae52 100644 --- a/src/ripple/protocol/impl/Rate2.cpp +++ b/src/ripple/protocol/impl/Rate2.cpp @@ -51,7 +51,7 @@ multiply(STAmount const& amount, Rate const& rate) if (rate == parityRate) return amount; - return multiply(amount, detail::as_amount(rate), amount.issue()); + return multiply(amount, detail::as_amount(rate), amount.asset()); } STAmount @@ -62,14 +62,14 @@ multiplyRound(STAmount const& amount, Rate const& rate, bool roundUp) if (rate == parityRate) return amount; - return mulRound(amount, detail::as_amount(rate), amount.issue(), roundUp); + return mulRound(amount, detail::as_amount(rate), amount.asset(), roundUp); } STAmount multiplyRound( STAmount const& amount, Rate const& rate, - Issue const& issue, + Asset const& asset, bool roundUp) { assert(rate.value != 0); @@ -79,7 +79,7 @@ multiplyRound( return amount; } - return mulRound(amount, detail::as_amount(rate), issue, roundUp); + return mulRound(amount, detail::as_amount(rate), asset, roundUp); } STAmount @@ -90,7 +90,7 @@ divide(STAmount const& amount, Rate const& rate) if (rate == parityRate) return amount; - return divide(amount, detail::as_amount(rate), amount.issue()); + return divide(amount, detail::as_amount(rate), amount.asset()); } STAmount @@ -101,14 +101,14 @@ divideRound(STAmount const& amount, Rate const& rate, bool roundUp) if (rate == parityRate) return amount; - return divRound(amount, detail::as_amount(rate), amount.issue(), roundUp); + return divRound(amount, detail::as_amount(rate), amount.asset(), roundUp); } STAmount divideRound( STAmount const& amount, Rate const& rate, - Issue const& issue, + Asset const& asset, bool roundUp) { assert(rate.value != 0); @@ -116,7 +116,7 @@ divideRound( if (rate == parityRate) return amount; - return divRound(amount, detail::as_amount(rate), issue, roundUp); + return divRound(amount, detail::as_amount(rate), asset, roundUp); } } // namespace ripple diff --git a/src/ripple/protocol/impl/STAmount.cpp b/src/ripple/protocol/impl/STAmount.cpp index 6d2337777e6..9de8fd2e7da 100644 --- a/src/ripple/protocol/impl/STAmount.cpp +++ b/src/ripple/protocol/impl/STAmount.cpp @@ -97,9 +97,10 @@ getMPTValue(STAmount const& amount) static bool areComparable(STAmount const& v1, STAmount const& v2) { - // TODO MPT - return (v1.native() == v2.native() || v1.isMPT() == v2.isMPT()) && - v1.issue().currency == v2.issue().currency; + return (v1.isMPT() && v2.isMPT() && + v1.asset().mptIssue() == v2.asset().mptIssue()) || + (v1.isIssue() && v1.native() == v2.native() && + v1.issue().currency == v2.issue().currency); } STAmount::STAmount(SerialIter& sit, SField const& name) : STBase(name) @@ -113,16 +114,13 @@ STAmount::STAmount(SerialIter& sit, SField const& name) : STBase(name) // native or MPT if ((value & cIssuedCurrency) == 0 || isMPT) { - // TODO MPT -#if 0 if (isMPT) { - mIssue = std::make_pair( + mAsset = std::make_pair( sit.get32(), static_cast(sit.get160())); } else -#endif - mIssue = xrpIssue(); + mAsset = xrpIssue(); // positive if ((value & cPositive) != 0) { @@ -171,7 +169,7 @@ STAmount::STAmount(SerialIter& sit, SField const& name) : STBase(name) Throw("invalid currency value"); } - mIssue = issue; + mAsset = issue; mValue = value; mOffset = offset; mIsNegative = isNegative; @@ -182,23 +180,24 @@ STAmount::STAmount(SerialIter& sit, SField const& name) : STBase(name) if (offset != 512) Throw("invalid currency value"); - mIssue = issue; + mAsset = issue; mValue = 0; mOffset = 0; mIsNegative = false; canonicalize(); } +#if 0 STAmount::STAmount( SField const& name, - Issue const& issue, + Asset const& asset, mantissa_type mantissa, exponent_type exponent, bool native, bool negative, unchecked) : STBase(name) - , mIssue(native ? xrpIssue() : issue) + , mAsset(native ? Asset{xrpIssue()} : asset) , mValue(mantissa) , mOffset(exponent) , mIsNative(native) @@ -207,13 +206,13 @@ STAmount::STAmount( } STAmount::STAmount( - Issue const& issue, + Asset const& asset, mantissa_type mantissa, exponent_type exponent, bool native, bool negative, unchecked) - : mIssue(native ? xrpIssue() : issue) + : mAsset(native ? Asset{xrpIssue()} : asset) , mValue(mantissa) , mOffset(exponent) , mIsNative(native) @@ -223,13 +222,13 @@ STAmount::STAmount( STAmount::STAmount( SField const& name, - Issue const& issue, + Asset const& asset, mantissa_type mantissa, exponent_type exponent, bool native, bool negative) : STBase(name) - , mIssue(native ? xrpIssue() : issue) + , mAsset(native ? Asset{xrpIssue()} : asset) , mValue(mantissa) , mOffset(exponent) , mIsNative(native) @@ -237,16 +236,17 @@ STAmount::STAmount( { canonicalize(); } +#endif STAmount::STAmount(SField const& name, std::int64_t mantissa) - : STBase(name), mIssue(xrpIssue()), mOffset(0), mIsNative(true) + : STBase(name), mAsset(xrpIssue()), mOffset(0), mIsNative(true) { set(mantissa); } STAmount::STAmount(SField const& name, std::uint64_t mantissa, bool negative) : STBase(name) - , mIssue(xrpIssue()) + , mAsset(xrpIssue()) , mValue(mantissa) , mOffset(0) , mIsNative(true) @@ -255,14 +255,15 @@ STAmount::STAmount(SField const& name, std::uint64_t mantissa, bool negative) assert(mValue <= std::numeric_limits::max()); } +#if 0 STAmount::STAmount( SField const& name, - Issue const& issue, + Asset const& asset, std::uint64_t mantissa, int exponent, bool negative) : STBase(name) - , mIssue(issue) + , mAsset(asset) , mValue(mantissa) , mOffset(exponent) , mIsNegative(negative) @@ -270,10 +271,11 @@ STAmount::STAmount( assert(mValue <= std::numeric_limits::max()); canonicalize(); } +#endif STAmount::STAmount(SField const& name, STAmount const& from) : STBase(name) - , mIssue(from.mIssue) + , mAsset(from.mAsset) , mValue(from.mValue) , mOffset(from.mOffset) , mIsNegative(from.mIsNegative) @@ -285,7 +287,7 @@ STAmount::STAmount(SField const& name, STAmount const& from) //------------------------------------------------------------------------------ STAmount::STAmount(std::uint64_t mantissa, bool negative) - : mIssue(xrpIssue()) + : mAsset(xrpIssue()) , mValue(mantissa) , mOffset(0) , mIsNative(true) @@ -294,40 +296,41 @@ STAmount::STAmount(std::uint64_t mantissa, bool negative) assert(mValue <= std::numeric_limits::max()); } +#if 0 STAmount::STAmount( - Issue const& issue, + Asset const& asset, std::uint64_t mantissa, int exponent, bool negative) - : mIssue(issue), mValue(mantissa), mOffset(exponent), mIsNegative(negative) + : mAsset(asset), mValue(mantissa), mOffset(exponent), mIsNegative(negative) { canonicalize(); } -STAmount::STAmount(Issue const& issue, std::int64_t mantissa, int exponent) - : mIssue(issue), mOffset(exponent) +STAmount::STAmount(Asset const& asset, std::int64_t mantissa, int exponent) + : mAsset(asset), mOffset(exponent) { set(mantissa); canonicalize(); } STAmount::STAmount( - Issue const& issue, + Asset const& asset, std::uint32_t mantissa, int exponent, bool negative) - : STAmount(issue, safe_cast(mantissa), exponent, negative) + : STAmount(asset, safe_cast(mantissa), exponent, negative) { } -STAmount::STAmount(Issue const& issue, int mantissa, int exponent) - : STAmount(issue, safe_cast(mantissa), exponent) +STAmount::STAmount(Asset const& asset, int mantissa, int exponent) + : STAmount(asset, safe_cast(mantissa), exponent) { } // Legacy support for new-style amounts -STAmount::STAmount(IOUAmount const& amount, Issue const& issue) - : mIssue(issue) +STAmount::STAmount(IOUAmount const& amount, Asset const& asset) + : mAsset(asset) , mOffset(amount.exponent()) , mIsNative(false) , mIsNegative(amount < beast::zero) @@ -339,9 +342,10 @@ STAmount::STAmount(IOUAmount const& amount, Issue const& issue) canonicalize(); } +#endif STAmount::STAmount(XRPAmount const& amount) - : mIssue(xrpIssue()) + : mAsset(xrpIssue()) , mOffset(0) , mIsNative(true) , mIsNegative(amount < beast::zero) @@ -354,8 +358,9 @@ STAmount::STAmount(XRPAmount const& amount) canonicalize(); } -STAmount::STAmount(MPTAmount const& amount, Issue const& issue) - : mIssue(issue) +#if 0 +STAmount::STAmount(MPTAmount const& amount, Asset const& asset) + : mAsset(asset) , mOffset(0) , mIsNative(false) , mIsNegative(amount < beast::zero) @@ -367,6 +372,7 @@ STAmount::STAmount(MPTAmount const& amount, Issue const& issue) canonicalize(); } +#endif std::unique_ptr STAmount::construct(SerialIter& sit, SField const& name) @@ -482,7 +488,7 @@ operator+(STAmount const& v1, STAmount const& v2) // Result must be in terms of v1 currency and issuer. return { v1.getFName(), - v1.issue(), + v1.asset(), v2.mantissa(), v2.exponent(), v2.negative()}; @@ -492,7 +498,7 @@ operator+(STAmount const& v1, STAmount const& v2) if (v1.native()) return {v1.getFName(), getSNValue(v1) + getSNValue(v2)}; if (v1.isMPT()) - return {v1.mIssue, v1.mpt().mpt() + v2.mpt().mpt()}; + return {v1.mAsset, v1.mpt().mpt() + v2.mpt().mpt()}; if (getSTNumberSwitchover()) { @@ -529,18 +535,18 @@ operator+(STAmount const& v1, STAmount const& v2) std::int64_t fv = vv1 + vv2; if ((fv >= -10) && (fv <= 10)) - return {v1.getFName(), v1.issue()}; + return {v1.getFName(), v1.asset()}; if (fv >= 0) return STAmount{ v1.getFName(), - v1.issue(), + v1.asset(), static_cast(fv), ov1, false}; return STAmount{ - v1.getFName(), v1.issue(), static_cast(-fv), ov1, true}; + v1.getFName(), v1.asset(), static_cast(-fv), ov1, true}; } STAmount @@ -556,7 +562,7 @@ std::uint64_t const STAmount::uRateOne = getRate(STAmount(1), STAmount(1)); void STAmount::setIssue(Issue const& issue) { - mIssue = issue; + mAsset = issue; mIsNative = isXRP(*this); } @@ -601,13 +607,12 @@ STAmount::setJson(Json::Value& elem) const // It is an error for currency or issuer not to be specified for valid // json. elem[jss::value] = getText(); - // TODO MPT - // if (mIssue.isMPT()) - // elem[jss::mpt_issuance_id] = to_string(mIssue.asset()); - // else + if (mAsset.isMPT()) + elem[jss::mpt_issuance_id] = to_string(mAsset.mptIssue()); + else { - elem[jss::currency] = to_string(mIssue.currency); - elem[jss::issuer] = to_string(mIssue.account); + elem[jss::currency] = to_string(mAsset.issue().currency); + elem[jss::issuer] = to_string(mAsset.issue().account); } } else @@ -634,7 +639,7 @@ STAmount::getFullText() const std::string ret; ret.reserve(64); - ret = getText() + "/" + mIssue.getText(); + ret = getText() + "/" + mAsset.getText(); return ret; } @@ -654,8 +659,7 @@ STAmount::getText() const bool const scientific( (mOffset != 0) && ((mOffset < -25) || (mOffset > -5))); - // TODO MPT - if (mIsNative || /*mIssue.isMPT() ||*/ scientific) + if (mIsNative || mAsset.isMPT() || scientific) { ret.append(raw_value); @@ -746,18 +750,16 @@ STAmount::add(Serializer& s) const } else { - // TODO MPT -#if 0 - if (mIssue.isMPT()) + if (mAsset.isMPT()) { if (mIsNegative) s.add64(mValue | cMPToken); else s.add64(mValue | cMPToken | cPositive); - s.add32(std::get(mIssue.asset().asset()).first); + s.add32(mAsset.mptIssue().sequence()); + s.addBitString(mAsset.mptIssue().account()); } else -#endif { if (*this == beast::zero) s.add64(cIssuedCurrency); @@ -771,9 +773,9 @@ STAmount::add(Serializer& s) const mValue | (static_cast(mOffset + 512 + 256 + 97) << (64 - 10))); - s.addBitString(mIssue.currency); + s.addBitString(mAsset.issue().currency); + s.addBitString(mAsset.issue().account); } - s.addBitString(mIssue.account); } } @@ -811,8 +813,7 @@ STAmount::isDefault() const void STAmount::canonicalize() { - // TODO MPT - if (isXRP(*this) /*|| mIssue.isMPT()*/) + if (isXRP(*this) || mAsset.isMPT()) { // native currency amounts should always have an offset of zero mIsNative = isXRP(*this); @@ -846,12 +847,9 @@ STAmount::canonicalize() } else { - // TODO MPT -#if 0 MPTAmount c{num}; mIsNegative = c.mpt() < 0; mValue = mIsNegative ? -c.mpt() : c.mpt(); -#endif } mOffset = 0; } @@ -961,7 +959,7 @@ amountFromQuality(std::uint64_t rate) } STAmount -amountFromString(Issue const& issue, std::string const& amount) +amountFromString(Asset const& asset, std::string const& amount) { static boost::regex const reNumber( "^" // the beginning of the string @@ -994,7 +992,7 @@ amountFromString(Issue const& issue, std::string const& amount) bool negative = (match[1].matched && (match[1] == "-")); // Can't specify XRP using fractional representation - if (isXRP(issue) && match[3].matched) + if (isXRP(asset) && match[3].matched) Throw("XRP must be specified in integral drops."); std::uint64_t mantissa; @@ -1022,7 +1020,7 @@ amountFromString(Issue const& issue, std::string const& amount) exponent += beast::lexicalCastThrow(std::string(match[7])); } - return {issue, mantissa, exponent, negative}; + return {asset, mantissa, exponent, negative}; } STAmount @@ -1031,10 +1029,10 @@ amountFromJson(SField const& name, Json::Value const& v) STAmount::mantissa_type mantissa = 0; STAmount::exponent_type exponent = 0; bool negative = false; - Issue issue; + Asset asset; Json::Value value; - Json::Value asset; + Json::Value currencyOrMPTID; Json::Value issuer; bool isMPT = false; @@ -1049,18 +1047,18 @@ amountFromJson(SField const& name, Json::Value const& v) if (v.isMember(jss::mpt_issuance_id)) { isMPT = true; - asset = v[jss::mpt_issuance_id]; + currencyOrMPTID = v[jss::mpt_issuance_id]; } else { - asset = v[jss::currency]; + currencyOrMPTID = v[jss::currency]; issuer = v[jss::issuer]; } } else if (v.isArray()) { value = v.get(Json::UInt(0), 0); - asset = v.get(Json::UInt(1), Json::nullValue); + currencyOrMPTID = v.get(Json::UInt(1), Json::nullValue); issuer = v.get(Json::UInt(2), Json::nullValue); } else if (v.isString()) @@ -1075,7 +1073,7 @@ amountFromJson(SField const& name, Json::Value const& v) value = elements[0]; if (elements.size() > 1) - asset = elements[1]; + currencyOrMPTID = elements[1]; if (elements.size() > 2) issuer = elements[2]; @@ -1085,38 +1083,38 @@ amountFromJson(SField const& name, Json::Value const& v) value = v; } - bool const native = !asset.isString() || asset.asString().empty() || - (asset.asString() == systemCurrencyCode()); + bool const native = !currencyOrMPTID.isString() || + currencyOrMPTID.asString().empty() || + (currencyOrMPTID.asString() == systemCurrencyCode()); if (native) { if (v.isObjectOrNull()) Throw("XRP may not be specified as an object"); - issue = xrpIssue(); + asset = xrpIssue(); } else { - // TODO MPT -#if 0 if (isMPT) { // sequence (32 bits) + account (160 bits) uint192 u; - if (!u.parseHex(asset.asString())) + if (!u.parseHex(currencyOrMPTID.asString())) Throw("invalid MPTokenIssuanceID"); - issue = u; + asset = u; } else -#endif { - if (!to_currency(issue.currency, asset.asString())) + Issue issue; + if (!to_currency(issue.currency, currencyOrMPTID.asString())) Throw("invalid currency"); if (!issuer.isString() || !to_issuer(issue.account, issuer.asString())) Throw("invalid issuer"); + if (isXRP(issue)) + Throw("invalid issuer"); + asset = issue; } - if (isXRP(issue)) - Throw("invalid issuer"); } if (value.isInt()) @@ -1137,7 +1135,7 @@ amountFromJson(SField const& name, Json::Value const& v) } else if (value.isString()) { - auto const ret = amountFromString(issue, value.asString()); + auto const ret = amountFromString(asset, value.asString()); mantissa = ret.mantissa(); exponent = ret.exponent(); @@ -1148,7 +1146,7 @@ amountFromJson(SField const& name, Json::Value const& v) Throw("invalid amount type"); } - return {name, issue, mantissa, exponent, native, negative}; + return {name, asset, mantissa, exponent, native, negative}; } bool @@ -1222,7 +1220,7 @@ operator-(STAmount const& value) return value; return STAmount( value.getFName(), - value.issue(), + value.asset(), value.mantissa(), value.exponent(), value.native(), @@ -1284,13 +1282,13 @@ muldiv_round( } STAmount -divide(STAmount const& num, STAmount const& den, Issue const& issue) +divide(STAmount const& num, STAmount const& den, Asset const& asset) { if (den == beast::zero) Throw("division by zero"); if (num == beast::zero) - return {issue}; + return {asset}; std::uint64_t numVal = num.mantissa(); std::uint64_t denVal = den.mantissa(); @@ -1322,19 +1320,19 @@ divide(STAmount const& num, STAmount const& den, Issue const& issue) // 10^32 to 10^33) followed by a division, so the result // is in the range of 10^16 to 10^15. return STAmount( - issue, + asset, muldiv(numVal, tenTo17, denVal) + 5, numOffset - denOffset - 17, num.negative() != den.negative()); } STAmount -multiply(STAmount const& v1, STAmount const& v2, Issue const& issue) +multiply(STAmount const& v1, STAmount const& v2, Asset const& asset) { if (v1 == beast::zero || v2 == beast::zero) - return STAmount(issue); + return STAmount(asset); - if (v1.native() && v2.native() && isXRP(issue)) + if (v1.native() && v2.native() && isXRP(asset)) { std::uint64_t const minV = getSNValue(v1) < getSNValue(v2) ? getSNValue(v1) : getSNValue(v2); @@ -1349,8 +1347,7 @@ multiply(STAmount const& v1, STAmount const& v2, Issue const& issue) return STAmount(v1.getFName(), minV * maxV); } - // TODO MPT - if (v1.isMPT() && v2.isMPT() /*&& issue.isMPT()*/) + if (v1.isMPT() && v2.isMPT() && asset.isMPT()) { std::uint64_t const minV = getMPTValue(v1) < getMPTValue(v2) ? getMPTValue(v1) @@ -1365,11 +1362,11 @@ multiply(STAmount const& v1, STAmount const& v2, Issue const& issue) if (((maxV >> 32) * minV) > 2095475792ull) // cMaxNative / 2^32 Throw("Asset value overflow"); - return STAmount(issue, minV * maxV); + return STAmount(asset, minV * maxV); } if (getSTNumberSwitchover()) - return {IOUAmount{Number{v1} * Number{v2}}, issue}; + return {IOUAmount{Number{v1} * Number{v2}}, asset}; std::uint64_t value1 = v1.mantissa(); std::uint64_t value2 = v2.mantissa(); @@ -1400,7 +1397,7 @@ multiply(STAmount const& v1, STAmount const& v2, Issue const& issue) // range. Dividing their product by 10^14 maintains the // precision, by scaling the result to 10^16 to 10^18. return STAmount( - issue, + asset, muldiv(value1, value2, tenTo14) + 7, offset1 + offset2 + 14, v1.negative() != v2.negative()); @@ -1557,13 +1554,13 @@ static STAmount mulRoundImpl( STAmount const& v1, STAmount const& v2, - Issue const& issue, + Asset const& asset, bool roundUp) { if (v1 == beast::zero || v2 == beast::zero) - return {issue}; + return {asset}; - bool const xrp = isXRP(issue); + bool const xrp = isXRP(asset); // TODO MPT if (v1.native() && v2.native() && xrp) @@ -1582,7 +1579,7 @@ mulRoundImpl( return STAmount(v1.getFName(), minV * maxV); } // TODO MPT - if (v1.isMPT() && v2.isMPT() /*&& issue.isMPT()*/) + if (v1.isMPT() && v2.isMPT() && asset.isMPT()) { std::uint64_t minV = (getMPTValue(v1) < getMPTValue(v2)) ? getMPTValue(v1) @@ -1597,7 +1594,7 @@ mulRoundImpl( if (((maxV >> 32) * minV) > 2095475792ull) // cMaxNative / 2^32 Throw("Asset value overflow"); - return STAmount(issue, minV * maxV); + return STAmount(asset, minV * maxV); } std::uint64_t value1 = v1.mantissa(), value2 = v2.mantissa(); @@ -1645,7 +1642,7 @@ mulRoundImpl( // If appropriate, tell Number to round down. This gives the desired // result from STAmount::canonicalize. MightSaveRound const savedRound(Number::towards_zero); - return STAmount(issue, amount, offset, resultNegative); + return STAmount(asset, amount, offset, resultNegative); }(); if (roundUp && !resultNegative && !result) @@ -1662,7 +1659,7 @@ mulRoundImpl( amount = STAmount::cMinValue; offset = STAmount::cMinOffset; } - return STAmount(issue, amount, offset, resultNegative); + return STAmount(asset, amount, offset, resultNegative); } return result; } @@ -1671,22 +1668,22 @@ STAmount mulRound( STAmount const& v1, STAmount const& v2, - Issue const& issue, + Asset const& asset, bool roundUp) { return mulRoundImpl( - v1, v2, issue, roundUp); + v1, v2, asset, roundUp); } STAmount mulRoundStrict( STAmount const& v1, STAmount const& v2, - Issue const& issue, + Asset const& asset, bool roundUp) { return mulRoundImpl( - v1, v2, issue, roundUp); + v1, v2, asset, roundUp); } // We might need to use NumberRoundModeGuard. Allow the caller @@ -1696,14 +1693,14 @@ static STAmount divRoundImpl( STAmount const& num, STAmount const& den, - Issue const& issue, + Asset const& asset, bool roundUp) { if (den == beast::zero) Throw("division by zero"); if (num == beast::zero) - return {issue}; + return {asset}; std::uint64_t numVal = num.mantissa(), denVal = den.mantissa(); int numOffset = num.exponent(), denOffset = den.exponent(); @@ -1746,7 +1743,7 @@ divRoundImpl( // TODO MPT if (resultNegative != roundUp) canonicalizeRound( - isXRP(issue) /*|| issue.isMPT()*/, amount, offset, roundUp); + isXRP(asset) /*|| issue.isMPT()*/, amount, offset, roundUp); STAmount result = [&]() { // If appropriate, tell Number the rounding mode we are using. @@ -1755,13 +1752,12 @@ divRoundImpl( using enum Number::rounding_mode; MightSaveRound const savedRound( roundUp ^ resultNegative ? upward : downward); - return STAmount(issue, amount, offset, resultNegative); + return STAmount(asset, amount, offset, resultNegative); }(); if (roundUp && !resultNegative && !result) { - // TODO MPT - if (isXRP(issue) /*|| issue.isMPT()*/) + if (isXRP(asset) || asset.isMPT()) { // return the smallest value above zero amount = 1; @@ -1773,7 +1769,7 @@ divRoundImpl( amount = STAmount::cMinValue; offset = STAmount::cMinOffset; } - return STAmount(issue, amount, offset, resultNegative); + return STAmount(asset, amount, offset, resultNegative); } return result; } @@ -1782,20 +1778,20 @@ STAmount divRound( STAmount const& num, STAmount const& den, - Issue const& issue, + Asset const& asset, bool roundUp) { - return divRoundImpl(num, den, issue, roundUp); + return divRoundImpl(num, den, asset, roundUp); } STAmount divRoundStrict( STAmount const& num, STAmount const& den, - Issue const& issue, + Asset const& asset, bool roundUp) { - return divRoundImpl(num, den, issue, roundUp); + return divRoundImpl(num, den, asset, roundUp); } } // namespace ripple diff --git a/src/ripple/protocol/impl/STTx.cpp b/src/ripple/protocol/impl/STTx.cpp index 2bc436db0ef..2f5f3458ffc 100644 --- a/src/ripple/protocol/impl/STTx.cpp +++ b/src/ripple/protocol/impl/STTx.cpp @@ -143,9 +143,14 @@ STTx::getMentionedAccounts() const } else if (auto samt = dynamic_cast(&it)) { - auto const& issuer = samt->getIssuer(); - if (!isXRP(issuer)) - list.insert(issuer); + if (samt->isIssue()) + { + auto const& issuer = samt->getIssuer(); + if (!isXRP(issuer)) + list.insert(issuer); + } + else + list.insert(samt->mptIssue().account()); } } diff --git a/src/ripple/protocol/impl/TER.cpp b/src/ripple/protocol/impl/TER.cpp index 9eaf86f5f76..43e12a8e346 100644 --- a/src/ripple/protocol/impl/TER.cpp +++ b/src/ripple/protocol/impl/TER.cpp @@ -85,6 +85,7 @@ transResults() MAKE_ERROR(tecHAS_OBLIGATIONS, "The account cannot be deleted since it has obligations."), MAKE_ERROR(tecTOO_SOON, "It is too early to attempt the requested operation. Please wait."), MAKE_ERROR(tecMAX_SEQUENCE_REACHED, "The maximum sequence number was reached."), + MAKE_ERROR(tecMPT_NOT_SUPPORTED, "MPT is not supported."), MAKE_ERROR(tecNO_SUITABLE_NFTOKEN_PAGE, "A suitable NFToken page could not be located."), MAKE_ERROR(tecNFTOKEN_BUY_SELL_MISMATCH, "The 'Buy' and 'Sell' NFToken offers are mismatched."), MAKE_ERROR(tecNFTOKEN_OFFER_TYPE_MISMATCH, "The type of NFToken offer is incorrect."), @@ -183,6 +184,7 @@ transResults() MAKE_ERROR(temEMPTY_DID, "Malformed: No DID data provided."), MAKE_ERROR(temINVALID, "The transaction is ill-formed."), MAKE_ERROR(temINVALID_FLAG, "The transaction has an invalid flag."), + MAKE_ERROR(temMPT_NOT_SUPPORTED, "MPT is not supported."), MAKE_ERROR(temREDUNDANT, "The transaction is redundant."), MAKE_ERROR(temRIPPLE_EMPTY, "PathSet with no paths."), MAKE_ERROR(temUNCERTAIN, "In process of determining result. Never returned."), diff --git a/src/ripple/rpc/impl/TransactionSign.cpp b/src/ripple/rpc/impl/TransactionSign.cpp index 0881881bd1a..fb585a6c62c 100644 --- a/src/ripple/rpc/impl/TransactionSign.cpp +++ b/src/ripple/rpc/impl/TransactionSign.cpp @@ -218,6 +218,9 @@ checkPayment( { if (!amountFromJsonNoThrow(sendMax, tx_json[jss::SendMax])) return RPC::invalid_field_error("tx_json.SendMax"); + if (sendMax.isMPT()) + return RPC::make_error( + rpcINVALID_PARAMS, "MPT is invalid in SendMax"); } else { diff --git a/src/test/app/MPToken_test.cpp b/src/test/app/MPToken_test.cpp index bcd62d94fb0..574b04e8639 100644 --- a/src/test/app/MPToken_test.cpp +++ b/src/test/app/MPToken_test.cpp @@ -159,12 +159,9 @@ class MPToken_test : public beast::unit_test::suite mptAlice.authorize({.account = &bob, .holderCount = 1}); // alice pays bob 100 tokens - // TODO MPT -#if 0 mptAlice.pay(alice, bob, 100); mptAlice.destroy({.err = tecHAS_OBLIGATIONS}); -#endif } } } @@ -270,8 +267,6 @@ class MPToken_test : public beast::unit_test::suite // Check that bob cannot delete MPToken when his balance is // non-zero { - // TODO MPT -#if 0 // alice pays bob 100 tokens mptAlice.pay(alice, bob, 100); @@ -284,7 +279,6 @@ class MPToken_test : public beast::unit_test::suite // bob pays back alice 100 tokens mptAlice.pay(bob, alice, 100); -#endif } // bob deletes/unauthorizes his MPToken @@ -887,14 +881,11 @@ class MPToken_test : public beast::unit_test::suite testSetValidation(all); testSetEnabled(all); - // TODO MPT -#if 0 // Test Direct Payment testPayment(all); // Test MPT Amount is invalid in non-Payment Tx testMPTInvalidInTx(all); -#endif // Test parsed MPTokenIssuanceID in API response metadata // TODO: This test exercises the parsing logic of mptID in `tx`, diff --git a/src/test/app/SetAuth_test.cpp b/src/test/app/SetAuth_test.cpp index 8066ef0dacf..6ce549e65bf 100644 --- a/src/test/app/SetAuth_test.cpp +++ b/src/test/app/SetAuth_test.cpp @@ -38,8 +38,8 @@ struct SetAuth_test : public beast::unit_test::suite using namespace jtx; Json::Value jv; jv[jss::Account] = account.human(); - jv[jss::LimitAmount] = - STAmount({to_currency(currency), dest}).getJson(JsonOptions::none); + jv[jss::LimitAmount] = STAmount(Issue{to_currency(currency), dest}) + .getJson(JsonOptions::none); jv[jss::TransactionType] = jss::TrustSet; jv[jss::Flags] = tfSetfAuth; return jv; diff --git a/src/test/jtx/amount.h b/src/test/jtx/amount.h index 9ab6db6c7a3..f04839dfa7b 100644 --- a/src/test/jtx/amount.h +++ b/src/test/jtx/amount.h @@ -314,6 +314,10 @@ class IOU { return issue(); } + operator Asset() const + { + return issue(); + } template < class T, @@ -369,11 +373,10 @@ class MPT { } - Issue - issue() const + ripple::MPT const& + mpt() const { - // TODO MPT - return noIssue(); //{mptID}; + return mptID; } /** Implicit conversion to Issue. @@ -381,9 +384,9 @@ class MPT This allows passing an MPT value where an Issue is expected. */ - operator Issue() const + operator ripple::MPT() const { - return issue(); + return mpt(); } template @@ -392,7 +395,7 @@ class MPT { // VFALCO NOTE Should throw if the // representation of v is not exact. - return {amountFromString(issue(), std::to_string(v)), name}; + return {amountFromString(mpt(), std::to_string(v)), name}; } PrettyAmount operator()(epsilon_t) const; @@ -402,10 +405,12 @@ class MPT // STAmount operator()(char const* s) const; /** Returns None-of-Issue */ +#if 0 None operator()(none_t) const { - return {issue()}; + return {Issue{}}; } +#endif friend BookSpec operator~(MPT const& mpt) diff --git a/src/test/jtx/impl/amount.cpp b/src/test/jtx/impl/amount.cpp index 86f51d135d7..a523b5ae47a 100644 --- a/src/test/jtx/impl/amount.cpp +++ b/src/test/jtx/impl/amount.cpp @@ -91,12 +91,17 @@ operator<<(std::ostream& os, PrettyAmount const& amount) os << to_places(d, 6) << " XRP"; } - else + else if (amount.value().isIssue()) { os << amount.value().getText() << "/" << to_string(amount.value().issue().currency) << "(" << amount.name() << ")"; } + else + { + os << amount.value().getText() << "/" << to_string(amount.value().mpt()) + << "(" << amount.name() << ")"; + } return os; } diff --git a/src/test/jtx/impl/mpt.cpp b/src/test/jtx/impl/mpt.cpp index 62a7abfbf48..49ccf9bd1b7 100644 --- a/src/test/jtx/impl/mpt.cpp +++ b/src/test/jtx/impl/mpt.cpp @@ -197,7 +197,7 @@ MPTTester::authorize(MPTAuthorize const& arg) } else if ( arg.account != nullptr && *arg.account != issuer_ && - arg.flags.value_or(0) == 0) + arg.flags.value_or(0) == 0 && issuanceKey_) { if (result == tecMPTOKEN_EXISTS) { @@ -330,9 +330,7 @@ MPTTester::pay( } else { - // TODO MPT -#if 0 - STAmount const saAmount = {Issue{*mpt_}, amount}; + STAmount const saAmount = {*mpt_, amount}; STAmount const saActual = multiply(saAmount, transferRateMPT(*env_.current(), *mpt_)); // Sender pays the transfer fee if any @@ -341,7 +339,6 @@ MPTTester::pay( // Outstanding amount is reduced by the transfer fee if any env_.require(mptpay( *this, issuer_, outstnAmt - (saActual - saAmount).mpt().mpt())); -#endif } } @@ -355,6 +352,7 @@ MPTTester::mpt(std::uint64_t amount) const std::uint64_t MPTTester::getAmount(Account const& account) const { + assert(issuanceKey_); if (account == issuer_) { if (auto const sle = env_.le(keylet::mptIssuance(*issuanceKey_))) diff --git a/src/test/protocol/Quality_test.cpp b/src/test/protocol/Quality_test.cpp index a36591b0247..7d51e334f0d 100644 --- a/src/test/protocol/Quality_test.cpp +++ b/src/test/protocol/Quality_test.cpp @@ -29,7 +29,7 @@ class Quality_test : public beast::unit_test::suite // Create a raw, non-integral amount from mantissa and exponent STAmount static raw(std::uint64_t mantissa, int exponent) { - return STAmount({Currency(3), AccountID(3)}, mantissa, exponent); + return STAmount(Issue{Currency(3), AccountID(3)}, mantissa, exponent); } template