From b1f794f06792a55bd33e5b544593425713c6cd0f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Jun 2026 09:39:38 -0400 Subject: [PATCH 01/17] ci: [DEPENDABOT] bump actions/checkout from 6.0.3 to 7.0.0 (#7585) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/check-pr-description.yml | 2 +- .github/workflows/on-pr.yml | 2 +- .github/workflows/publish-docs.yml | 2 +- .github/workflows/reusable-build-test-config.yml | 2 +- .github/workflows/reusable-check-levelization.yml | 2 +- .github/workflows/reusable-check-rename.yml | 2 +- .github/workflows/reusable-clang-tidy.yml | 2 +- .github/workflows/reusable-package.yml | 6 +++--- .github/workflows/reusable-strategy-matrix.yml | 2 +- .github/workflows/reusable-upload-recipe.yml | 2 +- .github/workflows/upload-conan-deps.yml | 2 +- 11 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/check-pr-description.yml b/.github/workflows/check-pr-description.yml index a60b83738a..744449f216 100644 --- a/.github/workflows/check-pr-description.yml +++ b/.github/workflows/check-pr-description.yml @@ -23,7 +23,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Write PR body to file env: diff --git a/.github/workflows/on-pr.yml b/.github/workflows/on-pr.yml index 4b2edeb93d..0cc9b375a7 100644 --- a/.github/workflows/on-pr.yml +++ b/.github/workflows/on-pr.yml @@ -33,7 +33,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Determine changed files # This step checks whether any files have changed that should # cause the next jobs to run. We do it this way rather than diff --git a/.github/workflows/publish-docs.yml b/.github/workflows/publish-docs.yml index 0de5347aab..cc7b6b6e7e 100644 --- a/.github/workflows/publish-docs.yml +++ b/.github/workflows/publish-docs.yml @@ -44,7 +44,7 @@ jobs: container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-fe4c8ae steps: - name: Checkout repository - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Prepare runner uses: XRPLF/actions/prepare-runner@c47daebb2f9db64ffbac71b47d68a661498d5ce8 diff --git a/.github/workflows/reusable-build-test-config.yml b/.github/workflows/reusable-build-test-config.yml index 95e6b0cbe2..3e6464aaba 100644 --- a/.github/workflows/reusable-build-test-config.yml +++ b/.github/workflows/reusable-build-test-config.yml @@ -110,7 +110,7 @@ jobs: uses: XRPLF/actions/cleanup-workspace@c7d9ce5ebb03c752a354889ecd870cadfc2b1cd4 - name: Checkout repository - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Prepare runner uses: XRPLF/actions/prepare-runner@c47daebb2f9db64ffbac71b47d68a661498d5ce8 diff --git a/.github/workflows/reusable-check-levelization.yml b/.github/workflows/reusable-check-levelization.yml index 813c0e1e36..88c95ac3ba 100644 --- a/.github/workflows/reusable-check-levelization.yml +++ b/.github/workflows/reusable-check-levelization.yml @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Check levelization run: python .github/scripts/levelization/generate.py - name: Check for differences diff --git a/.github/workflows/reusable-check-rename.yml b/.github/workflows/reusable-check-rename.yml index 5002cc7f40..9a91e98ee3 100644 --- a/.github/workflows/reusable-check-rename.yml +++ b/.github/workflows/reusable-check-rename.yml @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Check definitions run: .github/scripts/rename/definitions.sh . - name: Check copyright notices diff --git a/.github/workflows/reusable-clang-tidy.yml b/.github/workflows/reusable-clang-tidy.yml index 5d2325cb1d..c663f71842 100644 --- a/.github/workflows/reusable-clang-tidy.yml +++ b/.github/workflows/reusable-clang-tidy.yml @@ -45,7 +45,7 @@ jobs: issues: write steps: - name: Checkout repository - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Prepare runner uses: XRPLF/actions/prepare-runner@c47daebb2f9db64ffbac71b47d68a661498d5ce8 diff --git a/.github/workflows/reusable-package.yml b/.github/workflows/reusable-package.yml index 0e3f657006..eed4bfc4a3 100644 --- a/.github/workflows/reusable-package.yml +++ b/.github/workflows/reusable-package.yml @@ -27,7 +27,7 @@ jobs: matrix: ${{ steps.generate.outputs.matrix }} steps: - name: Checkout repository - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Set up Python uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 @@ -45,7 +45,7 @@ jobs: version: ${{ steps.version.outputs.version }} steps: - name: Checkout repository - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: sparse-checkout: | .github/actions/generate-version @@ -69,7 +69,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Download pre-built binary uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 diff --git a/.github/workflows/reusable-strategy-matrix.yml b/.github/workflows/reusable-strategy-matrix.yml index 4518a8ffef..c1a1c1a78b 100644 --- a/.github/workflows/reusable-strategy-matrix.yml +++ b/.github/workflows/reusable-strategy-matrix.yml @@ -23,7 +23,7 @@ jobs: matrix: ${{ steps.generate.outputs.matrix }} steps: - name: Checkout repository - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Set up Python uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 diff --git a/.github/workflows/reusable-upload-recipe.yml b/.github/workflows/reusable-upload-recipe.yml index ba7a0943d9..a389e98771 100644 --- a/.github/workflows/reusable-upload-recipe.yml +++ b/.github/workflows/reusable-upload-recipe.yml @@ -43,7 +43,7 @@ jobs: container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-fe4c8ae steps: - name: Checkout repository - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Generate build version number id: version diff --git a/.github/workflows/upload-conan-deps.yml b/.github/workflows/upload-conan-deps.yml index 7ca9d13007..5d3712cf9e 100644 --- a/.github/workflows/upload-conan-deps.yml +++ b/.github/workflows/upload-conan-deps.yml @@ -65,7 +65,7 @@ jobs: uses: XRPLF/actions/cleanup-workspace@c7d9ce5ebb03c752a354889ecd870cadfc2b1cd4 - name: Checkout repository - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Prepare runner uses: XRPLF/actions/prepare-runner@c47daebb2f9db64ffbac71b47d68a661498d5ce8 From e29b523620ee47cc1ecd1df523b80ffdcacc1c9e Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Mon, 22 Jun 2026 18:00:40 +0100 Subject: [PATCH 02/17] ci: Build and push docker images in forks too (#7588) --- .github/workflows/build-nix-images.yml | 6 +++--- .github/workflows/build-packaging-images.yml | 6 +++--- .github/workflows/pre-commit.yml | 2 +- .github/workflows/reusable-clang-tidy.yml | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build-nix-images.yml b/.github/workflows/build-nix-images.yml index 3af6a3b1d4..54911ef6e0 100644 --- a/.github/workflows/build-nix-images.yml +++ b/.github/workflows/build-nix-images.yml @@ -54,9 +54,9 @@ jobs: base_image: debian:bookworm - name: rhel base_image: registry.access.redhat.com/ubi9/ubi:latest - uses: XRPLF/actions/.github/workflows/build-multiarch-image.yml@c1b480188519e0cad040e6aa70db1cbc5a797e07 + uses: XRPLF/actions/.github/workflows/build-multiarch-image.yml@ee03d31bcc4501d7599dc1b1ecd7a34af582ad1c with: - image_name: ghcr.io/xrplf/xrpld/nix-${{ matrix.distro.name }} + image_name: xrpld/nix-${{ matrix.distro.name }} dockerfile: nix/docker/Dockerfile base_image: ${{ matrix.distro.base_image }} - push: ${{ github.repository == 'XRPLF/rippled' && github.event_name == 'push' }} + push: ${{ github.event_name == 'push' }} diff --git a/.github/workflows/build-packaging-images.yml b/.github/workflows/build-packaging-images.yml index d6dabb0f95..3633847ef3 100644 --- a/.github/workflows/build-packaging-images.yml +++ b/.github/workflows/build-packaging-images.yml @@ -38,9 +38,9 @@ jobs: base_image: debian:bookworm - name: rhel base_image: registry.access.redhat.com/ubi9/ubi:latest - uses: XRPLF/actions/.github/workflows/build-multiarch-image.yml@c1b480188519e0cad040e6aa70db1cbc5a797e07 + uses: XRPLF/actions/.github/workflows/build-multiarch-image.yml@ee03d31bcc4501d7599dc1b1ecd7a34af582ad1c with: - image_name: ghcr.io/xrplf/xrpld/packaging-${{ matrix.distro.name }} + image_name: xrpld/packaging-${{ matrix.distro.name }} dockerfile: package/Dockerfile base_image: ${{ matrix.distro.base_image }} - push: ${{ github.repository == 'XRPLF/rippled' && github.event_name == 'push' }} + push: ${{ github.event_name == 'push' }} diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index aecf0c2a8b..0363534af5 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -14,7 +14,7 @@ on: jobs: # Call the workflow in the XRPLF/actions repo that runs the pre-commit hooks. run-hooks: - uses: XRPLF/actions/.github/workflows/pre-commit.yml@312aaab296060ff89d7f798dcab59f019bea6e02 + uses: XRPLF/actions/.github/workflows/pre-commit.yml@e06d4138c9ec8dceeb7c818645faa38087ea9e3d with: runs_on: ubuntu-latest container: '{ "image": "ghcr.io/xrplf/ci/tools-rippled-pre-commit:sha-41ec7c1" }' diff --git a/.github/workflows/reusable-clang-tidy.yml b/.github/workflows/reusable-clang-tidy.yml index c663f71842..e99ef574bf 100644 --- a/.github/workflows/reusable-clang-tidy.yml +++ b/.github/workflows/reusable-clang-tidy.yml @@ -32,7 +32,7 @@ jobs: if: ${{ inputs.check_only_changed }} permissions: contents: read - uses: XRPLF/actions/.github/workflows/determine-tidy-files.yml@312aaab296060ff89d7f798dcab59f019bea6e02 + uses: XRPLF/actions/.github/workflows/determine-tidy-files.yml@c7045074aafe9fb92fa537aa4446f81fbfc17e8b run-clang-tidy: name: Run clang tidy From 997267f84555ca9758394681e26b888eb0699bed Mon Sep 17 00:00:00 2001 From: yinyiqian1 Date: Mon, 22 Jun 2026 13:36:06 -0400 Subject: [PATCH 03/17] feat: Remove clear mutable flags for DynamicMPT XLS-94 (#7439) --- include/xrpl/protocol/LedgerFormats.h | 12 +- include/xrpl/protocol/TxFlags.h | 42 +- include/xrpl/protocol/detail/features.macro | 2 +- .../transactors/token/MPTokenIssuanceSet.cpp | 66 +- src/test/app/AMMMPT_test.cpp | 16 +- src/test/app/Loan_test.cpp | 189 +-- src/test/app/MPToken_test.cpp | 1260 ++++++++--------- src/test/app/Vault_test.cpp | 243 +--- src/test/jtx/impl/mpt.cpp | 72 +- 9 files changed, 684 insertions(+), 1218 deletions(-) diff --git a/include/xrpl/protocol/LedgerFormats.h b/include/xrpl/protocol/LedgerFormats.h index 99d5d818f1..c1274e9e91 100644 --- a/include/xrpl/protocol/LedgerFormats.h +++ b/include/xrpl/protocol/LedgerFormats.h @@ -180,12 +180,12 @@ enum LedgerEntryType : std::uint16_t { LSF_FLAG(lsfMPTCanClawback, 0x00000040)) \ \ LEDGER_OBJECT(MPTokenIssuanceMutable, \ - LSF_FLAG(lsmfMPTCanMutateCanLock, 0x00000002) \ - LSF_FLAG(lsmfMPTCanMutateRequireAuth, 0x00000004) \ - LSF_FLAG(lsmfMPTCanMutateCanEscrow, 0x00000008) \ - LSF_FLAG(lsmfMPTCanMutateCanTrade, 0x00000010) \ - LSF_FLAG(lsmfMPTCanMutateCanTransfer, 0x00000020) \ - LSF_FLAG(lsmfMPTCanMutateCanClawback, 0x00000040) \ + LSF_FLAG(lsmfMPTCanEnableCanLock, 0x00000002) \ + LSF_FLAG(lsmfMPTCanEnableRequireAuth, 0x00000004) \ + LSF_FLAG(lsmfMPTCanEnableCanEscrow, 0x00000008) \ + LSF_FLAG(lsmfMPTCanEnableCanTrade, 0x00000010) \ + LSF_FLAG(lsmfMPTCanEnableCanTransfer, 0x00000020) \ + LSF_FLAG(lsmfMPTCanEnableCanClawback, 0x00000040) \ LSF_FLAG(lsmfMPTCanMutateMetadata, 0x00010000) \ LSF_FLAG(lsmfMPTCanMutateTransferFee, 0x00020000)) \ \ diff --git a/include/xrpl/protocol/TxFlags.h b/include/xrpl/protocol/TxFlags.h index 4652cc1bf0..f9c7bc1a5d 100644 --- a/include/xrpl/protocol/TxFlags.h +++ b/include/xrpl/protocol/TxFlags.h @@ -341,38 +341,32 @@ inline constexpr FlagValue tfTrustSetPermissionMask = // MPTokenIssuanceCreate MutableFlags: // Indicating specific fields or flags may be changed after issuance. -inline constexpr FlagValue tmfMPTCanMutateCanLock = lsmfMPTCanMutateCanLock; -inline constexpr FlagValue tmfMPTCanMutateRequireAuth = lsmfMPTCanMutateRequireAuth; -inline constexpr FlagValue tmfMPTCanMutateCanEscrow = lsmfMPTCanMutateCanEscrow; -inline constexpr FlagValue tmfMPTCanMutateCanTrade = lsmfMPTCanMutateCanTrade; -inline constexpr FlagValue tmfMPTCanMutateCanTransfer = lsmfMPTCanMutateCanTransfer; -inline constexpr FlagValue tmfMPTCanMutateCanClawback = lsmfMPTCanMutateCanClawback; +inline constexpr FlagValue tmfMPTCanEnableCanLock = lsmfMPTCanEnableCanLock; +inline constexpr FlagValue tmfMPTCanEnableRequireAuth = lsmfMPTCanEnableRequireAuth; +inline constexpr FlagValue tmfMPTCanEnableCanEscrow = lsmfMPTCanEnableCanEscrow; +inline constexpr FlagValue tmfMPTCanEnableCanTrade = lsmfMPTCanEnableCanTrade; +inline constexpr FlagValue tmfMPTCanEnableCanTransfer = lsmfMPTCanEnableCanTransfer; +inline constexpr FlagValue tmfMPTCanEnableCanClawback = lsmfMPTCanEnableCanClawback; inline constexpr FlagValue tmfMPTCanMutateMetadata = lsmfMPTCanMutateMetadata; inline constexpr FlagValue tmfMPTCanMutateTransferFee = lsmfMPTCanMutateTransferFee; inline constexpr FlagValue tmfMPTokenIssuanceCreateMutableMask = - ~(tmfMPTCanMutateCanLock | tmfMPTCanMutateRequireAuth | tmfMPTCanMutateCanEscrow | - tmfMPTCanMutateCanTrade | tmfMPTCanMutateCanTransfer | tmfMPTCanMutateCanClawback | + ~(tmfMPTCanEnableCanLock | tmfMPTCanEnableRequireAuth | tmfMPTCanEnableCanEscrow | + tmfMPTCanEnableCanTrade | tmfMPTCanEnableCanTransfer | tmfMPTCanEnableCanClawback | tmfMPTCanMutateMetadata | tmfMPTCanMutateTransferFee); // MPTokenIssuanceSet MutableFlags: -// Set or Clear flags. +// Enable mutable capability flags. These flags are one-way: once enabled, +// the corresponding capability cannot be disabled by MPTokenIssuanceSet. inline constexpr FlagValue tmfMPTSetCanLock = 0x00000001; -inline constexpr FlagValue tmfMPTClearCanLock = 0x00000002; -inline constexpr FlagValue tmfMPTSetRequireAuth = 0x00000004; -inline constexpr FlagValue tmfMPTClearRequireAuth = 0x00000008; -inline constexpr FlagValue tmfMPTSetCanEscrow = 0x00000010; -inline constexpr FlagValue tmfMPTClearCanEscrow = 0x00000020; -inline constexpr FlagValue tmfMPTSetCanTrade = 0x00000040; -inline constexpr FlagValue tmfMPTClearCanTrade = 0x00000080; -inline constexpr FlagValue tmfMPTSetCanTransfer = 0x00000100; -inline constexpr FlagValue tmfMPTClearCanTransfer = 0x00000200; -inline constexpr FlagValue tmfMPTSetCanClawback = 0x00000400; -inline constexpr FlagValue tmfMPTClearCanClawback = 0x00000800; -inline constexpr FlagValue tmfMPTokenIssuanceSetMutableMask = ~( - tmfMPTSetCanLock | tmfMPTClearCanLock | tmfMPTSetRequireAuth | tmfMPTClearRequireAuth | - tmfMPTSetCanEscrow | tmfMPTClearCanEscrow | tmfMPTSetCanTrade | tmfMPTClearCanTrade | - tmfMPTSetCanTransfer | tmfMPTClearCanTransfer | tmfMPTSetCanClawback | tmfMPTClearCanClawback); +inline constexpr FlagValue tmfMPTSetRequireAuth = 0x00000002; +inline constexpr FlagValue tmfMPTSetCanEscrow = 0x00000004; +inline constexpr FlagValue tmfMPTSetCanTrade = 0x00000008; +inline constexpr FlagValue tmfMPTSetCanTransfer = 0x00000010; +inline constexpr FlagValue tmfMPTSetCanClawback = 0x00000020; +inline constexpr FlagValue tmfMPTokenIssuanceSetMutableMask = + ~(tmfMPTSetCanLock | tmfMPTSetRequireAuth | tmfMPTSetCanEscrow | tmfMPTSetCanTrade | + tmfMPTSetCanTransfer | tmfMPTSetCanClawback); // Prior to fixRemoveNFTokenAutoTrustLine, transfer of an NFToken between accounts allowed a // TrustLine to be added to the issuer of that token without explicit permission from that issuer. diff --git a/include/xrpl/protocol/detail/features.macro b/include/xrpl/protocol/detail/features.macro index d25b0b1f2c..573dca951d 100644 --- a/include/xrpl/protocol/detail/features.macro +++ b/include/xrpl/protocol/detail/features.macro @@ -24,7 +24,7 @@ XRPL_FEATURE(LendingProtocol, Supported::Yes, VoteBehavior::DefaultN XRPL_FEATURE(PermissionDelegationV1_1, Supported::Yes, VoteBehavior::DefaultNo) XRPL_FIX (DirectoryLimit, Supported::Yes, VoteBehavior::DefaultNo) XRPL_FIX (IncludeKeyletFields, Supported::Yes, VoteBehavior::DefaultNo) -XRPL_FEATURE(DynamicMPT, Supported::No, VoteBehavior::DefaultNo) +XRPL_FEATURE(DynamicMPT, Supported::Yes, VoteBehavior::DefaultNo) XRPL_FIX (TokenEscrowV1, Supported::Yes, VoteBehavior::DefaultNo) XRPL_FIX (PriceOracleOrder, Supported::Yes, VoteBehavior::DefaultNo) XRPL_FIX (MPTDeliveredAmount, Supported::Yes, VoteBehavior::DefaultNo) diff --git a/src/libxrpl/tx/transactors/token/MPTokenIssuanceSet.cpp b/src/libxrpl/tx/transactors/token/MPTokenIssuanceSet.cpp index 683d65b6f9..1cb12709d5 100644 --- a/src/libxrpl/tx/transactors/token/MPTokenIssuanceSet.cpp +++ b/src/libxrpl/tx/transactors/token/MPTokenIssuanceSet.cpp @@ -38,35 +38,34 @@ MPTokenIssuanceSet::getFlagsMask(PreflightContext const& ctx) return tfMPTokenIssuanceSetMask; } -// Maps set/clear mutable flags in an MPTokenIssuanceSet transaction to the -// corresponding ledger mutable flags that control whether the change is -// allowed. +// Maps each MPTokenIssuanceSet MutableFlags to the corresponding mutable +// flag and the target ledger flag to mutate. struct MPTMutabilityFlags { std::uint32_t setFlag; - std::uint32_t clearFlag; - std::uint32_t canMutateFlag; + std::uint32_t canEnableFlag; + std::uint32_t ledgerFlag; }; static constexpr std::array kMptMutabilityFlags = { {{.setFlag = tmfMPTSetCanLock, - .clearFlag = tmfMPTClearCanLock, - .canMutateFlag = lsmfMPTCanMutateCanLock}, + .canEnableFlag = lsmfMPTCanEnableCanLock, + .ledgerFlag = lsfMPTCanLock}, {.setFlag = tmfMPTSetRequireAuth, - .clearFlag = tmfMPTClearRequireAuth, - .canMutateFlag = lsmfMPTCanMutateRequireAuth}, + .canEnableFlag = lsmfMPTCanEnableRequireAuth, + .ledgerFlag = lsfMPTRequireAuth}, {.setFlag = tmfMPTSetCanEscrow, - .clearFlag = tmfMPTClearCanEscrow, - .canMutateFlag = lsmfMPTCanMutateCanEscrow}, + .canEnableFlag = lsmfMPTCanEnableCanEscrow, + .ledgerFlag = lsfMPTCanEscrow}, {.setFlag = tmfMPTSetCanTrade, - .clearFlag = tmfMPTClearCanTrade, - .canMutateFlag = lsmfMPTCanMutateCanTrade}, + .canEnableFlag = lsmfMPTCanEnableCanTrade, + .ledgerFlag = lsfMPTCanTrade}, {.setFlag = tmfMPTSetCanTransfer, - .clearFlag = tmfMPTClearCanTransfer, - .canMutateFlag = lsmfMPTCanMutateCanTransfer}, + .canEnableFlag = lsmfMPTCanEnableCanTransfer, + .ledgerFlag = lsfMPTCanTransfer}, {.setFlag = tmfMPTSetCanClawback, - .clearFlag = tmfMPTClearCanClawback, - .canMutateFlag = lsmfMPTCanMutateCanClawback}}}; + .canEnableFlag = lsmfMPTCanEnableCanClawback, + .ledgerFlag = lsfMPTCanClawback}}}; NotTEC MPTokenIssuanceSet::preflight(PreflightContext const& ctx) @@ -118,17 +117,6 @@ MPTokenIssuanceSet::preflight(PreflightContext const& ctx) { if ((*mutableFlags == 0u) || ((*mutableFlags & tmfMPTokenIssuanceSetMutableMask) != 0u)) return temINVALID_FLAG; - - // Can not set and clear the same flag - if (std::ranges::any_of(kMptMutabilityFlags, [mutableFlags](auto const& f) { - return (*mutableFlags & f.setFlag) && (*mutableFlags & f.clearFlag); - })) - return temINVALID_FLAG; - - // Trying to set a non-zero TransferFee and clear MPTCanTransfer - // in the same transaction is not allowed. - if ((transferFee.value_or(0) != 0u) && ((*mutableFlags & tmfMPTClearCanTransfer) != 0u)) - return temMALFORMED; } } @@ -196,16 +184,9 @@ MPTokenIssuanceSet::preclaim(PreclaimContext const& ctx) if (auto const mutableFlags = ctx.tx[~sfMutableFlags]) { if (std::ranges::any_of(kMptMutabilityFlags, [mutableFlags, &isMutableFlag](auto const& f) { - return !isMutableFlag(f.canMutateFlag) && - ((*mutableFlags & (f.setFlag | f.clearFlag))); + return !isMutableFlag(f.canEnableFlag) && ((*mutableFlags & f.setFlag) != 0u); })) return tecNO_PERMISSION; - - // Clearing lsfMPTRequireAuth is invalid when the issuance already has - // a DomainID set, because a DomainID requires RequireAuth to be active. - if ((*mutableFlags & tmfMPTClearRequireAuth) != 0u && - sleMptIssuance->isFieldPresent(sfDomainID)) - return tecNO_PERMISSION; } if (!isMutableFlag(lsmfMPTCanMutateMetadata) && ctx.tx.isFieldPresent(sfMPTokenMetadata)) @@ -265,19 +246,8 @@ MPTokenIssuanceSet::doApply() { if ((mutableFlags & f.setFlag) != 0u) { - flagsOut |= f.canMutateFlag; + flagsOut |= f.ledgerFlag; } - else if ((mutableFlags & f.clearFlag) != 0u) - { - flagsOut &= ~f.canMutateFlag; - } - } - - if ((mutableFlags & tmfMPTClearCanTransfer) != 0u) - { - // If the lsfMPTCanTransfer flag is being cleared, then also clear - // the TransferFee field. - sle->makeFieldAbsent(sfTransferFee); } } diff --git a/src/test/app/AMMMPT_test.cpp b/src/test/app/AMMMPT_test.cpp index 31b54ceee0..5b576b41e4 100644 --- a/src/test/app/AMMMPT_test.cpp +++ b/src/test/app/AMMMPT_test.cpp @@ -2240,7 +2240,9 @@ private: .err = Ter(tecNO_AUTH)}); } - // MPTCanTransfer is not set and the account is not the issuer of MPT + // MPTCanTransfer is not set and the account is not the issuer of MPT. + // The issuer can create the AMM, and an existing LP token holder can + // still withdraw. { Env env{*this}; env.fund(XRP(30'000), gw_, alice_); @@ -2250,15 +2252,17 @@ private: .issuer = gw_, .holders = {alice_}, .pay = 30'000, - .flags = kMptDexFlags, - .mutableFlags = tmfMPTCanMutateCanTransfer, + .flags = tfMPTCanTrade, .authHolder = true}); AMM amm(env, gw_, XRP(10'000), btc(10'000)); - amm.deposit(DepositArg{.account = alice_, .asset1In = XRP(200), .asset2In = btc(200)}); - // Allow to withdraw if transfer is disabled - btc.set({.mutableFlags = tmfMPTClearCanTransfer}); + auto const lpIssue = amm.lptIssue(); + env.trust(STAmount{lpIssue, 20'000'000}, alice_); + env.close(); + env(pay(gw_, alice_, LPToken(1'000'000).tokens(lpIssue))); + env.close(); + amm.withdraw( WithdrawArg{.account = alice_, .asset1Out = btc(100), .assets = {{XRP, btc}}}); } diff --git a/src/test/app/Loan_test.cpp b/src/test/app/Loan_test.cpp index c3b5850231..dbb0033368 100644 --- a/src/test/app/Loan_test.cpp +++ b/src/test/app/Loan_test.cpp @@ -4,7 +4,6 @@ #include #include #include -#include #include #include #include @@ -5423,110 +5422,12 @@ protected: } void - testCoverDepositWithdrawNonTransferableMPT(FeatureBitset feature) + testLendingCanTradeDisabledNoImpact() { - testcase("CoverDeposit blocked, CoverWithdraw allowed when CanTransfer cleared"); - using namespace jtx; - using namespace loanBroker; - - Env env(*this, feature); - - Account const issuer{"issuer"}; - Account const alice{"alice"}; - - env.fund(XRP(100'000), issuer, alice); - env.close(); - - MPTTester mpt( - {.env = env, - .issuer = issuer, - .holders = {alice}, - .pay = 100, - .flags = tfMPTCanTransfer, - .mutableFlags = tmfMPTCanMutateCanTransfer}); - PrettyAsset const asset = mpt["MPT"]; - - Vault const vault{env}; - auto const [createTx, vaultKeylet] = vault.create({.owner = alice, .asset = asset}); - env(createTx); - env.close(); - - auto const brokerKeylet = keylet::loanbroker(alice.id(), env.seq(alice)); - env(set(alice, vaultKeylet.key)); - env.close(); - - auto const brokerSle = env.le(brokerKeylet); - if (!BEAST_EXPECT(brokerSle)) - return; - - Account const pseudoAccount{"Loan Broker pseudo-account", brokerSle->at(sfAccount)}; - - // First, deposit some cover while CanTransfer is set so we have an - // existing position to withdraw from after the governance action. - auto const depositAmount = asset(1); - 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)); - } - - // Issuer governance: clear CanTransfer. - mpt.set({.mutableFlags = tmfMPTClearCanTransfer}); - env.close(); - - // Standard Payment path still forbids third-party transfers. - auto const err = feature[featureMPTokensV2] ? tecNO_PERMISSION : tecNO_AUTH; - env(pay(alice, pseudoAccount, asset(1)), Ter(err)); - env.close(); - - // New cover deposits are blocked - this would create new exposure. - env(coverDeposit(alice, brokerKeylet.key, depositAmount), Ter{tecNO_AUTH}); - env.close(); - - if (auto const refreshed = env.le(brokerKeylet); BEAST_EXPECT(refreshed)) - { - BEAST_EXPECT(refreshed->at(sfCoverAvailable) == 1); - env.require(Balance(pseudoAccount, depositAmount)); - } - - bool const postAmendment = feature[fixCleanup3_2_0]; - if (postAmendment) - { - // Post-fixCleanup3_2_0: existing cover can always be withdrawn - // even when CanTransfer is cleared, so the broker is not trapped. - 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))); - } - } - else - { - // Pre-fixCleanup3_2_0 regression: cover withdraw was blocked, - // trapping the broker's first-loss capital. - env(coverWithdraw(alice, brokerKeylet.key, depositAmount), Ter{tecNO_AUTH}); - env.close(); - - if (auto const refreshed = env.le(brokerKeylet); BEAST_EXPECT(refreshed)) - { - BEAST_EXPECT(refreshed->at(sfCoverAvailable) == 1); - env.require(Balance(pseudoAccount, depositAmount)); - } - } - } - - void - testLoanSetBlockedLoanPayAllowedWhenCanTransferCleared() - { - testcase("LoanSet blocked, LoanPay allowed when CanTransfer cleared"); + testcase("Lending: CanTrade disabled has no impact"); using namespace jtx; using namespace loan; + using namespace loanBroker; Env env(*this, all_); @@ -5542,67 +5443,7 @@ protected: .issuer = issuer, .holders = {lender, borrower}, .flags = tfMPTCanTransfer | tfMPTCanLock, - .mutableFlags = tmfMPTCanMutateCanTransfer}); - PrettyAsset const asset = mpt.issuanceID(); - env(pay(issuer, lender, asset(10'000'000))); - // Fund the borrower with enough to cover principal+interest+fees - env(pay(issuer, borrower, asset(100'000))); - env.close(); - - // Create vault and broker while CanTransfer is set. - auto const broker = createVaultAndBroker(env, asset, lender); - - auto const loanSetFee = Fee(env.current()->fees().base * 2); - - // Create an existing loan while CanTransfer is set. - env(set(borrower, broker.brokerID, 1'000), - Sig(sfCounterpartySignature, lender), - loanSetFee); - env.close(); - auto const loanKeylet = keylet::loan(broker.brokerID, 1); - BEAST_EXPECT(env.le(loanKeylet)); - - // Issuer governance: clear CanTransfer. - mpt.set({.mutableFlags = tmfMPTClearCanTransfer}); - env.close(); - - // Issuing a NEW loan is blocked - it would create new exposure into - // a pool the issuer is restricting. - env(set(borrower, broker.brokerID, 1'000), - Sig(sfCounterpartySignature, lender), - loanSetFee, - Ter{tecNO_AUTH}); - env.close(); - - // Repaying an existing loan is always allowed - blocking it would - // create irrecoverable bad debt and trap SAV depositor principal. - env(pay(borrower, loanKeylet.key, asset(1'000))); - env.close(); - } - - void - testLendingCanTradeClearedNoImpact() - { - testcase("Lending: CanTrade cleared has no impact"); - using namespace jtx; - using namespace loan; - using namespace loanBroker; - - Env env(*this, all_); - - Account const issuer{"issuer"}; - Account const lender{"lender"}; - Account const borrower{"borrower"}; - - env.fund(XRP(1'000'000), issuer, lender, borrower); - env.close(); - - MPTTester mpt( - {.env = env, - .issuer = issuer, - .holders = {lender, borrower}, - .flags = tfMPTCanTransfer | tfMPTCanTrade | tfMPTCanLock, - .mutableFlags = tmfMPTCanMutateCanTrade}); + .mutableFlags = tmfMPTCanEnableCanTrade}); PrettyAsset const asset = mpt.issuanceID(); env(pay(issuer, lender, asset(10'000'000))); env(pay(issuer, borrower, asset(100'000))); @@ -5610,16 +5451,7 @@ protected: auto const broker = createVaultAndBroker(env, asset, lender); - // Sanity: while CanTrade is set, the asset can be placed on the DEX. - env(offer(lender, XRP(1), asset(10))); - env.close(); - - // Issuer governance: clear CanTrade. Loan origination and repayment - // are not trades: nothing in the Lending Protocol should be impacted. - mpt.set({.mutableFlags = tmfMPTClearCanTrade}); - env.close(); - - // Control: clearing CanTrade is observable on the DEX path. + // CanTrade is not set env(offer(lender, XRP(1), asset(10)), Ter{tecNO_PERMISSION}); env.close(); @@ -5644,6 +5476,13 @@ protected: // Cover withdrawal still works. env(coverWithdraw(lender, broker.brokerID, asset(100))); env.close(); + + // Enable CanTrade and verify the DEX path is restored. + mpt.set({.mutableFlags = tmfMPTSetCanTrade}); + env.close(); + + env(offer(lender, XRP(1), asset(10))); + env.close(); } #if LOAN_TODO @@ -8716,8 +8555,7 @@ protected: testRIPD3901(); testBorrowerIsBroker(); testLimitExceeded(); - testLoanSetBlockedLoanPayAllowedWhenCanTransferCleared(); - testLendingCanTradeClearedNoImpact(); + testLendingCanTradeDisabledNoImpact(); testBugOverpaymentPrincipalChange(); testBugOverpayUnroundedAmount(); @@ -8747,7 +8585,6 @@ protected: testPoCUnsignedUnderflowOnFullPayAfterEarlyPeriodic(features); testBatchBypassCounterparty(features); testLoanNextPaymentDueDateOverflow(features); - testCoverDepositWithdrawNonTransferableMPT(features); testSequentialFLCDepletion(features); // Invariants diff --git a/src/test/app/MPToken_test.cpp b/src/test/app/MPToken_test.cpp index 3d6cff0885..2cab3e7c89 100644 --- a/src/test/app/MPToken_test.cpp +++ b/src/test/app/MPToken_test.cpp @@ -3485,7 +3485,7 @@ class MPToken_test : public beast::unit_test::Suite MPTTester mptAlice(env, alice, {.holders = {bob}}); mptAlice.create( {.ownerCount = 1, - .mutableFlags = tmfMPTCanMutateMetadata | tmfMPTCanMutateCanLock | + .mutableFlags = tmfMPTCanMutateMetadata | tmfMPTCanEnableCanLock | tmfMPTCanMutateTransferFee}); // Setting flags is not allowed when MutableFlags is present @@ -3533,33 +3533,6 @@ class MPToken_test : public beast::unit_test::Suite } } - // Can not set and clear the same mutable flag - { - Env env{*this, features}; - MPTTester mptAlice(env, alice, {.holders = {bob}}); - auto const mptID = makeMptID(env.seq(alice), alice); - - auto const flagCombinations = { - tmfMPTSetCanLock | tmfMPTClearCanLock, - tmfMPTSetRequireAuth | tmfMPTClearRequireAuth, - tmfMPTSetCanEscrow | tmfMPTClearCanEscrow, - tmfMPTSetCanTrade | tmfMPTClearCanTrade, - tmfMPTSetCanTransfer | tmfMPTClearCanTransfer, - tmfMPTSetCanClawback | tmfMPTClearCanClawback, - tmfMPTSetCanLock | tmfMPTClearCanLock | tmfMPTClearCanTrade, - tmfMPTSetCanTransfer | tmfMPTClearCanTransfer | tmfMPTSetCanEscrow | - tmfMPTClearCanClawback}; - - for (auto const& mutableFlags : flagCombinations) - { - mptAlice.set( - {.account = alice, - .id = mptID, - .mutableFlags = mutableFlags, - .err = temINVALID_FLAG}); - } - } - // Can not mutate flag which is not mutable { Env env{*this, features}; @@ -3569,17 +3542,11 @@ class MPToken_test : public beast::unit_test::Suite auto const mutableFlags = { tmfMPTSetCanLock, - tmfMPTClearCanLock, tmfMPTSetRequireAuth, - tmfMPTClearRequireAuth, tmfMPTSetCanEscrow, - tmfMPTClearCanEscrow, tmfMPTSetCanTrade, - tmfMPTClearCanTrade, tmfMPTSetCanTransfer, - tmfMPTClearCanTransfer, - tmfMPTSetCanClawback, - tmfMPTClearCanClawback}; + tmfMPTSetCanClawback}; for (auto const& mutableFlag : mutableFlags) { @@ -3623,34 +3590,6 @@ class MPToken_test : public beast::unit_test::Suite .err = temBAD_TRANSFER_FEE}); } - // Test setting non-zero transfer fee and clearing MPTCanTransfer at the - // same time - { - Env env{*this, features}; - MPTTester mptAlice(env, alice, {.holders = {bob}}); - - mptAlice.create( - {.transferFee = 100, - .ownerCount = 1, - .flags = tfMPTCanTransfer, - .mutableFlags = tmfMPTCanMutateTransferFee | tmfMPTCanMutateCanTransfer}); - - // Can not set non-zero transfer fee and clear MPTCanTransfer at the - // same time - mptAlice.set( - {.account = alice, - .mutableFlags = tmfMPTClearCanTransfer, - .transferFee = 1, - .err = temMALFORMED}); - - // Can set transfer fee to zero and clear MPTCanTransfer at the same - // time. tfMPTCanTransfer will be cleared and TransferFee field will - // be removed. - mptAlice.set( - {.account = alice, .mutableFlags = tmfMPTClearCanTransfer, .transferFee = 0}); - BEAST_EXPECT(!mptAlice.isTransferFeePresent()); - } - // Can not set non-zero transfer fee when MPTCanTransfer is not set { Env env{*this, features}; @@ -3658,7 +3597,7 @@ class MPToken_test : public beast::unit_test::Suite mptAlice.create( {.ownerCount = 1, - .mutableFlags = tmfMPTCanMutateTransferFee | tmfMPTCanMutateCanTransfer}); + .mutableFlags = tmfMPTCanMutateTransferFee | tmfMPTCanEnableCanTransfer}); mptAlice.set({.account = alice, .transferFee = 100, .err = tecNO_PERMISSION}); @@ -3691,21 +3630,14 @@ class MPToken_test : public beast::unit_test::Suite mptAlice.create( {.ownerCount = 1, - .mutableFlags = tmfMPTCanMutateCanTrade | tmfMPTCanMutateCanTransfer | + .mutableFlags = tmfMPTCanEnableCanTrade | tmfMPTCanEnableCanTransfer | tmfMPTCanMutateMetadata}); // Can not mutate transfer fee mptAlice.set({.account = alice, .transferFee = 100, .err = tecNO_PERMISSION}); auto const invalidFlags = { - tmfMPTSetCanLock, - tmfMPTClearCanLock, - tmfMPTSetRequireAuth, - tmfMPTClearRequireAuth, - tmfMPTSetCanEscrow, - tmfMPTClearCanEscrow, - tmfMPTSetCanClawback, - tmfMPTClearCanClawback}; + tmfMPTSetCanLock, tmfMPTSetRequireAuth, tmfMPTSetCanEscrow, tmfMPTSetCanClawback}; // Can not mutate flags which are not mutable for (auto const& mutableFlag : invalidFlags) @@ -3716,11 +3648,9 @@ class MPToken_test : public beast::unit_test::Suite // Can mutate MPTCanTrade mptAlice.set({.account = alice, .mutableFlags = tmfMPTSetCanTrade}); - mptAlice.set({.account = alice, .mutableFlags = tmfMPTClearCanTrade}); // Can mutate MPTCanTransfer mptAlice.set({.account = alice, .mutableFlags = tmfMPTSetCanTransfer}); - mptAlice.set({.account = alice, .mutableFlags = tmfMPTClearCanTransfer}); // Can mutate metadata mptAlice.set({.account = alice, .metadata = "test"}); @@ -3789,37 +3719,26 @@ class MPToken_test : public beast::unit_test::Suite BEAST_EXPECT(mptAlice.checkTransferFee(10)); } - // Test flag toggling + // Test mutable flag enablement { - auto testFlagToggle = [&](std::uint32_t createFlags, - std::uint32_t setFlags, - std::uint32_t clearFlags) { + auto testFlagSet = [&](std::uint32_t createFlags, std::uint32_t setFlags) { Env env{*this, features}; MPTTester mptAlice(env, alice); // Create the MPT object with the specified initial flags mptAlice.create({.metadata = "test", .ownerCount = 1, .mutableFlags = createFlags}); - // Set and clear the flag multiple times - mptAlice.set({.account = alice, .mutableFlags = setFlags}); - mptAlice.set({.account = alice, .mutableFlags = clearFlags}); - mptAlice.set({.account = alice, .mutableFlags = clearFlags}); + // Setting the same mutable capability more than once is harmless. mptAlice.set({.account = alice, .mutableFlags = setFlags}); mptAlice.set({.account = alice, .mutableFlags = setFlags}); - mptAlice.set({.account = alice, .mutableFlags = clearFlags}); - mptAlice.set({.account = alice, .mutableFlags = setFlags}); - mptAlice.set({.account = alice, .mutableFlags = clearFlags}); }; - testFlagToggle(tmfMPTCanMutateCanLock, tfMPTCanLock, tmfMPTClearCanLock); - testFlagToggle( - tmfMPTCanMutateRequireAuth, tmfMPTSetRequireAuth, tmfMPTClearRequireAuth); - testFlagToggle(tmfMPTCanMutateCanEscrow, tmfMPTSetCanEscrow, tmfMPTClearCanEscrow); - testFlagToggle(tmfMPTCanMutateCanTrade, tmfMPTSetCanTrade, tmfMPTClearCanTrade); - testFlagToggle( - tmfMPTCanMutateCanTransfer, tmfMPTSetCanTransfer, tmfMPTClearCanTransfer); - testFlagToggle( - tmfMPTCanMutateCanClawback, tmfMPTSetCanClawback, tmfMPTClearCanClawback); + testFlagSet(tmfMPTCanEnableCanLock, tmfMPTSetCanLock); + testFlagSet(tmfMPTCanEnableRequireAuth, tmfMPTSetRequireAuth); + testFlagSet(tmfMPTCanEnableCanEscrow, tmfMPTSetCanEscrow); + testFlagSet(tmfMPTCanEnableCanTrade, tmfMPTSetCanTrade); + testFlagSet(tmfMPTCanEnableCanTransfer, tmfMPTSetCanTransfer); + testFlagSet(tmfMPTCanEnableCanClawback, tmfMPTSetCanClawback); } } @@ -3840,7 +3759,7 @@ class MPToken_test : public beast::unit_test::Suite {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanLock | tfMPTCanTransfer, - .mutableFlags = tmfMPTCanMutateCanLock | tmfMPTCanMutateCanTrade | + .mutableFlags = tmfMPTCanEnableCanLock | tmfMPTCanEnableCanTrade | tmfMPTCanMutateTransferFee}); mptAlice.authorize({.account = bob, .holderCount = 1}); @@ -3848,11 +3767,8 @@ class MPToken_test : public beast::unit_test::Suite mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock}); // Can mutate the mutable flags and fields - mptAlice.set({.account = alice, .mutableFlags = tmfMPTClearCanLock}); mptAlice.set({.account = alice, .mutableFlags = tmfMPTSetCanLock}); - mptAlice.set({.account = alice, .mutableFlags = tmfMPTClearCanLock}); mptAlice.set({.account = alice, .mutableFlags = tmfMPTSetCanTrade}); - mptAlice.set({.account = alice, .mutableFlags = tmfMPTClearCanTrade}); mptAlice.set({.account = alice, .transferFee = 200}); } @@ -3864,7 +3780,7 @@ class MPToken_test : public beast::unit_test::Suite {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanLock, - .mutableFlags = tmfMPTCanMutateCanLock | tmfMPTCanMutateCanClawback | + .mutableFlags = tmfMPTCanEnableCanLock | tmfMPTCanEnableCanClawback | tmfMPTCanMutateMetadata}); mptAlice.authorize({.account = bob, .holderCount = 1}); @@ -3872,36 +3788,23 @@ class MPToken_test : public beast::unit_test::Suite mptAlice.set({.account = alice, .flags = tfMPTLock}); // Can mutate the mutable flags and fields - mptAlice.set({.account = alice, .mutableFlags = tmfMPTClearCanLock}); mptAlice.set({.account = alice, .mutableFlags = tmfMPTSetCanLock}); - mptAlice.set({.account = alice, .mutableFlags = tmfMPTClearCanLock}); mptAlice.set({.account = alice, .mutableFlags = tmfMPTSetCanClawback}); - mptAlice.set({.account = alice, .mutableFlags = tmfMPTClearCanClawback}); mptAlice.set({.account = alice, .metadata = "mutate"}); } - // Test lock and unlock after mutating MPTCanLock + // Test lock and unlock after enabling MPTCanLock { Env env{*this, features}; MPTTester mptAlice(env, alice, {.holders = {bob}}); mptAlice.create( {.ownerCount = 1, .holderCount = 0, - .flags = tfMPTCanLock, - .mutableFlags = tmfMPTCanMutateCanLock | tmfMPTCanMutateCanClawback | + .mutableFlags = tmfMPTCanEnableCanLock | tmfMPTCanEnableCanClawback | tmfMPTCanMutateMetadata}); mptAlice.authorize({.account = bob, .holderCount = 1}); - // Can lock and unlock - mptAlice.set({.account = alice, .flags = tfMPTLock}); - mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock}); - mptAlice.set({.account = alice, .flags = tfMPTUnlock}); - mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTUnlock}); - - // Clear lsfMPTCanLock - mptAlice.set({.account = alice, .mutableFlags = tmfMPTClearCanLock}); - - // Can not lock or unlock + // Can not lock or unlock before MPTCanLock is enabled mptAlice.set({.account = alice, .flags = tfMPTLock, .err = tecNO_PERMISSION}); mptAlice.set({.account = alice, .flags = tfMPTUnlock, .err = tecNO_PERMISSION}); mptAlice.set( @@ -3909,10 +3812,10 @@ class MPToken_test : public beast::unit_test::Suite mptAlice.set( {.account = alice, .holder = bob, .flags = tfMPTUnlock, .err = tecNO_PERMISSION}); - // Set MPTCanLock again + // Set MPTCanLock mptAlice.set({.account = alice, .mutableFlags = tmfMPTSetCanLock}); - // Can lock and unlock again + // Can lock and unlock mptAlice.set({.account = alice, .flags = tfMPTLock}); mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock}); mptAlice.set({.account = alice, .flags = tfMPTUnlock}); @@ -3926,83 +3829,30 @@ class MPToken_test : public beast::unit_test::Suite testcase("Mutate MPTRequireAuth"); using namespace test::jtx; - // test mutating RequireAuth flag on the issuance and its effect on payment authorization - { - Env env{*this, features}; - Account const alice("alice"); - Account const bob("bob"); + // test enabling RequireAuth flag on the issuance and its effect on payment + // authorization + Env env{*this, features}; + Account const alice("alice"); + Account const bob("bob"); - MPTTester mptAlice(env, alice, {.holders = {bob}}); - mptAlice.create( - {.ownerCount = 1, - .flags = tfMPTRequireAuth, - .mutableFlags = tmfMPTCanMutateRequireAuth}); + MPTTester mptAlice(env, alice, {.holders = {bob}}); + mptAlice.create( + {.ownerCount = 1, + .flags = tfMPTCanTransfer, + .mutableFlags = tmfMPTCanEnableRequireAuth}); - mptAlice.authorize({.account = bob}); - mptAlice.authorize({.account = alice, .holder = bob}); + mptAlice.authorize({.account = bob}); + mptAlice.pay(alice, bob, 1000); - // Pay to bob - mptAlice.pay(alice, bob, 1000); + // Set RequireAuth because it is mutable. + mptAlice.set({.account = alice, .mutableFlags = tmfMPTSetRequireAuth}); - // Unauthorize bob - mptAlice.authorize({.account = alice, .holder = bob, .flags = tfMPTUnauthorize}); + // This should fail because bob is not authorized yet. + mptAlice.pay(alice, bob, 1000, tecNO_AUTH); - // Can not pay to bob - mptAlice.pay(bob, alice, 100, tecNO_AUTH); - - // Clear RequireAuth - mptAlice.set({.account = alice, .mutableFlags = tmfMPTClearRequireAuth}); - - // Can pay to bob - mptAlice.pay(alice, bob, 1000); - - // Set RequireAuth again - mptAlice.set({.account = alice, .mutableFlags = tmfMPTSetRequireAuth}); - - // Can not pay to bob since he is not authorized - mptAlice.pay(bob, alice, 100, tecNO_AUTH); - - // Authorize bob again - mptAlice.authorize({.account = alice, .holder = bob}); - - // Can pay to bob again - mptAlice.pay(alice, bob, 100); - } - - // Cannot clear RequireAuth when a DomainID is set on the issuance - { - Account const alice{"alice"}; - Account const bob{"bob"}; - Account const credIssuer{"credIssuer"}; - pdomain::Credentials const credentials{ - {.issuer = credIssuer, .credType = "credential"}}; - - Env env{*this, features}; - env.fund(XRP(1000), credIssuer); - env.close(); - - env(pdomain::setTx(credIssuer, credentials)); - env.close(); - auto const domainId = pdomain::getNewDomain(env.meta()); - - MPTTester mptAlice(env, alice, {.holders = {bob}}); - mptAlice.create({ - .ownerCount = 1, - .flags = tfMPTRequireAuth, - .mutableFlags = tmfMPTCanMutateRequireAuth, - .domainID = domainId, - }); - - // Clearing RequireAuth while a DomainID is present must be rejected, - mptAlice.set({ - .account = alice, - .mutableFlags = tmfMPTClearRequireAuth, - .err = tecNO_PERMISSION, - }); - - // Setting RequireAuth (already set) is still allowed, though it has no effect. - mptAlice.set({.account = alice, .mutableFlags = tmfMPTSetRequireAuth}); - } + // Issuer authorizes bob and pay should succeed. + mptAlice.authorize({.account = alice, .holder = bob}); + mptAlice.pay(alice, bob, 1000); } void @@ -4023,7 +3873,7 @@ class MPToken_test : public beast::unit_test::Suite {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer, - .mutableFlags = tmfMPTCanMutateCanEscrow}); + .mutableFlags = tmfMPTCanEnableCanEscrow}); mptAlice.authorize({.account = carol}); mptAlice.authorize({.account = bob}); @@ -4045,14 +3895,6 @@ class MPToken_test : public beast::unit_test::Suite escrow::kCondition(escrow::kCb1), escrow::kFinishTime(env.now() + 1s), Fee(baseFee * 150)); - - // Clear MPTCanEscrow - mptAlice.set({.account = alice, .mutableFlags = tmfMPTClearCanEscrow}); - env(escrow::create(carol, bob, mpt(3)), - escrow::kCondition(escrow::kCb1), - escrow::kFinishTime(env.now() + 1s), - Fee(baseFee * 150), - Ter(tecNO_PERMISSION)); } void @@ -4071,7 +3913,7 @@ class MPToken_test : public beast::unit_test::Suite MPTTester mptAlice(env, alice, {.holders = {bob, carol}}); mptAlice.create( {.ownerCount = 1, - .mutableFlags = tmfMPTCanMutateCanTransfer | tmfMPTCanMutateTransferFee}); + .mutableFlags = tmfMPTCanEnableCanTransfer | tmfMPTCanMutateTransferFee}); mptAlice.authorize({.account = bob}); mptAlice.authorize({.account = carol}); @@ -4115,19 +3957,9 @@ class MPToken_test : public beast::unit_test::Suite env(pay(bob, carol, mptAlice(50)), Txflags(tfPartialPayment)); BEAST_EXPECT(env.balance(carol, mptc) == mptc(49)); } - - // Alice clears MPTCanTransfer - mptAlice.set({.account = alice, .mutableFlags = tmfMPTClearCanTransfer}); - - // TransferFee field is removed when MPTCanTransfer is cleared - BEAST_EXPECT(!mptAlice.isTransferFeePresent()); - - // Bob can not pay - mptAlice.pay(bob, carol, 50, tecNO_AUTH); } - // Can set transfer fee to zero when MPTCanTransfer is not set, but - // tmfMPTCanMutateTransferFee is set. + // Can set transfer fee to zero when tmfMPTCanMutateTransferFee is set. { Env env{*this, features}; @@ -4136,18 +3968,12 @@ class MPToken_test : public beast::unit_test::Suite {.transferFee = 100, .ownerCount = 1, .flags = tfMPTCanTransfer, - .mutableFlags = tmfMPTCanMutateTransferFee | tmfMPTCanMutateCanTransfer}); + .mutableFlags = tmfMPTCanMutateTransferFee}); BEAST_EXPECT(mptAlice.checkTransferFee(100)); - // Clear MPTCanTransfer and transfer fee is removed - mptAlice.set({.account = alice, .mutableFlags = tmfMPTClearCanTransfer}); - BEAST_EXPECT(!mptAlice.isTransferFeePresent()); - - // Can still set transfer fee to zero, although it is already zero + // Setting transfer fee to zero removes the field. mptAlice.set({.account = alice, .transferFee = 0}); - - // TransferFee field is still not present BEAST_EXPECT(!mptAlice.isTransferFeePresent()); } } @@ -4165,7 +3991,7 @@ class MPToken_test : public beast::unit_test::Suite MPTTester mptAlice(env, alice, {.holders = {bob}}); mptAlice.create( - {.ownerCount = 1, .holderCount = 0, .mutableFlags = tmfMPTCanMutateCanClawback}); + {.ownerCount = 1, .holderCount = 0, .mutableFlags = tmfMPTCanEnableCanClawback}); // Bob creates an MPToken mptAlice.authorize({.account = bob}); @@ -4181,12 +4007,6 @@ class MPToken_test : public beast::unit_test::Suite // Can clawback now mptAlice.claw(alice, bob, 1); - - // Clear MPTCanClawback - mptAlice.set({.account = alice, .mutableFlags = tmfMPTClearCanClawback}); - - // Can not clawback - mptAlice.claw(alice, bob, 1, tecNO_PERMISSION); } void @@ -4536,27 +4356,22 @@ class MPToken_test : public beast::unit_test::Suite { Env env(*this); env.fund(XRP(1'000), gw, alice, carol); - MPTTester btc( + MPTTester const btc( {.env = env, .issuer = gw, .holders = {alice, carol}, .pay = 100, - .flags = tfMPTCanTrade, - .mutableFlags = tmfMPTCanMutateCanTransfer}); - MPTTester eth( + .flags = tfMPTCanTrade | tfMPTCanTransfer}); + MPTTester const eth( {.env = env, .issuer = gw, .holders = {alice, carol}, .pay = 100, - .flags = tfMPTCanTrade | tfMPTCanTransfer, - .mutableFlags = tmfMPTCanMutateCanTransfer}); + .flags = tfMPTCanTrade}); // Can create env(offer(alice, eth(10), btc(10)), Txflags(tfPassive)); - btc.set({.mutableFlags = tmfMPTSetCanTransfer}); - eth.set({.mutableFlags = tmfMPTClearCanTransfer}); - env(offer(alice, eth(10), btc(10)), Txflags(tfPassive)); - BEAST_EXPECT(getAccountOffers(env, alice)[jss::offers].size() == 2); + BEAST_EXPECT(getAccountOffers(env, alice)[jss::offers].size() == 1); // issuer can create env(offer(gw, eth(10), btc(10)), Txflags(tfPassive)); @@ -4584,14 +4399,14 @@ class MPToken_test : public beast::unit_test::Suite .holders = {alice, carol}, .pay = 100, .flags = tfMPTCanTransfer, - .mutableFlags = tmfMPTCanMutateCanTrade}); + .mutableFlags = tmfMPTCanEnableCanTrade}); MPTTester const eth( {.env = env, .issuer = gw, .holders = {alice, carol}, .pay = 100, .flags = tfMPTCanTrade, - .mutableFlags = tmfMPTCanMutateCanTrade}); + .mutableFlags = tmfMPTCanEnableCanTrade}); // Can't create env(offer(gw, eth(10), btc(10)), Ter(tecNO_PERMISSION)); @@ -4828,29 +4643,29 @@ class MPToken_test : public beast::unit_test::Suite .holders = {alice, carol, bob}, .pay = 1'000, .flags = tfMPTCanLock | kMptDexFlags, - .mutableFlags = tmfMPTCanMutateRequireAuth | tmfMPTCanMutateCanTrade | - tmfMPTCanMutateCanTransfer}); + .mutableFlags = tmfMPTCanEnableRequireAuth | tmfMPTCanEnableCanTrade | + tmfMPTCanEnableCanTransfer}); MPTTester eth( {.env = env, .issuer = gw, .holders = {alice, carol, bob}, .pay = 1'000, .flags = tfMPTCanLock | kMptDexFlags, - .mutableFlags = tmfMPTCanMutateCanTransfer}); + .mutableFlags = tmfMPTCanEnableCanTransfer}); MPTTester const usd( {.env = env, .issuer = gw, .holders = {alice, carol, bob}, .pay = 1'000, .flags = kMptDexFlags | tfMPTCanLock, - .mutableFlags = tmfMPTCanMutateCanTransfer}); + .mutableFlags = tmfMPTCanEnableCanTransfer}); MPTTester const cad( {.env = env, .issuer = gw, .holders = {alice, carol, bob}, .pay = 1'000, .flags = kMptDexFlags | tfMPTCanLock, - .mutableFlags = tmfMPTCanMutateCanTransfer}); + .mutableFlags = tmfMPTCanEnableCanTransfer}); env(offer(bob, eth(1'000), btc(1'000)), Txflags(tfPassive)); env.close(); @@ -4896,13 +4711,33 @@ class MPToken_test : public beast::unit_test::Suite // BTC is transferred from ed to bob, ed is not authorized env(pay(ed, gw, eth(10)), Path(~eth), Sendmax(btc(10)), Ter(tecNO_AUTH)); env.close(); - btc.set({.mutableFlags = tmfMPTClearRequireAuth}); + } - // MPTCanTransfer is not set + // MPTCanTransfer is not set. + { + auto const ed = Account{"ed"}; + Env env{*this, features}; + env.fund(XRP(1'000), gw, alice, carol, bob, ed); + MPTTester const btc( + {.env = env, + .issuer = gw, + .holders = {alice, carol, bob, ed}, + .pay = 1'000, + .flags = tfMPTCanTrade}); + MPTTester const eth( + {.env = env, + .issuer = gw, + .holders = {alice, carol, bob, ed}, + .pay = 1'000, + .flags = kMptDexFlags}); + + env(offer(bob, eth(1'000), btc(1'000)), Txflags(tfPassive)); + env.close(); + env(offer(bob, btc(1'000), eth(1'000)), Txflags(tfPassive)); + env.close(); // Fail regardless if source/destination is the issuer or // not since the offer is owned by a holder. - btc.set({.mutableFlags = tmfMPTClearCanTransfer}); env(pay(ed, carol, btc(10)), Path(~btc), Sendmax(eth(10)), Ter(tecPATH_PARTIAL)); env(pay(carol, ed, btc(10)), Path(~btc), Sendmax(eth(10)), Ter(tecPATH_PARTIAL)); env(pay(ed, carol, eth(10)), Path(~eth), Sendmax(btc(10)), Ter(tecPATH_PARTIAL)); @@ -4926,124 +4761,166 @@ class MPToken_test : public beast::unit_test::Suite env(pay(ed, gw, btc(10)), Path(~btc), Sendmax(eth(10))); env.close(); } - // Multiple steps: CAD/USD, USD/BTC, BTC/ETH + + // Multiple steps: CAD/USD, USD/BTC, BTC/ETH. + // takerGets can transfer if: + // - CanTransfer is set + // - The offer's owner is the issuer + // - BookStep is the last step, which means strand's destination is + // the issuer + // takerPays can transfer if + // - BookStep is the first step, which means strand's source is + // the issuer + // - The offer's owner is the issuer + // - Previous step is BookStep, which transfers per above + // - CanTransfer is set { - auto const ed = Account{"ed"}; - Env env{*this, features}; - env.fund(XRP(1'000), gw, alice, carol, bob, ed); - env.close(); - MPTTester btc( - {.env = env, - .issuer = gw, - .holders = {alice, carol, bob}, - .pay = 1'000, - .flags = tfMPTCanLock | kMptDexFlags, - .mutableFlags = tmfMPTCanMutateCanTransfer}); - MPTTester eth( - {.env = env, - .issuer = gw, - .holders = {alice, carol, bob}, - .pay = 1'000, - .flags = tfMPTCanLock | kMptDexFlags, - .mutableFlags = tmfMPTCanMutateCanTransfer}); - MPTTester usd( - {.env = env, - .issuer = gw, - .holders = {alice, carol, bob}, - .pay = 1'000, - .flags = kMptDexFlags | tfMPTCanLock, - .mutableFlags = tmfMPTCanMutateCanTransfer}); - MPTTester cad( - {.env = env, - .issuer = gw, - .holders = {alice, carol, bob}, - .pay = 1'000, - .flags = kMptDexFlags | tfMPTCanLock, - .mutableFlags = tmfMPTCanMutateCanTransfer}); - // takerGets can transfer if: - // - CanTransfer is set - // - The offer's owner is the issuer - // - BookStep is the last step, which means strand's destination is - // the issuer - // takerPays can transfer if - // - BookStep is the first step, which means strand's source is - // the issuer - // - The offer's owner is the issuer - // - Previous step is BookStep, which transfers per above - // - CanTransfer is set - env(offer(bob, cad(100), usd(100)), Txflags(tfPassive)); - env(offer(bob, usd(100), btc(100)), Txflags(tfPassive)); - env(offer(bob, btc(100), eth(100)), Txflags(tfPassive)); - env.close(); - BEAST_EXPECT(expectOffers(env, bob, 3)); - btc.set({.mutableFlags = tmfMPTSetCanTransfer}); - usd.set({.mutableFlags = tmfMPTClearCanTransfer}); - // TakerGets - // fail - CAD/USD is owned by bob - env(pay(alice, carol, eth(1)), - Path(~usd, ~btc, ~eth), - Sendmax(cad(1)), - Ter(tecPATH_PARTIAL)); - auto seq(env.seq(gw)); - env(offer(gw, usd(1), btc(1)), Txflags(tfPassive)); - env.close(); - // fail - CAD/USD is owned by bob - env(pay(alice, carol, eth(1)), - Path(~usd, ~btc, ~eth), - Sendmax(cad(1)), - Ter(tecPATH_PARTIAL)); - env.close(); - env(offerCancel(gw, seq)); - env(offer(gw, cad(1), usd(1)), Txflags(tfPassive)); - env.close(); - BEAST_EXPECT(expectOffers(env, bob, 3)); - // succeed - CAD/USD is owned by issuer - env(pay(alice, carol, eth(1)), Path(~usd, ~btc, ~eth), Sendmax(cad(1))); - env.close(); - // bob's CAD/USD is deleted - BEAST_EXPECT(expectOffers(env, bob, 2)); - env(offer(bob, cad(100), usd(100)), Txflags(tfPassive)); - BEAST_EXPECT(expectOffers(env, gw, 0)); - usd.set({.mutableFlags = tmfMPTSetCanTransfer}); - eth.set({.mutableFlags = tmfMPTClearCanTransfer}); - // fail - BTC/ETH is owned by bob, destination is carol - env(pay(alice, carol, eth(1)), - Path(~usd, ~btc, ~eth), - Sendmax(cad(1)), - Ter(tecPATH_PARTIAL)); - env.close(); - BEAST_EXPECT(expectOffers(env, bob, 3)); - // succeed - destination is an issuer - env(pay(alice, gw, eth(1)), Path(~usd, ~btc, ~eth), Sendmax(cad(1))); - env.close(); - BEAST_EXPECT(expectOffers(env, bob, 3)); - // TakerPays - eth.set({.mutableFlags = tmfMPTSetCanTransfer}); - cad.set({.mutableFlags = tmfMPTClearCanTransfer}); - // fail - CAD/USD is owned by bob, source is alice - env(pay(alice, carol, eth(1)), - Path(~usd, ~btc, ~eth), - Sendmax(cad(1)), - Ter(tecPATH_PARTIAL)); - // succeed - source is the issuer - env(pay(gw, carol, eth(1)), Path(~usd, ~btc, ~eth), Sendmax(cad(1))); - env.close(); - env(offer(gw, cad(1), usd(1)), Txflags(tfPassive)); - env.close(); - // succeed - CAD/USD is owned by issuer - env(pay(alice, carol, eth(1)), Path(~usd, ~btc, ~eth), Sendmax(cad(1))); - env.close(); - BEAST_EXPECT(expectOffers(env, gw, 0)); - BEAST_EXPECT(expectOffers(env, bob, 2)); - cad.set({.mutableFlags = tmfMPTSetCanTransfer}); - btc.set({.mutableFlags = tmfMPTClearCanTransfer}); - env(offer(bob, cad(1), usd(1)), Txflags(tfPassive)); - env(offer(gw, usd(1), btc(1)), Txflags(tfPassive)); - env.close(); - // succeed - USD/BTC is owned by issuer - env(pay(alice, carol, eth(1)), Path(~usd, ~btc, ~eth), Sendmax(cad(1))); - env.close(); - BEAST_EXPECT(expectOffers(env, gw, 0)); + // enum to indicate which MPT doesn't set CanTransfer flag. + enum class NoTransferMPT { BTC, ETH, USD, CAD }; + + // Lambda to test multi-step payment with one of the MPTs not setting CanTransfer flag. + auto const testMultiStepMPTCanTransfer = [&](NoTransferMPT const noTransferMPT, + auto const& test) { + auto const getFlags = [&](NoTransferMPT const mpt) { + return mpt == noTransferMPT ? tfMPTCanTrade : kMptDexFlags; + }; + + Env env{*this, features}; + env.fund(XRP(1'000), gw, alice, carol, bob); + env.close(); + MPTTester const btc( + {.env = env, + .issuer = gw, + .holders = {alice, carol, bob}, + .pay = 1'000, + .flags = getFlags(NoTransferMPT::BTC)}); + MPTTester const eth( + {.env = env, + .issuer = gw, + .holders = {alice, carol, bob}, + .pay = 1'000, + .flags = getFlags(NoTransferMPT::ETH)}); + MPTTester const usd( + {.env = env, + .issuer = gw, + .holders = {alice, carol, bob}, + .pay = 1'000, + .flags = getFlags(NoTransferMPT::USD)}); + MPTTester const cad( + {.env = env, + .issuer = gw, + .holders = {alice, carol, bob}, + .pay = 1'000, + .flags = getFlags(NoTransferMPT::CAD)}); + + env(offer(bob, cad(100), usd(100)), Txflags(tfPassive)); + env(offer(bob, usd(100), btc(100)), Txflags(tfPassive)); + env(offer(bob, btc(100), eth(100)), Txflags(tfPassive)); + env.close(); + + test(env, btc, eth, usd, cad); + }; + + // USD starts without MPTCanTransfer. + testMultiStepMPTCanTransfer( + NoTransferMPT::USD, + [&](Env& env, + MPTTester const& btc, + MPTTester const& eth, + MPTTester const& usd, + MPTTester const& cad) { + BEAST_EXPECT(expectOffers(env, bob, 3)); + + // fail - CAD/USD is owned by bob + env(pay(alice, carol, eth(1)), + Path(~usd, ~btc, ~eth), + Sendmax(cad(1)), + Ter(tecPATH_PARTIAL)); + + auto seq(env.seq(gw)); + env(offer(gw, usd(1), btc(1)), Txflags(tfPassive)); + env.close(); + // fail - CAD/USD is owned by bob + env(pay(alice, carol, eth(1)), + Path(~usd, ~btc, ~eth), + Sendmax(cad(1)), + Ter(tecPATH_PARTIAL)); + env.close(); + env(offerCancel(gw, seq)); + env(offer(gw, cad(1), usd(1)), Txflags(tfPassive)); + env.close(); + BEAST_EXPECT(expectOffers(env, bob, 3)); + // succeed - CAD/USD is owned by issuer + env(pay(alice, carol, eth(1)), Path(~usd, ~btc, ~eth), Sendmax(cad(1))); + env.close(); + // bob's CAD/USD is deleted. + BEAST_EXPECT(expectOffers(env, bob, 2)); + env(offer(bob, cad(100), usd(100)), Txflags(tfPassive)); + BEAST_EXPECT(expectOffers(env, gw, 0)); + }); + + // ETH starts without MPTCanTransfer. + testMultiStepMPTCanTransfer( + NoTransferMPT::ETH, + [&](Env& env, + MPTTester const& btc, + MPTTester const& eth, + MPTTester const& usd, + MPTTester const& cad) { + // fail - BTC/ETH is owned by bob, destination is carol + env(pay(alice, carol, eth(1)), + Path(~usd, ~btc, ~eth), + Sendmax(cad(1)), + Ter(tecPATH_PARTIAL)); + env.close(); + BEAST_EXPECT(expectOffers(env, bob, 3)); + + // succeed - destination is an issuer + env(pay(alice, gw, eth(1)), Path(~usd, ~btc, ~eth), Sendmax(cad(1))); + env.close(); + BEAST_EXPECT(expectOffers(env, bob, 3)); + }); + + // CAD starts without MPTCanTransfer. + testMultiStepMPTCanTransfer( + NoTransferMPT::CAD, + [&](Env& env, + MPTTester const& btc, + MPTTester const& eth, + MPTTester const& usd, + MPTTester const& cad) { + // fail - CAD/USD is owned by bob, source is alice + env(pay(alice, carol, eth(1)), + Path(~usd, ~btc, ~eth), + Sendmax(cad(1)), + Ter(tecPATH_PARTIAL)); + // succeed - source is the issuer + env(pay(gw, carol, eth(1)), Path(~usd, ~btc, ~eth), Sendmax(cad(1))); + env.close(); + env(offer(gw, cad(1), usd(1)), Txflags(tfPassive)); + env.close(); + // succeed - CAD/USD is owned by issuer + env(pay(alice, carol, eth(1)), Path(~usd, ~btc, ~eth), Sendmax(cad(1))); + env.close(); + BEAST_EXPECT(expectOffers(env, gw, 0)); + BEAST_EXPECT(expectOffers(env, bob, 2)); + }); + + // BTC starts without MPTCanTransfer. + testMultiStepMPTCanTransfer( + NoTransferMPT::BTC, + [&](Env& env, + MPTTester const& btc, + MPTTester const& eth, + MPTTester const& usd, + MPTTester const& cad) { + env(offer(gw, usd(1), btc(1)), Txflags(tfPassive)); + env.close(); + // succeed - USD/BTC is owned by issuer + env(pay(alice, carol, eth(1)), Path(~usd, ~btc, ~eth), Sendmax(cad(1))); + env.close(); + BEAST_EXPECT(expectOffers(env, gw, 0)); + }); } // MPTCanTrade is not set @@ -5057,48 +4934,38 @@ class MPToken_test : public beast::unit_test::Suite .holders = {alice, carol, bob}, .pay = 1'000, .flags = tfMPTCanTransfer, - .mutableFlags = tmfMPTCanMutateCanTrade}); + .mutableFlags = tmfMPTCanEnableCanTrade}); MPTTester const eth( {.env = env, .issuer = gw, .holders = {alice, carol, bob}, .pay = 1'000, - .flags = tfMPTCanTransfer | tfMPTCanTrade, - .mutableFlags = tmfMPTCanMutateCanTrade}); + .flags = kMptDexFlags}); MPTTester const usd( {.env = env, .issuer = gw, .holders = {alice, carol, bob}, .pay = 1'000, - .flags = tfMPTCanTransfer | tfMPTCanTrade, - .mutableFlags = tmfMPTCanMutateCanTrade}); + .flags = kMptDexFlags}); env(pay(alice, carol, eth(1)), Path(~eth), Sendmax(btc(1)), Ter(tecNO_PERMISSION)); env(pay(alice, carol, btc(1)), Path(~btc), Sendmax(eth(1)), Ter(tecNO_PERMISSION)); env.close(); + // Enable MPTCanTrade so BTC can be crossed through offers. btc.set({.mutableFlags = tmfMPTSetCanTrade}); env(offer(bob, XRP(1), btc(1))); env(offer(bob, btc(1), eth(1))); env(offer(bob, eth(1), usd(1))); env.close(); - btc.set({.mutableFlags = tmfMPTClearCanTrade}); + BEAST_EXPECT(expectOffers(env, bob, 3)); + env(pay(gw, carol, usd(1)), Path(~btc, ~eth, ~usd), Sendmax(XRP(1)), - Txflags(tfPartialPayment | tfNoRippleDirect), - Ter(tecNO_PERMISSION)); + Txflags(tfPartialPayment | tfNoRippleDirect)); env.close(); - BEAST_EXPECT(expectOffers(env, bob, 3)); - - env(pay(carol, bob, btc(10)), Sendmax(XRP(10)), Ter(tecNO_PERMISSION)); - env(pay(carol, bob, XRP(10)), Sendmax(btc(10)), Ter(tecNO_PERMISSION)); - env(pay(gw, bob, btc(10)), Sendmax(XRP(10)), Ter(tecNO_PERMISSION)); - env(pay(gw, bob, XRP(10)), Sendmax(btc(10)), Ter(tecNO_PERMISSION)); - env(pay(carol, gw, btc(10)), Sendmax(XRP(10)), Ter(tecNO_PERMISSION)); - env(pay(carol, gw, XRP(10)), Sendmax(btc(10)), Ter(tecNO_PERMISSION)); - env.close(); - BEAST_EXPECT(expectOffers(env, bob, 3)); + BEAST_EXPECT(expectOffers(env, bob, 0)); } // Holders are locked @@ -6891,7 +6758,7 @@ class MPToken_test : public beast::unit_test::Suite .issuer = gw, .holders = {alice, carol}, .flags = tfMPTCanTrade, - .mutableFlags = tmfMPTCanMutateCanTransfer}); + .mutableFlags = tmfMPTCanEnableCanTransfer}); // src is issuer uint256 checkId{keylet::check(gw, env.seq(gw)).key}; @@ -6933,13 +6800,8 @@ class MPToken_test : public beast::unit_test::Suite env.close(); env(pay(gw, alice, mpt(10))); env.close(); - // can't cash - mpt.set({.account = gw, .mutableFlags = tmfMPTClearCanTransfer}); - env.close(); - env(check::cash(carol, checkId, mpt(10)), Ter(tecNO_AUTH)); - env.close(); - // can cash - mpt.set({.account = gw, .mutableFlags = tmfMPTSetCanTransfer}); + + // can cash since MPTCanTransfer is enabled env(check::cash(carol, checkId, mpt(10))); env.close(); } @@ -7358,296 +7220,332 @@ class MPToken_test : public beast::unit_test::Suite Env env(*this); env.fund(XRP(1'000'000), gw, alice, carol); - auto usd = MPTTester( - {.env = env, - .issuer = gw, - .flags = tfMPTCanLock | kMptDexFlags, - .mutableFlags = tmfMPTCanMutateRequireAuth | tmfMPTCanMutateCanTransfer | - tmfMPTCanMutateCanClawback | tmfMPTCanMutateCanTrade}); - auto eur = MPTTester({.env = env, .issuer = gw, .holders = {alice}, .pay = 1'000'000}); - auto const increment = env.current()->fees().increment; auto const txfee = Fee(drops(increment)); auto const badMPT = MPT(gw, 1'000); - auto createDeleteAMM = [&](Account const& lp) { - AMM amm( - env, - lp, - usd(1'000), - eur(1'000), - CreateArg{.fee = static_cast(increment.value())}); - amm.withdrawAll(lp); - BEAST_EXPECT(!amm.ammExists()); + auto const makeMPT = [&](std::uint32_t const flags, + Holders holders = {}, + std::uint64_t const pay = 0, + std::optional const mutableFlags = + std::nullopt) { + return MPTTester( + {.env = env, + .issuer = gw, + .holders = holders, + .pay = pay ? std::optional{pay} : std::nullopt, + .flags = flags, + .mutableFlags = mutableFlags}); + }; + + auto const makeDexMPT = [&](Holders holders = {}, std::uint64_t const pay = 0) { + return makeMPT( + tfMPTCanLock | kMptDexFlags, + holders, + pay, + tmfMPTCanEnableRequireAuth | tmfMPTCanEnableCanTransfer | + tmfMPTCanEnableCanTrade); + }; + + auto const makeNoTransferMPT = [&](Holders holders = {}, std::uint64_t const pay = 0) { + return makeMPT( + tfMPTCanLock | tfMPTCanTrade, holders, pay, tmfMPTCanEnableCanTransfer); + }; + + auto const makeNoTradeMPT = [&](Holders holders = {}, std::uint64_t const pay = 0) { + return makeMPT( + tfMPTCanLock | tfMPTCanTransfer, holders, pay, tmfMPTCanEnableCanTrade); }; - // // AMMCreate - // - - auto createJv = AMM::createJv(alice, badMPT(1'000), eur(1'000), 0); - - auto createFail = [&](Account const& account, auto const& err) { - createJv[sfAccount] = account.human(); - env(createJv, txfee, Ter(err)); - env.close(); - }; - - // MPTokenIssuance doesn't exist - - createFail(alice, tecOBJECT_NOT_FOUND); - - // MPToken doesn't exist - - createJv[sfAmount] = STAmount{usd(1'000)}.getJson(); - createFail(alice, tecNO_AUTH); - - // alice authorizes MPToken, can create - usd.authorize({.account = alice}); - env(pay(gw, alice, usd(1'000'000)), txfee); - env.close(); - createDeleteAMM(alice); - - // MPTLock is set - - // alice and issuer can't create - usd.set({.flags = tfMPTLock}); - createFail(alice, tecLOCKED); - createFail(gw, tecLOCKED); - - // MPTRequireAuth is set - - // alice is not authorized - usd.set({.flags = tfMPTUnlock}); - usd.set({.mutableFlags = tmfMPTSetRequireAuth}); - createFail(alice, tecNO_AUTH); - // issuer can create - createDeleteAMM(gw); - - // alice is authorized, can create - usd.authorize({.account = gw, .holder = alice}); - createDeleteAMM(alice); - - // MPTCanTransfer is not set - - usd.set({.mutableFlags = tmfMPTClearRequireAuth}); - usd.set({.mutableFlags = tmfMPTClearCanTransfer}); - // alice can't create - createFail(alice, tecNO_AUTH); - // issuer can create - createDeleteAMM(gw); - usd.set({.mutableFlags = tmfMPTSetCanTransfer}); - // alice can create - createDeleteAMM(alice); - - // MPTCanTrade is not set - - usd.set({.mutableFlags = tmfMPTSetCanTransfer}); - usd.set({.mutableFlags = tmfMPTClearCanTrade}); - // alice and issuer can't create - createFail(alice, tecNO_PERMISSION); - createFail(gw, tecNO_PERMISSION); - usd.set({.mutableFlags = tmfMPTSetCanTrade}); - - // - // AMMDeposit - // - - AMM amm(env, gw, usd(1'000), eur(1'000)); - - // MPTokenIssuance doesn't exist - - amm.deposit( - {.account = alice, - .asset1In = badMPT(1), - .asset2In = eur(1), - .assets = std::make_pair(badMPT, eur), - .err = Ter(terNO_AMM)}); - - // MPToken doesn't exist - - amm.deposit( - {.account = carol, .asset1In = usd(1), .asset2In = eur(1), .err = Ter(tecNO_AUTH)}); - - // MPTLock is set - - usd.set({.flags = tfMPTLock}); - // alice and issuer can't deposit - for (auto const& account : {carol, gw}) { + auto usd = makeDexMPT(); + auto eur = makeDexMPT({alice}, 1'000'000); + + auto createDeleteAMM = [&](auto const& asset, Account const& lp) { + AMM amm( + env, + lp, + asset(1'000), + eur(1'000), + CreateArg{.fee = static_cast(increment.value())}); + amm.withdrawAll(lp); + BEAST_EXPECT(!amm.ammExists()); + }; + + auto createFail = [&](auto const& asset, Account const& account, auto const& err) { + auto const createJv = AMM::createJv(account, asset(1'000), eur(1'000), 0); + env(createJv, txfee, Ter(err)); + env.close(); + }; + + // MPTokenIssuance doesn't exist + createFail(badMPT, alice, tecOBJECT_NOT_FOUND); + + // MPToken doesn't exist + createFail(usd, alice, tecNO_AUTH); + + // alice authorizes MPToken, can create + usd.authorize({.account = alice}); + env(pay(gw, alice, usd(1'000'000)), txfee); + env.close(); + createDeleteAMM(usd, alice); + + // MPTLock is set + // alice and issuer can't create + usd.set({.flags = tfMPTLock}); + createFail(usd, alice, tecLOCKED); + createFail(usd, gw, tecLOCKED); + + // MPTRequireAuth is set + // alice is not authorized + usd.set({.flags = tfMPTUnlock}); + usd.set({.mutableFlags = tmfMPTSetRequireAuth}); + createFail(usd, alice, tecNO_AUTH); + // issuer can create + createDeleteAMM(usd, gw); + + // alice is authorized, can create + usd.authorize({.account = gw, .holder = alice}); + createDeleteAMM(usd, alice); + + // MPTCanTransfer is not set + { + auto usd2 = makeNoTransferMPT({alice}, 1'000'000); + + // alice can't create + createFail(usd2, alice, tecNO_AUTH); + // issuer can create + createDeleteAMM(usd2, gw); + usd2.set({.mutableFlags = tmfMPTSetCanTransfer}); + // alice can create + createDeleteAMM(usd2, alice); + } + + // MPTCanTrade is not set + { + auto usd3 = makeNoTradeMPT({alice}, 1'000'000); + + // alice and issuer can't create + createFail(usd3, alice, tecNO_PERMISSION); + createFail(usd3, gw, tecNO_PERMISSION); + usd3.set({.mutableFlags = tmfMPTSetCanTrade}); + // alice can create + createDeleteAMM(usd3, alice); + } + } + + // AMMDeposit + { + auto usd = makeDexMPT(); + auto eur = makeDexMPT({alice}, 1'000'000); + AMM amm(env, gw, usd(1'000), eur(1'000)); + + // MPTokenIssuance doesn't exist amm.deposit( - {.account = account, + {.account = alice, + .asset1In = badMPT(1), + .asset2In = eur(1), + .assets = std::make_pair(badMPT, eur), + .err = Ter(terNO_AMM)}); + + // MPToken doesn't exist + amm.deposit( + {.account = carol, .asset1In = usd(1), .asset2In = eur(1), - .err = Ter(tecLOCKED)}); + .err = Ter(tecNO_AUTH)}); + + // Fund carol for the AMMDeposit checks. + usd.authorize({.account = carol}); + env(pay(gw, carol, usd(1'000'000))); + eur.authorize({.account = carol}); + env(pay(gw, carol, eur(1'000'000))); + env.close(); + + // MPTLock is set + usd.set({.flags = tfMPTLock}); + + // alice and issuer can't deposit + for (auto const& account : {carol, gw}) + { + amm.deposit( + {.account = account, + .asset1In = usd(1), + .asset2In = eur(1), + .err = Ter(tecLOCKED)}); + amm.deposit( + {.account = account, + .asset1In = eur(1), + .assets = std::make_pair(eur, usd), + .err = Ter(tecLOCKED)}); + } + usd.set({.flags = tfMPTUnlock}); + + // MPTRequireAuth is set + // carol is not authorized by the issuer + usd.set({.mutableFlags = tmfMPTSetRequireAuth}); + env.close(); amm.deposit( - {.account = account, + {.account = carol, + .asset1In = usd(1), + .asset2In = eur(1), + .err = Ter(tecNO_AUTH)}); + amm.deposit( + {.account = carol, .asset1In = eur(1), .assets = std::make_pair(eur, usd), - .err = Ter(tecLOCKED)}); + .err = Ter(tecNO_AUTH)}); + // issuer can deposit + amm.deposit({.account = gw, .tokens = 1'000}); + // carol is authorized, can deposit + usd.authorize({.account = gw, .holder = carol}); + amm.deposit({.account = carol, .tokens = 1'000}); + // Can't authorize or unauthorize AMM pseudo-account + usd.authorize( + {.account = gw, + .holder = Account{"amm", amm.ammAccount()}, + .err = tecNO_PERMISSION}); + usd.authorize( + {.account = gw, + .holder = Account{"amm", amm.ammAccount()}, + .flags = tfMPTUnauthorize, + .err = tecNO_PERMISSION}); + + // MPTCanTransfer is not set + { + auto usd2 = makeNoTransferMPT({carol}, 1'000'000); + AMM amm2(env, gw, usd2(1'000), eur(1'000)); + + // carol can't deposit + amm2.deposit( + {.account = carol, + .asset1In = usd2(1), + .asset2In = eur(1), + .err = Ter(tecNO_AUTH)}); + amm2.deposit( + {.account = carol, + .asset1In = eur(1), + .assets = std::make_pair(eur, usd2), + .err = Ter(tecNO_AUTH)}); + // issuer can deposit + amm2.deposit({.account = gw, .tokens = 1'000}); + usd2.set({.mutableFlags = tmfMPTSetCanTransfer}); + // carol can deposit + amm2.deposit({.account = carol, .tokens = 1'000}); + } } - usd.set({.flags = tfMPTUnlock}); - // MPTRequireAuth is set - - // carol authorizes MPToken but is not authorized by the issuer - usd.authorize({.account = carol}); - env(pay(gw, carol, usd(1'000'000))); - // carol authorizes EUR - eur.authorize({.account = carol}); - env(pay(gw, carol, eur(1'000'000))); - usd.set({.mutableFlags = tmfMPTSetRequireAuth}); - env.close(); - amm.deposit( - {.account = carol, .asset1In = usd(1), .asset2In = eur(1), .err = Ter(tecNO_AUTH)}); - amm.deposit( - {.account = carol, - .asset1In = eur(1), - .assets = std::make_pair(eur, usd), - .err = Ter(tecNO_AUTH)}); - // issuer can deposit - amm.deposit({.account = gw, .tokens = 1'000}); - // carol is authorized, can deposit - usd.authorize({.account = gw, .holder = carol}); - amm.deposit({.account = carol, .tokens = 1'000}); - // Can't authorize or unauthorize AMM pseudo-account - usd.authorize( - {.account = gw, - .holder = Account{"amm", amm.ammAccount()}, - .err = tecNO_PERMISSION}); - usd.authorize( - {.account = gw, - .holder = Account{"amm", amm.ammAccount()}, - .flags = tfMPTUnauthorize, - .err = tecNO_PERMISSION}); - - // MPTCanTransfer is not set - - usd.set({.mutableFlags = tmfMPTClearRequireAuth}); - usd.set({.mutableFlags = tmfMPTClearCanTransfer}); - // carol can't deposit - amm.deposit( - {.account = carol, .asset1In = usd(1), .asset2In = eur(1), .err = Ter(tecNO_AUTH)}); - amm.deposit( - {.account = carol, - .asset1In = eur(1), - .assets = std::make_pair(eur, usd), - .err = Ter(tecNO_AUTH)}); - // issuer can deposit - amm.deposit({.account = gw, .tokens = 1'000}); - // carol can deposit - usd.set({.mutableFlags = tmfMPTSetCanTransfer}); - amm.deposit({.account = carol, .tokens = 1'000}); - - // MPTCanTrade is not set - - usd.set({.mutableFlags = tmfMPTSetCanTransfer}); - usd.set({.mutableFlags = tmfMPTClearCanTrade}); - amm.deposit({.account = gw, .tokens = 1'000, .err = Ter(tecNO_PERMISSION)}); - amm.deposit({.account = carol, .tokens = 1'000, .err = Ter(tecNO_PERMISSION)}); - usd.set({.mutableFlags = tmfMPTSetCanTrade}); - - // // AMMWithdraw - // - - // MPTokenIssuance doesn't exist - - amm.withdraw( - WithdrawArg{ - .account = carol, - .asset1Out = badMPT(1), - .asset2Out = eur(1), - .assets = std::make_pair(badMPT, eur), - .err = Ter(terNO_AMM)}); - - // MPToken doesn't exist - doesn't apply since MPToken is created - // on withdraw in this case - - // MPTLock is set - - usd.set({.flags = tfMPTLock}); - // carol and issuer can't withdraw - for (auto const& account : {carol, gw}) { + auto usd = makeDexMPT(); + auto eur = makeDexMPT({carol}, 1'000'000); + AMM amm(env, gw, usd(1'000), eur(1'000)); + + usd.authorize({.account = carol}); + env(pay(gw, carol, usd(1'000'000))); + env.close(); + amm.deposit({.account = carol, .tokens = 1'000}); + + // MPTokenIssuance doesn't exist amm.withdraw( - {.account = account, + WithdrawArg{ + .account = carol, + .asset1Out = badMPT(1), + .asset2Out = eur(1), + .assets = std::make_pair(badMPT, eur), + .err = Ter(terNO_AMM)}); + + // MPToken doesn't exist - doesn't apply since MPToken is created + // on withdraw in this case + + // MPTLock is set + usd.set({.flags = tfMPTLock}); + // carol and issuer can't withdraw + for (auto const& account : {carol, gw}) + { + amm.withdraw( + {.account = account, + .asset1Out = usd(1), + .asset2Out = eur(1), + .err = Ter(tecLOCKED)}); + amm.withdraw({.account = account, .tokens = 1'000, .err = Ter(tecLOCKED)}); + // can single withdraw another asset + amm.withdraw( + {.account = account, + .asset1Out = eur(1), + .assets = std::make_pair(eur, usd)}); + } + usd.set({.flags = tfMPTUnlock}); + + // MPTRequireAuth is set + usd.set({.mutableFlags = tmfMPTSetRequireAuth}); + usd.authorize({.account = gw, .holder = carol, .flags = tfMPTUnauthorize}); + // carol can't withdraw + amm.withdraw( + {.account = carol, .asset1Out = usd(1), .asset2Out = eur(1), - .err = Ter(tecLOCKED)}); - amm.withdraw({.account = account, .tokens = 1'000, .err = Ter(tecLOCKED)}); - // can single withdraw another asset + .err = Ter(tecNO_AUTH)}); + // can withdraw another asset amm.withdraw( - {.account = account, .asset1Out = eur(1), .assets = std::make_pair(eur, usd)}); + {.account = carol, .asset1Out = eur(1), .assets = std::make_pair(eur, usd)}); + // issuer can withdraw + amm.withdraw({.account = gw, .asset1Out = usd(1), .asset2Out = eur(1)}); + // carol is authorized, can withdraw + usd.authorize({.account = gw, .holder = carol}); + amm.withdraw({.account = carol, .asset1Out = usd(1), .asset2Out = eur(1)}); + + // MPTCanTransfer is not set, allow to withdraw + { + auto usd2 = makeNoTransferMPT({carol}, 1'000'000); + AMM amm2(env, gw, usd2(1'000), eur(1'000)); + + // carol cannot deposit usd2 without MPTCanTransfer, so give her + // LP tokens directly to test the withdraw path. + env.trust(STAmount{amm2.lptIssue(), 1'000}, carol); + env(pay(gw, carol, STAmount{amm2.lptIssue(), 100})); + env.close(); + + // carol can withdraw + amm2.withdraw({.account = carol, .asset1Out = usd2(1), .asset2Out = eur(1)}); + // can withdraw another asset + amm2.withdraw( + {.account = carol, + .asset1Out = eur(1), + .assets = std::make_pair(eur, usd2)}); + // issuer can withdraw + amm2.withdraw({.account = gw, .asset1Out = usd2(1), .asset2Out = eur(1)}); + // Holder can't transfer to another holder + env.fund(XRP(1'000), bob); + usd2.authorize({.account = bob}); + env(pay(carol, bob, usd2(1)), Ter(tecNO_AUTH)); + usd2.authorize({.account = bob, .flags = tfMPTUnauthorize}); + // Can redeem + env(pay(carol, gw, usd2(1))); + usd2.set({.mutableFlags = tmfMPTSetCanTransfer}); + // carol can withdraw + amm2.withdraw({.account = carol, .asset1Out = usd2(1), .asset2Out = eur(1)}); + } + + // MPToken created on withdraw + { + auto usd3 = makeDexMPT(); + auto eur3 = makeDexMPT({carol}, 1'000'000); + AMM amm3(env, gw, usd3(1'000), eur3(1'000)); + + BEAST_EXPECT(env.le(keylet::mptoken(usd3.issuanceID(), carol)) == nullptr); + // single-deposit EUR + amm3.deposit( + {.account = carol, + .asset1In = eur3(1'000), + .assets = std::make_pair(eur3, usd3)}); + BEAST_EXPECT(env.le(keylet::mptoken(usd3.issuanceID(), carol)) == nullptr); + // withdraw in USD to create MPToken + amm3.withdraw({.account = carol, .asset1Out = usd3(100)}); + BEAST_EXPECT(env.le(keylet::mptoken(usd3.issuanceID(), carol))); + } } - usd.set({.flags = tfMPTUnlock}); - - // MPTRequireAuth is set - - usd.set({.mutableFlags = tmfMPTSetRequireAuth}); - usd.authorize({.account = gw, .holder = carol, .flags = tfMPTUnauthorize}); - // carol can't withdraw - amm.withdraw( - {.account = carol, - .asset1Out = usd(1), - .asset2Out = eur(1), - .err = Ter(tecNO_AUTH)}); - // can withdraw another asset - amm.withdraw( - {.account = carol, .asset1Out = eur(1), .assets = std::make_pair(eur, usd)}); - // issuer can withdraw - amm.withdraw({.account = gw, .asset1Out = usd(1), .asset2Out = eur(1)}); - // carol is authorized, can withdraw - usd.authorize({.account = gw, .holder = carol}); - amm.withdraw({.account = carol, .asset1Out = usd(1), .asset2Out = eur(1)}); - - // MPTCanTransfer is not set, allow to withdraw - - usd.set({.mutableFlags = tmfMPTClearRequireAuth}); - usd.set({.mutableFlags = tmfMPTClearCanTransfer}); - // carol can withdraw - amm.withdraw({.account = carol, .asset1Out = usd(1), .asset2Out = eur(1)}); - // can withdraw another asset - amm.withdraw( - {.account = carol, .asset1Out = eur(1), .assets = std::make_pair(eur, usd)}); - // issuer can withdraw - amm.withdraw({.account = gw, .asset1Out = usd(1), .asset2Out = eur(1)}); - // Holder can't transfer to another holder - env.fund(XRP(1'000), bob); - usd.authorize({.account = bob}); - env(pay(carol, bob, usd(1)), Ter(tecNO_AUTH)); - usd.authorize({.account = bob, .flags = tfMPTUnauthorize}); - // Can redeem - env(pay(carol, gw, usd(1))); - // carol can withdraw - usd.set({.mutableFlags = tmfMPTSetCanTransfer}); - amm.withdraw({.account = carol, .asset1Out = usd(1), .asset2Out = eur(1)}); - - usd.set({.mutableFlags = tmfMPTSetCanTransfer}); - - // MPTCanTrade is not set, allow to withdraw - - usd.set({.mutableFlags = tmfMPTClearCanTrade}); - amm.withdraw({.account = gw, .tokens = 1'000}); - amm.withdraw({.account = carol, .tokens = 1'000}); - // Can't DEX - amm.deposit( - DepositArg{.account = carol, .asset1In = usd(1), .err = Ter(tecNO_PERMISSION)}); - usd.set({.mutableFlags = tmfMPTSetCanTrade}); - - // MPToken created on withdraw - - // redeem all carol's USD and unauthorize USD - amm.withdrawAll(carol); - env(pay(carol, gw, env.balance(carol, usd))); - usd.authorize({.account = carol, .flags = tfMPTUnauthorize}); - BEAST_EXPECT(env.le(keylet::mptoken(usd.issuanceID(), carol)) == nullptr); - // single-deposit EUR - amm.deposit( - {.account = carol, .asset1In = eur(1'000), .assets = std::make_pair(eur, usd)}); - // withdraw in USD to create MPToken - amm.withdraw({.account = carol, .asset1Out = usd(100)}); - BEAST_EXPECT(env.le(keylet::mptoken(usd.issuanceID(), carol))); } } @@ -7707,41 +7605,37 @@ class MPToken_test : public beast::unit_test::Suite Env env(*this); env.fund(XRP(1'000), gw, alice, carol); - MPTTester mpt( - {.env = env, - .issuer = gw, - .holders = {alice, carol}, - .pay = 100, - .flags = kMptDexFlags, - .mutableFlags = tmfMPTCanMutateCanTransfer | tmfMPTCanMutateCanTrade}); + auto const checkCanTradeCanTransfer = [&](std::uint32_t const flags, + TER const gwToGw, + TER const gwToAlice, + TER const aliceToAlice, + TER const aliceToCarol) { + MPTTester const mpt( + {.env = env, .issuer = gw, .holders = {alice, carol}, .pay = 100, .flags = flags}); + + BEAST_EXPECT(canMPTTradeAndTransfer(*env.current(), mpt, gw, gw) == gwToGw); + BEAST_EXPECT(canMPTTradeAndTransfer(*env.current(), mpt, gw, alice) == gwToAlice); + BEAST_EXPECT(canMPTTradeAndTransfer(*env.current(), mpt, alice, alice) == aliceToAlice); + BEAST_EXPECT(canMPTTradeAndTransfer(*env.current(), mpt, alice, carol) == aliceToCarol); + }; // Both flags are enabled - BEAST_EXPECT(isTesSuccess(canMPTTradeAndTransfer(*env.current(), mpt, gw, gw))); - BEAST_EXPECT(isTesSuccess(canMPTTradeAndTransfer(*env.current(), mpt, gw, alice))); - BEAST_EXPECT(isTesSuccess(canMPTTradeAndTransfer(*env.current(), mpt, alice, alice))); - BEAST_EXPECT(isTesSuccess(canMPTTradeAndTransfer(*env.current(), mpt, alice, carol))); + checkCanTradeCanTransfer(kMptDexFlags, tesSUCCESS, tesSUCCESS, tesSUCCESS, tesSUCCESS); // MPTCanTrade is disabled - mpt.set({.mutableFlags = tmfMPTClearCanTrade}); - BEAST_EXPECT(canMPTTradeAndTransfer(*env.current(), mpt, gw, gw) == tecNO_PERMISSION); - BEAST_EXPECT(canMPTTradeAndTransfer(*env.current(), mpt, gw, alice) == tecNO_PERMISSION); - BEAST_EXPECT(canMPTTradeAndTransfer(*env.current(), mpt, alice, alice) == tecNO_PERMISSION); - BEAST_EXPECT(canMPTTradeAndTransfer(*env.current(), mpt, alice, carol) == tecNO_PERMISSION); + checkCanTradeCanTransfer( + tfMPTCanTransfer, + tecNO_PERMISSION, + tecNO_PERMISSION, + tecNO_PERMISSION, + tecNO_PERMISSION); // MPTCanTransfer is disabled - mpt.set({.mutableFlags = tmfMPTSetCanTrade}); - mpt.set({.mutableFlags = tmfMPTClearCanTransfer}); - BEAST_EXPECT(isTesSuccess(canMPTTradeAndTransfer(*env.current(), mpt, gw, gw))); - BEAST_EXPECT(isTesSuccess(canMPTTradeAndTransfer(*env.current(), mpt, gw, alice))); - BEAST_EXPECT(canMPTTradeAndTransfer(*env.current(), mpt, alice, alice) == tecNO_AUTH); - BEAST_EXPECT(canMPTTradeAndTransfer(*env.current(), mpt, alice, carol) == tecNO_AUTH); + checkCanTradeCanTransfer(tfMPTCanTrade, tesSUCCESS, tesSUCCESS, tecNO_AUTH, tecNO_AUTH); // Both flags are disabled - mpt.set({.mutableFlags = tmfMPTClearCanTrade}); - BEAST_EXPECT(canMPTTradeAndTransfer(*env.current(), mpt, gw, gw) == tecNO_PERMISSION); - BEAST_EXPECT(canMPTTradeAndTransfer(*env.current(), mpt, gw, alice) == tecNO_PERMISSION); - BEAST_EXPECT(canMPTTradeAndTransfer(*env.current(), mpt, alice, alice) == tecNO_PERMISSION); - BEAST_EXPECT(canMPTTradeAndTransfer(*env.current(), mpt, alice, carol) == tecNO_PERMISSION); + checkCanTradeCanTransfer( + 0, tecNO_PERMISSION, tecNO_PERMISSION, tecNO_PERMISSION, tecNO_PERMISSION); } public: diff --git a/src/test/app/Vault_test.cpp b/src/test/app/Vault_test.cpp index 2c83ad91ec..065b6b0044 100644 --- a/src/test/app/Vault_test.cpp +++ b/src/test/app/Vault_test.cpp @@ -1599,7 +1599,7 @@ class Vault_test : public beast::unit_test::Suite {.flags = tfMPTCanTransfer | tfMPTCanLock | (args.enableClawback ? tfMPTCanClawback : kNone) | (args.requireAuth ? tfMPTRequireAuth : kNone), - .mutableFlags = tmfMPTCanMutateCanTransfer}); + .mutableFlags = tmfMPTCanEnableCanTransfer}); PrettyAsset const asset = mptt.issuanceID(); mptt.authorize({.account = owner}); mptt.authorize({.account = depositor}); @@ -2238,149 +2238,6 @@ class Vault_test : public beast::unit_test::Suite 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: block deposit, allow withdraw"); - - 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(); - - // Issuer governance: clear CanTransfer. New exposure must be - // blocked, but recovery paths must remain open so existing - // depositors are not trapped. - mptt.set({.mutableFlags = tmfMPTClearCanTransfer}); - env.close(); - - // New deposit is blocked. - env(tx, Ter{tecNO_AUTH}); - env.close(); - - // Existing depositor can always withdraw, even though the asset - // is no longer freely transferable. - tx = vault.withdraw({.depositor = depositor, .id = keylet.key, .amount = asset(100)}); - env(tx); - env.close(); - - // Delete vault with zero balance - env(vault.del({.owner = owner, .id = keylet.key})); - }); - - { - testcase("MPT non-transferable: pre-fixCleanup3_2_0 withdraw blocked"); - - // Regression: before fixCleanup3_2_0 a depositor was trapped if - // the issuer cleared lsfMPTCanTransfer. Verify that the legacy - // (broken) behavior is preserved when the amendment is disabled. - Env env{*this, testableAmendments() - fixCleanup3_2_0}; - Account const issuer{"issuer"}; - Account const owner{"owner"}; - Account const depositor{"depositor"}; - env.fund(XRP(10'000), issuer, owner, depositor); - env.close(); - Vault const vault{env}; - - MPTTester mptt{env, issuer, kMptInitNoFund}; - mptt.create( - {.flags = tfMPTCanTransfer | tfMPTCanLock, - .mutableFlags = tmfMPTCanMutateCanTransfer}); - PrettyAsset const asset = mptt.issuanceID(); - mptt.authorize({.account = owner}); - mptt.authorize({.account = depositor}); - env(pay(issuer, depositor, asset(1'000))); - env.close(); - - auto [tx, keylet] = vault.create({.owner = owner, .asset = asset}); - env(tx); - env.close(); - - env(vault.deposit({.depositor = depositor, .id = keylet.key, .amount = asset(100)})); - env.close(); - - mptt.set({.mutableFlags = tmfMPTClearCanTransfer}); - env.close(); - - // Pre-amendment: deposit blocked (matches new behavior). - env(vault.deposit({.depositor = depositor, .id = keylet.key, .amount = asset(100)}), - Ter{tecNO_AUTH}); - env.close(); - - // Pre-amendment: withdraw is also blocked - this is the bug - // that fixCleanup3_2_0 fixes. - env(vault.withdraw({.depositor = depositor, .id = keylet.key, .amount = asset(100)}), - Ter{tecNO_AUTH}); - env.close(); - } - - { - testcase("MPT non-transferable: vault shares inherit restriction"); - - Env env{*this, testableAmendments()}; - Account const issuer{"issuer"}; - Account const owner{"owner"}; - Account const alice{"alice"}; - Account const bob{"bob"}; - env.fund(XRP(10'000), issuer, owner, alice, bob); - env.close(); - Vault const vault{env}; - - MPTTester mptt{env, issuer, kMptInitNoFund}; - mptt.create( - {.flags = tfMPTCanTransfer | tfMPTCanLock, - .mutableFlags = tmfMPTCanMutateCanTransfer}); - PrettyAsset const asset = mptt.issuanceID(); - mptt.authorize({.account = owner}); - mptt.authorize({.account = alice}); - mptt.authorize({.account = bob}); - env(pay(issuer, alice, asset(1'000))); - env(pay(issuer, bob, asset(1'000))); - env.close(); - - auto [tx, keylet] = vault.create({.owner = owner, .asset = asset}); - env(tx); - env.close(); - - env(vault.deposit({.depositor = alice, .id = keylet.key, .amount = asset(500)})); - // Bob also deposits so he has a share MPToken to receive into. - env(vault.deposit({.depositor = bob, .id = keylet.key, .amount = asset(500)})); - env.close(); - - auto const shares = [&]() -> PrettyAsset { - auto const sle = env.le(keylet); - BEAST_EXPECT(sle != nullptr); - return MPTIssue(sle->at(sfShareMPTID)); - }(); - - // Sanity: while CanTransfer is set on the underlying, peer-to-peer - // share transfers are allowed. - env(pay(alice, bob, shares(1))); - env.close(); - - // Issuer governance: clear CanTransfer on the underlying. - mptt.set({.mutableFlags = tmfMPTClearCanTransfer}); - env.close(); - - // Vault shares inherit the restriction: third-party share-to-share - // payments are blocked. - env(pay(alice, bob, shares(1)), Ter{tecNO_AUTH}); - env.close(); - - // Recovery path: existing share holders can still redeem shares - // for the underlying asset via VaultWithdraw. - env(vault.withdraw({.depositor = alice, .id = keylet.key, .amount = shares(1)})); - env.close(); - } - { testcase("MPT locked: vault shares inherit underlying lock"); @@ -2458,56 +2315,6 @@ class Vault_test : public beast::unit_test::Suite BEAST_EXPECT(expectOffers(env, alice, 1)); } - { - testcase("MPT non-transferable: pre-fixCleanup3_2_0 share transfer succeeds"); - - // Regression: before fixCleanup3_2_0 a peer-to-peer share Payment - // succeeded even when the underlying asset's lsfMPTCanTransfer - // was cleared. Verify that the legacy (non-inheriting) behavior - // is preserved when the amendment is disabled. - Env env{*this, testableAmendments() - fixCleanup3_2_0}; - Account const issuer{"issuer"}; - Account const owner{"owner"}; - Account const alice{"alice"}; - Account const bob{"bob"}; - env.fund(XRP(10'000), issuer, owner, alice, bob); - env.close(); - Vault const vault{env}; - - MPTTester mptt{env, issuer, kMptInitNoFund}; - mptt.create( - {.flags = tfMPTCanTransfer | tfMPTCanLock, - .mutableFlags = tmfMPTCanMutateCanTransfer}); - PrettyAsset const asset = mptt.issuanceID(); - mptt.authorize({.account = owner}); - mptt.authorize({.account = alice}); - mptt.authorize({.account = bob}); - env(pay(issuer, alice, asset(1'000))); - env(pay(issuer, bob, asset(1'000))); - env.close(); - - auto [tx, keylet] = vault.create({.owner = owner, .asset = asset}); - env(tx); - env.close(); - - env(vault.deposit({.depositor = alice, .id = keylet.key, .amount = asset(500)})); - env(vault.deposit({.depositor = bob, .id = keylet.key, .amount = asset(500)})); - env.close(); - - auto const shares = [&]() -> PrettyAsset { - auto const sle = env.le(keylet); - BEAST_EXPECT(sle != nullptr); - return MPTIssue(sle->at(sfShareMPTID)); - }(); - - mptt.set({.mutableFlags = tmfMPTClearCanTransfer}); - env.close(); - - // Pre-amendment: share transfer leaks past underlying restriction. - env(pay(alice, bob, shares(1))); - env.close(); - } - { testcase("MPT CanTrade governance: share inherits underlying on DEX and AMM"); @@ -2522,8 +2329,8 @@ class Vault_test : public beast::unit_test::Suite MPTTester mptt{env, issuer, kMptInitNoFund}; mptt.create( - {.flags = tfMPTCanTransfer | tfMPTCanTrade | tfMPTCanLock, - .mutableFlags = tmfMPTCanMutateCanTrade}); + {.flags = tfMPTCanTransfer | tfMPTCanLock, + .mutableFlags = tmfMPTCanEnableCanTrade}); PrettyAsset const asset = mptt.issuanceID(); mptt.authorize({.account = owner}); mptt.authorize({.account = alice}); @@ -2547,38 +2354,18 @@ class Vault_test : public beast::unit_test::Suite return MPTIssue(sle->at(sfShareMPTID)); }(); - // Sanity: while CanTrade is set on the underlying, both the asset - // and the vault share can be placed on the DEX. - env(offer(alice, XRP(1), asset(10))); - env(offer(alice, XRP(1), shares(1))); - env.close(); - - // Issuer governance: clear CanTrade on the underlying. - mptt.set({.mutableFlags = tmfMPTClearCanTrade}); - env.close(); - - // Control: clearing CanTrade on the underlying is observable on - // the DEX path for that asset. + // CanTrade is not set on the underlying, both the asset and + // the vault share are blocked on the DEX. env(offer(alice, XRP(1), asset(10)), Ter{tecNO_PERMISSION}); + env(offer(alice, XRP(1), shares(1)), Ter{tecNO_PERMISSION}); env.close(); - // Control: clearing CanTrade on the underlying is also observable - // on the AMM path for that asset. - AMM const ammUnderlyingFails( + // The inherited CanTrade restriction also blocks AMM creation. + AMM const ammUnderlyingFail( env, alice, XRP(1'000), asset(1'000), Ter{tecNO_PERMISSION}); - - // Post-fixCleanup3_2_0: vault shares inherit the underlying's - // CanTrade restriction on the DEX path (canTrade reads the - // share's sfReferenceHolding and dispatches to the underlying). - env(offer(bob, XRP(1), shares(1)), Ter{tecNO_PERMISSION}); - env.close(); - - // checkMPTAllowed mirrors the inheritance for AMM/Offer- - // crossing/Check paths, so a share AMM also cannot be created - // when the underlying CanTrade is cleared. AMM const ammShares(env, alice, XRP(1'000), shares(100), Ter{tecNO_PERMISSION}); - // Deposit still works (canAddHolding does not consult the field). + // Deposit still works before enabling CanTrade. env(vault.deposit({.depositor = alice, .id = keylet.key, .amount = asset(100)})); env.close(); @@ -2587,9 +2374,19 @@ class Vault_test : public beast::unit_test::Suite env(pay(alice, bob, shares(1))); env.close(); - // Withdraw still works. + // Withdraw still works before enabling CanTrade. env(vault.withdraw({.depositor = alice, .id = keylet.key, .amount = asset(100)})); env.close(); + + // Enable CanTrade on the underlying. + mptt.set({.mutableFlags = tmfMPTSetCanTrade}); + env.close(); + + env(offer(alice, XRP(1), asset(10))); + env(offer(alice, XRP(1), shares(1))); + env.close(); + + AMM const ammUnderlying(env, alice, XRP(1'000), asset(1'000)); } { diff --git a/src/test/jtx/impl/mpt.cpp b/src/test/jtx/impl/mpt.cpp index 1e127e7c05..7da3305eec 100644 --- a/src/test/jtx/impl/mpt.cpp +++ b/src/test/jtx/impl/mpt.cpp @@ -28,6 +28,7 @@ #include #include +#include #include #include #include @@ -40,6 +41,21 @@ namespace xrpl::test::jtx { +struct MPTSetFlagMapping +{ + std::uint32_t setFlag; + std::uint32_t ledgerFlag; +}; + +static constexpr std::array mptSetFlagMappings = {{ + {.setFlag = tmfMPTSetCanLock, .ledgerFlag = lsfMPTCanLock}, + {.setFlag = tmfMPTSetRequireAuth, .ledgerFlag = lsfMPTRequireAuth}, + {.setFlag = tmfMPTSetCanEscrow, .ledgerFlag = lsfMPTCanEscrow}, + {.setFlag = tmfMPTSetCanClawback, .ledgerFlag = lsfMPTCanClawback}, + {.setFlag = tmfMPTSetCanTrade, .ledgerFlag = lsfMPTCanTrade}, + {.setFlag = tmfMPTSetCanTransfer, .ledgerFlag = lsfMPTCanTransfer}, +}}; + void MptFlags::operator()(Env& env) const { @@ -424,58 +440,12 @@ MPTTester::set(MPTSet const& arg) if (arg.mutableFlags) { - if (*arg.mutableFlags & tmfMPTSetCanLock) + for (auto const& [setFlag, ledgerFlag] : mptSetFlagMappings) { - flags |= lsfMPTCanLock; - } - else if (*arg.mutableFlags & tmfMPTClearCanLock) - { - flags &= ~lsfMPTCanLock; - } - - if (*arg.mutableFlags & tmfMPTSetRequireAuth) - { - flags |= lsfMPTRequireAuth; - } - else if (*arg.mutableFlags & tmfMPTClearRequireAuth) - { - flags &= ~lsfMPTRequireAuth; - } - - if (*arg.mutableFlags & tmfMPTSetCanEscrow) - { - flags |= lsfMPTCanEscrow; - } - else if (*arg.mutableFlags & tmfMPTClearCanEscrow) - { - flags &= ~lsfMPTCanEscrow; - } - - if (*arg.mutableFlags & tmfMPTSetCanClawback) - { - flags |= lsfMPTCanClawback; - } - else if (*arg.mutableFlags & tmfMPTClearCanClawback) - { - flags &= ~lsfMPTCanClawback; - } - - if (*arg.mutableFlags & tmfMPTSetCanTrade) - { - flags |= lsfMPTCanTrade; - } - else if (*arg.mutableFlags & tmfMPTClearCanTrade) - { - flags &= ~lsfMPTCanTrade; - } - - if (*arg.mutableFlags & tmfMPTSetCanTransfer) - { - flags |= lsfMPTCanTransfer; - } - else if (*arg.mutableFlags & tmfMPTClearCanTransfer) - { - flags &= ~lsfMPTCanTransfer; + if ((*arg.mutableFlags & setFlag) != 0u) + { + flags |= ledgerFlag; + } } } } From 93eab33dc23e0a026c55234ae7b5190bb195d93e Mon Sep 17 00:00:00 2001 From: Zhiyuan Wang <96991820+Kassaking7@users.noreply.github.com> Date: Mon, 22 Jun 2026 13:45:42 -0400 Subject: [PATCH 04/17] fix: Improve ValidAMM invariant (#7295) --- include/xrpl/tx/invariants/AMMInvariant.h | 11 ++- src/libxrpl/tx/invariants/AMMInvariant.cpp | 92 +++++++++++++++++++--- src/test/app/Invariants_test.cpp | 84 ++++++++++++++++++++ 3 files changed, 175 insertions(+), 12 deletions(-) diff --git a/include/xrpl/tx/invariants/AMMInvariant.h b/include/xrpl/tx/invariants/AMMInvariant.h index ee2fb66a1c..4b56370774 100644 --- a/include/xrpl/tx/invariants/AMMInvariant.h +++ b/include/xrpl/tx/invariants/AMMInvariant.h @@ -15,7 +15,9 @@ class ValidAMM std::optional ammAccount_; std::optional lptAMMBalanceAfter_; std::optional lptAMMBalanceBefore_; + std::optional lptAMMBalanceBeforeDeletion_; bool ammPoolChanged_{false}; + bool ammDeleted_{false}; public: enum class ZeroAllowed : bool { No = false, Yes = true }; @@ -35,12 +37,17 @@ private: [[nodiscard]] bool finalizeCreate(STTx const&, ReadView const&, bool enforce, beast::Journal const&) const; [[nodiscard]] bool - finalizeDelete(bool enforce, TER res, beast::Journal const&) const; + finalizeDelete(bool enforce, bool enforceAMMDelete, TER res, beast::Journal const&) const; [[nodiscard]] bool finalizeDeposit(STTx const&, ReadView const&, bool enforce, beast::Journal const&) const; // Includes clawback [[nodiscard]] bool - finalizeWithdraw(STTx const&, ReadView const&, bool enforce, beast::Journal const&) const; + finalizeWithdraw( + STTx const&, + ReadView const&, + bool enforce, + bool enforceAMMDelete, + beast::Journal const&) const; [[nodiscard]] bool finalizeDEX(bool enforce, beast::Journal const&) const; [[nodiscard]] bool diff --git a/src/libxrpl/tx/invariants/AMMInvariant.cpp b/src/libxrpl/tx/invariants/AMMInvariant.cpp index ecd7bedf89..356c26d6b0 100644 --- a/src/libxrpl/tx/invariants/AMMInvariant.cpp +++ b/src/libxrpl/tx/invariants/AMMInvariant.cpp @@ -27,7 +27,14 @@ void ValidAMM::visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) { if (isDelete) + { + if (before && before->getType() == ltAMM) + { + ammDeleted_ = true; + lptAMMBalanceBeforeDeletion_ = before->getFieldAmount(sfLPTokenBalance); + } return; + } if (after) { @@ -166,18 +173,60 @@ ValidAMM::finalizeCreate( } bool -ValidAMM::finalizeDelete(bool enforce, TER res, beast::Journal const& j) const +ValidAMM::finalizeDelete(bool enforce, bool enforceAMMDelete, TER res, beast::Journal const& j) + const { if (ammAccount_) { // LCOV_EXCL_START - std::string const msg = (isTesSuccess(res)) ? "AMM object is not deleted on tesSUCCESS" - : "AMM object is changed on tecINCOMPLETE"; + std::string const msg = (isTesSuccess(res)) ? "AMM object remained on tesSUCCESS" + : "AMM object changed on tecINCOMPLETE"; JLOG(j.error()) << "Invariant failed: AMMDelete failed, " << msg; if (enforce) return false; // LCOV_EXCL_STOP } + if (enforceAMMDelete) + { + if (isTesSuccess(res)) + { + if (!ammDeleted_) + { + // LCOV_EXCL_START + JLOG(j.error()) + << "Invariant failed: AMMDelete failed, AMM object remained on tesSUCCESS"; + return false; + // LCOV_EXCL_STOP + } + if (!lptAMMBalanceBeforeDeletion_) + { + // LCOV_EXCL_START + JLOG(j.error()) + << "Invariant failed: AMMDelete failed, AMM object deleted without LP balance"; + return false; + // LCOV_EXCL_STOP + } + if (*lptAMMBalanceBeforeDeletion_ != beast::kZero) + { + // LCOV_EXCL_START + JLOG(j.error()) + << "Invariant failed: AMMDelete failed, AMM object deleted with non-zero LP " + "balance: " + << *lptAMMBalanceBeforeDeletion_; + return false; + // LCOV_EXCL_STOP + } + } + else if (ammDeleted_) + { + // AMM should only be fully deleted when AMMDelete returns tesSUCCESS. + // LCOV_EXCL_START + JLOG(j.error()) << "Invariant failed: AMMDelete failed, AMM object deleted when result " + "is not tesSUCCESS"; + return false; + // LCOV_EXCL_STOP + } + } return true; } @@ -271,16 +320,20 @@ ValidAMM::finalizeWithdraw( xrpl::STTx const& tx, xrpl::ReadView const& view, bool enforce, + bool enforceAMMDelete, beast::Journal const& j) const { - if (!ammAccount_) + if (enforceAMMDelete && ammDeleted_) { - // Last Withdraw or Clawback deleted AMM + // Last Withdraw or Clawback can delete the AMM. We don't have to check + // the LPToken balance because a final AMMWithdraw or AMMClawback can + // redeem the remaining LP tokens and delete the AMM entry in the same + // transaction. + return true; } - else if (!generalInvariant(tx, view, ZeroAllowed::Yes, j)) + if (ammAccount_ && !generalInvariant(tx, view, ZeroAllowed::Yes, j) && enforce) { - if (enforce) - return false; + return false; } return true; @@ -300,6 +353,25 @@ ValidAMM::finalize( return true; bool const enforce = view.rules().enabled(fixAMMv1_3); + bool const enforceAMMDelete = view.rules().enabled(fixCleanup3_3_0); + + // AMM can only be deleted by AMMWithdraw, AMMClawback, and AMMDelete + if (enforceAMMDelete && ammDeleted_) + { + switch (tx.getTxnType()) + { + case ttAMM_WITHDRAW: + case ttAMM_CLAWBACK: + case ttAMM_DELETE: + break; + default: + // LCOV_EXCL_START + JLOG(j.error()) << "Invariant failed: AMM failed, unexpected AMM deletion by " + << tx.getTxnType(); + return false; + // LCOV_EXCL_STOP + } + } switch (tx.getTxnType()) { @@ -309,13 +381,13 @@ ValidAMM::finalize( return finalizeDeposit(tx, view, enforce, j); case ttAMM_CLAWBACK: case ttAMM_WITHDRAW: - return finalizeWithdraw(tx, view, enforce, j); + return finalizeWithdraw(tx, view, enforce, enforceAMMDelete, j); case ttAMM_BID: return finalizeBid(enforce, j); case ttAMM_VOTE: return finalizeVote(enforce, j); case ttAMM_DELETE: - return finalizeDelete(enforce, result, j); + return finalizeDelete(enforce, enforceAMMDelete, result, j); case ttCHECK_CASH: case ttOFFER_CREATE: case ttPAYMENT: diff --git a/src/test/app/Invariants_test.cpp b/src/test/app/Invariants_test.cpp index 6d53d25661..e0c29ea72a 100644 --- a/src/test/app/Invariants_test.cpp +++ b/src/test/app/Invariants_test.cpp @@ -51,6 +51,7 @@ #include #include #include +#include #include #include @@ -1277,6 +1278,87 @@ class Invariants_test : public beast::unit_test::Suite }); } + void + testAMMDeleteInvariants(FeatureBitset features) + { + using namespace test::jtx; + + bool const enforceAMMDelete = features[fixCleanup3_3_0]; + testcase << "AMM delete invariants" + std::string(enforceAMMDelete ? " fix" : ""); + + Env env(*this, features); + Account const issuer{"issuer"}; + Issue const lptIssue{Currency(0x4c50540000000000), issuer.id()}; + STAmount const zeroLP{lptIssue, 0}; + STAmount const nonZeroLP{lptIssue, 1}; + + auto const makeAMM = [](STAmount const& lptBalance) { + auto sleAMM = std::make_shared(keylet::amm(uint256(1))); + sleAMM->setFieldAmount(sfLPTokenBalance, lptBalance); + return sleAMM; + }; + + auto const checkInvariant = [&](TxType txType, + TER result, + std::optional const& deletedLPBalance, + bool expected, + std::string const& expectedLog) { + test::StreamSink sink{beast::Severity::Warning}; + beast::Journal const jlog{sink}; + ValidAMM invariant; + + if (deletedLPBalance) + invariant.visitEntry(true, makeAMM(*deletedLPBalance), nullptr); + + bool const actual = invariant.finalize( + STTx{txType, [](STObject&) {}}, result, XRPAmount{}, *env.current(), jlog); + + BEAST_EXPECTS(actual == expected, "unexpected AMM delete invariant result"); + auto const messages = sink.messages().str(); + auto const expectedLogWhenEnforced = enforceAMMDelete ? expectedLog : ""; + if (!expectedLogWhenEnforced.empty()) + { + BEAST_EXPECTS(messages.contains(expectedLogWhenEnforced), expectedLogWhenEnforced); + } + else + { + BEAST_EXPECTS(messages.empty(), messages); + } + }; + + checkInvariant( + ttPAYMENT, + tesSUCCESS, + nonZeroLP, + !enforceAMMDelete, + "Invariant failed: AMM failed, unexpected AMM deletion by"); + checkInvariant( + ttAMM_DELETE, + tesSUCCESS, + std::nullopt, + !enforceAMMDelete, + "Invariant failed: AMMDelete failed, AMM object remained on tesSUCCESS"); + checkInvariant( + ttAMM_DELETE, + tesSUCCESS, + nonZeroLP, + !enforceAMMDelete, + "Invariant failed: AMMDelete failed, AMM object deleted with non-zero LP balance"); + checkInvariant( + ttAMM_DELETE, + tecINCOMPLETE, + zeroLP, + !enforceAMMDelete, + "Invariant failed: AMMDelete failed, AMM object deleted when result is not tesSUCCESS"); + + checkInvariant(ttAMM_WITHDRAW, tesSUCCESS, nonZeroLP, true, ""); + checkInvariant(ttAMM_CLAWBACK, tesSUCCESS, nonZeroLP, true, ""); + + checkInvariant(ttAMM_DELETE, tesSUCCESS, zeroLP, true, ""); + checkInvariant(ttAMM_WITHDRAW, tesSUCCESS, zeroLP, true, ""); + checkInvariant(ttAMM_CLAWBACK, tesSUCCESS, zeroLP, true, ""); + } + static SLE::pointer createPermissionedDomain( ApplyContext& ac, @@ -4900,6 +4982,8 @@ public: testNoZeroEscrow(); testValidNewAccountRoot(); testNFTokenPageInvariants(); + testAMMDeleteInvariants(defaultAmendments()); + testAMMDeleteInvariants(defaultAmendments() - fixCleanup3_3_0); testPermissionedDomainInvariants(defaultAmendments() | fixCleanup3_1_3); testPermissionedDomainInvariants(defaultAmendments() - fixCleanup3_1_3); testPermissionedDEX(defaultAmendments() | fixCleanup3_1_3); From 19a9ed776761ee738da4ee2ffde88aa349a9ae31 Mon Sep 17 00:00:00 2001 From: Zhiyuan Wang <96991820+Kassaking7@users.noreply.github.com> Date: Mon, 22 Jun 2026 14:42:57 -0400 Subject: [PATCH 05/17] fix: Move AMMInvariant weakInvariantCheck logic into the transaction (#7032) --- include/xrpl/ledger/helpers/AMMHelpers.h | 26 +++++++++++++ src/libxrpl/ledger/helpers/AMMHelpers.cpp | 37 +++++++++++++++++++ src/libxrpl/tx/invariants/AMMInvariant.cpp | 9 +---- .../tx/transactors/dex/AMMClawback.cpp | 10 +++++ src/libxrpl/tx/transactors/dex/AMMDeposit.cpp | 13 +++++++ .../tx/transactors/dex/AMMWithdraw.cpp | 10 +++++ src/test/app/AMMClawback_test.cpp | 18 ++++++++- src/test/app/AMM_test.cpp | 16 ++++++-- 8 files changed, 127 insertions(+), 12 deletions(-) diff --git a/include/xrpl/ledger/helpers/AMMHelpers.h b/include/xrpl/ledger/helpers/AMMHelpers.h index d21e50e7cb..de8bb9d3f7 100644 --- a/include/xrpl/ledger/helpers/AMMHelpers.h +++ b/include/xrpl/ledger/helpers/AMMHelpers.h @@ -37,6 +37,8 @@ reduceOffer(auto const& amount) enum class IsDeposit : bool { No = false, Yes = true }; +inline Number const kAMMInvariantRelativeTolerance{1, -11}; + /** Calculate LP Tokens given AMM pool reserves. * @param asset1 AMM one side of the pool reserve * @param asset2 AMM another side of the pool reserve @@ -738,6 +740,30 @@ ammPoolHolds( AuthHandling authHandling, beast::Journal const j); +/** Check AMM pool product invariant after an AMM operation that changes LP tokens + * (deposit/withdraw/clawback) from an already calculated pool product mean. + * Returns tecPRECISION_LOSS if poolProductMean < newLPTokenBalance beyond the + * invariant tolerance, + * tesSUCCESS otherwise. Skips check when newLPTokenBalance is zero (last withdrawal). + */ +TER +checkAMMPrecisionLoss(Number const& poolProductMean, STAmount const& newLPTokenBalance); + +/** Check AMM pool product invariant after an AMM operation that changes LP tokens + * (deposit/withdraw/clawback). + * Returns tecPRECISION_LOSS if sqrt(asset1 * asset2) < newLPTokenBalance beyond + * the invariant tolerance, + * tesSUCCESS otherwise. Skips check when newLPTokenBalance is zero (last withdrawal). + */ +TER +checkAMMPrecisionLoss( + ReadView const& view, + AccountID const& ammAccountID, + Asset const& asset1, + Asset const& asset2, + STAmount const& newLPTokenBalance, + beast::Journal const j); + /** Get AMM pool and LP token balances. If both optIssue are * provided then they are used as the AMM token pair issues. * Otherwise the missing issues are fetched from ammSle. diff --git a/src/libxrpl/ledger/helpers/AMMHelpers.cpp b/src/libxrpl/ledger/helpers/AMMHelpers.cpp index a59b8e4436..cacbfc9d58 100644 --- a/src/libxrpl/ledger/helpers/AMMHelpers.cpp +++ b/src/libxrpl/ledger/helpers/AMMHelpers.cpp @@ -433,6 +433,43 @@ ammPoolHolds( return std::make_pair(assetInBalance, assetOutBalance); } +TER +checkAMMPrecisionLoss(Number const& poolProductMean, STAmount const& newLPTokenBalance) +{ + if (newLPTokenBalance <= beast::kZero) + return tesSUCCESS; + if (poolProductMean >= newLPTokenBalance) + return tesSUCCESS; + // Strong check failed. Allow the same relative tolerance as the invariant + // checker's weak check. Only return tecPRECISION_LOSS when both fail. + if (withinRelativeDistance( + poolProductMean, Number{newLPTokenBalance}, kAMMInvariantRelativeTolerance)) + return tesSUCCESS; + return tecPRECISION_LOSS; +} + +TER +checkAMMPrecisionLoss( + ReadView const& view, + AccountID const& ammAccountID, + Asset const& asset1, + Asset const& asset2, + STAmount const& newLPTokenBalance, + beast::Journal const j) +{ + if (newLPTokenBalance <= beast::kZero) + return tesSUCCESS; + auto const [amount, amount2] = ammPoolHolds( + view, + ammAccountID, + asset1, + asset2, + FreezeHandling::IgnoreFreeze, + AuthHandling::IgnoreAuth, + j); + return checkAMMPrecisionLoss(root2(amount * amount2), newLPTokenBalance); +} + std::expected, TER> ammHolds( ReadView const& view, diff --git a/src/libxrpl/tx/invariants/AMMInvariant.cpp b/src/libxrpl/tx/invariants/AMMInvariant.cpp index 356c26d6b0..cca0ce149c 100644 --- a/src/libxrpl/tx/invariants/AMMInvariant.cpp +++ b/src/libxrpl/tx/invariants/AMMInvariant.cpp @@ -270,13 +270,8 @@ ValidAMM::generalInvariant( auto const poolProductMean = root2(amount * amount2); bool const nonNegativeBalances = validBalances(amount, amount2, *lptAMMBalanceAfter_, zeroAllowed); - bool const strongInvariantCheck = poolProductMean >= *lptAMMBalanceAfter_; - // Allow for a small relative error if strongInvariantCheck fails - auto weakInvariantCheck = [&]() { - return *lptAMMBalanceAfter_ != beast::kZero && - withinRelativeDistance(poolProductMean, Number{*lptAMMBalanceAfter_}, Number{1, -11}); - }; - if (!nonNegativeBalances || (!strongInvariantCheck && !weakInvariantCheck())) + auto const precisionLoss = checkAMMPrecisionLoss(poolProductMean, *lptAMMBalanceAfter_); + if (!nonNegativeBalances || !isTesSuccess(precisionLoss)) { JLOG(j.error()) << "Invariant failed: AMM " << tx.getTxnType() << " " << tx.getHash(HashPrefix::TransactionId) << " " << ammPoolChanged_ << " " diff --git a/src/libxrpl/tx/transactors/dex/AMMClawback.cpp b/src/libxrpl/tx/transactors/dex/AMMClawback.cpp index b94e97e931..0cc2be381f 100644 --- a/src/libxrpl/tx/transactors/dex/AMMClawback.cpp +++ b/src/libxrpl/tx/transactors/dex/AMMClawback.cpp @@ -258,6 +258,16 @@ AMMClawback::applyGuts(Sandbox& sb) if (!isTesSuccess(result)) return result; // LCOV_EXCL_LINE + if (sb.rules().enabled(fixCleanup3_3_0) && sb.rules().enabled(fixAMMv1_3)) + { + if (auto const ter = + checkAMMPrecisionLoss(sb, ammAccount, asset, asset2, newLPTokenBalance, j_); + !isTesSuccess(ter)) + { + return ter; + } + } + auto const res = AMMWithdraw::deleteAMMAccountIfEmpty(sb, ammSle, newLPTokenBalance, asset, asset2, j_); if (!res.second) diff --git a/src/libxrpl/tx/transactors/dex/AMMDeposit.cpp b/src/libxrpl/tx/transactors/dex/AMMDeposit.cpp index 91858e3cd7..653e8c6961 100644 --- a/src/libxrpl/tx/transactors/dex/AMMDeposit.cpp +++ b/src/libxrpl/tx/transactors/dex/AMMDeposit.cpp @@ -470,6 +470,19 @@ AMMDeposit::applyGuts(Sandbox& sb) XRPL_ASSERT( newLPTokenBalance > beast::kZero, "xrpl::AMMDeposit::applyGuts : valid new LP token balance"); + // Defensive check: deposit formulas with fixAMMv1_3 round LP tokens + // down and asset amounts up, so sqrt(pool1*pool2) >= newLPTokenBalance + // is guaranteed to hold. A precision loss failure is not expected. + if (sb.rules().enabled(fixCleanup3_3_0) && sb.rules().enabled(fixAMMv1_3)) + { + if (auto const ter = checkAMMPrecisionLoss( + sb, ammAccountID, ctx_.tx[sfAsset], ctx_.tx[sfAsset2], newLPTokenBalance, j_); + !isTesSuccess(ter)) + { + UNREACHABLE("xrpl::AMMDeposit::applyGuts : AMM precision loss"); + return {ter, false}; // LCOV_EXCL_LINE + } + } ammSle->setFieldAmount(sfLPTokenBalance, newLPTokenBalance); // LP depositing into AMM empty state gets the auction slot // and the voting diff --git a/src/libxrpl/tx/transactors/dex/AMMWithdraw.cpp b/src/libxrpl/tx/transactors/dex/AMMWithdraw.cpp index d3a6c9c74c..17ce1a6b83 100644 --- a/src/libxrpl/tx/transactors/dex/AMMWithdraw.cpp +++ b/src/libxrpl/tx/transactors/dex/AMMWithdraw.cpp @@ -406,6 +406,16 @@ AMMWithdraw::applyGuts(Sandbox& sb) if (!isTesSuccess(result)) return {result, false}; + if (sb.rules().enabled(fixCleanup3_3_0) && sb.rules().enabled(fixAMMv1_3)) + { + if (auto const ter = checkAMMPrecisionLoss( + sb, ammAccountID, ctx_.tx[sfAsset], ctx_.tx[sfAsset2], newLPTokenBalance, j_); + !isTesSuccess(ter)) + { + return {ter, false}; + } + } + auto const res = deleteAMMAccountIfEmpty( sb, ammSle, newLPTokenBalance, ctx_.tx[sfAsset], ctx_.tx[sfAsset2], j_); // LCOV_EXCL_START diff --git a/src/test/app/AMMClawback_test.cpp b/src/test/app/AMMClawback_test.cpp index 9683e8ac17..ba416d8192 100644 --- a/src/test/app/AMMClawback_test.cpp +++ b/src/test/app/AMMClawback_test.cpp @@ -2486,8 +2486,17 @@ class AMMClawback_test : public beast::unit_test::Suite else if (!features[fixAMMClawbackRounding]) { // sqrt(amount * amount2) >= LPTokens and exceeds the allowed - // tolerance - env(amm::ammClawback(gw, alice, usd, eur, usd(1)), Ter(tecINVARIANT_FAILED)); + // tolerance. + // With fixCleanup3_3_0 this is caught in the transaction layer; + // without it the invariant checker fires instead. + if (features[fixCleanup3_3_0]) + { + env(amm::ammClawback(gw, alice, usd, eur, usd(1)), Ter(tecPRECISION_LOSS)); + } + else + { + env(amm::ammClawback(gw, alice, usd, eur, usd(1)), Ter(tecINVARIANT_FAILED)); + } BEAST_EXPECT(amm.ammExists()); } else if (features[fixAMMv1_3] && features[fixAMMClawbackRounding]) @@ -2514,6 +2523,11 @@ class AMMClawback_test : public beast::unit_test::Suite testFeatureDisabled(all - featureAMMClawback); for (auto const& features : {all - fixAMMv1_3 - fixAMMClawbackRounding - featureMPTokensV2, + // fixAMMv1_3 on, fixAMMClawbackRounding off, fixCleanup3_3_0 off: + // precision loss caught by invariant checker -> tecINVARIANT_FAILED + all - fixAMMClawbackRounding - fixCleanup3_3_0 - featureMPTokensV2, + // fixAMMv1_3 on, fixAMMClawbackRounding off, fixCleanup3_3_0 on: + // precision loss caught in transaction layer -> tecPRECISION_LOSS all - fixAMMClawbackRounding - featureMPTokensV2, all - featureMPTokensV2, all}) diff --git a/src/test/app/AMM_test.cpp b/src/test/app/AMM_test.cpp index 1b54c2aab9..b01d58ddff 100644 --- a/src/test/app/AMM_test.cpp +++ b/src/test/app/AMM_test.cpp @@ -1842,8 +1842,18 @@ private: // are rounded to all LP tokens. testAMM( [&](AMM& ammAlice, Env& env) { - auto const err = - env.enabled(fixAMMv1_3) ? Ter(tecINVARIANT_FAILED) : Ter(tecAMM_BALANCE); + // Without fixAMMv1_3: sub-method returns tecAMM_BALANCE early. + // With fixAMMv1_3 but without fixCleanup3_3_0: sub-method succeeds + // but invariant check catches the precision violation. + // With fixCleanup3_3_0: caught in the transaction layer before + // the invariant checker runs. + auto const err = [&] { + if (!env.enabled(fixAMMv1_3)) + return Ter(tecAMM_BALANCE); + if (env.enabled(fixCleanup3_3_0)) + return Ter(tecPRECISION_LOSS); + return Ter(tecINVARIANT_FAILED); + }(); ammAlice.withdraw( alice_, STAmount{USD, UINT64_C(9'999'999999999999), -12}, @@ -1851,7 +1861,7 @@ private: std::nullopt, err); }, - {.features = {all, all - fixAMMv1_3}, .noLog = true}); + {.features = {all, all - fixAMMv1_3, all - fixCleanup3_3_0}, .noLog = true}); // Tiny withdraw testAMM([&](AMM& ammAlice, Env&) { From dd7401fde21c6ddd5d5e1ef4761a3de6630b40fa Mon Sep 17 00:00:00 2001 From: Mayukha Vadari Date: Mon, 22 Jun 2026 14:44:42 -0400 Subject: [PATCH 06/17] refactor: Clean up tec object deletion logic (#6588) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- include/xrpl/tx/Transactor.h | 7 + src/libxrpl/tx/Transactor.cpp | 238 ++++++++++++++-------------- src/test/app/NFToken_test.cpp | 2 + src/test/app/Offer_test.cpp | 25 +++ src/test/app/SetRegularKey_test.cpp | 22 +++ 5 files changed, 172 insertions(+), 122 deletions(-) diff --git a/include/xrpl/tx/Transactor.h b/include/xrpl/tx/Transactor.h index a27d638107..470571eb48 100644 --- a/include/xrpl/tx/Transactor.h +++ b/include/xrpl/tx/Transactor.h @@ -7,6 +7,7 @@ #include #include +#include #include namespace xrpl { @@ -419,8 +420,13 @@ private: TER consumeSeqProxy(SLE::pointer const& sleAccount); + TER payFee(); + + std::tuple + processPersistentChanges(TER result, XRPAmount fee); + static NotTEC checkSingleSign( ReadView const& view, @@ -428,6 +434,7 @@ private: AccountID const& idAccount, SLE::const_pointer sleAccount, beast::Journal const j); + static NotTEC checkMultiSign( ReadView const& view, diff --git a/src/libxrpl/tx/Transactor.cpp b/src/libxrpl/tx/Transactor.cpp index b57d30d2b3..2ff24d92b5 100644 --- a/src/libxrpl/tx/Transactor.cpp +++ b/src/libxrpl/tx/Transactor.cpp @@ -45,8 +45,10 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -1078,26 +1080,6 @@ removeDeletedTrustLines( } } -static void -removeDeletedMPTs(ApplyView& view, std::vector const& mpts, beast::Journal viewJ) -{ - // There could be at most two MPTs - one for each side of AMM pool - if (mpts.size() > 2) - { - JLOG(viewJ.error()) << "removeDeletedMPTs: deleted mpts exceed 2 " << mpts.size(); - return; - } - - for (auto const& index : mpts) - { - if (auto const sleState = view.peek({ltMPTOKEN, index}); sleState && - deleteAMMMPToken(view, sleState, (*sleState)[sfIssuer], viewJ) != tesSUCCESS) - { - JLOG(viewJ.error()) << "removeDeletedMPTs: failed to delete AMM MPT"; - } - } -} - /** Reset the context, discarding any changes made and adjust the fee. @param fee The transaction fee to be charged. @@ -1160,6 +1142,118 @@ Transactor::trapTransaction(uint256 txHash) const JLOG(j_.debug()) << "Transaction trapped: " << txHash; } +std::tuple +Transactor::processPersistentChanges(TER result, XRPAmount fee) +{ + JLOG(j_.trace()) << "reapplying because of " << transToken(result); + + // FIXME: This mechanism for doing work while returning a `tec` is + // awkward and very limiting. A more general purpose approach + // should be used, making it possible to do more useful work + // when transactions fail with a `tec` code. + + auto typesForResult = [](TER const ter) { + std::unordered_set types; + if ((ter == tecOVERSIZE) || (ter == tecKILLED)) + { + types.insert(ltOFFER); + } + else if (ter == tecINCOMPLETE) + { + types.insert(ltRIPPLE_STATE); + } + else if (ter == tecEXPIRED) + { + types.insert(ltNFTOKEN_OFFER); + types.insert(ltCREDENTIAL); + } + return types; + }; + + // Build a list of ledger entry types to collect, based on the + // result code. Only deleted objects of these types will be + // re-applied after the context is reset. + auto const typesToCollect = typesForResult(result); + + std::map> deletedObjects; + if (!typesToCollect.empty()) + { + ctx_.visit( + [&typesToCollect, &deletedObjects]( + uint256 const& index, bool isDelete, SLE::const_ref before, SLE::const_ref after) { + if (isDelete) + { + XRPL_ASSERT( + before && after, + "xrpl::Transactor::processPersistentChanges : non-null " + "SLE inputs"); + if (before && after) + { + auto const type = before->getType(); + if (typesToCollect.contains(type)) + { + // For offers, only collect unfunded removals + // (where TakerPays is unchanged) + if (type == ltOFFER && + before->getFieldAmount(sfTakerPays) != + after->getFieldAmount(sfTakerPays)) + return; + + deletedObjects[type].push_back(index); + } + } + } + }); + } + + // Reset the context, potentially adjusting the fee. + { + auto const resetResult = reset(fee); + if (!isTesSuccess(resetResult.first)) + result = resetResult.first; + + fee = resetResult.second; + } + + // Re-apply the collected deletions, but only if the reset succeeded + // and the post-reset result still allows the same deletion type. + auto const typesToApply = typesForResult(result); + if (isTecClaim(result) && !typesToApply.empty()) + { + auto const viewJ = ctx_.registry.get().getJournal("View"); + for (auto const& [type, ids] : deletedObjects) + { + if (ids.empty() || !typesToApply.contains(type)) + continue; + + switch (type) + { + case ltOFFER: + removeUnfundedOffers(view(), ids, viewJ); + break; + case ltNFTOKEN_OFFER: + removeExpiredNFTokenOffers(view(), ids, viewJ); + break; + case ltRIPPLE_STATE: + removeDeletedTrustLines(view(), ids, viewJ); + break; + case ltCREDENTIAL: + removeExpiredCredentials(view(), ids, viewJ); + break; + // LCOV_EXCL_START + default: + UNREACHABLE( + "xrpl::Transactor::processPersistentChanges() : " + "unexpected type"); + break; + // LCOV_EXCL_STOP + } + } + } + + return {result, fee, isTecClaim(result)}; +} + [[nodiscard]] TER Transactor::checkTransactionInvariants(TER result, XRPAmount fee) { @@ -1209,6 +1303,7 @@ Transactor::checkInvariants(TER result, XRPAmount fee) */ return ctx_.checkInvariants(result, fee); } + //------------------------------------------------------------------------------ ApplyResult Transactor::operator()() @@ -1275,108 +1370,7 @@ Transactor::operator()() (result == tecOVERSIZE) || (result == tecKILLED) || (result == tecINCOMPLETE) || (result == tecEXPIRED) || (isTecClaimHardFail(result, view().flags()))) { - JLOG(j_.trace()) << "reapplying because of " << transToken(result); - - // FIXME: This mechanism for doing work while returning a `tec` is - // awkward and very limiting. A more general purpose approach - // should be used, making it possible to do more useful work - // when transactions fail with a `tec` code. - std::vector removedOffers; - std::vector removedTrustLines; - std::vector removedMPTs; - std::vector expiredNFTokenOffers; - std::vector expiredCredentials; - - bool const doOffers = ((result == tecOVERSIZE) || (result == tecKILLED)); - bool const doLinesOrMPTs = (result == tecINCOMPLETE); - bool const doNFTokenOffers = (result == tecEXPIRED); - bool const doCredentials = (result == tecEXPIRED); - if (doOffers || doLinesOrMPTs || doNFTokenOffers || doCredentials) - { - ctx_.visit([doOffers, - &removedOffers, - doLinesOrMPTs, - &removedTrustLines, - &removedMPTs, - doNFTokenOffers, - &expiredNFTokenOffers, - doCredentials, - &expiredCredentials]( - uint256 const& index, - bool isDelete, - SLE::const_ref before, - SLE::const_ref after) { - if (isDelete) - { - XRPL_ASSERT( - before && after, - "xrpl::Transactor::operator()::visit : non-null SLE " - "inputs"); - if (doOffers && before && after && (before->getType() == ltOFFER) && - (before->getFieldAmount(sfTakerPays) == after->getFieldAmount(sfTakerPays))) - { - // Removal of offer found or made unfunded - removedOffers.push_back(index); - } - - if (doLinesOrMPTs && before && after) - { - // Removal of obsolete AMM trust line - if (before->getType() == ltRIPPLE_STATE) - { - removedTrustLines.push_back(index); - } - else if (before->getType() == ltMPTOKEN) - { - removedMPTs.push_back(index); - } - } - - if (doNFTokenOffers && before && after && - (before->getType() == ltNFTOKEN_OFFER)) - expiredNFTokenOffers.push_back(index); - - if (doCredentials && before && after && (before->getType() == ltCREDENTIAL)) - expiredCredentials.push_back(index); - } - }); - } - - // Reset the context, potentially adjusting the fee. - { - auto const resetResult = reset(fee); - if (!isTesSuccess(resetResult.first)) - result = resetResult.first; - - fee = resetResult.second; - } - - // If necessary, remove any offers found unfunded during processing - if ((result == tecOVERSIZE) || (result == tecKILLED)) - { - removeUnfundedOffers(view(), removedOffers, ctx_.registry.get().getJournal("View")); - } - - if (result == tecEXPIRED) - { - removeExpiredNFTokenOffers( - view(), expiredNFTokenOffers, ctx_.registry.get().getJournal("View")); - } - - if (result == tecINCOMPLETE) - { - removeDeletedTrustLines( - view(), removedTrustLines, ctx_.registry.get().getJournal("View")); - removeDeletedMPTs(view(), removedMPTs, ctx_.registry.get().getJournal("View")); - } - - if (result == tecEXPIRED) - { - removeExpiredCredentials( - view(), expiredCredentials, ctx_.registry.get().getJournal("View")); - } - - applied = isTecClaim(result); + std::tie(result, fee, applied) = processPersistentChanges(result, fee); } if (applied) diff --git a/src/test/app/NFToken_test.cpp b/src/test/app/NFToken_test.cpp index ba8f09c449..cb92b23a4c 100644 --- a/src/test/app/NFToken_test.cpp +++ b/src/test/app/NFToken_test.cpp @@ -1120,6 +1120,7 @@ class NFTokenBaseUtil_test : public beast::unit_test::Suite if (features[fixCleanup3_1_3]) { buyerCount--; + BEAST_EXPECT(!env.closed()->exists(keylet::nftoffer(buyerExpOfferIndex))); } BEAST_EXPECT(ownerCount(env, buyer) == buyerCount); @@ -1143,6 +1144,7 @@ class NFTokenBaseUtil_test : public beast::unit_test::Suite if (features[fixCleanup3_1_3]) { aliceCount--; + BEAST_EXPECT(!env.closed()->exists(keylet::nftoffer(aliceExpOfferIndex))); } BEAST_EXPECT(ownerCount(env, alice) == aliceCount); BEAST_EXPECT(ownerCount(env, buyer) == buyerCount); diff --git a/src/test/app/Offer_test.cpp b/src/test/app/Offer_test.cpp index 7382f4f090..ea8b0a7c0e 100644 --- a/src/test/app/Offer_test.cpp +++ b/src/test/app/Offer_test.cpp @@ -797,11 +797,13 @@ public: // The offer expires (it's not removed yet). env.close(); env.require(Owners(bob, 1), offers(bob, 1)); + auto const expiredBobOffer = keylet::offer(bob, env.seq(bob) - 1); // bob creates the offer that will be crossed. env(offer(bob, usd(500), XRP(500)), Ter(tesSUCCESS)); env.close(); env.require(Owners(bob, 2), offers(bob, 2)); + auto const crossedBobOffer = keylet::offer(bob, env.seq(bob) - 1); env(trust(alice, usd(1000)), Ter(tesSUCCESS)); env(pay(gw, alice, usd(1000)), Ter(tesSUCCESS)); @@ -820,6 +822,8 @@ public: Balance(bob, usd(kNone)), Owners(bob, 1), offers(bob, 1)); + BEAST_EXPECT(!env.current()->exists(expiredBobOffer)); + BEAST_EXPECT(env.current()->exists(crossedBobOffer)); // Order that can be filled env(offer(alice, XRP(500), usd(500)), Txflags(tfFillOrKill), Ter(tesSUCCESS)); @@ -835,6 +839,27 @@ public: offers(bob, 0)); } + // A failed Fill-or-Kill may tentatively consume a funded offer before + // the transaction is reset. That offer must not be treated as an + // unfunded offer cleanup. + { + Env env{*this, features}; + + env.fund(startBalance, gw, alice, bob); + env.close(); + + env(offer(bob, usd(500), XRP(500)), Ter(tesSUCCESS)); + env.close(); + auto const bobOffer = keylet::offer(bob, env.seq(bob) - 1); + + env(trust(alice, usd(1000)), Ter(tesSUCCESS)); + env(pay(gw, alice, usd(1000)), Ter(tesSUCCESS)); + env(offer(alice, XRP(1000), usd(1000)), Txflags(tfFillOrKill), Ter(tecKILLED)); + + env.require(offers(alice, 0), offers(bob, 1), Balance(alice, usd(1000))); + BEAST_EXPECT(env.current()->exists(bobOffer)); + } + // Immediate or Cancel - cross as much as possible // and add nothing on the books: { diff --git a/src/test/app/SetRegularKey_test.cpp b/src/test/app/SetRegularKey_test.cpp index b5d1af9ef0..b512885606 100644 --- a/src/test/app/SetRegularKey_test.cpp +++ b/src/test/app/SetRegularKey_test.cpp @@ -72,6 +72,27 @@ public: env(regkey(alice, alice), Ter(temBAD_REGKEY)); } + void + testNoAlternativeKey() + { + using namespace test::jtx; + + testcase("Cannot remove last signing method"); + Env env{*this, testableAmendments()}; + Account const alice("alice"); + Account const bob("bob"); + env.fund(XRP(10000), alice); + + env(regkey(alice, bob)); + env(fset(alice, asfDisableMaster), Sig(alice)); + + env(regkey(alice, kDisabled), Sig(bob), Ter(tecNO_ALTERNATIVE_KEY)); + + auto const sle = env.le(alice); + BEAST_EXPECT( + sle && sle->isFlag(lsfDisableMaster) && sle->getAccountID(sfRegularKey) == bob.id()); + } + void testPasswordSpent() { @@ -169,6 +190,7 @@ public: { testDisabledMasterKey(); testDisabledRegularKey(); + testNoAlternativeKey(); testPasswordSpent(); testUniversalMask(); testTicketRegularKey(); From ff02269c0dd1707b38ae783fdf9ba05b07037af7 Mon Sep 17 00:00:00 2001 From: Bart Date: Mon, 22 Jun 2026 18:35:28 -0400 Subject: [PATCH 07/17] refactor: Use dispatch instead of post (#7438) Co-authored-by: Bart <11445373+bthomee@users.noreply.github.com> --- src/xrpld/overlay/detail/PeerImp.cpp | 367 ++++++++++++--------------- 1 file changed, 167 insertions(+), 200 deletions(-) diff --git a/src/xrpld/overlay/detail/PeerImp.cpp b/src/xrpld/overlay/detail/PeerImp.cpp index 21e84d6fc7..e1d7d23215 100644 --- a/src/xrpld/overlay/detail/PeerImp.cpp +++ b/src/xrpld/overlay/detail/PeerImp.cpp @@ -70,7 +70,6 @@ #include #include #include -#include #include #include #include @@ -198,78 +197,70 @@ stringIsUInt256Sized(std::string const& pBuffStr) void PeerImp::run() { - if (!strand_.running_in_this_thread()) - { - post(strand_, std::bind(&PeerImp::run, shared_from_this())); - return; - } + dispatch(strand_, [self = shared_from_this()]() { + auto parseLedgerHash = [](std::string_view value) -> std::optional { + if (uint256 ret; ret.parseHex(value)) + return ret; - auto parseLedgerHash = [](std::string_view value) -> std::optional { - if (uint256 ret; ret.parseHex(value)) - return ret; + if (auto const s = base64Decode(value); s.size() == uint256::size()) + return uint256::fromRaw(s); - if (auto const s = base64Decode(value); s.size() == uint256::size()) - return uint256::fromRaw(s); + return std::nullopt; + }; - return std::nullopt; - }; + std::optional closed; + std::optional previous; - std::optional closed; - std::optional previous; + if (auto const iter = self->headers_.find("Closed-Ledger"); iter != self->headers_.end()) + { + closed = parseLedgerHash(iter->value()); - if (auto const iter = headers_.find("Closed-Ledger"); iter != headers_.end()) - { - closed = parseLedgerHash(iter->value()); + if (!closed) + self->fail("Malformed handshake data (1)"); + } - if (!closed) - fail("Malformed handshake data (1)"); - } + if (auto const iter = self->headers_.find("Previous-Ledger"); iter != self->headers_.end()) + { + previous = parseLedgerHash(iter->value()); - if (auto const iter = headers_.find("Previous-Ledger"); iter != headers_.end()) - { - previous = parseLedgerHash(iter->value()); + if (!previous) + self->fail("Malformed handshake data (2)"); + } - if (!previous) - fail("Malformed handshake data (2)"); - } + if (previous && !closed) + self->fail("Malformed handshake data (3)"); - if (previous && !closed) - fail("Malformed handshake data (3)"); + { + std::scoped_lock const sl(self->recentLock_); + if (closed) + self->closedLedgerHash_ = *closed; + if (previous) + self->previousLedgerHash_ = *previous; + } - { - std::scoped_lock const sl(recentLock_); - if (closed) - closedLedgerHash_ = *closed; - if (previous) - previousLedgerHash_ = *previous; - } + if (self->inbound_) + { + self->doAccept(); + } + else + { + self->doProtocolStart(); + } - if (inbound_) - { - doAccept(); - } - else - { - doProtocolStart(); - } - - // Anything else that needs to be done with the connection should be - // done in doProtocolStart + // Anything else that needs to be done with the connection should be + // done in doProtocolStart + }); } void PeerImp::stop() { - if (!strand_.running_in_this_thread()) - { - post(strand_, std::bind(&PeerImp::stop, shared_from_this())); - return; - } + dispatch(strand_, [self = shared_from_this()]() { + if (!self->socket_.is_open()) + return; - if (!socket_.is_open()) - return; - - close(); + self->close(); + }); } //------------------------------------------------------------------------------ @@ -277,126 +268,111 @@ PeerImp::stop() void PeerImp::send(std::shared_ptr const& m) { - if (!strand_.running_in_this_thread()) - { - post(strand_, std::bind(&PeerImp::send, shared_from_this(), m)); - return; - } - if (gracefulClose_) - return; - if (detaching_) - return; - if (!socket_.is_open()) - return; + dispatch(strand_, [self = shared_from_this(), m]() { + if (self->gracefulClose_) + return; + if (self->detaching_) + return; + if (!self->socket_.is_open()) + return; - auto validator = m->getValidatorKey(); - if (validator && !squelch_.expireSquelch(*validator)) - { - overlay_.reportOutboundTraffic( - TrafficCount::Category::SquelchSuppressed, - static_cast(m->getBuffer(compressionEnabled_).size())); - return; - } + auto validator = m->getValidatorKey(); + if (validator && !self->squelch_.expireSquelch(*validator)) + { + self->overlay_.reportOutboundTraffic( + TrafficCount::Category::SquelchSuppressed, + static_cast(m->getBuffer(self->compressionEnabled_).size())); + return; + } - // report categorized outgoing traffic - overlay_.reportOutboundTraffic( - safeCast(m->getCategory()), - static_cast(m->getBuffer(compressionEnabled_).size())); + // report categorized outgoing traffic + self->overlay_.reportOutboundTraffic( + safeCast(m->getCategory()), + static_cast(m->getBuffer(self->compressionEnabled_).size())); - // report total outgoing traffic - overlay_.reportOutboundTraffic( - TrafficCount::Category::Total, static_cast(m->getBuffer(compressionEnabled_).size())); + // report total outgoing traffic + self->overlay_.reportOutboundTraffic( + TrafficCount::Category::Total, + static_cast(m->getBuffer(self->compressionEnabled_).size())); - auto sendqSize = sendQueue_.size(); + auto sendqSize = self->sendQueue_.size(); - if (sendqSize < Tuning::kTargetSendQueue) - { - // To detect a peer that does not read from their - // side of the connection, we expect a peer to have - // a small senq periodically - largeSendq_ = 0; - } - else if (auto sink = journal_.debug(); sink && (sendqSize % Tuning::kSendQueueLogFreq) == 0) - { - std::string const n = name(); - sink << n << " sendq: " << sendqSize; - } + if (sendqSize < Tuning::kTargetSendQueue) + { + // To detect a peer that does not read from their + // side of the connection, we expect a peer to have + // a small sendq periodically + self->largeSendq_ = 0; + } + else if ( + auto sink = self->journal_.debug(); + sink && (sendqSize % Tuning::kSendQueueLogFreq) == 0) + { + std::string const n = self->name(); + sink << n << " sendq: " << sendqSize; + } - sendQueue_.push(m); + self->sendQueue_.push(m); - if (sendqSize != 0) - return; + if (sendqSize != 0) + return; - boost::asio::async_write( - stream_, - boost::asio::buffer(sendQueue_.front()->getBuffer(compressionEnabled_)), - bind_executor( - strand_, - std::bind( - &PeerImp::onWriteMessage, - shared_from_this(), - std::placeholders::_1, - std::placeholders::_2))); + boost::asio::async_write( + self->stream_, + boost::asio::buffer(self->sendQueue_.front()->getBuffer(self->compressionEnabled_)), + bind_executor( + self->strand_, + std::bind( + &PeerImp::onWriteMessage, self, std::placeholders::_1, std::placeholders::_2))); + }); } void PeerImp::sendTxQueue() { - if (!strand_.running_in_this_thread()) - { - post(strand_, std::bind(&PeerImp::sendTxQueue, shared_from_this())); - return; - } - - if (!txQueue_.empty()) - { - protocol::TMHaveTransactions ht; - std::ranges::for_each( - txQueue_, [&](auto const& hash) { ht.add_hashes(hash.data(), hash.size()); }); - JLOG(pJournal_.trace()) << "sendTxQueue " << txQueue_.size(); - txQueue_.clear(); - send(std::make_shared(ht, protocol::mtHAVE_TRANSACTIONS)); - } + dispatch(strand_, [self = shared_from_this()]() { + if (!self->txQueue_.empty()) + { + protocol::TMHaveTransactions ht; + std::ranges::for_each( + self->txQueue_, [&](auto const& hash) { ht.add_hashes(hash.data(), hash.size()); }); + JLOG(self->pJournal_.trace()) << "sendTxQueue " << self->txQueue_.size(); + self->txQueue_.clear(); + self->send(std::make_shared(ht, protocol::mtHAVE_TRANSACTIONS)); + } + }); } void PeerImp::addTxQueue(uint256 const& hash) { - if (!strand_.running_in_this_thread()) - { - post(strand_, std::bind(&PeerImp::addTxQueue, shared_from_this(), hash)); - return; - } + dispatch(strand_, [self = shared_from_this(), hash]() { + if (self->txQueue_.size() == reduce_relay::kMaxTxQueueSize) + { + JLOG(self->pJournal_.warn()) << "addTxQueue exceeds the cap"; + self->sendTxQueue(); + } - if (txQueue_.size() == reduce_relay::kMaxTxQueueSize) - { - JLOG(pJournal_.warn()) << "addTxQueue exceeds the cap"; - sendTxQueue(); - } - - txQueue_.insert(hash); - JLOG(pJournal_.trace()) << "addTxQueue " << txQueue_.size(); + self->txQueue_.insert(hash); + JLOG(self->pJournal_.trace()) << "addTxQueue " << self->txQueue_.size(); + }); } void PeerImp::removeTxQueue(uint256 const& hash) { - if (!strand_.running_in_this_thread()) - { - post(strand_, std::bind(&PeerImp::removeTxQueue, shared_from_this(), hash)); - return; - } - - auto removed = txQueue_.erase(hash); - JLOG(pJournal_.trace()) << "removeTxQueue " << removed; + dispatch(strand_, [self = shared_from_this(), hash]() { + auto removed = self->txQueue_.erase(hash); + JLOG(self->pJournal_.trace()) << "removeTxQueue " << removed; + }); } void PeerImp::charge(Resource::Charge const& fee, std::string const& context) { - dispatch(strand_, [this, self = shared_from_this(), fee, context]() { - if ((usage_.charge(fee, context) == Resource::Disposition::Drop) && - usage_.disconnect(pJournal_)) + dispatch(strand_, [self = shared_from_this(), fee, context]() { + if ((self->usage_.charge(fee, context) == Resource::Disposition::Drop) && + self->usage_.disconnect(self->pJournal_)) { // Idempotent: only the first worker to observe Drop counts the // metric and posts fail(). Without the guard, several queued @@ -405,11 +381,11 @@ PeerImp::charge(Resource::Charge const& fee, std::string const& context) // shutdowns. fail(std::string const&) self-posts to strand_ // when invoked off-strand. bool expected = false; - if (chargeDisconnectFired_.compare_exchange_strong( + if (self->chargeDisconnectFired_.compare_exchange_strong( expected, true, std::memory_order_acq_rel)) { - overlay_.incPeerDisconnectCharges(); - fail("charge: Resources"); + self->overlay_.incPeerDisconnectCharges(); + self->fail("charge: Resources"); } } }); @@ -640,20 +616,14 @@ PeerImp::close() void PeerImp::fail(std::string const& reason) { - if (!strand_.running_in_this_thread()) - { - post( - strand_, - std::bind( - (void (Peer::*)(std::string const&))&PeerImp::fail, shared_from_this(), reason)); - return; - } - if (journal_.active(beast::Severity::Warning) && socket_.is_open()) - { - std::string const n = name(); - JLOG(journal_.warn()) << n << " failed: " << reason; - } - close(); + dispatch(strand_, [self = shared_from_this(), reason]() { + if (self->journal_.active(beast::Severity::Warning) && self->socket_.is_open()) + { + std::string const n = self->name(); + JLOG(self->journal_.warn()) << n << " failed: " << reason; + } + self->close(); + }); } void @@ -2752,45 +2722,42 @@ PeerImp::onMessage(std::shared_ptr const& m) void PeerImp::onMessage(std::shared_ptr const& m) { - using on_message_fn = void (PeerImp::*)(std::shared_ptr const&); - if (!strand_.running_in_this_thread()) - { - post(strand_, std::bind((on_message_fn)&PeerImp::onMessage, shared_from_this(), m)); - return; - } + dispatch(strand_, [self = shared_from_this(), m]() { + if (!m->has_validatorpubkey()) + { + self->fee_.update(Resource::kFeeInvalidData, "squelch no pubkey"); + return; + } + auto validator = m->validatorpubkey(); + auto const slice{makeSlice(validator)}; + if (!publicKeyType(slice)) + { + self->fee_.update(Resource::kFeeInvalidData, "squelch bad pubkey"); + return; + } + PublicKey const key(slice); - if (!m->has_validatorpubkey()) - { - fee_.update(Resource::kFeeInvalidData, "squelch no pubkey"); - return; - } - auto validator = m->validatorpubkey(); - auto const slice{makeSlice(validator)}; - if (!publicKeyType(slice)) - { - fee_.update(Resource::kFeeInvalidData, "squelch bad pubkey"); - return; - } - PublicKey const key(slice); + // Ignore the squelch for validator's own messages. + if (key == self->app_.getValidationPublicKey()) + { + JLOG(self->pJournal_.debug()) + << "onMessage: TMSquelch discarding validator's squelch " << slice; + return; + } - // Ignore the squelch for validator's own messages. - if (key == app_.getValidationPublicKey()) - { - JLOG(pJournal_.debug()) << "onMessage: TMSquelch discarding validator's squelch " << slice; - return; - } + std::uint32_t const duration = m->has_squelchduration() ? m->squelchduration() : 0; + if (!m->squelch()) + { + self->squelch_.removeSquelch(key); + } + else if (!self->squelch_.addSquelch(key, std::chrono::seconds{duration})) + { + self->fee_.update(Resource::kFeeInvalidData, "squelch duration"); + } - std::uint32_t const duration = m->has_squelchduration() ? m->squelchduration() : 0; - if (!m->squelch()) - { - squelch_.removeSquelch(key); - } - else if (!squelch_.addSquelch(key, std::chrono::seconds{duration})) - { - fee_.update(Resource::kFeeInvalidData, "squelch duration"); - } - - JLOG(pJournal_.debug()) << "onMessage: TMSquelch " << slice << " " << id() << " " << duration; + JLOG(self->pJournal_.debug()) + << "onMessage: TMSquelch " << slice << " " << self->id() << " " << duration; + }); } //-------------------------------------------------------------------------- From 0b22050b5e33e5a46e0a294124f547cd5fd6da49 Mon Sep 17 00:00:00 2001 From: Jingchen Date: Tue, 23 Jun 2026 20:25:38 +0100 Subject: [PATCH 08/17] ci: Update workflows and conan to use VS2026 and grpc 1.81.0 (#7550) Co-authored-by: Ayaz Salikhov --- .github/scripts/strategy-matrix/windows.json | 2 +- .github/workflows/reusable-build-test-config.yml | 4 ++-- conan.lock | 12 ++++++------ conanfile.py | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/scripts/strategy-matrix/windows.json b/.github/scripts/strategy-matrix/windows.json index e25f9ad131..370e9f5bc7 100644 --- a/.github/scripts/strategy-matrix/windows.json +++ b/.github/scripts/strategy-matrix/windows.json @@ -1,6 +1,6 @@ { "platform": "windows/amd64", - "runner": ["self-hosted", "Windows", "devbox"], + "runner": ["self-hosted", "Windows", "dev-box-windows-2026"], "configs": [ { "build_type": "Release" }, { diff --git a/.github/workflows/reusable-build-test-config.yml b/.github/workflows/reusable-build-test-config.yml index 3e6464aaba..fe441dba6e 100644 --- a/.github/workflows/reusable-build-test-config.yml +++ b/.github/workflows/reusable-build-test-config.yml @@ -82,7 +82,7 @@ jobs: name: ${{ inputs.config_name }} runs-on: ${{ fromJSON(inputs.runs_on) }} container: ${{ inputs.image != '' && inputs.image || null }} - timeout-minutes: ${{ inputs.sanitizers != '' && 360 || 90 }} + timeout-minutes: ${{ inputs.sanitizers != '' && 360 || 180 }} env: # Use a namespace to keep the objects separate for each configuration. CCACHE_NAMESPACE: ${{ inputs.config_name }} @@ -163,7 +163,7 @@ jobs: CMAKE_ARGS: ${{ inputs.cmake_args }} run: | cmake \ - -G '${{ runner.os == 'Windows' && 'Visual Studio 17 2022' || 'Ninja' }}' \ + -G '${{ runner.os == 'Windows' && 'Visual Studio 18 2026' || 'Ninja' }}' \ -DCMAKE_TOOLCHAIN_FILE:FILEPATH=build/generators/conan_toolchain.cmake \ -DCMAKE_BUILD_TYPE="${BUILD_TYPE}" \ ${CMAKE_ARGS} \ diff --git a/conan.lock b/conan.lock index e2eb8d871a..d80a6d0c57 100644 --- a/conan.lock +++ b/conan.lock @@ -1,9 +1,9 @@ { "version": "0.5", "requires": [ - "zlib/1.3.2#1cb806da49011867778ffb6ac7190fcb%1777558780.503", + "zlib/1.3.2#1cb806da49011867778ffb6ac7190fcb%1778091116.056", "xxhash/0.8.3#681d36a0a6111fc56e5e45ea182c19cc%1765850149.987", - "sqlite3/3.53.0#324ada52333108388a9a6108bfa96734%1776096494.149", + "sqlite3/3.53.0#324ada52333108388a9a6108bfa96734%1778091117.311", "soci/4.0.3#fe32b9ad5eb47e79ab9e45a68f363945%1774450067.231", "snappy/1.1.10#968fef506ff261592ec30c574d4a7809%1765850147.878", "secp256k1/0.7.1#481881709eb0bdd0185a12b912bbe8ad%1770910500.329", @@ -15,19 +15,19 @@ "lz4/1.10.0#59fc63cac7f10fbe8e05c7e62c2f3504%1765850143.914", "libiconv/1.17#1e65319e945f2d31941a9d28cc13c058%1765842973.492", "libbacktrace/cci.20210118#a7691bfccd8caaf66309df196790a5a1%1765842973.03", - "libarchive/3.8.7#c446109bd1f1d8ba7936c94189bc50e6%1776147552.838", + "libarchive/3.8.7#c446109bd1f1d8ba7936c94189bc50e6%1778091117.848", "jemalloc/5.3.1#1fc58d55316041f10fbc1e8a2eae632a%1776700028.228", "gtest/1.17.0#5224b3b3ff3b4ce1133cbdd27d53ee7d%1768312129.152", - "grpc/1.78.1#b1a9e74b145cc471bed4dc64dc6eb2c1%1774467387.342", + "grpc/1.81.0#2fb144aeb47e7f35c6ebb0e5f35bed31%1781620605.685", "ed25519/2015.03#ae761bdc52730a843f0809bdf6c1b1f6%1765850143.772", "date/3.0.4#862e11e80030356b53c2c38599ceb32b%1765850143.772", "c-ares/1.34.6#545240bb1c40e2cacd4362d6b8967650%1774439234.681", "bzip2/1.0.8#c470882369c2d95c5c77e970c0c7e321%1765850143.837", - "boost/1.91.0#ea540ca2133d831b560036aa24dece3c%1778050991.9", + "boost/1.91.0#ea540ca2133d831b560036aa24dece3c%1778091165.282", "abseil/20250127.0#bb0baf1f362bc4a725a24eddd419b8f7%1774365460.196" ], "build_requires": [ - "zlib/1.3.2#1cb806da49011867778ffb6ac7190fcb%1777558780.503", + "zlib/1.3.2#1cb806da49011867778ffb6ac7190fcb%1778091116.056", "strawberryperl/5.32.1.1#8d114504d172cfea8ea1662d09b6333e%1774447376.964", "protobuf/6.33.5#d96d52ba5baaaa532f47bda866ad87a5%1774467363.12", "nasm/2.16.01#31e26f2ee3c4346ecd347911bd126904%1765850144.707", diff --git a/conanfile.py b/conanfile.py index 2cf5aefbc2..5b78dc22e3 100644 --- a/conanfile.py +++ b/conanfile.py @@ -28,7 +28,7 @@ class Xrpl(ConanFile): requires = [ "ed25519/2015.03", - "grpc/1.78.1", + "grpc/1.81.0", "libarchive/3.8.7", "nudb/2.0.9", "openssl/3.6.2", From 5a2c82f699f1d5036f572d771e7be597ba80f896 Mon Sep 17 00:00:00 2001 From: yinyiqian1 Date: Tue, 23 Jun 2026 15:55:23 -0400 Subject: [PATCH 09/17] fix: Reject delegate permission to pseudo accounts (#7597) --- src/libxrpl/tx/transactors/delegate/DelegateSet.cpp | 6 +++++- src/test/app/Delegate_test.cpp | 13 +++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/libxrpl/tx/transactors/delegate/DelegateSet.cpp b/src/libxrpl/tx/transactors/delegate/DelegateSet.cpp index 82fe88aa9f..32a51555b1 100644 --- a/src/libxrpl/tx/transactors/delegate/DelegateSet.cpp +++ b/src/libxrpl/tx/transactors/delegate/DelegateSet.cpp @@ -52,9 +52,13 @@ DelegateSet::preclaim(PreclaimContext const& ctx) if (!ctx.view.exists(keylet::account(ctx.tx[sfAccount]))) return terNO_ACCOUNT; // LCOV_EXCL_LINE - if (!ctx.view.exists(keylet::account(ctx.tx[sfAuthorize]))) + auto const sleAuthorize = ctx.view.read(keylet::account(ctx.tx[sfAuthorize])); + if (!sleAuthorize) return tecNO_TARGET; + if (isPseudoAccount(sleAuthorize)) + return tecNO_PERMISSION; + // Deleting the delegate object is invalid if it doesn’t exist. if (ctx.tx.getFieldArray(sfPermissions).empty() && !ctx.view.exists(keylet::delegate(ctx.tx[sfAccount], ctx.tx[sfAuthorize]))) diff --git a/src/test/app/Delegate_test.cpp b/src/test/app/Delegate_test.cpp index 20668a42bf..1516219e46 100644 --- a/src/test/app/Delegate_test.cpp +++ b/src/test/app/Delegate_test.cpp @@ -235,6 +235,19 @@ class Delegate_test : public beast::unit_test::Suite env(delegate::set(gw, Account("unknown"), {"Payment"}), Ter(tecNO_TARGET)); } + // Delegating to a pseudo-account is not allowed, should return tecNO_PERMISSION + { + Vault const vault{env}; + auto [tx, keylet] = vault.create({.owner = gw, .asset = xrpIssue()}); + env(tx); + env.close(); + + auto const sleVault = env.le(keylet); + BEAST_EXPECT(sleVault); + Account const vaultPseudo{"vault", sleVault->at(sfAccount)}; + env(delegate::set(gw, vaultPseudo, {"Payment"}), Ter(tecNO_PERMISSION)); + } + // non-delegable transaction { env(delegate::set(gw, alice, {"SetRegularKey"}), Ter(temMALFORMED)); From 6341e752002f90dc87ed49650cb704eac5f62660 Mon Sep 17 00:00:00 2001 From: Jingchen Date: Wed, 24 Jun 2026 13:15:11 +0100 Subject: [PATCH 10/17] refactor: Refactor TaggedCache.ipp to remove const_cast in canonicalize_replace_cache (#5638) Signed-off-by: JCW Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: xrplf-ai-reviewer[bot] <266832837+xrplf-ai-reviewer[bot]@users.noreply.github.com> --- include/xrpl/basics/TaggedCache.h | 86 +++++++++++++++++++-- include/xrpl/basics/TaggedCache.ipp | 87 +++++++++++++++++---- src/test/basics/TaggedCache_test.cpp | 110 +++++++++++++++++++++++++++ 3 files changed, 264 insertions(+), 19 deletions(-) diff --git a/include/xrpl/basics/TaggedCache.h b/include/xrpl/basics/TaggedCache.h index 380b7c687f..ecf6071f8d 100644 --- a/include/xrpl/basics/TaggedCache.h +++ b/include/xrpl/basics/TaggedCache.h @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -17,6 +18,22 @@ namespace xrpl { +namespace detail { + +// Replace-policy tags selecting how TaggedCache::canonicalizeImpl resolves a +// collision when the key already exists (defined in TaggedCache.ipp): +// - ReplaceCached: always replace the cached value with `data`. `data` is +// never written back and may be const. +// - ReplaceClient: keep the cached value and write it back into `data` (the +// client's pointer), which must therefore be writable. +// - ReplaceDynamically: call the supplied callback to decide per call; `data` +// is written back when the cached value is kept, so it must be writable. +struct ReplaceCached; +struct ReplaceClient; +struct ReplaceDynamically; + +} // namespace detail + /** Map/cache combination. This class implements a cache and a map. The cache keeps objects alive in the map. The map allows multiple code paths that reference objects @@ -96,6 +113,32 @@ public: bool del(key_type const& key, bool valid); +private: + // Selects the `data` parameter type of canonicalizeImpl from the replace + // policy: const for detail::ReplaceCached (never written back), otherwise + // writable. + template + using CanonicalizeClientPointerType = std::conditional_t< + std::is_same_v, + SharedPointerType const&, + SharedPointerType&>; + + /** Shared implementation of the canonicalize family. + + `policy` selects how a collision is resolved when `key` already exists: + detail::ReplaceCached, detail::ReplaceClient or + detail::ReplaceDynamically. For ReplaceDynamically `replaceCallback` is + invoked with the existing strong pointer and returns whether to replace + the cached value with `data`; for the tag policies it is unused. + */ + template + bool + canonicalizeImpl( + key_type const& key, + CanonicalizeClientPointerType data, + Policy policy, + Callback&& replaceCallback = nullptr); + public: /** Replace aliased objects with originals. @@ -104,19 +147,52 @@ public: This routine eliminates the duplicate and performs a replacement on the callers shared pointer if needed. + `replaceCallback` is a callable taking the existing strong pointer and + returning whether to replace the cached value with `data` (true) or to + keep the cached value and write it back into `data` (false). Because the + write-back case mutates `data`, `data` must be writable. + @param key The key corresponding to the object @param data A shared pointer to the data corresponding to the object. - @param replace Function that decides if cache should be replaced + @param replaceCallback A callable (existing strong pointer -> bool). - @return `true` If the key already existed. - */ - template + @return `true` if an existing live entry was found and used; `false` if a new entry was + inserted or an expired tracked entry was re-cached. + **/ + template bool - canonicalize(key_type const& key, SharedPointerType& data, R&& replaceCallback); + canonicalize(key_type const& key, SharedPointerType& data, Callback&& replaceCallback); + /** Insert/update the canonical entry for `key`, always replacing the + cached value with `data`. + + If an entry already exists for `key`, the cached value is unconditionally + replaced with `data`; otherwise `data` is inserted. `data` is never + written back, so it may be const. + + @param key The key corresponding to the object. + @param data A shared pointer to the data corresponding to the object. + + @return `true` if an existing live entry was found and used; `false` if a new entry was + inserted or an expired tracked entry was re-cached. + **/ bool canonicalizeReplaceCache(key_type const& key, SharedPointerType const& data); + /** Insert the canonical entry for `key`, keeping any existing cached value. + + If an entry already exists for `key`, the cached value is kept and + written back into `data` so the caller ends up with the canonical + object; otherwise `data` is inserted. Because `data` may be overwritten + it must be writable. + + @param key The key corresponding to the object. + @param data A shared pointer to the data corresponding to the object; + updated to the canonical value when one already exists. + + @return `true` if an existing live entry was found and used; `false` if a new entry was + inserted or an expired tracked entry was re-cached. + **/ bool canonicalizeReplaceClient(key_type const& key, SharedPointerType& data); diff --git a/include/xrpl/basics/TaggedCache.ipp b/include/xrpl/basics/TaggedCache.ipp index cee02749c6..6973ec4ba0 100644 --- a/include/xrpl/basics/TaggedCache.ipp +++ b/include/xrpl/basics/TaggedCache.ipp @@ -5,6 +5,30 @@ namespace xrpl { +namespace detail { + +// Replace-policy tags selecting how TaggedCache::canonicalizeImpl resolves a +// collision when the key already exists: +// - ReplaceCached: always replace the cached value with `data`. `data` is +// never written back and may be const. +// - ReplaceClient: keep the cached value and write it back into `data` (the +// client's pointer), which must therefore be writable. +// - ReplaceDynamically: call the supplied callback to decide per call; `data` +// is written back when the cached value is kept, so it must be writable. +struct ReplaceCached +{ +}; + +struct ReplaceClient +{ +}; + +struct ReplaceDynamically +{ +}; + +} // namespace detail + template < class Key, class T, @@ -300,13 +324,29 @@ template < class Hash, class KeyEqual, class Mutex> -template +template inline bool TaggedCache:: - canonicalize(key_type const& key, SharedPointerType& data, R&& replaceCallback) + canonicalizeImpl( + key_type const& key, + CanonicalizeClientPointerType data, + [[maybe_unused]] Policy policy, + [[maybe_unused]] Callback&& replaceCallback) { // Return canonical value, store if needed, refresh in cache // Return values: true=we had the data already + + // `Policy` is one of: + // - detail::ReplaceCached: always replace the cached value with `data`; + // `data` is never written back and may be const. + // - detail::ReplaceClient: keep the cached value and write it back into + // `data` (the client's pointer), which must therefore be writable. + // - detail::ReplaceDynamically: call `replaceCallback` to decide at run + // time; `data` must be writable. + // For the latter two the write-back below requires a mutable `data`, so + // passing a const argument is a compile error. + constexpr bool replaceCached = std::is_same_v; + std::scoped_lock const lock(mutex_); auto cit = cache_.find(key); @@ -324,13 +364,14 @@ TaggedCachesecond; entry.touch(clock_.now()); - auto shouldReplace = [&] { - if constexpr (std::is_invocable_r_v) + auto shouldReplaceCached = [&] { + if constexpr (replaceCached) { - // The reason for this extra complexity is for intrusive - // strong/weak combo getting a strong is relatively expensive - // and not needed for many cases. - return replaceCallback(); + return true; + } + else if constexpr (std::is_same_v) + { + return false; } else { @@ -340,11 +381,11 @@ TaggedCache +template +inline bool +TaggedCache:: + canonicalize(key_type const& key, SharedPointerType& data, Callback&& replaceCallback) +{ + return canonicalizeImpl( + key, data, detail::ReplaceDynamically{}, std::forward(replaceCallback)); +} + template < class Key, class T, @@ -389,7 +448,7 @@ inline bool TaggedCache:: canonicalizeReplaceCache(key_type const& key, SharedPointerType const& data) { - return canonicalize(key, const_cast(data), []() { return true; }); + return canonicalizeImpl(key, data, detail::ReplaceCached{}); } template < @@ -405,7 +464,7 @@ inline bool TaggedCache:: canonicalizeReplaceClient(key_type const& key, SharedPointerType& data) { - return canonicalize(key, data, []() { return false; }); + return canonicalizeImpl(key, data, detail::ReplaceClient{}); } template < diff --git a/src/test/basics/TaggedCache_test.cpp b/src/test/basics/TaggedCache_test.cpp index 77cd25e543..26564a4de8 100644 --- a/src/test/basics/TaggedCache_test.cpp +++ b/src/test/basics/TaggedCache_test.cpp @@ -1,5 +1,7 @@ #include +#include +#include #include #include // IWYU pragma: keep #include @@ -8,6 +10,7 @@ #include #include +#include namespace xrpl { @@ -133,6 +136,113 @@ public: BEAST_EXPECT(c.getCacheSize() == 0); BEAST_EXPECT(c.getTrackSize() == 0); } + { + BEAST_EXPECT(!c.insert(5, "five")); + BEAST_EXPECT(c.getCacheSize() == 1); + BEAST_EXPECT(c.size() == 1); + + { + auto const p1 = c.fetch(5); + BEAST_EXPECT(p1 != nullptr); + BEAST_EXPECT(c.getCacheSize() == 1); + BEAST_EXPECT(c.size() == 1); + + // Advance the clock a lot + ++clock; + c.sweep(); + BEAST_EXPECT(c.getCacheSize() == 0); + BEAST_EXPECT(c.size() == 1); + + auto p2 = std::make_shared("five_2"); + BEAST_EXPECT(c.canonicalizeReplaceCache(5, p2)); + BEAST_EXPECT(c.getCacheSize() == 1); + BEAST_EXPECT(c.size() == 1); + // Make sure the caller's original pointer is unchanged + BEAST_EXPECT(p1.get() != p2.get()); + BEAST_EXPECT(*p2 == "five_2"); + + auto const p3 = c.fetch(5); + BEAST_EXPECT(p3 != nullptr); + BEAST_EXPECT(p3.get() == p2.get()); + BEAST_EXPECT(p3.get() != p1.get()); + } + + ++clock; + c.sweep(); + BEAST_EXPECT(c.getCacheSize() == 0); + BEAST_EXPECT(c.size() == 0); + } + + { + testcase("intrptr"); + + struct MyRefCountObject : IntrusiveRefCounts + { + std::string data; + + // Needed to support weak intrusive pointers + virtual void + partialDestructor() {}; + + MyRefCountObject() = default; + explicit MyRefCountObject(std::string data) : data(std::move(data)) + { + } + + bool + operator==(std::string const& other) const + { + return data == other; + } + }; + + using IntrPtrCache = TaggedCache< + Key, + MyRefCountObject, + /*IsKeyCache*/ false, + intr_ptr::SharedWeakUnionPtr, + intr_ptr::SharedPtr>; + + IntrPtrCache intrPtrCache("IntrPtrTest", 1, 1s, clock, journal); + + intrPtrCache.canonicalizeReplaceCache(1, intr_ptr::makeShared("one")); + BEAST_EXPECT(intrPtrCache.getCacheSize() == 1); + BEAST_EXPECT(intrPtrCache.size() == 1); + + { + { + intrPtrCache.canonicalizeReplaceCache( + 1, intr_ptr::makeShared("one_replaced")); + + auto p = intrPtrCache.fetch(1); + BEAST_EXPECT(*p == "one_replaced"); + + // Advance the clock a lot + ++clock; + intrPtrCache.sweep(); + BEAST_EXPECT(intrPtrCache.getCacheSize() == 0); + BEAST_EXPECT(intrPtrCache.size() == 1); + + intrPtrCache.canonicalizeReplaceCache( + 1, intr_ptr::makeShared("one_replaced_2")); + + auto p2 = intrPtrCache.fetch(1); + BEAST_EXPECT(*p2 == "one_replaced_2"); + + intrPtrCache.del(1, true); + } + + intrPtrCache.canonicalizeReplaceCache( + 1, intr_ptr::makeShared("one_replaced_3")); + auto p3 = intrPtrCache.fetch(1); + BEAST_EXPECT(*p3 == "one_replaced_3"); + } + + ++clock; + intrPtrCache.sweep(); + BEAST_EXPECT(intrPtrCache.getCacheSize() == 0); + BEAST_EXPECT(intrPtrCache.size() == 0); + } } }; From 69d289a388f7339470e861cee38b29a877ee26a3 Mon Sep 17 00:00:00 2001 From: Zhiyuan Wang <96991820+Kassaking7@users.noreply.github.com> Date: Wed, 24 Jun 2026 08:15:45 -0400 Subject: [PATCH 11/17] fix: AMM Quality Leak into Domain BookStep for Permissioned DEX (#6853) Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/libxrpl/tx/paths/BookStep.cpp | 5 ++ src/test/app/PermissionedDEX_test.cpp | 79 +++++++++++++++++++++++++++ 2 files changed, 84 insertions(+) diff --git a/src/libxrpl/tx/paths/BookStep.cpp b/src/libxrpl/tx/paths/BookStep.cpp index 5cc2a987b8..4b045521d2 100644 --- a/src/libxrpl/tx/paths/BookStep.cpp +++ b/src/libxrpl/tx/paths/BookStep.cpp @@ -905,6 +905,11 @@ BookStep::getAMMOffer( ReadView const& view, std::optional const& clobQuality) const { + // AMM doesn't support domain books. When fixCleanup3_3_0 is enabled, exclude + // AMM liquidity so quality estimation matches actual crossing (tryAMM skips + // AMM for domain books). + if (book_.domain && view.rules().enabled(fixCleanup3_3_0)) + return std::nullopt; if (ammLiquidity_) return ammLiquidity_->getOffer(view, clobQuality); return std::nullopt; diff --git a/src/test/app/PermissionedDEX_test.cpp b/src/test/app/PermissionedDEX_test.cpp index 99e69ce482..51ca321f7e 100644 --- a/src/test/app/PermissionedDEX_test.cpp +++ b/src/test/app/PermissionedDEX_test.cpp @@ -993,6 +993,83 @@ class PermissionedDEX_test : public beast::unit_test::Suite BEAST_EXPECT(usd == USD(45)); } + void + testAmmQualityNotLeaked(FeatureBitset features) + { + bool const excludesAmmFromDomainQuality = features[fixCleanup3_3_0]; + + testcase << "AMM quality not leaked into domain BookStep" + << (excludesAmmFromDomainQuality ? " (Cleanup3_3_0 enabled)" + : " (Cleanup3_3_0 disabled)"); + + Env env(*this, features); + auto const& [gw, domainOwner, alice, bob, carol, USD, domainID, credType] = + PermissionedDEX(env); + auto const eur = gw["EUR"]; + + env.trust(eur(1000), bob, domainOwner); + env.close(); + env(pay(gw, bob, eur(100))); + env.close(); + + env(pay(gw, alice, USD(500))); + env.close(); + + // The AMM makes the direct XRP->USD book look much better than it + // really is for domain payments. The domain LOB direct path is 1:1, + // while the competing XRP->EUR->USD path is 2:1. + AMM const amm(env, alice, XRP(10), USD(500)); + + auto const directOfferSeq{env.seq(bob)}; + env(offer(bob, XRP(10), USD(10)), Domain(domainID)); + env.close(); + + auto const xrpEurOfferSeq{env.seq(bob)}; + env(offer(bob, XRP(10), eur(20)), Domain(domainID)); + env.close(); + + auto const eurUsdOfferSeq{env.seq(domainOwner)}; + env(offer(domainOwner, eur(20), USD(20)), Domain(domainID)); + env.close(); + + auto const carolBalBefore = env.balance(carol, USD); + + // Both paths compete for the same XRP(10) sendmax. If AMM quality leaks + // into the direct domain book, the engine ranks direct XRP->USD first + // but crossing can only consume the 1:1 LOB offer. With the fix, the + // direct book is ranked by its domain LOB quality, so the 2:1 + // XRP->EUR->USD path executes first. + env(pay(alice, carol, USD(100)), + Path(~USD), + Path(~eur, ~USD), + Sendmax(XRP(10)), + Txflags(tfPartialPayment | tfNoRippleDirect), + Domain(domainID)); + env.close(); + + auto const delivered = env.balance(carol, USD) - carolBalBefore; + if (excludesAmmFromDomainQuality) + { + BEAST_EXPECT(delivered == USD(20)); + + BEAST_EXPECT(checkOffer(env, bob, directOfferSeq, XRP(10), USD(10), 0, true)); + BEAST_EXPECT(!offerExists(env, bob, xrpEurOfferSeq)); + BEAST_EXPECT(!offerExists(env, domainOwner, eurUsdOfferSeq)); + } + else + { + BEAST_EXPECT(delivered == USD(10)); + + BEAST_EXPECT(!offerExists(env, bob, directOfferSeq)); + BEAST_EXPECT(checkOffer(env, bob, xrpEurOfferSeq, XRP(10), eur(20), 0, true)); + BEAST_EXPECT(checkOffer(env, domainOwner, eurUsdOfferSeq, eur(20), USD(20), 0, true)); + } + + auto [xrp, usd, lpt] = amm.balances(XRP, USD); + BEAST_EXPECT(xrp == XRP(10)); + BEAST_EXPECT(usd == USD(500)); + } + void testHybridOfferCreate(FeatureBitset features) { @@ -1943,6 +2020,8 @@ public: testOfferTokenIssuerInDomain(all); testRemoveUnfundedOffer(all); testAmmNotUsed(all); + testAmmQualityNotLeaked(all); + testAmmQualityNotLeaked(all - fixCleanup3_3_0); testAutoBridge(all); // Test hybrid offers From bb7c4d1c9fdfd1267ac45198ff305545f9579a72 Mon Sep 17 00:00:00 2001 From: Timothy Banks Date: Wed, 24 Jun 2026 08:23:12 -0400 Subject: [PATCH 12/17] fix: Additional RPC validation checks on ammRpcInfo account and amm_account fields. (#7324) --- src/test/jtx/AMM.h | 14 +++++++-- src/test/jtx/impl/AMM.cpp | 30 +++++++++++++++++--- src/test/rpc/AMMInfo_test.cpp | 20 +++++++++++++ src/xrpld/rpc/handlers/orderbook/AMMInfo.cpp | 10 +++++-- 4 files changed, 66 insertions(+), 8 deletions(-) diff --git a/src/test/jtx/AMM.h b/src/test/jtx/AMM.h index deadd80290..99131637bb 100644 --- a/src/test/jtx/AMM.h +++ b/src/test/jtx/AMM.h @@ -174,12 +174,22 @@ public: ammRpcInfo( std::optional const& account = std::nullopt, std::optional const& ledgerIndex = std::nullopt, - std::optional asset1 = std::nullopt, - std::optional asset2 = std::nullopt, + std::optional const& asset1 = std::nullopt, + std::optional const& asset2 = std::nullopt, std::optional const& ammAccount = std::nullopt, bool ignoreParams = false, unsigned apiVersion = RPC::kApiInvalidVersion) const; + [[nodiscard]] json::Value + ammRpcInfo( + std::optional const& account, + std::optional const& ledgerIndex, + std::optional const& asset1, + std::optional const& asset2, + std::optional const& ammAccount, + bool ignoreParams, + unsigned apiVersion) const; + /** Verify the AMM balances. */ [[nodiscard]] bool diff --git a/src/test/jtx/impl/AMM.cpp b/src/test/jtx/impl/AMM.cpp index c6dc14081a..2184f7e1b0 100644 --- a/src/test/jtx/impl/AMM.cpp +++ b/src/test/jtx/impl/AMM.cpp @@ -183,15 +183,37 @@ json::Value AMM::ammRpcInfo( std::optional const& account, std::optional const& ledgerIndex, - std::optional asset1, - std::optional asset2, + std::optional const& asset1, + std::optional const& asset2, std::optional const& ammAccount, bool ignoreParams, unsigned apiVersion) const +{ + auto const toJson = [](AccountID const& a) { return json::Value{to_string(a)}; }; + + return ammRpcInfo( + account.transform(toJson), + ledgerIndex, + asset1, + asset2, + ammAccount.transform(toJson), + ignoreParams, + apiVersion); +} + +json::Value +AMM::ammRpcInfo( + std::optional const& account, + std::optional const& ledgerIndex, + std::optional const& asset1, + std::optional const& asset2, + std::optional const& ammAccount, + bool ignoreParams, + unsigned apiVersion) const { json::Value jv; if (account) - jv[jss::account] = to_string(*account); + jv[jss::account] = *account; if (ledgerIndex) jv[jss::ledger_index] = *ledgerIndex; if (!ignoreParams) @@ -209,7 +231,7 @@ AMM::ammRpcInfo( jv[jss::asset2] = STIssue(sfAsset2, asset2_.asset()).getJson(JsonOptions::Values::None); } if (ammAccount) - jv[jss::amm_account] = to_string(*ammAccount); + jv[jss::amm_account] = *ammAccount; } auto jr = (apiVersion == RPC::kApiInvalidVersion diff --git a/src/test/rpc/AMMInfo_test.cpp b/src/test/rpc/AMMInfo_test.cpp index 28c536aab9..987df6c724 100644 --- a/src/test/rpc/AMMInfo_test.cpp +++ b/src/test/rpc/AMMInfo_test.cpp @@ -65,6 +65,26 @@ public: BEAST_EXPECT(jv[jss::error_message] == "Account malformed."); }); + // Account is not a string + testAMM([&](AMM& ammAlice, Env&) { + auto const jv = + ammAlice.ammRpcInfo(json::Value{42}, std::nullopt, XRP, USD, std::nullopt, true, 3); + BEAST_EXPECT(jv[jss::error_message] == "Account malformed."); + }); + + // AMM Account is not a string + testAMM([&](AMM& ammAlice, Env&) { + auto const jv = ammAlice.ammRpcInfo( + json::Value{to_string(ammAlice.ammAccount())}, + std::nullopt, + XRP, + USD, + json::Value{42}, + false, + 3); + BEAST_EXPECT(jv[jss::error_message] == "Account malformed."); + }); + std::vector, std::optional, TestAccount, bool>> const invalidParams = { {xrpIssue(), std::nullopt, TestAccount::None, false}, diff --git a/src/xrpld/rpc/handlers/orderbook/AMMInfo.cpp b/src/xrpld/rpc/handlers/orderbook/AMMInfo.cpp index 2c2f96b0e8..043fdd2d7b 100644 --- a/src/xrpld/rpc/handlers/orderbook/AMMInfo.cpp +++ b/src/xrpld/rpc/handlers/orderbook/AMMInfo.cpp @@ -120,7 +120,10 @@ doAMMInfo(RPC::JsonContext& context) if (params.isMember(jss::amm_account)) { - auto const id = parseBase58((params[jss::amm_account].asString())); + auto const& ammAccount = params[jss::amm_account]; + if (!ammAccount.isString()) + return std::unexpected(RpcActMalformed); + auto const id = parseBase58(ammAccount.asString()); if (!id) return std::unexpected(RpcActMalformed); auto const sle = ledger->read(keylet::account(*id)); @@ -133,7 +136,10 @@ doAMMInfo(RPC::JsonContext& context) if (params.isMember(jss::account)) { - accountID = parseBase58(params[jss::account].asString()); + auto const& localAccount = params[jss::account]; + if (!localAccount.isString()) + return std::unexpected(RpcActMalformed); + accountID = parseBase58(localAccount.asString()); if (!accountID || !ledger->read(keylet::account(*accountID))) return std::unexpected(RpcActMalformed); } From b68e1f7170fd0de7b7b7110677919eb9df1773c2 Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Wed, 24 Jun 2026 13:24:04 +0100 Subject: [PATCH 13/17] fix: Add pragma once checker (#7580) --- .pre-commit-config.yaml | 8 ++++- bin/pre-commit/fix_pragma_once.py | 34 +++++++++++++++++++ .../ledger/helpers/PermissionedDEXHelpers.h | 1 + include/xrpl/protocol/Batch.h | 2 ++ include/xrpl/tx/paths/detail/AmountSpec.h | 0 include/xrpl/tx/paths/detail/FlowDebugInfo.h | 1 - include/xrpl/tx/paths/detail/StrandFlow.h | 1 - src/libxrpl/tx/paths/Flow.cpp | 1 - src/libxrpl/tx/paths/XRPEndpointStep.cpp | 1 - src/libxrpl/tx/transactors/escrow/Escrow.cpp | 0 src/test/beast/IPEndpointCommon.h | 2 ++ src/test/csf.h | 2 ++ src/test/unit_test/utils.h | 2 ++ src/xrpld/app/ledger/OrderBookDB.h | 0 src/xrpld/overlay/detail/Tuning.h | 1 + src/xrpld/rpc/detail/PathRequestManager.h | 1 - src/xrpld/rpc/detail/Pathfinder.cpp | 1 - src/xrpld/rpc/detail/RippleLineCache.cpp | 0 src/xrpld/rpc/detail/RippleLineCache.h | 0 .../rpc/handlers/ledger/LedgerEntryHelpers.h | 2 ++ 20 files changed, 53 insertions(+), 7 deletions(-) create mode 100755 bin/pre-commit/fix_pragma_once.py delete mode 100644 include/xrpl/tx/paths/detail/AmountSpec.h delete mode 100644 src/libxrpl/tx/transactors/escrow/Escrow.cpp delete mode 100644 src/xrpld/app/ledger/OrderBookDB.h delete mode 100644 src/xrpld/rpc/detail/RippleLineCache.cpp delete mode 100644 src/xrpld/rpc/detail/RippleLineCache.h diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c9dec89435..4cbf4c1dd0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,6 +15,7 @@ repos: hooks: - id: check-added-large-files args: [--maxkb=400, --enforce-all] + - id: check-executables-have-shebangs - id: trailing-whitespace - id: end-of-file-fixer - id: check-merge-conflict @@ -35,13 +36,18 @@ repos: language: python types_or: [c++, c] exclude: ^include/xrpl/protocol_autogen/(transactions|ledger_entries)/ + - id: fix-pragma-once + name: fix missing '#pragma once' declarations in header files + language: python + entry: ./bin/pre-commit/fix_pragma_once.py + files: \.(h|hpp)$ - repo: https://github.com/pre-commit/mirrors-clang-format rev: dd18dad857d6133e90bbe478f4f2f22ec0030269 # frozen: v22.1.5 hooks: - id: clang-format args: [--style=file] - "types_or": [c++, c, proto] + types_or: [c++, c, proto] exclude: ^include/xrpl/protocol_autogen/(transactions|ledger_entries)/ - repo: https://github.com/BlankSpruce/gersemi-pre-commit diff --git a/bin/pre-commit/fix_pragma_once.py b/bin/pre-commit/fix_pragma_once.py new file mode 100755 index 0000000000..08a505b6d0 --- /dev/null +++ b/bin/pre-commit/fix_pragma_once.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 + +""" +Adds "#pragma once" to the top of header files that don't already have it. + +Usage: ./bin/pre-commit/fix_pragma_once.py ... +""" + +import sys +from pathlib import Path + +PRAGMA_ONCE = "#pragma once\n\n" + + +def fix_pragma_once(path: Path) -> bool: + original = path.read_text(encoding="utf-8") + if PRAGMA_ONCE not in original: + path.write_text(PRAGMA_ONCE + original, encoding="utf-8") + return False + return True + + +def main() -> int: + files = [Path(f) for f in sys.argv[1:]] + success = True + + for path in files: + success &= fix_pragma_once(path) + + return 0 if success else 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/include/xrpl/ledger/helpers/PermissionedDEXHelpers.h b/include/xrpl/ledger/helpers/PermissionedDEXHelpers.h index 04b12f2fc5..695a4950f0 100644 --- a/include/xrpl/ledger/helpers/PermissionedDEXHelpers.h +++ b/include/xrpl/ledger/helpers/PermissionedDEXHelpers.h @@ -1,4 +1,5 @@ #pragma once + #include namespace xrpl::permissioned_dex { diff --git a/include/xrpl/protocol/Batch.h b/include/xrpl/protocol/Batch.h index fa7641af70..2f2412b3ff 100644 --- a/include/xrpl/protocol/Batch.h +++ b/include/xrpl/protocol/Batch.h @@ -1,3 +1,5 @@ +#pragma once + #include #include #include diff --git a/include/xrpl/tx/paths/detail/AmountSpec.h b/include/xrpl/tx/paths/detail/AmountSpec.h deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/include/xrpl/tx/paths/detail/FlowDebugInfo.h b/include/xrpl/tx/paths/detail/FlowDebugInfo.h index ec7df86e53..1ccfba34ce 100644 --- a/include/xrpl/tx/paths/detail/FlowDebugInfo.h +++ b/include/xrpl/tx/paths/detail/FlowDebugInfo.h @@ -3,7 +3,6 @@ #include #include #include -#include #include diff --git a/include/xrpl/tx/paths/detail/StrandFlow.h b/include/xrpl/tx/paths/detail/StrandFlow.h index 31f0182258..4d988a4c7e 100644 --- a/include/xrpl/tx/paths/detail/StrandFlow.h +++ b/include/xrpl/tx/paths/detail/StrandFlow.h @@ -9,7 +9,6 @@ #include #include #include -#include #include #include #include diff --git a/src/libxrpl/tx/paths/Flow.cpp b/src/libxrpl/tx/paths/Flow.cpp index 39a9e83e69..7be1f9f633 100644 --- a/src/libxrpl/tx/paths/Flow.cpp +++ b/src/libxrpl/tx/paths/Flow.cpp @@ -10,7 +10,6 @@ #include #include #include -#include #include #include #include diff --git a/src/libxrpl/tx/paths/XRPEndpointStep.cpp b/src/libxrpl/tx/paths/XRPEndpointStep.cpp index 314780c3a7..efdad92791 100644 --- a/src/libxrpl/tx/paths/XRPEndpointStep.cpp +++ b/src/libxrpl/tx/paths/XRPEndpointStep.cpp @@ -16,7 +16,6 @@ #include #include #include -#include #include #include #include diff --git a/src/libxrpl/tx/transactors/escrow/Escrow.cpp b/src/libxrpl/tx/transactors/escrow/Escrow.cpp deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/test/beast/IPEndpointCommon.h b/src/test/beast/IPEndpointCommon.h index 15566cb830..0ff0da35be 100644 --- a/src/test/beast/IPEndpointCommon.h +++ b/src/test/beast/IPEndpointCommon.h @@ -1,3 +1,5 @@ +#pragma once + #include #include diff --git a/src/test/csf.h b/src/test/csf.h index 81af3491c4..d2ddbb460d 100644 --- a/src/test/csf.h +++ b/src/test/csf.h @@ -1,3 +1,5 @@ +#pragma once + #include #include #include diff --git a/src/test/unit_test/utils.h b/src/test/unit_test/utils.h index 028823c763..677bbff31b 100644 --- a/src/test/unit_test/utils.h +++ b/src/test/unit_test/utils.h @@ -1,3 +1,5 @@ +#pragma once + #include #include diff --git a/src/xrpld/app/ledger/OrderBookDB.h b/src/xrpld/app/ledger/OrderBookDB.h deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/xrpld/overlay/detail/Tuning.h b/src/xrpld/overlay/detail/Tuning.h index 20a60d470e..8357fcd130 100644 --- a/src/xrpld/overlay/detail/Tuning.h +++ b/src/xrpld/overlay/detail/Tuning.h @@ -1,4 +1,5 @@ #pragma once + #include #include diff --git a/src/xrpld/rpc/detail/PathRequestManager.h b/src/xrpld/rpc/detail/PathRequestManager.h index 5a5cfde402..c8e272a97d 100644 --- a/src/xrpld/rpc/detail/PathRequestManager.h +++ b/src/xrpld/rpc/detail/PathRequestManager.h @@ -3,7 +3,6 @@ #include #include #include -#include #include #include diff --git a/src/xrpld/rpc/detail/Pathfinder.cpp b/src/xrpld/rpc/detail/Pathfinder.cpp index 25da86ef8f..e1a2a4acf6 100644 --- a/src/xrpld/rpc/detail/Pathfinder.cpp +++ b/src/xrpld/rpc/detail/Pathfinder.cpp @@ -3,7 +3,6 @@ #include #include #include -#include #include #include diff --git a/src/xrpld/rpc/detail/RippleLineCache.cpp b/src/xrpld/rpc/detail/RippleLineCache.cpp deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/xrpld/rpc/detail/RippleLineCache.h b/src/xrpld/rpc/detail/RippleLineCache.h deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/xrpld/rpc/handlers/ledger/LedgerEntryHelpers.h b/src/xrpld/rpc/handlers/ledger/LedgerEntryHelpers.h index 463547a90d..11f6553dfa 100644 --- a/src/xrpld/rpc/handlers/ledger/LedgerEntryHelpers.h +++ b/src/xrpld/rpc/handlers/ledger/LedgerEntryHelpers.h @@ -1,3 +1,5 @@ +#pragma once + #include #include From 6736ab39df871bbb49107c0af8a2821fc3afebaf Mon Sep 17 00:00:00 2001 From: Mayukha Vadari Date: Wed, 24 Jun 2026 08:24:27 -0400 Subject: [PATCH 14/17] test: Add test for Permissioned Domain sequence fix (#7591) Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/test/app/PermissionedDomains_test.cpp | 46 +++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/src/test/app/PermissionedDomains_test.cpp b/src/test/app/PermissionedDomains_test.cpp index f2d7bce152..2cffc18682 100644 --- a/src/test/app/PermissionedDomains_test.cpp +++ b/src/test/app/PermissionedDomains_test.cpp @@ -8,18 +8,21 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include #include #include #include +#include #include #include #include @@ -526,6 +529,47 @@ class PermissionedDomains_test : public beast::unit_test::Suite BEAST_EXPECT(env.ownerCount(alice) == 1); } + void + testTicket(FeatureBitset features) + { + testcase("Tickets"); + + using namespace test::jtx; + + Env env(*this, features); + Account const alice("alice"); + env.fund(XRP(1000), alice); + + pdomain::Credentials const credentials{ + {.issuer = alice, .credType = "credential1"}, + }; + + std::uint32_t seq{env.seq(alice)}; + env(ticket::create(alice, 2)); + + { + env(pdomain::setTx(alice, credentials), ticket::Use(++seq)); + auto domain = pdomain::getNewDomain(env.meta()); + if (features[fixCleanup3_1_3]) + { + BEAST_EXPECT(domain == keylet::permissionedDomain(alice.id(), seq).key); + } + else + { + BEAST_EXPECT(domain == keylet::permissionedDomain(alice.id(), 0).key); + } + } + + if (features[fixCleanup3_1_3]) + { + env(pdomain::setTx(alice, credentials), ticket::Use(++seq)); + } + else + { + env(pdomain::setTx(alice, credentials), ticket::Use(++seq), Ter(tefEXCEPTION)); + } + } + public: void run() override @@ -540,6 +584,8 @@ public: testDelete(withFix_); testAccountReserve(withFeature_); testAccountReserve(withFix_); + testTicket(withFeature_); + testTicket(withFix_); } }; From 8bbbc2051e04edb4d0959deb6c8b322e3e29a63e Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Wed, 24 Jun 2026 13:25:03 +0100 Subject: [PATCH 15/17] chore: Check more tools to be available (#7600) --- .github/scripts/strategy-matrix/linux.json | 2 +- .github/workflows/on-pr.yml | 1 + .github/workflows/on-trigger.yml | 1 + .github/workflows/publish-docs.yml | 2 +- .github/workflows/reusable-clang-tidy.yml | 2 +- .github/workflows/reusable-upload-recipe.yml | 2 +- bin/check-tools.sh | 3 +++ 7 files changed, 9 insertions(+), 4 deletions(-) diff --git a/.github/scripts/strategy-matrix/linux.json b/.github/scripts/strategy-matrix/linux.json index a9b85b766a..863b910dda 100644 --- a/.github/scripts/strategy-matrix/linux.json +++ b/.github/scripts/strategy-matrix/linux.json @@ -1,5 +1,5 @@ { - "image_tag": "sha-fe4c8ae", + "image_tag": "sha-e29b523", "configs": { "ubuntu": [ { diff --git a/.github/workflows/on-pr.yml b/.github/workflows/on-pr.yml index 0cc9b375a7..0c9eeda712 100644 --- a/.github/workflows/on-pr.yml +++ b/.github/workflows/on-pr.yml @@ -70,6 +70,7 @@ jobs: .github/workflows/reusable-upload-recipe.yml .clang-tidy .codecov.yml + bin/check-tools.sh cfg/** cmake/** conan/** diff --git a/.github/workflows/on-trigger.yml b/.github/workflows/on-trigger.yml index 74bca82019..063cdbff7f 100644 --- a/.github/workflows/on-trigger.yml +++ b/.github/workflows/on-trigger.yml @@ -27,6 +27,7 @@ on: - ".github/workflows/reusable-upload-recipe.yml" - ".clang-tidy" - ".codecov.yml" + - "bin/check-tools.sh" - "cfg/**" - "cmake/**" - "conan/**" diff --git a/.github/workflows/publish-docs.yml b/.github/workflows/publish-docs.yml index cc7b6b6e7e..cb7d4c5382 100644 --- a/.github/workflows/publish-docs.yml +++ b/.github/workflows/publish-docs.yml @@ -41,7 +41,7 @@ env: jobs: build: runs-on: ubuntu-latest - container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-fe4c8ae + container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-e29b523 steps: - name: Checkout repository uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 diff --git a/.github/workflows/reusable-clang-tidy.yml b/.github/workflows/reusable-clang-tidy.yml index e99ef574bf..f36463a5d0 100644 --- a/.github/workflows/reusable-clang-tidy.yml +++ b/.github/workflows/reusable-clang-tidy.yml @@ -39,7 +39,7 @@ jobs: needs: [determine-files] if: ${{ always() && !cancelled() && (!inputs.check_only_changed || needs.determine-files.outputs.cpp_changed_files != '' || needs.determine-files.outputs.clang_tidy_config_changed == 'true') }} runs-on: ["self-hosted", "Linux", "X64", "heavy"] - container: "ghcr.io/xrplf/xrpld/nix-debian:sha-fe4c8ae" + container: "ghcr.io/xrplf/xrpld/nix-debian:sha-e29b523" permissions: contents: read issues: write diff --git a/.github/workflows/reusable-upload-recipe.yml b/.github/workflows/reusable-upload-recipe.yml index a389e98771..a18f76796a 100644 --- a/.github/workflows/reusable-upload-recipe.yml +++ b/.github/workflows/reusable-upload-recipe.yml @@ -40,7 +40,7 @@ defaults: jobs: upload: runs-on: ubuntu-latest - container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-fe4c8ae + container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-e29b523 steps: - name: Checkout repository uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 diff --git a/bin/check-tools.sh b/bin/check-tools.sh index 15b16b6fc8..808f384d5b 100755 --- a/bin/check-tools.sh +++ b/bin/check-tools.sh @@ -90,16 +90,19 @@ if [ "${os}" = "linux" ] || [ "${os}" = "macos" ]; then check perl check pkg-config check vim + check zip # These tools are present in our Linux CI images and in local development # setups, but not in the macOS CI environment. So check them everywhere # except when running in CI on macOS. if [ "${os}" = "linux" ] || [ -z "${CI:-}" ]; then check clang-format + check dot check doxygen check gcovr check gh check git-cliff + check git-lfs check gpg # pre-commit, or its alternative implementation prek check pre-commit sh -c 'pre-commit --version || prek --version' From 4fec58251b8d20fd5356f08ed04ddc80d008c2bf Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Wed, 24 Jun 2026 14:56:18 +0100 Subject: [PATCH 16/17] build: Patch nix binaries in CMake (#7539) Co-authored-by: Bart --- .gersemi/definitions.cmake | 3 ++ .../workflows/reusable-build-test-config.yml | 15 ------ CMakeLists.txt | 2 + cmake/CompilationEnv.cmake | 13 +++++ cmake/PatchNixBinary.cmake | 53 +++++++++++++++++++ cmake/XrplCompiler.cmake | 9 ++++ cmake/XrplCore.cmake | 1 + cmake/XrplSanitizers.cmake | 4 +- src/tests/libxrpl/CMakeLists.txt | 1 + 9 files changed, 83 insertions(+), 18 deletions(-) create mode 100644 cmake/PatchNixBinary.cmake diff --git a/.gersemi/definitions.cmake b/.gersemi/definitions.cmake index 245f827f90..58bc74c70a 100644 --- a/.gersemi/definitions.cmake +++ b/.gersemi/definitions.cmake @@ -96,3 +96,6 @@ function(verbose_find_path variable name) ${ARGN} ) endfunction() + +function(patch_nix_binary target) +endfunction() diff --git a/.github/workflows/reusable-build-test-config.yml b/.github/workflows/reusable-build-test-config.yml index fe441dba6e..a81d9aec67 100644 --- a/.github/workflows/reusable-build-test-config.yml +++ b/.github/workflows/reusable-build-test-config.yml @@ -229,21 +229,6 @@ jobs: --parallel "${BUILD_NPROC}" \ --target "${CMAKE_TARGET}" - # This step is needed to allow running in non-Nix environments - - name: Patch binary to use default loader and remove rpath (Linux) - if: ${{ runner.os == 'Linux' && env.SANITIZERS_ENABLED == 'false' }} - run: | - loader="$(/tmp/loader-path.sh)" - patchelf --set-interpreter "${loader}" --remove-rpath "${{ env.BUILD_DIR }}/xrpld" - - # We're only running aarch64 Linux builds in Ubuntu-based images, so this is kept simple - - name: Install libatomic (Linux aarch64) - if: ${{ runner.os == 'Linux' && runner.arch == 'ARM64' }} - run: | - apt update --yes - apt install -y --no-install-recommends \ - libatomic1 - - name: Show ccache statistics if: ${{ inputs.ccache_enabled }} run: | diff --git a/CMakeLists.txt b/CMakeLists.txt index 3dbe60a220..bdc62442b3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -57,6 +57,8 @@ if(target) ) endif() +include(PatchNixBinary) + include(XrplSanity) include(XrplVersion) include(XrplSettings) diff --git a/cmake/CompilationEnv.cmake b/cmake/CompilationEnv.cmake index 0d44f90974..8e69a4dfdd 100644 --- a/cmake/CompilationEnv.cmake +++ b/cmake/CompilationEnv.cmake @@ -56,3 +56,16 @@ elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|arm64|ARM64") else() message(FATAL_ERROR "Unknown architecture: ${CMAKE_SYSTEM_PROCESSOR}") endif() + +# -------------------------------------------------------------------- +# Sanitizers +# -------------------------------------------------------------------- +# SANITIZERS is injected by the Conan toolchain when a sanitizer build is +# requested (see conan/profiles/sanitizers). The flags are applied to the +# 'common' target in XrplSanitizers; this flag lets other modules know a +# sanitizer build is active without depending on that module. +if(DEFINED SANITIZERS) + set(SANITIZERS_ENABLED TRUE) +else() + set(SANITIZERS_ENABLED FALSE) +endif() diff --git a/cmake/PatchNixBinary.cmake b/cmake/PatchNixBinary.cmake new file mode 100644 index 0000000000..79ca0b150c --- /dev/null +++ b/cmake/PatchNixBinary.cmake @@ -0,0 +1,53 @@ +#[===================================================================[ + Patch executables to run in non-Nix environments. + + The Nix-based CI image links binaries against an ELF interpreter (loader) + that lives in the Nix store, so the resulting binaries don't run elsewhere + (including once installed from the .deb package). `patch_nix_binary` adds a + POST_BUILD step that resets the interpreter to the system default loader and + drops the rpath. + + This is only active inside the Nix-based image, detected by the presence of + /tmp/loader-path.sh (shipped by that image, resolves the default loader). It + is skipped for sanitizer builds, whose runtime libraries are resolved through + the rpath. Everywhere else `patch_nix_binary` is a no-op. +#]===================================================================] + +include_guard(GLOBAL) + +include(CompilationEnv) + +# Provided by the Nix-based CI image; prints the system default ELF loader path. +set(_loader_path_script "/tmp/loader-path.sh") + +if(is_linux AND NOT SANITIZERS_ENABLED AND EXISTS "${_loader_path_script}") + execute_process( + COMMAND "${_loader_path_script}" + OUTPUT_VARIABLE DEFAULT_LOADER_PATH + OUTPUT_STRIP_TRAILING_WHITESPACE + COMMAND_ERROR_IS_FATAL ANY + ) + find_program(PATCHELF_COMMAND patchelf REQUIRED) + set(PATCH_NIX_BINARIES TRUE) + message( + STATUS + "Binaries will be patched to use loader '${DEFAULT_LOADER_PATH}'" + ) +else() + set(PATCH_NIX_BINARIES FALSE) +endif() + +function(patch_nix_binary target) + if(NOT PATCH_NIX_BINARIES) + return() + endif() + add_custom_command( + TARGET ${target} + POST_BUILD + COMMAND + "${PATCHELF_COMMAND}" --set-interpreter "${DEFAULT_LOADER_PATH}" + --remove-rpath "$" + COMMENT "Patching ${target}: set default loader, remove rpath" + VERBATIM + ) +endfunction() diff --git a/cmake/XrplCompiler.cmake b/cmake/XrplCompiler.cmake index 9af8e962d0..cb4e797137 100644 --- a/cmake/XrplCompiler.cmake +++ b/cmake/XrplCompiler.cmake @@ -154,6 +154,15 @@ else() > ) + # On aarch64, libatomic is required for atomic operations. It is not needed on x86_64. + # Linking it statically on Linux + if(is_arm64 AND is_linux) + target_link_options( + common + INTERFACE -Wl,--push-state -Wl,-Bstatic -latomic -Wl,--pop-state + ) + endif() + # Keep -stdlib=libstdc++ off the compile commands, but preserve it for linking. # # Conan turns `compiler.libcxx=libstdc++` into `-stdlib=libstdc++` and puts it in diff --git a/cmake/XrplCore.cmake b/cmake/XrplCore.cmake index 52d7714a99..4d4a800d9a 100644 --- a/cmake/XrplCore.cmake +++ b/cmake/XrplCore.cmake @@ -247,6 +247,7 @@ target_link_modules( if(xrpld) add_executable(xrpld) + patch_nix_binary(xrpld) if(tests) target_compile_definitions(xrpld PUBLIC ENABLE_TESTS) target_compile_definitions( diff --git a/cmake/XrplSanitizers.cmake b/cmake/XrplSanitizers.cmake index 64f1841bfb..893c880374 100644 --- a/cmake/XrplSanitizers.cmake +++ b/cmake/XrplSanitizers.cmake @@ -14,11 +14,9 @@ include_guard(GLOBAL) include(CompilationEnv) -if(NOT DEFINED SANITIZERS) - set(SANITIZERS_ENABLED FALSE) +if(NOT SANITIZERS_ENABLED) return() endif() -set(SANITIZERS_ENABLED TRUE) message(STATUS "=== Configuring Sanitizers ===") message(STATUS " SANITIZERS: ${SANITIZERS}") diff --git a/src/tests/libxrpl/CMakeLists.txt b/src/tests/libxrpl/CMakeLists.txt index 2dae6fccb9..bd56028728 100644 --- a/src/tests/libxrpl/CMakeLists.txt +++ b/src/tests/libxrpl/CMakeLists.txt @@ -13,6 +13,7 @@ add_executable( helpers/TestSink.cpp helpers/TxTest.cpp ) +patch_nix_binary(xrpl_tests) set_target_properties( xrpl_tests PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}" From eef8f4a4ff29a76acdc261ab2a309b55ae3cb18e Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Wed, 24 Jun 2026 18:23:29 +0100 Subject: [PATCH 17/17] chore: Use clang-tidy v22 new features (#7427) --- .clang-tidy | 8 ++- .../scripts/levelization/results/ordering.txt | 1 - include/xrpl/basics/DecayingSample.h | 6 +- include/xrpl/basics/Log.h | 3 +- include/xrpl/basics/TaggedCache.h | 4 +- include/xrpl/basics/TaggedCache.ipp | 4 +- include/xrpl/basics/hardened_hash.h | 2 +- .../xrpl/basics/partitioned_unordered_map.h | 8 +-- include/xrpl/basics/random.h | 1 + include/xrpl/beast/asio/io_latency_probe.h | 4 +- include/xrpl/beast/clock/abstract_clock.h | 8 +-- .../xrpl/beast/clock/basic_seconds_clock.h | 8 +-- .../detail/aged_container_iterator.h | 6 +- .../container/detail/aged_ordered_container.h | 29 ++++---- .../detail/aged_unordered_container.h | 32 ++++----- include/xrpl/beast/core/List.h | 6 +- include/xrpl/beast/core/LockFreeStack.h | 6 +- include/xrpl/beast/hash/uhash.h | 2 +- include/xrpl/beast/rfc2616.h | 2 +- .../beast/unit_test/detail/const_container.h | 10 +-- include/xrpl/beast/unit_test/reporter.h | 14 ++-- include/xrpl/beast/utility/Journal.h | 6 +- include/xrpl/beast/utility/maybe_const.h | 2 +- include/xrpl/beast/utility/rngfill.h | 4 +- include/xrpl/json/json_value.h | 7 +- include/xrpl/protocol/KnownFormats.h | 4 +- include/xrpl/protocol/STBitString.h | 2 +- include/xrpl/protocol/STInteger.h | 2 +- include/xrpl/protocol/STObject.h | 25 +++---- include/xrpl/protocol/TER.h | 4 +- include/xrpl/protocol/Units.h | 2 +- include/xrpl/protocol/XChainAttestations.h | 10 +-- include/xrpl/protocol/digest.h | 4 +- include/xrpl/shamap/FullBelowCache.h | 2 +- src/libxrpl/json/json_valueiterator.cpp | 2 +- src/libxrpl/protocol/XChainAttestations.cpp | 8 +-- src/libxrpl/protocol/tokens.cpp | 8 +-- src/libxrpl/shamap/SHAMapInnerNode.cpp | 2 +- src/libxrpl/tx/invariants/InvariantCheck.cpp | 2 + src/libxrpl/tx/paths/BookStep.cpp | 4 +- src/libxrpl/tx/paths/Flow.cpp | 4 +- .../tx/transactors/system/TicketCreate.cpp | 2 +- src/test/app/LedgerReplay_test.cpp | 4 +- src/test/app/Loan_test.cpp | 4 +- src/test/app/NFTokenDir_test.cpp | 10 +-- src/test/app/OfferMPT_test.cpp | 4 +- src/test/app/Offer_test.cpp | 8 +-- src/test/app/TxQ_test.cpp | 2 +- .../beast/aged_associative_container_test.cpp | 68 +++++++++---------- .../beast/beast_io_latency_probe_test.cpp | 4 +- .../consensus/ByzantineFailureSim_test.cpp | 1 - src/test/consensus/Consensus_test.cpp | 1 - .../DistributedValidatorsSim_test.cpp | 1 - src/test/consensus/ScaleFreeSim_test.cpp | 1 - src/test/csf/BasicNetwork.h | 4 +- src/test/csf/Digraph.h | 6 +- src/test/csf/Scheduler.h | 18 ++--- src/test/csf/SimTime.h | 4 +- src/test/csf/random.h | 4 +- src/test/jtx/TestHelpers.h | 7 +- src/test/nodestore/Timing_test.cpp | 2 +- src/test/server/Server_test.cpp | 2 +- src/test/unit_test/multi_runner.cpp | 4 +- src/test/unit_test/multi_runner.h | 6 +- src/xrpld/app/consensus/RCLConsensus.cpp | 4 ++ src/xrpld/app/consensus/RCLValidations.cpp | 2 + src/xrpld/app/ledger/LedgerHistory.cpp | 4 ++ src/xrpld/app/ledger/detail/BuildLedger.cpp | 2 + src/xrpld/app/ledger/detail/LedgerCleaner.cpp | 2 + src/xrpld/app/ledger/detail/LedgerMaster.cpp | 6 ++ src/xrpld/app/main/Application.cpp | 2 + src/xrpld/app/misc/NetworkOPs.cpp | 2 + src/xrpld/app/misc/detail/Transaction.cpp | 2 +- src/xrpld/app/misc/detail/ValidatorSite.cpp | 2 + src/xrpld/app/rdb/backend/detail/Node.cpp | 10 +-- src/xrpld/consensus/Consensus.h | 30 ++++---- src/xrpld/consensus/ConsensusTypes.h | 8 +-- src/xrpld/consensus/DisputedTx.h | 2 +- src/xrpld/consensus/LedgerTrie.h | 12 ++-- src/xrpld/consensus/Validations.h | 14 ++-- src/xrpld/overlay/Slot.h | 21 +++--- src/xrpld/overlay/Squelch.h | 2 +- src/xrpld/overlay/detail/PeerImp.h | 2 +- src/xrpld/overlay/detail/ZeroCopyStream.h | 6 +- src/xrpld/peerfinder/detail/Checker.h | 8 +-- src/xrpld/peerfinder/detail/Livecache.h | 23 +++---- src/xrpld/peerfinder/detail/Logic.h | 2 +- src/xrpld/rpc/detail/RPCCall.cpp | 2 +- .../handlers/admin/keygen/WalletPropose.cpp | 2 +- src/xrpld/rpc/json_body.h | 2 +- 90 files changed, 315 insertions(+), 294 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index e8e2ca7ac9..35427810a3 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -11,6 +11,7 @@ Checks: "-*, bugprone-copy-constructor-init, bugprone-crtp-constructor-accessibility, bugprone-dangling-handle, + bugprone-derived-method-shadowing-base-method, bugprone-dynamic-static-initializers, bugprone-empty-catch, bugprone-fold-init-type, @@ -21,6 +22,7 @@ Checks: "-*, bugprone-incorrect-roundings, bugprone-infinite-loop, bugprone-integer-division, + bugprone-invalid-enum-default-initialization, bugprone-lambda-function-name, bugprone-macro-parentheses, bugprone-macro-repeated-side-effects, @@ -137,6 +139,7 @@ Checks: "-*, readability-enum-initial-value, readability-identifier-naming, readability-implicit-bool-conversion, + readability-inconsistent-ifelse-braces, readability-make-member-function-const, readability-math-missing-parentheses, readability-misleading-indentation, @@ -145,7 +148,9 @@ Checks: "-*, readability-redundant-declaration, readability-redundant-inline-specifier, readability-redundant-member-init, + readability-redundant-parentheses, readability-redundant-string-init, + readability-redundant-typename, readability-reference-to-constructed-temporary, readability-simplify-boolean-expr, readability-static-definition-in-anonymous-namespace, @@ -153,7 +158,8 @@ Checks: "-*, readability-use-std-min-max " # --- -# bugprone-narrowing-conversions, # this will break a lot of code but we should enable it in the future because it can eliminate a lot of bugs +# bugprone-narrowing-conversions, # This will break a lot of code but we should enable it in the future because it can eliminate a lot of bugs +# misc-override-with-different-visibility, # Will be addressed in a future PR, but for now it generates too many warnings # readability-inconsistent-declaration-parameter-name, # In this codebase this check will break a lot of arg names # readability-static-accessed-through-instance, # this check is probably unnecessary. It makes the code less readable # --- diff --git a/.github/scripts/levelization/results/ordering.txt b/.github/scripts/levelization/results/ordering.txt index 547c1b3539..7b31042158 100644 --- a/.github/scripts/levelization/results/ordering.txt +++ b/.github/scripts/levelization/results/ordering.txt @@ -83,7 +83,6 @@ test.conditions > xrpl.basics test.conditions > xrpl.conditions test.consensus > test.csf test.consensus > test.jtx -test.consensus > test.toplevel test.consensus > test.unit_test test.consensus > xrpl.basics test.consensus > xrpld.app diff --git a/include/xrpl/basics/DecayingSample.h b/include/xrpl/basics/DecayingSample.h index d1861ebc4a..910c8f9e14 100644 --- a/include/xrpl/basics/DecayingSample.h +++ b/include/xrpl/basics/DecayingSample.h @@ -12,8 +12,8 @@ template class DecayingSample { public: - using value_type = typename Clock::duration::rep; - using time_point = typename Clock::time_point; + using value_type = Clock::duration::rep; + using time_point = Clock::time_point; DecayingSample() = delete; @@ -93,7 +93,7 @@ template class DecayWindow { public: - using time_point = typename Clock::time_point; + using time_point = Clock::time_point; explicit DecayWindow(time_point now) : when_(now) { diff --git a/include/xrpl/basics/Log.h b/include/xrpl/basics/Log.h index 6bafbc7c54..0699cdd3d9 100644 --- a/include/xrpl/basics/Log.h +++ b/include/xrpl/basics/Log.h @@ -206,8 +206,7 @@ private: #ifndef JLOG #define JLOG(x) \ if (!(x)) \ - { \ - } \ + ; \ else \ x #endif diff --git a/include/xrpl/basics/TaggedCache.h b/include/xrpl/basics/TaggedCache.h index ecf6071f8d..973fcd828a 100644 --- a/include/xrpl/basics/TaggedCache.h +++ b/include/xrpl/basics/TaggedCache.h @@ -338,7 +338,7 @@ private: sweepHelper( clock_type::time_point const& whenExpire, [[maybe_unused]] clock_type::time_point const& now, - typename KeyValueCacheType::map_type& partition, + KeyValueCacheType::map_type& partition, SweptPointersVector& stuffToSweep, std::atomic& allRemovals, std::scoped_lock const&); @@ -347,7 +347,7 @@ private: sweepHelper( clock_type::time_point const& whenExpire, clock_type::time_point const& now, - typename KeyOnlyCacheType::map_type& partition, + KeyOnlyCacheType::map_type& partition, SweptPointersVector&, std::atomic& allRemovals, std::scoped_lock const&); diff --git a/include/xrpl/basics/TaggedCache.ipp b/include/xrpl/basics/TaggedCache.ipp index 6973ec4ba0..7e812ce4c7 100644 --- a/include/xrpl/basics/TaggedCache.ipp +++ b/include/xrpl/basics/TaggedCache.ipp @@ -735,7 +735,7 @@ TaggedCache& allRemovals, std::scoped_lock const&) @@ -815,7 +815,7 @@ TaggedCache& allRemovals, std::scoped_lock const&) diff --git a/include/xrpl/basics/hardened_hash.h b/include/xrpl/basics/hardened_hash.h index efc77e058b..b8ea1e0f3f 100644 --- a/include/xrpl/basics/hardened_hash.h +++ b/include/xrpl/basics/hardened_hash.h @@ -75,7 +75,7 @@ private: detail::seed_pair seeds_{detail::makeSeedPair<>()}; public: - using result_type = typename HashAlgorithm::result_type; + using result_type = HashAlgorithm::result_type; HardenedHash() = default; diff --git a/include/xrpl/basics/partitioned_unordered_map.h b/include/xrpl/basics/partitioned_unordered_map.h index 3bf64985e5..c51cedf2dd 100644 --- a/include/xrpl/basics/partitioned_unordered_map.h +++ b/include/xrpl/basics/partitioned_unordered_map.h @@ -57,8 +57,8 @@ public: { using iterator_category = std::forward_iterator_tag; partition_map_type* map{nullptr}; - typename partition_map_type::iterator ait{}; - typename map_type::iterator mit; + partition_map_type::iterator ait{}; + map_type::iterator mit; Iterator() = default; @@ -126,8 +126,8 @@ public: using iterator_category = std::forward_iterator_tag; partition_map_type* map{nullptr}; - typename partition_map_type::iterator ait{}; - typename map_type::iterator mit; + partition_map_type::iterator ait{}; + map_type::iterator mit; ConstIterator() = default; diff --git a/include/xrpl/basics/random.h b/include/xrpl/basics/random.h index 17f4a1c213..0b298e12d9 100644 --- a/include/xrpl/basics/random.h +++ b/include/xrpl/basics/random.h @@ -29,6 +29,7 @@ static_assert( namespace detail { // Determines if a type can be called like an Engine +// NOLINTNEXTLINE(readability-redundant-typename): typename required by MSVC template using is_engine = std::is_invocable_r; } // namespace detail diff --git a/include/xrpl/beast/asio/io_latency_probe.h b/include/xrpl/beast/asio/io_latency_probe.h index ce3929a394..5e1b098dcb 100644 --- a/include/xrpl/beast/asio/io_latency_probe.h +++ b/include/xrpl/beast/asio/io_latency_probe.h @@ -18,8 +18,8 @@ template class IOLatencyProbe { private: - using duration = typename Clock::duration; - using time_point = typename Clock::time_point; + using duration = Clock::duration; + using time_point = Clock::time_point; std::recursive_mutex mutex_; std::condition_variable_any cond_; diff --git a/include/xrpl/beast/clock/abstract_clock.h b/include/xrpl/beast/clock/abstract_clock.h index 33d4096d0e..15d785d138 100644 --- a/include/xrpl/beast/clock/abstract_clock.h +++ b/include/xrpl/beast/clock/abstract_clock.h @@ -34,10 +34,10 @@ template class AbstractClock { public: - using rep = typename Clock::rep; - using period = typename Clock::period; - using duration = typename Clock::duration; - using time_point = typename Clock::time_point; + using rep = Clock::rep; + using period = Clock::period; + using duration = Clock::duration; + using time_point = Clock::time_point; using clock_type = Clock; static bool const is_steady = Clock::is_steady; // NOLINT(readability-identifier-naming) diff --git a/include/xrpl/beast/clock/basic_seconds_clock.h b/include/xrpl/beast/clock/basic_seconds_clock.h index 8460dbe26b..5a267e9458 100644 --- a/include/xrpl/beast/clock/basic_seconds_clock.h +++ b/include/xrpl/beast/clock/basic_seconds_clock.h @@ -20,10 +20,10 @@ public: explicit BasicSecondsClock() = default; - using rep = typename Clock::rep; - using period = typename Clock::period; - using duration = typename Clock::duration; - using time_point = typename Clock::time_point; + using rep = Clock::rep; + using period = Clock::period; + using duration = Clock::duration; + using time_point = Clock::time_point; static bool const is_steady = // NOLINT(readability-identifier-naming) Clock::is_steady; diff --git a/include/xrpl/beast/container/detail/aged_container_iterator.h b/include/xrpl/beast/container/detail/aged_container_iterator.h index 8d44646cd4..02fb3927dd 100644 --- a/include/xrpl/beast/container/detail/aged_container_iterator.h +++ b/include/xrpl/beast/container/detail/aged_container_iterator.h @@ -16,15 +16,15 @@ template class AgedContainerIterator { public: - using iterator_category = typename std::iterator_traits::iterator_category; + using iterator_category = std::iterator_traits::iterator_category; using value_type = std::conditional_t< IsConst, typename Iterator::value_type::Stashed::value_type const, typename Iterator::value_type::Stashed::value_type>; - using difference_type = typename std::iterator_traits::difference_type; + using difference_type = std::iterator_traits::difference_type; using pointer = value_type*; using reference = value_type&; - using time_point = typename Iterator::value_type::Stashed::time_point; + using time_point = Iterator::value_type::Stashed::time_point; AgedContainerIterator() = default; diff --git a/include/xrpl/beast/container/detail/aged_ordered_container.h b/include/xrpl/beast/container/detail/aged_ordered_container.h index ad368f2754..4cb2246a22 100644 --- a/include/xrpl/beast/container/detail/aged_ordered_container.h +++ b/include/xrpl/beast/container/detail/aged_ordered_container.h @@ -62,8 +62,8 @@ class AgedOrderedContainer { public: using clock_type = AbstractClock; - using time_point = typename clock_type::time_point; - using duration = typename clock_type::duration; + using time_point = clock_type::time_point; + using duration = clock_type::duration; using key_type = Key; using mapped_type = T; using value_type = std::conditional_t, Key>; @@ -94,8 +94,8 @@ private: { explicit Stashed() = default; - using value_type = typename AgedOrderedContainer::value_type; - using time_point = typename AgedOrderedContainer::time_point; + using value_type = AgedOrderedContainer::value_type; + using time_point = AgedOrderedContainer::time_point; }; Element(time_point const& when, value_type const& value) : value(value), when(when) @@ -192,8 +192,8 @@ private: } }; - using list_type = typename boost::intrusive:: - make_list>::type; + using list_type = + boost::intrusive::make_list>::type; using cont_type = std::conditional_t< IsMulti, @@ -206,8 +206,7 @@ private: boost::intrusive::constant_time_size, boost::intrusive::compare>::type>; - using ElementAllocator = - typename std::allocator_traits::template rebind_alloc; + using ElementAllocator = std::allocator_traits::template rebind_alloc; using ElementAllocatorTraits = std::allocator_traits; @@ -373,8 +372,8 @@ public: using allocator_type = Allocator; using reference = value_type&; using const_reference = value_type const&; - using pointer = typename std::allocator_traits::pointer; - using const_pointer = typename std::allocator_traits::const_pointer; + using pointer = std::allocator_traits::pointer; + using const_pointer = std::allocator_traits::const_pointer; // A set iterator (IsMap==false) is always const // because the elements of a set are immutable. @@ -617,7 +616,7 @@ public: bool MaybeMulti = IsMulti, bool MaybeMap = IsMap, class = std::enable_if_t> - typename std::conditional::type const& + std::conditional::type const& at(K const& k) const; template < @@ -1146,7 +1145,7 @@ private: void touch( beast::detail::AgedContainerIterator pos, - typename clock_type::time_point const& now); + clock_type::time_point const& now); template < bool MaybePropagate = std::allocator_traits::propagate_on_container_swap::value> @@ -1393,7 +1392,7 @@ AgedOrderedContainer::at(K co template template -typename std::conditional::type const& +std::conditional::type const& AgedOrderedContainer::at(K const& k) const { auto const iter(cont_.find(k, std::cref(config_.keyCompare()))); @@ -1732,7 +1731,7 @@ AgedOrderedContainer::operato cend(), other.cbegin(), other.cend(), - [&eq, &other](value_type const& lhs, typename Other::value_type const& rhs) { + [&eq, &other](value_type const& lhs, Other::value_type const& rhs) { return eq(extract(lhs), other.extract(rhs)); }); } @@ -1744,7 +1743,7 @@ template void AgedOrderedContainer::touch( beast::detail::AgedContainerIterator pos, - typename clock_type::time_point const& now) + clock_type::time_point const& now) { auto& e(*pos.iterator()); e.when = now; diff --git a/include/xrpl/beast/container/detail/aged_unordered_container.h b/include/xrpl/beast/container/detail/aged_unordered_container.h index 7162c237d6..3bad12d9e5 100644 --- a/include/xrpl/beast/container/detail/aged_unordered_container.h +++ b/include/xrpl/beast/container/detail/aged_unordered_container.h @@ -67,8 +67,8 @@ class AgedUnorderedContainer { public: using clock_type = AbstractClock; - using time_point = typename clock_type::time_point; - using duration = typename clock_type::duration; + using time_point = clock_type::time_point; + using duration = clock_type::duration; using key_type = Key; using mapped_type = T; using value_type = std::conditional_t, Key>; @@ -99,8 +99,8 @@ private: { explicit Stashed() = default; - using value_type = typename AgedUnorderedContainer::value_type; - using time_point = typename AgedUnorderedContainer::time_point; + using value_type = AgedUnorderedContainer::value_type; + using time_point = AgedUnorderedContainer::time_point; }; Element(time_point const& when, value_type const& value) : value(value), when(when) @@ -201,8 +201,8 @@ private: } }; - using list_type = typename boost::intrusive:: - make_list>::type; + using list_type = + boost::intrusive::make_list>::type; using cont_type = std::conditional_t< IsMulti, @@ -219,16 +219,14 @@ private: boost::intrusive::equal, boost::intrusive::cache_begin>::type>; - using bucket_type = typename cont_type::bucket_type; - using bucket_traits = typename cont_type::bucket_traits; + using bucket_type = cont_type::bucket_type; + using bucket_traits = cont_type::bucket_traits; - using ElementAllocator = - typename std::allocator_traits::template rebind_alloc; + using ElementAllocator = std::allocator_traits::template rebind_alloc; using ElementAllocatorTraits = std::allocator_traits; - using BucketAllocator = - typename std::allocator_traits::template rebind_alloc; + using BucketAllocator = std::allocator_traits::template rebind_alloc; using BucketAllocatorTraits = std::allocator_traits; @@ -542,8 +540,8 @@ public: using allocator_type = Allocator; using reference = value_type&; using const_reference = value_type const&; - using pointer = typename std::allocator_traits::pointer; - using const_pointer = typename std::allocator_traits::const_pointer; + using pointer = std::allocator_traits::pointer; + using const_pointer = std::allocator_traits::const_pointer; // A set iterator (IsMap==false) is always const // because the elements of a set are immutable. @@ -850,7 +848,7 @@ public: bool MaybeMulti = IsMulti, bool MaybeMap = IsMap, class = std::enable_if_t> - typename std::conditional::type const& + std::conditional::type const& at(K const& k) const; template < @@ -1414,7 +1412,7 @@ private: void touch( beast::detail::AgedContainerIterator pos, - typename clock_type::time_point const& now) + clock_type::time_point const& now) { auto& e(*pos.iterator()); e.when = now; @@ -2111,7 +2109,7 @@ template < class KeyEqual, class Allocator> template -typename std::conditional::type const& +std::conditional::type const& AgedUnorderedContainer::at( K const& k) const { diff --git a/include/xrpl/beast/core/List.h b/include/xrpl/beast/core/List.h index 946126a004..1c3827ae1c 100644 --- a/include/xrpl/beast/core/List.h +++ b/include/xrpl/beast/core/List.h @@ -24,7 +24,7 @@ struct CopyConst { explicit CopyConst() = default; - using type = typename std::remove_const::type const; + using type = std::remove_const::type const; }; /** @} */ @@ -56,7 +56,7 @@ class ListIterator { public: using iterator_category = std::bidirectional_iterator_tag; - using value_type = typename beast::detail::CopyConst::type; + using value_type = beast::detail::CopyConst::type; using difference_type = std::ptrdiff_t; using pointer = value_type*; using reference = value_type&; @@ -259,7 +259,7 @@ template class List { public: - using Node = typename detail::ListNode; + using Node = detail::ListNode; using value_type = T; using pointer = value_type*; diff --git a/include/xrpl/beast/core/LockFreeStack.h b/include/xrpl/beast/core/LockFreeStack.h index 6b8f686246..d4ad45cf5c 100644 --- a/include/xrpl/beast/core/LockFreeStack.h +++ b/include/xrpl/beast/core/LockFreeStack.h @@ -12,13 +12,13 @@ template class LockFreeStackIterator { protected: - using Node = typename Container::Node; + using Node = Container::Node; using NodePtr = std::conditional_t; public: using iterator_category = std::forward_iterator_tag; - using value_type = typename Container::value_type; - using difference_type = typename Container::difference_type; + using value_type = Container::value_type; + using difference_type = Container::difference_type; using pointer = std::conditional_t; using reference = std:: diff --git a/include/xrpl/beast/hash/uhash.h b/include/xrpl/beast/hash/uhash.h index 97461f67c7..9ffd5924f7 100644 --- a/include/xrpl/beast/hash/uhash.h +++ b/include/xrpl/beast/hash/uhash.h @@ -11,7 +11,7 @@ struct Uhash { Uhash() = default; - using result_type = typename Hasher::result_type; + using result_type = Hasher::result_type; template result_type diff --git a/include/xrpl/beast/rfc2616.h b/include/xrpl/beast/rfc2616.h index cf80bd26c0..e810733210 100644 --- a/include/xrpl/beast/rfc2616.h +++ b/include/xrpl/beast/rfc2616.h @@ -102,7 +102,7 @@ Result split(FwdIt first, FwdIt last, Char delim) { using namespace detail; - using string = typename Result::value_type; + using string = Result::value_type; Result result; diff --git a/include/xrpl/beast/unit_test/detail/const_container.h b/include/xrpl/beast/unit_test/detail/const_container.h index c171771e45..6826bf4258 100644 --- a/include/xrpl/beast/unit_test/detail/const_container.h +++ b/include/xrpl/beast/unit_test/detail/const_container.h @@ -32,11 +32,11 @@ protected: } public: - using value_type = typename cont_type::value_type; - using size_type = typename cont_type::size_type; - using difference_type = typename cont_type::difference_type; - using iterator = typename cont_type::const_iterator; - using const_iterator = typename cont_type::const_iterator; + using value_type = cont_type::value_type; + using size_type = cont_type::size_type; + using difference_type = cont_type::difference_type; + using iterator = cont_type::const_iterator; + using const_iterator = cont_type::const_iterator; /** Returns `true` if the container is empty. */ [[nodiscard]] bool diff --git a/include/xrpl/beast/unit_test/reporter.h b/include/xrpl/beast/unit_test/reporter.h index 1fdbb451a6..ff990dece5 100644 --- a/include/xrpl/beast/unit_test/reporter.h +++ b/include/xrpl/beast/unit_test/reporter.h @@ -48,7 +48,7 @@ private: std::size_t cases = 0; std::size_t total = 0; std::size_t failed = 0; - typename clock_type::time_point start = clock_type::now(); + clock_type::time_point start = clock_type::now(); explicit SuiteResults(std::string name = "") : name(std::move(name)) { @@ -60,7 +60,7 @@ private: struct Results { - using run_time = std::pair; + using run_time = std::pair; static constexpr auto kMaxTop = 10; @@ -69,7 +69,7 @@ private: std::size_t total = 0; std::size_t failed = 0; std::vector top; - typename clock_type::time_point start = clock_type::now(); + clock_type::time_point start = clock_type::now(); void add(SuiteResults const& r); @@ -91,7 +91,7 @@ public: private: static std::string - fmtdur(typename clock_type::duration const& d); + fmtdur(clock_type::duration const& d); void onSuiteBegin(SuiteInfo const& info) override; @@ -141,9 +141,7 @@ Reporter::Results::add(SuiteResults const& r) top.begin(), top.end(), elapsed, - [](run_time const& t1, typename clock_type::duration const& t2) { - return t1.second > t2; - }); + [](run_time const& t1, clock_type::duration const& t2) { return t1.second > t2; }); if (iter != top.end()) { if (top.size() == kMaxTop) @@ -181,7 +179,7 @@ Reporter::~Reporter() template std::string -Reporter::fmtdur(typename clock_type::duration const& d) +Reporter::fmtdur(clock_type::duration const& d) { using namespace std::chrono; auto const ms = duration_cast(d); diff --git a/include/xrpl/beast/utility/Journal.h b/include/xrpl/beast/utility/Journal.h index 1a0c148d1f..1262a64179 100644 --- a/include/xrpl/beast/utility/Journal.h +++ b/include/xrpl/beast/utility/Journal.h @@ -411,9 +411,9 @@ class BasicLogstream : public std::basic_ostream { using char_type = CharT; using traits_type = Traits; - using int_type = typename traits_type::int_type; - using pos_type = typename traits_type::pos_type; - using off_type = typename traits_type::off_type; + using int_type = traits_type::int_type; + using pos_type = traits_type::pos_type; + using off_type = traits_type::off_type; detail::LogStreamBuf buf_; diff --git a/include/xrpl/beast/utility/maybe_const.h b/include/xrpl/beast/utility/maybe_const.h index 40904471be..10b2eaf7f6 100644 --- a/include/xrpl/beast/utility/maybe_const.h +++ b/include/xrpl/beast/utility/maybe_const.h @@ -15,6 +15,6 @@ struct MaybeConst /** Alias for omitting `typename`. */ template -using maybe_const_t = typename MaybeConst::type; +using maybe_const_t = MaybeConst::type; } // namespace beast diff --git a/include/xrpl/beast/utility/rngfill.h b/include/xrpl/beast/utility/rngfill.h index 1614a594c5..2ea84a7a3d 100644 --- a/include/xrpl/beast/utility/rngfill.h +++ b/include/xrpl/beast/utility/rngfill.h @@ -13,7 +13,7 @@ template void rngfill(void* const buffer, std::size_t const bytes, Generator& g) { - using result_type = typename Generator::result_type; + using result_type = Generator::result_type; constexpr std::size_t kResultSize = sizeof(result_type); std::uint8_t* const bufferStart = static_cast(buffer); @@ -42,7 +42,7 @@ template < void rngfill(std::array& a, Generator& g) { - using result_type = typename Generator::result_type; + using result_type = Generator::result_type; auto i = N / sizeof(result_type); result_type* p = reinterpret_cast(a.data()); while (i--) diff --git a/include/xrpl/json/json_value.h b/include/xrpl/json/json_value.h index e9dcb8bcbe..f786c6a9dc 100644 --- a/include/xrpl/json/json_value.h +++ b/include/xrpl/json/json_value.h @@ -566,6 +566,7 @@ public: using SelfType = ValueConstIterator; ValueConstIterator() = default; + ValueConstIterator(ValueConstIterator const& other) = default; private: /*! \internal Use by Value to create an iterator. @@ -574,12 +575,12 @@ private: public: SelfType& - operator=(ValueIteratorBase const& other); + operator=(SelfType const& other); SelfType operator++(int) { - SelfType temp(*this); + SelfType const temp(*this); ++*this; return temp; } @@ -587,7 +588,7 @@ public: SelfType operator--(int) { - SelfType temp(*this); + SelfType const temp(*this); --*this; return temp; } diff --git a/include/xrpl/protocol/KnownFormats.h b/include/xrpl/protocol/KnownFormats.h index 9aa914ba97..6e21d4bc3a 100644 --- a/include/xrpl/protocol/KnownFormats.h +++ b/include/xrpl/protocol/KnownFormats.h @@ -118,13 +118,13 @@ public: } // begin() and end() are provided for testing purposes. - [[nodiscard]] typename std::forward_list::const_iterator + [[nodiscard]] std::forward_list::const_iterator begin() const { return formats_.begin(); } - [[nodiscard]] typename std::forward_list::const_iterator + [[nodiscard]] std::forward_list::const_iterator end() const { return formats_.end(); diff --git a/include/xrpl/protocol/STBitString.h b/include/xrpl/protocol/STBitString.h index 87c8cd4f45..0267eac22d 100644 --- a/include/xrpl/protocol/STBitString.h +++ b/include/xrpl/protocol/STBitString.h @@ -163,7 +163,7 @@ STBitString::setValue(BaseUInt const& v) } template -typename STBitString::value_type const& +STBitString::value_type const& STBitString::value() const { return value_; diff --git a/include/xrpl/protocol/STInteger.h b/include/xrpl/protocol/STInteger.h index 4e3c9a8923..52e0f7a365 100644 --- a/include/xrpl/protocol/STInteger.h +++ b/include/xrpl/protocol/STInteger.h @@ -120,7 +120,7 @@ STInteger::operator=(value_type const& v) } template -inline typename STInteger::value_type +inline STInteger::value_type STInteger::value() const noexcept { return value_; diff --git a/include/xrpl/protocol/STObject.h b/include/xrpl/protocol/STObject.h index c635e8ce22..e65cc79c78 100644 --- a/include/xrpl/protocol/STObject.h +++ b/include/xrpl/protocol/STObject.h @@ -243,7 +243,7 @@ public: @throws STObject::FieldErr if the field is not present. */ template - typename T::value_type + T::value_type operator[](TypedField const& f) const; /** Get the value of a field as a std::optional @@ -290,7 +290,7 @@ public: @throws STObject::FieldErr if the field is not present. */ template - [[nodiscard]] typename T::value_type + [[nodiscard]] T::value_type at(TypedField const& f) const; /** Get the value of a field as std::optional @@ -478,7 +478,7 @@ template class STObject::Proxy { public: - using value_type = typename T::value_type; + using value_type = T::value_type; [[nodiscard]] value_type value() const; @@ -513,13 +513,10 @@ protected: template concept IsArithmeticNumber = std::is_arithmetic_v || std::is_same_v || std::is_same_v; -template < - typename U, - typename Value = typename U::value_type, - typename Unit = typename U::unit_type> +template concept IsArithmeticValueUnit = std::is_same_v> && IsArithmeticNumber && std::is_class_v; -template +template concept IsArithmeticST = !IsArithmeticValueUnit && IsArithmeticNumber; template concept IsArithmetic = IsArithmeticNumber || IsArithmeticST || IsArithmeticValueUnit; @@ -534,7 +531,7 @@ template class STObject::ValueProxy : public Proxy { private: - using value_type = typename T::value_type; + using value_type = T::value_type; public: ValueProxy(ValueProxy const&) = default; @@ -576,7 +573,7 @@ template class STObject::OptionalProxy : public Proxy { private: - using value_type = typename T::value_type; + using value_type = T::value_type; using optional_type = std::optional>; @@ -840,7 +837,7 @@ operator typename STObject::OptionalProxy::optional_type() const } template -typename STObject::OptionalProxy::optional_type +STObject::OptionalProxy::optional_type STObject::OptionalProxy::operator~() const { return optionalValue(); @@ -933,7 +930,7 @@ STObject::OptionalProxy::optionalValue() const -> optional_type } template -typename STObject::OptionalProxy::value_type +STObject::OptionalProxy::value_type STObject::OptionalProxy::valueOr(value_type val) const { return engaged() ? this->value() : val; @@ -1040,7 +1037,7 @@ STObject::getPIndex(int offset) } template -typename T::value_type +T::value_type STObject::operator[](TypedField const& f) const { return at(f); @@ -1068,7 +1065,7 @@ STObject::operator[](OptionaledField const& of) -> OptionalProxy } template -[[nodiscard]] typename T::value_type +[[nodiscard]] T::value_type STObject::at(TypedField const& f) const { auto const b = peekAtPField(f); diff --git a/include/xrpl/protocol/TER.h b/include/xrpl/protocol/TER.h index c89610f354..072bd4778f 100644 --- a/include/xrpl/protocol/TER.h +++ b/include/xrpl/protocol/TER.h @@ -657,13 +657,13 @@ inline bool isTesSuccess(TER x) noexcept { // Makes use of TERSubset::operator bool() - return !(x); + return !x; } inline bool isTecClaim(TER x) noexcept { - return ((x) >= tecCLAIM); + return (x >= tecCLAIM); } std::unordered_map> const& diff --git a/include/xrpl/protocol/Units.h b/include/xrpl/protocol/Units.h index 7fedc05a0d..39f745e84e 100644 --- a/include/xrpl/protocol/Units.h +++ b/include/xrpl/protocol/Units.h @@ -391,7 +391,7 @@ mulDivU(Source1 value, Dest mul, Source2 div) return std::nullopt; } - using desttype = typename Dest::value_type; + using desttype = Dest::value_type; constexpr auto kMax = std::numeric_limits::max(); // Shortcuts, since these happen a lot in the real world diff --git a/include/xrpl/protocol/XChainAttestations.h b/include/xrpl/protocol/XChainAttestations.h index 457af727a2..1aec5fe549 100644 --- a/include/xrpl/protocol/XChainAttestations.h +++ b/include/xrpl/protocol/XChainAttestations.h @@ -379,16 +379,16 @@ public: [[nodiscard]] STArray toSTArray() const; - [[nodiscard]] typename AttCollection::const_iterator + [[nodiscard]] AttCollection::const_iterator begin() const; - [[nodiscard]] typename AttCollection::const_iterator + [[nodiscard]] AttCollection::const_iterator end() const; - typename AttCollection::iterator + AttCollection::iterator begin(); - typename AttCollection::iterator + AttCollection::iterator end(); template @@ -419,7 +419,7 @@ operator==( } template -inline typename XChainAttestationsBase::AttCollection const& +inline XChainAttestationsBase::AttCollection const& XChainAttestationsBase::attestations() const { return attestations_; diff --git a/include/xrpl/protocol/digest.h b/include/xrpl/protocol/digest.h index 50bf2735fb..721ce60767 100644 --- a/include/xrpl/protocol/digest.h +++ b/include/xrpl/protocol/digest.h @@ -206,7 +206,7 @@ sha512Half(Args const&... args) sha512_half_hasher h; using beast::hash_append; hash_append(h, args...); - return static_cast(h); + return static_cast(h); } /** Returns the SHA512-Half of a series of objects. @@ -222,7 +222,7 @@ sha512HalfS(Args const&... args) sha512_half_hasher_s h; using beast::hash_append; hash_append(h, args...); - return static_cast(h); + return static_cast(h); } } // namespace xrpl diff --git a/include/xrpl/shamap/FullBelowCache.h b/include/xrpl/shamap/FullBelowCache.h index 07290dfbd1..e9fd04ac58 100644 --- a/include/xrpl/shamap/FullBelowCache.h +++ b/include/xrpl/shamap/FullBelowCache.h @@ -25,7 +25,7 @@ public: static constexpr auto kDefaultCacheTargetSize = 0; using key_type = uint256; - using clock_type = typename CacheType::clock_type; + using clock_type = CacheType::clock_type; /** Construct the cache. diff --git a/src/libxrpl/json/json_valueiterator.cpp b/src/libxrpl/json/json_valueiterator.cpp index 5a3a5ffcdb..bb49902f70 100644 --- a/src/libxrpl/json/json_valueiterator.cpp +++ b/src/libxrpl/json/json_valueiterator.cpp @@ -132,7 +132,7 @@ ValueConstIterator::ValueConstIterator(Value::ObjectValues::iterator const& curr } ValueConstIterator& -ValueConstIterator::operator=(ValueIteratorBase const& other) +ValueConstIterator::operator=(SelfType const& other) { copy(other); return *this; diff --git a/src/libxrpl/protocol/XChainAttestations.cpp b/src/libxrpl/protocol/XChainAttestations.cpp index 805d08c097..792fe5da9d 100644 --- a/src/libxrpl/protocol/XChainAttestations.cpp +++ b/src/libxrpl/protocol/XChainAttestations.cpp @@ -628,28 +628,28 @@ XChainAttestationsBase::XChainAttestationsBase( } template -typename XChainAttestationsBase::AttCollection::const_iterator +XChainAttestationsBase::AttCollection::const_iterator XChainAttestationsBase::begin() const { return attestations_.begin(); } template -typename XChainAttestationsBase::AttCollection::const_iterator +XChainAttestationsBase::AttCollection::const_iterator XChainAttestationsBase::end() const { return attestations_.end(); } template -typename XChainAttestationsBase::AttCollection::iterator +XChainAttestationsBase::AttCollection::iterator XChainAttestationsBase::begin() { return attestations_.begin(); } template -typename XChainAttestationsBase::AttCollection::iterator +XChainAttestationsBase::AttCollection::iterator XChainAttestationsBase::end() { return attestations_.end(); diff --git a/src/libxrpl/protocol/tokens.cpp b/src/libxrpl/protocol/tokens.cpp index fcd822a747..d04ceaa3a6 100644 --- a/src/libxrpl/protocol/tokens.cpp +++ b/src/libxrpl/protocol/tokens.cpp @@ -135,16 +135,16 @@ static constexpr std::array const kAlphabetReverse = []() { }(); template -static typename Hasher::result_type +static Hasher::result_type digest(void const* data, std::size_t size) noexcept { Hasher h; h(data, size); - return static_cast(h); + return static_cast(h); } template > -static typename Hasher::result_type +static Hasher::result_type digest(std::array const& v) { return digest(v.data(), v.size()); @@ -152,7 +152,7 @@ digest(std::array const& v) // Computes a double digest (e.g. digest of the digest) template -static typename Hasher::result_type +static Hasher::result_type digest2(Args const&... args) { return digest(digest(args...)); diff --git a/src/libxrpl/shamap/SHAMapInnerNode.cpp b/src/libxrpl/shamap/SHAMapInnerNode.cpp index ee6ebf7f3f..74a0e4515f 100644 --- a/src/libxrpl/shamap/SHAMapInnerNode.cpp +++ b/src/libxrpl/shamap/SHAMapInnerNode.cpp @@ -200,7 +200,7 @@ SHAMapInnerNode::updateHash() using beast::hash_append; hash_append(h, HashPrefix::InnerNode); iterChildren([&](SHAMapHash const& hh) { hash_append(h, hh); }); - nh = static_cast(h); + nh = static_cast(h); } hash_ = SHAMapHash{nh}; } diff --git a/src/libxrpl/tx/invariants/InvariantCheck.cpp b/src/libxrpl/tx/invariants/InvariantCheck.cpp index b4a533905c..e29a9fe661 100644 --- a/src/libxrpl/tx/invariants/InvariantCheck.cpp +++ b/src/libxrpl/tx/invariants/InvariantCheck.cpp @@ -407,8 +407,10 @@ AccountRootsNotDeleted::finalize( "succeeded without deleting an account"; } else + { JLOG(j.fatal()) << "Invariant failed: account deletion " "succeeded but deleted multiple accounts!"; + } return false; } diff --git a/src/libxrpl/tx/paths/BookStep.cpp b/src/libxrpl/tx/paths/BookStep.cpp index 4b045521d2..448172872a 100644 --- a/src/libxrpl/tx/paths/BookStep.cpp +++ b/src/libxrpl/tx/paths/BookStep.cpp @@ -1477,8 +1477,8 @@ bookStepEqual(Step const& step, xrpl::Book const& book) { return std::visit( [&](TIn const&, TOut const&) { - using TIn_ = typename TIn::amount_type; - using TOut_ = typename TOut::amount_type; + using TIn_ = TIn::amount_type; + using TOut_ = TOut::amount_type; if constexpr (ValidTaker) { diff --git a/src/libxrpl/tx/paths/Flow.cpp b/src/libxrpl/tx/paths/Flow.cpp index 7be1f9f633..80e05f058c 100644 --- a/src/libxrpl/tx/paths/Flow.cpp +++ b/src/libxrpl/tx/paths/Flow.cpp @@ -125,8 +125,8 @@ flow( // amount types. return std::visit( [&, &strands = strands](TIn const&, TOut const&) { - using TIn_ = typename TIn::amount_type; - using TOut_ = typename TOut::amount_type; + using TIn_ = TIn::amount_type; + using TOut_ = TOut::amount_type; return finishFlow( sb, srcAsset, diff --git a/src/libxrpl/tx/transactors/system/TicketCreate.cpp b/src/libxrpl/tx/transactors/system/TicketCreate.cpp index 5be00fe76c..be24b6326a 100644 --- a/src/libxrpl/tx/transactors/system/TicketCreate.cpp +++ b/src/libxrpl/tx/transactors/system/TicketCreate.cpp @@ -120,7 +120,7 @@ TicketCreate::doApply() } // Update the record of the number of Tickets this account owns. - std::uint32_t const oldTicketCount = (*(sleAccountRoot))[~sfTicketCount].valueOr(0u); + std::uint32_t const oldTicketCount = (*sleAccountRoot)[~sfTicketCount].valueOr(0u); sleAccountRoot->setFieldU32(sfTicketCount, oldTicketCount + ticketCount); diff --git a/src/test/app/LedgerReplay_test.cpp b/src/test/app/LedgerReplay_test.cpp index 810d93e6e1..2422db05de 100644 --- a/src/test/app/LedgerReplay_test.cpp +++ b/src/test/app/LedgerReplay_test.cpp @@ -1077,10 +1077,10 @@ struct LedgerReplayer_test : public beast::unit_test::Suite { Config c; - std::string const toLoad = (R"xrpldConfig( + std::string const toLoad = R"xrpldConfig( [ledger_replay] 0 -)xrpldConfig"); +)xrpldConfig"; c.loadFromString(toLoad); BEAST_EXPECT(c.ledgerReplay == false); } diff --git a/src/test/app/Loan_test.cpp b/src/test/app/Loan_test.cpp index dbb0033368..2d70efd9b2 100644 --- a/src/test/app/Loan_test.cpp +++ b/src/test/app/Loan_test.cpp @@ -3006,9 +3006,9 @@ protected: } if (mptTest) - (mptTest)(env, brokers[0], mptt); + mptTest(env, brokers[0], mptt); if (iouTest) - (iouTest)(env, brokers[1]); + iouTest(env, brokers[1]); }; testCase( diff --git a/src/test/app/NFTokenDir_test.cpp b/src/test/app/NFTokenDir_test.cpp index 117f2bc816..e24a524b81 100644 --- a/src/test/app/NFTokenDir_test.cpp +++ b/src/test/app/NFTokenDir_test.cpp @@ -143,7 +143,7 @@ class NFTokenDir_test : public beast::unit_test::Suite for (uint256 const& nftID : nftIDs) { offers.emplace_back(keylet::nftoffer(issuer, env.seq(issuer)).key); - env(token::createOffer(issuer, nftID, XRP(0)), Txflags((tfSellNFToken))); + env(token::createOffer(issuer, nftID, XRP(0)), Txflags(tfSellNFToken)); env.close(); } @@ -217,7 +217,7 @@ class NFTokenDir_test : public beast::unit_test::Suite offers.emplace_back(keylet::nftoffer(account, env.seq(account)).key); env(token::createOffer(account, nftID, XRP(0)), token::Destination(buyer), - Txflags((tfSellNFToken))); + Txflags(tfSellNFToken)); } env.close(); @@ -421,7 +421,7 @@ class NFTokenDir_test : public beast::unit_test::Suite offers.emplace_back(keylet::nftoffer(account, env.seq(account)).key); env(token::createOffer(account, nftID, XRP(0)), token::Destination(buyer), - Txflags((tfSellNFToken))); + Txflags(tfSellNFToken)); } env.close(); @@ -651,7 +651,7 @@ class NFTokenDir_test : public beast::unit_test::Suite offers.emplace_back(keylet::nftoffer(account, env.seq(account)).key); env(token::createOffer(account, nftID, XRP(0)), token::Destination(buyer), - Txflags((tfSellNFToken))); + Txflags(tfSellNFToken)); } env.close(); @@ -823,7 +823,7 @@ class NFTokenDir_test : public beast::unit_test::Suite offers[i].emplace_back(keylet::nftoffer(account, env.seq(account)).key); env(token::createOffer(account, nftID, XRP(0)), token::Destination(buyer), - Txflags((tfSellNFToken))); + Txflags(tfSellNFToken)); } } env.close(); diff --git a/src/test/app/OfferMPT_test.cpp b/src/test/app/OfferMPT_test.cpp index ed0b2ffbe3..ac50924ac2 100644 --- a/src/test/app/OfferMPT_test.cpp +++ b/src/test/app/OfferMPT_test.cpp @@ -3431,7 +3431,7 @@ public: auto const gw = Account("gateway"); auto const fee = env.current()->fees().base; - env.fund(reserve(env, 2) + drops(9999640) + (fee), ann); + env.fund(reserve(env, 2) + drops(9999640) + fee, ann); env.fund(reserve(env, 2) + (fee * 4), gw); env.close(); @@ -3467,7 +3467,7 @@ public: auto const bob = Account("bob"); auto const fee = env.current()->fees().base; - env.fund(reserve(env, 2) + drops(400'000'000'000) + (fee), alice, bob); + env.fund(reserve(env, 2) + drops(400'000'000'000) + fee, alice, bob); env.fund(reserve(env, 2) + (fee * 4), gw); env.close(); diff --git a/src/test/app/Offer_test.cpp b/src/test/app/Offer_test.cpp index ea8b0a7c0e..1a2f0b2b12 100644 --- a/src/test/app/Offer_test.cpp +++ b/src/test/app/Offer_test.cpp @@ -3621,7 +3621,7 @@ public: auto const btc = gw["BTC"]; auto const fee = env.current()->fees().base; - env.fund(reserve(env, 2) + drops(9999640) + (fee), ann); + env.fund(reserve(env, 2) + drops(9999640) + fee, ann); env.fund(reserve(env, 2) + (fee * 4), gw); env.close(); @@ -3659,7 +3659,7 @@ public: auto const cny = gw["CNY"]; auto const fee = env.current()->fees().base; - env.fund(reserve(env, 2) + drops(400000000000) + (fee), alice, bob); + env.fund(reserve(env, 2) + drops(400000000000) + fee, alice, bob); env.fund(reserve(env, 2) + (fee * 4), gw); env.close(); @@ -3706,7 +3706,7 @@ public: auto const jpy = gw["JPY"]; auto const fee = env.current()->fees().base; - env.fund(reserve(env, 2) + drops(400000000000) + (fee), alice, bob); + env.fund(reserve(env, 2) + drops(400000000000) + fee, alice, bob); env.fund(reserve(env, 2) + (fee * 4), gw); env.close(); @@ -3759,7 +3759,7 @@ public: auto const jpy = gw2["JPY"]; auto const fee = env.current()->fees().base; - env.fund(reserve(env, 2) + drops(400000000000) + (fee), alice, bob); + env.fund(reserve(env, 2) + drops(400000000000) + fee, alice, bob); env.fund(reserve(env, 2) + (fee * 4), gw1, gw2); env.close(); diff --git a/src/test/app/TxQ_test.cpp b/src/test/app/TxQ_test.cpp index 0ae6b4d80a..97cb035578 100644 --- a/src/test/app/TxQ_test.cpp +++ b/src/test/app/TxQ_test.cpp @@ -1176,7 +1176,7 @@ public: // bankrupt Alice. Fails, because an account can't have // more than the minimum reserve in flight before the // last queued transaction - aliceFee = env.le(alice)->getFieldAmount(sfBalance).xrp().drops() - (62); + aliceFee = env.le(alice)->getFieldAmount(sfBalance).xrp().drops() - 62; env(noop(alice), Seq(aliceSeq), Fee(aliceFee), Ter(telCAN_NOT_QUEUE_BALANCE)); checkMetrics(*this, env, 4, 10, 6, 5); diff --git a/src/test/beast/aged_associative_container_test.cpp b/src/test/beast/aged_associative_container_test.cpp index d7f74aaa7d..2ac5fc5a33 100644 --- a/src/test/beast/aged_associative_container_test.cpp +++ b/src/test/beast/aged_associative_container_test.cpp @@ -11,6 +11,7 @@ #include #include +#include #include #include #include @@ -232,10 +233,10 @@ public: { public: using T = void; - using Value = typename Base::Key; + using Value = Base::Key; using Values = std::vector; - static typename Base::Key const& + static Base::Key const& extract(Value const& value) { return value; // NOLINT(bugprone-return-const-ref-from-parameter) @@ -271,7 +272,7 @@ public: using Value = std::pair; using Values = std::vector; - static typename Base::Key const& + static Base::Key const& extract(Value const& value) { return value.first; @@ -387,7 +388,7 @@ public: struct EqualValue { bool - operator()(typename Traits::Value const& lhs, typename Traits::Value const& rhs) + operator()(Traits::Value const& lhs, Traits::Value const& rhs) { return Traits::extract(lhs) == Traits::extract(rhs); } @@ -647,7 +648,7 @@ AgedAssociativeContainerTestBase::checkUnorderedContentsRefRef(C&& c, Values con using Cont = std::remove_reference_t; using Traits = TestTraits; - using size_type = typename Cont::size_type; + using size_type = Cont::size_type; auto const hash(c.hashFunction()); auto const keyEq(c.keyEq()); for (size_type i(0); i < c.bucketCount(); ++i) @@ -655,10 +656,9 @@ AgedAssociativeContainerTestBase::checkUnorderedContentsRefRef(C&& c, Values con auto const last(c.end(i)); for (auto iter(c.begin(i)); iter != last; ++iter) { - auto const match( - std::find_if(v.begin(), v.end(), [iter](typename Values::value_type const& e) { - return Traits::extract(*iter) == Traits::extract(e); - })); + auto const match(std::ranges::find_if(v, [iter](Values::value_type const& e) { + return Traits::extract(*iter) == Traits::extract(e); + })); BEAST_EXPECT(match != v.end()); BEAST_EXPECT(keyEq(Traits::extract(*iter), Traits::extract(*match))); BEAST_EXPECT(hash(Traits::extract(*iter)) == hash(Traits::extract(*match))); @@ -671,7 +671,7 @@ void AgedAssociativeContainerTestBase::checkContentsRefRef(C&& c, Values const& v) { using Cont = std::remove_reference_t; - using size_type = typename Cont::size_type; + using size_type = Cont::size_type; BEAST_EXPECT(c.size() == v.size()); BEAST_EXPECT(size_type(std::distance(c.begin(), c.end())) == v.size()); @@ -703,7 +703,7 @@ AgedAssociativeContainerTestBase::checkContents(Cont& c) { using Traits = TestTraits; - using Values = typename Traits::Values; + using Values = Traits::Values; checkContents(c, Values()); } @@ -719,10 +719,10 @@ std::enable_if_t AgedAssociativeContainerTestBase::testConstructEmpty() { using Traits = TestTraits; - using Comp = typename Traits::Comp; - using Alloc = typename Traits::Alloc; - using MyComp = typename Traits::MyComp; - using MyAlloc = typename Traits::MyAlloc; + using Comp = Traits::Comp; + using Alloc = Traits::Alloc; + using MyComp = Traits::MyComp; + using MyAlloc = Traits::MyAlloc; typename Traits::ManualClock clock; // testcase (Traits::name() + " empty"); @@ -755,12 +755,12 @@ std::enable_if_t AgedAssociativeContainerTestBase::testConstructEmpty() { using Traits = TestTraits; - using Hash = typename Traits::Hash; - using Equal = typename Traits::Equal; - using Alloc = typename Traits::Alloc; - using MyHash = typename Traits::MyHash; - using MyEqual = typename Traits::MyEqual; - using MyAlloc = typename Traits::MyAlloc; + using Hash = Traits::Hash; + using Equal = Traits::Equal; + using Alloc = Traits::Alloc; + using MyHash = Traits::MyHash; + using MyEqual = Traits::MyEqual; + using MyAlloc = Traits::MyAlloc; typename Traits::ManualClock clock; // testcase (Traits::name() + " empty"); @@ -813,10 +813,10 @@ std::enable_if_t AgedAssociativeContainerTestBase::testConstructRange() { using Traits = TestTraits; - using Comp = typename Traits::Comp; - using Alloc = typename Traits::Alloc; - using MyComp = typename Traits::MyComp; - using MyAlloc = typename Traits::MyAlloc; + using Comp = Traits::Comp; + using Alloc = Traits::Alloc; + using MyComp = Traits::MyComp; + using MyAlloc = Traits::MyAlloc; typename Traits::ManualClock clock; auto const v(Traits::values()); @@ -860,12 +860,12 @@ std::enable_if_t AgedAssociativeContainerTestBase::testConstructRange() { using Traits = TestTraits; - using Hash = typename Traits::Hash; - using Equal = typename Traits::Equal; - using Alloc = typename Traits::Alloc; - using MyHash = typename Traits::MyHash; - using MyEqual = typename Traits::MyEqual; - using MyAlloc = typename Traits::MyAlloc; + using Hash = Traits::Hash; + using Equal = Traits::Equal; + using Alloc = Traits::Alloc; + using MyHash = Traits::MyHash; + using MyEqual = Traits::MyEqual; + using MyAlloc = Traits::MyAlloc; typename Traits::ManualClock clock; auto const v(Traits::values()); @@ -962,7 +962,7 @@ void AgedAssociativeContainerTestBase::testCopyMove() { using Traits = TestTraits; - using Alloc = typename Traits::Alloc; + using Alloc = Traits::Alloc; typename Traits::ManualClock clock; auto const v(Traits::values()); @@ -1307,7 +1307,7 @@ AgedAssociativeContainerTestBase::testChronological() // Test touch() with a non-const iterator. for (auto iter(v.crbegin()); iter != v.crend(); ++iter) { - using iterator = typename decltype(c)::iterator; + using iterator = decltype(c)::iterator; iterator const found(c.find(Traits::extract(*iter))); BEAST_EXPECT(found != c.cend()); @@ -1327,7 +1327,7 @@ AgedAssociativeContainerTestBase::testChronological() // Test touch() with a const_iterator for (auto iter(v.cbegin()); iter != v.cend(); ++iter) { - using const_iterator = typename decltype(c)::const_iterator; + using const_iterator = decltype(c)::const_iterator; const_iterator const found(c.find(Traits::extract(*iter))); BEAST_EXPECT(found != c.cend()); diff --git a/src/test/beast/beast_io_latency_probe_test.cpp b/src/test/beast/beast_io_latency_probe_test.cpp index 5f183ef091..b7e4980f05 100644 --- a/src/test/beast/beast_io_latency_probe_test.cpp +++ b/src/test/beast/beast_io_latency_probe_test.cpp @@ -36,8 +36,8 @@ class io_latency_probe_test : public beast::unit_test::Suite, public beast::test template struct MeasureAsioTimers { - using duration = typename Clock::duration; - using rep = typename MeasureClock::duration::rep; + using duration = Clock::duration; + using rep = MeasureClock::duration::rep; std::vector elapsedTimes; diff --git a/src/test/consensus/ByzantineFailureSim_test.cpp b/src/test/consensus/ByzantineFailureSim_test.cpp index ad75a78086..c3c51125b5 100644 --- a/src/test/consensus/ByzantineFailureSim_test.cpp +++ b/src/test/consensus/ByzantineFailureSim_test.cpp @@ -1,4 +1,3 @@ -#include #include #include #include diff --git a/src/test/consensus/Consensus_test.cpp b/src/test/consensus/Consensus_test.cpp index 629a97f0ea..92a4c67e32 100644 --- a/src/test/consensus/Consensus_test.cpp +++ b/src/test/consensus/Consensus_test.cpp @@ -1,4 +1,3 @@ -#include #include #include #include diff --git a/src/test/consensus/DistributedValidatorsSim_test.cpp b/src/test/consensus/DistributedValidatorsSim_test.cpp index 6d9ac6bede..1def09db13 100644 --- a/src/test/consensus/DistributedValidatorsSim_test.cpp +++ b/src/test/consensus/DistributedValidatorsSim_test.cpp @@ -1,4 +1,3 @@ -#include #include #include #include diff --git a/src/test/consensus/ScaleFreeSim_test.cpp b/src/test/consensus/ScaleFreeSim_test.cpp index 7e75aea72a..e533e09eb0 100644 --- a/src/test/consensus/ScaleFreeSim_test.cpp +++ b/src/test/consensus/ScaleFreeSim_test.cpp @@ -1,4 +1,3 @@ -#include #include #include #include diff --git a/src/test/csf/BasicNetwork.h b/src/test/csf/BasicNetwork.h index e1d519b30b..418cdcf289 100644 --- a/src/test/csf/BasicNetwork.h +++ b/src/test/csf/BasicNetwork.h @@ -64,9 +64,9 @@ class BasicNetwork using clock_type = Scheduler::clock_type; - using duration = typename clock_type::duration; + using duration = clock_type::duration; - using time_point = typename clock_type::time_point; + using time_point = clock_type::time_point; struct LinkType { diff --git a/src/test/csf/Digraph.h b/src/test/csf/Digraph.h index 6fbb8c3514..20e45faa5b 100644 --- a/src/test/csf/Digraph.h +++ b/src/test/csf/Digraph.h @@ -128,7 +128,7 @@ public: outVertices() const { return boost::adaptors::transform( - graph_, [](typename Graph::value_type const& v) { return v.first; }); + graph_, [](Graph::value_type const& v) { return v.first; }); } /** Range over target vertices @@ -139,7 +139,7 @@ public: [[nodiscard]] auto outVertices(Vertex source) const { - auto transform = [](typename Links::value_type const& link) { return link.first; }; + auto transform = [](Links::value_type const& link) { return link.first; }; auto it = graph_.find(source); if (it != graph_.end()) return boost::adaptors::transform(it->second, transform); @@ -165,7 +165,7 @@ public: [[nodiscard]] auto outEdges(Vertex source) const { - auto transform = [source](typename Links::value_type const& link) { + auto transform = [source](Links::value_type const& link) { return Edge{source, link.first, link.second}; }; diff --git a/src/test/csf/Scheduler.h b/src/test/csf/Scheduler.h index ede43be854..e7cbe27036 100644 --- a/src/test/csf/Scheduler.h +++ b/src/test/csf/Scheduler.h @@ -27,9 +27,9 @@ class Scheduler public: using clock_type = beast::ManualClock; - using duration = typename clock_type::duration; + using duration = clock_type::duration; - using time_point = typename clock_type::time_point; + using time_point = clock_type::time_point; private: using by_when_hook = @@ -87,14 +87,14 @@ private: class QueueType { private: - using by_when_set = typename boost::intrusive:: + using by_when_set = boost::intrusive:: make_multiset>::type; // alloc_ is owned by the scheduler boost::container::pmr::monotonic_buffer_resource* alloc_; by_when_set byWhen_; public: - using iterator = typename by_when_set::iterator; + using iterator = by_when_set::iterator; QueueType(QueueType const&) = delete; QueueType& @@ -114,7 +114,7 @@ private: end(); template - typename by_when_set::iterator + by_when_set::iterator emplace(time_point when, Handler&& h); iterator @@ -287,7 +287,7 @@ Scheduler::QueueType::end() -> iterator template inline auto -Scheduler::QueueType::emplace(time_point when, Handler&& h) -> typename by_when_set::iterator +Scheduler::QueueType::emplace(time_point when, Handler&& h) -> by_when_set::iterator { using event_type = EventImpl>; auto const p = alloc_->allocate(sizeof(event_type)); @@ -296,7 +296,7 @@ Scheduler::QueueType::emplace(time_point when, Handler&& h) -> typename by_when_ } inline auto -Scheduler::QueueType::erase(iterator iter) -> typename by_when_set::iterator +Scheduler::QueueType::erase(iterator iter) -> by_when_set::iterator { auto& e = *iter; auto next = byWhen_.erase(iter); @@ -309,7 +309,7 @@ Scheduler::QueueType::erase(iterator iter) -> typename by_when_set::iterator struct Scheduler::CancelToken { private: - typename QueueType::iterator iter_; + QueueType::iterator iter_; public: CancelToken() = delete; @@ -319,7 +319,7 @@ public: private: friend class Scheduler; - CancelToken(typename QueueType::iterator iter) : iter_(iter) + CancelToken(QueueType::iterator iter) : iter_(iter) { } }; diff --git a/src/test/csf/SimTime.h b/src/test/csf/SimTime.h index e38a375e02..125674f15d 100644 --- a/src/test/csf/SimTime.h +++ b/src/test/csf/SimTime.h @@ -11,7 +11,7 @@ using RealDuration = RealClock::duration; using RealTime = RealClock::time_point; using SimClock = beast::ManualClock; -using SimDuration = typename SimClock::duration; -using SimTime = typename SimClock::time_point; +using SimDuration = SimClock::duration; +using SimTime = SimClock::time_point; } // namespace xrpl::test::csf diff --git a/src/test/csf/random.h b/src/test/csf/random.h index 08002aed7f..30f98e37fe 100644 --- a/src/test/csf/random.h +++ b/src/test/csf/random.h @@ -72,14 +72,14 @@ public: Selector(RAIter first, RAIter last, std::vector const& w, Generator& g) : first_{first}, last_{last}, dd_{w.begin(), w.end()}, g_{g} { - using tag = typename std::iterator_traits::iterator_category; + using tag = std::iterator_traits::iterator_category; static_assert( std::is_same_v, "Selector only supports random access iterators."); // TODO: Allow for forward iterators } - typename std::iterator_traits::value_type + std::iterator_traits::value_type operator()() { auto idx = dd_(g_); diff --git a/src/test/jtx/TestHelpers.h b/src/test/jtx/TestHelpers.h index 27c54d830b..0e35e6b9ec 100644 --- a/src/test/jtx/TestHelpers.h +++ b/src/test/jtx/TestHelpers.h @@ -27,6 +27,7 @@ namespace xrpl::test::jtx { */ template < class SField, + // NOLINTNEXTLINE(readability-redundant-typename): typename required by MSVC class StoredValue = typename SField::type::value_type, class OutputValue = StoredValue> struct JTxField @@ -213,8 +214,8 @@ template struct JTxFieldWrapper { using JF = JTxField; - using SF = typename JF::SF; - using SV = typename JF::SV; + using SF = JF::SF; + using SV = JF::SV; protected: SF const& sfield_; @@ -266,9 +267,11 @@ public: } }; +// NOLINTNEXTLINE(readability-redundant-typename): typename required by MSVC template using valueUnitWrapper = JTxFieldWrapper>; +// NOLINTNEXTLINE(readability-redundant-typename): typename required by MSVC template using simpleField = JTxFieldWrapper>; diff --git a/src/test/nodestore/Timing_test.cpp b/src/test/nodestore/Timing_test.cpp index f5e6bf8aa4..e308d0d5ff 100644 --- a/src/test/nodestore/Timing_test.cpp +++ b/src/test/nodestore/Timing_test.cpp @@ -57,7 +57,7 @@ template static void rngcpy(void* buffer, std::size_t bytes, Generator& g) { - using result_type = typename Generator::result_type; + using result_type = Generator::result_type; while (bytes >= sizeof(result_type)) { auto const v = g(); diff --git a/src/test/server/Server_test.cpp b/src/test/server/Server_test.cpp index 54091ef767..9ff0015b5d 100644 --- a/src/test/server/Server_test.cpp +++ b/src/test/server/Server_test.cpp @@ -169,7 +169,7 @@ public: // Connect to an address template bool - connect(Socket& s, typename Socket::endpoint_type const& ep) + connect(Socket& s, Socket::endpoint_type const& ep) { try { diff --git a/src/test/unit_test/multi_runner.cpp b/src/test/unit_test/multi_runner.cpp index 3a56d22654..71208313a4 100644 --- a/src/test/unit_test/multi_runner.cpp +++ b/src/test/unit_test/multi_runner.cpp @@ -69,9 +69,7 @@ Results::add(SuiteResults const& r) top.begin(), top.end(), elapsed, - [](run_time const& t1, typename clock_type::duration const& t2) { - return t1.second > t2; - }); + [](run_time const& t1, clock_type::duration const& t2) { return t1.second > t2; }); if (iter != top.end()) { diff --git a/src/test/unit_test/multi_runner.h b/src/test/unit_test/multi_runner.h index 8b07559e8c..55cf6c25fa 100644 --- a/src/test/unit_test/multi_runner.h +++ b/src/test/unit_test/multi_runner.h @@ -42,7 +42,7 @@ struct SuiteResults std::size_t cases = 0; std::size_t total = 0; std::size_t failed = 0; - typename clock_type::time_point start = clock_type::now(); + clock_type::time_point start = clock_type::now(); explicit SuiteResults(std::string name = "") : name(std::move(name)) { @@ -57,7 +57,7 @@ struct Results using static_string = boost::beast::static_string<256>; // results may be stored in shared memory. Use `static_string` to ensure // pointers from different memory spaces do not co-mingle - using run_time = std::pair; + using run_time = std::pair; static constexpr auto kMaxTop = 10; @@ -66,7 +66,7 @@ struct Results std::size_t total = 0; std::size_t failed = 0; boost::container::static_vector top; - typename clock_type::time_point start = clock_type::now(); + clock_type::time_point start = clock_type::now(); void add(SuiteResults const& r); diff --git a/src/xrpld/app/consensus/RCLConsensus.cpp b/src/xrpld/app/consensus/RCLConsensus.cpp index a474c9c339..a7c2b26c04 100644 --- a/src/xrpld/app/consensus/RCLConsensus.cpp +++ b/src/xrpld/app/consensus/RCLConsensus.cpp @@ -580,7 +580,9 @@ RCLConsensus::Adaptor::doAccept( JLOG(j_.info()) << "CNF Val " << newLCLHash; } else + { JLOG(j_.info()) << "CNF buildLCL " << newLCLHash; + } // See if we can accept a ledger as fully-validated ledgerMaster_.consensusBuilt(built.ledger, result.txns.id(), std::move(consensusJson)); @@ -796,7 +798,9 @@ RCLConsensus::Adaptor::buildLCL( JLOG(j_.debug()) << "Consensus built ledger we were acquiring"; } else + { JLOG(j_.debug()) << "Consensus built new ledger"; + } return RCLCxLedger{std::move(built)}; } diff --git a/src/xrpld/app/consensus/RCLValidations.cpp b/src/xrpld/app/consensus/RCLValidations.cpp index 02ff86b23d..9d40e60b00 100644 --- a/src/xrpld/app/consensus/RCLValidations.cpp +++ b/src/xrpld/app/consensus/RCLValidations.cpp @@ -46,8 +46,10 @@ RCLValidatedLedger::RCLValidatedLedger( ancestors_ = hashIndex->getFieldV256(sfHashes).value(); } else + { JLOG(j_.warn()) << "Ledger " << ledgerSeq_ << ":" << ledgerID_ << " missing recent ancestor hashes"; + } } auto diff --git a/src/xrpld/app/ledger/LedgerHistory.cpp b/src/xrpld/app/ledger/LedgerHistory.cpp index 8520fc941f..b7e1772942 100644 --- a/src/xrpld/app/ledger/LedgerHistory.cpp +++ b/src/xrpld/app/ledger/LedgerHistory.cpp @@ -368,8 +368,10 @@ LedgerHistory::handleMismatch( << " validated: " << to_string(*validatedConsensusHash); } else + { JLOG(j_.error()) << "MISMATCH with same consensus transaction set: " << to_string(*builtConsensusHash); + } } // Find differences between built and valid ledgers @@ -381,8 +383,10 @@ LedgerHistory::handleMismatch( JLOG(j_.error()) << "MISMATCH with same " << builtTx.size() << " transactions"; } else + { JLOG(j_.error()) << "MISMATCH with " << builtTx.size() << " built and " << validTx.size() << " valid transactions."; + } JLOG(j_.error()) << "built\n" << getJson({*builtLedger, {}}); JLOG(j_.error()) << "valid\n" << getJson({*validLedger, {}}); diff --git a/src/xrpld/app/ledger/detail/BuildLedger.cpp b/src/xrpld/app/ledger/detail/BuildLedger.cpp index a77c1c9c50..2a4121ede9 100644 --- a/src/xrpld/app/ledger/detail/BuildLedger.cpp +++ b/src/xrpld/app/ledger/detail/BuildLedger.cpp @@ -204,9 +204,11 @@ buildLedger( << accum.txCount(); } else + { JLOG(j.debug()) << "Applied " << applied << " transactions. " << "Total transactions in ledger (including Inner Batch): " << accum.txCount(); + } }); } diff --git a/src/xrpld/app/ledger/detail/LedgerCleaner.cpp b/src/xrpld/app/ledger/detail/LedgerCleaner.cpp index 9f2db9d2f2..b96f01e577 100644 --- a/src/xrpld/app/ledger/detail/LedgerCleaner.cpp +++ b/src/xrpld/app/ledger/detail/LedgerCleaner.cpp @@ -366,7 +366,9 @@ private: } } else + { JLOG(j_.warn()) << "Validated ledger is prior to target ledger"; + } return ledgerHash; } diff --git a/src/xrpld/app/ledger/detail/LedgerMaster.cpp b/src/xrpld/app/ledger/detail/LedgerMaster.cpp index 9baad0ec90..31510b44d2 100644 --- a/src/xrpld/app/ledger/detail/LedgerMaster.cpp +++ b/src/xrpld/app/ledger/detail/LedgerMaster.cpp @@ -755,7 +755,9 @@ LedgerMaster::getFetchPack(LedgerIndex missing, InboundLedger::Reason reason) JLOG(journal_.trace()) << "Requested fetch pack for " << missing; } else + { JLOG(journal_.debug()) << "No peer for fetch pack"; + } } void @@ -1797,10 +1799,14 @@ LedgerMaster::fetchForHistory( getFetchPack(missing, reason); } else + { JLOG(journal_.trace()) << "fetchForHistory no fetch pack for " << missing; + } } else + { JLOG(journal_.debug()) << "fetchForHistory found failed acquire"; + } } if (ledger) { diff --git a/src/xrpld/app/main/Application.cpp b/src/xrpld/app/main/Application.cpp index 67b5e30eb7..c2fb78ce79 100644 --- a/src/xrpld/app/main/Application.cpp +++ b/src/xrpld/app/main/Application.cpp @@ -1612,7 +1612,9 @@ ApplicationImp::signalStop(std::string const& msg) JLOG(journal_.warn()) << "Server stopping"; } else + { JLOG(journal_.warn()) << "Server stopping: " << msg; + } isTimeToStop.notify_all(); } diff --git a/src/xrpld/app/misc/NetworkOPs.cpp b/src/xrpld/app/misc/NetworkOPs.cpp index d807dea10a..1802441a66 100644 --- a/src/xrpld/app/misc/NetworkOPs.cpp +++ b/src/xrpld/app/misc/NetworkOPs.cpp @@ -1687,11 +1687,13 @@ NetworkOPsImp::apply(std::unique_lock& batchLock) e.transaction->setKept(); } else + { JLOG(journal_.debug()) << "Not holding transaction " << e.transaction->getID() << ": " << (e.local ? "local" : "network") << ", " << "result: " << e.result << " ledgers left: " << (ledgersLeft ? to_string(*ledgersLeft) : "unspecified"); + } } } else diff --git a/src/xrpld/app/misc/detail/Transaction.cpp b/src/xrpld/app/misc/detail/Transaction.cpp index 425a5723fb..2c55c474eb 100644 --- a/src/xrpld/app/misc/detail/Transaction.cpp +++ b/src/xrpld/app/misc/detail/Transaction.cpp @@ -73,7 +73,7 @@ Transaction::setStatus( TransStatus Transaction::sqlTransactionStatus(boost::optional const& status) { - auto const c = (status) ? safeCast((*status)[0]) : TxnSql::Unknown; + auto const c = status ? safeCast((*status)[0]) : TxnSql::Unknown; switch (static_cast(c)) { diff --git a/src/xrpld/app/misc/detail/ValidatorSite.cpp b/src/xrpld/app/misc/detail/ValidatorSite.cpp index 76fd078174..6ce3711652 100644 --- a/src/xrpld/app/misc/detail/ValidatorSite.cpp +++ b/src/xrpld/app/misc/detail/ValidatorSite.cpp @@ -339,8 +339,10 @@ ValidatorSite::onRequestTimeout(std::size_t siteIdx, error_code const& ec) JLOG(j_.warn()) << "Request for " << site.activeResource->uri << " took too long"; } else + { JLOG(j_.error()) << "Request took too long, but a response has " "already been processed"; + } } std::scoped_lock const lockState{stateMutex_}; diff --git a/src/xrpld/app/rdb/backend/detail/Node.cpp b/src/xrpld/app/rdb/backend/detail/Node.cpp index 9b7db6f0f5..dcc146397b 100644 --- a/src/xrpld/app/rdb/backend/detail/Node.cpp +++ b/src/xrpld/app/rdb/backend/detail/Node.cpp @@ -125,7 +125,7 @@ makeLedgerDBs( std::size_t notnull = 0, dfltValue = 0, pk = 0; soci::indicator ind = soci::i_null; soci::statement st = - (tx->getSession().prepare << ("PRAGMA table_info(AccountTransactions);"), + (tx->getSession().prepare << "PRAGMA table_info(AccountTransactions);", soci::into(cid), soci::into(name), soci::into(type), @@ -1065,10 +1065,10 @@ accountTxPage( if (findLedger == 0) { sql = boost::str( - boost::format(kPrefix + (R"(AccountTransactions.LedgerSeq BETWEEN %u AND %u + boost::format(kPrefix + R"(AccountTransactions.LedgerSeq BETWEEN %u AND %u ORDER BY AccountTransactions.LedgerSeq %s, AccountTransactions.TxnSeq %s - LIMIT %u;)")) % + LIMIT %u;)") % toBase58(options.account) % options.ledgerRange.min % options.ledgerRange.max % order % order % queryLimit); } @@ -1080,7 +1080,7 @@ accountTxPage( auto b58acct = toBase58(options.account); sql = boost::str( - boost::format(( + boost::format( R"(SELECT AccountTransactions.LedgerSeq,AccountTransactions.TxnSeq, Status,RawTxn,TxnMeta FROM AccountTransactions, Transactions WHERE @@ -1097,7 +1097,7 @@ accountTxPage( ORDER BY AccountTransactions.LedgerSeq %s, AccountTransactions.TxnSeq %s LIMIT %u; - )")) % + )") % b58acct % minLedger % maxLedger % b58acct % findLedger % compare % findSeq % order % order % queryLimit); } diff --git a/src/xrpld/consensus/Consensus.h b/src/xrpld/consensus/Consensus.h index 131db30ce0..b8d04e18b5 100644 --- a/src/xrpld/consensus/Consensus.h +++ b/src/xrpld/consensus/Consensus.h @@ -276,11 +276,11 @@ checkConsensus( template class Consensus { - using Ledger_t = typename Adaptor::Ledger_t; - using TxSet_t = typename Adaptor::TxSet_t; - using NodeID_t = typename Adaptor::NodeID_t; - using Tx_t = typename TxSet_t::Tx; - using PeerPosition_t = typename Adaptor::PeerPosition_t; + using Ledger_t = Adaptor::Ledger_t; + using TxSet_t = Adaptor::TxSet_t; + using NodeID_t = Adaptor::NodeID_t; + using Tx_t = TxSet_t::Tx; + using PeerPosition_t = Adaptor::PeerPosition_t; using Proposal_t = ConsensusProposal; using Result = ConsensusResult; @@ -341,7 +341,7 @@ public: void startRound( NetClock::time_point const& now, - typename Ledger_t::ID const& prevLedgerID, + Ledger_t::ID const& prevLedgerID, Ledger_t prevLedger, hash_set const& nowUntrusted, bool proposing, @@ -402,7 +402,7 @@ public: @return ID of previous ledger */ - typename Ledger_t::ID + Ledger_t::ID prevLedgerID() const { return prevLedgerID_; @@ -428,16 +428,14 @@ private: void startRoundInternal( NetClock::time_point const& now, - typename Ledger_t::ID const& prevLedgerID, + Ledger_t::ID const& prevLedgerID, Ledger_t const& prevLedger, ConsensusMode mode, std::unique_ptr const& clog); // Change our view of the previous ledger void - handleWrongLedger( - typename Ledger_t::ID const& lgrId, - std::unique_ptr const& clog); + handleWrongLedger(Ledger_t::ID const& lgrId, std::unique_ptr const& clog); /** Check if our previous ledger matches the network's. @@ -568,7 +566,7 @@ private: // Non-peer (self) consensus data // Last validated ledger ID provided to consensus - typename Ledger_t::ID prevLedgerID_; + Ledger_t::ID prevLedgerID_; // Last validated ledger seen by consensus Ledger_t previousLedger_; @@ -616,7 +614,7 @@ template void Consensus::startRound( NetClock::time_point const& now, - typename Ledger_t::ID const& prevLedgerID, + Ledger_t::ID const& prevLedgerID, Ledger_t prevLedger, hash_set const& nowUntrusted, bool proposing, @@ -661,7 +659,7 @@ template void Consensus::startRoundInternal( NetClock::time_point const& now, - typename Ledger_t::ID const& prevLedgerID, + Ledger_t::ID const& prevLedgerID, Ledger_t const& prevLedger, ConsensusMode mode, std::unique_ptr const& clog) @@ -811,7 +809,9 @@ Consensus::peerProposalInternal( gotTxSet(now_, *set); } else + { JLOG(j_.debug()) << "Don't have tx set for peer"; + } } else if (result_) { @@ -1025,7 +1025,7 @@ Consensus::getJson(bool full) const template void Consensus::handleWrongLedger( - typename Ledger_t::ID const& lgrId, + Ledger_t::ID const& lgrId, std::unique_ptr const& clog) { CLOG(clog) << "handleWrongLedger. "; diff --git a/src/xrpld/consensus/ConsensusTypes.h b/src/xrpld/consensus/ConsensusTypes.h index 64a7f5fdea..f043fc0663 100644 --- a/src/xrpld/consensus/ConsensusTypes.h +++ b/src/xrpld/consensus/ConsensusTypes.h @@ -183,11 +183,11 @@ enum class ConsensusState { template struct ConsensusResult { - using Ledger_t = typename Traits::Ledger_t; - using TxSet_t = typename Traits::TxSet_t; - using NodeID_t = typename Traits::NodeID_t; + using Ledger_t = Traits::Ledger_t; + using TxSet_t = Traits::TxSet_t; + using NodeID_t = Traits::NodeID_t; - using Tx_t = typename TxSet_t::Tx; + using Tx_t = TxSet_t::Tx; using Proposal_t = ConsensusProposal; using Dispute_t = DisputedTx; diff --git a/src/xrpld/consensus/DisputedTx.h b/src/xrpld/consensus/DisputedTx.h index 1c0c069f54..ba8329714b 100644 --- a/src/xrpld/consensus/DisputedTx.h +++ b/src/xrpld/consensus/DisputedTx.h @@ -29,7 +29,7 @@ namespace xrpl { template class DisputedTx { - using TxID_t = typename Tx::ID; + using TxID_t = Tx::ID; using Map_t = boost::container::flat_map; public: diff --git a/src/xrpld/consensus/LedgerTrie.h b/src/xrpld/consensus/LedgerTrie.h index 9d76c7f283..cd9662ff02 100644 --- a/src/xrpld/consensus/LedgerTrie.h +++ b/src/xrpld/consensus/LedgerTrie.h @@ -21,8 +21,8 @@ template class SpanTip { public: - using Seq = typename Ledger::Seq; - using ID = typename Ledger::ID; + using Seq = Ledger::Seq; + using ID = Ledger::ID; SpanTip(Seq s, ID i, Ledger const lgr) : seq{s}, id{i}, ledger_{std::move(lgr)} { @@ -58,8 +58,8 @@ namespace ledger_trie_detail { template class Span { - using Seq = typename Ledger::Seq; - using ID = typename Ledger::ID; + using Seq = Ledger::Seq; + using ID = Ledger::ID; // The span is the half-open interval [start,end) of ledger_ Seq start_{0}; @@ -323,8 +323,8 @@ struct Node template class LedgerTrie { - using Seq = typename Ledger::Seq; - using ID = typename Ledger::ID; + using Seq = Ledger::Seq; + using ID = Ledger::ID; using Node = ledger_trie_detail::Node; using Span = ledger_trie_detail::Span; diff --git a/src/xrpld/consensus/Validations.h b/src/xrpld/consensus/Validations.h index 2f5762ce83..f109ae620b 100644 --- a/src/xrpld/consensus/Validations.h +++ b/src/xrpld/consensus/Validations.h @@ -267,13 +267,13 @@ to_string(ValStatus m) template class Validations { - using Mutex = typename Adaptor::Mutex; - using Validation = typename Adaptor::Validation; - using Ledger = typename Adaptor::Ledger; - using ID = typename Ledger::ID; - using Seq = typename Ledger::Seq; - using NodeID = typename Validation::NodeID; - using NodeKey = typename Validation::NodeKey; + using Mutex = Adaptor::Mutex; + using Validation = Adaptor::Validation; + using Ledger = Adaptor::Ledger; + using ID = Ledger::ID; + using Seq = Ledger::Seq; + using NodeID = Validation::NodeID; + using NodeKey = Validation::NodeKey; using WrappedValidationType = std::decay_t>; diff --git a/src/xrpld/overlay/Slot.h b/src/xrpld/overlay/Slot.h index 0600265500..7490798787 100644 --- a/src/xrpld/overlay/Slot.h +++ b/src/xrpld/overlay/Slot.h @@ -82,7 +82,7 @@ class Slot final private: friend class Slots; using id_t = Peer::id_t; - using time_point = typename ClockType::time_point; + using time_point = ClockType::time_point; // a callback to report ignored squelches using ignored_squelch_callback = std::function; @@ -217,7 +217,7 @@ private: std::uint16_t reachedThreshold_{0}; // last time peers were selected, used to age the slot - typename ClockType::time_point lastSelected_; + ClockType::time_point lastSelected_; SlotState state_{SlotState::Counting}; // slot's state SquelchHandler const& handler_; // squelch/unsquelch handler @@ -483,7 +483,7 @@ Slot::notInState(PeerState state) const } template -std::set +std::set Slot::getSelected() const { std::set r; @@ -496,7 +496,7 @@ Slot::getSelected() const } template -std::unordered_map> +std::unordered_map> Slot::getPeers() const { using namespace std::chrono; @@ -526,8 +526,8 @@ Slot::getPeers() const template class Slots final { - using time_point = typename ClockType::time_point; - using id_t = typename Peer::id_t; + using time_point = ClockType::time_point; + using id_t = Peer::id_t; using messages = beast::aged_unordered_map< uint256, std::unordered_set, @@ -600,7 +600,7 @@ public: PublicKey const& validator, id_t id, protocol::MessageType type, - typename Slot::ignored_squelch_callback callback); + Slot::ignored_squelch_callback callback); /** Check if peers stopped relaying messages * and if slots stopped receiving messages from the validator. @@ -651,9 +651,8 @@ public: /** Get peers info. Return map of peer's state, count, and squelch * expiration milliseconds. */ - std:: - unordered_map> - getPeers(PublicKey const& validator) + std::unordered_map> + getPeers(PublicKey const& validator) { auto const& it = slots_.find(validator); if (it != slots_.end()) @@ -742,7 +741,7 @@ Slots::updateSlotAndSquelch( PublicKey const& validator, id_t id, protocol::MessageType type, - typename Slot::ignored_squelch_callback callback) + Slot::ignored_squelch_callback callback) { if (!addPeerMessage(key, id)) return; diff --git a/src/xrpld/overlay/Squelch.h b/src/xrpld/overlay/Squelch.h index b509f293c2..96d8c26f1d 100644 --- a/src/xrpld/overlay/Squelch.h +++ b/src/xrpld/overlay/Squelch.h @@ -14,7 +14,7 @@ namespace xrpl::reduce_relay { template class Squelch { - using time_point = typename ClockType::time_point; + using time_point = ClockType::time_point; public: explicit Squelch(beast::Journal journal) : journal_(journal) diff --git a/src/xrpld/overlay/detail/PeerImp.h b/src/xrpld/overlay/detail/PeerImp.h index 26d7e0a832..28fb6b33a4 100644 --- a/src/xrpld/overlay/detail/PeerImp.h +++ b/src/xrpld/overlay/detail/PeerImp.h @@ -293,7 +293,7 @@ public: /** Send a set of PeerFinder endpoints as a protocol message. */ template < class FwdIt, - class = typename std::enable_if_t< + class = std::enable_if_t< std::is_same_v::value_type, PeerFinder::Endpoint>>> void sendEndpoints(FwdIt first, FwdIt last); diff --git a/src/xrpld/overlay/detail/ZeroCopyStream.h b/src/xrpld/overlay/detail/ZeroCopyStream.h index d8d311105d..f77f266321 100644 --- a/src/xrpld/overlay/detail/ZeroCopyStream.h +++ b/src/xrpld/overlay/detail/ZeroCopyStream.h @@ -17,7 +17,7 @@ template class ZeroCopyInputStream : public ::google::protobuf::io::ZeroCopyInputStream { private: - using iterator = typename Buffers::const_iterator; + using iterator = Buffers::const_iterator; using const_buffer = boost::asio::const_buffer; google::protobuf::int64 count_ = 0; @@ -110,8 +110,8 @@ template class ZeroCopyOutputStream : public ::google::protobuf::io::ZeroCopyOutputStream { private: - using buffers_type = typename Streambuf::mutable_buffers_type; - using iterator = typename buffers_type::const_iterator; + using buffers_type = Streambuf::mutable_buffers_type; + using iterator = buffers_type::const_iterator; using mutable_buffer = boost::asio::mutable_buffer; Streambuf& streambuf_; diff --git a/src/xrpld/peerfinder/detail/Checker.h b/src/xrpld/peerfinder/detail/Checker.h index 8ab084830c..2f324bf8b6 100644 --- a/src/xrpld/peerfinder/detail/Checker.h +++ b/src/xrpld/peerfinder/detail/Checker.h @@ -34,8 +34,8 @@ private: template struct AsyncOp : BasicAsyncOp { - using socket_type = typename Protocol::socket; - using endpoint_type = typename Protocol::endpoint; + using socket_type = Protocol::socket; + using endpoint_type = Protocol::endpoint; Checker& checker; socket_type socket; @@ -57,8 +57,8 @@ private: //-------------------------------------------------------------------------- - using list_type = typename boost::intrusive:: - make_list>::type; + using list_type = + boost::intrusive::make_list>::type; std::mutex mutex_; std::condition_variable cond_; diff --git a/src/xrpld/peerfinder/detail/Livecache.h b/src/xrpld/peerfinder/detail/Livecache.h index 84efcb7bd1..c5f04be90a 100644 --- a/src/xrpld/peerfinder/detail/Livecache.h +++ b/src/xrpld/peerfinder/detail/Livecache.h @@ -65,12 +65,12 @@ public: }; public: - using iterator = boost::transform_iterator; + using iterator = boost::transform_iterator; using const_iterator = iterator; using reverse_iterator = - boost::transform_iterator; + boost::transform_iterator; using const_reverse_iterator = reverse_iterator; @@ -132,7 +132,7 @@ public: } private: - explicit Hop(typename beast::MaybeConst::type& list) : list_(list) + explicit Hop(beast::MaybeConst::type& list) : list_(list) { } @@ -145,7 +145,7 @@ protected: // Work-around to call Hop's private constructor from Livecache template static Hop - makeHop(typename beast::MaybeConst::type& list) + makeHop(beast::MaybeConst::type& list) { return Hop(list); } @@ -208,30 +208,29 @@ public: template struct Transform { - using first_argument = typename lists_type::value_type; + using first_argument = lists_type::value_type; using result_type = Hop; explicit Transform() = default; Hop - operator()(typename beast::MaybeConst::type& - list) const + operator()(beast::MaybeConst::type& list) const { return makeHop(list); } }; public: - using iterator = boost::transform_iterator, typename lists_type::iterator>; + using iterator = boost::transform_iterator, lists_type::iterator>; using const_iterator = - boost::transform_iterator, typename lists_type::const_iterator>; + boost::transform_iterator, lists_type::const_iterator>; using reverse_iterator = - boost::transform_iterator, typename lists_type::reverse_iterator>; + boost::transform_iterator, lists_type::reverse_iterator>; using const_reverse_iterator = - boost::transform_iterator, typename lists_type::const_reverse_iterator>; + boost::transform_iterator, lists_type::const_reverse_iterator>; iterator begin() @@ -338,7 +337,7 @@ public: } /** Returns the number of entries in the cache. */ - typename cache_type::size_type + cache_type::size_type size() const { return cache_.size(); diff --git a/src/xrpld/peerfinder/detail/Logic.h b/src/xrpld/peerfinder/detail/Logic.h index 815858cf00..55cce506ba 100644 --- a/src/xrpld/peerfinder/detail/Logic.h +++ b/src/xrpld/peerfinder/detail/Logic.h @@ -980,7 +980,7 @@ public: /** Adds eligible Fixed addresses for outbound attempts. */ template void - getFixed(std::size_t needed, Container& c, typename ConnectHandouts::Squelches& squelches) + getFixed(std::size_t needed, Container& c, ConnectHandouts::Squelches& squelches) { auto const now(clock.now()); for (auto iter = fixed_.begin(); needed && iter != fixed_.end(); ++iter) diff --git a/src/xrpld/rpc/detail/RPCCall.cpp b/src/xrpld/rpc/detail/RPCCall.cpp index f405ffe4de..123b9fc7a5 100644 --- a/src/xrpld/rpc/detail/RPCCall.cpp +++ b/src/xrpld/rpc/detail/RPCCall.cpp @@ -1589,7 +1589,7 @@ struct RPCCallImp jvResult["result"] = jvReply; - (callbackFuncP)(jvResult); + callbackFuncP(jvResult); } return false; diff --git a/src/xrpld/rpc/handlers/admin/keygen/WalletPropose.cpp b/src/xrpld/rpc/handlers/admin/keygen/WalletPropose.cpp index c783319049..4b5f1821e3 100644 --- a/src/xrpld/rpc/handlers/admin/keygen/WalletPropose.cpp +++ b/src/xrpld/rpc/handlers/admin/keygen/WalletPropose.cpp @@ -39,7 +39,7 @@ estimateEntropy(std::string const& input) { (void)_; auto x = f / input.length(); - se += (x)*log2(x); + se += x * log2(x); } // We multiply it by the length, to get an estimate of diff --git a/src/xrpld/rpc/json_body.h b/src/xrpld/rpc/json_body.h index 4520a8bf39..5c4c56cef8 100644 --- a/src/xrpld/rpc/json_body.h +++ b/src/xrpld/rpc/json_body.h @@ -22,7 +22,7 @@ struct JsonBody dynamic_buffer_type buffer_; public: - using const_buffers_type = typename dynamic_buffer_type::const_buffers_type; + using const_buffers_type = dynamic_buffer_type::const_buffers_type; using is_deferred = std::false_type;