diff --git a/.cspell.config.yaml b/.cspell.config.yaml index 04f5dab8ca..4d1935a87f 100644 --- a/.cspell.config.yaml +++ b/.cspell.config.yaml @@ -64,6 +64,7 @@ words: - blindings - bookdir - Bougalis + - bthomee - Britto - Btrfs - Buildx diff --git a/.github/workflows/reusable-build-test-config.yml b/.github/workflows/reusable-build-test-config.yml index a37ab386b8..2a0c9332f3 100644 --- a/.github/workflows/reusable-build-test-config.yml +++ b/.github/workflows/reusable-build-test-config.yml @@ -439,7 +439,7 @@ jobs: - name: Upload coverage report if: ${{ github.repository_owner == 'XRPLF' && !inputs.build_only && env.COVERAGE_ENABLED == 'true' }} - uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 + uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7.1.1 with: disable_search: true disable_telem: true diff --git a/.github/workflows/reusable-rust.yml b/.github/workflows/reusable-rust.yml index 12a31aacbd..bdd5f75ca6 100644 --- a/.github/workflows/reusable-rust.yml +++ b/.github/workflows/reusable-rust.yml @@ -57,7 +57,7 @@ jobs: - name: Upload coverage report if: ${{ github.repository == 'XRPLF/rippled' }} - uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 + uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7.1.1 with: disable_search: true disable_telem: true diff --git a/include/xrpl/basics/base_uint.h b/include/xrpl/basics/base_uint.h index bee8b8b945..3786f28c6a 100644 --- a/include/xrpl/basics/base_uint.h +++ b/include/xrpl/basics/base_uint.h @@ -518,7 +518,7 @@ public: * The input must be precisely `2 * bytes` hexadecimal characters * long, with one exception: the value '0'. * - * @param sv A null-terminated string of hexadecimal characters + * @param sv A string of hexadecimal characters * @return true if the input was parsed properly; false otherwise. */ [[nodiscard]] constexpr bool diff --git a/include/xrpl/ledger/entries/AMMEntry.h b/include/xrpl/ledger/entries/AMMEntry.h new file mode 100644 index 0000000000..0c1c0fe629 --- /dev/null +++ b/include/xrpl/ledger/entries/AMMEntry.h @@ -0,0 +1,45 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl { + +template +class AMMEntry : public SLEBase +{ +public: + using Base = SLEBase; + + // Inherit base constructors: adopt an existing SLE, or resolve one from a + // Keylet against the view. + using Base::Base; + + explicit AMMEntry( + Asset const& issue1, + Asset const& issue2, + Base::ViewRefType view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : Base(keylet::amm(issue1, issue2), view, j) + { + } + + explicit AMMEntry( + uint256 const& ammID, + Base::ViewRefType view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : Base(keylet::amm(ammID), view, j) + { + } +}; + +using AMMEntryR = AMMEntry; +using AMMEntryW = AMMEntry; + +} // namespace xrpl diff --git a/include/xrpl/ledger/entries/AccountRootEntry.h b/include/xrpl/ledger/entries/AccountRootEntry.h new file mode 100644 index 0000000000..5555ea8c50 --- /dev/null +++ b/include/xrpl/ledger/entries/AccountRootEntry.h @@ -0,0 +1,35 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl { + +template +class AccountRootEntry : public SLEBase +{ +public: + using Base = SLEBase; + + // Inherit base constructors: adopt an existing SLE, or resolve one from a + // Keylet against the view. + using Base::Base; + + explicit AccountRootEntry( + AccountID const& id, + Base::ViewRefType view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : Base(keylet::account(id), view, j) + { + } +}; + +using AccountRootEntryR = AccountRootEntry; +using AccountRootEntryW = AccountRootEntry; + +} // namespace xrpl diff --git a/include/xrpl/ledger/entries/AmendmentsEntry.h b/include/xrpl/ledger/entries/AmendmentsEntry.h new file mode 100644 index 0000000000..ce4dca7e80 --- /dev/null +++ b/include/xrpl/ledger/entries/AmendmentsEntry.h @@ -0,0 +1,33 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace xrpl { + +template +class AmendmentsEntry : public SLEBase +{ +public: + using Base = SLEBase; + + // Inherit base constructors: adopt an existing SLE, or resolve one from a + // Keylet against the view. + using Base::Base; + + explicit AmendmentsEntry( + Base::ViewRefType view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : Base(keylet::amendments(), view, j) + { + } +}; + +using AmendmentsEntryR = AmendmentsEntry; +using AmendmentsEntryW = AmendmentsEntry; + +} // namespace xrpl diff --git a/include/xrpl/ledger/entries/BridgeEntry.h b/include/xrpl/ledger/entries/BridgeEntry.h new file mode 100644 index 0000000000..a51fbe0c12 --- /dev/null +++ b/include/xrpl/ledger/entries/BridgeEntry.h @@ -0,0 +1,36 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl { + +template +class BridgeEntry : public SLEBase +{ +public: + using Base = SLEBase; + + // Inherit base constructors: adopt an existing SLE, or resolve one from a + // Keylet against the view. + using Base::Base; + + explicit BridgeEntry( + STXChainBridge const& bridge, + STXChainBridge::ChainType chainType, + Base::ViewRefType view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : Base(keylet::bridge(bridge, chainType), view, j) + { + } +}; + +using BridgeEntryR = BridgeEntry; +using BridgeEntryW = BridgeEntry; + +} // namespace xrpl diff --git a/include/xrpl/ledger/entries/CheckEntry.h b/include/xrpl/ledger/entries/CheckEntry.h new file mode 100644 index 0000000000..bf6a188ff5 --- /dev/null +++ b/include/xrpl/ledger/entries/CheckEntry.h @@ -0,0 +1,46 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl { + +template +class CheckEntry : public SLEBase +{ +public: + using Base = SLEBase; + + // Inherit base constructors: adopt an existing SLE, or resolve one from a + // Keylet against the view. + using Base::Base; + + explicit CheckEntry( + AccountID const& id, + SeqProxy const& seq, + Base::ViewRefType view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : Base(keylet::check(id, seq), view, j) + { + } + + explicit CheckEntry( + uint256 const& checkID, + Base::ViewRefType view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : Base(keylet::check(checkID), view, j) + { + } +}; + +using CheckEntryR = CheckEntry; +using CheckEntryW = CheckEntry; + +} // namespace xrpl diff --git a/include/xrpl/ledger/entries/CredentialEntry.h b/include/xrpl/ledger/entries/CredentialEntry.h new file mode 100644 index 0000000000..4b6a3386a1 --- /dev/null +++ b/include/xrpl/ledger/entries/CredentialEntry.h @@ -0,0 +1,47 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl { + +template +class CredentialEntry : public SLEBase +{ +public: + using Base = SLEBase; + + // Inherit base constructors: adopt an existing SLE, or resolve one from a + // Keylet against the view. + using Base::Base; + + explicit CredentialEntry( + AccountID const& subject, + AccountID const& issuer, + Slice const& credType, + Base::ViewRefType view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : Base(keylet::credential(subject, issuer, credType), view, j) + { + } + + explicit CredentialEntry( + uint256 const& credentialID, + Base::ViewRefType view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : Base(keylet::credential(credentialID), view, j) + { + } +}; + +using CredentialEntryR = CredentialEntry; +using CredentialEntryW = CredentialEntry; + +} // namespace xrpl diff --git a/include/xrpl/ledger/entries/DIDEntry.h b/include/xrpl/ledger/entries/DIDEntry.h new file mode 100644 index 0000000000..ee3eaf8153 --- /dev/null +++ b/include/xrpl/ledger/entries/DIDEntry.h @@ -0,0 +1,35 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl { + +template +class DIDEntry : public SLEBase +{ +public: + using Base = SLEBase; + + // Inherit base constructors: adopt an existing SLE, or resolve one from a + // Keylet against the view. + using Base::Base; + + explicit DIDEntry( + AccountID const& account, + Base::ViewRefType view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : Base(keylet::did(account), view, j) + { + } +}; + +using DIDEntryR = DIDEntry; +using DIDEntryW = DIDEntry; + +} // namespace xrpl diff --git a/include/xrpl/ledger/entries/DelegateEntry.h b/include/xrpl/ledger/entries/DelegateEntry.h new file mode 100644 index 0000000000..de908d4dcd --- /dev/null +++ b/include/xrpl/ledger/entries/DelegateEntry.h @@ -0,0 +1,36 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl { + +template +class DelegateEntry : public SLEBase +{ +public: + using Base = SLEBase; + + // Inherit base constructors: adopt an existing SLE, or resolve one from a + // Keylet against the view. + using Base::Base; + + explicit DelegateEntry( + AccountID const& account, + AccountID const& authorizedAccount, + Base::ViewRefType view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : Base(keylet::delegate(account, authorizedAccount), view, j) + { + } +}; + +using DelegateEntryR = DelegateEntry; +using DelegateEntryW = DelegateEntry; + +} // namespace xrpl diff --git a/include/xrpl/ledger/entries/DepositPreauthEntry.h b/include/xrpl/ledger/entries/DepositPreauthEntry.h new file mode 100644 index 0000000000..783363084d --- /dev/null +++ b/include/xrpl/ledger/entries/DepositPreauthEntry.h @@ -0,0 +1,58 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace xrpl { + +template +class DepositPreauthEntry : public SLEBase +{ +public: + using Base = SLEBase; + + // Inherit base constructors: adopt an existing SLE, or resolve one from a + // Keylet against the view. + using Base::Base; + + explicit DepositPreauthEntry( + AccountID const& owner, + AccountID const& preauthorized, + Base::ViewRefType view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : Base(keylet::depositPreauth(owner, preauthorized), view, j) + { + } + + explicit DepositPreauthEntry( + AccountID const& owner, + std::set> const& authCreds, + Base::ViewRefType view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : Base(keylet::depositPreauth(owner, authCreds), view, j) + { + } + + explicit DepositPreauthEntry( + uint256 const& preauthID, + Base::ViewRefType view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : Base(keylet::depositPreauth(preauthID), view, j) + { + } +}; + +using DepositPreauthEntryR = DepositPreauthEntry; +using DepositPreauthEntryW = DepositPreauthEntry; + +} // namespace xrpl diff --git a/include/xrpl/ledger/entries/DirectoryNodeEntry.h b/include/xrpl/ledger/entries/DirectoryNodeEntry.h new file mode 100644 index 0000000000..b57bec7348 --- /dev/null +++ b/include/xrpl/ledger/entries/DirectoryNodeEntry.h @@ -0,0 +1,50 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace xrpl { + +template +class DirectoryNodeEntry : public SLEBase +{ +public: + using Base = SLEBase; + + // Inherit base constructors: adopt an existing SLE, or resolve one from a + // Keylet against the view. + using Base::Base; + + explicit DirectoryNodeEntry( + AccountID const& id, + Base::ViewRefType view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : Base(keylet::ownerDir(id), view, j) + { + } + + /** + * Resolve a specific page of the directory rooted at @p root. + */ + explicit DirectoryNodeEntry( + uint256 const& root, + std::uint64_t index, + Base::ViewRefType view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : Base(keylet::page(root, index), view, j) + { + } +}; + +using DirectoryNodeEntryR = DirectoryNodeEntry; +using DirectoryNodeEntryW = DirectoryNodeEntry; + +} // namespace xrpl diff --git a/include/xrpl/ledger/entries/EscrowEntry.h b/include/xrpl/ledger/entries/EscrowEntry.h new file mode 100644 index 0000000000..9eb1418a4c --- /dev/null +++ b/include/xrpl/ledger/entries/EscrowEntry.h @@ -0,0 +1,37 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl { + +template +class EscrowEntry : public SLEBase +{ +public: + using Base = SLEBase; + + // Inherit base constructors: adopt an existing SLE, or resolve one from a + // Keylet against the view. + using Base::Base; + + explicit EscrowEntry( + AccountID const& src, + SeqProxy const& seq, + Base::ViewRefType view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : Base(keylet::escrow(src, seq), view, j) + { + } +}; + +using EscrowEntryR = EscrowEntry; +using EscrowEntryW = EscrowEntry; + +} // namespace xrpl diff --git a/include/xrpl/ledger/entries/FeeSettingsEntry.h b/include/xrpl/ledger/entries/FeeSettingsEntry.h new file mode 100644 index 0000000000..66d61892e5 --- /dev/null +++ b/include/xrpl/ledger/entries/FeeSettingsEntry.h @@ -0,0 +1,33 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace xrpl { + +template +class FeeSettingsEntry : public SLEBase +{ +public: + using Base = SLEBase; + + // Inherit base constructors: adopt an existing SLE, or resolve one from a + // Keylet against the view. + using Base::Base; + + explicit FeeSettingsEntry( + Base::ViewRefType view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : Base(keylet::feeSettings(), view, j) + { + } +}; + +using FeeSettingsEntryR = FeeSettingsEntry; +using FeeSettingsEntryW = FeeSettingsEntry; + +} // namespace xrpl diff --git a/include/xrpl/ledger/entries/LedgerHashesEntry.h b/include/xrpl/ledger/entries/LedgerHashesEntry.h new file mode 100644 index 0000000000..ab54d820a1 --- /dev/null +++ b/include/xrpl/ledger/entries/LedgerHashesEntry.h @@ -0,0 +1,33 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace xrpl { + +template +class LedgerHashesEntry : public SLEBase +{ +public: + using Base = SLEBase; + + // Inherit base constructors: adopt an existing SLE, or resolve one from a + // Keylet against the view. + using Base::Base; + + explicit LedgerHashesEntry( + Base::ViewRefType view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : Base(keylet::skip(), view, j) + { + } +}; + +using LedgerHashesEntryR = LedgerHashesEntry; +using LedgerHashesEntryW = LedgerHashesEntry; + +} // namespace xrpl diff --git a/include/xrpl/ledger/entries/LoanBrokerEntry.h b/include/xrpl/ledger/entries/LoanBrokerEntry.h new file mode 100644 index 0000000000..77e6b660ed --- /dev/null +++ b/include/xrpl/ledger/entries/LoanBrokerEntry.h @@ -0,0 +1,46 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl { + +template +class LoanBrokerEntry : public SLEBase +{ +public: + using Base = SLEBase; + + // Inherit base constructors: adopt an existing SLE, or resolve one from a + // Keylet against the view. + using Base::Base; + + explicit LoanBrokerEntry( + AccountID const& owner, + SeqProxy const& seq, + Base::ViewRefType view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : Base(keylet::loanBroker(owner, seq), view, j) + { + } + + explicit LoanBrokerEntry( + uint256 const& loanBrokerID, + Base::ViewRefType view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : Base(keylet::loanBroker(loanBrokerID), view, j) + { + } +}; + +using LoanBrokerEntryR = LoanBrokerEntry; +using LoanBrokerEntryW = LoanBrokerEntry; + +} // namespace xrpl diff --git a/include/xrpl/ledger/entries/LoanEntry.h b/include/xrpl/ledger/entries/LoanEntry.h new file mode 100644 index 0000000000..124c5915fc --- /dev/null +++ b/include/xrpl/ledger/entries/LoanEntry.h @@ -0,0 +1,45 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl { + +template +class LoanEntry : public SLEBase +{ +public: + using Base = SLEBase; + + // Inherit base constructors: adopt an existing SLE, or resolve one from a + // Keylet against the view. + using Base::Base; + + explicit LoanEntry( + uint256 const& loanBrokerID, + SeqProxy const& loanSeq, + Base::ViewRefType view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : Base(keylet::loan(loanBrokerID, loanSeq), view, j) + { + } + + explicit LoanEntry( + uint256 const& loanID, + Base::ViewRefType view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : Base(keylet::loan(loanID), view, j) + { + } +}; + +using LoanEntryR = LoanEntry; +using LoanEntryW = LoanEntry; + +} // namespace xrpl diff --git a/include/xrpl/ledger/entries/MPTokenEntry.h b/include/xrpl/ledger/entries/MPTokenEntry.h new file mode 100644 index 0000000000..dcd6c5adb1 --- /dev/null +++ b/include/xrpl/ledger/entries/MPTokenEntry.h @@ -0,0 +1,55 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl { + +template +class MPTokenEntry : public SLEBase +{ +public: + using Base = SLEBase; + + // Inherit base constructors: adopt an existing SLE, or resolve one from a + // Keylet against the view. + using Base::Base; + + explicit MPTokenEntry( + MPTID const& issuanceID, + AccountID const& holder, + Base::ViewRefType view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : Base(keylet::mptoken(issuanceID, holder), view, j) + { + } + + explicit MPTokenEntry( + uint256 const& issuanceKey, + AccountID const& holder, + Base::ViewRefType view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : Base(keylet::mptoken(issuanceKey, holder), view, j) + { + } + + explicit MPTokenEntry( + uint256 const& mptokenKey, + Base::ViewRefType view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : Base(keylet::mptoken(mptokenKey), view, j) + { + } +}; + +using MPTokenEntryR = MPTokenEntry; +using MPTokenEntryW = MPTokenEntry; + +} // namespace xrpl diff --git a/include/xrpl/ledger/entries/MPTokenIssuanceEntry.h b/include/xrpl/ledger/entries/MPTokenIssuanceEntry.h new file mode 100644 index 0000000000..52dac53217 --- /dev/null +++ b/include/xrpl/ledger/entries/MPTokenIssuanceEntry.h @@ -0,0 +1,56 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace xrpl { + +template +class MPTokenIssuanceEntry : public SLEBase +{ +public: + using Base = SLEBase; + + // Inherit base constructors: adopt an existing SLE, or resolve one from a + // Keylet against the view. + using Base::Base; + + explicit MPTokenIssuanceEntry( + std::uint32_t seq, + AccountID const& issuer, + Base::ViewRefType view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : Base(keylet::mptokenIssuance(makeMptID(seq, issuer)), view, j) + { + } + + explicit MPTokenIssuanceEntry( + MPTID const& issuanceID, + Base::ViewRefType view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : Base(keylet::mptokenIssuance(issuanceID), view, j) + { + } + + explicit MPTokenIssuanceEntry( + uint256 const& issuanceKey, + Base::ViewRefType view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : Base(keylet::mptokenIssuance(issuanceKey), view, j) + { + } +}; + +using MPTokenIssuanceEntryR = MPTokenIssuanceEntry; +using MPTokenIssuanceEntryW = MPTokenIssuanceEntry; + +} // namespace xrpl diff --git a/include/xrpl/ledger/entries/NFTokenOfferEntry.h b/include/xrpl/ledger/entries/NFTokenOfferEntry.h new file mode 100644 index 0000000000..a2e71652cd --- /dev/null +++ b/include/xrpl/ledger/entries/NFTokenOfferEntry.h @@ -0,0 +1,46 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl { + +template +class NFTokenOfferEntry : public SLEBase +{ +public: + using Base = SLEBase; + + // Inherit base constructors: adopt an existing SLE, or resolve one from a + // Keylet against the view. + using Base::Base; + + explicit NFTokenOfferEntry( + AccountID const& owner, + SeqProxy const& seq, + Base::ViewRefType view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : Base(keylet::nftokenOffer(owner, seq), view, j) + { + } + + explicit NFTokenOfferEntry( + uint256 const& offerID, + Base::ViewRefType view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : Base(keylet::nftokenOffer(offerID), view, j) + { + } +}; + +using NFTokenOfferEntryR = NFTokenOfferEntry; +using NFTokenOfferEntryW = NFTokenOfferEntry; + +} // namespace xrpl diff --git a/include/xrpl/ledger/entries/NFTokenPageEntry.h b/include/xrpl/ledger/entries/NFTokenPageEntry.h new file mode 100644 index 0000000000..11701f17ea --- /dev/null +++ b/include/xrpl/ledger/entries/NFTokenPageEntry.h @@ -0,0 +1,37 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl { + +template +class NFTokenPageEntry : public SLEBase +{ +public: + using Base = SLEBase; + + // Inherit base constructors: adopt an existing SLE, or resolve one from a + // Keylet against the view. + using Base::Base; + + explicit NFTokenPageEntry( + Keylet const& page, + uint256 const& token, + Base::ViewRefType view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : Base(keylet::nftokenPage(page, token), view, j) + { + } +}; + +using NFTokenPageEntryR = NFTokenPageEntry; +using NFTokenPageEntryW = NFTokenPageEntry; + +} // namespace xrpl diff --git a/include/xrpl/ledger/entries/NegativeUNLEntry.h b/include/xrpl/ledger/entries/NegativeUNLEntry.h new file mode 100644 index 0000000000..9c61058622 --- /dev/null +++ b/include/xrpl/ledger/entries/NegativeUNLEntry.h @@ -0,0 +1,33 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace xrpl { + +template +class NegativeUNLEntry : public SLEBase +{ +public: + using Base = SLEBase; + + // Inherit base constructors: adopt an existing SLE, or resolve one from a + // Keylet against the view. + using Base::Base; + + explicit NegativeUNLEntry( + Base::ViewRefType view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : Base(keylet::negativeUNL(), view, j) + { + } +}; + +using NegativeUNLEntryR = NegativeUNLEntry; +using NegativeUNLEntryW = NegativeUNLEntry; + +} // namespace xrpl diff --git a/include/xrpl/ledger/entries/OfferEntry.h b/include/xrpl/ledger/entries/OfferEntry.h new file mode 100644 index 0000000000..2180d3c89c --- /dev/null +++ b/include/xrpl/ledger/entries/OfferEntry.h @@ -0,0 +1,46 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl { + +template +class OfferEntry : public SLEBase +{ +public: + using Base = SLEBase; + + // Inherit base constructors: adopt an existing SLE, or resolve one from a + // Keylet against the view. + using Base::Base; + + explicit OfferEntry( + AccountID const& id, + SeqProxy const& seq, + Base::ViewRefType view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : Base(keylet::offer(id, seq), view, j) + { + } + + explicit OfferEntry( + uint256 const& offerID, + Base::ViewRefType view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : Base(keylet::offer(offerID), view, j) + { + } +}; + +using OfferEntryR = OfferEntry; +using OfferEntryW = OfferEntry; + +} // namespace xrpl diff --git a/include/xrpl/ledger/entries/OracleEntry.h b/include/xrpl/ledger/entries/OracleEntry.h new file mode 100644 index 0000000000..69013a2745 --- /dev/null +++ b/include/xrpl/ledger/entries/OracleEntry.h @@ -0,0 +1,38 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace xrpl { + +template +class OracleEntry : public SLEBase +{ +public: + using Base = SLEBase; + + // Inherit base constructors: adopt an existing SLE, or resolve one from a + // Keylet against the view. + using Base::Base; + + explicit OracleEntry( + AccountID const& account, + std::uint32_t documentID, + Base::ViewRefType view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : Base(keylet::oracle(account, documentID), view, j) + { + } +}; + +using OracleEntryR = OracleEntry; +using OracleEntryW = OracleEntry; + +} // namespace xrpl diff --git a/include/xrpl/ledger/entries/PayChannelEntry.h b/include/xrpl/ledger/entries/PayChannelEntry.h new file mode 100644 index 0000000000..3540474633 --- /dev/null +++ b/include/xrpl/ledger/entries/PayChannelEntry.h @@ -0,0 +1,38 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl { + +template +class PayChannelEntry : public SLEBase +{ +public: + using Base = SLEBase; + + // Inherit base constructors: adopt an existing SLE, or resolve one from a + // Keylet against the view. + using Base::Base; + + explicit PayChannelEntry( + AccountID const& src, + AccountID const& dst, + SeqProxy const& seq, + Base::ViewRefType view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : Base(keylet::payChannel(src, dst, seq), view, j) + { + } +}; + +using PayChannelEntryR = PayChannelEntry; +using PayChannelEntryW = PayChannelEntry; + +} // namespace xrpl diff --git a/include/xrpl/ledger/entries/PermissionedDomainEntry.h b/include/xrpl/ledger/entries/PermissionedDomainEntry.h new file mode 100644 index 0000000000..0668a8f8b6 --- /dev/null +++ b/include/xrpl/ledger/entries/PermissionedDomainEntry.h @@ -0,0 +1,46 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl { + +template +class PermissionedDomainEntry : public SLEBase +{ +public: + using Base = SLEBase; + + // Inherit base constructors: adopt an existing SLE, or resolve one from a + // Keylet against the view. + using Base::Base; + + explicit PermissionedDomainEntry( + AccountID const& account, + SeqProxy const& seq, + Base::ViewRefType view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : Base(keylet::permissionedDomain(account, seq), view, j) + { + } + + explicit PermissionedDomainEntry( + uint256 const& domainID, + Base::ViewRefType view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : Base(keylet::permissionedDomain(domainID), view, j) + { + } +}; + +using PermissionedDomainEntryR = PermissionedDomainEntry; +using PermissionedDomainEntryW = PermissionedDomainEntry; + +} // namespace xrpl diff --git a/include/xrpl/ledger/entries/RippleStateEntry.h b/include/xrpl/ledger/entries/RippleStateEntry.h new file mode 100644 index 0000000000..f9bd8aa703 --- /dev/null +++ b/include/xrpl/ledger/entries/RippleStateEntry.h @@ -0,0 +1,48 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl { + +template +class RippleStateEntry : public SLEBase +{ +public: + using Base = SLEBase; + + // Inherit base constructors: adopt an existing SLE, or resolve one from a + // Keylet against the view. + using Base::Base; + + explicit RippleStateEntry( + AccountID const& id0, + AccountID const& id1, + Currency const& currency, + Base::ViewRefType view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : Base(keylet::trustLine(id0, id1, currency), view, j) + { + } + + explicit RippleStateEntry( + AccountID const& id, + Issue const& issue, + Base::ViewRefType view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : Base(keylet::trustLine(id, issue), view, j) + { + } +}; + +using RippleStateEntryR = RippleStateEntry; +using RippleStateEntryW = RippleStateEntry; + +} // namespace xrpl diff --git a/include/xrpl/ledger/entries/SLEBase.h b/include/xrpl/ledger/entries/SLEBase.h new file mode 100644 index 0000000000..182e03b55d --- /dev/null +++ b/include/xrpl/ledger/entries/SLEBase.h @@ -0,0 +1,503 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace xrpl { + +// Concept to distinguish read-only vs writable view types +template +concept IsWritableView = std::derived_from; + +namespace detail { + +/** + * Resolves a keylet for a read-only entry. + * + * ReadView::read() on an ApplyView returns the underlying ledger's entry + * whenever the view is not already tracking one, while peek() installs the + * view's own copy and returns that. A read-only entry built with read() + * would therefore hold an SLE that goes stale the moment anything peeks the + * same key and modifies it. Resolve through peek() whenever the view really is + * an ApplyView, so every entry over that view shares one SLE. + * + * @note The const_cast is what makes reaching ApplyView::peek() possible, and + * it is defined behavior only when the view really is a non-const + * object that the caller merely observes through a const reference. + * That holds for every production view today, but it is not a + * guarantee the codebase makes: the unit tests already build + * genuinely const ApplyView-derived objects (`Sandbox const` in + * Directory_test.cpp and View_test.cpp, `PaymentSandbox const` in + * TheoreticalQuality_test.cpp and View_test.cpp). Constructing a + * read-only entry over one of those would be undefined behavior, so + * do not, until #8069 removes the cast -- by giving ApplyView a + * const-qualified peek(), which needs no amendment because + * Action::Cache is invisible to apply(), visit() and metadata. + * + * @note Consequently a "read-only" entry over an ApplyView is not free of + * side effects: peek() installs an Action::Cache entry in the apply + * state table. That is benign for transaction metadata -- Cache entries + * are skipped in ApplyStateTable::apply(), ::visit() and in metadata + * generation -- but it does cost one deep SLE copy on first touch. + */ +inline SLE::const_pointer +resolveEntry(ReadView const& view, Keylet const& key) +{ + // Safe only for a view that is not itself a const object -- see the + // note above. The entry holds a const reference because it does not + // modify the view, not because the view is const. + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) + if (auto const applyView = dynamic_cast(const_cast(&view))) + return applyView->peek(key); + return view.read(key); +} + +} // namespace detail + +/** + * View-parameterized base class for all ledger entries. + * + * SLEBase — read-only: holds shared_ptr + ReadView const& + * SLEBase — writable: holds shared_ptr + ApplyView& + Keylet, + * plus insert/update/erase operations + * + * Write-only members are gated by `requires` clauses, providing compile-time + * guarantees that read-only entries cannot mutate state. + * + * @tparam EntryType the ledger entry type this entry is statically bound to. + * Derived per-type entries pass their own type (e.g. ltACCOUNT_ROOT); the + * generic ReadOnlySLE / WritableSLE aliases leave it at ltANY, which opts out + * of the static type check. Binding the type here is what keeps an entry for + * one entry type from being constructed or converted from another -- see the + * converting constructor below. + * + * Derived classes should provide domain-specific accessors that hide + * implementation details of the underlying ledger entry format. + */ +template +class SLEBase +{ +public: + static constexpr bool kIsWritable = IsWritableView; + + // The ledger entry type this entry is bound to, and whether that binding + // is meaningful (ltANY means "any type", i.e. no static check). + static constexpr LedgerEntryType kEntryType = EntryType; + static constexpr bool kIsTyped = (EntryType != ltANY); + + // SLE pointer type: mutable for writable views, const for read-only + using SlePtrType = std::conditional_t; + + // View reference type: ApplyView& for writable, ReadView const& for + // read-only + using ViewRefType = std::conditional_t; + + // Non-virtual by design: these entries are parameterized on the view and + // entry type, never used polymorphically through a base pointer. A vptr + // would be 8 bytes of pure overhead on a type meant to be as cheap as the + // shared_ptr it wraps. See the static_assert below the class. + // + // The destructor is public because the ReadOnlySLE / WritableSLE aliases + // name this class directly and are used as value types. Since it is not + // virtual, never delete a derived entry through an SLEBase*. + ~SLEBase() = default; + + SLEBase(SLEBase const&) + requires(!kIsWritable) + = default; + SLEBase(SLEBase&&) = default; + SLEBase& + operator=(SLEBase const&) = delete; + SLEBase& + operator=(SLEBase&&) = delete; + SLEBase() = delete; + + // --- Constructors that adopt/resolve an SLE (public so the ReadOnlySLE / + // WritableSLE aliases and the per-type entries can be built directly + // from a keylet, or -- read-only only -- from an already-fetched + // SLE). --- + + /** + * Constructor for read-only context (adopt an already-fetched SLE). + * + * There is deliberately no writable equivalent: a writable entry needs + * a Keylet so that newSLE() can still build an entry when none exists, + * and that cannot be recovered from a null SLE. + */ + explicit SLEBase( + SLE::const_pointer sle, + ViewRefType view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + requires(!kIsWritable) + : view_(view), sle_(std::move(sle)), j_(j) + { + XRPL_ASSERT( + !kIsTyped || !sle_ || sle_->getType() == kEntryType, + "xrpl::SLEBase::SLEBase : adopted SLE matches bound entry type"); + } + + /** + * Constructor for read-only context (read from view by keylet) + */ + explicit SLEBase( + Keylet const& key, + ViewRefType view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + requires(!kIsWritable) + : view_(view), sle_(detail::resolveEntry(view, key)), j_(j) + { + XRPL_ASSERT( + !kIsTyped || key.type == kEntryType, + "xrpl::SLEBase::SLEBase : keylet matches bound entry type"); + } + + /** + * Converting constructor: writable → read-only. + * + * Enables implicit conversion from SLEBase to + * SLEBase, so functions taking ReadOnlySLE const& can accept + * WritableSLE. + * + * Constrained to the same entry type (or to a ltANY target, i.e. widening + * a typed entry to a generic ReadOnlySLE). The constraint is load-bearing: + * this constructor is inherited into every per-type entry, and unconstrained + * it would bind any writable entry that slices to SLEBase, so an OfferEntryW + * would convert to an AccountRootEntryR with no cast at the call site. + */ + template + SLEBase(SLEBase const& other) + requires(!kIsWritable && IsWritableView && + (OtherType == EntryType || EntryType == ltANY)) + : view_(other.readView()), sle_(other.rawSle()), j_(other.journal()) + { + } + + /** + * Constructor for writable context (peek from view by keylet) + */ + explicit SLEBase( + Keylet const& key, + ApplyView& view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + requires kIsWritable + : view_(view), key_(key), sle_(view_.peek(key)), j_(j) + { + XRPL_ASSERT( + !kIsTyped || key.type == kEntryType, + "xrpl::SLEBase::SLEBase : keylet matches bound entry type"); + } + + /** + * Constructor for writable context, for call sites that hold an + * ApplyViewContext (peek from ctx.view by keylet). + * + * ctx.tx is not retained: this exists purely so transactors can pass the + * context they already have instead of spelling out ctx.view. If an entry + * ever needs the applying transaction, store it here rather than adding + * another overload. + */ + explicit SLEBase( + Keylet const& key, + ApplyViewContext const& ctx, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + requires kIsWritable + : SLEBase(key, ctx.view, j) + { + } + + // --- Common interface (always available) --- + + /** + * Returns true if the ledger entry exists + */ + [[nodiscard]] bool + exists() const + { + return sle_ != nullptr; + } + + /** + * Explicit conversion to bool for convenient existence checking + */ + explicit + operator bool() const + { + return exists(); + } + + /** + * Returns the underlying SLE for read access. + * + * Prefer operator-> / operator* for field access; this is for the call + * sites that need the shared_ptr itself. + */ + [[nodiscard]] SLE::const_pointer + rawSle() const + { + return sle_; + } + + /** + * Returns the ledger entry type of this entry. + * + * For a per-type entry this is kEntryType, known at compile time and + * valid whether or not the entry exists. Only the generic ReadOnlySLE / + * WritableSLE aliases have to read it back out of the SLE. + * + * @throws std::logic_error for a generic (ltANY) entry if exists() is + * false. + */ + [[nodiscard]] LedgerEntryType + type() const + { + if constexpr (kIsTyped) + { + return kEntryType; + } + else + { + if (!exists()) + Throw("xrpl::SLEBase::type : entry does not exist"); + return sle_->getType(); + } + } + + /** + * Returns the keylet identifying this entry. + * + * Writable entries keep the keylet they were built from, so it is valid + * even before newSLE(). Read-only entries derive it from the SLE, which + * must therefore exist. + * + * @throws std::logic_error for a read-only entry if exists() is false. + */ + [[nodiscard]] Keylet + keylet() const + { + if constexpr (kIsWritable) + { + return key_; + } + else + { + if (!exists()) + Throw("xrpl::SLEBase::keylet : entry does not exist"); + // Take the type from the SLE, not from kEntryType: the adopt-SLE + // constructor's type check is assert-only, so a Release build can + // be holding an SLE whose type disagrees with the binding, and the + // SLE is the one telling the truth. + return Keylet(sle_->getType(), sle_->key()); + } + } + + /** + * Returns the ledger key of this entry. + * + * @throws std::logic_error same as keylet(): for read-only entries, + * if exists() is false. + */ + [[nodiscard]] uint256 + key() const + { + return keylet().key; + } + + /** + * Returns the read view (always available; ApplyView inherits ReadView) + */ + [[nodiscard]] ReadView const& + readView() const + { + return view_; + } + + /** + * Const dereference operators (always available) + * + * @throws std::logic_error if exists() is false. + */ + STLedgerEntry const* + operator->() const + { + if (!exists()) + Throw("xrpl::SLEBase::operator-> : entry does not exist"); + return sle_.get(); + } + + STLedgerEntry const& + operator*() const + { + if (!exists()) + Throw("xrpl::SLEBase::operator* : entry does not exist"); + return *sle_; + } + + // --- Writable interface (compile-time gated) --- + // + // Everything that hands out mutable access (or mutates) is non-const, so + // that a `FooEntryW const&` is as inert as a `FooEntryR`. Use readView() + // when a const entry only needs to inspect the view. + + /** + * Returns the underlying SLE for write access. + * + * Prefer operator-> / operator* for field access; this is for the call + * sites that need the shared_ptr itself. + */ + [[nodiscard]] SlePtrType const& + mutableRawSle() + requires kIsWritable + { + return sle_; + } + + /** + * Returns the apply view for write operations + */ + [[nodiscard]] ApplyView& + applyView() + requires kIsWritable + { + return view_; + } + + /** + * Mutable dereference operators + * + * @throws std::logic_error if exists() is false. + */ + STLedgerEntry* + operator->() + requires kIsWritable + { + if (!exists()) + Throw("xrpl::SLEBase::operator-> : entry does not exist"); + return sle_.get(); + } + + STLedgerEntry& + operator*() + requires kIsWritable + { + if (!exists()) + Throw("xrpl::SLEBase::operator* : entry does not exist"); + return *sle_; + } + + /** + * Inserts the entry into the view. + * + * @throws std::logic_error if exists() is false. + */ + void + insert() + requires kIsWritable + { + if (!exists()) + Throw("xrpl::SLEBase::insert : entry does not exist"); + view_.insert(sle_); + } + + /** + * Erases the entry from the view. + * + * Drops the SLE afterwards, so the entry reports !exists() and any + * further use throws here rather than either throwing from deep inside + * ApplyStateTable or -- worse -- silently succeeding. For an + * entry that already existed, ApplyStateTable::erase keeps holding this + * exact SLE and builds the DeletedNode's FinalFields from it, so a write + * through the entry after erase() would land in transaction metadata + * with no diagnostic at all. + * + * @throws std::logic_error if exists() is false. + */ + void + erase() + requires kIsWritable + { + if (!exists()) + Throw("xrpl::SLEBase::erase : entry does not exist"); + view_.erase(sle_); + sle_ = nullptr; + } + + /** + * @throws std::logic_error if exists() is false. + */ + void + update() + requires kIsWritable + { + if (!exists()) + Throw("xrpl::SLEBase::update : entry does not exist"); + view_.update(sle_); + } + + /** + * @throws std::logic_error if exists() is true: newSLE() would otherwise + * silently discard the SLE already held. + */ + void + newSLE() + requires kIsWritable + { + if (exists()) + Throw("xrpl::SLEBase::newSLE : entry already exists"); + sle_ = std::make_shared(key_); + } + + [[nodiscard]] beast::Journal + journal() const + { + return j_; + } + +protected: + ViewRefType view_; + + // Keylet is only meaningful for writable views, which need it to build an + // SLE that does not exist yet; read-only entries derive it from the SLE. + struct Empty + { + }; + + // No default member initializer: Keylet is not default-constructible, so + // every writable constructor must initialize key_ explicitly. + [[no_unique_address]] + std::conditional_t key_; + + SlePtrType sle_{}; + beast::Journal j_; +}; + +/** + * Generic (any-entry-type) SLE entries. + * + * Use these when the concrete ledger entry type is not known at a given site; + * otherwise prefer the per-type entries (e.g. AccountRootEntry.h), which + * additionally enforce the entry type at compile time. + * + * SLE::const_pointer / SLE::const_ref -> ReadOnlySLE + * SLE::pointer / SLE::ref -> WritableSLE + */ +using ReadOnlySLE = SLEBase; +using WritableSLE = SLEBase; + +static_assert( + !std::is_polymorphic_v && !std::is_polymorphic_v, + "SLEBase must stay a thin value type; it must not acquire a vtable"); + +} // namespace xrpl diff --git a/include/xrpl/ledger/entries/SignerListEntry.h b/include/xrpl/ledger/entries/SignerListEntry.h new file mode 100644 index 0000000000..f6f881cd69 --- /dev/null +++ b/include/xrpl/ledger/entries/SignerListEntry.h @@ -0,0 +1,35 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl { + +template +class SignerListEntry : public SLEBase +{ +public: + using Base = SLEBase; + + // Inherit base constructors: adopt an existing SLE, or resolve one from a + // Keylet against the view. + using Base::Base; + + explicit SignerListEntry( + AccountID const& account, + Base::ViewRefType view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : Base(keylet::signerList(account), view, j) + { + } +}; + +using SignerListEntryR = SignerListEntry; +using SignerListEntryW = SignerListEntry; + +} // namespace xrpl diff --git a/include/xrpl/ledger/entries/SponsorshipEntry.h b/include/xrpl/ledger/entries/SponsorshipEntry.h new file mode 100644 index 0000000000..98445479d9 --- /dev/null +++ b/include/xrpl/ledger/entries/SponsorshipEntry.h @@ -0,0 +1,36 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl { + +template +class SponsorshipEntry : public SLEBase +{ +public: + using Base = SLEBase; + + // Inherit base constructors: adopt an existing SLE, or resolve one from a + // Keylet against the view. + using Base::Base; + + explicit SponsorshipEntry( + AccountID const& sponsor, + AccountID const& sponsee, + Base::ViewRefType view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : Base(keylet::sponsorship(sponsor, sponsee), view, j) + { + } +}; + +using SponsorshipEntryR = SponsorshipEntry; +using SponsorshipEntryW = SponsorshipEntry; + +} // namespace xrpl diff --git a/include/xrpl/ledger/entries/TicketEntry.h b/include/xrpl/ledger/entries/TicketEntry.h new file mode 100644 index 0000000000..16d9a9f3d9 --- /dev/null +++ b/include/xrpl/ledger/entries/TicketEntry.h @@ -0,0 +1,46 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl { + +template +class TicketEntry : public SLEBase +{ +public: + using Base = SLEBase; + + // Inherit base constructors: adopt an existing SLE, or resolve one from a + // Keylet against the view. + using Base::Base; + + explicit TicketEntry( + AccountID const& id, + SeqProxy const& ticketSeq, + Base::ViewRefType view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : Base(keylet::ticket(id, ticketSeq), view, j) + { + } + + explicit TicketEntry( + uint256 const& ticketID, + Base::ViewRefType view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : Base(keylet::ticket(ticketID), view, j) + { + } +}; + +using TicketEntryR = TicketEntry; +using TicketEntryW = TicketEntry; + +} // namespace xrpl diff --git a/include/xrpl/ledger/entries/VaultEntry.h b/include/xrpl/ledger/entries/VaultEntry.h new file mode 100644 index 0000000000..897a9117b1 --- /dev/null +++ b/include/xrpl/ledger/entries/VaultEntry.h @@ -0,0 +1,46 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl { + +template +class VaultEntry : public SLEBase +{ +public: + using Base = SLEBase; + + // Inherit base constructors: adopt an existing SLE, or resolve one from a + // Keylet against the view. + using Base::Base; + + explicit VaultEntry( + AccountID const& owner, + SeqProxy const& seq, + Base::ViewRefType view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : Base(keylet::vault(owner, seq), view, j) + { + } + + explicit VaultEntry( + uint256 const& vaultID, + Base::ViewRefType view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : Base(keylet::vault(vaultID), view, j) + { + } +}; + +using VaultEntryR = VaultEntry; +using VaultEntryW = VaultEntry; + +} // namespace xrpl diff --git a/include/xrpl/ledger/entries/XChainOwnedClaimIDEntry.h b/include/xrpl/ledger/entries/XChainOwnedClaimIDEntry.h new file mode 100644 index 0000000000..9cec1fcec9 --- /dev/null +++ b/include/xrpl/ledger/entries/XChainOwnedClaimIDEntry.h @@ -0,0 +1,38 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace xrpl { + +template +class XChainOwnedClaimIDEntry : public SLEBase +{ +public: + using Base = SLEBase; + + // Inherit base constructors: adopt an existing SLE, or resolve one from a + // Keylet against the view. + using Base::Base; + + explicit XChainOwnedClaimIDEntry( + STXChainBridge const& bridge, + std::uint64_t seq, + Base::ViewRefType view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : Base(keylet::xChainClaimID(bridge, seq), view, j) + { + } +}; + +using XChainOwnedClaimIDEntryR = XChainOwnedClaimIDEntry; +using XChainOwnedClaimIDEntryW = XChainOwnedClaimIDEntry; + +} // namespace xrpl diff --git a/include/xrpl/ledger/entries/XChainOwnedCreateAccountClaimIDEntry.h b/include/xrpl/ledger/entries/XChainOwnedCreateAccountClaimIDEntry.h new file mode 100644 index 0000000000..e9494e702f --- /dev/null +++ b/include/xrpl/ledger/entries/XChainOwnedCreateAccountClaimIDEntry.h @@ -0,0 +1,39 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace xrpl { + +template +class XChainOwnedCreateAccountClaimIDEntry + : public SLEBase +{ +public: + using Base = SLEBase; + + // Inherit base constructors: adopt an existing SLE, or resolve one from a + // Keylet against the view. + using Base::Base; + + explicit XChainOwnedCreateAccountClaimIDEntry( + STXChainBridge const& bridge, + std::uint64_t seq, + Base::ViewRefType view, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) + : Base(keylet::xChainCreateAccountClaimID(bridge, seq), view, j) + { + } +}; + +using XChainOwnedCreateAccountClaimIDEntryR = XChainOwnedCreateAccountClaimIDEntry; +using XChainOwnedCreateAccountClaimIDEntryW = XChainOwnedCreateAccountClaimIDEntry; + +} // namespace xrpl diff --git a/include/xrpl/shamap/SHAMap.h b/include/xrpl/shamap/SHAMap.h index 05de33ddf3..0baea78931 100644 --- a/include/xrpl/shamap/SHAMap.h +++ b/include/xrpl/shamap/SHAMap.h @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -34,7 +35,6 @@ namespace xrpl { -class SHAMapNodeID; class SHAMapSyncFilter; /** @@ -420,7 +420,107 @@ public: invariants() const; private: - using SharedPtrNodeStack = std::stack>; + /** + * A path from the root of the map down to some node, pairing each node with the ID naming its + * position. + * + * The two halves of an entry must agree, and the only way to get that wrong is to compute an ID + * from the wrong branch. So this type does not accept an ID at all: every push takes the branch + * being descended and derives the ID itself, so a node and its ID cannot disagree. Reads are + * exposed through the same accessors a std::stack would offer. + */ + class NodePathStack + { + public: + [[nodiscard]] bool + empty() const + { + return stack_.empty(); + } + + [[nodiscard]] std::size_t + size() const + { + return stack_.size(); + } + + [[nodiscard]] std::pair const& + top() const + { + XRPL_ASSERT(!stack_.empty(), "xrpl::SHAMap::NodePathStack::top : non-empty stack"); + return stack_.top(); + } + + void + pop() + { + XRPL_ASSERT(!stack_.empty(), "xrpl::SHAMap::NodePathStack::pop : non-empty stack"); + stack_.pop(); + } + + void + clear() + { + stack_ = {}; + } + + /** + * Start a path at the root of the map, whose ID is the zero-depth ID by definition. + */ + void + pushRoot(SHAMapTreeNodePtr node) + { + XRPL_ASSERT(stack_.empty(), "xrpl::SHAMap::NodePathStack::pushRoot : empty stack"); + stack_.emplace(std::move(node), SHAMapNodeID{}); + } + + /** + * Extend the path to the child of the current node reached by `branch`. + * + * A node keeps the depth it was reached at, never a normalized kLeafDepth. Only a leaf may + * sit at kLeafDepth, since an inner node there would have no branch left to select. + */ + void + pushChild(SHAMapTreeNodePtr node, unsigned int branch) + { + XRPL_ASSERT(node, "xrpl::SHAMap::NodePathStack::pushChild : non-null node input"); + XRPL_ASSERT( + !stack_.empty(), "xrpl::SHAMap::NodePathStack::pushChild : non-empty stack"); + auto childID = stack_.top().second.getChildNodeID(branch); + XRPL_ASSERT_IF( + node->isInner(), + childID.getDepth() < kLeafDepth, + "xrpl::SHAMap::NodePathStack::pushChild : inner node above leaf depth"); + XRPL_ASSERT_IF( + node->isLeaf(), + childID.isPrefixOf(leafKey(*node)), + "xrpl::SHAMap::NodePathStack::pushChild : leaf key below branch"); + stack_.emplace(std::move(node), std::move(childID)); + } + + /** + * Extend the path to a node lying on the path to `target`. + * + * For nodes not reached by descending a known branch: the walk tracks only the key it is + * heading for, or the node is newly created. Either way `target` selects the branch. + */ + void + pushNode(SHAMapTreeNodePtr node, uint256 const& target) + { + if (stack_.empty()) + { + pushRoot(std::move(node)); + } + else + { + pushChild(std::move(node), selectBranch(stack_.top().second, target)); + } + } + + private: + std::stack> stack_; + }; + using DeltaRef = std::pair, boost::intrusive_ptr>; @@ -447,7 +547,7 @@ private: * Update hashes up to the root */ void - dirtyUp(SharedPtrNodeStack& stack, uint256 const& target, SHAMapTreeNodePtr terminal); + dirtyUp(NodePathStack& stack, uint256 const& target, SHAMapTreeNodePtr terminal); /** * Walk towards the specified id, returning the node. Caller must check @@ -455,7 +555,7 @@ private: * id */ SHAMapLeafNode* - walkTowardsKey(uint256 const& id, SharedPtrNodeStack* stack = nullptr) const; + walkTowardsKey(uint256 const& id, NodePathStack* stack = nullptr) const; /** * Return nullptr if key not found */ @@ -482,27 +582,15 @@ private: SHAMapTreeNodePtr writeNode(NodeObjectType t, SHAMapTreeNodePtr node) const; - // returns the first item at or below this node - SHAMapLeafNode* - firstBelow(SHAMapTreeNodePtr node, SharedPtrNodeStack& stack, unsigned int branch = 0u) const; - - // returns the last item at or below this node - SHAMapLeafNode* - lastBelow( - SHAMapTreeNodePtr node, - SharedPtrNodeStack& stack, - unsigned int branch = kBranchFactor) const; - - // direction in which belowHelper scans an inner node's branches + // direction in which a scan walks an inner node's branches enum class BelowDirection { First, Last }; - // helper function for firstBelow and lastBelow + /** + * Returns the first or last item at or below the node already on top of `stack`, extending + * `stack` with the path walked to reach it. + */ SHAMapLeafNode* - belowHelper( - SHAMapTreeNodePtr node, - SharedPtrNodeStack& stack, - unsigned int branch, - BelowDirection direction) const; + belowHelper(NodePathStack& stack, BelowDirection direction) const; // Simple descent // Get a child of the specified node @@ -550,9 +638,9 @@ private: hasLeafNode(uint256 const& tag, SHAMapHash const& hash) const; SHAMapLeafNode const* - peekFirstItem(SharedPtrNodeStack& stack) const; + peekFirstItem(NodePathStack& stack) const; SHAMapLeafNode const* - peekNextItem(uint256 const& id, SharedPtrNodeStack& stack) const; + peekNextItem(uint256 const& id, NodePathStack& stack) const; bool walkBranch( SHAMapTreeNode* node, @@ -697,7 +785,7 @@ public: using pointer = value_type const*; private: - SharedPtrNodeStack stack_; + NodePathStack stack_; SHAMap const* map_ = nullptr; pointer item_ = nullptr; @@ -723,7 +811,7 @@ public: private: explicit ConstIterator(SHAMap const* map); ConstIterator(SHAMap const* map, std::nullptr_t); - ConstIterator(SHAMap const* map, pointer item, SharedPtrNodeStack&& stack); + ConstIterator(SHAMap const* map, pointer item, NodePathStack&& stack); friend bool operator==(ConstIterator const& x, ConstIterator const& y); @@ -742,10 +830,7 @@ inline SHAMap::ConstIterator::ConstIterator(SHAMap const* map, std::nullptr_t) : { } -inline SHAMap::ConstIterator::ConstIterator( - SHAMap const* map, - pointer item, - SharedPtrNodeStack&& stack) +inline SHAMap::ConstIterator::ConstIterator(SHAMap const* map, pointer item, NodePathStack&& stack) : stack_(std::move(stack)), map_(map), item_(item) { } diff --git a/src/libxrpl/shamap/SHAMap.cpp b/src/libxrpl/shamap/SHAMap.cpp index 3fa8d66be0..0e28c0222a 100644 --- a/src/libxrpl/shamap/SHAMap.cpp +++ b/src/libxrpl/shamap/SHAMap.cpp @@ -97,7 +97,7 @@ SHAMap::snapShot(bool isMutable) const } void -SHAMap::dirtyUp(SharedPtrNodeStack& stack, uint256 const& target, SHAMapTreeNodePtr child) +SHAMap::dirtyUp(NodePathStack& stack, uint256 const& target, SHAMapTreeNodePtr child) { // walk the tree up from through the inner nodes to the root_ // update hashes and links @@ -126,29 +126,34 @@ SHAMap::dirtyUp(SharedPtrNodeStack& stack, uint256 const& target, SHAMapTreeNode } SHAMapLeafNode* -SHAMap::walkTowardsKey(uint256 const& id, SharedPtrNodeStack* stack) const +SHAMap::walkTowardsKey(uint256 const& id, NodePathStack* stack) const { XRPL_ASSERT( stack == nullptr || stack->empty(), "xrpl::SHAMap::walkTowardsKey : empty stack input"); auto inNode = root_; SHAMapNodeID nodeID; + // Every node on this walk lies on the path to `id`, so the stack can derive each ID from the + // branch `id` selects at the node above it. + auto pushCurrent = [&] { + if (stack != nullptr) + stack->pushNode(inNode, id); + }; + while (inNode->isInner()) { - if (stack != nullptr) - stack->emplace(inNode, nodeID); + pushCurrent(); - auto const inner = intr_ptr::staticPointerCast(inNode); + auto& inner = safeDowncast(*inNode); auto const branch = selectBranch(nodeID, id); - if (inner->isEmptyBranch(branch)) + if (inner.isEmptyBranch(branch)) return nullptr; - inNode = descendThrow(*inner, branch); + inNode = descendThrow(inner, branch); nodeID = nodeID.getChildNodeID(branch); } - if (stack != nullptr) - stack->emplace(inNode, nodeID); + pushCurrent(); return safeDowncast(inNode.get()); } @@ -428,65 +433,40 @@ SHAMap::unshareNode(intr_ptr::SharedPtr node, SHAMapNodeID const& nodeID) } SHAMapLeafNode* -SHAMap::belowHelper( - SHAMapTreeNodePtr node, - SharedPtrNodeStack& stack, - unsigned int branch, - BelowDirection direction) const +SHAMap::belowHelper(NodePathStack& stack, BelowDirection direction) const { - if (node->isLeaf()) - { - auto n = intr_ptr::staticPointerCast(node); - stack.push({node, {kLeafDepth, n->peekItem()->key()}}); - return n.get(); - } - auto inner = intr_ptr::staticPointerCast(node); - if (stack.empty()) - { - stack.emplace(inner, SHAMapNodeID{}); - } - else - { - stack.emplace(inner, stack.top().second.getChildNodeID(branch)); - } - // `scanned` counts how many branches of `inner` we have examined; the branch we look at is - // derived from it, so no index ever goes out of range. + XRPL_ASSERT(!stack.empty(), "xrpl::SHAMap::belowHelper : non-empty stack input"); + if (auto const& top = stack.top().first; top->isLeaf()) + return safeDowncast(top.get()); + + // The stack owns the node/ID pairing, so descending is only ever "push the branch we took". + // `scanned` counts how many branches of the current node we have examined; the branch we look + // at is derived from it, so no index ever goes out of range. `inner` tracks the node on top of + // the stack, which keeps it alive, so it only needs recomputing after a push. + auto* inner = safeDowncast(stack.top().first.get()); for (auto scanned = 0u; scanned < kBranchFactor;) { auto const childBranch = (direction == BelowDirection::Last) ? (kBranchFactor - 1u - scanned) : scanned; - if (!inner->isEmptyBranch(childBranch)) - { - node.adopt(descendThrow(inner.get(), childBranch)); - XRPL_ASSERT(!stack.empty(), "xrpl::SHAMap::belowHelper : non-empty stack"); - if (node->isLeaf()) - { - auto n = intr_ptr::staticPointerCast(node); - stack.push({n, {kLeafDepth, n->peekItem()->key()}}); - return n.get(); - } - inner = intr_ptr::staticPointerCast(node); - stack.emplace(inner, stack.top().second.getChildNodeID(branch)); - scanned = 0u; // descend and restart the scan on the new node - } - else + if (inner->isEmptyBranch(childBranch)) { ++scanned; // scan next branch + continue; } + + stack.pushChild(descendThrow(*inner, childBranch), childBranch); + + auto const& child = stack.top().first; + if (child->isLeaf()) + return safeDowncast(child.get()); + + inner = safeDowncast(child.get()); + scanned = 0u; // descend and restart the scan on the new node } return nullptr; } -SHAMapLeafNode* -SHAMap::lastBelow(SHAMapTreeNodePtr node, SharedPtrNodeStack& stack, unsigned int branch) const -{ - return belowHelper(node, stack, branch, BelowDirection::Last); -} -SHAMapLeafNode* -SHAMap::firstBelow(SHAMapTreeNodePtr node, SharedPtrNodeStack& stack, unsigned int branch) const -{ - return belowHelper(node, stack, branch, BelowDirection::First); -} + static boost::intrusive_ptr const kNoItem; boost::intrusive_ptr const& @@ -529,36 +509,36 @@ SHAMap::onlyBelow(SHAMapTreeNode* node) const } SHAMapLeafNode const* -SHAMap::peekFirstItem(SharedPtrNodeStack& stack) const +SHAMap::peekFirstItem(NodePathStack& stack) const { XRPL_ASSERT(stack.empty(), "xrpl::SHAMap::peekFirstItem : empty stack input"); - SHAMapLeafNode const* node = firstBelow(root_, stack); + stack.pushRoot(root_); + SHAMapLeafNode const* node = belowHelper(stack, BelowDirection::First); if (node == nullptr) { - while (!stack.empty()) - stack.pop(); + stack.clear(); return nullptr; } return node; } SHAMapLeafNode const* -SHAMap::peekNextItem(uint256 const& id, SharedPtrNodeStack& stack) const +SHAMap::peekNextItem(uint256 const& id, NodePathStack& stack) const { XRPL_ASSERT(!stack.empty(), "xrpl::SHAMap::peekNextItem : non-empty stack input"); XRPL_ASSERT(stack.top().first->isLeaf(), "xrpl::SHAMap::peekNextItem : stack starts with leaf"); stack.pop(); while (!stack.empty()) { - auto [node, nodeID] = stack.top(); + auto const [node, nodeID] = stack.top(); XRPL_ASSERT(!node->isLeaf(), "xrpl::SHAMap::peekNextItem : another node is not leaf"); - auto inner = intr_ptr::staticPointerCast(node); + auto& inner = safeDowncast(*node); for (auto i = selectBranch(nodeID, id) + 1; i < kBranchFactor; ++i) { - if (!inner->isEmptyBranch(i)) + if (!inner.isEmptyBranch(i)) { - node = descendThrow(*inner, i); - auto leaf = firstBelow(node, stack, i); + stack.pushChild(descendThrow(inner, i), i); + auto leaf = belowHelper(stack, BelowDirection::First); if (leaf == nullptr) Throw(type_, id); XRPL_ASSERT(leaf->isLeaf(), "xrpl::SHAMap::peekNextItem : leaf is valid"); @@ -597,11 +577,11 @@ SHAMap::peekItem(uint256 const& id, SHAMapHash& hash) const SHAMap::ConstIterator SHAMap::upperBound(uint256 const& id) const { - SharedPtrNodeStack stack; + NodePathStack stack; walkTowardsKey(id, &stack); while (!stack.empty()) { - auto [node, nodeID] = stack.top(); + auto const [node, nodeID] = stack.top(); if (node->isLeaf()) { auto leaf = safeDowncast(node.get()); @@ -610,13 +590,13 @@ SHAMap::upperBound(uint256 const& id) const } else { - auto inner = intr_ptr::staticPointerCast(node); + auto& inner = safeDowncast(*node); for (auto branch = selectBranch(nodeID, id) + 1; branch < kBranchFactor; ++branch) { - if (!inner->isEmptyBranch(branch)) + if (!inner.isEmptyBranch(branch)) { - node = descendThrow(*inner, branch); - auto leaf = firstBelow(node, stack, branch); + stack.pushChild(descendThrow(inner, branch), branch); + auto leaf = belowHelper(stack, BelowDirection::First); if (leaf == nullptr) Throw(type_, id); return ConstIterator(this, leaf->peekItem().get(), std::move(stack)); @@ -630,11 +610,11 @@ SHAMap::upperBound(uint256 const& id) const SHAMap::ConstIterator SHAMap::lowerBound(uint256 const& id) const { - SharedPtrNodeStack stack; + NodePathStack stack; walkTowardsKey(id, &stack); while (!stack.empty()) { - auto [node, nodeID] = stack.top(); + auto const [node, nodeID] = stack.top(); if (node->isLeaf()) { auto leaf = safeDowncast(node.get()); @@ -643,14 +623,14 @@ SHAMap::lowerBound(uint256 const& id) const } else { - auto inner = intr_ptr::staticPointerCast(node); + auto& inner = safeDowncast(*node); for (auto branch = selectBranch(nodeID, id); branch > 0u;) { --branch; - if (!inner->isEmptyBranch(branch)) + if (!inner.isEmptyBranch(branch)) { - node = descendThrow(*inner, branch); - auto leaf = lastBelow(node, stack, branch); + stack.pushChild(descendThrow(inner, branch), branch); + auto leaf = belowHelper(stack, BelowDirection::Last); if (leaf == nullptr) Throw(type_, id); return ConstIterator(this, leaf->peekItem().get(), std::move(stack)); @@ -675,7 +655,7 @@ SHAMap::delItem(uint256 const& id) // delete the item with this ID XRPL_ASSERT(state_ != SHAMapState::Immutable, "xrpl::SHAMap::delItem : not immutable"); - SharedPtrNodeStack stack; + NodePathStack stack; walkTowardsKey(id, &stack); if (stack.empty()) @@ -761,7 +741,7 @@ SHAMap::addGiveItem(SHAMapNodeType type, boost::intrusive_ptr // add the specified item, does not update uint256 const tag = item->key(); - SharedPtrNodeStack stack; + NodePathStack stack; walkTowardsKey(tag, &stack); if (stack.empty()) @@ -801,7 +781,7 @@ SHAMap::addGiveItem(SHAMapNodeType type, boost::intrusive_ptr while ((b1 = selectBranch(nodeID, tag)) == (b2 = selectBranch(nodeID, otherItem->key()))) { - stack.emplace(node, nodeID); + stack.pushNode(node, tag); // we need a new inner node, since both go on same branch at this // level @@ -848,7 +828,7 @@ SHAMap::updateGiveItem(SHAMapNodeType type, boost::intrusive_ptrisLeaf(), "xrpl::SHAMap::invariants : root node is not leaf"); - SharedPtrNodeStack stack; + NodePathStack stack; for (auto leaf = peekFirstItem(stack); leaf != nullptr; leaf = peekNextItem(leaf->peekItem()->key(), stack)) ; diff --git a/src/libxrpl/shamap/SHAMapSync.cpp b/src/libxrpl/shamap/SHAMapSync.cpp index 4319d0bcd4..602d8e629c 100644 --- a/src/libxrpl/shamap/SHAMapSync.cpp +++ b/src/libxrpl/shamap/SHAMapSync.cpp @@ -793,7 +793,7 @@ SHAMap::hasLeafNode(uint256 const& tag, SHAMapHash const& targetNodeHash) const std::optional> SHAMap::getProofPath(uint256 const& key) const { - SharedPtrNodeStack stack; + NodePathStack stack; walkTowardsKey(key, &stack); if (stack.empty()) diff --git a/src/test/app/LedgerNodeHelpers_test.cpp b/src/test/app/LedgerNodeHelpers_test.cpp index a9e4e3ebfc..76c3e3cc52 100644 --- a/src/test/app/LedgerNodeHelpers_test.cpp +++ b/src/test/app/LedgerNodeHelpers_test.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include diff --git a/src/test/basics/PerfLog_test.cpp b/src/test/basics/PerfLog_test.cpp index 0896de8eee..5e790810f7 100644 --- a/src/test/basics/PerfLog_test.cpp +++ b/src/test/basics/PerfLog_test.cpp @@ -1032,8 +1032,9 @@ public: // makePerfLog() copies the range of names it is given, so only the names have // to outlive the PerfLog. Here the range does not: it is destroyed before the - // counters are read. Retaining it instead is a use-after-free that a - // sanitizer build reports and this test would otherwise pass through. + // counters are read. Retaining it instead is a use-after-free, which a + // sanitizer build reports directly and which otherwise surfaces as a failed + // assertion or a Debug-mode heap-corruption abort, not a silent pass. void testCallerRangeNeedNotOutlive() { diff --git a/src/tests/libxrpl/CMakeLists.txt b/src/tests/libxrpl/CMakeLists.txt index 9cbfb8ca10..81f4abc126 100644 --- a/src/tests/libxrpl/CMakeLists.txt +++ b/src/tests/libxrpl/CMakeLists.txt @@ -31,6 +31,7 @@ set(test_modules consensus crypto json + ledger nodestore peerfinder protocol diff --git a/src/tests/libxrpl/ledger/AMMEntry.cpp b/src/tests/libxrpl/ledger/AMMEntry.cpp new file mode 100644 index 0000000000..6189d2b3e6 --- /dev/null +++ b/src/tests/libxrpl/ledger/AMMEntry.cpp @@ -0,0 +1,25 @@ +#include + +#include +#include +#include + +#include +#include +#include + +namespace xrpl::test { + +TEST(AMMEntryTests, Constructors) +{ + EntryTestEnv e; + + Asset const xrp{xrpIssue()}; + Asset const usd{IOU("USD", e.alice).issue()}; + + expectKeylet(e, keylet::amm(xrp, usd), "amm(asset, asset)", xrp, usd); + + expectKeylet(e, keylet::amm(e.someID()), "amm(uint256)", e.someID()); +} + +} // namespace xrpl::test diff --git a/src/tests/libxrpl/ledger/AccountRootEntry.cpp b/src/tests/libxrpl/ledger/AccountRootEntry.cpp new file mode 100644 index 0000000000..964d0f2f43 --- /dev/null +++ b/src/tests/libxrpl/ledger/AccountRootEntry.cpp @@ -0,0 +1,21 @@ +#include + +#include + +#include +#include +#include + +namespace xrpl::test { + +TEST(AccountRootEntryTests, Constructors) +{ + EntryTestEnv e; + + expectKeylet(e, keylet::account(e.alice.id()), "account(id)", e.alice.id()); + + expectKeylet( + e, keylet::account(Account("nobody").id()), "account(id) absent", Account("nobody").id()); +} + +} // namespace xrpl::test diff --git a/src/tests/libxrpl/ledger/AmendmentsEntry.cpp b/src/tests/libxrpl/ledger/AmendmentsEntry.cpp new file mode 100644 index 0000000000..ec45b291ec --- /dev/null +++ b/src/tests/libxrpl/ledger/AmendmentsEntry.cpp @@ -0,0 +1,17 @@ +#include + +#include + +#include +#include + +namespace xrpl::test { + +TEST(AmendmentsEntryTests, Constructors) +{ + EntryTestEnv e; + + expectKeylet(e, keylet::amendments(), "amendments()"); +} + +} // namespace xrpl::test diff --git a/src/tests/libxrpl/ledger/BridgeEntry.cpp b/src/tests/libxrpl/ledger/BridgeEntry.cpp new file mode 100644 index 0000000000..bae65ea8b5 --- /dev/null +++ b/src/tests/libxrpl/ledger/BridgeEntry.cpp @@ -0,0 +1,41 @@ +#include + +#include +#include +#include +#include + +#include +#include +#include + +namespace xrpl::test { + +TEST(BridgeEntryTests, Constructors) +{ + EntryTestEnv e; + + STXChainBridge const bridge{e.alice.id(), xrpIssue(), e.bob.id(), IOU("USD", e.bob).issue()}; + + expectKeylet( + e, + keylet::bridge(bridge, STXChainBridge::ChainType::Locking), + "bridge(bridge, Locking)", + bridge, + STXChainBridge::ChainType::Locking); + + expectKeylet( + e, + keylet::bridge(bridge, STXChainBridge::ChainType::Issuing), + "bridge(bridge, Issuing)", + bridge, + STXChainBridge::ChainType::Issuing); + + // The two chain types must not collide, or the assertions above would + // pass with chainType ignored entirely. + EXPECT_NE( + keylet::bridge(bridge, STXChainBridge::ChainType::Locking).key, + keylet::bridge(bridge, STXChainBridge::ChainType::Issuing).key); +} + +} // namespace xrpl::test diff --git a/src/tests/libxrpl/ledger/CheckEntry.cpp b/src/tests/libxrpl/ledger/CheckEntry.cpp new file mode 100644 index 0000000000..da51da047e --- /dev/null +++ b/src/tests/libxrpl/ledger/CheckEntry.cpp @@ -0,0 +1,23 @@ +#include + +#include +#include + +#include +#include + +namespace xrpl::test { + +TEST(CheckEntryTests, Constructors) +{ + EntryTestEnv e; + + SeqProxy const seq = SeqProxy::rawSequence(7); + + expectKeylet( + e, keylet::check(e.alice.id(), seq), "check(id, seq)", e.alice.id(), seq); + + expectKeylet(e, keylet::check(e.someID()), "check(uint256)", e.someID()); +} + +} // namespace xrpl::test diff --git a/src/tests/libxrpl/ledger/CredentialEntry.cpp b/src/tests/libxrpl/ledger/CredentialEntry.cpp new file mode 100644 index 0000000000..ce3f80dca0 --- /dev/null +++ b/src/tests/libxrpl/ledger/CredentialEntry.cpp @@ -0,0 +1,39 @@ +#include + +#include +#include +#include + +#include +#include + +#include + +namespace xrpl::test { + +TEST(CredentialEntryTests, Constructors) +{ + EntryTestEnv e; + + std::string const credTypeStr = "termsandconditions"; + Slice const credType = makeSlice(credTypeStr); + + expectKeylet( + e, + keylet::credential(e.alice.id(), e.bob.id(), credType), + "credential(subject, issuer, credType)", + e.alice.id(), + e.bob.id(), + credType); + + expectKeylet( + e, keylet::credential(e.someID()), "credential(uint256)", e.someID()); + + // Subject and issuer are both AccountIDs, so the assertion above only + // has teeth if their order matters. + EXPECT_NE( + keylet::credential(e.alice.id(), e.bob.id(), credType).key, + keylet::credential(e.bob.id(), e.alice.id(), credType).key); +} + +} // namespace xrpl::test diff --git a/src/tests/libxrpl/ledger/DIDEntry.cpp b/src/tests/libxrpl/ledger/DIDEntry.cpp new file mode 100644 index 0000000000..41b27a486c --- /dev/null +++ b/src/tests/libxrpl/ledger/DIDEntry.cpp @@ -0,0 +1,17 @@ +#include + +#include + +#include +#include + +namespace xrpl::test { + +TEST(DIDEntryTests, Constructors) +{ + EntryTestEnv e; + + expectKeylet(e, keylet::did(e.alice.id()), "did(account)", e.alice.id()); +} + +} // namespace xrpl::test diff --git a/src/tests/libxrpl/ledger/DelegateEntry.cpp b/src/tests/libxrpl/ledger/DelegateEntry.cpp new file mode 100644 index 0000000000..a27299df46 --- /dev/null +++ b/src/tests/libxrpl/ledger/DelegateEntry.cpp @@ -0,0 +1,29 @@ +#include + +#include +#include + +#include +#include + +namespace xrpl::test { + +TEST(DelegateEntryTests, Constructors) +{ + EntryTestEnv e; + + expectKeylet( + e, + keylet::delegate(e.alice.id(), e.bob.id()), + "delegate(account, authorizedAccount)", + e.alice.id(), + e.bob.id()); + + // Both arguments are AccountIDs, so the assertion above only has teeth + // if their order matters. + EXPECT_NE( + keylet::delegate(e.alice.id(), e.bob.id()).key, + keylet::delegate(e.bob.id(), e.alice.id()).key); +} + +} // namespace xrpl::test diff --git a/src/tests/libxrpl/ledger/DepositPreauthEntry.cpp b/src/tests/libxrpl/ledger/DepositPreauthEntry.cpp new file mode 100644 index 0000000000..bba2a58c8a --- /dev/null +++ b/src/tests/libxrpl/ledger/DepositPreauthEntry.cpp @@ -0,0 +1,54 @@ +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +namespace xrpl::test { + +TEST(DepositPreauthEntryTests, Constructors) +{ + EntryTestEnv e; + + std::string const credTypeStr = "termsandconditions"; + std::set> const authCreds{{e.bob.id(), makeSlice(credTypeStr)}}; + + expectKeylet( + e, + keylet::depositPreauth(e.alice.id(), e.bob.id()), + "depositPreauth(owner, preauthorized)", + e.alice.id(), + e.bob.id()); + + expectKeylet( + e, + keylet::depositPreauth(e.alice.id(), authCreds), + "depositPreauth(owner, authCreds)", + e.alice.id(), + authCreds); + + expectKeylet( + e, keylet::depositPreauth(e.someID()), "depositPreauth(uint256)", e.someID()); + + // Owner and preauthorized are both AccountIDs, so the assertion above + // only has teeth if their order matters. + EXPECT_NE( + keylet::depositPreauth(e.alice.id(), e.bob.id()).key, + keylet::depositPreauth(e.bob.id(), e.alice.id()).key); + + // The credential-set overload must not collide with the single-account + // one. + EXPECT_NE( + keylet::depositPreauth(e.alice.id(), authCreds).key, + keylet::depositPreauth(e.alice.id(), e.bob.id()).key); +} + +} // namespace xrpl::test diff --git a/src/tests/libxrpl/ledger/DirectoryNodeEntry.cpp b/src/tests/libxrpl/ledger/DirectoryNodeEntry.cpp new file mode 100644 index 0000000000..0efcb365f6 --- /dev/null +++ b/src/tests/libxrpl/ledger/DirectoryNodeEntry.cpp @@ -0,0 +1,28 @@ +#include + +#include +#include + +#include +#include + +#include + +namespace xrpl::test { + +TEST(DirectoryNodeEntryTests, Constructors) +{ + EntryTestEnv e; + + expectKeylet( + e, keylet::ownerDir(e.alice.id()), "ownerDir(id)", e.alice.id()); + + expectKeylet( + e, keylet::page(e.someID(), 3u), "page(root, index)", e.someID(), std::uint64_t{3}); + + // The two overloads reach different keylet:: functions; a copy-paste + // slip between them would be invisible otherwise. + EXPECT_NE(keylet::ownerDir(e.alice.id()).key, keylet::page(e.someID(), 3u).key); +} + +} // namespace xrpl::test diff --git a/src/tests/libxrpl/ledger/EntryTestHelpers.h b/src/tests/libxrpl/ledger/EntryTestHelpers.h new file mode 100644 index 0000000000..d98b40795e --- /dev/null +++ b/src/tests/libxrpl/ledger/EntryTestHelpers.h @@ -0,0 +1,123 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +namespace xrpl::test { + +/** + * Scaffolding shared by the per-entry-type suites. + * + * Each of those suites needs the same three things: a ledger with a few funded + * accounts, a throwaway ApplyView that is never applied, and some arbitrary + * uint256 to stand in for an object ID. Build one of these per test case -- + * TxTest construction dominates the runtime of these tests by a wide margin, + * and none of the assertions mutate the ledger. + */ +class EntryTestEnv +{ +public: + TxTest env; + Account const alice{"alice"}; + Account const bob{"bob"}; + Account const carol{"carol"}; + + EntryTestEnv() : av_(&fundAndClose(), TapNone) + { + } + + /** + * The closed ledger apply() was built over. Nothing here closes another + * ledger or submits a transaction afterward, so this and apply() never + * diverge. + */ + [[nodiscard]] ReadView const& + read() const + { + return env.getClosedLedger(); + } + + [[nodiscard]] ApplyView& + apply() + { + return av_; + } + + /** + * An arbitrary but stable uint256, for the entry constructors that take + * an object ID directly. Nothing in the ledger has this key, which is the + * point: those overloads should resolve to a non-existent entry. + */ + [[nodiscard]] uint256 + someID() const + { + return read().header().parentHash; + } + +private: + // Runs from the av_ member initializer, so it may only touch env and the + // accounts -- everything declared above av_. + ReadView const& + fundAndClose() + { + env.createAccount(alice, XRP(10'000)); + env.createAccount(bob, XRP(10'000)); + env.createAccount(carol, XRP(10'000)); + env.close(); + return env.getClosedLedger(); + } + + ApplyViewImpl av_; +}; + +/** + * Assert that both flavors of @p Entry built from @p args resolve the ledger + * object that @p expected names. + * + * The entry classes are near identical, so the defect they invite is a + * copy-paste one: a constructor that reaches the wrong keylet:: function, or + * that transposes two same-typed arguments. Comparing against an independently + * spelled-out keylet at the call site catches exactly that. + * + * @p what names the overload under test, so a failure says which one broke. + */ +template