From 6f35d94b2f20c760aea4d00991ab94e53b34348a Mon Sep 17 00:00:00 2001 From: Ed Hennis Date: Thu, 19 Feb 2026 21:58:47 -0400 Subject: [PATCH 01/41] Fix tautological assertion (#6393) --- include/xrpl/beast/container/aged_multiset.h | 1 - include/xrpl/beast/container/aged_unordered_map.h | 1 - include/xrpl/beast/container/aged_unordered_multimap.h | 1 - include/xrpl/beast/container/aged_unordered_set.h | 1 - src/libxrpl/protocol/Keylet.cpp | 2 +- 5 files changed, 1 insertion(+), 5 deletions(-) diff --git a/include/xrpl/beast/container/aged_multiset.h b/include/xrpl/beast/container/aged_multiset.h index f55e7b34c1..8d906c694f 100644 --- a/include/xrpl/beast/container/aged_multiset.h +++ b/include/xrpl/beast/container/aged_multiset.h @@ -15,5 +15,4 @@ template < class Allocator = std::allocator> using aged_multiset = detail::aged_ordered_container; - } diff --git a/include/xrpl/beast/container/aged_unordered_map.h b/include/xrpl/beast/container/aged_unordered_map.h index 585fc6e5a4..520ffe5848 100644 --- a/include/xrpl/beast/container/aged_unordered_map.h +++ b/include/xrpl/beast/container/aged_unordered_map.h @@ -17,5 +17,4 @@ template < class Allocator = std::allocator>> using aged_unordered_map = detail::aged_unordered_container; - } diff --git a/include/xrpl/beast/container/aged_unordered_multimap.h b/include/xrpl/beast/container/aged_unordered_multimap.h index f2c31b4370..dc6338949b 100644 --- a/include/xrpl/beast/container/aged_unordered_multimap.h +++ b/include/xrpl/beast/container/aged_unordered_multimap.h @@ -17,5 +17,4 @@ template < class Allocator = std::allocator>> using aged_unordered_multimap = detail::aged_unordered_container; - } diff --git a/include/xrpl/beast/container/aged_unordered_set.h b/include/xrpl/beast/container/aged_unordered_set.h index e5879e5919..a1c032e159 100644 --- a/include/xrpl/beast/container/aged_unordered_set.h +++ b/include/xrpl/beast/container/aged_unordered_set.h @@ -16,5 +16,4 @@ template < class Allocator = std::allocator> using aged_unordered_set = detail::aged_unordered_container; - } diff --git a/src/libxrpl/protocol/Keylet.cpp b/src/libxrpl/protocol/Keylet.cpp index 2c65bb9ed4..6f9656c4ea 100644 --- a/src/libxrpl/protocol/Keylet.cpp +++ b/src/libxrpl/protocol/Keylet.cpp @@ -9,7 +9,7 @@ bool Keylet::check(STLedgerEntry const& sle) const { XRPL_ASSERT( - sle.getType() != ltANY || sle.getType() != ltCHILD, + sle.getType() != ltANY && sle.getType() != ltCHILD, "xrpl::Keylet::check : valid input type"); if (type == ltANY) From d03d72bfd57a76b4f4ec9259a851ef52271618cf Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Fri, 20 Feb 2026 09:19:00 +0000 Subject: [PATCH 02/41] ci: Add dependabot config (#6379) --- .github/dependabot.yml | 56 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..66e319e0e7 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,56 @@ +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + day: monday + time: "04:00" + timezone: Etc/GMT + commit-message: + prefix: "ci: [DEPENDABOT] " + target-branch: develop + + - package-ecosystem: github-actions + directory: .github/actions/build-deps/ + schedule: + interval: weekly + day: monday + time: "04:00" + timezone: Etc/GMT + commit-message: + prefix: "ci: [DEPENDABOT] " + target-branch: develop + + - package-ecosystem: github-actions + directory: .github/actions/generate-version/ + schedule: + interval: weekly + day: monday + time: "04:00" + timezone: Etc/GMT + commit-message: + prefix: "ci: [DEPENDABOT] " + target-branch: develop + + - package-ecosystem: github-actions + directory: .github/actions/print-env/ + schedule: + interval: weekly + day: monday + time: "04:00" + timezone: Etc/GMT + commit-message: + prefix: "ci: [DEPENDABOT] " + target-branch: develop + + - package-ecosystem: github-actions + directory: .github/actions/setup-conan/ + schedule: + interval: weekly + day: monday + time: "04:00" + timezone: Etc/GMT + commit-message: + prefix: "ci: [DEPENDABOT] " + target-branch: develop From cb54adefedb886f5c7d89b97d52aa9d4220d4a1d Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Fri, 20 Feb 2026 18:41:43 +0000 Subject: [PATCH 03/41] ci: Build docs in PRs and in private repos (#6400) --- .github/workflows/publish-docs.yml | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish-docs.yml b/.github/workflows/publish-docs.yml index f61559d6d3..121026d13c 100644 --- a/.github/workflows/publish-docs.yml +++ b/.github/workflows/publish-docs.yml @@ -4,6 +4,18 @@ name: Build and publish documentation on: push: + branches: + - "develop" + - "release*" + paths: + - ".github/workflows/publish-docs.yml" + - "*.md" + - "**/*.md" + - "docs/**" + - "include/**" + - "src/libxrpl/**" + - "src/xrpld/**" + pull_request: paths: - ".github/workflows/publish-docs.yml" - "*.md" @@ -23,7 +35,9 @@ defaults: env: BUILD_DIR: build - NPROC_SUBTRACT: 2 + # ubuntu-latest has only 2 CPUs for private repositories + # https://docs.github.com/en/actions/reference/runners/github-hosted-runners#standard-github-hosted-runners-for--private-repositories + NPROC_SUBTRACT: ${{ github.event.repository.private && '1' || '2' }} jobs: publish: @@ -65,7 +79,7 @@ jobs: cmake --build . --target docs --parallel ${BUILD_NPROC} - name: Publish documentation - if: ${{ github.ref_type == 'branch' && github.ref_name == github.event.repository.default_branch }} + if: ${{ github.event_name == 'push' }} uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e # v4.0.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} From 0bc4a0cfe862096b1b6aeac3c32cc308ca9490c3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 20 Feb 2026 19:11:26 +0000 Subject: [PATCH 04/41] ci: [DEPENDABOT] bump codecov/codecov-action from 5.4.3 to 5.5.2 (#6398) Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 5.4.3 to 5.5.2. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/18283e04ce6e62d37312384ff67231eb8fd56d24...671740ac38dd9b0130fbe1cec585b89eea48d3de) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-version: 5.5.2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/reusable-build-test-config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/reusable-build-test-config.yml b/.github/workflows/reusable-build-test-config.yml index 85b973ea0c..9978ea4122 100644 --- a/.github/workflows/reusable-build-test-config.yml +++ b/.github/workflows/reusable-build-test-config.yml @@ -254,7 +254,7 @@ jobs: - name: Upload coverage report if: ${{ github.repository_owner == 'XRPLF' && !inputs.build_only && env.COVERAGE_ENABLED == 'true' }} - uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3 + uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2 with: disable_search: true disable_telem: true From 4bcbc6e50f08439fc595ee7a006cb7a2626f7d02 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 20 Feb 2026 19:59:37 +0000 Subject: [PATCH 05/41] ci: [DEPENDABOT] bump tj-actions/changed-files from 46.0.5 to 47.0.4 (#6394) Bumps [tj-actions/changed-files](https://github.com/tj-actions/changed-files) from 46.0.5 to 47.0.4. - [Release notes](https://github.com/tj-actions/changed-files/releases) - [Changelog](https://github.com/tj-actions/changed-files/blob/main/HISTORY.md) - [Commits](https://github.com/tj-actions/changed-files/compare/ed68ef82c095e0d48ec87eccea555d944a631a4c...7dee1b0c1557f278e5c7dc244927139d78c0e22a) --- updated-dependencies: - dependency-name: tj-actions/changed-files dependency-version: 47.0.4 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/on-pr.yml | 2 +- .github/workflows/reusable-clang-tidy.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/on-pr.yml b/.github/workflows/on-pr.yml index b0710303a0..545e65e4eb 100644 --- a/.github/workflows/on-pr.yml +++ b/.github/workflows/on-pr.yml @@ -46,7 +46,7 @@ jobs: # that Github considers any skipped jobs to have passed, and in # turn the required checks as well. id: changes - uses: tj-actions/changed-files@ed68ef82c095e0d48ec87eccea555d944a631a4c # v46.0.5 + uses: tj-actions/changed-files@7dee1b0c1557f278e5c7dc244927139d78c0e22a # v47.0.4 with: files: | # These paths are unique to `on-pr.yml`. diff --git a/.github/workflows/reusable-clang-tidy.yml b/.github/workflows/reusable-clang-tidy.yml index f2f1aeee78..7c300ee26e 100644 --- a/.github/workflows/reusable-clang-tidy.yml +++ b/.github/workflows/reusable-clang-tidy.yml @@ -30,7 +30,7 @@ jobs: - name: Get changed C++ files id: changed_files - uses: tj-actions/changed-files@ed68ef82c095e0d48ec87eccea555d944a631a4c # v46.0.5 + uses: tj-actions/changed-files@7dee1b0c1557f278e5c7dc244927139d78c0e22a # v47.0.4 with: files: | **/*.cpp From 625becff183f4c8c22b62df19a9f1fdc1cea3874 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 20 Feb 2026 21:29:05 +0000 Subject: [PATCH 06/41] ci: [DEPENDABOT] bump actions/setup-python from 5.6.0 to 6.2.0 (#6395) Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5.6.0 to 6.2.0. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/a26af69be951a213d495a4c3e4e4022e16d87065...a309ff8b426b58ec0e2a45f0f869d46889d02405) --- updated-dependencies: - dependency-name: actions/setup-python dependency-version: 6.2.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/reusable-strategy-matrix.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/reusable-strategy-matrix.yml b/.github/workflows/reusable-strategy-matrix.yml index c975347307..47d1af3082 100644 --- a/.github/workflows/reusable-strategy-matrix.yml +++ b/.github/workflows/reusable-strategy-matrix.yml @@ -32,7 +32,7 @@ jobs: uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 - name: Set up Python - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: 3.13 From fd9f57ec97fdc5a3d4e672c0c9b38d6f79dc0895 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 20 Feb 2026 22:09:48 +0000 Subject: [PATCH 07/41] ci: [DEPENDABOT] bump actions/checkout from 4.3.0 to 6.0.2 (#6397) Bumps [actions/checkout](https://github.com/actions/checkout) from 4.3.0 to 6.0.2. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4.3.0...de0fac2e4500dabe0009e67214ff5f5447ce83dd) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: 6.0.2 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .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-files.yml | 2 +- .github/workflows/reusable-strategy-matrix.yml | 2 +- .github/workflows/reusable-upload-recipe.yml | 2 +- .github/workflows/upload-conan-deps.yml | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/on-pr.yml b/.github/workflows/on-pr.yml index 545e65e4eb..be6a92eea2 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@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - 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 121026d13c..acde57fd91 100644 --- a/.github/workflows/publish-docs.yml +++ b/.github/workflows/publish-docs.yml @@ -47,7 +47,7 @@ jobs: contents: write steps: - name: Checkout repository - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Get number of processors uses: XRPLF/actions/get-nproc@cf0433aa74563aead044a1e395610c96d65a37cf diff --git a/.github/workflows/reusable-build-test-config.yml b/.github/workflows/reusable-build-test-config.yml index 9978ea4122..c6f28d584a 100644 --- a/.github/workflows/reusable-build-test-config.yml +++ b/.github/workflows/reusable-build-test-config.yml @@ -104,7 +104,7 @@ jobs: uses: XRPLF/actions/cleanup-workspace@cf0433aa74563aead044a1e395610c96d65a37cf - name: Checkout repository - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Prepare runner uses: XRPLF/actions/prepare-runner@2cbf481018d930656e9276fcc20dc0e3a0be5b6d diff --git a/.github/workflows/reusable-check-levelization.yml b/.github/workflows/reusable-check-levelization.yml index 29a1dc1480..ae3eed0f16 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@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Check levelization run: .github/scripts/levelization/generate.sh - name: Check for differences diff --git a/.github/workflows/reusable-check-rename.yml b/.github/workflows/reusable-check-rename.yml index a73ac49b7d..0e335ab9ca 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@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Check definitions run: .github/scripts/rename/definitions.sh . - name: Check copyright notices diff --git a/.github/workflows/reusable-clang-tidy-files.yml b/.github/workflows/reusable-clang-tidy-files.yml index c410a2e55d..0b17b9d800 100644 --- a/.github/workflows/reusable-clang-tidy-files.yml +++ b/.github/workflows/reusable-clang-tidy-files.yml @@ -32,7 +32,7 @@ jobs: contents: read steps: - name: Checkout repository - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Prepare runner uses: XRPLF/actions/prepare-runner@2cbf481018d930656e9276fcc20dc0e3a0be5b6d diff --git a/.github/workflows/reusable-strategy-matrix.yml b/.github/workflows/reusable-strategy-matrix.yml index 47d1af3082..b1232a138f 100644 --- a/.github/workflows/reusable-strategy-matrix.yml +++ b/.github/workflows/reusable-strategy-matrix.yml @@ -29,7 +29,7 @@ jobs: matrix: ${{ steps.generate.outputs.matrix }} steps: - name: Checkout repository - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - 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 79af516fb3..6245fd06e1 100644 --- a/.github/workflows/reusable-upload-recipe.yml +++ b/.github/workflows/reusable-upload-recipe.yml @@ -43,7 +43,7 @@ jobs: container: ghcr.io/xrplf/ci/ubuntu-noble:gcc-13-sha-5dd7158 steps: - name: Checkout repository - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Generate build version number id: version diff --git a/.github/workflows/upload-conan-deps.yml b/.github/workflows/upload-conan-deps.yml index 60696a9769..b260c4c4f3 100644 --- a/.github/workflows/upload-conan-deps.yml +++ b/.github/workflows/upload-conan-deps.yml @@ -67,7 +67,7 @@ jobs: uses: XRPLF/actions/cleanup-workspace@cf0433aa74563aead044a1e395610c96d65a37cf - name: Checkout repository - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Prepare runner uses: XRPLF/actions/prepare-runner@2cbf481018d930656e9276fcc20dc0e3a0be5b6d From 3542daa4cc0b29f7721229651a4ffa5989ba90c0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 20 Feb 2026 22:48:01 +0000 Subject: [PATCH 08/41] ci: [DEPENDABOT] bump actions/upload-artifact from 4.6.2 to 6.0.0 (#6396) Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.6.2 to 6.0.0. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/ea165f8d65b6e75b540449e92b4886f43607fa02...b7c566a772e6b6bfb58ed0dc250532a479d7789f) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: 6.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/reusable-build-test-config.yml | 2 +- .github/workflows/reusable-clang-tidy-files.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/reusable-build-test-config.yml b/.github/workflows/reusable-build-test-config.yml index c6f28d584a..4f52b68b84 100644 --- a/.github/workflows/reusable-build-test-config.yml +++ b/.github/workflows/reusable-build-test-config.yml @@ -177,7 +177,7 @@ jobs: - name: Upload the binary (Linux) if: ${{ github.repository_owner == 'XRPLF' && runner.os == 'Linux' }} - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: xrpld-${{ inputs.config_name }} path: ${{ env.BUILD_DIR }}/xrpld diff --git a/.github/workflows/reusable-clang-tidy-files.yml b/.github/workflows/reusable-clang-tidy-files.yml index 0b17b9d800..432da1d15c 100644 --- a/.github/workflows/reusable-clang-tidy-files.yml +++ b/.github/workflows/reusable-clang-tidy-files.yml @@ -84,7 +84,7 @@ jobs: - name: Upload clang-tidy output if: steps.run_clang_tidy.outcome != 'success' - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: clang-tidy-results path: clang-tidy-output.txt From 0fd237d707ceb56fe77974d888fe35eb61c2be66 Mon Sep 17 00:00:00 2001 From: Sergey Kuznetsov Date: Tue, 24 Feb 2026 01:10:07 +0000 Subject: [PATCH 09/41] chore: Add nix development environment (#6314) --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .github/workflows/pre-commit.yml | 2 +- .gitignore | 6 ++ .pre-commit-config.yaml | 10 +++ cspell.config.yaml | 3 + docs/build/environment.md | 2 + docs/build/nix.md | 95 +++++++++++++++++++++ flake.lock | 26 ++++++ flake.nix | 16 ++++ nix/devshell.nix | 140 +++++++++++++++++++++++++++++++ nix/utils.nix | 19 +++++ 10 files changed, 318 insertions(+), 1 deletion(-) create mode 100644 docs/build/nix.md create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 nix/devshell.nix create mode 100644 nix/utils.nix diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index f43275201c..7793d1e3ab 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -14,4 +14,4 @@ jobs: uses: XRPLF/actions/.github/workflows/pre-commit.yml@320be44621ca2a080f05aeb15817c44b84518108 with: runs_on: ubuntu-latest - container: '{ "image": "ghcr.io/xrplf/ci/tools-rippled-pre-commit:sha-ab4d1f0" }' + container: '{ "image": "ghcr.io/xrplf/ci/tools-rippled-pre-commit:sha-41ec7c1" }' diff --git a/.gitignore b/.gitignore index a1c2f034d1..60e8fef56c 100644 --- a/.gitignore +++ b/.gitignore @@ -42,6 +42,9 @@ gmon.out # Locally patched Conan recipes external/conan-center-index/ +# Local conan directory +.conan + # XCode IDE. *.pbxuser !default.pbxuser @@ -72,5 +75,8 @@ DerivedData /.claude /CLAUDE.md +# Direnv's directory +/.direnv + # clangd cache /.cache diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9117fe0d3e..6e04c752e9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -57,6 +57,16 @@ repos: - .git/COMMIT_EDITMSG stages: [commit-msg] + - repo: local + hooks: + - id: nix-fmt + name: Format Nix files + entry: nix --extra-experimental-features 'nix-command flakes' fmt + language: system + types: + - nix + pass_filenames: true + exclude: | (?x)^( external/.*| diff --git a/cspell.config.yaml b/cspell.config.yaml index 87258758c4..e2b20ac098 100644 --- a/cspell.config.yaml +++ b/cspell.config.yaml @@ -173,6 +173,9 @@ words: - nftokens - nftpage - nikb + - nixfmt + - nixos + - nixpkgs - nonxrp - noripple - nudb diff --git a/docs/build/environment.md b/docs/build/environment.md index c6b735ba48..c67877a082 100644 --- a/docs/build/environment.md +++ b/docs/build/environment.md @@ -3,6 +3,8 @@ environment complete with Git, Python, Conan, CMake, and a C++ compiler. This document exists to help readers set one up on any of the Big Three platforms: Linux, macOS, or Windows. +As an alternative to system packages, the Nix development shell can be used to provide a development environment. See [using nix development shell](./nix.md) for more details. + [BUILD.md]: ../../BUILD.md ## Linux diff --git a/docs/build/nix.md b/docs/build/nix.md new file mode 100644 index 0000000000..33bb3711d0 --- /dev/null +++ b/docs/build/nix.md @@ -0,0 +1,95 @@ +# Using Nix Development Shell for xrpld Development + +This guide explains how to use Nix to set up a reproducible development environment for xrpld. Using Nix eliminates the need to manually install utilities and ensures consistent tooling across different machines. + +## Benefits of Using Nix + +- **Reproducible environment**: Everyone gets the same versions of tools and compilers +- **No system pollution**: Dependencies are isolated and don't affect your system packages +- **Multiple compiler versions**: Easily switch between different GCC and Clang versions +- **Quick setup**: Get started with a single command +- **Works on Linux and macOS**: Consistent experience across platforms + +## Install Nix + +Please follow [the official installation instructions of nix package manager](https://nixos.org/download/) for your system. + +## Entering the Development Shell + +### Basic Usage + +From the root of the xrpld repository, enter the default development shell: + +```bash +nix --experimental-features 'nix-command flakes' develop +``` + +This will: + +- Download and set up all required development tools (CMake, Ninja, Conan, etc.) +- Configure the appropriate compiler for your platform: + - **macOS**: Apple Clang (default system compiler) + - **Linux**: GCC 15 + +The first time you run this command, it will take a few minutes to download and build the environment. Subsequent runs will be much faster. + +> [!TIP] +> To avoid typing `--experimental-features 'nix-command flakes'` every time, you can permanently enable flakes by creating `~/.config/nix/nix.conf`: +> +> ```bash +> mkdir -p ~/.config/nix +> echo "experimental-features = nix-command flakes" >> ~/.config/nix/nix.conf +> ``` +> +> After this, you can simply use `nix develop` instead. + +> [!NOTE] +> The examples below assume you've enabled flakes in your config. If you haven't, add `--experimental-features 'nix-command flakes'` after each `nix` command. + +### Choosing a different compiler + +A compiler can be chosen by providing its name with the `.#` prefix, e.g. `nix develop .#gcc15`. +Use `nix flake show` to see all the available development shells. + +Use `nix develop .#no_compiler` to use the compiler from your system. + +### Example Usage + +```bash +# Use GCC 14 +nix develop .#gcc14 + +# Use Clang 19 +nix develop .#clang19 + +# Use default for your platform +nix develop +``` + +### Using a different shell + +`nix develop` opens bash by default. If you want to use another shell this could be done by adding `-c` flag. For example: + +```bash +nix develop -c zsh +``` + +## Building xrpld with Nix + +Once inside the Nix development shell, follow the standard [build instructions](../../BUILD.md#steps). The Nix shell provides all necessary tools (CMake, Ninja, Conan, etc.). + +## Automatic Activation with direnv + +[direnv](https://direnv.net/) or [nix-direnv](https://github.com/nix-community/nix-direnv) can automatically activate the Nix development shell when you enter the repository directory. + +## Conan and Prebuilt Packages + +Please note that there is no guarantee that binaries from conan cache will work when using nix. If you encounter any errors, please use `--build '*'` to force conan to compile everything from source: + +```bash +conan install .. --output-folder . --build '*' --settings build_type=Release +``` + +## Updating `flake.lock` file + +To update `flake.lock` to the latest revision use `nix flake update` command. diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000000..fd43f5b683 --- /dev/null +++ b/flake.lock @@ -0,0 +1,26 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1769461804, + "narHash": "sha256-6h5sROT/3CTHvzPy9koKBmoCa2eJKh4fzQK8eYFEgl8=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "b579d443b37c9c5373044201ea77604e37e748c8", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "ref": "nixos-unstable", + "type": "indirect" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000000..4c500f1933 --- /dev/null +++ b/flake.nix @@ -0,0 +1,16 @@ +{ + description = "Nix related things for xrpld"; + inputs = { + nixpkgs.url = "nixpkgs/nixos-unstable"; + }; + + outputs = + { nixpkgs, ... }: + let + forEachSystem = (import ./nix/utils.nix { inherit nixpkgs; }).forEachSystem; + in + { + devShells = forEachSystem (import ./nix/devshell.nix); + formatter = forEachSystem ({ pkgs, ... }: pkgs.nixfmt); + }; +} diff --git a/nix/devshell.nix b/nix/devshell.nix new file mode 100644 index 0000000000..1d907f4d87 --- /dev/null +++ b/nix/devshell.nix @@ -0,0 +1,140 @@ +{ pkgs, ... }: +let + commonPackages = with pkgs; [ + ccache + cmake + conan + gcovr + git + gnumake + llvmPackages_21.clang-tools + ninja + perl # needed for openssl + pkg-config + pre-commit + python314 + ]; + + # Supported compiler versions + gccVersion = pkgs.lib.range 13 15; + clangVersions = pkgs.lib.range 18 21; + + defaultCompiler = if pkgs.stdenv.isDarwin then "apple-clang" else "gcc"; + defaultGccVersion = pkgs.lib.last gccVersion; + defaultClangVersion = pkgs.lib.last clangVersions; + + strToCompilerEnv = + compiler: version: + ( + if compiler == "gcc" then + let + gccPkg = pkgs."gcc${toString version}Stdenv" or null; + in + if gccPkg != null && builtins.elem version gccVersion then + gccPkg + else + throw "Invalid GCC version: ${toString version}. Must be one of: ${toString gccVersion}" + else if compiler == "clang" then + let + clangPkg = pkgs."llvmPackages_${toString version}".stdenv or null; + in + if clangPkg != null && builtins.elem version clangVersions then + clangPkg + else + throw "Invalid Clang version: ${toString version}. Must be one of: ${toString clangVersions}" + else if compiler == "apple-clang" || compiler == "none" then + pkgs.stdenvNoCC + else + throw "Invalid compiler: ${compiler}. Must be one of: gcc, clang, apple-clang, none" + ); + + # Helper function to create a shell with a specific compiler + makeShell = + { + compiler ? defaultCompiler, + version ? ( + if compiler == "gcc" then + defaultGccVersion + else if compiler == "clang" then + defaultClangVersion + else + null + ), + }: + let + compilerStdEnv = strToCompilerEnv compiler version; + + compilerName = + if compiler == "apple-clang" then + "clang" + else if compiler == "none" then + null + else + compiler; + + gccOnMacWarning = + if pkgs.stdenv.isDarwin && compiler == "gcc" then + '' + echo "WARNING: Using GCC on macOS with Conan may not work." + echo " Consider using 'nix develop .#clang' or the default shell instead." + echo "" + '' + else + ""; + + compilerVersion = + if compilerName != null then + '' + echo "Compiler: " + ${compilerName} --version + '' + else + '' + echo "No compiler specified - using system compiler" + ''; + + shellAttrs = { + packages = commonPackages; + + shellHook = '' + echo "Welcome to xrpld development shell"; + ${gccOnMacWarning}${compilerVersion} + ''; + }; + in + pkgs.mkShell.override { stdenv = compilerStdEnv; } shellAttrs; + + # Generate shells for each compiler version + gccShells = builtins.listToAttrs ( + map (version: { + name = "gcc${toString version}"; + value = makeShell { + compiler = "gcc"; + version = version; + }; + }) gccVersion + ); + + clangShells = builtins.listToAttrs ( + map (version: { + name = "clang${toString version}"; + value = makeShell { + compiler = "clang"; + version = version; + }; + }) clangVersions + ); + +in +gccShells +// clangShells +// { + # Default shells + default = makeShell { }; + gcc = makeShell { compiler = "gcc"; }; + clang = makeShell { compiler = "clang"; }; + + # No compiler + no-compiler = makeShell { compiler = "none"; }; + apple-clang = makeShell { compiler = "apple-clang"; }; +} diff --git a/nix/utils.nix b/nix/utils.nix new file mode 100644 index 0000000000..821d60a6f6 --- /dev/null +++ b/nix/utils.nix @@ -0,0 +1,19 @@ +{ nixpkgs }: +{ + forEachSystem = + function: + nixpkgs.lib.genAttrs + [ + "x86_64-linux" + "aarch64-linux" + "x86_64-darwin" + "aarch64-darwin" + ] + ( + system: + function { + inherit system; + pkgs = import nixpkgs { inherit system; }; + } + ); +} From 3a805cc646bf003a934a7624014b427b569e9a10 Mon Sep 17 00:00:00 2001 From: Valentin Balaschenko <13349202+vlntb@users.noreply.github.com> Date: Fri, 20 Feb 2026 20:03:42 +0000 Subject: [PATCH 10/41] Disable featureBatch and fixBatchInnerSigs amendments (#6402) --- include/xrpl/protocol/detail/features.macro | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/include/xrpl/protocol/detail/features.macro b/include/xrpl/protocol/detail/features.macro index 961bc6e44c..a40d524c70 100644 --- a/include/xrpl/protocol/detail/features.macro +++ b/include/xrpl/protocol/detail/features.macro @@ -15,9 +15,10 @@ // Add new amendments to the top of this list. // Keep it sorted in reverse chronological order. + XRPL_FIX (PermissionedDomainInvariant, Supported::yes, VoteBehavior::DefaultNo) XRPL_FIX (ExpiredNFTokenOfferRemoval, Supported::yes, VoteBehavior::DefaultNo) -XRPL_FIX (BatchInnerSigs, Supported::yes, VoteBehavior::DefaultNo) +XRPL_FIX (BatchInnerSigs, Supported::no, VoteBehavior::DefaultNo) XRPL_FEATURE(LendingProtocol, Supported::yes, VoteBehavior::DefaultNo) XRPL_FEATURE(PermissionDelegationV1_1, Supported::no, VoteBehavior::DefaultNo) XRPL_FIX (DirectoryLimit, Supported::yes, VoteBehavior::DefaultNo) @@ -31,7 +32,7 @@ XRPL_FEATURE(TokenEscrow, Supported::yes, VoteBehavior::DefaultNo XRPL_FIX (EnforceNFTokenTrustlineV2, Supported::yes, VoteBehavior::DefaultNo) XRPL_FIX (AMMv1_3, Supported::yes, VoteBehavior::DefaultNo) XRPL_FEATURE(PermissionedDEX, Supported::yes, VoteBehavior::DefaultNo) -XRPL_FEATURE(Batch, Supported::yes, VoteBehavior::DefaultNo) +XRPL_FEATURE(Batch, Supported::no, VoteBehavior::DefaultNo) XRPL_FEATURE(SingleAssetVault, Supported::yes, VoteBehavior::DefaultNo) XRPL_FIX (PayChanCancelAfter, Supported::yes, VoteBehavior::DefaultNo) // Check flags in Credential transactions From 24cbaf76a5cdd2ad95d1eeee7e4382d3a63bb8ea Mon Sep 17 00:00:00 2001 From: Valentin Balaschenko <13349202+vlntb@users.noreply.github.com> Date: Fri, 20 Feb 2026 20:04:47 +0000 Subject: [PATCH 11/41] ci: Update prepare-runner action to fix macOS build environment (empty) Updates XRPLF/actions prepare-runner to version 2cbf48101 which fixes pip upgrade failures on macOS runners with Homebrew-managed Python. * This commit was cherry-picked from "release-3.1", but ended up empty because the changes are already present. It is included only for accounting - to indicate that all changes/commits from the previous release will be in the next one. From bdd106d992b66f3f07703dce837a9ae1166f4284 Mon Sep 17 00:00:00 2001 From: Valentin Balaschenko <13349202+vlntb@users.noreply.github.com> Date: Tue, 24 Feb 2026 21:33:13 +0000 Subject: [PATCH 12/41] Explicitly trim the heap after cache sweeps (#6022) Limited to Linux/glibc builds. --- include/xrpl/basics/MallocTrim.h | 73 +++++++++ src/libxrpl/basics/MallocTrim.cpp | 157 ++++++++++++++++++ src/tests/libxrpl/basics/MallocTrim.cpp | 209 ++++++++++++++++++++++++ src/xrpld/app/main/Application.cpp | 3 + 4 files changed, 442 insertions(+) create mode 100644 include/xrpl/basics/MallocTrim.h create mode 100644 src/libxrpl/basics/MallocTrim.cpp create mode 100644 src/tests/libxrpl/basics/MallocTrim.cpp diff --git a/include/xrpl/basics/MallocTrim.h b/include/xrpl/basics/MallocTrim.h new file mode 100644 index 0000000000..2d0cf989ba --- /dev/null +++ b/include/xrpl/basics/MallocTrim.h @@ -0,0 +1,73 @@ +#pragma once + +#include + +#include +#include +#include + +namespace xrpl { + +// cSpell:ignore ptmalloc + +// ----------------------------------------------------------------------------- +// Allocator interaction note: +// - This facility invokes glibc's malloc_trim(0) on Linux/glibc to request that +// ptmalloc return free heap pages to the OS. +// - If an alternative allocator (e.g. jemalloc or tcmalloc) is linked or +// preloaded (LD_PRELOAD), calling glibc's malloc_trim typically has no effect +// on the *active* heap. The call is harmless but may not reclaim memory +// because those allocators manage their own arenas. +// - Only glibc sbrk/arena space is eligible for trimming; large mmap-backed +// allocations are usually returned to the OS on free regardless of trimming. +// - Call at known reclamation points (e.g., after cache sweeps / online delete) +// and consider rate limiting to avoid churn. +// ----------------------------------------------------------------------------- + +struct MallocTrimReport +{ + bool supported{false}; + int trimResult{-1}; + std::int64_t rssBeforeKB{-1}; + std::int64_t rssAfterKB{-1}; + std::chrono::microseconds durationUs{-1}; + std::int64_t minfltDelta{-1}; + std::int64_t majfltDelta{-1}; + + [[nodiscard]] std::int64_t + deltaKB() const noexcept + { + if (rssBeforeKB < 0 || rssAfterKB < 0) + return 0; + return rssAfterKB - rssBeforeKB; + } +}; + +/** + * @brief Attempt to return freed memory to the operating system. + * + * On Linux with glibc malloc, this issues ::malloc_trim(0), which may release + * free space from ptmalloc arenas back to the kernel. On other platforms, or if + * a different allocator is in use, this function is a no-op and the report will + * indicate that trimming is unsupported or had no effect. + * + * @param tag Identifier for logging/debugging purposes. + * @param journal Journal for diagnostic logging. + * @return Report containing before/after metrics and the trim result. + * + * @note If an alternative allocator (jemalloc/tcmalloc) is linked or preloaded, + * calling glibc's malloc_trim may have no effect on the active heap. The + * call is harmless but typically does not reclaim memory under those + * allocators. + * + * @note Only memory served from glibc's sbrk/arena heaps is eligible for trim. + * Large allocations satisfied via mmap are usually returned on free + * independently of trimming. + * + * @note Intended for use after operations that free significant memory (e.g., + * cache sweeps, ledger cleanup, online delete). Consider rate limiting. + */ +MallocTrimReport +mallocTrim(std::string_view tag, beast::Journal journal); + +} // namespace xrpl diff --git a/src/libxrpl/basics/MallocTrim.cpp b/src/libxrpl/basics/MallocTrim.cpp new file mode 100644 index 0000000000..1b0932b39d --- /dev/null +++ b/src/libxrpl/basics/MallocTrim.cpp @@ -0,0 +1,157 @@ +#include +#include + +#include + +#include +#include +#include +#include +#include + +#if defined(__GLIBC__) && BOOST_OS_LINUX +#include + +#include +#include + +// Require RUSAGE_THREAD for thread-scoped page fault tracking +#ifndef RUSAGE_THREAD +#error "MallocTrim rusage instrumentation requires RUSAGE_THREAD on Linux/glibc" +#endif + +namespace { + +bool +getRusageThread(struct rusage& ru) +{ + return ::getrusage(RUSAGE_THREAD, &ru) == 0; // LCOV_EXCL_LINE +} + +} // namespace +#endif + +namespace xrpl { + +namespace detail { + +// cSpell:ignore statm + +#if defined(__GLIBC__) && BOOST_OS_LINUX + +inline int +mallocTrimWithPad(std::size_t padBytes) +{ + return ::malloc_trim(padBytes); +} + +long +parseStatmRSSkB(std::string const& statm) +{ + // /proc/self/statm format: size resident shared text lib data dt + // We want the second field (resident) which is in pages + std::istringstream iss(statm); + long size, resident; + if (!(iss >> size >> resident)) + return -1; + + // Convert pages to KB + long const pageSize = ::sysconf(_SC_PAGESIZE); + if (pageSize <= 0) + return -1; + + return (resident * pageSize) / 1024; +} + +#endif // __GLIBC__ && BOOST_OS_LINUX + +} // namespace detail + +MallocTrimReport +mallocTrim(std::string_view tag, beast::Journal journal) +{ + // LCOV_EXCL_START + + MallocTrimReport report; + +#if !(defined(__GLIBC__) && BOOST_OS_LINUX) + JLOG(journal.debug()) << "malloc_trim not supported on this platform (tag=" << tag << ")"; +#else + // Keep glibc malloc_trim padding at 0 (default): 12h Mainnet tests across 0/256KB/1MB/16MB + // showed no clear, consistent benefit from custom padding—0 provided the best overall balance + // of RSS reduction and trim-latency stability without adding a tuning surface. + constexpr std::size_t TRIM_PAD = 0; + + report.supported = true; + + if (journal.debug()) + { + auto readFile = [](std::string const& path) -> std::string { + std::ifstream ifs(path, std::ios::in | std::ios::binary); + if (!ifs.is_open()) + return {}; + + // /proc files are often not seekable; read as a stream. + std::ostringstream oss; + oss << ifs.rdbuf(); + return oss.str(); + }; + + std::string const tagStr{tag}; + std::string const statmPath = "/proc/self/statm"; + + auto const statmBefore = readFile(statmPath); + long const rssBeforeKB = detail::parseStatmRSSkB(statmBefore); + + struct rusage ru0{}; + bool const have_ru0 = getRusageThread(ru0); + + auto const t0 = std::chrono::steady_clock::now(); + + report.trimResult = detail::mallocTrimWithPad(TRIM_PAD); + + auto const t1 = std::chrono::steady_clock::now(); + + struct rusage ru1{}; + bool const have_ru1 = getRusageThread(ru1); + + auto const statmAfter = readFile(statmPath); + long const rssAfterKB = detail::parseStatmRSSkB(statmAfter); + + // Populate report fields + report.rssBeforeKB = rssBeforeKB; + report.rssAfterKB = rssAfterKB; + report.durationUs = std::chrono::duration_cast(t1 - t0); + + if (have_ru0 && have_ru1) + { + report.minfltDelta = ru1.ru_minflt - ru0.ru_minflt; + report.majfltDelta = ru1.ru_majflt - ru0.ru_majflt; + } + + std::int64_t const deltaKB = (rssBeforeKB < 0 || rssAfterKB < 0) + ? 0 + : (static_cast(rssAfterKB) - static_cast(rssBeforeKB)); + + JLOG(journal.debug()) << "malloc_trim tag=" << tagStr << " result=" << report.trimResult + << " pad=" << TRIM_PAD << " bytes" + << " rss_before=" << rssBeforeKB << "kB" + << " rss_after=" << rssAfterKB << "kB" + << " delta=" << deltaKB << "kB" + << " duration_us=" << report.durationUs.count() + << " minflt_delta=" << report.minfltDelta + << " majflt_delta=" << report.majfltDelta; + } + else + { + report.trimResult = detail::mallocTrimWithPad(TRIM_PAD); + } + +#endif + + return report; + + // LCOV_EXCL_STOP +} + +} // namespace xrpl diff --git a/src/tests/libxrpl/basics/MallocTrim.cpp b/src/tests/libxrpl/basics/MallocTrim.cpp new file mode 100644 index 0000000000..f01bd91bbf --- /dev/null +++ b/src/tests/libxrpl/basics/MallocTrim.cpp @@ -0,0 +1,209 @@ +#include + +#include + +#include + +using namespace xrpl; + +// cSpell:ignore statm + +#if defined(__GLIBC__) && BOOST_OS_LINUX +namespace xrpl::detail { +long +parseStatmRSSkB(std::string const& statm); +} // namespace xrpl::detail +#endif + +TEST(MallocTrimReport, structure) +{ + // Test default construction + MallocTrimReport report; + EXPECT_EQ(report.supported, false); + EXPECT_EQ(report.trimResult, -1); + EXPECT_EQ(report.rssBeforeKB, -1); + EXPECT_EQ(report.rssAfterKB, -1); + EXPECT_EQ(report.durationUs, std::chrono::microseconds{-1}); + EXPECT_EQ(report.minfltDelta, -1); + EXPECT_EQ(report.majfltDelta, -1); + EXPECT_EQ(report.deltaKB(), 0); + + // Test deltaKB calculation - memory freed + report.rssBeforeKB = 1000; + report.rssAfterKB = 800; + EXPECT_EQ(report.deltaKB(), -200); + + // Test deltaKB calculation - memory increased + report.rssBeforeKB = 500; + report.rssAfterKB = 600; + EXPECT_EQ(report.deltaKB(), 100); + + // Test deltaKB calculation - no change + report.rssBeforeKB = 1234; + report.rssAfterKB = 1234; + EXPECT_EQ(report.deltaKB(), 0); +} + +#if defined(__GLIBC__) && BOOST_OS_LINUX +TEST(parseStatmRSSkB, standard_format) +{ + using xrpl::detail::parseStatmRSSkB; + + // Test standard format: size resident shared text lib data dt + // Assuming 4KB page size: resident=1000 pages = 4000 KB + { + std::string statm = "25365 1000 2377 0 0 5623 0"; + long result = parseStatmRSSkB(statm); + // Note: actual result depends on system page size + // On most systems it's 4KB, so 1000 pages = 4000 KB + EXPECT_GT(result, 0); + } + + // Test with newline + { + std::string statm = "12345 2000 1234 0 0 3456 0\n"; + long result = parseStatmRSSkB(statm); + EXPECT_GT(result, 0); + } + + // Test with tabs + { + std::string statm = "12345\t2000\t1234\t0\t0\t3456\t0"; + long result = parseStatmRSSkB(statm); + EXPECT_GT(result, 0); + } + + // Test zero resident pages + { + std::string statm = "25365 0 2377 0 0 5623 0"; + long result = parseStatmRSSkB(statm); + EXPECT_EQ(result, 0); + } + + // Test with extra whitespace + { + std::string statm = " 25365 1000 2377 "; + long result = parseStatmRSSkB(statm); + EXPECT_GT(result, 0); + } + + // Test empty string + { + std::string statm = ""; + long result = parseStatmRSSkB(statm); + EXPECT_EQ(result, -1); + } + + // Test malformed data (only one field) + { + std::string statm = "25365"; + long result = parseStatmRSSkB(statm); + EXPECT_EQ(result, -1); + } + + // Test malformed data (non-numeric) + { + std::string statm = "abc def ghi"; + long result = parseStatmRSSkB(statm); + EXPECT_EQ(result, -1); + } + + // Test malformed data (second field non-numeric) + { + std::string statm = "25365 abc 2377"; + long result = parseStatmRSSkB(statm); + EXPECT_EQ(result, -1); + } +} +#endif + +TEST(mallocTrim, without_debug_logging) +{ + beast::Journal journal{beast::Journal::getNullSink()}; + + MallocTrimReport report = mallocTrim("without_debug", journal); + +#if defined(__GLIBC__) && BOOST_OS_LINUX + EXPECT_EQ(report.supported, true); + EXPECT_GE(report.trimResult, 0); + EXPECT_EQ(report.durationUs, std::chrono::microseconds{-1}); + EXPECT_EQ(report.minfltDelta, -1); + EXPECT_EQ(report.majfltDelta, -1); +#else + EXPECT_EQ(report.supported, false); + EXPECT_EQ(report.trimResult, -1); + EXPECT_EQ(report.rssBeforeKB, -1); + EXPECT_EQ(report.rssAfterKB, -1); + EXPECT_EQ(report.durationUs, std::chrono::microseconds{-1}); + EXPECT_EQ(report.minfltDelta, -1); + EXPECT_EQ(report.majfltDelta, -1); +#endif +} + +TEST(mallocTrim, empty_tag) +{ + beast::Journal journal{beast::Journal::getNullSink()}; + MallocTrimReport report = mallocTrim("", journal); + +#if defined(__GLIBC__) && BOOST_OS_LINUX + EXPECT_EQ(report.supported, true); + EXPECT_GE(report.trimResult, 0); +#else + EXPECT_EQ(report.supported, false); +#endif +} + +TEST(mallocTrim, with_debug_logging) +{ + struct DebugSink : public beast::Journal::Sink + { + DebugSink() : Sink(beast::severities::kDebug, false) + { + } + void + write(beast::severities::Severity, std::string const&) override + { + } + void + writeAlways(beast::severities::Severity, std::string const&) override + { + } + }; + + DebugSink sink; + beast::Journal journal{sink}; + + MallocTrimReport report = mallocTrim("debug_test", journal); + +#if defined(__GLIBC__) && BOOST_OS_LINUX + EXPECT_EQ(report.supported, true); + EXPECT_GE(report.trimResult, 0); + EXPECT_GE(report.durationUs.count(), 0); + EXPECT_GE(report.minfltDelta, 0); + EXPECT_GE(report.majfltDelta, 0); +#else + EXPECT_EQ(report.supported, false); + EXPECT_EQ(report.trimResult, -1); + EXPECT_EQ(report.durationUs, std::chrono::microseconds{-1}); + EXPECT_EQ(report.minfltDelta, -1); + EXPECT_EQ(report.majfltDelta, -1); +#endif +} + +TEST(mallocTrim, repeated_calls) +{ + beast::Journal journal{beast::Journal::getNullSink()}; + + // Call malloc_trim multiple times to ensure it's safe + for (int i = 0; i < 5; ++i) + { + MallocTrimReport report = mallocTrim("iteration_" + std::to_string(i), journal); + +#if defined(__GLIBC__) && BOOST_OS_LINUX + EXPECT_EQ(report.supported, true); + EXPECT_GE(report.trimResult, 0); +#else + EXPECT_EQ(report.supported, false); +#endif + } +} diff --git a/src/xrpld/app/main/Application.cpp b/src/xrpld/app/main/Application.cpp index 91cc387d54..1162bc497a 100644 --- a/src/xrpld/app/main/Application.cpp +++ b/src/xrpld/app/main/Application.cpp @@ -31,6 +31,7 @@ #include #include +#include #include #include #include @@ -1053,6 +1054,8 @@ public: << "; size after: " << cachedSLEs_.size(); } + mallocTrim("doSweep", m_journal); + // Set timer to do another sweep later. setSweepTimer(); } From 65e63ebef3e95c804cae4f1085a6e5f45e4748f5 Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Wed, 25 Feb 2026 01:12:16 +0000 Subject: [PATCH 13/41] chore: Update cleanup-workspace to delete old .conan2 dir on macOS (#6412) --- .github/workflows/reusable-build-test-config.yml | 2 +- .github/workflows/upload-conan-deps.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/reusable-build-test-config.yml b/.github/workflows/reusable-build-test-config.yml index 4f52b68b84..6060a208fe 100644 --- a/.github/workflows/reusable-build-test-config.yml +++ b/.github/workflows/reusable-build-test-config.yml @@ -101,7 +101,7 @@ jobs: steps: - name: Cleanup workspace (macOS and Windows) if: ${{ runner.os == 'macOS' || runner.os == 'Windows' }} - uses: XRPLF/actions/cleanup-workspace@cf0433aa74563aead044a1e395610c96d65a37cf + uses: XRPLF/actions/cleanup-workspace@c7d9ce5ebb03c752a354889ecd870cadfc2b1cd4 - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 diff --git a/.github/workflows/upload-conan-deps.yml b/.github/workflows/upload-conan-deps.yml index b260c4c4f3..df8aa43a18 100644 --- a/.github/workflows/upload-conan-deps.yml +++ b/.github/workflows/upload-conan-deps.yml @@ -64,7 +64,7 @@ jobs: steps: - name: Cleanup workspace (macOS and Windows) if: ${{ runner.os == 'macOS' || runner.os == 'Windows' }} - uses: XRPLF/actions/cleanup-workspace@cf0433aa74563aead044a1e395610c96d65a37cf + uses: XRPLF/actions/cleanup-workspace@c7d9ce5ebb03c752a354889ecd870cadfc2b1cd4 - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 From 3a8a18c2cabe704623517eb3767f50e8a908ad95 Mon Sep 17 00:00:00 2001 From: Bart Date: Wed, 25 Feb 2026 18:23:34 -0500 Subject: [PATCH 14/41] refactor: Use uint256 directly as key instead of void pointer (#6313) This change replaces `void const*` by `uint256 const&` for database fetches. Object hashes are expressed using the `uint256` data type, and are converted to `void *` when calling the `fetch` or `fetchBatch` functions. However, in these fetch functions they are converted back to `uint256`, making the conversion process unnecessary. In a few cases the underlying pointer is needed, but that can then be easy obtained via `[hash variable].data()`. --- include/xrpl/nodestore/Backend.h | 6 +++--- src/libxrpl/nodestore/DatabaseNodeImp.cpp | 12 ++---------- src/libxrpl/nodestore/DatabaseRotatingImp.cpp | 2 +- src/libxrpl/nodestore/backend/MemoryFactory.cpp | 7 +++---- src/libxrpl/nodestore/backend/NuDBFactory.cpp | 12 ++++++------ src/libxrpl/nodestore/backend/NullFactory.cpp | 4 ++-- .../nodestore/backend/RocksDBFactory.cpp | 15 +++++++-------- src/test/nodestore/TestBase.h | 4 ++-- src/test/nodestore/Timing_test.cpp | 17 ++++++++--------- 9 files changed, 34 insertions(+), 45 deletions(-) diff --git a/include/xrpl/nodestore/Backend.h b/include/xrpl/nodestore/Backend.h index 7c3ea57bb8..36fd36ec00 100644 --- a/include/xrpl/nodestore/Backend.h +++ b/include/xrpl/nodestore/Backend.h @@ -77,16 +77,16 @@ public: If the object is not found or an error is encountered, the result will indicate the condition. @note This will be called concurrently. - @param key A pointer to the key data. + @param hash The hash of the object. @param pObject [out] The created object if successful. @return The result of the operation. */ virtual Status - fetch(void const* key, std::shared_ptr* pObject) = 0; + fetch(uint256 const& hash, std::shared_ptr* pObject) = 0; /** Fetch a batch synchronously. */ virtual std::pair>, Status> - fetchBatch(std::vector const& hashes) = 0; + fetchBatch(std::vector const& hashes) = 0; /** Store a single object. Depending on the implementation this may happen immediately diff --git a/src/libxrpl/nodestore/DatabaseNodeImp.cpp b/src/libxrpl/nodestore/DatabaseNodeImp.cpp index 5596cb4853..d1452dba86 100644 --- a/src/libxrpl/nodestore/DatabaseNodeImp.cpp +++ b/src/libxrpl/nodestore/DatabaseNodeImp.cpp @@ -33,7 +33,7 @@ DatabaseNodeImp::fetchNodeObject( try { - status = backend_->fetch(hash.data(), &nodeObject); + status = backend_->fetch(hash, &nodeObject); } catch (std::exception const& e) { @@ -68,18 +68,10 @@ DatabaseNodeImp::fetchBatch(std::vector const& hashes) using namespace std::chrono; auto const before = steady_clock::now(); - std::vector batch{}; - batch.reserve(hashes.size()); - for (size_t i = 0; i < hashes.size(); ++i) - { - auto const& hash = hashes[i]; - batch.push_back(&hash); - } - // Get the node objects that match the hashes from the backend. To protect // against the backends returning fewer or more results than expected, the // container is resized to the number of hashes. - auto results = backend_->fetchBatch(batch).first; + auto results = backend_->fetchBatch(hashes).first; XRPL_ASSERT( results.size() == hashes.size() || results.empty(), "number of output objects either matches number of input hashes or is empty"); diff --git a/src/libxrpl/nodestore/DatabaseRotatingImp.cpp b/src/libxrpl/nodestore/DatabaseRotatingImp.cpp index 26d8c30931..e058fa76ac 100644 --- a/src/libxrpl/nodestore/DatabaseRotatingImp.cpp +++ b/src/libxrpl/nodestore/DatabaseRotatingImp.cpp @@ -105,7 +105,7 @@ DatabaseRotatingImp::fetchNodeObject( std::shared_ptr nodeObject; try { - status = backend->fetch(hash.data(), &nodeObject); + status = backend->fetch(hash, &nodeObject); } catch (std::exception const& e) { diff --git a/src/libxrpl/nodestore/backend/MemoryFactory.cpp b/src/libxrpl/nodestore/backend/MemoryFactory.cpp index 8ac23a0bb6..b11d90610a 100644 --- a/src/libxrpl/nodestore/backend/MemoryFactory.cpp +++ b/src/libxrpl/nodestore/backend/MemoryFactory.cpp @@ -116,10 +116,9 @@ public: //-------------------------------------------------------------------------- Status - fetch(void const* key, std::shared_ptr* pObject) override + fetch(uint256 const& hash, std::shared_ptr* pObject) override { XRPL_ASSERT(db_, "xrpl::NodeStore::MemoryBackend::fetch : non-null database"); - uint256 const hash(uint256::fromVoid(key)); std::lock_guard _(db_->mutex); @@ -134,14 +133,14 @@ public: } std::pair>, Status> - fetchBatch(std::vector const& hashes) override + fetchBatch(std::vector const& hashes) override { std::vector> results; results.reserve(hashes.size()); for (auto const& h : hashes) { std::shared_ptr nObj; - Status status = fetch(h->begin(), &nObj); + Status status = fetch(h, &nObj); if (status != ok) results.push_back({}); else diff --git a/src/libxrpl/nodestore/backend/NuDBFactory.cpp b/src/libxrpl/nodestore/backend/NuDBFactory.cpp index e8efa464af..4d7e7be668 100644 --- a/src/libxrpl/nodestore/backend/NuDBFactory.cpp +++ b/src/libxrpl/nodestore/backend/NuDBFactory.cpp @@ -179,17 +179,17 @@ public: } Status - fetch(void const* key, std::shared_ptr* pno) override + fetch(uint256 const& hash, std::shared_ptr* pno) override { Status status; pno->reset(); nudb::error_code ec; db_.fetch( - key, - [key, pno, &status](void const* data, std::size_t size) { + hash.data(), + [&hash, pno, &status](void const* data, std::size_t size) { nudb::detail::buffer bf; auto const result = nodeobject_decompress(data, size, bf); - DecodedBlob decoded(key, result.first, result.second); + DecodedBlob decoded(hash.data(), result.first, result.second); if (!decoded.wasOk()) { status = dataCorrupt; @@ -207,14 +207,14 @@ public: } std::pair>, Status> - fetchBatch(std::vector const& hashes) override + fetchBatch(std::vector const& hashes) override { std::vector> results; results.reserve(hashes.size()); for (auto const& h : hashes) { std::shared_ptr nObj; - Status status = fetch(h->begin(), &nObj); + Status status = fetch(h, &nObj); if (status != ok) results.push_back({}); else diff --git a/src/libxrpl/nodestore/backend/NullFactory.cpp b/src/libxrpl/nodestore/backend/NullFactory.cpp index 4ecca46a9a..ab5b7d0117 100644 --- a/src/libxrpl/nodestore/backend/NullFactory.cpp +++ b/src/libxrpl/nodestore/backend/NullFactory.cpp @@ -36,13 +36,13 @@ public: } Status - fetch(void const*, std::shared_ptr*) override + fetch(uint256 const&, std::shared_ptr*) override { return notFound; } std::pair>, Status> - fetchBatch(std::vector const& hashes) override + fetchBatch(std::vector const& hashes) override { return {}; } diff --git a/src/libxrpl/nodestore/backend/RocksDBFactory.cpp b/src/libxrpl/nodestore/backend/RocksDBFactory.cpp index c84c5f6982..01bc74f5ed 100644 --- a/src/libxrpl/nodestore/backend/RocksDBFactory.cpp +++ b/src/libxrpl/nodestore/backend/RocksDBFactory.cpp @@ -244,7 +244,7 @@ public: //-------------------------------------------------------------------------- Status - fetch(void const* key, std::shared_ptr* pObject) override + fetch(uint256 const& hash, std::shared_ptr* pObject) override { XRPL_ASSERT(m_db, "xrpl::NodeStore::RocksDBBackend::fetch : non-null database"); pObject->reset(); @@ -252,7 +252,7 @@ public: Status status(ok); rocksdb::ReadOptions const options; - rocksdb::Slice const slice(static_cast(key), m_keyBytes); + rocksdb::Slice const slice(std::bit_cast(hash.data()), m_keyBytes); std::string string; @@ -260,7 +260,7 @@ public: if (getStatus.ok()) { - DecodedBlob decoded(key, string.data(), string.size()); + DecodedBlob decoded(hash.data(), string.data(), string.size()); if (decoded.wasOk()) { @@ -295,14 +295,14 @@ public: } std::pair>, Status> - fetchBatch(std::vector const& hashes) override + fetchBatch(std::vector const& hashes) override { std::vector> results; results.reserve(hashes.size()); for (auto const& h : hashes) { std::shared_ptr nObj; - Status status = fetch(h->begin(), &nObj); + Status status = fetch(h, &nObj); if (status != ok) results.push_back({}); else @@ -332,9 +332,8 @@ public: EncodedBlob encoded(e); wb.Put( - rocksdb::Slice(reinterpret_cast(encoded.getKey()), m_keyBytes), - rocksdb::Slice( - reinterpret_cast(encoded.getData()), encoded.getSize())); + rocksdb::Slice(std::bit_cast(encoded.getKey()), m_keyBytes), + rocksdb::Slice(std::bit_cast(encoded.getData()), encoded.getSize())); } rocksdb::WriteOptions const options; diff --git a/src/test/nodestore/TestBase.h b/src/test/nodestore/TestBase.h index 4a4d21002e..cb2a8e3bd5 100644 --- a/src/test/nodestore/TestBase.h +++ b/src/test/nodestore/TestBase.h @@ -138,7 +138,7 @@ public: { std::shared_ptr object; - Status const status = backend.fetch(batch[i]->getHash().cbegin(), &object); + Status const status = backend.fetch(batch[i]->getHash(), &object); BEAST_EXPECT(status == ok); @@ -158,7 +158,7 @@ public: { std::shared_ptr object; - Status const status = backend.fetch(batch[i]->getHash().cbegin(), &object); + Status const status = backend.fetch(batch[i]->getHash(), &object); BEAST_EXPECT(status == notFound); } diff --git a/src/test/nodestore/Timing_test.cpp b/src/test/nodestore/Timing_test.cpp index dae131e5e7..b537e3abb7 100644 --- a/src/test/nodestore/Timing_test.cpp +++ b/src/test/nodestore/Timing_test.cpp @@ -314,7 +314,7 @@ public: std::shared_ptr obj; std::shared_ptr result; obj = seq1_.obj(dist_(gen_)); - backend_.fetch(obj->getHash().data(), &result); + backend_.fetch(obj->getHash(), &result); suite_.expect(result && isSame(result, obj)); } catch (std::exception const& e) @@ -377,9 +377,9 @@ public: { try { - auto const key = seq2_.key(i); + auto const hash = seq2_.key(i); std::shared_ptr result; - backend_.fetch(key.data(), &result); + backend_.fetch(hash, &result); suite_.expect(!result); } catch (std::exception const& e) @@ -449,9 +449,9 @@ public: { if (rand_(gen_) < missingNodePercent) { - auto const key = seq2_.key(dist_(gen_)); + auto const hash = seq2_.key(dist_(gen_)); std::shared_ptr result; - backend_.fetch(key.data(), &result); + backend_.fetch(hash, &result); suite_.expect(!result); } else @@ -459,7 +459,7 @@ public: std::shared_ptr obj; std::shared_ptr result; obj = seq1_.obj(dist_(gen_)); - backend_.fetch(obj->getHash().data(), &result); + backend_.fetch(obj->getHash(), &result); suite_.expect(result && isSame(result, obj)); } } @@ -540,8 +540,7 @@ public: std::shared_ptr result; auto const j = older_(gen_); obj = seq1_.obj(j); - std::shared_ptr result1; - backend_.fetch(obj->getHash().data(), &result); + backend_.fetch(obj->getHash(), &result); suite_.expect(result != nullptr); suite_.expect(isSame(result, obj)); } @@ -559,7 +558,7 @@ public: std::shared_ptr result; auto const j = recent_(gen_); obj = seq1_.obj(j); - backend_.fetch(obj->getHash().data(), &result); + backend_.fetch(obj->getHash(), &result); suite_.expect(!result || isSame(result, obj)); break; } From 2e595b603118963d209ef81386dfed4a413912c2 Mon Sep 17 00:00:00 2001 From: Alex Kremer Date: Thu, 26 Feb 2026 18:26:58 +0000 Subject: [PATCH 15/41] chore: Enable clang-tidy checks without issues (#6414) This change enables all clang-tidy checks that are already passing. It also modifies the clang-tidy CI job, so it runs against all files if .clang-tidy changed. --- .clang-tidy | 311 +++++++++--------- .../workflows/reusable-clang-tidy-files.yml | 4 +- .github/workflows/reusable-clang-tidy.yml | 14 +- CONTRIBUTING.md | 23 ++ 4 files changed, 196 insertions(+), 156 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index f7009c4666..5f4187b008 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,105 +1,143 @@ --- Checks: "-*, - bugprone-argument-comment + bugprone-argument-comment, + bugprone-assert-side-effect, + bugprone-bad-signal-to-kill-thread, + bugprone-bool-pointer-implicit-conversion, + bugprone-casting-through-void, + bugprone-chained-comparison, + bugprone-compare-pointer-to-member-virtual-function, + bugprone-copy-constructor-init, + bugprone-dangling-handle, + bugprone-dynamic-static-initializers, + bugprone-fold-init-type, + bugprone-forward-declaration-namespace, + bugprone-inaccurate-erase, + bugprone-incorrect-enable-if, + bugprone-incorrect-roundings, + bugprone-infinite-loop, + bugprone-integer-division, + bugprone-lambda-function-name, + bugprone-macro-parentheses, + bugprone-macro-repeated-side-effects, + bugprone-misplaced-operator-in-strlen-in-alloc, + bugprone-misplaced-pointer-arithmetic-in-alloc, + bugprone-misplaced-widening-cast, + bugprone-multi-level-implicit-pointer-conversion, + bugprone-multiple-new-in-one-expression, + bugprone-multiple-statement-macro, + bugprone-no-escape, + bugprone-non-zero-enum-to-bool-conversion, + bugprone-parent-virtual-call, + bugprone-posix-return, + bugprone-redundant-branch-condition, + bugprone-shared-ptr-array-mismatch, + bugprone-signal-handler, + bugprone-signed-char-misuse, + bugprone-sizeof-container, + bugprone-spuriously-wake-up-functions, + bugprone-standalone-empty, + bugprone-string-constructor, + bugprone-string-integer-assignment, + bugprone-string-literal-with-embedded-nul, + bugprone-stringview-nullptr, + bugprone-suspicious-enum-usage, + bugprone-suspicious-include, + bugprone-suspicious-memory-comparison, + bugprone-suspicious-memset-usage, + bugprone-suspicious-realloc-usage, + bugprone-suspicious-semicolon, + bugprone-suspicious-string-compare, + bugprone-swapped-arguments, + bugprone-terminating-continue, + bugprone-throw-keyword-missing, + bugprone-undefined-memory-manipulation, + bugprone-undelegated-constructor, + bugprone-unhandled-exception-at-new, + bugprone-unique-ptr-array-mismatch, + bugprone-unsafe-functions, + bugprone-virtual-near-miss, + cppcoreguidelines-no-suspend-with-lock, + cppcoreguidelines-virtual-class-destructor, + hicpp-ignored-remove-result, + misc-definitions-in-headers, + misc-header-include-cycle, + misc-misplaced-const, + misc-static-assert, + misc-throw-by-value-catch-by-reference, + misc-unused-alias-decls, + misc-unused-using-decls, + readability-duplicate-include, + readability-enum-initial-value, + readability-misleading-indentation, + readability-non-const-parameter, + readability-redundant-declaration, + readability-reference-to-constructed-temporary, + modernize-deprecated-headers, + modernize-make-shared, + modernize-make-unique, + performance-implicit-conversion-in-loop, + performance-move-constructor-init, + performance-trivially-destructible " -# bugprone-assert-side-effect, -# bugprone-bad-signal-to-kill-thread, -# bugprone-bool-pointer-implicit-conversion, -# bugprone-casting-through-void, -# bugprone-chained-comparison, -# bugprone-compare-pointer-to-member-virtual-function, -# bugprone-copy-constructor-init, -# bugprone-crtp-constructor-accessibility, -# bugprone-dangling-handle, -# bugprone-dynamic-static-initializers, +# --- +# checks that have some issues that need to be resolved: +# # bugprone-empty-catch, -# bugprone-fold-init-type, -# bugprone-forward-declaration-namespace, -# bugprone-inaccurate-erase, +# bugprone-crtp-constructor-accessibility, # bugprone-inc-dec-in-conditions, -# bugprone-incorrect-enable-if, -# bugprone-incorrect-roundings, -# bugprone-infinite-loop, -# bugprone-integer-division, -# bugprone-lambda-function-name, -# bugprone-macro-parentheses, -# bugprone-macro-repeated-side-effects, -# bugprone-misplaced-operator-in-strlen-in-alloc, -# bugprone-misplaced-pointer-arithmetic-in-alloc, -# bugprone-misplaced-widening-cast, -# bugprone-move-forwarding-reference, -# bugprone-multi-level-implicit-pointer-conversion, -# bugprone-multiple-new-in-one-expression, -# bugprone-multiple-statement-macro, -# bugprone-no-escape, -# bugprone-non-zero-enum-to-bool-conversion, -# bugprone-optional-value-conversion, -# bugprone-parent-virtual-call, -# bugprone-pointer-arithmetic-on-polymorphic-object, -# bugprone-posix-return, -# bugprone-redundant-branch-condition, # bugprone-reserved-identifier, -# bugprone-return-const-ref-from-parameter, -# bugprone-shared-ptr-array-mismatch, -# bugprone-signal-handler, -# bugprone-signed-char-misuse, -# bugprone-sizeof-container, -# bugprone-sizeof-expression, -# bugprone-spuriously-wake-up-functions, -# bugprone-standalone-empty, -# bugprone-string-constructor, -# bugprone-string-integer-assignment, -# bugprone-string-literal-with-embedded-nul, -# bugprone-stringview-nullptr, -# bugprone-suspicious-enum-usage, -# bugprone-suspicious-include, -# bugprone-suspicious-memory-comparison, -# bugprone-suspicious-memset-usage, -# bugprone-suspicious-missing-comma, -# bugprone-suspicious-realloc-usage, -# bugprone-suspicious-semicolon, -# bugprone-suspicious-string-compare, -# bugprone-suspicious-stringview-data-usage, -# bugprone-swapped-arguments, -# bugprone-switch-missing-default-case, -# bugprone-terminating-continue, -# bugprone-throw-keyword-missing, -# bugprone-too-small-loop-variable, -# bugprone-undefined-memory-manipulation, -# bugprone-undelegated-constructor, -# bugprone-unhandled-exception-at-new, -# bugprone-unhandled-self-assignment, -# bugprone-unique-ptr-array-mismatch, -# bugprone-unsafe-functions, +# bugprone-move-forwarding-reference, # bugprone-unused-local-non-trivial-variable, -# bugprone-unused-raii, +# bugprone-return-const-ref-from-parameter, +# bugprone-switch-missing-default-case, +# bugprone-sizeof-expression, +# bugprone-suspicious-stringview-data-usage, +# bugprone-suspicious-missing-comma, +# bugprone-pointer-arithmetic-on-polymorphic-object, +# bugprone-optional-value-conversion, +# bugprone-too-small-loop-variable, # bugprone-unused-return-value, # bugprone-use-after-move, -# bugprone-virtual-near-miss, -# cppcoreguidelines-init-variables, +# bugprone-unhandled-self-assignment, +# bugprone-unused-raii, +# # cppcoreguidelines-misleading-capture-default-by-value, -# cppcoreguidelines-no-suspend-with-lock, +# cppcoreguidelines-init-variables, # cppcoreguidelines-pro-type-member-init, # cppcoreguidelines-pro-type-static-cast-downcast, -# cppcoreguidelines-rvalue-reference-param-not-moved, # cppcoreguidelines-use-default-member-init, -# cppcoreguidelines-virtual-class-destructor, -# hicpp-ignored-remove-result, +# cppcoreguidelines-rvalue-reference-param-not-moved, +# # llvm-namespace-comment, # misc-const-correctness, -# misc-definitions-in-headers, -# misc-header-include-cycle, # misc-include-cleaner, -# misc-misplaced-const, # misc-redundant-expression, -# misc-static-assert, -# misc-throw-by-value-catch-by-reference, -# misc-unused-alias-decls, -# misc-unused-using-decls, +# +# readability-avoid-nested-conditional-operator, +# readability-avoid-return-with-void-value, +# readability-braces-around-statements, +# readability-container-contains, +# readability-container-size-empty, +# readability-convert-member-functions-to-static, +# readability-const-return-type, +# readability-else-after-return, +# readability-implicit-bool-conversion, +# readability-inconsistent-declaration-parameter-name, +# readability-identifier-naming, +# readability-make-member-function-const, +# readability-math-missing-parentheses, +# readability-redundant-inline-specifier, +# readability-redundant-member-init, +# readability-redundant-casting, +# readability-redundant-string-init, +# readability-simplify-boolean-expr, +# readability-static-definition-in-anonymous-namespace, +# readability-suspicious-call-argument, +# readability-use-std-min-max, +# readability-static-accessed-through-instance, +# # modernize-concat-nested-namespaces, -# modernize-deprecated-headers, -# modernize-make-shared, -# modernize-make-unique, # modernize-pass-by-value, # modernize-type-traits, # modernize-use-designated-initializers, @@ -111,79 +149,50 @@ Checks: "-*, # modernize-use-starts-ends-with, # modernize-use-std-numbers, # modernize-use-using, +# # performance-faster-string-find, # performance-for-range-copy, -# performance-implicit-conversion-in-loop, # performance-inefficient-vector-operation, # performance-move-const-arg, -# performance-move-constructor-init, # performance-no-automatic-move, -# performance-trivially-destructible, -# readability-avoid-nested-conditional-operator, -# readability-avoid-return-with-void-value, -# readability-braces-around-statements, -# readability-const-return-type, -# readability-container-contains, -# readability-container-size-empty, -# readability-convert-member-functions-to-static, -# readability-duplicate-include, -# readability-else-after-return, -# readability-enum-initial-value, -# readability-implicit-bool-conversion, -# readability-inconsistent-declaration-parameter-name, -# readability-identifier-naming, -# readability-make-member-function-const, -# readability-math-missing-parentheses, -# readability-misleading-indentation, -# readability-non-const-parameter, -# readability-redundant-casting, -# readability-redundant-declaration, -# readability-redundant-inline-specifier, -# readability-redundant-member-init, -# readability-redundant-string-init, -# readability-reference-to-constructed-temporary, -# readability-simplify-boolean-expr, -# readability-static-accessed-through-instance, -# readability-static-definition-in-anonymous-namespace, -# readability-suspicious-call-argument, -# readability-use-std-min-max +# --- # -# CheckOptions: -# readability-braces-around-statements.ShortStatementLines: 2 -# readability-identifier-naming.MacroDefinitionCase: UPPER_CASE -# readability-identifier-naming.ClassCase: CamelCase -# readability-identifier-naming.StructCase: CamelCase -# readability-identifier-naming.UnionCase: CamelCase -# readability-identifier-naming.EnumCase: CamelCase -# readability-identifier-naming.EnumConstantCase: CamelCase -# readability-identifier-naming.ScopedEnumConstantCase: CamelCase -# readability-identifier-naming.GlobalConstantCase: UPPER_CASE -# readability-identifier-naming.GlobalConstantPrefix: "k" -# readability-identifier-naming.GlobalVariableCase: CamelCase -# readability-identifier-naming.GlobalVariablePrefix: "g" -# readability-identifier-naming.ConstexprFunctionCase: camelBack -# readability-identifier-naming.ConstexprMethodCase: camelBack -# readability-identifier-naming.ClassMethodCase: camelBack -# readability-identifier-naming.ClassMemberCase: camelBack -# readability-identifier-naming.ClassConstantCase: UPPER_CASE -# readability-identifier-naming.ClassConstantPrefix: "k" -# readability-identifier-naming.StaticConstantCase: UPPER_CASE -# readability-identifier-naming.StaticConstantPrefix: "k" -# readability-identifier-naming.StaticVariableCase: UPPER_CASE -# readability-identifier-naming.StaticVariablePrefix: "k" -# readability-identifier-naming.ConstexprVariableCase: UPPER_CASE -# readability-identifier-naming.ConstexprVariablePrefix: "k" -# readability-identifier-naming.LocalConstantCase: camelBack -# readability-identifier-naming.LocalVariableCase: camelBack -# readability-identifier-naming.TemplateParameterCase: CamelCase -# readability-identifier-naming.ParameterCase: camelBack -# readability-identifier-naming.FunctionCase: camelBack -# readability-identifier-naming.MemberCase: camelBack -# readability-identifier-naming.PrivateMemberSuffix: _ -# readability-identifier-naming.ProtectedMemberSuffix: _ -# readability-identifier-naming.PublicMemberSuffix: "" -# readability-identifier-naming.FunctionIgnoredRegexp: ".*tag_invoke.*" -# bugprone-unsafe-functions.ReportMoreUnsafeFunctions: true +CheckOptions: + # readability-braces-around-statements.ShortStatementLines: 2 + # readability-identifier-naming.MacroDefinitionCase: UPPER_CASE + # readability-identifier-naming.ClassCase: CamelCase + # readability-identifier-naming.StructCase: CamelCase + # readability-identifier-naming.UnionCase: CamelCase + # readability-identifier-naming.EnumCase: CamelCase + # readability-identifier-naming.EnumConstantCase: CamelCase + # readability-identifier-naming.ScopedEnumConstantCase: CamelCase + # readability-identifier-naming.GlobalConstantCase: UPPER_CASE + # readability-identifier-naming.GlobalConstantPrefix: "k" + # readability-identifier-naming.GlobalVariableCase: CamelCase + # readability-identifier-naming.GlobalVariablePrefix: "g" + # readability-identifier-naming.ConstexprFunctionCase: camelBack + # readability-identifier-naming.ConstexprMethodCase: camelBack + # readability-identifier-naming.ClassMethodCase: camelBack + # readability-identifier-naming.ClassMemberCase: camelBack + # readability-identifier-naming.ClassConstantCase: UPPER_CASE + # readability-identifier-naming.ClassConstantPrefix: "k" + # readability-identifier-naming.StaticConstantCase: UPPER_CASE + # readability-identifier-naming.StaticConstantPrefix: "k" + # readability-identifier-naming.StaticVariableCase: UPPER_CASE + # readability-identifier-naming.StaticVariablePrefix: "k" + # readability-identifier-naming.ConstexprVariableCase: UPPER_CASE + # readability-identifier-naming.ConstexprVariablePrefix: "k" + # readability-identifier-naming.LocalConstantCase: camelBack + # readability-identifier-naming.LocalVariableCase: camelBack + # readability-identifier-naming.TemplateParameterCase: CamelCase + # readability-identifier-naming.ParameterCase: camelBack + # readability-identifier-naming.FunctionCase: camelBack + # readability-identifier-naming.MemberCase: camelBack + # readability-identifier-naming.PrivateMemberSuffix: _ + # readability-identifier-naming.ProtectedMemberSuffix: _ + # readability-identifier-naming.PublicMemberSuffix: "" + # readability-identifier-naming.FunctionIgnoredRegexp: ".*tag_invoke.*" + bugprone-unsafe-functions.ReportMoreUnsafeFunctions: true # bugprone-unused-return-value.CheckedReturnTypes: ::std::error_code;::std::error_condition;::std::errc # misc-include-cleaner.IgnoreHeaders: '.*/(detail|impl)/.*;.*(expected|unexpected).*;.*ranges_lower_bound\.h;time.h;stdlib.h;__chrono/.*;fmt/chrono.h;boost/uuid/uuid_hash.hpp' # diff --git a/.github/workflows/reusable-clang-tidy-files.yml b/.github/workflows/reusable-clang-tidy-files.yml index 432da1d15c..d36dea747c 100644 --- a/.github/workflows/reusable-clang-tidy-files.yml +++ b/.github/workflows/reusable-clang-tidy-files.yml @@ -78,9 +78,9 @@ jobs: id: run_clang_tidy continue-on-error: true env: - FILES: ${{ inputs.files }} + TARGETS: ${{ inputs.files != '' && inputs.files || 'src tests' }} run: | - run-clang-tidy -j ${{ steps.nproc.outputs.nproc }} -p "$BUILD_DIR" $FILES 2>&1 | tee clang-tidy-output.txt + run-clang-tidy -j ${{ steps.nproc.outputs.nproc }} -p "${BUILD_DIR}" ${TARGETS} 2>&1 | tee clang-tidy-output.txt - name: Upload clang-tidy output if: steps.run_clang_tidy.outcome != 'success' diff --git a/.github/workflows/reusable-clang-tidy.yml b/.github/workflows/reusable-clang-tidy.yml index 7c300ee26e..7050d3509f 100644 --- a/.github/workflows/reusable-clang-tidy.yml +++ b/.github/workflows/reusable-clang-tidy.yml @@ -22,7 +22,8 @@ jobs: if: ${{ inputs.check_only_changed }} runs-on: ubuntu-latest outputs: - any_changed: ${{ steps.changed_files.outputs.any_changed }} + clang_tidy_config_changed: ${{ steps.changed_clang_tidy.outputs.any_changed }} + any_cpp_changed: ${{ steps.changed_files.outputs.any_changed }} all_changed_files: ${{ steps.changed_files.outputs.all_changed_files }} steps: - name: Checkout repository @@ -38,10 +39,17 @@ jobs: **/*.ipp separator: " " + - name: Get changed clang-tidy configuration + id: changed_clang_tidy + uses: tj-actions/changed-files@7dee1b0c1557f278e5c7dc244927139d78c0e22a # v47.0.4 + with: + files: | + .clang-tidy + run-clang-tidy: needs: [determine-files] - if: ${{ always() && !cancelled() && (!inputs.check_only_changed || needs.determine-files.outputs.any_changed == 'true') }} + if: ${{ always() && !cancelled() && (!inputs.check_only_changed || needs.determine-files.outputs.any_cpp_changed == 'true' || needs.determine-files.outputs.clang_tidy_config_changed == 'true') }} uses: ./.github/workflows/reusable-clang-tidy-files.yml with: - files: ${{ inputs.check_only_changed && needs.determine-files.outputs.all_changed_files || '' }} + files: ${{ (needs.determine-files.outputs.clang_tidy_config_changed == 'true' && '') || (inputs.check_only_changed && needs.determine-files.outputs.all_changed_files || '') }} create_issue_on_failure: ${{ inputs.create_issue_on_failure }} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a928065ef2..4bb1db8689 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -251,6 +251,29 @@ pip3 install pre-commit pre-commit install ``` +## Clang-tidy + +All code must pass `clang-tidy` checks according to the settings in [`.clang-tidy`](./.clang-tidy). + +There is a Continuous Integration job that runs clang-tidy on pull requests. The CI will check: + +- All changed C++ files (`.cpp`, `.h`, `.ipp`) when only code files are modified +- **All files in the repository** when the `.clang-tidy` configuration file is changed + +This ensures that configuration changes don't introduce new warnings across the codebase. + +### Running clang-tidy locally + +Before running clang-tidy, you must build the project to generate required files (particularly protobuf headers). Refer to [`BUILD.md`](./BUILD.md) for build instructions. + +Then run clang-tidy on your local changes: + +``` +run-clang-tidy -p build src tests +``` + +This will check all source files in the `src` and `tests` directories using the compile commands from your `build` directory. + ## Contracts and instrumentation We are using [Antithesis](https://antithesis.com/) for continuous fuzzing, From 404f35d5568f60d914b9f2749fff9518cd1324cb Mon Sep 17 00:00:00 2001 From: Mayukha Vadari Date: Thu, 26 Feb 2026 22:01:38 -0500 Subject: [PATCH 16/41] test: Grep for failures in CI (#6339) This change adjusts the CI tests to make it easier to spot errors, without needing to sift through the thousands of lines of output. --- .github/workflows/reusable-build-test-config.yml | 15 ++++++++++++++- src/test/app/Vault_test.cpp | 8 ++++---- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/.github/workflows/reusable-build-test-config.yml b/.github/workflows/reusable-build-test-config.yml index 6060a208fe..dabcc737f8 100644 --- a/.github/workflows/reusable-build-test-config.yml +++ b/.github/workflows/reusable-build-test-config.yml @@ -229,8 +229,21 @@ jobs: env: BUILD_NPROC: ${{ steps.nproc.outputs.nproc }} run: | - ./xrpld --unittest --unittest-jobs "${BUILD_NPROC}" + set -o pipefail + ./xrpld --unittest --unittest-jobs "${BUILD_NPROC}" 2>&1 | tee unittest.log + - name: Show test failure summary + if: ${{ failure() && !inputs.build_only }} + working-directory: ${{ runner.os == 'Windows' && format('{0}/{1}', env.BUILD_DIR, inputs.build_type) || env.BUILD_DIR }} + run: | + if [ ! -f unittest.log ]; then + echo "unittest.log not found; embedded tests may not have run." + exit 0 + fi + + if ! grep -E "failed" unittest.log; then + echo "Log present but no failure lines found in unittest.log." + fi - name: Debug failure (Linux) if: ${{ failure() && runner.os == 'Linux' && !inputs.build_only }} run: | diff --git a/src/test/app/Vault_test.cpp b/src/test/app/Vault_test.cpp index 93ac94d7ce..7ae9faf18f 100644 --- a/src/test/app/Vault_test.cpp +++ b/src/test/app/Vault_test.cpp @@ -5340,20 +5340,20 @@ class Vault_test : public beast::unit_test::suite env.close(); // 2. Mantissa larger than uint64 max + env.set_parse_failure_expected(true); try { tx[sfAssetsMaximum] = "18446744073709551617e5"; // uint64 max + 1 env(tx, THISLINE); - BEAST_EXPECT(false); + BEAST_EXPECTS(false, "Expected parse_error for mantissa larger than uint64 max"); } catch (parse_error const& e) { using namespace std::string_literals; BEAST_EXPECT( - e.what() == - "invalidParamsField 'tx_json.AssetsMaximum' has invalid " - "data."s); + e.what() == "invalidParamsField 'tx_json.AssetsMaximum' has invalid data."s); } + env.set_parse_failure_expected(false); } } From b58c681189d361fa3bca9f537d1e732aa2369467 Mon Sep 17 00:00:00 2001 From: Sergey Kuznetsov Date: Fri, 27 Feb 2026 18:36:10 +0000 Subject: [PATCH 17/41] chore: Make nix hook optional (#6431) This change makes the `nix` pre-commit hook optional in development environments, and enforced only inside Github Actions. --- .github/workflows/pre-commit.yml | 2 +- .pre-commit-config.yaml | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 7793d1e3ab..54a84a426a 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -11,7 +11,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@320be44621ca2a080f05aeb15817c44b84518108 + uses: XRPLF/actions/.github/workflows/pre-commit.yml@56de1bdf19639e009639a50b8d17c28ca954f267 with: runs_on: ubuntu-latest container: '{ "image": "ghcr.io/xrplf/ci/tools-rippled-pre-commit:sha-41ec7c1" }' diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6e04c752e9..c17eb92787 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -61,7 +61,15 @@ repos: hooks: - id: nix-fmt name: Format Nix files - entry: nix --extra-experimental-features 'nix-command flakes' fmt + entry: | + bash -c ' + if command -v nix &> /dev/null || [ "$GITHUB_ACTIONS" = "true" ]; then + nix --extra-experimental-features "nix-command flakes" fmt "$@" + else + echo "Skipping nix-fmt: nix not installed and not in GitHub Actions" + exit 0 + fi + ' -- language: system types: - nix From 1a7f824b8944b3cf92cd86a8f6049337a6fac6c3 Mon Sep 17 00:00:00 2001 From: Vito Tumas <5780819+Tapanito@users.noreply.github.com> Date: Fri, 27 Feb 2026 22:02:39 +0100 Subject: [PATCH 18/41] refactor: Splits invariant checks into multiple classes (#6440) The invariant check system had grown into a single monolithic file pair containing 24 invariant checker classes. The large `InvariantCheck.cpp` file was a frequent source of merge conflicts and difficult to navigate. This refactoring improves maintainability and readability with zero behavioral changes. In particular, this change: - Splits `InvariantCheck.h` and `InvariantCheck.cpp` into 10 focused header/source pairs organized by domain under a new `invariants/` subdirectory. - Extracts the shared `Privilege` enum and `hasPrivilege()` function into a dedicated `InvariantCheckPrivilege.h` header, so domain-specific files can reference them independently. --- include/xrpl/tx/InvariantCheck.h | 732 ---- include/xrpl/tx/invariants/AMMInvariant.h | 53 + include/xrpl/tx/invariants/FreezeInvariant.h | 84 + include/xrpl/tx/invariants/InvariantCheck.h | 385 ++ .../tx/invariants/InvariantCheckPrivilege.h | 60 + include/xrpl/tx/invariants/LoanInvariant.h | 75 + include/xrpl/tx/invariants/MPTInvariant.h | 31 + include/xrpl/tx/invariants/NFTInvariant.h | 70 + .../tx/invariants/PermissionedDEXInvariant.h | 25 + .../invariants/PermissionedDomainInvariant.h | 41 + include/xrpl/tx/invariants/VaultInvariant.h | 77 + src/libxrpl/tx/ApplyContext.cpp | 5 +- src/libxrpl/tx/InvariantCheck.cpp | 3483 ----------------- src/libxrpl/tx/invariants/AMMInvariant.cpp | 305 ++ src/libxrpl/tx/invariants/FreezeInvariant.cpp | 278 ++ src/libxrpl/tx/invariants/InvariantCheck.cpp | 1009 +++++ src/libxrpl/tx/invariants/LoanInvariant.cpp | 278 ++ src/libxrpl/tx/invariants/MPTInvariant.cpp | 192 + src/libxrpl/tx/invariants/NFTInvariant.cpp | 274 ++ .../invariants/PermissionedDEXInvariant.cpp | 93 + .../PermissionedDomainInvariant.cpp | 162 + src/libxrpl/tx/invariants/VaultInvariant.cpp | 926 +++++ .../PermissionedDomainSet.cpp | 5 +- 23 files changed, 4423 insertions(+), 4220 deletions(-) delete mode 100644 include/xrpl/tx/InvariantCheck.h create mode 100644 include/xrpl/tx/invariants/AMMInvariant.h create mode 100644 include/xrpl/tx/invariants/FreezeInvariant.h create mode 100644 include/xrpl/tx/invariants/InvariantCheck.h create mode 100644 include/xrpl/tx/invariants/InvariantCheckPrivilege.h create mode 100644 include/xrpl/tx/invariants/LoanInvariant.h create mode 100644 include/xrpl/tx/invariants/MPTInvariant.h create mode 100644 include/xrpl/tx/invariants/NFTInvariant.h create mode 100644 include/xrpl/tx/invariants/PermissionedDEXInvariant.h create mode 100644 include/xrpl/tx/invariants/PermissionedDomainInvariant.h create mode 100644 include/xrpl/tx/invariants/VaultInvariant.h delete mode 100644 src/libxrpl/tx/InvariantCheck.cpp create mode 100644 src/libxrpl/tx/invariants/AMMInvariant.cpp create mode 100644 src/libxrpl/tx/invariants/FreezeInvariant.cpp create mode 100644 src/libxrpl/tx/invariants/InvariantCheck.cpp create mode 100644 src/libxrpl/tx/invariants/LoanInvariant.cpp create mode 100644 src/libxrpl/tx/invariants/MPTInvariant.cpp create mode 100644 src/libxrpl/tx/invariants/NFTInvariant.cpp create mode 100644 src/libxrpl/tx/invariants/PermissionedDEXInvariant.cpp create mode 100644 src/libxrpl/tx/invariants/PermissionedDomainInvariant.cpp create mode 100644 src/libxrpl/tx/invariants/VaultInvariant.cpp diff --git a/include/xrpl/tx/InvariantCheck.h b/include/xrpl/tx/InvariantCheck.h deleted file mode 100644 index dc42f9d38c..0000000000 --- a/include/xrpl/tx/InvariantCheck.h +++ /dev/null @@ -1,732 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -namespace xrpl { - -class ReadView; - -#if GENERATING_DOCS -/** - * @brief Prototype for invariant check implementations. - * - * __THIS CLASS DOES NOT EXIST__ - or rather it exists in documentation only to - * communicate the interface required of any invariant checker. Any invariant - * check implementation should implement the public methods documented here. - * - */ -class InvariantChecker_PROTOTYPE -{ -public: - explicit InvariantChecker_PROTOTYPE() = default; - - /** - * @brief called for each ledger entry in the current transaction. - * - * @param isDelete true if the SLE is being deleted - * @param before ledger entry before modification by the transaction - * @param after ledger entry after modification by the transaction - */ - void - visitEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after); - - /** - * @brief called after all ledger entries have been visited to determine - * the final status of the check - * - * @param tx the transaction being applied - * @param tec the current TER result of the transaction - * @param fee the fee actually charged for this transaction - * @param view a ReadView of the ledger being modified - * @param j journal for logging - * - * @return true if check passes, false if it fails - */ - bool - finalize( - STTx const& tx, - TER const tec, - XRPAmount const fee, - ReadView const& view, - beast::Journal const& j); -}; -#endif - -/** - * @brief Invariant: We should never charge a transaction a negative fee or a - * fee that is larger than what the transaction itself specifies. - * - * We can, in some circumstances, charge less. - */ -class TransactionFeeCheck -{ -public: - void - visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); - - bool - finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); -}; - -/** - * @brief Invariant: A transaction must not create XRP and should only destroy - * the XRP fee. - * - * We iterate through all account roots, payment channels and escrow entries - * that were modified and calculate the net change in XRP caused by the - * transactions. - */ -class XRPNotCreated -{ - std::int64_t drops_ = 0; - -public: - void - visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); - - bool - finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); -}; - -/** - * @brief Invariant: we cannot remove an account ledger entry - * - * We iterate all account roots that were modified, and ensure that any that - * were present before the transaction was applied continue to be present - * afterwards unless they were explicitly deleted by a successful - * AccountDelete transaction. - */ -class AccountRootsNotDeleted -{ - std::uint32_t accountsDeleted_ = 0; - -public: - void - visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); - - bool - finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); -}; - -/** - * @brief Invariant: a deleted account must not have any objects left - * - * We iterate all deleted account roots, and ensure that there are no - * objects left that are directly accessible with that account's ID. - * - * There should only be one deleted account, but that's checked by - * AccountRootsNotDeleted. This invariant will handle multiple deleted account - * roots without a problem. - */ -class AccountRootsDeletedClean -{ - // Pair is . Before is used for most of the checks, so that - // if, for example, an object ID field is cleared, but the object is not - // deleted, it can still be found. After is used specifically for any checks - // that are expected as part of the deletion, such as zeroing out the - // balance. - std::vector, std::shared_ptr>> accountsDeleted_; - -public: - void - visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); - - bool - finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); -}; - -/** - * @brief Invariant: An account XRP balance must be in XRP and take a value - * between 0 and INITIAL_XRP drops, inclusive. - * - * We iterate all account roots modified by the transaction and ensure that - * their XRP balances are reasonable. - */ -class XRPBalanceChecks -{ - bool bad_ = false; - -public: - void - visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); - - bool - finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); -}; - -/** - * @brief Invariant: corresponding modified ledger entries should match in type - * and added entries should be a valid type. - */ -class LedgerEntryTypesMatch -{ - bool typeMismatch_ = false; - bool invalidTypeAdded_ = false; - -public: - void - visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); - - bool - finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); -}; - -/** - * @brief Invariant: Trust lines using XRP are not allowed. - * - * We iterate all the trust lines created by this transaction and ensure - * that they are against a valid issuer. - */ -class NoXRPTrustLines -{ - bool xrpTrustLine_ = false; - -public: - void - visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); - - bool - finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); -}; - -/** - * @brief Invariant: Trust lines with deep freeze flag are not allowed if normal - * freeze flag is not set. - * - * We iterate all the trust lines created by this transaction and ensure - * that they don't have deep freeze flag set without normal freeze flag set. - */ -class NoDeepFreezeTrustLinesWithoutFreeze -{ - bool deepFreezeWithoutFreeze_ = false; - -public: - void - visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); - - bool - finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); -}; - -/** - * @brief Invariant: frozen trust line balance change is not allowed. - * - * We iterate all affected trust lines and ensure that they don't have - * unexpected change of balance if they're frozen. - */ -class TransfersNotFrozen -{ - struct BalanceChange - { - std::shared_ptr const line; - int const balanceChangeSign; - }; - - struct IssuerChanges - { - std::vector senders; - std::vector receivers; - }; - - using ByIssuer = std::map; - ByIssuer balanceChanges_; - - std::map const> possibleIssuers_; - -public: - void - visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); - - bool - finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); - -private: - bool - isValidEntry(std::shared_ptr const& before, std::shared_ptr const& after); - - STAmount - calculateBalanceChange( - std::shared_ptr const& before, - std::shared_ptr const& after, - bool isDelete); - - void - recordBalance(Issue const& issue, BalanceChange change); - - void - recordBalanceChanges(std::shared_ptr const& after, STAmount const& balanceChange); - - std::shared_ptr - findIssuer(AccountID const& issuerID, ReadView const& view); - - bool - validateIssuerChanges( - std::shared_ptr const& issuer, - IssuerChanges const& changes, - STTx const& tx, - beast::Journal const& j, - bool enforce); - - bool - validateFrozenState( - BalanceChange const& change, - bool high, - STTx const& tx, - beast::Journal const& j, - bool enforce, - bool globalFreeze); -}; - -/** - * @brief Invariant: offers should be for non-negative amounts and must not - * be XRP to XRP. - * - * Examine all offers modified by the transaction and ensure that there are - * no offers which contain negative amounts or which exchange XRP for XRP. - */ -class NoBadOffers -{ - bool bad_ = false; - -public: - void - visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); - - bool - finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); -}; - -/** - * @brief Invariant: an escrow entry must take a value between 0 and - * INITIAL_XRP drops exclusive. - */ -class NoZeroEscrow -{ - bool bad_ = false; - -public: - void - visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); - - bool - finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); -}; - -/** - * @brief Invariant: a new account root must be the consequence of a payment, - * must have the right starting sequence, and the payment - * may not create more than one new account root. - */ -class ValidNewAccountRoot -{ - std::uint32_t accountsCreated_ = 0; - std::uint32_t accountSeq_ = 0; - bool pseudoAccount_ = false; - std::uint32_t flags_ = 0; - -public: - void - visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); - - bool - finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); -}; - -/** - * @brief Invariant: Validates several invariants for NFToken pages. - * - * The following checks are made: - * - The page is correctly associated with the owner. - * - The page is correctly ordered between the next and previous links. - * - The page contains at least one and no more than 32 NFTokens. - * - The NFTokens on this page do not belong on a lower or higher page. - * - The NFTokens are correctly sorted on the page. - * - Each URI, if present, is not empty. - */ -class ValidNFTokenPage -{ - bool badEntry_ = false; - bool badLink_ = false; - bool badSort_ = false; - bool badURI_ = false; - bool invalidSize_ = false; - bool deletedFinalPage_ = false; - bool deletedLink_ = false; - -public: - void - visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); - - bool - finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); -}; - -/** - * @brief Invariant: Validates counts of NFTokens after all transaction types. - * - * The following checks are made: - * - The number of minted or burned NFTokens can only be changed by - * NFTokenMint or NFTokenBurn transactions. - * - A successful NFTokenMint must increase the number of NFTokens. - * - A failed NFTokenMint must not change the number of minted NFTokens. - * - An NFTokenMint transaction cannot change the number of burned NFTokens. - * - A successful NFTokenBurn must increase the number of burned NFTokens. - * - A failed NFTokenBurn must not change the number of burned NFTokens. - * - An NFTokenBurn transaction cannot change the number of minted NFTokens. - */ -class NFTokenCountTracking -{ - std::uint32_t beforeMintedTotal = 0; - std::uint32_t beforeBurnedTotal = 0; - std::uint32_t afterMintedTotal = 0; - std::uint32_t afterBurnedTotal = 0; - -public: - void - visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); - - bool - finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); -}; - -/** - * @brief Invariant: Token holder's trustline balance cannot be negative after - * Clawback. - * - * We iterate all the trust lines affected by this transaction and ensure - * that no more than one trustline is modified, and also holder's balance is - * non-negative. - */ -class ValidClawback -{ - std::uint32_t trustlinesChanged = 0; - std::uint32_t mptokensChanged = 0; - -public: - void - visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); - - bool - finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); -}; - -class ValidMPTIssuance -{ - std::uint32_t mptIssuancesCreated_ = 0; - std::uint32_t mptIssuancesDeleted_ = 0; - - std::uint32_t mptokensCreated_ = 0; - std::uint32_t mptokensDeleted_ = 0; - // non-MPT transactions may attempt to create - // MPToken by an issuer - bool mptCreatedByIssuer_ = false; - -public: - void - visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); - - bool - finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); -}; - -/** - * @brief Invariants: Permissioned Domains must have some rules and - * AcceptedCredentials must have length between 1 and 10 inclusive. - * - * Since only permissions constitute rules, an empty credentials list - * means that there are no rules and the invariant is violated. - * - * Credentials must be sorted and no duplicates allowed - * - */ -class ValidPermissionedDomain -{ - struct SleStatus - { - std::size_t credentialsSize_{0}; - bool isSorted_ = false; - bool isUnique_ = false; - bool isDelete_ = false; - }; - std::vector sleStatus_; - -public: - void - visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); - - bool - finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); -}; - -/** - * @brief Invariants: Pseudo-accounts have valid and consistent properties - * - * Pseudo-accounts have certain properties, and some of those properties are - * unique to pseudo-accounts. Check that all pseudo-accounts are following the - * rules, and that only pseudo-accounts look like pseudo-accounts. - * - */ -class ValidPseudoAccounts -{ - std::vector errors_; - -public: - void - visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); - - bool - finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); -}; - -class ValidPermissionedDEX -{ - bool regularOffers_ = false; - bool badHybrids_ = false; - hash_set domains_; - -public: - void - visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); - - bool - finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); -}; - -class ValidAMM -{ - std::optional ammAccount_; - std::optional lptAMMBalanceAfter_; - std::optional lptAMMBalanceBefore_; - bool ammPoolChanged_; - -public: - enum class ZeroAllowed : bool { No = false, Yes = true }; - - ValidAMM() : ammPoolChanged_{false} - { - } - void - visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); - - bool - finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); - -private: - bool - finalizeBid(bool enforce, beast::Journal const&) const; - bool - finalizeVote(bool enforce, beast::Journal const&) const; - bool - finalizeCreate(STTx const&, ReadView const&, bool enforce, beast::Journal const&) const; - bool - finalizeDelete(bool enforce, TER res, beast::Journal const&) const; - bool - finalizeDeposit(STTx const&, ReadView const&, bool enforce, beast::Journal const&) const; - // Includes clawback - bool - finalizeWithdraw(STTx const&, ReadView const&, bool enforce, beast::Journal const&) const; - bool - finalizeDEX(bool enforce, beast::Journal const&) const; - bool - generalInvariant(STTx const&, ReadView const&, ZeroAllowed zeroAllowed, beast::Journal const&) - const; -}; - -/** - * @brief Invariants: Some fields are unmodifiable - * - * Check that any fields specified as unmodifiable are not modified when the - * object is modified. Creation and deletion are ignored. - * - */ -class NoModifiedUnmodifiableFields -{ - // Pair is . - std::set> changedEntries_; - -public: - void - visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); - - bool - finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); -}; - -/** - * @brief Invariants: Loan brokers are internally consistent - * - * 1. If `LoanBroker.OwnerCount = 0` the `DirectoryNode` will have at most one - * node (the root), which will only hold entries for `RippleState` or - * `MPToken` objects. - * - */ -class ValidLoanBroker -{ - // Not all of these elements will necessarily be populated. Remaining items - // will be looked up as needed. - struct BrokerInfo - { - SLE::const_pointer brokerBefore = nullptr; - // After is used for most of the checks, except - // those that check changed values. - SLE::const_pointer brokerAfter = nullptr; - }; - // Collect all the LoanBrokers found directly or indirectly through - // pseudo-accounts. Key is the brokerID / index. It will be used to find the - // LoanBroker object if brokerBefore and brokerAfter are nullptr - std::map brokers_; - // Collect all the modified trust lines. Their high and low accounts will be - // loaded to look for LoanBroker pseudo-accounts. - std::vector lines_; - // Collect all the modified MPTokens. Their accounts will be loaded to look - // for LoanBroker pseudo-accounts. - std::vector mpts_; - - bool - goodZeroDirectory(ReadView const& view, SLE::const_ref dir, beast::Journal const& j) const; - -public: - void - visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); - - bool - finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); -}; - -/** - * @brief Invariants: Loans are internally consistent - * - * 1. If `Loan.PaymentRemaining = 0` then `Loan.PrincipalOutstanding = 0` - * - */ -class ValidLoan -{ - // Pair is . After is used for most of the checks, except - // those that check changed values. - std::vector> loans_; - -public: - void - visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); - - bool - finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); -}; - -/* - * @brief Invariants: Vault object and MPTokenIssuance for vault shares - * - * - vault deleted and vault created is empty - * - vault created must be linked to pseudo-account for shares and assets - * - vault must have MPTokenIssuance for shares - * - vault without shares outstanding must have no shares - * - loss unrealized does not exceed the difference between assets total and - * assets available - * - assets available do not exceed assets total - * - vault deposit increases assets and share issuance, and adds to: - * total assets, assets available, shares outstanding - * - vault withdrawal and clawback reduce assets and share issuance, and - * subtracts from: total assets, assets available, shares outstanding - * - vault set must not alter the vault assets or shares balance - * - no vault transaction can change loss unrealized (it's updated by loan - * transactions) - * - */ -class ValidVault -{ - Number static constexpr zero{}; - - struct Vault final - { - uint256 key = beast::zero; - Asset asset = {}; - AccountID pseudoId = {}; - AccountID owner = {}; - uint192 shareMPTID = beast::zero; - Number assetsTotal = 0; - Number assetsAvailable = 0; - Number assetsMaximum = 0; - Number lossUnrealized = 0; - - Vault static make(SLE const&); - }; - - struct Shares final - { - MPTIssue share = {}; - std::uint64_t sharesTotal = 0; - std::uint64_t sharesMaximum = 0; - - Shares static make(SLE const&); - }; - - std::vector afterVault_ = {}; - std::vector afterMPTs_ = {}; - std::vector beforeVault_ = {}; - std::vector beforeMPTs_ = {}; - std::unordered_map deltas_ = {}; - -public: - void - visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); - - bool - finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); -}; - -// additional invariant checks can be declared above and then added to this -// tuple -using InvariantChecks = std::tuple< - TransactionFeeCheck, - AccountRootsNotDeleted, - AccountRootsDeletedClean, - LedgerEntryTypesMatch, - XRPBalanceChecks, - XRPNotCreated, - NoXRPTrustLines, - NoDeepFreezeTrustLinesWithoutFreeze, - TransfersNotFrozen, - NoBadOffers, - NoZeroEscrow, - ValidNewAccountRoot, - ValidNFTokenPage, - NFTokenCountTracking, - ValidClawback, - ValidMPTIssuance, - ValidPermissionedDomain, - ValidPermissionedDEX, - ValidAMM, - NoModifiedUnmodifiableFields, - ValidPseudoAccounts, - ValidLoanBroker, - ValidLoan, - ValidVault>; - -/** - * @brief get a tuple of all invariant checks - * - * @return std::tuple of instances that implement the required invariant check - * methods - * - * @see xrpl::InvariantChecker_PROTOTYPE - */ -inline InvariantChecks -getInvariantChecks() -{ - return InvariantChecks{}; -} - -} // namespace xrpl diff --git a/include/xrpl/tx/invariants/AMMInvariant.h b/include/xrpl/tx/invariants/AMMInvariant.h new file mode 100644 index 0000000000..63ebb804ae --- /dev/null +++ b/include/xrpl/tx/invariants/AMMInvariant.h @@ -0,0 +1,53 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include + +namespace xrpl { + +class ValidAMM +{ + std::optional ammAccount_; + std::optional lptAMMBalanceAfter_; + std::optional lptAMMBalanceBefore_; + bool ammPoolChanged_; + +public: + enum class ZeroAllowed : bool { No = false, Yes = true }; + + ValidAMM() : ammPoolChanged_{false} + { + } + void + visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); + + bool + finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); + +private: + bool + finalizeBid(bool enforce, beast::Journal const&) const; + bool + finalizeVote(bool enforce, beast::Journal const&) const; + bool + finalizeCreate(STTx const&, ReadView const&, bool enforce, beast::Journal const&) const; + bool + finalizeDelete(bool enforce, TER res, beast::Journal const&) const; + bool + finalizeDeposit(STTx const&, ReadView const&, bool enforce, beast::Journal const&) const; + // Includes clawback + bool + finalizeWithdraw(STTx const&, ReadView const&, bool enforce, beast::Journal const&) const; + bool + finalizeDEX(bool enforce, beast::Journal const&) const; + bool + generalInvariant(STTx const&, ReadView const&, ZeroAllowed zeroAllowed, beast::Journal const&) + const; +}; + +} // namespace xrpl diff --git a/include/xrpl/tx/invariants/FreezeInvariant.h b/include/xrpl/tx/invariants/FreezeInvariant.h new file mode 100644 index 0000000000..ac9d83166e --- /dev/null +++ b/include/xrpl/tx/invariants/FreezeInvariant.h @@ -0,0 +1,84 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace xrpl { + +/** + * @brief Invariant: frozen trust line balance change is not allowed. + * + * We iterate all affected trust lines and ensure that they don't have + * unexpected change of balance if they're frozen. + */ +class TransfersNotFrozen +{ + struct BalanceChange + { + std::shared_ptr const line; + int const balanceChangeSign; + }; + + struct IssuerChanges + { + std::vector senders; + std::vector receivers; + }; + + using ByIssuer = std::map; + ByIssuer balanceChanges_; + + std::map const> possibleIssuers_; + +public: + void + visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); + + bool + finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); + +private: + bool + isValidEntry(std::shared_ptr const& before, std::shared_ptr const& after); + + STAmount + calculateBalanceChange( + std::shared_ptr const& before, + std::shared_ptr const& after, + bool isDelete); + + void + recordBalance(Issue const& issue, BalanceChange change); + + void + recordBalanceChanges(std::shared_ptr const& after, STAmount const& balanceChange); + + std::shared_ptr + findIssuer(AccountID const& issuerID, ReadView const& view); + + bool + validateIssuerChanges( + std::shared_ptr const& issuer, + IssuerChanges const& changes, + STTx const& tx, + beast::Journal const& j, + bool enforce); + + bool + validateFrozenState( + BalanceChange const& change, + bool high, + STTx const& tx, + beast::Journal const& j, + bool enforce, + bool globalFreeze); +}; + +} // namespace xrpl diff --git a/include/xrpl/tx/invariants/InvariantCheck.h b/include/xrpl/tx/invariants/InvariantCheck.h new file mode 100644 index 0000000000..5ded5980da --- /dev/null +++ b/include/xrpl/tx/invariants/InvariantCheck.h @@ -0,0 +1,385 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace xrpl { + +#if GENERATING_DOCS +/** + * @brief Prototype for invariant check implementations. + * + * __THIS CLASS DOES NOT EXIST__ - or rather it exists in documentation only to + * communicate the interface required of any invariant checker. Any invariant + * check implementation should implement the public methods documented here. + * + */ +class InvariantChecker_PROTOTYPE +{ +public: + explicit InvariantChecker_PROTOTYPE() = default; + + /** + * @brief called for each ledger entry in the current transaction. + * + * @param isDelete true if the SLE is being deleted + * @param before ledger entry before modification by the transaction + * @param after ledger entry after modification by the transaction + */ + void + visitEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after); + + /** + * @brief called after all ledger entries have been visited to determine + * the final status of the check + * + * @param tx the transaction being applied + * @param tec the current TER result of the transaction + * @param fee the fee actually charged for this transaction + * @param view a ReadView of the ledger being modified + * @param j journal for logging + * + * @return true if check passes, false if it fails + */ + bool + finalize( + STTx const& tx, + TER const tec, + XRPAmount const fee, + ReadView const& view, + beast::Journal const& j); +}; +#endif + +/** + * @brief Invariant: We should never charge a transaction a negative fee or a + * fee that is larger than what the transaction itself specifies. + * + * We can, in some circumstances, charge less. + */ +class TransactionFeeCheck +{ +public: + void + visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); + + bool + finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); +}; + +/** + * @brief Invariant: A transaction must not create XRP and should only destroy + * the XRP fee. + * + * We iterate through all account roots, payment channels and escrow entries + * that were modified and calculate the net change in XRP caused by the + * transactions. + */ +class XRPNotCreated +{ + std::int64_t drops_ = 0; + +public: + void + visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); + + bool + finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); +}; + +/** + * @brief Invariant: we cannot remove an account ledger entry + * + * We iterate all account roots that were modified, and ensure that any that + * were present before the transaction was applied continue to be present + * afterwards unless they were explicitly deleted by a successful + * AccountDelete transaction. + */ +class AccountRootsNotDeleted +{ + std::uint32_t accountsDeleted_ = 0; + +public: + void + visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); + + bool + finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); +}; + +/** + * @brief Invariant: a deleted account must not have any objects left + * + * We iterate all deleted account roots, and ensure that there are no + * objects left that are directly accessible with that account's ID. + * + * There should only be one deleted account, but that's checked by + * AccountRootsNotDeleted. This invariant will handle multiple deleted account + * roots without a problem. + */ +class AccountRootsDeletedClean +{ + // Pair is . Before is used for most of the checks, so that + // if, for example, an object ID field is cleared, but the object is not + // deleted, it can still be found. After is used specifically for any checks + // that are expected as part of the deletion, such as zeroing out the + // balance. + std::vector, std::shared_ptr>> accountsDeleted_; + +public: + void + visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); + + bool + finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); +}; + +/** + * @brief Invariant: An account XRP balance must be in XRP and take a value + * between 0 and INITIAL_XRP drops, inclusive. + * + * We iterate all account roots modified by the transaction and ensure that + * their XRP balances are reasonable. + */ +class XRPBalanceChecks +{ + bool bad_ = false; + +public: + void + visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); + + bool + finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); +}; + +/** + * @brief Invariant: corresponding modified ledger entries should match in type + * and added entries should be a valid type. + */ +class LedgerEntryTypesMatch +{ + bool typeMismatch_ = false; + bool invalidTypeAdded_ = false; + +public: + void + visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); + + bool + finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); +}; + +/** + * @brief Invariant: Trust lines using XRP are not allowed. + * + * We iterate all the trust lines created by this transaction and ensure + * that they are against a valid issuer. + */ +class NoXRPTrustLines +{ + bool xrpTrustLine_ = false; + +public: + void + visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); + + bool + finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); +}; + +/** + * @brief Invariant: Trust lines with deep freeze flag are not allowed if normal + * freeze flag is not set. + * + * We iterate all the trust lines created by this transaction and ensure + * that they don't have deep freeze flag set without normal freeze flag set. + */ +class NoDeepFreezeTrustLinesWithoutFreeze +{ + bool deepFreezeWithoutFreeze_ = false; + +public: + void + visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); + + bool + finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); +}; + +/** + * @brief Invariant: offers should be for non-negative amounts and must not + * be XRP to XRP. + * + * Examine all offers modified by the transaction and ensure that there are + * no offers which contain negative amounts or which exchange XRP for XRP. + */ +class NoBadOffers +{ + bool bad_ = false; + +public: + void + visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); + + bool + finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); +}; + +/** + * @brief Invariant: an escrow entry must take a value between 0 and + * INITIAL_XRP drops exclusive. + */ +class NoZeroEscrow +{ + bool bad_ = false; + +public: + void + visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); + + bool + finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); +}; + +/** + * @brief Invariant: a new account root must be the consequence of a payment, + * must have the right starting sequence, and the payment + * may not create more than one new account root. + */ +class ValidNewAccountRoot +{ + std::uint32_t accountsCreated_ = 0; + std::uint32_t accountSeq_ = 0; + bool pseudoAccount_ = false; + std::uint32_t flags_ = 0; + +public: + void + visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); + + bool + finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); +}; + +/** + * @brief Invariant: Token holder's trustline balance cannot be negative after + * Clawback. + * + * We iterate all the trust lines affected by this transaction and ensure + * that no more than one trustline is modified, and also holder's balance is + * non-negative. + */ +class ValidClawback +{ + std::uint32_t trustlinesChanged = 0; + std::uint32_t mptokensChanged = 0; + +public: + void + visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); + + bool + finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); +}; + +/** + * @brief Invariants: Pseudo-accounts have valid and consistent properties + * + * Pseudo-accounts have certain properties, and some of those properties are + * unique to pseudo-accounts. Check that all pseudo-accounts are following the + * rules, and that only pseudo-accounts look like pseudo-accounts. + * + */ +class ValidPseudoAccounts +{ + std::vector errors_; + +public: + void + visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); + + bool + finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); +}; + +/** + * @brief Invariants: Some fields are unmodifiable + * + * Check that any fields specified as unmodifiable are not modified when the + * object is modified. Creation and deletion are ignored. + * + */ +class NoModifiedUnmodifiableFields +{ + // Pair is . + std::set> changedEntries_; + +public: + void + visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); + + bool + finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); +}; + +// additional invariant checks can be declared above and then added to this +// tuple +using InvariantChecks = std::tuple< + TransactionFeeCheck, + AccountRootsNotDeleted, + AccountRootsDeletedClean, + LedgerEntryTypesMatch, + XRPBalanceChecks, + XRPNotCreated, + NoXRPTrustLines, + NoDeepFreezeTrustLinesWithoutFreeze, + TransfersNotFrozen, + NoBadOffers, + NoZeroEscrow, + ValidNewAccountRoot, + ValidNFTokenPage, + NFTokenCountTracking, + ValidClawback, + ValidMPTIssuance, + ValidPermissionedDomain, + ValidPermissionedDEX, + ValidAMM, + NoModifiedUnmodifiableFields, + ValidPseudoAccounts, + ValidLoanBroker, + ValidLoan, + ValidVault>; + +/** + * @brief get a tuple of all invariant checks + * + * @return std::tuple of instances that implement the required invariant check + * methods + * + * @see xrpl::InvariantChecker_PROTOTYPE + */ +inline InvariantChecks +getInvariantChecks() +{ + return InvariantChecks{}; +} + +} // namespace xrpl diff --git a/include/xrpl/tx/invariants/InvariantCheckPrivilege.h b/include/xrpl/tx/invariants/InvariantCheckPrivilege.h new file mode 100644 index 0000000000..161b3572db --- /dev/null +++ b/include/xrpl/tx/invariants/InvariantCheckPrivilege.h @@ -0,0 +1,60 @@ +#pragma once + +#include + +#include + +namespace xrpl { + +/* +assert(enforce) + +There are several asserts (or XRPL_ASSERTs) in invariant check files that check +a variable named `enforce` when an invariant fails. At first glance, those +asserts may look incorrect, but they are not. + +Those asserts take advantage of two facts: +1. `asserts` are not (normally) executed in release builds. +2. Invariants should *never* fail, except in tests that specifically modify + the open ledger to break them. + +This makes `assert(enforce)` sort of a second-layer of invariant enforcement +aimed at _developers_. It's designed to fire if a developer writes code that +violates an invariant, and runs it in unit tests or a develop build that _does +not have the relevant amendments enabled_. It's intentionally a pain in the neck +so that bad code gets caught and fixed as early as possible. +*/ + +enum Privilege { + noPriv = 0x0000, // The transaction can not do any of the enumerated operations + createAcct = 0x0001, // The transaction can create a new ACCOUNT_ROOT object. + createPseudoAcct = 0x0002, // The transaction can create a pseudo account, + // which implies createAcct + mustDeleteAcct = 0x0004, // The transaction must delete an ACCOUNT_ROOT object + mayDeleteAcct = 0x0008, // The transaction may delete an ACCOUNT_ROOT + // object, but does not have to + overrideFreeze = 0x0010, // The transaction can override some freeze rules + changeNFTCounts = 0x0020, // The transaction can mint or burn an NFT + createMPTIssuance = 0x0040, // The transaction can create a new MPT issuance + destroyMPTIssuance = 0x0080, // The transaction can destroy an MPT issuance + mustAuthorizeMPT = 0x0100, // The transaction MUST create or delete an MPT + // object (except by issuer) + mayAuthorizeMPT = 0x0200, // The transaction MAY create or delete an MPT + // object (except by issuer) + mayDeleteMPT = 0x0400, // The transaction MAY delete an MPT object. May not create. + mustModifyVault = 0x0800, // The transaction must modify, delete or create, a vault + mayModifyVault = 0x1000, // The transaction MAY modify, delete or create, a vault +}; + +constexpr Privilege +operator|(Privilege lhs, Privilege rhs) +{ + return safe_cast( + safe_cast>(lhs) | + safe_cast>(rhs)); +} + +bool +hasPrivilege(STTx const& tx, Privilege priv); + +} // namespace xrpl diff --git a/include/xrpl/tx/invariants/LoanInvariant.h b/include/xrpl/tx/invariants/LoanInvariant.h new file mode 100644 index 0000000000..be771cd582 --- /dev/null +++ b/include/xrpl/tx/invariants/LoanInvariant.h @@ -0,0 +1,75 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include +#include + +namespace xrpl { + +/** + * @brief Invariants: Loan brokers are internally consistent + * + * 1. If `LoanBroker.OwnerCount = 0` the `DirectoryNode` will have at most one + * node (the root), which will only hold entries for `RippleState` or + * `MPToken` objects. + * + */ +class ValidLoanBroker +{ + // Not all of these elements will necessarily be populated. Remaining items + // will be looked up as needed. + struct BrokerInfo + { + SLE::const_pointer brokerBefore = nullptr; + // After is used for most of the checks, except + // those that check changed values. + SLE::const_pointer brokerAfter = nullptr; + }; + // Collect all the LoanBrokers found directly or indirectly through + // pseudo-accounts. Key is the brokerID / index. It will be used to find the + // LoanBroker object if brokerBefore and brokerAfter are nullptr + std::map brokers_; + // Collect all the modified trust lines. Their high and low accounts will be + // loaded to look for LoanBroker pseudo-accounts. + std::vector lines_; + // Collect all the modified MPTokens. Their accounts will be loaded to look + // for LoanBroker pseudo-accounts. + std::vector mpts_; + + bool + goodZeroDirectory(ReadView const& view, SLE::const_ref dir, beast::Journal const& j) const; + +public: + void + visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); + + bool + finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); +}; + +/** + * @brief Invariants: Loans are internally consistent + * + * 1. If `Loan.PaymentRemaining = 0` then `Loan.PrincipalOutstanding = 0` + * + */ +class ValidLoan +{ + // Pair is . After is used for most of the checks, except + // those that check changed values. + std::vector> loans_; + +public: + void + visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); + + bool + finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); +}; + +} // namespace xrpl diff --git a/include/xrpl/tx/invariants/MPTInvariant.h b/include/xrpl/tx/invariants/MPTInvariant.h new file mode 100644 index 0000000000..b6533c263d --- /dev/null +++ b/include/xrpl/tx/invariants/MPTInvariant.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include +#include +#include + +#include + +namespace xrpl { + +class ValidMPTIssuance +{ + std::uint32_t mptIssuancesCreated_ = 0; + std::uint32_t mptIssuancesDeleted_ = 0; + + std::uint32_t mptokensCreated_ = 0; + std::uint32_t mptokensDeleted_ = 0; + // non-MPT transactions may attempt to create + // MPToken by an issuer + bool mptCreatedByIssuer_ = false; + +public: + void + visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); + + bool + finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); +}; + +} // namespace xrpl diff --git a/include/xrpl/tx/invariants/NFTInvariant.h b/include/xrpl/tx/invariants/NFTInvariant.h new file mode 100644 index 0000000000..8a88ca1c63 --- /dev/null +++ b/include/xrpl/tx/invariants/NFTInvariant.h @@ -0,0 +1,70 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include + +namespace xrpl { + +/** + * @brief Invariant: Validates several invariants for NFToken pages. + * + * The following checks are made: + * - The page is correctly associated with the owner. + * - The page is correctly ordered between the next and previous links. + * - The page contains at least one and no more than 32 NFTokens. + * - The NFTokens on this page do not belong on a lower or higher page. + * - The NFTokens are correctly sorted on the page. + * - Each URI, if present, is not empty. + */ +class ValidNFTokenPage +{ + bool badEntry_ = false; + bool badLink_ = false; + bool badSort_ = false; + bool badURI_ = false; + bool invalidSize_ = false; + bool deletedFinalPage_ = false; + bool deletedLink_ = false; + +public: + void + visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); + + bool + finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); +}; + +/** + * @brief Invariant: Validates counts of NFTokens after all transaction types. + * + * The following checks are made: + * - The number of minted or burned NFTokens can only be changed by + * NFTokenMint or NFTokenBurn transactions. + * - A successful NFTokenMint must increase the number of NFTokens. + * - A failed NFTokenMint must not change the number of minted NFTokens. + * - An NFTokenMint transaction cannot change the number of burned NFTokens. + * - A successful NFTokenBurn must increase the number of burned NFTokens. + * - A failed NFTokenBurn must not change the number of burned NFTokens. + * - An NFTokenBurn transaction cannot change the number of minted NFTokens. + */ +class NFTokenCountTracking +{ + std::uint32_t beforeMintedTotal = 0; + std::uint32_t beforeBurnedTotal = 0; + std::uint32_t afterMintedTotal = 0; + std::uint32_t afterBurnedTotal = 0; + +public: + void + visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); + + bool + finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); +}; + +} // namespace xrpl diff --git a/include/xrpl/tx/invariants/PermissionedDEXInvariant.h b/include/xrpl/tx/invariants/PermissionedDEXInvariant.h new file mode 100644 index 0000000000..b4e06cd212 --- /dev/null +++ b/include/xrpl/tx/invariants/PermissionedDEXInvariant.h @@ -0,0 +1,25 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace xrpl { + +class ValidPermissionedDEX +{ + bool regularOffers_ = false; + bool badHybrids_ = false; + hash_set domains_; + +public: + void + visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); + + bool + finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); +}; + +} // namespace xrpl diff --git a/include/xrpl/tx/invariants/PermissionedDomainInvariant.h b/include/xrpl/tx/invariants/PermissionedDomainInvariant.h new file mode 100644 index 0000000000..f6c902ecb2 --- /dev/null +++ b/include/xrpl/tx/invariants/PermissionedDomainInvariant.h @@ -0,0 +1,41 @@ +#pragma once + +#include +#include +#include +#include + +#include + +namespace xrpl { + +/** + * @brief Invariants: Permissioned Domains must have some rules and + * AcceptedCredentials must have length between 1 and 10 inclusive. + * + * Since only permissions constitute rules, an empty credentials list + * means that there are no rules and the invariant is violated. + * + * Credentials must be sorted and no duplicates allowed + * + */ +class ValidPermissionedDomain +{ + struct SleStatus + { + std::size_t credentialsSize_{0}; + bool isSorted_ = false; + bool isUnique_ = false; + bool isDelete_ = false; + }; + std::vector sleStatus_; + +public: + void + visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); + + bool + finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); +}; + +} // namespace xrpl diff --git a/include/xrpl/tx/invariants/VaultInvariant.h b/include/xrpl/tx/invariants/VaultInvariant.h new file mode 100644 index 0000000000..ded9e4618b --- /dev/null +++ b/include/xrpl/tx/invariants/VaultInvariant.h @@ -0,0 +1,77 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace xrpl { + +/* + * @brief Invariants: Vault object and MPTokenIssuance for vault shares + * + * - vault deleted and vault created is empty + * - vault created must be linked to pseudo-account for shares and assets + * - vault must have MPTokenIssuance for shares + * - vault without shares outstanding must have no shares + * - loss unrealized does not exceed the difference between assets total and + * assets available + * - assets available do not exceed assets total + * - vault deposit increases assets and share issuance, and adds to: + * total assets, assets available, shares outstanding + * - vault withdrawal and clawback reduce assets and share issuance, and + * subtracts from: total assets, assets available, shares outstanding + * - vault set must not alter the vault assets or shares balance + * - no vault transaction can change loss unrealized (it's updated by loan + * transactions) + * + */ +class ValidVault +{ + Number static constexpr zero{}; + + struct Vault final + { + uint256 key = beast::zero; + Asset asset = {}; + AccountID pseudoId = {}; + AccountID owner = {}; + uint192 shareMPTID = beast::zero; + Number assetsTotal = 0; + Number assetsAvailable = 0; + Number assetsMaximum = 0; + Number lossUnrealized = 0; + + Vault static make(SLE const&); + }; + + struct Shares final + { + MPTIssue share = {}; + std::uint64_t sharesTotal = 0; + std::uint64_t sharesMaximum = 0; + + Shares static make(SLE const&); + }; + + std::vector afterVault_ = {}; + std::vector afterMPTs_ = {}; + std::vector beforeVault_ = {}; + std::vector beforeMPTs_ = {}; + std::unordered_map deltas_ = {}; + +public: + void + visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); + + bool + finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); +}; + +} // namespace xrpl diff --git a/src/libxrpl/tx/ApplyContext.cpp b/src/libxrpl/tx/ApplyContext.cpp index a8eca09ff2..f62c63d1e6 100644 --- a/src/libxrpl/tx/ApplyContext.cpp +++ b/src/libxrpl/tx/ApplyContext.cpp @@ -1,8 +1,9 @@ +#include +// #include #include #include -#include -#include +#include namespace xrpl { diff --git a/src/libxrpl/tx/InvariantCheck.cpp b/src/libxrpl/tx/InvariantCheck.cpp deleted file mode 100644 index b94f11100b..0000000000 --- a/src/libxrpl/tx/InvariantCheck.cpp +++ /dev/null @@ -1,3483 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -namespace xrpl { - -/* -assert(enforce) - -There are several asserts (or XRPL_ASSERTs) in this file that check a variable -named `enforce` when an invariant fails. At first glance, those asserts may look -incorrect, but they are not. - -Those asserts take advantage of two facts: -1. `asserts` are not (normally) executed in release builds. -2. Invariants should *never* fail, except in tests that specifically modify - the open ledger to break them. - -This makes `assert(enforce)` sort of a second-layer of invariant enforcement -aimed at _developers_. It's designed to fire if a developer writes code that -violates an invariant, and runs it in unit tests or a develop build that _does -not have the relevant amendments enabled_. It's intentionally a pain in the neck -so that bad code gets caught and fixed as early as possible. -*/ - -enum Privilege { - noPriv = 0x0000, // The transaction can not do any of the enumerated operations - createAcct = 0x0001, // The transaction can create a new ACCOUNT_ROOT object. - createPseudoAcct = 0x0002, // The transaction can create a pseudo account, - // which implies createAcct - mustDeleteAcct = 0x0004, // The transaction must delete an ACCOUNT_ROOT object - mayDeleteAcct = 0x0008, // The transaction may delete an ACCOUNT_ROOT - // object, but does not have to - overrideFreeze = 0x0010, // The transaction can override some freeze rules - changeNFTCounts = 0x0020, // The transaction can mint or burn an NFT - createMPTIssuance = 0x0040, // The transaction can create a new MPT issuance - destroyMPTIssuance = 0x0080, // The transaction can destroy an MPT issuance - mustAuthorizeMPT = 0x0100, // The transaction MUST create or delete an MPT - // object (except by issuer) - mayAuthorizeMPT = 0x0200, // The transaction MAY create or delete an MPT - // object (except by issuer) - mayDeleteMPT = 0x0400, // The transaction MAY delete an MPT object. May not create. - mustModifyVault = 0x0800, // The transaction must modify, delete or create, a vault - mayModifyVault = 0x1000, // The transaction MAY modify, delete or create, a vault -}; -constexpr Privilege -operator|(Privilege lhs, Privilege rhs) -{ - return safe_cast( - safe_cast>(lhs) | - safe_cast>(rhs)); -} - -#pragma push_macro("TRANSACTION") -#undef TRANSACTION - -#define TRANSACTION(tag, value, name, delegable, amendment, privileges, ...) \ - case tag: { \ - return (privileges) & priv; \ - } - -bool -hasPrivilege(STTx const& tx, Privilege priv) -{ - switch (tx.getTxnType()) - { -#include - - // Deprecated types - default: - return false; - } -}; - -#undef TRANSACTION -#pragma pop_macro("TRANSACTION") - -void -TransactionFeeCheck::visitEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const&) -{ - // nothing to do -} - -bool -TransactionFeeCheck::finalize( - STTx const& tx, - TER const, - XRPAmount const fee, - ReadView const&, - beast::Journal const& j) -{ - // We should never charge a negative fee - if (fee.drops() < 0) - { - JLOG(j.fatal()) << "Invariant failed: fee paid was negative: " << fee.drops(); - return false; - } - - // We should never charge a fee that's greater than or equal to the - // entire XRP supply. - if (fee >= INITIAL_XRP) - { - JLOG(j.fatal()) << "Invariant failed: fee paid exceeds system limit: " << fee.drops(); - return false; - } - - // We should never charge more for a transaction than the transaction - // authorizes. It's possible to charge less in some circumstances. - if (fee > tx.getFieldAmount(sfFee).xrp()) - { - JLOG(j.fatal()) << "Invariant failed: fee paid is " << fee.drops() - << " exceeds fee specified in transaction."; - return false; - } - - return true; -} - -//------------------------------------------------------------------------------ - -void -XRPNotCreated::visitEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) -{ - /* We go through all modified ledger entries, looking only at account roots, - * escrow payments, and payment channels. We remove from the total any - * previous XRP values and add to the total any new XRP values. The net - * balance of a payment channel is computed from two fields (amount and - * balance) and deletions are ignored for paychan and escrow because the - * amount fields have not been adjusted for those in the case of deletion. - */ - if (before) - { - switch (before->getType()) - { - case ltACCOUNT_ROOT: - drops_ -= (*before)[sfBalance].xrp().drops(); - break; - case ltPAYCHAN: - drops_ -= ((*before)[sfAmount] - (*before)[sfBalance]).xrp().drops(); - break; - case ltESCROW: - if (isXRP((*before)[sfAmount])) - drops_ -= (*before)[sfAmount].xrp().drops(); - break; - default: - break; - } - } - - if (after) - { - switch (after->getType()) - { - case ltACCOUNT_ROOT: - drops_ += (*after)[sfBalance].xrp().drops(); - break; - case ltPAYCHAN: - if (!isDelete) - drops_ += ((*after)[sfAmount] - (*after)[sfBalance]).xrp().drops(); - break; - case ltESCROW: - if (!isDelete && isXRP((*after)[sfAmount])) - drops_ += (*after)[sfAmount].xrp().drops(); - break; - default: - break; - } - } -} - -bool -XRPNotCreated::finalize( - STTx const& tx, - TER const, - XRPAmount const fee, - ReadView const&, - beast::Journal const& j) -{ - // The net change should never be positive, as this would mean that the - // transaction created XRP out of thin air. That's not possible. - if (drops_ > 0) - { - JLOG(j.fatal()) << "Invariant failed: XRP net change was positive: " << drops_; - return false; - } - - // The negative of the net change should be equal to actual fee charged. - if (-drops_ != fee.drops()) - { - JLOG(j.fatal()) << "Invariant failed: XRP net change of " << drops_ << " doesn't match fee " - << fee.drops(); - return false; - } - - return true; -} - -//------------------------------------------------------------------------------ - -void -XRPBalanceChecks::visitEntry( - bool, - std::shared_ptr const& before, - std::shared_ptr const& after) -{ - auto isBad = [](STAmount const& balance) { - if (!balance.native()) - return true; - - auto const drops = balance.xrp(); - - // Can't have more than the number of drops instantiated - // in the genesis ledger. - if (drops > INITIAL_XRP) - return true; - - // Can't have a negative balance (0 is OK) - if (drops < XRPAmount{0}) - return true; - - return false; - }; - - if (before && before->getType() == ltACCOUNT_ROOT) - bad_ |= isBad((*before)[sfBalance]); - - if (after && after->getType() == ltACCOUNT_ROOT) - bad_ |= isBad((*after)[sfBalance]); -} - -bool -XRPBalanceChecks::finalize( - STTx const&, - TER const, - XRPAmount const, - ReadView const&, - beast::Journal const& j) -{ - if (bad_) - { - JLOG(j.fatal()) << "Invariant failed: incorrect account XRP balance"; - return false; - } - - return true; -} - -//------------------------------------------------------------------------------ - -void -NoBadOffers::visitEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) -{ - auto isBad = [](STAmount const& pays, STAmount const& gets) { - // An offer should never be negative - if (pays < beast::zero) - return true; - - if (gets < beast::zero) - return true; - - // Can't have an XRP to XRP offer: - return pays.native() && gets.native(); - }; - - if (before && before->getType() == ltOFFER) - bad_ |= isBad((*before)[sfTakerPays], (*before)[sfTakerGets]); - - if (after && after->getType() == ltOFFER) - bad_ |= isBad((*after)[sfTakerPays], (*after)[sfTakerGets]); -} - -bool -NoBadOffers::finalize( - STTx const&, - TER const, - XRPAmount const, - ReadView const&, - beast::Journal const& j) -{ - if (bad_) - { - JLOG(j.fatal()) << "Invariant failed: offer with a bad amount"; - return false; - } - - return true; -} - -//------------------------------------------------------------------------------ - -void -NoZeroEscrow::visitEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) -{ - auto isBad = [](STAmount const& amount) { - // XRP case - if (amount.native()) - { - if (amount.xrp() <= XRPAmount{0}) - return true; - - if (amount.xrp() >= INITIAL_XRP) - return true; - } - else - { - // IOU case - if (amount.holds()) - { - if (amount <= beast::zero) - return true; - - if (badCurrency() == amount.getCurrency()) - return true; - } - - // MPT case - if (amount.holds()) - { - if (amount <= beast::zero) - return true; - - if (amount.mpt() > MPTAmount{maxMPTokenAmount}) - return true; // LCOV_EXCL_LINE - } - } - return false; - }; - - if (before && before->getType() == ltESCROW) - bad_ |= isBad((*before)[sfAmount]); - - if (after && after->getType() == ltESCROW) - bad_ |= isBad((*after)[sfAmount]); - - auto checkAmount = [this](std::int64_t amount) { - if (amount > maxMPTokenAmount || amount < 0) - bad_ = true; - }; - - if (after && after->getType() == ltMPTOKEN_ISSUANCE) - { - auto const outstanding = (*after)[sfOutstandingAmount]; - checkAmount(outstanding); - if (auto const locked = (*after)[~sfLockedAmount]) - { - checkAmount(*locked); - bad_ = outstanding < *locked; - } - } - - if (after && after->getType() == ltMPTOKEN) - { - auto const mptAmount = (*after)[sfMPTAmount]; - checkAmount(mptAmount); - if (auto const locked = (*after)[~sfLockedAmount]) - { - checkAmount(*locked); - } - } -} - -bool -NoZeroEscrow::finalize( - STTx const& txn, - TER const, - XRPAmount const, - ReadView const& rv, - beast::Journal const& j) -{ - if (bad_) - { - JLOG(j.fatal()) << "Invariant failed: escrow specifies invalid amount"; - return false; - } - - return true; -} - -//------------------------------------------------------------------------------ - -void -AccountRootsNotDeleted::visitEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const&) -{ - if (isDelete && before && before->getType() == ltACCOUNT_ROOT) - accountsDeleted_++; -} - -bool -AccountRootsNotDeleted::finalize( - STTx const& tx, - TER const result, - XRPAmount const, - ReadView const&, - beast::Journal const& j) -{ - // AMM account root can be deleted as the result of AMM withdraw/delete - // transaction when the total AMM LP Tokens balance goes to 0. - // A successful AccountDelete or AMMDelete MUST delete exactly - // one account root. - if (hasPrivilege(tx, mustDeleteAcct) && result == tesSUCCESS) - { - if (accountsDeleted_ == 1) - return true; - - if (accountsDeleted_ == 0) - JLOG(j.fatal()) << "Invariant failed: account deletion " - "succeeded without deleting an account"; - else - JLOG(j.fatal()) << "Invariant failed: account deletion " - "succeeded but deleted multiple accounts!"; - return false; - } - - // A successful AMMWithdraw/AMMClawback MAY delete one account root - // when the total AMM LP Tokens balance goes to 0. Not every AMM withdraw - // deletes the AMM account, accountsDeleted_ is set if it is deleted. - if (hasPrivilege(tx, mayDeleteAcct) && result == tesSUCCESS && accountsDeleted_ == 1) - return true; - - if (accountsDeleted_ == 0) - return true; - - JLOG(j.fatal()) << "Invariant failed: an account root was deleted"; - return false; -} - -//------------------------------------------------------------------------------ - -void -AccountRootsDeletedClean::visitEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) -{ - if (isDelete && before && before->getType() == ltACCOUNT_ROOT) - accountsDeleted_.emplace_back(before, after); -} - -bool -AccountRootsDeletedClean::finalize( - STTx const& tx, - TER const result, - XRPAmount const, - ReadView const& view, - beast::Journal const& j) -{ - // Always check for objects in the ledger, but to prevent differing - // transaction processing results, however unlikely, only fail if the - // feature is enabled. Enabled, or not, though, a fatal-level message will - // be logged - [[maybe_unused]] bool const enforce = view.rules().enabled(featureInvariantsV1_1) || - view.rules().enabled(featureSingleAssetVault) || - view.rules().enabled(featureLendingProtocol); - - auto const objectExists = [&view, enforce, &j](auto const& keylet) { - (void)enforce; - if (auto const sle = view.read(keylet)) - { - // Finding the object is bad - auto const typeName = [&sle]() { - auto item = LedgerFormats::getInstance().findByType(sle->getType()); - - if (item != nullptr) - return item->getName(); - return std::to_string(sle->getType()); - }(); - - JLOG(j.fatal()) << "Invariant failed: account deletion left behind a " << typeName - << " object"; - // The comment above starting with "assert(enforce)" explains this - // assert. - XRPL_ASSERT( - enforce, - "xrpl::AccountRootsDeletedClean::finalize::objectExists : " - "account deletion left no objects behind"); - return true; - } - return false; - }; - - for (auto const& [before, after] : accountsDeleted_) - { - auto const accountID = before->getAccountID(sfAccount); - // An account should not be deleted with a balance - if (after->at(sfBalance) != beast::zero) - { - JLOG(j.fatal()) << "Invariant failed: account deletion left " - "behind a non-zero balance"; - XRPL_ASSERT( - enforce, - "xrpl::AccountRootsDeletedClean::finalize : " - "deleted account has zero balance"); - if (enforce) - return false; - } - // An account should not be deleted with a non-zero owner count - if (after->at(sfOwnerCount) != 0) - { - JLOG(j.fatal()) << "Invariant failed: account deletion left " - "behind a non-zero owner count"; - XRPL_ASSERT( - enforce, - "xrpl::AccountRootsDeletedClean::finalize : " - "deleted account has zero owner count"); - if (enforce) - return false; - } - // Simple types - for (auto const& [keyletfunc, _, __] : directAccountKeylets) - { - if (objectExists(std::invoke(keyletfunc, accountID)) && enforce) - return false; - } - - { - // NFT pages. nftpage_min and nftpage_max were already explicitly - // checked above as entries in directAccountKeylets. This uses - // view.succ() to check for any NFT pages in between the two - // endpoints. - Keylet const first = keylet::nftpage_min(accountID); - Keylet const last = keylet::nftpage_max(accountID); - - std::optional key = view.succ(first.key, last.key.next()); - - // current page - if (key && objectExists(Keylet{ltNFTOKEN_PAGE, *key}) && enforce) - return false; - } - - // If the account is a pseudo account, then the linked object must - // also be deleted. e.g. AMM, Vault, etc. - for (auto const& field : getPseudoAccountFields()) - { - if (before->isFieldPresent(*field)) - { - auto const key = before->getFieldH256(*field); - if (objectExists(keylet::unchecked(key)) && enforce) - return false; - } - } - } - - return true; -} - -//------------------------------------------------------------------------------ - -void -LedgerEntryTypesMatch::visitEntry( - bool, - std::shared_ptr const& before, - std::shared_ptr const& after) -{ - if (before && after && before->getType() != after->getType()) - typeMismatch_ = true; - - if (after) - { -#pragma push_macro("LEDGER_ENTRY") -#undef LEDGER_ENTRY - -#define LEDGER_ENTRY(tag, ...) case tag: - - switch (after->getType()) - { -#include - - break; - default: - invalidTypeAdded_ = true; - break; - } - -#undef LEDGER_ENTRY -#pragma pop_macro("LEDGER_ENTRY") - } -} - -bool -LedgerEntryTypesMatch::finalize( - STTx const&, - TER const, - XRPAmount const, - ReadView const&, - beast::Journal const& j) -{ - if ((!typeMismatch_) && (!invalidTypeAdded_)) - return true; - - if (typeMismatch_) - { - JLOG(j.fatal()) << "Invariant failed: ledger entry type mismatch"; - } - - if (invalidTypeAdded_) - { - JLOG(j.fatal()) << "Invariant failed: invalid ledger entry type added"; - } - - return false; -} - -//------------------------------------------------------------------------------ - -void -NoXRPTrustLines::visitEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const& after) -{ - if (after && after->getType() == ltRIPPLE_STATE) - { - // checking the issue directly here instead of - // relying on .native() just in case native somehow - // were systematically incorrect - xrpTrustLine_ = after->getFieldAmount(sfLowLimit).issue() == xrpIssue() || - after->getFieldAmount(sfHighLimit).issue() == xrpIssue(); - } -} - -bool -NoXRPTrustLines::finalize( - STTx const&, - TER const, - XRPAmount const, - ReadView const&, - beast::Journal const& j) -{ - if (!xrpTrustLine_) - return true; - - JLOG(j.fatal()) << "Invariant failed: an XRP trust line was created"; - return false; -} - -//------------------------------------------------------------------------------ - -void -NoDeepFreezeTrustLinesWithoutFreeze::visitEntry( - bool, - std::shared_ptr const&, - std::shared_ptr const& after) -{ - if (after && after->getType() == ltRIPPLE_STATE) - { - std::uint32_t const uFlags = after->getFieldU32(sfFlags); - bool const lowFreeze = uFlags & lsfLowFreeze; - bool const lowDeepFreeze = uFlags & lsfLowDeepFreeze; - - bool const highFreeze = uFlags & lsfHighFreeze; - bool const highDeepFreeze = uFlags & lsfHighDeepFreeze; - - deepFreezeWithoutFreeze_ = (lowDeepFreeze && !lowFreeze) || (highDeepFreeze && !highFreeze); - } -} - -bool -NoDeepFreezeTrustLinesWithoutFreeze::finalize( - STTx const&, - TER const, - XRPAmount const, - ReadView const&, - beast::Journal const& j) -{ - if (!deepFreezeWithoutFreeze_) - return true; - - JLOG(j.fatal()) << "Invariant failed: a trust line with deep freeze flag " - "without normal freeze was created"; - return false; -} - -//------------------------------------------------------------------------------ - -void -TransfersNotFrozen::visitEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) -{ - /* - * A trust line freeze state alone doesn't determine if a transfer is - * frozen. The transfer must be examined "end-to-end" because both sides of - * the transfer may have different freeze states and freeze impact depends - * on the transfer direction. This is why first we need to track the - * transfers using IssuerChanges senders/receivers. - * - * Only in validateIssuerChanges, after we collected all changes can we - * determine if the transfer is valid. - */ - if (!isValidEntry(before, after)) - { - return; - } - - auto const balanceChange = calculateBalanceChange(before, after, isDelete); - if (balanceChange.signum() == 0) - { - return; - } - - recordBalanceChanges(after, balanceChange); -} - -bool -TransfersNotFrozen::finalize( - STTx const& tx, - TER const ter, - XRPAmount const fee, - ReadView const& view, - beast::Journal const& j) -{ - /* - * We check this invariant regardless of deep freeze amendment status, - * allowing for detection and logging of potential issues even when the - * amendment is disabled. - * - * If an exploit that allows moving frozen assets is discovered, - * we can alert operators who monitor fatal messages and trigger assert in - * debug builds for an early warning. - * - * In an unlikely event that an exploit is found, this early detection - * enables encouraging the UNL to expedite deep freeze amendment activation - * or deploy hotfixes via new amendments. In case of a new amendment, we'd - * only have to change this line setting 'enforce' variable. - * enforce = view.rules().enabled(featureDeepFreeze) || - * view.rules().enabled(fixFreezeExploit); - */ - [[maybe_unused]] bool const enforce = view.rules().enabled(featureDeepFreeze); - - for (auto const& [issue, changes] : balanceChanges_) - { - auto const issuerSle = findIssuer(issue.account, view); - // It should be impossible for the issuer to not be found, but check - // just in case so rippled doesn't crash in release. - if (!issuerSle) - { - // The comment above starting with "assert(enforce)" explains this - // assert. - XRPL_ASSERT( - enforce, - "xrpl::TransfersNotFrozen::finalize : enforce " - "invariant."); - if (enforce) - { - return false; - } - continue; - } - - if (!validateIssuerChanges(issuerSle, changes, tx, j, enforce)) - { - return false; - } - } - - return true; -} - -bool -TransfersNotFrozen::isValidEntry( - std::shared_ptr const& before, - std::shared_ptr const& after) -{ - // `after` can never be null, even if the trust line is deleted. - XRPL_ASSERT(after, "xrpl::TransfersNotFrozen::isValidEntry : valid after."); - if (!after) - { - return false; - } - - if (after->getType() == ltACCOUNT_ROOT) - { - possibleIssuers_.emplace(after->at(sfAccount), after); - return false; - } - - /* While LedgerEntryTypesMatch invariant also checks types, all invariants - * are processed regardless of previous failures. - * - * This type check is still necessary here because it prevents potential - * issues in subsequent processing. - */ - return after->getType() == ltRIPPLE_STATE && (!before || before->getType() == ltRIPPLE_STATE); -} - -STAmount -TransfersNotFrozen::calculateBalanceChange( - std::shared_ptr const& before, - std::shared_ptr const& after, - bool isDelete) -{ - auto const getBalance = [](auto const& line, auto const& other, bool zero) { - STAmount amt = line ? line->at(sfBalance) : other->at(sfBalance).zeroed(); - return zero ? amt.zeroed() : amt; - }; - - /* Trust lines can be created dynamically by other transactions such as - * Payment and OfferCreate that cross offers. Such trust line won't be - * created frozen, but the sender might be, so the starting balance must be - * treated as zero. - */ - auto const balanceBefore = getBalance(before, after, false); - - /* Same as above, trust lines can be dynamically deleted, and for frozen - * trust lines, payments not involving the issuer must be blocked. This is - * achieved by treating the final balance as zero when isDelete=true to - * ensure frozen line restrictions are enforced even during deletion. - */ - auto const balanceAfter = getBalance(after, before, isDelete); - - return balanceAfter - balanceBefore; -} - -void -TransfersNotFrozen::recordBalance(Issue const& issue, BalanceChange change) -{ - XRPL_ASSERT( - change.balanceChangeSign, - "xrpl::TransfersNotFrozen::recordBalance : valid trustline " - "balance sign."); - auto& changes = balanceChanges_[issue]; - if (change.balanceChangeSign < 0) - changes.senders.emplace_back(std::move(change)); - else - changes.receivers.emplace_back(std::move(change)); -} - -void -TransfersNotFrozen::recordBalanceChanges( - std::shared_ptr const& after, - STAmount const& balanceChange) -{ - auto const balanceChangeSign = balanceChange.signum(); - auto const currency = after->at(sfBalance).getCurrency(); - - // Change from low account's perspective, which is trust line default - recordBalance({currency, after->at(sfHighLimit).getIssuer()}, {after, balanceChangeSign}); - - // Change from high account's perspective, which reverses the sign. - recordBalance({currency, after->at(sfLowLimit).getIssuer()}, {after, -balanceChangeSign}); -} - -std::shared_ptr -TransfersNotFrozen::findIssuer(AccountID const& issuerID, ReadView const& view) -{ - if (auto it = possibleIssuers_.find(issuerID); it != possibleIssuers_.end()) - { - return it->second; - } - - return view.read(keylet::account(issuerID)); -} - -bool -TransfersNotFrozen::validateIssuerChanges( - std::shared_ptr const& issuer, - IssuerChanges const& changes, - STTx const& tx, - beast::Journal const& j, - bool enforce) -{ - if (!issuer) - { - return false; - } - - bool const globalFreeze = issuer->isFlag(lsfGlobalFreeze); - if (changes.receivers.empty() || changes.senders.empty()) - { - /* If there are no receivers, then the holder(s) are returning - * their tokens to the issuer. Likewise, if there are no - * senders, then the issuer is issuing tokens to the holder(s). - * This is allowed regardless of the issuer's freeze flags. (The - * holder may have contradicting freeze flags, but that will be - * checked when the holder is treated as issuer.) - */ - return true; - } - - for (auto const& actors : {changes.senders, changes.receivers}) - { - for (auto const& change : actors) - { - bool const high = change.line->at(sfLowLimit).getIssuer() == issuer->at(sfAccount); - - if (!validateFrozenState(change, high, tx, j, enforce, globalFreeze)) - { - return false; - } - } - } - return true; -} - -bool -TransfersNotFrozen::validateFrozenState( - BalanceChange const& change, - bool high, - STTx const& tx, - beast::Journal const& j, - bool enforce, - bool globalFreeze) -{ - bool const freeze = - change.balanceChangeSign < 0 && change.line->isFlag(high ? lsfLowFreeze : lsfHighFreeze); - bool const deepFreeze = change.line->isFlag(high ? lsfLowDeepFreeze : lsfHighDeepFreeze); - bool const frozen = globalFreeze || deepFreeze || freeze; - - bool const isAMMLine = change.line->isFlag(lsfAMMNode); - - if (!frozen) - { - return true; - } - - // AMMClawbacks are allowed to override some freeze rules - if ((!isAMMLine || globalFreeze) && hasPrivilege(tx, overrideFreeze)) - { - JLOG(j.debug()) << "Invariant check allowing funds to be moved " - << (change.balanceChangeSign > 0 ? "to" : "from") - << " a frozen trustline for AMMClawback " << tx.getTransactionID(); - return true; - } - - JLOG(j.fatal()) << "Invariant failed: Attempting to move frozen funds for " - << tx.getTransactionID(); - // The comment above starting with "assert(enforce)" explains this assert. - XRPL_ASSERT( - enforce, - "xrpl::TransfersNotFrozen::validateFrozenState : enforce " - "invariant."); - - if (enforce) - { - return false; - } - - return true; -} - -//------------------------------------------------------------------------------ - -void -ValidNewAccountRoot::visitEntry( - bool, - std::shared_ptr const& before, - std::shared_ptr const& after) -{ - if (!before && after->getType() == ltACCOUNT_ROOT) - { - accountsCreated_++; - accountSeq_ = (*after)[sfSequence]; - pseudoAccount_ = isPseudoAccount(after); - flags_ = after->getFlags(); - } -} - -bool -ValidNewAccountRoot::finalize( - STTx const& tx, - TER const result, - XRPAmount const, - ReadView const& view, - beast::Journal const& j) -{ - if (accountsCreated_ == 0) - return true; - - if (accountsCreated_ > 1) - { - JLOG(j.fatal()) << "Invariant failed: multiple accounts " - "created in a single transaction"; - return false; - } - - // From this point on we know exactly one account was created. - if (hasPrivilege(tx, createAcct | createPseudoAcct) && result == tesSUCCESS) - { - bool const pseudoAccount = - (pseudoAccount_ && - (view.rules().enabled(featureSingleAssetVault) || - view.rules().enabled(featureLendingProtocol))); - - if (pseudoAccount && !hasPrivilege(tx, createPseudoAcct)) - { - JLOG(j.fatal()) << "Invariant failed: pseudo-account created by a " - "wrong transaction type"; - return false; - } - - std::uint32_t const startingSeq = pseudoAccount ? 0 : view.seq(); - - if (accountSeq_ != startingSeq) - { - JLOG(j.fatal()) << "Invariant failed: account created with " - "wrong starting sequence number"; - return false; - } - - if (pseudoAccount) - { - std::uint32_t const expected = (lsfDisableMaster | lsfDefaultRipple | lsfDepositAuth); - if (flags_ != expected) - { - JLOG(j.fatal()) << "Invariant failed: pseudo-account created with " - "wrong flags"; - return false; - } - } - - return true; - } - - JLOG(j.fatal()) << "Invariant failed: account root created illegally"; - return false; -} // namespace xrpl - -//------------------------------------------------------------------------------ - -void -ValidNFTokenPage::visitEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) -{ - static constexpr uint256 const& pageBits = nft::pageMask; - static constexpr uint256 const accountBits = ~pageBits; - - if ((before && before->getType() != ltNFTOKEN_PAGE) || - (after && after->getType() != ltNFTOKEN_PAGE)) - return; - - auto check = [this, isDelete](std::shared_ptr const& sle) { - uint256 const account = sle->key() & accountBits; - uint256 const hiLimit = sle->key() & pageBits; - std::optional const prev = (*sle)[~sfPreviousPageMin]; - - // Make sure that any page links... - // 1. Are properly associated with the owning account and - // 2. The page is correctly ordered between links. - if (prev) - { - if (account != (*prev & accountBits)) - badLink_ = true; - - if (hiLimit <= (*prev & pageBits)) - badLink_ = true; - } - - if (auto const next = (*sle)[~sfNextPageMin]) - { - if (account != (*next & accountBits)) - badLink_ = true; - - if (hiLimit >= (*next & pageBits)) - badLink_ = true; - } - - { - auto const& nftokens = sle->getFieldArray(sfNFTokens); - - // An NFTokenPage should never contain too many tokens or be empty. - if (std::size_t const nftokenCount = nftokens.size(); - (!isDelete && nftokenCount == 0) || nftokenCount > dirMaxTokensPerPage) - invalidSize_ = true; - - // If prev is valid, use it to establish a lower bound for - // page entries. If prev is not valid the lower bound is zero. - uint256 const loLimit = prev ? *prev & pageBits : uint256(beast::zero); - - // Also verify that all NFTokenIDs in the page are sorted. - uint256 loCmp = loLimit; - for (auto const& obj : nftokens) - { - uint256 const tokenID = obj[sfNFTokenID]; - if (!nft::compareTokens(loCmp, tokenID)) - badSort_ = true; - loCmp = tokenID; - - // None of the NFTs on this page should belong on lower or - // higher pages. - if (uint256 const tokenPageBits = tokenID & pageBits; - tokenPageBits < loLimit || tokenPageBits >= hiLimit) - badEntry_ = true; - - if (auto uri = obj[~sfURI]; uri && uri->empty()) - badURI_ = true; - } - } - }; - - if (before) - { - check(before); - - // While an account's NFToken directory contains any NFTokens, the last - // NFTokenPage (with 96 bits of 1 in the low part of the index) should - // never be deleted. - if (isDelete && (before->key() & nft::pageMask) == nft::pageMask && - before->isFieldPresent(sfPreviousPageMin)) - { - deletedFinalPage_ = true; - } - } - - if (after) - check(after); - - if (!isDelete && before && after) - { - // If the NFTokenPage - // 1. Has a NextMinPage field in before, but loses it in after, and - // 2. This is not the last page in the directory - // Then we have identified a corruption in the links between the - // NFToken pages in the NFToken directory. - if ((before->key() & nft::pageMask) != nft::pageMask && - before->isFieldPresent(sfNextPageMin) && !after->isFieldPresent(sfNextPageMin)) - { - deletedLink_ = true; - } - } -} - -bool -ValidNFTokenPage::finalize( - STTx const& tx, - TER const result, - XRPAmount const, - ReadView const& view, - beast::Journal const& j) -{ - if (badLink_) - { - JLOG(j.fatal()) << "Invariant failed: NFT page is improperly linked."; - return false; - } - - if (badEntry_) - { - JLOG(j.fatal()) << "Invariant failed: NFT found in incorrect page."; - return false; - } - - if (badSort_) - { - JLOG(j.fatal()) << "Invariant failed: NFTs on page are not sorted."; - return false; - } - - if (badURI_) - { - JLOG(j.fatal()) << "Invariant failed: NFT contains empty URI."; - return false; - } - - if (invalidSize_) - { - JLOG(j.fatal()) << "Invariant failed: NFT page has invalid size."; - return false; - } - - if (view.rules().enabled(fixNFTokenPageLinks)) - { - if (deletedFinalPage_) - { - JLOG(j.fatal()) << "Invariant failed: Last NFT page deleted with " - "non-empty directory."; - return false; - } - if (deletedLink_) - { - JLOG(j.fatal()) << "Invariant failed: Lost NextMinPage link."; - return false; - } - } - - return true; -} - -//------------------------------------------------------------------------------ -void -NFTokenCountTracking::visitEntry( - bool, - std::shared_ptr const& before, - std::shared_ptr const& after) -{ - if (before && before->getType() == ltACCOUNT_ROOT) - { - beforeMintedTotal += (*before)[~sfMintedNFTokens].value_or(0); - beforeBurnedTotal += (*before)[~sfBurnedNFTokens].value_or(0); - } - - if (after && after->getType() == ltACCOUNT_ROOT) - { - afterMintedTotal += (*after)[~sfMintedNFTokens].value_or(0); - afterBurnedTotal += (*after)[~sfBurnedNFTokens].value_or(0); - } -} - -bool -NFTokenCountTracking::finalize( - STTx const& tx, - TER const result, - XRPAmount const, - ReadView const& view, - beast::Journal const& j) -{ - if (!hasPrivilege(tx, changeNFTCounts)) - { - if (beforeMintedTotal != afterMintedTotal) - { - JLOG(j.fatal()) << "Invariant failed: the number of minted tokens " - "changed without a mint transaction!"; - return false; - } - - if (beforeBurnedTotal != afterBurnedTotal) - { - JLOG(j.fatal()) << "Invariant failed: the number of burned tokens " - "changed without a burn transaction!"; - return false; - } - - return true; - } - - if (tx.getTxnType() == ttNFTOKEN_MINT) - { - if (result == tesSUCCESS && beforeMintedTotal >= afterMintedTotal) - { - JLOG(j.fatal()) << "Invariant failed: successful minting didn't increase " - "the number of minted tokens."; - return false; - } - - if (result != tesSUCCESS && beforeMintedTotal != afterMintedTotal) - { - JLOG(j.fatal()) << "Invariant failed: failed minting changed the " - "number of minted tokens."; - return false; - } - - if (beforeBurnedTotal != afterBurnedTotal) - { - JLOG(j.fatal()) << "Invariant failed: minting changed the number of " - "burned tokens."; - return false; - } - } - - if (tx.getTxnType() == ttNFTOKEN_BURN) - { - if (result == tesSUCCESS) - { - if (beforeBurnedTotal >= afterBurnedTotal) - { - JLOG(j.fatal()) << "Invariant failed: successful burning didn't increase " - "the number of burned tokens."; - return false; - } - } - - if (result != tesSUCCESS && beforeBurnedTotal != afterBurnedTotal) - { - JLOG(j.fatal()) << "Invariant failed: failed burning changed the " - "number of burned tokens."; - return false; - } - - if (beforeMintedTotal != afterMintedTotal) - { - JLOG(j.fatal()) << "Invariant failed: burning changed the number of " - "minted tokens."; - return false; - } - } - - return true; -} - -//------------------------------------------------------------------------------ - -void -ValidClawback::visitEntry( - bool, - std::shared_ptr const& before, - std::shared_ptr const&) -{ - if (before && before->getType() == ltRIPPLE_STATE) - trustlinesChanged++; - - if (before && before->getType() == ltMPTOKEN) - mptokensChanged++; -} - -bool -ValidClawback::finalize( - STTx const& tx, - TER const result, - XRPAmount const, - ReadView const& view, - beast::Journal const& j) -{ - if (tx.getTxnType() != ttCLAWBACK) - return true; - - if (result == tesSUCCESS) - { - if (trustlinesChanged > 1) - { - JLOG(j.fatal()) << "Invariant failed: more than one trustline changed."; - return false; - } - - if (mptokensChanged > 1) - { - JLOG(j.fatal()) << "Invariant failed: more than one mptokens changed."; - return false; - } - - if (trustlinesChanged == 1) - { - AccountID const issuer = tx.getAccountID(sfAccount); - STAmount const& amount = tx.getFieldAmount(sfAmount); - AccountID const& holder = amount.getIssuer(); - STAmount const holderBalance = - accountHolds(view, holder, amount.getCurrency(), issuer, fhIGNORE_FREEZE, j); - - if (holderBalance.signum() < 0) - { - JLOG(j.fatal()) << "Invariant failed: trustline balance is negative"; - return false; - } - } - } - else - { - if (trustlinesChanged != 0) - { - JLOG(j.fatal()) << "Invariant failed: some trustlines were changed " - "despite failure of the transaction."; - return false; - } - - if (mptokensChanged != 0) - { - JLOG(j.fatal()) << "Invariant failed: some mptokens were changed " - "despite failure of the transaction."; - return false; - } - } - - return true; -} - -//------------------------------------------------------------------------------ - -void -ValidMPTIssuance::visitEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) -{ - if (after && after->getType() == ltMPTOKEN_ISSUANCE) - { - if (isDelete) - mptIssuancesDeleted_++; - else if (!before) - mptIssuancesCreated_++; - } - - if (after && after->getType() == ltMPTOKEN) - { - if (isDelete) - mptokensDeleted_++; - else if (!before) - { - mptokensCreated_++; - MPTIssue const mptIssue{after->at(sfMPTokenIssuanceID)}; - if (mptIssue.getIssuer() == after->at(sfAccount)) - mptCreatedByIssuer_ = true; - } - } -} - -bool -ValidMPTIssuance::finalize( - STTx const& tx, - TER const result, - XRPAmount const _fee, - ReadView const& view, - beast::Journal const& j) -{ - if (result == tesSUCCESS) - { - auto const& rules = view.rules(); - [[maybe_unused]] - bool enforceCreatedByIssuer = - rules.enabled(featureSingleAssetVault) || rules.enabled(featureLendingProtocol); - if (mptCreatedByIssuer_) - { - JLOG(j.fatal()) << "Invariant failed: MPToken created for the MPT issuer"; - // The comment above starting with "assert(enforce)" explains this - // assert. - XRPL_ASSERT_PARTS( - enforceCreatedByIssuer, "xrpl::ValidMPTIssuance::finalize", "no issuer MPToken"); - if (enforceCreatedByIssuer) - return false; - } - - auto const txnType = tx.getTxnType(); - if (hasPrivilege(tx, createMPTIssuance)) - { - if (mptIssuancesCreated_ == 0) - { - JLOG(j.fatal()) << "Invariant failed: transaction " - "succeeded without creating a MPT issuance"; - } - else if (mptIssuancesDeleted_ != 0) - { - JLOG(j.fatal()) << "Invariant failed: transaction " - "succeeded while removing MPT issuances"; - } - else if (mptIssuancesCreated_ > 1) - { - JLOG(j.fatal()) << "Invariant failed: transaction " - "succeeded but created multiple issuances"; - } - - return mptIssuancesCreated_ == 1 && mptIssuancesDeleted_ == 0; - } - - if (hasPrivilege(tx, destroyMPTIssuance)) - { - if (mptIssuancesDeleted_ == 0) - { - JLOG(j.fatal()) << "Invariant failed: MPT issuance deletion " - "succeeded without removing a MPT issuance"; - } - else if (mptIssuancesCreated_ > 0) - { - JLOG(j.fatal()) << "Invariant failed: MPT issuance deletion " - "succeeded while creating MPT issuances"; - } - else if (mptIssuancesDeleted_ > 1) - { - JLOG(j.fatal()) << "Invariant failed: MPT issuance deletion " - "succeeded but deleted multiple issuances"; - } - - return mptIssuancesCreated_ == 0 && mptIssuancesDeleted_ == 1; - } - - bool const lendingProtocolEnabled = view.rules().enabled(featureLendingProtocol); - // ttESCROW_FINISH may authorize an MPT, but it can't have the - // mayAuthorizeMPT privilege, because that may cause - // non-amendment-gated side effects. - bool const enforceEscrowFinish = (txnType == ttESCROW_FINISH) && - (view.rules().enabled(featureSingleAssetVault) || lendingProtocolEnabled); - if (hasPrivilege(tx, mustAuthorizeMPT | mayAuthorizeMPT) || enforceEscrowFinish) - { - bool const submittedByIssuer = tx.isFieldPresent(sfHolder); - - if (mptIssuancesCreated_ > 0) - { - JLOG(j.fatal()) << "Invariant failed: MPT authorize " - "succeeded but created MPT issuances"; - return false; - } - else if (mptIssuancesDeleted_ > 0) - { - JLOG(j.fatal()) << "Invariant failed: MPT authorize " - "succeeded but deleted issuances"; - return false; - } - else if (lendingProtocolEnabled && mptokensCreated_ + mptokensDeleted_ > 1) - { - JLOG(j.fatal()) << "Invariant failed: MPT authorize succeeded " - "but created/deleted bad number mptokens"; - return false; - } - else if (submittedByIssuer && (mptokensCreated_ > 0 || mptokensDeleted_ > 0)) - { - JLOG(j.fatal()) << "Invariant failed: MPT authorize submitted by issuer " - "succeeded but created/deleted mptokens"; - return false; - } - else if ( - !submittedByIssuer && hasPrivilege(tx, mustAuthorizeMPT) && - (mptokensCreated_ + mptokensDeleted_ != 1)) - { - // if the holder submitted this tx, then a mptoken must be - // either created or deleted. - JLOG(j.fatal()) << "Invariant failed: MPT authorize submitted by holder " - "succeeded but created/deleted bad number of mptokens"; - return false; - } - - return true; - } - if (txnType == ttESCROW_FINISH) - { - // ttESCROW_FINISH may authorize an MPT, but it can't have the - // mayAuthorizeMPT privilege, because that may cause - // non-amendment-gated side effects. - XRPL_ASSERT_PARTS( - !enforceEscrowFinish, "xrpl::ValidMPTIssuance::finalize", "not escrow finish tx"); - return true; - } - - if (hasPrivilege(tx, mayDeleteMPT) && mptokensDeleted_ == 1 && mptokensCreated_ == 0 && - mptIssuancesCreated_ == 0 && mptIssuancesDeleted_ == 0) - return true; - } - - if (mptIssuancesCreated_ != 0) - { - JLOG(j.fatal()) << "Invariant failed: a MPT issuance was created"; - } - else if (mptIssuancesDeleted_ != 0) - { - JLOG(j.fatal()) << "Invariant failed: a MPT issuance was deleted"; - } - else if (mptokensCreated_ != 0) - { - JLOG(j.fatal()) << "Invariant failed: a MPToken was created"; - } - else if (mptokensDeleted_ != 0) - { - JLOG(j.fatal()) << "Invariant failed: a MPToken was deleted"; - } - - return mptIssuancesCreated_ == 0 && mptIssuancesDeleted_ == 0 && mptokensCreated_ == 0 && - mptokensDeleted_ == 0; -} - -//------------------------------------------------------------------------------ - -void -ValidPermissionedDomain::visitEntry( - bool isDel, - std::shared_ptr const& before, - std::shared_ptr const& after) -{ - if (before && before->getType() != ltPERMISSIONED_DOMAIN) - return; - if (after && after->getType() != ltPERMISSIONED_DOMAIN) - return; - - auto check = [isDel](std::vector& sleStatus, std::shared_ptr const& sle) { - auto const& credentials = sle->getFieldArray(sfAcceptedCredentials); - auto const sorted = credentials::makeSorted(credentials); - - SleStatus ss{credentials.size(), false, !sorted.empty(), isDel}; - - // If array have duplicates then all the other checks are invalid - if (ss.isUnique_) - { - unsigned i = 0; - for (auto const& cred : sorted) - { - auto const& credTx = credentials[i++]; - ss.isSorted_ = - (cred.first == credTx[sfIssuer]) && (cred.second == credTx[sfCredentialType]); - if (!ss.isSorted_) - break; - } - } - sleStatus.emplace_back(std::move(ss)); - }; - - if (after) - check(sleStatus_, after); -} - -bool -ValidPermissionedDomain::finalize( - STTx const& tx, - TER const result, - XRPAmount const, - ReadView const& view, - beast::Journal const& j) -{ - auto check = [](SleStatus const& sleStatus, beast::Journal const& j) { - if (!sleStatus.credentialsSize_) - { - JLOG(j.fatal()) << "Invariant failed: permissioned domain with " - "no rules."; - return false; - } - - if (sleStatus.credentialsSize_ > maxPermissionedDomainCredentialsArraySize) - { - JLOG(j.fatal()) << "Invariant failed: permissioned domain bad " - "credentials size " - << sleStatus.credentialsSize_; - return false; - } - - if (!sleStatus.isUnique_) - { - JLOG(j.fatal()) << "Invariant failed: permissioned domain credentials " - "aren't unique"; - return false; - } - - if (!sleStatus.isSorted_) - { - JLOG(j.fatal()) << "Invariant failed: permissioned domain credentials " - "aren't sorted"; - return false; - } - - return true; - }; - - if (view.rules().enabled(fixPermissionedDomainInvariant)) - { - // No permissioned domains should be affected if the transaction failed - if (result != tesSUCCESS) - // If nothing changed, all is good. If there were changes, that's - // bad. - return sleStatus_.empty(); - - if (sleStatus_.size() > 1) - { - JLOG(j.fatal()) << "Invariant failed: transaction affected more " - "than 1 permissioned domain entry."; - return false; - } - - switch (tx.getTxnType()) - { - case ttPERMISSIONED_DOMAIN_SET: { - if (sleStatus_.empty()) - { - JLOG(j.fatal()) << "Invariant failed: no domain objects affected by " - "PermissionedDomainSet"; - return false; - } - - auto const& sleStatus = sleStatus_[0]; - if (sleStatus.isDelete_) - { - JLOG(j.fatal()) << "Invariant failed: domain object " - "deleted by PermissionedDomainSet"; - return false; - } - return check(sleStatus, j); - } - case ttPERMISSIONED_DOMAIN_DELETE: { - if (sleStatus_.empty()) - { - JLOG(j.fatal()) << "Invariant failed: no domain objects affected by " - "PermissionedDomainDelete"; - return false; - } - - if (!sleStatus_[0].isDelete_) - { - JLOG(j.fatal()) << "Invariant failed: domain object " - "modified, but not deleted by " - "PermissionedDomainDelete"; - return false; - } - return true; - } - default: { - if (!sleStatus_.empty()) - { - JLOG(j.fatal()) << "Invariant failed: " << sleStatus_.size() - << " domain object(s) affected by an " - "unauthorized transaction. " - << tx.getTxnType(); - return false; - } - return true; - } - } - } - else - { - if (tx.getTxnType() != ttPERMISSIONED_DOMAIN_SET || result != tesSUCCESS || - sleStatus_.empty()) - return true; - return check(sleStatus_[0], j); - } -} - -//------------------------------------------------------------------------------ - -void -ValidPseudoAccounts::visitEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) -{ - if (isDelete) - // Deletion is ignored - return; - - if (after && after->getType() == ltACCOUNT_ROOT) - { - bool const isPseudo = [&]() { - // isPseudoAccount checks that any of the pseudo-account fields are - // set. - if (isPseudoAccount(after)) - return true; - // Not all pseudo-accounts have a zero sequence, but all accounts - // with a zero sequence had better be pseudo-accounts. - if (after->at(sfSequence) == 0) - return true; - - return false; - }(); - if (isPseudo) - { - // Pseudo accounts must have the following properties: - // 1. Exactly one of the pseudo-account fields is set. - // 2. The sequence number is not changed. - // 3. The lsfDisableMaster, lsfDefaultRipple, and lsfDepositAuth - // flags are set. - // 4. The RegularKey is not set. - { - std::vector const& fields = getPseudoAccountFields(); - - auto const numFields = - std::count_if(fields.begin(), fields.end(), [&after](SField const* sf) -> bool { - return after->isFieldPresent(*sf); - }); - if (numFields != 1) - { - std::stringstream error; - error << "pseudo-account has " << numFields << " pseudo-account fields set"; - errors_.emplace_back(error.str()); - } - } - if (before && before->at(sfSequence) != after->at(sfSequence)) - { - errors_.emplace_back("pseudo-account sequence changed"); - } - if (!after->isFlag(lsfDisableMaster | lsfDefaultRipple | lsfDepositAuth)) - { - errors_.emplace_back("pseudo-account flags are not set"); - } - if (after->isFieldPresent(sfRegularKey)) - { - errors_.emplace_back("pseudo-account has a regular key"); - } - } - } -} - -bool -ValidPseudoAccounts::finalize( - STTx const& tx, - TER const, - XRPAmount const, - ReadView const& view, - beast::Journal const& j) -{ - bool const enforce = view.rules().enabled(featureSingleAssetVault); - XRPL_ASSERT( - errors_.empty() || enforce, - "xrpl::ValidPseudoAccounts::finalize : no bad " - "changes or enforce invariant"); - if (!errors_.empty()) - { - for (auto const& error : errors_) - { - JLOG(j.fatal()) << "Invariant failed: " << error; - } - if (enforce) - return false; - } - return true; -} - -//------------------------------------------------------------------------------ - -void -ValidPermissionedDEX::visitEntry( - bool, - std::shared_ptr const& before, - std::shared_ptr const& after) -{ - if (after && after->getType() == ltDIR_NODE) - { - if (after->isFieldPresent(sfDomainID)) - domains_.insert(after->getFieldH256(sfDomainID)); - } - - if (after && after->getType() == ltOFFER) - { - if (after->isFieldPresent(sfDomainID)) - domains_.insert(after->getFieldH256(sfDomainID)); - else - regularOffers_ = true; - - // if a hybrid offer is missing domain or additional book, there's - // something wrong - if (after->isFlag(lsfHybrid) && - (!after->isFieldPresent(sfDomainID) || !after->isFieldPresent(sfAdditionalBooks) || - after->getFieldArray(sfAdditionalBooks).size() > 1)) - badHybrids_ = true; - } -} - -bool -ValidPermissionedDEX::finalize( - STTx const& tx, - TER const result, - XRPAmount const, - ReadView const& view, - beast::Journal const& j) -{ - auto const txType = tx.getTxnType(); - if ((txType != ttPAYMENT && txType != ttOFFER_CREATE) || result != tesSUCCESS) - return true; - - // For each offercreate transaction, check if - // permissioned offers are valid - if (txType == ttOFFER_CREATE && badHybrids_) - { - JLOG(j.fatal()) << "Invariant failed: hybrid offer is malformed"; - return false; - } - - if (!tx.isFieldPresent(sfDomainID)) - return true; - - auto const domain = tx.getFieldH256(sfDomainID); - - if (!view.exists(keylet::permissionedDomain(domain))) - { - JLOG(j.fatal()) << "Invariant failed: domain doesn't exist"; - return false; - } - - // for both payment and offercreate, there shouldn't be another domain - // that's different from the domain specified - for (auto const& d : domains_) - { - if (d != domain) - { - JLOG(j.fatal()) << "Invariant failed: transaction" - " consumed wrong domains"; - return false; - } - } - - if (regularOffers_) - { - JLOG(j.fatal()) << "Invariant failed: domain transaction" - " affected regular offers"; - return false; - } - - return true; -} - -void -ValidAMM::visitEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) -{ - if (isDelete) - return; - - if (after) - { - auto const type = after->getType(); - // AMM object changed - if (type == ltAMM) - { - ammAccount_ = after->getAccountID(sfAccount); - lptAMMBalanceAfter_ = after->getFieldAmount(sfLPTokenBalance); - } - // AMM pool changed - else if ( - (type == ltRIPPLE_STATE && after->getFlags() & lsfAMMNode) || - (type == ltACCOUNT_ROOT && after->isFieldPresent(sfAMMID))) - { - ammPoolChanged_ = true; - } - } - - if (before) - { - // AMM object changed - if (before->getType() == ltAMM) - { - lptAMMBalanceBefore_ = before->getFieldAmount(sfLPTokenBalance); - } - } -} - -static bool -validBalances( - STAmount const& amount, - STAmount const& amount2, - STAmount const& lptAMMBalance, - ValidAMM::ZeroAllowed zeroAllowed) -{ - bool const positive = - amount > beast::zero && amount2 > beast::zero && lptAMMBalance > beast::zero; - if (zeroAllowed == ValidAMM::ZeroAllowed::Yes) - return positive || - (amount == beast::zero && amount2 == beast::zero && lptAMMBalance == beast::zero); - return positive; -} - -bool -ValidAMM::finalizeVote(bool enforce, beast::Journal const& j) const -{ - if (lptAMMBalanceAfter_ != lptAMMBalanceBefore_ || ammPoolChanged_) - { - // LPTokens and the pool can not change on vote - // LCOV_EXCL_START - JLOG(j.error()) << "AMMVote invariant failed: " << lptAMMBalanceBefore_.value_or(STAmount{}) - << " " << lptAMMBalanceAfter_.value_or(STAmount{}) << " " - << ammPoolChanged_; - if (enforce) - return false; - // LCOV_EXCL_STOP - } - - return true; -} - -bool -ValidAMM::finalizeBid(bool enforce, beast::Journal const& j) const -{ - if (ammPoolChanged_) - { - // The pool can not change on bid - // LCOV_EXCL_START - JLOG(j.error()) << "AMMBid invariant failed: pool changed"; - if (enforce) - return false; - // LCOV_EXCL_STOP - } - // LPTokens are burnt, therefore there should be fewer LPTokens - else if ( - lptAMMBalanceBefore_ && lptAMMBalanceAfter_ && - (*lptAMMBalanceAfter_ > *lptAMMBalanceBefore_ || *lptAMMBalanceAfter_ <= beast::zero)) - { - // LCOV_EXCL_START - JLOG(j.error()) << "AMMBid invariant failed: " << *lptAMMBalanceBefore_ << " " - << *lptAMMBalanceAfter_; - if (enforce) - return false; - // LCOV_EXCL_STOP - } - - return true; -} - -bool -ValidAMM::finalizeCreate( - STTx const& tx, - ReadView const& view, - bool enforce, - beast::Journal const& j) const -{ - if (!ammAccount_) - { - // LCOV_EXCL_START - JLOG(j.error()) << "AMMCreate invariant failed: AMM object is not created"; - if (enforce) - return false; - // LCOV_EXCL_STOP - } - else - { - auto const [amount, amount2] = ammPoolHolds( - view, - *ammAccount_, - tx[sfAmount].get(), - tx[sfAmount2].get(), - fhIGNORE_FREEZE, - j); - // Create invariant: - // sqrt(amount * amount2) == LPTokens - // all balances are greater than zero - if (!validBalances(amount, amount2, *lptAMMBalanceAfter_, ZeroAllowed::No) || - ammLPTokens(amount, amount2, lptAMMBalanceAfter_->issue()) != *lptAMMBalanceAfter_) - { - JLOG(j.error()) << "AMMCreate invariant failed: " << amount << " " << amount2 << " " - << *lptAMMBalanceAfter_; - if (enforce) - return false; - } - } - - return true; -} - -bool -ValidAMM::finalizeDelete(bool enforce, TER res, beast::Journal const& j) const -{ - if (ammAccount_) - { - // LCOV_EXCL_START - std::string const msg = (res == tesSUCCESS) ? "AMM object is not deleted on tesSUCCESS" - : "AMM object is changed on tecINCOMPLETE"; - JLOG(j.error()) << "AMMDelete invariant failed: " << msg; - if (enforce) - return false; - // LCOV_EXCL_STOP - } - - return true; -} - -bool -ValidAMM::finalizeDEX(bool enforce, beast::Journal const& j) const -{ - if (ammAccount_) - { - // LCOV_EXCL_START - JLOG(j.error()) << "AMM swap invariant failed: AMM object changed"; - if (enforce) - return false; - // LCOV_EXCL_STOP - } - - return true; -} - -bool -ValidAMM::generalInvariant( - xrpl::STTx const& tx, - xrpl::ReadView const& view, - ZeroAllowed zeroAllowed, - beast::Journal const& j) const -{ - auto const [amount, amount2] = ammPoolHolds( - view, - *ammAccount_, - tx[sfAsset].get(), - tx[sfAsset2].get(), - fhIGNORE_FREEZE, - j); - // Deposit and Withdrawal invariant: - // sqrt(amount * amount2) >= LPTokens - // all balances are greater than zero - // unless on last withdrawal - 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::zero && - withinRelativeDistance(poolProductMean, Number{*lptAMMBalanceAfter_}, Number{1, -11}); - }; - if (!nonNegativeBalances || (!strongInvariantCheck && !weakInvariantCheck())) - { - JLOG(j.error()) << "AMM " << tx.getTxnType() - << " invariant failed: " << tx.getHash(HashPrefix::transactionID) << " " - << ammPoolChanged_ << " " << amount << " " << amount2 << " " - << poolProductMean << " " << lptAMMBalanceAfter_->getText() << " " - << ((*lptAMMBalanceAfter_ == beast::zero) - ? Number{1} - : ((*lptAMMBalanceAfter_ - poolProductMean) / poolProductMean)); - return false; - } - - return true; -} - -bool -ValidAMM::finalizeDeposit( - xrpl::STTx const& tx, - xrpl::ReadView const& view, - bool enforce, - beast::Journal const& j) const -{ - if (!ammAccount_) - { - // LCOV_EXCL_START - JLOG(j.error()) << "AMMDeposit invariant failed: AMM object is deleted"; - if (enforce) - return false; - // LCOV_EXCL_STOP - } - else if (!generalInvariant(tx, view, ZeroAllowed::No, j) && enforce) - return false; - - return true; -} - -bool -ValidAMM::finalizeWithdraw( - xrpl::STTx const& tx, - xrpl::ReadView const& view, - bool enforce, - beast::Journal const& j) const -{ - if (!ammAccount_) - { - // Last Withdraw or Clawback deleted AMM - } - else if (!generalInvariant(tx, view, ZeroAllowed::Yes, j)) - { - if (enforce) - return false; - } - - return true; -} - -bool -ValidAMM::finalize( - STTx const& tx, - TER const result, - XRPAmount const, - ReadView const& view, - beast::Journal const& j) -{ - // Delete may return tecINCOMPLETE if there are too many - // trustlines to delete. - if (result != tesSUCCESS && result != tecINCOMPLETE) - return true; - - bool const enforce = view.rules().enabled(fixAMMv1_3); - - switch (tx.getTxnType()) - { - case ttAMM_CREATE: - return finalizeCreate(tx, view, enforce, j); - case ttAMM_DEPOSIT: - return finalizeDeposit(tx, view, enforce, j); - case ttAMM_CLAWBACK: - case ttAMM_WITHDRAW: - return finalizeWithdraw(tx, view, enforce, j); - case ttAMM_BID: - return finalizeBid(enforce, j); - case ttAMM_VOTE: - return finalizeVote(enforce, j); - case ttAMM_DELETE: - return finalizeDelete(enforce, result, j); - case ttCHECK_CASH: - case ttOFFER_CREATE: - case ttPAYMENT: - return finalizeDEX(enforce, j); - default: - break; - } - - return true; -} - -//------------------------------------------------------------------------------ - -void -NoModifiedUnmodifiableFields::visitEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) -{ - if (isDelete || !before) - // Creation and deletion are ignored - return; - - changedEntries_.emplace(before, after); -} - -bool -NoModifiedUnmodifiableFields::finalize( - STTx const& tx, - TER const, - XRPAmount const, - ReadView const& view, - beast::Journal const& j) -{ - static auto const fieldChanged = [](auto const& before, auto const& after, auto const& field) { - bool const beforeField = before->isFieldPresent(field); - bool const afterField = after->isFieldPresent(field); - return beforeField != afterField || (afterField && before->at(field) != after->at(field)); - }; - for (auto const& slePair : changedEntries_) - { - auto const& before = slePair.first; - auto const& after = slePair.second; - auto const type = after->getType(); - bool bad = false; - [[maybe_unused]] bool enforce = false; - switch (type) - { - case ltLOAN_BROKER: - /* - * We check this invariant regardless of lending protocol - * amendment status, allowing for detection and logging of - * potential issues even when the amendment is disabled. - */ - enforce = view.rules().enabled(featureLendingProtocol); - bad = fieldChanged(before, after, sfLedgerEntryType) || - fieldChanged(before, after, sfLedgerIndex) || - fieldChanged(before, after, sfSequence) || - fieldChanged(before, after, sfOwnerNode) || - fieldChanged(before, after, sfVaultNode) || - fieldChanged(before, after, sfVaultID) || - fieldChanged(before, after, sfAccount) || - fieldChanged(before, after, sfOwner) || - fieldChanged(before, after, sfManagementFeeRate) || - fieldChanged(before, after, sfCoverRateMinimum) || - fieldChanged(before, after, sfCoverRateLiquidation); - break; - case ltLOAN: - /* - * We check this invariant regardless of lending protocol - * amendment status, allowing for detection and logging of - * potential issues even when the amendment is disabled. - */ - enforce = view.rules().enabled(featureLendingProtocol); - bad = fieldChanged(before, after, sfLedgerEntryType) || - fieldChanged(before, after, sfLedgerIndex) || - fieldChanged(before, after, sfSequence) || - fieldChanged(before, after, sfOwnerNode) || - fieldChanged(before, after, sfLoanBrokerNode) || - fieldChanged(before, after, sfLoanBrokerID) || - fieldChanged(before, after, sfBorrower) || - fieldChanged(before, after, sfLoanOriginationFee) || - fieldChanged(before, after, sfLoanServiceFee) || - fieldChanged(before, after, sfLatePaymentFee) || - fieldChanged(before, after, sfClosePaymentFee) || - fieldChanged(before, after, sfOverpaymentFee) || - fieldChanged(before, after, sfInterestRate) || - fieldChanged(before, after, sfLateInterestRate) || - fieldChanged(before, after, sfCloseInterestRate) || - fieldChanged(before, after, sfOverpaymentInterestRate) || - fieldChanged(before, after, sfStartDate) || - fieldChanged(before, after, sfPaymentInterval) || - fieldChanged(before, after, sfGracePeriod) || - fieldChanged(before, after, sfLoanScale); - break; - default: - /* - * We check this invariant regardless of lending protocol - * amendment status, allowing for detection and logging of - * potential issues even when the amendment is disabled. - * - * We use the lending protocol as a gate, even though - * all transactions are affected because that's when it - * was added. - */ - enforce = view.rules().enabled(featureLendingProtocol); - bad = fieldChanged(before, after, sfLedgerEntryType) || - fieldChanged(before, after, sfLedgerIndex); - } - XRPL_ASSERT( - !bad || enforce, - "xrpl::NoModifiedUnmodifiableFields::finalize : no bad " - "changes or enforce invariant"); - if (bad) - { - JLOG(j.fatal()) << "Invariant failed: changed an unchangeable field for " - << tx.getTransactionID(); - if (enforce) - return false; - } - } - return true; -} - -//------------------------------------------------------------------------------ - -void -ValidLoanBroker::visitEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) -{ - if (after) - { - if (after->getType() == ltLOAN_BROKER) - { - auto& broker = brokers_[after->key()]; - broker.brokerBefore = before; - broker.brokerAfter = after; - } - else if (after->getType() == ltACCOUNT_ROOT && after->isFieldPresent(sfLoanBrokerID)) - { - auto const& loanBrokerID = after->at(sfLoanBrokerID); - // create an entry if one doesn't already exist - brokers_.emplace(loanBrokerID, BrokerInfo{}); - } - else if (after->getType() == ltRIPPLE_STATE) - { - lines_.emplace_back(after); - } - else if (after->getType() == ltMPTOKEN) - { - mpts_.emplace_back(after); - } - } -} - -bool -ValidLoanBroker::goodZeroDirectory( - ReadView const& view, - SLE::const_ref dir, - beast::Journal const& j) const -{ - auto const next = dir->at(~sfIndexNext); - auto const prev = dir->at(~sfIndexPrevious); - if ((prev && *prev) || (next && *next)) - { - JLOG(j.fatal()) << "Invariant failed: Loan Broker with zero " - "OwnerCount has multiple directory pages"; - return false; - } - auto indexes = dir->getFieldV256(sfIndexes); - if (indexes.size() > 1) - { - JLOG(j.fatal()) << "Invariant failed: Loan Broker with zero " - "OwnerCount has multiple indexes in the Directory root"; - return false; - } - if (indexes.size() == 1) - { - auto const index = indexes.value().front(); - auto const sle = view.read(keylet::unchecked(index)); - if (!sle) - { - JLOG(j.fatal()) << "Invariant failed: Loan Broker directory corrupt"; - return false; - } - if (sle->getType() != ltRIPPLE_STATE && sle->getType() != ltMPTOKEN) - { - JLOG(j.fatal()) << "Invariant failed: Loan Broker with zero " - "OwnerCount has an unexpected entry in the directory"; - return false; - } - } - - return true; -} - -bool -ValidLoanBroker::finalize( - STTx const& tx, - TER const, - XRPAmount const, - ReadView const& view, - beast::Journal const& j) -{ - // Loan Brokers will not exist on ledger if the Lending Protocol amendment - // is not enabled, so there's no need to check it. - - for (auto const& line : lines_) - { - for (auto const& field : {&sfLowLimit, &sfHighLimit}) - { - auto const account = view.read(keylet::account(line->at(*field).getIssuer())); - // This Invariant doesn't know about the rules for Trust Lines, so - // if the account is missing, don't treat it as an error. This - // loop is only concerned with finding Broker pseudo-accounts - if (account && account->isFieldPresent(sfLoanBrokerID)) - { - auto const& loanBrokerID = account->at(sfLoanBrokerID); - // create an entry if one doesn't already exist - brokers_.emplace(loanBrokerID, BrokerInfo{}); - } - } - } - for (auto const& mpt : mpts_) - { - auto const account = view.read(keylet::account(mpt->at(sfAccount))); - // This Invariant doesn't know about the rules for MPTokens, so - // if the account is missing, don't treat is as an error. This - // loop is only concerned with finding Broker pseudo-accounts - if (account && account->isFieldPresent(sfLoanBrokerID)) - { - auto const& loanBrokerID = account->at(sfLoanBrokerID); - // create an entry if one doesn't already exist - brokers_.emplace(loanBrokerID, BrokerInfo{}); - } - } - - for (auto const& [brokerID, broker] : brokers_) - { - auto const& after = - broker.brokerAfter ? broker.brokerAfter : view.read(keylet::loanbroker(brokerID)); - - if (!after) - { - JLOG(j.fatal()) << "Invariant failed: Loan Broker missing"; - return false; - } - - auto const& before = broker.brokerBefore; - - // https://github.com/Tapanito/XRPL-Standards/blob/xls-66-lending-protocol/XLS-0066d-lending-protocol/README.md#3123-invariants - // If `LoanBroker.OwnerCount = 0` the `DirectoryNode` will have at most - // one node (the root), which will only hold entries for `RippleState` - // or `MPToken` objects. - if (after->at(sfOwnerCount) == 0) - { - auto const dir = view.read(keylet::ownerDir(after->at(sfAccount))); - if (dir) - { - if (!goodZeroDirectory(view, dir, j)) - { - return false; - } - } - } - if (before && before->at(sfLoanSequence) > after->at(sfLoanSequence)) - { - JLOG(j.fatal()) << "Invariant failed: Loan Broker sequence number " - "decreased"; - return false; - } - if (after->at(sfDebtTotal) < 0) - { - JLOG(j.fatal()) << "Invariant failed: Loan Broker debt total is negative"; - return false; - } - if (after->at(sfCoverAvailable) < 0) - { - JLOG(j.fatal()) << "Invariant failed: Loan Broker cover available is negative"; - return false; - } - auto const vault = view.read(keylet::vault(after->at(sfVaultID))); - if (!vault) - { - JLOG(j.fatal()) << "Invariant failed: Loan Broker vault ID is invalid"; - return false; - } - auto const& vaultAsset = vault->at(sfAsset); - if (after->at(sfCoverAvailable) < accountHolds( - view, - after->at(sfAccount), - vaultAsset, - FreezeHandling::fhIGNORE_FREEZE, - AuthHandling::ahIGNORE_AUTH, - j)) - { - JLOG(j.fatal()) << "Invariant failed: Loan Broker cover available " - "is less than pseudo-account asset balance"; - return false; - } - } - return true; -} - -//------------------------------------------------------------------------------ - -void -ValidLoan::visitEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) -{ - if (after && after->getType() == ltLOAN) - { - loans_.emplace_back(before, after); - } -} - -bool -ValidLoan::finalize( - STTx const& tx, - TER const, - XRPAmount const, - ReadView const& view, - beast::Journal const& j) -{ - // Loans will not exist on ledger if the Lending Protocol amendment - // is not enabled, so there's no need to check it. - - for (auto const& [before, after] : loans_) - { - // https://github.com/Tapanito/XRPL-Standards/blob/xls-66-lending-protocol/XLS-0066d-lending-protocol/README.md#3223-invariants - // If `Loan.PaymentRemaining = 0` then the loan MUST be fully paid off - if (after->at(sfPaymentRemaining) == 0 && - (after->at(sfTotalValueOutstanding) != beast::zero || - after->at(sfPrincipalOutstanding) != beast::zero || - after->at(sfManagementFeeOutstanding) != beast::zero)) - { - JLOG(j.fatal()) << "Invariant failed: Loan with zero payments " - "remaining has not been paid off"; - return false; - } - // If `Loan.PaymentRemaining != 0` then the loan MUST NOT be fully paid - // off - if (after->at(sfPaymentRemaining) != 0 && - after->at(sfTotalValueOutstanding) == beast::zero && - after->at(sfPrincipalOutstanding) == beast::zero && - after->at(sfManagementFeeOutstanding) == beast::zero) - { - JLOG(j.fatal()) << "Invariant failed: Loan with zero payments " - "remaining has not been paid off"; - return false; - } - if (before && (before->isFlag(lsfLoanOverpayment) != after->isFlag(lsfLoanOverpayment))) - { - JLOG(j.fatal()) << "Invariant failed: Loan Overpayment flag changed"; - return false; - } - // Must not be negative - STNumber - for (auto const field : - {&sfLoanServiceFee, - &sfLatePaymentFee, - &sfClosePaymentFee, - &sfPrincipalOutstanding, - &sfTotalValueOutstanding, - &sfManagementFeeOutstanding}) - { - if (after->at(*field) < 0) - { - JLOG(j.fatal()) << "Invariant failed: " << field->getName() << " is negative "; - return false; - } - } - // Must be positive - STNumber - for (auto const field : { - &sfPeriodicPayment, - }) - { - if (after->at(*field) <= 0) - { - JLOG(j.fatal()) << "Invariant failed: " << field->getName() - << " is zero or negative "; - return false; - } - } - } - return true; -} - -ValidVault::Vault -ValidVault::Vault::make(SLE const& from) -{ - XRPL_ASSERT(from.getType() == ltVAULT, "ValidVault::Vault::make : from Vault object"); - - ValidVault::Vault self; - self.key = from.key(); - self.asset = from.at(sfAsset); - self.pseudoId = from.getAccountID(sfAccount); - self.owner = from.at(sfOwner); - self.shareMPTID = from.getFieldH192(sfShareMPTID); - self.assetsTotal = from.at(sfAssetsTotal); - self.assetsAvailable = from.at(sfAssetsAvailable); - self.assetsMaximum = from.at(sfAssetsMaximum); - self.lossUnrealized = from.at(sfLossUnrealized); - return self; -} - -ValidVault::Shares -ValidVault::Shares::make(SLE const& from) -{ - XRPL_ASSERT( - from.getType() == ltMPTOKEN_ISSUANCE, - "ValidVault::Shares::make : from MPTokenIssuance object"); - - ValidVault::Shares self; - self.share = MPTIssue(makeMptID(from.getFieldU32(sfSequence), from.getAccountID(sfIssuer))); - self.sharesTotal = from.at(sfOutstandingAmount); - self.sharesMaximum = from[~sfMaximumAmount].value_or(maxMPTokenAmount); - return self; -} - -void -ValidVault::visitEntry( - bool isDelete, - std::shared_ptr const& before, - std::shared_ptr const& after) -{ - // If `before` is empty, this means an object is being created, in which - // case `isDelete` must be false. Otherwise `before` and `after` are set and - // `isDelete` indicates whether an object is being deleted or modified. - XRPL_ASSERT( - after != nullptr && (before != nullptr || !isDelete), - "xrpl::ValidVault::visitEntry : some object is available"); - - // Number balanceDelta will capture the difference (delta) between "before" - // state (zero if created) and "after" state (zero if destroyed), so the - // invariants can validate that the change in account balances matches the - // change in vault balances, stored to deltas_ at the end of this function. - Number balanceDelta{}; - - std::int8_t sign = 0; - if (before) - { - switch (before->getType()) - { - case ltVAULT: - beforeVault_.push_back(Vault::make(*before)); - break; - case ltMPTOKEN_ISSUANCE: - // At this moment we have no way of telling if this object holds - // vault shares or something else. Save it for finalize. - beforeMPTs_.push_back(Shares::make(*before)); - balanceDelta = static_cast(before->getFieldU64(sfOutstandingAmount)); - sign = 1; - break; - case ltMPTOKEN: - balanceDelta = static_cast(before->getFieldU64(sfMPTAmount)); - sign = -1; - break; - case ltACCOUNT_ROOT: - case ltRIPPLE_STATE: - balanceDelta = before->getFieldAmount(sfBalance); - sign = -1; - break; - default:; - } - } - - if (!isDelete && after) - { - switch (after->getType()) - { - case ltVAULT: - afterVault_.push_back(Vault::make(*after)); - break; - case ltMPTOKEN_ISSUANCE: - // At this moment we have no way of telling if this object holds - // vault shares or something else. Save it for finalize. - afterMPTs_.push_back(Shares::make(*after)); - balanceDelta -= - Number(static_cast(after->getFieldU64(sfOutstandingAmount))); - sign = 1; - break; - case ltMPTOKEN: - balanceDelta -= Number(static_cast(after->getFieldU64(sfMPTAmount))); - sign = -1; - break; - case ltACCOUNT_ROOT: - case ltRIPPLE_STATE: - balanceDelta -= Number(after->getFieldAmount(sfBalance)); - sign = -1; - break; - default:; - } - } - - uint256 const key = (before ? before->key() : after->key()); - // Append to deltas if sign is non-zero, i.e. an object of an interesting - // type has been updated. A transaction may update an object even when - // its balance has not changed, e.g. transaction fee equals the amount - // transferred to the account. We intentionally do not compare balanceDelta - // against zero, to avoid missing such updates. - if (sign != 0) - deltas_[key] = balanceDelta * sign; -} - -bool -ValidVault::finalize( - STTx const& tx, - TER const ret, - XRPAmount const fee, - ReadView const& view, - beast::Journal const& j) -{ - bool const enforce = view.rules().enabled(featureSingleAssetVault); - - if (!isTesSuccess(ret)) - return true; // Do not perform checks - - if (afterVault_.empty() && beforeVault_.empty()) - { - if (hasPrivilege(tx, mustModifyVault)) - { - JLOG(j.fatal()) << // - "Invariant failed: vault operation succeeded without modifying " - "a vault"; - XRPL_ASSERT(enforce, "xrpl::ValidVault::finalize : vault noop invariant"); - return !enforce; - } - - return true; // Not a vault operation - } - else if (!(hasPrivilege(tx, mustModifyVault) || hasPrivilege(tx, mayModifyVault))) - { - JLOG(j.fatal()) << // - "Invariant failed: vault updated by a wrong transaction type"; - XRPL_ASSERT( - enforce, - "xrpl::ValidVault::finalize : illegal vault transaction " - "invariant"); - return !enforce; // Also not a vault operation - } - - if (beforeVault_.size() > 1 || afterVault_.size() > 1) - { - JLOG(j.fatal()) << // - "Invariant failed: vault operation updated more than single vault"; - XRPL_ASSERT(enforce, "xrpl::ValidVault::finalize : single vault invariant"); - return !enforce; // That's all we can do here - } - - auto const txnType = tx.getTxnType(); - - // We do special handling for ttVAULT_DELETE first, because it's the only - // vault-modifying transaction without an "after" state of the vault - if (afterVault_.empty()) - { - if (txnType != ttVAULT_DELETE) - { - JLOG(j.fatal()) << // - "Invariant failed: vault deleted by a wrong transaction type"; - XRPL_ASSERT( - enforce, - "xrpl::ValidVault::finalize : illegal vault deletion " - "invariant"); - return !enforce; // That's all we can do here - } - - // Note, if afterVault_ is empty then we know that beforeVault_ is not - // empty, as enforced at the top of this function - auto const& beforeVault = beforeVault_[0]; - - // At this moment we only know a vault is being deleted and there - // might be some MPTokenIssuance objects which are deleted in the - // same transaction. Find the one matching this vault. - auto const deletedShares = [&]() -> std::optional { - for (auto const& e : beforeMPTs_) - { - if (e.share.getMptID() == beforeVault.shareMPTID) - return std::move(e); - } - return std::nullopt; - }(); - - if (!deletedShares) - { - JLOG(j.fatal()) << "Invariant failed: deleted vault must also " - "delete shares"; - XRPL_ASSERT(enforce, "xrpl::ValidVault::finalize : shares deletion invariant"); - return !enforce; // That's all we can do here - } - - bool result = true; - if (deletedShares->sharesTotal != 0) - { - JLOG(j.fatal()) << "Invariant failed: deleted vault must have no " - "shares outstanding"; - result = false; - } - if (beforeVault.assetsTotal != zero) - { - JLOG(j.fatal()) << "Invariant failed: deleted vault must have no " - "assets outstanding"; - result = false; - } - if (beforeVault.assetsAvailable != zero) - { - JLOG(j.fatal()) << "Invariant failed: deleted vault must have no " - "assets available"; - result = false; - } - - return result; - } - else if (txnType == ttVAULT_DELETE) - { - JLOG(j.fatal()) << "Invariant failed: vault deletion succeeded without " - "deleting a vault"; - XRPL_ASSERT(enforce, "xrpl::ValidVault::finalize : vault deletion invariant"); - return !enforce; // That's all we can do here - } - - // Note, `afterVault_.empty()` is handled above - auto const& afterVault = afterVault_[0]; - XRPL_ASSERT( - beforeVault_.empty() || beforeVault_[0].key == afterVault.key, - "xrpl::ValidVault::finalize : single vault operation"); - - auto const updatedShares = [&]() -> std::optional { - // At this moment we only know that a vault is being updated and there - // might be some MPTokenIssuance objects which are also updated in the - // same transaction. Find the one matching the shares to this vault. - // Note, we expect updatedMPTs collection to be extremely small. For - // such collections linear search is faster than lookup. - for (auto const& e : afterMPTs_) - { - if (e.share.getMptID() == afterVault.shareMPTID) - return e; - } - - auto const sleShares = view.read(keylet::mptIssuance(afterVault.shareMPTID)); - - return sleShares ? std::optional(Shares::make(*sleShares)) : std::nullopt; - }(); - - bool result = true; - - // Universal transaction checks - if (!beforeVault_.empty()) - { - auto const& beforeVault = beforeVault_[0]; - if (afterVault.asset != beforeVault.asset || afterVault.pseudoId != beforeVault.pseudoId || - afterVault.shareMPTID != beforeVault.shareMPTID) - { - JLOG(j.fatal()) << "Invariant failed: violation of vault immutable data"; - result = false; - } - } - - if (!updatedShares) - { - JLOG(j.fatal()) << "Invariant failed: updated vault must have shares"; - XRPL_ASSERT(enforce, "xrpl::ValidVault::finalize : vault has shares invariant"); - return !enforce; // That's all we can do here - } - - if (updatedShares->sharesTotal == 0) - { - if (afterVault.assetsTotal != zero) - { - JLOG(j.fatal()) << "Invariant failed: updated zero sized " - "vault must have no assets outstanding"; - result = false; - } - if (afterVault.assetsAvailable != zero) - { - JLOG(j.fatal()) << "Invariant failed: updated zero sized " - "vault must have no assets available"; - result = false; - } - } - else if (updatedShares->sharesTotal > updatedShares->sharesMaximum) - { - JLOG(j.fatal()) // - << "Invariant failed: updated shares must not exceed maximum " - << updatedShares->sharesMaximum; - result = false; - } - - if (afterVault.assetsAvailable < zero) - { - JLOG(j.fatal()) << "Invariant failed: assets available must be positive"; - result = false; - } - - if (afterVault.assetsAvailable > afterVault.assetsTotal) - { - JLOG(j.fatal()) << "Invariant failed: assets available must " - "not be greater than assets outstanding"; - result = false; - } - else if (afterVault.lossUnrealized > afterVault.assetsTotal - afterVault.assetsAvailable) - { - JLOG(j.fatal()) // - << "Invariant failed: loss unrealized must not exceed " - "the difference between assets outstanding and available"; - result = false; - } - - if (afterVault.assetsTotal < zero) - { - JLOG(j.fatal()) << "Invariant failed: assets outstanding must be positive"; - result = false; - } - - if (afterVault.assetsMaximum < zero) - { - JLOG(j.fatal()) << "Invariant failed: assets maximum must be positive"; - result = false; - } - - // Thanks to this check we can simply do `assert(!beforeVault_.empty()` when - // enforcing invariants on transaction types other than ttVAULT_CREATE - if (beforeVault_.empty() && txnType != ttVAULT_CREATE) - { - JLOG(j.fatal()) << // - "Invariant failed: vault created by a wrong transaction type"; - XRPL_ASSERT(enforce, "xrpl::ValidVault::finalize : vault creation invariant"); - return !enforce; // That's all we can do here - } - - if (!beforeVault_.empty() && afterVault.lossUnrealized != beforeVault_[0].lossUnrealized && - txnType != ttLOAN_MANAGE && txnType != ttLOAN_PAY) - { - JLOG(j.fatal()) << // - "Invariant failed: vault transaction must not change loss " - "unrealized"; - result = false; - } - - auto const beforeShares = [&]() -> std::optional { - if (beforeVault_.empty()) - return std::nullopt; - auto const& beforeVault = beforeVault_[0]; - - for (auto const& e : beforeMPTs_) - { - if (e.share.getMptID() == beforeVault.shareMPTID) - return std::move(e); - } - return std::nullopt; - }(); - - if (!beforeShares && - (tx.getTxnType() == ttVAULT_DEPOSIT || // - tx.getTxnType() == ttVAULT_WITHDRAW || // - tx.getTxnType() == ttVAULT_CLAWBACK)) - { - JLOG(j.fatal()) << "Invariant failed: vault operation succeeded " - "without updating shares"; - XRPL_ASSERT(enforce, "xrpl::ValidVault::finalize : shares noop invariant"); - return !enforce; // That's all we can do here - } - - auto const& vaultAsset = afterVault.asset; - auto const deltaAssets = [&](AccountID const& id) -> std::optional { - auto const get = // - [&](auto const& it, std::int8_t sign = 1) -> std::optional { - if (it == deltas_.end()) - return std::nullopt; - - return it->second * sign; - }; - - return std::visit( - [&](TIss const& issue) { - if constexpr (std::is_same_v) - { - if (isXRP(issue)) - return get(deltas_.find(keylet::account(id).key)); - return get( - deltas_.find(keylet::line(id, issue).key), id > issue.getIssuer() ? -1 : 1); - } - else if constexpr (std::is_same_v) - { - return get(deltas_.find(keylet::mptoken(issue.getMptID(), id).key)); - } - }, - vaultAsset.value()); - }; - auto const deltaAssetsTxAccount = [&]() -> std::optional { - auto ret = deltaAssets(tx[sfAccount]); - // Nothing returned or not XRP transaction - if (!ret.has_value() || !vaultAsset.native()) - return ret; - - // Delegated transaction; no need to compensate for fees - if (auto const delegate = tx[~sfDelegate]; - delegate.has_value() && *delegate != tx[sfAccount]) - return ret; - - *ret += fee.drops(); - if (*ret == zero) - return std::nullopt; - - return ret; - }; - auto const deltaShares = [&](AccountID const& id) -> std::optional { - auto const it = [&]() { - if (id == afterVault.pseudoId) - return deltas_.find(keylet::mptIssuance(afterVault.shareMPTID).key); - return deltas_.find(keylet::mptoken(afterVault.shareMPTID, id).key); - }(); - - return it != deltas_.end() ? std::optional(it->second) : std::nullopt; - }; - - auto const vaultHoldsNoAssets = [&](Vault const& vault) { - return vault.assetsAvailable == 0 && vault.assetsTotal == 0; - }; - - // Technically this does not need to be a lambda, but it's more - // convenient thanks to early "return false"; the not-so-nice - // alternatives are several layers of nested if/else or more complex - // (i.e. brittle) if statements. - result &= [&]() { - switch (txnType) - { - case ttVAULT_CREATE: { - bool result = true; - - if (!beforeVault_.empty()) - { - JLOG(j.fatal()) // - << "Invariant failed: create operation must not have " - "updated a vault"; - result = false; - } - - if (afterVault.assetsAvailable != zero || afterVault.assetsTotal != zero || - afterVault.lossUnrealized != zero || updatedShares->sharesTotal != 0) - { - JLOG(j.fatal()) // - << "Invariant failed: created vault must be empty"; - result = false; - } - - if (afterVault.pseudoId != updatedShares->share.getIssuer()) - { - JLOG(j.fatal()) // - << "Invariant failed: shares issuer and vault " - "pseudo-account must be the same"; - result = false; - } - - auto const sleSharesIssuer = - view.read(keylet::account(updatedShares->share.getIssuer())); - if (!sleSharesIssuer) - { - JLOG(j.fatal()) // - << "Invariant failed: shares issuer must exist"; - return false; - } - - if (!isPseudoAccount(sleSharesIssuer)) - { - JLOG(j.fatal()) // - << "Invariant failed: shares issuer must be a " - "pseudo-account"; - result = false; - } - - if (auto const vaultId = (*sleSharesIssuer)[~sfVaultID]; - !vaultId || *vaultId != afterVault.key) - { - JLOG(j.fatal()) // - << "Invariant failed: shares issuer pseudo-account " - "must point back to the vault"; - result = false; - } - - return result; - } - case ttVAULT_SET: { - bool result = true; - - XRPL_ASSERT( - !beforeVault_.empty(), "xrpl::ValidVault::finalize : set updated a vault"); - auto const& beforeVault = beforeVault_[0]; - - auto const vaultDeltaAssets = deltaAssets(afterVault.pseudoId); - if (vaultDeltaAssets) - { - JLOG(j.fatal()) << // - "Invariant failed: set must not change vault balance"; - result = false; - } - - if (beforeVault.assetsTotal != afterVault.assetsTotal) - { - JLOG(j.fatal()) << // - "Invariant failed: set must not change assets " - "outstanding"; - result = false; - } - - if (afterVault.assetsMaximum > zero && - afterVault.assetsTotal > afterVault.assetsMaximum) - { - JLOG(j.fatal()) << // - "Invariant failed: set assets outstanding must not " - "exceed assets maximum"; - result = false; - } - - if (beforeVault.assetsAvailable != afterVault.assetsAvailable) - { - JLOG(j.fatal()) << // - "Invariant failed: set must not change assets " - "available"; - result = false; - } - - if (beforeShares && updatedShares && - beforeShares->sharesTotal != updatedShares->sharesTotal) - { - JLOG(j.fatal()) << // - "Invariant failed: set must not change shares " - "outstanding"; - result = false; - } - - return result; - } - case ttVAULT_DEPOSIT: { - bool result = true; - - XRPL_ASSERT( - !beforeVault_.empty(), "xrpl::ValidVault::finalize : deposit updated a vault"); - auto const& beforeVault = beforeVault_[0]; - - auto const vaultDeltaAssets = deltaAssets(afterVault.pseudoId); - - if (!vaultDeltaAssets) - { - JLOG(j.fatal()) << // - "Invariant failed: deposit must change vault balance"; - return false; // That's all we can do - } - - if (*vaultDeltaAssets > tx[sfAmount]) - { - JLOG(j.fatal()) << // - "Invariant failed: deposit must not change vault " - "balance by more than deposited amount"; - result = false; - } - - if (*vaultDeltaAssets <= zero) - { - JLOG(j.fatal()) << // - "Invariant failed: deposit must increase vault balance"; - result = false; - } - - // Any payments (including deposits) made by the issuer - // do not change their balance, but create funds instead. - bool const issuerDeposit = [&]() -> bool { - if (vaultAsset.native()) - return false; - return tx[sfAccount] == vaultAsset.getIssuer(); - }(); - - if (!issuerDeposit) - { - auto const accountDeltaAssets = deltaAssetsTxAccount(); - if (!accountDeltaAssets) - { - JLOG(j.fatal()) << // - "Invariant failed: deposit must change depositor " - "balance"; - return false; - } - - if (*accountDeltaAssets >= zero) - { - JLOG(j.fatal()) << // - "Invariant failed: deposit must decrease depositor " - "balance"; - result = false; - } - - if (*accountDeltaAssets * -1 != *vaultDeltaAssets) - { - JLOG(j.fatal()) << // - "Invariant failed: deposit must change vault and " - "depositor balance by equal amount"; - result = false; - } - } - - if (afterVault.assetsMaximum > zero && - afterVault.assetsTotal > afterVault.assetsMaximum) - { - JLOG(j.fatal()) << // - "Invariant failed: deposit assets outstanding must not " - "exceed assets maximum"; - result = false; - } - - auto const accountDeltaShares = deltaShares(tx[sfAccount]); - if (!accountDeltaShares) - { - JLOG(j.fatal()) << // - "Invariant failed: deposit must change depositor " - "shares"; - return false; // That's all we can do - } - - if (*accountDeltaShares <= zero) - { - JLOG(j.fatal()) << // - "Invariant failed: deposit must increase depositor " - "shares"; - result = false; - } - - auto const vaultDeltaShares = deltaShares(afterVault.pseudoId); - if (!vaultDeltaShares || *vaultDeltaShares == zero) - { - JLOG(j.fatal()) << // - "Invariant failed: deposit must change vault shares"; - return false; // That's all we can do - } - - if (*vaultDeltaShares * -1 != *accountDeltaShares) - { - JLOG(j.fatal()) << // - "Invariant failed: deposit must change depositor and " - "vault shares by equal amount"; - result = false; - } - - if (beforeVault.assetsTotal + *vaultDeltaAssets != afterVault.assetsTotal) - { - JLOG(j.fatal()) << "Invariant failed: deposit and assets " - "outstanding must add up"; - result = false; - } - if (beforeVault.assetsAvailable + *vaultDeltaAssets != afterVault.assetsAvailable) - { - JLOG(j.fatal()) << "Invariant failed: deposit and assets " - "available must add up"; - result = false; - } - - return result; - } - case ttVAULT_WITHDRAW: { - bool result = true; - - XRPL_ASSERT( - !beforeVault_.empty(), - "xrpl::ValidVault::finalize : withdrawal updated a " - "vault"); - auto const& beforeVault = beforeVault_[0]; - - auto const vaultDeltaAssets = deltaAssets(afterVault.pseudoId); - - if (!vaultDeltaAssets) - { - JLOG(j.fatal()) << "Invariant failed: withdrawal must " - "change vault balance"; - return false; // That's all we can do - } - - if (*vaultDeltaAssets >= zero) - { - JLOG(j.fatal()) << "Invariant failed: withdrawal must " - "decrease vault balance"; - result = false; - } - - // Any payments (including withdrawal) going to the issuer - // do not change their balance, but destroy funds instead. - bool const issuerWithdrawal = [&]() -> bool { - if (vaultAsset.native()) - return false; - auto const destination = tx[~sfDestination].value_or(tx[sfAccount]); - return destination == vaultAsset.getIssuer(); - }(); - - if (!issuerWithdrawal) - { - auto const accountDeltaAssets = deltaAssetsTxAccount(); - auto const otherAccountDelta = [&]() -> std::optional { - if (auto const destination = tx[~sfDestination]; - destination && *destination != tx[sfAccount]) - return deltaAssets(*destination); - return std::nullopt; - }(); - - if (accountDeltaAssets.has_value() == otherAccountDelta.has_value()) - { - JLOG(j.fatal()) << // - "Invariant failed: withdrawal must change one " - "destination balance"; - return false; - } - - auto const destinationDelta = // - accountDeltaAssets ? *accountDeltaAssets : *otherAccountDelta; - - if (destinationDelta <= zero) - { - JLOG(j.fatal()) << // - "Invariant failed: withdrawal must increase " - "destination balance"; - result = false; - } - - if (*vaultDeltaAssets * -1 != destinationDelta) - { - JLOG(j.fatal()) << // - "Invariant failed: withdrawal must change vault " - "and destination balance by equal amount"; - result = false; - } - } - - auto const accountDeltaShares = deltaShares(tx[sfAccount]); - if (!accountDeltaShares) - { - JLOG(j.fatal()) << // - "Invariant failed: withdrawal must change depositor " - "shares"; - return false; - } - - if (*accountDeltaShares >= zero) - { - JLOG(j.fatal()) << // - "Invariant failed: withdrawal must decrease depositor " - "shares"; - result = false; - } - - auto const vaultDeltaShares = deltaShares(afterVault.pseudoId); - if (!vaultDeltaShares || *vaultDeltaShares == zero) - { - JLOG(j.fatal()) << // - "Invariant failed: withdrawal must change vault shares"; - return false; // That's all we can do - } - - if (*vaultDeltaShares * -1 != *accountDeltaShares) - { - JLOG(j.fatal()) << // - "Invariant failed: withdrawal must change depositor " - "and vault shares by equal amount"; - result = false; - } - - // Note, vaultBalance is negative (see check above) - if (beforeVault.assetsTotal + *vaultDeltaAssets != afterVault.assetsTotal) - { - JLOG(j.fatal()) << "Invariant failed: withdrawal and " - "assets outstanding must add up"; - result = false; - } - - if (beforeVault.assetsAvailable + *vaultDeltaAssets != afterVault.assetsAvailable) - { - JLOG(j.fatal()) << "Invariant failed: withdrawal and " - "assets available must add up"; - result = false; - } - - return result; - } - case ttVAULT_CLAWBACK: { - bool result = true; - - XRPL_ASSERT( - !beforeVault_.empty(), "xrpl::ValidVault::finalize : clawback updated a vault"); - auto const& beforeVault = beforeVault_[0]; - - if (vaultAsset.native() || vaultAsset.getIssuer() != tx[sfAccount]) - { - // The owner can use clawback to force-burn shares when the - // vault is empty but there are outstanding shares - if (!(beforeShares && beforeShares->sharesTotal > 0 && - vaultHoldsNoAssets(beforeVault) && beforeVault.owner == tx[sfAccount])) - { - JLOG(j.fatal()) << // - "Invariant failed: clawback may only be performed " - "by the asset issuer, or by the vault owner of an " - "empty vault"; - return false; // That's all we can do - } - } - - auto const vaultDeltaAssets = deltaAssets(afterVault.pseudoId); - if (vaultDeltaAssets) - { - if (*vaultDeltaAssets >= zero) - { - JLOG(j.fatal()) << // - "Invariant failed: clawback must decrease vault " - "balance"; - result = false; - } - - if (beforeVault.assetsTotal + *vaultDeltaAssets != afterVault.assetsTotal) - { - JLOG(j.fatal()) << // - "Invariant failed: clawback and assets outstanding " - "must add up"; - result = false; - } - - if (beforeVault.assetsAvailable + *vaultDeltaAssets != - afterVault.assetsAvailable) - { - JLOG(j.fatal()) << // - "Invariant failed: clawback and assets available " - "must add up"; - result = false; - } - } - else if (!vaultHoldsNoAssets(beforeVault)) - { - JLOG(j.fatal()) << // - "Invariant failed: clawback must change vault balance"; - return false; // That's all we can do - } - - auto const accountDeltaShares = deltaShares(tx[sfHolder]); - if (!accountDeltaShares) - { - JLOG(j.fatal()) << // - "Invariant failed: clawback must change holder shares"; - return false; // That's all we can do - } - - if (*accountDeltaShares >= zero) - { - JLOG(j.fatal()) << // - "Invariant failed: clawback must decrease holder " - "shares"; - result = false; - } - - auto const vaultDeltaShares = deltaShares(afterVault.pseudoId); - if (!vaultDeltaShares || *vaultDeltaShares == zero) - { - JLOG(j.fatal()) << // - "Invariant failed: clawback must change vault shares"; - return false; // That's all we can do - } - - if (*vaultDeltaShares * -1 != *accountDeltaShares) - { - JLOG(j.fatal()) << // - "Invariant failed: clawback must change holder and " - "vault shares by equal amount"; - result = false; - } - - return result; - } - - case ttLOAN_SET: - case ttLOAN_MANAGE: - case ttLOAN_PAY: { - // TBD - return true; - } - - default: - // LCOV_EXCL_START - UNREACHABLE("xrpl::ValidVault::finalize : unknown transaction type"); - return false; - // LCOV_EXCL_STOP - } - }(); - - if (!result) - { - // The comment at the top of this file starting with "assert(enforce)" - // explains this assert. - XRPL_ASSERT(enforce, "xrpl::ValidVault::finalize : vault invariants"); - return !enforce; - } - - return true; -} - -} // namespace xrpl diff --git a/src/libxrpl/tx/invariants/AMMInvariant.cpp b/src/libxrpl/tx/invariants/AMMInvariant.cpp new file mode 100644 index 0000000000..d98c0a6f50 --- /dev/null +++ b/src/libxrpl/tx/invariants/AMMInvariant.cpp @@ -0,0 +1,305 @@ +#include +// +#include +#include +#include +#include +#include + +namespace xrpl { + +void +ValidAMM::visitEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) +{ + if (isDelete) + return; + + if (after) + { + auto const type = after->getType(); + // AMM object changed + if (type == ltAMM) + { + ammAccount_ = after->getAccountID(sfAccount); + lptAMMBalanceAfter_ = after->getFieldAmount(sfLPTokenBalance); + } + // AMM pool changed + else if ( + (type == ltRIPPLE_STATE && after->getFlags() & lsfAMMNode) || + (type == ltACCOUNT_ROOT && after->isFieldPresent(sfAMMID))) + { + ammPoolChanged_ = true; + } + } + + if (before) + { + // AMM object changed + if (before->getType() == ltAMM) + { + lptAMMBalanceBefore_ = before->getFieldAmount(sfLPTokenBalance); + } + } +} + +static bool +validBalances( + STAmount const& amount, + STAmount const& amount2, + STAmount const& lptAMMBalance, + ValidAMM::ZeroAllowed zeroAllowed) +{ + bool const positive = + amount > beast::zero && amount2 > beast::zero && lptAMMBalance > beast::zero; + if (zeroAllowed == ValidAMM::ZeroAllowed::Yes) + return positive || + (amount == beast::zero && amount2 == beast::zero && lptAMMBalance == beast::zero); + return positive; +} + +bool +ValidAMM::finalizeVote(bool enforce, beast::Journal const& j) const +{ + if (lptAMMBalanceAfter_ != lptAMMBalanceBefore_ || ammPoolChanged_) + { + // LPTokens and the pool can not change on vote + // LCOV_EXCL_START + JLOG(j.error()) << "AMMVote invariant failed: " << lptAMMBalanceBefore_.value_or(STAmount{}) + << " " << lptAMMBalanceAfter_.value_or(STAmount{}) << " " + << ammPoolChanged_; + if (enforce) + return false; + // LCOV_EXCL_STOP + } + + return true; +} + +bool +ValidAMM::finalizeBid(bool enforce, beast::Journal const& j) const +{ + if (ammPoolChanged_) + { + // The pool can not change on bid + // LCOV_EXCL_START + JLOG(j.error()) << "AMMBid invariant failed: pool changed"; + if (enforce) + return false; + // LCOV_EXCL_STOP + } + // LPTokens are burnt, therefore there should be fewer LPTokens + else if ( + lptAMMBalanceBefore_ && lptAMMBalanceAfter_ && + (*lptAMMBalanceAfter_ > *lptAMMBalanceBefore_ || *lptAMMBalanceAfter_ <= beast::zero)) + { + // LCOV_EXCL_START + JLOG(j.error()) << "AMMBid invariant failed: " << *lptAMMBalanceBefore_ << " " + << *lptAMMBalanceAfter_; + if (enforce) + return false; + // LCOV_EXCL_STOP + } + + return true; +} + +bool +ValidAMM::finalizeCreate( + STTx const& tx, + ReadView const& view, + bool enforce, + beast::Journal const& j) const +{ + if (!ammAccount_) + { + // LCOV_EXCL_START + JLOG(j.error()) << "AMMCreate invariant failed: AMM object is not created"; + if (enforce) + return false; + // LCOV_EXCL_STOP + } + else + { + auto const [amount, amount2] = ammPoolHolds( + view, + *ammAccount_, + tx[sfAmount].get(), + tx[sfAmount2].get(), + fhIGNORE_FREEZE, + j); + // Create invariant: + // sqrt(amount * amount2) == LPTokens + // all balances are greater than zero + if (!validBalances(amount, amount2, *lptAMMBalanceAfter_, ZeroAllowed::No) || + ammLPTokens(amount, amount2, lptAMMBalanceAfter_->issue()) != *lptAMMBalanceAfter_) + { + JLOG(j.error()) << "AMMCreate invariant failed: " << amount << " " << amount2 << " " + << *lptAMMBalanceAfter_; + if (enforce) + return false; + } + } + + return true; +} + +bool +ValidAMM::finalizeDelete(bool enforce, TER res, beast::Journal const& j) const +{ + if (ammAccount_) + { + // LCOV_EXCL_START + std::string const msg = (res == tesSUCCESS) ? "AMM object is not deleted on tesSUCCESS" + : "AMM object is changed on tecINCOMPLETE"; + JLOG(j.error()) << "AMMDelete invariant failed: " << msg; + if (enforce) + return false; + // LCOV_EXCL_STOP + } + + return true; +} + +bool +ValidAMM::finalizeDEX(bool enforce, beast::Journal const& j) const +{ + if (ammAccount_) + { + // LCOV_EXCL_START + JLOG(j.error()) << "AMM swap invariant failed: AMM object changed"; + if (enforce) + return false; + // LCOV_EXCL_STOP + } + + return true; +} + +bool +ValidAMM::generalInvariant( + xrpl::STTx const& tx, + xrpl::ReadView const& view, + ZeroAllowed zeroAllowed, + beast::Journal const& j) const +{ + auto const [amount, amount2] = ammPoolHolds( + view, + *ammAccount_, + tx[sfAsset].get(), + tx[sfAsset2].get(), + fhIGNORE_FREEZE, + j); + // Deposit and Withdrawal invariant: + // sqrt(amount * amount2) >= LPTokens + // all balances are greater than zero + // unless on last withdrawal + 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::zero && + withinRelativeDistance(poolProductMean, Number{*lptAMMBalanceAfter_}, Number{1, -11}); + }; + if (!nonNegativeBalances || (!strongInvariantCheck && !weakInvariantCheck())) + { + JLOG(j.error()) << "AMM " << tx.getTxnType() + << " invariant failed: " << tx.getHash(HashPrefix::transactionID) << " " + << ammPoolChanged_ << " " << amount << " " << amount2 << " " + << poolProductMean << " " << lptAMMBalanceAfter_->getText() << " " + << ((*lptAMMBalanceAfter_ == beast::zero) + ? Number{1} + : ((*lptAMMBalanceAfter_ - poolProductMean) / poolProductMean)); + return false; + } + + return true; +} + +bool +ValidAMM::finalizeDeposit( + xrpl::STTx const& tx, + xrpl::ReadView const& view, + bool enforce, + beast::Journal const& j) const +{ + if (!ammAccount_) + { + // LCOV_EXCL_START + JLOG(j.error()) << "AMMDeposit invariant failed: AMM object is deleted"; + if (enforce) + return false; + // LCOV_EXCL_STOP + } + else if (!generalInvariant(tx, view, ZeroAllowed::No, j) && enforce) + return false; + + return true; +} + +bool +ValidAMM::finalizeWithdraw( + xrpl::STTx const& tx, + xrpl::ReadView const& view, + bool enforce, + beast::Journal const& j) const +{ + if (!ammAccount_) + { + // Last Withdraw or Clawback deleted AMM + } + else if (!generalInvariant(tx, view, ZeroAllowed::Yes, j)) + { + if (enforce) + return false; + } + + return true; +} + +bool +ValidAMM::finalize( + STTx const& tx, + TER const result, + XRPAmount const, + ReadView const& view, + beast::Journal const& j) +{ + // Delete may return tecINCOMPLETE if there are too many + // trustlines to delete. + if (result != tesSUCCESS && result != tecINCOMPLETE) + return true; + + bool const enforce = view.rules().enabled(fixAMMv1_3); + + switch (tx.getTxnType()) + { + case ttAMM_CREATE: + return finalizeCreate(tx, view, enforce, j); + case ttAMM_DEPOSIT: + return finalizeDeposit(tx, view, enforce, j); + case ttAMM_CLAWBACK: + case ttAMM_WITHDRAW: + return finalizeWithdraw(tx, view, enforce, j); + case ttAMM_BID: + return finalizeBid(enforce, j); + case ttAMM_VOTE: + return finalizeVote(enforce, j); + case ttAMM_DELETE: + return finalizeDelete(enforce, result, j); + case ttCHECK_CASH: + case ttOFFER_CREATE: + case ttPAYMENT: + return finalizeDEX(enforce, j); + default: + break; + } + + return true; +} + +} // namespace xrpl diff --git a/src/libxrpl/tx/invariants/FreezeInvariant.cpp b/src/libxrpl/tx/invariants/FreezeInvariant.cpp new file mode 100644 index 0000000000..858c4cdcb8 --- /dev/null +++ b/src/libxrpl/tx/invariants/FreezeInvariant.cpp @@ -0,0 +1,278 @@ +#include +// +#include +#include +#include +#include +#include + +namespace xrpl { + +void +TransfersNotFrozen::visitEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) +{ + /* + * A trust line freeze state alone doesn't determine if a transfer is + * frozen. The transfer must be examined "end-to-end" because both sides of + * the transfer may have different freeze states and freeze impact depends + * on the transfer direction. This is why first we need to track the + * transfers using IssuerChanges senders/receivers. + * + * Only in validateIssuerChanges, after we collected all changes can we + * determine if the transfer is valid. + */ + if (!isValidEntry(before, after)) + { + return; + } + + auto const balanceChange = calculateBalanceChange(before, after, isDelete); + if (balanceChange.signum() == 0) + { + return; + } + + recordBalanceChanges(after, balanceChange); +} + +bool +TransfersNotFrozen::finalize( + STTx const& tx, + TER const ter, + XRPAmount const fee, + ReadView const& view, + beast::Journal const& j) +{ + /* + * We check this invariant regardless of deep freeze amendment status, + * allowing for detection and logging of potential issues even when the + * amendment is disabled. + * + * If an exploit that allows moving frozen assets is discovered, + * we can alert operators who monitor fatal messages and trigger assert in + * debug builds for an early warning. + * + * In an unlikely event that an exploit is found, this early detection + * enables encouraging the UNL to expedite deep freeze amendment activation + * or deploy hotfixes via new amendments. In case of a new amendment, we'd + * only have to change this line setting 'enforce' variable. + * enforce = view.rules().enabled(featureDeepFreeze) || + * view.rules().enabled(fixFreezeExploit); + */ + [[maybe_unused]] bool const enforce = view.rules().enabled(featureDeepFreeze); + + for (auto const& [issue, changes] : balanceChanges_) + { + auto const issuerSle = findIssuer(issue.account, view); + // It should be impossible for the issuer to not be found, but check + // just in case so rippled doesn't crash in release. + if (!issuerSle) + { + // The comment above starting with "assert(enforce)" explains this + // assert. + XRPL_ASSERT( + enforce, + "xrpl::TransfersNotFrozen::finalize : enforce " + "invariant."); + if (enforce) + { + return false; + } + continue; + } + + if (!validateIssuerChanges(issuerSle, changes, tx, j, enforce)) + { + return false; + } + } + + return true; +} + +bool +TransfersNotFrozen::isValidEntry( + std::shared_ptr const& before, + std::shared_ptr const& after) +{ + // `after` can never be null, even if the trust line is deleted. + XRPL_ASSERT(after, "xrpl::TransfersNotFrozen::isValidEntry : valid after."); + if (!after) + { + return false; + } + + if (after->getType() == ltACCOUNT_ROOT) + { + possibleIssuers_.emplace(after->at(sfAccount), after); + return false; + } + + /* While LedgerEntryTypesMatch invariant also checks types, all invariants + * are processed regardless of previous failures. + * + * This type check is still necessary here because it prevents potential + * issues in subsequent processing. + */ + return after->getType() == ltRIPPLE_STATE && (!before || before->getType() == ltRIPPLE_STATE); +} + +STAmount +TransfersNotFrozen::calculateBalanceChange( + std::shared_ptr const& before, + std::shared_ptr const& after, + bool isDelete) +{ + auto const getBalance = [](auto const& line, auto const& other, bool zero) { + STAmount amt = line ? line->at(sfBalance) : other->at(sfBalance).zeroed(); + return zero ? amt.zeroed() : amt; + }; + + /* Trust lines can be created dynamically by other transactions such as + * Payment and OfferCreate that cross offers. Such trust line won't be + * created frozen, but the sender might be, so the starting balance must be + * treated as zero. + */ + auto const balanceBefore = getBalance(before, after, false); + + /* Same as above, trust lines can be dynamically deleted, and for frozen + * trust lines, payments not involving the issuer must be blocked. This is + * achieved by treating the final balance as zero when isDelete=true to + * ensure frozen line restrictions are enforced even during deletion. + */ + auto const balanceAfter = getBalance(after, before, isDelete); + + return balanceAfter - balanceBefore; +} + +void +TransfersNotFrozen::recordBalance(Issue const& issue, BalanceChange change) +{ + XRPL_ASSERT( + change.balanceChangeSign, + "xrpl::TransfersNotFrozen::recordBalance : valid trustline " + "balance sign."); + auto& changes = balanceChanges_[issue]; + if (change.balanceChangeSign < 0) + changes.senders.emplace_back(std::move(change)); + else + changes.receivers.emplace_back(std::move(change)); +} + +void +TransfersNotFrozen::recordBalanceChanges( + std::shared_ptr const& after, + STAmount const& balanceChange) +{ + auto const balanceChangeSign = balanceChange.signum(); + auto const currency = after->at(sfBalance).getCurrency(); + + // Change from low account's perspective, which is trust line default + recordBalance({currency, after->at(sfHighLimit).getIssuer()}, {after, balanceChangeSign}); + + // Change from high account's perspective, which reverses the sign. + recordBalance({currency, after->at(sfLowLimit).getIssuer()}, {after, -balanceChangeSign}); +} + +std::shared_ptr +TransfersNotFrozen::findIssuer(AccountID const& issuerID, ReadView const& view) +{ + if (auto it = possibleIssuers_.find(issuerID); it != possibleIssuers_.end()) + { + return it->second; + } + + return view.read(keylet::account(issuerID)); +} + +bool +TransfersNotFrozen::validateIssuerChanges( + std::shared_ptr const& issuer, + IssuerChanges const& changes, + STTx const& tx, + beast::Journal const& j, + bool enforce) +{ + if (!issuer) + { + return false; + } + + bool const globalFreeze = issuer->isFlag(lsfGlobalFreeze); + if (changes.receivers.empty() || changes.senders.empty()) + { + /* If there are no receivers, then the holder(s) are returning + * their tokens to the issuer. Likewise, if there are no + * senders, then the issuer is issuing tokens to the holder(s). + * This is allowed regardless of the issuer's freeze flags. (The + * holder may have contradicting freeze flags, but that will be + * checked when the holder is treated as issuer.) + */ + return true; + } + + for (auto const& actors : {changes.senders, changes.receivers}) + { + for (auto const& change : actors) + { + bool const high = change.line->at(sfLowLimit).getIssuer() == issuer->at(sfAccount); + + if (!validateFrozenState(change, high, tx, j, enforce, globalFreeze)) + { + return false; + } + } + } + return true; +} + +bool +TransfersNotFrozen::validateFrozenState( + BalanceChange const& change, + bool high, + STTx const& tx, + beast::Journal const& j, + bool enforce, + bool globalFreeze) +{ + bool const freeze = + change.balanceChangeSign < 0 && change.line->isFlag(high ? lsfLowFreeze : lsfHighFreeze); + bool const deepFreeze = change.line->isFlag(high ? lsfLowDeepFreeze : lsfHighDeepFreeze); + bool const frozen = globalFreeze || deepFreeze || freeze; + + bool const isAMMLine = change.line->isFlag(lsfAMMNode); + + if (!frozen) + { + return true; + } + + // AMMClawbacks are allowed to override some freeze rules + if ((!isAMMLine || globalFreeze) && hasPrivilege(tx, overrideFreeze)) + { + JLOG(j.debug()) << "Invariant check allowing funds to be moved " + << (change.balanceChangeSign > 0 ? "to" : "from") + << " a frozen trustline for AMMClawback " << tx.getTransactionID(); + return true; + } + + JLOG(j.fatal()) << "Invariant failed: Attempting to move frozen funds for " + << tx.getTransactionID(); + // The comment above starting with "assert(enforce)" explains this assert. + XRPL_ASSERT( + enforce, + "xrpl::TransfersNotFrozen::validateFrozenState : enforce " + "invariant."); + + if (enforce) + { + return false; + } + + return true; +} + +} // namespace xrpl diff --git a/src/libxrpl/tx/invariants/InvariantCheck.cpp b/src/libxrpl/tx/invariants/InvariantCheck.cpp new file mode 100644 index 0000000000..79c593c57c --- /dev/null +++ b/src/libxrpl/tx/invariants/InvariantCheck.cpp @@ -0,0 +1,1009 @@ +#include +// +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace xrpl { + +#pragma push_macro("TRANSACTION") +#undef TRANSACTION + +#define TRANSACTION(tag, value, name, delegable, amendment, privileges, ...) \ + case tag: { \ + return (privileges) & priv; \ + } + +bool +hasPrivilege(STTx const& tx, Privilege priv) +{ + switch (tx.getTxnType()) + { +#include + + // Deprecated types + default: + return false; + } +}; + +#undef TRANSACTION +#pragma pop_macro("TRANSACTION") + +void +TransactionFeeCheck::visitEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ + // nothing to do +} + +bool +TransactionFeeCheck::finalize( + STTx const& tx, + TER const, + XRPAmount const fee, + ReadView const&, + beast::Journal const& j) +{ + // We should never charge a negative fee + if (fee.drops() < 0) + { + JLOG(j.fatal()) << "Invariant failed: fee paid was negative: " << fee.drops(); + return false; + } + + // We should never charge a fee that's greater than or equal to the + // entire XRP supply. + if (fee >= INITIAL_XRP) + { + JLOG(j.fatal()) << "Invariant failed: fee paid exceeds system limit: " << fee.drops(); + return false; + } + + // We should never charge more for a transaction than the transaction + // authorizes. It's possible to charge less in some circumstances. + if (fee > tx.getFieldAmount(sfFee).xrp()) + { + JLOG(j.fatal()) << "Invariant failed: fee paid is " << fee.drops() + << " exceeds fee specified in transaction."; + return false; + } + + return true; +} + +//------------------------------------------------------------------------------ + +void +XRPNotCreated::visitEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) +{ + /* We go through all modified ledger entries, looking only at account roots, + * escrow payments, and payment channels. We remove from the total any + * previous XRP values and add to the total any new XRP values. The net + * balance of a payment channel is computed from two fields (amount and + * balance) and deletions are ignored for paychan and escrow because the + * amount fields have not been adjusted for those in the case of deletion. + */ + if (before) + { + switch (before->getType()) + { + case ltACCOUNT_ROOT: + drops_ -= (*before)[sfBalance].xrp().drops(); + break; + case ltPAYCHAN: + drops_ -= ((*before)[sfAmount] - (*before)[sfBalance]).xrp().drops(); + break; + case ltESCROW: + if (isXRP((*before)[sfAmount])) + drops_ -= (*before)[sfAmount].xrp().drops(); + break; + default: + break; + } + } + + if (after) + { + switch (after->getType()) + { + case ltACCOUNT_ROOT: + drops_ += (*after)[sfBalance].xrp().drops(); + break; + case ltPAYCHAN: + if (!isDelete) + drops_ += ((*after)[sfAmount] - (*after)[sfBalance]).xrp().drops(); + break; + case ltESCROW: + if (!isDelete && isXRP((*after)[sfAmount])) + drops_ += (*after)[sfAmount].xrp().drops(); + break; + default: + break; + } + } +} + +bool +XRPNotCreated::finalize( + STTx const& tx, + TER const, + XRPAmount const fee, + ReadView const&, + beast::Journal const& j) +{ + // The net change should never be positive, as this would mean that the + // transaction created XRP out of thin air. That's not possible. + if (drops_ > 0) + { + JLOG(j.fatal()) << "Invariant failed: XRP net change was positive: " << drops_; + return false; + } + + // The negative of the net change should be equal to actual fee charged. + if (-drops_ != fee.drops()) + { + JLOG(j.fatal()) << "Invariant failed: XRP net change of " << drops_ << " doesn't match fee " + << fee.drops(); + return false; + } + + return true; +} + +//------------------------------------------------------------------------------ + +void +XRPBalanceChecks::visitEntry( + bool, + std::shared_ptr const& before, + std::shared_ptr const& after) +{ + auto isBad = [](STAmount const& balance) { + if (!balance.native()) + return true; + + auto const drops = balance.xrp(); + + // Can't have more than the number of drops instantiated + // in the genesis ledger. + if (drops > INITIAL_XRP) + return true; + + // Can't have a negative balance (0 is OK) + if (drops < XRPAmount{0}) + return true; + + return false; + }; + + if (before && before->getType() == ltACCOUNT_ROOT) + bad_ |= isBad((*before)[sfBalance]); + + if (after && after->getType() == ltACCOUNT_ROOT) + bad_ |= isBad((*after)[sfBalance]); +} + +bool +XRPBalanceChecks::finalize( + STTx const&, + TER const, + XRPAmount const, + ReadView const&, + beast::Journal const& j) +{ + if (bad_) + { + JLOG(j.fatal()) << "Invariant failed: incorrect account XRP balance"; + return false; + } + + return true; +} + +//------------------------------------------------------------------------------ + +void +NoBadOffers::visitEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) +{ + auto isBad = [](STAmount const& pays, STAmount const& gets) { + // An offer should never be negative + if (pays < beast::zero) + return true; + + if (gets < beast::zero) + return true; + + // Can't have an XRP to XRP offer: + return pays.native() && gets.native(); + }; + + if (before && before->getType() == ltOFFER) + bad_ |= isBad((*before)[sfTakerPays], (*before)[sfTakerGets]); + + if (after && after->getType() == ltOFFER) + bad_ |= isBad((*after)[sfTakerPays], (*after)[sfTakerGets]); +} + +bool +NoBadOffers::finalize( + STTx const&, + TER const, + XRPAmount const, + ReadView const&, + beast::Journal const& j) +{ + if (bad_) + { + JLOG(j.fatal()) << "Invariant failed: offer with a bad amount"; + return false; + } + + return true; +} + +//------------------------------------------------------------------------------ + +void +NoZeroEscrow::visitEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) +{ + auto isBad = [](STAmount const& amount) { + // XRP case + if (amount.native()) + { + if (amount.xrp() <= XRPAmount{0}) + return true; + + if (amount.xrp() >= INITIAL_XRP) + return true; + } + else + { + // IOU case + if (amount.holds()) + { + if (amount <= beast::zero) + return true; + + if (badCurrency() == amount.getCurrency()) + return true; + } + + // MPT case + if (amount.holds()) + { + if (amount <= beast::zero) + return true; + + if (amount.mpt() > MPTAmount{maxMPTokenAmount}) + return true; // LCOV_EXCL_LINE + } + } + return false; + }; + + if (before && before->getType() == ltESCROW) + bad_ |= isBad((*before)[sfAmount]); + + if (after && after->getType() == ltESCROW) + bad_ |= isBad((*after)[sfAmount]); + + auto checkAmount = [this](std::int64_t amount) { + if (amount > maxMPTokenAmount || amount < 0) + bad_ = true; + }; + + if (after && after->getType() == ltMPTOKEN_ISSUANCE) + { + auto const outstanding = (*after)[sfOutstandingAmount]; + checkAmount(outstanding); + if (auto const locked = (*after)[~sfLockedAmount]) + { + checkAmount(*locked); + bad_ = outstanding < *locked; + } + } + + if (after && after->getType() == ltMPTOKEN) + { + auto const mptAmount = (*after)[sfMPTAmount]; + checkAmount(mptAmount); + if (auto const locked = (*after)[~sfLockedAmount]) + { + checkAmount(*locked); + } + } +} + +bool +NoZeroEscrow::finalize( + STTx const& txn, + TER const, + XRPAmount const, + ReadView const& rv, + beast::Journal const& j) +{ + if (bad_) + { + JLOG(j.fatal()) << "Invariant failed: escrow specifies invalid amount"; + return false; + } + + return true; +} + +//------------------------------------------------------------------------------ + +void +AccountRootsNotDeleted::visitEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const&) +{ + if (isDelete && before && before->getType() == ltACCOUNT_ROOT) + accountsDeleted_++; +} + +bool +AccountRootsNotDeleted::finalize( + STTx const& tx, + TER const result, + XRPAmount const, + ReadView const&, + beast::Journal const& j) +{ + // AMM account root can be deleted as the result of AMM withdraw/delete + // transaction when the total AMM LP Tokens balance goes to 0. + // A successful AccountDelete or AMMDelete MUST delete exactly + // one account root. + if (hasPrivilege(tx, mustDeleteAcct) && result == tesSUCCESS) + { + if (accountsDeleted_ == 1) + return true; + + if (accountsDeleted_ == 0) + JLOG(j.fatal()) << "Invariant failed: account deletion " + "succeeded without deleting an account"; + else + JLOG(j.fatal()) << "Invariant failed: account deletion " + "succeeded but deleted multiple accounts!"; + return false; + } + + // A successful AMMWithdraw/AMMClawback MAY delete one account root + // when the total AMM LP Tokens balance goes to 0. Not every AMM withdraw + // deletes the AMM account, accountsDeleted_ is set if it is deleted. + if (hasPrivilege(tx, mayDeleteAcct) && result == tesSUCCESS && accountsDeleted_ == 1) + return true; + + if (accountsDeleted_ == 0) + return true; + + JLOG(j.fatal()) << "Invariant failed: an account root was deleted"; + return false; +} + +//------------------------------------------------------------------------------ + +void +AccountRootsDeletedClean::visitEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) +{ + if (isDelete && before && before->getType() == ltACCOUNT_ROOT) + accountsDeleted_.emplace_back(before, after); +} + +bool +AccountRootsDeletedClean::finalize( + STTx const& tx, + TER const result, + XRPAmount const, + ReadView const& view, + beast::Journal const& j) +{ + // Always check for objects in the ledger, but to prevent differing + // transaction processing results, however unlikely, only fail if the + // feature is enabled. Enabled, or not, though, a fatal-level message will + // be logged + [[maybe_unused]] bool const enforce = view.rules().enabled(featureInvariantsV1_1) || + view.rules().enabled(featureSingleAssetVault) || + view.rules().enabled(featureLendingProtocol); + + auto const objectExists = [&view, enforce, &j](auto const& keylet) { + (void)enforce; + if (auto const sle = view.read(keylet)) + { + // Finding the object is bad + auto const typeName = [&sle]() { + auto item = LedgerFormats::getInstance().findByType(sle->getType()); + + if (item != nullptr) + return item->getName(); + return std::to_string(sle->getType()); + }(); + + JLOG(j.fatal()) << "Invariant failed: account deletion left behind a " << typeName + << " object"; + // The comment above starting with "assert(enforce)" explains this + // assert. + XRPL_ASSERT( + enforce, + "xrpl::AccountRootsDeletedClean::finalize::objectExists : " + "account deletion left no objects behind"); + return true; + } + return false; + }; + + for (auto const& [before, after] : accountsDeleted_) + { + auto const accountID = before->getAccountID(sfAccount); + // An account should not be deleted with a balance + if (after->at(sfBalance) != beast::zero) + { + JLOG(j.fatal()) << "Invariant failed: account deletion left " + "behind a non-zero balance"; + XRPL_ASSERT( + enforce, + "xrpl::AccountRootsDeletedClean::finalize : " + "deleted account has zero balance"); + if (enforce) + return false; + } + // An account should not be deleted with a non-zero owner count + if (after->at(sfOwnerCount) != 0) + { + JLOG(j.fatal()) << "Invariant failed: account deletion left " + "behind a non-zero owner count"; + XRPL_ASSERT( + enforce, + "xrpl::AccountRootsDeletedClean::finalize : " + "deleted account has zero owner count"); + if (enforce) + return false; + } + // Simple types + for (auto const& [keyletfunc, _, __] : directAccountKeylets) + { + if (objectExists(std::invoke(keyletfunc, accountID)) && enforce) + return false; + } + + { + // NFT pages. nftpage_min and nftpage_max were already explicitly + // checked above as entries in directAccountKeylets. This uses + // view.succ() to check for any NFT pages in between the two + // endpoints. + Keylet const first = keylet::nftpage_min(accountID); + Keylet const last = keylet::nftpage_max(accountID); + + std::optional key = view.succ(first.key, last.key.next()); + + // current page + if (key && objectExists(Keylet{ltNFTOKEN_PAGE, *key}) && enforce) + return false; + } + + // If the account is a pseudo account, then the linked object must + // also be deleted. e.g. AMM, Vault, etc. + for (auto const& field : getPseudoAccountFields()) + { + if (before->isFieldPresent(*field)) + { + auto const key = before->getFieldH256(*field); + if (objectExists(keylet::unchecked(key)) && enforce) + return false; + } + } + } + + return true; +} + +//------------------------------------------------------------------------------ + +void +LedgerEntryTypesMatch::visitEntry( + bool, + std::shared_ptr const& before, + std::shared_ptr const& after) +{ + if (before && after && before->getType() != after->getType()) + typeMismatch_ = true; + + if (after) + { +#pragma push_macro("LEDGER_ENTRY") +#undef LEDGER_ENTRY + +#define LEDGER_ENTRY(tag, ...) case tag: + + switch (after->getType()) + { +#include + + break; + default: + invalidTypeAdded_ = true; + break; + } + +#undef LEDGER_ENTRY +#pragma pop_macro("LEDGER_ENTRY") + } +} + +bool +LedgerEntryTypesMatch::finalize( + STTx const&, + TER const, + XRPAmount const, + ReadView const&, + beast::Journal const& j) +{ + if ((!typeMismatch_) && (!invalidTypeAdded_)) + return true; + + if (typeMismatch_) + { + JLOG(j.fatal()) << "Invariant failed: ledger entry type mismatch"; + } + + if (invalidTypeAdded_) + { + JLOG(j.fatal()) << "Invariant failed: invalid ledger entry type added"; + } + + return false; +} + +//------------------------------------------------------------------------------ + +void +NoXRPTrustLines::visitEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const& after) +{ + if (after && after->getType() == ltRIPPLE_STATE) + { + // checking the issue directly here instead of + // relying on .native() just in case native somehow + // were systematically incorrect + xrpTrustLine_ = after->getFieldAmount(sfLowLimit).issue() == xrpIssue() || + after->getFieldAmount(sfHighLimit).issue() == xrpIssue(); + } +} + +bool +NoXRPTrustLines::finalize( + STTx const&, + TER const, + XRPAmount const, + ReadView const&, + beast::Journal const& j) +{ + if (!xrpTrustLine_) + return true; + + JLOG(j.fatal()) << "Invariant failed: an XRP trust line was created"; + return false; +} + +//------------------------------------------------------------------------------ + +void +NoDeepFreezeTrustLinesWithoutFreeze::visitEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const& after) +{ + if (after && after->getType() == ltRIPPLE_STATE) + { + std::uint32_t const uFlags = after->getFieldU32(sfFlags); + bool const lowFreeze = uFlags & lsfLowFreeze; + bool const lowDeepFreeze = uFlags & lsfLowDeepFreeze; + + bool const highFreeze = uFlags & lsfHighFreeze; + bool const highDeepFreeze = uFlags & lsfHighDeepFreeze; + + deepFreezeWithoutFreeze_ = (lowDeepFreeze && !lowFreeze) || (highDeepFreeze && !highFreeze); + } +} + +bool +NoDeepFreezeTrustLinesWithoutFreeze::finalize( + STTx const&, + TER const, + XRPAmount const, + ReadView const&, + beast::Journal const& j) +{ + if (!deepFreezeWithoutFreeze_) + return true; + + JLOG(j.fatal()) << "Invariant failed: a trust line with deep freeze flag " + "without normal freeze was created"; + return false; +} + +//------------------------------------------------------------------------------ + +void +ValidNewAccountRoot::visitEntry( + bool, + std::shared_ptr const& before, + std::shared_ptr const& after) +{ + if (!before && after->getType() == ltACCOUNT_ROOT) + { + accountsCreated_++; + accountSeq_ = (*after)[sfSequence]; + pseudoAccount_ = isPseudoAccount(after); + flags_ = after->getFlags(); + } +} + +bool +ValidNewAccountRoot::finalize( + STTx const& tx, + TER const result, + XRPAmount const, + ReadView const& view, + beast::Journal const& j) +{ + if (accountsCreated_ == 0) + return true; + + if (accountsCreated_ > 1) + { + JLOG(j.fatal()) << "Invariant failed: multiple accounts " + "created in a single transaction"; + return false; + } + + // From this point on we know exactly one account was created. + if (hasPrivilege(tx, createAcct | createPseudoAcct) && result == tesSUCCESS) + { + bool const pseudoAccount = + (pseudoAccount_ && + (view.rules().enabled(featureSingleAssetVault) || + view.rules().enabled(featureLendingProtocol))); + + if (pseudoAccount && !hasPrivilege(tx, createPseudoAcct)) + { + JLOG(j.fatal()) << "Invariant failed: pseudo-account created by a " + "wrong transaction type"; + return false; + } + + std::uint32_t const startingSeq = pseudoAccount ? 0 : view.seq(); + + if (accountSeq_ != startingSeq) + { + JLOG(j.fatal()) << "Invariant failed: account created with " + "wrong starting sequence number"; + return false; + } + + if (pseudoAccount) + { + std::uint32_t const expected = (lsfDisableMaster | lsfDefaultRipple | lsfDepositAuth); + if (flags_ != expected) + { + JLOG(j.fatal()) << "Invariant failed: pseudo-account created with " + "wrong flags"; + return false; + } + } + + return true; + } + + JLOG(j.fatal()) << "Invariant failed: account root created illegally"; + return false; +} // namespace xrpl + +//------------------------------------------------------------------------------ + +void +ValidClawback::visitEntry( + bool, + std::shared_ptr const& before, + std::shared_ptr const&) +{ + if (before && before->getType() == ltRIPPLE_STATE) + trustlinesChanged++; + + if (before && before->getType() == ltMPTOKEN) + mptokensChanged++; +} + +bool +ValidClawback::finalize( + STTx const& tx, + TER const result, + XRPAmount const, + ReadView const& view, + beast::Journal const& j) +{ + if (tx.getTxnType() != ttCLAWBACK) + return true; + + if (result == tesSUCCESS) + { + if (trustlinesChanged > 1) + { + JLOG(j.fatal()) << "Invariant failed: more than one trustline changed."; + return false; + } + + if (mptokensChanged > 1) + { + JLOG(j.fatal()) << "Invariant failed: more than one mptokens changed."; + return false; + } + + if (trustlinesChanged == 1) + { + AccountID const issuer = tx.getAccountID(sfAccount); + STAmount const& amount = tx.getFieldAmount(sfAmount); + AccountID const& holder = amount.getIssuer(); + STAmount const holderBalance = + accountHolds(view, holder, amount.getCurrency(), issuer, fhIGNORE_FREEZE, j); + + if (holderBalance.signum() < 0) + { + JLOG(j.fatal()) << "Invariant failed: trustline balance is negative"; + return false; + } + } + } + else + { + if (trustlinesChanged != 0) + { + JLOG(j.fatal()) << "Invariant failed: some trustlines were changed " + "despite failure of the transaction."; + return false; + } + + if (mptokensChanged != 0) + { + JLOG(j.fatal()) << "Invariant failed: some mptokens were changed " + "despite failure of the transaction."; + return false; + } + } + + return true; +} + +//------------------------------------------------------------------------------ + +void +ValidPseudoAccounts::visitEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) +{ + if (isDelete) + // Deletion is ignored + return; + + if (after && after->getType() == ltACCOUNT_ROOT) + { + bool const isPseudo = [&]() { + // isPseudoAccount checks that any of the pseudo-account fields are + // set. + if (isPseudoAccount(after)) + return true; + // Not all pseudo-accounts have a zero sequence, but all accounts + // with a zero sequence had better be pseudo-accounts. + if (after->at(sfSequence) == 0) + return true; + + return false; + }(); + if (isPseudo) + { + // Pseudo accounts must have the following properties: + // 1. Exactly one of the pseudo-account fields is set. + // 2. The sequence number is not changed. + // 3. The lsfDisableMaster, lsfDefaultRipple, and lsfDepositAuth + // flags are set. + // 4. The RegularKey is not set. + { + std::vector const& fields = getPseudoAccountFields(); + + auto const numFields = + std::count_if(fields.begin(), fields.end(), [&after](SField const* sf) -> bool { + return after->isFieldPresent(*sf); + }); + if (numFields != 1) + { + std::stringstream error; + error << "pseudo-account has " << numFields << " pseudo-account fields set"; + errors_.emplace_back(error.str()); + } + } + if (before && before->at(sfSequence) != after->at(sfSequence)) + { + errors_.emplace_back("pseudo-account sequence changed"); + } + if (!after->isFlag(lsfDisableMaster | lsfDefaultRipple | lsfDepositAuth)) + { + errors_.emplace_back("pseudo-account flags are not set"); + } + if (after->isFieldPresent(sfRegularKey)) + { + errors_.emplace_back("pseudo-account has a regular key"); + } + } + } +} + +bool +ValidPseudoAccounts::finalize( + STTx const& tx, + TER const, + XRPAmount const, + ReadView const& view, + beast::Journal const& j) +{ + bool const enforce = view.rules().enabled(featureSingleAssetVault); + XRPL_ASSERT( + errors_.empty() || enforce, + "xrpl::ValidPseudoAccounts::finalize : no bad " + "changes or enforce invariant"); + if (!errors_.empty()) + { + for (auto const& error : errors_) + { + JLOG(j.fatal()) << "Invariant failed: " << error; + } + if (enforce) + return false; + } + return true; +} + +//------------------------------------------------------------------------------ + +void +NoModifiedUnmodifiableFields::visitEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) +{ + if (isDelete || !before) + // Creation and deletion are ignored + return; + + changedEntries_.emplace(before, after); +} + +bool +NoModifiedUnmodifiableFields::finalize( + STTx const& tx, + TER const, + XRPAmount const, + ReadView const& view, + beast::Journal const& j) +{ + static auto const fieldChanged = [](auto const& before, auto const& after, auto const& field) { + bool const beforeField = before->isFieldPresent(field); + bool const afterField = after->isFieldPresent(field); + return beforeField != afterField || (afterField && before->at(field) != after->at(field)); + }; + for (auto const& slePair : changedEntries_) + { + auto const& before = slePair.first; + auto const& after = slePair.second; + auto const type = after->getType(); + bool bad = false; + [[maybe_unused]] bool enforce = false; + switch (type) + { + case ltLOAN_BROKER: + /* + * We check this invariant regardless of lending protocol + * amendment status, allowing for detection and logging of + * potential issues even when the amendment is disabled. + */ + enforce = view.rules().enabled(featureLendingProtocol); + bad = fieldChanged(before, after, sfLedgerEntryType) || + fieldChanged(before, after, sfLedgerIndex) || + fieldChanged(before, after, sfSequence) || + fieldChanged(before, after, sfOwnerNode) || + fieldChanged(before, after, sfVaultNode) || + fieldChanged(before, after, sfVaultID) || + fieldChanged(before, after, sfAccount) || + fieldChanged(before, after, sfOwner) || + fieldChanged(before, after, sfManagementFeeRate) || + fieldChanged(before, after, sfCoverRateMinimum) || + fieldChanged(before, after, sfCoverRateLiquidation); + break; + case ltLOAN: + /* + * We check this invariant regardless of lending protocol + * amendment status, allowing for detection and logging of + * potential issues even when the amendment is disabled. + */ + enforce = view.rules().enabled(featureLendingProtocol); + bad = fieldChanged(before, after, sfLedgerEntryType) || + fieldChanged(before, after, sfLedgerIndex) || + fieldChanged(before, after, sfSequence) || + fieldChanged(before, after, sfOwnerNode) || + fieldChanged(before, after, sfLoanBrokerNode) || + fieldChanged(before, after, sfLoanBrokerID) || + fieldChanged(before, after, sfBorrower) || + fieldChanged(before, after, sfLoanOriginationFee) || + fieldChanged(before, after, sfLoanServiceFee) || + fieldChanged(before, after, sfLatePaymentFee) || + fieldChanged(before, after, sfClosePaymentFee) || + fieldChanged(before, after, sfOverpaymentFee) || + fieldChanged(before, after, sfInterestRate) || + fieldChanged(before, after, sfLateInterestRate) || + fieldChanged(before, after, sfCloseInterestRate) || + fieldChanged(before, after, sfOverpaymentInterestRate) || + fieldChanged(before, after, sfStartDate) || + fieldChanged(before, after, sfPaymentInterval) || + fieldChanged(before, after, sfGracePeriod) || + fieldChanged(before, after, sfLoanScale); + break; + default: + /* + * We check this invariant regardless of lending protocol + * amendment status, allowing for detection and logging of + * potential issues even when the amendment is disabled. + * + * We use the lending protocol as a gate, even though + * all transactions are affected because that's when it + * was added. + */ + enforce = view.rules().enabled(featureLendingProtocol); + bad = fieldChanged(before, after, sfLedgerEntryType) || + fieldChanged(before, after, sfLedgerIndex); + } + XRPL_ASSERT( + !bad || enforce, + "xrpl::NoModifiedUnmodifiableFields::finalize : no bad " + "changes or enforce invariant"); + if (bad) + { + JLOG(j.fatal()) << "Invariant failed: changed an unchangeable field for " + << tx.getTransactionID(); + if (enforce) + return false; + } + } + return true; +} + +} // namespace xrpl diff --git a/src/libxrpl/tx/invariants/LoanInvariant.cpp b/src/libxrpl/tx/invariants/LoanInvariant.cpp new file mode 100644 index 0000000000..01c4da46ac --- /dev/null +++ b/src/libxrpl/tx/invariants/LoanInvariant.cpp @@ -0,0 +1,278 @@ +#include +// +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl { + +void +ValidLoanBroker::visitEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) +{ + if (after) + { + if (after->getType() == ltLOAN_BROKER) + { + auto& broker = brokers_[after->key()]; + broker.brokerBefore = before; + broker.brokerAfter = after; + } + else if (after->getType() == ltACCOUNT_ROOT && after->isFieldPresent(sfLoanBrokerID)) + { + auto const& loanBrokerID = after->at(sfLoanBrokerID); + // create an entry if one doesn't already exist + brokers_.emplace(loanBrokerID, BrokerInfo{}); + } + else if (after->getType() == ltRIPPLE_STATE) + { + lines_.emplace_back(after); + } + else if (after->getType() == ltMPTOKEN) + { + mpts_.emplace_back(after); + } + } +} + +bool +ValidLoanBroker::goodZeroDirectory( + ReadView const& view, + SLE::const_ref dir, + beast::Journal const& j) const +{ + auto const next = dir->at(~sfIndexNext); + auto const prev = dir->at(~sfIndexPrevious); + if ((prev && *prev) || (next && *next)) + { + JLOG(j.fatal()) << "Invariant failed: Loan Broker with zero " + "OwnerCount has multiple directory pages"; + return false; + } + auto indexes = dir->getFieldV256(sfIndexes); + if (indexes.size() > 1) + { + JLOG(j.fatal()) << "Invariant failed: Loan Broker with zero " + "OwnerCount has multiple indexes in the Directory root"; + return false; + } + if (indexes.size() == 1) + { + auto const index = indexes.value().front(); + auto const sle = view.read(keylet::unchecked(index)); + if (!sle) + { + JLOG(j.fatal()) << "Invariant failed: Loan Broker directory corrupt"; + return false; + } + if (sle->getType() != ltRIPPLE_STATE && sle->getType() != ltMPTOKEN) + { + JLOG(j.fatal()) << "Invariant failed: Loan Broker with zero " + "OwnerCount has an unexpected entry in the directory"; + return false; + } + } + + return true; +} + +bool +ValidLoanBroker::finalize( + STTx const& tx, + TER const, + XRPAmount const, + ReadView const& view, + beast::Journal const& j) +{ + // Loan Brokers will not exist on ledger if the Lending Protocol amendment + // is not enabled, so there's no need to check it. + + for (auto const& line : lines_) + { + for (auto const& field : {&sfLowLimit, &sfHighLimit}) + { + auto const account = view.read(keylet::account(line->at(*field).getIssuer())); + // This Invariant doesn't know about the rules for Trust Lines, so + // if the account is missing, don't treat it as an error. This + // loop is only concerned with finding Broker pseudo-accounts + if (account && account->isFieldPresent(sfLoanBrokerID)) + { + auto const& loanBrokerID = account->at(sfLoanBrokerID); + // create an entry if one doesn't already exist + brokers_.emplace(loanBrokerID, BrokerInfo{}); + } + } + } + for (auto const& mpt : mpts_) + { + auto const account = view.read(keylet::account(mpt->at(sfAccount))); + // This Invariant doesn't know about the rules for MPTokens, so + // if the account is missing, don't treat is as an error. This + // loop is only concerned with finding Broker pseudo-accounts + if (account && account->isFieldPresent(sfLoanBrokerID)) + { + auto const& loanBrokerID = account->at(sfLoanBrokerID); + // create an entry if one doesn't already exist + brokers_.emplace(loanBrokerID, BrokerInfo{}); + } + } + + for (auto const& [brokerID, broker] : brokers_) + { + auto const& after = + broker.brokerAfter ? broker.brokerAfter : view.read(keylet::loanbroker(brokerID)); + + if (!after) + { + JLOG(j.fatal()) << "Invariant failed: Loan Broker missing"; + return false; + } + + auto const& before = broker.brokerBefore; + + // https://github.com/Tapanito/XRPL-Standards/blob/xls-66-lending-protocol/XLS-0066d-lending-protocol/README.md#3123-invariants + // If `LoanBroker.OwnerCount = 0` the `DirectoryNode` will have at most + // one node (the root), which will only hold entries for `RippleState` + // or `MPToken` objects. + if (after->at(sfOwnerCount) == 0) + { + auto const dir = view.read(keylet::ownerDir(after->at(sfAccount))); + if (dir) + { + if (!goodZeroDirectory(view, dir, j)) + { + return false; + } + } + } + if (before && before->at(sfLoanSequence) > after->at(sfLoanSequence)) + { + JLOG(j.fatal()) << "Invariant failed: Loan Broker sequence number " + "decreased"; + return false; + } + if (after->at(sfDebtTotal) < 0) + { + JLOG(j.fatal()) << "Invariant failed: Loan Broker debt total is negative"; + return false; + } + if (after->at(sfCoverAvailable) < 0) + { + JLOG(j.fatal()) << "Invariant failed: Loan Broker cover available is negative"; + return false; + } + auto const vault = view.read(keylet::vault(after->at(sfVaultID))); + if (!vault) + { + JLOG(j.fatal()) << "Invariant failed: Loan Broker vault ID is invalid"; + return false; + } + auto const& vaultAsset = vault->at(sfAsset); + if (after->at(sfCoverAvailable) < accountHolds( + view, + after->at(sfAccount), + vaultAsset, + FreezeHandling::fhIGNORE_FREEZE, + AuthHandling::ahIGNORE_AUTH, + j)) + { + JLOG(j.fatal()) << "Invariant failed: Loan Broker cover available " + "is less than pseudo-account asset balance"; + return false; + } + } + return true; +} + +//------------------------------------------------------------------------------ + +void +ValidLoan::visitEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) +{ + if (after && after->getType() == ltLOAN) + { + loans_.emplace_back(before, after); + } +} + +bool +ValidLoan::finalize( + STTx const& tx, + TER const, + XRPAmount const, + ReadView const& view, + beast::Journal const& j) +{ + // Loans will not exist on ledger if the Lending Protocol amendment + // is not enabled, so there's no need to check it. + + for (auto const& [before, after] : loans_) + { + // https://github.com/Tapanito/XRPL-Standards/blob/xls-66-lending-protocol/XLS-0066d-lending-protocol/README.md#3223-invariants + // If `Loan.PaymentRemaining = 0` then the loan MUST be fully paid off + if (after->at(sfPaymentRemaining) == 0 && + (after->at(sfTotalValueOutstanding) != beast::zero || + after->at(sfPrincipalOutstanding) != beast::zero || + after->at(sfManagementFeeOutstanding) != beast::zero)) + { + JLOG(j.fatal()) << "Invariant failed: Loan with zero payments " + "remaining has not been paid off"; + return false; + } + // If `Loan.PaymentRemaining != 0` then the loan MUST NOT be fully paid + // off + if (after->at(sfPaymentRemaining) != 0 && + after->at(sfTotalValueOutstanding) == beast::zero && + after->at(sfPrincipalOutstanding) == beast::zero && + after->at(sfManagementFeeOutstanding) == beast::zero) + { + JLOG(j.fatal()) << "Invariant failed: Loan with zero payments " + "remaining has not been paid off"; + return false; + } + if (before && (before->isFlag(lsfLoanOverpayment) != after->isFlag(lsfLoanOverpayment))) + { + JLOG(j.fatal()) << "Invariant failed: Loan Overpayment flag changed"; + return false; + } + // Must not be negative - STNumber + for (auto const field : + {&sfLoanServiceFee, + &sfLatePaymentFee, + &sfClosePaymentFee, + &sfPrincipalOutstanding, + &sfTotalValueOutstanding, + &sfManagementFeeOutstanding}) + { + if (after->at(*field) < 0) + { + JLOG(j.fatal()) << "Invariant failed: " << field->getName() << " is negative "; + return false; + } + } + // Must be positive - STNumber + for (auto const field : { + &sfPeriodicPayment, + }) + { + if (after->at(*field) <= 0) + { + JLOG(j.fatal()) << "Invariant failed: " << field->getName() + << " is zero or negative "; + return false; + } + } + } + return true; +} + +} // namespace xrpl diff --git a/src/libxrpl/tx/invariants/MPTInvariant.cpp b/src/libxrpl/tx/invariants/MPTInvariant.cpp new file mode 100644 index 0000000000..20957b8d43 --- /dev/null +++ b/src/libxrpl/tx/invariants/MPTInvariant.cpp @@ -0,0 +1,192 @@ +#include +// +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl { + +void +ValidMPTIssuance::visitEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) +{ + if (after && after->getType() == ltMPTOKEN_ISSUANCE) + { + if (isDelete) + mptIssuancesDeleted_++; + else if (!before) + mptIssuancesCreated_++; + } + + if (after && after->getType() == ltMPTOKEN) + { + if (isDelete) + mptokensDeleted_++; + else if (!before) + { + mptokensCreated_++; + MPTIssue const mptIssue{after->at(sfMPTokenIssuanceID)}; + if (mptIssue.getIssuer() == after->at(sfAccount)) + mptCreatedByIssuer_ = true; + } + } +} + +bool +ValidMPTIssuance::finalize( + STTx const& tx, + TER const result, + XRPAmount const _fee, + ReadView const& view, + beast::Journal const& j) +{ + if (result == tesSUCCESS) + { + auto const& rules = view.rules(); + [[maybe_unused]] + bool enforceCreatedByIssuer = + rules.enabled(featureSingleAssetVault) || rules.enabled(featureLendingProtocol); + if (mptCreatedByIssuer_) + { + JLOG(j.fatal()) << "Invariant failed: MPToken created for the MPT issuer"; + // The comment above starting with "assert(enforce)" explains this + // assert. + XRPL_ASSERT_PARTS( + enforceCreatedByIssuer, "xrpl::ValidMPTIssuance::finalize", "no issuer MPToken"); + if (enforceCreatedByIssuer) + return false; + } + + auto const txnType = tx.getTxnType(); + if (hasPrivilege(tx, createMPTIssuance)) + { + if (mptIssuancesCreated_ == 0) + { + JLOG(j.fatal()) << "Invariant failed: transaction " + "succeeded without creating a MPT issuance"; + } + else if (mptIssuancesDeleted_ != 0) + { + JLOG(j.fatal()) << "Invariant failed: transaction " + "succeeded while removing MPT issuances"; + } + else if (mptIssuancesCreated_ > 1) + { + JLOG(j.fatal()) << "Invariant failed: transaction " + "succeeded but created multiple issuances"; + } + + return mptIssuancesCreated_ == 1 && mptIssuancesDeleted_ == 0; + } + + if (hasPrivilege(tx, destroyMPTIssuance)) + { + if (mptIssuancesDeleted_ == 0) + { + JLOG(j.fatal()) << "Invariant failed: MPT issuance deletion " + "succeeded without removing a MPT issuance"; + } + else if (mptIssuancesCreated_ > 0) + { + JLOG(j.fatal()) << "Invariant failed: MPT issuance deletion " + "succeeded while creating MPT issuances"; + } + else if (mptIssuancesDeleted_ > 1) + { + JLOG(j.fatal()) << "Invariant failed: MPT issuance deletion " + "succeeded but deleted multiple issuances"; + } + + return mptIssuancesCreated_ == 0 && mptIssuancesDeleted_ == 1; + } + + bool const lendingProtocolEnabled = view.rules().enabled(featureLendingProtocol); + // ttESCROW_FINISH may authorize an MPT, but it can't have the + // mayAuthorizeMPT privilege, because that may cause + // non-amendment-gated side effects. + bool const enforceEscrowFinish = (txnType == ttESCROW_FINISH) && + (view.rules().enabled(featureSingleAssetVault) || lendingProtocolEnabled); + if (hasPrivilege(tx, mustAuthorizeMPT | mayAuthorizeMPT) || enforceEscrowFinish) + { + bool const submittedByIssuer = tx.isFieldPresent(sfHolder); + + if (mptIssuancesCreated_ > 0) + { + JLOG(j.fatal()) << "Invariant failed: MPT authorize " + "succeeded but created MPT issuances"; + return false; + } + else if (mptIssuancesDeleted_ > 0) + { + JLOG(j.fatal()) << "Invariant failed: MPT authorize " + "succeeded but deleted issuances"; + return false; + } + else if (lendingProtocolEnabled && mptokensCreated_ + mptokensDeleted_ > 1) + { + JLOG(j.fatal()) << "Invariant failed: MPT authorize succeeded " + "but created/deleted bad number mptokens"; + return false; + } + else if (submittedByIssuer && (mptokensCreated_ > 0 || mptokensDeleted_ > 0)) + { + JLOG(j.fatal()) << "Invariant failed: MPT authorize submitted by issuer " + "succeeded but created/deleted mptokens"; + return false; + } + else if ( + !submittedByIssuer && hasPrivilege(tx, mustAuthorizeMPT) && + (mptokensCreated_ + mptokensDeleted_ != 1)) + { + // if the holder submitted this tx, then a mptoken must be + // either created or deleted. + JLOG(j.fatal()) << "Invariant failed: MPT authorize submitted by holder " + "succeeded but created/deleted bad number of mptokens"; + return false; + } + + return true; + } + if (txnType == ttESCROW_FINISH) + { + // ttESCROW_FINISH may authorize an MPT, but it can't have the + // mayAuthorizeMPT privilege, because that may cause + // non-amendment-gated side effects. + XRPL_ASSERT_PARTS( + !enforceEscrowFinish, "xrpl::ValidMPTIssuance::finalize", "not escrow finish tx"); + return true; + } + + if (hasPrivilege(tx, mayDeleteMPT) && mptokensDeleted_ == 1 && mptokensCreated_ == 0 && + mptIssuancesCreated_ == 0 && mptIssuancesDeleted_ == 0) + return true; + } + + if (mptIssuancesCreated_ != 0) + { + JLOG(j.fatal()) << "Invariant failed: a MPT issuance was created"; + } + else if (mptIssuancesDeleted_ != 0) + { + JLOG(j.fatal()) << "Invariant failed: a MPT issuance was deleted"; + } + else if (mptokensCreated_ != 0) + { + JLOG(j.fatal()) << "Invariant failed: a MPToken was created"; + } + else if (mptokensDeleted_ != 0) + { + JLOG(j.fatal()) << "Invariant failed: a MPToken was deleted"; + } + + return mptIssuancesCreated_ == 0 && mptIssuancesDeleted_ == 0 && mptokensCreated_ == 0 && + mptokensDeleted_ == 0; +} + +} // namespace xrpl diff --git a/src/libxrpl/tx/invariants/NFTInvariant.cpp b/src/libxrpl/tx/invariants/NFTInvariant.cpp new file mode 100644 index 0000000000..db06896023 --- /dev/null +++ b/src/libxrpl/tx/invariants/NFTInvariant.cpp @@ -0,0 +1,274 @@ +#include +// +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl { + +void +ValidNFTokenPage::visitEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) +{ + static constexpr uint256 const& pageBits = nft::pageMask; + static constexpr uint256 const accountBits = ~pageBits; + + if ((before && before->getType() != ltNFTOKEN_PAGE) || + (after && after->getType() != ltNFTOKEN_PAGE)) + return; + + auto check = [this, isDelete](std::shared_ptr const& sle) { + uint256 const account = sle->key() & accountBits; + uint256 const hiLimit = sle->key() & pageBits; + std::optional const prev = (*sle)[~sfPreviousPageMin]; + + // Make sure that any page links... + // 1. Are properly associated with the owning account and + // 2. The page is correctly ordered between links. + if (prev) + { + if (account != (*prev & accountBits)) + badLink_ = true; + + if (hiLimit <= (*prev & pageBits)) + badLink_ = true; + } + + if (auto const next = (*sle)[~sfNextPageMin]) + { + if (account != (*next & accountBits)) + badLink_ = true; + + if (hiLimit >= (*next & pageBits)) + badLink_ = true; + } + + { + auto const& nftokens = sle->getFieldArray(sfNFTokens); + + // An NFTokenPage should never contain too many tokens or be empty. + if (std::size_t const nftokenCount = nftokens.size(); + (!isDelete && nftokenCount == 0) || nftokenCount > dirMaxTokensPerPage) + invalidSize_ = true; + + // If prev is valid, use it to establish a lower bound for + // page entries. If prev is not valid the lower bound is zero. + uint256 const loLimit = prev ? *prev & pageBits : uint256(beast::zero); + + // Also verify that all NFTokenIDs in the page are sorted. + uint256 loCmp = loLimit; + for (auto const& obj : nftokens) + { + uint256 const tokenID = obj[sfNFTokenID]; + if (!nft::compareTokens(loCmp, tokenID)) + badSort_ = true; + loCmp = tokenID; + + // None of the NFTs on this page should belong on lower or + // higher pages. + if (uint256 const tokenPageBits = tokenID & pageBits; + tokenPageBits < loLimit || tokenPageBits >= hiLimit) + badEntry_ = true; + + if (auto uri = obj[~sfURI]; uri && uri->empty()) + badURI_ = true; + } + } + }; + + if (before) + { + check(before); + + // While an account's NFToken directory contains any NFTokens, the last + // NFTokenPage (with 96 bits of 1 in the low part of the index) should + // never be deleted. + if (isDelete && (before->key() & nft::pageMask) == nft::pageMask && + before->isFieldPresent(sfPreviousPageMin)) + { + deletedFinalPage_ = true; + } + } + + if (after) + check(after); + + if (!isDelete && before && after) + { + // If the NFTokenPage + // 1. Has a NextMinPage field in before, but loses it in after, and + // 2. This is not the last page in the directory + // Then we have identified a corruption in the links between the + // NFToken pages in the NFToken directory. + if ((before->key() & nft::pageMask) != nft::pageMask && + before->isFieldPresent(sfNextPageMin) && !after->isFieldPresent(sfNextPageMin)) + { + deletedLink_ = true; + } + } +} + +bool +ValidNFTokenPage::finalize( + STTx const& tx, + TER const result, + XRPAmount const, + ReadView const& view, + beast::Journal const& j) +{ + if (badLink_) + { + JLOG(j.fatal()) << "Invariant failed: NFT page is improperly linked."; + return false; + } + + if (badEntry_) + { + JLOG(j.fatal()) << "Invariant failed: NFT found in incorrect page."; + return false; + } + + if (badSort_) + { + JLOG(j.fatal()) << "Invariant failed: NFTs on page are not sorted."; + return false; + } + + if (badURI_) + { + JLOG(j.fatal()) << "Invariant failed: NFT contains empty URI."; + return false; + } + + if (invalidSize_) + { + JLOG(j.fatal()) << "Invariant failed: NFT page has invalid size."; + return false; + } + + if (view.rules().enabled(fixNFTokenPageLinks)) + { + if (deletedFinalPage_) + { + JLOG(j.fatal()) << "Invariant failed: Last NFT page deleted with " + "non-empty directory."; + return false; + } + if (deletedLink_) + { + JLOG(j.fatal()) << "Invariant failed: Lost NextMinPage link."; + return false; + } + } + + return true; +} + +//------------------------------------------------------------------------------ +void +NFTokenCountTracking::visitEntry( + bool, + std::shared_ptr const& before, + std::shared_ptr const& after) +{ + if (before && before->getType() == ltACCOUNT_ROOT) + { + beforeMintedTotal += (*before)[~sfMintedNFTokens].value_or(0); + beforeBurnedTotal += (*before)[~sfBurnedNFTokens].value_or(0); + } + + if (after && after->getType() == ltACCOUNT_ROOT) + { + afterMintedTotal += (*after)[~sfMintedNFTokens].value_or(0); + afterBurnedTotal += (*after)[~sfBurnedNFTokens].value_or(0); + } +} + +bool +NFTokenCountTracking::finalize( + STTx const& tx, + TER const result, + XRPAmount const, + ReadView const& view, + beast::Journal const& j) +{ + if (!hasPrivilege(tx, changeNFTCounts)) + { + if (beforeMintedTotal != afterMintedTotal) + { + JLOG(j.fatal()) << "Invariant failed: the number of minted tokens " + "changed without a mint transaction!"; + return false; + } + + if (beforeBurnedTotal != afterBurnedTotal) + { + JLOG(j.fatal()) << "Invariant failed: the number of burned tokens " + "changed without a burn transaction!"; + return false; + } + + return true; + } + + if (tx.getTxnType() == ttNFTOKEN_MINT) + { + if (result == tesSUCCESS && beforeMintedTotal >= afterMintedTotal) + { + JLOG(j.fatal()) << "Invariant failed: successful minting didn't increase " + "the number of minted tokens."; + return false; + } + + if (result != tesSUCCESS && beforeMintedTotal != afterMintedTotal) + { + JLOG(j.fatal()) << "Invariant failed: failed minting changed the " + "number of minted tokens."; + return false; + } + + if (beforeBurnedTotal != afterBurnedTotal) + { + JLOG(j.fatal()) << "Invariant failed: minting changed the number of " + "burned tokens."; + return false; + } + } + + if (tx.getTxnType() == ttNFTOKEN_BURN) + { + if (result == tesSUCCESS) + { + if (beforeBurnedTotal >= afterBurnedTotal) + { + JLOG(j.fatal()) << "Invariant failed: successful burning didn't increase " + "the number of burned tokens."; + return false; + } + } + + if (result != tesSUCCESS && beforeBurnedTotal != afterBurnedTotal) + { + JLOG(j.fatal()) << "Invariant failed: failed burning changed the " + "number of burned tokens."; + return false; + } + + if (beforeMintedTotal != afterMintedTotal) + { + JLOG(j.fatal()) << "Invariant failed: burning changed the number of " + "minted tokens."; + return false; + } + } + + return true; +} + +} // namespace xrpl diff --git a/src/libxrpl/tx/invariants/PermissionedDEXInvariant.cpp b/src/libxrpl/tx/invariants/PermissionedDEXInvariant.cpp new file mode 100644 index 0000000000..2ece1f3fc0 --- /dev/null +++ b/src/libxrpl/tx/invariants/PermissionedDEXInvariant.cpp @@ -0,0 +1,93 @@ +#include +// +#include +#include +#include +#include +#include +#include + +namespace xrpl { + +void +ValidPermissionedDEX::visitEntry( + bool, + std::shared_ptr const& before, + std::shared_ptr const& after) +{ + if (after && after->getType() == ltDIR_NODE) + { + if (after->isFieldPresent(sfDomainID)) + domains_.insert(after->getFieldH256(sfDomainID)); + } + + if (after && after->getType() == ltOFFER) + { + if (after->isFieldPresent(sfDomainID)) + domains_.insert(after->getFieldH256(sfDomainID)); + else + regularOffers_ = true; + + // if a hybrid offer is missing domain or additional book, there's + // something wrong + if (after->isFlag(lsfHybrid) && + (!after->isFieldPresent(sfDomainID) || !after->isFieldPresent(sfAdditionalBooks) || + after->getFieldArray(sfAdditionalBooks).size() > 1)) + badHybrids_ = true; + } +} + +bool +ValidPermissionedDEX::finalize( + STTx const& tx, + TER const result, + XRPAmount const, + ReadView const& view, + beast::Journal const& j) +{ + auto const txType = tx.getTxnType(); + if ((txType != ttPAYMENT && txType != ttOFFER_CREATE) || result != tesSUCCESS) + return true; + + // For each offercreate transaction, check if + // permissioned offers are valid + if (txType == ttOFFER_CREATE && badHybrids_) + { + JLOG(j.fatal()) << "Invariant failed: hybrid offer is malformed"; + return false; + } + + if (!tx.isFieldPresent(sfDomainID)) + return true; + + auto const domain = tx.getFieldH256(sfDomainID); + + if (!view.exists(keylet::permissionedDomain(domain))) + { + JLOG(j.fatal()) << "Invariant failed: domain doesn't exist"; + return false; + } + + // for both payment and offercreate, there shouldn't be another domain + // that's different from the domain specified + for (auto const& d : domains_) + { + if (d != domain) + { + JLOG(j.fatal()) << "Invariant failed: transaction" + " consumed wrong domains"; + return false; + } + } + + if (regularOffers_) + { + JLOG(j.fatal()) << "Invariant failed: domain transaction" + " affected regular offers"; + return false; + } + + return true; +} + +} // namespace xrpl diff --git a/src/libxrpl/tx/invariants/PermissionedDomainInvariant.cpp b/src/libxrpl/tx/invariants/PermissionedDomainInvariant.cpp new file mode 100644 index 0000000000..77acbe12c6 --- /dev/null +++ b/src/libxrpl/tx/invariants/PermissionedDomainInvariant.cpp @@ -0,0 +1,162 @@ +#include +// +#include +#include +#include +#include +#include + +namespace xrpl { + +void +ValidPermissionedDomain::visitEntry( + bool isDel, + std::shared_ptr const& before, + std::shared_ptr const& after) +{ + if (before && before->getType() != ltPERMISSIONED_DOMAIN) + return; + if (after && after->getType() != ltPERMISSIONED_DOMAIN) + return; + + auto check = [isDel](std::vector& sleStatus, std::shared_ptr const& sle) { + auto const& credentials = sle->getFieldArray(sfAcceptedCredentials); + auto const sorted = credentials::makeSorted(credentials); + + SleStatus ss{credentials.size(), false, !sorted.empty(), isDel}; + + // If array have duplicates then all the other checks are invalid + if (ss.isUnique_) + { + unsigned i = 0; + for (auto const& cred : sorted) + { + auto const& credTx = credentials[i++]; + ss.isSorted_ = + (cred.first == credTx[sfIssuer]) && (cred.second == credTx[sfCredentialType]); + if (!ss.isSorted_) + break; + } + } + sleStatus.emplace_back(std::move(ss)); + }; + + if (after) + check(sleStatus_, after); +} + +bool +ValidPermissionedDomain::finalize( + STTx const& tx, + TER const result, + XRPAmount const, + ReadView const& view, + beast::Journal const& j) +{ + auto check = [](SleStatus const& sleStatus, beast::Journal const& j) { + if (!sleStatus.credentialsSize_) + { + JLOG(j.fatal()) << "Invariant failed: permissioned domain with " + "no rules."; + return false; + } + + if (sleStatus.credentialsSize_ > maxPermissionedDomainCredentialsArraySize) + { + JLOG(j.fatal()) << "Invariant failed: permissioned domain bad " + "credentials size " + << sleStatus.credentialsSize_; + return false; + } + + if (!sleStatus.isUnique_) + { + JLOG(j.fatal()) << "Invariant failed: permissioned domain credentials " + "aren't unique"; + return false; + } + + if (!sleStatus.isSorted_) + { + JLOG(j.fatal()) << "Invariant failed: permissioned domain credentials " + "aren't sorted"; + return false; + } + + return true; + }; + + if (view.rules().enabled(fixPermissionedDomainInvariant)) + { + // No permissioned domains should be affected if the transaction failed + if (result != tesSUCCESS) + // If nothing changed, all is good. If there were changes, that's + // bad. + return sleStatus_.empty(); + + if (sleStatus_.size() > 1) + { + JLOG(j.fatal()) << "Invariant failed: transaction affected more " + "than 1 permissioned domain entry."; + return false; + } + + switch (tx.getTxnType()) + { + case ttPERMISSIONED_DOMAIN_SET: { + if (sleStatus_.empty()) + { + JLOG(j.fatal()) << "Invariant failed: no domain objects affected by " + "PermissionedDomainSet"; + return false; + } + + auto const& sleStatus = sleStatus_[0]; + if (sleStatus.isDelete_) + { + JLOG(j.fatal()) << "Invariant failed: domain object " + "deleted by PermissionedDomainSet"; + return false; + } + return check(sleStatus, j); + } + case ttPERMISSIONED_DOMAIN_DELETE: { + if (sleStatus_.empty()) + { + JLOG(j.fatal()) << "Invariant failed: no domain objects affected by " + "PermissionedDomainDelete"; + return false; + } + + if (!sleStatus_[0].isDelete_) + { + JLOG(j.fatal()) << "Invariant failed: domain object " + "modified, but not deleted by " + "PermissionedDomainDelete"; + return false; + } + return true; + } + default: { + if (!sleStatus_.empty()) + { + JLOG(j.fatal()) << "Invariant failed: " << sleStatus_.size() + << " domain object(s) affected by an " + "unauthorized transaction. " + << tx.getTxnType(); + return false; + } + return true; + } + } + } + else + { + if (tx.getTxnType() != ttPERMISSIONED_DOMAIN_SET || result != tesSUCCESS || + sleStatus_.empty()) + return true; + return check(sleStatus_[0], j); + } +} + +} // namespace xrpl diff --git a/src/libxrpl/tx/invariants/VaultInvariant.cpp b/src/libxrpl/tx/invariants/VaultInvariant.cpp new file mode 100644 index 0000000000..c3db3a563a --- /dev/null +++ b/src/libxrpl/tx/invariants/VaultInvariant.cpp @@ -0,0 +1,926 @@ +#include +// +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl { + +ValidVault::Vault +ValidVault::Vault::make(SLE const& from) +{ + XRPL_ASSERT(from.getType() == ltVAULT, "ValidVault::Vault::make : from Vault object"); + + ValidVault::Vault self; + self.key = from.key(); + self.asset = from.at(sfAsset); + self.pseudoId = from.getAccountID(sfAccount); + self.owner = from.at(sfOwner); + self.shareMPTID = from.getFieldH192(sfShareMPTID); + self.assetsTotal = from.at(sfAssetsTotal); + self.assetsAvailable = from.at(sfAssetsAvailable); + self.assetsMaximum = from.at(sfAssetsMaximum); + self.lossUnrealized = from.at(sfLossUnrealized); + return self; +} + +ValidVault::Shares +ValidVault::Shares::make(SLE const& from) +{ + XRPL_ASSERT( + from.getType() == ltMPTOKEN_ISSUANCE, + "ValidVault::Shares::make : from MPTokenIssuance object"); + + ValidVault::Shares self; + self.share = MPTIssue(makeMptID(from.getFieldU32(sfSequence), from.getAccountID(sfIssuer))); + self.sharesTotal = from.at(sfOutstandingAmount); + self.sharesMaximum = from[~sfMaximumAmount].value_or(maxMPTokenAmount); + return self; +} + +void +ValidVault::visitEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) +{ + // If `before` is empty, this means an object is being created, in which + // case `isDelete` must be false. Otherwise `before` and `after` are set and + // `isDelete` indicates whether an object is being deleted or modified. + XRPL_ASSERT( + after != nullptr && (before != nullptr || !isDelete), + "xrpl::ValidVault::visitEntry : some object is available"); + + // Number balanceDelta will capture the difference (delta) between "before" + // state (zero if created) and "after" state (zero if destroyed), so the + // invariants can validate that the change in account balances matches the + // change in vault balances, stored to deltas_ at the end of this function. + Number balanceDelta{}; + + std::int8_t sign = 0; + if (before) + { + switch (before->getType()) + { + case ltVAULT: + beforeVault_.push_back(Vault::make(*before)); + break; + case ltMPTOKEN_ISSUANCE: + // At this moment we have no way of telling if this object holds + // vault shares or something else. Save it for finalize. + beforeMPTs_.push_back(Shares::make(*before)); + balanceDelta = static_cast(before->getFieldU64(sfOutstandingAmount)); + sign = 1; + break; + case ltMPTOKEN: + balanceDelta = static_cast(before->getFieldU64(sfMPTAmount)); + sign = -1; + break; + case ltACCOUNT_ROOT: + case ltRIPPLE_STATE: + balanceDelta = before->getFieldAmount(sfBalance); + sign = -1; + break; + default:; + } + } + + if (!isDelete && after) + { + switch (after->getType()) + { + case ltVAULT: + afterVault_.push_back(Vault::make(*after)); + break; + case ltMPTOKEN_ISSUANCE: + // At this moment we have no way of telling if this object holds + // vault shares or something else. Save it for finalize. + afterMPTs_.push_back(Shares::make(*after)); + balanceDelta -= + Number(static_cast(after->getFieldU64(sfOutstandingAmount))); + sign = 1; + break; + case ltMPTOKEN: + balanceDelta -= Number(static_cast(after->getFieldU64(sfMPTAmount))); + sign = -1; + break; + case ltACCOUNT_ROOT: + case ltRIPPLE_STATE: + balanceDelta -= Number(after->getFieldAmount(sfBalance)); + sign = -1; + break; + default:; + } + } + + uint256 const key = (before ? before->key() : after->key()); + // Append to deltas if sign is non-zero, i.e. an object of an interesting + // type has been updated. A transaction may update an object even when + // its balance has not changed, e.g. transaction fee equals the amount + // transferred to the account. We intentionally do not compare balanceDelta + // against zero, to avoid missing such updates. + if (sign != 0) + deltas_[key] = balanceDelta * sign; +} + +bool +ValidVault::finalize( + STTx const& tx, + TER const ret, + XRPAmount const fee, + ReadView const& view, + beast::Journal const& j) +{ + bool const enforce = view.rules().enabled(featureSingleAssetVault); + + if (!isTesSuccess(ret)) + return true; // Do not perform checks + + if (afterVault_.empty() && beforeVault_.empty()) + { + if (hasPrivilege(tx, mustModifyVault)) + { + JLOG(j.fatal()) << // + "Invariant failed: vault operation succeeded without modifying " + "a vault"; + XRPL_ASSERT(enforce, "xrpl::ValidVault::finalize : vault noop invariant"); + return !enforce; + } + + return true; // Not a vault operation + } + else if (!(hasPrivilege(tx, mustModifyVault) || hasPrivilege(tx, mayModifyVault))) + { + JLOG(j.fatal()) << // + "Invariant failed: vault updated by a wrong transaction type"; + XRPL_ASSERT( + enforce, + "xrpl::ValidVault::finalize : illegal vault transaction " + "invariant"); + return !enforce; // Also not a vault operation + } + + if (beforeVault_.size() > 1 || afterVault_.size() > 1) + { + JLOG(j.fatal()) << // + "Invariant failed: vault operation updated more than single vault"; + XRPL_ASSERT(enforce, "xrpl::ValidVault::finalize : single vault invariant"); + return !enforce; // That's all we can do here + } + + auto const txnType = tx.getTxnType(); + + // We do special handling for ttVAULT_DELETE first, because it's the only + // vault-modifying transaction without an "after" state of the vault + if (afterVault_.empty()) + { + if (txnType != ttVAULT_DELETE) + { + JLOG(j.fatal()) << // + "Invariant failed: vault deleted by a wrong transaction type"; + XRPL_ASSERT( + enforce, + "xrpl::ValidVault::finalize : illegal vault deletion " + "invariant"); + return !enforce; // That's all we can do here + } + + // Note, if afterVault_ is empty then we know that beforeVault_ is not + // empty, as enforced at the top of this function + auto const& beforeVault = beforeVault_[0]; + + // At this moment we only know a vault is being deleted and there + // might be some MPTokenIssuance objects which are deleted in the + // same transaction. Find the one matching this vault. + auto const deletedShares = [&]() -> std::optional { + for (auto const& e : beforeMPTs_) + { + if (e.share.getMptID() == beforeVault.shareMPTID) + return std::move(e); + } + return std::nullopt; + }(); + + if (!deletedShares) + { + JLOG(j.fatal()) << "Invariant failed: deleted vault must also " + "delete shares"; + XRPL_ASSERT(enforce, "xrpl::ValidVault::finalize : shares deletion invariant"); + return !enforce; // That's all we can do here + } + + bool result = true; + if (deletedShares->sharesTotal != 0) + { + JLOG(j.fatal()) << "Invariant failed: deleted vault must have no " + "shares outstanding"; + result = false; + } + if (beforeVault.assetsTotal != zero) + { + JLOG(j.fatal()) << "Invariant failed: deleted vault must have no " + "assets outstanding"; + result = false; + } + if (beforeVault.assetsAvailable != zero) + { + JLOG(j.fatal()) << "Invariant failed: deleted vault must have no " + "assets available"; + result = false; + } + + return result; + } + else if (txnType == ttVAULT_DELETE) + { + JLOG(j.fatal()) << "Invariant failed: vault deletion succeeded without " + "deleting a vault"; + XRPL_ASSERT(enforce, "xrpl::ValidVault::finalize : vault deletion invariant"); + return !enforce; // That's all we can do here + } + + // Note, `afterVault_.empty()` is handled above + auto const& afterVault = afterVault_[0]; + XRPL_ASSERT( + beforeVault_.empty() || beforeVault_[0].key == afterVault.key, + "xrpl::ValidVault::finalize : single vault operation"); + + auto const updatedShares = [&]() -> std::optional { + // At this moment we only know that a vault is being updated and there + // might be some MPTokenIssuance objects which are also updated in the + // same transaction. Find the one matching the shares to this vault. + // Note, we expect updatedMPTs collection to be extremely small. For + // such collections linear search is faster than lookup. + for (auto const& e : afterMPTs_) + { + if (e.share.getMptID() == afterVault.shareMPTID) + return e; + } + + auto const sleShares = view.read(keylet::mptIssuance(afterVault.shareMPTID)); + + return sleShares ? std::optional(Shares::make(*sleShares)) : std::nullopt; + }(); + + bool result = true; + + // Universal transaction checks + if (!beforeVault_.empty()) + { + auto const& beforeVault = beforeVault_[0]; + if (afterVault.asset != beforeVault.asset || afterVault.pseudoId != beforeVault.pseudoId || + afterVault.shareMPTID != beforeVault.shareMPTID) + { + JLOG(j.fatal()) << "Invariant failed: violation of vault immutable data"; + result = false; + } + } + + if (!updatedShares) + { + JLOG(j.fatal()) << "Invariant failed: updated vault must have shares"; + XRPL_ASSERT(enforce, "xrpl::ValidVault::finalize : vault has shares invariant"); + return !enforce; // That's all we can do here + } + + if (updatedShares->sharesTotal == 0) + { + if (afterVault.assetsTotal != zero) + { + JLOG(j.fatal()) << "Invariant failed: updated zero sized " + "vault must have no assets outstanding"; + result = false; + } + if (afterVault.assetsAvailable != zero) + { + JLOG(j.fatal()) << "Invariant failed: updated zero sized " + "vault must have no assets available"; + result = false; + } + } + else if (updatedShares->sharesTotal > updatedShares->sharesMaximum) + { + JLOG(j.fatal()) // + << "Invariant failed: updated shares must not exceed maximum " + << updatedShares->sharesMaximum; + result = false; + } + + if (afterVault.assetsAvailable < zero) + { + JLOG(j.fatal()) << "Invariant failed: assets available must be positive"; + result = false; + } + + if (afterVault.assetsAvailable > afterVault.assetsTotal) + { + JLOG(j.fatal()) << "Invariant failed: assets available must " + "not be greater than assets outstanding"; + result = false; + } + else if (afterVault.lossUnrealized > afterVault.assetsTotal - afterVault.assetsAvailable) + { + JLOG(j.fatal()) // + << "Invariant failed: loss unrealized must not exceed " + "the difference between assets outstanding and available"; + result = false; + } + + if (afterVault.assetsTotal < zero) + { + JLOG(j.fatal()) << "Invariant failed: assets outstanding must be positive"; + result = false; + } + + if (afterVault.assetsMaximum < zero) + { + JLOG(j.fatal()) << "Invariant failed: assets maximum must be positive"; + result = false; + } + + // Thanks to this check we can simply do `assert(!beforeVault_.empty()` when + // enforcing invariants on transaction types other than ttVAULT_CREATE + if (beforeVault_.empty() && txnType != ttVAULT_CREATE) + { + JLOG(j.fatal()) << // + "Invariant failed: vault created by a wrong transaction type"; + XRPL_ASSERT(enforce, "xrpl::ValidVault::finalize : vault creation invariant"); + return !enforce; // That's all we can do here + } + + if (!beforeVault_.empty() && afterVault.lossUnrealized != beforeVault_[0].lossUnrealized && + txnType != ttLOAN_MANAGE && txnType != ttLOAN_PAY) + { + JLOG(j.fatal()) << // + "Invariant failed: vault transaction must not change loss " + "unrealized"; + result = false; + } + + auto const beforeShares = [&]() -> std::optional { + if (beforeVault_.empty()) + return std::nullopt; + auto const& beforeVault = beforeVault_[0]; + + for (auto const& e : beforeMPTs_) + { + if (e.share.getMptID() == beforeVault.shareMPTID) + return std::move(e); + } + return std::nullopt; + }(); + + if (!beforeShares && + (tx.getTxnType() == ttVAULT_DEPOSIT || // + tx.getTxnType() == ttVAULT_WITHDRAW || // + tx.getTxnType() == ttVAULT_CLAWBACK)) + { + JLOG(j.fatal()) << "Invariant failed: vault operation succeeded " + "without updating shares"; + XRPL_ASSERT(enforce, "xrpl::ValidVault::finalize : shares noop invariant"); + return !enforce; // That's all we can do here + } + + auto const& vaultAsset = afterVault.asset; + auto const deltaAssets = [&](AccountID const& id) -> std::optional { + auto const get = // + [&](auto const& it, std::int8_t sign = 1) -> std::optional { + if (it == deltas_.end()) + return std::nullopt; + + return it->second * sign; + }; + + return std::visit( + [&](TIss const& issue) { + if constexpr (std::is_same_v) + { + if (isXRP(issue)) + return get(deltas_.find(keylet::account(id).key)); + return get( + deltas_.find(keylet::line(id, issue).key), id > issue.getIssuer() ? -1 : 1); + } + else if constexpr (std::is_same_v) + { + return get(deltas_.find(keylet::mptoken(issue.getMptID(), id).key)); + } + }, + vaultAsset.value()); + }; + auto const deltaAssetsTxAccount = [&]() -> std::optional { + auto ret = deltaAssets(tx[sfAccount]); + // Nothing returned or not XRP transaction + if (!ret.has_value() || !vaultAsset.native()) + return ret; + + // Delegated transaction; no need to compensate for fees + if (auto const delegate = tx[~sfDelegate]; + delegate.has_value() && *delegate != tx[sfAccount]) + return ret; + + *ret += fee.drops(); + if (*ret == zero) + return std::nullopt; + + return ret; + }; + auto const deltaShares = [&](AccountID const& id) -> std::optional { + auto const it = [&]() { + if (id == afterVault.pseudoId) + return deltas_.find(keylet::mptIssuance(afterVault.shareMPTID).key); + return deltas_.find(keylet::mptoken(afterVault.shareMPTID, id).key); + }(); + + return it != deltas_.end() ? std::optional(it->second) : std::nullopt; + }; + + auto const vaultHoldsNoAssets = [&](Vault const& vault) { + return vault.assetsAvailable == 0 && vault.assetsTotal == 0; + }; + + // Technically this does not need to be a lambda, but it's more + // convenient thanks to early "return false"; the not-so-nice + // alternatives are several layers of nested if/else or more complex + // (i.e. brittle) if statements. + result &= [&]() { + switch (txnType) + { + case ttVAULT_CREATE: { + bool result = true; + + if (!beforeVault_.empty()) + { + JLOG(j.fatal()) // + << "Invariant failed: create operation must not have " + "updated a vault"; + result = false; + } + + if (afterVault.assetsAvailable != zero || afterVault.assetsTotal != zero || + afterVault.lossUnrealized != zero || updatedShares->sharesTotal != 0) + { + JLOG(j.fatal()) // + << "Invariant failed: created vault must be empty"; + result = false; + } + + if (afterVault.pseudoId != updatedShares->share.getIssuer()) + { + JLOG(j.fatal()) // + << "Invariant failed: shares issuer and vault " + "pseudo-account must be the same"; + result = false; + } + + auto const sleSharesIssuer = + view.read(keylet::account(updatedShares->share.getIssuer())); + if (!sleSharesIssuer) + { + JLOG(j.fatal()) // + << "Invariant failed: shares issuer must exist"; + return false; + } + + if (!isPseudoAccount(sleSharesIssuer)) + { + JLOG(j.fatal()) // + << "Invariant failed: shares issuer must be a " + "pseudo-account"; + result = false; + } + + if (auto const vaultId = (*sleSharesIssuer)[~sfVaultID]; + !vaultId || *vaultId != afterVault.key) + { + JLOG(j.fatal()) // + << "Invariant failed: shares issuer pseudo-account " + "must point back to the vault"; + result = false; + } + + return result; + } + case ttVAULT_SET: { + bool result = true; + + XRPL_ASSERT( + !beforeVault_.empty(), "xrpl::ValidVault::finalize : set updated a vault"); + auto const& beforeVault = beforeVault_[0]; + + auto const vaultDeltaAssets = deltaAssets(afterVault.pseudoId); + if (vaultDeltaAssets) + { + JLOG(j.fatal()) << // + "Invariant failed: set must not change vault balance"; + result = false; + } + + if (beforeVault.assetsTotal != afterVault.assetsTotal) + { + JLOG(j.fatal()) << // + "Invariant failed: set must not change assets " + "outstanding"; + result = false; + } + + if (afterVault.assetsMaximum > zero && + afterVault.assetsTotal > afterVault.assetsMaximum) + { + JLOG(j.fatal()) << // + "Invariant failed: set assets outstanding must not " + "exceed assets maximum"; + result = false; + } + + if (beforeVault.assetsAvailable != afterVault.assetsAvailable) + { + JLOG(j.fatal()) << // + "Invariant failed: set must not change assets " + "available"; + result = false; + } + + if (beforeShares && updatedShares && + beforeShares->sharesTotal != updatedShares->sharesTotal) + { + JLOG(j.fatal()) << // + "Invariant failed: set must not change shares " + "outstanding"; + result = false; + } + + return result; + } + case ttVAULT_DEPOSIT: { + bool result = true; + + XRPL_ASSERT( + !beforeVault_.empty(), "xrpl::ValidVault::finalize : deposit updated a vault"); + auto const& beforeVault = beforeVault_[0]; + + auto const vaultDeltaAssets = deltaAssets(afterVault.pseudoId); + + if (!vaultDeltaAssets) + { + JLOG(j.fatal()) << // + "Invariant failed: deposit must change vault balance"; + return false; // That's all we can do + } + + if (*vaultDeltaAssets > tx[sfAmount]) + { + JLOG(j.fatal()) << // + "Invariant failed: deposit must not change vault " + "balance by more than deposited amount"; + result = false; + } + + if (*vaultDeltaAssets <= zero) + { + JLOG(j.fatal()) << // + "Invariant failed: deposit must increase vault balance"; + result = false; + } + + // Any payments (including deposits) made by the issuer + // do not change their balance, but create funds instead. + bool const issuerDeposit = [&]() -> bool { + if (vaultAsset.native()) + return false; + return tx[sfAccount] == vaultAsset.getIssuer(); + }(); + + if (!issuerDeposit) + { + auto const accountDeltaAssets = deltaAssetsTxAccount(); + if (!accountDeltaAssets) + { + JLOG(j.fatal()) << // + "Invariant failed: deposit must change depositor " + "balance"; + return false; + } + + if (*accountDeltaAssets >= zero) + { + JLOG(j.fatal()) << // + "Invariant failed: deposit must decrease depositor " + "balance"; + result = false; + } + + if (*accountDeltaAssets * -1 != *vaultDeltaAssets) + { + JLOG(j.fatal()) << // + "Invariant failed: deposit must change vault and " + "depositor balance by equal amount"; + result = false; + } + } + + if (afterVault.assetsMaximum > zero && + afterVault.assetsTotal > afterVault.assetsMaximum) + { + JLOG(j.fatal()) << // + "Invariant failed: deposit assets outstanding must not " + "exceed assets maximum"; + result = false; + } + + auto const accountDeltaShares = deltaShares(tx[sfAccount]); + if (!accountDeltaShares) + { + JLOG(j.fatal()) << // + "Invariant failed: deposit must change depositor " + "shares"; + return false; // That's all we can do + } + + if (*accountDeltaShares <= zero) + { + JLOG(j.fatal()) << // + "Invariant failed: deposit must increase depositor " + "shares"; + result = false; + } + + auto const vaultDeltaShares = deltaShares(afterVault.pseudoId); + if (!vaultDeltaShares || *vaultDeltaShares == zero) + { + JLOG(j.fatal()) << // + "Invariant failed: deposit must change vault shares"; + return false; // That's all we can do + } + + if (*vaultDeltaShares * -1 != *accountDeltaShares) + { + JLOG(j.fatal()) << // + "Invariant failed: deposit must change depositor and " + "vault shares by equal amount"; + result = false; + } + + if (beforeVault.assetsTotal + *vaultDeltaAssets != afterVault.assetsTotal) + { + JLOG(j.fatal()) << "Invariant failed: deposit and assets " + "outstanding must add up"; + result = false; + } + if (beforeVault.assetsAvailable + *vaultDeltaAssets != afterVault.assetsAvailable) + { + JLOG(j.fatal()) << "Invariant failed: deposit and assets " + "available must add up"; + result = false; + } + + return result; + } + case ttVAULT_WITHDRAW: { + bool result = true; + + XRPL_ASSERT( + !beforeVault_.empty(), + "xrpl::ValidVault::finalize : withdrawal updated a " + "vault"); + auto const& beforeVault = beforeVault_[0]; + + auto const vaultDeltaAssets = deltaAssets(afterVault.pseudoId); + + if (!vaultDeltaAssets) + { + JLOG(j.fatal()) << "Invariant failed: withdrawal must " + "change vault balance"; + return false; // That's all we can do + } + + if (*vaultDeltaAssets >= zero) + { + JLOG(j.fatal()) << "Invariant failed: withdrawal must " + "decrease vault balance"; + result = false; + } + + // Any payments (including withdrawal) going to the issuer + // do not change their balance, but destroy funds instead. + bool const issuerWithdrawal = [&]() -> bool { + if (vaultAsset.native()) + return false; + auto const destination = tx[~sfDestination].value_or(tx[sfAccount]); + return destination == vaultAsset.getIssuer(); + }(); + + if (!issuerWithdrawal) + { + auto const accountDeltaAssets = deltaAssetsTxAccount(); + auto const otherAccountDelta = [&]() -> std::optional { + if (auto const destination = tx[~sfDestination]; + destination && *destination != tx[sfAccount]) + return deltaAssets(*destination); + return std::nullopt; + }(); + + if (accountDeltaAssets.has_value() == otherAccountDelta.has_value()) + { + JLOG(j.fatal()) << // + "Invariant failed: withdrawal must change one " + "destination balance"; + return false; + } + + auto const destinationDelta = // + accountDeltaAssets ? *accountDeltaAssets : *otherAccountDelta; + + if (destinationDelta <= zero) + { + JLOG(j.fatal()) << // + "Invariant failed: withdrawal must increase " + "destination balance"; + result = false; + } + + if (*vaultDeltaAssets * -1 != destinationDelta) + { + JLOG(j.fatal()) << // + "Invariant failed: withdrawal must change vault " + "and destination balance by equal amount"; + result = false; + } + } + + auto const accountDeltaShares = deltaShares(tx[sfAccount]); + if (!accountDeltaShares) + { + JLOG(j.fatal()) << // + "Invariant failed: withdrawal must change depositor " + "shares"; + return false; + } + + if (*accountDeltaShares >= zero) + { + JLOG(j.fatal()) << // + "Invariant failed: withdrawal must decrease depositor " + "shares"; + result = false; + } + + auto const vaultDeltaShares = deltaShares(afterVault.pseudoId); + if (!vaultDeltaShares || *vaultDeltaShares == zero) + { + JLOG(j.fatal()) << // + "Invariant failed: withdrawal must change vault shares"; + return false; // That's all we can do + } + + if (*vaultDeltaShares * -1 != *accountDeltaShares) + { + JLOG(j.fatal()) << // + "Invariant failed: withdrawal must change depositor " + "and vault shares by equal amount"; + result = false; + } + + // Note, vaultBalance is negative (see check above) + if (beforeVault.assetsTotal + *vaultDeltaAssets != afterVault.assetsTotal) + { + JLOG(j.fatal()) << "Invariant failed: withdrawal and " + "assets outstanding must add up"; + result = false; + } + + if (beforeVault.assetsAvailable + *vaultDeltaAssets != afterVault.assetsAvailable) + { + JLOG(j.fatal()) << "Invariant failed: withdrawal and " + "assets available must add up"; + result = false; + } + + return result; + } + case ttVAULT_CLAWBACK: { + bool result = true; + + XRPL_ASSERT( + !beforeVault_.empty(), "xrpl::ValidVault::finalize : clawback updated a vault"); + auto const& beforeVault = beforeVault_[0]; + + if (vaultAsset.native() || vaultAsset.getIssuer() != tx[sfAccount]) + { + // The owner can use clawback to force-burn shares when the + // vault is empty but there are outstanding shares + if (!(beforeShares && beforeShares->sharesTotal > 0 && + vaultHoldsNoAssets(beforeVault) && beforeVault.owner == tx[sfAccount])) + { + JLOG(j.fatal()) << // + "Invariant failed: clawback may only be performed " + "by the asset issuer, or by the vault owner of an " + "empty vault"; + return false; // That's all we can do + } + } + + auto const vaultDeltaAssets = deltaAssets(afterVault.pseudoId); + if (vaultDeltaAssets) + { + if (*vaultDeltaAssets >= zero) + { + JLOG(j.fatal()) << // + "Invariant failed: clawback must decrease vault " + "balance"; + result = false; + } + + if (beforeVault.assetsTotal + *vaultDeltaAssets != afterVault.assetsTotal) + { + JLOG(j.fatal()) << // + "Invariant failed: clawback and assets outstanding " + "must add up"; + result = false; + } + + if (beforeVault.assetsAvailable + *vaultDeltaAssets != + afterVault.assetsAvailable) + { + JLOG(j.fatal()) << // + "Invariant failed: clawback and assets available " + "must add up"; + result = false; + } + } + else if (!vaultHoldsNoAssets(beforeVault)) + { + JLOG(j.fatal()) << // + "Invariant failed: clawback must change vault balance"; + return false; // That's all we can do + } + + auto const accountDeltaShares = deltaShares(tx[sfHolder]); + if (!accountDeltaShares) + { + JLOG(j.fatal()) << // + "Invariant failed: clawback must change holder shares"; + return false; // That's all we can do + } + + if (*accountDeltaShares >= zero) + { + JLOG(j.fatal()) << // + "Invariant failed: clawback must decrease holder " + "shares"; + result = false; + } + + auto const vaultDeltaShares = deltaShares(afterVault.pseudoId); + if (!vaultDeltaShares || *vaultDeltaShares == zero) + { + JLOG(j.fatal()) << // + "Invariant failed: clawback must change vault shares"; + return false; // That's all we can do + } + + if (*vaultDeltaShares * -1 != *accountDeltaShares) + { + JLOG(j.fatal()) << // + "Invariant failed: clawback must change holder and " + "vault shares by equal amount"; + result = false; + } + + return result; + } + + case ttLOAN_SET: + case ttLOAN_MANAGE: + case ttLOAN_PAY: { + // TBD + return true; + } + + default: + // LCOV_EXCL_START + UNREACHABLE("xrpl::ValidVault::finalize : unknown transaction type"); + return false; + // LCOV_EXCL_STOP + } + }(); + + if (!result) + { + // The comment at the top of this file starting with "assert(enforce)" + // explains this assert. + XRPL_ASSERT(enforce, "xrpl::ValidVault::finalize : vault invariants"); + return !enforce; + } + + return true; +} + +} // namespace xrpl diff --git a/src/libxrpl/tx/transactors/PermissionedDomain/PermissionedDomainSet.cpp b/src/libxrpl/tx/transactors/PermissionedDomain/PermissionedDomainSet.cpp index 51ff4d8217..15bb79b239 100644 --- a/src/libxrpl/tx/transactors/PermissionedDomain/PermissionedDomainSet.cpp +++ b/src/libxrpl/tx/transactors/PermissionedDomain/PermissionedDomainSet.cpp @@ -1,10 +1,9 @@ +#include +// #include #include #include #include -#include - -#include namespace xrpl { From afc660a1b5764536cf40350714ee5849ac60a067 Mon Sep 17 00:00:00 2001 From: Alex Kremer Date: Mon, 2 Mar 2026 17:08:56 +0000 Subject: [PATCH 19/41] refactor: Fix clang-tidy `bugprone-empty-catch` check (#6419) This change fixes or suppresses instances detected by the `bugprone-empty-catch` clang-tidy check. --- .clang-tidy | 2 +- cspell.config.yaml | 2 ++ src/libxrpl/beast/insight/StatsDCollector.cpp | 2 +- src/libxrpl/nodestore/backend/NuDBFactory.cpp | 2 +- src/libxrpl/protocol/STAmount.cpp | 8 ++++---- src/libxrpl/protocol/STTx.cpp | 4 ++-- src/libxrpl/tx/transactors/XChainBridge.cpp | 2 +- src/test/app/Manifest_test.cpp | 4 ++-- src/test/core/SociDB_test.cpp | 4 ++-- src/test/jtx/impl/Env.cpp | 8 ++++---- src/test/jtx/impl/Oracle.cpp | 2 +- src/test/jtx/impl/WSClient.cpp | 1 + src/tests/libxrpl/basics/scope.cpp | 12 ++++++------ src/xrpld/app/ledger/detail/InboundLedgers.cpp | 2 +- src/xrpld/app/ledger/detail/LedgerMaster.cpp | 2 +- src/xrpld/app/ledger/detail/SkipListAcquire.cpp | 2 +- src/xrpld/app/main/GRPCServer.cpp | 2 +- src/xrpld/app/misc/detail/ValidatorSite.cpp | 4 ++-- src/xrpld/overlay/detail/ConnectAttempt.cpp | 2 +- src/xrpld/rpc/detail/RPCCall.cpp | 2 +- 20 files changed, 36 insertions(+), 33 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index 5f4187b008..5971b5dd14 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -10,6 +10,7 @@ Checks: "-*, bugprone-copy-constructor-init, bugprone-dangling-handle, bugprone-dynamic-static-initializers, + bugprone-empty-catch, bugprone-fold-init-type, bugprone-forward-declaration-namespace, bugprone-inaccurate-erase, @@ -83,7 +84,6 @@ Checks: "-*, # --- # checks that have some issues that need to be resolved: # -# bugprone-empty-catch, # bugprone-crtp-constructor-accessibility, # bugprone-inc-dec-in-conditions, # bugprone-reserved-identifier, diff --git a/cspell.config.yaml b/cspell.config.yaml index e2b20ac098..98b6be81e7 100644 --- a/cspell.config.yaml +++ b/cspell.config.yaml @@ -176,6 +176,8 @@ words: - nixfmt - nixos - nixpkgs + - NOLINT + - NOLINTNEXTLINE - nonxrp - noripple - nudb diff --git a/src/libxrpl/beast/insight/StatsDCollector.cpp b/src/libxrpl/beast/insight/StatsDCollector.cpp index 8462a00b3d..143bc51bd8 100644 --- a/src/libxrpl/beast/insight/StatsDCollector.cpp +++ b/src/libxrpl/beast/insight/StatsDCollector.cpp @@ -249,7 +249,7 @@ public: { m_timer.cancel(); } - catch (boost::system::system_error const&) + catch (boost::system::system_error const&) // NOLINT(bugprone-empty-catch) { // ignored } diff --git a/src/libxrpl/nodestore/backend/NuDBFactory.cpp b/src/libxrpl/nodestore/backend/NuDBFactory.cpp index 4d7e7be668..c79938bcf8 100644 --- a/src/libxrpl/nodestore/backend/NuDBFactory.cpp +++ b/src/libxrpl/nodestore/backend/NuDBFactory.cpp @@ -83,7 +83,7 @@ public: // close can throw and we don't want the destructor to throw. close(); } - catch (nudb::system_error const&) + catch (nudb::system_error const&) // NOLINT(bugprone-empty-catch) { // Don't allow exceptions to propagate out of destructors. // close() has already logged the error. diff --git a/src/libxrpl/protocol/STAmount.cpp b/src/libxrpl/protocol/STAmount.cpp index 650cc4369d..9503da57a2 100644 --- a/src/libxrpl/protocol/STAmount.cpp +++ b/src/libxrpl/protocol/STAmount.cpp @@ -443,6 +443,7 @@ getRate(STAmount const& offerOut, STAmount const& offerIn) { if (offerOut == beast::zero) return 0; + try { STAmount r = divide(offerIn, offerOut, noIssue()); @@ -454,12 +455,11 @@ getRate(STAmount const& offerOut, STAmount const& offerIn) std::uint64_t ret = r.exponent() + 100; return (ret << (64 - 8)) | r.mantissa(); } - catch (std::exception const&) + catch (...) { + // overflow -- very bad offer + return 0; } - - // overflow -- very bad offer - return 0; } /** diff --git a/src/libxrpl/protocol/STTx.cpp b/src/libxrpl/protocol/STTx.cpp index 0c5e299702..098ca1a400 100644 --- a/src/libxrpl/protocol/STTx.cpp +++ b/src/libxrpl/protocol/STTx.cpp @@ -246,10 +246,10 @@ STTx::checkSign(Rules const& rules, STObject const& sigObject) const return signingPubKey.empty() ? checkMultiSign(rules, sigObject) : checkSingleSign(sigObject); } - catch (std::exception const&) + catch (...) { + return Unexpected("Internal signature check failure."); } - return Unexpected("Internal signature check failure."); } Expected diff --git a/src/libxrpl/tx/transactors/XChainBridge.cpp b/src/libxrpl/tx/transactors/XChainBridge.cpp index 30fc9f59e1..64daa6d1ee 100644 --- a/src/libxrpl/tx/transactors/XChainBridge.cpp +++ b/src/libxrpl/tx/transactors/XChainBridge.cpp @@ -1126,8 +1126,8 @@ toClaim(STTx const& tx) } catch (...) { + return std::nullopt; } - return std::nullopt; } template diff --git a/src/test/app/Manifest_test.cpp b/src/test/app/Manifest_test.cpp index a790584ac2..294d5210d9 100644 --- a/src/test/app/Manifest_test.cpp +++ b/src/test/app/Manifest_test.cpp @@ -71,7 +71,7 @@ public: { setupDatabaseDir(getDatabasePath()); } - catch (std::exception const&) + catch (std::exception const&) // NOLINT(bugprone-empty-catch) { } } @@ -81,7 +81,7 @@ public: { cleanupDatabaseDir(getDatabasePath()); } - catch (std::exception const&) + catch (std::exception const&) // NOLINT(bugprone-empty-catch) { } } diff --git a/src/test/core/SociDB_test.cpp b/src/test/core/SociDB_test.cpp index a06193ae86..66b368176d 100644 --- a/src/test/core/SociDB_test.cpp +++ b/src/test/core/SociDB_test.cpp @@ -58,7 +58,7 @@ public: { setupDatabaseDir(getDatabasePath()); } - catch (std::exception const&) + catch (std::exception const&) // NOLINT(bugprone-empty-catch) { } } @@ -68,7 +68,7 @@ public: { cleanupDatabaseDir(getDatabasePath()); } - catch (std::exception const&) + catch (std::exception const&) // NOLINT(bugprone-empty-catch) { } } diff --git a/src/test/jtx/impl/Env.cpp b/src/test/jtx/impl/Env.cpp index df86aaa2e4..4dfd2f2b38 100644 --- a/src/test/jtx/impl/Env.cpp +++ b/src/test/jtx/impl/Env.cpp @@ -587,10 +587,10 @@ Env::st(JTx const& jt) { return sterilize(STTx{std::move(*obj)}); } - catch (std::exception const&) + catch (...) { + return nullptr; } - return nullptr; } std::shared_ptr @@ -613,10 +613,10 @@ Env::ust(JTx const& jt) { return std::make_shared(std::move(*obj)); } - catch (std::exception const&) + catch (...) { + return nullptr; } - return nullptr; } Json::Value diff --git a/src/test/jtx/impl/Oracle.cpp b/src/test/jtx/impl/Oracle.cpp index c9d8c0ce27..302880c972 100644 --- a/src/test/jtx/impl/Oracle.cpp +++ b/src/test/jtx/impl/Oracle.cpp @@ -339,8 +339,8 @@ validDocumentID(AnyValue const& v) } catch (...) { + return false; } - return false; } } // namespace oracle diff --git a/src/test/jtx/impl/WSClient.cpp b/src/test/jtx/impl/WSClient.cpp index 2b92eb5ec3..84424be222 100644 --- a/src/test/jtx/impl/WSClient.cpp +++ b/src/test/jtx/impl/WSClient.cpp @@ -107,6 +107,7 @@ class WSClientImpl : public WSClient { stream_.cancel(); } + // NOLINTNEXTLINE(bugprone-empty-catch) catch (boost::system::system_error const&) { // ignored diff --git a/src/tests/libxrpl/basics/scope.cpp b/src/tests/libxrpl/basics/scope.cpp index 309a41ec04..8efa4a84b1 100644 --- a/src/tests/libxrpl/basics/scope.cpp +++ b/src/tests/libxrpl/basics/scope.cpp @@ -35,7 +35,7 @@ TEST(scope, scope_exit) scope_exit x{[&i]() { i = 5; }}; throw 1; } - catch (...) + catch (...) // NOLINT(bugprone-empty-catch) { } } @@ -47,7 +47,7 @@ TEST(scope, scope_exit) x.release(); throw 1; } - catch (...) + catch (...) // NOLINT(bugprone-empty-catch) { } } @@ -85,7 +85,7 @@ TEST(scope, scope_fail) scope_fail x{[&i]() { i = 5; }}; throw 1; } - catch (...) + catch (...) // NOLINT(bugprone-empty-catch) { } } @@ -97,7 +97,7 @@ TEST(scope, scope_fail) x.release(); throw 1; } - catch (...) + catch (...) // NOLINT(bugprone-empty-catch) { } } @@ -135,7 +135,7 @@ TEST(scope, scope_success) scope_success x{[&i]() { i = 5; }}; throw 1; } - catch (...) + catch (...) // NOLINT(bugprone-empty-catch) { } } @@ -147,7 +147,7 @@ TEST(scope, scope_success) x.release(); throw 1; } - catch (...) + catch (...) // NOLINT(bugprone-empty-catch) { } } diff --git a/src/xrpld/app/ledger/detail/InboundLedgers.cpp b/src/xrpld/app/ledger/detail/InboundLedgers.cpp index a8ae530bde..e17437d64f 100644 --- a/src/xrpld/app/ledger/detail/InboundLedgers.cpp +++ b/src/xrpld/app/ledger/detail/InboundLedgers.cpp @@ -241,7 +241,7 @@ public: newNode->getHash().as_uint256(), std::make_shared(s.begin(), s.end())); } } - catch (std::exception const&) + catch (std::exception const&) // NOLINT(bugprone-empty-catch) { } } diff --git a/src/xrpld/app/ledger/detail/LedgerMaster.cpp b/src/xrpld/app/ledger/detail/LedgerMaster.cpp index 8072b619e1..64bdf04df1 100644 --- a/src/xrpld/app/ledger/detail/LedgerMaster.cpp +++ b/src/xrpld/app/ledger/detail/LedgerMaster.cpp @@ -1637,7 +1637,7 @@ LedgerMaster::getLedgerBySeq(std::uint32_t index) if (hash) return mLedgerHistory.getLedgerByHash(*hash); } - catch (std::exception const&) + catch (std::exception const&) // NOLINT(bugprone-empty-catch) { // Missing nodes are already handled } diff --git a/src/xrpld/app/ledger/detail/SkipListAcquire.cpp b/src/xrpld/app/ledger/detail/SkipListAcquire.cpp index 0fb1239c49..2191ef965a 100644 --- a/src/xrpld/app/ledger/detail/SkipListAcquire.cpp +++ b/src/xrpld/app/ledger/detail/SkipListAcquire.cpp @@ -127,7 +127,7 @@ SkipListAcquire::processData( return; } } - catch (...) + catch (...) // NOLINT(bugprone-empty-catch) { } diff --git a/src/xrpld/app/main/GRPCServer.cpp b/src/xrpld/app/main/GRPCServer.cpp index ced252cb71..c6b5c91e14 100644 --- a/src/xrpld/app/main/GRPCServer.cpp +++ b/src/xrpld/app/main/GRPCServer.cpp @@ -29,7 +29,7 @@ getEndpoint(std::string const& peer) if (endpoint) return beast::IP::to_asio_endpoint(endpoint.value()); } - catch (std::exception const&) + catch (std::exception const&) // NOLINT(bugprone-empty-catch) { } return {}; diff --git a/src/xrpld/app/misc/detail/ValidatorSite.cpp b/src/xrpld/app/misc/detail/ValidatorSite.cpp index fb68bf5ef4..c4077a1b8b 100644 --- a/src/xrpld/app/misc/detail/ValidatorSite.cpp +++ b/src/xrpld/app/misc/detail/ValidatorSite.cpp @@ -177,7 +177,7 @@ ValidatorSite::stop() { timer_.cancel(); } - catch (boost::system::system_error const&) + catch (boost::system::system_error const&) // NOLINT(bugprone-empty-catch) { } stopping_ = false; @@ -222,7 +222,7 @@ ValidatorSite::makeRequest( { timer_.cancel_one(); } - catch (boost::system::system_error const&) + catch (boost::system::system_error const&) // NOLINT(bugprone-empty-catch) { } }; diff --git a/src/xrpld/overlay/detail/ConnectAttempt.cpp b/src/xrpld/overlay/detail/ConnectAttempt.cpp index c9361a2a5d..ac0743e936 100644 --- a/src/xrpld/overlay/detail/ConnectAttempt.cpp +++ b/src/xrpld/overlay/detail/ConnectAttempt.cpp @@ -252,7 +252,7 @@ ConnectAttempt::cancelTimer() timer_.cancel(); stepTimer_.cancel(); } - catch (boost::system::system_error const&) + catch (boost::system::system_error const&) // NOLINT(bugprone-empty-catch) { // ignored } diff --git a/src/xrpld/rpc/detail/RPCCall.cpp b/src/xrpld/rpc/detail/RPCCall.cpp index 7b65daa839..134cbb34f8 100644 --- a/src/xrpld/rpc/detail/RPCCall.cpp +++ b/src/xrpld/rpc/detail/RPCCall.cpp @@ -1479,7 +1479,7 @@ rpcClient( setup = setup_ServerHandler( config, beast::logstream{logs.journal("HTTPClient").warn()}); } - catch (std::exception const&) + catch (std::exception const&) // NOLINT(bugprone-empty-catch) { // ignore any exceptions, so the command // line client works without a config file From 5300e656864e5cbdd94ab0615d5aacd1e705f202 Mon Sep 17 00:00:00 2001 From: Sergey Kuznetsov Date: Tue, 3 Mar 2026 13:46:55 +0000 Subject: [PATCH 20/41] tests: Improve stability of Subscribe tests (#6420) The `Subscribe` tests were flaky, because each test performs some operations (e.g. sends transactions) and waits for messages to appear in subscription with a 100ms timeout. If tests are slow (e.g. compiled in debug mode or a slow machine) then some of them could fail. This change adds an attempt to synchronize the background Env's thread and the test's thread by ensuring that all the scheduled operations are started before the test's thread starts to wait for a websocket message. This is done by limiting I/O threads of the app inside Env to 1 and adding a synchronization barrier after closing the ledger. --- src/test/jtx/Env.h | 43 +++++++++++ src/test/jtx/envconfig.h | 2 + src/test/jtx/impl/envconfig.cpp | 6 ++ src/test/rpc/Subscribe_test.cpp | 113 +++++++++++++++-------------- src/xrpld/app/main/Application.cpp | 6 ++ src/xrpld/app/main/Application.h | 4 + src/xrpld/app/main/BasicApp.h | 6 ++ 7 files changed, 127 insertions(+), 53 deletions(-) diff --git a/src/test/jtx/Env.h b/src/test/jtx/Env.h index 9caf257aa1..2ac0ca7435 100644 --- a/src/test/jtx/Env.h +++ b/src/test/jtx/Env.h @@ -31,6 +31,7 @@ #include #include +#include #include #include #include @@ -393,6 +394,48 @@ public: return close(std::chrono::seconds(5)); } + /** Close and advance the ledger, then synchronize with the server's + io_context to ensure all async operations initiated by the close have + been started. + + This function performs the same ledger close as close(), but additionally + ensures that all tasks posted to the server's io_context (such as + WebSocket subscription message sends) have been initiated before returning. + + What it guarantees: + - All async operations posted before syncClose() have been STARTED + - For WebSocket sends: async_write_some() has been called + - The actual I/O completion may still be pending (async) + + What it does NOT guarantee: + - Async operations have COMPLETED + - WebSocket messages have been received by clients + - However, for localhost connections, the remaining latency is typically + microseconds, making tests reliable + + Use this instead of close() when: + - Test code immediately checks for subscription messages + - Race conditions between test and worker threads must be avoided + - Deterministic test behavior is required + + @param timeout Maximum time to wait for the barrier task to execute + @return true if close succeeded and barrier executed within timeout, + false otherwise + */ + [[nodiscard]] bool + syncClose(std::chrono::steady_clock::duration timeout = std::chrono::seconds{1}) + { + XRPL_ASSERT( + app().getNumberOfThreads() == 1, + "syncClose() is only useful on an application with a single thread"); + auto const result = close(); + auto serverBarrier = std::make_shared>(); + auto future = serverBarrier->get_future(); + boost::asio::post(app().getIOContext(), [serverBarrier]() { serverBarrier->set_value(); }); + auto const status = future.wait_for(timeout); + return result && status == std::future_status::ready; + } + /** Turn on JSON tracing. With no arguments, trace all */ diff --git a/src/test/jtx/envconfig.h b/src/test/jtx/envconfig.h index f2f67f935b..e4a1975e74 100644 --- a/src/test/jtx/envconfig.h +++ b/src/test/jtx/envconfig.h @@ -73,6 +73,8 @@ std::unique_ptr admin_localnet(std::unique_ptr); std::unique_ptr secure_gateway_localnet(std::unique_ptr); +std::unique_ptr single_thread_io(std::unique_ptr); + /// @brief adjust configuration with params needed to be a validator /// /// this is intended for use with envconfig, as in diff --git a/src/test/jtx/impl/envconfig.cpp b/src/test/jtx/impl/envconfig.cpp index 31034f3b63..e31e687c3d 100644 --- a/src/test/jtx/impl/envconfig.cpp +++ b/src/test/jtx/impl/envconfig.cpp @@ -87,6 +87,12 @@ secure_gateway_localnet(std::unique_ptr cfg) (*cfg)[PORT_WS].set("secure_gateway", "127.0.0.0/8"); return cfg; } +std::unique_ptr +single_thread_io(std::unique_ptr cfg) +{ + cfg->IO_WORKERS = 1; + return cfg; +} auto constexpr defaultseed = "shUwVw52ofnCUX5m7kPTKzJdr4HEH"; diff --git a/src/test/rpc/Subscribe_test.cpp b/src/test/rpc/Subscribe_test.cpp index d83711324d..414bceefd7 100644 --- a/src/test/rpc/Subscribe_test.cpp +++ b/src/test/rpc/Subscribe_test.cpp @@ -26,7 +26,7 @@ public: { using namespace std::chrono_literals; using namespace jtx; - Env env(*this); + Env env{*this, single_thread_io(envconfig())}; auto wsc = makeWSClient(env.app().config()); Json::Value stream; @@ -92,7 +92,7 @@ public: { using namespace std::chrono_literals; using namespace jtx; - Env env(*this); + Env env{*this, single_thread_io(envconfig())}; auto wsc = makeWSClient(env.app().config()); Json::Value stream; @@ -114,7 +114,7 @@ public: { // Accept a ledger - env.close(); + BEAST_EXPECT(env.syncClose()); // Check stream update BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) { @@ -125,7 +125,7 @@ public: { // Accept another ledger - env.close(); + BEAST_EXPECT(env.syncClose()); // Check stream update BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) { @@ -150,7 +150,7 @@ public: { using namespace std::chrono_literals; using namespace jtx; - Env env(*this); + Env env(*this, single_thread_io(envconfig())); auto baseFee = env.current()->fees().base.drops(); auto wsc = makeWSClient(env.app().config()); Json::Value stream; @@ -171,7 +171,7 @@ public: { env.fund(XRP(10000), "alice"); - env.close(); + BEAST_EXPECT(env.syncClose()); // Check stream update for payment transaction BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) { @@ -195,7 +195,7 @@ public: })); env.fund(XRP(10000), "bob"); - env.close(); + BEAST_EXPECT(env.syncClose()); // Check stream update for payment transaction BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) { @@ -249,12 +249,12 @@ public: { // Transaction that does not affect stream env.fund(XRP(10000), "carol"); - env.close(); + BEAST_EXPECT(env.syncClose()); BEAST_EXPECT(!wsc->getMsg(10ms)); // Transactions concerning alice env.trust(Account("bob")["USD"](100), "alice"); - env.close(); + BEAST_EXPECT(env.syncClose()); // Check stream updates BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) { @@ -288,6 +288,7 @@ public: using namespace jtx; Env env(*this, envconfig([](std::unique_ptr cfg) { cfg->FEES.reference_fee = 10; + cfg = single_thread_io(std::move(cfg)); return cfg; })); auto wsc = makeWSClient(env.app().config()); @@ -310,7 +311,7 @@ public: { env.fund(XRP(10000), "alice"); - env.close(); + BEAST_EXPECT(env.syncClose()); // Check stream update for payment transaction BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) { @@ -360,7 +361,7 @@ public: testManifests() { using namespace jtx; - Env env(*this); + Env env(*this, single_thread_io(envconfig())); auto wsc = makeWSClient(env.app().config()); Json::Value stream; @@ -394,7 +395,7 @@ public: { using namespace jtx; - Env env{*this, envconfig(validator, ""), features}; + Env env{*this, single_thread_io(envconfig(validator, "")), features}; auto& cfg = env.app().config(); if (!BEAST_EXPECT(cfg.section(SECTION_VALIDATION_SEED).empty())) return; @@ -483,7 +484,7 @@ public: // at least one flag ledger. while (env.closed()->header().seq < 300) { - env.close(); + BEAST_EXPECT(env.syncClose()); using namespace std::chrono_literals; BEAST_EXPECT(wsc->findMsg(5s, validValidationFields)); } @@ -505,7 +506,7 @@ public: { using namespace jtx; testcase("Subscribe by url"); - Env env{*this}; + Env env{*this, single_thread_io(envconfig())}; Json::Value jv; jv[jss::url] = "http://localhost/events"; @@ -536,7 +537,7 @@ public: auto const method = subscribe ? "subscribe" : "unsubscribe"; testcase << "Error cases for " << method; - Env env{*this}; + Env env{*this, single_thread_io(envconfig())}; auto wsc = makeWSClient(env.app().config()); { @@ -572,7 +573,7 @@ public: } { - Env env_nonadmin{*this, no_admin(envconfig())}; + Env env_nonadmin{*this, single_thread_io(no_admin(envconfig()))}; Json::Value jv; jv[jss::url] = "no-url"; auto jr = env_nonadmin.rpc("json", method, to_string(jv))[jss::result]; @@ -834,12 +835,13 @@ public: * send payments between the two accounts a and b, * and close ledgersToClose ledgers */ - auto sendPayments = [](Env& env, - Account const& a, - Account const& b, - int newTxns, - std::uint32_t ledgersToClose, - int numXRP = 10) { + auto sendPayments = [this]( + Env& env, + Account const& a, + Account const& b, + int newTxns, + std::uint32_t ledgersToClose, + int numXRP = 10) { env.memoize(a); env.memoize(b); for (int i = 0; i < newTxns; ++i) @@ -852,7 +854,7 @@ public: jtx::sig(jtx::autofill)); } for (int i = 0; i < ledgersToClose; ++i) - env.close(); + BEAST_EXPECT(env.syncClose()); return newTxns; }; @@ -945,7 +947,7 @@ public: * * also test subscribe to the account before it is created */ - Env env(*this); + Env env(*this, single_thread_io(envconfig())); auto wscTxHistory = makeWSClient(env.app().config()); Json::Value request; request[jss::account_history_tx_stream] = Json::objectValue; @@ -988,7 +990,7 @@ public: * subscribe genesis account tx history without txns * subscribe to bob's account after it is created */ - Env env(*this); + Env env(*this, single_thread_io(envconfig())); auto wscTxHistory = makeWSClient(env.app().config()); Json::Value request; request[jss::account_history_tx_stream] = Json::objectValue; @@ -998,6 +1000,7 @@ public: if (!BEAST_EXPECT(goodSubRPC(jv))) return; IdxHashVec genesisFullHistoryVec; + BEAST_EXPECT(env.syncClose()); if (!BEAST_EXPECT(!getTxHash(*wscTxHistory, genesisFullHistoryVec, 1).first)) return; @@ -1016,6 +1019,7 @@ public: if (!BEAST_EXPECT(goodSubRPC(jv))) return; IdxHashVec bobFullHistoryVec; + BEAST_EXPECT(env.syncClose()); r = getTxHash(*wscTxHistory, bobFullHistoryVec, 1); if (!BEAST_EXPECT(r.first && r.second)) return; @@ -1050,6 +1054,7 @@ public: "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh"; jv = wscTxHistory->invoke("subscribe", request); genesisFullHistoryVec.clear(); + BEAST_EXPECT(env.syncClose()); BEAST_EXPECT(getTxHash(*wscTxHistory, genesisFullHistoryVec, 31).second); jv = wscTxHistory->invoke("unsubscribe", request); @@ -1062,13 +1067,13 @@ public: * subscribe account and subscribe account tx history * and compare txns streamed */ - Env env(*this); + Env env(*this, single_thread_io(envconfig())); auto wscAccount = makeWSClient(env.app().config()); auto wscTxHistory = makeWSClient(env.app().config()); std::array accounts = {alice, bob}; env.fund(XRP(222222), accounts); - env.close(); + BEAST_EXPECT(env.syncClose()); // subscribe account Json::Value stream = Json::objectValue; @@ -1131,18 +1136,18 @@ public: * alice issues USD to carol * mix USD and XRP payments */ - Env env(*this); + Env env(*this, single_thread_io(envconfig())); auto const USD_a = alice["USD"]; std::array accounts = {alice, carol}; env.fund(XRP(333333), accounts); env.trust(USD_a(20000), carol); - env.close(); + BEAST_EXPECT(env.syncClose()); auto mixedPayments = [&]() -> int { sendPayments(env, alice, carol, 1, 0); env(pay(alice, carol, USD_a(100))); - env.close(); + BEAST_EXPECT(env.syncClose()); return 2; }; @@ -1152,6 +1157,7 @@ public: request[jss::account_history_tx_stream][jss::account] = carol.human(); auto ws = makeWSClient(env.app().config()); auto jv = ws->invoke("subscribe", request); + BEAST_EXPECT(env.syncClose()); { // take out existing txns from the stream IdxHashVec tempVec; @@ -1169,10 +1175,10 @@ public: /* * long transaction history */ - Env env(*this); + Env env(*this, single_thread_io(envconfig())); std::array accounts = {alice, carol}; env.fund(XRP(444444), accounts); - env.close(); + BEAST_EXPECT(env.syncClose()); // many payments, and close lots of ledgers auto oneRound = [&](int numPayments) { @@ -1185,6 +1191,7 @@ public: request[jss::account_history_tx_stream][jss::account] = carol.human(); auto wscLong = makeWSClient(env.app().config()); auto jv = wscLong->invoke("subscribe", request); + BEAST_EXPECT(env.syncClose()); { // take out existing txns from the stream IdxHashVec tempVec; @@ -1222,7 +1229,7 @@ public: jtx::testable_amendments() | featurePermissionedDomains | featureCredentials | featurePermissionedDEX}; - Env env(*this, all); + Env env(*this, single_thread_io(envconfig()), all); PermissionedDEX permDex(env); auto const alice = permDex.alice; auto const bob = permDex.bob; @@ -1241,10 +1248,10 @@ public: if (!BEAST_EXPECT(jv[jss::status] == "success")) return; env(offer(alice, XRP(10), USD(10)), domain(domainID), txflags(tfHybrid)); - env.close(); + BEAST_EXPECT(env.syncClose()); env(pay(bob, carol, USD(5)), path(~USD), sendmax(XRP(5)), domain(domainID)); - env.close(); + BEAST_EXPECT(env.syncClose()); BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) { if (jv[jss::changes].size() != 1) @@ -1284,9 +1291,9 @@ public: Account const bob{"bob"}; Account const broker{"broker"}; - Env env{*this, features}; + Env env{*this, single_thread_io(envconfig()), features}; env.fund(XRP(10000), alice, bob, broker); - env.close(); + BEAST_EXPECT(env.syncClose()); auto wsc = test::makeWSClient(env.app().config()); Json::Value stream; @@ -1350,12 +1357,12 @@ public: // Verify the NFTokenIDs are correct in the NFTokenMint tx meta uint256 const nftId1{token::getNextID(env, alice, 0u, tfTransferable)}; env(token::mint(alice, 0u), txflags(tfTransferable)); - env.close(); + BEAST_EXPECT(env.syncClose()); verifyNFTokenID(nftId1); uint256 const nftId2{token::getNextID(env, alice, 0u, tfTransferable)}; env(token::mint(alice, 0u), txflags(tfTransferable)); - env.close(); + BEAST_EXPECT(env.syncClose()); verifyNFTokenID(nftId2); // Alice creates one sell offer for each NFT @@ -1363,32 +1370,32 @@ public: // meta uint256 const aliceOfferIndex1 = keylet::nftoffer(alice, env.seq(alice)).key; env(token::createOffer(alice, nftId1, drops(1)), txflags(tfSellNFToken)); - env.close(); + BEAST_EXPECT(env.syncClose()); verifyNFTokenOfferID(aliceOfferIndex1); uint256 const aliceOfferIndex2 = keylet::nftoffer(alice, env.seq(alice)).key; env(token::createOffer(alice, nftId2, drops(1)), txflags(tfSellNFToken)); - env.close(); + BEAST_EXPECT(env.syncClose()); verifyNFTokenOfferID(aliceOfferIndex2); // Alice cancels two offers she created // Verify the NFTokenIDs are correct in the NFTokenCancelOffer tx // meta env(token::cancelOffer(alice, {aliceOfferIndex1, aliceOfferIndex2})); - env.close(); + BEAST_EXPECT(env.syncClose()); verifyNFTokenIDsInCancelOffer({nftId1, nftId2}); // Bobs creates a buy offer for nftId1 // Verify the offer id is correct in the NFTokenCreateOffer tx meta auto const bobBuyOfferIndex = keylet::nftoffer(bob, env.seq(bob)).key; env(token::createOffer(bob, nftId1, drops(1)), token::owner(alice)); - env.close(); + BEAST_EXPECT(env.syncClose()); verifyNFTokenOfferID(bobBuyOfferIndex); // Alice accepts bob's buy offer // Verify the NFTokenID is correct in the NFTokenAcceptOffer tx meta env(token::acceptBuyOffer(alice, bobBuyOfferIndex)); - env.close(); + BEAST_EXPECT(env.syncClose()); verifyNFTokenID(nftId1); } @@ -1397,7 +1404,7 @@ public: // Alice mints a NFT uint256 const nftId{token::getNextID(env, alice, 0u, tfTransferable)}; env(token::mint(alice, 0u), txflags(tfTransferable)); - env.close(); + BEAST_EXPECT(env.syncClose()); verifyNFTokenID(nftId); // Alice creates sell offer and set broker as destination @@ -1405,18 +1412,18 @@ public: env(token::createOffer(alice, nftId, drops(1)), token::destination(broker), txflags(tfSellNFToken)); - env.close(); + BEAST_EXPECT(env.syncClose()); verifyNFTokenOfferID(offerAliceToBroker); // Bob creates buy offer uint256 const offerBobToBroker = keylet::nftoffer(bob, env.seq(bob)).key; env(token::createOffer(bob, nftId, drops(1)), token::owner(alice)); - env.close(); + BEAST_EXPECT(env.syncClose()); verifyNFTokenOfferID(offerBobToBroker); // Check NFTokenID meta for NFTokenAcceptOffer in brokered mode env(token::brokerOffers(broker, offerBobToBroker, offerAliceToBroker)); - env.close(); + BEAST_EXPECT(env.syncClose()); verifyNFTokenID(nftId); } @@ -1426,24 +1433,24 @@ public: // Alice mints a NFT uint256 const nftId{token::getNextID(env, alice, 0u, tfTransferable)}; env(token::mint(alice, 0u), txflags(tfTransferable)); - env.close(); + BEAST_EXPECT(env.syncClose()); verifyNFTokenID(nftId); // Alice creates 2 sell offers for the same NFT uint256 const aliceOfferIndex1 = keylet::nftoffer(alice, env.seq(alice)).key; env(token::createOffer(alice, nftId, drops(1)), txflags(tfSellNFToken)); - env.close(); + BEAST_EXPECT(env.syncClose()); verifyNFTokenOfferID(aliceOfferIndex1); uint256 const aliceOfferIndex2 = keylet::nftoffer(alice, env.seq(alice)).key; env(token::createOffer(alice, nftId, drops(1)), txflags(tfSellNFToken)); - env.close(); + BEAST_EXPECT(env.syncClose()); verifyNFTokenOfferID(aliceOfferIndex2); // Make sure the metadata only has 1 nft id, since both offers are // for the same nft env(token::cancelOffer(alice, {aliceOfferIndex1, aliceOfferIndex2})); - env.close(); + BEAST_EXPECT(env.syncClose()); verifyNFTokenIDsInCancelOffer({nftId}); } @@ -1451,7 +1458,7 @@ public: { uint256 const aliceMintWithOfferIndex1 = keylet::nftoffer(alice, env.seq(alice)).key; env(token::mint(alice), token::amount(XRP(0))); - env.close(); + BEAST_EXPECT(env.syncClose()); verifyNFTokenOfferID(aliceMintWithOfferIndex1); } } diff --git a/src/xrpld/app/main/Application.cpp b/src/xrpld/app/main/Application.cpp index 1162bc497a..3e3d87dcd5 100644 --- a/src/xrpld/app/main/Application.cpp +++ b/src/xrpld/app/main/Application.cpp @@ -1072,6 +1072,12 @@ public: return trapTxID_; } + size_t + getNumberOfThreads() const override + { + return get_number_of_threads(); + } + private: // For a newly-started validator, this is the greatest persisted ledger // and new validations must be greater than this. diff --git a/src/xrpld/app/main/Application.h b/src/xrpld/app/main/Application.h index 433992bcda..0000ae010b 100644 --- a/src/xrpld/app/main/Application.h +++ b/src/xrpld/app/main/Application.h @@ -157,6 +157,10 @@ public: * than the last ledger it persisted. */ virtual LedgerIndex getMaxDisallowedLedger() = 0; + + /** Returns the number of io_context (I/O worker) threads used by the application. */ + virtual size_t + getNumberOfThreads() const = 0; }; std::unique_ptr diff --git a/src/xrpld/app/main/BasicApp.h b/src/xrpld/app/main/BasicApp.h index 278c255af3..19f07d1e5b 100644 --- a/src/xrpld/app/main/BasicApp.h +++ b/src/xrpld/app/main/BasicApp.h @@ -23,4 +23,10 @@ public: { return io_context_; } + + size_t + get_number_of_threads() const + { + return threads_.size(); + } }; From 0abd76278139e72b0f34c3f12d8d8332d23046ac Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Mar 2026 17:17:08 +0000 Subject: [PATCH 21/41] ci: [DEPENDABOT] bump actions/upload-artifact from 6.0.0 to 7.0.0 (#6450) --- .github/workflows/reusable-build-test-config.yml | 2 +- .github/workflows/reusable-clang-tidy-files.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/reusable-build-test-config.yml b/.github/workflows/reusable-build-test-config.yml index dabcc737f8..75fe546b18 100644 --- a/.github/workflows/reusable-build-test-config.yml +++ b/.github/workflows/reusable-build-test-config.yml @@ -177,7 +177,7 @@ jobs: - name: Upload the binary (Linux) if: ${{ github.repository_owner == 'XRPLF' && runner.os == 'Linux' }} - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: xrpld-${{ inputs.config_name }} path: ${{ env.BUILD_DIR }}/xrpld diff --git a/.github/workflows/reusable-clang-tidy-files.yml b/.github/workflows/reusable-clang-tidy-files.yml index d36dea747c..129726ec8f 100644 --- a/.github/workflows/reusable-clang-tidy-files.yml +++ b/.github/workflows/reusable-clang-tidy-files.yml @@ -84,7 +84,7 @@ jobs: - name: Upload clang-tidy output if: steps.run_clang_tidy.outcome != 'success' - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: clang-tidy-results path: clang-tidy-output.txt From fcec31ed20be643d1a113b4d6fd0e50d02605ceb Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Tue, 3 Mar 2026 21:23:22 +0100 Subject: [PATCH 22/41] chore: Update pre-commit hooks (#6460) --- .pre-commit-config.yaml | 8 ++++---- include/xrpl/ledger/detail/RawStateTable.h | 8 ++++---- src/libxrpl/ledger/OpenView.cpp | 8 ++++---- src/libxrpl/protocol/STVar.cpp | 6 +++--- src/libxrpl/tx/transactors/AMM/AMMUtils.cpp | 5 +++-- src/libxrpl/tx/transactors/AMM/AMMVote.cpp | 5 +++-- src/libxrpl/tx/transactors/Lending/LoanSet.cpp | 11 ++++++----- 7 files changed, 27 insertions(+), 24 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c17eb92787..2d0ff63b38 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -20,7 +20,7 @@ repos: args: [--assume-in-merge] - repo: https://github.com/pre-commit/mirrors-clang-format - rev: 75ca4ad908dc4a99f57921f29b7e6c1521e10b26 # frozen: v21.1.8 + rev: cd481d7b0bfb5c7b3090c21846317f9a8262e891 # frozen: v22.1.0 hooks: - id: clang-format args: [--style=file] @@ -33,17 +33,17 @@ repos: additional_dependencies: [PyYAML] - repo: https://github.com/rbubley/mirrors-prettier - rev: 5ba47274f9b181bce26a5150a725577f3c336011 # frozen: v3.6.2 + rev: c2bc67fe8f8f549cc489e00ba8b45aa18ee713b1 # frozen: v3.8.1 hooks: - id: prettier - repo: https://github.com/psf/black-pre-commit-mirror - rev: 831207fd435b47aeffdf6af853097e64322b4d44 # frozen: v25.12.0 + rev: ea488cebbfd88a5f50b8bd95d5c829d0bb76feb8 # frozen: 26.1.0 hooks: - id: black - repo: https://github.com/streetsidesoftware/cspell-cli - rev: 1cfa010f078c354f3ffb8413616280cc28f5ba21 # frozen: v9.4.0 + rev: a42085ade523f591dca134379a595e7859986445 # frozen: v9.7.0 hooks: - id: cspell # Spell check changed files exclude: .config/cspell.config.yaml diff --git a/include/xrpl/ledger/detail/RawStateTable.h b/include/xrpl/ledger/detail/RawStateTable.h index 7a3e2077ff..499b9204c6 100644 --- a/include/xrpl/ledger/detail/RawStateTable.h +++ b/include/xrpl/ledger/detail/RawStateTable.h @@ -23,13 +23,13 @@ public: static constexpr size_t initialBufferSize = kilobytes(256); RawStateTable() - : monotonic_resource_{std::make_unique( - initialBufferSize)} + : monotonic_resource_{ + std::make_unique(initialBufferSize)} , items_{monotonic_resource_.get()} {}; RawStateTable(RawStateTable const& rhs) - : monotonic_resource_{std::make_unique( - initialBufferSize)} + : monotonic_resource_{ + std::make_unique(initialBufferSize)} , items_{rhs.items_, monotonic_resource_.get()} , dropsDestroyed_{rhs.dropsDestroyed_} {}; diff --git a/src/libxrpl/ledger/OpenView.cpp b/src/libxrpl/ledger/OpenView.cpp index d27d755c66..5b94be5da8 100644 --- a/src/libxrpl/ledger/OpenView.cpp +++ b/src/libxrpl/ledger/OpenView.cpp @@ -72,8 +72,8 @@ OpenView::OpenView( ReadView const* base, Rules const& rules, std::shared_ptr hold) - : monotonic_resource_{std::make_unique( - initialBufferSize)} + : monotonic_resource_{ + std::make_unique(initialBufferSize)} , txs_{monotonic_resource_.get()} , rules_(rules) , header_(base->header()) @@ -88,8 +88,8 @@ OpenView::OpenView( } OpenView::OpenView(ReadView const* base, std::shared_ptr hold) - : monotonic_resource_{std::make_unique( - initialBufferSize)} + : monotonic_resource_{ + std::make_unique(initialBufferSize)} , txs_{monotonic_resource_.get()} , rules_(base->rules()) , header_(base->header()) diff --git a/src/libxrpl/protocol/STVar.cpp b/src/libxrpl/protocol/STVar.cpp index 994077da56..6218bb6db6 100644 --- a/src/libxrpl/protocol/STVar.cpp +++ b/src/libxrpl/protocol/STVar.cpp @@ -133,9 +133,9 @@ STVar::constructST(SerializedTypeID id, int depth, Args&&... args) { construct(std::forward(args)...); } - else if constexpr (std::is_same_v< - std::tuple...>, - std::tuple>) + else if constexpr ( + std:: + is_same_v...>, std::tuple>) { construct(std::forward(args)..., depth); } diff --git a/src/libxrpl/tx/transactors/AMM/AMMUtils.cpp b/src/libxrpl/tx/transactors/AMM/AMMUtils.cpp index ac67e861f1..55d71ced4f 100644 --- a/src/libxrpl/tx/transactors/AMM/AMMUtils.cpp +++ b/src/libxrpl/tx/transactors/AMM/AMMUtils.cpp @@ -180,8 +180,9 @@ ammAccountHolds(ReadView const& view, AccountID const& ammAccountID, Issue const if (auto const sle = view.read(keylet::account(ammAccountID))) return (*sle)[sfBalance]; } - else if (auto const sle = view.read(keylet::line(ammAccountID, issue.account, issue.currency)); - sle && !isFrozen(view, ammAccountID, issue.currency, issue.account)) + else if ( + auto const sle = view.read(keylet::line(ammAccountID, issue.account, issue.currency)); + sle && !isFrozen(view, ammAccountID, issue.currency, issue.account)) { auto amount = (*sle)[sfBalance]; if (ammAccountID > issue.account) diff --git a/src/libxrpl/tx/transactors/AMM/AMMVote.cpp b/src/libxrpl/tx/transactors/AMM/AMMVote.cpp index f40015ac08..549d9705b5 100644 --- a/src/libxrpl/tx/transactors/AMM/AMMVote.cpp +++ b/src/libxrpl/tx/transactors/AMM/AMMVote.cpp @@ -42,8 +42,9 @@ AMMVote::preclaim(PreclaimContext const& ctx) } else if (ammSle->getFieldAmount(sfLPTokenBalance) == beast::zero) return tecAMM_EMPTY; - else if (auto const lpTokensNew = ammLPHolds(ctx.view, *ammSle, ctx.tx[sfAccount], ctx.j); - lpTokensNew == beast::zero) + else if ( + auto const lpTokensNew = ammLPHolds(ctx.view, *ammSle, ctx.tx[sfAccount], ctx.j); + lpTokensNew == beast::zero) { JLOG(ctx.j.debug()) << "AMM Vote: account is not LP."; return tecAMM_INVALID_TOKENS; diff --git a/src/libxrpl/tx/transactors/Lending/LoanSet.cpp b/src/libxrpl/tx/transactors/Lending/LoanSet.cpp index 82a64bc89b..5e45bd5a9a 100644 --- a/src/libxrpl/tx/transactors/Lending/LoanSet.cpp +++ b/src/libxrpl/tx/transactors/Lending/LoanSet.cpp @@ -84,11 +84,12 @@ LoanSet::preflight(PreflightContext const& ctx) !validNumericMinimum(paymentInterval, LoanSet::minPaymentInterval)) return temINVALID; // Grace period is between min default value and payment interval - else if (auto const gracePeriod = tx[~sfGracePeriod]; // - !validNumericRange( - gracePeriod, - paymentInterval.value_or(LoanSet::defaultPaymentInterval), - defaultGracePeriod)) + else if ( + auto const gracePeriod = tx[~sfGracePeriod]; // + !validNumericRange( + gracePeriod, + paymentInterval.value_or(LoanSet::defaultPaymentInterval), + defaultGracePeriod)) return temINVALID; // Copied from preflight2 From 3cd1e3d94e3e2498f98352e720dbc9948c1213a4 Mon Sep 17 00:00:00 2001 From: tequ Date: Wed, 4 Mar 2026 12:11:58 +0900 Subject: [PATCH 23/41] refactor: Update PermissionedDomainDelete to use keylet for sle access (#6063) --- .../PermissionedDomain/PermissionedDomainDelete.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libxrpl/tx/transactors/PermissionedDomain/PermissionedDomainDelete.cpp b/src/libxrpl/tx/transactors/PermissionedDomain/PermissionedDomainDelete.cpp index e013bd8d2b..861bb934be 100644 --- a/src/libxrpl/tx/transactors/PermissionedDomain/PermissionedDomainDelete.cpp +++ b/src/libxrpl/tx/transactors/PermissionedDomain/PermissionedDomainDelete.cpp @@ -18,7 +18,7 @@ TER PermissionedDomainDelete::preclaim(PreclaimContext const& ctx) { auto const domain = ctx.tx.getFieldH256(sfDomainID); - auto const sleDomain = ctx.view.read({ltPERMISSIONED_DOMAIN, domain}); + auto const sleDomain = ctx.view.read(keylet::permissionedDomain(domain)); if (!sleDomain) return tecNO_ENTRY; @@ -40,7 +40,7 @@ PermissionedDomainDelete::doApply() ctx_.tx.isFieldPresent(sfDomainID), "xrpl::PermissionedDomainDelete::doApply : required field present"); - auto const slePd = view().peek({ltPERMISSIONED_DOMAIN, ctx_.tx.at(sfDomainID)}); + auto const slePd = view().peek(keylet::permissionedDomain(ctx_.tx.at(sfDomainID))); auto const page = (*slePd)[sfOwnerNode]; if (!view().dirRemove(keylet::ownerDir(account_), page, slePd->key(), true)) From e39954d12809042001e94ce212bd9533cce84fc3 Mon Sep 17 00:00:00 2001 From: Peter Chen <34582813+PeterChen13579@users.noreply.github.com> Date: Wed, 4 Mar 2026 07:50:51 -0800 Subject: [PATCH 24/41] fix: Gateway balance with MPT (#6143) When `gateway_balances` gets called on an account that is involved in the `EscrowCreate` transaction (with MPT being escrowed), the method returns internal error. This change fixes this case by excluding the MPT type when totaling escrow amount. --- src/test/rpc/GatewayBalances_test.cpp | 41 +++++++++++++++++++++- src/xrpld/rpc/handlers/GatewayBalances.cpp | 4 +++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/src/test/rpc/GatewayBalances_test.cpp b/src/test/rpc/GatewayBalances_test.cpp index 6415f6a51f..0deb1fc627 100644 --- a/src/test/rpc/GatewayBalances_test.cpp +++ b/src/test/rpc/GatewayBalances_test.cpp @@ -223,6 +223,45 @@ public: expect(jv[jss::result][jss::obligations]["USD"] == maxUSD.getText()); } + void + testGWBWithMPT() + { + testcase("Gateway Balances with MPT Escrow"); + using namespace std::chrono_literals; + using namespace jtx; + + // Ensure MPT is enabled + FeatureBitset features = testable_amendments() | featureMPTokensV1; + Env env(*this, features); + + Account const alice{"alice"}; + Account const bob{"bob"}; + + env.fund(XRP(10000), alice, bob); + env.close(); + + // Create MPT issuance (Alice) with Escrow capability + MPTTester mpt(env, alice, {.holders = {bob}, .fund = false}); + mpt.create({.flags = tfMPTCanEscrow}); + + // Authorize Bob and fund him + mpt.authorize({.account = bob, .holderCount = 1}); + mpt.pay(alice, bob, 1000); + + // Bob creates an escrow of MPT to Alice. + auto const MPT = mpt["MPT"]; + env(escrow::create(bob, alice, MPT(100)), escrow::finish_time(env.now() + 10s)); + env.close(); + + // Query gateway_balances for Bob. + auto wsc = makeWSClient(env.app().config()); + Json::Value qry; + qry[jss::account] = bob.human(); + + auto jv = wsc->invoke("gateway_balances", qry); + expect(jv[jss::status] == "success"); + } + void run() override { @@ -233,7 +272,7 @@ public: testGWB(feature); testGWBApiVersions(feature); } - + testGWBWithMPT(); testGWBOverflow(); } }; diff --git a/src/xrpld/rpc/handlers/GatewayBalances.cpp b/src/xrpld/rpc/handlers/GatewayBalances.cpp index e5e95d6835..a2176ab388 100644 --- a/src/xrpld/rpc/handlers/GatewayBalances.cpp +++ b/src/xrpld/rpc/handlers/GatewayBalances.cpp @@ -131,6 +131,10 @@ doGatewayBalances(RPC::JsonContext& context) if (sle->getType() == ltESCROW) { auto const& escrow = sle->getFieldAmount(sfAmount); + // Gateway Balance should not include MPTs + if (escrow.holds()) + return; + auto& bal = locked[escrow.getCurrency()]; if (bal == beast::zero) { From af97df5a63fce2c5db6ca557394d722c5c0d7cab Mon Sep 17 00:00:00 2001 From: Alex Kremer Date: Wed, 4 Mar 2026 17:03:27 +0000 Subject: [PATCH 25/41] chore: Enable clang-tidy bugprone-move-forwarding-reference check (#6457) --- .clang-tidy | 1 + src/test/protocol/MultiApiJson_test.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.clang-tidy b/.clang-tidy index 5971b5dd14..e7169e47fe 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -24,6 +24,7 @@ Checks: "-*, bugprone-misplaced-operator-in-strlen-in-alloc, bugprone-misplaced-pointer-arithmetic-in-alloc, bugprone-misplaced-widening-cast, + bugprone-move-forwarding-reference, bugprone-multi-level-implicit-pointer-conversion, bugprone-multiple-new-in-one-expression, bugprone-multiple-statement-macro, diff --git a/src/test/protocol/MultiApiJson_test.cpp b/src/test/protocol/MultiApiJson_test.cpp index ccf719e349..bbd6ec1a07 100644 --- a/src/test/protocol/MultiApiJson_test.cpp +++ b/src/test/protocol/MultiApiJson_test.cpp @@ -556,7 +556,7 @@ struct MultiApiJson_test : beast::unit_test::suite static_assert([](auto&& v) { return !requires { v.visitor( - std::move(v), // cannot bind rvalue + decltype(v){}, // cannot bind rvalue 1, [](Json::Value&, auto) {}); }; From b451d5e412590f55426de91bacea0f3bdaf45183 Mon Sep 17 00:00:00 2001 From: Alex Kremer Date: Wed, 4 Mar 2026 18:10:10 +0000 Subject: [PATCH 26/41] chore: Enable clang-tidy `bugprone-return-const-ref-from-parameter` check (#6459) --- .clang-tidy | 2 +- src/test/beast/aged_associative_container_test.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index e7169e47fe..f6e826323a 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -33,6 +33,7 @@ Checks: "-*, bugprone-parent-virtual-call, bugprone-posix-return, bugprone-redundant-branch-condition, + bugprone-return-const-ref-from-parameter, bugprone-shared-ptr-array-mismatch, bugprone-signal-handler, bugprone-signed-char-misuse, @@ -90,7 +91,6 @@ Checks: "-*, # bugprone-reserved-identifier, # bugprone-move-forwarding-reference, # bugprone-unused-local-non-trivial-variable, -# bugprone-return-const-ref-from-parameter, # bugprone-switch-missing-default-case, # bugprone-sizeof-expression, # bugprone-suspicious-stringview-data-usage, diff --git a/src/test/beast/aged_associative_container_test.cpp b/src/test/beast/aged_associative_container_test.cpp index 19927c2d35..690f03cd49 100644 --- a/src/test/beast/aged_associative_container_test.cpp +++ b/src/test/beast/aged_associative_container_test.cpp @@ -227,7 +227,7 @@ public: static typename Base::Key const& extract(Value const& value) { - return value; + return value; // NOLINT(bugprone-return-const-ref-from-parameter) } static Values From 595f0dd4619a668f629a7bc8982924b9742134bc Mon Sep 17 00:00:00 2001 From: Alex Kremer Date: Wed, 4 Mar 2026 19:15:22 +0000 Subject: [PATCH 27/41] chore: Enable clang-tidy `bugprone-sizeof-expression` check (#6466) --- .clang-tidy | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index f6e826323a..26c7995631 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -38,6 +38,7 @@ Checks: "-*, bugprone-signal-handler, bugprone-signed-char-misuse, bugprone-sizeof-container, + bugprone-sizeof-expression, bugprone-spuriously-wake-up-functions, bugprone-standalone-empty, bugprone-string-constructor, @@ -84,7 +85,7 @@ Checks: "-*, performance-trivially-destructible " # --- -# checks that have some issues that need to be resolved: +# more checks that have some issues that need to be resolved: # # bugprone-crtp-constructor-accessibility, # bugprone-inc-dec-in-conditions, @@ -92,7 +93,6 @@ Checks: "-*, # bugprone-move-forwarding-reference, # bugprone-unused-local-non-trivial-variable, # bugprone-switch-missing-default-case, -# bugprone-sizeof-expression, # bugprone-suspicious-stringview-data-usage, # bugprone-suspicious-missing-comma, # bugprone-pointer-arithmetic-on-polymorphic-object, From c69091bded554a0820b8b449b1d3d10bcaa2553c Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Wed, 4 Mar 2026 19:45:28 +0000 Subject: [PATCH 28/41] chore: Add Git information compile-time info to only one file (#6464) The existing code added the git commit info (`GIT_COMMIT_HASH` and `GIT_BRANCH`) to every file, which was a problem for leveraging `ccache` to cache build objects. This change adds a separate C++ file from where these compile-time variables are propagated to wherever they are needed. A new CMake file is added to set the commit info if the `git` binary is available. --- .github/workflows/publish-docs.yml | 5 ++ CMakeLists.txt | 20 ------- cmake/GitInfo.cmake | 21 +++++++ cmake/XrplCore.cmake | 10 +++- cmake/XrplInstall.cmake | 1 + include/xrpl/beast/core/SemanticVersion.h | 5 +- include/xrpl/git/Git.h | 13 +++++ include/xrpl/protocol/BuildInfo.h | 2 +- src/libxrpl/beast/core/SemanticVersion.cpp | 4 +- src/libxrpl/git/Git.cpp | 31 ++++++++++ src/libxrpl/protocol/BuildInfo.cpp | 67 ++++++++++++++-------- src/xrpld/app/main/Main.cpp | 9 +-- src/xrpld/app/misc/NetworkOPs.cpp | 14 ++--- 13 files changed, 137 insertions(+), 65 deletions(-) create mode 100644 cmake/GitInfo.cmake create mode 100644 include/xrpl/git/Git.h create mode 100644 src/libxrpl/git/Git.cpp diff --git a/.github/workflows/publish-docs.yml b/.github/workflows/publish-docs.yml index acde57fd91..c4fc2e65ee 100644 --- a/.github/workflows/publish-docs.yml +++ b/.github/workflows/publish-docs.yml @@ -49,6 +49,11 @@ jobs: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Prepare runner + uses: XRPLF/actions/prepare-runner@2cbf481018d930656e9276fcc20dc0e3a0be5b6d + with: + enable_ccache: false + - name: Get number of processors uses: XRPLF/actions/get-nproc@cf0433aa74563aead044a1e395610c96d65a37cf id: nproc diff --git a/CMakeLists.txt b/CMakeLists.txt index 764e917498..f0d8519327 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,26 +36,6 @@ endif () # Enable ccache to speed up builds. include(Ccache) -# make GIT_COMMIT_HASH define available to all sources -find_package(Git) -if (Git_FOUND) - execute_process(COMMAND ${GIT_EXECUTABLE} --git-dir=${CMAKE_CURRENT_SOURCE_DIR}/.git rev-parse - HEAD OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE gch) - if (gch) - set(GIT_COMMIT_HASH "${gch}") - message(STATUS gch: ${GIT_COMMIT_HASH}) - add_definitions(-DGIT_COMMIT_HASH="${GIT_COMMIT_HASH}") - endif () - - execute_process(COMMAND ${GIT_EXECUTABLE} --git-dir=${CMAKE_CURRENT_SOURCE_DIR}/.git rev-parse - --abbrev-ref HEAD OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE gb) - if (gb) - set(GIT_BRANCH "${gb}") - message(STATUS gb: ${GIT_BRANCH}) - add_definitions(-DGIT_BRANCH="${GIT_BRANCH}") - endif () -endif () # git - if (thread_safety_analysis) add_compile_options(-Wthread-safety -D_LIBCPP_ENABLE_THREAD_SAFETY_ANNOTATIONS -DXRPL_ENABLE_THREAD_SAFETY_ANNOTATIONS) diff --git a/cmake/GitInfo.cmake b/cmake/GitInfo.cmake new file mode 100644 index 0000000000..1281182977 --- /dev/null +++ b/cmake/GitInfo.cmake @@ -0,0 +1,21 @@ +include_guard() + +set(GIT_BUILD_BRANCH "") +set(GIT_COMMIT_HASH "") + +find_package(Git) +if (NOT Git_FOUND) + message(WARNING "Git not found. Git branch and commit hash will be empty.") + return() +endif () + +set(GIT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/.git) + +execute_process(COMMAND ${GIT_EXECUTABLE} --git-dir=${GIT_DIRECTORY} rev-parse --abbrev-ref HEAD + OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE GIT_BUILD_BRANCH) + +execute_process(COMMAND ${GIT_EXECUTABLE} --git-dir=${GIT_DIRECTORY} rev-parse HEAD + OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE GIT_COMMIT_HASH) + +message(STATUS "Git branch: ${GIT_BUILD_BRANCH}") +message(STATUS "Git commit hash: ${GIT_COMMIT_HASH}") diff --git a/cmake/XrplCore.cmake b/cmake/XrplCore.cmake index e54dd09953..cd44f29df9 100644 --- a/cmake/XrplCore.cmake +++ b/cmake/XrplCore.cmake @@ -58,6 +58,12 @@ include(target_link_modules) add_module(xrpl beast) target_link_libraries(xrpl.libxrpl.beast PUBLIC xrpl.imports.main) +include(GitInfo) +add_module(xrpl git) +target_compile_definitions(xrpl.libxrpl.git PRIVATE GIT_COMMIT_HASH="${GIT_COMMIT_HASH}" + GIT_BUILD_BRANCH="${GIT_BUILD_BRANCH}") +target_link_libraries(xrpl.libxrpl.git PUBLIC xrpl.imports.main) + # Level 02 add_module(xrpl basics) target_link_libraries(xrpl.libxrpl.basics PUBLIC xrpl.libxrpl.beast) @@ -71,7 +77,8 @@ target_link_libraries(xrpl.libxrpl.crypto PUBLIC xrpl.libxrpl.basics) # Level 04 add_module(xrpl protocol) -target_link_libraries(xrpl.libxrpl.protocol PUBLIC xrpl.libxrpl.crypto xrpl.libxrpl.json) +target_link_libraries(xrpl.libxrpl.protocol PUBLIC xrpl.libxrpl.crypto xrpl.libxrpl.git + xrpl.libxrpl.json) # Level 05 add_module(xrpl core) @@ -135,6 +142,7 @@ target_link_modules( conditions core crypto + git json ledger net diff --git a/cmake/XrplInstall.cmake b/cmake/XrplInstall.cmake index 4cbf381f87..666fc0712f 100644 --- a/cmake/XrplInstall.cmake +++ b/cmake/XrplInstall.cmake @@ -23,6 +23,7 @@ install(TARGETS common xrpl.libxrpl.conditions xrpl.libxrpl.core xrpl.libxrpl.crypto + xrpl.libxrpl.git xrpl.libxrpl.json xrpl.libxrpl.rdb xrpl.libxrpl.ledger diff --git a/include/xrpl/beast/core/SemanticVersion.h b/include/xrpl/beast/core/SemanticVersion.h index 1d3525de25..f839ef8c53 100644 --- a/include/xrpl/beast/core/SemanticVersion.h +++ b/include/xrpl/beast/core/SemanticVersion.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include namespace beast { @@ -26,14 +27,14 @@ public: SemanticVersion(); - SemanticVersion(std::string const& version); + SemanticVersion(std::string_view version); /** Parse a semantic version string. The parsing is as strict as possible. @return `true` if the string was parsed. */ bool - parse(std::string const& input); + parse(std::string_view input); /** Produce a string from semantic version components. */ std::string diff --git a/include/xrpl/git/Git.h b/include/xrpl/git/Git.h new file mode 100644 index 0000000000..001c09aea7 --- /dev/null +++ b/include/xrpl/git/Git.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +namespace xrpl::git { + +std::string const& +getCommitHash(); + +std::string const& +getBuildBranch(); + +} // namespace xrpl::git diff --git a/include/xrpl/protocol/BuildInfo.h b/include/xrpl/protocol/BuildInfo.h index 3c42a4323f..cc7633cfe1 100644 --- a/include/xrpl/protocol/BuildInfo.h +++ b/include/xrpl/protocol/BuildInfo.h @@ -49,7 +49,7 @@ getFullVersionString(); @return the encoded version in a 64-bit integer */ std::uint64_t -encodeSoftwareVersion(char const* const versionStr); +encodeSoftwareVersion(std::string_view versionStr); /** Returns this server's version packed in a 64-bit integer. */ std::uint64_t diff --git a/src/libxrpl/beast/core/SemanticVersion.cpp b/src/libxrpl/beast/core/SemanticVersion.cpp index b18043d5f0..06cd622722 100644 --- a/src/libxrpl/beast/core/SemanticVersion.cpp +++ b/src/libxrpl/beast/core/SemanticVersion.cpp @@ -138,14 +138,14 @@ SemanticVersion::SemanticVersion() : majorVersion(0), minorVersion(0), patchVers { } -SemanticVersion::SemanticVersion(std::string const& version) : SemanticVersion() +SemanticVersion::SemanticVersion(std::string_view version) : SemanticVersion() { if (!parse(version)) throw std::invalid_argument("invalid version string"); } bool -SemanticVersion::parse(std::string const& input) +SemanticVersion::parse(std::string_view input) { // May not have leading or trailing whitespace auto left_iter = std::find_if_not(input.begin(), input.end(), [](std::string::value_type c) { diff --git a/src/libxrpl/git/Git.cpp b/src/libxrpl/git/Git.cpp new file mode 100644 index 0000000000..2992852632 --- /dev/null +++ b/src/libxrpl/git/Git.cpp @@ -0,0 +1,31 @@ +#include "xrpl/git/Git.h" + +#include + +#ifndef GIT_COMMIT_HASH +#error "GIT_COMMIT_HASH must be defined" +#endif +#ifndef GIT_BUILD_BRANCH +#error "GIT_BUILD_BRANCH must be defined" +#endif + +namespace xrpl::git { + +static constexpr char kGIT_COMMIT_HASH[] = GIT_COMMIT_HASH; +static constexpr char kGIT_BUILD_BRANCH[] = GIT_BUILD_BRANCH; + +std::string const& +getCommitHash() +{ + static std::string const kVALUE = kGIT_COMMIT_HASH; + return kVALUE; +} + +std::string const& +getBuildBranch() +{ + static std::string const kVALUE = kGIT_BUILD_BRANCH; + return kVALUE; +} + +} // namespace xrpl::git diff --git a/src/libxrpl/protocol/BuildInfo.cpp b/src/libxrpl/protocol/BuildInfo.cpp index 32b3431798..c7531f8376 100644 --- a/src/libxrpl/protocol/BuildInfo.cpp +++ b/src/libxrpl/protocol/BuildInfo.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include @@ -14,44 +15,60 @@ namespace xrpl { namespace BuildInfo { +namespace { + //-------------------------------------------------------------------------- // The build version number. You must edit this for each release // and follow the format described at http://semver.org/ //------------------------------------------------------------------------------ // clang-format off char const* const versionString = "3.2.0-b0" -// clang-format on - -#if defined(DEBUG) || defined(SANITIZERS) - "+" -#ifdef GIT_COMMIT_HASH - GIT_COMMIT_HASH - "." -#endif -#ifdef DEBUG - "DEBUG" -#ifdef SANITIZERS - "." -#endif -#endif - -#ifdef SANITIZERS - BOOST_PP_STRINGIZE(SANITIZERS) // cspell: disable-line -#endif -#endif - - //-------------------------------------------------------------------------- + // clang-format on ; // // Don't touch anything below this line // +std::string +buildVersionString() +{ + std::string version = versionString; + +#if defined(DEBUG) || defined(SANITIZERS) + std::string metadata; + + std::string const& commitHash = xrpl::git::getCommitHash(); + if (!commitHash.empty()) + metadata += commitHash + "."; + +#ifdef DEBUG + metadata += "DEBUG"; +#endif + +#if defined(DEBUG) && defined(SANITIZERS) + metadata += "."; +#endif + +#ifdef SANITIZERS + metadata += BOOST_PP_STRINGIZE(SANITIZERS); // cspell: disable-line +#endif + + if (!metadata.empty()) + version += "+" + metadata; +#endif + + return version; +} + +} // namespace + std::string const& getVersionString() { static std::string const value = [] { - std::string const s = versionString; + std::string const s = buildVersionString(); + beast::SemanticVersion v; if (!v.parse(s) || v.print() != s) LogicError(s + ": Bad server version string"); @@ -71,13 +88,13 @@ static constexpr std::uint64_t implementationVersionIdentifier = 0x183B'0000'000 static constexpr std::uint64_t implementationVersionIdentifierMask = 0xFFFF'0000'0000'0000LLU; std::uint64_t -encodeSoftwareVersion(char const* const versionStr) +encodeSoftwareVersion(std::string_view versionStr) { std::uint64_t c = implementationVersionIdentifier; beast::SemanticVersion v; - if (v.parse(std::string(versionStr))) + if (v.parse(versionStr)) { if (v.majorVersion >= 0 && v.majorVersion <= 255) c |= static_cast(v.majorVersion) << 40; @@ -137,7 +154,7 @@ encodeSoftwareVersion(char const* const versionStr) std::uint64_t getEncodedVersion() { - static std::uint64_t const cookie = {encodeSoftwareVersion(versionString)}; + static std::uint64_t const cookie = {encodeSoftwareVersion(getVersionString())}; return cookie; } diff --git a/src/xrpld/app/main/Main.cpp b/src/xrpld/app/main/Main.cpp index 83bdb51ec1..32d246f493 100644 --- a/src/xrpld/app/main/Main.cpp +++ b/src/xrpld/app/main/Main.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -476,12 +477,8 @@ run(int argc, char** argv) if (vm.count("version")) { std::cout << "rippled version " << BuildInfo::getVersionString() << std::endl; -#ifdef GIT_COMMIT_HASH - std::cout << "Git commit hash: " << GIT_COMMIT_HASH << std::endl; -#endif -#ifdef GIT_BRANCH - std::cout << "Git build branch: " << GIT_BRANCH << std::endl; -#endif + std::cout << "Git commit hash: " << xrpl::git::getCommitHash() << std::endl; + std::cout << "Git build branch: " << xrpl::git::getBuildBranch() << std::endl; return 0; } diff --git a/src/xrpld/app/misc/NetworkOPs.cpp b/src/xrpld/app/misc/NetworkOPs.cpp index 8178611c61..b8663a76fb 100644 --- a/src/xrpld/app/misc/NetworkOPs.cpp +++ b/src/xrpld/app/misc/NetworkOPs.cpp @@ -40,6 +40,7 @@ #include #include #include +#include #include #include #include @@ -2593,17 +2594,14 @@ NetworkOPsImp::getServerInfo(bool human, bool admin, bool counters) } } -#if defined(GIT_COMMIT_HASH) || defined(GIT_BRANCH) + if (!xrpl::git::getCommitHash().empty() || !xrpl::git::getBuildBranch().empty()) { auto& x = (info[jss::git] = Json::objectValue); -#ifdef GIT_COMMIT_HASH - x[jss::hash] = GIT_COMMIT_HASH; -#endif -#ifdef GIT_BRANCH - x[jss::branch] = GIT_BRANCH; -#endif + if (!xrpl::git::getCommitHash().empty()) + x[jss::hash] = xrpl::git::getCommitHash(); + if (!xrpl::git::getBuildBranch().empty()) + x[jss::branch] = xrpl::git::getBuildBranch(); } -#endif } info[jss::io_latency_ms] = static_cast(registry_.app().getIOLatency().count()); From 77518394e8ced1581460b3fb02f7ac8aa41a2683 Mon Sep 17 00:00:00 2001 From: Michael Legleux Date: Wed, 4 Mar 2026 19:19:57 -0800 Subject: [PATCH 29/41] fix: Stop committing generated docs to prevent repo bloat (#6474) --- .github/workflows/publish-docs.yml | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/.github/workflows/publish-docs.yml b/.github/workflows/publish-docs.yml index c4fc2e65ee..aab2f9ca7d 100644 --- a/.github/workflows/publish-docs.yml +++ b/.github/workflows/publish-docs.yml @@ -44,7 +44,11 @@ jobs: runs-on: ubuntu-latest container: ghcr.io/xrplf/ci/tools-rippled-documentation:sha-a8c7be1 permissions: - contents: write + pages: write + id-token: write + environment: + name: github-pages + url: ${{ steps.deploy.outputs.page_url }} steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -83,9 +87,13 @@ jobs: cmake -Donly_docs=ON .. cmake --build . --target docs --parallel ${BUILD_NPROC} - - name: Publish documentation + - name: Create documentation artifact if: ${{ github.event_name == 'push' }} - uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e # v4.0.0 + uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b # v4.0.0 with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ${{ env.BUILD_DIR }}/docs/html + path: ${{ env.BUILD_DIR }}/docs/html + + - name: Deploy documentation to GitHub Pages + id: deploy + if: ${{ github.event_name == 'push' }} + uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4.0.5 From 08e734457fa16cb3a851ecfae34f9a4f8accd0eb Mon Sep 17 00:00:00 2001 From: Michael Legleux Date: Thu, 5 Mar 2026 00:12:41 -0800 Subject: [PATCH 30/41] fix: Fix docs deployment for pull requests (#6482) --- .github/workflows/publish-docs.yml | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/.github/workflows/publish-docs.yml b/.github/workflows/publish-docs.yml index aab2f9ca7d..d4abd74363 100644 --- a/.github/workflows/publish-docs.yml +++ b/.github/workflows/publish-docs.yml @@ -40,15 +40,9 @@ env: NPROC_SUBTRACT: ${{ github.event.repository.private && '1' || '2' }} jobs: - publish: + build: runs-on: ubuntu-latest container: ghcr.io/xrplf/ci/tools-rippled-documentation:sha-a8c7be1 - permissions: - pages: write - id-token: write - environment: - name: github-pages - url: ${{ steps.deploy.outputs.page_url }} steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -93,7 +87,17 @@ jobs: with: path: ${{ env.BUILD_DIR }}/docs/html - - name: Deploy documentation to GitHub Pages + deploy: + if: ${{ github.event_name == 'push' }} + needs: build + runs-on: ubuntu-latest + permissions: + pages: write + id-token: write + environment: + name: github-pages + url: ${{ steps.deploy.outputs.page_url }} + steps: + - name: Deploy to GitHub Pages id: deploy - if: ${{ github.event_name == 'push' }} uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4.0.5 From dde450784dafe5bb84799cae3f7e4a4968e5496e Mon Sep 17 00:00:00 2001 From: Alex Kremer Date: Thu, 5 Mar 2026 16:11:27 +0000 Subject: [PATCH 31/41] Add Formats and Flags to `server_definitions` (#6321) This change implements https://github.com/XRPLF/XRPL-Standards/discussions/418: "System XLS: Add Formats and Flags to server_definitions". --- API-CHANGELOG.md | 13 + include/xrpl/protocol/KnownFormats.h | 18 +- include/xrpl/protocol/LedgerFormats.h | 309 +++++--- include/xrpl/protocol/SOTemplate.h | 9 +- include/xrpl/protocol/TxFlags.h | 680 +++++++++++------- include/xrpl/protocol/TxFormats.h | 5 + include/xrpl/protocol/jss.h | 7 + include/xrpl/tx/transactors/Clawback.h | 3 - .../transactors/MPT/MPTokenIssuanceDestroy.h | 3 - .../tx/transactors/NFT/NFTokenAcceptOffer.h | 3 - .../tx/transactors/NFT/NFTokenCancelOffer.h | 3 - src/libxrpl/protocol/LedgerFormats.cpp | 14 +- src/libxrpl/protocol/SOTemplate.cpp | 15 +- src/libxrpl/protocol/TxFormats.cpp | 14 +- src/libxrpl/tx/transactors/AMM/AMMDeposit.cpp | 2 +- .../tx/transactors/AMM/AMMWithdraw.cpp | 2 +- src/libxrpl/tx/transactors/Change.cpp | 6 +- src/libxrpl/tx/transactors/Clawback.cpp | 6 - .../MPT/MPTokenIssuanceDestroy.cpp | 6 - .../tx/transactors/MPT/MPTokenIssuanceSet.cpp | 2 +- .../tx/transactors/NFT/NFTokenAcceptOffer.cpp | 6 - .../tx/transactors/NFT/NFTokenCancelOffer.cpp | 6 - .../tx/transactors/NFT/NFTokenMint.cpp | 5 +- src/libxrpl/tx/transactors/PayChan.cpp | 2 +- src/libxrpl/tx/transactors/XChainBridge.cpp | 2 +- src/test/{rpc => app}/AccountSet_test.cpp | 2 +- src/test/app/Batch_test.cpp | 1 + src/test/rpc/ServerDefinitions_test.cpp | 397 +++++++++- src/xrpld/rpc/handlers/ServerDefinitions.cpp | 123 +++- 29 files changed, 1165 insertions(+), 499 deletions(-) rename src/test/{rpc => app}/AccountSet_test.cpp (99%) diff --git a/API-CHANGELOG.md b/API-CHANGELOG.md index c7a31d27fa..31aca39782 100644 --- a/API-CHANGELOG.md +++ b/API-CHANGELOG.md @@ -22,6 +22,19 @@ API version 2 is available in `rippled` version 2.0.0 and later. See [API-VERSIO This version is supported by all `rippled` versions. For WebSocket and HTTP JSON-RPC requests, it is currently the default API version used when no `api_version` is specified. +## Unreleased + +This section contains changes targeting a future version. + +### Additions + +- `server_definitions`: Added the following new sections to the response ([#6321](https://github.com/XRPLF/rippled/pull/6321)): + - `TRANSACTION_FORMATS`: Describes the fields and their optionality for each transaction type, including common fields shared across all transactions. + - `LEDGER_ENTRY_FORMATS`: Describes the fields and their optionality for each ledger entry type, including common fields shared across all ledger entries. + - `TRANSACTION_FLAGS`: Maps transaction type names to their supported flags and flag values. + - `LEDGER_ENTRY_FLAGS`: Maps ledger entry type names to their flags and flag values. + - `ACCOUNT_SET_FLAGS`: Maps AccountSet flag names (asf flags) to their numeric values. + ## XRP Ledger server version 3.1.0 [Version 3.1.0](https://github.com/XRPLF/rippled/releases/tag/3.1.0) was released on Jan 27, 2026. diff --git a/include/xrpl/protocol/KnownFormats.h b/include/xrpl/protocol/KnownFormats.h index ce14a210ab..c454683e19 100644 --- a/include/xrpl/protocol/KnownFormats.h +++ b/include/xrpl/protocol/KnownFormats.h @@ -30,9 +30,11 @@ public: Item( char const* name, KeyType type, - std::initializer_list uniqueFields, - std::initializer_list commonFields) - : soTemplate_(uniqueFields, commonFields), name_(name), type_(type) + std::vector uniqueFields, + std::vector commonFields) + : soTemplate_(std::move(uniqueFields), std::move(commonFields)) + , name_(name) + , type_(type) { // Verify that KeyType is appropriate. static_assert( @@ -142,16 +144,16 @@ protected: @param name The name of this format. @param type The type of this format. - @param uniqueFields An std::initializer_list of unique fields - @param commonFields An std::initializer_list of common fields + @param uniqueFields A std::vector of unique fields + @param commonFields A std::vector of common fields @return The created format. */ Item const& add(char const* name, KeyType type, - std::initializer_list uniqueFields, - std::initializer_list commonFields = {}) + std::vector uniqueFields, + std::vector commonFields = {}) { if (auto const item = findByType(type)) { @@ -160,7 +162,7 @@ protected: item->getName()); } - formats_.emplace_front(name, type, uniqueFields, commonFields); + formats_.emplace_front(name, type, std::move(uniqueFields), std::move(commonFields)); Item const& item{formats_.front()}; names_[name] = &item; diff --git a/include/xrpl/protocol/LedgerFormats.h b/include/xrpl/protocol/LedgerFormats.h index fa39c15843..a43f6a7134 100644 --- a/include/xrpl/protocol/LedgerFormats.h +++ b/include/xrpl/protocol/LedgerFormats.h @@ -2,36 +2,34 @@ #include -namespace xrpl { +#include +#include +#include +namespace xrpl { /** Identifiers for on-ledger objects. - Each ledger object requires a unique type identifier, which is stored - within the object itself; this makes it possible to iterate the entire - ledger and determine each object's type and verify that the object you - retrieved from a given hash matches the expected type. + Each ledger object requires a unique type identifier, which is stored within the object itself; + this makes it possible to iterate the entire ledger and determine each object's type and verify + that the object you retrieved from a given hash matches the expected type. - @warning Since these values are stored inside objects stored on the ledger - they are part of the protocol. **Changing them should be avoided - because without special handling, this will result in a hard + @warning Since these values are stored inside objects stored on the ledger they are part of the + protocol. + **Changing them should be avoided because without special handling, this will result in a hard fork.** - @note Values outside this range may be used internally by the code for - various purposes, but attempting to use such values to identify - on-ledger objects will results in an invariant failure. + @note Values outside this range may be used internally by the code for various purposes, but + attempting to use such values to identify on-ledger objects will result in an invariant failure. - @note When retiring types, the specific values should not be removed but - should be marked as [[deprecated]]. This is to avoid accidental - reuse of identifiers. + @note When retiring types, the specific values should not be removed but should be marked as + [[deprecated]]. This is to avoid accidental reuse of identifiers. - @todo The C++ language does not enable checking for duplicate values - here. If it becomes possible then we should do this. + @todo The C++ language does not enable checking for duplicate values here. + If it becomes possible then we should do this. @ingroup protocol */ -// clang-format off -enum LedgerEntryType : std::uint16_t -{ +enum LedgerEntryType : std::uint16_t { #pragma push_macro("LEDGER_ENTRY") #undef LEDGER_ENTRY @@ -46,12 +44,10 @@ enum LedgerEntryType : std::uint16_t //--------------------------------------------------------------------------- /** A special type, matching any ledger entry type. - The value does not represent a concrete type, but rather is used in - contexts where the specific type of a ledger object is unimportant, - unknown or unavailable. + The value does not represent a concrete type, but rather is used in contexts where the + specific type of a ledger object is unimportant, unknown or unavailable. - Objects with this special type cannot be created or stored on the - ledger. + Objects with this special type cannot be created or stored on the ledger. \sa keylet::unchecked */ @@ -59,12 +55,11 @@ enum LedgerEntryType : std::uint16_t /** A special type, matching any ledger type except directory nodes. - The value does not represent a concrete type, but rather is used in - contexts where the ledger object must not be a directory node but - its specific type is otherwise unimportant, unknown or unavailable. + The value does not represent a concrete type, but rather is used in contexts where the + ledger object must not be a directory node but its specific type is otherwise unimportant, + unknown or unavailable. - Objects with this special type cannot be created or stored on the - ledger. + Objects with this special type cannot be created or stored on the ledger. \sa keylet::child */ @@ -93,104 +88,188 @@ enum LedgerEntryType : std::uint16_t Support for this type of object was never implemented. No objects of this type were ever created. */ - ltGENERATOR_MAP [[deprecated("This object type is not supported and should not be used.")]] = 0x0067, + ltGENERATOR_MAP [[deprecated("This object type is not supported and should not be used.")]] = + 0x0067, }; -// clang-format off -/** +/** Ledger object flags. + + These flags are specified in ledger objects and modify their behavior. + + @warning Ledger object flags form part of the protocol. + **Changing them should be avoided because without special handling, this will result in a hard + fork.** + @ingroup protocol */ -enum LedgerSpecificFlags { - // ltACCOUNT_ROOT - lsfPasswordSpent = 0x00010000, // True, if password set fee is spent. - lsfRequireDestTag = - 0x00020000, // True, to require a DestinationTag for payments. - lsfRequireAuth = - 0x00040000, // True, to require a authorization to hold IOUs. - lsfDisallowXRP = 0x00080000, // True, to disallow sending XRP. - lsfDisableMaster = 0x00100000, // True, force regular key - lsfNoFreeze = 0x00200000, // True, cannot freeze ripple states - lsfGlobalFreeze = 0x00400000, // True, all assets frozen - lsfDefaultRipple = - 0x00800000, // True, incoming trust lines allow rippling by default - lsfDepositAuth = 0x01000000, // True, all deposits require authorization -/* // reserved for Hooks amendment - lsfTshCollect = 0x02000000, // True, allow TSH collect-calls to acc hooks -*/ - lsfDisallowIncomingNFTokenOffer = - 0x04000000, // True, reject new incoming NFT offers - lsfDisallowIncomingCheck = - 0x08000000, // True, reject new checks - lsfDisallowIncomingPayChan = - 0x10000000, // True, reject new paychans - lsfDisallowIncomingTrustline = - 0x20000000, // True, reject new trustlines (only if no issued assets) - lsfAllowTrustLineLocking = - 0x40000000, // True, enable trustline locking - lsfAllowTrustLineClawback = - 0x80000000, // True, enable clawback +#pragma push_macro("XMACRO") +#pragma push_macro("TO_VALUE") +#pragma push_macro("VALUE_TO_MAP") +#pragma push_macro("NULL_NAME") +#pragma push_macro("TO_MAP") +#pragma push_macro("ALL_LEDGER_FLAGS") - // ltOFFER - lsfPassive = 0x00010000, - lsfSell = 0x00020000, // True, offer was placed as a sell. - lsfHybrid = 0x00040000, // True, offer is hybrid. +#undef XMACRO +#undef TO_VALUE +#undef VALUE_TO_MAP +#undef NULL_NAME +#undef TO_MAP - // ltRIPPLE_STATE - lsfLowReserve = 0x00010000, // True, if entry counts toward reserve. - lsfHighReserve = 0x00020000, - lsfLowAuth = 0x00040000, - lsfHighAuth = 0x00080000, - lsfLowNoRipple = 0x00100000, - lsfHighNoRipple = 0x00200000, - lsfLowFreeze = 0x00400000, // True, low side has set freeze flag - lsfHighFreeze = 0x00800000, // True, high side has set freeze flag - lsfLowDeepFreeze = 0x02000000, // True, low side has set deep freeze flag - lsfHighDeepFreeze = 0x04000000, // True, high side has set deep freeze flag - lsfAMMNode = 0x01000000, // True, trust line to AMM. Used by client - // apps to identify payments via AMM. +#undef ALL_LEDGER_FLAGS - // ltSIGNER_LIST - lsfOneOwnerCount = 0x00010000, // True, uses only one OwnerCount +// clang-format off - // ltDIR_NODE - lsfNFTokenBuyOffers = 0x00000001, - lsfNFTokenSellOffers = 0x00000002, +#define XMACRO(LEDGER_OBJECT, LSF_FLAG, LSF_FLAG2) \ + LEDGER_OBJECT(AccountRoot, \ + LSF_FLAG(lsfPasswordSpent, 0x00010000) /* True, if password set fee is spent. */ \ + LSF_FLAG(lsfRequireDestTag, 0x00020000) /* True, to require a DestinationTag for payments. */ \ + LSF_FLAG(lsfRequireAuth, 0x00040000) /* True, to require a authorization to hold IOUs. */ \ + LSF_FLAG(lsfDisallowXRP, 0x00080000) /* True, to disallow sending XRP. */ \ + LSF_FLAG(lsfDisableMaster, 0x00100000) /* True, force regular key */ \ + LSF_FLAG(lsfNoFreeze, 0x00200000) /* True, cannot freeze ripple states */ \ + LSF_FLAG(lsfGlobalFreeze, 0x00400000) /* True, all assets frozen */ \ + LSF_FLAG(lsfDefaultRipple, 0x00800000) /* True, incoming trust lines allow rippling by default */ \ + LSF_FLAG(lsfDepositAuth, 0x01000000) /* True, all deposits require authorization */ \ + LSF_FLAG(lsfDisallowIncomingNFTokenOffer, 0x04000000) /* True, reject new incoming NFT offers */ \ + LSF_FLAG(lsfDisallowIncomingCheck, 0x08000000) /* True, reject new checks */ \ + LSF_FLAG(lsfDisallowIncomingPayChan, 0x10000000) /* True, reject new paychans */ \ + LSF_FLAG(lsfDisallowIncomingTrustline, 0x20000000) /* True, reject new trustlines (only if no issued assets) */ \ + LSF_FLAG(lsfAllowTrustLineLocking, 0x40000000) /* True, enable trustline locking */ \ + LSF_FLAG(lsfAllowTrustLineClawback, 0x80000000)) /* True, enable clawback */ \ + \ + LEDGER_OBJECT(Offer, \ + LSF_FLAG(lsfPassive, 0x00010000) \ + LSF_FLAG(lsfSell, 0x00020000) /* True, offer was placed as a sell. */ \ + LSF_FLAG(lsfHybrid, 0x00040000)) /* True, offer is hybrid. */ \ + \ + LEDGER_OBJECT(RippleState, \ + LSF_FLAG(lsfLowReserve, 0x00010000) /* True, if entry counts toward reserve. */ \ + LSF_FLAG(lsfHighReserve, 0x00020000) \ + LSF_FLAG(lsfLowAuth, 0x00040000) \ + LSF_FLAG(lsfHighAuth, 0x00080000) \ + LSF_FLAG(lsfLowNoRipple, 0x00100000) \ + LSF_FLAG(lsfHighNoRipple, 0x00200000) \ + LSF_FLAG(lsfLowFreeze, 0x00400000) /* True, low side has set freeze flag */ \ + LSF_FLAG(lsfHighFreeze, 0x00800000) /* True, high side has set freeze flag */ \ + LSF_FLAG(lsfAMMNode, 0x01000000) /* True, trust line to AMM. */ \ + /* Used by client apps to identify payments via AMM. */ \ + LSF_FLAG(lsfLowDeepFreeze, 0x02000000) /* True, low side has set deep freeze flag */ \ + LSF_FLAG(lsfHighDeepFreeze, 0x04000000)) /* True, high side has set deep freeze flag */ \ + \ + LEDGER_OBJECT(SignerList, \ + LSF_FLAG(lsfOneOwnerCount, 0x00010000)) /* True, uses only one OwnerCount */ \ + \ + LEDGER_OBJECT(DirNode, \ + LSF_FLAG(lsfNFTokenBuyOffers, 0x00000001) \ + LSF_FLAG(lsfNFTokenSellOffers, 0x00000002)) \ + \ + LEDGER_OBJECT(NFTokenOffer, \ + LSF_FLAG(lsfSellNFToken, 0x00000001)) \ + \ + LEDGER_OBJECT(MPTokenIssuance, \ + LSF_FLAG(lsfMPTLocked, 0x00000001) /* Also used in ltMPTOKEN */ \ + LSF_FLAG(lsfMPTCanLock, 0x00000002) \ + LSF_FLAG(lsfMPTRequireAuth, 0x00000004) \ + LSF_FLAG(lsfMPTCanEscrow, 0x00000008) \ + LSF_FLAG(lsfMPTCanTrade, 0x00000010) \ + LSF_FLAG(lsfMPTCanTransfer, 0x00000020) \ + 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(lsmfMPTCanMutateMetadata, 0x00010000) \ + LSF_FLAG(lsmfMPTCanMutateTransferFee, 0x00020000)) \ + \ + LEDGER_OBJECT(MPToken, \ + LSF_FLAG2(lsfMPTLocked, 0x00000001) \ + LSF_FLAG(lsfMPTAuthorized, 0x00000002)) \ + \ + LEDGER_OBJECT(Credential, \ + LSF_FLAG(lsfAccepted, 0x00010000)) \ + \ + LEDGER_OBJECT(Vault, \ + LSF_FLAG(lsfVaultPrivate, 0x00010000)) \ + \ + LEDGER_OBJECT(Loan, \ + LSF_FLAG(lsfLoanDefault, 0x00010000) \ + LSF_FLAG(lsfLoanImpaired, 0x00020000) \ + LSF_FLAG(lsfLoanOverpayment, 0x00040000)) /* True, loan allows overpayments */ - // ltNFTOKEN_OFFER - lsfSellNFToken = 0x00000001, +// clang-format on - // ltMPTOKEN_ISSUANCE - lsfMPTLocked = 0x00000001, // Also used in ltMPTOKEN - lsfMPTCanLock = 0x00000002, - lsfMPTRequireAuth = 0x00000004, - lsfMPTCanEscrow = 0x00000008, - lsfMPTCanTrade = 0x00000010, - lsfMPTCanTransfer = 0x00000020, - lsfMPTCanClawback = 0x00000040, +// Create all the flag values as an enum. +// +// example: +// enum LedgerSpecificFlags { +// lsfPasswordSpent = 0x00010000, +// lsfRequireDestTag = 0x00020000, +// ... +// }; +#define TO_VALUE(name, value) name = value, +#define NULL_NAME(name, values) values +#define NULL_OUTPUT(name, value) +enum LedgerSpecificFlags : std::uint32_t { XMACRO(NULL_NAME, TO_VALUE, NULL_OUTPUT) }; - lsmfMPTCanMutateCanLock = 0x00000002, - lsmfMPTCanMutateRequireAuth = 0x00000004, - lsmfMPTCanMutateCanEscrow = 0x00000008, - lsmfMPTCanMutateCanTrade = 0x00000010, - lsmfMPTCanMutateCanTransfer = 0x00000020, - lsmfMPTCanMutateCanClawback = 0x00000040, - lsmfMPTCanMutateMetadata = 0x00010000, - lsmfMPTCanMutateTransferFee = 0x00020000, +// Create getter functions for each set of flags using Meyer's singleton pattern. +// This avoids static initialization order fiasco while still providing efficient access. +// This is used below in `getAllLedgerFlags()` to generate the server_definitions RPC output. +// +// example: +// inline LedgerFlagMap const& getAccountRootFlags() { +// static LedgerFlagMap const flags = { +// {"lsfPasswordSpent", 0x00010000}, +// {"lsfRequireDestTag", 0x00020000}, +// ...}; +// return flags; +// } +using LedgerFlagMap = std::map; +#define VALUE_TO_MAP(name, value) {#name, value}, +#define TO_MAP(name, values) \ + inline LedgerFlagMap const& get##name##Flags() \ + { \ + static LedgerFlagMap const flags = {values}; \ + return flags; \ + } +XMACRO(TO_MAP, VALUE_TO_MAP, VALUE_TO_MAP) - // ltMPTOKEN - lsfMPTAuthorized = 0x00000002, +// Create a getter function for all ledger flag maps using Meyer's singleton pattern. +// This is used to generate the server_definitions RPC output. +// +// example: +// inline std::vector> const& getAllLedgerFlags() { +// static std::vector> const flags = { +// {"AccountRoot", getAccountRootFlags()}, +// ...}; +// return flags; +// } +#define ALL_LEDGER_FLAGS(name, values) {#name, get##name##Flags()}, +inline std::vector> const& +getAllLedgerFlags() +{ + static std::vector> const flags = { + XMACRO(ALL_LEDGER_FLAGS, NULL_OUTPUT, NULL_OUTPUT)}; + return flags; +} - // ltCREDENTIAL - lsfAccepted = 0x00010000, +#undef XMACRO +#undef TO_VALUE +#undef VALUE_TO_MAP +#undef NULL_NAME +#undef NULL_OUTPUT +#undef TO_MAP +#undef ALL_LEDGER_FLAGS - // ltVAULT - lsfVaultPrivate = 0x00010000, - - // ltLOAN - lsfLoanDefault = 0x00010000, - lsfLoanImpaired = 0x00020000, - lsfLoanOverpayment = 0x00040000, // True, loan allows overpayments -}; +#pragma pop_macro("XMACRO") +#pragma pop_macro("TO_VALUE") +#pragma pop_macro("VALUE_TO_MAP") +#pragma pop_macro("NULL_NAME") +#pragma pop_macro("TO_MAP") +#pragma pop_macro("ALL_LEDGER_FLAGS") //------------------------------------------------------------------------------ @@ -207,6 +286,10 @@ private: public: static LedgerFormats const& getInstance(); + + // Fields shared by all ledger entry formats: + static std::vector const& + getCommonFields(); }; } // namespace xrpl diff --git a/include/xrpl/protocol/SOTemplate.h b/include/xrpl/protocol/SOTemplate.h index 576bd8c34a..41cea7936c 100644 --- a/include/xrpl/protocol/SOTemplate.h +++ b/include/xrpl/protocol/SOTemplate.h @@ -6,6 +6,7 @@ #include #include #include +#include namespace xrpl { @@ -97,8 +98,12 @@ public: operator=(SOTemplate&& other) = default; /** Create a template populated with all fields. - After creating the template fields cannot be - added, modified, or removed. + After creating the template fields cannot be added, modified, or removed. + */ + SOTemplate(std::vector uniqueFields, std::vector commonFields = {}); + + /** Create a template populated with all fields. + Note: Defers to the vector constructor above. */ SOTemplate( std::initializer_list uniqueFields, diff --git a/include/xrpl/protocol/TxFlags.h b/include/xrpl/protocol/TxFlags.h index 6c6ef5e369..7c2085109f 100644 --- a/include/xrpl/protocol/TxFlags.h +++ b/include/xrpl/protocol/TxFlags.h @@ -3,294 +3,444 @@ #include #include +#include +#include +#include +#include namespace xrpl { /** Transaction flags. - These flags are specified in a transaction's 'Flags' field and modify the - behavior of that transaction. + These flags are specified in a transaction's 'Flags' field and modify + the behavior of that transaction. There are two types of flags: - (1) Universal flags: these are flags which apply to, and are interpreted - the same way by, all transactions, except, perhaps, - to special pseudo-transactions. + (1) Universal flags: these are flags which apply to, and are interpreted the same way by, + all transactions, except, perhaps, to special pseudo-transactions. - (2) Tx-Specific flags: these are flags which are interpreted according - to the type of the transaction being executed. - That is, the same numerical flag value may have - different effects, depending on the transaction - being executed. + (2) Tx-Specific flags: these are flags which are interpreted according to the type of the + transaction being executed. That is, the same numerical flag value may have different + effects, depending on the transaction being executed. - @note The universal transaction flags occupy the high-order 8 bits. The - tx-specific flags occupy the remaining 24 bits. + @note The universal transaction flags occupy the high-order 8 bits. + The tx-specific flags occupy the remaining 24 bits. - @warning Transaction flags form part of the protocol. **Changing them - should be avoided because without special handling, this will - result in a hard fork.** + @warning Transaction flags form part of the protocol. + **Changing them should be avoided because without special handling, this will result in + a hard fork.** @ingroup protocol */ -// Formatting equals sign aligned 4 spaces after longest prefix, except for -// wrapped lines -// clang-format off +using FlagValue = std::uint32_t; + // Universal Transaction flags: -constexpr std::uint32_t tfFullyCanonicalSig = 0x80000000; -constexpr std::uint32_t tfInnerBatchTxn = 0x40000000; -constexpr std::uint32_t tfUniversal = tfFullyCanonicalSig | tfInnerBatchTxn; -constexpr std::uint32_t tfUniversalMask = ~tfUniversal; +inline constexpr FlagValue tfFullyCanonicalSig = 0x80000000; +inline constexpr FlagValue tfInnerBatchTxn = 0x40000000; +inline constexpr FlagValue tfUniversal = tfFullyCanonicalSig | tfInnerBatchTxn; +inline constexpr FlagValue tfUniversalMask = ~tfUniversal; -// AccountSet flags: -constexpr std::uint32_t tfRequireDestTag = 0x00010000; -constexpr std::uint32_t tfOptionalDestTag = 0x00020000; -constexpr std::uint32_t tfRequireAuth = 0x00040000; -constexpr std::uint32_t tfOptionalAuth = 0x00080000; -constexpr std::uint32_t tfDisallowXRP = 0x00100000; -constexpr std::uint32_t tfAllowXRP = 0x00200000; -constexpr std::uint32_t tfAccountSetMask = - ~(tfUniversal | tfRequireDestTag | tfOptionalDestTag | tfRequireAuth | - tfOptionalAuth | tfDisallowXRP | tfAllowXRP); +#pragma push_macro("XMACRO") +#pragma push_macro("TO_VALUE") +#pragma push_macro("VALUE_TO_MAP") +#pragma push_macro("NULL_NAME") +#pragma push_macro("NULL_OUTPUT") +#pragma push_macro("TO_MAP") +#pragma push_macro("TO_MASK") +#pragma push_macro("VALUE_TO_MASK") +#pragma push_macro("ALL_TX_FLAGS") +#pragma push_macro("NULL_MASK_ADJ") +#pragma push_macro("MASK_ADJ_TO_MASK") -// AccountSet SetFlag/ClearFlag values -constexpr std::uint32_t asfRequireDest = 1; -constexpr std::uint32_t asfRequireAuth = 2; -constexpr std::uint32_t asfDisallowXRP = 3; -constexpr std::uint32_t asfDisableMaster = 4; -constexpr std::uint32_t asfAccountTxnID = 5; -constexpr std::uint32_t asfNoFreeze = 6; -constexpr std::uint32_t asfGlobalFreeze = 7; -constexpr std::uint32_t asfDefaultRipple = 8; -constexpr std::uint32_t asfDepositAuth = 9; -constexpr std::uint32_t asfAuthorizedNFTokenMinter = 10; -/* // reserved for Hooks amendment -constexpr std::uint32_t asfTshCollect = 11; -*/ -constexpr std::uint32_t asfDisallowIncomingNFTokenOffer = 12; -constexpr std::uint32_t asfDisallowIncomingCheck = 13; -constexpr std::uint32_t asfDisallowIncomingPayChan = 14; -constexpr std::uint32_t asfDisallowIncomingTrustline = 15; -constexpr std::uint32_t asfAllowTrustLineClawback = 16; -constexpr std::uint32_t asfAllowTrustLineLocking = 17; +#undef XMACRO +#undef TO_VALUE +#undef VALUE_TO_MAP +#undef NULL_NAME +#undef NULL_OUTPUT +#undef TO_MAP +#undef TO_MASK +#undef VALUE_TO_MASK +#undef NULL_MASK_ADJ +#undef MASK_ADJ_TO_MASK -// OfferCreate flags: -constexpr std::uint32_t tfPassive = 0x00010000; -constexpr std::uint32_t tfImmediateOrCancel = 0x00020000; -constexpr std::uint32_t tfFillOrKill = 0x00040000; -constexpr std::uint32_t tfSell = 0x00080000; -constexpr std::uint32_t tfHybrid = 0x00100000; -constexpr std::uint32_t tfOfferCreateMask = - ~(tfUniversal | tfPassive | tfImmediateOrCancel | tfFillOrKill | tfSell | tfHybrid); +// clang-format off +#undef ALL_TX_FLAGS -// Payment flags: -constexpr std::uint32_t tfNoRippleDirect = 0x00010000; -constexpr std::uint32_t tfPartialPayment = 0x00020000; -constexpr std::uint32_t tfLimitQuality = 0x00040000; -constexpr std::uint32_t tfPaymentMask = - ~(tfUniversal | tfPartialPayment | tfLimitQuality | tfNoRippleDirect); -constexpr std::uint32_t tfMPTPaymentMask = ~(tfUniversal | tfPartialPayment); - -// TrustSet flags: -constexpr std::uint32_t tfSetfAuth = 0x00010000; -constexpr std::uint32_t tfSetNoRipple = 0x00020000; -constexpr std::uint32_t tfClearNoRipple = 0x00040000; -constexpr std::uint32_t tfSetFreeze = 0x00100000; -constexpr std::uint32_t tfClearFreeze = 0x00200000; -constexpr std::uint32_t tfSetDeepFreeze = 0x00400000; -constexpr std::uint32_t tfClearDeepFreeze = 0x00800000; -constexpr std::uint32_t tfTrustSetMask = - ~(tfUniversal | tfSetfAuth | tfSetNoRipple | tfClearNoRipple | tfSetFreeze | - tfClearFreeze | tfSetDeepFreeze | tfClearDeepFreeze); -constexpr std::uint32_t tfTrustSetPermissionMask = ~(tfUniversal | tfSetfAuth | tfSetFreeze | tfClearFreeze); - -// EnableAmendment flags: -constexpr std::uint32_t tfGotMajority = 0x00010000; -constexpr std::uint32_t tfLostMajority = 0x00020000; -constexpr std::uint32_t tfChangeMask = - ~( tfUniversal | tfGotMajority | tfLostMajority); - -// PaymentChannelClaim flags: -constexpr std::uint32_t tfRenew = 0x00010000; -constexpr std::uint32_t tfClose = 0x00020000; -constexpr std::uint32_t tfPayChanClaimMask = ~(tfUniversal | tfRenew | tfClose); - -// NFTokenMint flags: -constexpr std::uint32_t const tfBurnable = 0x00000001; -constexpr std::uint32_t const tfOnlyXRP = 0x00000002; -constexpr std::uint32_t const tfTrustLine = 0x00000004; -constexpr std::uint32_t const tfTransferable = 0x00000008; -constexpr std::uint32_t const tfMutable = 0x00000010; - -// MPTokenIssuanceCreate flags: -// Note: tf/lsfMPTLocked is intentionally omitted, since this transaction -// is not allowed to modify it. -constexpr std::uint32_t const tfMPTCanLock = lsfMPTCanLock; -constexpr std::uint32_t const tfMPTRequireAuth = lsfMPTRequireAuth; -constexpr std::uint32_t const tfMPTCanEscrow = lsfMPTCanEscrow; -constexpr std::uint32_t const tfMPTCanTrade = lsfMPTCanTrade; -constexpr std::uint32_t const tfMPTCanTransfer = lsfMPTCanTransfer; -constexpr std::uint32_t const tfMPTCanClawback = lsfMPTCanClawback; -constexpr std::uint32_t const tfMPTokenIssuanceCreateMask = - ~(tfUniversal | tfMPTCanLock | tfMPTRequireAuth | tfMPTCanEscrow | tfMPTCanTrade | tfMPTCanTransfer | tfMPTCanClawback); - -// MPTokenIssuanceCreate MutableFlags: -// Indicating specific fields or flags may be changed after issuance. -constexpr std::uint32_t const tmfMPTCanMutateCanLock = lsmfMPTCanMutateCanLock; -constexpr std::uint32_t const tmfMPTCanMutateRequireAuth = lsmfMPTCanMutateRequireAuth; -constexpr std::uint32_t const tmfMPTCanMutateCanEscrow = lsmfMPTCanMutateCanEscrow; -constexpr std::uint32_t const tmfMPTCanMutateCanTrade = lsmfMPTCanMutateCanTrade; -constexpr std::uint32_t const tmfMPTCanMutateCanTransfer = lsmfMPTCanMutateCanTransfer; -constexpr std::uint32_t const tmfMPTCanMutateCanClawback = lsmfMPTCanMutateCanClawback; -constexpr std::uint32_t const tmfMPTCanMutateMetadata = lsmfMPTCanMutateMetadata; -constexpr std::uint32_t const tmfMPTCanMutateTransferFee = lsmfMPTCanMutateTransferFee; -constexpr std::uint32_t const tmfMPTokenIssuanceCreateMutableMask = - ~(tmfMPTCanMutateCanLock | tmfMPTCanMutateRequireAuth | tmfMPTCanMutateCanEscrow | tmfMPTCanMutateCanTrade - | tmfMPTCanMutateCanTransfer | tmfMPTCanMutateCanClawback | tmfMPTCanMutateMetadata | tmfMPTCanMutateTransferFee); - -// MPTokenAuthorize flags: -constexpr std::uint32_t const tfMPTUnauthorize = 0x00000001; -constexpr std::uint32_t const tfMPTokenAuthorizeMask = ~(tfUniversal | tfMPTUnauthorize); - -// MPTokenIssuanceSet flags: -constexpr std::uint32_t const tfMPTLock = 0x00000001; -constexpr std::uint32_t const tfMPTUnlock = 0x00000002; -constexpr std::uint32_t const tfMPTokenIssuanceSetMask = ~(tfUniversal | tfMPTLock | tfMPTUnlock); -constexpr std::uint32_t const tfMPTokenIssuanceSetPermissionMask = ~(tfUniversal | tfMPTLock | tfMPTUnlock); - -// MPTokenIssuanceSet MutableFlags: -// Set or Clear flags. -constexpr std::uint32_t const tmfMPTSetCanLock = 0x00000001; -constexpr std::uint32_t const tmfMPTClearCanLock = 0x00000002; -constexpr std::uint32_t const tmfMPTSetRequireAuth = 0x00000004; -constexpr std::uint32_t const tmfMPTClearRequireAuth = 0x00000008; -constexpr std::uint32_t const tmfMPTSetCanEscrow = 0x00000010; -constexpr std::uint32_t const tmfMPTClearCanEscrow = 0x00000020; -constexpr std::uint32_t const tmfMPTSetCanTrade = 0x00000040; -constexpr std::uint32_t const tmfMPTClearCanTrade = 0x00000080; -constexpr std::uint32_t const tmfMPTSetCanTransfer = 0x00000100; -constexpr std::uint32_t const tmfMPTClearCanTransfer = 0x00000200; -constexpr std::uint32_t const tmfMPTSetCanClawback = 0x00000400; -constexpr std::uint32_t const tmfMPTClearCanClawback = 0x00000800; -constexpr std::uint32_t const tmfMPTokenIssuanceSetMutableMask = ~(tmfMPTSetCanLock | tmfMPTClearCanLock | - tmfMPTSetRequireAuth | tmfMPTClearRequireAuth | tmfMPTSetCanEscrow | tmfMPTClearCanEscrow | - tmfMPTSetCanTrade | tmfMPTClearCanTrade | tmfMPTSetCanTransfer | tmfMPTClearCanTransfer | - tmfMPTSetCanClawback | tmfMPTClearCanClawback); - -// MPTokenIssuanceDestroy flags: -constexpr std::uint32_t const tfMPTokenIssuanceDestroyMask = ~tfUniversal; - -// 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. This was enabled by -// minting the NFToken with the tfTrustLine flag set. +// XMACRO parameters: +// - TRANSACTION: handles the transaction name, its flags, and mask adjustment +// - TF_FLAG: defines a new flag constant +// - TF_FLAG2: references an existing flag constant (no new definition) +// - MASK_ADJ: specifies flags to add back to the mask (making them invalid for this tx type) // -// That capability could be used to attack the NFToken issuer. It -// would be possible for two accounts to trade the NFToken back and forth -// building up any number of TrustLines on the issuer, increasing the -// issuer's reserve without bound. +// Note: MASK_ADJ is used when a universal flag should be invalid for a specific transaction. +// For example, Batch uses MASK_ADJ(tfInnerBatchTxn) because the outer Batch transaction +// must not have tfInnerBatchTxn set (only inner transactions should have it). // -// The fixRemoveNFTokenAutoTrustLine amendment disables minting with the -// tfTrustLine flag as a way to prevent the attack. But until the -// amendment passes we still need to keep the old behavior available. -constexpr std::uint32_t const tfNFTokenMintMask = - ~(tfUniversal | tfBurnable | tfOnlyXRP | tfTransferable); - -constexpr std::uint32_t const tfNFTokenMintOldMask = - ~( ~tfNFTokenMintMask | tfTrustLine); - -// if featureDynamicNFT enabled then new flag allowing mutable URI available. -constexpr std::uint32_t const tfNFTokenMintOldMaskWithMutable = - ~( ~tfNFTokenMintOldMask | tfMutable); - -constexpr std::uint32_t const tfNFTokenMintMaskWithMutable = - ~( ~tfNFTokenMintMask | tfMutable); - -// NFTokenCreateOffer flags: -constexpr std::uint32_t const tfSellNFToken = 0x00000001; -constexpr std::uint32_t const tfNFTokenCreateOfferMask = - ~(tfUniversal | tfSellNFToken); - -// NFTokenCancelOffer flags: -constexpr std::uint32_t const tfNFTokenCancelOfferMask = ~tfUniversal; - -// NFTokenAcceptOffer flags: -constexpr std::uint32_t const tfNFTokenAcceptOfferMask = ~tfUniversal; - -// Clawback flags: -constexpr std::uint32_t const tfClawbackMask = ~tfUniversal; - -// AMM Flags: -constexpr std::uint32_t tfLPToken = 0x00010000; -constexpr std::uint32_t tfWithdrawAll = 0x00020000; -constexpr std::uint32_t tfOneAssetWithdrawAll = 0x00040000; -constexpr std::uint32_t tfSingleAsset = 0x00080000; -constexpr std::uint32_t tfTwoAsset = 0x00100000; -constexpr std::uint32_t tfOneAssetLPToken = 0x00200000; -constexpr std::uint32_t tfLimitLPToken = 0x00400000; -constexpr std::uint32_t tfTwoAssetIfEmpty = 0x00800000; -constexpr std::uint32_t tfWithdrawSubTx = - tfLPToken | tfSingleAsset | tfTwoAsset | tfOneAssetLPToken | - tfLimitLPToken | tfWithdrawAll | tfOneAssetWithdrawAll; -constexpr std::uint32_t tfDepositSubTx = - tfLPToken | tfSingleAsset | tfTwoAsset | tfOneAssetLPToken | - tfLimitLPToken | tfTwoAssetIfEmpty; -constexpr std::uint32_t tfWithdrawMask = ~(tfUniversal | tfWithdrawSubTx); -constexpr std::uint32_t tfDepositMask = ~(tfUniversal | tfDepositSubTx); - -// AMMClawback flags: -constexpr std::uint32_t tfClawTwoAssets = 0x00000001; -constexpr std::uint32_t tfAMMClawbackMask = ~(tfUniversal | tfClawTwoAssets); - -// BridgeModify flags: -constexpr std::uint32_t tfClearAccountCreateAmount = 0x00010000; -constexpr std::uint32_t tfBridgeModifyMask = ~(tfUniversal | tfClearAccountCreateAmount); - -// VaultCreate flags: -constexpr std::uint32_t const tfVaultPrivate = 0x00010000; -static_assert(tfVaultPrivate == lsfVaultPrivate); -constexpr std::uint32_t const tfVaultShareNonTransferable = 0x00020000; -constexpr std::uint32_t const tfVaultCreateMask = ~(tfUniversal | tfVaultPrivate | tfVaultShareNonTransferable); - -// Batch Flags: -constexpr std::uint32_t tfAllOrNothing = 0x00010000; -constexpr std::uint32_t tfOnlyOne = 0x00020000; -constexpr std::uint32_t tfUntilFailure = 0x00040000; -constexpr std::uint32_t tfIndependent = 0x00080000; -/** - * @note If nested Batch transactions are supported in the future, the tfInnerBatchTxn flag - * will need to be removed from this mask to allow Batch transaction to be inside - * the sfRawTransactions array. - */ -constexpr std::uint32_t const tfBatchMask = - ~(tfUniversal | tfAllOrNothing | tfOnlyOne | tfUntilFailure | tfIndependent) | tfInnerBatchTxn; - -// LoanSet and LoanPay flags: -// LoanSet: True, indicates the loan supports overpayments -// LoanPay: True, indicates any excess in this payment can be used -// as an overpayment. False, no overpayments will be taken. -constexpr std::uint32_t const tfLoanOverpayment = 0x00010000; -// LoanPay exclusive flags: -// tfLoanFullPayment: True, indicates that the payment is an early -// full payment. It must pay the entire loan including close -// interest and fees, or it will fail. False: Not a full payment. -constexpr std::uint32_t const tfLoanFullPayment = 0x00020000; -// tfLoanLatePayment: True, indicates that the payment is late, -// and includes late interest and fees. If the loan is not late, -// it will fail. False: not a late payment. If the current payment -// is overdue, the transaction will fail. -constexpr std::uint32_t const tfLoanLatePayment = 0x00040000; -constexpr std::uint32_t const tfLoanSetMask = ~(tfUniversal | - tfLoanOverpayment); -constexpr std::uint32_t const tfLoanPayMask = ~(tfUniversal | - tfLoanOverpayment | tfLoanFullPayment | tfLoanLatePayment); - -// LoanManage flags: -constexpr std::uint32_t const tfLoanDefault = 0x00010000; -constexpr std::uint32_t const tfLoanImpair = 0x00020000; -constexpr std::uint32_t const tfLoanUnimpair = 0x00040000; -constexpr std::uint32_t const tfLoanManageMask = ~(tfUniversal | tfLoanDefault | tfLoanImpair | tfLoanUnimpair); +// TODO: Consider rewriting this using reflection in C++26 or later. Alternatively this could be a DSL processed by a script at build time. +#define XMACRO(TRANSACTION, TF_FLAG, TF_FLAG2, MASK_ADJ) \ + TRANSACTION(AccountSet, \ + TF_FLAG(tfRequireDestTag, 0x00010000) \ + TF_FLAG(tfOptionalDestTag, 0x00020000) \ + TF_FLAG(tfRequireAuth, 0x00040000) \ + TF_FLAG(tfOptionalAuth, 0x00080000) \ + TF_FLAG(tfDisallowXRP, 0x00100000) \ + TF_FLAG(tfAllowXRP, 0x00200000), \ + MASK_ADJ(0)) \ + \ + TRANSACTION(OfferCreate, \ + TF_FLAG(tfPassive, 0x00010000) \ + TF_FLAG(tfImmediateOrCancel, 0x00020000) \ + TF_FLAG(tfFillOrKill, 0x00040000) \ + TF_FLAG(tfSell, 0x00080000) \ + TF_FLAG(tfHybrid, 0x00100000), \ + MASK_ADJ(0)) \ + \ + TRANSACTION(Payment, \ + TF_FLAG(tfNoRippleDirect, 0x00010000) \ + TF_FLAG(tfPartialPayment, 0x00020000) \ + TF_FLAG(tfLimitQuality, 0x00040000), \ + MASK_ADJ(0)) \ + \ + TRANSACTION(TrustSet, \ + TF_FLAG(tfSetfAuth, 0x00010000) \ + TF_FLAG(tfSetNoRipple, 0x00020000) \ + TF_FLAG(tfClearNoRipple, 0x00040000) \ + TF_FLAG(tfSetFreeze, 0x00100000) \ + TF_FLAG(tfClearFreeze, 0x00200000) \ + TF_FLAG(tfSetDeepFreeze, 0x00400000) \ + TF_FLAG(tfClearDeepFreeze, 0x00800000), \ + MASK_ADJ(0)) \ + \ + TRANSACTION(EnableAmendment, \ + TF_FLAG(tfGotMajority, 0x00010000) \ + TF_FLAG(tfLostMajority, 0x00020000), \ + MASK_ADJ(0)) \ + \ + TRANSACTION(PaymentChannelClaim, \ + TF_FLAG(tfRenew, 0x00010000) \ + TF_FLAG(tfClose, 0x00020000), \ + MASK_ADJ(0)) \ + \ + TRANSACTION(NFTokenMint, \ + TF_FLAG(tfBurnable, 0x00000001) \ + TF_FLAG(tfOnlyXRP, 0x00000002) \ + /* deprecated TF_FLAG(tfTrustLine, 0x00000004) */ \ + TF_FLAG(tfTransferable, 0x00000008) \ + TF_FLAG(tfMutable, 0x00000010), \ + MASK_ADJ(0)) \ + \ + TRANSACTION(MPTokenIssuanceCreate, \ + /* Note: tf/lsfMPTLocked is intentionally omitted since this transaction is not allowed to modify it. */ \ + TF_FLAG(tfMPTCanLock, lsfMPTCanLock) \ + TF_FLAG(tfMPTRequireAuth, lsfMPTRequireAuth) \ + TF_FLAG(tfMPTCanEscrow, lsfMPTCanEscrow) \ + TF_FLAG(tfMPTCanTrade, lsfMPTCanTrade) \ + TF_FLAG(tfMPTCanTransfer, lsfMPTCanTransfer) \ + TF_FLAG(tfMPTCanClawback, lsfMPTCanClawback), \ + MASK_ADJ(0)) \ + \ + TRANSACTION(MPTokenAuthorize, \ + TF_FLAG(tfMPTUnauthorize, 0x00000001), \ + MASK_ADJ(0)) \ + \ + TRANSACTION(MPTokenIssuanceSet, \ + TF_FLAG(tfMPTLock, 0x00000001) \ + TF_FLAG(tfMPTUnlock, 0x00000002), \ + MASK_ADJ(0)) \ + \ + TRANSACTION(NFTokenCreateOffer, \ + TF_FLAG(tfSellNFToken, 0x00000001), \ + MASK_ADJ(0)) \ + \ + TRANSACTION(AMMDeposit, \ + TF_FLAG(tfLPToken, 0x00010000) \ + TF_FLAG(tfSingleAsset, 0x00080000) \ + TF_FLAG(tfTwoAsset, 0x00100000) \ + TF_FLAG(tfOneAssetLPToken, 0x00200000) \ + TF_FLAG(tfLimitLPToken, 0x00400000) \ + TF_FLAG(tfTwoAssetIfEmpty, 0x00800000), \ + MASK_ADJ(0)) \ + \ + TRANSACTION(AMMWithdraw, \ + TF_FLAG2(tfLPToken, 0x00010000) \ + TF_FLAG(tfWithdrawAll, 0x00020000) \ + TF_FLAG(tfOneAssetWithdrawAll, 0x00040000) \ + TF_FLAG2(tfSingleAsset, 0x00080000) \ + TF_FLAG2(tfTwoAsset, 0x00100000) \ + TF_FLAG2(tfOneAssetLPToken, 0x00200000) \ + TF_FLAG2(tfLimitLPToken, 0x00400000), \ + MASK_ADJ(0)) \ + \ + TRANSACTION(AMMClawback, \ + TF_FLAG(tfClawTwoAssets, 0x00000001), \ + MASK_ADJ(0)) \ + \ + TRANSACTION(XChainModifyBridge, \ + TF_FLAG(tfClearAccountCreateAmount, 0x00010000), \ + MASK_ADJ(0)) \ + \ + TRANSACTION(VaultCreate, \ + TF_FLAG(tfVaultPrivate, lsfVaultPrivate) \ + TF_FLAG(tfVaultShareNonTransferable, 0x00020000), \ + MASK_ADJ(0)) \ + \ + TRANSACTION(Batch, \ + TF_FLAG(tfAllOrNothing, 0x00010000) \ + TF_FLAG(tfOnlyOne, 0x00020000) \ + TF_FLAG(tfUntilFailure, 0x00040000) \ + TF_FLAG(tfIndependent, 0x00080000), \ + MASK_ADJ(tfInnerBatchTxn)) /* Batch must reject tfInnerBatchTxn - only inner transactions should have this flag */ \ + \ + TRANSACTION(LoanSet, /* True indicates the loan supports overpayments */ \ + TF_FLAG(tfLoanOverpayment, 0x00010000), \ + MASK_ADJ(0)) \ + \ + TRANSACTION(LoanPay, /* True indicates any excess in this payment can be used as an overpayment. */ \ + /* False: no overpayments will be taken. */ \ + TF_FLAG2(tfLoanOverpayment, 0x00010000) \ + TF_FLAG(tfLoanFullPayment, 0x00020000) /* True indicates that the payment is an early full payment. */ \ + /* It must pay the entire loan including close interest and fees, or it will fail. */ \ + /* False: Not a full payment. */ \ + TF_FLAG(tfLoanLatePayment, 0x00040000), /* True indicates that the payment is late, and includes late interest and fees. */ \ + /* If the loan is not late, it will fail. */ \ + /* False: not a late payment. If the current payment is overdue, the transaction will fail.*/ \ + MASK_ADJ(0)) \ + \ + TRANSACTION(LoanManage, \ + TF_FLAG(tfLoanDefault, 0x00010000) \ + TF_FLAG(tfLoanImpair, 0x00020000) \ + TF_FLAG(tfLoanUnimpair, 0x00040000), \ + MASK_ADJ(0)) // clang-format on +// Create all the flag values. +// +// example: +// inline constexpr FlagValue tfAccountSetRequireDestTag = 0x00010000; +#define TO_VALUE(name, value) inline constexpr FlagValue name = value; +#define NULL_NAME(name, values, maskAdj) values +#define NULL_OUTPUT(name, value) +#define NULL_MASK_ADJ(value) +XMACRO(NULL_NAME, TO_VALUE, NULL_OUTPUT, NULL_MASK_ADJ) + +// Create masks for each transaction type that has flags. +// +// example: +// inline constexpr FlagValue tfAccountSetMask = ~(tfUniversal | tfRequireDestTag | +// tfOptionalDestTag | tfRequireAuth | tfOptionalAuth | tfDisallowXRP | tfAllowXRP); +// +// The mask adjustment (maskAdj) allows adding flags back to the mask, making them invalid. +// For example, Batch uses MASK_ADJ(tfInnerBatchTxn) to reject tfInnerBatchTxn on outer Batch. +#define TO_MASK(name, values, maskAdj) \ + inline constexpr FlagValue tf##name##Mask = ~(tfUniversal values) | maskAdj; +#define VALUE_TO_MASK(name, value) | name +#define MASK_ADJ_TO_MASK(value) value +XMACRO(TO_MASK, VALUE_TO_MASK, VALUE_TO_MASK, MASK_ADJ_TO_MASK) + +// Verify that tfBatchMask correctly rejects tfInnerBatchTxn. +// The outer Batch transaction must NOT have tfInnerBatchTxn set; only inner transactions should +// have it. +static_assert( + (tfBatchMask & tfInnerBatchTxn) == tfInnerBatchTxn, + "tfBatchMask must include tfInnerBatchTxn to reject it on outer Batch"); + +// Verify that other transaction masks correctly allow tfInnerBatchTxn. +// Inner transactions need tfInnerBatchTxn to be valid, so these masks must not reject it. +static_assert( + (tfPaymentMask & tfInnerBatchTxn) == 0, + "tfPaymentMask must not reject tfInnerBatchTxn"); +static_assert( + (tfAccountSetMask & tfInnerBatchTxn) == 0, + "tfAccountSetMask must not reject tfInnerBatchTxn"); + +// Create getter functions for each set of flags using Meyer's singleton pattern. +// This avoids static initialization order fiasco while still providing efficient access. +// This is used below in `getAllTxFlags()` to generate the server_definitions RPC +// output. +// +// example: +// inline FlagMap const& getAccountSetFlags() { +// static FlagMap const flags = { +// {"tfRequireDestTag", 0x00010000}, +// {"tfOptionalDestTag", 0x00020000}, +// ...}; +// return flags; +// } +using FlagMap = std::map; +#define VALUE_TO_MAP(name, value) {#name, value}, +#define TO_MAP(name, values, maskAdj) \ + inline FlagMap const& get##name##Flags() \ + { \ + static FlagMap const flags = {values}; \ + return flags; \ + } +XMACRO(TO_MAP, VALUE_TO_MAP, VALUE_TO_MAP, NULL_MASK_ADJ) + +inline FlagMap const& +getUniversalFlags() +{ + static FlagMap const flags = { + {"tfFullyCanonicalSig", tfFullyCanonicalSig}, {"tfInnerBatchTxn", tfInnerBatchTxn}}; + return flags; +} + +// Create a getter function for all transaction flag maps using Meyer's singleton pattern. +// This is used to generate the server_definitions RPC output. +// +// example: +// inline FlagMapPairList const& getAllTxFlags() { +// static FlagMapPairList const flags = { +// {"AccountSet", getAccountSetFlags()}, +// ...}; +// return flags; +// } +using FlagMapPairList = std::vector>; +#define ALL_TX_FLAGS(name, values, maskAdj) {#name, get##name##Flags()}, +inline FlagMapPairList const& +getAllTxFlags() +{ + static FlagMapPairList const flags = { + {"universal", getUniversalFlags()}, + XMACRO(ALL_TX_FLAGS, NULL_OUTPUT, NULL_OUTPUT, NULL_MASK_ADJ)}; + return flags; +} + +#undef XMACRO +#undef TO_VALUE +#undef VALUE_TO_MAP +#undef NULL_NAME +#undef NULL_OUTPUT +#undef TO_MAP +#undef TO_MASK +#undef VALUE_TO_MASK +#undef ALL_TX_FLAGS +#undef NULL_MASK_ADJ +#undef MASK_ADJ_TO_MASK + +#pragma pop_macro("XMACRO") +#pragma pop_macro("TO_VALUE") +#pragma pop_macro("VALUE_TO_MAP") +#pragma pop_macro("NULL_NAME") +#pragma pop_macro("NULL_OUTPUT") +#pragma pop_macro("TO_MAP") +#pragma pop_macro("TO_MASK") +#pragma pop_macro("VALUE_TO_MASK") +#pragma pop_macro("ALL_TX_FLAGS") +#pragma pop_macro("NULL_MASK_ADJ") +#pragma pop_macro("MASK_ADJ_TO_MASK") + +// Additional transaction masks and combos +inline constexpr FlagValue tfMPTPaymentMask = ~(tfUniversal | tfPartialPayment); +inline constexpr FlagValue tfTrustSetPermissionMask = + ~(tfUniversal | tfSetfAuth | tfSetFreeze | tfClearFreeze); + +// 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 tmfMPTCanMutateMetadata = lsmfMPTCanMutateMetadata; +inline constexpr FlagValue tmfMPTCanMutateTransferFee = lsmfMPTCanMutateTransferFee; +inline constexpr FlagValue tmfMPTokenIssuanceCreateMutableMask = + ~(tmfMPTCanMutateCanLock | tmfMPTCanMutateRequireAuth | tmfMPTCanMutateCanEscrow | + tmfMPTCanMutateCanTrade | tmfMPTCanMutateCanTransfer | tmfMPTCanMutateCanClawback | + tmfMPTCanMutateMetadata | tmfMPTCanMutateTransferFee); + +// MPTokenIssuanceSet MutableFlags: +// Set or Clear flags. + +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); + +// 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. +// This was enabled by minting the NFToken with the tfTrustLine flag set. +// +// That capability could be used to attack the NFToken issuer. +// It would be possible for two accounts to trade the NFToken back and forth building up any number +// of TrustLines on the issuer, increasing the issuer's reserve without bound. +// +// The fixRemoveNFTokenAutoTrustLine amendment disables minting with the tfTrustLine flag as a way +// to prevent the attack. But until the amendment passes we still need to keep the old behavior +// available. +inline constexpr FlagValue tfTrustLine = 0x00000004; // needed for backwards compatibility +inline constexpr FlagValue tfNFTokenMintMaskWithoutMutable = + ~(tfUniversal | tfBurnable | tfOnlyXRP | tfTransferable); + +inline constexpr FlagValue tfNFTokenMintOldMask = ~(~tfNFTokenMintMaskWithoutMutable | tfTrustLine); + +// if featureDynamicNFT enabled then new flag allowing mutable URI available. +inline constexpr FlagValue tfNFTokenMintOldMaskWithMutable = ~(~tfNFTokenMintOldMask | tfMutable); + +inline constexpr FlagValue tfWithdrawSubTx = tfLPToken | tfSingleAsset | tfTwoAsset | + tfOneAssetLPToken | tfLimitLPToken | tfWithdrawAll | tfOneAssetWithdrawAll; +inline constexpr FlagValue tfDepositSubTx = + tfLPToken | tfSingleAsset | tfTwoAsset | tfOneAssetLPToken | tfLimitLPToken | tfTwoAssetIfEmpty; + +#pragma push_macro("ACCOUNTSET_FLAGS") +#pragma push_macro("ACCOUNTSET_FLAG_TO_VALUE") +#pragma push_macro("ACCOUNTSET_FLAG_TO_MAP") + +// AccountSet SetFlag/ClearFlag values +#define ACCOUNTSET_FLAGS(ASF_FLAG) \ + ASF_FLAG(asfRequireDest, 1) \ + ASF_FLAG(asfRequireAuth, 2) \ + ASF_FLAG(asfDisallowXRP, 3) \ + ASF_FLAG(asfDisableMaster, 4) \ + ASF_FLAG(asfAccountTxnID, 5) \ + ASF_FLAG(asfNoFreeze, 6) \ + ASF_FLAG(asfGlobalFreeze, 7) \ + ASF_FLAG(asfDefaultRipple, 8) \ + ASF_FLAG(asfDepositAuth, 9) \ + ASF_FLAG(asfAuthorizedNFTokenMinter, 10) \ + /* 11 is reserved for Hooks amendment */ \ + /* ASF_FLAG(asfTshCollect, 11) */ \ + ASF_FLAG(asfDisallowIncomingNFTokenOffer, 12) \ + ASF_FLAG(asfDisallowIncomingCheck, 13) \ + ASF_FLAG(asfDisallowIncomingPayChan, 14) \ + ASF_FLAG(asfDisallowIncomingTrustline, 15) \ + ASF_FLAG(asfAllowTrustLineClawback, 16) \ + ASF_FLAG(asfAllowTrustLineLocking, 17) + +#define ACCOUNTSET_FLAG_TO_VALUE(name, value) inline constexpr FlagValue name = value; +#define ACCOUNTSET_FLAG_TO_MAP(name, value) {#name, value}, + +ACCOUNTSET_FLAGS(ACCOUNTSET_FLAG_TO_VALUE) + +inline std::map const& +getAsfFlagMap() +{ + static std::map const flags = { + ACCOUNTSET_FLAGS(ACCOUNTSET_FLAG_TO_MAP)}; + return flags; +} + +#undef ACCOUNTSET_FLAG_TO_VALUE +#undef ACCOUNTSET_FLAG_TO_MAP +#undef ACCOUNTSET_FLAGS + +#pragma pop_macro("ACCOUNTSET_FLAG_TO_VALUE") +#pragma pop_macro("ACCOUNTSET_FLAG_TO_MAP") +#pragma pop_macro("ACCOUNTSET_FLAGS") + } // namespace xrpl diff --git a/include/xrpl/protocol/TxFormats.h b/include/xrpl/protocol/TxFormats.h index b76eef7585..563a28f39f 100644 --- a/include/xrpl/protocol/TxFormats.h +++ b/include/xrpl/protocol/TxFormats.h @@ -2,6 +2,8 @@ #include +#include + namespace xrpl { /** Transaction type identifiers. @@ -73,6 +75,9 @@ private: public: static TxFormats const& getInstance(); + + static std::vector const& + getCommonFields(); }; } // namespace xrpl diff --git a/include/xrpl/protocol/jss.h b/include/xrpl/protocol/jss.h index 0264f625eb..5c605dde04 100644 --- a/include/xrpl/protocol/jss.h +++ b/include/xrpl/protocol/jss.h @@ -25,6 +25,7 @@ namespace jss { JSS(AL_size); // out: GetCounts JSS(AL_hit_rate); // out: GetCounts JSS(AcceptedCredentials); // out: AccountObjects +JSS(ACCOUNT_SET_FLAGS); // out: RPC server_definitions JSS(Account); // in: TransactionSign; field. JSS(AMMID); // field JSS(Amount); // in: TransactionSign; field. @@ -187,6 +188,7 @@ JSS(closed_ledger); // out: NetworkOPs JSS(cluster); // out: PeerImp JSS(code); // out: errors JSS(command); // in: RPCHandler +JSS(common); // out: RPC server_definitions JSS(complete); // out: NetworkOPs, InboundLedger JSS(complete_ledgers); // out: NetworkOPs, PeerImp JSS(consensus); // out: NetworkOPs, LedgerConsensus @@ -356,6 +358,8 @@ JSS(ledger_min); // in, out: AccountTx* JSS(ledger_time); // out: NetworkOPs JSS(LEDGER_ENTRY_TYPES); // out: RPC server_definitions // matches definitions.json format +JSS(LEDGER_ENTRY_FLAGS); // out: RPC server_definitions +JSS(LEDGER_ENTRY_FORMATS); // out: RPC server_definitions JSS(levels); // LogLevels JSS(limit); // in/out: AccountTx*, AccountOffers, // AccountLines, AccountObjects @@ -457,6 +461,7 @@ JSS(open); // out: handlers/Ledger JSS(open_ledger_cost); // out: SubmitTransaction JSS(open_ledger_fee); // out: TxQ JSS(open_ledger_level); // out: TxQ +JSS(optionality); // out: server_definitions JSS(oracles); // in: get_aggregate_price JSS(oracle_document_id); // in: get_aggregate_price JSS(owner); // in: LedgerEntry, out: NetworkOPs @@ -616,6 +621,8 @@ JSS(TRANSACTION_RESULTS); // out: RPC server_definitions // matches definitions.json format JSS(TRANSACTION_TYPES); // out: RPC server_definitions // matches definitions.json format +JSS(TRANSACTION_FLAGS); // out: RPC server_definitions +JSS(TRANSACTION_FORMATS); // out: RPC server_definitions JSS(TYPES); // out: RPC server_definitions // matches definitions.json format JSS(transfer_rate); // out: nft_info (clio) diff --git a/include/xrpl/tx/transactors/Clawback.h b/include/xrpl/tx/transactors/Clawback.h index 7451266461..a795115f7a 100644 --- a/include/xrpl/tx/transactors/Clawback.h +++ b/include/xrpl/tx/transactors/Clawback.h @@ -13,9 +13,6 @@ public: { } - static std::uint32_t - getFlagsMask(PreflightContext const& ctx); - static NotTEC preflight(PreflightContext const& ctx); diff --git a/include/xrpl/tx/transactors/MPT/MPTokenIssuanceDestroy.h b/include/xrpl/tx/transactors/MPT/MPTokenIssuanceDestroy.h index c4a448032a..416708565a 100644 --- a/include/xrpl/tx/transactors/MPT/MPTokenIssuanceDestroy.h +++ b/include/xrpl/tx/transactors/MPT/MPTokenIssuanceDestroy.h @@ -13,9 +13,6 @@ public: { } - static std::uint32_t - getFlagsMask(PreflightContext const& ctx); - static NotTEC preflight(PreflightContext const& ctx); diff --git a/include/xrpl/tx/transactors/NFT/NFTokenAcceptOffer.h b/include/xrpl/tx/transactors/NFT/NFTokenAcceptOffer.h index d876a70362..60962fc9ca 100644 --- a/include/xrpl/tx/transactors/NFT/NFTokenAcceptOffer.h +++ b/include/xrpl/tx/transactors/NFT/NFTokenAcceptOffer.h @@ -26,9 +26,6 @@ public: { } - static std::uint32_t - getFlagsMask(PreflightContext const& ctx); - static NotTEC preflight(PreflightContext const& ctx); diff --git a/include/xrpl/tx/transactors/NFT/NFTokenCancelOffer.h b/include/xrpl/tx/transactors/NFT/NFTokenCancelOffer.h index bb8cd4c216..6d60a0bebd 100644 --- a/include/xrpl/tx/transactors/NFT/NFTokenCancelOffer.h +++ b/include/xrpl/tx/transactors/NFT/NFTokenCancelOffer.h @@ -13,9 +13,6 @@ public: { } - static std::uint32_t - getFlagsMask(PreflightContext const& ctx); - static NotTEC preflight(PreflightContext const& ctx); diff --git a/src/libxrpl/protocol/LedgerFormats.cpp b/src/libxrpl/protocol/LedgerFormats.cpp index 2056cfab7b..30725f44a9 100644 --- a/src/libxrpl/protocol/LedgerFormats.cpp +++ b/src/libxrpl/protocol/LedgerFormats.cpp @@ -3,19 +3,23 @@ #include #include -#include +#include namespace xrpl { -LedgerFormats::LedgerFormats() +std::vector const& +LedgerFormats::getCommonFields() { - // Fields shared by all ledger formats: - static std::initializer_list const commonFields{ + static auto const commonFields = std::vector{ {sfLedgerIndex, soeOPTIONAL}, {sfLedgerEntryType, soeREQUIRED}, {sfFlags, soeREQUIRED}, }; + return commonFields; +} +LedgerFormats::LedgerFormats() +{ #pragma push_macro("UNWRAP") #undef UNWRAP #pragma push_macro("LEDGER_ENTRY") @@ -23,7 +27,7 @@ LedgerFormats::LedgerFormats() #define UNWRAP(...) __VA_ARGS__ #define LEDGER_ENTRY(tag, value, name, rpcName, fields) \ - add(jss::name, tag, UNWRAP fields, commonFields); + add(jss::name, tag, UNWRAP fields, getCommonFields()); #include diff --git a/src/libxrpl/protocol/SOTemplate.cpp b/src/libxrpl/protocol/SOTemplate.cpp index 46edf98710..b90bff6192 100644 --- a/src/libxrpl/protocol/SOTemplate.cpp +++ b/src/libxrpl/protocol/SOTemplate.cpp @@ -2,23 +2,32 @@ #include #include +#include #include #include +#include #include +#include namespace xrpl { SOTemplate::SOTemplate( std::initializer_list uniqueFields, std::initializer_list commonFields) + : SOTemplate(std::vector(uniqueFields), std::vector(commonFields)) +{ +} + +SOTemplate::SOTemplate(std::vector uniqueFields, std::vector commonFields) : indices_(SField::getNumFields() + 1, -1) // Unmapped indices == -1 { // Add all SOElements. - elements_.reserve(uniqueFields.size() + commonFields.size()); - elements_.assign(uniqueFields); - elements_.insert(elements_.end(), commonFields); + // + elements_ = std::move(uniqueFields); + std::ranges::move(commonFields, std::back_inserter(elements_)); // Validate and index elements_. + // for (std::size_t i = 0; i < elements_.size(); ++i) { SField const& sField{elements_[i].sField()}; diff --git a/src/libxrpl/protocol/TxFormats.cpp b/src/libxrpl/protocol/TxFormats.cpp index 00a560de1b..4492ae271b 100644 --- a/src/libxrpl/protocol/TxFormats.cpp +++ b/src/libxrpl/protocol/TxFormats.cpp @@ -3,14 +3,14 @@ #include #include -#include +#include namespace xrpl { -TxFormats::TxFormats() +std::vector const& +TxFormats::getCommonFields() { - // Fields shared by all txFormats: - static std::initializer_list const commonFields{ + static auto const commonFields = std::vector{ {sfTransactionType, soeREQUIRED}, {sfFlags, soeOPTIONAL}, {sfSourceTag, soeOPTIONAL}, @@ -29,7 +29,11 @@ TxFormats::TxFormats() {sfNetworkID, soeOPTIONAL}, {sfDelegate, soeOPTIONAL}, }; + return commonFields; +} +TxFormats::TxFormats() +{ #pragma push_macro("UNWRAP") #undef UNWRAP #pragma push_macro("TRANSACTION") @@ -37,7 +41,7 @@ TxFormats::TxFormats() #define UNWRAP(...) __VA_ARGS__ #define TRANSACTION(tag, value, name, delegable, amendment, privileges, fields) \ - add(jss::name, tag, UNWRAP fields, commonFields); + add(jss::name, tag, UNWRAP fields, getCommonFields()); #include diff --git a/src/libxrpl/tx/transactors/AMM/AMMDeposit.cpp b/src/libxrpl/tx/transactors/AMM/AMMDeposit.cpp index ba48295851..dad1bc536a 100644 --- a/src/libxrpl/tx/transactors/AMM/AMMDeposit.cpp +++ b/src/libxrpl/tx/transactors/AMM/AMMDeposit.cpp @@ -19,7 +19,7 @@ std::uint32_t AMMDeposit::getFlagsMask(PreflightContext const& ctx) { - return tfDepositMask; + return tfAMMDepositMask; } NotTEC diff --git a/src/libxrpl/tx/transactors/AMM/AMMWithdraw.cpp b/src/libxrpl/tx/transactors/AMM/AMMWithdraw.cpp index 3c974ee4a4..f58dbaefc6 100644 --- a/src/libxrpl/tx/transactors/AMM/AMMWithdraw.cpp +++ b/src/libxrpl/tx/transactors/AMM/AMMWithdraw.cpp @@ -17,7 +17,7 @@ AMMWithdraw::checkExtraFeatures(PreflightContext const& ctx) std::uint32_t AMMWithdraw::getFlagsMask(PreflightContext const& ctx) { - return tfWithdrawMask; + return tfAMMWithdrawMask; } NotTEC diff --git a/src/libxrpl/tx/transactors/Change.cpp b/src/libxrpl/tx/transactors/Change.cpp index 315aeb89bd..9ae6504718 100644 --- a/src/libxrpl/tx/transactors/Change.cpp +++ b/src/libxrpl/tx/transactors/Change.cpp @@ -16,11 +16,11 @@ NotTEC Transactor::invokePreflight(PreflightContext const& ctx) { // 0 means "Allow any flags" - // The check for tfChangeMask is gated by LendingProtocol because that - // feature introduced this parameter, and it's not worth adding another + // The check for tfEnableAmendmentMask is gated by LendingProtocol because + // that feature introduced this parameter, and it's not worth adding another // amendment just for this. if (auto const ret = - preflight0(ctx, ctx.rules.enabled(featureLendingProtocol) ? tfChangeMask : 0)) + preflight0(ctx, ctx.rules.enabled(featureLendingProtocol) ? tfEnableAmendmentMask : 0)) return ret; auto account = ctx.tx.getAccountID(sfAccount); diff --git a/src/libxrpl/tx/transactors/Clawback.cpp b/src/libxrpl/tx/transactors/Clawback.cpp index ac13cc10da..789017e3ec 100644 --- a/src/libxrpl/tx/transactors/Clawback.cpp +++ b/src/libxrpl/tx/transactors/Clawback.cpp @@ -54,12 +54,6 @@ preflightHelper(PreflightContext const& ctx) return tesSUCCESS; } -std::uint32_t -Clawback::getFlagsMask(PreflightContext const& ctx) -{ - return tfClawbackMask; -} - NotTEC Clawback::preflight(PreflightContext const& ctx) { diff --git a/src/libxrpl/tx/transactors/MPT/MPTokenIssuanceDestroy.cpp b/src/libxrpl/tx/transactors/MPT/MPTokenIssuanceDestroy.cpp index acdd004bae..4b4d7c53f5 100644 --- a/src/libxrpl/tx/transactors/MPT/MPTokenIssuanceDestroy.cpp +++ b/src/libxrpl/tx/transactors/MPT/MPTokenIssuanceDestroy.cpp @@ -5,12 +5,6 @@ namespace xrpl { -std::uint32_t -MPTokenIssuanceDestroy::getFlagsMask(PreflightContext const& ctx) -{ - return tfMPTokenIssuanceDestroyMask; -} - NotTEC MPTokenIssuanceDestroy::preflight(PreflightContext const& ctx) { diff --git a/src/libxrpl/tx/transactors/MPT/MPTokenIssuanceSet.cpp b/src/libxrpl/tx/transactors/MPT/MPTokenIssuanceSet.cpp index bacd4585d7..2ae6bde083 100644 --- a/src/libxrpl/tx/transactors/MPT/MPTokenIssuanceSet.cpp +++ b/src/libxrpl/tx/transactors/MPT/MPTokenIssuanceSet.cpp @@ -130,7 +130,7 @@ MPTokenIssuanceSet::checkPermission(ReadView const& view, STTx const& tx) // this is added in case more flags will be added for MPTokenIssuanceSet // in the future. Currently unreachable. - if (txFlags & tfMPTokenIssuanceSetPermissionMask) + if (txFlags & tfMPTokenIssuanceSetMask) return terNO_DELEGATE_PERMISSION; // LCOV_EXCL_LINE std::unordered_set granularPermissions; diff --git a/src/libxrpl/tx/transactors/NFT/NFTokenAcceptOffer.cpp b/src/libxrpl/tx/transactors/NFT/NFTokenAcceptOffer.cpp index 45668d4273..90a28a2e7c 100644 --- a/src/libxrpl/tx/transactors/NFT/NFTokenAcceptOffer.cpp +++ b/src/libxrpl/tx/transactors/NFT/NFTokenAcceptOffer.cpp @@ -7,12 +7,6 @@ namespace xrpl { -std::uint32_t -NFTokenAcceptOffer::getFlagsMask(PreflightContext const& ctx) -{ - return tfNFTokenAcceptOfferMask; -} - NotTEC NFTokenAcceptOffer::preflight(PreflightContext const& ctx) { diff --git a/src/libxrpl/tx/transactors/NFT/NFTokenCancelOffer.cpp b/src/libxrpl/tx/transactors/NFT/NFTokenCancelOffer.cpp index 9a3e36b7c8..ce2cdf83c5 100644 --- a/src/libxrpl/tx/transactors/NFT/NFTokenCancelOffer.cpp +++ b/src/libxrpl/tx/transactors/NFT/NFTokenCancelOffer.cpp @@ -8,12 +8,6 @@ namespace xrpl { -std::uint32_t -NFTokenCancelOffer::getFlagsMask(PreflightContext const& ctx) -{ - return tfNFTokenCancelOfferMask; -} - NotTEC NFTokenCancelOffer::preflight(PreflightContext const& ctx) { diff --git a/src/libxrpl/tx/transactors/NFT/NFTokenMint.cpp b/src/libxrpl/tx/transactors/NFT/NFTokenMint.cpp index d8c063571b..a32a659bbd 100644 --- a/src/libxrpl/tx/transactors/NFT/NFTokenMint.cpp +++ b/src/libxrpl/tx/transactors/NFT/NFTokenMint.cpp @@ -48,9 +48,8 @@ NFTokenMint::getFlagsMask(PreflightContext const& ctx) // tfTrustLine flag as a way to prevent the attack. But until the // amendment passes we still need to keep the old behavior available. std::uint32_t const nfTokenMintMask = ctx.rules.enabled(fixRemoveNFTokenAutoTrustLine) - // if featureDynamicNFT enabled then new flag allowing mutable URI - // available - ? ctx.rules.enabled(featureDynamicNFT) ? tfNFTokenMintMaskWithMutable : tfNFTokenMintMask + // if featureDynamicNFT enabled then new flag allowing mutable URI available + ? ctx.rules.enabled(featureDynamicNFT) ? tfNFTokenMintMask : tfNFTokenMintMaskWithoutMutable : ctx.rules.enabled(featureDynamicNFT) ? tfNFTokenMintOldMaskWithMutable : tfNFTokenMintOldMask; diff --git a/src/libxrpl/tx/transactors/PayChan.cpp b/src/libxrpl/tx/transactors/PayChan.cpp index 3b29169af6..265030bebf 100644 --- a/src/libxrpl/tx/transactors/PayChan.cpp +++ b/src/libxrpl/tx/transactors/PayChan.cpp @@ -378,7 +378,7 @@ PayChanClaim::checkExtraFeatures(PreflightContext const& ctx) std::uint32_t PayChanClaim::getFlagsMask(PreflightContext const&) { - return tfPayChanClaimMask; + return tfPaymentChannelClaimMask; } NotTEC diff --git a/src/libxrpl/tx/transactors/XChainBridge.cpp b/src/libxrpl/tx/transactors/XChainBridge.cpp index 64daa6d1ee..44a1974f93 100644 --- a/src/libxrpl/tx/transactors/XChainBridge.cpp +++ b/src/libxrpl/tx/transactors/XChainBridge.cpp @@ -1448,7 +1448,7 @@ XChainCreateBridge::doApply() std::uint32_t BridgeModify::getFlagsMask(PreflightContext const& ctx) { - return tfBridgeModifyMask; + return tfXChainModifyBridgeMask; } NotTEC diff --git a/src/test/rpc/AccountSet_test.cpp b/src/test/app/AccountSet_test.cpp similarity index 99% rename from src/test/rpc/AccountSet_test.cpp rename to src/test/app/AccountSet_test.cpp index d342bf366a..87c6474be2 100644 --- a/src/test/rpc/AccountSet_test.cpp +++ b/src/test/app/AccountSet_test.cpp @@ -575,6 +575,6 @@ public: } }; -BEAST_DEFINE_TESTSUITE_PRIO(AccountSet, rpc, xrpl, 1); +BEAST_DEFINE_TESTSUITE_PRIO(AccountSet, app, xrpl, 1); } // namespace xrpl diff --git a/src/test/app/Batch_test.cpp b/src/test/app/Batch_test.cpp index bd1d701381..f429299f01 100644 --- a/src/test/app/Batch_test.cpp +++ b/src/test/app/Batch_test.cpp @@ -4355,6 +4355,7 @@ public: run() override { using namespace test::jtx; + auto const sa = testable_amendments(); testWithFeats(sa - fixBatchInnerSigs); testWithFeats(sa); diff --git a/src/test/rpc/ServerDefinitions_test.cpp b/src/test/rpc/ServerDefinitions_test.cpp index 69533939cd..a2c45cfa65 100644 --- a/src/test/rpc/ServerDefinitions_test.cpp +++ b/src/test/rpc/ServerDefinitions_test.cpp @@ -1,6 +1,9 @@ #include #include +#include +#include +#include #include namespace xrpl { @@ -81,43 +84,371 @@ public: BEAST_EXPECT(types["Hash384"].asUInt() == 22); BEAST_EXPECT(types["Hash512"].asUInt() == 23); } - } - // test providing the same hash - { - Env env(*this); - auto const firstResult = env.rpc("server_definitions"); - auto const hash = firstResult[jss::result][jss::hash].asString(); - auto const hashParam = std::string("{ ") + "\"hash\": \"" + hash + "\"}"; + // test the properties of the LEDGER_ENTRY_FLAGS section + { + BEAST_EXPECT(result[jss::result].isMember(jss::LEDGER_ENTRY_FLAGS)); + Json::Value const& leFlags = result[jss::result][jss::LEDGER_ENTRY_FLAGS]; - auto const result = env.rpc("json", "server_definitions", hashParam); - BEAST_EXPECT(!result[jss::result].isMember(jss::error)); - BEAST_EXPECT(result[jss::result][jss::status] == "success"); - BEAST_EXPECT(!result[jss::result].isMember(jss::FIELDS)); - BEAST_EXPECT(!result[jss::result].isMember(jss::LEDGER_ENTRY_TYPES)); - BEAST_EXPECT(!result[jss::result].isMember(jss::TRANSACTION_RESULTS)); - BEAST_EXPECT(!result[jss::result].isMember(jss::TRANSACTION_TYPES)); - BEAST_EXPECT(!result[jss::result].isMember(jss::TYPES)); - BEAST_EXPECT(result[jss::result].isMember(jss::hash)); - } + // sanity test the mapped value of a few arbitrarily chosen flags + BEAST_EXPECT(leFlags["AccountRoot"]["lsfDisallowXRP"] == 0x00080000); + BEAST_EXPECT(leFlags["AccountRoot"]["lsfDepositAuth"] == 0x01000000); + BEAST_EXPECT(leFlags["AccountRoot"]["lsfAllowTrustLineClawback"] == 0x80000000); - // test providing a different hash - { - Env env(*this); - std::string const hash = - "54296160385A27154BFA70A239DD8E8FD4CC2DB7BA32D970BA3A5B132CF749" - "D1"; - auto const hashParam = std::string("{ ") + "\"hash\": \"" + hash + "\"}"; + BEAST_EXPECT(leFlags["RippleState"]["lsfHighFreeze"] == 0x00800000); + BEAST_EXPECT(leFlags["RippleState"]["lsfAMMNode"] == 0x01000000); - auto const result = env.rpc("json", "server_definitions", hashParam); - BEAST_EXPECT(!result[jss::result].isMember(jss::error)); - BEAST_EXPECT(result[jss::result][jss::status] == "success"); - BEAST_EXPECT(result[jss::result].isMember(jss::FIELDS)); - BEAST_EXPECT(result[jss::result].isMember(jss::LEDGER_ENTRY_TYPES)); - BEAST_EXPECT(result[jss::result].isMember(jss::TRANSACTION_RESULTS)); - BEAST_EXPECT(result[jss::result].isMember(jss::TRANSACTION_TYPES)); - BEAST_EXPECT(result[jss::result].isMember(jss::TYPES)); - BEAST_EXPECT(result[jss::result].isMember(jss::hash)); + BEAST_EXPECT(leFlags["DirNode"]["lsfNFTokenBuyOffers"] == 0x00000001); + BEAST_EXPECT(leFlags["MPTokenIssuance"]["lsfMPTCanTrade"] == 0x00000010); + BEAST_EXPECT(leFlags["Credential"]["lsfAccepted"] == 0x00010000); + BEAST_EXPECT(leFlags["Loan"]["lsfLoanImpaired"] == 0x00020000); + BEAST_EXPECT(leFlags["Vault"]["lsfVaultPrivate"] == 0x00010000); + BEAST_EXPECT(leFlags["MPToken"]["lsfMPTAuthorized"] == 0x00000002); + } + + // validate the correctness of few chosen transaction flags + { + BEAST_EXPECT(result[jss::result].isMember(jss::TRANSACTION_FLAGS)); + Json::Value const& txFlags = result[jss::result][jss::TRANSACTION_FLAGS]; + + BEAST_EXPECT(txFlags["universal"]["tfFullyCanonicalSig"] == 0x80000000); + BEAST_EXPECT(txFlags["universal"]["tfInnerBatchTxn"] == 0x40000000); + + BEAST_EXPECT(txFlags["AccountSet"]["tfRequireAuth"] == 0x00040000); + BEAST_EXPECT(txFlags["AccountSet"]["tfAllowXRP"] == 0x00200000); + + BEAST_EXPECT(txFlags["MPTokenIssuanceSet"]["tfMPTLock"] == 0x00000001); + BEAST_EXPECT(txFlags["MPTokenIssuanceSet"]["tfMPTUnlock"] == 0x00000002); + + BEAST_EXPECT(txFlags["AMMDeposit"]["tfLPToken"] == 0x00010000); + BEAST_EXPECT(txFlags["AMMDeposit"]["tfLimitLPToken"] == 0x00400000); + } + + // validate the correctness of the AccountSpecificFlags section + { + BEAST_EXPECT(result[jss::result].isMember(jss::ACCOUNT_SET_FLAGS)); + Json::Value const& asFlags = result[jss::result][jss::ACCOUNT_SET_FLAGS]; + + BEAST_EXPECT(asFlags["asfDisallowXRP"] == 3); + BEAST_EXPECT(asFlags["asfGlobalFreeze"] == 7); + BEAST_EXPECT(asFlags["asfDisallowIncomingNFTokenOffer"] == 12); + BEAST_EXPECT(asFlags["asfDisallowIncomingTrustline"] == 15); + } + + // test the response fields of the TRANSACTION_FORMATS section + { + BEAST_EXPECT(result[jss::result].isMember(jss::TRANSACTION_FORMATS)); + Json::Value const& txnFormats = result[jss::result][jss::TRANSACTION_FORMATS]; + + // first validate the contents of "common" + { + BEAST_EXPECT(txnFormats.isMember("common")); + Json::Value const& section = txnFormats["common"]; + + BEAST_EXPECT(section[0u][jss::name] == "TransactionType"); + BEAST_EXPECT(section[0u][jss::optionality] == soeREQUIRED); + + BEAST_EXPECT(section[1u][jss::name] == "Flags"); + BEAST_EXPECT(section[1u][jss::optionality] == soeOPTIONAL); + + BEAST_EXPECT(section[2u][jss::name] == "SourceTag"); + BEAST_EXPECT(section[2u][jss::optionality] == soeOPTIONAL); + + BEAST_EXPECT(section[3u][jss::name] == "Account"); + BEAST_EXPECT(section[3u][jss::optionality] == soeREQUIRED); + + BEAST_EXPECT(section[4u][jss::name] == "Sequence"); + BEAST_EXPECT(section[4u][jss::optionality] == soeREQUIRED); + + BEAST_EXPECT(section[5u][jss::name] == "PreviousTxnID"); + BEAST_EXPECT(section[5u][jss::optionality] == soeOPTIONAL); + + BEAST_EXPECT(section[6u][jss::name] == "LastLedgerSequence"); + BEAST_EXPECT(section[6u][jss::optionality] == soeOPTIONAL); + + BEAST_EXPECT(section[7u][jss::name] == "AccountTxnID"); + BEAST_EXPECT(section[7u][jss::optionality] == soeOPTIONAL); + + BEAST_EXPECT(section[8u][jss::name] == "Fee"); + BEAST_EXPECT(section[8u][jss::optionality] == soeREQUIRED); + + BEAST_EXPECT(section[9u][jss::name] == "OperationLimit"); + BEAST_EXPECT(section[9u][jss::optionality] == soeOPTIONAL); + + BEAST_EXPECT(section[10u][jss::name] == "Memos"); + BEAST_EXPECT(section[10u][jss::optionality] == soeOPTIONAL); + + BEAST_EXPECT(section[11u][jss::name] == "SigningPubKey"); + BEAST_EXPECT(section[11u][jss::optionality] == soeREQUIRED); + + BEAST_EXPECT(section[12u][jss::name] == "TicketSequence"); + BEAST_EXPECT(section[12u][jss::optionality] == soeOPTIONAL); + + BEAST_EXPECT(section[13u][jss::name] == "TxnSignature"); + BEAST_EXPECT(section[13u][jss::optionality] == soeOPTIONAL); + + BEAST_EXPECT(section[14u][jss::name] == "Signers"); + BEAST_EXPECT(section[14u][jss::optionality] == soeOPTIONAL); + + BEAST_EXPECT(section[15u][jss::name] == "NetworkID"); + BEAST_EXPECT(section[15u][jss::optionality] == soeOPTIONAL); + + BEAST_EXPECT(section[16u][jss::name] == "Delegate"); + BEAST_EXPECT(section[16u][jss::optionality] == soeOPTIONAL); + } + + // validate the contents of four arbitrarily selected transactions validate the + // format of the OracleSet transaction + { + BEAST_EXPECT(txnFormats.isMember("OracleSet")); + Json::Value const& section = txnFormats["OracleSet"]; + + BEAST_EXPECT(section[0u][jss::name] == "OracleDocumentID"); + BEAST_EXPECT(section[0u][jss::optionality] == soeREQUIRED); + + BEAST_EXPECT(section[1u][jss::name] == "Provider"); + BEAST_EXPECT(section[1u][jss::optionality] == soeOPTIONAL); + + BEAST_EXPECT(section[2u][jss::name] == "URI"); + BEAST_EXPECT(section[2u][jss::optionality] == soeOPTIONAL); + + BEAST_EXPECT(section[3u][jss::name] == "AssetClass"); + BEAST_EXPECT(section[3u][jss::optionality] == soeOPTIONAL); + + BEAST_EXPECT(section[4u][jss::name] == "LastUpdateTime"); + BEAST_EXPECT(section[4u][jss::optionality] == soeREQUIRED); + + BEAST_EXPECT(section[5u][jss::name] == "PriceDataSeries"); + BEAST_EXPECT(section[5u][jss::optionality] == soeREQUIRED); + } + + // validate the format of the PermissionedDomainDelete transaction + { + BEAST_EXPECT(txnFormats.isMember("PermissionedDomainDelete")); + Json::Value const& section = txnFormats["PermissionedDomainDelete"]; + + BEAST_EXPECT(section[0u][jss::name] == "DomainID"); + BEAST_EXPECT(section[0u][jss::optionality] == soeREQUIRED); + } + + // validate the format of the Clawback transaction + { + BEAST_EXPECT(txnFormats.isMember("Clawback")); + Json::Value const& section = txnFormats["Clawback"]; + + BEAST_EXPECT(section[0u][jss::name] == "Amount"); + BEAST_EXPECT(section[0u][jss::optionality] == soeREQUIRED); + + BEAST_EXPECT(section[1u][jss::name] == "Holder"); + BEAST_EXPECT(section[1u][jss::optionality] == soeOPTIONAL); + } + + // validate the format of the SetFee transaction + { + BEAST_EXPECT(txnFormats.isMember("SetFee")); + Json::Value const& section = txnFormats["SetFee"]; + + BEAST_EXPECT(section[0u][jss::name] == "LedgerSequence"); + BEAST_EXPECT(section[0u][jss::optionality] == soeOPTIONAL); + + BEAST_EXPECT(section[1u][jss::name] == "BaseFee"); + BEAST_EXPECT(section[1u][jss::optionality] == soeOPTIONAL); + + BEAST_EXPECT(section[2u][jss::name] == "ReferenceFeeUnits"); + BEAST_EXPECT(section[2u][jss::optionality] == soeOPTIONAL); + + BEAST_EXPECT(section[3u][jss::name] == "ReserveBase"); + BEAST_EXPECT(section[3u][jss::optionality] == soeOPTIONAL); + + BEAST_EXPECT(section[4u][jss::name] == "ReserveIncrement"); + BEAST_EXPECT(section[4u][jss::optionality] == soeOPTIONAL); + + BEAST_EXPECT(section[5u][jss::name] == "BaseFeeDrops"); + BEAST_EXPECT(section[5u][jss::optionality] == soeOPTIONAL); + + BEAST_EXPECT(section[6u][jss::name] == "ReserveBaseDrops"); + BEAST_EXPECT(section[6u][jss::optionality] == soeOPTIONAL); + + BEAST_EXPECT(section[7u][jss::name] == "ReserveIncrementDrops"); + BEAST_EXPECT(section[7u][jss::optionality] == soeOPTIONAL); + } + } + + // test the properties of the LEDGER_ENTRY_FORMATS section in server_definitions + // response + { + BEAST_EXPECT(result[jss::result].isMember(jss::LEDGER_ENTRY_FORMATS)); + + // Note: For the purposes of software maintenance, this test does not exhaustively + // validate all the LEDGER_ENTRY_FORMATS + + // check "common" first + { + Json::Value const& observedCommonLedgerEntry = + result[jss::result][jss::LEDGER_ENTRY_FORMATS]["common"]; + + BEAST_EXPECT(observedCommonLedgerEntry[0u][jss::name] == "LedgerIndex"); + BEAST_EXPECT(observedCommonLedgerEntry[0u][jss::optionality] == soeOPTIONAL); + + BEAST_EXPECT(observedCommonLedgerEntry[1u][jss::name] == "LedgerEntryType"); + BEAST_EXPECT(observedCommonLedgerEntry[1u][jss::optionality] == soeREQUIRED); + + BEAST_EXPECT(observedCommonLedgerEntry[2u][jss::name] == "Flags"); + BEAST_EXPECT(observedCommonLedgerEntry[2u][jss::optionality] == soeREQUIRED); + } + + // test the contents of an arbitrary ledger-entry (DID) + { + Json::Value const& observedDIDLedgerEntry = + result[jss::result][jss::LEDGER_ENTRY_FORMATS]["DID"]; + + BEAST_EXPECT(observedDIDLedgerEntry[0u][jss::name] == "Account"); + BEAST_EXPECT(observedDIDLedgerEntry[0u][jss::optionality] == soeREQUIRED); + + BEAST_EXPECT(observedDIDLedgerEntry[1u][jss::name] == "DIDDocument"); + BEAST_EXPECT(observedDIDLedgerEntry[1u][jss::optionality] == soeOPTIONAL); + + BEAST_EXPECT(observedDIDLedgerEntry[2u][jss::name] == "URI"); + BEAST_EXPECT(observedDIDLedgerEntry[2u][jss::optionality] == soeOPTIONAL); + + BEAST_EXPECT(observedDIDLedgerEntry[3u][jss::name] == "Data"); + BEAST_EXPECT(observedDIDLedgerEntry[3u][jss::optionality] == soeOPTIONAL); + + BEAST_EXPECT(observedDIDLedgerEntry[4u][jss::name] == "OwnerNode"); + BEAST_EXPECT(observedDIDLedgerEntry[4u][jss::optionality] == soeREQUIRED); + + BEAST_EXPECT(observedDIDLedgerEntry[5u][jss::name] == "PreviousTxnID"); + BEAST_EXPECT(observedDIDLedgerEntry[5u][jss::optionality] == soeREQUIRED); + + BEAST_EXPECT(observedDIDLedgerEntry[6u][jss::name] == "PreviousTxnLgrSeq"); + BEAST_EXPECT(observedDIDLedgerEntry[6u][jss::optionality] == soeREQUIRED); + } + + // test the contents of an arbitrary ledger-entry (NegativeUNL) + { + Json::Value const& observedNunlLedgerEntry = + result[jss::result][jss::LEDGER_ENTRY_FORMATS]["NegativeUNL"]; + + BEAST_EXPECT(observedNunlLedgerEntry[0u][jss::name] == "DisabledValidators"); + BEAST_EXPECT(observedNunlLedgerEntry[0u][jss::optionality] == soeOPTIONAL); + + BEAST_EXPECT(observedNunlLedgerEntry[1u][jss::name] == "ValidatorToDisable"); + BEAST_EXPECT(observedNunlLedgerEntry[1u][jss::optionality] == soeOPTIONAL); + + BEAST_EXPECT(observedNunlLedgerEntry[2u][jss::name] == "ValidatorToReEnable"); + BEAST_EXPECT(observedNunlLedgerEntry[2u][jss::optionality] == soeOPTIONAL); + + BEAST_EXPECT(observedNunlLedgerEntry[3u][jss::name] == "PreviousTxnID"); + BEAST_EXPECT(observedNunlLedgerEntry[3u][jss::optionality] == soeOPTIONAL); + + BEAST_EXPECT(observedNunlLedgerEntry[4u][jss::name] == "PreviousTxnLgrSeq"); + BEAST_EXPECT(observedNunlLedgerEntry[4u][jss::optionality] == soeOPTIONAL); + } + } + + // Exhaustive test: verify all transaction flags from getAllTxFlags() appear in the + // output + { + Json::Value const& txFlags = result[jss::result][jss::TRANSACTION_FLAGS]; + + for (auto const& [txName, flagMap] : getAllTxFlags()) + { + BEAST_EXPECT(txFlags.isMember(txName)); + if (txFlags.isMember(txName)) + { + for (auto const& [flagName, flagValue] : flagMap) + { + BEAST_EXPECT(txFlags[txName].isMember(flagName)); + if (txFlags[txName].isMember(flagName)) + { + BEAST_EXPECT(txFlags[txName][flagName].asUInt() == flagValue); + } + } + } + } + } + + // Exhaustive test: verify all ledger entry flags from getAllLedgerFlags() appear in the + // output + { + Json::Value const& leFlags = result[jss::result][jss::LEDGER_ENTRY_FLAGS]; + + for (auto const& [ledgerType, flagMap] : getAllLedgerFlags()) + { + BEAST_EXPECT(leFlags.isMember(ledgerType)); + if (leFlags.isMember(ledgerType)) + { + for (auto const& [flagName, flagValue] : flagMap) + { + BEAST_EXPECT(leFlags[ledgerType].isMember(flagName)); + if (leFlags[ledgerType].isMember(flagName)) + { + BEAST_EXPECT(leFlags[ledgerType][flagName].asUInt() == flagValue); + } + } + } + } + } + + // Exhaustive test: verify all AccountSet flags from getAsfFlagMap() appear in the + // output + { + Json::Value const& asFlags = result[jss::result][jss::ACCOUNT_SET_FLAGS]; + + for (auto const& [flagName, flagValue] : getAsfFlagMap()) + { + BEAST_EXPECT(asFlags.isMember(flagName)); + if (asFlags.isMember(flagName)) + { + BEAST_EXPECT(asFlags[flagName].asInt() == flagValue); + } + } + } + + // test providing the same hash + { + Env env(*this); + auto const firstResult = env.rpc("server_definitions"); + auto const hash = firstResult[jss::result][jss::hash].asString(); + auto const hashParam = std::string("{ ") + "\"hash\": \"" + hash + "\"}"; + + auto const result = env.rpc("json", "server_definitions", hashParam); + BEAST_EXPECT(!result[jss::result].isMember(jss::error)); + BEAST_EXPECT(result[jss::result][jss::status] == "success"); + BEAST_EXPECT(!result[jss::result].isMember(jss::FIELDS)); + BEAST_EXPECT(!result[jss::result].isMember(jss::LEDGER_ENTRY_TYPES)); + BEAST_EXPECT(!result[jss::result].isMember(jss::LEDGER_ENTRY_FLAGS)); + BEAST_EXPECT(!result[jss::result].isMember(jss::LEDGER_ENTRY_FORMATS)); + BEAST_EXPECT(!result[jss::result].isMember(jss::TRANSACTION_RESULTS)); + BEAST_EXPECT(!result[jss::result].isMember(jss::TRANSACTION_TYPES)); + BEAST_EXPECT(!result[jss::result].isMember(jss::TRANSACTION_FLAGS)); + BEAST_EXPECT(!result[jss::result].isMember(jss::TRANSACTION_FORMATS)); + BEAST_EXPECT(!result[jss::result].isMember(jss::TYPES)); + BEAST_EXPECT(result[jss::result].isMember(jss::hash)); + } + + // test providing a different hash + { + Env env(*this); + std::string const hash = + "54296160385A27154BFA70A239DD8E8FD4CC2DB7BA32D970BA3A5B132CF749" + "D1"; + auto const hashParam = std::string("{ ") + "\"hash\": \"" + hash + "\"}"; + + auto const result = env.rpc("json", "server_definitions", hashParam); + BEAST_EXPECT(!result[jss::result].isMember(jss::error)); + BEAST_EXPECT(result[jss::result][jss::status] == "success"); + BEAST_EXPECT(result[jss::result].isMember(jss::FIELDS)); + BEAST_EXPECT(result[jss::result].isMember(jss::LEDGER_ENTRY_TYPES)); + BEAST_EXPECT(result[jss::result].isMember(jss::LEDGER_ENTRY_FLAGS)); + BEAST_EXPECT(result[jss::result].isMember(jss::LEDGER_ENTRY_FORMATS)); + BEAST_EXPECT(result[jss::result].isMember(jss::TRANSACTION_RESULTS)); + BEAST_EXPECT(result[jss::result].isMember(jss::TRANSACTION_TYPES)); + BEAST_EXPECT(result[jss::result].isMember(jss::TRANSACTION_FLAGS)); + BEAST_EXPECT(result[jss::result].isMember(jss::TRANSACTION_FORMATS)); + BEAST_EXPECT(result[jss::result].isMember(jss::TYPES)); + BEAST_EXPECT(result[jss::result].isMember(jss::hash)); + } } } diff --git a/src/xrpld/rpc/handlers/ServerDefinitions.cpp b/src/xrpld/rpc/handlers/ServerDefinitions.cpp index ea1912f66e..e153065ea9 100644 --- a/src/xrpld/rpc/handlers/ServerDefinitions.cpp +++ b/src/xrpld/rpc/handlers/ServerDefinitions.cpp @@ -6,12 +6,15 @@ #include #include #include +#include #include #include #include #include +#include +#include #include namespace xrpl { @@ -47,13 +50,14 @@ public: std::string ServerDefinitions::translate(std::string const& inp) { - auto replace = [&](char const* oldStr, char const* newStr) -> std::string { + auto replace = [&](std::string_view oldStr, std::string_view newStr) -> std::string { std::string out = inp; boost::replace_all(out, oldStr, newStr); return out; }; - auto contains = [&](char const* s) -> bool { return inp.find(s) != std::string::npos; }; + // TODO: use string::contains with C++23 + auto contains = [&](std::string_view s) -> bool { return inp.find(s) != std::string::npos; }; if (contains("UINT")) { @@ -64,7 +68,7 @@ ServerDefinitions::translate(std::string const& inp) return replace("UINT", "UInt"); } - std::unordered_map replacements{ + static std::unordered_map const replacements{ {"OBJECT", "STObject"}, {"ARRAY", "STArray"}, {"ACCOUNT", "AccountID"}, @@ -77,7 +81,7 @@ ServerDefinitions::translate(std::string const& inp) if (auto const& it = replacements.find(inp); it != replacements.end()) { - return it->second; + return std::string(it->second); } std::string out; @@ -211,36 +215,35 @@ ServerDefinitions::ServerDefinitions() : defs_{Json::objectValue} defs_[jss::FIELDS][i++] = a; } - for (auto const& [code, f] : xrpl::SField::getKnownCodeToField()) + for (auto const& [code, field] : xrpl::SField::getKnownCodeToField()) { - if (f->fieldName == "") + if (field->fieldName == "") continue; Json::Value innerObj = Json::objectValue; - uint32_t type = f->fieldType; + uint32_t type = field->fieldType; - innerObj[jss::nth] = f->fieldValue; + innerObj[jss::nth] = field->fieldValue; - // whether the field is variable-length encoded - // this means that the length is included before the content + // whether the field is variable-length encoded this means that the length is included + // before the content innerObj[jss::isVLEncoded] = - (type == 7U /* Blob */ || type == 8U /* AccountID */ || - type == 19U /* Vector256 */); + (type == 7U /* Blob */ || type == 8U /* AccountID */ || type == 19U /* Vector256 */); // whether the field is included in serialization innerObj[jss::isSerialized] = - (type < 10000 && f->fieldName != "hash" && - f->fieldName != "index"); /* hash, index, TRANSACTION, - LEDGER_ENTRY, VALIDATION, METADATA */ + (type < 10000 && field->fieldName != "hash" && + field->fieldName != + "index"); // hash, index, TRANSACTION, LEDGER_ENTRY, VALIDATION, METADATA // whether the field is included in serialization when signing - innerObj[jss::isSigningField] = f->shouldInclude(false); + innerObj[jss::isSigningField] = field->shouldInclude(false); innerObj[jss::type] = typeMap[type]; Json::Value innerArray = Json::arrayValue; - innerArray[0U] = f->fieldName; + innerArray[0U] = field->fieldName; innerArray[1U] = innerObj; defs_[jss::FIELDS][i++] = innerArray; @@ -262,6 +265,92 @@ ServerDefinitions::ServerDefinitions() : defs_{Json::objectValue} defs_[jss::TRANSACTION_TYPES][f.getName()] = f.getType(); } + // populate TxFormats + defs_[jss::TRANSACTION_FORMATS] = Json::objectValue; + + defs_[jss::TRANSACTION_FORMATS][jss::common] = Json::arrayValue; + auto txCommonFields = std::set(); + for (auto const& element : TxFormats::getCommonFields()) + { + Json::Value elementObj = Json::objectValue; + elementObj[jss::name] = element.sField().getName(); + elementObj[jss::optionality] = element.style(); + defs_[jss::TRANSACTION_FORMATS][jss::common].append(elementObj); + txCommonFields.insert(element.sField().getName()); + } + + for (auto const& format : TxFormats::getInstance()) + { + auto const& soTemplate = format.getSOTemplate(); + Json::Value templateArray = Json::arrayValue; + for (auto const& element : soTemplate) + { + if (txCommonFields.contains(element.sField().getName())) + continue; // skip common fields, already added + Json::Value elementObj = Json::objectValue; + elementObj[jss::name] = element.sField().getName(); + elementObj[jss::optionality] = element.style(); + templateArray.append(elementObj); + } + defs_[jss::TRANSACTION_FORMATS][format.getName()] = templateArray; + } + + // populate LedgerFormats + defs_[jss::LEDGER_ENTRY_FORMATS] = Json::objectValue; + defs_[jss::LEDGER_ENTRY_FORMATS][jss::common] = Json::arrayValue; + auto ledgerCommonFields = std::set(); + for (auto const& element : LedgerFormats::getCommonFields()) + { + Json::Value elementObj = Json::objectValue; + elementObj[jss::name] = element.sField().getName(); + elementObj[jss::optionality] = element.style(); + defs_[jss::LEDGER_ENTRY_FORMATS][jss::common].append(elementObj); + ledgerCommonFields.insert(element.sField().getName()); + } + for (auto const& format : LedgerFormats::getInstance()) + { + auto const& soTemplate = format.getSOTemplate(); + Json::Value templateArray = Json::arrayValue; + for (auto const& element : soTemplate) + { + if (ledgerCommonFields.contains(element.sField().getName())) + continue; // skip common fields, already added + Json::Value elementObj = Json::objectValue; + elementObj[jss::name] = element.sField().getName(); + elementObj[jss::optionality] = element.style(); + templateArray.append(elementObj); + } + defs_[jss::LEDGER_ENTRY_FORMATS][format.getName()] = templateArray; + } + + defs_[jss::TRANSACTION_FLAGS] = Json::objectValue; + for (auto const& [name, value] : getAllTxFlags()) + { + Json::Value txObj = Json::objectValue; + for (auto const& [flagName, flagValue] : value) + { + txObj[flagName] = flagValue; + } + defs_[jss::TRANSACTION_FLAGS][name] = txObj; + } + + defs_[jss::LEDGER_ENTRY_FLAGS] = Json::objectValue; + for (auto const& [name, value] : getAllLedgerFlags()) + { + Json::Value ledgerObj = Json::objectValue; + for (auto const& [flagName, flagValue] : value) + { + ledgerObj[flagName] = flagValue; + } + defs_[jss::LEDGER_ENTRY_FLAGS][name] = ledgerObj; + } + + defs_[jss::ACCOUNT_SET_FLAGS] = Json::objectValue; + for (auto const& [name, value] : getAsfFlagMap()) + { + defs_[jss::ACCOUNT_SET_FLAGS][name] = value; + } + // generate hash { std::string const out = Json::FastWriter().write(defs_); From 0c74270b055133a57a497b5c9fc5a75f7647b1f4 Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Thu, 5 Mar 2026 19:17:43 +0000 Subject: [PATCH 32/41] chore: Use gersemi instead of ancient cmake-format (#6486) --- .cmake-format.yaml | 247 ---------------------------------------- .pre-commit-config.yaml | 7 +- 2 files changed, 3 insertions(+), 251 deletions(-) delete mode 100644 .cmake-format.yaml diff --git a/.cmake-format.yaml b/.cmake-format.yaml deleted file mode 100644 index 40c1101208..0000000000 --- a/.cmake-format.yaml +++ /dev/null @@ -1,247 +0,0 @@ -_help_parse: Options affecting listfile parsing -parse: - _help_additional_commands: - - Specify structure for custom cmake functions - additional_commands: - target_protobuf_sources: - pargs: - - target - - prefix - kwargs: - PROTOS: "*" - LANGUAGE: cpp - IMPORT_DIRS: "*" - GENERATE_EXTENSIONS: "*" - PLUGIN: "*" - _help_override_spec: - - Override configurations per-command where available - override_spec: {} - _help_vartags: - - Specify variable tags. - vartags: [] - _help_proptags: - - Specify property tags. - proptags: [] -_help_format: Options affecting formatting. -format: - _help_disable: - - Disable formatting entirely, making cmake-format a no-op - disable: false - _help_line_width: - - How wide to allow formatted cmake files - line_width: 100 - _help_tab_size: - - How many spaces to tab for indent - tab_size: 4 - _help_use_tabchars: - - If true, lines are indented using tab characters (utf-8 - - 0x09) instead of space characters (utf-8 0x20). - - In cases where the layout would require a fractional tab - - character, the behavior of the fractional indentation is - - governed by - use_tabchars: false - _help_fractional_tab_policy: - - If is True, then the value of this variable - - indicates how fractional indentions are handled during - - whitespace replacement. If set to 'use-space', fractional - - indentation is left as spaces (utf-8 0x20). If set to - - "`round-up` fractional indentation is replaced with a single" - - tab character (utf-8 0x09) effectively shifting the column - - to the next tabstop - fractional_tab_policy: use-space - _help_max_subgroups_hwrap: - - If an argument group contains more than this many sub-groups - - (parg or kwarg groups) then force it to a vertical layout. - max_subgroups_hwrap: 4 - _help_max_pargs_hwrap: - - If a positional argument group contains more than this many - - arguments, then force it to a vertical layout. - max_pargs_hwrap: 5 - _help_max_rows_cmdline: - - If a cmdline positional group consumes more than this many - - lines without nesting, then invalidate the layout (and nest) - max_rows_cmdline: 2 - _help_separate_ctrl_name_with_space: - - If true, separate flow control names from their parentheses - - with a space - separate_ctrl_name_with_space: true - _help_separate_fn_name_with_space: - - If true, separate function names from parentheses with a - - space - separate_fn_name_with_space: false - _help_dangle_parens: - - If a statement is wrapped to more than one line, than dangle - - the closing parenthesis on its own line. - dangle_parens: false - _help_dangle_align: - - If the trailing parenthesis must be 'dangled' on its on - - "line, then align it to this reference: `prefix`: the start" - - "of the statement, `prefix-indent`: the start of the" - - "statement, plus one indentation level, `child`: align to" - - the column of the arguments - dangle_align: prefix - _help_min_prefix_chars: - - If the statement spelling length (including space and - - parenthesis) is smaller than this amount, then force reject - - nested layouts. - min_prefix_chars: 18 - _help_max_prefix_chars: - - If the statement spelling length (including space and - - parenthesis) is larger than the tab width by more than this - - amount, then force reject un-nested layouts. - max_prefix_chars: 10 - _help_max_lines_hwrap: - - If a candidate layout is wrapped horizontally but it exceeds - - this many lines, then reject the layout. - max_lines_hwrap: 2 - _help_line_ending: - - What style line endings to use in the output. - line_ending: unix - _help_command_case: - - Format command names consistently as 'lower' or 'upper' case - command_case: canonical - _help_keyword_case: - - Format keywords consistently as 'lower' or 'upper' case - keyword_case: unchanged - _help_always_wrap: - - A list of command names which should always be wrapped - always_wrap: [] - _help_enable_sort: - - If true, the argument lists which are known to be sortable - - will be sorted lexicographicall - enable_sort: true - _help_autosort: - - If true, the parsers may infer whether or not an argument - - list is sortable (without annotation). - autosort: true - _help_require_valid_layout: - - By default, if cmake-format cannot successfully fit - - everything into the desired linewidth it will apply the - - last, most aggressive attempt that it made. If this flag is - - True, however, cmake-format will print error, exit with non- - - zero status code, and write-out nothing - require_valid_layout: false - _help_layout_passes: - - A dictionary mapping layout nodes to a list of wrap - - decisions. See the documentation for more information. - layout_passes: {} -_help_markup: Options affecting comment reflow and formatting. -markup: - _help_bullet_char: - - What character to use for bulleted lists - bullet_char: "-" - _help_enum_char: - - What character to use as punctuation after numerals in an - - enumerated list - enum_char: . - _help_first_comment_is_literal: - - If comment markup is enabled, don't reflow the first comment - - block in each listfile. Use this to preserve formatting of - - your copyright/license statements. - first_comment_is_literal: false - _help_literal_comment_pattern: - - If comment markup is enabled, don't reflow any comment block - - which matches this (regex) pattern. Default is `None` - - (disabled). - literal_comment_pattern: null - _help_fence_pattern: - - Regular expression to match preformat fences in comments - - default= ``r'^\s*([`~]{3}[`~]*)(.*)$'`` - fence_pattern: ^\s*([`~]{3}[`~]*)(.*)$ - _help_ruler_pattern: - - Regular expression to match rulers in comments default= - - '``r''^\s*[^\w\s]{3}.*[^\w\s]{3}$''``' - ruler_pattern: ^\s*[^\w\s]{3}.*[^\w\s]{3}$ - _help_explicit_trailing_pattern: - - If a comment line matches starts with this pattern then it - - is explicitly a trailing comment for the preceding - - argument. Default is '#<' - explicit_trailing_pattern: "#<" - _help_hashruler_min_length: - - If a comment line starts with at least this many consecutive - - hash characters, then don't lstrip() them off. This allows - - for lazy hash rulers where the first hash char is not - - separated by space - hashruler_min_length: 10 - _help_canonicalize_hashrulers: - - If true, then insert a space between the first hash char and - - remaining hash chars in a hash ruler, and normalize its - - length to fill the column - canonicalize_hashrulers: true - _help_enable_markup: - - enable comment markup parsing and reflow - enable_markup: false -_help_lint: Options affecting the linter -lint: - _help_disabled_codes: - - a list of lint codes to disable - disabled_codes: [] - _help_function_pattern: - - regular expression pattern describing valid function names - function_pattern: "[0-9a-z_]+" - _help_macro_pattern: - - regular expression pattern describing valid macro names - macro_pattern: "[0-9A-Z_]+" - _help_global_var_pattern: - - regular expression pattern describing valid names for - - variables with global (cache) scope - global_var_pattern: "[A-Z][0-9A-Z_]+" - _help_internal_var_pattern: - - regular expression pattern describing valid names for - - variables with global scope (but internal semantic) - internal_var_pattern: _[A-Z][0-9A-Z_]+ - _help_local_var_pattern: - - regular expression pattern describing valid names for - - variables with local scope - local_var_pattern: "[a-z][a-z0-9_]+" - _help_private_var_pattern: - - regular expression pattern describing valid names for - - privatedirectory variables - private_var_pattern: _[0-9a-z_]+ - _help_public_var_pattern: - - regular expression pattern describing valid names for public - - directory variables - public_var_pattern: "[A-Z][0-9A-Z_]+" - _help_argument_var_pattern: - - regular expression pattern describing valid names for - - function/macro arguments and loop variables. - argument_var_pattern: "[a-z][a-z0-9_]+" - _help_keyword_pattern: - - regular expression pattern describing valid names for - - keywords used in functions or macros - keyword_pattern: "[A-Z][0-9A-Z_]+" - _help_max_conditionals_custom_parser: - - In the heuristic for C0201, how many conditionals to match - - within a loop in before considering the loop a parser. - max_conditionals_custom_parser: 2 - _help_min_statement_spacing: - - Require at least this many newlines between statements - min_statement_spacing: 1 - _help_max_statement_spacing: - - Require no more than this many newlines between statements - max_statement_spacing: 2 - max_returns: 6 - max_branches: 12 - max_arguments: 5 - max_localvars: 15 - max_statements: 50 -_help_encode: Options affecting file encoding -encode: - _help_emit_byteorder_mark: - - If true, emit the unicode byte-order mark (BOM) at the start - - of the file - emit_byteorder_mark: false - _help_input_encoding: - - Specify the encoding of the input file. Defaults to utf-8 - input_encoding: utf-8 - _help_output_encoding: - - Specify the encoding of the output file. Defaults to utf-8. - - Note that cmake only claims to support utf-8 so be careful - - when using anything else - output_encoding: utf-8 -_help_misc: Miscellaneous configurations options. -misc: - _help_per_command: - - A dictionary containing any per-command configuration - - overrides. Currently only `command_case` is supported. - per_command: {} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2d0ff63b38..586cfe860c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -26,11 +26,10 @@ repos: args: [--style=file] "types_or": [c++, c, proto] - - repo: https://github.com/cheshirekow/cmake-format-precommit - rev: e2c2116d86a80e72e7146a06e68b7c228afc6319 # frozen: v0.6.13 + - repo: https://github.com/BlankSpruce/gersemi + rev: 0.26.0 hooks: - - id: cmake-format - additional_dependencies: [PyYAML] + - id: gersemi - repo: https://github.com/rbubley/mirrors-prettier rev: c2bc67fe8f8f549cc489e00ba8b45aa18ee713b1 # frozen: v3.8.1 From af0ec7defdac8dde0c32fec58e79f70c6a7fcf77 Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Thu, 5 Mar 2026 19:18:31 +0000 Subject: [PATCH 33/41] chore: Apply gersemi changes (#6486) --- CMakeLists.txt | 97 ++++--- cmake/CMakeFuncs.cmake | 37 +-- cmake/Ccache.cmake | 52 ++-- cmake/CodeCoverage.cmake | 415 ++++++++++++++++++---------- cmake/CompilationEnv.cmake | 30 +- cmake/GitInfo.cmake | 19 +- cmake/XrplAddTest.cmake | 12 +- cmake/XrplCompiler.cmake | 283 ++++++++++--------- cmake/XrplConfig.cmake | 70 ++--- cmake/XrplCore.cmake | 183 ++++++++---- cmake/XrplCov.cmake | 37 +-- cmake/XrplDocs.cmake | 77 +++--- cmake/XrplInstall.cmake | 114 +++++--- cmake/XrplInterface.cmake | 108 ++++---- cmake/XrplSanitizers.cmake | 132 +++++---- cmake/XrplSanity.cmake | 55 ++-- cmake/XrplSettings.cmake | 140 ++++++---- cmake/XrplValidatorKeys.cmake | 28 +- cmake/XrplVersion.cmake | 14 +- cmake/add_module.cmake | 22 +- cmake/create_symbolic_link.cmake | 26 +- cmake/deps/Boost.cmake | 79 +++--- cmake/isolate_headers.cmake | 4 +- cmake/target_link_modules.cmake | 13 +- cmake/target_protobuf_sources.cmake | 35 ++- src/tests/libxrpl/CMakeLists.txt | 9 +- 26 files changed, 1264 insertions(+), 827 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f0d8519327..7ca0798ae4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,16 +1,16 @@ cmake_minimum_required(VERSION 3.16) -if (POLICY CMP0074) +if(POLICY CMP0074) cmake_policy(SET CMP0074 NEW) -endif () -if (POLICY CMP0077) +endif() +if(POLICY CMP0077) cmake_policy(SET CMP0077 NEW) -endif () +endif() # Fix "unrecognized escape" issues when passing CMAKE_MODULE_PATH on Windows. -if (DEFINED CMAKE_MODULE_PATH) +if(DEFINED CMAKE_MODULE_PATH) file(TO_CMAKE_PATH "${CMAKE_MODULE_PATH}" CMAKE_MODULE_PATH) -endif () +endif() list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") project(xrpl) @@ -21,57 +21,64 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON) include(CompilationEnv) -if (is_gcc) +if(is_gcc) # GCC-specific fixes add_compile_options(-Wno-unknown-pragmas -Wno-subobject-linkage) # -Wno-subobject-linkage can be removed when we upgrade GCC version to at least 13.3 -elseif (is_clang) +elseif(is_clang) # Clang-specific fixes add_compile_options(-Wno-unknown-warning-option) # Ignore unknown warning options -elseif (is_msvc) +elseif(is_msvc) # MSVC-specific fixes add_compile_options(/wd4068) # Ignore unknown pragmas -endif () +endif() # Enable ccache to speed up builds. include(Ccache) -if (thread_safety_analysis) - add_compile_options(-Wthread-safety -D_LIBCPP_ENABLE_THREAD_SAFETY_ANNOTATIONS - -DXRPL_ENABLE_THREAD_SAFETY_ANNOTATIONS) +if(thread_safety_analysis) + add_compile_options( + -Wthread-safety + -D_LIBCPP_ENABLE_THREAD_SAFETY_ANNOTATIONS + -DXRPL_ENABLE_THREAD_SAFETY_ANNOTATIONS + ) add_compile_options("-stdlib=libc++") add_link_options("-stdlib=libc++") -endif () +endif() include(CheckCXXCompilerFlag) include(FetchContent) include(ExternalProject) include(CMakeFuncs) # must come *after* ExternalProject b/c it overrides one function in EP -if (target) - message(FATAL_ERROR "The target option has been removed - use native cmake options to control build" +if(target) + message( + FATAL_ERROR + "The target option has been removed - use native cmake options to control build" ) -endif () +endif() include(XrplSanity) include(XrplVersion) include(XrplSettings) # this check has to remain in the top-level cmake because of the early return statement -if (packages_only) - if (NOT TARGET rpm) - message(FATAL_ERROR "packages_only requested, but targets were not created - is docker installed?" +if(packages_only) + if(NOT TARGET rpm) + message( + FATAL_ERROR + "packages_only requested, but targets were not created - is docker installed?" ) - endif () + endif() return() -endif () +endif() include(XrplCompiler) include(XrplSanitizers) include(XrplInterface) option(only_docs "Include only the docs target?" FALSE) include(XrplDocs) -if (only_docs) +if(only_docs) return() -endif () +endif() include(deps/Boost) @@ -90,42 +97,46 @@ find_package(xxHash REQUIRED) target_link_libraries( xrpl_libs - INTERFACE ed25519::ed25519 - lz4::lz4 - OpenSSL::Crypto - OpenSSL::SSL - secp256k1::secp256k1 - soci::soci - SQLite::SQLite3) + INTERFACE + ed25519::ed25519 + lz4::lz4 + OpenSSL::Crypto + OpenSSL::SSL + secp256k1::secp256k1 + soci::soci + SQLite::SQLite3 +) option(rocksdb "Enable RocksDB" ON) -if (rocksdb) +if(rocksdb) find_package(RocksDB REQUIRED) - set_target_properties(RocksDB::rocksdb PROPERTIES INTERFACE_COMPILE_DEFINITIONS - XRPL_ROCKSDB_AVAILABLE=1) + set_target_properties( + RocksDB::rocksdb + PROPERTIES INTERFACE_COMPILE_DEFINITIONS XRPL_ROCKSDB_AVAILABLE=1 + ) target_link_libraries(xrpl_libs INTERFACE RocksDB::rocksdb) -endif () +endif() # Work around changes to Conan recipe for now. -if (TARGET nudb::core) +if(TARGET nudb::core) set(nudb nudb::core) -elseif (TARGET NuDB::nudb) +elseif(TARGET NuDB::nudb) set(nudb NuDB::nudb) -else () +else() message(FATAL_ERROR "unknown nudb target") -endif () +endif() target_link_libraries(xrpl_libs INTERFACE ${nudb}) -if (coverage) +if(coverage) include(XrplCov) -endif () +endif() set(PROJECT_EXPORT_SET XrplExports) include(XrplCore) include(XrplInstall) include(XrplValidatorKeys) -if (tests) +if(tests) include(CTest) add_subdirectory(src/tests/libxrpl) -endif () +endif() diff --git a/cmake/CMakeFuncs.cmake b/cmake/CMakeFuncs.cmake index ec2d9db330..391ed3364c 100644 --- a/cmake/CMakeFuncs.cmake +++ b/cmake/CMakeFuncs.cmake @@ -1,29 +1,32 @@ -macro (exclude_from_default target_) +macro(exclude_from_default target_) set_target_properties(${target_} PROPERTIES EXCLUDE_FROM_ALL ON) set_target_properties(${target_} PROPERTIES EXCLUDE_FROM_DEFAULT_BUILD ON) -endmacro () +endmacro() -macro (exclude_if_included target_) +macro(exclude_if_included target_) get_directory_property(has_parent PARENT_DIRECTORY) - if (has_parent) + if(has_parent) exclude_from_default(${target_}) - endif () -endmacro () + endif() +endmacro() find_package(Git) -function (git_branch branch_val) - if (NOT GIT_FOUND) +function(git_branch branch_val) + if(NOT GIT_FOUND) return() - endif () + endif() set(_branch "") - execute_process(COMMAND ${GIT_EXECUTABLE} "rev-parse" "--abbrev-ref" "HEAD" - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - RESULT_VARIABLE _git_exit_code - OUTPUT_VARIABLE _temp_branch - OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET) - if (_git_exit_code EQUAL 0) + execute_process( + COMMAND ${GIT_EXECUTABLE} "rev-parse" "--abbrev-ref" "HEAD" + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + RESULT_VARIABLE _git_exit_code + OUTPUT_VARIABLE _temp_branch + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET + ) + if(_git_exit_code EQUAL 0) set(_branch ${_temp_branch}) - endif () + endif() set(${branch_val} "${_branch}" PARENT_SCOPE) -endfunction () +endfunction() diff --git a/cmake/Ccache.cmake b/cmake/Ccache.cmake index e34627ad8a..f8972dbb30 100644 --- a/cmake/Ccache.cmake +++ b/cmake/Ccache.cmake @@ -1,50 +1,62 @@ find_program(CCACHE_PATH "ccache") -if (NOT CCACHE_PATH) +if(NOT CCACHE_PATH) return() -endif () +endif() # For Linux and macOS we can use the ccache binary directly. -if (NOT MSVC) +if(NOT MSVC) set(CMAKE_C_COMPILER_LAUNCHER "${CCACHE_PATH}") set(CMAKE_CXX_COMPILER_LAUNCHER "${CCACHE_PATH}") message(STATUS "Found ccache: ${CCACHE_PATH}") return() -endif () +endif() # For Windows more effort is required. The code below is a modified version of # https://github.com/ccache/ccache/wiki/MS-Visual-Studio#usage-with-cmake. -if ("${CCACHE_PATH}" MATCHES "chocolatey") +if("${CCACHE_PATH}" MATCHES "chocolatey") message(DEBUG "Ccache path: ${CCACHE_PATH}") # Chocolatey uses a shim executable that we cannot use directly, in which case we have to find the executable it # points to. If we cannot find the target executable then we cannot use ccache. find_program(BASH_PATH "bash") - if (NOT BASH_PATH) + if(NOT BASH_PATH) message(WARNING "Could not find bash.") return() - endif () + endif() - execute_process(COMMAND bash -c - "export LC_ALL='en_US.UTF-8'; ${CCACHE_PATH} --shimgen-noop | grep -oP 'path to executable: \\K.+' | head -c -1" - OUTPUT_VARIABLE CCACHE_PATH) + execute_process( + COMMAND + bash -c + "export LC_ALL='en_US.UTF-8'; ${CCACHE_PATH} --shimgen-noop | grep -oP 'path to executable: \\K.+' | head -c -1" + OUTPUT_VARIABLE CCACHE_PATH + ) - if (NOT CCACHE_PATH) + if(NOT CCACHE_PATH) message(WARNING "Could not find ccache target.") return() - endif () + endif() file(TO_CMAKE_PATH "${CCACHE_PATH}" CCACHE_PATH) -endif () +endif() message(STATUS "Found ccache: ${CCACHE_PATH}") # Tell cmake to use ccache for compiling with Visual Studio. file(COPY_FILE ${CCACHE_PATH} ${CMAKE_BINARY_DIR}/cl.exe ONLY_IF_DIFFERENT) -set(CMAKE_VS_GLOBALS "CLToolExe=cl.exe" "CLToolPath=${CMAKE_BINARY_DIR}" "TrackFileAccess=false" - "UseMultiToolTask=true") +set(CMAKE_VS_GLOBALS + "CLToolExe=cl.exe" + "CLToolPath=${CMAKE_BINARY_DIR}" + "TrackFileAccess=false" + "UseMultiToolTask=true" +) # By default Visual Studio generators will use /Zi to capture debug information, which is not compatible with ccache, so # tell it to use /Z7 instead. -if (MSVC) - foreach (var_ CMAKE_C_FLAGS_DEBUG CMAKE_C_FLAGS_RELEASE CMAKE_CXX_FLAGS_DEBUG - CMAKE_CXX_FLAGS_RELEASE) +if(MSVC) + foreach( + var_ + CMAKE_C_FLAGS_DEBUG + CMAKE_C_FLAGS_RELEASE + CMAKE_CXX_FLAGS_DEBUG + CMAKE_CXX_FLAGS_RELEASE + ) string(REPLACE "/Zi" "/Z7" ${var_} "${${var_}}") - endforeach () -endif () + endforeach() +endif() diff --git a/cmake/CodeCoverage.cmake b/cmake/CodeCoverage.cmake index 74796718c1..5681f76640 100644 --- a/cmake/CodeCoverage.cmake +++ b/cmake/CodeCoverage.cmake @@ -174,37 +174,45 @@ option(CODE_COVERAGE_VERBOSE "Verbose information" FALSE) # Check prereqs find_program(GCOVR_PATH gcovr PATHS ${CMAKE_SOURCE_DIR}/scripts/test) -if (DEFINED CODE_COVERAGE_GCOV_TOOL) +if(DEFINED CODE_COVERAGE_GCOV_TOOL) set(GCOV_TOOL "${CODE_COVERAGE_GCOV_TOOL}") -elseif (DEFINED ENV{CODE_COVERAGE_GCOV_TOOL}) +elseif(DEFINED ENV{CODE_COVERAGE_GCOV_TOOL}) set(GCOV_TOOL "$ENV{CODE_COVERAGE_GCOV_TOOL}") -elseif ("${CMAKE_CXX_COMPILER_ID}" MATCHES "(Apple)?[Cc]lang") - if (APPLE) - execute_process(COMMAND xcrun -f llvm-cov OUTPUT_VARIABLE LLVMCOV_PATH - OUTPUT_STRIP_TRAILING_WHITESPACE) - else () +elseif("${CMAKE_CXX_COMPILER_ID}" MATCHES "(Apple)?[Cc]lang") + if(APPLE) + execute_process( + COMMAND xcrun -f llvm-cov + OUTPUT_VARIABLE LLVMCOV_PATH + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + else() find_program(LLVMCOV_PATH llvm-cov) - endif () - if (LLVMCOV_PATH) + endif() + if(LLVMCOV_PATH) set(GCOV_TOOL "${LLVMCOV_PATH} gcov") - endif () -elseif ("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") + endif() +elseif("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") find_program(GCOV_PATH gcov) set(GCOV_TOOL "${GCOV_PATH}") -endif () +endif() # Check supported compiler (Clang, GNU and Flang) get_property(LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES) -foreach (LANG ${LANGUAGES}) - if ("${CMAKE_${LANG}_COMPILER_ID}" MATCHES "(Apple)?[Cc]lang") - if ("${CMAKE_${LANG}_COMPILER_VERSION}" VERSION_LESS 3) - message(FATAL_ERROR "Clang version must be 3.0.0 or greater! Aborting...") - endif () - elseif (NOT "${CMAKE_${LANG}_COMPILER_ID}" MATCHES "GNU" AND NOT "${CMAKE_${LANG}_COMPILER_ID}" - MATCHES "(LLVM)?[Ff]lang") +foreach(LANG ${LANGUAGES}) + if("${CMAKE_${LANG}_COMPILER_ID}" MATCHES "(Apple)?[Cc]lang") + if("${CMAKE_${LANG}_COMPILER_VERSION}" VERSION_LESS 3) + message( + FATAL_ERROR + "Clang version must be 3.0.0 or greater! Aborting..." + ) + endif() + elseif( + NOT "${CMAKE_${LANG}_COMPILER_ID}" MATCHES "GNU" + AND NOT "${CMAKE_${LANG}_COMPILER_ID}" MATCHES "(LLVM)?[Ff]lang" + ) message(FATAL_ERROR "Compiler is not GNU or Flang! Aborting...") - endif () -endforeach () + endif() +endforeach() set(COVERAGE_COMPILER_FLAGS "-g --coverage" CACHE INTERNAL "") @@ -213,7 +221,7 @@ set(COVERAGE_C_COMPILER_FLAGS "") set(COVERAGE_CXX_LINKER_FLAGS "") set(COVERAGE_C_LINKER_FLAGS "") -if (CMAKE_CXX_COMPILER_ID MATCHES "(GNU|Clang)") +if(CMAKE_CXX_COMPILER_ID MATCHES "(GNU|Clang)") include(CheckCXXCompilerFlag) include(CheckCCompilerFlag) include(CheckLinkerFlag) @@ -224,51 +232,77 @@ if (CMAKE_CXX_COMPILER_ID MATCHES "(GNU|Clang)") set(COVERAGE_C_LINKER_FLAGS ${COVERAGE_COMPILER_FLAGS}) check_cxx_compiler_flag(-fprofile-abs-path HAVE_cxx_fprofile_abs_path) - if (HAVE_cxx_fprofile_abs_path) - set(COVERAGE_CXX_COMPILER_FLAGS "${COVERAGE_CXX_COMPILER_FLAGS} -fprofile-abs-path") - endif () + if(HAVE_cxx_fprofile_abs_path) + set(COVERAGE_CXX_COMPILER_FLAGS + "${COVERAGE_CXX_COMPILER_FLAGS} -fprofile-abs-path" + ) + endif() check_c_compiler_flag(-fprofile-abs-path HAVE_c_fprofile_abs_path) - if (HAVE_c_fprofile_abs_path) - set(COVERAGE_C_COMPILER_FLAGS "${COVERAGE_C_COMPILER_FLAGS} -fprofile-abs-path") - endif () + if(HAVE_c_fprofile_abs_path) + set(COVERAGE_C_COMPILER_FLAGS + "${COVERAGE_C_COMPILER_FLAGS} -fprofile-abs-path" + ) + endif() check_linker_flag(CXX -fprofile-abs-path HAVE_cxx_linker_fprofile_abs_path) - if (HAVE_cxx_linker_fprofile_abs_path) - set(COVERAGE_CXX_LINKER_FLAGS "${COVERAGE_CXX_LINKER_FLAGS} -fprofile-abs-path") - endif () + if(HAVE_cxx_linker_fprofile_abs_path) + set(COVERAGE_CXX_LINKER_FLAGS + "${COVERAGE_CXX_LINKER_FLAGS} -fprofile-abs-path" + ) + endif() check_linker_flag(C -fprofile-abs-path HAVE_c_linker_fprofile_abs_path) - if (HAVE_c_linker_fprofile_abs_path) - set(COVERAGE_C_LINKER_FLAGS "${COVERAGE_C_LINKER_FLAGS} -fprofile-abs-path") - endif () + if(HAVE_c_linker_fprofile_abs_path) + set(COVERAGE_C_LINKER_FLAGS + "${COVERAGE_C_LINKER_FLAGS} -fprofile-abs-path" + ) + endif() check_cxx_compiler_flag(-fprofile-update=atomic HAVE_cxx_fprofile_update) - if (HAVE_cxx_fprofile_update) - set(COVERAGE_CXX_COMPILER_FLAGS "${COVERAGE_CXX_COMPILER_FLAGS} -fprofile-update=atomic") - endif () + if(HAVE_cxx_fprofile_update) + set(COVERAGE_CXX_COMPILER_FLAGS + "${COVERAGE_CXX_COMPILER_FLAGS} -fprofile-update=atomic" + ) + endif() check_c_compiler_flag(-fprofile-update=atomic HAVE_c_fprofile_update) - if (HAVE_c_fprofile_update) - set(COVERAGE_C_COMPILER_FLAGS "${COVERAGE_C_COMPILER_FLAGS} -fprofile-update=atomic") - endif () + if(HAVE_c_fprofile_update) + set(COVERAGE_C_COMPILER_FLAGS + "${COVERAGE_C_COMPILER_FLAGS} -fprofile-update=atomic" + ) + endif() - check_linker_flag(CXX -fprofile-update=atomic HAVE_cxx_linker_fprofile_update) - if (HAVE_cxx_linker_fprofile_update) - set(COVERAGE_CXX_LINKER_FLAGS "${COVERAGE_CXX_LINKER_FLAGS} -fprofile-update=atomic") - endif () + check_linker_flag( + CXX + -fprofile-update=atomic + HAVE_cxx_linker_fprofile_update + ) + if(HAVE_cxx_linker_fprofile_update) + set(COVERAGE_CXX_LINKER_FLAGS + "${COVERAGE_CXX_LINKER_FLAGS} -fprofile-update=atomic" + ) + endif() check_linker_flag(C -fprofile-update=atomic HAVE_c_linker_fprofile_update) - if (HAVE_c_linker_fprofile_update) - set(COVERAGE_C_LINKER_FLAGS "${COVERAGE_C_LINKER_FLAGS} -fprofile-update=atomic") - endif () + if(HAVE_c_linker_fprofile_update) + set(COVERAGE_C_LINKER_FLAGS + "${COVERAGE_C_LINKER_FLAGS} -fprofile-update=atomic" + ) + endif() +endif() -endif () - -get_property(GENERATOR_IS_MULTI_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) -if (NOT (CMAKE_BUILD_TYPE STREQUAL "Debug" OR GENERATOR_IS_MULTI_CONFIG)) - message(WARNING "Code coverage results with an optimised (non-Debug) build may be misleading") -endif () # NOT (CMAKE_BUILD_TYPE STREQUAL "Debug" OR GENERATOR_IS_MULTI_CONFIG) +get_property( + GENERATOR_IS_MULTI_CONFIG + GLOBAL + PROPERTY GENERATOR_IS_MULTI_CONFIG +) +if(NOT (CMAKE_BUILD_TYPE STREQUAL "Debug" OR GENERATOR_IS_MULTI_CONFIG)) + message( + WARNING + "Code coverage results with an optimised (non-Debug) build may be misleading" + ) +endif() # NOT (CMAKE_BUILD_TYPE STREQUAL "Debug" OR GENERATOR_IS_MULTI_CONFIG) # Defines a target for running and collection code coverage information # Builds dependencies, runs the given executable and outputs reports. @@ -292,125 +326,167 @@ endif () # NOT (CMAKE_BUILD_TYPE STREQUAL "Debug" OR GENERATOR_IS_MULTI_CONFIG) # ) # The user can set the variable GCOVR_ADDITIONAL_ARGS to supply additional flags to the # GCVOR command. -function (setup_target_for_coverage_gcovr) +function(setup_target_for_coverage_gcovr) set(options NONE) set(oneValueArgs BASE_DIRECTORY NAME FORMAT) set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES) - cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + cmake_parse_arguments( + Coverage + "${options}" + "${oneValueArgs}" + "${multiValueArgs}" + ${ARGN} + ) - if (NOT GCOV_TOOL) + if(NOT GCOV_TOOL) message(FATAL_ERROR "Could not find gcov or llvm-cov tool! Aborting...") - endif () + endif() - if (NOT GCOVR_PATH) + if(NOT GCOVR_PATH) message(FATAL_ERROR "Could not find gcovr tool! Aborting...") - endif () + endif() # Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR - if (DEFINED Coverage_BASE_DIRECTORY) + if(DEFINED Coverage_BASE_DIRECTORY) get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE) - else () + else() set(BASEDIR ${PROJECT_SOURCE_DIR}) - endif () + endif() - if (NOT DEFINED Coverage_FORMAT) + if(NOT DEFINED Coverage_FORMAT) set(Coverage_FORMAT xml) - endif () + endif() - if (NOT DEFINED Coverage_EXECUTABLE AND DEFINED Coverage_EXECUTABLE_ARGS) - message(FATAL_ERROR "EXECUTABLE_ARGS must not be set if EXECUTABLE is not set") - endif () - - if ("--output" IN_LIST GCOVR_ADDITIONAL_ARGS) - message(FATAL_ERROR "Unsupported --output option detected in GCOVR_ADDITIONAL_ARGS! Aborting..." + if(NOT DEFINED Coverage_EXECUTABLE AND DEFINED Coverage_EXECUTABLE_ARGS) + message( + FATAL_ERROR + "EXECUTABLE_ARGS must not be set if EXECUTABLE is not set" ) - else () - if ((Coverage_FORMAT STREQUAL "html-details") OR (Coverage_FORMAT STREQUAL "html-nested")) - set(GCOVR_OUTPUT_FILE ${PROJECT_BINARY_DIR}/${Coverage_NAME}/index.html) - set(GCOVR_CREATE_FOLDER ${PROJECT_BINARY_DIR}/${Coverage_NAME}) - elseif (Coverage_FORMAT STREQUAL "html-single") - set(GCOVR_OUTPUT_FILE ${Coverage_NAME}.html) - elseif ((Coverage_FORMAT STREQUAL "json-summary") OR (Coverage_FORMAT STREQUAL - "json-details") - OR (Coverage_FORMAT STREQUAL "coveralls")) - set(GCOVR_OUTPUT_FILE ${Coverage_NAME}.json) - elseif (Coverage_FORMAT STREQUAL "txt") - set(GCOVR_OUTPUT_FILE ${Coverage_NAME}.txt) - elseif (Coverage_FORMAT STREQUAL "csv") - set(GCOVR_OUTPUT_FILE ${Coverage_NAME}.csv) - elseif (Coverage_FORMAT STREQUAL "lcov") - set(GCOVR_OUTPUT_FILE ${Coverage_NAME}.lcov) - else () - set(GCOVR_OUTPUT_FILE ${Coverage_NAME}.xml) - endif () - endif () + endif() - if ((Coverage_FORMAT STREQUAL "cobertura") OR (Coverage_FORMAT STREQUAL "xml")) + if("--output" IN_LIST GCOVR_ADDITIONAL_ARGS) + message( + FATAL_ERROR + "Unsupported --output option detected in GCOVR_ADDITIONAL_ARGS! Aborting..." + ) + else() + if( + (Coverage_FORMAT STREQUAL "html-details") + OR (Coverage_FORMAT STREQUAL "html-nested") + ) + set(GCOVR_OUTPUT_FILE + ${PROJECT_BINARY_DIR}/${Coverage_NAME}/index.html + ) + set(GCOVR_CREATE_FOLDER ${PROJECT_BINARY_DIR}/${Coverage_NAME}) + elseif(Coverage_FORMAT STREQUAL "html-single") + set(GCOVR_OUTPUT_FILE ${Coverage_NAME}.html) + elseif( + (Coverage_FORMAT STREQUAL "json-summary") + OR (Coverage_FORMAT STREQUAL "json-details") + OR (Coverage_FORMAT STREQUAL "coveralls") + ) + set(GCOVR_OUTPUT_FILE ${Coverage_NAME}.json) + elseif(Coverage_FORMAT STREQUAL "txt") + set(GCOVR_OUTPUT_FILE ${Coverage_NAME}.txt) + elseif(Coverage_FORMAT STREQUAL "csv") + set(GCOVR_OUTPUT_FILE ${Coverage_NAME}.csv) + elseif(Coverage_FORMAT STREQUAL "lcov") + set(GCOVR_OUTPUT_FILE ${Coverage_NAME}.lcov) + else() + set(GCOVR_OUTPUT_FILE ${Coverage_NAME}.xml) + endif() + endif() + + if( + (Coverage_FORMAT STREQUAL "cobertura") + OR (Coverage_FORMAT STREQUAL "xml") + ) list(APPEND GCOVR_ADDITIONAL_ARGS --cobertura "${GCOVR_OUTPUT_FILE}") list(APPEND GCOVR_ADDITIONAL_ARGS --cobertura-pretty) set(Coverage_FORMAT cobertura) # overwrite xml - elseif (Coverage_FORMAT STREQUAL "sonarqube") + elseif(Coverage_FORMAT STREQUAL "sonarqube") list(APPEND GCOVR_ADDITIONAL_ARGS --sonarqube "${GCOVR_OUTPUT_FILE}") - elseif (Coverage_FORMAT STREQUAL "jacoco") + elseif(Coverage_FORMAT STREQUAL "jacoco") list(APPEND GCOVR_ADDITIONAL_ARGS --jacoco "${GCOVR_OUTPUT_FILE}") list(APPEND GCOVR_ADDITIONAL_ARGS --jacoco-pretty) - elseif (Coverage_FORMAT STREQUAL "clover") + elseif(Coverage_FORMAT STREQUAL "clover") list(APPEND GCOVR_ADDITIONAL_ARGS --clover "${GCOVR_OUTPUT_FILE}") list(APPEND GCOVR_ADDITIONAL_ARGS --clover-pretty) - elseif (Coverage_FORMAT STREQUAL "lcov") + elseif(Coverage_FORMAT STREQUAL "lcov") list(APPEND GCOVR_ADDITIONAL_ARGS --lcov "${GCOVR_OUTPUT_FILE}") - elseif (Coverage_FORMAT STREQUAL "json-summary") + elseif(Coverage_FORMAT STREQUAL "json-summary") list(APPEND GCOVR_ADDITIONAL_ARGS --json-summary "${GCOVR_OUTPUT_FILE}") list(APPEND GCOVR_ADDITIONAL_ARGS --json-summary-pretty) - elseif (Coverage_FORMAT STREQUAL "json-details") + elseif(Coverage_FORMAT STREQUAL "json-details") list(APPEND GCOVR_ADDITIONAL_ARGS --json "${GCOVR_OUTPUT_FILE}") list(APPEND GCOVR_ADDITIONAL_ARGS --json-pretty) - elseif (Coverage_FORMAT STREQUAL "coveralls") + elseif(Coverage_FORMAT STREQUAL "coveralls") list(APPEND GCOVR_ADDITIONAL_ARGS --coveralls "${GCOVR_OUTPUT_FILE}") list(APPEND GCOVR_ADDITIONAL_ARGS --coveralls-pretty) - elseif (Coverage_FORMAT STREQUAL "csv") + elseif(Coverage_FORMAT STREQUAL "csv") list(APPEND GCOVR_ADDITIONAL_ARGS --csv "${GCOVR_OUTPUT_FILE}") - elseif (Coverage_FORMAT STREQUAL "txt") + elseif(Coverage_FORMAT STREQUAL "txt") list(APPEND GCOVR_ADDITIONAL_ARGS --txt "${GCOVR_OUTPUT_FILE}") - elseif (Coverage_FORMAT STREQUAL "html-single") + elseif(Coverage_FORMAT STREQUAL "html-single") list(APPEND GCOVR_ADDITIONAL_ARGS --html "${GCOVR_OUTPUT_FILE}") list(APPEND GCOVR_ADDITIONAL_ARGS --html-self-contained) - elseif (Coverage_FORMAT STREQUAL "html-nested") + elseif(Coverage_FORMAT STREQUAL "html-nested") list(APPEND GCOVR_ADDITIONAL_ARGS --html-nested "${GCOVR_OUTPUT_FILE}") - elseif (Coverage_FORMAT STREQUAL "html-details") + elseif(Coverage_FORMAT STREQUAL "html-details") list(APPEND GCOVR_ADDITIONAL_ARGS --html-details "${GCOVR_OUTPUT_FILE}") - else () - message(FATAL_ERROR "Unsupported output style ${Coverage_FORMAT}! Aborting...") - endif () + else() + message( + FATAL_ERROR + "Unsupported output style ${Coverage_FORMAT}! Aborting..." + ) + endif() # Collect excludes (CMake 3.4+: Also compute absolute paths) set(GCOVR_EXCLUDES "") - foreach (EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_GCOVR_EXCLUDES}) - if (CMAKE_VERSION VERSION_GREATER 3.4) - get_filename_component(EXCLUDE ${EXCLUDE} ABSOLUTE BASE_DIR ${BASEDIR}) - endif () + foreach( + EXCLUDE + ${Coverage_EXCLUDE} + ${COVERAGE_EXCLUDES} + ${COVERAGE_GCOVR_EXCLUDES} + ) + if(CMAKE_VERSION VERSION_GREATER 3.4) + get_filename_component( + EXCLUDE + ${EXCLUDE} + ABSOLUTE + BASE_DIR ${BASEDIR} + ) + endif() list(APPEND GCOVR_EXCLUDES "${EXCLUDE}") - endforeach () + endforeach() list(REMOVE_DUPLICATES GCOVR_EXCLUDES) # Combine excludes to several -e arguments set(GCOVR_EXCLUDE_ARGS "") - foreach (EXCLUDE ${GCOVR_EXCLUDES}) + foreach(EXCLUDE ${GCOVR_EXCLUDES}) list(APPEND GCOVR_EXCLUDE_ARGS "-e") list(APPEND GCOVR_EXCLUDE_ARGS "${EXCLUDE}") - endforeach () + endforeach() # Set up commands which will be run to generate coverage data # If EXECUTABLE is not set, the user is expected to run the tests manually # before running the coverage target NAME - if (DEFINED Coverage_EXECUTABLE) - set(GCOVR_EXEC_TESTS_CMD ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS}) - endif () + if(DEFINED Coverage_EXECUTABLE) + set(GCOVR_EXEC_TESTS_CMD + ${Coverage_EXECUTABLE} + ${Coverage_EXECUTABLE_ARGS} + ) + endif() # Create folder - if (DEFINED GCOVR_CREATE_FOLDER) - set(GCOVR_FOLDER_CMD ${CMAKE_COMMAND} -E make_directory ${GCOVR_CREATE_FOLDER}) - endif () + if(DEFINED GCOVR_CREATE_FOLDER) + set(GCOVR_FOLDER_CMD + ${CMAKE_COMMAND} + -E + make_directory + ${GCOVR_CREATE_FOLDER} + ) + endif() # Running gcovr set(GCOVR_CMD @@ -422,56 +498,95 @@ function (setup_target_for_coverage_gcovr) ${BASEDIR} ${GCOVR_ADDITIONAL_ARGS} ${GCOVR_EXCLUDE_ARGS} - --object-directory=${PROJECT_BINARY_DIR}) + --object-directory=${PROJECT_BINARY_DIR} + ) - if (CODE_COVERAGE_VERBOSE) + if(CODE_COVERAGE_VERBOSE) message(STATUS "Executed command report") - if (NOT "${GCOVR_EXEC_TESTS_CMD}" STREQUAL "") + if(NOT "${GCOVR_EXEC_TESTS_CMD}" STREQUAL "") message(STATUS "Command to run tests: ") - string(REPLACE ";" " " GCOVR_EXEC_TESTS_CMD_SPACED "${GCOVR_EXEC_TESTS_CMD}") + string( + REPLACE ";" + " " + GCOVR_EXEC_TESTS_CMD_SPACED + "${GCOVR_EXEC_TESTS_CMD}" + ) message(STATUS "${GCOVR_EXEC_TESTS_CMD_SPACED}") - endif () + endif() - if (NOT "${GCOVR_FOLDER_CMD}" STREQUAL "") + if(NOT "${GCOVR_FOLDER_CMD}" STREQUAL "") message(STATUS "Command to create a folder: ") - string(REPLACE ";" " " GCOVR_FOLDER_CMD_SPACED "${GCOVR_FOLDER_CMD}") + string( + REPLACE ";" + " " + GCOVR_FOLDER_CMD_SPACED + "${GCOVR_FOLDER_CMD}" + ) message(STATUS "${GCOVR_FOLDER_CMD_SPACED}") - endif () + endif() message(STATUS "Command to generate gcovr coverage data: ") string(REPLACE ";" " " GCOVR_CMD_SPACED "${GCOVR_CMD}") message(STATUS "${GCOVR_CMD_SPACED}") - endif () + endif() - add_custom_target(${Coverage_NAME} - COMMAND ${GCOVR_EXEC_TESTS_CMD} - COMMAND ${GCOVR_FOLDER_CMD} - COMMAND ${GCOVR_CMD} - BYPRODUCTS ${GCOVR_OUTPUT_FILE} - WORKING_DIRECTORY ${PROJECT_BINARY_DIR} - DEPENDS ${Coverage_DEPENDENCIES} - VERBATIM # Protect arguments to commands - COMMENT "Running gcovr to produce code coverage report.") + add_custom_target( + ${Coverage_NAME} + COMMAND ${GCOVR_EXEC_TESTS_CMD} + COMMAND ${GCOVR_FOLDER_CMD} + COMMAND ${GCOVR_CMD} + BYPRODUCTS ${GCOVR_OUTPUT_FILE} + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + DEPENDS ${Coverage_DEPENDENCIES} + VERBATIM # Protect arguments to commands + COMMENT "Running gcovr to produce code coverage report." + ) # Show info where to find the report add_custom_command( - TARGET ${Coverage_NAME} POST_BUILD COMMAND echo - COMMENT "Code coverage report saved in ${GCOVR_OUTPUT_FILE} formatted as ${Coverage_FORMAT}" + TARGET ${Coverage_NAME} + POST_BUILD + COMMAND echo + COMMENT + "Code coverage report saved in ${GCOVR_OUTPUT_FILE} formatted as ${Coverage_FORMAT}" ) -endfunction () # setup_target_for_coverage_gcovr +endfunction() # setup_target_for_coverage_gcovr -function (add_code_coverage_to_target name scope) - separate_arguments(COVERAGE_CXX_COMPILER_FLAGS NATIVE_COMMAND "${COVERAGE_CXX_COMPILER_FLAGS}") - separate_arguments(COVERAGE_C_COMPILER_FLAGS NATIVE_COMMAND "${COVERAGE_C_COMPILER_FLAGS}") - separate_arguments(COVERAGE_CXX_LINKER_FLAGS NATIVE_COMMAND "${COVERAGE_CXX_LINKER_FLAGS}") - separate_arguments(COVERAGE_C_LINKER_FLAGS NATIVE_COMMAND "${COVERAGE_C_LINKER_FLAGS}") +function(add_code_coverage_to_target name scope) + separate_arguments( + COVERAGE_CXX_COMPILER_FLAGS + NATIVE_COMMAND + "${COVERAGE_CXX_COMPILER_FLAGS}" + ) + separate_arguments( + COVERAGE_C_COMPILER_FLAGS + NATIVE_COMMAND + "${COVERAGE_C_COMPILER_FLAGS}" + ) + separate_arguments( + COVERAGE_CXX_LINKER_FLAGS + NATIVE_COMMAND + "${COVERAGE_CXX_LINKER_FLAGS}" + ) + separate_arguments( + COVERAGE_C_LINKER_FLAGS + NATIVE_COMMAND + "${COVERAGE_C_LINKER_FLAGS}" + ) # Add compiler options to the target target_compile_options( - ${name} ${scope} $<$:${COVERAGE_CXX_COMPILER_FLAGS}> - $<$:${COVERAGE_C_COMPILER_FLAGS}>) + ${name} + ${scope} + $<$:${COVERAGE_CXX_COMPILER_FLAGS}> + $<$:${COVERAGE_C_COMPILER_FLAGS}> + ) - target_link_libraries(${name} ${scope} $<$:${COVERAGE_CXX_LINKER_FLAGS}> - $<$:${COVERAGE_C_LINKER_FLAGS}>) -endfunction () # add_code_coverage_to_target + target_link_libraries( + ${name} + ${scope} + $<$:${COVERAGE_CXX_LINKER_FLAGS}> + $<$:${COVERAGE_C_LINKER_FLAGS}> + ) +endfunction() # add_code_coverage_to_target diff --git a/cmake/CompilationEnv.cmake b/cmake/CompilationEnv.cmake index 59b903c13a..0d44f90974 100644 --- a/cmake/CompilationEnv.cmake +++ b/cmake/CompilationEnv.cmake @@ -14,20 +14,20 @@ set(is_gcc FALSE) set(is_msvc FALSE) set(is_xcode FALSE) -if (CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") # Clang or AppleClang +if(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") # Clang or AppleClang set(is_clang TRUE) -elseif (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") +elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") set(is_gcc TRUE) -elseif (MSVC) +elseif(MSVC) set(is_msvc TRUE) -else () +else() message(FATAL_ERROR "Unsupported C++ compiler: ${CMAKE_CXX_COMPILER_ID}") -endif () +endif() # Xcode generator detection -if (CMAKE_GENERATOR STREQUAL "Xcode") +if(CMAKE_GENERATOR STREQUAL "Xcode") set(is_xcode TRUE) -endif () +endif() # -------------------------------------------------------------------- # Operating system detection @@ -36,23 +36,23 @@ set(is_linux FALSE) set(is_windows FALSE) set(is_macos FALSE) -if (CMAKE_SYSTEM_NAME STREQUAL "Linux") +if(CMAKE_SYSTEM_NAME STREQUAL "Linux") set(is_linux TRUE) -elseif (CMAKE_SYSTEM_NAME STREQUAL "Windows") +elseif(CMAKE_SYSTEM_NAME STREQUAL "Windows") set(is_windows TRUE) -elseif (CMAKE_SYSTEM_NAME STREQUAL "Darwin") +elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin") set(is_macos TRUE) -endif () +endif() # -------------------------------------------------------------------- # Architecture # -------------------------------------------------------------------- set(is_amd64 FALSE) set(is_arm64 FALSE) -if (CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|AMD64") +if(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|AMD64") set(is_amd64 TRUE) -elseif (CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|arm64|ARM64") +elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|arm64|ARM64") set(is_arm64 TRUE) -else () +else() message(FATAL_ERROR "Unknown architecture: ${CMAKE_SYSTEM_PROCESSOR}") -endif () +endif() diff --git a/cmake/GitInfo.cmake b/cmake/GitInfo.cmake index 1281182977..788efe624a 100644 --- a/cmake/GitInfo.cmake +++ b/cmake/GitInfo.cmake @@ -4,18 +4,25 @@ set(GIT_BUILD_BRANCH "") set(GIT_COMMIT_HASH "") find_package(Git) -if (NOT Git_FOUND) +if(NOT Git_FOUND) message(WARNING "Git not found. Git branch and commit hash will be empty.") return() -endif () +endif() set(GIT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/.git) -execute_process(COMMAND ${GIT_EXECUTABLE} --git-dir=${GIT_DIRECTORY} rev-parse --abbrev-ref HEAD - OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE GIT_BUILD_BRANCH) +execute_process( + COMMAND + ${GIT_EXECUTABLE} --git-dir=${GIT_DIRECTORY} rev-parse --abbrev-ref HEAD + OUTPUT_STRIP_TRAILING_WHITESPACE + OUTPUT_VARIABLE GIT_BUILD_BRANCH +) -execute_process(COMMAND ${GIT_EXECUTABLE} --git-dir=${GIT_DIRECTORY} rev-parse HEAD - OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE GIT_COMMIT_HASH) +execute_process( + COMMAND ${GIT_EXECUTABLE} --git-dir=${GIT_DIRECTORY} rev-parse HEAD + OUTPUT_STRIP_TRAILING_WHITESPACE + OUTPUT_VARIABLE GIT_COMMIT_HASH +) message(STATUS "Git branch: ${GIT_BUILD_BRANCH}") message(STATUS "Git commit hash: ${GIT_COMMIT_HASH}") diff --git a/cmake/XrplAddTest.cmake b/cmake/XrplAddTest.cmake index 35189e203f..0f8f9e2c01 100644 --- a/cmake/XrplAddTest.cmake +++ b/cmake/XrplAddTest.cmake @@ -1,13 +1,17 @@ include(isolate_headers) -function (xrpl_add_test name) +function(xrpl_add_test name) set(target ${PROJECT_NAME}.test.${name}) - file(GLOB_RECURSE sources CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/${name}/*.cpp" - "${CMAKE_CURRENT_SOURCE_DIR}/${name}.cpp") + file( + GLOB_RECURSE sources + CONFIGURE_DEPENDS + "${CMAKE_CURRENT_SOURCE_DIR}/${name}/*.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/${name}.cpp" + ) add_executable(${target} ${ARGN} ${sources}) isolate_headers(${target} "${CMAKE_SOURCE_DIR}" "${CMAKE_SOURCE_DIR}/tests/${name}" PRIVATE) add_test(NAME ${target} COMMAND ${target}) -endfunction () +endfunction() diff --git a/cmake/XrplCompiler.cmake b/cmake/XrplCompiler.cmake index 8093004b2e..0b77ff3525 100644 --- a/cmake/XrplCompiler.cmake +++ b/cmake/XrplCompiler.cmake @@ -14,33 +14,42 @@ include(XrplSanitizers) # add a single global dependency on this interface lib link_libraries(Xrpl::common) # Respect CMAKE_POSITION_INDEPENDENT_CODE setting (may be set by Conan toolchain) -if (NOT DEFINED CMAKE_POSITION_INDEPENDENT_CODE) +if(NOT DEFINED CMAKE_POSITION_INDEPENDENT_CODE) set(CMAKE_POSITION_INDEPENDENT_CODE ON) -endif () -set_target_properties(common PROPERTIES INTERFACE_POSITION_INDEPENDENT_CODE - ${CMAKE_POSITION_INDEPENDENT_CODE}) +endif() +set_target_properties( + common + PROPERTIES + INTERFACE_POSITION_INDEPENDENT_CODE ${CMAKE_POSITION_INDEPENDENT_CODE} +) set(CMAKE_CXX_EXTENSIONS OFF) target_compile_definitions( common - INTERFACE $<$:DEBUG - _DEBUG> - #[===[ + INTERFACE + $<$:DEBUG + _DEBUG> + #[===[ NOTE: CMAKE release builds already have NDEBUG defined, so no need to add it explicitly except for the special case of (profile ON) and (assert OFF). Presumably this is because we don't want profile builds asserting unless asserts were specifically requested. ]===] - $<$,$>>:NDEBUG> - # TODO: Remove once we have migrated functions from OpenSSL 1.x to 3.x. - OPENSSL_SUPPRESS_DEPRECATED) + $<$,$>>:NDEBUG> + # TODO: Remove once we have migrated functions from OpenSSL 1.x to 3.x. + OPENSSL_SUPPRESS_DEPRECATED +) -if (MSVC) +if(MSVC) # remove existing exception flag since we set it to -EHa string(REGEX REPLACE "[-/]EH[a-z]+" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") - foreach (var_ CMAKE_C_FLAGS_DEBUG CMAKE_C_FLAGS_RELEASE CMAKE_CXX_FLAGS_DEBUG - CMAKE_CXX_FLAGS_RELEASE) - + foreach( + var_ + CMAKE_C_FLAGS_DEBUG + CMAKE_C_FLAGS_RELEASE + CMAKE_CXX_FLAGS_DEBUG + CMAKE_CXX_FLAGS_RELEASE + ) # also remove dynamic runtime string(REGEX REPLACE "[-/]MD[d]*" " " ${var_} "${${var_}}") @@ -48,120 +57,143 @@ if (MSVC) string(REPLACE "/ZI" "/Zi" ${var_} "${${var_}}") # omit debug info completely under CI (not needed) - if (is_ci) + if(is_ci) string(REPLACE "/Zi" " " ${var_} "${${var_}}") string(REPLACE "/Z7" " " ${var_} "${${var_}}") - endif () - endforeach () + endif() + endforeach() target_compile_options( common INTERFACE # Increase object file max size - -bigobj - # Floating point behavior - -fp:precise - # __cdecl calling convention - -Gd - # Minimal rebuild: disabled - -Gm- - # Function level linking: disabled - -Gy- - # Multiprocessor compilation - -MP - # pragma omp: disabled - -openmp- - # No error reporting to Internet - -errorReport:none - # Suppress login banner - -nologo - # Disable signed/unsigned comparison warnings - -wd4018 - # Disable float to int possible loss of data warnings - -wd4244 - # Disable size_t to T possible loss of data warnings - -wd4267 - # Disable C4800(int to bool performance) - -wd4800 - # Decorated name length exceeded, name was truncated - -wd4503 - $<$: - -EHa - -GR - > - $<$:-Ox> - $<$,$>: - -GS - -Zc:forScope - > - # static runtime - $<$:-MTd> - $<$>:-MT> - $<$:-WX>) + -bigobj + # Floating point behavior + -fp:precise + # __cdecl calling convention + -Gd + # Minimal rebuild: disabled + -Gm- + # Function level linking: disabled + -Gy- + # Multiprocessor compilation + -MP + # pragma omp: disabled + -openmp- + # No error reporting to Internet + -errorReport:none + # Suppress login banner + -nologo + # Disable signed/unsigned comparison warnings + -wd4018 + # Disable float to int possible loss of data warnings + -wd4244 + # Disable size_t to T possible loss of data warnings + -wd4267 + # Disable C4800(int to bool performance) + -wd4800 + # Decorated name length exceeded, name was truncated + -wd4503 + $<$: + -EHa + -GR + > + $<$:-Ox> + $<$,$>: + -GS + -Zc:forScope + > + # static runtime + $<$:-MTd> + $<$>:-MT> + $<$:-WX> + ) target_compile_definitions( common - INTERFACE _WIN32_WINNT=0x6000 - _SCL_SECURE_NO_WARNINGS - _CRT_SECURE_NO_WARNINGS - WIN32_CONSOLE - WIN32_LEAN_AND_MEAN - NOMINMAX - # TODO: Resolve these warnings, don't just silence them - _SILENCE_ALL_CXX17_DEPRECATION_WARNINGS - $<$,$>:_CRTDBG_MAP_ALLOC>) + INTERFACE + _WIN32_WINNT=0x6000 + _SCL_SECURE_NO_WARNINGS + _CRT_SECURE_NO_WARNINGS + WIN32_CONSOLE + WIN32_LEAN_AND_MEAN + NOMINMAX + # TODO: Resolve these warnings, don't just silence them + _SILENCE_ALL_CXX17_DEPRECATION_WARNINGS + $<$,$>:_CRTDBG_MAP_ALLOC> + ) target_link_libraries(common INTERFACE -errorreport:none -machine:X64) -else () +else() target_compile_options( common - INTERFACE -Wall - -Wdeprecated - $<$:-Wno-deprecated-declarations> - $<$:-Wextra - -Wno-unused-parameter> - $<$:-Werror> - -fstack-protector - -Wno-sign-compare - -Wno-unused-but-set-variable - $<$>:-fno-strict-aliasing> - # tweak gcc optimization for debug - $<$,$>:-O0> - # Add debug symbols to release config - $<$:-g>) + INTERFACE + -Wall + -Wdeprecated + $<$:-Wno-deprecated-declarations> + $<$:-Wextra + -Wno-unused-parameter> + $<$:-Werror> + -fstack-protector + -Wno-sign-compare + -Wno-unused-but-set-variable + $<$>:-fno-strict-aliasing> + # tweak gcc optimization for debug + $<$,$>:-O0> + # Add debug symbols to release config + $<$:-g> + ) target_link_libraries( common - INTERFACE -rdynamic - $<$:-Wl,-z,relro,-z,now,--build-id> - # link to static libc/c++ iff: * static option set and * NOT APPLE (AppleClang does not support static - # libc/c++) and * NOT SANITIZERS (sanitizers typically don't work with static libc/c++) - $<$,$>,$>>: - -static-libstdc++ - -static-libgcc - >) -endif () + INTERFACE + -rdynamic + $<$:-Wl,-z,relro,-z,now,--build-id> + # link to static libc/c++ iff: * static option set and * NOT APPLE (AppleClang does not support static + # libc/c++) and * NOT SANITIZERS (sanitizers typically don't work with static libc/c++) + $<$,$>,$>>: + -static-libstdc++ + -static-libgcc + > + ) +endif() # Antithesis instrumentation will only be built and deployed using machines running Linux. -if (voidstar) - if (NOT CMAKE_BUILD_TYPE STREQUAL "Debug") - message(FATAL_ERROR "Antithesis instrumentation requires Debug build type, aborting...") - elseif (NOT is_linux) - message(FATAL_ERROR "Antithesis instrumentation requires Linux, aborting...") - elseif (NOT (is_clang AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 16.0)) - message(FATAL_ERROR "Antithesis instrumentation requires Clang version 16 or later, aborting..." +if(voidstar) + if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug") + message( + FATAL_ERROR + "Antithesis instrumentation requires Debug build type, aborting..." ) - endif () -endif () + elseif(NOT is_linux) + message( + FATAL_ERROR + "Antithesis instrumentation requires Linux, aborting..." + ) + elseif( + NOT (is_clang AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 16.0) + ) + message( + FATAL_ERROR + "Antithesis instrumentation requires Clang version 16 or later, aborting..." + ) + endif() +endif() -if (use_mold) +if(use_mold) # use mold linker if available - execute_process(COMMAND ${CMAKE_CXX_COMPILER} -fuse-ld=mold -Wl,--version ERROR_QUIET - OUTPUT_VARIABLE LD_VERSION) - if ("${LD_VERSION}" MATCHES "mold") + execute_process( + COMMAND ${CMAKE_CXX_COMPILER} -fuse-ld=mold -Wl,--version + ERROR_QUIET + OUTPUT_VARIABLE LD_VERSION + ) + if("${LD_VERSION}" MATCHES "mold") target_link_libraries(common INTERFACE -fuse-ld=mold) - endif () + endif() unset(LD_VERSION) -elseif (use_gold AND is_gcc) +elseif(use_gold AND is_gcc) # use gold linker if available - execute_process(COMMAND ${CMAKE_CXX_COMPILER} -fuse-ld=gold -Wl,--version ERROR_QUIET - OUTPUT_VARIABLE LD_VERSION) + execute_process( + COMMAND ${CMAKE_CXX_COMPILER} -fuse-ld=gold -Wl,--version + ERROR_QUIET + OUTPUT_VARIABLE LD_VERSION + ) #[=========================================================[ NOTE: THE gold linker inserts -rpath as DT_RUNPATH by default instead of DT_RPATH, so you might have slightly @@ -175,32 +207,37 @@ elseif (use_gold AND is_gcc) disabling would be to figure out all the settings required to make gold play nicely with jemalloc. #]=========================================================] - if (("${LD_VERSION}" MATCHES "GNU gold") AND (NOT jemalloc)) + if(("${LD_VERSION}" MATCHES "GNU gold") AND (NOT jemalloc)) target_link_libraries( common - INTERFACE -fuse-ld=gold - -Wl,--no-as-needed - #[=========================================================[ + INTERFACE + -fuse-ld=gold + -Wl,--no-as-needed + #[=========================================================[ see https://bugs.launchpad.net/ubuntu/+source/eglibc/+bug/1253638/comments/5 DT_RUNPATH does not work great for transitive dependencies (of which boost has a few) - so just switch to DT_RPATH if doing dynamic linking with gold #]=========================================================] - $<$>:-Wl,--disable-new-dtags>) - endif () + $<$>:-Wl,--disable-new-dtags> + ) + endif() unset(LD_VERSION) -elseif (use_lld) +elseif(use_lld) # use lld linker if available - execute_process(COMMAND ${CMAKE_CXX_COMPILER} -fuse-ld=lld -Wl,--version ERROR_QUIET - OUTPUT_VARIABLE LD_VERSION) - if ("${LD_VERSION}" MATCHES "LLD") + execute_process( + COMMAND ${CMAKE_CXX_COMPILER} -fuse-ld=lld -Wl,--version + ERROR_QUIET + OUTPUT_VARIABLE LD_VERSION + ) + if("${LD_VERSION}" MATCHES "LLD") target_link_libraries(common INTERFACE -fuse-ld=lld) - endif () + endif() unset(LD_VERSION) -endif () +endif() -if (assert) - foreach (var_ CMAKE_C_FLAGS_RELEASE CMAKE_CXX_FLAGS_RELEASE) +if(assert) + foreach(var_ CMAKE_C_FLAGS_RELEASE CMAKE_CXX_FLAGS_RELEASE) string(REGEX REPLACE "[-/]DNDEBUG" "" ${var_} "${${var_}}") - endforeach () -endif () + endforeach() +endif() diff --git a/cmake/XrplConfig.cmake b/cmake/XrplConfig.cmake index bf6aa475ba..76f9af14b1 100644 --- a/cmake/XrplConfig.cmake +++ b/cmake/XrplConfig.cmake @@ -3,50 +3,58 @@ include(CMakeFindDependencyMacro) #[=========================================================[ Boost #]=========================================================] -if (static OR APPLE OR MSVC) +if(static OR APPLE OR MSVC) set(Boost_USE_STATIC_LIBS ON) -endif () +endif() set(Boost_USE_MULTITHREADED ON) -if (static OR MSVC) +if(static OR MSVC) set(Boost_USE_STATIC_RUNTIME ON) -else () +else() set(Boost_USE_STATIC_RUNTIME OFF) -endif () -find_dependency(Boost - COMPONENTS - chrono - container - context - coroutine - date_time - filesystem - program_options - regex - system - thread) +endif() +find_dependency( + Boost + COMPONENTS + chrono + container + context + coroutine + date_time + filesystem + program_options + regex + system + thread +) #[=========================================================[ OpenSSL #]=========================================================] -if (NOT DEFINED OPENSSL_ROOT_DIR) - if (DEFINED ENV{OPENSSL_ROOT}) +if(NOT DEFINED OPENSSL_ROOT_DIR) + if(DEFINED ENV{OPENSSL_ROOT}) set(OPENSSL_ROOT_DIR $ENV{OPENSSL_ROOT}) - elseif (APPLE) + elseif(APPLE) find_program(homebrew brew) - if (homebrew) - execute_process(COMMAND ${homebrew} --prefix openssl OUTPUT_VARIABLE OPENSSL_ROOT_DIR - OUTPUT_STRIP_TRAILING_WHITESPACE) - endif () - endif () + if(homebrew) + execute_process( + COMMAND ${homebrew} --prefix openssl + OUTPUT_VARIABLE OPENSSL_ROOT_DIR + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + endif() + endif() file(TO_CMAKE_PATH "${OPENSSL_ROOT_DIR}" OPENSSL_ROOT_DIR) -endif () +endif() -if (static OR APPLE OR MSVC) +if(static OR APPLE OR MSVC) set(OPENSSL_USE_STATIC_LIBS ON) -endif () +endif() set(OPENSSL_MSVC_STATIC_RT ON) find_dependency(OpenSSL REQUIRED) find_dependency(ZLIB) find_dependency(date) -if (TARGET ZLIB::ZLIB) - set_target_properties(OpenSSL::Crypto PROPERTIES INTERFACE_LINK_LIBRARIES ZLIB::ZLIB) -endif () +if(TARGET ZLIB::ZLIB) + set_target_properties( + OpenSSL::Crypto + PROPERTIES INTERFACE_LINK_LIBRARIES ZLIB::ZLIB + ) +endif() diff --git a/cmake/XrplCore.cmake b/cmake/XrplCore.cmake index cd44f29df9..1c8f5e33d6 100644 --- a/cmake/XrplCore.cmake +++ b/cmake/XrplCore.cmake @@ -11,24 +11,34 @@ include(target_protobuf_sources) add_library(xrpl.libpb) set_target_properties(xrpl.libpb PROPERTIES UNITY_BUILD OFF) target_protobuf_sources(xrpl.libpb xrpl/proto LANGUAGE cpp IMPORT_DIRS include/xrpl/proto - PROTOS include/xrpl/proto/xrpl.proto) + PROTOS include/xrpl/proto/xrpl.proto +) file(GLOB_RECURSE protos "include/xrpl/proto/org/*.proto") target_protobuf_sources(xrpl.libpb xrpl/proto LANGUAGE cpp IMPORT_DIRS include/xrpl/proto - PROTOS "${protos}") + PROTOS "${protos}" +) target_protobuf_sources( xrpl.libpb xrpl/proto LANGUAGE grpc IMPORT_DIRS include/xrpl/proto PROTOS "${protos}" PLUGIN protoc-gen-grpc=$ - GENERATE_EXTENSIONS .grpc.pb.h .grpc.pb.cc) + GENERATE_EXTENSIONS .grpc.pb.h .grpc.pb.cc +) target_compile_options( xrpl.libpb - PUBLIC $<$:-wd4996> $<$: - --system-header-prefix="google/protobuf" -Wno-deprecated-dynamic-exception-spec > - PRIVATE $<$:-wd4065> $<$>:-Wno-deprecated-declarations>) + PUBLIC + $<$:-wd4996> + $<$: + --system-header-prefix="google/protobuf" + -Wno-deprecated-dynamic-exception-spec + > + PRIVATE + $<$:-wd4065> + $<$>:-Wno-deprecated-declarations> +) target_link_libraries(xrpl.libpb PUBLIC protobuf::libprotobuf gRPC::grpc++) @@ -37,19 +47,21 @@ add_library(xrpl.imports.main INTERFACE) target_link_libraries( xrpl.imports.main - INTERFACE absl::random_random - date::date - ed25519::ed25519 - LibArchive::LibArchive - OpenSSL::Crypto - Xrpl::boost - Xrpl::libs - Xrpl::opts - Xrpl::syslibs - secp256k1::secp256k1 - xrpl.libpb - xxHash::xxhash - $<$:antithesis-sdk-cpp>) + INTERFACE + absl::random_random + date::date + ed25519::ed25519 + LibArchive::LibArchive + OpenSSL::Crypto + Xrpl::boost + Xrpl::libs + Xrpl::opts + Xrpl::syslibs + secp256k1::secp256k1 + xrpl.libpb + xxHash::xxhash + $<$:antithesis-sdk-cpp> +) include(add_module) include(target_link_modules) @@ -60,8 +72,12 @@ target_link_libraries(xrpl.libxrpl.beast PUBLIC xrpl.imports.main) include(GitInfo) add_module(xrpl git) -target_compile_definitions(xrpl.libxrpl.git PRIVATE GIT_COMMIT_HASH="${GIT_COMMIT_HASH}" - GIT_BUILD_BRANCH="${GIT_BUILD_BRANCH}") +target_compile_definitions( + xrpl.libxrpl.git + PRIVATE + GIT_COMMIT_HASH="${GIT_COMMIT_HASH}" + GIT_BUILD_BRANCH="${GIT_BUILD_BRANCH}" +) target_link_libraries(xrpl.libxrpl.git PUBLIC xrpl.imports.main) # Level 02 @@ -77,13 +93,17 @@ target_link_libraries(xrpl.libxrpl.crypto PUBLIC xrpl.libxrpl.basics) # Level 04 add_module(xrpl protocol) -target_link_libraries(xrpl.libxrpl.protocol PUBLIC xrpl.libxrpl.crypto xrpl.libxrpl.git - xrpl.libxrpl.json) +target_link_libraries( + xrpl.libxrpl.protocol + PUBLIC xrpl.libxrpl.crypto xrpl.libxrpl.git xrpl.libxrpl.json +) # Level 05 add_module(xrpl core) -target_link_libraries(xrpl.libxrpl.core PUBLIC xrpl.libxrpl.basics xrpl.libxrpl.json - xrpl.libxrpl.protocol) +target_link_libraries( + xrpl.libxrpl.core + PUBLIC xrpl.libxrpl.basics xrpl.libxrpl.json xrpl.libxrpl.protocol +) # Level 06 add_module(xrpl resource) @@ -91,23 +111,46 @@ target_link_libraries(xrpl.libxrpl.resource PUBLIC xrpl.libxrpl.protocol) # Level 07 add_module(xrpl net) -target_link_libraries(xrpl.libxrpl.net PUBLIC xrpl.libxrpl.basics xrpl.libxrpl.json - xrpl.libxrpl.protocol xrpl.libxrpl.resource) +target_link_libraries( + xrpl.libxrpl.net + PUBLIC + xrpl.libxrpl.basics + xrpl.libxrpl.json + xrpl.libxrpl.protocol + xrpl.libxrpl.resource +) add_module(xrpl nodestore) -target_link_libraries(xrpl.libxrpl.nodestore PUBLIC xrpl.libxrpl.basics xrpl.libxrpl.json - xrpl.libxrpl.protocol) +target_link_libraries( + xrpl.libxrpl.nodestore + PUBLIC xrpl.libxrpl.basics xrpl.libxrpl.json xrpl.libxrpl.protocol +) add_module(xrpl shamap) -target_link_libraries(xrpl.libxrpl.shamap PUBLIC xrpl.libxrpl.basics xrpl.libxrpl.crypto - xrpl.libxrpl.protocol xrpl.libxrpl.nodestore) +target_link_libraries( + xrpl.libxrpl.shamap + PUBLIC + xrpl.libxrpl.basics + xrpl.libxrpl.crypto + xrpl.libxrpl.protocol + xrpl.libxrpl.nodestore +) add_module(xrpl rdb) -target_link_libraries(xrpl.libxrpl.rdb PUBLIC xrpl.libxrpl.basics xrpl.libxrpl.core) +target_link_libraries( + xrpl.libxrpl.rdb + PUBLIC xrpl.libxrpl.basics xrpl.libxrpl.core +) add_module(xrpl server) -target_link_libraries(xrpl.libxrpl.server PUBLIC xrpl.libxrpl.protocol xrpl.libxrpl.core - xrpl.libxrpl.rdb xrpl.libxrpl.resource) +target_link_libraries( + xrpl.libxrpl.server + PUBLIC + xrpl.libxrpl.protocol + xrpl.libxrpl.core + xrpl.libxrpl.rdb + xrpl.libxrpl.resource +) add_module(xrpl conditions) target_link_libraries(xrpl.libxrpl.conditions PUBLIC xrpl.libxrpl.server) @@ -115,13 +158,15 @@ target_link_libraries(xrpl.libxrpl.conditions PUBLIC xrpl.libxrpl.server) add_module(xrpl ledger) target_link_libraries( xrpl.libxrpl.ledger - PUBLIC xrpl.libxrpl.basics - xrpl.libxrpl.json - xrpl.libxrpl.protocol - xrpl.libxrpl.rdb - xrpl.libxrpl.server - xrpl.libxrpl.shamap - xrpl.libxrpl.conditions) + PUBLIC + xrpl.libxrpl.basics + xrpl.libxrpl.json + xrpl.libxrpl.protocol + xrpl.libxrpl.rdb + xrpl.libxrpl.server + xrpl.libxrpl.shamap + xrpl.libxrpl.conditions +) add_module(xrpl tx) target_link_libraries(xrpl.libxrpl.tx PUBLIC xrpl.libxrpl.ledger) @@ -131,7 +176,11 @@ set_target_properties(xrpl.libxrpl PROPERTIES OUTPUT_NAME xrpl) add_library(xrpl::libxrpl ALIAS xrpl.libxrpl) -file(GLOB_RECURSE sources CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src/libxrpl/*.cpp") +file( + GLOB_RECURSE sources + CONFIGURE_DEPENDS + "${CMAKE_CURRENT_SOURCE_DIR}/src/libxrpl/*.cpp" +) target_sources(xrpl.libxrpl PRIVATE ${sources}) target_link_modules( @@ -152,7 +201,8 @@ target_link_modules( resource server shamap - tx) + tx +) # All headers in libxrpl are in modules. # Uncomment this stanza if you have not yet moved new headers into a module. @@ -163,34 +213,51 @@ target_link_modules( # $ # $) -if (xrpld) +if(xrpld) add_executable(xrpld) - if (tests) + if(tests) target_compile_definitions(xrpld PUBLIC ENABLE_TESTS) - target_compile_definitions(xrpld PRIVATE UNIT_TEST_REFERENCE_FEE=${UNIT_TEST_REFERENCE_FEE}) - endif () - target_include_directories(xrpld PRIVATE $) + target_compile_definitions( + xrpld + PRIVATE UNIT_TEST_REFERENCE_FEE=${UNIT_TEST_REFERENCE_FEE} + ) + endif() + target_include_directories( + xrpld + PRIVATE $ + ) - file(GLOB_RECURSE sources CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src/xrpld/*.cpp") + file( + GLOB_RECURSE sources + CONFIGURE_DEPENDS + "${CMAKE_CURRENT_SOURCE_DIR}/src/xrpld/*.cpp" + ) target_sources(xrpld PRIVATE ${sources}) - if (tests) - file(GLOB_RECURSE sources CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src/test/*.cpp") + if(tests) + file( + GLOB_RECURSE sources + CONFIGURE_DEPENDS + "${CMAKE_CURRENT_SOURCE_DIR}/src/test/*.cpp" + ) target_sources(xrpld PRIVATE ${sources}) - endif () + endif() target_link_libraries(xrpld Xrpl::boost Xrpl::opts Xrpl::libs xrpl.libxrpl) exclude_if_included(xrpld) # define a macro for tests that might need to # be excluded or run differently in CI environment - if (is_ci) + if(is_ci) target_compile_definitions(xrpld PRIVATE XRPL_RUNNING_IN_CI) - endif () + endif() - if (voidstar) + if(voidstar) target_compile_options(xrpld PRIVATE -fsanitize-coverage=trace-pc-guard) # xrpld requires access to antithesis-sdk-cpp implementation file # antithesis_instrumentation.h, which is not exported as INTERFACE - target_include_directories(xrpld PRIVATE ${CMAKE_SOURCE_DIR}/external/antithesis-sdk) - endif () -endif () + target_include_directories( + xrpld + PRIVATE ${CMAKE_SOURCE_DIR}/external/antithesis-sdk + ) + endif() +endif() diff --git a/cmake/XrplCov.cmake b/cmake/XrplCov.cmake index 62c40407f2..109f5049fd 100644 --- a/cmake/XrplCov.cmake +++ b/cmake/XrplCov.cmake @@ -2,14 +2,17 @@ coverage report target #]===================================================================] -if (NOT coverage) +if(NOT coverage) message(FATAL_ERROR "Code coverage not enabled! Aborting ...") -endif () +endif() -if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC") - message(WARNING "Code coverage on Windows is not supported, ignoring 'coverage' flag") +if(CMAKE_CXX_COMPILER_ID MATCHES "MSVC") + message( + WARNING + "Code coverage on Windows is not supported, ignoring 'coverage' flag" + ) return() -endif () +endif() include(ProcessorCount) ProcessorCount(PROCESSOR_COUNT) @@ -21,18 +24,19 @@ include(CodeCoverage) # `CodeCoverage.cmake`) set(GCOVR_ADDITIONAL_ARGS ${coverage_extra_args}) -if (NOT GCOVR_ADDITIONAL_ARGS STREQUAL "") +if(NOT GCOVR_ADDITIONAL_ARGS STREQUAL "") separate_arguments(GCOVR_ADDITIONAL_ARGS) -endif () +endif() -list(APPEND - GCOVR_ADDITIONAL_ARGS - --exclude-throw-branches - --exclude-noncode-lines - --exclude-unreachable-branches - -s - -j - ${PROCESSOR_COUNT}) +list( + APPEND GCOVR_ADDITIONAL_ARGS + --exclude-throw-branches + --exclude-noncode-lines + --exclude-unreachable-branches + -s + -j + ${PROCESSOR_COUNT} +) setup_target_for_coverage_gcovr( NAME @@ -47,6 +51,7 @@ setup_target_for_coverage_gcovr( "${CMAKE_BINARY_DIR}/pb-xrpl.libpb" DEPENDENCIES xrpld - xrpl.tests) + xrpl.tests +) add_code_coverage_to_target(opts INTERFACE) diff --git a/cmake/XrplDocs.cmake b/cmake/XrplDocs.cmake index 4892e390ce..aefffba81b 100644 --- a/cmake/XrplDocs.cmake +++ b/cmake/XrplDocs.cmake @@ -2,44 +2,45 @@ docs target (optional) #]===================================================================] -if (NOT only_docs) +if(NOT only_docs) return() -endif () +endif() find_package(Doxygen) -if (NOT TARGET Doxygen::doxygen) +if(NOT TARGET Doxygen::doxygen) message(STATUS "doxygen executable not found -- skipping docs target") return() -endif () +endif() set(doxygen_output_directory "${CMAKE_BINARY_DIR}/docs") set(doxygen_include_path "${CMAKE_CURRENT_SOURCE_DIR}/src") set(doxygen_index_file "${doxygen_output_directory}/html/index.html") set(doxyfile "${CMAKE_CURRENT_SOURCE_DIR}/docs/Doxyfile") -file(GLOB_RECURSE - doxygen_input - docs/*.md - include/*.h - include/*.cpp - include/*.md - src/*.h - src/*.cpp - src/*.md - Builds/*.md - *.md) +file( + GLOB_RECURSE doxygen_input + docs/*.md + include/*.h + include/*.cpp + include/*.md + src/*.h + src/*.cpp + src/*.md + Builds/*.md + *.md +) list(APPEND doxygen_input external/README.md) set(dependencies "${doxygen_input}" "${doxyfile}") -function (verbose_find_path variable name) +function(verbose_find_path variable name) # find_path sets a CACHE variable, so don't try using a "local" variable. find_path(${variable} "${name}" ${ARGN}) - if (NOT ${variable}) + if(NOT ${variable}) message(NOTICE "could not find ${name}") - else () + else() message(STATUS "found ${name}: ${${variable}}/${name}") - endif () -endfunction () + endif() +endfunction() verbose_find_path(doxygen_plantuml_jar_path plantuml.jar PATH_SUFFIXES share/plantuml) verbose_find_path(doxygen_dot_path dot) @@ -47,26 +48,40 @@ verbose_find_path(doxygen_dot_path dot) # https://en.cppreference.com/w/Cppreference:Archives # https://stackoverflow.com/questions/60822559/how-to-move-a-file-download-from-configure-step-to-build-step set(download_script "${CMAKE_BINARY_DIR}/docs/download-cppreference.cmake") -file(WRITE "${download_script}" - "file(DOWNLOAD \ +file( + WRITE "${download_script}" + "file(DOWNLOAD \ https://github.com/PeterFeicht/cppreference-doc/releases/download/v20250209/html-book-20250209.zip \ ${CMAKE_BINARY_DIR}/docs/cppreference.zip \ EXPECTED_HASH MD5=bda585f72fbca4b817b29a3d5746567b \ )\n \ execute_process( \ COMMAND \"${CMAKE_COMMAND}\" -E tar -xf cppreference.zip \ - )\n") + )\n" +) set(tagfile "${CMAKE_BINARY_DIR}/docs/cppreference-doxygen-web.tag.xml") -add_custom_command(OUTPUT "${tagfile}" COMMAND "${CMAKE_COMMAND}" -P "${download_script}" - WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/docs") +add_custom_command( + OUTPUT "${tagfile}" + COMMAND "${CMAKE_COMMAND}" -P "${download_script}" + WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/docs" +) set(doxygen_tagfiles "${tagfile}=http://en.cppreference.com/w/") add_custom_command( OUTPUT "${doxygen_index_file}" - COMMAND "${CMAKE_COMMAND}" -E env "DOXYGEN_OUTPUT_DIRECTORY=${doxygen_output_directory}" - "DOXYGEN_INCLUDE_PATH=${doxygen_include_path}" "DOXYGEN_TAGFILES=${doxygen_tagfiles}" - "DOXYGEN_PLANTUML_JAR_PATH=${doxygen_plantuml_jar_path}" - "DOXYGEN_DOT_PATH=${doxygen_dot_path}" "${DOXYGEN_EXECUTABLE}" "${doxyfile}" + COMMAND + "${CMAKE_COMMAND}" -E env + "DOXYGEN_OUTPUT_DIRECTORY=${doxygen_output_directory}" + "DOXYGEN_INCLUDE_PATH=${doxygen_include_path}" + "DOXYGEN_TAGFILES=${doxygen_tagfiles}" + "DOXYGEN_PLANTUML_JAR_PATH=${doxygen_plantuml_jar_path}" + "DOXYGEN_DOT_PATH=${doxygen_dot_path}" "${DOXYGEN_EXECUTABLE}" + "${doxyfile}" WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" - DEPENDS "${dependencies}" "${tagfile}") -add_custom_target(docs DEPENDS "${doxygen_index_file}" SOURCES "${dependencies}") + DEPENDS "${dependencies}" "${tagfile}" +) +add_custom_target( + docs + DEPENDS "${doxygen_index_file}" + SOURCES "${dependencies}" +) diff --git a/cmake/XrplInstall.cmake b/cmake/XrplInstall.cmake index 666fc0712f..6ea41b5ffd 100644 --- a/cmake/XrplInstall.cmake +++ b/cmake/XrplInstall.cmake @@ -6,57 +6,71 @@ include(create_symbolic_link) # If no suffix is defined for executables (e.g. Windows uses .exe but Linux # and macOS use none), then explicitly set it to the empty string. -if (NOT DEFINED suffix) +if(NOT DEFINED suffix) set(suffix "") -endif () +endif() -install(TARGETS common - opts - xrpl_boost - xrpl_libs - xrpl_syslibs - xrpl.imports.main - xrpl.libpb - xrpl.libxrpl - xrpl.libxrpl.basics - xrpl.libxrpl.beast - xrpl.libxrpl.conditions - xrpl.libxrpl.core - xrpl.libxrpl.crypto - xrpl.libxrpl.git - xrpl.libxrpl.json - xrpl.libxrpl.rdb - xrpl.libxrpl.ledger - xrpl.libxrpl.net - xrpl.libxrpl.nodestore - xrpl.libxrpl.protocol - xrpl.libxrpl.resource - xrpl.libxrpl.server - xrpl.libxrpl.shamap - xrpl.libxrpl.tx - antithesis-sdk-cpp - EXPORT XrplExports - LIBRARY DESTINATION lib - ARCHIVE DESTINATION lib - RUNTIME DESTINATION bin - INCLUDES - DESTINATION include) +install( + TARGETS + common + opts + xrpl_boost + xrpl_libs + xrpl_syslibs + xrpl.imports.main + xrpl.libpb + xrpl.libxrpl + xrpl.libxrpl.basics + xrpl.libxrpl.beast + xrpl.libxrpl.conditions + xrpl.libxrpl.core + xrpl.libxrpl.crypto + xrpl.libxrpl.git + xrpl.libxrpl.json + xrpl.libxrpl.rdb + xrpl.libxrpl.ledger + xrpl.libxrpl.net + xrpl.libxrpl.nodestore + xrpl.libxrpl.protocol + xrpl.libxrpl.resource + xrpl.libxrpl.server + xrpl.libxrpl.shamap + xrpl.libxrpl.tx + antithesis-sdk-cpp + EXPORT XrplExports + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib + RUNTIME DESTINATION bin + INCLUDES DESTINATION include +) -install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/include/xrpl" - DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}") +install( + DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/include/xrpl" + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" +) -install(EXPORT XrplExports FILE XrplTargets.cmake NAMESPACE Xrpl:: DESTINATION lib/cmake/xrpl) +install( + EXPORT XrplExports + FILE XrplTargets.cmake + NAMESPACE Xrpl:: + DESTINATION lib/cmake/xrpl +) include(CMakePackageConfigHelpers) -write_basic_package_version_file(XrplConfigVersion.cmake VERSION ${xrpld_version} - COMPATIBILITY SameMajorVersion) +write_basic_package_version_file( + XrplConfigVersion.cmake + VERSION ${xrpld_version} + COMPATIBILITY SameMajorVersion +) -if (is_root_project AND TARGET xrpld) +if(is_root_project AND TARGET xrpld) install(TARGETS xrpld RUNTIME DESTINATION bin) set_target_properties(xrpld PROPERTIES INSTALL_RPATH_USE_LINK_PATH ON) # sample configs should not overwrite existing files # install if-not-exists workaround as suggested by # https://cmake.org/Bug/view.php?id=12646 - install(CODE " + install( + CODE + " macro (copy_if_not_exists SRC DEST NEWNAME) if (NOT EXISTS \"\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/\${DEST}/\${NEWNAME}\") file (INSTALL FILE_PERMISSIONS OWNER_READ OWNER_WRITE DESTINATION \"\${CMAKE_INSTALL_PREFIX}/\${DEST}\" FILES \"\${SRC}\" RENAME \"\${NEWNAME}\") @@ -66,14 +80,22 @@ if (is_root_project AND TARGET xrpld) endmacro() copy_if_not_exists(\"${CMAKE_CURRENT_SOURCE_DIR}/cfg/xrpld-example.cfg\" etc xrpld.cfg) copy_if_not_exists(\"${CMAKE_CURRENT_SOURCE_DIR}/cfg/validators-example.txt\" etc validators.txt) - ") - install(CODE " + " + ) + install( + CODE + " set(CMAKE_MODULE_PATH \"${CMAKE_MODULE_PATH}\") include(create_symbolic_link) create_symbolic_link(xrpld${suffix} \ \$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}/rippled${suffix}) - ") -endif () + " + ) +endif() -install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/cmake/XrplConfig.cmake - ${CMAKE_CURRENT_BINARY_DIR}/XrplConfigVersion.cmake DESTINATION lib/cmake/xrpl) +install( + FILES + ${CMAKE_CURRENT_SOURCE_DIR}/cmake/XrplConfig.cmake + ${CMAKE_CURRENT_BINARY_DIR}/XrplConfigVersion.cmake + DESTINATION lib/cmake/xrpl +) diff --git a/cmake/XrplInterface.cmake b/cmake/XrplInterface.cmake index dbf907c7b4..21fa76501d 100644 --- a/cmake/XrplInterface.cmake +++ b/cmake/XrplInterface.cmake @@ -5,47 +5,55 @@ include(CompilationEnv) # Set defaults for optional variables to avoid uninitialized variable warnings -if (NOT DEFINED voidstar) +if(NOT DEFINED voidstar) set(voidstar OFF) -endif () +endif() add_library(opts INTERFACE) add_library(Xrpl::opts ALIAS opts) target_compile_definitions( opts - INTERFACE BOOST_ASIO_DISABLE_HANDLER_TYPE_REQUIREMENTS - BOOST_ASIO_USE_TS_EXECUTOR_AS_DEFAULT - BOOST_CONTAINER_FWD_BAD_DEQUE - HAS_UNCAUGHT_EXCEPTIONS=1 - $<$: - BOOST_ASIO_NO_DEPRECATED - BOOST_FILESYSTEM_NO_DEPRECATED - > - $<$>: - BOOST_COROUTINES_NO_DEPRECATION_WARNING - BOOST_BEAST_ALLOW_DEPRECATED - BOOST_FILESYSTEM_DEPRECATED - > - $<$:BEAST_NO_UNIT_TEST_INLINE=1> - $<$:BEAST_DONT_AUTOLINK_TO_WIN32_LIBRARIES=1> - $<$:XRPL_SINGLE_IO_SERVICE_THREAD=1> - $<$:ENABLE_VOIDSTAR>) + INTERFACE + BOOST_ASIO_DISABLE_HANDLER_TYPE_REQUIREMENTS + BOOST_ASIO_USE_TS_EXECUTOR_AS_DEFAULT + BOOST_CONTAINER_FWD_BAD_DEQUE + HAS_UNCAUGHT_EXCEPTIONS=1 + $<$: + BOOST_ASIO_NO_DEPRECATED + BOOST_FILESYSTEM_NO_DEPRECATED + > + $<$>: + BOOST_COROUTINES_NO_DEPRECATION_WARNING + BOOST_BEAST_ALLOW_DEPRECATED + BOOST_FILESYSTEM_DEPRECATED + > + $<$:BEAST_NO_UNIT_TEST_INLINE=1> + $<$:BEAST_DONT_AUTOLINK_TO_WIN32_LIBRARIES=1> + $<$:XRPL_SINGLE_IO_SERVICE_THREAD=1> + $<$:ENABLE_VOIDSTAR> +) target_compile_options( opts - INTERFACE $<$,$>:-Wsuggest-override> - $<$:-Wno-maybe-uninitialized> - $<$:-fno-omit-frame-pointer> - $<$:-pg> - $<$,$>:-p>) + INTERFACE + $<$,$>:-Wsuggest-override> + $<$:-Wno-maybe-uninitialized> + $<$:-fno-omit-frame-pointer> + $<$:-pg> + $<$,$>:-p> +) -target_link_libraries(opts INTERFACE $<$:-pg> - $<$,$>:-p>) +target_link_libraries( + opts + INTERFACE + $<$:-pg> + $<$,$>:-p> +) -if (jemalloc) +if(jemalloc) find_package(jemalloc REQUIRED) target_compile_definitions(opts INTERFACE PROFILE_JEMALLOC) target_link_libraries(opts INTERFACE jemalloc::jemalloc) -endif () +endif() #[===================================================================[ xrpld transitive library deps via an interface library @@ -55,31 +63,33 @@ add_library(xrpl_syslibs INTERFACE) add_library(Xrpl::syslibs ALIAS xrpl_syslibs) target_link_libraries( xrpl_syslibs - INTERFACE $<$: - legacy_stdio_definitions.lib - Shlwapi - kernel32 - user32 - gdi32 - winspool - comdlg32 - advapi32 - shell32 - ole32 - oleaut32 - uuid - odbc32 - odbccp32 - crypt32 - > - $<$>:dl> - $<$,$>>:rt>) + INTERFACE + $<$: + legacy_stdio_definitions.lib + Shlwapi + kernel32 + user32 + gdi32 + winspool + comdlg32 + advapi32 + shell32 + ole32 + oleaut32 + uuid + odbc32 + odbccp32 + crypt32 + > + $<$>:dl> + $<$,$>>:rt> +) -if (NOT is_msvc) +if(NOT is_msvc) set(THREADS_PREFER_PTHREAD_FLAG ON) find_package(Threads) target_link_libraries(xrpl_syslibs INTERFACE Threads::Threads) -endif () +endif() add_library(xrpl_libs INTERFACE) add_library(Xrpl::libs ALIAS xrpl_libs) diff --git a/cmake/XrplSanitizers.cmake b/cmake/XrplSanitizers.cmake index 6ddc0b7e7d..f9630f6856 100644 --- a/cmake/XrplSanitizers.cmake +++ b/cmake/XrplSanitizers.cmake @@ -44,23 +44,26 @@ include(CompilationEnv) # Read environment variable set(SANITIZERS "") -if (DEFINED ENV{SANITIZERS}) +if(DEFINED ENV{SANITIZERS}) set(SANITIZERS "$ENV{SANITIZERS}") -endif () +endif() # Set SANITIZERS_ENABLED flag for use in other modules -if (SANITIZERS MATCHES "address|thread|undefinedbehavior") +if(SANITIZERS MATCHES "address|thread|undefinedbehavior") set(SANITIZERS_ENABLED TRUE) -else () +else() set(SANITIZERS_ENABLED FALSE) return() -endif () +endif() # Sanitizers are not supported on Windows/MSVC -if (is_msvc) - message(FATAL_ERROR "Sanitizers are not supported on Windows/MSVC. " - "Please unset the SANITIZERS environment variable.") -endif () +if(is_msvc) + message( + FATAL_ERROR + "Sanitizers are not supported on Windows/MSVC. " + "Please unset the SANITIZERS environment variable." + ) +endif() message(STATUS "Configuring sanitizers: ${SANITIZERS}") @@ -74,24 +77,30 @@ set(san_list "${SANITIZERS}") string(REPLACE "," ";" san_list "${san_list}") separate_arguments(san_list) -foreach (san IN LISTS san_list) - if (san STREQUAL "address") +foreach(san IN LISTS san_list) + if(san STREQUAL "address") set(enable_asan TRUE) - elseif (san STREQUAL "thread") + elseif(san STREQUAL "thread") set(enable_tsan TRUE) - elseif (san STREQUAL "undefinedbehavior") + elseif(san STREQUAL "undefinedbehavior") set(enable_ubsan TRUE) - else () - message(FATAL_ERROR "Unsupported sanitizer type: ${san}" - "Supported: address, thread, undefinedbehavior and their combinations.") - endif () -endforeach () + else() + message( + FATAL_ERROR + "Unsupported sanitizer type: ${san}" + "Supported: address, thread, undefinedbehavior and their combinations." + ) + endif() +endforeach() # Validate sanitizer compatibility -if (enable_asan AND enable_tsan) - message(FATAL_ERROR "AddressSanitizer and ThreadSanitizer are incompatible and cannot be enabled simultaneously. " - "Use 'address' or 'thread', optionally with 'undefinedbehavior'.") -endif () +if(enable_asan AND enable_tsan) + message( + FATAL_ERROR + "AddressSanitizer and ThreadSanitizer are incompatible and cannot be enabled simultaneously. " + "Use 'address' or 'thread', optionally with 'undefinedbehavior'." + ) +endif() # Frame pointer is required for meaningful stack traces. Sanitizers recommend minimum of -O1 for reasonable performance set(SANITIZERS_COMPILE_FLAGS "-fno-omit-frame-pointer" "-O1") @@ -99,66 +108,79 @@ set(SANITIZERS_COMPILE_FLAGS "-fno-omit-frame-pointer" "-O1") # Build the sanitizer flags list set(SANITIZER_TYPES) -if (enable_asan) +if(enable_asan) list(APPEND SANITIZER_TYPES "address") -elseif (enable_tsan) +elseif(enable_tsan) list(APPEND SANITIZER_TYPES "thread") -endif () +endif() -if (enable_ubsan) +if(enable_ubsan) # UB sanitizer flags list(APPEND SANITIZER_TYPES "undefined" "float-divide-by-zero") - if (is_clang) + if(is_clang) # Clang supports additional UB checks. More info here # https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html list(APPEND SANITIZER_TYPES "unsigned-integer-overflow") - endif () -endif () + endif() +endif() # Configure code model for GCC on amd64 Use large code model for ASAN to avoid relocation errors Use medium code model # for TSAN (large is not compatible with TSAN) set(SANITIZERS_RELOCATION_FLAGS) # Compiler-specific configuration -if (is_gcc) +if(is_gcc) # Disable mold, gold and lld linkers for GCC with sanitizers Use default linker (bfd/ld) which is more lenient with # mixed code models This is needed since the size of instrumented binary exceeds the limits set by mold, lld and # gold linkers set(use_mold OFF CACHE BOOL "Use mold linker" FORCE) set(use_gold OFF CACHE BOOL "Use gold linker" FORCE) set(use_lld OFF CACHE BOOL "Use lld linker" FORCE) - message(STATUS " Disabled mold, gold, and lld linkers for GCC with sanitizers") + message( + STATUS + " Disabled mold, gold, and lld linkers for GCC with sanitizers" + ) # Suppress false positive warnings in GCC with stringop-overflow list(APPEND SANITIZERS_COMPILE_FLAGS "-Wno-stringop-overflow") - if (is_amd64 AND enable_asan) + if(is_amd64 AND enable_asan) message(STATUS " Using large code model (-mcmodel=large)") list(APPEND SANITIZERS_COMPILE_FLAGS "-mcmodel=large") list(APPEND SANITIZERS_RELOCATION_FLAGS "-mcmodel=large") - elseif (enable_tsan) + elseif(enable_tsan) # GCC doesn't support atomic_thread_fence with tsan. Suppress warnings. list(APPEND SANITIZERS_COMPILE_FLAGS "-Wno-tsan") message(STATUS " Using medium code model (-mcmodel=medium)") list(APPEND SANITIZERS_COMPILE_FLAGS "-mcmodel=medium") list(APPEND SANITIZERS_RELOCATION_FLAGS "-mcmodel=medium") - endif () + endif() # Join sanitizer flags with commas for -fsanitize option list(JOIN SANITIZER_TYPES "," SANITIZER_TYPES_STR) # Add sanitizer to compile and link flags list(APPEND SANITIZERS_COMPILE_FLAGS "-fsanitize=${SANITIZER_TYPES_STR}") - set(SANITIZERS_LINK_FLAGS "${SANITIZERS_RELOCATION_FLAGS}" "-fsanitize=${SANITIZER_TYPES_STR}") - -elseif (is_clang) + set(SANITIZERS_LINK_FLAGS + "${SANITIZERS_RELOCATION_FLAGS}" + "-fsanitize=${SANITIZER_TYPES_STR}" + ) +elseif(is_clang) # Add ignorelist for Clang (GCC doesn't support this) Use CMAKE_SOURCE_DIR to get the path to the ignorelist - set(IGNORELIST_PATH "${CMAKE_SOURCE_DIR}/sanitizers/suppressions/sanitizer-ignorelist.txt") - if (NOT EXISTS "${IGNORELIST_PATH}") - message(FATAL_ERROR "Sanitizer ignorelist not found: ${IGNORELIST_PATH}") - endif () + set(IGNORELIST_PATH + "${CMAKE_SOURCE_DIR}/sanitizers/suppressions/sanitizer-ignorelist.txt" + ) + if(NOT EXISTS "${IGNORELIST_PATH}") + message( + FATAL_ERROR + "Sanitizer ignorelist not found: ${IGNORELIST_PATH}" + ) + endif() - list(APPEND SANITIZERS_COMPILE_FLAGS "-fsanitize-ignorelist=${IGNORELIST_PATH}") + list( + APPEND SANITIZERS_COMPILE_FLAGS + "-fsanitize-ignorelist=${IGNORELIST_PATH}" + ) message(STATUS " Using sanitizer ignorelist: ${IGNORELIST_PATH}") # Join sanitizer flags with commas for -fsanitize option @@ -167,31 +189,35 @@ elseif (is_clang) # Add sanitizer to compile and link flags list(APPEND SANITIZERS_COMPILE_FLAGS "-fsanitize=${SANITIZER_TYPES_STR}") set(SANITIZERS_LINK_FLAGS "-fsanitize=${SANITIZER_TYPES_STR}") -endif () +endif() message(STATUS " Compile flags: ${SANITIZERS_COMPILE_FLAGS}") message(STATUS " Link flags: ${SANITIZERS_LINK_FLAGS}") # Apply the sanitizer flags to the 'common' interface library This is the same library used by XrplCompiler.cmake -target_compile_options(common INTERFACE $<$:${SANITIZERS_COMPILE_FLAGS}> - $<$:${SANITIZERS_COMPILE_FLAGS}>) +target_compile_options( + common + INTERFACE + $<$:${SANITIZERS_COMPILE_FLAGS}> + $<$:${SANITIZERS_COMPILE_FLAGS}> +) # Apply linker flags target_link_options(common INTERFACE ${SANITIZERS_LINK_FLAGS}) # Define SANITIZERS macro for BuildInfo.cpp set(sanitizers_list) -if (enable_asan) +if(enable_asan) list(APPEND sanitizers_list "ASAN") -endif () -if (enable_tsan) +endif() +if(enable_tsan) list(APPEND sanitizers_list "TSAN") -endif () -if (enable_ubsan) +endif() +if(enable_ubsan) list(APPEND sanitizers_list "UBSAN") -endif () +endif() -if (sanitizers_list) +if(sanitizers_list) list(JOIN sanitizers_list "." sanitizers_str) target_compile_definitions(common INTERFACE SANITIZERS=${sanitizers_str}) -endif () +endif() diff --git a/cmake/XrplSanity.cmake b/cmake/XrplSanity.cmake index 489ad026e1..24b4d4d408 100644 --- a/cmake/XrplSanity.cmake +++ b/cmake/XrplSanity.cmake @@ -7,40 +7,49 @@ include(CompilationEnv) get_property(is_multiconfig GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) set(CMAKE_CONFIGURATION_TYPES "Debug;Release" CACHE STRING "" FORCE) -if (NOT is_multiconfig) - if (NOT CMAKE_BUILD_TYPE) +if(NOT is_multiconfig) + if(NOT CMAKE_BUILD_TYPE) message(STATUS "Build type not specified - defaulting to Release") set(CMAKE_BUILD_TYPE Release CACHE STRING "build type" FORCE) - elseif (NOT (CMAKE_BUILD_TYPE STREQUAL Debug OR CMAKE_BUILD_TYPE STREQUAL Release)) + elseif( + NOT (CMAKE_BUILD_TYPE STREQUAL Debug OR CMAKE_BUILD_TYPE STREQUAL Release) + ) # for simplicity, these are the only two config types we care about. Limiting the build types simplifies dealing # with external project builds especially - message(FATAL_ERROR " *** Only Debug or Release build types are currently supported ***") - endif () -endif () + message( + FATAL_ERROR + " *** Only Debug or Release build types are currently supported ***" + ) + endif() +endif() -if (is_clang) # both Clang and AppleClang - if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS - 16.0) +if(is_clang) # both Clang and AppleClang + if( + "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" + AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 16.0 + ) message(FATAL_ERROR "This project requires clang 16 or later") - endif () -elseif (is_gcc) - if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 12.0) + endif() +elseif(is_gcc) + if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 12.0) message(FATAL_ERROR "This project requires GCC 12 or later") - endif () -endif () + endif() +endif() # check for in-source build and fail -if ("${CMAKE_CURRENT_SOURCE_DIR}" STREQUAL "${CMAKE_BINARY_DIR}") - message(FATAL_ERROR "Builds (in-source) are not allowed in " - "${CMAKE_CURRENT_SOURCE_DIR}. Please remove CMakeCache.txt and the CMakeFiles " - "directory from ${CMAKE_CURRENT_SOURCE_DIR} and try building in a separate directory." +if("${CMAKE_CURRENT_SOURCE_DIR}" STREQUAL "${CMAKE_BINARY_DIR}") + message( + FATAL_ERROR + "Builds (in-source) are not allowed in " + "${CMAKE_CURRENT_SOURCE_DIR}. Please remove CMakeCache.txt and the CMakeFiles " + "directory from ${CMAKE_CURRENT_SOURCE_DIR} and try building in a separate directory." ) -endif () +endif() -if (MSVC AND CMAKE_GENERATOR_PLATFORM STREQUAL "Win32") +if(MSVC AND CMAKE_GENERATOR_PLATFORM STREQUAL "Win32") message(FATAL_ERROR "Visual Studio 32-bit build is not supported.") -endif () +endif() -if (APPLE AND NOT HOMEBREW) +if(APPLE AND NOT HOMEBREW) find_program(HOMEBREW brew) -endif () +endif() diff --git a/cmake/XrplSettings.cmake b/cmake/XrplSettings.cmake index 5c753fdcda..44a727a994 100644 --- a/cmake/XrplSettings.cmake +++ b/cmake/XrplSettings.cmake @@ -5,59 +5,67 @@ include(CompilationEnv) set(is_ci FALSE) -if (DEFINED ENV{CI}) - if ("$ENV{CI}" STREQUAL "true") +if(DEFINED ENV{CI}) + if("$ENV{CI}" STREQUAL "true") set(is_ci TRUE) - endif () -endif () + endif() +endif() get_directory_property(has_parent PARENT_DIRECTORY) -if (has_parent) +if(has_parent) set(is_root_project OFF) -else () +else() set(is_root_project ON) -endif () +endif() option(assert "Enables asserts, even in release builds" OFF) option(xrpld "Build xrpld" ON) option(tests "Build tests" ON) -if (tests) +if(tests) # This setting allows making a separate workflow to test fees other than default 10 - if (NOT UNIT_TEST_REFERENCE_FEE) + if(NOT UNIT_TEST_REFERENCE_FEE) set(UNIT_TEST_REFERENCE_FEE "10" CACHE STRING "") - endif () -endif () + endif() +endif() option(unity "Creates a build using UNITY support in cmake." OFF) -if (unity) - if (NOT is_ci) +if(unity) + if(NOT is_ci) set(CMAKE_UNITY_BUILD_BATCH_SIZE 15 CACHE STRING "") - endif () + endif() set(CMAKE_UNITY_BUILD ON CACHE BOOL "Do a unity build") -endif () +endif() -if (is_clang AND is_linux) +if(is_clang AND is_linux) option(voidstar "Enable Antithesis instrumentation." OFF) -endif () +endif() -if (is_gcc OR is_clang) +if(is_gcc OR is_clang) include(ProcessorCount) ProcessorCount(PROCESSOR_COUNT) option(coverage "Generates coverage info." OFF) option(profile "Add profiling flags" OFF) - set(coverage_format "html-details" CACHE STRING "Output format of the coverage report.") - set(coverage_extra_args "" CACHE STRING "Additional arguments to pass to gcovr.") + set(coverage_format + "html-details" + CACHE STRING + "Output format of the coverage report." + ) + set(coverage_extra_args + "" + CACHE STRING + "Additional arguments to pass to gcovr." + ) option(wextra "compile with extra gcc/clang warnings enabled" ON) -else () +else() set(profile OFF CACHE BOOL "gcc/clang only" FORCE) set(coverage OFF CACHE BOOL "gcc/clang only" FORCE) set(wextra OFF CACHE BOOL "gcc/clang only" FORCE) -endif () +endif() -if (is_linux AND NOT SANITIZER) +if(is_linux AND NOT SANITIZER) option(BUILD_SHARED_LIBS "build shared xrpl libraries" OFF) option(static "link protobuf, openssl, libc++, and boost statically" ON) option(perf "Enables flags that assist with perf recording" OFF) @@ -65,53 +73,83 @@ if (is_linux AND NOT SANITIZER) option(use_mold "enables detection of mold (binutils) linker" ON) # Set a default value for the log flag based on the build type. This provides a sensible default (on for debug, off # for release) while still allowing the user to override it for any build. - if (CMAKE_BUILD_TYPE STREQUAL "Debug") + if(CMAKE_BUILD_TYPE STREQUAL "Debug") set(TRUNCATED_LOGS_DEFAULT ON) - else () + else() set(TRUNCATED_LOGS_DEFAULT OFF) - endif () - option(TRUNCATED_THREAD_NAME_LOGS "Show warnings about truncated thread names on Linux." - ${TRUNCATED_LOGS_DEFAULT}) - if (TRUNCATED_THREAD_NAME_LOGS) + endif() + option( + TRUNCATED_THREAD_NAME_LOGS + "Show warnings about truncated thread names on Linux." + ${TRUNCATED_LOGS_DEFAULT} + ) + if(TRUNCATED_THREAD_NAME_LOGS) add_compile_definitions(TRUNCATED_THREAD_NAME_LOGS) - endif () -else () + endif() +else() # we are not ready to allow shared-libs on windows because it would require export declarations. On macos it's more # feasible, but static openssl produces odd linker errors, thus we disable shared lib builds for now. - set(BUILD_SHARED_LIBS OFF CACHE BOOL "build shared xrpl libraries - OFF for win/macos" FORCE) + set(BUILD_SHARED_LIBS + OFF + CACHE BOOL + "build shared xrpl libraries - OFF for win/macos" + FORCE + ) set(static ON CACHE BOOL "static link, linux only. ON for WIN/macos" FORCE) set(perf OFF CACHE BOOL "perf flags, linux only" FORCE) set(use_gold OFF CACHE BOOL "gold linker, linux only" FORCE) set(use_mold OFF CACHE BOOL "mold linker, linux only" FORCE) -endif () +endif() -if (is_clang) +if(is_clang) option(use_lld "enables detection of lld linker" ON) -else () +else() set(use_lld OFF CACHE BOOL "try lld linker, clang only" FORCE) -endif () +endif() option(jemalloc "Enables jemalloc for heap profiling" OFF) option(werr "treat warnings as errors" OFF) -option(local_protobuf - "Force a local build of protobuf instead of looking for an installed version." OFF) -option(local_grpc "Force a local build of gRPC instead of looking for an installed version." OFF) +option( + local_protobuf + "Force a local build of protobuf instead of looking for an installed version." + OFF +) +option( + local_grpc + "Force a local build of gRPC instead of looking for an installed version." + OFF +) # the remaining options are obscure and rarely used -option(beast_no_unit_test_inline - "Prevents unit test definitions from being inserted into global table" OFF) -option(single_io_service_thread "Restricts the number of threads calling io_context::run to one. \ - This can be useful when debugging." OFF) -option(boost_show_deprecated "Allow boost to fail on deprecated usage. Only useful if you're trying\ - to find deprecated calls." OFF) +option( + beast_no_unit_test_inline + "Prevents unit test definitions from being inserted into global table" + OFF +) +option( + single_io_service_thread + "Restricts the number of threads calling io_context::run to one. \ + This can be useful when debugging." + OFF +) +option( + boost_show_deprecated + "Allow boost to fail on deprecated usage. Only useful if you're trying\ + to find deprecated calls." + OFF +) -if (WIN32) - option(beast_disable_autolink "Disables autolinking of system libraries on WIN32" OFF) -else () +if(WIN32) + option( + beast_disable_autolink + "Disables autolinking of system libraries on WIN32" + OFF + ) +else() set(beast_disable_autolink OFF CACHE BOOL "WIN32 only" FORCE) -endif () +endif() -if (coverage) +if(coverage) message(STATUS "coverage build requested - forcing Debug build") set(CMAKE_BUILD_TYPE Debug CACHE STRING "build type" FORCE) -endif () +endif() diff --git a/cmake/XrplValidatorKeys.cmake b/cmake/XrplValidatorKeys.cmake index 58672aada5..0e511b6a88 100644 --- a/cmake/XrplValidatorKeys.cmake +++ b/cmake/XrplValidatorKeys.cmake @@ -1,20 +1,26 @@ -option(validator_keys - "Enables building of validator-keys tool as a separate target (imported via FetchContent)" - OFF) +option( + validator_keys + "Enables building of validator-keys tool as a separate target (imported via FetchContent)" + OFF +) -if (validator_keys) +if(validator_keys) git_branch(current_branch) # default to tracking VK master branch unless we are on release - if (NOT (current_branch STREQUAL "release")) + if(NOT (current_branch STREQUAL "release")) set(current_branch "master") - endif () + endif() message(STATUS "Tracking ValidatorKeys branch: ${current_branch}") FetchContent_Declare( - validator_keys GIT_REPOSITORY https://github.com/ripple/validator-keys-tool.git - GIT_TAG "${current_branch}") + validator_keys + GIT_REPOSITORY https://github.com/ripple/validator-keys-tool.git + GIT_TAG "${current_branch}" + ) FetchContent_MakeAvailable(validator_keys) - set_target_properties(validator-keys PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}") + set_target_properties( + validator-keys + PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}" + ) install(TARGETS validator-keys RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) - -endif () +endif() diff --git a/cmake/XrplVersion.cmake b/cmake/XrplVersion.cmake index 6eeda7b1eb..595b750d69 100644 --- a/cmake/XrplVersion.cmake +++ b/cmake/XrplVersion.cmake @@ -3,13 +3,13 @@ #]===================================================================] file(STRINGS src/libxrpl/protocol/BuildInfo.cpp BUILD_INFO) -foreach (line_ ${BUILD_INFO}) - if (line_ MATCHES "versionString[ ]*=[ ]*\"(.+)\"") +foreach(line_ ${BUILD_INFO}) + if(line_ MATCHES "versionString[ ]*=[ ]*\"(.+)\"") set(xrpld_version ${CMAKE_MATCH_1}) - endif () -endforeach () -if (xrpld_version) + endif() +endforeach() +if(xrpld_version) message(STATUS "xrpld version: ${xrpld_version}") -else () +else() message(FATAL_ERROR "unable to determine xrpld version") -endif () +endif() diff --git a/cmake/add_module.cmake b/cmake/add_module.cmake index 73618e8be3..5b1864a0bb 100644 --- a/cmake/add_module.cmake +++ b/cmake/add_module.cmake @@ -12,15 +12,23 @@ include(isolate_headers) # add_module(parent a) # add_module(parent b) # target_link_libraries(project.libparent.b PUBLIC project.libparent.a) -function (add_module parent name) +function(add_module parent name) set(target ${PROJECT_NAME}.lib${parent}.${name}) add_library(${target} OBJECT) - file(GLOB_RECURSE sources CONFIGURE_DEPENDS - "${CMAKE_CURRENT_SOURCE_DIR}/src/lib${parent}/${name}/*.cpp") + file( + GLOB_RECURSE sources + CONFIGURE_DEPENDS + "${CMAKE_CURRENT_SOURCE_DIR}/src/lib${parent}/${name}/*.cpp" + ) target_sources(${target} PRIVATE ${sources}) - target_include_directories(${target} PUBLIC "$") + target_include_directories( + ${target} + PUBLIC "$" + ) isolate_headers(${target} "${CMAKE_CURRENT_SOURCE_DIR}/include" - "${CMAKE_CURRENT_SOURCE_DIR}/include/${parent}/${name}" PUBLIC) + "${CMAKE_CURRENT_SOURCE_DIR}/include/${parent}/${name}" PUBLIC + ) isolate_headers(${target} "${CMAKE_CURRENT_SOURCE_DIR}/src" - "${CMAKE_CURRENT_SOURCE_DIR}/src/lib${parent}/${name}" PRIVATE) -endfunction () + "${CMAKE_CURRENT_SOURCE_DIR}/src/lib${parent}/${name}" PRIVATE + ) +endfunction() diff --git a/cmake/create_symbolic_link.cmake b/cmake/create_symbolic_link.cmake index 82ca915f5c..0cd8e1f567 100644 --- a/cmake/create_symbolic_link.cmake +++ b/cmake/create_symbolic_link.cmake @@ -1,19 +1,21 @@ # file(CREATE_SYMLINK) only works on Windows with administrator privileges. https://stackoverflow.com/a/61244115/618906 -function (create_symbolic_link target link) - if (WIN32) - if (NOT IS_SYMLINK "${link}") - if (NOT IS_ABSOLUTE "${target}") +function(create_symbolic_link target link) + if(WIN32) + if(NOT IS_SYMLINK "${link}") + if(NOT IS_ABSOLUTE "${target}") # Relative links work do not work on Windows. set(target "${link}/../${target}") - endif () + endif() file(TO_NATIVE_PATH "${target}" target) file(TO_NATIVE_PATH "${link}" link) - execute_process(COMMAND cmd.exe /c mklink /J "${link}" "${target}") - endif () - else () + execute_process( + COMMAND cmd.exe /c mklink /J "${link}" "${target}" + ) + endif() + else() file(CREATE_LINK "${target}" "${link}" SYMBOLIC) - endif () - if (NOT IS_SYMLINK "${link}") + endif() + if(NOT IS_SYMLINK "${link}") message(ERROR "failed to create symlink: <${link}>") - endif () -endfunction () + endif() +endfunction() diff --git a/cmake/deps/Boost.cmake b/cmake/deps/Boost.cmake index a7b145364d..10dc3e271a 100644 --- a/cmake/deps/Boost.cmake +++ b/cmake/deps/Boost.cmake @@ -1,45 +1,60 @@ include(CompilationEnv) include(XrplSanitizers) -find_package(Boost REQUIRED - COMPONENTS chrono - container - coroutine - date_time - filesystem - json - program_options - regex - system - thread) +find_package( + Boost + REQUIRED + COMPONENTS + chrono + container + coroutine + date_time + filesystem + json + program_options + regex + system + thread +) add_library(xrpl_boost INTERFACE) add_library(Xrpl::boost ALIAS xrpl_boost) target_link_libraries( xrpl_boost - INTERFACE Boost::headers - Boost::chrono - Boost::container - Boost::coroutine - Boost::date_time - Boost::filesystem - Boost::json - Boost::process - Boost::program_options - Boost::regex - Boost::thread) -if (Boost_COMPILER) + INTERFACE + Boost::headers + Boost::chrono + Boost::container + Boost::coroutine + Boost::date_time + Boost::filesystem + Boost::json + Boost::process + Boost::program_options + Boost::regex + Boost::thread +) +if(Boost_COMPILER) target_link_libraries(xrpl_boost INTERFACE Boost::disable_autolinking) -endif () -if (SANITIZERS_ENABLED AND is_clang) +endif() +if(SANITIZERS_ENABLED AND is_clang) # TODO: gcc does not support -fsanitize-blacklist...can we do something else for gcc ? - if (NOT Boost_INCLUDE_DIRS AND TARGET Boost::headers) - get_target_property(Boost_INCLUDE_DIRS Boost::headers INTERFACE_INCLUDE_DIRECTORIES) - endif () + if(NOT Boost_INCLUDE_DIRS AND TARGET Boost::headers) + get_target_property( + Boost_INCLUDE_DIRS + Boost::headers + INTERFACE_INCLUDE_DIRECTORIES + ) + endif() message(STATUS "Adding [${Boost_INCLUDE_DIRS}] to sanitizer blacklist") - file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/san_bl.txt "src:${Boost_INCLUDE_DIRS}/*") + file( + WRITE ${CMAKE_CURRENT_BINARY_DIR}/san_bl.txt + "src:${Boost_INCLUDE_DIRS}/*" + ) target_compile_options( - opts INTERFACE # ignore boost headers for sanitizing - -fsanitize-blacklist=${CMAKE_CURRENT_BINARY_DIR}/san_bl.txt) -endif () + opts + INTERFACE # ignore boost headers for sanitizing + -fsanitize-blacklist=${CMAKE_CURRENT_BINARY_DIR}/san_bl.txt + ) +endif() diff --git a/cmake/isolate_headers.cmake b/cmake/isolate_headers.cmake index ef53b3f20a..4053cd6831 100644 --- a/cmake/isolate_headers.cmake +++ b/cmake/isolate_headers.cmake @@ -37,7 +37,7 @@ include(create_symbolic_link) # `${CMAKE_CURRENT_BINARY_DIR}/include/${target}`. # # isolate_headers(target A B scope) -function (isolate_headers target A B scope) +function(isolate_headers target A B scope) file(RELATIVE_PATH C "${A}" "${B}") set(X "${CMAKE_CURRENT_BINARY_DIR}/modules/${target}") set(Y "${X}/${C}") @@ -45,4 +45,4 @@ function (isolate_headers target A B scope) file(MAKE_DIRECTORY "${parent}") create_symbolic_link("${B}" "${Y}") target_include_directories(${target} ${scope} "$") -endfunction () +endfunction() diff --git a/cmake/target_link_modules.cmake b/cmake/target_link_modules.cmake index bcd80591b0..a923d81c66 100644 --- a/cmake/target_link_modules.cmake +++ b/cmake/target_link_modules.cmake @@ -6,9 +6,9 @@ # target_link_libraries(project.libparent.b PUBLIC project.libparent.a) # add_library(project.libparent) # target_link_modules(parent PUBLIC a b) -function (target_link_modules parent scope) +function(target_link_modules parent scope) set(library ${PROJECT_NAME}.lib${parent}) - foreach (name ${ARGN}) + foreach(name ${ARGN}) set(module ${library}.${name}) get_target_property(sources ${library} SOURCES) list(LENGTH sources before) @@ -17,8 +17,11 @@ function (target_link_modules parent scope) list(REMOVE_ITEM sources ${dupes}) list(LENGTH sources after) math(EXPR actual "${before} - ${after}") - message(STATUS "${module} with ${expected} sources took ${actual} sources from ${library}") + message( + STATUS + "${module} with ${expected} sources took ${actual} sources from ${library}" + ) set_target_properties(${library} PROPERTIES SOURCES "${sources}") target_link_libraries(${library} ${scope} ${module}) - endforeach () -endfunction () + endforeach() +endfunction() diff --git a/cmake/target_protobuf_sources.cmake b/cmake/target_protobuf_sources.cmake index bb5de02b53..2a04de9e11 100644 --- a/cmake/target_protobuf_sources.cmake +++ b/cmake/target_protobuf_sources.cmake @@ -35,20 +35,31 @@ find_package(Protobuf REQUIRED) # This prefix should appear at the start of all your consumer includes. # ARGN: # A list of .proto files. -function (target_protobuf_sources target prefix) +function(target_protobuf_sources target prefix) set(dir "${CMAKE_CURRENT_BINARY_DIR}/pb-${target}") file(MAKE_DIRECTORY "${dir}/${prefix}") - protobuf_generate(TARGET ${target} PROTOC_OUT_DIR "${dir}/${prefix}" "${ARGN}") + protobuf_generate( + TARGET ${target} + PROTOC_OUT_DIR "${dir}/${prefix}" + "${ARGN}" + ) target_include_directories( - ${target} SYSTEM + ${target} + SYSTEM PUBLIC # Allows #include used by consumer files. - $ - # Allows #include "path/to/file.proto" used by generated files. - $ - # Allows #include used by consumer files. - $ - # Allows #include "path/to/file.proto" used by generated files. - $) - install(DIRECTORY ${dir}/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} FILES_MATCHING PATTERN "*.h") -endfunction () + $ + # Allows #include "path/to/file.proto" used by generated files. + $ + # Allows #include used by consumer files. + $ + # Allows #include "path/to/file.proto" used by generated files. + $ + ) + install( + DIRECTORY ${dir}/ + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} + FILES_MATCHING + PATTERN "*.h" + ) +endfunction() diff --git a/src/tests/libxrpl/CMakeLists.txt b/src/tests/libxrpl/CMakeLists.txt index cfa056b0aa..6bcb6aff89 100644 --- a/src/tests/libxrpl/CMakeLists.txt +++ b/src/tests/libxrpl/CMakeLists.txt @@ -14,7 +14,10 @@ target_link_libraries(xrpl.helpers.test PRIVATE xrpl.libxrpl) # Common library dependencies for the rest of the tests. add_library(xrpl.imports.test INTERFACE) -target_link_libraries(xrpl.imports.test INTERFACE gtest::gtest xrpl.libxrpl xrpl.helpers.test) +target_link_libraries( + xrpl.imports.test + INTERFACE gtest::gtest xrpl.libxrpl xrpl.helpers.test +) # One test for each module. xrpl_add_test(basics) @@ -30,8 +33,8 @@ target_link_libraries(xrpl.test.json PRIVATE xrpl.imports.test) add_dependencies(xrpl.tests xrpl.test.json) # Network unit tests are currently not supported on Windows -if (NOT WIN32) +if(NOT WIN32) xrpl_add_test(net) target_link_libraries(xrpl.test.net PRIVATE xrpl.imports.test) add_dependencies(xrpl.tests xrpl.test.net) -endif () +endif() From 5865bd017f777491b4a956f9210be0c4161f5442 Mon Sep 17 00:00:00 2001 From: Vito Tumas <5780819+Tapanito@users.noreply.github.com> Date: Fri, 6 Mar 2026 09:25:31 +0100 Subject: [PATCH 34/41] refactor: Update transaction folder structure (#6483) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change reorganizes the `tx/transactors` directory for consistency and discoverability. There are no behavioral changes, this is a pure refactor. Underscores were chosen as the way to separate multi-words as this is the more popular option in C++ projects. Specific changes: - Rename all subdirectories to lowercase/snake_case (`AMM` → `amm`, `Check` → `check`, `NFT` → `nft`, `PermissionedDomain` → `permissioned_domain`, etc.) - Merge `AMM/` and `Offer/` into `dex/`, including `PermissionedDEXHelpers` - Rename `MPT/` → `token/`, absorbing `SetTrust` and `Clawback` - Move top-level transactors into named groups: `account/`, `bridge/`, `credentials/`, `did/`, `escrow/`, `oracle/`, `payment/`, `payment_channel/`, `system/` - Update all include paths across the codebase and `transactions.macro` --- .../xrpl/protocol/detail/transactions.macro | 120 +++++++++--------- include/xrpl/tx/paths/detail/StrandFlow.h | 4 +- .../transactors/{ => account}/DeleteAccount.h | 0 .../tx/transactors/{ => account}/SetAccount.h | 0 .../transactors/{ => account}/SetRegularKey.h | 0 .../transactors/{ => account}/SetSignerList.h | 0 .../transactors/{ => bridge}/XChainBridge.h | 0 .../{Check => check}/CancelCheck.h | 0 .../transactors/{Check => check}/CashCheck.h | 0 .../{Check => check}/CreateCheck.h | 0 .../{ => credentials}/Credentials.h | 0 .../{Delegate => delegate}/DelegateSet.h | 0 .../{Delegate => delegate}/DelegateUtils.h | 0 .../xrpl/tx/transactors/{AMM => dex}/AMMBid.h | 0 .../tx/transactors/{AMM => dex}/AMMClawback.h | 0 .../tx/transactors/{AMM => dex}/AMMContext.h | 0 .../tx/transactors/{AMM => dex}/AMMCreate.h | 0 .../tx/transactors/{AMM => dex}/AMMDelete.h | 0 .../tx/transactors/{AMM => dex}/AMMDeposit.h | 0 .../tx/transactors/{AMM => dex}/AMMHelpers.h | 0 .../tx/transactors/{AMM => dex}/AMMUtils.h | 0 .../tx/transactors/{AMM => dex}/AMMVote.h | 0 .../tx/transactors/{AMM => dex}/AMMWithdraw.h | 0 .../transactors/{Offer => dex}/CancelOffer.h | 0 .../transactors/{Offer => dex}/CreateOffer.h | 0 .../{ => dex}/PermissionedDEXHelpers.h | 0 include/xrpl/tx/transactors/{ => did}/DID.h | 0 .../xrpl/tx/transactors/{ => escrow}/Escrow.h | 0 .../{Lending => lending}/LendingHelpers.h | 0 .../LoanBrokerCoverClawback.h | 0 .../LoanBrokerCoverDeposit.h | 0 .../LoanBrokerCoverWithdraw.h | 0 .../{Lending => lending}/LoanBrokerDelete.h | 0 .../{Lending => lending}/LoanBrokerSet.h | 0 .../{Lending => lending}/LoanDelete.h | 0 .../{Lending => lending}/LoanManage.h | 0 .../{Lending => lending}/LoanPay.h | 0 .../{Lending => lending}/LoanSet.h | 2 +- .../{NFT => nft}/NFTokenAcceptOffer.h | 0 .../tx/transactors/{NFT => nft}/NFTokenBurn.h | 0 .../{NFT => nft}/NFTokenCancelOffer.h | 0 .../{NFT => nft}/NFTokenCreateOffer.h | 0 .../tx/transactors/{NFT => nft}/NFTokenMint.h | 2 +- .../transactors/{NFT => nft}/NFTokenModify.h | 0 .../transactors/{NFT => nft}/NFTokenUtils.h | 0 .../transactors/{ => oracle}/DeleteOracle.h | 0 .../tx/transactors/{ => oracle}/SetOracle.h | 0 .../{ => payment}/DepositPreauth.h | 0 .../tx/transactors/{ => payment}/Payment.h | 0 .../{ => payment_channel}/PayChan.h | 0 .../PermissionedDomainDelete.h | 0 .../PermissionedDomainSet.h | 0 .../xrpl/tx/transactors/{ => system}/Batch.h | 0 .../xrpl/tx/transactors/{ => system}/Change.h | 0 .../transactors/{ => system}/CreateTicket.h | 0 .../transactors/{ => system}/LedgerStateFix.h | 0 .../tx/transactors/{ => token}/Clawback.h | 0 .../{MPT => token}/MPTokenAuthorize.h | 0 .../{MPT => token}/MPTokenIssuanceCreate.h | 0 .../{MPT => token}/MPTokenIssuanceDestroy.h | 0 .../{MPT => token}/MPTokenIssuanceSet.h | 0 .../tx/transactors/{ => token}/SetTrust.h | 0 .../{Vault => vault}/VaultClawback.h | 0 .../{Vault => vault}/VaultCreate.h | 0 .../{Vault => vault}/VaultDelete.h | 0 .../{Vault => vault}/VaultDeposit.h | 0 .../transactors/{Vault => vault}/VaultSet.h | 0 .../{Vault => vault}/VaultWithdraw.h | 0 src/libxrpl/tx/Transactor.cpp | 4 +- src/libxrpl/tx/invariants/AMMInvariant.cpp | 4 +- src/libxrpl/tx/invariants/NFTInvariant.cpp | 2 +- src/libxrpl/tx/paths/Flow.cpp | 2 +- src/libxrpl/tx/paths/OfferStream.cpp | 2 +- .../{ => account}/DeleteAccount.cpp | 14 +- .../transactors/{ => account}/SetAccount.cpp | 4 +- .../{ => account}/SetRegularKey.cpp | 2 +- .../{ => account}/SetSignerList.cpp | 2 +- .../transactors/{ => bridge}/XChainBridge.cpp | 2 +- .../{Check => check}/CancelCheck.cpp | 2 +- .../{Check => check}/CashCheck.cpp | 2 +- .../{Check => check}/CreateCheck.cpp | 2 +- .../{ => credentials}/Credentials.cpp | 2 +- .../{Delegate => delegate}/DelegateSet.cpp | 2 +- .../{Delegate => delegate}/DelegateUtils.cpp | 2 +- .../tx/transactors/{AMM => dex}/AMMBid.cpp | 6 +- .../transactors/{AMM => dex}/AMMClawback.cpp | 8 +- .../tx/transactors/{AMM => dex}/AMMCreate.cpp | 6 +- .../tx/transactors/{AMM => dex}/AMMDelete.cpp | 4 +- .../transactors/{AMM => dex}/AMMDeposit.cpp | 6 +- .../transactors/{AMM => dex}/AMMHelpers.cpp | 2 +- .../tx/transactors/{AMM => dex}/AMMUtils.cpp | 4 +- .../tx/transactors/{AMM => dex}/AMMVote.cpp | 4 +- .../transactors/{AMM => dex}/AMMWithdraw.cpp | 6 +- .../{Offer => dex}/CancelOffer.cpp | 2 +- .../{Offer => dex}/CreateOffer.cpp | 4 +- .../PermissionedDEXHelpers.cpp | 2 +- src/libxrpl/tx/transactors/{ => did}/DID.cpp | 2 +- .../tx/transactors/{ => escrow}/Escrow.cpp | 4 +- .../{Lending => lending}/LendingHelpers.cpp | 4 +- .../LoanBrokerCoverClawback.cpp | 4 +- .../LoanBrokerCoverDeposit.cpp | 4 +- .../LoanBrokerCoverWithdraw.cpp | 6 +- .../{Lending => lending}/LoanBrokerDelete.cpp | 4 +- .../{Lending => lending}/LoanBrokerSet.cpp | 4 +- .../{Lending => lending}/LoanDelete.cpp | 4 +- .../{Lending => lending}/LoanManage.cpp | 4 +- .../{Lending => lending}/LoanPay.cpp | 6 +- .../{Lending => lending}/LoanSet.cpp | 4 +- .../{NFT => nft}/NFTokenAcceptOffer.cpp | 4 +- .../transactors/{NFT => nft}/NFTokenBurn.cpp | 4 +- .../{NFT => nft}/NFTokenCancelOffer.cpp | 4 +- .../{NFT => nft}/NFTokenCreateOffer.cpp | 4 +- .../transactors/{NFT => nft}/NFTokenMint.cpp | 2 +- .../{NFT => nft}/NFTokenModify.cpp | 4 +- .../transactors/{NFT => nft}/NFTokenUtils.cpp | 2 +- .../transactors/{ => oracle}/DeleteOracle.cpp | 2 +- .../tx/transactors/{ => oracle}/SetOracle.cpp | 2 +- .../{ => payment}/DepositPreauth.cpp | 2 +- .../tx/transactors/{ => payment}/Payment.cpp | 6 +- .../{ => payment_channel}/PayChan.cpp | 2 +- .../PermissionedDomainDelete.cpp | 2 +- .../PermissionedDomainSet.cpp | 2 +- .../tx/transactors/{ => system}/Batch.cpp | 2 +- .../tx/transactors/{ => system}/Change.cpp | 2 +- .../transactors/{ => system}/CreateTicket.cpp | 2 +- .../{ => system}/LedgerStateFix.cpp | 4 +- .../tx/transactors/{ => token}/Clawback.cpp | 2 +- .../{MPT => token}/MPTokenAuthorize.cpp | 2 +- .../{MPT => token}/MPTokenIssuanceCreate.cpp | 2 +- .../{MPT => token}/MPTokenIssuanceDestroy.cpp | 2 +- .../{MPT => token}/MPTokenIssuanceSet.cpp | 4 +- .../tx/transactors/{ => token}/SetTrust.cpp | 4 +- .../{Vault => vault}/VaultClawback.cpp | 2 +- .../{Vault => vault}/VaultCreate.cpp | 6 +- .../{Vault => vault}/VaultDelete.cpp | 2 +- .../{Vault => vault}/VaultDeposit.cpp | 4 +- .../transactors/{Vault => vault}/VaultSet.cpp | 2 +- .../{Vault => vault}/VaultWithdraw.cpp | 2 +- src/test/app/AMMCalc_test.cpp | 2 +- src/test/app/AMMClawback_test.cpp | 2 +- src/test/app/AMMExtended_test.cpp | 4 +- src/test/app/AMM_test.cpp | 8 +- src/test/app/Batch_test.cpp | 2 +- src/test/app/FixNFTokenPageLinks_test.cpp | 2 +- src/test/app/LendingHelpers_test.cpp | 6 +- src/test/app/LoanBroker_test.cpp | 2 +- src/test/app/Loan_test.cpp | 6 +- src/test/app/NFTokenAuth_test.cpp | 2 +- src/test/app/NFTokenBurn_test.cpp | 2 +- src/test/app/NFTokenDir_test.cpp | 2 +- src/test/app/NFToken_test.cpp | 2 +- src/test/app/PayStrand_test.cpp | 2 +- src/test/app/PermissionedDEX_test.cpp | 2 +- src/test/app/PermissionedDomains_test.cpp | 2 +- src/test/app/TheoreticalQuality_test.cpp | 2 +- src/test/jtx/impl/AMM.cpp | 4 +- src/test/jtx/impl/ledgerStateFixes.cpp | 2 +- src/test/jtx/impl/token.cpp | 2 +- src/test/rpc/AccountObjects_test.cpp | 2 +- src/xrpld/app/ledger/OrderBookDBImpl.cpp | 2 +- src/xrpld/app/paths/AMMLiquidity.h | 6 +- src/xrpld/app/paths/detail/BookStep.cpp | 2 +- src/xrpld/rpc/detail/RPCHelpers.cpp | 2 +- src/xrpld/rpc/handlers/AMMInfo.cpp | 2 +- src/xrpld/rpc/handlers/AccountObjects.cpp | 2 +- 165 files changed, 222 insertions(+), 222 deletions(-) rename include/xrpl/tx/transactors/{ => account}/DeleteAccount.h (100%) rename include/xrpl/tx/transactors/{ => account}/SetAccount.h (100%) rename include/xrpl/tx/transactors/{ => account}/SetRegularKey.h (100%) rename include/xrpl/tx/transactors/{ => account}/SetSignerList.h (100%) rename include/xrpl/tx/transactors/{ => bridge}/XChainBridge.h (100%) rename include/xrpl/tx/transactors/{Check => check}/CancelCheck.h (100%) rename include/xrpl/tx/transactors/{Check => check}/CashCheck.h (100%) rename include/xrpl/tx/transactors/{Check => check}/CreateCheck.h (100%) rename include/xrpl/tx/transactors/{ => credentials}/Credentials.h (100%) rename include/xrpl/tx/transactors/{Delegate => delegate}/DelegateSet.h (100%) rename include/xrpl/tx/transactors/{Delegate => delegate}/DelegateUtils.h (100%) rename include/xrpl/tx/transactors/{AMM => dex}/AMMBid.h (100%) rename include/xrpl/tx/transactors/{AMM => dex}/AMMClawback.h (100%) rename include/xrpl/tx/transactors/{AMM => dex}/AMMContext.h (100%) rename include/xrpl/tx/transactors/{AMM => dex}/AMMCreate.h (100%) rename include/xrpl/tx/transactors/{AMM => dex}/AMMDelete.h (100%) rename include/xrpl/tx/transactors/{AMM => dex}/AMMDeposit.h (100%) rename include/xrpl/tx/transactors/{AMM => dex}/AMMHelpers.h (100%) rename include/xrpl/tx/transactors/{AMM => dex}/AMMUtils.h (100%) rename include/xrpl/tx/transactors/{AMM => dex}/AMMVote.h (100%) rename include/xrpl/tx/transactors/{AMM => dex}/AMMWithdraw.h (100%) rename include/xrpl/tx/transactors/{Offer => dex}/CancelOffer.h (100%) rename include/xrpl/tx/transactors/{Offer => dex}/CreateOffer.h (100%) rename include/xrpl/tx/transactors/{ => dex}/PermissionedDEXHelpers.h (100%) rename include/xrpl/tx/transactors/{ => did}/DID.h (100%) rename include/xrpl/tx/transactors/{ => escrow}/Escrow.h (100%) rename include/xrpl/tx/transactors/{Lending => lending}/LendingHelpers.h (100%) rename include/xrpl/tx/transactors/{Lending => lending}/LoanBrokerCoverClawback.h (100%) rename include/xrpl/tx/transactors/{Lending => lending}/LoanBrokerCoverDeposit.h (100%) rename include/xrpl/tx/transactors/{Lending => lending}/LoanBrokerCoverWithdraw.h (100%) rename include/xrpl/tx/transactors/{Lending => lending}/LoanBrokerDelete.h (100%) rename include/xrpl/tx/transactors/{Lending => lending}/LoanBrokerSet.h (100%) rename include/xrpl/tx/transactors/{Lending => lending}/LoanDelete.h (100%) rename include/xrpl/tx/transactors/{Lending => lending}/LoanManage.h (100%) rename include/xrpl/tx/transactors/{Lending => lending}/LoanPay.h (100%) rename include/xrpl/tx/transactors/{Lending => lending}/LoanSet.h (96%) rename include/xrpl/tx/transactors/{NFT => nft}/NFTokenAcceptOffer.h (100%) rename include/xrpl/tx/transactors/{NFT => nft}/NFTokenBurn.h (100%) rename include/xrpl/tx/transactors/{NFT => nft}/NFTokenCancelOffer.h (100%) rename include/xrpl/tx/transactors/{NFT => nft}/NFTokenCreateOffer.h (100%) rename include/xrpl/tx/transactors/{NFT => nft}/NFTokenMint.h (94%) rename include/xrpl/tx/transactors/{NFT => nft}/NFTokenModify.h (100%) rename include/xrpl/tx/transactors/{NFT => nft}/NFTokenUtils.h (100%) rename include/xrpl/tx/transactors/{ => oracle}/DeleteOracle.h (100%) rename include/xrpl/tx/transactors/{ => oracle}/SetOracle.h (100%) rename include/xrpl/tx/transactors/{ => payment}/DepositPreauth.h (100%) rename include/xrpl/tx/transactors/{ => payment}/Payment.h (100%) rename include/xrpl/tx/transactors/{ => payment_channel}/PayChan.h (100%) rename include/xrpl/tx/transactors/{PermissionedDomain => permissioned_domain}/PermissionedDomainDelete.h (100%) rename include/xrpl/tx/transactors/{PermissionedDomain => permissioned_domain}/PermissionedDomainSet.h (100%) rename include/xrpl/tx/transactors/{ => system}/Batch.h (100%) rename include/xrpl/tx/transactors/{ => system}/Change.h (100%) rename include/xrpl/tx/transactors/{ => system}/CreateTicket.h (100%) rename include/xrpl/tx/transactors/{ => system}/LedgerStateFix.h (100%) rename include/xrpl/tx/transactors/{ => token}/Clawback.h (100%) rename include/xrpl/tx/transactors/{MPT => token}/MPTokenAuthorize.h (100%) rename include/xrpl/tx/transactors/{MPT => token}/MPTokenIssuanceCreate.h (100%) rename include/xrpl/tx/transactors/{MPT => token}/MPTokenIssuanceDestroy.h (100%) rename include/xrpl/tx/transactors/{MPT => token}/MPTokenIssuanceSet.h (100%) rename include/xrpl/tx/transactors/{ => token}/SetTrust.h (100%) rename include/xrpl/tx/transactors/{Vault => vault}/VaultClawback.h (100%) rename include/xrpl/tx/transactors/{Vault => vault}/VaultCreate.h (100%) rename include/xrpl/tx/transactors/{Vault => vault}/VaultDelete.h (100%) rename include/xrpl/tx/transactors/{Vault => vault}/VaultDeposit.h (100%) rename include/xrpl/tx/transactors/{Vault => vault}/VaultSet.h (100%) rename include/xrpl/tx/transactors/{Vault => vault}/VaultWithdraw.h (100%) rename src/libxrpl/tx/transactors/{ => account}/DeleteAccount.cpp (97%) rename src/libxrpl/tx/transactors/{ => account}/SetAccount.cpp (99%) rename src/libxrpl/tx/transactors/{ => account}/SetRegularKey.cpp (96%) rename src/libxrpl/tx/transactors/{ => account}/SetSignerList.cpp (99%) rename src/libxrpl/tx/transactors/{ => bridge}/XChainBridge.cpp (99%) rename src/libxrpl/tx/transactors/{Check => check}/CancelCheck.cpp (98%) rename src/libxrpl/tx/transactors/{Check => check}/CashCheck.cpp (99%) rename src/libxrpl/tx/transactors/{Check => check}/CreateCheck.cpp (99%) rename src/libxrpl/tx/transactors/{ => credentials}/Credentials.cpp (99%) rename src/libxrpl/tx/transactors/{Delegate => delegate}/DelegateSet.cpp (98%) rename src/libxrpl/tx/transactors/{Delegate => delegate}/DelegateUtils.cpp (95%) rename src/libxrpl/tx/transactors/{AMM => dex}/AMMBid.cpp (98%) rename src/libxrpl/tx/transactors/{AMM => dex}/AMMClawback.cpp (98%) rename src/libxrpl/tx/transactors/{AMM => dex}/AMMCreate.cpp (98%) rename src/libxrpl/tx/transactors/{AMM => dex}/AMMDelete.cpp (92%) rename src/libxrpl/tx/transactors/{AMM => dex}/AMMDeposit.cpp (99%) rename src/libxrpl/tx/transactors/{AMM => dex}/AMMHelpers.cpp (99%) rename src/libxrpl/tx/transactors/{AMM => dex}/AMMUtils.cpp (99%) rename src/libxrpl/tx/transactors/{AMM => dex}/AMMVote.cpp (98%) rename src/libxrpl/tx/transactors/{AMM => dex}/AMMWithdraw.cpp (99%) rename src/libxrpl/tx/transactors/{Offer => dex}/CancelOffer.cpp (96%) rename src/libxrpl/tx/transactors/{Offer => dex}/CreateOffer.cpp (99%) rename src/libxrpl/tx/transactors/{PermissionedDomain => dex}/PermissionedDEXHelpers.cpp (97%) rename src/libxrpl/tx/transactors/{ => did}/DID.cpp (99%) rename src/libxrpl/tx/transactors/{ => escrow}/Escrow.cpp (99%) rename src/libxrpl/tx/transactors/{Lending => lending}/LendingHelpers.cpp (99%) rename src/libxrpl/tx/transactors/{Lending => lending}/LoanBrokerCoverClawback.cpp (98%) rename src/libxrpl/tx/transactors/{Lending => lending}/LoanBrokerCoverDeposit.cpp (96%) rename src/libxrpl/tx/transactors/{Lending => lending}/LoanBrokerCoverWithdraw.cpp (96%) rename src/libxrpl/tx/transactors/{Lending => lending}/LoanBrokerDelete.cpp (97%) rename src/libxrpl/tx/transactors/{Lending => lending}/LoanBrokerSet.cpp (98%) rename src/libxrpl/tx/transactors/{Lending => lending}/LoanDelete.cpp (97%) rename src/libxrpl/tx/transactors/{Lending => lending}/LoanManage.cpp (99%) rename src/libxrpl/tx/transactors/{Lending => lending}/LoanPay.cpp (99%) rename src/libxrpl/tx/transactors/{Lending => lending}/LoanSet.cpp (99%) rename src/libxrpl/tx/transactors/{NFT => nft}/NFTokenAcceptOffer.cpp (99%) rename src/libxrpl/tx/transactors/{NFT => nft}/NFTokenBurn.cpp (96%) rename src/libxrpl/tx/transactors/{NFT => nft}/NFTokenCancelOffer.cpp (95%) rename src/libxrpl/tx/transactors/{NFT => nft}/NFTokenCreateOffer.cpp (94%) rename src/libxrpl/tx/transactors/{NFT => nft}/NFTokenMint.cpp (99%) rename src/libxrpl/tx/transactors/{NFT => nft}/NFTokenModify.cpp (93%) rename src/libxrpl/tx/transactors/{NFT => nft}/NFTokenUtils.cpp (99%) rename src/libxrpl/tx/transactors/{ => oracle}/DeleteOracle.cpp (97%) rename src/libxrpl/tx/transactors/{ => oracle}/SetOracle.cpp (99%) rename src/libxrpl/tx/transactors/{ => payment}/DepositPreauth.cpp (99%) rename src/libxrpl/tx/transactors/{ => payment}/Payment.cpp (99%) rename src/libxrpl/tx/transactors/{ => payment_channel}/PayChan.cpp (99%) rename src/libxrpl/tx/transactors/{PermissionedDomain => permissioned_domain}/PermissionedDomainDelete.cpp (95%) rename src/libxrpl/tx/transactors/{PermissionedDomain => permissioned_domain}/PermissionedDomainSet.cpp (98%) rename src/libxrpl/tx/transactors/{ => system}/Batch.cpp (99%) rename src/libxrpl/tx/transactors/{ => system}/Change.cpp (99%) rename src/libxrpl/tx/transactors/{ => system}/CreateTicket.cpp (98%) rename src/libxrpl/tx/transactors/{ => system}/LedgerStateFix.cpp (93%) rename src/libxrpl/tx/transactors/{ => token}/Clawback.cpp (99%) rename src/libxrpl/tx/transactors/{MPT => token}/MPTokenAuthorize.cpp (98%) rename src/libxrpl/tx/transactors/{MPT => token}/MPTokenIssuanceCreate.cpp (98%) rename src/libxrpl/tx/transactors/{MPT => token}/MPTokenIssuanceDestroy.cpp (95%) rename src/libxrpl/tx/transactors/{MPT => token}/MPTokenIssuanceSet.cpp (98%) rename src/libxrpl/tx/transactors/{ => token}/SetTrust.cpp (99%) rename src/libxrpl/tx/transactors/{Vault => vault}/VaultClawback.cpp (99%) rename src/libxrpl/tx/transactors/{Vault => vault}/VaultCreate.cpp (97%) rename src/libxrpl/tx/transactors/{Vault => vault}/VaultDelete.cpp (99%) rename src/libxrpl/tx/transactors/{Vault => vault}/VaultDeposit.cpp (98%) rename src/libxrpl/tx/transactors/{Vault => vault}/VaultSet.cpp (99%) rename src/libxrpl/tx/transactors/{Vault => vault}/VaultWithdraw.cpp (99%) diff --git a/include/xrpl/protocol/detail/transactions.macro b/include/xrpl/protocol/detail/transactions.macro index b696a1d1c2..08832acde7 100644 --- a/include/xrpl/protocol/detail/transactions.macro +++ b/include/xrpl/protocol/detail/transactions.macro @@ -22,7 +22,7 @@ /** This transaction type executes a payment. */ #if TRANSACTION_INCLUDE -# include +# include #endif TRANSACTION(ttPAYMENT, 0, Payment, Delegation::delegable, @@ -42,7 +42,7 @@ TRANSACTION(ttPAYMENT, 0, Payment, /** This transaction type creates an escrow object. */ #if TRANSACTION_INCLUDE -# include +# include #endif TRANSACTION(ttESCROW_CREATE, 1, EscrowCreate, Delegation::delegable, @@ -73,7 +73,7 @@ TRANSACTION(ttESCROW_FINISH, 2, EscrowFinish, /** This transaction type adjusts various account settings. */ #if TRANSACTION_INCLUDE -# include +# include #endif TRANSACTION(ttACCOUNT_SET, 3, AccountSet, Delegation::notDelegable, @@ -94,7 +94,7 @@ TRANSACTION(ttACCOUNT_SET, 3, AccountSet, /** This transaction type cancels an existing escrow. */ #if TRANSACTION_INCLUDE -# include +# include #endif TRANSACTION(ttESCROW_CANCEL, 4, EscrowCancel, Delegation::delegable, @@ -107,7 +107,7 @@ TRANSACTION(ttESCROW_CANCEL, 4, EscrowCancel, /** This transaction type sets or clears an account's "regular key". */ #if TRANSACTION_INCLUDE -# include +# include #endif TRANSACTION(ttREGULAR_KEY_SET, 5, SetRegularKey, Delegation::notDelegable, @@ -121,7 +121,7 @@ TRANSACTION(ttREGULAR_KEY_SET, 5, SetRegularKey, /** This transaction type creates an offer to trade one asset for another. */ #if TRANSACTION_INCLUDE -# include +# include #endif TRANSACTION(ttOFFER_CREATE, 7, OfferCreate, Delegation::delegable, @@ -137,7 +137,7 @@ TRANSACTION(ttOFFER_CREATE, 7, OfferCreate, /** This transaction type cancels existing offers to trade one asset for another. */ #if TRANSACTION_INCLUDE -# include +# include #endif TRANSACTION(ttOFFER_CANCEL, 8, OfferCancel, Delegation::delegable, @@ -151,7 +151,7 @@ TRANSACTION(ttOFFER_CANCEL, 8, OfferCancel, /** This transaction type creates a new set of tickets. */ #if TRANSACTION_INCLUDE -# include +# include #endif TRANSACTION(ttTICKET_CREATE, 10, TicketCreate, Delegation::delegable, @@ -167,7 +167,7 @@ TRANSACTION(ttTICKET_CREATE, 10, TicketCreate, // The SignerEntries are optional because a SignerList is deleted by // setting the SignerQuorum to zero and omitting SignerEntries. #if TRANSACTION_INCLUDE -# include +# include #endif TRANSACTION(ttSIGNER_LIST_SET, 12, SignerListSet, Delegation::notDelegable, @@ -180,7 +180,7 @@ TRANSACTION(ttSIGNER_LIST_SET, 12, SignerListSet, /** This transaction type creates a new unidirectional XRP payment channel. */ #if TRANSACTION_INCLUDE -# include +# include #endif TRANSACTION(ttPAYCHAN_CREATE, 13, PaymentChannelCreate, Delegation::delegable, @@ -222,7 +222,7 @@ TRANSACTION(ttPAYCHAN_CLAIM, 15, PaymentChannelClaim, /** This transaction type creates a new check. */ #if TRANSACTION_INCLUDE -# include +# include #endif TRANSACTION(ttCHECK_CREATE, 16, CheckCreate, Delegation::delegable, @@ -238,7 +238,7 @@ TRANSACTION(ttCHECK_CREATE, 16, CheckCreate, /** This transaction type cashes an existing check. */ #if TRANSACTION_INCLUDE -# include +# include #endif TRANSACTION(ttCHECK_CASH, 17, CheckCash, Delegation::delegable, @@ -252,7 +252,7 @@ TRANSACTION(ttCHECK_CASH, 17, CheckCash, /** This transaction type cancels an existing check. */ #if TRANSACTION_INCLUDE -# include +# include #endif TRANSACTION(ttCHECK_CANCEL, 18, CheckCancel, Delegation::delegable, @@ -264,7 +264,7 @@ TRANSACTION(ttCHECK_CANCEL, 18, CheckCancel, /** This transaction type grants or revokes authorization to transfer funds. */ #if TRANSACTION_INCLUDE -# include +# include #endif TRANSACTION(ttDEPOSIT_PREAUTH, 19, DepositPreauth, Delegation::delegable, @@ -279,7 +279,7 @@ TRANSACTION(ttDEPOSIT_PREAUTH, 19, DepositPreauth, /** This transaction type modifies a trustline between two accounts. */ #if TRANSACTION_INCLUDE -# include +# include #endif TRANSACTION(ttTRUST_SET, 20, TrustSet, Delegation::delegable, @@ -293,7 +293,7 @@ TRANSACTION(ttTRUST_SET, 20, TrustSet, /** This transaction type deletes an existing account. */ #if TRANSACTION_INCLUDE -# include +# include #endif TRANSACTION(ttACCOUNT_DELETE, 21, AccountDelete, Delegation::notDelegable, @@ -309,7 +309,7 @@ TRANSACTION(ttACCOUNT_DELETE, 21, AccountDelete, /** This transaction mints a new NFT. */ #if TRANSACTION_INCLUDE -# include +# include #endif TRANSACTION(ttNFTOKEN_MINT, 25, NFTokenMint, Delegation::delegable, @@ -327,7 +327,7 @@ TRANSACTION(ttNFTOKEN_MINT, 25, NFTokenMint, /** This transaction burns (i.e. destroys) an existing NFT. */ #if TRANSACTION_INCLUDE -# include +# include #endif TRANSACTION(ttNFTOKEN_BURN, 26, NFTokenBurn, Delegation::delegable, @@ -340,7 +340,7 @@ TRANSACTION(ttNFTOKEN_BURN, 26, NFTokenBurn, /** This transaction creates a new offer to buy or sell an NFT. */ #if TRANSACTION_INCLUDE -# include +# include #endif TRANSACTION(ttNFTOKEN_CREATE_OFFER, 27, NFTokenCreateOffer, Delegation::delegable, @@ -356,7 +356,7 @@ TRANSACTION(ttNFTOKEN_CREATE_OFFER, 27, NFTokenCreateOffer, /** This transaction cancels an existing offer to buy or sell an existing NFT. */ #if TRANSACTION_INCLUDE -# include +# include #endif TRANSACTION(ttNFTOKEN_CANCEL_OFFER, 28, NFTokenCancelOffer, Delegation::delegable, @@ -368,7 +368,7 @@ TRANSACTION(ttNFTOKEN_CANCEL_OFFER, 28, NFTokenCancelOffer, /** This transaction accepts an existing offer to buy or sell an existing NFT. */ #if TRANSACTION_INCLUDE -# include +# include #endif TRANSACTION(ttNFTOKEN_ACCEPT_OFFER, 29, NFTokenAcceptOffer, Delegation::delegable, @@ -382,7 +382,7 @@ TRANSACTION(ttNFTOKEN_ACCEPT_OFFER, 29, NFTokenAcceptOffer, /** This transaction claws back issued tokens. */ #if TRANSACTION_INCLUDE -# include +# include #endif TRANSACTION(ttCLAWBACK, 30, Clawback, Delegation::delegable, @@ -395,7 +395,7 @@ TRANSACTION(ttCLAWBACK, 30, Clawback, /** This transaction claws back tokens from an AMM pool. */ #if TRANSACTION_INCLUDE -# include +# include #endif TRANSACTION(ttAMM_CLAWBACK, 31, AMMClawback, Delegation::delegable, @@ -410,7 +410,7 @@ TRANSACTION(ttAMM_CLAWBACK, 31, AMMClawback, /** This transaction type creates an AMM instance */ #if TRANSACTION_INCLUDE -# include +# include #endif TRANSACTION(ttAMM_CREATE, 35, AMMCreate, Delegation::delegable, @@ -424,7 +424,7 @@ TRANSACTION(ttAMM_CREATE, 35, AMMCreate, /** This transaction type deposits into an AMM instance */ #if TRANSACTION_INCLUDE -# include +# include #endif TRANSACTION(ttAMM_DEPOSIT, 36, AMMDeposit, Delegation::delegable, @@ -442,7 +442,7 @@ TRANSACTION(ttAMM_DEPOSIT, 36, AMMDeposit, /** This transaction type withdraws from an AMM instance */ #if TRANSACTION_INCLUDE -# include +# include #endif TRANSACTION(ttAMM_WITHDRAW, 37, AMMWithdraw, Delegation::delegable, @@ -459,7 +459,7 @@ TRANSACTION(ttAMM_WITHDRAW, 37, AMMWithdraw, /** This transaction type votes for the trading fee */ #if TRANSACTION_INCLUDE -# include +# include #endif TRANSACTION(ttAMM_VOTE, 38, AMMVote, Delegation::delegable, @@ -473,7 +473,7 @@ TRANSACTION(ttAMM_VOTE, 38, AMMVote, /** This transaction type bids for the auction slot */ #if TRANSACTION_INCLUDE -# include +# include #endif TRANSACTION(ttAMM_BID, 39, AMMBid, Delegation::delegable, @@ -489,7 +489,7 @@ TRANSACTION(ttAMM_BID, 39, AMMBid, /** This transaction type deletes AMM in the empty state */ #if TRANSACTION_INCLUDE -# include +# include #endif TRANSACTION(ttAMM_DELETE, 40, AMMDelete, Delegation::delegable, @@ -502,7 +502,7 @@ TRANSACTION(ttAMM_DELETE, 40, AMMDelete, /** This transactions creates a crosschain sequence number */ #if TRANSACTION_INCLUDE -# include +# include #endif TRANSACTION(ttXCHAIN_CREATE_CLAIM_ID, 41, XChainCreateClaimID, Delegation::delegable, @@ -617,7 +617,7 @@ TRANSACTION(ttXCHAIN_CREATE_BRIDGE, 48, XChainCreateBridge, /** This transaction type creates or updates a DID */ #if TRANSACTION_INCLUDE -# include +# include #endif TRANSACTION(ttDID_SET, 49, DIDSet, Delegation::delegable, @@ -638,7 +638,7 @@ TRANSACTION(ttDID_DELETE, 50, DIDDelete, /** This transaction type creates an Oracle instance */ #if TRANSACTION_INCLUDE -# include +# include #endif TRANSACTION(ttORACLE_SET, 51, OracleSet, Delegation::delegable, @@ -655,7 +655,7 @@ TRANSACTION(ttORACLE_SET, 51, OracleSet, /** This transaction type deletes an Oracle instance */ #if TRANSACTION_INCLUDE -# include +# include #endif TRANSACTION(ttORACLE_DELETE, 52, OracleDelete, Delegation::delegable, @@ -667,7 +667,7 @@ TRANSACTION(ttORACLE_DELETE, 52, OracleDelete, /** This transaction type fixes a problem in the ledger state */ #if TRANSACTION_INCLUDE -# include +# include #endif TRANSACTION(ttLEDGER_STATE_FIX, 53, LedgerStateFix, Delegation::delegable, @@ -680,7 +680,7 @@ TRANSACTION(ttLEDGER_STATE_FIX, 53, LedgerStateFix, /** This transaction type creates a MPTokensIssuance instance */ #if TRANSACTION_INCLUDE -# include +# include #endif TRANSACTION(ttMPTOKEN_ISSUANCE_CREATE, 54, MPTokenIssuanceCreate, Delegation::delegable, @@ -697,7 +697,7 @@ TRANSACTION(ttMPTOKEN_ISSUANCE_CREATE, 54, MPTokenIssuanceCreate, /** This transaction type destroys a MPTokensIssuance instance */ #if TRANSACTION_INCLUDE -# include +# include #endif TRANSACTION(ttMPTOKEN_ISSUANCE_DESTROY, 55, MPTokenIssuanceDestroy, Delegation::delegable, @@ -709,7 +709,7 @@ TRANSACTION(ttMPTOKEN_ISSUANCE_DESTROY, 55, MPTokenIssuanceDestroy, /** This transaction type sets flags on a MPTokensIssuance or MPToken instance */ #if TRANSACTION_INCLUDE -# include +# include #endif TRANSACTION(ttMPTOKEN_ISSUANCE_SET, 56, MPTokenIssuanceSet, Delegation::delegable, @@ -726,7 +726,7 @@ TRANSACTION(ttMPTOKEN_ISSUANCE_SET, 56, MPTokenIssuanceSet, /** This transaction type authorizes a MPToken instance */ #if TRANSACTION_INCLUDE -# include +# include #endif TRANSACTION(ttMPTOKEN_AUTHORIZE, 57, MPTokenAuthorize, Delegation::delegable, @@ -739,7 +739,7 @@ TRANSACTION(ttMPTOKEN_AUTHORIZE, 57, MPTokenAuthorize, /** This transaction type create an Credential instance */ #if TRANSACTION_INCLUDE -# include +# include #endif TRANSACTION(ttCREDENTIAL_CREATE, 58, CredentialCreate, Delegation::delegable, @@ -775,7 +775,7 @@ TRANSACTION(ttCREDENTIAL_DELETE, 60, CredentialDelete, /** This transaction type modify a NFToken */ #if TRANSACTION_INCLUDE -# include +# include #endif TRANSACTION(ttNFTOKEN_MODIFY, 61, NFTokenModify, Delegation::delegable, @@ -789,7 +789,7 @@ TRANSACTION(ttNFTOKEN_MODIFY, 61, NFTokenModify, /** This transaction type creates or modifies a Permissioned Domain */ #if TRANSACTION_INCLUDE -# include +# include #endif TRANSACTION(ttPERMISSIONED_DOMAIN_SET, 62, PermissionedDomainSet, Delegation::delegable, @@ -802,7 +802,7 @@ TRANSACTION(ttPERMISSIONED_DOMAIN_SET, 62, PermissionedDomainSet, /** This transaction type deletes a Permissioned Domain */ #if TRANSACTION_INCLUDE -# include +# include #endif TRANSACTION(ttPERMISSIONED_DOMAIN_DELETE, 63, PermissionedDomainDelete, Delegation::delegable, @@ -814,7 +814,7 @@ TRANSACTION(ttPERMISSIONED_DOMAIN_DELETE, 63, PermissionedDomainDelete, /** This transaction type delegates authorized account specified permissions */ #if TRANSACTION_INCLUDE -# include +# include #endif TRANSACTION(ttDELEGATE_SET, 64, DelegateSet, Delegation::notDelegable, @@ -827,7 +827,7 @@ TRANSACTION(ttDELEGATE_SET, 64, DelegateSet, /** This transaction creates a single asset vault. */ #if TRANSACTION_INCLUDE -# include +# include #endif TRANSACTION(ttVAULT_CREATE, 65, VaultCreate, Delegation::delegable, @@ -845,7 +845,7 @@ TRANSACTION(ttVAULT_CREATE, 65, VaultCreate, /** This transaction updates a single asset vault. */ #if TRANSACTION_INCLUDE -# include +# include #endif TRANSACTION(ttVAULT_SET, 66, VaultSet, Delegation::delegable, @@ -860,7 +860,7 @@ TRANSACTION(ttVAULT_SET, 66, VaultSet, /** This transaction deletes a single asset vault. */ #if TRANSACTION_INCLUDE -# include +# include #endif TRANSACTION(ttVAULT_DELETE, 67, VaultDelete, Delegation::delegable, @@ -872,7 +872,7 @@ TRANSACTION(ttVAULT_DELETE, 67, VaultDelete, /** This transaction trades assets for shares with a vault. */ #if TRANSACTION_INCLUDE -# include +# include #endif TRANSACTION(ttVAULT_DEPOSIT, 68, VaultDeposit, Delegation::delegable, @@ -885,7 +885,7 @@ TRANSACTION(ttVAULT_DEPOSIT, 68, VaultDeposit, /** This transaction trades shares for assets with a vault. */ #if TRANSACTION_INCLUDE -# include +# include #endif TRANSACTION(ttVAULT_WITHDRAW, 69, VaultWithdraw, Delegation::delegable, @@ -900,7 +900,7 @@ TRANSACTION(ttVAULT_WITHDRAW, 69, VaultWithdraw, /** This transaction claws back tokens from a vault. */ #if TRANSACTION_INCLUDE -# include +# include #endif TRANSACTION(ttVAULT_CLAWBACK, 70, VaultClawback, Delegation::delegable, @@ -914,7 +914,7 @@ TRANSACTION(ttVAULT_CLAWBACK, 70, VaultClawback, /** This transaction type batches together transactions. */ #if TRANSACTION_INCLUDE -# include +# include #endif TRANSACTION(ttBATCH, 71, Batch, Delegation::notDelegable, @@ -929,7 +929,7 @@ TRANSACTION(ttBATCH, 71, Batch, /** This transaction creates and updates a Loan Broker */ #if TRANSACTION_INCLUDE -# include +# include #endif TRANSACTION(ttLOAN_BROKER_SET, 74, LoanBrokerSet, Delegation::delegable, @@ -946,7 +946,7 @@ TRANSACTION(ttLOAN_BROKER_SET, 74, LoanBrokerSet, /** This transaction deletes a Loan Broker */ #if TRANSACTION_INCLUDE -# include +# include #endif TRANSACTION(ttLOAN_BROKER_DELETE, 75, LoanBrokerDelete, Delegation::delegable, @@ -957,7 +957,7 @@ TRANSACTION(ttLOAN_BROKER_DELETE, 75, LoanBrokerDelete, /** This transaction deposits First Loss Capital into a Loan Broker */ #if TRANSACTION_INCLUDE -# include +# include #endif TRANSACTION(ttLOAN_BROKER_COVER_DEPOSIT, 76, LoanBrokerCoverDeposit, Delegation::delegable, @@ -969,7 +969,7 @@ TRANSACTION(ttLOAN_BROKER_COVER_DEPOSIT, 76, LoanBrokerCoverDeposit, /** This transaction withdraws First Loss Capital from a Loan Broker */ #if TRANSACTION_INCLUDE -# include +# include #endif TRANSACTION(ttLOAN_BROKER_COVER_WITHDRAW, 77, LoanBrokerCoverWithdraw, Delegation::delegable, @@ -984,7 +984,7 @@ TRANSACTION(ttLOAN_BROKER_COVER_WITHDRAW, 77, LoanBrokerCoverWithdraw, /** This transaction claws back First Loss Capital from a Loan Broker to the issuer of the capital */ #if TRANSACTION_INCLUDE -# include +# include #endif TRANSACTION(ttLOAN_BROKER_COVER_CLAWBACK, 78, LoanBrokerCoverClawback, Delegation::delegable, @@ -996,7 +996,7 @@ TRANSACTION(ttLOAN_BROKER_COVER_CLAWBACK, 78, LoanBrokerCoverClawback, /** This transaction creates a Loan */ #if TRANSACTION_INCLUDE -# include +# include #endif TRANSACTION(ttLOAN_SET, 80, LoanSet, Delegation::delegable, @@ -1023,7 +1023,7 @@ TRANSACTION(ttLOAN_SET, 80, LoanSet, /** This transaction deletes an existing Loan */ #if TRANSACTION_INCLUDE -# include +# include #endif TRANSACTION(ttLOAN_DELETE, 81, LoanDelete, Delegation::delegable, @@ -1034,7 +1034,7 @@ TRANSACTION(ttLOAN_DELETE, 81, LoanDelete, /** This transaction is used to change the delinquency status of an existing Loan */ #if TRANSACTION_INCLUDE -# include +# include #endif TRANSACTION(ttLOAN_MANAGE, 82, LoanManage, Delegation::delegable, @@ -1048,7 +1048,7 @@ TRANSACTION(ttLOAN_MANAGE, 82, LoanManage, /** The Borrower uses this transaction to make a Payment on the Loan. */ #if TRANSACTION_INCLUDE -# include +# include #endif TRANSACTION(ttLOAN_PAY, 84, LoanPay, Delegation::delegable, @@ -1063,7 +1063,7 @@ TRANSACTION(ttLOAN_PAY, 84, LoanPay, For details, see: https://xrpl.org/amendments.html */ #if TRANSACTION_INCLUDE -# include +# include #endif TRANSACTION(ttAMENDMENT, 100, EnableAmendment, Delegation::notDelegable, diff --git a/include/xrpl/tx/paths/detail/StrandFlow.h b/include/xrpl/tx/paths/detail/StrandFlow.h index 929aca0663..67e333f2e6 100644 --- a/include/xrpl/tx/paths/detail/StrandFlow.h +++ b/include/xrpl/tx/paths/detail/StrandFlow.h @@ -10,8 +10,8 @@ #include #include #include -#include -#include +#include +#include #include diff --git a/include/xrpl/tx/transactors/DeleteAccount.h b/include/xrpl/tx/transactors/account/DeleteAccount.h similarity index 100% rename from include/xrpl/tx/transactors/DeleteAccount.h rename to include/xrpl/tx/transactors/account/DeleteAccount.h diff --git a/include/xrpl/tx/transactors/SetAccount.h b/include/xrpl/tx/transactors/account/SetAccount.h similarity index 100% rename from include/xrpl/tx/transactors/SetAccount.h rename to include/xrpl/tx/transactors/account/SetAccount.h diff --git a/include/xrpl/tx/transactors/SetRegularKey.h b/include/xrpl/tx/transactors/account/SetRegularKey.h similarity index 100% rename from include/xrpl/tx/transactors/SetRegularKey.h rename to include/xrpl/tx/transactors/account/SetRegularKey.h diff --git a/include/xrpl/tx/transactors/SetSignerList.h b/include/xrpl/tx/transactors/account/SetSignerList.h similarity index 100% rename from include/xrpl/tx/transactors/SetSignerList.h rename to include/xrpl/tx/transactors/account/SetSignerList.h diff --git a/include/xrpl/tx/transactors/XChainBridge.h b/include/xrpl/tx/transactors/bridge/XChainBridge.h similarity index 100% rename from include/xrpl/tx/transactors/XChainBridge.h rename to include/xrpl/tx/transactors/bridge/XChainBridge.h diff --git a/include/xrpl/tx/transactors/Check/CancelCheck.h b/include/xrpl/tx/transactors/check/CancelCheck.h similarity index 100% rename from include/xrpl/tx/transactors/Check/CancelCheck.h rename to include/xrpl/tx/transactors/check/CancelCheck.h diff --git a/include/xrpl/tx/transactors/Check/CashCheck.h b/include/xrpl/tx/transactors/check/CashCheck.h similarity index 100% rename from include/xrpl/tx/transactors/Check/CashCheck.h rename to include/xrpl/tx/transactors/check/CashCheck.h diff --git a/include/xrpl/tx/transactors/Check/CreateCheck.h b/include/xrpl/tx/transactors/check/CreateCheck.h similarity index 100% rename from include/xrpl/tx/transactors/Check/CreateCheck.h rename to include/xrpl/tx/transactors/check/CreateCheck.h diff --git a/include/xrpl/tx/transactors/Credentials.h b/include/xrpl/tx/transactors/credentials/Credentials.h similarity index 100% rename from include/xrpl/tx/transactors/Credentials.h rename to include/xrpl/tx/transactors/credentials/Credentials.h diff --git a/include/xrpl/tx/transactors/Delegate/DelegateSet.h b/include/xrpl/tx/transactors/delegate/DelegateSet.h similarity index 100% rename from include/xrpl/tx/transactors/Delegate/DelegateSet.h rename to include/xrpl/tx/transactors/delegate/DelegateSet.h diff --git a/include/xrpl/tx/transactors/Delegate/DelegateUtils.h b/include/xrpl/tx/transactors/delegate/DelegateUtils.h similarity index 100% rename from include/xrpl/tx/transactors/Delegate/DelegateUtils.h rename to include/xrpl/tx/transactors/delegate/DelegateUtils.h diff --git a/include/xrpl/tx/transactors/AMM/AMMBid.h b/include/xrpl/tx/transactors/dex/AMMBid.h similarity index 100% rename from include/xrpl/tx/transactors/AMM/AMMBid.h rename to include/xrpl/tx/transactors/dex/AMMBid.h diff --git a/include/xrpl/tx/transactors/AMM/AMMClawback.h b/include/xrpl/tx/transactors/dex/AMMClawback.h similarity index 100% rename from include/xrpl/tx/transactors/AMM/AMMClawback.h rename to include/xrpl/tx/transactors/dex/AMMClawback.h diff --git a/include/xrpl/tx/transactors/AMM/AMMContext.h b/include/xrpl/tx/transactors/dex/AMMContext.h similarity index 100% rename from include/xrpl/tx/transactors/AMM/AMMContext.h rename to include/xrpl/tx/transactors/dex/AMMContext.h diff --git a/include/xrpl/tx/transactors/AMM/AMMCreate.h b/include/xrpl/tx/transactors/dex/AMMCreate.h similarity index 100% rename from include/xrpl/tx/transactors/AMM/AMMCreate.h rename to include/xrpl/tx/transactors/dex/AMMCreate.h diff --git a/include/xrpl/tx/transactors/AMM/AMMDelete.h b/include/xrpl/tx/transactors/dex/AMMDelete.h similarity index 100% rename from include/xrpl/tx/transactors/AMM/AMMDelete.h rename to include/xrpl/tx/transactors/dex/AMMDelete.h diff --git a/include/xrpl/tx/transactors/AMM/AMMDeposit.h b/include/xrpl/tx/transactors/dex/AMMDeposit.h similarity index 100% rename from include/xrpl/tx/transactors/AMM/AMMDeposit.h rename to include/xrpl/tx/transactors/dex/AMMDeposit.h diff --git a/include/xrpl/tx/transactors/AMM/AMMHelpers.h b/include/xrpl/tx/transactors/dex/AMMHelpers.h similarity index 100% rename from include/xrpl/tx/transactors/AMM/AMMHelpers.h rename to include/xrpl/tx/transactors/dex/AMMHelpers.h diff --git a/include/xrpl/tx/transactors/AMM/AMMUtils.h b/include/xrpl/tx/transactors/dex/AMMUtils.h similarity index 100% rename from include/xrpl/tx/transactors/AMM/AMMUtils.h rename to include/xrpl/tx/transactors/dex/AMMUtils.h diff --git a/include/xrpl/tx/transactors/AMM/AMMVote.h b/include/xrpl/tx/transactors/dex/AMMVote.h similarity index 100% rename from include/xrpl/tx/transactors/AMM/AMMVote.h rename to include/xrpl/tx/transactors/dex/AMMVote.h diff --git a/include/xrpl/tx/transactors/AMM/AMMWithdraw.h b/include/xrpl/tx/transactors/dex/AMMWithdraw.h similarity index 100% rename from include/xrpl/tx/transactors/AMM/AMMWithdraw.h rename to include/xrpl/tx/transactors/dex/AMMWithdraw.h diff --git a/include/xrpl/tx/transactors/Offer/CancelOffer.h b/include/xrpl/tx/transactors/dex/CancelOffer.h similarity index 100% rename from include/xrpl/tx/transactors/Offer/CancelOffer.h rename to include/xrpl/tx/transactors/dex/CancelOffer.h diff --git a/include/xrpl/tx/transactors/Offer/CreateOffer.h b/include/xrpl/tx/transactors/dex/CreateOffer.h similarity index 100% rename from include/xrpl/tx/transactors/Offer/CreateOffer.h rename to include/xrpl/tx/transactors/dex/CreateOffer.h diff --git a/include/xrpl/tx/transactors/PermissionedDEXHelpers.h b/include/xrpl/tx/transactors/dex/PermissionedDEXHelpers.h similarity index 100% rename from include/xrpl/tx/transactors/PermissionedDEXHelpers.h rename to include/xrpl/tx/transactors/dex/PermissionedDEXHelpers.h diff --git a/include/xrpl/tx/transactors/DID.h b/include/xrpl/tx/transactors/did/DID.h similarity index 100% rename from include/xrpl/tx/transactors/DID.h rename to include/xrpl/tx/transactors/did/DID.h diff --git a/include/xrpl/tx/transactors/Escrow.h b/include/xrpl/tx/transactors/escrow/Escrow.h similarity index 100% rename from include/xrpl/tx/transactors/Escrow.h rename to include/xrpl/tx/transactors/escrow/Escrow.h diff --git a/include/xrpl/tx/transactors/Lending/LendingHelpers.h b/include/xrpl/tx/transactors/lending/LendingHelpers.h similarity index 100% rename from include/xrpl/tx/transactors/Lending/LendingHelpers.h rename to include/xrpl/tx/transactors/lending/LendingHelpers.h diff --git a/include/xrpl/tx/transactors/Lending/LoanBrokerCoverClawback.h b/include/xrpl/tx/transactors/lending/LoanBrokerCoverClawback.h similarity index 100% rename from include/xrpl/tx/transactors/Lending/LoanBrokerCoverClawback.h rename to include/xrpl/tx/transactors/lending/LoanBrokerCoverClawback.h diff --git a/include/xrpl/tx/transactors/Lending/LoanBrokerCoverDeposit.h b/include/xrpl/tx/transactors/lending/LoanBrokerCoverDeposit.h similarity index 100% rename from include/xrpl/tx/transactors/Lending/LoanBrokerCoverDeposit.h rename to include/xrpl/tx/transactors/lending/LoanBrokerCoverDeposit.h diff --git a/include/xrpl/tx/transactors/Lending/LoanBrokerCoverWithdraw.h b/include/xrpl/tx/transactors/lending/LoanBrokerCoverWithdraw.h similarity index 100% rename from include/xrpl/tx/transactors/Lending/LoanBrokerCoverWithdraw.h rename to include/xrpl/tx/transactors/lending/LoanBrokerCoverWithdraw.h diff --git a/include/xrpl/tx/transactors/Lending/LoanBrokerDelete.h b/include/xrpl/tx/transactors/lending/LoanBrokerDelete.h similarity index 100% rename from include/xrpl/tx/transactors/Lending/LoanBrokerDelete.h rename to include/xrpl/tx/transactors/lending/LoanBrokerDelete.h diff --git a/include/xrpl/tx/transactors/Lending/LoanBrokerSet.h b/include/xrpl/tx/transactors/lending/LoanBrokerSet.h similarity index 100% rename from include/xrpl/tx/transactors/Lending/LoanBrokerSet.h rename to include/xrpl/tx/transactors/lending/LoanBrokerSet.h diff --git a/include/xrpl/tx/transactors/Lending/LoanDelete.h b/include/xrpl/tx/transactors/lending/LoanDelete.h similarity index 100% rename from include/xrpl/tx/transactors/Lending/LoanDelete.h rename to include/xrpl/tx/transactors/lending/LoanDelete.h diff --git a/include/xrpl/tx/transactors/Lending/LoanManage.h b/include/xrpl/tx/transactors/lending/LoanManage.h similarity index 100% rename from include/xrpl/tx/transactors/Lending/LoanManage.h rename to include/xrpl/tx/transactors/lending/LoanManage.h diff --git a/include/xrpl/tx/transactors/Lending/LoanPay.h b/include/xrpl/tx/transactors/lending/LoanPay.h similarity index 100% rename from include/xrpl/tx/transactors/Lending/LoanPay.h rename to include/xrpl/tx/transactors/lending/LoanPay.h diff --git a/include/xrpl/tx/transactors/Lending/LoanSet.h b/include/xrpl/tx/transactors/lending/LoanSet.h similarity index 96% rename from include/xrpl/tx/transactors/Lending/LoanSet.h rename to include/xrpl/tx/transactors/lending/LoanSet.h index e9916b9d6b..86101d0735 100644 --- a/include/xrpl/tx/transactors/Lending/LoanSet.h +++ b/include/xrpl/tx/transactors/lending/LoanSet.h @@ -1,7 +1,7 @@ #pragma once #include -#include +#include namespace xrpl { diff --git a/include/xrpl/tx/transactors/NFT/NFTokenAcceptOffer.h b/include/xrpl/tx/transactors/nft/NFTokenAcceptOffer.h similarity index 100% rename from include/xrpl/tx/transactors/NFT/NFTokenAcceptOffer.h rename to include/xrpl/tx/transactors/nft/NFTokenAcceptOffer.h diff --git a/include/xrpl/tx/transactors/NFT/NFTokenBurn.h b/include/xrpl/tx/transactors/nft/NFTokenBurn.h similarity index 100% rename from include/xrpl/tx/transactors/NFT/NFTokenBurn.h rename to include/xrpl/tx/transactors/nft/NFTokenBurn.h diff --git a/include/xrpl/tx/transactors/NFT/NFTokenCancelOffer.h b/include/xrpl/tx/transactors/nft/NFTokenCancelOffer.h similarity index 100% rename from include/xrpl/tx/transactors/NFT/NFTokenCancelOffer.h rename to include/xrpl/tx/transactors/nft/NFTokenCancelOffer.h diff --git a/include/xrpl/tx/transactors/NFT/NFTokenCreateOffer.h b/include/xrpl/tx/transactors/nft/NFTokenCreateOffer.h similarity index 100% rename from include/xrpl/tx/transactors/NFT/NFTokenCreateOffer.h rename to include/xrpl/tx/transactors/nft/NFTokenCreateOffer.h diff --git a/include/xrpl/tx/transactors/NFT/NFTokenMint.h b/include/xrpl/tx/transactors/nft/NFTokenMint.h similarity index 94% rename from include/xrpl/tx/transactors/NFT/NFTokenMint.h rename to include/xrpl/tx/transactors/nft/NFTokenMint.h index c3a7db4581..d4eeba2bf0 100644 --- a/include/xrpl/tx/transactors/NFT/NFTokenMint.h +++ b/include/xrpl/tx/transactors/nft/NFTokenMint.h @@ -2,7 +2,7 @@ #include #include -#include +#include namespace xrpl { diff --git a/include/xrpl/tx/transactors/NFT/NFTokenModify.h b/include/xrpl/tx/transactors/nft/NFTokenModify.h similarity index 100% rename from include/xrpl/tx/transactors/NFT/NFTokenModify.h rename to include/xrpl/tx/transactors/nft/NFTokenModify.h diff --git a/include/xrpl/tx/transactors/NFT/NFTokenUtils.h b/include/xrpl/tx/transactors/nft/NFTokenUtils.h similarity index 100% rename from include/xrpl/tx/transactors/NFT/NFTokenUtils.h rename to include/xrpl/tx/transactors/nft/NFTokenUtils.h diff --git a/include/xrpl/tx/transactors/DeleteOracle.h b/include/xrpl/tx/transactors/oracle/DeleteOracle.h similarity index 100% rename from include/xrpl/tx/transactors/DeleteOracle.h rename to include/xrpl/tx/transactors/oracle/DeleteOracle.h diff --git a/include/xrpl/tx/transactors/SetOracle.h b/include/xrpl/tx/transactors/oracle/SetOracle.h similarity index 100% rename from include/xrpl/tx/transactors/SetOracle.h rename to include/xrpl/tx/transactors/oracle/SetOracle.h diff --git a/include/xrpl/tx/transactors/DepositPreauth.h b/include/xrpl/tx/transactors/payment/DepositPreauth.h similarity index 100% rename from include/xrpl/tx/transactors/DepositPreauth.h rename to include/xrpl/tx/transactors/payment/DepositPreauth.h diff --git a/include/xrpl/tx/transactors/Payment.h b/include/xrpl/tx/transactors/payment/Payment.h similarity index 100% rename from include/xrpl/tx/transactors/Payment.h rename to include/xrpl/tx/transactors/payment/Payment.h diff --git a/include/xrpl/tx/transactors/PayChan.h b/include/xrpl/tx/transactors/payment_channel/PayChan.h similarity index 100% rename from include/xrpl/tx/transactors/PayChan.h rename to include/xrpl/tx/transactors/payment_channel/PayChan.h diff --git a/include/xrpl/tx/transactors/PermissionedDomain/PermissionedDomainDelete.h b/include/xrpl/tx/transactors/permissioned_domain/PermissionedDomainDelete.h similarity index 100% rename from include/xrpl/tx/transactors/PermissionedDomain/PermissionedDomainDelete.h rename to include/xrpl/tx/transactors/permissioned_domain/PermissionedDomainDelete.h diff --git a/include/xrpl/tx/transactors/PermissionedDomain/PermissionedDomainSet.h b/include/xrpl/tx/transactors/permissioned_domain/PermissionedDomainSet.h similarity index 100% rename from include/xrpl/tx/transactors/PermissionedDomain/PermissionedDomainSet.h rename to include/xrpl/tx/transactors/permissioned_domain/PermissionedDomainSet.h diff --git a/include/xrpl/tx/transactors/Batch.h b/include/xrpl/tx/transactors/system/Batch.h similarity index 100% rename from include/xrpl/tx/transactors/Batch.h rename to include/xrpl/tx/transactors/system/Batch.h diff --git a/include/xrpl/tx/transactors/Change.h b/include/xrpl/tx/transactors/system/Change.h similarity index 100% rename from include/xrpl/tx/transactors/Change.h rename to include/xrpl/tx/transactors/system/Change.h diff --git a/include/xrpl/tx/transactors/CreateTicket.h b/include/xrpl/tx/transactors/system/CreateTicket.h similarity index 100% rename from include/xrpl/tx/transactors/CreateTicket.h rename to include/xrpl/tx/transactors/system/CreateTicket.h diff --git a/include/xrpl/tx/transactors/LedgerStateFix.h b/include/xrpl/tx/transactors/system/LedgerStateFix.h similarity index 100% rename from include/xrpl/tx/transactors/LedgerStateFix.h rename to include/xrpl/tx/transactors/system/LedgerStateFix.h diff --git a/include/xrpl/tx/transactors/Clawback.h b/include/xrpl/tx/transactors/token/Clawback.h similarity index 100% rename from include/xrpl/tx/transactors/Clawback.h rename to include/xrpl/tx/transactors/token/Clawback.h diff --git a/include/xrpl/tx/transactors/MPT/MPTokenAuthorize.h b/include/xrpl/tx/transactors/token/MPTokenAuthorize.h similarity index 100% rename from include/xrpl/tx/transactors/MPT/MPTokenAuthorize.h rename to include/xrpl/tx/transactors/token/MPTokenAuthorize.h diff --git a/include/xrpl/tx/transactors/MPT/MPTokenIssuanceCreate.h b/include/xrpl/tx/transactors/token/MPTokenIssuanceCreate.h similarity index 100% rename from include/xrpl/tx/transactors/MPT/MPTokenIssuanceCreate.h rename to include/xrpl/tx/transactors/token/MPTokenIssuanceCreate.h diff --git a/include/xrpl/tx/transactors/MPT/MPTokenIssuanceDestroy.h b/include/xrpl/tx/transactors/token/MPTokenIssuanceDestroy.h similarity index 100% rename from include/xrpl/tx/transactors/MPT/MPTokenIssuanceDestroy.h rename to include/xrpl/tx/transactors/token/MPTokenIssuanceDestroy.h diff --git a/include/xrpl/tx/transactors/MPT/MPTokenIssuanceSet.h b/include/xrpl/tx/transactors/token/MPTokenIssuanceSet.h similarity index 100% rename from include/xrpl/tx/transactors/MPT/MPTokenIssuanceSet.h rename to include/xrpl/tx/transactors/token/MPTokenIssuanceSet.h diff --git a/include/xrpl/tx/transactors/SetTrust.h b/include/xrpl/tx/transactors/token/SetTrust.h similarity index 100% rename from include/xrpl/tx/transactors/SetTrust.h rename to include/xrpl/tx/transactors/token/SetTrust.h diff --git a/include/xrpl/tx/transactors/Vault/VaultClawback.h b/include/xrpl/tx/transactors/vault/VaultClawback.h similarity index 100% rename from include/xrpl/tx/transactors/Vault/VaultClawback.h rename to include/xrpl/tx/transactors/vault/VaultClawback.h diff --git a/include/xrpl/tx/transactors/Vault/VaultCreate.h b/include/xrpl/tx/transactors/vault/VaultCreate.h similarity index 100% rename from include/xrpl/tx/transactors/Vault/VaultCreate.h rename to include/xrpl/tx/transactors/vault/VaultCreate.h diff --git a/include/xrpl/tx/transactors/Vault/VaultDelete.h b/include/xrpl/tx/transactors/vault/VaultDelete.h similarity index 100% rename from include/xrpl/tx/transactors/Vault/VaultDelete.h rename to include/xrpl/tx/transactors/vault/VaultDelete.h diff --git a/include/xrpl/tx/transactors/Vault/VaultDeposit.h b/include/xrpl/tx/transactors/vault/VaultDeposit.h similarity index 100% rename from include/xrpl/tx/transactors/Vault/VaultDeposit.h rename to include/xrpl/tx/transactors/vault/VaultDeposit.h diff --git a/include/xrpl/tx/transactors/Vault/VaultSet.h b/include/xrpl/tx/transactors/vault/VaultSet.h similarity index 100% rename from include/xrpl/tx/transactors/Vault/VaultSet.h rename to include/xrpl/tx/transactors/vault/VaultSet.h diff --git a/include/xrpl/tx/transactors/Vault/VaultWithdraw.h b/include/xrpl/tx/transactors/vault/VaultWithdraw.h similarity index 100% rename from include/xrpl/tx/transactors/Vault/VaultWithdraw.h rename to include/xrpl/tx/transactors/vault/VaultWithdraw.h diff --git a/src/libxrpl/tx/Transactor.cpp b/src/libxrpl/tx/Transactor.cpp index 9da1028b3a..3ddac350e8 100644 --- a/src/libxrpl/tx/Transactor.cpp +++ b/src/libxrpl/tx/Transactor.cpp @@ -14,8 +14,8 @@ #include #include #include -#include -#include +#include +#include namespace xrpl { diff --git a/src/libxrpl/tx/invariants/AMMInvariant.cpp b/src/libxrpl/tx/invariants/AMMInvariant.cpp index d98c0a6f50..60d05f298c 100644 --- a/src/libxrpl/tx/invariants/AMMInvariant.cpp +++ b/src/libxrpl/tx/invariants/AMMInvariant.cpp @@ -3,8 +3,8 @@ #include #include #include -#include -#include +#include +#include namespace xrpl { diff --git a/src/libxrpl/tx/invariants/NFTInvariant.cpp b/src/libxrpl/tx/invariants/NFTInvariant.cpp index db06896023..67f1e51231 100644 --- a/src/libxrpl/tx/invariants/NFTInvariant.cpp +++ b/src/libxrpl/tx/invariants/NFTInvariant.cpp @@ -6,7 +6,7 @@ #include #include #include -#include +#include namespace xrpl { diff --git a/src/libxrpl/tx/paths/Flow.cpp b/src/libxrpl/tx/paths/Flow.cpp index 2178997049..3e9adca814 100644 --- a/src/libxrpl/tx/paths/Flow.cpp +++ b/src/libxrpl/tx/paths/Flow.cpp @@ -6,7 +6,7 @@ #include #include #include -#include +#include namespace xrpl { diff --git a/src/libxrpl/tx/paths/OfferStream.cpp b/src/libxrpl/tx/paths/OfferStream.cpp index 9d2a94e3a8..596ae36b96 100644 --- a/src/libxrpl/tx/paths/OfferStream.cpp +++ b/src/libxrpl/tx/paths/OfferStream.cpp @@ -3,7 +3,7 @@ #include #include #include -#include +#include namespace xrpl { diff --git a/src/libxrpl/tx/transactors/DeleteAccount.cpp b/src/libxrpl/tx/transactors/account/DeleteAccount.cpp similarity index 97% rename from src/libxrpl/tx/transactors/DeleteAccount.cpp rename to src/libxrpl/tx/transactors/account/DeleteAccount.cpp index 79ce03ca9a..a9f7dbe674 100644 --- a/src/libxrpl/tx/transactors/DeleteAccount.cpp +++ b/src/libxrpl/tx/transactors/account/DeleteAccount.cpp @@ -8,13 +8,13 @@ #include #include #include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include namespace xrpl { diff --git a/src/libxrpl/tx/transactors/SetAccount.cpp b/src/libxrpl/tx/transactors/account/SetAccount.cpp similarity index 99% rename from src/libxrpl/tx/transactors/SetAccount.cpp rename to src/libxrpl/tx/transactors/account/SetAccount.cpp index 64b52d12d5..32eb2afbb8 100644 --- a/src/libxrpl/tx/transactors/SetAccount.cpp +++ b/src/libxrpl/tx/transactors/account/SetAccount.cpp @@ -5,8 +5,8 @@ #include #include #include -#include -#include +#include +#include namespace xrpl { diff --git a/src/libxrpl/tx/transactors/SetRegularKey.cpp b/src/libxrpl/tx/transactors/account/SetRegularKey.cpp similarity index 96% rename from src/libxrpl/tx/transactors/SetRegularKey.cpp rename to src/libxrpl/tx/transactors/account/SetRegularKey.cpp index e26e6b1bd5..08175b196f 100644 --- a/src/libxrpl/tx/transactors/SetRegularKey.cpp +++ b/src/libxrpl/tx/transactors/account/SetRegularKey.cpp @@ -1,7 +1,7 @@ #include #include #include -#include +#include namespace xrpl { diff --git a/src/libxrpl/tx/transactors/SetSignerList.cpp b/src/libxrpl/tx/transactors/account/SetSignerList.cpp similarity index 99% rename from src/libxrpl/tx/transactors/SetSignerList.cpp rename to src/libxrpl/tx/transactors/account/SetSignerList.cpp index 858fb33bff..d5b41c6acf 100644 --- a/src/libxrpl/tx/transactors/SetSignerList.cpp +++ b/src/libxrpl/tx/transactors/account/SetSignerList.cpp @@ -7,7 +7,7 @@ #include #include #include -#include +#include #include #include diff --git a/src/libxrpl/tx/transactors/XChainBridge.cpp b/src/libxrpl/tx/transactors/bridge/XChainBridge.cpp similarity index 99% rename from src/libxrpl/tx/transactors/XChainBridge.cpp rename to src/libxrpl/tx/transactors/bridge/XChainBridge.cpp index 44a1974f93..9c18630b61 100644 --- a/src/libxrpl/tx/transactors/XChainBridge.cpp +++ b/src/libxrpl/tx/transactors/bridge/XChainBridge.cpp @@ -21,7 +21,7 @@ #include #include #include -#include +#include #include #include diff --git a/src/libxrpl/tx/transactors/Check/CancelCheck.cpp b/src/libxrpl/tx/transactors/check/CancelCheck.cpp similarity index 98% rename from src/libxrpl/tx/transactors/Check/CancelCheck.cpp rename to src/libxrpl/tx/transactors/check/CancelCheck.cpp index c9a947ae6a..645327a4c5 100644 --- a/src/libxrpl/tx/transactors/Check/CancelCheck.cpp +++ b/src/libxrpl/tx/transactors/check/CancelCheck.cpp @@ -5,7 +5,7 @@ #include #include #include -#include +#include namespace xrpl { diff --git a/src/libxrpl/tx/transactors/Check/CashCheck.cpp b/src/libxrpl/tx/transactors/check/CashCheck.cpp similarity index 99% rename from src/libxrpl/tx/transactors/Check/CashCheck.cpp rename to src/libxrpl/tx/transactors/check/CashCheck.cpp index c459ef9b67..1628b284a9 100644 --- a/src/libxrpl/tx/transactors/Check/CashCheck.cpp +++ b/src/libxrpl/tx/transactors/check/CashCheck.cpp @@ -6,7 +6,7 @@ #include #include #include -#include +#include #include diff --git a/src/libxrpl/tx/transactors/Check/CreateCheck.cpp b/src/libxrpl/tx/transactors/check/CreateCheck.cpp similarity index 99% rename from src/libxrpl/tx/transactors/Check/CreateCheck.cpp rename to src/libxrpl/tx/transactors/check/CreateCheck.cpp index 7ec4746d20..9e883e7fbb 100644 --- a/src/libxrpl/tx/transactors/Check/CreateCheck.cpp +++ b/src/libxrpl/tx/transactors/check/CreateCheck.cpp @@ -4,7 +4,7 @@ #include #include #include -#include +#include namespace xrpl { diff --git a/src/libxrpl/tx/transactors/Credentials.cpp b/src/libxrpl/tx/transactors/credentials/Credentials.cpp similarity index 99% rename from src/libxrpl/tx/transactors/Credentials.cpp rename to src/libxrpl/tx/transactors/credentials/Credentials.cpp index ce95d8144c..079b75a9fe 100644 --- a/src/libxrpl/tx/transactors/Credentials.cpp +++ b/src/libxrpl/tx/transactors/credentials/Credentials.cpp @@ -5,7 +5,7 @@ #include #include #include -#include +#include #include diff --git a/src/libxrpl/tx/transactors/Delegate/DelegateSet.cpp b/src/libxrpl/tx/transactors/delegate/DelegateSet.cpp similarity index 98% rename from src/libxrpl/tx/transactors/Delegate/DelegateSet.cpp rename to src/libxrpl/tx/transactors/delegate/DelegateSet.cpp index 69ec707dd2..afb84c1c1e 100644 --- a/src/libxrpl/tx/transactors/Delegate/DelegateSet.cpp +++ b/src/libxrpl/tx/transactors/delegate/DelegateSet.cpp @@ -3,7 +3,7 @@ #include #include #include -#include +#include namespace xrpl { diff --git a/src/libxrpl/tx/transactors/Delegate/DelegateUtils.cpp b/src/libxrpl/tx/transactors/delegate/DelegateUtils.cpp similarity index 95% rename from src/libxrpl/tx/transactors/Delegate/DelegateUtils.cpp rename to src/libxrpl/tx/transactors/delegate/DelegateUtils.cpp index eceb73503f..d9d74a1212 100644 --- a/src/libxrpl/tx/transactors/Delegate/DelegateUtils.cpp +++ b/src/libxrpl/tx/transactors/delegate/DelegateUtils.cpp @@ -1,5 +1,5 @@ #include -#include +#include namespace xrpl { NotTEC diff --git a/src/libxrpl/tx/transactors/AMM/AMMBid.cpp b/src/libxrpl/tx/transactors/dex/AMMBid.cpp similarity index 98% rename from src/libxrpl/tx/transactors/AMM/AMMBid.cpp rename to src/libxrpl/tx/transactors/dex/AMMBid.cpp index 8ba7970472..d73e65ec3a 100644 --- a/src/libxrpl/tx/transactors/AMM/AMMBid.cpp +++ b/src/libxrpl/tx/transactors/dex/AMMBid.cpp @@ -4,9 +4,9 @@ #include #include #include -#include -#include -#include +#include +#include +#include namespace xrpl { diff --git a/src/libxrpl/tx/transactors/AMM/AMMClawback.cpp b/src/libxrpl/tx/transactors/dex/AMMClawback.cpp similarity index 98% rename from src/libxrpl/tx/transactors/AMM/AMMClawback.cpp rename to src/libxrpl/tx/transactors/dex/AMMClawback.cpp index c9ea1e44eb..168583ff69 100644 --- a/src/libxrpl/tx/transactors/AMM/AMMClawback.cpp +++ b/src/libxrpl/tx/transactors/dex/AMMClawback.cpp @@ -4,10 +4,10 @@ #include #include #include -#include -#include -#include -#include +#include +#include +#include +#include #include diff --git a/src/libxrpl/tx/transactors/AMM/AMMCreate.cpp b/src/libxrpl/tx/transactors/dex/AMMCreate.cpp similarity index 98% rename from src/libxrpl/tx/transactors/AMM/AMMCreate.cpp rename to src/libxrpl/tx/transactors/dex/AMMCreate.cpp index 53933df629..1c5b2bae6f 100644 --- a/src/libxrpl/tx/transactors/AMM/AMMCreate.cpp +++ b/src/libxrpl/tx/transactors/dex/AMMCreate.cpp @@ -5,9 +5,9 @@ #include #include #include -#include -#include -#include +#include +#include +#include namespace xrpl { diff --git a/src/libxrpl/tx/transactors/AMM/AMMDelete.cpp b/src/libxrpl/tx/transactors/dex/AMMDelete.cpp similarity index 92% rename from src/libxrpl/tx/transactors/AMM/AMMDelete.cpp rename to src/libxrpl/tx/transactors/dex/AMMDelete.cpp index 39b7c9a11b..da0ea4cb80 100644 --- a/src/libxrpl/tx/transactors/AMM/AMMDelete.cpp +++ b/src/libxrpl/tx/transactors/dex/AMMDelete.cpp @@ -2,8 +2,8 @@ #include #include #include -#include -#include +#include +#include namespace xrpl { diff --git a/src/libxrpl/tx/transactors/AMM/AMMDeposit.cpp b/src/libxrpl/tx/transactors/dex/AMMDeposit.cpp similarity index 99% rename from src/libxrpl/tx/transactors/AMM/AMMDeposit.cpp rename to src/libxrpl/tx/transactors/dex/AMMDeposit.cpp index dad1bc536a..4edd199134 100644 --- a/src/libxrpl/tx/transactors/AMM/AMMDeposit.cpp +++ b/src/libxrpl/tx/transactors/dex/AMMDeposit.cpp @@ -3,9 +3,9 @@ #include #include #include -#include -#include -#include +#include +#include +#include namespace xrpl { diff --git a/src/libxrpl/tx/transactors/AMM/AMMHelpers.cpp b/src/libxrpl/tx/transactors/dex/AMMHelpers.cpp similarity index 99% rename from src/libxrpl/tx/transactors/AMM/AMMHelpers.cpp rename to src/libxrpl/tx/transactors/dex/AMMHelpers.cpp index 5d5af6da03..c232f82543 100644 --- a/src/libxrpl/tx/transactors/AMM/AMMHelpers.cpp +++ b/src/libxrpl/tx/transactors/dex/AMMHelpers.cpp @@ -1,4 +1,4 @@ -#include +#include namespace xrpl { diff --git a/src/libxrpl/tx/transactors/AMM/AMMUtils.cpp b/src/libxrpl/tx/transactors/dex/AMMUtils.cpp similarity index 99% rename from src/libxrpl/tx/transactors/AMM/AMMUtils.cpp rename to src/libxrpl/tx/transactors/dex/AMMUtils.cpp index 55d71ced4f..ce44c9ffd6 100644 --- a/src/libxrpl/tx/transactors/AMM/AMMUtils.cpp +++ b/src/libxrpl/tx/transactors/dex/AMMUtils.cpp @@ -2,8 +2,8 @@ #include #include #include -#include -#include +#include +#include namespace xrpl { diff --git a/src/libxrpl/tx/transactors/AMM/AMMVote.cpp b/src/libxrpl/tx/transactors/dex/AMMVote.cpp similarity index 98% rename from src/libxrpl/tx/transactors/AMM/AMMVote.cpp rename to src/libxrpl/tx/transactors/dex/AMMVote.cpp index 549d9705b5..d784b67f88 100644 --- a/src/libxrpl/tx/transactors/AMM/AMMVote.cpp +++ b/src/libxrpl/tx/transactors/dex/AMMVote.cpp @@ -2,8 +2,8 @@ #include #include #include -#include -#include +#include +#include namespace xrpl { diff --git a/src/libxrpl/tx/transactors/AMM/AMMWithdraw.cpp b/src/libxrpl/tx/transactors/dex/AMMWithdraw.cpp similarity index 99% rename from src/libxrpl/tx/transactors/AMM/AMMWithdraw.cpp rename to src/libxrpl/tx/transactors/dex/AMMWithdraw.cpp index f58dbaefc6..cf35de321d 100644 --- a/src/libxrpl/tx/transactors/AMM/AMMWithdraw.cpp +++ b/src/libxrpl/tx/transactors/dex/AMMWithdraw.cpp @@ -2,9 +2,9 @@ #include #include #include -#include -#include -#include +#include +#include +#include namespace xrpl { diff --git a/src/libxrpl/tx/transactors/Offer/CancelOffer.cpp b/src/libxrpl/tx/transactors/dex/CancelOffer.cpp similarity index 96% rename from src/libxrpl/tx/transactors/Offer/CancelOffer.cpp rename to src/libxrpl/tx/transactors/dex/CancelOffer.cpp index 3e5d2441e1..2549fd327e 100644 --- a/src/libxrpl/tx/transactors/Offer/CancelOffer.cpp +++ b/src/libxrpl/tx/transactors/dex/CancelOffer.cpp @@ -1,7 +1,7 @@ #include #include #include -#include +#include namespace xrpl { diff --git a/src/libxrpl/tx/transactors/Offer/CreateOffer.cpp b/src/libxrpl/tx/transactors/dex/CreateOffer.cpp similarity index 99% rename from src/libxrpl/tx/transactors/Offer/CreateOffer.cpp rename to src/libxrpl/tx/transactors/dex/CreateOffer.cpp index 9516926693..515b72eae9 100644 --- a/src/libxrpl/tx/transactors/Offer/CreateOffer.cpp +++ b/src/libxrpl/tx/transactors/dex/CreateOffer.cpp @@ -8,8 +8,8 @@ #include #include #include -#include -#include +#include +#include namespace xrpl { TxConsequences diff --git a/src/libxrpl/tx/transactors/PermissionedDomain/PermissionedDEXHelpers.cpp b/src/libxrpl/tx/transactors/dex/PermissionedDEXHelpers.cpp similarity index 97% rename from src/libxrpl/tx/transactors/PermissionedDomain/PermissionedDEXHelpers.cpp rename to src/libxrpl/tx/transactors/dex/PermissionedDEXHelpers.cpp index 7a4fa62078..37db2d632f 100644 --- a/src/libxrpl/tx/transactors/PermissionedDomain/PermissionedDEXHelpers.cpp +++ b/src/libxrpl/tx/transactors/dex/PermissionedDEXHelpers.cpp @@ -1,5 +1,5 @@ #include -#include +#include namespace xrpl { namespace permissioned_dex { diff --git a/src/libxrpl/tx/transactors/DID.cpp b/src/libxrpl/tx/transactors/did/DID.cpp similarity index 99% rename from src/libxrpl/tx/transactors/DID.cpp rename to src/libxrpl/tx/transactors/did/DID.cpp index ade60221df..080a031c79 100644 --- a/src/libxrpl/tx/transactors/DID.cpp +++ b/src/libxrpl/tx/transactors/did/DID.cpp @@ -4,7 +4,7 @@ #include #include #include -#include +#include namespace xrpl { diff --git a/src/libxrpl/tx/transactors/Escrow.cpp b/src/libxrpl/tx/transactors/escrow/Escrow.cpp similarity index 99% rename from src/libxrpl/tx/transactors/Escrow.cpp rename to src/libxrpl/tx/transactors/escrow/Escrow.cpp index 8ea2de24d9..3d7d408af7 100644 --- a/src/libxrpl/tx/transactors/Escrow.cpp +++ b/src/libxrpl/tx/transactors/escrow/Escrow.cpp @@ -11,8 +11,8 @@ #include #include #include -#include -#include +#include +#include namespace xrpl { diff --git a/src/libxrpl/tx/transactors/Lending/LendingHelpers.cpp b/src/libxrpl/tx/transactors/lending/LendingHelpers.cpp similarity index 99% rename from src/libxrpl/tx/transactors/Lending/LendingHelpers.cpp rename to src/libxrpl/tx/transactors/lending/LendingHelpers.cpp index b0626b4650..d7162bbb0e 100644 --- a/src/libxrpl/tx/transactors/Lending/LendingHelpers.cpp +++ b/src/libxrpl/tx/transactors/lending/LendingHelpers.cpp @@ -1,6 +1,6 @@ -#include +#include // DO NOT REMOVE forces header file include to sort first -#include +#include namespace xrpl { diff --git a/src/libxrpl/tx/transactors/Lending/LoanBrokerCoverClawback.cpp b/src/libxrpl/tx/transactors/lending/LoanBrokerCoverClawback.cpp similarity index 98% rename from src/libxrpl/tx/transactors/Lending/LoanBrokerCoverClawback.cpp rename to src/libxrpl/tx/transactors/lending/LoanBrokerCoverClawback.cpp index 870af54f94..8513ae5999 100644 --- a/src/libxrpl/tx/transactors/Lending/LoanBrokerCoverClawback.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanBrokerCoverClawback.cpp @@ -1,7 +1,7 @@ -#include +#include // #include -#include +#include namespace xrpl { diff --git a/src/libxrpl/tx/transactors/Lending/LoanBrokerCoverDeposit.cpp b/src/libxrpl/tx/transactors/lending/LoanBrokerCoverDeposit.cpp similarity index 96% rename from src/libxrpl/tx/transactors/Lending/LoanBrokerCoverDeposit.cpp rename to src/libxrpl/tx/transactors/lending/LoanBrokerCoverDeposit.cpp index d112959fea..83f7d2d1c6 100644 --- a/src/libxrpl/tx/transactors/Lending/LoanBrokerCoverDeposit.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanBrokerCoverDeposit.cpp @@ -1,7 +1,7 @@ -#include +#include // #include -#include +#include namespace xrpl { diff --git a/src/libxrpl/tx/transactors/Lending/LoanBrokerCoverWithdraw.cpp b/src/libxrpl/tx/transactors/lending/LoanBrokerCoverWithdraw.cpp similarity index 96% rename from src/libxrpl/tx/transactors/Lending/LoanBrokerCoverWithdraw.cpp rename to src/libxrpl/tx/transactors/lending/LoanBrokerCoverWithdraw.cpp index 43ff3659ef..9140a0dc23 100644 --- a/src/libxrpl/tx/transactors/Lending/LoanBrokerCoverWithdraw.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanBrokerCoverWithdraw.cpp @@ -1,9 +1,9 @@ -#include +#include // #include #include -#include -#include +#include +#include namespace xrpl { diff --git a/src/libxrpl/tx/transactors/Lending/LoanBrokerDelete.cpp b/src/libxrpl/tx/transactors/lending/LoanBrokerDelete.cpp similarity index 97% rename from src/libxrpl/tx/transactors/Lending/LoanBrokerDelete.cpp rename to src/libxrpl/tx/transactors/lending/LoanBrokerDelete.cpp index 989c26d024..85026654da 100644 --- a/src/libxrpl/tx/transactors/Lending/LoanBrokerDelete.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanBrokerDelete.cpp @@ -1,7 +1,7 @@ -#include +#include // #include -#include +#include namespace xrpl { diff --git a/src/libxrpl/tx/transactors/Lending/LoanBrokerSet.cpp b/src/libxrpl/tx/transactors/lending/LoanBrokerSet.cpp similarity index 98% rename from src/libxrpl/tx/transactors/Lending/LoanBrokerSet.cpp rename to src/libxrpl/tx/transactors/lending/LoanBrokerSet.cpp index 1ede32dc23..c825a7fafe 100644 --- a/src/libxrpl/tx/transactors/Lending/LoanBrokerSet.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanBrokerSet.cpp @@ -1,7 +1,7 @@ -#include +#include // #include -#include +#include namespace xrpl { diff --git a/src/libxrpl/tx/transactors/Lending/LoanDelete.cpp b/src/libxrpl/tx/transactors/lending/LoanDelete.cpp similarity index 97% rename from src/libxrpl/tx/transactors/Lending/LoanDelete.cpp rename to src/libxrpl/tx/transactors/lending/LoanDelete.cpp index fa3ccba7da..e7d219d6c8 100644 --- a/src/libxrpl/tx/transactors/Lending/LoanDelete.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanDelete.cpp @@ -1,7 +1,7 @@ -#include +#include // #include -#include +#include namespace xrpl { diff --git a/src/libxrpl/tx/transactors/Lending/LoanManage.cpp b/src/libxrpl/tx/transactors/lending/LoanManage.cpp similarity index 99% rename from src/libxrpl/tx/transactors/Lending/LoanManage.cpp rename to src/libxrpl/tx/transactors/lending/LoanManage.cpp index c066c4b3a9..2dacb453b5 100644 --- a/src/libxrpl/tx/transactors/Lending/LoanManage.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanManage.cpp @@ -1,8 +1,8 @@ -#include +#include // #include #include -#include +#include namespace xrpl { diff --git a/src/libxrpl/tx/transactors/Lending/LoanPay.cpp b/src/libxrpl/tx/transactors/lending/LoanPay.cpp similarity index 99% rename from src/libxrpl/tx/transactors/Lending/LoanPay.cpp rename to src/libxrpl/tx/transactors/lending/LoanPay.cpp index 6b1f2acf7c..d894c56631 100644 --- a/src/libxrpl/tx/transactors/Lending/LoanPay.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanPay.cpp @@ -1,11 +1,11 @@ -#include +#include // #include #include #include #include -#include -#include +#include +#include #include diff --git a/src/libxrpl/tx/transactors/Lending/LoanSet.cpp b/src/libxrpl/tx/transactors/lending/LoanSet.cpp similarity index 99% rename from src/libxrpl/tx/transactors/Lending/LoanSet.cpp rename to src/libxrpl/tx/transactors/lending/LoanSet.cpp index 5e45bd5a9a..f74522e737 100644 --- a/src/libxrpl/tx/transactors/Lending/LoanSet.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanSet.cpp @@ -1,8 +1,8 @@ -#include +#include // #include #include -#include +#include namespace xrpl { diff --git a/src/libxrpl/tx/transactors/NFT/NFTokenAcceptOffer.cpp b/src/libxrpl/tx/transactors/nft/NFTokenAcceptOffer.cpp similarity index 99% rename from src/libxrpl/tx/transactors/NFT/NFTokenAcceptOffer.cpp rename to src/libxrpl/tx/transactors/nft/NFTokenAcceptOffer.cpp index 90a28a2e7c..32af49355a 100644 --- a/src/libxrpl/tx/transactors/NFT/NFTokenAcceptOffer.cpp +++ b/src/libxrpl/tx/transactors/nft/NFTokenAcceptOffer.cpp @@ -2,8 +2,8 @@ #include #include #include -#include -#include +#include +#include namespace xrpl { diff --git a/src/libxrpl/tx/transactors/NFT/NFTokenBurn.cpp b/src/libxrpl/tx/transactors/nft/NFTokenBurn.cpp similarity index 96% rename from src/libxrpl/tx/transactors/NFT/NFTokenBurn.cpp rename to src/libxrpl/tx/transactors/nft/NFTokenBurn.cpp index 65ef9e7542..6d9ee56c51 100644 --- a/src/libxrpl/tx/transactors/NFT/NFTokenBurn.cpp +++ b/src/libxrpl/tx/transactors/nft/NFTokenBurn.cpp @@ -1,8 +1,8 @@ #include #include #include -#include -#include +#include +#include namespace xrpl { diff --git a/src/libxrpl/tx/transactors/NFT/NFTokenCancelOffer.cpp b/src/libxrpl/tx/transactors/nft/NFTokenCancelOffer.cpp similarity index 95% rename from src/libxrpl/tx/transactors/NFT/NFTokenCancelOffer.cpp rename to src/libxrpl/tx/transactors/nft/NFTokenCancelOffer.cpp index ce2cdf83c5..df0561e076 100644 --- a/src/libxrpl/tx/transactors/NFT/NFTokenCancelOffer.cpp +++ b/src/libxrpl/tx/transactors/nft/NFTokenCancelOffer.cpp @@ -1,8 +1,8 @@ #include #include #include -#include -#include +#include +#include #include diff --git a/src/libxrpl/tx/transactors/NFT/NFTokenCreateOffer.cpp b/src/libxrpl/tx/transactors/nft/NFTokenCreateOffer.cpp similarity index 94% rename from src/libxrpl/tx/transactors/NFT/NFTokenCreateOffer.cpp rename to src/libxrpl/tx/transactors/nft/NFTokenCreateOffer.cpp index 6fe911ef5b..ca90bcafab 100644 --- a/src/libxrpl/tx/transactors/NFT/NFTokenCreateOffer.cpp +++ b/src/libxrpl/tx/transactors/nft/NFTokenCreateOffer.cpp @@ -1,8 +1,8 @@ #include #include #include -#include -#include +#include +#include namespace xrpl { diff --git a/src/libxrpl/tx/transactors/NFT/NFTokenMint.cpp b/src/libxrpl/tx/transactors/nft/NFTokenMint.cpp similarity index 99% rename from src/libxrpl/tx/transactors/NFT/NFTokenMint.cpp rename to src/libxrpl/tx/transactors/nft/NFTokenMint.cpp index a32a659bbd..a643a6042c 100644 --- a/src/libxrpl/tx/transactors/NFT/NFTokenMint.cpp +++ b/src/libxrpl/tx/transactors/nft/NFTokenMint.cpp @@ -4,7 +4,7 @@ #include #include #include -#include +#include #include diff --git a/src/libxrpl/tx/transactors/NFT/NFTokenModify.cpp b/src/libxrpl/tx/transactors/nft/NFTokenModify.cpp similarity index 93% rename from src/libxrpl/tx/transactors/NFT/NFTokenModify.cpp rename to src/libxrpl/tx/transactors/nft/NFTokenModify.cpp index 316bcb0f27..af852530e4 100644 --- a/src/libxrpl/tx/transactors/NFT/NFTokenModify.cpp +++ b/src/libxrpl/tx/transactors/nft/NFTokenModify.cpp @@ -1,7 +1,7 @@ #include #include -#include -#include +#include +#include namespace xrpl { diff --git a/src/libxrpl/tx/transactors/NFT/NFTokenUtils.cpp b/src/libxrpl/tx/transactors/nft/NFTokenUtils.cpp similarity index 99% rename from src/libxrpl/tx/transactors/NFT/NFTokenUtils.cpp rename to src/libxrpl/tx/transactors/nft/NFTokenUtils.cpp index 383929d54e..650fdabf29 100644 --- a/src/libxrpl/tx/transactors/NFT/NFTokenUtils.cpp +++ b/src/libxrpl/tx/transactors/nft/NFTokenUtils.cpp @@ -5,7 +5,7 @@ #include #include #include -#include +#include #include #include diff --git a/src/libxrpl/tx/transactors/DeleteOracle.cpp b/src/libxrpl/tx/transactors/oracle/DeleteOracle.cpp similarity index 97% rename from src/libxrpl/tx/transactors/DeleteOracle.cpp rename to src/libxrpl/tx/transactors/oracle/DeleteOracle.cpp index ebaffd334a..5452096b6d 100644 --- a/src/libxrpl/tx/transactors/DeleteOracle.cpp +++ b/src/libxrpl/tx/transactors/oracle/DeleteOracle.cpp @@ -2,7 +2,7 @@ #include #include #include -#include +#include namespace xrpl { diff --git a/src/libxrpl/tx/transactors/SetOracle.cpp b/src/libxrpl/tx/transactors/oracle/SetOracle.cpp similarity index 99% rename from src/libxrpl/tx/transactors/SetOracle.cpp rename to src/libxrpl/tx/transactors/oracle/SetOracle.cpp index 140938e2d7..be827c25d5 100644 --- a/src/libxrpl/tx/transactors/SetOracle.cpp +++ b/src/libxrpl/tx/transactors/oracle/SetOracle.cpp @@ -4,7 +4,7 @@ #include #include #include -#include +#include namespace xrpl { diff --git a/src/libxrpl/tx/transactors/DepositPreauth.cpp b/src/libxrpl/tx/transactors/payment/DepositPreauth.cpp similarity index 99% rename from src/libxrpl/tx/transactors/DepositPreauth.cpp rename to src/libxrpl/tx/transactors/payment/DepositPreauth.cpp index 6857816d1f..cd86a6906c 100644 --- a/src/libxrpl/tx/transactors/DepositPreauth.cpp +++ b/src/libxrpl/tx/transactors/payment/DepositPreauth.cpp @@ -4,7 +4,7 @@ #include #include #include -#include +#include #include diff --git a/src/libxrpl/tx/transactors/Payment.cpp b/src/libxrpl/tx/transactors/payment/Payment.cpp similarity index 99% rename from src/libxrpl/tx/transactors/Payment.cpp rename to src/libxrpl/tx/transactors/payment/Payment.cpp index b98bbf11bd..1605c7d886 100644 --- a/src/libxrpl/tx/transactors/Payment.cpp +++ b/src/libxrpl/tx/transactors/payment/Payment.cpp @@ -6,9 +6,9 @@ #include #include #include -#include -#include -#include +#include +#include +#include namespace xrpl { diff --git a/src/libxrpl/tx/transactors/PayChan.cpp b/src/libxrpl/tx/transactors/payment_channel/PayChan.cpp similarity index 99% rename from src/libxrpl/tx/transactors/PayChan.cpp rename to src/libxrpl/tx/transactors/payment_channel/PayChan.cpp index 265030bebf..f963153e87 100644 --- a/src/libxrpl/tx/transactors/PayChan.cpp +++ b/src/libxrpl/tx/transactors/payment_channel/PayChan.cpp @@ -10,7 +10,7 @@ #include #include #include -#include +#include namespace xrpl { diff --git a/src/libxrpl/tx/transactors/PermissionedDomain/PermissionedDomainDelete.cpp b/src/libxrpl/tx/transactors/permissioned_domain/PermissionedDomainDelete.cpp similarity index 95% rename from src/libxrpl/tx/transactors/PermissionedDomain/PermissionedDomainDelete.cpp rename to src/libxrpl/tx/transactors/permissioned_domain/PermissionedDomainDelete.cpp index 861bb934be..2c7e934fc0 100644 --- a/src/libxrpl/tx/transactors/PermissionedDomain/PermissionedDomainDelete.cpp +++ b/src/libxrpl/tx/transactors/permissioned_domain/PermissionedDomainDelete.cpp @@ -1,6 +1,6 @@ #include #include -#include +#include namespace xrpl { diff --git a/src/libxrpl/tx/transactors/PermissionedDomain/PermissionedDomainSet.cpp b/src/libxrpl/tx/transactors/permissioned_domain/PermissionedDomainSet.cpp similarity index 98% rename from src/libxrpl/tx/transactors/PermissionedDomain/PermissionedDomainSet.cpp rename to src/libxrpl/tx/transactors/permissioned_domain/PermissionedDomainSet.cpp index 15bb79b239..c27c884b10 100644 --- a/src/libxrpl/tx/transactors/PermissionedDomain/PermissionedDomainSet.cpp +++ b/src/libxrpl/tx/transactors/permissioned_domain/PermissionedDomainSet.cpp @@ -1,4 +1,4 @@ -#include +#include // #include #include diff --git a/src/libxrpl/tx/transactors/Batch.cpp b/src/libxrpl/tx/transactors/system/Batch.cpp similarity index 99% rename from src/libxrpl/tx/transactors/Batch.cpp rename to src/libxrpl/tx/transactors/system/Batch.cpp index 67bb1f26e4..ed3dabec75 100644 --- a/src/libxrpl/tx/transactors/Batch.cpp +++ b/src/libxrpl/tx/transactors/system/Batch.cpp @@ -7,7 +7,7 @@ #include #include #include -#include +#include namespace xrpl { diff --git a/src/libxrpl/tx/transactors/Change.cpp b/src/libxrpl/tx/transactors/system/Change.cpp similarity index 99% rename from src/libxrpl/tx/transactors/Change.cpp rename to src/libxrpl/tx/transactors/system/Change.cpp index 9ae6504718..d1e2d148d6 100644 --- a/src/libxrpl/tx/transactors/Change.cpp +++ b/src/libxrpl/tx/transactors/system/Change.cpp @@ -5,7 +5,7 @@ #include #include #include -#include +#include #include diff --git a/src/libxrpl/tx/transactors/CreateTicket.cpp b/src/libxrpl/tx/transactors/system/CreateTicket.cpp similarity index 98% rename from src/libxrpl/tx/transactors/CreateTicket.cpp rename to src/libxrpl/tx/transactors/system/CreateTicket.cpp index b1991c6e77..7334d88be4 100644 --- a/src/libxrpl/tx/transactors/CreateTicket.cpp +++ b/src/libxrpl/tx/transactors/system/CreateTicket.cpp @@ -3,7 +3,7 @@ #include #include #include -#include +#include namespace xrpl { diff --git a/src/libxrpl/tx/transactors/LedgerStateFix.cpp b/src/libxrpl/tx/transactors/system/LedgerStateFix.cpp similarity index 93% rename from src/libxrpl/tx/transactors/LedgerStateFix.cpp rename to src/libxrpl/tx/transactors/system/LedgerStateFix.cpp index 28028b6f2b..da04d60142 100644 --- a/src/libxrpl/tx/transactors/LedgerStateFix.cpp +++ b/src/libxrpl/tx/transactors/system/LedgerStateFix.cpp @@ -2,8 +2,8 @@ #include #include #include -#include -#include +#include +#include namespace xrpl { diff --git a/src/libxrpl/tx/transactors/Clawback.cpp b/src/libxrpl/tx/transactors/token/Clawback.cpp similarity index 99% rename from src/libxrpl/tx/transactors/Clawback.cpp rename to src/libxrpl/tx/transactors/token/Clawback.cpp index 789017e3ec..c64459faa9 100644 --- a/src/libxrpl/tx/transactors/Clawback.cpp +++ b/src/libxrpl/tx/transactors/token/Clawback.cpp @@ -4,7 +4,7 @@ #include #include #include -#include +#include namespace xrpl { diff --git a/src/libxrpl/tx/transactors/MPT/MPTokenAuthorize.cpp b/src/libxrpl/tx/transactors/token/MPTokenAuthorize.cpp similarity index 98% rename from src/libxrpl/tx/transactors/MPT/MPTokenAuthorize.cpp rename to src/libxrpl/tx/transactors/token/MPTokenAuthorize.cpp index 846b25ca38..01513e17e0 100644 --- a/src/libxrpl/tx/transactors/MPT/MPTokenAuthorize.cpp +++ b/src/libxrpl/tx/transactors/token/MPTokenAuthorize.cpp @@ -2,7 +2,7 @@ #include #include #include -#include +#include namespace xrpl { diff --git a/src/libxrpl/tx/transactors/MPT/MPTokenIssuanceCreate.cpp b/src/libxrpl/tx/transactors/token/MPTokenIssuanceCreate.cpp similarity index 98% rename from src/libxrpl/tx/transactors/MPT/MPTokenIssuanceCreate.cpp rename to src/libxrpl/tx/transactors/token/MPTokenIssuanceCreate.cpp index ae697aa670..cf435a30ca 100644 --- a/src/libxrpl/tx/transactors/MPT/MPTokenIssuanceCreate.cpp +++ b/src/libxrpl/tx/transactors/token/MPTokenIssuanceCreate.cpp @@ -1,7 +1,7 @@ #include #include #include -#include +#include namespace xrpl { diff --git a/src/libxrpl/tx/transactors/MPT/MPTokenIssuanceDestroy.cpp b/src/libxrpl/tx/transactors/token/MPTokenIssuanceDestroy.cpp similarity index 95% rename from src/libxrpl/tx/transactors/MPT/MPTokenIssuanceDestroy.cpp rename to src/libxrpl/tx/transactors/token/MPTokenIssuanceDestroy.cpp index 4b4d7c53f5..1df4da47ab 100644 --- a/src/libxrpl/tx/transactors/MPT/MPTokenIssuanceDestroy.cpp +++ b/src/libxrpl/tx/transactors/token/MPTokenIssuanceDestroy.cpp @@ -1,7 +1,7 @@ #include #include #include -#include +#include namespace xrpl { diff --git a/src/libxrpl/tx/transactors/MPT/MPTokenIssuanceSet.cpp b/src/libxrpl/tx/transactors/token/MPTokenIssuanceSet.cpp similarity index 98% rename from src/libxrpl/tx/transactors/MPT/MPTokenIssuanceSet.cpp rename to src/libxrpl/tx/transactors/token/MPTokenIssuanceSet.cpp index 2ae6bde083..e6b1ffbbc3 100644 --- a/src/libxrpl/tx/transactors/MPT/MPTokenIssuanceSet.cpp +++ b/src/libxrpl/tx/transactors/token/MPTokenIssuanceSet.cpp @@ -1,8 +1,8 @@ #include #include #include -#include -#include +#include +#include namespace xrpl { diff --git a/src/libxrpl/tx/transactors/SetTrust.cpp b/src/libxrpl/tx/transactors/token/SetTrust.cpp similarity index 99% rename from src/libxrpl/tx/transactors/SetTrust.cpp rename to src/libxrpl/tx/transactors/token/SetTrust.cpp index 268d626432..b6ae288a8c 100644 --- a/src/libxrpl/tx/transactors/SetTrust.cpp +++ b/src/libxrpl/tx/transactors/token/SetTrust.cpp @@ -6,8 +6,8 @@ #include #include #include -#include -#include +#include +#include namespace { diff --git a/src/libxrpl/tx/transactors/Vault/VaultClawback.cpp b/src/libxrpl/tx/transactors/vault/VaultClawback.cpp similarity index 99% rename from src/libxrpl/tx/transactors/Vault/VaultClawback.cpp rename to src/libxrpl/tx/transactors/vault/VaultClawback.cpp index bce53c706d..bfce93b1dd 100644 --- a/src/libxrpl/tx/transactors/Vault/VaultClawback.cpp +++ b/src/libxrpl/tx/transactors/vault/VaultClawback.cpp @@ -8,7 +8,7 @@ #include #include #include -#include +#include #include diff --git a/src/libxrpl/tx/transactors/Vault/VaultCreate.cpp b/src/libxrpl/tx/transactors/vault/VaultCreate.cpp similarity index 97% rename from src/libxrpl/tx/transactors/Vault/VaultCreate.cpp rename to src/libxrpl/tx/transactors/vault/VaultCreate.cpp index 230651fd9b..b1f227ddf3 100644 --- a/src/libxrpl/tx/transactors/Vault/VaultCreate.cpp +++ b/src/libxrpl/tx/transactors/vault/VaultCreate.cpp @@ -10,9 +10,9 @@ #include #include #include -#include -#include -#include +#include +#include +#include namespace xrpl { diff --git a/src/libxrpl/tx/transactors/Vault/VaultDelete.cpp b/src/libxrpl/tx/transactors/vault/VaultDelete.cpp similarity index 99% rename from src/libxrpl/tx/transactors/Vault/VaultDelete.cpp rename to src/libxrpl/tx/transactors/vault/VaultDelete.cpp index 0b3aef19a8..ad39ed9cf2 100644 --- a/src/libxrpl/tx/transactors/Vault/VaultDelete.cpp +++ b/src/libxrpl/tx/transactors/vault/VaultDelete.cpp @@ -5,7 +5,7 @@ #include #include #include -#include +#include namespace xrpl { diff --git a/src/libxrpl/tx/transactors/Vault/VaultDeposit.cpp b/src/libxrpl/tx/transactors/vault/VaultDeposit.cpp similarity index 98% rename from src/libxrpl/tx/transactors/Vault/VaultDeposit.cpp rename to src/libxrpl/tx/transactors/vault/VaultDeposit.cpp index c3a9327f2a..58912f5010 100644 --- a/src/libxrpl/tx/transactors/Vault/VaultDeposit.cpp +++ b/src/libxrpl/tx/transactors/vault/VaultDeposit.cpp @@ -9,8 +9,8 @@ #include #include #include -#include -#include +#include +#include namespace xrpl { diff --git a/src/libxrpl/tx/transactors/Vault/VaultSet.cpp b/src/libxrpl/tx/transactors/vault/VaultSet.cpp similarity index 99% rename from src/libxrpl/tx/transactors/Vault/VaultSet.cpp rename to src/libxrpl/tx/transactors/vault/VaultSet.cpp index c27c9aae3c..532edcdcef 100644 --- a/src/libxrpl/tx/transactors/Vault/VaultSet.cpp +++ b/src/libxrpl/tx/transactors/vault/VaultSet.cpp @@ -7,7 +7,7 @@ #include #include #include -#include +#include namespace xrpl { diff --git a/src/libxrpl/tx/transactors/Vault/VaultWithdraw.cpp b/src/libxrpl/tx/transactors/vault/VaultWithdraw.cpp similarity index 99% rename from src/libxrpl/tx/transactors/Vault/VaultWithdraw.cpp rename to src/libxrpl/tx/transactors/vault/VaultWithdraw.cpp index 3e468df2fc..9f808c2dfc 100644 --- a/src/libxrpl/tx/transactors/Vault/VaultWithdraw.cpp +++ b/src/libxrpl/tx/transactors/vault/VaultWithdraw.cpp @@ -7,7 +7,7 @@ #include #include #include -#include +#include namespace xrpl { diff --git a/src/test/app/AMMCalc_test.cpp b/src/test/app/AMMCalc_test.cpp index e6e68a95ce..690329d2f3 100644 --- a/src/test/app/AMMCalc_test.cpp +++ b/src/test/app/AMMCalc_test.cpp @@ -1,7 +1,7 @@ #include #include -#include +#include #include diff --git a/src/test/app/AMMClawback_test.cpp b/src/test/app/AMMClawback_test.cpp index 114a96b081..3034c628f4 100644 --- a/src/test/app/AMMClawback_test.cpp +++ b/src/test/app/AMMClawback_test.cpp @@ -3,7 +3,7 @@ #include #include -#include +#include namespace xrpl { namespace test { diff --git a/src/test/app/AMMExtended_test.cpp b/src/test/app/AMMExtended_test.cpp index 06f82418da..14be260822 100644 --- a/src/test/app/AMMExtended_test.cpp +++ b/src/test/app/AMMExtended_test.cpp @@ -12,8 +12,8 @@ #include #include #include -#include -#include +#include +#include #include #include diff --git a/src/test/app/AMM_test.cpp b/src/test/app/AMM_test.cpp index fc801be816..39a3abcd9e 100644 --- a/src/test/app/AMM_test.cpp +++ b/src/test/app/AMM_test.cpp @@ -10,10 +10,10 @@ #include #include #include -#include -#include -#include -#include +#include +#include +#include +#include #include diff --git a/src/test/app/Batch_test.cpp b/src/test/app/Batch_test.cpp index f429299f01..d8a8cd6c5d 100644 --- a/src/test/app/Batch_test.cpp +++ b/src/test/app/Batch_test.cpp @@ -13,7 +13,7 @@ #include #include #include -#include +#include namespace xrpl { namespace test { diff --git a/src/test/app/FixNFTokenPageLinks_test.cpp b/src/test/app/FixNFTokenPageLinks_test.cpp index 67dadac5e1..8022c52a5d 100644 --- a/src/test/app/FixNFTokenPageLinks_test.cpp +++ b/src/test/app/FixNFTokenPageLinks_test.cpp @@ -3,7 +3,7 @@ #include #include #include -#include +#include namespace xrpl { diff --git a/src/test/app/LendingHelpers_test.cpp b/src/test/app/LendingHelpers_test.cpp index aae60a252a..24661c18fb 100644 --- a/src/test/app/LendingHelpers_test.cpp +++ b/src/test/app/LendingHelpers_test.cpp @@ -8,9 +8,9 @@ #include #include #include -#include -#include -#include +#include +#include +#include #include #include diff --git a/src/test/app/LoanBroker_test.cpp b/src/test/app/LoanBroker_test.cpp index 31cfb5c231..d7c4344ef5 100644 --- a/src/test/app/LoanBroker_test.cpp +++ b/src/test/app/LoanBroker_test.cpp @@ -1,7 +1,7 @@ #include #include -#include +#include namespace xrpl { namespace test { diff --git a/src/test/app/Loan_test.cpp b/src/test/app/Loan_test.cpp index 607e84abeb..5477994c53 100644 --- a/src/test/app/Loan_test.cpp +++ b/src/test/app/Loan_test.cpp @@ -6,9 +6,9 @@ #include #include #include -#include -#include -#include +#include +#include +#include #include diff --git a/src/test/app/NFTokenAuth_test.cpp b/src/test/app/NFTokenAuth_test.cpp index 6fc92bc6cd..b973530f41 100644 --- a/src/test/app/NFTokenAuth_test.cpp +++ b/src/test/app/NFTokenAuth_test.cpp @@ -1,6 +1,6 @@ #include -#include +#include namespace xrpl { diff --git a/src/test/app/NFTokenBurn_test.cpp b/src/test/app/NFTokenBurn_test.cpp index c75e22e954..8cd67bd72b 100644 --- a/src/test/app/NFTokenBurn_test.cpp +++ b/src/test/app/NFTokenBurn_test.cpp @@ -2,7 +2,7 @@ #include #include -#include +#include #include diff --git a/src/test/app/NFTokenDir_test.cpp b/src/test/app/NFTokenDir_test.cpp index c186a3d9e5..551b987204 100644 --- a/src/test/app/NFTokenDir_test.cpp +++ b/src/test/app/NFTokenDir_test.cpp @@ -3,7 +3,7 @@ #include #include #include -#include +#include #include diff --git a/src/test/app/NFToken_test.cpp b/src/test/app/NFToken_test.cpp index 6d30fc6854..7ff2e7d1cb 100644 --- a/src/test/app/NFToken_test.cpp +++ b/src/test/app/NFToken_test.cpp @@ -3,7 +3,7 @@ #include #include #include -#include +#include #include diff --git a/src/test/app/PayStrand_test.cpp b/src/test/app/PayStrand_test.cpp index 2cc2a188c3..a45da5f5f6 100644 --- a/src/test/app/PayStrand_test.cpp +++ b/src/test/app/PayStrand_test.cpp @@ -10,7 +10,7 @@ #include #include #include -#include +#include #include diff --git a/src/test/app/PermissionedDEX_test.cpp b/src/test/app/PermissionedDEX_test.cpp index 8ecc66911c..3cb732b9d1 100644 --- a/src/test/app/PermissionedDEX_test.cpp +++ b/src/test/app/PermissionedDEX_test.cpp @@ -16,7 +16,7 @@ #include #include #include -#include +#include #include #include diff --git a/src/test/app/PermissionedDomains_test.cpp b/src/test/app/PermissionedDomains_test.cpp index 39ce85d23d..d2a14ce82a 100644 --- a/src/test/app/PermissionedDomains_test.cpp +++ b/src/test/app/PermissionedDomains_test.cpp @@ -2,7 +2,7 @@ #include #include -#include +#include #include #include diff --git a/src/test/app/TheoreticalQuality_test.cpp b/src/test/app/TheoreticalQuality_test.cpp index 9b6d86dd5f..024ee0742e 100644 --- a/src/test/app/TheoreticalQuality_test.cpp +++ b/src/test/app/TheoreticalQuality_test.cpp @@ -9,7 +9,7 @@ #include #include #include -#include +#include namespace xrpl { namespace test { diff --git a/src/test/jtx/impl/AMM.cpp b/src/test/jtx/impl/AMM.cpp index faf47259dc..bb159d6bc1 100644 --- a/src/test/jtx/impl/AMM.cpp +++ b/src/test/jtx/impl/AMM.cpp @@ -5,8 +5,8 @@ #include #include #include -#include -#include +#include +#include namespace xrpl { namespace test { diff --git a/src/test/jtx/impl/ledgerStateFixes.cpp b/src/test/jtx/impl/ledgerStateFixes.cpp index 04860a5e3a..e5a7495a44 100644 --- a/src/test/jtx/impl/ledgerStateFixes.cpp +++ b/src/test/jtx/impl/ledgerStateFixes.cpp @@ -2,7 +2,7 @@ #include #include -#include +#include namespace xrpl { namespace test { diff --git a/src/test/jtx/impl/token.cpp b/src/test/jtx/impl/token.cpp index 59f8c822fb..9db79361eb 100644 --- a/src/test/jtx/impl/token.cpp +++ b/src/test/jtx/impl/token.cpp @@ -3,7 +3,7 @@ #include #include -#include +#include namespace xrpl { namespace test { diff --git a/src/test/rpc/AccountObjects_test.cpp b/src/test/rpc/AccountObjects_test.cpp index 7c1877136d..090f2a43b9 100644 --- a/src/test/rpc/AccountObjects_test.cpp +++ b/src/test/rpc/AccountObjects_test.cpp @@ -7,7 +7,7 @@ #include #include #include -#include +#include #include diff --git a/src/xrpld/app/ledger/OrderBookDBImpl.cpp b/src/xrpld/app/ledger/OrderBookDBImpl.cpp index a4eb0fb1ca..b82fa3896b 100644 --- a/src/xrpld/app/ledger/OrderBookDBImpl.cpp +++ b/src/xrpld/app/ledger/OrderBookDBImpl.cpp @@ -5,7 +5,7 @@ #include #include #include -#include +#include namespace xrpl { diff --git a/src/xrpld/app/paths/AMMLiquidity.h b/src/xrpld/app/paths/AMMLiquidity.h index 5c3ff2a8e6..71b8dbb12a 100644 --- a/src/xrpld/app/paths/AMMLiquidity.h +++ b/src/xrpld/app/paths/AMMLiquidity.h @@ -4,9 +4,9 @@ #include #include #include -#include -#include -#include +#include +#include +#include namespace xrpl { diff --git a/src/xrpld/app/paths/detail/BookStep.cpp b/src/xrpld/app/paths/detail/BookStep.cpp index 4124fdfa30..dca38f8df5 100644 --- a/src/xrpld/app/paths/detail/BookStep.cpp +++ b/src/xrpld/app/paths/detail/BookStep.cpp @@ -13,7 +13,7 @@ #include #include #include -#include +#include #include diff --git a/src/xrpld/rpc/detail/RPCHelpers.cpp b/src/xrpld/rpc/detail/RPCHelpers.cpp index 14e1108955..385bc7f4af 100644 --- a/src/xrpld/rpc/detail/RPCHelpers.cpp +++ b/src/xrpld/rpc/detail/RPCHelpers.cpp @@ -10,7 +10,7 @@ #include #include #include -#include +#include #include #include diff --git a/src/xrpld/rpc/handlers/AMMInfo.cpp b/src/xrpld/rpc/handlers/AMMInfo.cpp index 3b843c9410..4e91c9c308 100644 --- a/src/xrpld/rpc/handlers/AMMInfo.cpp +++ b/src/xrpld/rpc/handlers/AMMInfo.cpp @@ -6,7 +6,7 @@ #include #include #include -#include +#include #include diff --git a/src/xrpld/rpc/handlers/AccountObjects.cpp b/src/xrpld/rpc/handlers/AccountObjects.cpp index 9dcdf7bdf6..e01822e901 100644 --- a/src/xrpld/rpc/handlers/AccountObjects.cpp +++ b/src/xrpld/rpc/handlers/AccountObjects.cpp @@ -11,7 +11,7 @@ #include #include #include -#include +#include #include From 7b12c00e6bdae511dd965f0f81c83972368d84cc Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Fri, 6 Mar 2026 12:50:00 +0000 Subject: [PATCH 35/41] chore: Add custom cmake definitions for gersemi (#6491) This change adds definitions for our custom functions/macros, so gersemi will nicely format them too. --- .gersemi/definitions.cmake | 98 ++++++++++++++++++++++++++++++++++++++ .gersemirc | 1 + cmake/XrplAddTest.cmake | 7 ++- cmake/XrplCore.cmake | 19 ++++++-- cmake/XrplCov.cmake | 20 ++++---- cmake/XrplDocs.cmake | 6 ++- cmake/add_module.cmake | 14 ++++-- 7 files changed, 142 insertions(+), 23 deletions(-) create mode 100644 .gersemi/definitions.cmake create mode 100644 .gersemirc diff --git a/.gersemi/definitions.cmake b/.gersemi/definitions.cmake new file mode 100644 index 0000000000..13061629a4 --- /dev/null +++ b/.gersemi/definitions.cmake @@ -0,0 +1,98 @@ +# Custom CMake command definitions for gersemi formatting. +# These stubs teach gersemi the signatures of project-specific commands +# so it can format their invocations correctly. + +function(git_branch branch_val) +endfunction() + +function(isolate_headers target A B scope) +endfunction() + +function(create_symbolic_link target link) +endfunction() + +function(xrpl_add_test name) +endfunction() + +macro(exclude_from_default target_) +endmacro() + +macro(exclude_if_included target_) +endmacro() + +function(target_protobuf_sources target prefix) + set(options APPEND_PATH DESCRIPTORS) + set(oneValueArgs + LANGUAGE + OUT_VAR + EXPORT_MACRO + TARGET + PROTOC_OUT_DIR + PLUGIN + PLUGIN_OPTIONS + PROTOC_EXE + ) + set(multiValueArgs + PROTOS + IMPORT_DIRS + GENERATE_EXTENSIONS + PROTOC_OPTIONS + DEPENDENCIES + ) + cmake_parse_arguments( + THIS_FUNCTION_PREFIX + "${options}" + "${oneValueArgs}" + "${multiValueArgs}" + ${ARGN} + ) +endfunction() + +function(add_module parent name) +endfunction() + +function(target_link_modules parent scope) +endfunction() + +function(setup_target_for_coverage_gcovr) + set(options NONE) + set(oneValueArgs BASE_DIRECTORY NAME FORMAT) + set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES) + cmake_parse_arguments( + THIS_FUNCTION_PREFIX + "${options}" + "${oneValueArgs}" + "${multiValueArgs}" + ${ARGN} + ) +endfunction() + +function(add_code_coverage_to_target name scope) +endfunction() + +function(verbose_find_path variable name) + set(options + NO_CACHE + REQUIRED + OPTIONAL + NO_DEFAULT_PATH + NO_PACKAGE_ROOT_PATH + NO_CMAKE_PATH + NO_CMAKE_ENVIRONMENT_PATH + NO_SYSTEM_ENVIRONMENT_PATH + NO_CMAKE_SYSTEM_PATH + NO_CMAKE_INSTALL_PREFIX + CMAKE_FIND_ROOT_PATH_BOTH + ONLY_CMAKE_FIND_ROOT_PATH + NO_CMAKE_FIND_ROOT_PATH + ) + set(oneValueArgs REGISTRY_VIEW VALIDATOR DOC) + set(multiValueArgs NAMES HINTS PATHS PATH_SUFFIXES) + cmake_parse_arguments( + THIS_FUNCTION_PREFIX + "${options}" + "${oneValueArgs}" + "${multiValueArgs}" + ${ARGN} + ) +endfunction() diff --git a/.gersemirc b/.gersemirc new file mode 100644 index 0000000000..5abd52ffd5 --- /dev/null +++ b/.gersemirc @@ -0,0 +1 @@ +definitions: [.gersemi] diff --git a/cmake/XrplAddTest.cmake b/cmake/XrplAddTest.cmake index 0f8f9e2c01..2f1209e03c 100644 --- a/cmake/XrplAddTest.cmake +++ b/cmake/XrplAddTest.cmake @@ -11,7 +11,12 @@ function(xrpl_add_test name) ) add_executable(${target} ${ARGN} ${sources}) - isolate_headers(${target} "${CMAKE_SOURCE_DIR}" "${CMAKE_SOURCE_DIR}/tests/${name}" PRIVATE) + isolate_headers( + ${target} + "${CMAKE_SOURCE_DIR}" + "${CMAKE_SOURCE_DIR}/tests/${name}" + PRIVATE + ) add_test(NAME ${target} COMMAND ${target}) endfunction() diff --git a/cmake/XrplCore.cmake b/cmake/XrplCore.cmake index 1c8f5e33d6..580c4155eb 100644 --- a/cmake/XrplCore.cmake +++ b/cmake/XrplCore.cmake @@ -10,16 +10,25 @@ include(target_protobuf_sources) # so we just build them as a separate library. add_library(xrpl.libpb) set_target_properties(xrpl.libpb PROPERTIES UNITY_BUILD OFF) -target_protobuf_sources(xrpl.libpb xrpl/proto LANGUAGE cpp IMPORT_DIRS include/xrpl/proto - PROTOS include/xrpl/proto/xrpl.proto +target_protobuf_sources( + xrpl.libpb + xrpl/proto + LANGUAGE cpp + IMPORT_DIRS include/xrpl/proto + PROTOS include/xrpl/proto/xrpl.proto ) file(GLOB_RECURSE protos "include/xrpl/proto/org/*.proto") -target_protobuf_sources(xrpl.libpb xrpl/proto LANGUAGE cpp IMPORT_DIRS include/xrpl/proto - PROTOS "${protos}" +target_protobuf_sources( + xrpl.libpb + xrpl/proto + LANGUAGE cpp + IMPORT_DIRS include/xrpl/proto + PROTOS "${protos}" ) target_protobuf_sources( - xrpl.libpb xrpl/proto + xrpl.libpb + xrpl/proto LANGUAGE grpc IMPORT_DIRS include/xrpl/proto PROTOS "${protos}" diff --git a/cmake/XrplCov.cmake b/cmake/XrplCov.cmake index 109f5049fd..d81d7e689f 100644 --- a/cmake/XrplCov.cmake +++ b/cmake/XrplCov.cmake @@ -39,19 +39,15 @@ list( ) setup_target_for_coverage_gcovr( - NAME - coverage - FORMAT - ${coverage_format} + NAME coverage + FORMAT ${coverage_format} EXCLUDE - "src/test" - "src/tests" - "include/xrpl/beast/test" - "include/xrpl/beast/unit_test" - "${CMAKE_BINARY_DIR}/pb-xrpl.libpb" - DEPENDENCIES - xrpld - xrpl.tests + "src/test" + "src/tests" + "include/xrpl/beast/test" + "include/xrpl/beast/unit_test" + "${CMAKE_BINARY_DIR}/pb-xrpl.libpb" + DEPENDENCIES xrpld xrpl.tests ) add_code_coverage_to_target(opts INTERFACE) diff --git a/cmake/XrplDocs.cmake b/cmake/XrplDocs.cmake index aefffba81b..7b3e9b3b30 100644 --- a/cmake/XrplDocs.cmake +++ b/cmake/XrplDocs.cmake @@ -42,7 +42,11 @@ function(verbose_find_path variable name) endif() endfunction() -verbose_find_path(doxygen_plantuml_jar_path plantuml.jar PATH_SUFFIXES share/plantuml) +verbose_find_path( + doxygen_plantuml_jar_path + plantuml.jar + PATH_SUFFIXES share/plantuml +) verbose_find_path(doxygen_dot_path dot) # https://en.cppreference.com/w/Cppreference:Archives diff --git a/cmake/add_module.cmake b/cmake/add_module.cmake index 5b1864a0bb..316d6c627b 100644 --- a/cmake/add_module.cmake +++ b/cmake/add_module.cmake @@ -25,10 +25,16 @@ function(add_module parent name) ${target} PUBLIC "$" ) - isolate_headers(${target} "${CMAKE_CURRENT_SOURCE_DIR}/include" - "${CMAKE_CURRENT_SOURCE_DIR}/include/${parent}/${name}" PUBLIC + isolate_headers( + ${target} + "${CMAKE_CURRENT_SOURCE_DIR}/include" + "${CMAKE_CURRENT_SOURCE_DIR}/include/${parent}/${name}" + PUBLIC ) - isolate_headers(${target} "${CMAKE_CURRENT_SOURCE_DIR}/src" - "${CMAKE_CURRENT_SOURCE_DIR}/src/lib${parent}/${name}" PRIVATE + isolate_headers( + ${target} + "${CMAKE_CURRENT_SOURCE_DIR}/src" + "${CMAKE_CURRENT_SOURCE_DIR}/src/lib${parent}/${name}" + PRIVATE ) endfunction() From 9e0d350fca688cfa6adae8ee0b16ec8ed8653f69 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Mar 2026 15:27:03 +0100 Subject: [PATCH 36/41] ci: [DEPENDABOT] bump tj-actions/changed-files from 47.0.4 to 47.0.5 (#6501) --- .github/workflows/on-pr.yml | 2 +- .github/workflows/reusable-clang-tidy.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/on-pr.yml b/.github/workflows/on-pr.yml index be6a92eea2..b0010fe0ba 100644 --- a/.github/workflows/on-pr.yml +++ b/.github/workflows/on-pr.yml @@ -46,7 +46,7 @@ jobs: # that Github considers any skipped jobs to have passed, and in # turn the required checks as well. id: changes - uses: tj-actions/changed-files@7dee1b0c1557f278e5c7dc244927139d78c0e22a # v47.0.4 + uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5 with: files: | # These paths are unique to `on-pr.yml`. diff --git a/.github/workflows/reusable-clang-tidy.yml b/.github/workflows/reusable-clang-tidy.yml index 7050d3509f..5319c1627a 100644 --- a/.github/workflows/reusable-clang-tidy.yml +++ b/.github/workflows/reusable-clang-tidy.yml @@ -31,7 +31,7 @@ jobs: - name: Get changed C++ files id: changed_files - uses: tj-actions/changed-files@7dee1b0c1557f278e5c7dc244927139d78c0e22a # v47.0.4 + uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5 with: files: | **/*.cpp @@ -41,7 +41,7 @@ jobs: - name: Get changed clang-tidy configuration id: changed_clang_tidy - uses: tj-actions/changed-files@7dee1b0c1557f278e5c7dc244927139d78c0e22a # v47.0.4 + uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5 with: files: | .clang-tidy From 6b301efc8c0e66e29b57491b5c26640cd8b3bb54 Mon Sep 17 00:00:00 2001 From: Alex Kremer Date: Mon, 9 Mar 2026 15:25:52 +0000 Subject: [PATCH 37/41] chore: Enable clang-tidy `bugprone-unused-local-non-trivial-variable` check (#6458) --- .clang-tidy | 4 ++-- src/test/app/Manifest_test.cpp | 1 - src/test/rpc/LedgerEntry_test.cpp | 12 ++++-------- src/test/rpc/Peers_test.cpp | 4 ++-- src/xrpld/app/consensus/RCLConsensus.cpp | 2 -- 5 files changed, 8 insertions(+), 15 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index 26c7995631..8eefa5fe37 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -60,6 +60,7 @@ Checks: "-*, bugprone-unhandled-exception-at-new, bugprone-unique-ptr-array-mismatch, bugprone-unsafe-functions, + bugprone-unused-local-non-trivial-variable, bugprone-virtual-near-miss, cppcoreguidelines-no-suspend-with-lock, cppcoreguidelines-virtual-class-destructor, @@ -85,13 +86,12 @@ Checks: "-*, performance-trivially-destructible " # --- -# more checks that have some issues that need to be resolved: +# checks that have some issues that need to be resolved: # # bugprone-crtp-constructor-accessibility, # bugprone-inc-dec-in-conditions, # bugprone-reserved-identifier, # bugprone-move-forwarding-reference, -# bugprone-unused-local-non-trivial-variable, # bugprone-switch-missing-default-case, # bugprone-suspicious-stringview-data-usage, # bugprone-suspicious-missing-comma, diff --git a/src/test/app/Manifest_test.cpp b/src/test/app/Manifest_test.cpp index 294d5210d9..dfa67e350f 100644 --- a/src/test/app/Manifest_test.cpp +++ b/src/test/app/Manifest_test.cpp @@ -248,7 +248,6 @@ public: // save should store all trusted master keys to db std::vector s1; std::vector keys; - std::string cfgManifest; for (auto const& man : inManifests) s1.push_back(toBase58(TokenType::NodePublic, man->masterKey)); unl->load({}, s1, keys); diff --git a/src/test/rpc/LedgerEntry_test.cpp b/src/test/rpc/LedgerEntry_test.cpp index 9d274b3549..ffc4329109 100644 --- a/src/test/rpc/LedgerEntry_test.cpp +++ b/src/test/rpc/LedgerEntry_test.cpp @@ -2583,13 +2583,10 @@ class LedgerEntry_test : public beast::unit_test::suite env(check::create(env.master, alice, XRP(100))); env.close(); - std::string const ledgerHash{to_string(env.closed()->header().hash)}; - { - // Request a check. - Json::Value const jrr = env.rpc("ledger_entry", to_string(checkId.key))[jss::result]; - BEAST_EXPECT(jrr[jss::node][sfLedgerEntryType.jsonName] == jss::Check); - BEAST_EXPECT(jrr[jss::node][sfSendMax.jsonName] == "100000000"); - } + // Request a check. + Json::Value const jrr = env.rpc("ledger_entry", to_string(checkId.key))[jss::result]; + BEAST_EXPECT(jrr[jss::node][sfLedgerEntryType.jsonName] == jss::Check); + BEAST_EXPECT(jrr[jss::node][sfSendMax.jsonName] == "100000000"); } public: @@ -2753,7 +2750,6 @@ class LedgerEntry_XChain_test : public beast::unit_test::suite, scEnv(xchain_create_claim_id(scBob, jvb, reward, mcBob)); scEnv.close(); - std::string bridge_index; { // request the xchain_claim_id via RPC Json::Value jvParams; diff --git a/src/test/rpc/Peers_test.cpp b/src/test/rpc/Peers_test.cpp index 8e4e560f3e..d72ad6d878 100644 --- a/src/test/rpc/Peers_test.cpp +++ b/src/test/rpc/Peers_test.cpp @@ -53,8 +53,8 @@ class Peers_test : public beast::unit_test::suite continue; if (!BEAST_EXPECT((*it).isMember(jss::tag))) continue; - auto tag = (*it)[jss::tag].asString(); - BEAST_EXPECTS((*it)[jss::tag].asString() == nodes[key], key); + auto const tag = (*it)[jss::tag].asString(); + BEAST_EXPECTS(tag == nodes[key], key); } BEAST_EXPECT(peers.isMember(jss::peers) && peers[jss::peers].isNull()); } diff --git a/src/xrpld/app/consensus/RCLConsensus.cpp b/src/xrpld/app/consensus/RCLConsensus.cpp index 98dccc0089..91f24a650c 100644 --- a/src/xrpld/app/consensus/RCLConsensus.cpp +++ b/src/xrpld/app/consensus/RCLConsensus.cpp @@ -78,8 +78,6 @@ RCLConsensus::Adaptor::Adaptor( if (validatorKeys_.nodeID != beast::zero && validatorKeys_.keys) { - std::stringstream ss; - JLOG(j_.info()) << "Validator identity: " << toBase58(TokenType::NodePublic, validatorKeys_.keys->masterPublicKey); From 1ee0567b140bf77a229c7699aa86d9c419f64d0f Mon Sep 17 00:00:00 2001 From: Alex Kremer Date: Mon, 9 Mar 2026 15:48:38 +0000 Subject: [PATCH 38/41] chore: Enable clang-tidy `bugprone-suspicious-missing-comma` check (#6468) --- .clang-tidy | 2 +- src/test/app/Invariants_test.cpp | 13 ++++++----- src/test/app/Manifest_test.cpp | 27 ++++++++-------------- src/test/app/ValidatorKeys_test.cpp | 19 +++++++--------- src/test/rpc/DepositAuthorized_test.cpp | 28 +++++++++-------------- src/test/rpc/ValidatorInfo_test.cpp | 30 ++++++++----------------- 6 files changed, 44 insertions(+), 75 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index 8eefa5fe37..42cad608c4 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -49,6 +49,7 @@ Checks: "-*, bugprone-suspicious-include, bugprone-suspicious-memory-comparison, bugprone-suspicious-memset-usage, + bugprone-suspicious-missing-comma, bugprone-suspicious-realloc-usage, bugprone-suspicious-semicolon, bugprone-suspicious-string-compare, @@ -94,7 +95,6 @@ Checks: "-*, # bugprone-move-forwarding-reference, # bugprone-switch-missing-default-case, # bugprone-suspicious-stringview-data-usage, -# bugprone-suspicious-missing-comma, # bugprone-pointer-arithmetic-on-polymorphic-object, # bugprone-optional-value-conversion, # bugprone-too-small-loop-variable, diff --git a/src/test/app/Invariants_test.cpp b/src/test/app/Invariants_test.cpp index a01026c8ef..6a010a7e82 100644 --- a/src/test/app/Invariants_test.cpp +++ b/src/test/app/Invariants_test.cpp @@ -3499,12 +3499,13 @@ class Invariants_test : public beast::unit_test::suite precloseXrp); doInvariantCheck( - {"withdrawal must change vault and destination balance by " - "equal amount", - "withdrawal must decrease vault balance", - "withdrawal must increase destination balance", - "withdrawal and assets outstanding must add up", - "withdrawal and assets available must add up"}, + { + "withdrawal must change vault and destination balance by equal amount", + "withdrawal must decrease vault balance", + "withdrawal must increase destination balance", + "withdrawal and assets outstanding must add up", + "withdrawal and assets available must add up", + }, [&](Account const& A1, Account const& A2, ApplyContext& ac) { auto const keylet = keylet::vault(A1.id(), ac.view().seq()); diff --git a/src/test/app/Manifest_test.cpp b/src/test/app/Manifest_test.cpp index dfa67e350f..d9ecf89b26 100644 --- a/src/test/app/Manifest_test.cpp +++ b/src/test/app/Manifest_test.cpp @@ -423,24 +423,15 @@ public: // Format token string to test trim() std::vector const tokenBlob = { - " " - "eyJ2YWxpZGF0aW9uX3NlY3JldF9rZXkiOiI5ZWQ0NWY4NjYyNDFjYzE4YTI3ND" - "diNT\n", - " \tQzODdjMDYyNTkwNzk3MmY0ZTcxOTAyMzFmYWE5Mzc0NTdmYTlkYWY2Iiwib" - "WFuaWZl \n", - "\tc3QiOiJKQUFBQUFGeEllMUZ0d21pbXZHdEgyaUNjTUpxQzlnVkZLaWxHZncx" - "L3ZDeE\n", - "\t " - "hYWExwbGMyR25NaEFrRTFhZ3FYeEJ3RHdEYklENk9NU1l1TTBGREFscEFnTms4" - "U0tG\t \t\n", - "bjdNTzJmZGtjd1JRSWhBT25ndTlzQUtxWFlvdUorbDJWMFcrc0FPa1ZCK1pSUz" - "ZQU2\n", - "hsSkFmVXNYZkFpQnNWSkdlc2FhZE9KYy9hQVpva1MxdnltR21WcmxIUEtXWDNZ" - "eXd1\n", - "NmluOEhBU1FLUHVnQkQ2N2tNYVJGR3ZtcEFUSGxHS0pkdkRGbFdQWXk1QXFEZW" - "RGdj\n", - "VUSmEydzBpMjFlcTNNWXl3TFZKWm5GT3I3QzBrdzJBaVR6U0NqSXpkaXRROD0i" - "fQ==\n"}; + " eyJ2YWxpZGF0aW9uX3NlY3JldF9rZXkiOiI5ZWQ0NWY4NjYyNDFjYzE4YTI3NDdiNT\n", + " \tQzODdjMDYyNTkwNzk3MmY0ZTcxOTAyMzFmYWE5Mzc0NTdmYTlkYWY2IiwibWFuaWZl \n", + "\tc3QiOiJKQUFBQUFGeEllMUZ0d21pbXZHdEgyaUNjTUpxQzlnVkZLaWxHZncxL3ZDeE\n", + "\t hYWExwbGMyR25NaEFrRTFhZ3FYeEJ3RHdEYklENk9NU1l1TTBGREFscEFnTms4U0tG\t \t\n", + "bjdNTzJmZGtjd1JRSWhBT25ndTlzQUtxWFlvdUorbDJWMFcrc0FPa1ZCK1pSUzZQU2\n", + "hsSkFmVXNYZkFpQnNWSkdlc2FhZE9KYy9hQVpva1MxdnltR21WcmxIUEtXWDNZeXd1\n", + "NmluOEhBU1FLUHVnQkQ2N2tNYVJGR3ZtcEFUSGxHS0pkdkRGbFdQWXk1QXFEZWRGdj\n", + "VUSmEydzBpMjFlcTNNWXl3TFZKWm5GT3I3QzBrdzJBaVR6U0NqSXpkaXRROD0ifQ==\n", + }; auto const manifest = "JAAAAAFxIe1FtwmimvGtH2iCcMJqC9gVFKilGfw1/" diff --git a/src/test/app/ValidatorKeys_test.cpp b/src/test/app/ValidatorKeys_test.cpp index 32f6305a37..925c02131b 100644 --- a/src/test/app/ValidatorKeys_test.cpp +++ b/src/test/app/ValidatorKeys_test.cpp @@ -22,19 +22,15 @@ class ValidatorKeys_test : public beast::unit_test::suite std::string const tokenSecretStr = "paQmjZ37pKKPMrgadBLsuf9ab7Y7EUNzh27LQrZqoexpAs31nJi"; std::vector const tokenBlob = { - " " - "eyJ2YWxpZGF0aW9uX3NlY3JldF9rZXkiOiI5ZWQ0NWY4NjYyNDFjYzE4YTI3NDdiNT\n", - " \tQzODdjMDYyNTkwNzk3MmY0ZTcxOTAyMzFmYWE5Mzc0NTdmYTlkYWY2IiwibWFuaWZl " - " \n", - "\tc3QiOiJKQUFBQUFGeEllMUZ0d21pbXZHdEgyaUNjTUpxQzlnVkZLaWxHZncxL3ZDeE" - "\n", - "\t " - "hYWExwbGMyR25NaEFrRTFhZ3FYeEJ3RHdEYklENk9NU1l1TTBGREFscEFnTms4U0tG\t " - "\t\n", + " eyJ2YWxpZGF0aW9uX3NlY3JldF9rZXkiOiI5ZWQ0NWY4NjYyNDFjYzE4YTI3NDdiNT\n", + " \tQzODdjMDYyNTkwNzk3MmY0ZTcxOTAyMzFmYWE5Mzc0NTdmYTlkYWY2IiwibWFuaWZl \n", + "\tc3QiOiJKQUFBQUFGeEllMUZ0d21pbXZHdEgyaUNjTUpxQzlnVkZLaWxHZncxL3ZDeE\n", + "\t hYWExwbGMyR25NaEFrRTFhZ3FYeEJ3RHdEYklENk9NU1l1TTBGREFscEFnTms4U0tG\t \t\n", "bjdNTzJmZGtjd1JRSWhBT25ndTlzQUtxWFlvdUorbDJWMFcrc0FPa1ZCK1pSUzZQU2\n", "hsSkFmVXNYZkFpQnNWSkdlc2FhZE9KYy9hQVpva1MxdnltR21WcmxIUEtXWDNZeXd1\n", "NmluOEhBU1FLUHVnQkQ2N2tNYVJGR3ZtcEFUSGxHS0pkdkRGbFdQWXk1QXFEZWRGdj\n", - "VUSmEydzBpMjFlcTNNWXl3TFZKWm5GT3I3QzBrdzJBaVR6U0NqSXpkaXRROD0ifQ==\n"}; + "VUSmEydzBpMjFlcTNNWXl3TFZKWm5GT3I3QzBrdzJBaVR6U0NqSXpkaXRROD0ifQ==\n", + }; std::string const tokenManifest = "JAAAAAFxIe1FtwmimvGtH2iCcMJqC9gVFKilGfw1/vCxHXXLplc2GnMhAkE1agqXxBwD" @@ -52,7 +48,8 @@ class ValidatorKeys_test : public beast::unit_test::suite "NWF6dTJMVHlqL1pjQkpBbitmNGhtQTQ0U0tYbGtTTUFqak1rSWRyR1Rxa21SNjBzVG\n", "JaTjZOOUYwdk9UV3VYcUZ6eDFoSGIyL0RqWElVZXhDVGlITEcxTG9UdUp1eXdXbk55\n", "RFE9PSIsInZhbGlkYXRpb25fc2VjcmV0X2tleSI6IjkyRDhCNDBGMzYwMTc5MTkwMU\n", - "MzQTUzMzI3NzBDMkUwMTA4MDI0NTZFOEM2QkI0NEQ0N0FFREQ0NzJGMDQ2RkYifQ==\n"}; + "MzQTUzMzI3NzBDMkUwMTA4MDI0NTZFOEM2QkI0NEQ0N0FFREQ0NzJGMDQ2RkYifQ==\n", + }; public: void diff --git a/src/test/rpc/DepositAuthorized_test.cpp b/src/test/rpc/DepositAuthorized_test.cpp index 755609439c..9d6de94ccc 100644 --- a/src/test/rpc/DepositAuthorized_test.cpp +++ b/src/test/rpc/DepositAuthorized_test.cpp @@ -398,24 +398,16 @@ public: { static std::vector const credIds = { - "18004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288B" - "E4", - "28004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288B" - "E4", - "38004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288B" - "E4", - "48004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288B" - "E4", - "58004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288B" - "E4", - "68004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288B" - "E4", - "78004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288B" - "E4", - "88004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288B" - "E4", - "98004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288B" - "E4"}; + "18004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288BE4", + "28004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288BE4", + "38004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288BE4", + "48004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288BE4", + "58004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288BE4", + "68004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288BE4", + "78004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288BE4", + "88004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288BE4", + "98004829F915654A81B11C4AB8218D96FED67F209B58328A72314FB6EA288BE4", + }; assert(credIds.size() > maxCredentialsArraySize); testcase("deposit_authorized too long credentials"); diff --git a/src/test/rpc/ValidatorInfo_test.cpp b/src/test/rpc/ValidatorInfo_test.cpp index 41fa95940b..d4769f40fb 100644 --- a/src/test/rpc/ValidatorInfo_test.cpp +++ b/src/test/rpc/ValidatorInfo_test.cpp @@ -46,27 +46,15 @@ public: using namespace jtx; std::vector const tokenBlob = { - " " - "eyJ2YWxpZGF0aW9uX3NlY3JldF9rZXkiOiI5ZWQ0NWY4NjYyNDFjYzE4YTI3NDdiNT" - "\n", - " \tQzODdjMDYyNTkwNzk3MmY0ZTcxOTAyMzFmYWE5Mzc0NTdmYTlkYWY2IiwibWFua" - "WZl " - " \n", - "\tc3QiOiJKQUFBQUFGeEllMUZ0d21pbXZHdEgyaUNjTUpxQzlnVkZLaWxHZncxL3ZD" - "eE" - "\n", - "\t " - "hYWExwbGMyR25NaEFrRTFhZ3FYeEJ3RHdEYklENk9NU1l1TTBGREFscEFnTms4U0tG" - "\t " - "\t\n", - "bjdNTzJmZGtjd1JRSWhBT25ndTlzQUtxWFlvdUorbDJWMFcrc0FPa1ZCK1pSUzZQU2" - "\n", - "hsSkFmVXNYZkFpQnNWSkdlc2FhZE9KYy9hQVpva1MxdnltR21WcmxIUEtXWDNZeXd1" - "\n", - "NmluOEhBU1FLUHVnQkQ2N2tNYVJGR3ZtcEFUSGxHS0pkdkRGbFdQWXk1QXFEZWRGdj" - "\n", - "VUSmEydzBpMjFlcTNNWXl3TFZKWm5GT3I3QzBrdzJBaVR6U0NqSXpkaXRROD0ifQ==" - "\n"}; + " eyJ2YWxpZGF0aW9uX3NlY3JldF9rZXkiOiI5ZWQ0NWY4NjYyNDFjYzE4YTI3NDdiNT\n", + " \tQzODdjMDYyNTkwNzk3MmY0ZTcxOTAyMzFmYWE5Mzc0NTdmYTlkYWY2IiwibWFuaWZl \n", + "\tc3QiOiJKQUFBQUFGeEllMUZ0d21pbXZHdEgyaUNjTUpxQzlnVkZLaWxHZncxL3ZDeE\n", + "\t hYWExwbGMyR25NaEFrRTFhZ3FYeEJ3RHdEYklENk9NU1l1TTBGREFscEFnTms4U0tG\t \t\n", + "bjdNTzJmZGtjd1JRSWhBT25ndTlzQUtxWFlvdUorbDJWMFcrc0FPa1ZCK1pSUzZQU2\n", + "hsSkFmVXNYZkFpQnNWSkdlc2FhZE9KYy9hQVpva1MxdnltR21WcmxIUEtXWDNZeXd1\n", + "NmluOEhBU1FLUHVnQkQ2N2tNYVJGR3ZtcEFUSGxHS0pkdkRGbFdQWXk1QXFEZWRGdj\n", + "VUSmEydzBpMjFlcTNNWXl3TFZKWm5GT3I3QzBrdzJBaVR6U0NqSXpkaXRROD0ifQ==\n", + }; std::string const master_key = "nHBt9fsb4849WmZiCds4r5TXyBeQjqnH5kzPtqgMAQMgi39YZRPa"; std::string const ephemeral_key = "n9KsDYGKhABVc4wK5u3MnVhgPinyJimyKGpr9VJYuBaY8EnJXR2x"; From e2290b1a0af2d90ca58b67c7d8b38cd8db1c2879 Mon Sep 17 00:00:00 2001 From: Sergey Kuznetsov Date: Mon, 9 Mar 2026 16:33:20 +0000 Subject: [PATCH 39/41] feat: Add mutex wrapper from clio (#6447) This change adds a mutex wrapper copied from clio. The wrapper attaches a mutex to the data it protects, which improves safety and readability. --- include/xrpl/basics/Mutex.hpp | 155 ++++++++++++++++ src/tests/libxrpl/basics/Mutex.cpp | 288 +++++++++++++++++++++++++++++ 2 files changed, 443 insertions(+) create mode 100644 include/xrpl/basics/Mutex.hpp create mode 100644 src/tests/libxrpl/basics/Mutex.cpp diff --git a/include/xrpl/basics/Mutex.hpp b/include/xrpl/basics/Mutex.hpp new file mode 100644 index 0000000000..18c57370b1 --- /dev/null +++ b/include/xrpl/basics/Mutex.hpp @@ -0,0 +1,155 @@ +/* + This file is part of clio: https://github.com/XRPLF/clio + Copyright (c) 2024, the clio developers. + + Permission to use, copy, modify, and distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. +*/ + +#pragma once + +#include +#include + +namespace xrpl { + +template +class Mutex; + +/** + * @brief A lock on a mutex that provides access to the protected data. + * + * @tparam ProtectedDataType data type to hold + * @tparam LockType type of lock + * @tparam MutexType type of mutex + */ +template typename LockType, typename MutexType> +class Lock +{ + LockType lock_; + ProtectedDataType& data_; + +public: + /** @cond */ + ProtectedDataType const& + operator*() const + { + return data_; + } + + ProtectedDataType& + operator*() + { + return data_; + } + + ProtectedDataType const& + get() const + { + return data_; + } + + ProtectedDataType& + get() + { + return data_; + } + + ProtectedDataType const* + operator->() const + { + return &data_; + } + + ProtectedDataType* + operator->() + { + return &data_; + } + + operator LockType&() & + { + return lock_; + } + + operator LockType const&() const& + { + return lock_; + } + /** @endcond */ + +private: + friend class Mutex, MutexType>; + + Lock(MutexType& mutex, ProtectedDataType& data) : lock_(mutex), data_(data) + { + } +}; + +/** + * @brief A container for data that is protected by a mutex. Inspired by Mutex in Rust. + * + * @tparam ProtectedDataType data type to hold + * @tparam MutexType type of mutex + */ +template +class Mutex +{ + mutable MutexType mutex_; + ProtectedDataType data_{}; + +public: + Mutex() = default; + + /** + * @brief Construct a new Mutex object with the given data + * + * @param data The data to protect + */ + explicit Mutex(ProtectedDataType data) : data_(std::move(data)) + { + } + + /** + * @brief Make a new Mutex object with the given data + * + * @tparam Args The types of the arguments to forward to the constructor of the protected data + * @param args The arguments to forward to the constructor of the protected data + * @return The Mutex object that protects the given data + */ + template + static Mutex + make(Args&&... args) + { + return Mutex{ProtectedDataType{std::forward(args)...}}; + } + + /** + * @brief Lock the mutex and get a lock object allowing access to the protected data + * + * @tparam LockType The type of lock to use + * @return A lock on the mutex and a reference to the protected data + */ + template