diff --git a/.github/scripts/strategy-matrix/generate.py b/.github/scripts/strategy-matrix/generate.py index c762d61ccd..230c18c2df 100755 --- a/.github/scripts/strategy-matrix/generate.py +++ b/.github/scripts/strategy-matrix/generate.py @@ -130,8 +130,8 @@ def generate_strategy_matrix(all: bool, config: Config) -> list: if os['distro_name'] == 'rhel' and architecture['platform'] == 'linux/arm64': continue - # We skip all clang-20 on arm64 due to boost 1.86 build error - if f'{os['compiler_name']}-{os['compiler_version']}' == 'clang-20' and architecture['platform'] == 'linux/arm64': + # We skip all clang 20+ on arm64 due to Boost build error. + if f'{os['compiler_name']}-{os['compiler_version']}' in ['clang-20', 'clang-21'] and architecture['platform'] == 'linux/arm64': continue # Enable code coverage for Debian Bookworm using GCC 15 in Debug and no diff --git a/.github/scripts/strategy-matrix/linux.json b/.github/scripts/strategy-matrix/linux.json index a8fe90e6ac..748ee031c9 100644 --- a/.github/scripts/strategy-matrix/linux.json +++ b/.github/scripts/strategy-matrix/linux.json @@ -15,63 +15,91 @@ "distro_version": "bookworm", "compiler_name": "gcc", "compiler_version": "12", - "image_sha": "e1782cd" + "image_sha": "0525eae" }, { "distro_name": "debian", "distro_version": "bookworm", "compiler_name": "gcc", "compiler_version": "13", - "image_sha": "e1782cd" + "image_sha": "0525eae" }, { "distro_name": "debian", "distro_version": "bookworm", "compiler_name": "gcc", "compiler_version": "14", - "image_sha": "e1782cd" + "image_sha": "0525eae" }, { "distro_name": "debian", "distro_version": "bookworm", "compiler_name": "gcc", "compiler_version": "15", - "image_sha": "e1782cd" + "image_sha": "0525eae" }, { "distro_name": "debian", "distro_version": "bookworm", "compiler_name": "clang", "compiler_version": "16", - "image_sha": "e1782cd" + "image_sha": "0525eae" }, { "distro_name": "debian", "distro_version": "bookworm", "compiler_name": "clang", "compiler_version": "17", - "image_sha": "e1782cd" + "image_sha": "0525eae" }, { "distro_name": "debian", "distro_version": "bookworm", "compiler_name": "clang", "compiler_version": "18", - "image_sha": "e1782cd" + "image_sha": "0525eae" }, { "distro_name": "debian", "distro_version": "bookworm", "compiler_name": "clang", "compiler_version": "19", - "image_sha": "e1782cd" + "image_sha": "0525eae" }, { "distro_name": "debian", "distro_version": "bookworm", "compiler_name": "clang", "compiler_version": "20", - "image_sha": "e1782cd" + "image_sha": "0525eae" + }, + { + "distro_name": "debian", + "distro_version": "trixie", + "compiler_name": "gcc", + "compiler_version": "14", + "image_sha": "0525eae" + }, + { + "distro_name": "debian", + "distro_version": "trixie", + "compiler_name": "gcc", + "compiler_version": "15", + "image_sha": "0525eae" + }, + { + "distro_name": "debian", + "distro_version": "trixie", + "compiler_name": "clang", + "compiler_version": "20", + "image_sha": "0525eae" + }, + { + "distro_name": "debian", + "distro_version": "trixie", + "compiler_name": "clang", + "compiler_version": "21", + "image_sha": "0525eae" }, { "distro_name": "rhel", diff --git a/external/secp256k1/include/secp256k1.h b/external/secp256k1/include/secp256k1.h index c6e9417f05..e562cd00e8 100644 --- a/external/secp256k1/include/secp256k1.h +++ b/external/secp256k1/include/secp256k1.h @@ -541,7 +541,7 @@ SECP256K1_API int secp256k1_ecdsa_signature_serialize_compact( /** Verify an ECDSA signature. * * Returns: 1: correct signature - * 0: incorrect or unparseable signature + * 0: incorrect or unparsable signature * Args: ctx: pointer to a context object * In: sig: the signature being verified. * msghash32: the 32-byte message hash being verified. diff --git a/include/xrpl/basics/SlabAllocator.h b/include/xrpl/basics/SlabAllocator.h index b85b2149ca..d27616ecdd 100644 --- a/include/xrpl/basics/SlabAllocator.h +++ b/include/xrpl/basics/SlabAllocator.h @@ -159,7 +159,7 @@ public: @param count the number of items the slab allocator can allocate; note that a count of 0 is valid and means that the allocator is, effectively, disabled. This can be very useful in some - contexts (e.g. when mimimal memory usage is needed) and + contexts (e.g. when minimal memory usage is needed) and allows for graceful failure. */ constexpr explicit SlabAllocator( diff --git a/include/xrpl/basics/base_uint.h b/include/xrpl/basics/base_uint.h index 48c8030f57..4be4b35af3 100644 --- a/include/xrpl/basics/base_uint.h +++ b/include/xrpl/basics/base_uint.h @@ -546,7 +546,7 @@ operator<=>(base_uint const& lhs, base_uint const& rhs) // This comparison might seem wrong on a casual inspection because it // compares data internally stored as std::uint32_t byte-by-byte. But // note that the underlying data is stored in big endian, even if the - // plaform is little endian. This makes the comparison correct. + // platform is little endian. This makes the comparison correct. // // FIXME: use std::lexicographical_compare_three_way once support is // added to MacOS. diff --git a/include/xrpl/basics/comparators.h b/include/xrpl/basics/comparators.h index 6b1d693e65..4858a0ad74 100644 --- a/include/xrpl/basics/comparators.h +++ b/include/xrpl/basics/comparators.h @@ -9,7 +9,7 @@ namespace ripple { /* * MSVC 2019 version 16.9.0 added [[nodiscard]] to the std comparison - * operator() functions. boost::bimap checks that the comparitor is a + * operator() functions. boost::bimap checks that the comparator is a * BinaryFunction, in part by calling the function and ignoring the value. * These two things don't play well together. These wrapper classes simply * strip [[nodiscard]] from operator() for use in boost::bimap. diff --git a/include/xrpl/beast/unit_test/runner.h b/include/xrpl/beast/unit_test/runner.h index f0742c7b01..f91cc92c91 100644 --- a/include/xrpl/beast/unit_test/runner.h +++ b/include/xrpl/beast/unit_test/runner.h @@ -39,7 +39,7 @@ public: The argument string is available to suites and allows for customization of the test. Each suite - defines its own syntax for the argumnet string. + defines its own syntax for the argument string. The same argument is passed to all suites. */ void diff --git a/include/xrpl/ledger/View.h b/include/xrpl/ledger/View.h index ece574e52d..5e6fb8f5ce 100644 --- a/include/xrpl/ledger/View.h +++ b/include/xrpl/ledger/View.h @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -1082,6 +1083,26 @@ canTransfer( AccountID const& from, AccountID const& to); +[[nodiscard]] TER +canTransfer( + ReadView const& view, + Issue const& issue, + AccountID const& from, + AccountID const& to); + +[[nodiscard]] TER inline canTransfer( + ReadView const& view, + Asset const& asset, + AccountID const& from, + AccountID const& to) +{ + return std::visit( + [&](TIss const& issue) -> TER { + return canTransfer(view, issue, from, to); + }, + asset.value()); +} + /** Deleter function prototype. Returns the status of the entry deletion * (if should not be skipped) and if the entry should be skipped. The status * is always tesSUCCESS if the entry should be skipped. diff --git a/include/xrpl/protocol/detail/features.macro b/include/xrpl/protocol/detail/features.macro index c6bbcf47c4..115f013817 100644 --- a/include/xrpl/protocol/detail/features.macro +++ b/include/xrpl/protocol/detail/features.macro @@ -69,7 +69,6 @@ XRPL_FEATURE(XRPFees, Supported::yes, VoteBehavior::DefaultNo XRPL_FEATURE(DisallowIncoming, Supported::yes, VoteBehavior::DefaultNo) XRPL_FIX (RemoveNFTokenAutoTrustLine, Supported::yes, VoteBehavior::DefaultYes) XRPL_FEATURE(FlowSortStrands, Supported::yes, VoteBehavior::DefaultYes) -XRPL_FEATURE(NegativeUNL, Supported::yes, VoteBehavior::DefaultYes) XRPL_FEATURE(RequireFullyCanonicalSig, Supported::yes, VoteBehavior::DefaultYes) XRPL_FEATURE(DeletableAccounts, Supported::yes, VoteBehavior::DefaultYes) XRPL_FEATURE(Checks, Supported::yes, VoteBehavior::DefaultYes) @@ -133,6 +132,7 @@ XRPL_RETIRE_FEATURE(HardenedValidations) XRPL_RETIRE_FEATURE(ImmediateOfferKilled) XRPL_RETIRE_FEATURE(MultiSign) XRPL_RETIRE_FEATURE(MultiSignReserve) +XRPL_RETIRE_FEATURE(NegativeUNL) XRPL_RETIRE_FEATURE(NonFungibleTokensV1_1) XRPL_RETIRE_FEATURE(PayChan) XRPL_RETIRE_FEATURE(SortedDirectories) diff --git a/src/libxrpl/ledger/View.cpp b/src/libxrpl/ledger/View.cpp index 9b6a8723c5..28f6bb89be 100644 --- a/src/libxrpl/ledger/View.cpp +++ b/src/libxrpl/ledger/View.cpp @@ -3277,6 +3277,42 @@ canTransfer( return tesSUCCESS; } +[[nodiscard]] TER +canTransfer( + ReadView const& view, + Issue const& issue, + AccountID const& from, + AccountID const& to) +{ + if (issue.native()) + return tesSUCCESS; + + auto const issuerId = issue.getIssuer(); + if (issuerId == from || issuerId == to) + return tesSUCCESS; + auto const sleIssuer = view.read(keylet::account(issuerId)); + if (sleIssuer == nullptr) + return tefINTERNAL; // LCOV_EXCL_LINE + + auto const isRippleDisabled = [&](AccountID account) -> bool { + // Line might not exist, but some transfers can create it. If this + // is the case, just check the default ripple on the issuer account. + auto const line = view.read(keylet::line(account, issue)); + if (line) + { + bool const issuerHigh = issuerId > account; + return line->isFlag(issuerHigh ? lsfHighNoRipple : lsfLowNoRipple); + } + return sleIssuer->isFlag(lsfDefaultRipple) == false; + }; + + // Fail if rippling disabled on both trust lines + if (isRippleDisabled(from) && isRippleDisabled(to)) + return terNO_RIPPLE; + + return tesSUCCESS; +} + TER cleanupOnAccountDelete( ApplyView& view, diff --git a/src/test/app/Loan_test.cpp b/src/test/app/Loan_test.cpp index 6e13ee5731..1db46cf91d 100644 --- a/src/test/app/Loan_test.cpp +++ b/src/test/app/Loan_test.cpp @@ -376,7 +376,7 @@ protected: loan->at(sfPeriodicPayment) == periodicPayment); env.test.BEAST_EXPECT(loan->at(sfFlags) == flags); - auto const ls = calculateRoundedLoanState(loan); + auto const ls = constructRoundedLoanState(loan); auto const interestRate = TenthBips32{loan->at(sfInterestRate)}; auto const paymentInterval = loan->at(sfPaymentInterval); @@ -546,7 +546,7 @@ protected: state.loanScale)); BEAST_EXPECT( state.managementFeeOutstanding == - computeFee( + computeManagementFee( broker.asset, state.totalValue - state.principalOutstanding, broker.params.managementFeeRate, @@ -897,7 +897,7 @@ protected: periodicRate, state.paymentRemaining, broker.params.managementFeeRate); - auto const rounded = calculateRoundedLoanState( + auto const rounded = constructRoundedLoanState( state.totalValue, state.principalOutstanding, state.managementFeeOutstanding); @@ -1004,23 +1004,21 @@ protected: broker.params.managementFeeRate); detail::LoanDeltas const deltas = currentTrueState - nextTrueState; BEAST_EXPECT( - deltas.valueDelta() == - deltas.principalDelta + deltas.interestDueDelta + - deltas.managementFeeDueDelta); + deltas.total() == + deltas.principal + deltas.interest + deltas.managementFee); BEAST_EXPECT( paymentComponents.specialCase == detail::PaymentSpecialCase::final || - deltas.valueDelta() == state.periodicPayment || + deltas.total() == state.periodicPayment || (state.loanScale - - (deltas.valueDelta() - state.periodicPayment).exponent()) > - 14); + (deltas.total() - state.periodicPayment).exponent()) > 14); if (!showStepBalances) log << currencyLabel << " Payment components: " << state.paymentRemaining << ", " - << deltas.interestDueDelta << ", " << deltas.principalDelta - << ", " << deltas.managementFeeDueDelta << ", " + << deltas.interest << ", " << deltas.principal << ", " + << deltas.managementFee << ", " << paymentComponents.trackedValueDelta << ", " << paymentComponents.trackedPrincipalDelta << ", " << paymentComponents.trackedInterestPart() << ", " @@ -1092,24 +1090,24 @@ protected: if (!BEAST_EXPECT(loanSle)) // No reason for this not to exist return; - auto const current = calculateRoundedLoanState(loanSle); + auto const current = constructRoundedLoanState(loanSle); auto const errors = nextTrueState - current; log << currencyLabel << " Loan balances: " << "\n\tAmount taken: " << paymentComponents.trackedValueDelta << "\n\tTotal value: " << current.valueOutstanding << " (true: " << truncate(nextTrueState.valueOutstanding) - << ", error: " << truncate(errors.valueDelta()) + << ", error: " << truncate(errors.total()) << ")\n\tPrincipal: " << current.principalOutstanding << " (true: " << truncate(nextTrueState.principalOutstanding) - << ", error: " << truncate(errors.principalDelta) + << ", error: " << truncate(errors.principal) << ")\n\tInterest: " << current.interestDue << " (true: " << truncate(nextTrueState.interestDue) - << ", error: " << truncate(errors.interestDueDelta) + << ", error: " << truncate(errors.interest) << ")\n\tMgmt fee: " << current.managementFeeDue << " (true: " << truncate(nextTrueState.managementFeeDue) - << ", error: " << truncate(errors.managementFeeDueDelta) + << ", error: " << truncate(errors.managementFee) << ")\n\tPayments remaining " << loanSle->at(sfPaymentRemaining) << std::endl; } @@ -2628,7 +2626,7 @@ protected: periodicRate, state.paymentRemaining, broker.params.managementFeeRate); - auto const rounded = calculateRoundedLoanState( + auto const rounded = constructRoundedLoanState( state.totalValue, state.principalOutstanding, state.managementFeeOutstanding); @@ -2703,9 +2701,8 @@ protected: testcase << currencyLabel << " Payment components: " << state.paymentRemaining - << ", " << deltas.interestDueDelta << ", " - << deltas.principalDelta << ", " - << deltas.managementFeeDueDelta << ", " + << ", " << deltas.interest << ", " << deltas.principal + << ", " << deltas.managementFee << ", " << paymentComponents.trackedValueDelta << ", " << paymentComponents.trackedPrincipalDelta << ", " << paymentComponents.trackedInterestPart() << ", " @@ -2752,7 +2749,7 @@ protected: state.paymentRemaining < 12 || roundToAsset( broker.asset, - deltas.principalDelta, + deltas.principal, state.loanScale, Number::upward) == roundToScale( @@ -2775,8 +2772,8 @@ protected: paymentComponents.specialCase == detail::PaymentSpecialCase::final || (state.periodicPayment.exponent() - - (deltas.principalDelta + deltas.interestDueDelta + - deltas.managementFeeDueDelta - state.periodicPayment) + (deltas.principal + deltas.interest + + deltas.managementFee - state.periodicPayment) .exponent()) > 14); auto const borrowerBalanceBeforePayment = @@ -4519,8 +4516,8 @@ protected: // preclaim Env env(*this); env.fund(XRP(1'000), lender, issuer, borrower); - env(trust(lender, IOU(10'000'000))); - env(pay(issuer, lender, IOU(5'000'000))); + env(trust(lender, IOU(10'000'000)), THISLINE); + env(pay(issuer, lender, IOU(5'000'000)), THISLINE); BrokerInfo brokerInfo{createVaultAndBroker(env, issuer["IOU"], lender)}; auto const loanSetFee = fee(env.current()->fees().base * 2); @@ -4528,21 +4525,24 @@ protected: env(set(borrower, brokerInfo.brokerID, debtMaximumRequest), sig(sfCounterpartySignature, lender), - loanSetFee); + loanSetFee, + THISLINE); env.close(); std::uint32_t const loanSequence = 1; auto const loanKeylet = keylet::loan(brokerInfo.brokerID, loanSequence); - env(fset(issuer, asfGlobalFreeze)); + env(fset(issuer, asfGlobalFreeze), THISLINE); env.close(); // preclaim: tecFROZEN - env(pay(borrower, loanKeylet.key, debtMaximumRequest), ter(tecFROZEN)); + env(pay(borrower, loanKeylet.key, debtMaximumRequest), + ter(tecFROZEN), + THISLINE); env.close(); - env(fclear(issuer, asfGlobalFreeze)); + env(fclear(issuer, asfGlobalFreeze), THISLINE); env.close(); auto const pseudoBroker = [&]() -> std::optional { @@ -4562,37 +4562,51 @@ protected: // Lender and pseudoaccount must both be frozen env(trust( - issuer, - lender["IOU"](1'000), - lender, - tfSetFreeze | tfSetDeepFreeze)); + issuer, + lender["IOU"](1'000), + lender, + tfSetFreeze | tfSetDeepFreeze), + THISLINE); env(trust( - issuer, - (*pseudoBroker)["IOU"](1'000), - *pseudoBroker, - tfSetFreeze | tfSetDeepFreeze)); + issuer, + (*pseudoBroker)["IOU"](1'000), + *pseudoBroker, + tfSetFreeze | tfSetDeepFreeze), + THISLINE); env.close(); // preclaim: tecFROZEN due to deep frozen - env(pay(borrower, loanKeylet.key, debtMaximumRequest), ter(tecFROZEN)); + env(pay(borrower, loanKeylet.key, debtMaximumRequest), + ter(tecFROZEN), + THISLINE); env.close(); // Only one needs to be unfrozen env(trust( - issuer, lender["IOU"](1'000), tfClearFreeze | tfClearDeepFreeze)); + issuer, + lender["IOU"](1'000), + tfClearFreeze | tfClearDeepFreeze), + THISLINE); env.close(); // The payment is late by this point - env(pay(borrower, loanKeylet.key, debtMaximumRequest), ter(tecEXPIRED)); + env(pay(borrower, loanKeylet.key, debtMaximumRequest), + ter(tecEXPIRED), + THISLINE); env.close(); - env(pay( - borrower, loanKeylet.key, debtMaximumRequest, tfLoanLatePayment)); + env(pay(borrower, + loanKeylet.key, + debtMaximumRequest, + tfLoanLatePayment), + THISLINE); env.close(); // preclaim: tecKILLED // note that tecKILLED in loanMakePayment() // doesn't happen because of the preclaim check. - env(pay(borrower, loanKeylet.key, debtMaximumRequest), ter(tecKILLED)); + env(pay(borrower, loanKeylet.key, debtMaximumRequest), + ter(tecKILLED), + THISLINE); } void @@ -5591,11 +5605,11 @@ protected: }); } -#if LOANTODO void - testCoverDepositAllowsNonTransferableMPT() + testCoverDepositWithdrawNonTransferableMPT() { - testcase("CoverDeposit accepts MPT without CanTransfer"); + testcase( + "CoverDeposit and CoverWithdraw reject MPT without CanTransfer"); using namespace jtx; using namespace loanBroker; @@ -5615,7 +5629,7 @@ protected: env.close(); - PrettyAsset const asset = mpt["BUG"]; + PrettyAsset const asset = mpt["MPT"]; mpt.authorize({.account = alice}); env.close(); @@ -5649,21 +5663,58 @@ protected: env(pay(alice, pseudoAccount, asset(1)), ter(tecNO_AUTH)); env.close(); + // Cover cannot be transferred to broker account auto const depositAmount = asset(1); - env(coverDeposit(alice, brokerKeylet.key, depositAmount)); - BEAST_EXPECT(env.ter() == tesSUCCESS); + env(coverDeposit(alice, brokerKeylet.key, depositAmount), + ter{tecNO_AUTH}); env.close(); if (auto const refreshed = env.le(brokerKeylet); BEAST_EXPECT(refreshed)) { - // with an MPT that cannot be transferred the covrAvailable should - // remain zero BEAST_EXPECT(refreshed->at(sfCoverAvailable) == 0); + env.require(balance(pseudoAccount, asset(0))); + } + + // Set CanTransfer again and transfer some deposit + mpt.set({.mutableFlags = tmfMPTSetCanTransfer}); + env.close(); + + env(coverDeposit(alice, brokerKeylet.key, depositAmount)); + env.close(); + + if (auto const refreshed = env.le(brokerKeylet); + BEAST_EXPECT(refreshed)) + { + BEAST_EXPECT(refreshed->at(sfCoverAvailable) == 1); env.require(balance(pseudoAccount, depositAmount)); } + + // Remove CanTransfer after the deposit + mpt.set({.mutableFlags = tmfMPTClearCanTransfer}); + env.close(); + + // Cover cannot be transferred from broker account + env(coverWithdraw(alice, brokerKeylet.key, depositAmount), + ter{tecNO_AUTH}); + env.close(); + + // Set CanTransfer again and withdraw + mpt.set({.mutableFlags = tmfMPTSetCanTransfer}); + env.close(); + + env(coverWithdraw(alice, brokerKeylet.key, depositAmount)); + env.close(); + + if (auto const refreshed = env.le(brokerKeylet); + BEAST_EXPECT(refreshed)) + { + BEAST_EXPECT(refreshed->at(sfCoverAvailable) == 0); + env.require(balance(pseudoAccount, asset(0))); + } } +#if LOANTODO void testLoanPayLateFullPaymentBypassesPenalties() { @@ -5747,7 +5798,7 @@ protected: Number const latePaymentFeeRounded = roundToAsset( broker.asset, loanSle->at(sfLatePaymentFee), state.loanScale); - auto const roundedLoanState = calculateRoundedLoanState( + auto const roundedLoanState = constructRoundedLoanState( state.totalValue, state.principalOutstanding, state.managementFeeOutstanding); @@ -5776,7 +5827,7 @@ protected: Number const roundedFullInterestAmount = roundToAsset(broker.asset, fullPaymentInterest, state.loanScale); - Number const roundedFullManagementFee = computeFee( + Number const roundedFullManagementFee = computeManagementFee( broker.asset, roundedFullInterestAmount, managementFeeRate, @@ -5807,7 +5858,7 @@ protected: Number const lateInterestRaw = state.principalOutstanding * overdueRate; Number const lateInterestRounded = roundToAsset(broker.asset, lateInterestRaw, state.loanScale); - Number const lateManagementFeeRounded = computeFee( + Number const lateManagementFeeRounded = computeManagementFee( broker.asset, lateInterestRounded, managementFeeRate, @@ -6060,7 +6111,7 @@ protected: // Round to asset scale and split interest/fee parts auto const roundedInterest = roundToAsset(asset.raw(), fullPaymentInterest, after.loanScale); - Number const roundedFullMgmtFee = computeFee( + Number const roundedFullMgmtFee = computeManagementFee( asset.raw(), roundedInterest, managementFeeRate, after.loanScale); Number const roundedFullInterest = roundedInterest - roundedFullMgmtFee; @@ -6094,7 +6145,7 @@ protected: closeInterestRate); auto const roundedInterestClamped = roundToAsset( asset.raw(), fullPaymentInterestClamped, after.loanScale); - Number const roundedFullMgmtFeeClamped = computeFee( + Number const roundedFullMgmtFeeClamped = computeManagementFee( asset.raw(), roundedInterestClamped, managementFeeRate, @@ -6268,7 +6319,7 @@ protected: auto const loanSle = env.le(loanKeylet); if (!BEAST_EXPECT(loanSle)) return; - auto const state = calculateRoundedLoanState(loanSle); + auto const state = constructRoundedLoanState(loanSle); log << "Loan state:" << std::endl; log << " ValueOutstanding: " << state.valueOutstanding @@ -6858,10 +6909,10 @@ public: run() override { #if LOANTODO - testCoverDepositAllowsNonTransferableMPT(); testLoanPayLateFullPaymentBypassesPenalties(); testLoanCoverMinimumRoundingExploit(); #endif + testCoverDepositWithdrawNonTransferableMPT(); testPoC_UnsignedUnderflowOnFullPayAfterEarlyPeriodic(); testDisabled(); diff --git a/src/test/app/PermissionedDEX_test.cpp b/src/test/app/PermissionedDEX_test.cpp index d202b39e09..25d94a968b 100644 --- a/src/test/app/PermissionedDEX_test.cpp +++ b/src/test/app/PermissionedDEX_test.cpp @@ -966,8 +966,8 @@ class PermissionedDEX_test : public beast::unit_test::suite { testcase("Remove unfunded offer"); - // checking that an unfunded offer will be implictly removed by a - // successfuly payment tx + // checking that an unfunded offer will be implicitly removed by a + // successful payment tx Env env(*this, features); auto const& [gw, domainOwner, alice, bob, carol, USD, domainID, credType] = PermissionedDEX(env); diff --git a/src/test/app/ValidatorList_test.cpp b/src/test/app/ValidatorList_test.cpp index 370f262075..2f88f0ea1e 100644 --- a/src/test/app/ValidatorList_test.cpp +++ b/src/test/app/ValidatorList_test.cpp @@ -1740,7 +1740,7 @@ private: // locals[0]: from 0 to maxKeys - 4 // locals[1]: from 1 to maxKeys - 2 // locals[2]: from 2 to maxKeys - // interesection of at least 2: same as locals[1] + // intersection of at least 2: same as locals[1] // intersection when 1 is dropped: from 2 to maxKeys - 4 constexpr static int publishers = 3; std::array< diff --git a/src/test/app/Vault_test.cpp b/src/test/app/Vault_test.cpp index a4a3fdd26a..ce76e06912 100644 --- a/src/test/app/Vault_test.cpp +++ b/src/test/app/Vault_test.cpp @@ -2,6 +2,8 @@ #include #include #include +#include +#include #include #include @@ -1765,7 +1767,8 @@ class Vault_test : public beast::unit_test::suite mptt.create( {.flags = tfMPTCanTransfer | tfMPTCanLock | (args.enableClawback ? tfMPTCanClawback : none) | - (args.requireAuth ? tfMPTRequireAuth : none)}); + (args.requireAuth ? tfMPTRequireAuth : none), + .mutableFlags = tmfMPTCanMutateCanTransfer}); PrettyAsset asset = mptt.issuanceID(); mptt.authorize({.account = owner}); mptt.authorize({.account = depositor}); @@ -2483,6 +2486,53 @@ class Vault_test : public beast::unit_test::suite env(tx2, ter{tecWRONG_ASSET}); env.close(); } + + testCase([this]( + Env& env, + Account const&, + Account const& owner, + Account const& depositor, + PrettyAsset const& asset, + Vault& vault, + MPTTester& mptt) { + testcase("MPT non-transferable"); + + auto [tx, keylet] = vault.create({.owner = owner, .asset = asset}); + env(tx); + env.close(); + + tx = vault.deposit( + {.depositor = depositor, + .id = keylet.key, + .amount = asset(100)}); + env(tx); + env.close(); + + // Remove CanTransfer + mptt.set({.mutableFlags = tmfMPTClearCanTransfer}); + env.close(); + + env(tx, ter{tecNO_AUTH}); + env.close(); + + tx = vault.withdraw( + {.depositor = depositor, + .id = keylet.key, + .amount = asset(100)}); + + env(tx, ter{tecNO_AUTH}); + env.close(); + + // Restore CanTransfer + mptt.set({.mutableFlags = tmfMPTSetCanTransfer}); + env.close(); + + env(tx); + env.close(); + + // Delete vault with zero balance + env(vault.del({.owner = owner, .id = keylet.key})); + }); } void @@ -2495,6 +2545,7 @@ class Vault_test : public beast::unit_test::suite int initialXRP = 1000; Number initialIOU = 200; double transferRate = 1.0; + bool charlieRipple = true; }; auto testCase = @@ -2520,8 +2571,21 @@ class Vault_test : public beast::unit_test::suite PrettyAsset const asset = issuer["IOU"]; env.trust(asset(1000), owner); - env.trust(asset(1000), charlie); env(pay(issuer, owner, asset(args.initialIOU))); + env.close(); + if (!args.charlieRipple) + { + env(fset(issuer, 0, asfDefaultRipple)); + env.close(); + env.trust(asset(1000), charlie); + env.close(); + env(pay(issuer, charlie, asset(args.initialIOU))); + env.close(); + env(fset(issuer, asfDefaultRipple)); + } + else + env.trust(asset(1000), charlie); + env.close(); env(rate(issuer, args.transferRate)); env.close(); @@ -2899,6 +2963,94 @@ class Vault_test : public beast::unit_test::suite env(tx1); }); + testCase( + [&, this]( + Env& env, + Account const& owner, + Account const& issuer, + Account const& charlie, + auto vaultAccount, + Vault& vault, + PrettyAsset const& asset, + std::function issuanceId) { + testcase("IOU non-transferable"); + + auto [tx, keylet] = + vault.create({.owner = owner, .asset = asset}); + tx[sfScale] = 0; + env(tx); + env.close(); + + // Turn on noripple on the pseudo account's trust line. + // Charlie's is already set. + env(trust(issuer, vaultAccount(keylet)["IOU"], tfSetNoRipple), + THISLINE); + + { + // Charlie cannot deposit + auto tx = vault.deposit( + {.depositor = charlie, + .id = keylet.key, + .amount = asset(100)}); + env(tx, ter{terNO_RIPPLE}, THISLINE); + env.close(); + } + + { + PrettyAsset shares = issuanceId(keylet); + auto tx1 = vault.deposit( + {.depositor = owner, + .id = keylet.key, + .amount = asset(100)}); + env(tx1, THISLINE); + env.close(); + + // Charlie cannot receive funds + auto tx2 = vault.withdraw( + {.depositor = owner, + .id = keylet.key, + .amount = shares(100)}); + tx2[sfDestination] = charlie.human(); + env(tx2, ter{terNO_RIPPLE}, THISLINE); + env.close(); + + { + // Create MPToken for shares held by Charlie + Json::Value tx{Json::objectValue}; + tx[sfAccount] = charlie.human(); + tx[sfMPTokenIssuanceID] = + to_string(shares.raw().get().getMptID()); + tx[sfTransactionType] = jss::MPTokenAuthorize; + env(tx); + env.close(); + } + env(pay(owner, charlie, shares(100)), THISLINE); + env.close(); + + // Charlie cannot withdraw + auto tx3 = vault.withdraw( + {.depositor = charlie, + .id = keylet.key, + .amount = shares(100)}); + env(tx3, ter{terNO_RIPPLE}); + env.close(); + + env(pay(charlie, owner, shares(100)), THISLINE); + env.close(); + } + + tx = vault.withdraw( + {.depositor = owner, + .id = keylet.key, + .amount = asset(100)}); + env(tx, THISLINE); + env.close(); + + // Delete vault with zero balance + env(vault.del({.owner = owner, .id = keylet.key}), THISLINE); + }, + {.charlieRipple = false}); + testCase( [&, this]( Env& env, diff --git a/src/test/app/tx/apply_test.cpp b/src/test/app/tx/apply_test.cpp index fcfa1a8328..236905cefd 100644 --- a/src/test/app/tx/apply_test.cpp +++ b/src/test/app/tx/apply_test.cpp @@ -49,7 +49,7 @@ public: .first; if (valid != Validity::Valid) - fail("Non-Fully canoncial signature was not permitted"); + fail("Non-Fully canonical signature was not permitted"); } { @@ -63,7 +63,7 @@ public: fully_canonical.app().config()) .first; if (valid == Validity::Valid) - fail("Non-Fully canoncial signature was permitted"); + fail("Non-Fully canonical signature was permitted"); } pass(); diff --git a/src/test/consensus/NegativeUNL_test.cpp b/src/test/consensus/NegativeUNL_test.cpp index 8457d0e2be..5b7eebed2f 100644 --- a/src/test/consensus/NegativeUNL_test.cpp +++ b/src/test/consensus/NegativeUNL_test.cpp @@ -15,7 +15,6 @@ namespace test { /* * This file implements the following negative UNL related tests: * -- test filling and applying ttUNL_MODIFY Tx and ledger update - * -- test ttUNL_MODIFY Tx failure without featureNegativeUNL amendment * -- test the NegativeUNLVote class. The test cases are split to multiple * test classes to allow parallel execution. * -- test the negativeUNLFilter function @@ -208,7 +207,7 @@ class NegativeUNL_test : public beast::unit_test::suite testcase("Create UNLModify Tx and apply to ledgers"); - jtx::Env env(*this, jtx::testable_amendments() | featureNegativeUNL); + jtx::Env env(*this, jtx::testable_amendments()); std::vector publicKeys = createPublicKeys(3); // genesis ledger auto l = std::make_shared( @@ -216,7 +215,6 @@ class NegativeUNL_test : public beast::unit_test::suite env.app().config(), std::vector{}, env.app().getNodeFamily()); - BEAST_EXPECT(l->rules().enabled(featureNegativeUNL)); // Record the public keys and ledger sequences of expected negative UNL // validators when we build the ledger history @@ -500,44 +498,6 @@ class NegativeUNL_test : public beast::unit_test::suite } }; -class NegativeUNLNoAmendment_test : public beast::unit_test::suite -{ - void - testNegativeUNLNoAmendment() - { - testcase("No negative UNL amendment"); - - jtx::Env env(*this, jtx::testable_amendments() - featureNegativeUNL); - std::vector publicKeys = createPublicKeys(1); - // genesis ledger - auto l = std::make_shared( - create_genesis, - env.app().config(), - std::vector{}, - env.app().getNodeFamily()); - BEAST_EXPECT(!l->rules().enabled(featureNegativeUNL)); - - // generate more ledgers - for (auto i = 0; i < 256 - 1; ++i) - { - l = std::make_shared( - *l, env.app().timeKeeper().closeTime()); - } - BEAST_EXPECT(l->seq() == 256); - auto txDisable_0 = createTx(true, l->seq(), publicKeys[0]); - OpenView accum(&*l); - BEAST_EXPECT(applyAndTestResult(env, accum, txDisable_0, false)); - accum.apply(*l); - BEAST_EXPECT(negUnlSizeTest(l, 0, false, false)); - } - - void - run() override - { - testNegativeUNLNoAmendment(); - } -}; - /** * Utility class for creating validators and ledger history */ @@ -563,7 +523,7 @@ struct NetworkHistory }; NetworkHistory(beast::unit_test::suite& suite, Parameter const& p) - : env(suite, jtx::testable_amendments() | featureNegativeUNL) + : env(suite, jtx::testable_amendments()) , param(p) , validations(env.app().getValidations()) { @@ -1867,7 +1827,6 @@ class NegativeUNLVoteFilterValidations_test : public beast::unit_test::suite }; BEAST_DEFINE_TESTSUITE(NegativeUNL, consensus, ripple); -BEAST_DEFINE_TESTSUITE(NegativeUNLNoAmendment, consensus, ripple); BEAST_DEFINE_TESTSUITE(NegativeUNLVoteInternal, consensus, ripple); BEAST_DEFINE_TESTSUITE_MANUAL(NegativeUNLVoteScoreTable, consensus, ripple); diff --git a/src/test/core/Config_test.cpp b/src/test/core/Config_test.cpp index 34c4a553b4..6f11877202 100644 --- a/src/test/core/Config_test.cpp +++ b/src/test/core/Config_test.cpp @@ -1183,7 +1183,7 @@ r.ripple.com:51235 BEAST_EXPECT(cfg.IPS_FIXED[6] == "12.34.12.123 12345"); BEAST_EXPECT(cfg.IPS_FIXED[7] == "12.34.12.123 12345"); - // all ipv6 should be ignored by colon replacer, howsoever formated + // all ipv6 should be ignored by colon replacer, howsoever formatted BEAST_EXPECT(cfg.IPS_FIXED[8] == "::"); BEAST_EXPECT(cfg.IPS_FIXED[9] == "2001:db8::"); BEAST_EXPECT(cfg.IPS_FIXED[10] == "::1"); diff --git a/src/test/core/SociDB_test.cpp b/src/test/core/SociDB_test.cpp index e201f75dba..2d38ac4697 100644 --- a/src/test/core/SociDB_test.cpp +++ b/src/test/core/SociDB_test.cpp @@ -79,7 +79,7 @@ public: void testSQLiteFileNames() { - // confirm that files are given the correct exensions + // confirm that files are given the correct extensions testcase("sqliteFileNames"); BasicConfig c; setupSQLiteConfig(c, getDatabasePath()); diff --git a/src/test/jtx.h b/src/test/jtx.h index 4c33495ad8..1d7f38ff54 100644 --- a/src/test/jtx.h +++ b/src/test/jtx.h @@ -52,6 +52,7 @@ #include #include #include +#include #include #include #include diff --git a/src/test/jtx/JTx.h b/src/test/jtx/JTx.h index d1160b0f34..2baaf8cd10 100644 --- a/src/test/jtx/JTx.h +++ b/src/test/jtx/JTx.h @@ -40,6 +40,9 @@ struct JTx // Functions that sign something else after the mainSigners, such as // sfCounterpartySignature std::vector> postSigners; + // Metadata about the unit test itself + // The line where the JTx was constructed + std::optional testLine = std::nullopt; JTx() = default; JTx(JTx const&) = default; diff --git a/src/test/jtx/amount.h b/src/test/jtx/amount.h index b7c3d77e20..ed0fa57cbf 100644 --- a/src/test/jtx/amount.h +++ b/src/test/jtx/amount.h @@ -262,7 +262,7 @@ struct XRP_t } /** Returns an amount of XRP as PrettyAmount, - which is trivially convertable to STAmount + which is trivially convertible to STAmount @param v The number of XRP (not drops) */ diff --git a/src/test/jtx/envconfig.h b/src/test/jtx/envconfig.h index efc1c50901..4c8476d95f 100644 --- a/src/test/jtx/envconfig.h +++ b/src/test/jtx/envconfig.h @@ -6,7 +6,7 @@ namespace ripple { namespace test { -// frequently used macros defined here for convinience. +// frequently used macros defined here for convenience. #define PORT_WS "port_ws" #define PORT_RPC "port_rpc" #define PORT_PEER "port_peer" diff --git a/src/test/jtx/impl/Env.cpp b/src/test/jtx/impl/Env.cpp index 2ef7879c97..9ce76d01c9 100644 --- a/src/test/jtx/impl/Env.cpp +++ b/src/test/jtx/impl/Env.cpp @@ -427,14 +427,16 @@ Env::postconditions( ParsedResult const& parsed, Json::Value const& jr) { - bool bad = !test.expect(parsed.ter, "apply: No ter result!"); + auto const line = jt.testLine ? " (" + to_string(*jt.testLine) + ")" : ""; + bool bad = !test.expect(parsed.ter, "apply: No ter result!" + line); bad = (jt.ter && parsed.ter && !test.expect( *parsed.ter == *jt.ter, "apply: Got " + transToken(*parsed.ter) + " (" + transHuman(*parsed.ter) + "); Expected " + - transToken(*jt.ter) + " (" + transHuman(*jt.ter) + ")")); + transToken(*jt.ter) + " (" + transHuman(*jt.ter) + ")" + + line)); using namespace std::string_literals; bad = (jt.rpcCode && !test.expect( @@ -446,21 +448,21 @@ Env::postconditions( : "NO RESULT") + " (" + parsed.rpcMessage + "); Expected " + RPC::get_error_info(jt.rpcCode->first).token.c_str() + " (" + - jt.rpcCode->second + ")")) || + jt.rpcCode->second + ")" + line)) || bad; // If we have an rpcCode (just checked), then the rpcException check is // optional - the 'error' field may not be defined, but if it is, it must // match rpcError. - bad = - (jt.rpcException && - !test.expect( - (jt.rpcCode && parsed.rpcError.empty()) || - (parsed.rpcError == jt.rpcException->first && - (!jt.rpcException->second || - parsed.rpcException == *jt.rpcException->second)), - "apply: Got RPC result "s + parsed.rpcError + " (" + - parsed.rpcException + "); Expected " + jt.rpcException->first + - " (" + jt.rpcException->second.value_or("n/a") + ")")) || + bad = (jt.rpcException && + !test.expect( + (jt.rpcCode && parsed.rpcError.empty()) || + (parsed.rpcError == jt.rpcException->first && + (!jt.rpcException->second || + parsed.rpcException == *jt.rpcException->second)), + "apply: Got RPC result "s + parsed.rpcError + " (" + + parsed.rpcException + "); Expected " + + jt.rpcException->first + " (" + + jt.rpcException->second.value_or("n/a") + ")" + line)) || bad; if (bad) { diff --git a/src/test/jtx/impl/testline.cpp b/src/test/jtx/impl/testline.cpp new file mode 100644 index 0000000000..722dc33bff --- /dev/null +++ b/src/test/jtx/impl/testline.cpp @@ -0,0 +1,15 @@ +#include + +namespace ripple { +namespace test { +namespace jtx { + +void +testline::operator()(Env&, JTx& jt) const +{ + jt.testLine = line_; +} + +} // namespace jtx +} // namespace test +} // namespace ripple diff --git a/src/test/jtx/testline.h b/src/test/jtx/testline.h new file mode 100644 index 0000000000..65dd3b7d92 --- /dev/null +++ b/src/test/jtx/testline.h @@ -0,0 +1,34 @@ +#ifndef XRPL_TEST_JTX_TESTLINE_H_INCLUDED +#define XRPL_TEST_JTX_TESTLINE_H_INCLUDED + +#include + +namespace ripple { +namespace test { +namespace jtx { + +/** Store the line number of the current test in a JTx. + + Intended to help debug failing transaction submission tests. +*/ +class testline +{ +private: + int line_; + +public: + explicit testline(int line) : line_(line) + { + } + + void + operator()(Env&, JTx& jt) const; +}; + +#define THISLINE testline(__LINE__) + +} // namespace jtx +} // namespace test +} // namespace ripple + +#endif diff --git a/src/test/overlay/cluster_test.cpp b/src/test/overlay/cluster_test.cpp index e5fd20bc1b..d29e143cff 100644 --- a/src/test/overlay/cluster_test.cpp +++ b/src/test/overlay/cluster_test.cpp @@ -227,7 +227,7 @@ public: BEAST_EXPECT(!c->load(s4)); // Check if we properly terminate when we encounter - // a malformed or unparseable entry: + // a malformed or unparsable entry: auto const node1 = randomNode(); auto const node2 = randomNode(); diff --git a/src/test/rpc/Feature_test.cpp b/src/test/rpc/Feature_test.cpp index 250c634007..10f82ac88d 100644 --- a/src/test/rpc/Feature_test.cpp +++ b/src/test/rpc/Feature_test.cpp @@ -124,7 +124,8 @@ class Feature_test : public beast::unit_test::suite featureToName(fixRemoveNFTokenAutoTrustLine) == "fixRemoveNFTokenAutoTrustLine"); BEAST_EXPECT(featureToName(featureFlow) == "Flow"); - BEAST_EXPECT(featureToName(featureNegativeUNL) == "NegativeUNL"); + BEAST_EXPECT( + featureToName(featureDeletableAccounts) == "DeletableAccounts"); BEAST_EXPECT( featureToName(fixIncludeKeyletFields) == "fixIncludeKeyletFields"); BEAST_EXPECT(featureToName(featureTokenEscrow) == "TokenEscrow"); diff --git a/src/xrpld/app/consensus/RCLConsensus.cpp b/src/xrpld/app/consensus/RCLConsensus.cpp index 7734ab790d..09584f2e77 100644 --- a/src/xrpld/app/consensus/RCLConsensus.cpp +++ b/src/xrpld/app/consensus/RCLConsensus.cpp @@ -346,9 +346,7 @@ RCLConsensus::Adaptor::onClose( prevLedger, validations, initialSet, j_); } } - else if ( - prevLedger->isVotingLedger() && - prevLedger->rules().enabled(featureNegativeUNL)) + else if (prevLedger->isVotingLedger()) { // previous ledger was a voting ledger, // so the current consensus session is for a flag ledger, @@ -1015,8 +1013,7 @@ RCLConsensus::Adaptor::preStartRound( inboundTransactions_.newRound(prevLgr.seq()); // Notify NegativeUNLVote that new validators are added - if (prevLgr.ledger_->rules().enabled(featureNegativeUNL) && - !nowTrusted.empty()) + if (!nowTrusted.empty()) nUnlVote_.newValidators(prevLgr.seq() + 1, nowTrusted); // propose only if we're in sync with the network (and validating) diff --git a/src/xrpld/app/ledger/detail/BuildLedger.cpp b/src/xrpld/app/ledger/detail/BuildLedger.cpp index 97824e3023..dd3593e1cf 100644 --- a/src/xrpld/app/ledger/detail/BuildLedger.cpp +++ b/src/xrpld/app/ledger/detail/BuildLedger.cpp @@ -28,7 +28,7 @@ buildLedgerImpl( { auto built = std::make_shared(*parent, closeTime); - if (built->isFlagLedger() && built->rules().enabled(featureNegativeUNL)) + if (built->isFlagLedger()) { built->updateNegativeUNL(); } diff --git a/src/xrpld/app/misc/LendingHelpers.h b/src/xrpld/app/misc/LendingHelpers.h index 459c2ecf40..f430a0f0d5 100644 --- a/src/xrpld/app/misc/LendingHelpers.h +++ b/src/xrpld/app/misc/LendingHelpers.h @@ -118,18 +118,18 @@ calculateRawLoanState( TenthBips32 const managementFeeRate); LoanState -calculateRoundedLoanState( +constructRoundedLoanState( Number const& totalValueOutstanding, Number const& principalOutstanding, Number const& managementFeeOutstanding); LoanState -calculateRoundedLoanState(SLE::const_ref loan); +constructRoundedLoanState(SLE::const_ref loan); Number -computeFee( +computeManagementFee( Asset const& asset, - Number const& value, + Number const& interest, TenthBips32 managementFeeRate, std::int32_t scale); @@ -183,14 +183,14 @@ struct PaymentComponents struct LoanDeltas { - Number principalDelta; - Number interestDueDelta; - Number managementFeeDueDelta; + Number principal; + Number interest; + Number managementFee; Number - valueDelta() const + total() const { - return principalDelta + interestDueDelta + managementFeeDueDelta; + return principal + interest + managementFee; } void @@ -217,13 +217,6 @@ operator-(LoanState const& lhs, LoanState const& rhs); LoanState operator-(LoanState const& lhs, detail::LoanDeltas const& rhs); -Number -valueMinusFee( - Asset const& asset, - Number const& value, - TenthBips16 managementFeeRate, - std::int32_t scale); - LoanProperties computeLoanProperties( Asset const& asset, diff --git a/src/xrpld/app/misc/NetworkOPs.cpp b/src/xrpld/app/misc/NetworkOPs.cpp index 14cd1df559..157de028e5 100644 --- a/src/xrpld/app/misc/NetworkOPs.cpp +++ b/src/xrpld/app/misc/NetworkOPs.cpp @@ -2064,8 +2064,7 @@ NetworkOPsImp::beginConsensus( "ripple::NetworkOPsImp::beginConsensus : closedLedger parent matches " "hash"); - if (prevLedger->rules().enabled(featureNegativeUNL)) - app_.validators().setNegativeUNL(prevLedger->negativeUNL()); + app_.validators().setNegativeUNL(prevLedger->negativeUNL()); TrustChanges const changes = app_.validators().updateTrusted( app_.getValidations().getCurrentNodeIDs(), closingInfo.parentCloseTime, diff --git a/src/xrpld/app/misc/detail/LendingHelpers.cpp b/src/xrpld/app/misc/detail/LendingHelpers.cpp index 551842efb0..62f567e7c0 100644 --- a/src/xrpld/app/misc/detail/LendingHelpers.cpp +++ b/src/xrpld/app/misc/detail/LendingHelpers.cpp @@ -219,7 +219,7 @@ loanAccruedInterest( paymentInterval; } -struct PaymentComponentsPlus : public PaymentComponents +struct ExtendedPaymentComponents : public PaymentComponents { // untrackedManagementFeeDelta includes any fees that go directly to the // Broker, such as late fees. This value may be negative, though the final @@ -232,7 +232,7 @@ struct PaymentComponentsPlus : public PaymentComponents Number untrackedInterest; Number totalDue; - PaymentComponentsPlus( + ExtendedPaymentComponents( PaymentComponents const& p, Number f, Number v = numZero) @@ -248,7 +248,7 @@ struct PaymentComponentsPlus : public PaymentComponents template LoanPaymentParts doPayment( - PaymentComponentsPlus const& payment, + ExtendedPaymentComponents const& payment, NumberProxy& totalValueOutstandingProxy, NumberProxy& principalOutstandingProxy, NumberProxy& managementFeeOutstandingProxy, @@ -353,7 +353,7 @@ Expected tryOverpayment( Asset const& asset, std::int32_t loanScale, - PaymentComponentsPlus const& overpaymentComponents, + ExtendedPaymentComponents const& overpaymentComponents, Number& totalValueOutstanding, Number& principalOutstanding, Number& managementFeeOutstanding, @@ -369,7 +369,7 @@ tryOverpayment( { auto const raw = calculateRawLoanState( periodicPayment, periodicRate, paymentRemaining, managementFeeRate); - auto const rounded = calculateRoundedLoanState( + auto const rounded = constructRoundedLoanState( totalValueOutstanding, principalOutstanding, managementFeeOutstanding); auto const totalValueError = totalValueOutstanding - raw.valueOutstanding; @@ -437,7 +437,7 @@ tryOverpayment( // LCOV_EXCL_STOP } - auto const newRounded = calculateRoundedLoanState( + auto const newRounded = constructRoundedLoanState( totalValueOutstanding, principalOutstanding, managementFeeOutstanding); auto const valueChange = newRounded.interestOutstanding() - rounded.interestOutstanding(); @@ -460,7 +460,7 @@ Expected doOverpayment( Asset const& asset, std::int32_t loanScale, - PaymentComponentsPlus const& overpaymentComponents, + ExtendedPaymentComponents const& overpaymentComponents, NumberProxy& totalValueOutstandingProxy, NumberProxy& principalOutstandingProxy, NumberProxy& managementFeeOutstandingProxy, @@ -563,7 +563,8 @@ computeInterestAndFeeParts( TenthBips16 managementFeeRate, std::int32_t loanScale) { - auto const fee = computeFee(asset, interest, managementFeeRate, loanScale); + auto const fee = + computeManagementFee(asset, interest, managementFeeRate, loanScale); return std::make_pair(interest - fee, fee); } @@ -582,13 +583,13 @@ computeInterestAndFeeParts( * * section 3.2.4.1.2 (Late Payment) */ -Expected +Expected computeLatePayment( Asset const& asset, ApplyView const& view, Number const& principalOutstanding, std::int32_t nextDueDate, - PaymentComponentsPlus const& periodic, + ExtendedPaymentComponents const& periodic, TenthBips32 lateInterestRate, std::int32_t loanScale, Number const& latePaymentFee, @@ -624,10 +625,10 @@ computeLatePayment( "no extra parts to this payment"); // Copy the periodic payment values, and add on the late interest. // This preserves all the other fields without having to enumerate them. - PaymentComponentsPlus const late = [&]() { + ExtendedPaymentComponents const late = [&]() { auto inner = periodic; - return PaymentComponentsPlus{ + return ExtendedPaymentComponents{ inner, // A late payment pays both the normal fee, and the extra fees periodic.untrackedManagementFee + latePaymentFee + @@ -655,12 +656,12 @@ computeLatePayment( /* Handle possible full payments. * * If this function processed a full payment, the return value will be - * a PaymentComponentsPlus object. Otherwise, it'll be an Unexpected with the - * error code the caller is expected to return. It should NEVER return + * an ExtendedPaymentComponents object. Otherwise, it'll be an Unexpected with + * the error code the caller is expected to return. It should NEVER return * tesSUCCESS */ -Expected +Expected computeFullPayment( Asset const& asset, ApplyView& view, @@ -681,8 +682,12 @@ computeFullPayment( beast::Journal j) { if (paymentRemaining <= 1) + { // If this is the last payment, it has to be a regular payment + JLOG(j.warn()) << "Full payment requested when only final " + << "payment remains."; return Unexpected(tecKILLED); + } Number const rawPrincipalOutstanding = loanPrincipalFromPeriodicPayment( periodicPayment, periodicRate, paymentRemaining); @@ -708,7 +713,7 @@ computeFullPayment( return std::make_tuple(parts.first, parts.second); }(); - PaymentComponentsPlus const full{ + ExtendedPaymentComponents const full{ PaymentComponents{ .trackedValueDelta = principalOutstanding + totalInterestOutstanding + managementFeeOutstanding, @@ -762,12 +767,12 @@ PaymentComponents::trackedInterestPart() const void LoanDeltas::nonNegative() { - if (principalDelta < beast::zero) - principalDelta = numZero; - if (interestDueDelta < beast::zero) - interestDueDelta = numZero; - if (managementFeeDueDelta < beast::zero) - managementFeeDueDelta = numZero; + if (principal < beast::zero) + principal = numZero; + if (interest < beast::zero) + interest = numZero; + if (managementFee < beast::zero) + managementFee = numZero; } PaymentComponents @@ -809,7 +814,7 @@ computePaymentComponents( .interestDue = roundToAsset(asset, trueTarget.interestDue, scale), .managementFeeDue = roundToAsset(asset, trueTarget.managementFeeDue, scale)}; - LoanState const currentLedgerState = calculateRoundedLoanState( + LoanState const currentLedgerState = constructRoundedLoanState( totalValueOutstanding, principalOutstanding, managementFeeOutstanding); LoanDeltas deltas = currentLedgerState - roundedTarget; @@ -817,32 +822,31 @@ computePaymentComponents( // Adjust the deltas if necessary for data integrity XRPL_ASSERT_PARTS( - deltas.principalDelta <= currentLedgerState.principalOutstanding, + deltas.principal <= currentLedgerState.principalOutstanding, "ripple::detail::computePaymentComponents", "principal delta not greater than outstanding"); - deltas.principalDelta = std::min( - deltas.principalDelta, currentLedgerState.principalOutstanding); + deltas.principal = + std::min(deltas.principal, currentLedgerState.principalOutstanding); XRPL_ASSERT_PARTS( - deltas.interestDueDelta <= currentLedgerState.interestDue, + deltas.interest <= currentLedgerState.interestDue, "ripple::detail::computePaymentComponents", "interest due delta not greater than outstanding"); - deltas.interestDueDelta = std::min( - {deltas.interestDueDelta, - std::max(numZero, roundedPeriodicPayment - deltas.principalDelta), + deltas.interest = std::min( + {deltas.interest, + std::max(numZero, roundedPeriodicPayment - deltas.principal), currentLedgerState.interestDue}); XRPL_ASSERT_PARTS( - deltas.managementFeeDueDelta <= currentLedgerState.managementFeeDue, + deltas.managementFee <= currentLedgerState.managementFeeDue, "ripple::detail::computePaymentComponents", "management fee due delta not greater than outstanding"); - deltas.managementFeeDueDelta = std::min( - {deltas.managementFeeDueDelta, - roundedPeriodicPayment - - (deltas.principalDelta + deltas.interestDueDelta), + deltas.managementFee = std::min( + {deltas.managementFee, + roundedPeriodicPayment - (deltas.principal + deltas.interest), currentLedgerState.managementFeeDue}); if (paymentRemaining == 1 || @@ -852,15 +856,15 @@ computePaymentComponents( // parts. XRPL_ASSERT_PARTS( - deltas.valueDelta() <= totalValueOutstanding, + deltas.total() <= totalValueOutstanding, "ripple::detail::computePaymentComponents", "last payment total value agrees"); XRPL_ASSERT_PARTS( - deltas.principalDelta <= principalOutstanding, + deltas.principal <= principalOutstanding, "ripple::detail::computePaymentComponents", "last payment principal agrees"); XRPL_ASSERT_PARTS( - deltas.managementFeeDueDelta <= managementFeeOutstanding, + deltas.managementFee <= managementFeeOutstanding, "ripple::detail::computePaymentComponents", "last payment management fee agrees"); @@ -894,12 +898,12 @@ computePaymentComponents( }; auto addressExcess = [&takeFrom](LoanDeltas& deltas, Number& excess) { // This order is based on where errors are the least problematic - takeFrom(deltas.interestDueDelta, excess); - takeFrom(deltas.managementFeeDueDelta, excess); - takeFrom(deltas.principalDelta, excess); + takeFrom(deltas.interest, excess); + takeFrom(deltas.managementFee, excess); + takeFrom(deltas.principal, excess); }; Number totalOverpayment = - deltas.valueDelta() - currentLedgerState.valueOutstanding; + deltas.total() - currentLedgerState.valueOutstanding; if (totalOverpayment > beast::zero) { // LCOV_EXCL_START @@ -911,7 +915,7 @@ computePaymentComponents( } // Make sure the parts don't add up to too much - Number shortage = roundedPeriodicPayment - deltas.valueDelta(); + Number shortage = roundedPeriodicPayment - deltas.total(); XRPL_ASSERT_PARTS( isRounded(asset, shortage, scale), @@ -936,32 +940,29 @@ computePaymentComponents( "no shortage or excess"); XRPL_ASSERT_PARTS( - deltas.valueDelta() == - deltas.principalDelta + deltas.interestDueDelta + - deltas.managementFeeDueDelta, + deltas.total() == + deltas.principal + deltas.interest + deltas.managementFee, "ripple::detail::computePaymentComponents", "total value adds up"); XRPL_ASSERT_PARTS( - deltas.principalDelta >= beast::zero && - deltas.principalDelta <= currentLedgerState.principalOutstanding, + deltas.principal >= beast::zero && + deltas.principal <= currentLedgerState.principalOutstanding, "ripple::detail::computePaymentComponents", "valid principal result"); XRPL_ASSERT_PARTS( - deltas.interestDueDelta >= beast::zero && - deltas.interestDueDelta <= currentLedgerState.interestDue, + deltas.interest >= beast::zero && + deltas.interest <= currentLedgerState.interestDue, "ripple::detail::computePaymentComponents", "valid interest result"); XRPL_ASSERT_PARTS( - deltas.managementFeeDueDelta >= beast::zero && - deltas.managementFeeDueDelta <= currentLedgerState.managementFeeDue, + deltas.managementFee >= beast::zero && + deltas.managementFee <= currentLedgerState.managementFeeDue, "ripple::detail::computePaymentComponents", "valid fee result"); XRPL_ASSERT_PARTS( - deltas.principalDelta + deltas.interestDueDelta + - deltas.managementFeeDueDelta > - beast::zero, + deltas.principal + deltas.interest + deltas.managementFee > beast::zero, "ripple::detail::computePaymentComponents", "payment parts add to payment"); @@ -969,19 +970,15 @@ computePaymentComponents( // As a final safety check, ensure the value is non-negative, and won't // make the corresponding item negative .trackedValueDelta = std::clamp( - deltas.valueDelta(), numZero, currentLedgerState.valueOutstanding), + deltas.total(), numZero, currentLedgerState.valueOutstanding), .trackedPrincipalDelta = std::clamp( - deltas.principalDelta, - numZero, - currentLedgerState.principalOutstanding), + deltas.principal, numZero, currentLedgerState.principalOutstanding), .trackedManagementFeeDelta = std::clamp( - deltas.managementFeeDueDelta, - numZero, - currentLedgerState.managementFeeDue), + deltas.managementFee, numZero, currentLedgerState.managementFeeDue), }; } -PaymentComponentsPlus +ExtendedPaymentComponents computeOverpaymentComponents( Asset const& asset, int32_t const loanScale, @@ -1013,7 +1010,7 @@ computeOverpaymentComponents( asset, interest, managementFeeRate, loanScale); }(); - return detail::PaymentComponentsPlus{ + return detail::ExtendedPaymentComponents{ detail::PaymentComponents{ .trackedValueDelta = payment, .trackedPrincipalDelta = payment - roundedOverpaymentInterest - @@ -1030,9 +1027,9 @@ detail::LoanDeltas operator-(LoanState const& lhs, LoanState const& rhs) { detail::LoanDeltas result{ - .principalDelta = lhs.principalOutstanding - rhs.principalOutstanding, - .interestDueDelta = lhs.interestDue - rhs.interestDue, - .managementFeeDueDelta = lhs.managementFeeDue - rhs.managementFeeDue, + .principal = lhs.principalOutstanding - rhs.principalOutstanding, + .interest = lhs.interestDue - rhs.interestDue, + .managementFee = lhs.managementFeeDue - rhs.managementFeeDue, }; return result; @@ -1042,10 +1039,10 @@ LoanState operator-(LoanState const& lhs, detail::LoanDeltas const& rhs) { LoanState result{ - .valueOutstanding = lhs.valueOutstanding - rhs.valueDelta(), - .principalOutstanding = lhs.principalOutstanding - rhs.principalDelta, - .interestDue = lhs.interestDue - rhs.interestDueDelta, - .managementFeeDue = lhs.managementFeeDue - rhs.managementFeeDueDelta, + .valueOutstanding = lhs.valueOutstanding - rhs.total(), + .principalOutstanding = lhs.principalOutstanding - rhs.principal, + .interestDue = lhs.interestDue - rhs.interest, + .managementFeeDue = lhs.managementFeeDue - rhs.managementFee, }; return result; @@ -1156,14 +1153,14 @@ calculateRawLoanState( } LoanState -calculateRoundedLoanState( +constructRoundedLoanState( Number const& totalValueOutstanding, Number const& principalOutstanding, Number const& managementFeeOutstanding) { // This implementation is pretty trivial, but ensures the calculations are // consistent everywhere, and reduces copy/paste errors. - return { + return LoanState{ .valueOutstanding = totalValueOutstanding, .principalOutstanding = principalOutstanding, .interestDue = totalValueOutstanding - principalOutstanding - @@ -1172,16 +1169,16 @@ calculateRoundedLoanState( } LoanState -calculateRoundedLoanState(SLE::const_ref loan) +constructRoundedLoanState(SLE::const_ref loan) { - return calculateRoundedLoanState( + return constructRoundedLoanState( loan->at(sfTotalValueOutstanding), loan->at(sfPrincipalOutstanding), loan->at(sfManagementFeeOutstanding)); } Number -computeFee( +computeManagementFee( Asset const& asset, Number const& value, TenthBips32 managementFeeRate, @@ -1194,16 +1191,6 @@ computeFee( Number::downward); } -Number -valueMinusFee( - Asset const& asset, - Number const& value, - TenthBips16 managementFeeRate, - std::int32_t scale) -{ - return value - computeFee(asset, value, managementFeeRate, scale); -} - LoanProperties computeLoanProperties( Asset const& asset, @@ -1258,7 +1245,7 @@ computeLoanProperties( principalOutstanding = roundToAsset( asset, principalOutstanding, loanScale, Number::to_nearest); - auto const feeOwedToBroker = computeFee( + auto const feeOwedToBroker = computeManagementFee( asset, /* * This formula is from the XLS-66 spec, section 3.2.4.2 (Total Loan @@ -1384,7 +1371,7 @@ loanMakePayment( Number const closePaymentFee = roundToAsset(asset, loan->at(sfClosePaymentFee), loanScale); - LoanState const roundedLoanState = calculateRoundedLoanState( + LoanState const roundedLoanState = constructRoundedLoanState( totalValueOutstandingProxy, principalOutstandingProxy, managementFeeOutstandingProxy); @@ -1426,6 +1413,7 @@ loanMakePayment( // LCOV_EXCL_START UNREACHABLE("ripple::loanMakePayment : invalid full payment result"); + JLOG(j.error()) << "Full payment computation failed unexpectedly."; return Unexpected(tecINTERNAL); // LCOV_EXCL_STOP } @@ -1433,7 +1421,7 @@ loanMakePayment( // ------------------------------------------------------------- // compute the periodic payment info that will be needed whether the payment // is late or regular - detail::PaymentComponentsPlus periodic{ + detail::ExtendedPaymentComponents periodic{ detail::computePaymentComponents( asset, loanScale, @@ -1489,6 +1477,7 @@ loanMakePayment( // LCOV_EXCL_START UNREACHABLE("ripple::loanMakePayment : invalid late payment result"); + JLOG(j.error()) << "Late payment computation failed unexpectedly."; return Unexpected(tecINTERNAL); // LCOV_EXCL_STOP } @@ -1540,7 +1529,7 @@ loanMakePayment( if (periodic.specialCase == detail::PaymentSpecialCase::final) break; - periodic = detail::PaymentComponentsPlus{ + periodic = detail::ExtendedPaymentComponents{ detail::computePaymentComponents( asset, loanScale, @@ -1588,7 +1577,7 @@ loanMakePayment( Number const overpayment = std::min(amount - totalPaid, *totalValueOutstandingProxy); - detail::PaymentComponentsPlus const overpaymentComponents = + detail::ExtendedPaymentComponents const overpaymentComponents = detail::computeOverpaymentComponents( asset, loanScale, diff --git a/src/xrpld/app/rdb/RelationalDatabase.h b/src/xrpld/app/rdb/RelationalDatabase.h index 570bce2b95..18a5536cf6 100644 --- a/src/xrpld/app/rdb/RelationalDatabase.h +++ b/src/xrpld/app/rdb/RelationalDatabase.h @@ -6,7 +6,7 @@ #include #include #include -#include +#include #include diff --git a/src/xrpld/app/tx/detail/Change.cpp b/src/xrpld/app/tx/detail/Change.cpp index 4747cdd371..43c6f7c619 100644 --- a/src/xrpld/app/tx/detail/Change.cpp +++ b/src/xrpld/app/tx/detail/Change.cpp @@ -55,13 +55,6 @@ Transactor::invokePreflight(PreflightContext const& ctx) return temBAD_SEQUENCE; } - if (ctx.tx.getTxnType() == ttUNL_MODIFY && - !ctx.rules.enabled(featureNegativeUNL)) - { - JLOG(ctx.j.warn()) << "Change: NegativeUNL not enabled"; - return temDISABLED; - } - return tesSUCCESS; } diff --git a/src/xrpld/app/tx/detail/LoanBrokerCoverDeposit.cpp b/src/xrpld/app/tx/detail/LoanBrokerCoverDeposit.cpp index 25075582c5..974bb36a93 100644 --- a/src/xrpld/app/tx/detail/LoanBrokerCoverDeposit.cpp +++ b/src/xrpld/app/tx/detail/LoanBrokerCoverDeposit.cpp @@ -52,13 +52,16 @@ LoanBrokerCoverDeposit::preclaim(PreclaimContext const& ctx) JLOG(ctx.j.fatal()) << "Vault is missing for Broker " << brokerID; return tefBAD_LEDGER; } - auto const vaultAsset = vault->at(sfAsset); + auto const vaultAsset = vault->at(sfAsset); if (amount.asset() != vaultAsset) return tecWRONG_ASSET; auto const pseudoAccountID = sleBroker->at(sfAccount); - + // Cannot transfer a non-transferable Asset + if (auto const ret = + canTransfer(ctx.view, vaultAsset, account, pseudoAccountID)) + return ret; // Cannot transfer a frozen Asset if (auto const ret = checkFrozen(ctx.view, account, vaultAsset)) return ret; diff --git a/src/xrpld/app/tx/detail/LoanBrokerCoverWithdraw.cpp b/src/xrpld/app/tx/detail/LoanBrokerCoverWithdraw.cpp index aebda09e0b..58204b70d5 100644 --- a/src/xrpld/app/tx/detail/LoanBrokerCoverWithdraw.cpp +++ b/src/xrpld/app/tx/detail/LoanBrokerCoverWithdraw.cpp @@ -60,14 +60,21 @@ LoanBrokerCoverWithdraw::preclaim(PreclaimContext const& ctx) auto const vault = ctx.view.read(keylet::vault(sleBroker->at(sfVaultID))); if (!vault) return tefBAD_LEDGER; // LCOV_EXCL_LINE - auto const vaultAsset = vault->at(sfAsset); + auto const vaultAsset = vault->at(sfAsset); if (amount.asset() != vaultAsset) return tecWRONG_ASSET; + // The broker's pseudo-account is the source of funds. + auto const pseudoAccountID = sleBroker->at(sfAccount); + // Cannot transfer a non-transferable Asset + if (auto const ret = + canTransfer(ctx.view, vaultAsset, pseudoAccountID, dstAcct)) + return ret; + // Withdrawal to a 3rd party destination account is essentially a transfer. // Enforce all the usual asset transfer checks. - AuthType authType = AuthType::Legacy; + AuthType authType = AuthType::WeakAuth; if (account != dstAcct) { if (auto const ret = canWithdraw(ctx.view, tx)) @@ -82,9 +89,6 @@ LoanBrokerCoverWithdraw::preclaim(PreclaimContext const& ctx) if (auto const ter = requireAuth(ctx.view, vaultAsset, dstAcct, authType)) return ter; - // The broker's pseudo-account is the source of funds. - auto const pseudoAccountID = sleBroker->at(sfAccount); - // Check for freezes, unless sending directly to the issuer if (dstAcct != vaultAsset.getIssuer()) { @@ -146,8 +150,16 @@ LoanBrokerCoverWithdraw::doApply() broker->at(sfCoverAvailable) -= amount; view().update(broker); - // Move the funds from the broker's pseudo-account to the dstAcct + // Create trust line or MPToken for the receiving account + if (dstAcct == account_) + { + if (auto const ter = addEmptyHolding( + view(), account_, mPriorBalance, amount.asset(), j_); + !isTesSuccess(ter) && ter != tecDUPLICATE) + return ter; + } + // Move the funds from the broker's pseudo-account to the dstAcct if (dstAcct == account_ || amount.native()) { // Transfer assets directly from pseudo-account to depositor. diff --git a/src/xrpld/app/tx/detail/LoanSet.cpp b/src/xrpld/app/tx/detail/LoanSet.cpp index c129673222..f72ad33cee 100644 --- a/src/xrpld/app/tx/detail/LoanSet.cpp +++ b/src/xrpld/app/tx/detail/LoanSet.cpp @@ -495,7 +495,7 @@ LoanSet::doApply() // LCOV_EXCL_STOP } - LoanState const state = calculateRoundedLoanState( + LoanState const state = constructRoundedLoanState( properties.totalValueOutstanding, principalRequested, properties.managementFeeOwedToBroker); diff --git a/src/xrpld/app/tx/detail/VaultDeposit.cpp b/src/xrpld/app/tx/detail/VaultDeposit.cpp index 3e5ae741e3..aeaf890126 100644 --- a/src/xrpld/app/tx/detail/VaultDeposit.cpp +++ b/src/xrpld/app/tx/detail/VaultDeposit.cpp @@ -36,41 +36,19 @@ VaultDeposit::preclaim(PreclaimContext const& ctx) if (!vault) return tecNO_ENTRY; - auto const account = ctx.tx[sfAccount]; + auto const& account = ctx.tx[sfAccount]; auto const assets = ctx.tx[sfAmount]; auto const vaultAsset = vault->at(sfAsset); if (assets.asset() != vaultAsset) return tecWRONG_ASSET; - if (vaultAsset.native()) - ; // No special checks for XRP - else if (vaultAsset.holds()) + auto const& vaultAccount = vault->at(sfAccount); + if (auto ter = canTransfer(ctx.view, vaultAsset, account, vaultAccount); + !isTesSuccess(ter)) { - auto mptID = vaultAsset.get().getMptID(); - auto issuance = ctx.view.read(keylet::mptIssuance(mptID)); - if (!issuance) - return tecOBJECT_NOT_FOUND; - if (!issuance->isFlag(lsfMPTCanTransfer)) - { - // LCOV_EXCL_START - JLOG(ctx.j.error()) - << "VaultDeposit: vault assets are non-transferable."; - return tecNO_AUTH; - // LCOV_EXCL_STOP - } - } - else if (vaultAsset.holds()) - { - auto const issuer = - ctx.view.read(keylet::account(vaultAsset.getIssuer())); - if (!issuer) - { - // LCOV_EXCL_START - JLOG(ctx.j.error()) - << "VaultDeposit: missing issuer of vault assets."; - return tefINTERNAL; - // LCOV_EXCL_STOP - } + JLOG(ctx.j.debug()) + << "VaultDeposit: vault assets are non-transferable."; + return ter; } auto const mptIssuanceID = vault->at(sfShareMPTID); diff --git a/src/xrpld/app/tx/detail/VaultWithdraw.cpp b/src/xrpld/app/tx/detail/VaultWithdraw.cpp index dc8cbde7c0..de84a372e7 100644 --- a/src/xrpld/app/tx/detail/VaultWithdraw.cpp +++ b/src/xrpld/app/tx/detail/VaultWithdraw.cpp @@ -47,35 +47,15 @@ VaultWithdraw::preclaim(PreclaimContext const& ctx) if (assets.asset() != vaultAsset && assets.asset() != vaultShare) return tecWRONG_ASSET; - if (vaultAsset.native()) - ; // No special checks for XRP - else if (vaultAsset.holds()) + auto const& vaultAccount = vault->at(sfAccount); + auto const& account = ctx.tx[sfAccount]; + auto const& dstAcct = ctx.tx[~sfDestination].value_or(account); + if (auto ter = canTransfer(ctx.view, vaultAsset, vaultAccount, dstAcct); + !isTesSuccess(ter)) { - auto mptID = vaultAsset.get().getMptID(); - auto issuance = ctx.view.read(keylet::mptIssuance(mptID)); - if (!issuance) - return tecOBJECT_NOT_FOUND; - if (!issuance->isFlag(lsfMPTCanTransfer)) - { - // LCOV_EXCL_START - JLOG(ctx.j.error()) - << "VaultWithdraw: vault assets are non-transferable."; - return tecNO_AUTH; - // LCOV_EXCL_STOP - } - } - else if (vaultAsset.holds()) - { - auto const issuer = - ctx.view.read(keylet::account(vaultAsset.getIssuer())); - if (!issuer) - { - // LCOV_EXCL_START - JLOG(ctx.j.error()) - << "VaultWithdraw: missing issuer of vault assets."; - return tefINTERNAL; - // LCOV_EXCL_STOP - } + JLOG(ctx.j.debug()) + << "VaultWithdraw: vault assets are non-transferable."; + return ter; } // Enforce valid withdrawal policy @@ -87,9 +67,6 @@ VaultWithdraw::preclaim(PreclaimContext const& ctx) // LCOV_EXCL_STOP } - auto const account = ctx.tx[sfAccount]; - auto const dstAcct = ctx.tx[~sfDestination].value_or(account); - if (auto const ret = canWithdraw(ctx.view, ctx.tx)) return ret; @@ -259,9 +236,9 @@ VaultWithdraw::doApply() } auto const dstAcct = ctx_.tx[~sfDestination].value_or(account_); - if (!vaultAsset.native() && // - dstAcct != vaultAsset.getIssuer() && // - dstAcct == account_) + + // Create trust line or MPToken for the receiving account + if (dstAcct == account_) { if (auto const ter = addEmptyHolding( view(), account_, mPriorBalance, vaultAsset, j_); diff --git a/src/xrpld/peerfinder/detail/SlotImp.h b/src/xrpld/peerfinder/detail/SlotImp.h index 2badd9a2ef..9898ca490d 100644 --- a/src/xrpld/peerfinder/detail/SlotImp.h +++ b/src/xrpld/peerfinder/detail/SlotImp.h @@ -176,7 +176,7 @@ public: // DEPRECATED public data members // Tells us if we checked the connection. Outbound connections - // are always considered checked since we successfuly connected. + // are always considered checked since we successfully connected. bool checked; // Set to indicate if the connection can receive incoming at the diff --git a/src/xrpld/rpc/detail/RPCHelpers.cpp b/src/xrpld/rpc/detail/RPCHelpers.cpp index d9f640d537..6d1314ff51 100644 --- a/src/xrpld/rpc/detail/RPCHelpers.cpp +++ b/src/xrpld/rpc/detail/RPCHelpers.cpp @@ -130,513 +130,6 @@ isRelatedToAccount( return false; } -bool -getAccountObjects( - ReadView const& ledger, - AccountID const& account, - std::optional> const& typeFilter, - uint256 dirIndex, - uint256 entryIndex, - std::uint32_t const limit, - Json::Value& jvResult) -{ - // check if dirIndex is valid - if (!dirIndex.isZero() && !ledger.read({ltDIR_NODE, dirIndex})) - return false; - - auto typeMatchesFilter = [](std::vector const& typeFilter, - LedgerEntryType ledgerType) { - auto it = std::find(typeFilter.begin(), typeFilter.end(), ledgerType); - return it != typeFilter.end(); - }; - - // if dirIndex != 0, then all NFTs have already been returned. only - // iterate NFT pages if the filter says so AND dirIndex == 0 - bool iterateNFTPages = - (!typeFilter.has_value() || - typeMatchesFilter(typeFilter.value(), ltNFTOKEN_PAGE)) && - dirIndex == beast::zero; - - Keylet const firstNFTPage = keylet::nftpage_min(account); - - // we need to check the marker to see if it is an NFTTokenPage index. - if (iterateNFTPages && entryIndex != beast::zero) - { - // if it is we will try to iterate the pages up to the limit - // and then change over to the owner directory - - if (firstNFTPage.key != (entryIndex & ~nft::pageMask)) - iterateNFTPages = false; - } - - auto& jvObjects = (jvResult[jss::account_objects] = Json::arrayValue); - - // this is a mutable version of limit, used to seamlessly switch - // to iterating directory entries when nftokenpages are exhausted - uint32_t mlimit = limit; - - // iterate NFTokenPages preferentially - if (iterateNFTPages) - { - Keylet const first = entryIndex == beast::zero - ? firstNFTPage - : Keylet{ltNFTOKEN_PAGE, entryIndex}; - - Keylet const last = keylet::nftpage_max(account); - - // current key - uint256 ck = ledger.succ(first.key, last.key.next()).value_or(last.key); - - // current page - auto cp = ledger.read(Keylet{ltNFTOKEN_PAGE, ck}); - - while (cp) - { - jvObjects.append(cp->getJson(JsonOptions::none)); - auto const npm = (*cp)[~sfNextPageMin]; - if (npm) - cp = ledger.read(Keylet(ltNFTOKEN_PAGE, *npm)); - else - cp = nullptr; - - if (--mlimit == 0) - { - if (cp) - { - jvResult[jss::limit] = limit; - jvResult[jss::marker] = std::string("0,") + to_string(ck); - return true; - } - } - - if (!npm) - break; - - ck = *npm; - } - - // if execution reaches here then we're about to transition - // to iterating the root directory (and the conventional - // behaviour of this RPC function.) Therefore we should - // zero entryIndex so as not to terribly confuse things. - entryIndex = beast::zero; - } - - auto const root = keylet::ownerDir(account); - auto found = false; - - if (dirIndex.isZero()) - { - dirIndex = root.key; - found = true; - } - - auto dir = ledger.read({ltDIR_NODE, dirIndex}); - if (!dir) - { - // it's possible the user had nftoken pages but no - // directory entries. If there's no nftoken page, we will - // give empty array for account_objects. - if (mlimit >= limit) - jvResult[jss::account_objects] = Json::arrayValue; - - // non-zero dirIndex validity was checked in the beginning of this - // function; by this point, it should be zero. This function returns - // true regardless of nftoken page presence; if absent, account_objects - // is already set as an empty array. Notice we will only return false in - // this function when entryIndex can not be found, indicating an invalid - // marker error. - return true; - } - - std::uint32_t i = 0; - for (;;) - { - auto const& entries = dir->getFieldV256(sfIndexes); - auto iter = entries.begin(); - - if (!found) - { - iter = std::find(iter, entries.end(), entryIndex); - if (iter == entries.end()) - return false; - - found = true; - } - - // it's possible that the returned NFTPages exactly filled the - // response. Check for that condition. - if (i == mlimit && mlimit < limit) - { - jvResult[jss::limit] = limit; - jvResult[jss::marker] = - to_string(dirIndex) + ',' + to_string(*iter); - return true; - } - - for (; iter != entries.end(); ++iter) - { - auto const sleNode = ledger.read(keylet::child(*iter)); - - if (!typeFilter.has_value() || - typeMatchesFilter(typeFilter.value(), sleNode->getType())) - { - jvObjects.append(sleNode->getJson(JsonOptions::none)); - } - - if (++i == mlimit) - { - if (++iter != entries.end()) - { - jvResult[jss::limit] = limit; - jvResult[jss::marker] = - to_string(dirIndex) + ',' + to_string(*iter); - return true; - } - - break; - } - } - - auto const nodeIndex = dir->getFieldU64(sfIndexNext); - if (nodeIndex == 0) - return true; - - dirIndex = keylet::page(root, nodeIndex).key; - dir = ledger.read({ltDIR_NODE, dirIndex}); - if (!dir) - return true; - - if (i == mlimit) - { - auto const& e = dir->getFieldV256(sfIndexes); - if (!e.empty()) - { - jvResult[jss::limit] = limit; - jvResult[jss::marker] = - to_string(dirIndex) + ',' + to_string(*e.begin()); - } - - return true; - } - } -} - -namespace { - -bool -isValidatedOld(LedgerMaster& ledgerMaster, bool standalone) -{ - if (standalone) - return false; - - return ledgerMaster.getValidatedLedgerAge() > Tuning::maxValidatedLedgerAge; -} - -template -Status -ledgerFromRequest(T& ledger, JsonContext& context) -{ - ledger.reset(); - - auto& params = context.params; - - auto indexValue = params[jss::ledger_index]; - auto hashValue = params[jss::ledger_hash]; - - // We need to support the legacy "ledger" field. - auto& legacyLedger = params[jss::ledger]; - if (legacyLedger) - { - if (legacyLedger.asString().size() > 12) - hashValue = legacyLedger; - else - indexValue = legacyLedger; - } - - if (!hashValue.isNull()) - { - if (!hashValue.isString()) - return {rpcINVALID_PARAMS, "ledgerHashNotString"}; - - uint256 ledgerHash; - if (!ledgerHash.parseHex(hashValue.asString())) - return {rpcINVALID_PARAMS, "ledgerHashMalformed"}; - return getLedger(ledger, ledgerHash, context); - } - - if (!indexValue.isConvertibleTo(Json::stringValue)) - return {rpcINVALID_PARAMS, "ledgerIndexMalformed"}; - - auto const index = indexValue.asString(); - - if (index == "current" || index.empty()) - return getLedger(ledger, LedgerShortcut::CURRENT, context); - - if (index == "validated") - return getLedger(ledger, LedgerShortcut::VALIDATED, context); - - if (index == "closed") - return getLedger(ledger, LedgerShortcut::CLOSED, context); - - std::uint32_t val; - if (!beast::lexicalCastChecked(val, index)) - return {rpcINVALID_PARAMS, "ledgerIndexMalformed"}; - - return getLedger(ledger, val, context); -} -} // namespace - -template -Status -ledgerFromRequest(T& ledger, GRPCContext& context) -{ - R& request = context.params; - return ledgerFromSpecifier(ledger, request.ledger(), context); -} - -// explicit instantiation of above function -template Status -ledgerFromRequest<>( - std::shared_ptr&, - GRPCContext&); - -// explicit instantiation of above function -template Status -ledgerFromRequest<>( - std::shared_ptr&, - GRPCContext&); - -// explicit instantiation of above function -template Status -ledgerFromRequest<>( - std::shared_ptr&, - GRPCContext&); - -template -Status -ledgerFromSpecifier( - T& ledger, - org::xrpl::rpc::v1::LedgerSpecifier const& specifier, - Context& context) -{ - ledger.reset(); - - using LedgerCase = org::xrpl::rpc::v1::LedgerSpecifier::LedgerCase; - LedgerCase ledgerCase = specifier.ledger_case(); - switch (ledgerCase) - { - case LedgerCase::kHash: { - if (auto hash = uint256::fromVoidChecked(specifier.hash())) - { - return getLedger(ledger, *hash, context); - } - return {rpcINVALID_PARAMS, "ledgerHashMalformed"}; - } - case LedgerCase::kSequence: - return getLedger(ledger, specifier.sequence(), context); - case LedgerCase::kShortcut: - [[fallthrough]]; - case LedgerCase::LEDGER_NOT_SET: { - auto const shortcut = specifier.shortcut(); - if (shortcut == - org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_VALIDATED) - { - return getLedger(ledger, LedgerShortcut::VALIDATED, context); - } - else - { - if (shortcut == - org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_CURRENT || - shortcut == - org::xrpl::rpc::v1::LedgerSpecifier:: - SHORTCUT_UNSPECIFIED) - { - return getLedger(ledger, LedgerShortcut::CURRENT, context); - } - else if ( - shortcut == - org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_CLOSED) - { - return getLedger(ledger, LedgerShortcut::CLOSED, context); - } - } - } - } - - return Status::OK; -} - -template -Status -getLedger(T& ledger, uint256 const& ledgerHash, Context& context) -{ - ledger = context.ledgerMaster.getLedgerByHash(ledgerHash); - if (ledger == nullptr) - return {rpcLGR_NOT_FOUND, "ledgerNotFound"}; - return Status::OK; -} - -template -Status -getLedger(T& ledger, uint32_t ledgerIndex, Context& context) -{ - ledger = context.ledgerMaster.getLedgerBySeq(ledgerIndex); - if (ledger == nullptr) - { - auto cur = context.ledgerMaster.getCurrentLedger(); - if (cur->info().seq == ledgerIndex) - { - ledger = cur; - } - } - - if (ledger == nullptr) - return {rpcLGR_NOT_FOUND, "ledgerNotFound"}; - - if (ledger->info().seq > context.ledgerMaster.getValidLedgerIndex() && - isValidatedOld(context.ledgerMaster, context.app.config().standalone())) - { - ledger.reset(); - if (context.apiVersion == 1) - return {rpcNO_NETWORK, "InsufficientNetworkMode"}; - return {rpcNOT_SYNCED, "notSynced"}; - } - - return Status::OK; -} - -template -Status -getLedger(T& ledger, LedgerShortcut shortcut, Context& context) -{ - if (isValidatedOld(context.ledgerMaster, context.app.config().standalone())) - { - if (context.apiVersion == 1) - return {rpcNO_NETWORK, "InsufficientNetworkMode"}; - return {rpcNOT_SYNCED, "notSynced"}; - } - - if (shortcut == LedgerShortcut::VALIDATED) - { - ledger = context.ledgerMaster.getValidatedLedger(); - if (ledger == nullptr) - { - if (context.apiVersion == 1) - return {rpcNO_NETWORK, "InsufficientNetworkMode"}; - return {rpcNOT_SYNCED, "notSynced"}; - } - - XRPL_ASSERT( - !ledger->open(), "ripple::RPC::getLedger : validated is not open"); - } - else - { - if (shortcut == LedgerShortcut::CURRENT) - { - ledger = context.ledgerMaster.getCurrentLedger(); - XRPL_ASSERT( - ledger->open(), "ripple::RPC::getLedger : current is open"); - } - else if (shortcut == LedgerShortcut::CLOSED) - { - ledger = context.ledgerMaster.getClosedLedger(); - XRPL_ASSERT( - !ledger->open(), "ripple::RPC::getLedger : closed is not open"); - } - else - { - return {rpcINVALID_PARAMS, "ledgerIndexMalformed"}; - } - - if (ledger == nullptr) - { - if (context.apiVersion == 1) - return {rpcNO_NETWORK, "InsufficientNetworkMode"}; - return {rpcNOT_SYNCED, "notSynced"}; - } - - static auto const minSequenceGap = 10; - - if (ledger->info().seq + minSequenceGap < - context.ledgerMaster.getValidLedgerIndex()) - { - ledger.reset(); - if (context.apiVersion == 1) - return {rpcNO_NETWORK, "InsufficientNetworkMode"}; - return {rpcNOT_SYNCED, "notSynced"}; - } - } - return Status::OK; -} - -// Explicit instantiation of above three functions -template Status -getLedger<>(std::shared_ptr&, uint32_t, Context&); - -template Status -getLedger<>( - std::shared_ptr&, - LedgerShortcut shortcut, - Context&); - -template Status -getLedger<>(std::shared_ptr&, uint256 const&, Context&); - -// The previous version of the lookupLedger command would accept the -// "ledger_index" argument as a string and silently treat it as a request to -// return the current ledger which, while not strictly wrong, could cause a lot -// of confusion. -// -// The code now robustly validates the input and ensures that the only possible -// values for the "ledger_index" parameter are the index of a ledger passed as -// an integer or one of the strings "current", "closed" or "validated". -// Additionally, the code ensures that the value passed in "ledger_hash" is a -// string and a valid hash. Invalid values will return an appropriate error -// code. -// -// In the absence of the "ledger_hash" or "ledger_index" parameters, the code -// assumes that "ledger_index" has the value "current". -// -// Returns a Json::objectValue. If there was an error, it will be in that -// return value. Otherwise, the object contains the field "validated" and -// optionally the fields "ledger_hash", "ledger_index" and -// "ledger_current_index", if they are defined. -Status -lookupLedger( - std::shared_ptr& ledger, - JsonContext& context, - Json::Value& result) -{ - if (auto status = ledgerFromRequest(ledger, context)) - return status; - - auto& info = ledger->info(); - - if (!ledger->open()) - { - result[jss::ledger_hash] = to_string(info.hash); - result[jss::ledger_index] = info.seq; - } - else - { - result[jss::ledger_current_index] = info.seq; - } - - result[jss::validated] = context.ledgerMaster.isValidated(*ledger); - return Status::OK; -} - -Json::Value -lookupLedger(std::shared_ptr& ledger, JsonContext& context) -{ - Json::Value result; - if (auto status = lookupLedger(ledger, context, result)) - status.inject(result); - - return result; -} - hash_set parseAccountIds(Json::Value const& jvArray) { @@ -988,123 +481,5 @@ isAccountObjectsValidType(LedgerEntryType const& type) } } -std::variant, Json::Value> -getLedgerByContext(RPC::JsonContext& context) -{ - auto const hasHash = context.params.isMember(jss::ledger_hash); - auto const hasIndex = context.params.isMember(jss::ledger_index); - std::uint32_t ledgerIndex = 0; - - auto& ledgerMaster = context.app.getLedgerMaster(); - LedgerHash ledgerHash; - - if ((hasHash && hasIndex) || !(hasHash || hasIndex)) - { - return RPC::make_param_error( - "Exactly one of ledger_hash and ledger_index can be set."); - } - - context.loadType = Resource::feeHeavyBurdenRPC; - - if (hasHash) - { - auto const& jsonHash = context.params[jss::ledger_hash]; - if (!jsonHash.isString() || !ledgerHash.parseHex(jsonHash.asString())) - return RPC::invalid_field_error(jss::ledger_hash); - } - else - { - auto const& jsonIndex = context.params[jss::ledger_index]; - if (!jsonIndex.isInt()) - return RPC::invalid_field_error(jss::ledger_index); - - // We need a validated ledger to get the hash from the sequence - if (ledgerMaster.getValidatedLedgerAge() > - RPC::Tuning::maxValidatedLedgerAge) - { - if (context.apiVersion == 1) - return rpcError(rpcNO_CURRENT); - return rpcError(rpcNOT_SYNCED); - } - - ledgerIndex = jsonIndex.asInt(); - auto ledger = ledgerMaster.getValidatedLedger(); - - if (ledgerIndex >= ledger->info().seq) - return RPC::make_param_error("Ledger index too large"); - if (ledgerIndex <= 0) - return RPC::make_param_error("Ledger index too small"); - - auto const j = context.app.journal("RPCHandler"); - // Try to get the hash of the desired ledger from the validated - // ledger - auto neededHash = hashOfSeq(*ledger, ledgerIndex, j); - if (!neededHash) - { - // Find a ledger more likely to have the hash of the desired - // ledger - auto const refIndex = getCandidateLedger(ledgerIndex); - auto refHash = hashOfSeq(*ledger, refIndex, j); - XRPL_ASSERT( - refHash, - "ripple::RPC::getLedgerByContext : nonzero ledger hash"); - - ledger = ledgerMaster.getLedgerByHash(*refHash); - if (!ledger) - { - // We don't have the ledger we need to figure out which - // ledger they want. Try to get it. - - if (auto il = context.app.getInboundLedgers().acquire( - *refHash, refIndex, InboundLedger::Reason::GENERIC)) - { - Json::Value jvResult = RPC::make_error( - rpcLGR_NOT_FOUND, - "acquiring ledger containing requested index"); - jvResult[jss::acquiring] = - getJson(LedgerFill(*il, &context)); - return jvResult; - } - - if (auto il = context.app.getInboundLedgers().find(*refHash)) - { - Json::Value jvResult = RPC::make_error( - rpcLGR_NOT_FOUND, - "acquiring ledger containing requested index"); - jvResult[jss::acquiring] = il->getJson(0); - return jvResult; - } - - // Likely the app is shutting down - return Json::Value(); - } - - neededHash = hashOfSeq(*ledger, ledgerIndex, j); - } - XRPL_ASSERT( - neededHash, - "ripple::RPC::getLedgerByContext : nonzero needed hash"); - ledgerHash = neededHash ? *neededHash : beast::zero; // kludge - } - - // Try to get the desired ledger - // Verify all nodes even if we think we have it - auto ledger = context.app.getInboundLedgers().acquire( - ledgerHash, ledgerIndex, InboundLedger::Reason::GENERIC); - - // In standalone mode, accept the ledger from the ledger cache - if (!ledger && context.app.config().standalone()) - ledger = ledgerMaster.getLedgerByHash(ledgerHash); - - if (ledger) - return ledger; - - if (auto il = context.app.getInboundLedgers().find(ledgerHash)) - return il->getJson(0); - - return RPC::make_error( - rpcNOT_READY, "findCreate failed to return an inbound ledger"); -} - } // namespace RPC } // namespace ripple diff --git a/src/xrpld/rpc/detail/RPCHelpers.h b/src/xrpld/rpc/detail/RPCHelpers.h index 9ee4376411..808b975de3 100644 --- a/src/xrpld/rpc/detail/RPCHelpers.h +++ b/src/xrpld/rpc/detail/RPCHelpers.h @@ -73,81 +73,6 @@ isRelatedToAccount( std::shared_ptr const& sle, AccountID const& accountID); -/** Gathers all objects for an account in a ledger. - @param ledger Ledger to search account objects. - @param account AccountID to find objects for. - @param typeFilter Gathers objects of these types. empty gathers all types. - @param dirIndex Begin gathering account objects from this directory. - @param entryIndex Begin gathering objects from this directory node. - @param limit Maximum number of objects to find. - @param jvResult A JSON result that holds the request objects. -*/ -bool -getAccountObjects( - ReadView const& ledger, - AccountID const& account, - std::optional> const& typeFilter, - uint256 dirIndex, - uint256 entryIndex, - std::uint32_t const limit, - Json::Value& jvResult); - -/** Get ledger by hash - If there is no error in the return value, the ledger pointer will have - been filled -*/ -template -Status -getLedger(T& ledger, uint256 const& ledgerHash, Context& context); - -/** Get ledger by sequence - If there is no error in the return value, the ledger pointer will have - been filled -*/ -template -Status -getLedger(T& ledger, uint32_t ledgerIndex, Context& context); - -enum LedgerShortcut { CURRENT, CLOSED, VALIDATED }; -/** Get ledger specified in shortcut. - If there is no error in the return value, the ledger pointer will have - been filled -*/ -template -Status -getLedger(T& ledger, LedgerShortcut shortcut, Context& context); - -/** Look up a ledger from a request and fill a Json::Result with either - an error, or data representing a ledger. - - If there is no error in the return value, then the ledger pointer will have - been filled. -*/ -Json::Value -lookupLedger(std::shared_ptr&, JsonContext&); - -/** Look up a ledger from a request and fill a Json::Result with the data - representing a ledger. - - If the returned Status is OK, the ledger pointer will have been filled. -*/ -Status -lookupLedger( - std::shared_ptr&, - JsonContext&, - Json::Value& result); - -template -Status -ledgerFromRequest(T& ledger, GRPCContext& context); - -template -Status -ledgerFromSpecifier( - T& ledger, - org::xrpl::rpc::v1::LedgerSpecifier const& specifier, - Context& context); - hash_set parseAccountIds(Json::Value const& jvArray); @@ -194,11 +119,6 @@ chooseLedgerEntryType(Json::Value const& params); bool isAccountObjectsValidType(LedgerEntryType const& type); -/** Return a ledger based on ledger_hash or ledger_index, - or an RPC error */ -std::variant, Json::Value> -getLedgerByContext(RPC::JsonContext& context); - std::optional> keypairForSignature( Json::Value const& params, diff --git a/src/xrpld/rpc/detail/RPCLedgerHelpers.cpp b/src/xrpld/rpc/detail/RPCLedgerHelpers.cpp new file mode 100644 index 0000000000..8ba7eeddcd --- /dev/null +++ b/src/xrpld/rpc/detail/RPCLedgerHelpers.cpp @@ -0,0 +1,458 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +namespace ripple { +namespace RPC { + +namespace { + +bool +isValidatedOld(LedgerMaster& ledgerMaster, bool standalone) +{ + if (standalone) + return false; + + return ledgerMaster.getValidatedLedgerAge() > Tuning::maxValidatedLedgerAge; +} + +template +Status +ledgerFromRequest(T& ledger, JsonContext& context) +{ + ledger.reset(); + + auto& params = context.params; + + auto indexValue = params[jss::ledger_index]; + auto hashValue = params[jss::ledger_hash]; + + // We need to support the legacy "ledger" field. + auto& legacyLedger = params[jss::ledger]; + if (legacyLedger) + { + if (legacyLedger.asString().size() > 12) + hashValue = legacyLedger; + else + indexValue = legacyLedger; + } + + if (!hashValue.isNull()) + { + if (!hashValue.isString()) + return {rpcINVALID_PARAMS, "ledgerHashNotString"}; + + uint256 ledgerHash; + if (!ledgerHash.parseHex(hashValue.asString())) + return {rpcINVALID_PARAMS, "ledgerHashMalformed"}; + return getLedger(ledger, ledgerHash, context); + } + + if (!indexValue.isConvertibleTo(Json::stringValue)) + return {rpcINVALID_PARAMS, "ledgerIndexMalformed"}; + + auto const index = indexValue.asString(); + + if (index == "current" || index.empty()) + return getLedger(ledger, LedgerShortcut::CURRENT, context); + + if (index == "validated") + return getLedger(ledger, LedgerShortcut::VALIDATED, context); + + if (index == "closed") + return getLedger(ledger, LedgerShortcut::CLOSED, context); + + std::uint32_t val; + if (!beast::lexicalCastChecked(val, index)) + return {rpcINVALID_PARAMS, "ledgerIndexMalformed"}; + + return getLedger(ledger, val, context); +} +} // namespace + +template +Status +ledgerFromRequest(T& ledger, GRPCContext& context) +{ + R& request = context.params; + return ledgerFromSpecifier(ledger, request.ledger(), context); +} + +// explicit instantiation of above function +template Status +ledgerFromRequest<>( + std::shared_ptr&, + GRPCContext&); + +// explicit instantiation of above function +template Status +ledgerFromRequest<>( + std::shared_ptr&, + GRPCContext&); + +// explicit instantiation of above function +template Status +ledgerFromRequest<>( + std::shared_ptr&, + GRPCContext&); + +template +Status +ledgerFromSpecifier( + T& ledger, + org::xrpl::rpc::v1::LedgerSpecifier const& specifier, + Context& context) +{ + ledger.reset(); + + using LedgerCase = org::xrpl::rpc::v1::LedgerSpecifier::LedgerCase; + LedgerCase ledgerCase = specifier.ledger_case(); + switch (ledgerCase) + { + case LedgerCase::kHash: { + if (auto hash = uint256::fromVoidChecked(specifier.hash())) + { + return getLedger(ledger, *hash, context); + } + return {rpcINVALID_PARAMS, "ledgerHashMalformed"}; + } + case LedgerCase::kSequence: + return getLedger(ledger, specifier.sequence(), context); + case LedgerCase::kShortcut: + [[fallthrough]]; + case LedgerCase::LEDGER_NOT_SET: { + auto const shortcut = specifier.shortcut(); + if (shortcut == + org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_VALIDATED) + { + return getLedger(ledger, LedgerShortcut::VALIDATED, context); + } + else + { + if (shortcut == + org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_CURRENT || + shortcut == + org::xrpl::rpc::v1::LedgerSpecifier:: + SHORTCUT_UNSPECIFIED) + { + return getLedger(ledger, LedgerShortcut::CURRENT, context); + } + else if ( + shortcut == + org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_CLOSED) + { + return getLedger(ledger, LedgerShortcut::CLOSED, context); + } + } + } + } + + return Status::OK; +} + +template +Status +getLedger(T& ledger, uint256 const& ledgerHash, Context& context) +{ + ledger = context.ledgerMaster.getLedgerByHash(ledgerHash); + if (ledger == nullptr) + return {rpcLGR_NOT_FOUND, "ledgerNotFound"}; + return Status::OK; +} + +template +Status +getLedger(T& ledger, uint32_t ledgerIndex, Context& context) +{ + ledger = context.ledgerMaster.getLedgerBySeq(ledgerIndex); + if (ledger == nullptr) + { + auto cur = context.ledgerMaster.getCurrentLedger(); + if (cur->info().seq == ledgerIndex) + { + ledger = cur; + } + } + + if (ledger == nullptr) + return {rpcLGR_NOT_FOUND, "ledgerNotFound"}; + + if (ledger->info().seq > context.ledgerMaster.getValidLedgerIndex() && + isValidatedOld(context.ledgerMaster, context.app.config().standalone())) + { + ledger.reset(); + if (context.apiVersion == 1) + return {rpcNO_NETWORK, "InsufficientNetworkMode"}; + return {rpcNOT_SYNCED, "notSynced"}; + } + + return Status::OK; +} + +template +Status +getLedger(T& ledger, LedgerShortcut shortcut, Context& context) +{ + if (isValidatedOld(context.ledgerMaster, context.app.config().standalone())) + { + if (context.apiVersion == 1) + return {rpcNO_NETWORK, "InsufficientNetworkMode"}; + return {rpcNOT_SYNCED, "notSynced"}; + } + + if (shortcut == LedgerShortcut::VALIDATED) + { + ledger = context.ledgerMaster.getValidatedLedger(); + if (ledger == nullptr) + { + if (context.apiVersion == 1) + return {rpcNO_NETWORK, "InsufficientNetworkMode"}; + return {rpcNOT_SYNCED, "notSynced"}; + } + + XRPL_ASSERT( + !ledger->open(), "ripple::RPC::getLedger : validated is not open"); + } + else + { + if (shortcut == LedgerShortcut::CURRENT) + { + ledger = context.ledgerMaster.getCurrentLedger(); + XRPL_ASSERT( + ledger->open(), "ripple::RPC::getLedger : current is open"); + } + else if (shortcut == LedgerShortcut::CLOSED) + { + ledger = context.ledgerMaster.getClosedLedger(); + XRPL_ASSERT( + !ledger->open(), "ripple::RPC::getLedger : closed is not open"); + } + else + { + return {rpcINVALID_PARAMS, "ledgerIndexMalformed"}; + } + + if (ledger == nullptr) + { + if (context.apiVersion == 1) + return {rpcNO_NETWORK, "InsufficientNetworkMode"}; + return {rpcNOT_SYNCED, "notSynced"}; + } + + static auto const minSequenceGap = 10; + + if (ledger->info().seq + minSequenceGap < + context.ledgerMaster.getValidLedgerIndex()) + { + ledger.reset(); + if (context.apiVersion == 1) + return {rpcNO_NETWORK, "InsufficientNetworkMode"}; + return {rpcNOT_SYNCED, "notSynced"}; + } + } + return Status::OK; +} + +// Explicit instantiation of above three functions +template Status +getLedger<>(std::shared_ptr&, uint32_t, Context&); + +template Status +getLedger<>( + std::shared_ptr&, + LedgerShortcut shortcut, + Context&); + +template Status +getLedger<>(std::shared_ptr&, uint256 const&, Context&); + +// The previous version of the lookupLedger command would accept the +// "ledger_index" argument as a string and silently treat it as a request to +// return the current ledger which, while not strictly wrong, could cause a lot +// of confusion. +// +// The code now robustly validates the input and ensures that the only possible +// values for the "ledger_index" parameter are the index of a ledger passed as +// an integer or one of the strings "current", "closed" or "validated". +// Additionally, the code ensures that the value passed in "ledger_hash" is a +// string and a valid hash. Invalid values will return an appropriate error +// code. +// +// In the absence of the "ledger_hash" or "ledger_index" parameters, the code +// assumes that "ledger_index" has the value "current". +// +// Returns a Json::objectValue. If there was an error, it will be in that +// return value. Otherwise, the object contains the field "validated" and +// optionally the fields "ledger_hash", "ledger_index" and +// "ledger_current_index", if they are defined. +Status +lookupLedger( + std::shared_ptr& ledger, + JsonContext& context, + Json::Value& result) +{ + if (auto status = ledgerFromRequest(ledger, context)) + return status; + + auto& info = ledger->info(); + + if (!ledger->open()) + { + result[jss::ledger_hash] = to_string(info.hash); + result[jss::ledger_index] = info.seq; + } + else + { + result[jss::ledger_current_index] = info.seq; + } + + result[jss::validated] = context.ledgerMaster.isValidated(*ledger); + return Status::OK; +} + +Json::Value +lookupLedger(std::shared_ptr& ledger, JsonContext& context) +{ + Json::Value result; + if (auto status = lookupLedger(ledger, context, result)) + status.inject(result); + + return result; +} + +std::variant, Json::Value> +getLedgerByContext(RPC::JsonContext& context) +{ + auto const hasHash = context.params.isMember(jss::ledger_hash); + auto const hasIndex = context.params.isMember(jss::ledger_index); + std::uint32_t ledgerIndex = 0; + + auto& ledgerMaster = context.app.getLedgerMaster(); + LedgerHash ledgerHash; + + if ((hasHash && hasIndex) || !(hasHash || hasIndex)) + { + return RPC::make_param_error( + "Exactly one of ledger_hash and ledger_index can be set."); + } + + context.loadType = Resource::feeHeavyBurdenRPC; + + if (hasHash) + { + auto const& jsonHash = context.params[jss::ledger_hash]; + if (!jsonHash.isString() || !ledgerHash.parseHex(jsonHash.asString())) + return RPC::invalid_field_error(jss::ledger_hash); + } + else + { + auto const& jsonIndex = context.params[jss::ledger_index]; + if (!jsonIndex.isInt()) + return RPC::invalid_field_error(jss::ledger_index); + + // We need a validated ledger to get the hash from the sequence + if (ledgerMaster.getValidatedLedgerAge() > + RPC::Tuning::maxValidatedLedgerAge) + { + if (context.apiVersion == 1) + return rpcError(rpcNO_CURRENT); + return rpcError(rpcNOT_SYNCED); + } + + ledgerIndex = jsonIndex.asInt(); + auto ledger = ledgerMaster.getValidatedLedger(); + + if (ledgerIndex >= ledger->info().seq) + return RPC::make_param_error("Ledger index too large"); + if (ledgerIndex <= 0) + return RPC::make_param_error("Ledger index too small"); + + auto const j = context.app.journal("RPCHandler"); + // Try to get the hash of the desired ledger from the validated + // ledger + auto neededHash = hashOfSeq(*ledger, ledgerIndex, j); + if (!neededHash) + { + // Find a ledger more likely to have the hash of the desired + // ledger + auto const refIndex = getCandidateLedger(ledgerIndex); + auto refHash = hashOfSeq(*ledger, refIndex, j); + XRPL_ASSERT( + refHash, + "ripple::RPC::getLedgerByContext : nonzero ledger hash"); + + ledger = ledgerMaster.getLedgerByHash(*refHash); + if (!ledger) + { + // We don't have the ledger we need to figure out which + // ledger they want. Try to get it. + + if (auto il = context.app.getInboundLedgers().acquire( + *refHash, refIndex, InboundLedger::Reason::GENERIC)) + { + Json::Value jvResult = RPC::make_error( + rpcLGR_NOT_FOUND, + "acquiring ledger containing requested index"); + jvResult[jss::acquiring] = + getJson(LedgerFill(*il, &context)); + return jvResult; + } + + if (auto il = context.app.getInboundLedgers().find(*refHash)) + { + Json::Value jvResult = RPC::make_error( + rpcLGR_NOT_FOUND, + "acquiring ledger containing requested index"); + jvResult[jss::acquiring] = il->getJson(0); + return jvResult; + } + + // Likely the app is shutting down + return Json::Value(); + } + + neededHash = hashOfSeq(*ledger, ledgerIndex, j); + } + XRPL_ASSERT( + neededHash, + "ripple::RPC::getLedgerByContext : nonzero needed hash"); + ledgerHash = neededHash ? *neededHash : beast::zero; // kludge + } + + // Try to get the desired ledger + // Verify all nodes even if we think we have it + auto ledger = context.app.getInboundLedgers().acquire( + ledgerHash, ledgerIndex, InboundLedger::Reason::GENERIC); + + // In standalone mode, accept the ledger from the ledger cache + if (!ledger && context.app.config().standalone()) + ledger = ledgerMaster.getLedgerByHash(ledgerHash); + + if (ledger) + return ledger; + + if (auto il = context.app.getInboundLedgers().find(ledgerHash)) + return il->getJson(0); + + return RPC::make_error( + rpcNOT_READY, "findCreate failed to return an inbound ledger"); +} + +} // namespace RPC +} // namespace ripple diff --git a/src/xrpld/rpc/detail/RPCLedgerHelpers.h b/src/xrpld/rpc/detail/RPCLedgerHelpers.h new file mode 100644 index 0000000000..17382d3c53 --- /dev/null +++ b/src/xrpld/rpc/detail/RPCLedgerHelpers.h @@ -0,0 +1,96 @@ +#ifndef XRPL_RPC_RPCLEDGERHELPERS_H_INCLUDED +#define XRPL_RPC_RPCLEDGERHELPERS_H_INCLUDED + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +namespace Json { +class Value; +} + +namespace ripple { + +class ReadView; +class Transaction; + +namespace RPC { + +struct JsonContext; + +/** Get ledger by hash + If there is no error in the return value, the ledger pointer will have + been filled +*/ +template +Status +getLedger(T& ledger, uint256 const& ledgerHash, Context& context); + +/** Get ledger by sequence + If there is no error in the return value, the ledger pointer will have + been filled +*/ +template +Status +getLedger(T& ledger, uint32_t ledgerIndex, Context& context); + +enum LedgerShortcut { CURRENT, CLOSED, VALIDATED }; +/** Get ledger specified in shortcut. + If there is no error in the return value, the ledger pointer will have + been filled +*/ +template +Status +getLedger(T& ledger, LedgerShortcut shortcut, Context& context); + +/** Look up a ledger from a request and fill a Json::Result with either + an error, or data representing a ledger. + + If there is no error in the return value, then the ledger pointer will have + been filled. +*/ +Json::Value +lookupLedger(std::shared_ptr&, JsonContext&); + +/** Look up a ledger from a request and fill a Json::Result with the data + representing a ledger. + + If the returned Status is OK, the ledger pointer will have been filled. +*/ +Status +lookupLedger( + std::shared_ptr&, + JsonContext&, + Json::Value& result); + +template +Status +ledgerFromRequest(T& ledger, GRPCContext& context); + +template +Status +ledgerFromSpecifier( + T& ledger, + org::xrpl::rpc::v1::LedgerSpecifier const& specifier, + Context& context); + +/** Return a ledger based on ledger_hash or ledger_index, + or an RPC error */ +std::variant, Json::Value> +getLedgerByContext(RPC::JsonContext& context); + +} // namespace RPC + +} // namespace ripple + +#endif diff --git a/src/xrpld/rpc/handlers/AMMInfo.cpp b/src/xrpld/rpc/handlers/AMMInfo.cpp index 01e9f0cd16..dda30fcfa7 100644 --- a/src/xrpld/rpc/handlers/AMMInfo.cpp +++ b/src/xrpld/rpc/handlers/AMMInfo.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include diff --git a/src/xrpld/rpc/handlers/AccountChannels.cpp b/src/xrpld/rpc/handlers/AccountChannels.cpp index e1f86630c5..281b0ec8c4 100644 --- a/src/xrpld/rpc/handlers/AccountChannels.cpp +++ b/src/xrpld/rpc/handlers/AccountChannels.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include diff --git a/src/xrpld/rpc/handlers/AccountCurrenciesHandler.cpp b/src/xrpld/rpc/handlers/AccountCurrenciesHandler.cpp index ca165e7592..10f22ad654 100644 --- a/src/xrpld/rpc/handlers/AccountCurrenciesHandler.cpp +++ b/src/xrpld/rpc/handlers/AccountCurrenciesHandler.cpp @@ -1,6 +1,6 @@ #include #include -#include +#include #include #include diff --git a/src/xrpld/rpc/handlers/AccountInfo.cpp b/src/xrpld/rpc/handlers/AccountInfo.cpp index 40800934b5..683077be6e 100644 --- a/src/xrpld/rpc/handlers/AccountInfo.cpp +++ b/src/xrpld/rpc/handlers/AccountInfo.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include diff --git a/src/xrpld/rpc/handlers/AccountLines.cpp b/src/xrpld/rpc/handlers/AccountLines.cpp index 22ca1bc2f9..b28f4d7483 100644 --- a/src/xrpld/rpc/handlers/AccountLines.cpp +++ b/src/xrpld/rpc/handlers/AccountLines.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include diff --git a/src/xrpld/rpc/handlers/AccountObjects.cpp b/src/xrpld/rpc/handlers/AccountObjects.cpp index f68ab5dfae..82ff1782e4 100644 --- a/src/xrpld/rpc/handlers/AccountObjects.cpp +++ b/src/xrpld/rpc/handlers/AccountObjects.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include @@ -158,6 +159,198 @@ doAccountNFTs(RPC::JsonContext& context) return result; } +bool +getAccountObjects( + ReadView const& ledger, + AccountID const& account, + std::optional> const& typeFilter, + uint256 dirIndex, + uint256 entryIndex, + std::uint32_t const limit, + Json::Value& jvResult) +{ + // check if dirIndex is valid + if (!dirIndex.isZero() && !ledger.read({ltDIR_NODE, dirIndex})) + return false; + + auto typeMatchesFilter = [](std::vector const& typeFilter, + LedgerEntryType ledgerType) { + auto it = std::find(typeFilter.begin(), typeFilter.end(), ledgerType); + return it != typeFilter.end(); + }; + + // if dirIndex != 0, then all NFTs have already been returned. only + // iterate NFT pages if the filter says so AND dirIndex == 0 + bool iterateNFTPages = + (!typeFilter.has_value() || + typeMatchesFilter(typeFilter.value(), ltNFTOKEN_PAGE)) && + dirIndex == beast::zero; + + Keylet const firstNFTPage = keylet::nftpage_min(account); + + // we need to check the marker to see if it is an NFTTokenPage index. + if (iterateNFTPages && entryIndex != beast::zero) + { + // if it is we will try to iterate the pages up to the limit + // and then change over to the owner directory + + if (firstNFTPage.key != (entryIndex & ~nft::pageMask)) + iterateNFTPages = false; + } + + auto& jvObjects = (jvResult[jss::account_objects] = Json::arrayValue); + + // this is a mutable version of limit, used to seamlessly switch + // to iterating directory entries when nftokenpages are exhausted + uint32_t mlimit = limit; + + // iterate NFTokenPages preferentially + if (iterateNFTPages) + { + Keylet const first = entryIndex == beast::zero + ? firstNFTPage + : Keylet{ltNFTOKEN_PAGE, entryIndex}; + + Keylet const last = keylet::nftpage_max(account); + + // current key + uint256 ck = ledger.succ(first.key, last.key.next()).value_or(last.key); + + // current page + auto cp = ledger.read(Keylet{ltNFTOKEN_PAGE, ck}); + + while (cp) + { + jvObjects.append(cp->getJson(JsonOptions::none)); + auto const npm = (*cp)[~sfNextPageMin]; + if (npm) + cp = ledger.read(Keylet(ltNFTOKEN_PAGE, *npm)); + else + cp = nullptr; + + if (--mlimit == 0) + { + if (cp) + { + jvResult[jss::limit] = limit; + jvResult[jss::marker] = std::string("0,") + to_string(ck); + return true; + } + } + + if (!npm) + break; + + ck = *npm; + } + + // if execution reaches here then we're about to transition + // to iterating the root directory (and the conventional + // behaviour of this RPC function.) Therefore we should + // zero entryIndex so as not to terribly confuse things. + entryIndex = beast::zero; + } + + auto const root = keylet::ownerDir(account); + auto found = false; + + if (dirIndex.isZero()) + { + dirIndex = root.key; + found = true; + } + + auto dir = ledger.read({ltDIR_NODE, dirIndex}); + if (!dir) + { + // it's possible the user had nftoken pages but no + // directory entries. If there's no nftoken page, we will + // give empty array for account_objects. + if (mlimit >= limit) + jvResult[jss::account_objects] = Json::arrayValue; + + // non-zero dirIndex validity was checked in the beginning of this + // function; by this point, it should be zero. This function returns + // true regardless of nftoken page presence; if absent, account_objects + // is already set as an empty array. Notice we will only return false in + // this function when entryIndex can not be found, indicating an invalid + // marker error. + return true; + } + + std::uint32_t i = 0; + for (;;) + { + auto const& entries = dir->getFieldV256(sfIndexes); + auto iter = entries.begin(); + + if (!found) + { + iter = std::find(iter, entries.end(), entryIndex); + if (iter == entries.end()) + return false; + + found = true; + } + + // it's possible that the returned NFTPages exactly filled the + // response. Check for that condition. + if (i == mlimit && mlimit < limit) + { + jvResult[jss::limit] = limit; + jvResult[jss::marker] = + to_string(dirIndex) + ',' + to_string(*iter); + return true; + } + + for (; iter != entries.end(); ++iter) + { + auto const sleNode = ledger.read(keylet::child(*iter)); + + if (!typeFilter.has_value() || + typeMatchesFilter(typeFilter.value(), sleNode->getType())) + { + jvObjects.append(sleNode->getJson(JsonOptions::none)); + } + + if (++i == mlimit) + { + if (++iter != entries.end()) + { + jvResult[jss::limit] = limit; + jvResult[jss::marker] = + to_string(dirIndex) + ',' + to_string(*iter); + return true; + } + + break; + } + } + + auto const nodeIndex = dir->getFieldU64(sfIndexNext); + if (nodeIndex == 0) + return true; + + dirIndex = keylet::page(root, nodeIndex).key; + dir = ledger.read({ltDIR_NODE, dirIndex}); + if (!dir) + return true; + + if (i == mlimit) + { + auto const& e = dir->getFieldV256(sfIndexes); + if (!e.empty()) + { + jvResult[jss::limit] = limit; + jvResult[jss::marker] = + to_string(dirIndex) + ',' + to_string(*e.begin()); + } + + return true; + } + } +} + Json::Value doAccountObjects(RPC::JsonContext& context) { @@ -265,7 +458,7 @@ doAccountObjects(RPC::JsonContext& context) return RPC::invalid_field_error(jss::marker); } - if (!RPC::getAccountObjects( + if (!getAccountObjects( *ledger, accountID, typeFilter, diff --git a/src/xrpld/rpc/handlers/AccountOffers.cpp b/src/xrpld/rpc/handlers/AccountOffers.cpp index acba153554..e8bc2bcf79 100644 --- a/src/xrpld/rpc/handlers/AccountOffers.cpp +++ b/src/xrpld/rpc/handlers/AccountOffers.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include diff --git a/src/xrpld/rpc/handlers/BookOffers.cpp b/src/xrpld/rpc/handlers/BookOffers.cpp index d0544460d1..e82b2fa345 100644 --- a/src/xrpld/rpc/handlers/BookOffers.cpp +++ b/src/xrpld/rpc/handlers/BookOffers.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include diff --git a/src/xrpld/rpc/handlers/DepositAuthorized.cpp b/src/xrpld/rpc/handlers/DepositAuthorized.cpp index 7490dbea94..707db63e36 100644 --- a/src/xrpld/rpc/handlers/DepositAuthorized.cpp +++ b/src/xrpld/rpc/handlers/DepositAuthorized.cpp @@ -1,5 +1,5 @@ #include -#include +#include #include #include diff --git a/src/xrpld/rpc/handlers/GatewayBalances.cpp b/src/xrpld/rpc/handlers/GatewayBalances.cpp index 3e614c2a24..88411b020e 100644 --- a/src/xrpld/rpc/handlers/GatewayBalances.cpp +++ b/src/xrpld/rpc/handlers/GatewayBalances.cpp @@ -1,7 +1,7 @@ #include #include #include -#include +#include #include #include diff --git a/src/xrpld/rpc/handlers/GetAggregatePrice.cpp b/src/xrpld/rpc/handlers/GetAggregatePrice.cpp index a1121e260b..c74a4331fd 100644 --- a/src/xrpld/rpc/handlers/GetAggregatePrice.cpp +++ b/src/xrpld/rpc/handlers/GetAggregatePrice.cpp @@ -1,7 +1,7 @@ #include #include #include -#include +#include #include #include diff --git a/src/xrpld/rpc/handlers/LedgerData.cpp b/src/xrpld/rpc/handlers/LedgerData.cpp index d5f27a138e..c5e39f69b4 100644 --- a/src/xrpld/rpc/handlers/LedgerData.cpp +++ b/src/xrpld/rpc/handlers/LedgerData.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include diff --git a/src/xrpld/rpc/handlers/LedgerDiff.cpp b/src/xrpld/rpc/handlers/LedgerDiff.cpp index d713f43bfd..3628138329 100644 --- a/src/xrpld/rpc/handlers/LedgerDiff.cpp +++ b/src/xrpld/rpc/handlers/LedgerDiff.cpp @@ -1,5 +1,5 @@ #include -#include +#include namespace ripple { std::pair diff --git a/src/xrpld/rpc/handlers/LedgerEntry.cpp b/src/xrpld/rpc/handlers/LedgerEntry.cpp index 22b43c5a31..b8b17e6b59 100644 --- a/src/xrpld/rpc/handlers/LedgerEntry.cpp +++ b/src/xrpld/rpc/handlers/LedgerEntry.cpp @@ -1,6 +1,6 @@ #include #include -#include +#include #include #include diff --git a/src/xrpld/rpc/handlers/LedgerHandler.cpp b/src/xrpld/rpc/handlers/LedgerHandler.cpp index f282d0ddd3..4483838fa0 100644 --- a/src/xrpld/rpc/handlers/LedgerHandler.cpp +++ b/src/xrpld/rpc/handlers/LedgerHandler.cpp @@ -3,7 +3,7 @@ #include #include #include -#include +#include #include #include diff --git a/src/xrpld/rpc/handlers/LedgerHeader.cpp b/src/xrpld/rpc/handlers/LedgerHeader.cpp index 193ca355d3..ebbd6beb01 100644 --- a/src/xrpld/rpc/handlers/LedgerHeader.cpp +++ b/src/xrpld/rpc/handlers/LedgerHeader.cpp @@ -1,5 +1,5 @@ #include -#include +#include #include #include diff --git a/src/xrpld/rpc/handlers/LedgerRequest.cpp b/src/xrpld/rpc/handlers/LedgerRequest.cpp index ea3208605f..fd06ab30ba 100644 --- a/src/xrpld/rpc/handlers/LedgerRequest.cpp +++ b/src/xrpld/rpc/handlers/LedgerRequest.cpp @@ -1,6 +1,6 @@ #include #include -#include +#include #include #include diff --git a/src/xrpld/rpc/handlers/NFTOffers.cpp b/src/xrpld/rpc/handlers/NFTOffers.cpp index 6d11ff5b0a..51290a404c 100644 --- a/src/xrpld/rpc/handlers/NFTOffers.cpp +++ b/src/xrpld/rpc/handlers/NFTOffers.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include diff --git a/src/xrpld/rpc/handlers/NoRippleCheck.cpp b/src/xrpld/rpc/handlers/NoRippleCheck.cpp index e5c1170969..9ff2757a40 100644 --- a/src/xrpld/rpc/handlers/NoRippleCheck.cpp +++ b/src/xrpld/rpc/handlers/NoRippleCheck.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include diff --git a/src/xrpld/rpc/handlers/RipplePathFind.cpp b/src/xrpld/rpc/handlers/RipplePathFind.cpp index 1cfea8dd5c..771c1cb95f 100644 --- a/src/xrpld/rpc/handlers/RipplePathFind.cpp +++ b/src/xrpld/rpc/handlers/RipplePathFind.cpp @@ -2,7 +2,7 @@ #include #include #include -#include +#include #include #include diff --git a/src/xrpld/rpc/handlers/TransactionEntry.cpp b/src/xrpld/rpc/handlers/TransactionEntry.cpp index 5dd8d685e1..9553c2d41e 100644 --- a/src/xrpld/rpc/handlers/TransactionEntry.cpp +++ b/src/xrpld/rpc/handlers/TransactionEntry.cpp @@ -1,7 +1,7 @@ #include #include #include -#include +#include #include #include diff --git a/src/xrpld/rpc/handlers/VaultInfo.cpp b/src/xrpld/rpc/handlers/VaultInfo.cpp index 3e8226352a..7e040fef82 100644 --- a/src/xrpld/rpc/handlers/VaultInfo.cpp +++ b/src/xrpld/rpc/handlers/VaultInfo.cpp @@ -1,5 +1,5 @@ #include -#include +#include #include #include