Compare commits

...

30 Commits

Author SHA1 Message Date
Michael Legleux
b7f16c341c feat: Build packages in GitHub 2026-03-05 11:29:07 -08:00
Alex Kremer
dde450784d 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".
2026-03-05 16:11:27 +00:00
Michael Legleux
08e734457f fix: Fix docs deployment for pull requests (#6482) 2026-03-05 00:12:41 -08:00
Michael Legleux
77518394e8 fix: Stop committing generated docs to prevent repo bloat (#6474) 2026-03-04 19:19:57 -08:00
Ayaz Salikhov
c69091bded 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.
2026-03-04 19:45:28 +00:00
Alex Kremer
595f0dd461 chore: Enable clang-tidy bugprone-sizeof-expression check (#6466) 2026-03-04 19:15:22 +00:00
Alex Kremer
b451d5e412 chore: Enable clang-tidy bugprone-return-const-ref-from-parameter check (#6459) 2026-03-04 18:10:10 +00:00
Alex Kremer
af97df5a63 chore: Enable clang-tidy bugprone-move-forwarding-reference check (#6457) 2026-03-04 17:03:27 +00:00
Peter Chen
e39954d128 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.
2026-03-04 15:50:51 +00:00
tequ
3cd1e3d94e refactor: Update PermissionedDomainDelete to use keylet for sle access (#6063) 2026-03-04 04:11:58 +01:00
Ayaz Salikhov
fcec31ed20 chore: Update pre-commit hooks (#6460) 2026-03-03 20:23:22 +00:00
dependabot[bot]
0abd762781 ci: [DEPENDABOT] bump actions/upload-artifact from 6.0.0 to 7.0.0 (#6450) 2026-03-03 17:17:08 +00:00
Sergey Kuznetsov
5300e65686 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.
2026-03-03 08:46:55 -05:00
Alex Kremer
afc660a1b5 refactor: Fix clang-tidy bugprone-empty-catch check (#6419)
This change fixes or suppresses instances detected by the `bugprone-empty-catch` clang-tidy check.
2026-03-02 17:08:56 +00:00
Vito Tumas
1a7f824b89 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.
2026-02-27 21:02:39 +00:00
Sergey Kuznetsov
b58c681189 chore: Make nix hook optional (#6431)
This change makes the `nix` pre-commit hook optional in development environments, and enforced only inside Github Actions.
2026-02-27 13:36:10 -05:00
Mayukha Vadari
404f35d556 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.
2026-02-27 03:01:38 +00:00
Alex Kremer
2e595b6031 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.
2026-02-26 18:26:58 +00:00
Bart
3a8a18c2ca 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()`.
2026-02-25 18:23:34 -05:00
Ayaz Salikhov
65e63ebef3 chore: Update cleanup-workspace to delete old .conan2 dir on macOS (#6412) 2026-02-25 01:12:16 +00:00
Valentin Balaschenko
bdd106d992 Explicitly trim the heap after cache sweeps (#6022)
Limited to Linux/glibc builds.
2026-02-24 21:33:13 +00:00
Valentin Balaschenko
24cbaf76a5 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.
2026-02-24 12:52:32 -05:00
Valentin Balaschenko
3a805cc646 Disable featureBatch and fixBatchInnerSigs amendments (#6402) 2026-02-24 12:49:59 -05:00
Sergey Kuznetsov
0fd237d707 chore: Add nix development environment (#6314)
---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-23 20:10:07 -05:00
dependabot[bot]
3542daa4cc 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](ea165f8d65...b7c566a772)

---
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] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-20 22:48:01 +00:00
dependabot[bot]
fd9f57ec97 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] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-20 22:09:48 +00:00
dependabot[bot]
625becff18 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](a26af69be9...a309ff8b42)

---
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] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-20 21:29:05 +00:00
dependabot[bot]
4bcbc6e50f 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](ed68ef82c0...7dee1b0c15)

---
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] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-20 19:59:37 +00:00
dependabot[bot]
0bc4a0cfe8 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](18283e04ce...671740ac38)

---
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] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-20 19:11:26 +00:00
Ayaz Salikhov
cb54adefed ci: Build docs in PRs and in private repos (#6400) 2026-02-20 13:41:43 -05:00
142 changed files with 7180 additions and 5134 deletions

View File

@@ -1,105 +1,144 @@
---
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-empty-catch,
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-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-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,
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-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,
# ---
# more checks that have some issues that need to be resolved:
#
# bugprone-crtp-constructor-accessibility,
# bugprone-dangling-handle,
# bugprone-dynamic-static-initializers,
# bugprone-empty-catch,
# bugprone-fold-init-type,
# bugprone-forward-declaration-namespace,
# bugprone-inaccurate-erase,
# 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-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,
# 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 +150,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'
#

View File

@@ -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
@@ -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`.

View File

@@ -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-ab4d1f0" }'
container: '{ "image": "ghcr.io/xrplf/ci/tools-rippled-pre-commit:sha-41ec7c1" }'

View File

@@ -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,17 +35,22 @@ 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:
build:
runs-on: ubuntu-latest
container: ghcr.io/xrplf/ci/tools-rippled-documentation:sha-a8c7be1
permissions:
contents: write
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
with:
enable_ccache: false
- name: Get number of processors
uses: XRPLF/actions/get-nproc@cf0433aa74563aead044a1e395610c96d65a37cf
@@ -64,9 +81,23 @@ jobs:
cmake -Donly_docs=ON ..
cmake --build . --target docs --parallel ${BUILD_NPROC}
- name: Publish documentation
if: ${{ github.ref_type == 'branch' && github.ref_name == github.event.repository.default_branch }}
uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e # v4.0.0
- name: Create documentation artifact
if: ${{ github.event_name == 'push' }}
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
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
uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4.0.5

View File

@@ -101,10 +101,10 @@ 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@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Prepare runner
uses: XRPLF/actions/prepare-runner@2cbf481018d930656e9276fcc20dc0e3a0be5b6d
@@ -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@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: xrpld-${{ inputs.config_name }}
path: ${{ env.BUILD_DIR }}/xrpld
@@ -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: |
@@ -254,7 +267,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

View File

@@ -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

View File

@@ -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

View File

@@ -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
@@ -78,13 +78,13 @@ 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'
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: clang-tidy-results
path: clang-tidy-output.txt

View File

@@ -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
@@ -30,7 +31,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
@@ -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 }}

View File

@@ -29,10 +29,10 @@ 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@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: 3.13

View File

@@ -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

View File

@@ -64,10 +64,10 @@ 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@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Prepare runner
uses: XRPLF/actions/prepare-runner@2cbf481018d930656e9276fcc20dc0e3a0be5b6d

6
.gitignore vendored
View File

@@ -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

View File

@@ -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
@@ -57,6 +57,24 @@ repos:
- .git/COMMIT_EDITMSG
stages: [commit-msg]
- repo: local
hooks:
- id: nix-fmt
name: Format Nix files
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
pass_filenames: true
exclude: |
(?x)^(
external/.*|

View File

@@ -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.

View File

@@ -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)

View File

@@ -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,

View File

@@ -0,0 +1,21 @@
Source: rippled
Section: net
Priority: optional
Maintainer: Michael Legleux <mlegleux@ripple.com>
Rules-Requires-Root: no
Build-Depends:
debhelper-compat (= 13),
Standards-Version: 4.7.0
Homepage: https://github.com/XRPLF/rippled
Vcs-Git: https://github.com/XRPLF/rippled.git
Vcs-Browser: https://github.com/XRPLF/rippled
Package: rippled
Section: net
Priority: optional
Architecture: any
Depends:
${shlibs:Depends},
${misc:Depends}
Description: XRP Ledger server daemon
XRPL server daemon providing ledger validation and p2p network services.

View File

@@ -0,0 +1,11 @@
opt/ripple/bin/rippled
opt/ripple/etc/rippled.cfg
opt/ripple/etc/validators.txt
# systemd unit (if you keep it in debian/ as a source file, do NOT list it here)
# If instead you stage it into debian/tmp, uncomment:
# lib/systemd/system/rippled.service
usr/share/doc/rippled/README.md
usr/share/doc/rippled/LICENSE.md

View File

@@ -0,0 +1,8 @@
/opt/ripple/bin/rippled /usr/bin/rippled
/opt/ripple/etc/rippled.cfg /etc/opt/ripple/xrpld.cfg
/opt/ripple/etc/validators.txt /etc/opt/ripple/validators.txt
# TODO: Remove when rippled deprecated
/opt/ripple/bin/rippled /opt/ripple/bin/xrpld
/opt/ripple/etc/rippled.cfg /etc/opt/ripple/xrpld.cfg
/opt/ripple /opt/xrpld

View File

@@ -0,0 +1,15 @@
/var/log/rippled/*.log {
daily
minsize 200M
rotate 7
nocreate
missingok
notifempty
compress
compresscmd /usr/bin/nice
compressoptions -n19 ionice -c3 gzip
compressext .gz
postrotate
/opt/ripple/bin/rippled --conf /opt/ripple/etc/rippled.cfg logrotate
endscript
}

View File

@@ -0,0 +1,40 @@
[Unit]
Description=XRPL daemon
After=network-online.target
Wants=network-online.target
[Service]
Type=notify
User=rippled
Group=rippled
# Canonical config location (as you described)
ExecStart=/opt/ripple/bin/rippled --conf /etc/opt/ripple/rippled.cfg
# Reasonable hardening defaults (trim if they break your runtime)
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ProtectControlGroups=true
ProtectKernelTunables=true
ProtectKernelModules=true
LockPersonality=true
RestrictRealtime=true
RestrictSUIDSGID=true
MemoryDenyWriteExecute=true
SystemCallArchitectures=native
# Allow writes only where you actually need them
ReadWritePaths=/var/lib/rippled /var/log/rippled
StateDirectory=rippled
LogsDirectory=rippled
Restart=on-failure
RestartSec=2s
TimeoutStopSec=30s
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,5 @@
u rippled - "XRPL Daemon" /var/lib/ripled
# Type Name ID GECOS Home Shell
g rippled - - - -
u rippled - "XRPL rippled" /var/lib/rippled /usr/sbin/nologin

View File

@@ -0,0 +1,3 @@
# StateDirectory/LogsDirectory/RuntimeDirectory in systemd service makes this redundant but this enables apt purge.
d /var/lib/rippled 0750 rippled rippled -
d /var/log/rippled 0750 rippled rippled -

42
ci/packaging/debian/rules Normal file
View File

@@ -0,0 +1,42 @@
#!/usr/bin/make -f
export DH_VERBOSE = 1
%:
dh $@
override_dh_auto_configure override_dh_auto_build override_dh_auto_test:
@:
override_dh_auto_install:
rm -rf debian/tmp
mkdir -p debian/tmp/opt/ripple/bin
mkdir -p debian/tmp/opt/ripple/etc
mkdir -p debian/tmp/usr
cp -a "$(INSTALL_TREE)/bin" debian/tmp/opt/ripple
cp -a "$(INSTALL_TREE)/etc" debian/tmp/opt/ripple
cp -a "$(INSTALL_TREE)/usr" debian/tmp
rm -rf debian/tmp/usr/include
rm -rf debian/tmp/usr/lib
install -Dm0644 README.md debian/tmp/usr/share/doc/rippled/README.md
install -Dm0644 LICENSE.md debian/tmp/usr/share/doc/rippled/LICENSE.md
install -Dm0644 cfg/rippled-example.cfg debian/tmp/opt/ripple/etc/rippled.cfg
install -Dm0644 cfg/validators-example.txt debian/tmp/opt/ripple/etc/validators.txt
override_dh_installsystemd:
dh_installsystemd
override_dh_installsysusers:
dh_installsysusers
override_dh_installtmpfiles:
dh_installtmpfiles
override_dh_install:
dh_install
override_dh_dwz:
@:
override_dh_strip:
dh_strip --no-automatic-dbgsym

21
cmake/GitInfo.cmake Normal file
View File

@@ -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}")

View File

@@ -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

View File

@@ -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

View File

@@ -173,6 +173,11 @@ words:
- nftokens
- nftpage
- nikb
- nixfmt
- nixos
- nixpkgs
- NOLINT
- NOLINTNEXTLINE
- nonxrp
- noripple
- nudb

View File

@@ -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

95
docs/build/nix.md vendored Normal file
View File

@@ -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.

26
flake.lock generated Normal file
View File

@@ -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
}

16
flake.nix Normal file
View File

@@ -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);
};
}

View File

@@ -0,0 +1,73 @@
#pragma once
#include <xrpl/beast/utility/Journal.h>
#include <chrono>
#include <cstdint>
#include <string_view>
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

View File

@@ -1,6 +1,7 @@
#pragma once
#include <string>
#include <string_view>
#include <vector>
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

13
include/xrpl/git/Git.h Normal file
View File

@@ -0,0 +1,13 @@
#pragma once
#include <string>
namespace xrpl::git {
std::string const&
getCommitHash();
std::string const&
getBuildBranch();
} // namespace xrpl::git

View File

@@ -23,13 +23,13 @@ public:
static constexpr size_t initialBufferSize = kilobytes(256);
RawStateTable()
: monotonic_resource_{std::make_unique<boost::container::pmr::monotonic_buffer_resource>(
initialBufferSize)}
: monotonic_resource_{
std::make_unique<boost::container::pmr::monotonic_buffer_resource>(initialBufferSize)}
, items_{monotonic_resource_.get()} {};
RawStateTable(RawStateTable const& rhs)
: monotonic_resource_{std::make_unique<boost::container::pmr::monotonic_buffer_resource>(
initialBufferSize)}
: monotonic_resource_{
std::make_unique<boost::container::pmr::monotonic_buffer_resource>(initialBufferSize)}
, items_{rhs.items_, monotonic_resource_.get()}
, dropsDestroyed_{rhs.dropsDestroyed_} {};

View File

@@ -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<NodeObject>* pObject) = 0;
fetch(uint256 const& hash, std::shared_ptr<NodeObject>* pObject) = 0;
/** Fetch a batch synchronously. */
virtual std::pair<std::vector<std::shared_ptr<NodeObject>>, Status>
fetchBatch(std::vector<uint256 const*> const& hashes) = 0;
fetchBatch(std::vector<uint256> const& hashes) = 0;
/** Store a single object.
Depending on the implementation this may happen immediately

View File

@@ -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

View File

@@ -30,9 +30,11 @@ public:
Item(
char const* name,
KeyType type,
std::initializer_list<SOElement> uniqueFields,
std::initializer_list<SOElement> commonFields)
: soTemplate_(uniqueFields, commonFields), name_(name), type_(type)
std::vector<SOElement> uniqueFields,
std::vector<SOElement> 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<SOElement> uniqueFields,
std::initializer_list<SOElement> commonFields = {})
std::vector<SOElement> uniqueFields,
std::vector<SOElement> 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;

View File

@@ -2,36 +2,34 @@
#include <xrpl/protocol/KnownFormats.h>
namespace xrpl {
#include <map>
#include <string>
#include <vector>
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<std::string, std::uint32_t>;
#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<std::pair<std::string, LedgerFlagMap>> const& getAllLedgerFlags() {
// static std::vector<std::pair<std::string, LedgerFlagMap>> const flags = {
// {"AccountRoot", getAccountRootFlags()},
// ...};
// return flags;
// }
#define ALL_LEDGER_FLAGS(name, values) {#name, get##name##Flags()},
inline std::vector<std::pair<std::string, LedgerFlagMap>> const&
getAllLedgerFlags()
{
static std::vector<std::pair<std::string, LedgerFlagMap>> 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<SOElement> const&
getCommonFields();
};
} // namespace xrpl

View File

@@ -6,6 +6,7 @@
#include <functional>
#include <initializer_list>
#include <stdexcept>
#include <vector>
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<SOElement> uniqueFields, std::vector<SOElement> commonFields = {});
/** Create a template populated with all fields.
Note: Defers to the vector constructor above.
*/
SOTemplate(
std::initializer_list<SOElement> uniqueFields,

View File

@@ -3,294 +3,444 @@
#include <xrpl/protocol/LedgerFormats.h>
#include <cstdint>
#include <map>
#include <string>
#include <utility>
#include <vector>
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<std::string, FlagValue>;
#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<std::pair<std::string, FlagMap>>;
#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<std::string, FlagValue> const&
getAsfFlagMap()
{
static std::map<std::string, FlagValue> 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

View File

@@ -2,6 +2,8 @@
#include <xrpl/protocol/KnownFormats.h>
#include <vector>
namespace xrpl {
/** Transaction type identifiers.
@@ -73,6 +75,9 @@ private:
public:
static TxFormats const&
getInstance();
static std::vector<SOElement> const&
getCommonFields();
};
} // namespace xrpl

View File

@@ -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

View File

@@ -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)

View File

@@ -1,732 +0,0 @@
#pragma once
#include <xrpl/basics/Number.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/protocol/MPTIssue.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <cstdint>
#include <tuple>
#include <unordered_set>
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<SLE const> const& before,
std::shared_ptr<SLE const> 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<SLE const> const&, std::shared_ptr<SLE const> 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<SLE const> const&, std::shared_ptr<SLE const> 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<SLE const> const&, std::shared_ptr<SLE const> 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, after>. 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::pair<std::shared_ptr<SLE const>, std::shared_ptr<SLE const>>> accountsDeleted_;
public:
void
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> 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<SLE const> const&, std::shared_ptr<SLE const> 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<SLE const> const&, std::shared_ptr<SLE const> 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<SLE const> const&, std::shared_ptr<SLE const> 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<SLE const> const&, std::shared_ptr<SLE const> 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<SLE const> const line;
int const balanceChangeSign;
};
struct IssuerChanges
{
std::vector<BalanceChange> senders;
std::vector<BalanceChange> receivers;
};
using ByIssuer = std::map<Issue, IssuerChanges>;
ByIssuer balanceChanges_;
std::map<AccountID, std::shared_ptr<SLE const> const> possibleIssuers_;
public:
void
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
private:
bool
isValidEntry(std::shared_ptr<SLE const> const& before, std::shared_ptr<SLE const> const& after);
STAmount
calculateBalanceChange(
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after,
bool isDelete);
void
recordBalance(Issue const& issue, BalanceChange change);
void
recordBalanceChanges(std::shared_ptr<SLE const> const& after, STAmount const& balanceChange);
std::shared_ptr<SLE const>
findIssuer(AccountID const& issuerID, ReadView const& view);
bool
validateIssuerChanges(
std::shared_ptr<SLE const> 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<SLE const> const&, std::shared_ptr<SLE const> 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<SLE const> const&, std::shared_ptr<SLE const> 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<SLE const> const&, std::shared_ptr<SLE const> 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<SLE const> const&, std::shared_ptr<SLE const> 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<SLE const> const&, std::shared_ptr<SLE const> 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<SLE const> const&, std::shared_ptr<SLE const> 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<SLE const> const&, std::shared_ptr<SLE const> 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> sleStatus_;
public:
void
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> 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<std::string> errors_;
public:
void
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
};
class ValidPermissionedDEX
{
bool regularOffers_ = false;
bool badHybrids_ = false;
hash_set<uint256> domains_;
public:
void
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
};
class ValidAMM
{
std::optional<AccountID> ammAccount_;
std::optional<STAmount> lptAMMBalanceAfter_;
std::optional<STAmount> lptAMMBalanceBefore_;
bool ammPoolChanged_;
public:
enum class ZeroAllowed : bool { No = false, Yes = true };
ValidAMM() : ammPoolChanged_{false}
{
}
void
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> 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 <before, after>.
std::set<std::pair<SLE::const_pointer, SLE::const_pointer>> changedEntries_;
public:
void
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> 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<uint256, BrokerInfo> brokers_;
// Collect all the modified trust lines. Their high and low accounts will be
// loaded to look for LoanBroker pseudo-accounts.
std::vector<SLE::const_pointer> lines_;
// Collect all the modified MPTokens. Their accounts will be loaded to look
// for LoanBroker pseudo-accounts.
std::vector<SLE::const_pointer> mpts_;
bool
goodZeroDirectory(ReadView const& view, SLE::const_ref dir, beast::Journal const& j) const;
public:
void
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> 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 <before, after>. After is used for most of the checks, except
// those that check changed values.
std::vector<std::pair<SLE::const_pointer, SLE::const_pointer>> loans_;
public:
void
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> 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<Vault> afterVault_ = {};
std::vector<Shares> afterMPTs_ = {};
std::vector<Vault> beforeVault_ = {};
std::vector<Shares> beforeMPTs_ = {};
std::unordered_map<uint256, Number> deltas_ = {};
public:
void
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> 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

View File

@@ -0,0 +1,53 @@
#pragma once
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <optional>
namespace xrpl {
class ValidAMM
{
std::optional<AccountID> ammAccount_;
std::optional<STAmount> lptAMMBalanceAfter_;
std::optional<STAmount> lptAMMBalanceBefore_;
bool ammPoolChanged_;
public:
enum class ZeroAllowed : bool { No = false, Yes = true };
ValidAMM() : ammPoolChanged_{false}
{
}
void
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> 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

View File

@@ -0,0 +1,84 @@
#pragma once
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <map>
#include <vector>
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<SLE const> const line;
int const balanceChangeSign;
};
struct IssuerChanges
{
std::vector<BalanceChange> senders;
std::vector<BalanceChange> receivers;
};
using ByIssuer = std::map<Issue, IssuerChanges>;
ByIssuer balanceChanges_;
std::map<AccountID, std::shared_ptr<SLE const> const> possibleIssuers_;
public:
void
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
private:
bool
isValidEntry(std::shared_ptr<SLE const> const& before, std::shared_ptr<SLE const> const& after);
STAmount
calculateBalanceChange(
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after,
bool isDelete);
void
recordBalance(Issue const& issue, BalanceChange change);
void
recordBalanceChanges(std::shared_ptr<SLE const> const& after, STAmount const& balanceChange);
std::shared_ptr<SLE const>
findIssuer(AccountID const& issuerID, ReadView const& view);
bool
validateIssuerChanges(
std::shared_ptr<SLE const> 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

View File

@@ -0,0 +1,385 @@
#pragma once
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/tx/invariants/AMMInvariant.h>
#include <xrpl/tx/invariants/FreezeInvariant.h>
#include <xrpl/tx/invariants/LoanInvariant.h>
#include <xrpl/tx/invariants/MPTInvariant.h>
#include <xrpl/tx/invariants/NFTInvariant.h>
#include <xrpl/tx/invariants/PermissionedDEXInvariant.h>
#include <xrpl/tx/invariants/PermissionedDomainInvariant.h>
#include <xrpl/tx/invariants/VaultInvariant.h>
#include <cstdint>
#include <tuple>
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<SLE const> const& before,
std::shared_ptr<SLE const> 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<SLE const> const&, std::shared_ptr<SLE const> 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<SLE const> const&, std::shared_ptr<SLE const> 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<SLE const> const&, std::shared_ptr<SLE const> 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, after>. 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::pair<std::shared_ptr<SLE const>, std::shared_ptr<SLE const>>> accountsDeleted_;
public:
void
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> 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<SLE const> const&, std::shared_ptr<SLE const> 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<SLE const> const&, std::shared_ptr<SLE const> 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<SLE const> const&, std::shared_ptr<SLE const> 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<SLE const> const&, std::shared_ptr<SLE const> 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<SLE const> const&, std::shared_ptr<SLE const> 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<SLE const> const&, std::shared_ptr<SLE const> 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<SLE const> const&, std::shared_ptr<SLE const> 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<SLE const> const&, std::shared_ptr<SLE const> 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<std::string> errors_;
public:
void
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> 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 <before, after>.
std::set<std::pair<SLE::const_pointer, SLE::const_pointer>> changedEntries_;
public:
void
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> 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

View File

@@ -0,0 +1,60 @@
#pragma once
#include <xrpl/protocol/STTx.h>
#include <type_traits>
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<Privilege>(
safe_cast<std::underlying_type_t<Privilege>>(lhs) |
safe_cast<std::underlying_type_t<Privilege>>(rhs));
}
bool
hasPrivilege(STTx const& tx, Privilege priv);
} // namespace xrpl

View File

@@ -0,0 +1,75 @@
#pragma once
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <map>
#include <vector>
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<uint256, BrokerInfo> brokers_;
// Collect all the modified trust lines. Their high and low accounts will be
// loaded to look for LoanBroker pseudo-accounts.
std::vector<SLE::const_pointer> lines_;
// Collect all the modified MPTokens. Their accounts will be loaded to look
// for LoanBroker pseudo-accounts.
std::vector<SLE::const_pointer> mpts_;
bool
goodZeroDirectory(ReadView const& view, SLE::const_ref dir, beast::Journal const& j) const;
public:
void
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> 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 <before, after>. After is used for most of the checks, except
// those that check changed values.
std::vector<std::pair<SLE::const_pointer, SLE::const_pointer>> loans_;
public:
void
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
};
} // namespace xrpl

View File

@@ -0,0 +1,31 @@
#pragma once
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <cstdint>
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<SLE const> const&, std::shared_ptr<SLE const> const&);
bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
};
} // namespace xrpl

View File

@@ -0,0 +1,70 @@
#pragma once
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <cstdint>
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<SLE const> const&, std::shared_ptr<SLE const> 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<SLE const> const&, std::shared_ptr<SLE const> const&);
bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
};
} // namespace xrpl

View File

@@ -0,0 +1,25 @@
#pragma once
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
namespace xrpl {
class ValidPermissionedDEX
{
bool regularOffers_ = false;
bool badHybrids_ = false;
hash_set<uint256> domains_;
public:
void
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
};
} // namespace xrpl

View File

@@ -0,0 +1,41 @@
#pragma once
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <vector>
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> sleStatus_;
public:
void
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
};
} // namespace xrpl

View File

@@ -0,0 +1,77 @@
#pragma once
#include <xrpl/basics/Number.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/MPTIssue.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <unordered_map>
#include <vector>
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<Vault> afterVault_ = {};
std::vector<Shares> afterMPTs_ = {};
std::vector<Vault> beforeVault_ = {};
std::vector<Shares> beforeMPTs_ = {};
std::unordered_map<uint256, Number> deltas_ = {};
public:
void
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
};
} // namespace xrpl

View File

@@ -13,9 +13,6 @@ public:
{
}
static std::uint32_t
getFlagsMask(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);

View File

@@ -13,9 +13,6 @@ public:
{
}
static std::uint32_t
getFlagsMask(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);

View File

@@ -26,9 +26,6 @@ public:
{
}
static std::uint32_t
getFlagsMask(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);

View File

@@ -13,9 +13,6 @@ public:
{
}
static std::uint32_t
getFlagsMask(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);

140
nix/devshell.nix Normal file
View File

@@ -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"; };
}

19
nix/utils.nix Normal file
View File

@@ -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; };
}
);
}

View File

@@ -0,0 +1,157 @@
#include <xrpl/basics/Log.h>
#include <xrpl/basics/MallocTrim.h>
#include <boost/predef.h>
#include <chrono>
#include <cstdint>
#include <cstdio>
#include <fstream>
#include <sstream>
#if defined(__GLIBC__) && BOOST_OS_LINUX
#include <sys/resource.h>
#include <malloc.h>
#include <unistd.h>
// 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<std::chrono::microseconds>(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<std::int64_t>(rssAfterKB) - static_cast<std::int64_t>(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

View File

@@ -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) {

View File

@@ -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
}

31
src/libxrpl/git/Git.cpp Normal file
View File

@@ -0,0 +1,31 @@
#include "xrpl/git/Git.h"
#include <string>
#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

View File

@@ -72,8 +72,8 @@ OpenView::OpenView(
ReadView const* base,
Rules const& rules,
std::shared_ptr<void const> hold)
: monotonic_resource_{std::make_unique<boost::container::pmr::monotonic_buffer_resource>(
initialBufferSize)}
: monotonic_resource_{
std::make_unique<boost::container::pmr::monotonic_buffer_resource>(initialBufferSize)}
, txs_{monotonic_resource_.get()}
, rules_(rules)
, header_(base->header())
@@ -88,8 +88,8 @@ OpenView::OpenView(
}
OpenView::OpenView(ReadView const* base, std::shared_ptr<void const> hold)
: monotonic_resource_{std::make_unique<boost::container::pmr::monotonic_buffer_resource>(
initialBufferSize)}
: monotonic_resource_{
std::make_unique<boost::container::pmr::monotonic_buffer_resource>(initialBufferSize)}
, txs_{monotonic_resource_.get()}
, rules_(base->rules())
, header_(base->header())

View File

@@ -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<uint256> const& hashes)
using namespace std::chrono;
auto const before = steady_clock::now();
std::vector<uint256 const*> 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");

View File

@@ -105,7 +105,7 @@ DatabaseRotatingImp::fetchNodeObject(
std::shared_ptr<NodeObject> nodeObject;
try
{
status = backend->fetch(hash.data(), &nodeObject);
status = backend->fetch(hash, &nodeObject);
}
catch (std::exception const& e)
{

View File

@@ -116,10 +116,9 @@ public:
//--------------------------------------------------------------------------
Status
fetch(void const* key, std::shared_ptr<NodeObject>* pObject) override
fetch(uint256 const& hash, std::shared_ptr<NodeObject>* 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<std::vector<std::shared_ptr<NodeObject>>, Status>
fetchBatch(std::vector<uint256 const*> const& hashes) override
fetchBatch(std::vector<uint256> const& hashes) override
{
std::vector<std::shared_ptr<NodeObject>> results;
results.reserve(hashes.size());
for (auto const& h : hashes)
{
std::shared_ptr<NodeObject> nObj;
Status status = fetch(h->begin(), &nObj);
Status status = fetch(h, &nObj);
if (status != ok)
results.push_back({});
else

View File

@@ -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.
@@ -179,17 +179,17 @@ public:
}
Status
fetch(void const* key, std::shared_ptr<NodeObject>* pno) override
fetch(uint256 const& hash, std::shared_ptr<NodeObject>* 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<std::vector<std::shared_ptr<NodeObject>>, Status>
fetchBatch(std::vector<uint256 const*> const& hashes) override
fetchBatch(std::vector<uint256> const& hashes) override
{
std::vector<std::shared_ptr<NodeObject>> results;
results.reserve(hashes.size());
for (auto const& h : hashes)
{
std::shared_ptr<NodeObject> nObj;
Status status = fetch(h->begin(), &nObj);
Status status = fetch(h, &nObj);
if (status != ok)
results.push_back({});
else

View File

@@ -36,13 +36,13 @@ public:
}
Status
fetch(void const*, std::shared_ptr<NodeObject>*) override
fetch(uint256 const&, std::shared_ptr<NodeObject>*) override
{
return notFound;
}
std::pair<std::vector<std::shared_ptr<NodeObject>>, Status>
fetchBatch(std::vector<uint256 const*> const& hashes) override
fetchBatch(std::vector<uint256> const& hashes) override
{
return {};
}

View File

@@ -244,7 +244,7 @@ public:
//--------------------------------------------------------------------------
Status
fetch(void const* key, std::shared_ptr<NodeObject>* pObject) override
fetch(uint256 const& hash, std::shared_ptr<NodeObject>* 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<char const*>(key), m_keyBytes);
rocksdb::Slice const slice(std::bit_cast<char const*>(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<std::vector<std::shared_ptr<NodeObject>>, Status>
fetchBatch(std::vector<uint256 const*> const& hashes) override
fetchBatch(std::vector<uint256> const& hashes) override
{
std::vector<std::shared_ptr<NodeObject>> results;
results.reserve(hashes.size());
for (auto const& h : hashes)
{
std::shared_ptr<NodeObject> 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<char const*>(encoded.getKey()), m_keyBytes),
rocksdb::Slice(
reinterpret_cast<char const*>(encoded.getData()), encoded.getSize()));
rocksdb::Slice(std::bit_cast<char const*>(encoded.getKey()), m_keyBytes),
rocksdb::Slice(std::bit_cast<char const*>(encoded.getData()), encoded.getSize()));
}
rocksdb::WriteOptions const options;

View File

@@ -1,6 +1,7 @@
#include <xrpl/basics/contract.h>
#include <xrpl/beast/core/LexicalCast.h>
#include <xrpl/beast/core/SemanticVersion.h>
#include <xrpl/git/Git.h>
#include <xrpl/protocol/BuildInfo.h>
#include <boost/preprocessor/stringize.hpp>
@@ -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<std::uint64_t>(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;
}

View File

@@ -3,19 +3,23 @@
#include <xrpl/protocol/SOTemplate.h>
#include <xrpl/protocol/jss.h>
#include <initializer_list>
#include <vector>
namespace xrpl {
LedgerFormats::LedgerFormats()
std::vector<SOElement> const&
LedgerFormats::getCommonFields()
{
// Fields shared by all ledger formats:
static std::initializer_list<SOElement> const commonFields{
static auto const commonFields = std::vector<SOElement>{
{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 <xrpl/protocol/detail/ledger_entries.macro>

View File

@@ -2,23 +2,32 @@
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/SOTemplate.h>
#include <algorithm>
#include <cstddef>
#include <initializer_list>
#include <iterator>
#include <stdexcept>
#include <vector>
namespace xrpl {
SOTemplate::SOTemplate(
std::initializer_list<SOElement> uniqueFields,
std::initializer_list<SOElement> commonFields)
: SOTemplate(std::vector(uniqueFields), std::vector(commonFields))
{
}
SOTemplate::SOTemplate(std::vector<SOElement> uniqueFields, std::vector<SOElement> 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()};

View File

@@ -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;
}
/**

View File

@@ -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<void, std::string>

View File

@@ -133,9 +133,9 @@ STVar::constructST(SerializedTypeID id, int depth, Args&&... args)
{
construct<T>(std::forward<Args>(args)...);
}
else if constexpr (std::is_same_v<
std::tuple<std::remove_cvref_t<Args>...>,
std::tuple<SerialIter, SField>>)
else if constexpr (
std::
is_same_v<std::tuple<std::remove_cvref_t<Args>...>, std::tuple<SerialIter, SField>>)
{
construct<T>(std::forward<Args>(args)..., depth);
}

View File

@@ -3,14 +3,14 @@
#include <xrpl/protocol/TxFormats.h>
#include <xrpl/protocol/jss.h>
#include <initializer_list>
#include <vector>
namespace xrpl {
TxFormats::TxFormats()
std::vector<SOElement> const&
TxFormats::getCommonFields()
{
// Fields shared by all txFormats:
static std::initializer_list<SOElement> const commonFields{
static auto const commonFields = std::vector<SOElement>{
{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 <xrpl/protocol/detail/transactions.macro>

View File

@@ -1,8 +1,9 @@
#include <xrpl/tx/ApplyContext.h>
//
#include <xrpl/basics/Log.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/json/to_string.h>
#include <xrpl/tx/ApplyContext.h>
#include <xrpl/tx/InvariantCheck.h>
#include <xrpl/tx/invariants/InvariantCheck.h>
namespace xrpl {

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,305 @@
#include <xrpl/tx/invariants/AMMInvariant.h>
//
#include <xrpl/basics/Log.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/protocol/TxFormats.h>
#include <xrpl/tx/transactors/AMM/AMMHelpers.h>
#include <xrpl/tx/transactors/AMM/AMMUtils.h>
namespace xrpl {
void
ValidAMM::visitEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> 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<Issue>(),
tx[sfAmount2].get<Issue>(),
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<Issue>(),
tx[sfAsset2].get<Issue>(),
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

View File

@@ -0,0 +1,278 @@
#include <xrpl/tx/invariants/FreezeInvariant.h>
//
#include <xrpl/basics/Log.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/TxFormats.h>
#include <xrpl/tx/invariants/InvariantCheckPrivilege.h>
namespace xrpl {
void
TransfersNotFrozen::visitEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> 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<SLE const> const& before,
std::shared_ptr<SLE const> 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<SLE const> const& before,
std::shared_ptr<SLE const> 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<SLE const> 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<SLE const>
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<SLE const> 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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,278 @@
#include <xrpl/tx/invariants/LoanInvariant.h>
//
#include <xrpl/basics/Log.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/View.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/STNumber.h>
#include <xrpl/protocol/TxFormats.h>
namespace xrpl {
void
ValidLoanBroker::visitEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> 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<SLE const> const& before,
std::shared_ptr<SLE const> 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

View File

@@ -0,0 +1,192 @@
#include <xrpl/tx/invariants/MPTInvariant.h>
//
#include <xrpl/basics/Log.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/MPTIssue.h>
#include <xrpl/protocol/TxFormats.h>
#include <xrpl/tx/invariants/InvariantCheckPrivilege.h>
namespace xrpl {
void
ValidMPTIssuance::visitEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> 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

View File

@@ -0,0 +1,274 @@
#include <xrpl/tx/invariants/NFTInvariant.h>
//
#include <xrpl/basics/Log.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/TxFormats.h>
#include <xrpl/protocol/nftPageMask.h>
#include <xrpl/tx/invariants/InvariantCheckPrivilege.h>
#include <xrpl/tx/transactors/NFT/NFTokenUtils.h>
namespace xrpl {
void
ValidNFTokenPage::visitEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> 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<SLE const> const& sle) {
uint256 const account = sle->key() & accountBits;
uint256 const hiLimit = sle->key() & pageBits;
std::optional<uint256> 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<SLE const> const& before,
std::shared_ptr<SLE const> 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

View File

@@ -0,0 +1,93 @@
#include <xrpl/tx/invariants/PermissionedDEXInvariant.h>
//
#include <xrpl/basics/Log.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/STArray.h>
#include <xrpl/protocol/TxFormats.h>
namespace xrpl {
void
ValidPermissionedDEX::visitEntry(
bool,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> 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

View File

@@ -0,0 +1,162 @@
#include <xrpl/tx/invariants/PermissionedDomainInvariant.h>
//
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/CredentialHelpers.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/STArray.h>
#include <xrpl/protocol/TxFormats.h>
namespace xrpl {
void
ValidPermissionedDomain::visitEntry(
bool isDel,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after)
{
if (before && before->getType() != ltPERMISSIONED_DOMAIN)
return;
if (after && after->getType() != ltPERMISSIONED_DOMAIN)
return;
auto check = [isDel](std::vector<SleStatus>& sleStatus, std::shared_ptr<SLE const> 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

View File

@@ -0,0 +1,926 @@
#include <xrpl/tx/invariants/VaultInvariant.h>
//
#include <xrpl/basics/Log.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/View.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STNumber.h>
#include <xrpl/protocol/TxFormats.h>
#include <xrpl/tx/invariants/InvariantCheckPrivilege.h>
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<SLE const> const& before,
std::shared_ptr<SLE const> 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<std::int64_t>(before->getFieldU64(sfOutstandingAmount));
sign = 1;
break;
case ltMPTOKEN:
balanceDelta = static_cast<std::int64_t>(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<std::int64_t>(after->getFieldU64(sfOutstandingAmount)));
sign = 1;
break;
case ltMPTOKEN:
balanceDelta -= Number(static_cast<std::int64_t>(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<Shares> {
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<Shares> {
// 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>(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<Shares> {
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<Number> {
auto const get = //
[&](auto const& it, std::int8_t sign = 1) -> std::optional<Number> {
if (it == deltas_.end())
return std::nullopt;
return it->second * sign;
};
return std::visit(
[&]<typename TIss>(TIss const& issue) {
if constexpr (std::is_same_v<TIss, Issue>)
{
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<TIss, MPTIssue>)
{
return get(deltas_.find(keylet::mptoken(issue.getMptID(), id).key));
}
},
vaultAsset.value());
};
auto const deltaAssetsTxAccount = [&]() -> std::optional<Number> {
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<Number> {
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<Number>(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<Number> {
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

View File

@@ -19,7 +19,7 @@ std::uint32_t
AMMDeposit::getFlagsMask(PreflightContext const& ctx)
{
return tfDepositMask;
return tfAMMDepositMask;
}
NotTEC

View File

@@ -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)

View File

@@ -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;

View File

@@ -17,7 +17,7 @@ AMMWithdraw::checkExtraFeatures(PreflightContext const& ctx)
std::uint32_t
AMMWithdraw::getFlagsMask(PreflightContext const& ctx)
{
return tfWithdrawMask;
return tfAMMWithdrawMask;
}
NotTEC

View File

@@ -16,11 +16,11 @@ NotTEC
Transactor::invokePreflight<Change>(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);

View File

@@ -54,12 +54,6 @@ preflightHelper<MPTIssue>(PreflightContext const& ctx)
return tesSUCCESS;
}
std::uint32_t
Clawback::getFlagsMask(PreflightContext const& ctx)
{
return tfClawbackMask;
}
NotTEC
Clawback::preflight(PreflightContext const& ctx)
{

View File

@@ -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

View File

@@ -5,12 +5,6 @@
namespace xrpl {
std::uint32_t
MPTokenIssuanceDestroy::getFlagsMask(PreflightContext const& ctx)
{
return tfMPTokenIssuanceDestroyMask;
}
NotTEC
MPTokenIssuanceDestroy::preflight(PreflightContext const& ctx)
{

Some files were not shown because too many files have changed in this diff Show More