diff --git a/.clang-format b/.clang-format index ca8edf678f..b6089a2cd4 100644 --- a/.clang-format +++ b/.clang-format @@ -50,20 +50,21 @@ ForEachMacros: [Q_FOREACH, BOOST_FOREACH] IncludeBlocks: Regroup IncludeCategories: - Regex: "^<(test)/" - Priority: 0 - - Regex: "^<(xrpld)/" Priority: 1 - - Regex: "^<(xrpl)/" + - Regex: "^<(xrpld)/" Priority: 2 - - Regex: "^<(boost)/" + - Regex: "^<(xrpl)/" Priority: 3 - - Regex: "^.*/" + - Regex: "^<(boost)/" Priority: 4 - - Regex: '^.*\.h' + - Regex: "^.*/" Priority: 5 - - Regex: ".*" + - Regex: '^.*\.h' Priority: 6 + - Regex: ".*" + Priority: 7 IncludeIsMainRegex: "$" +MainIncludeChar: AngleBracket IndentCaseLabels: true IndentFunctionDeclarationAfterType: false IndentRequiresClause: true diff --git a/.clang-tidy b/.clang-tidy index 07274eb53a..3a21eba6c0 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -10,26 +10,26 @@ Checks: "-*, bugprone-chained-comparison, bugprone-compare-pointer-to-member-virtual-function, bugprone-copy-constructor-init, - # bugprone-crtp-constructor-accessibility, # has issues + bugprone-crtp-constructor-accessibility, bugprone-dangling-handle, bugprone-dynamic-static-initializers, - # bugprone-empty-catch, # has issues + bugprone-empty-catch, bugprone-fold-init-type, - # bugprone-forward-declaration-namespace, # has issues - # bugprone-inaccurate-erase, - # bugprone-inc-dec-in-conditions, - # bugprone-incorrect-enable-if, - # bugprone-incorrect-roundings, - # bugprone-infinite-loop, - # bugprone-integer-division, + 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, # has issues + 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, # has issues + bugprone-multi-level-implicit-pointer-conversion, bugprone-multiple-new-in-one-expression, bugprone-multiple-statement-macro, bugprone-no-escape, @@ -39,13 +39,13 @@ Checks: "-*, bugprone-pointer-arithmetic-on-polymorphic-object, bugprone-posix-return, bugprone-redundant-branch-condition, - # bugprone-reserved-identifier, # has issues - # bugprone-return-const-ref-from-parameter, # has issues + 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, # has issues + bugprone-sizeof-expression, bugprone-spuriously-wake-up-functions, bugprone-standalone-empty, bugprone-string-constructor, @@ -62,7 +62,7 @@ Checks: "-*, bugprone-suspicious-string-compare, bugprone-suspicious-stringview-data-usage, bugprone-swapped-arguments, - # bugprone-switch-missing-default-case, # has issues + bugprone-switch-missing-default-case, bugprone-terminating-continue, bugprone-throw-keyword-missing, bugprone-too-small-loop-variable, @@ -73,7 +73,7 @@ Checks: "-*, bugprone-unhandled-self-assignment, bugprone-unique-ptr-array-mismatch, bugprone-unsafe-functions, - # bugprone-use-after-move, # has issues + bugprone-use-after-move, # has issues bugprone-unused-raii, bugprone-unused-return-value, bugprone-unused-local-non-trivial-variable, @@ -90,15 +90,28 @@ Checks: "-*, 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, - modernize-deprecated-headers, + modernize-concat-nested-namespaces, modernize-make-shared, modernize-make-unique, + modernize-pass-by-value, + modernize-type-traits, + modernize-use-designated-initializers, + modernize-use-emplace, + modernize-use-equals-default, + modernize-use-equals-delete, + modernize-use-override, + modernize-use-ranges, + modernize-use-starts-ends-with, + modernize-use-std-numbers, + modernize-use-using, + modernize-deprecated-headers, llvm-namespace-comment, performance-faster-string-find, performance-for-range-copy, @@ -108,53 +121,38 @@ Checks: "-*, performance-move-constructor-init, performance-no-automatic-move, performance-trivially-destructible, - # readability-avoid-nested-conditional-operator, # has issues - # readability-avoid-return-with-void-value, # has issues - # readability-braces-around-statements, # has issues - # readability-const-return-type, # has issues - # readability-container-contains, # has issues - # readability-container-size-empty, # has issues - # readability-convert-member-functions-to-static, # has issues + 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, # has issues - # readability-enum-initial-value, # has issues - # readability-implicit-bool-conversion, # has issues - # readability-make-member-function-const, # has issues - # readability-math-missing-parentheses, # has issues + readability-else-after-return, + readability-enum-initial-value, + readability-implicit-bool-conversion, + readability-make-member-function-const, + readability-math-missing-parentheses, readability-misleading-indentation, readability-non-const-parameter, - # readability-redundant-casting, # has issues - # readability-redundant-declaration, # has issues - # readability-redundant-inline-specifier, # has issues - # readability-redundant-member-init, # has issues + 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, # has issues - # readability-static-definition-in-anonymous-namespace, # has issues - # readability-suspicious-call-argument, # has issues + readability-simplify-boolean-expr, + readability-static-definition-in-anonymous-namespace, + readability-suspicious-call-argument, readability-use-std-min-max " # --- # other checks that have issues that need to be resolved: # -# misc-include-cleaner, -# # readability-inconsistent-declaration-parameter-name, # in this codebase this check will break a lot of arg names # readability-static-accessed-through-instance, # this check is probably unnecessary. it makes the code less readable # readability-identifier-naming, # https://github.com/XRPLF/rippled/pull/6571 -# -# modernize-concat-nested-namespaces, -# modernize-pass-by-value, -# modernize-type-traits, -# modernize-use-designated-initializers, -# modernize-use-emplace, -# modernize-use-equals-default, -# modernize-use-equals-delete, -# modernize-use-override, -# modernize-use-ranges, -# modernize-use-starts-ends-with, -# modernize-use-std-numbers, -# modernize-use-using, # --- # CheckOptions: @@ -194,7 +192,7 @@ CheckOptions: # 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' + misc-include-cleaner.IgnoreHeaders: ".*/(detail|impl)/.*;.*fwd\\.h(pp)?;time.h;stdlib.h;sqlite3.h;netinet/in\\.h;sys/resource\\.h;sys/sysinfo\\.h;linux/sysinfo\\.h;__chrono/.*;bits/.*;_abort\\.h;boost/uuid/uuid_hash.hpp;boost/beast/core/flat_buffer\\.hpp;boost/beast/http/field\\.hpp;boost/beast/http/dynamic_body\\.hpp;boost/beast/http/message\\.hpp;boost/beast/http/read\\.hpp;boost/beast/http/write\\.hpp;openssl/obj_mac\\.h" # HeaderFilterRegex: '^.*/(test|xrpl|xrpld)/.*\.(h|hpp)$' ExcludeHeaderFilterRegex: '^.*/protocol_autogen/.*\.(h|hpp)$' diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 967b3c1817..05e87d16e0 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -1,7 +1,7 @@ --- name: Feature Request -about: Suggest a new feature for the rippled project -title: "[Title with short description] (Version: [rippled version])" +about: Suggest a new feature for the xrpld project +title: "[Title with short description] (Version: [xrpld version])" labels: Feature Request assignees: "" --- diff --git a/.github/scripts/check-pr-description.py b/.github/scripts/check-pr-description.py new file mode 100644 index 0000000000..36abb0f5a4 --- /dev/null +++ b/.github/scripts/check-pr-description.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 + +""" +Checks that a pull request description has been customized from the +pull_request_template.md. Exits with code 1 if the description is empty +or identical to the template (ignoring HTML comments and whitespace). + +Usage: + python check-pr-description.py --template-file TEMPLATE --pr-body-file BODY +""" + +import argparse +import re +import sys +from pathlib import Path + + +def normalize(text: str) -> str: + """Strip HTML comments, trim lines, and remove blank lines.""" + # Remove HTML comments (possibly multi-line) + text = re.sub(r"", "", text, flags=re.DOTALL) + # Strip each line and drop empties + lines = [line.strip() for line in text.splitlines()] + lines = [line for line in lines if line] + return "\n".join(lines) + + +def main() -> int: + parser = argparse.ArgumentParser( + description="Check that a PR description differs from the template." + ) + parser.add_argument( + "--template-file", + type=Path, + required=True, + help="Path to the pull request template file.", + ) + parser.add_argument( + "--pr-body-file", + type=Path, + required=True, + help="Path to a file containing the PR body text.", + ) + args = parser.parse_args() + + template_path: Path = args.template_file + pr_body_path: Path = args.pr_body_file + + if not template_path.is_file(): + print(f"::error::Template file {template_path} not found") + return 1 + + if not pr_body_path.is_file(): + print(f"::error::PR body file {pr_body_path} not found") + return 1 + + template = template_path.read_text(encoding="utf-8") + pr_body = pr_body_path.read_text(encoding="utf-8") + + # Check if the PR body is empty or whitespace-only + if not pr_body.strip(): + print( + "::error::PR description is empty. " + "Please fill in the pull request template." + ) + return 1 + + norm_template = normalize(template) + norm_pr_body = normalize(pr_body) + + if norm_pr_body == norm_template: + print( + "::error::PR description (ignoring HTML comments) is identical" + " to the template. Please fill in the details of your change." + f"\n\nVisible template content:\n---\n{norm_template}\n---" + f"\n\nVisible PR description content:\n---\n{norm_pr_body}\n---" + ) + return 1 + + print("PR description has been customized from the template.") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/.github/scripts/levelization/README.md b/.github/scripts/levelization/README.md index 25eb52f079..f657344827 100644 --- a/.github/scripts/levelization/README.md +++ b/.github/scripts/levelization/README.md @@ -1,14 +1,14 @@ # Levelization -Levelization is the term used to describe efforts to prevent rippled from +Levelization is the term used to describe efforts to prevent xrpld from having or creating cyclic dependencies. -rippled code is organized into directories under `src/xrpld`, `src/libxrpl` (and +xrpld code is organized into directories under `src/xrpld`, `src/libxrpl` (and `src/test`) representing modules. The modules are intended to be organized into "tiers" or "levels" such that a module from one level can only include code from lower levels. Additionally, a module in one level should never include code in an `impl` or `detail` folder of any level -other than it's own. +other than its own. The codebase is split into two main areas: @@ -22,7 +22,7 @@ levelization violations they find (by moving files or individual classes). At the very least, don't make things worse. The table below summarizes the _desired_ division of modules, based on the current -state of the rippled code. The levels are numbered from +state of the xrpld code. The levels are numbered from the bottom up with the lower level, lower numbered, more independent modules listed first, and the higher level, higher numbered modules with more dependencies listed later. @@ -72,10 +72,10 @@ that `test` code should _never_ be included in `xrpl` or `xrpld` code.) The [levelization](generate.py) script takes no parameters, reads no environment variables, and can be run from any directory, -as long as it is in the expected location in the rippled repo. +as long as it is in the expected location in the xrpld repo. It can be run at any time from within a checked out repo, and will do an analysis of all the `#include`s in -the rippled source. The only caveat is that it runs much slower +the xrpld source. The only caveat is that it runs much slower under Windows than in Linux. It hasn't yet been tested under MacOS. It generates many files of [results](results): diff --git a/.github/scripts/levelization/results/loops.txt b/.github/scripts/levelization/results/loops.txt index 7914704f9d..fb449441e3 100644 --- a/.github/scripts/levelization/results/loops.txt +++ b/.github/scripts/levelization/results/loops.txt @@ -2,19 +2,19 @@ Loop: test.jtx test.toplevel test.toplevel > test.jtx Loop: test.jtx test.unit_test - test.unit_test == test.jtx + test.unit_test ~= test.jtx Loop: xrpld.app xrpld.overlay - xrpld.overlay ~= xrpld.app + xrpld.app > xrpld.overlay Loop: xrpld.app xrpld.peerfinder - xrpld.peerfinder == xrpld.app + xrpld.peerfinder ~= xrpld.app Loop: xrpld.app xrpld.rpc xrpld.rpc > xrpld.app Loop: xrpld.app xrpld.shamap - xrpld.shamap ~= xrpld.app + xrpld.shamap > xrpld.app Loop: xrpld.overlay xrpld.rpc xrpld.rpc ~= xrpld.overlay diff --git a/.github/scripts/levelization/results/ordering.txt b/.github/scripts/levelization/results/ordering.txt index 38e77dedf8..02a14a0077 100644 --- a/.github/scripts/levelization/results/ordering.txt +++ b/.github/scripts/levelization/results/ordering.txt @@ -3,13 +3,17 @@ libxrpl.conditions > xrpl.basics libxrpl.conditions > xrpl.conditions libxrpl.core > xrpl.basics libxrpl.core > xrpl.core +libxrpl.core > xrpl.json libxrpl.crypto > xrpl.basics libxrpl.json > xrpl.basics libxrpl.json > xrpl.json libxrpl.ledger > xrpl.basics libxrpl.ledger > xrpl.json libxrpl.ledger > xrpl.ledger +libxrpl.ledger > xrpl.nodestore libxrpl.ledger > xrpl.protocol +libxrpl.ledger > xrpl.server +libxrpl.ledger > xrpl.shamap libxrpl.net > xrpl.basics libxrpl.net > xrpl.net libxrpl.nodestore > xrpl.basics @@ -19,19 +23,22 @@ libxrpl.nodestore > xrpl.protocol libxrpl.protocol > xrpl.basics libxrpl.protocol > xrpl.json libxrpl.protocol > xrpl.protocol -libxrpl.protocol_autogen > xrpl.protocol_autogen libxrpl.rdb > xrpl.basics libxrpl.rdb > xrpl.core libxrpl.rdb > xrpl.rdb libxrpl.resource > xrpl.basics libxrpl.resource > xrpl.json +libxrpl.resource > xrpl.protocol libxrpl.resource > xrpl.resource libxrpl.server > xrpl.basics +libxrpl.server > xrpl.core libxrpl.server > xrpl.json libxrpl.server > xrpl.protocol libxrpl.server > xrpl.rdb +libxrpl.server > xrpl.resource libxrpl.server > xrpl.server libxrpl.shamap > xrpl.basics +libxrpl.shamap > xrpl.nodestore libxrpl.shamap > xrpl.protocol libxrpl.shamap > xrpl.shamap libxrpl.tx > xrpl.basics @@ -43,12 +50,11 @@ libxrpl.tx > xrpl.protocol libxrpl.tx > xrpl.server libxrpl.tx > xrpl.tx test.app > test.jtx -test.app > test.rpc -test.app > test.toplevel test.app > test.unit_test test.app > xrpl.basics test.app > xrpl.core test.app > xrpld.app +test.app > xrpld.consensus test.app > xrpld.core test.app > xrpld.overlay test.app > xrpld.rpc @@ -56,9 +62,9 @@ test.app > xrpl.json test.app > xrpl.ledger test.app > xrpl.nodestore test.app > xrpl.protocol -test.app > xrpl.rdb test.app > xrpl.resource test.app > xrpl.server +test.app > xrpl.shamap test.app > xrpl.tx test.basics > test.jtx test.basics > test.unit_test @@ -71,16 +77,17 @@ test.beast > xrpl.basics test.conditions > xrpl.basics test.conditions > xrpl.conditions test.consensus > test.csf +test.consensus > test.jtx test.consensus > test.toplevel test.consensus > test.unit_test test.consensus > xrpl.basics test.consensus > xrpld.app test.consensus > xrpld.consensus -test.consensus > xrpl.json test.consensus > xrpl.ledger +test.consensus > xrpl.protocol +test.consensus > xrpl.shamap test.consensus > xrpl.tx test.core > test.jtx -test.core > test.toplevel test.core > test.unit_test test.core > xrpl.basics test.core > xrpl.core @@ -108,27 +115,32 @@ test.jtx > xrpl.resource test.jtx > xrpl.server test.jtx > xrpl.tx test.ledger > test.jtx -test.ledger > test.toplevel test.ledger > xrpl.basics +test.ledger > xrpl.core +test.ledger > xrpld.app test.ledger > xrpld.core +test.ledger > xrpl.json test.ledger > xrpl.ledger test.ledger > xrpl.protocol test.nodestore > test.jtx -test.nodestore > test.toplevel test.nodestore > test.unit_test test.nodestore > xrpl.basics +test.nodestore > xrpld.core test.nodestore > xrpl.nodestore +test.nodestore > xrpl.protocol test.nodestore > xrpl.rdb test.overlay > test.jtx -test.overlay > test.toplevel test.overlay > test.unit_test test.overlay > xrpl.basics test.overlay > xrpld.app +test.overlay > xrpld.core test.overlay > xrpld.overlay test.overlay > xrpld.peerfinder -test.overlay > xrpl.ledger +test.overlay > xrpl.json test.overlay > xrpl.nodestore test.overlay > xrpl.protocol +test.overlay > xrpl.resource +test.overlay > xrpl.server test.overlay > xrpl.shamap test.peerfinder > test.beast test.peerfinder > test.unit_test @@ -136,7 +148,7 @@ test.peerfinder > xrpl.basics test.peerfinder > xrpld.core test.peerfinder > xrpld.peerfinder test.peerfinder > xrpl.protocol -test.protocol > test.toplevel +test.protocol > test.jtx test.protocol > test.unit_test test.protocol > xrpl.basics test.protocol > xrpl.json @@ -145,7 +157,6 @@ test.resource > test.unit_test test.resource > xrpl.basics test.resource > xrpl.resource test.rpc > test.jtx -test.rpc > test.toplevel test.rpc > xrpl.basics test.rpc > xrpl.core test.rpc > xrpld.app @@ -159,13 +170,12 @@ test.rpc > xrpl.resource test.rpc > xrpl.server test.rpc > xrpl.tx test.server > test.jtx -test.server > test.toplevel test.server > test.unit_test test.server > xrpl.basics test.server > xrpld.app test.server > xrpld.core -test.server > xrpld.rpc test.server > xrpl.json +test.server > xrpl.protocol test.server > xrpl.server test.shamap > test.unit_test test.shamap > xrpl.basics @@ -239,19 +249,20 @@ xrpld.consensus > xrpl.ledger xrpld.consensus > xrpl.protocol xrpld.core > xrpl.basics xrpld.core > xrpl.core -xrpld.core > xrpl.json xrpld.core > xrpl.net xrpld.core > xrpl.protocol xrpld.core > xrpl.rdb xrpld.overlay > xrpl.basics xrpld.overlay > xrpl.core +xrpld.overlay > xrpld.consensus xrpld.overlay > xrpld.core xrpld.overlay > xrpld.peerfinder xrpld.overlay > xrpl.json +xrpld.overlay > xrpl.ledger xrpld.overlay > xrpl.protocol -xrpld.overlay > xrpl.rdb xrpld.overlay > xrpl.resource xrpld.overlay > xrpl.server +xrpld.overlay > xrpl.shamap xrpld.overlay > xrpl.tx xrpld.peerfinder > xrpl.basics xrpld.peerfinder > xrpld.core @@ -261,6 +272,7 @@ xrpld.perflog > xrpl.basics xrpld.perflog > xrpl.core xrpld.perflog > xrpld.rpc xrpld.perflog > xrpl.json +xrpld.perflog > xrpl.protocol xrpld.rpc > xrpl.basics xrpld.rpc > xrpl.core xrpld.rpc > xrpld.core @@ -272,5 +284,9 @@ xrpld.rpc > xrpl.protocol xrpld.rpc > xrpl.rdb xrpld.rpc > xrpl.resource xrpld.rpc > xrpl.server +xrpld.rpc > xrpl.shamap xrpld.rpc > xrpl.tx +xrpld.shamap > xrpl.basics +xrpld.shamap > xrpld.core +xrpld.shamap > xrpl.protocol xrpld.shamap > xrpl.shamap diff --git a/.github/scripts/rename/README.md b/.github/scripts/rename/README.md index cc004a335f..ab685bb0c3 100644 --- a/.github/scripts/rename/README.md +++ b/.github/scripts/rename/README.md @@ -34,6 +34,8 @@ run from the repository root. 6. `.github/scripts/rename/config.sh`: This script will rename the config from `rippled.cfg` to `xrpld.cfg`, and updating the code accordingly. The old filename will still be accepted. +7. `.github/scripts/rename/docs.sh`: This script will rename any lingering + references of `ripple(d)` to `xrpl(d)` in code, comments, and documentation. You can run all these scripts from the repository root as follows: @@ -44,4 +46,5 @@ You can run all these scripts from the repository root as follows: ./.github/scripts/rename/binary.sh . ./.github/scripts/rename/namespace.sh . ./.github/scripts/rename/config.sh . +./.github/scripts/rename/docs.sh . ``` diff --git a/.github/scripts/rename/binary.sh b/.github/scripts/rename/binary.sh index deb4dd5fb4..cdce6db4ba 100755 --- a/.github/scripts/rename/binary.sh +++ b/.github/scripts/rename/binary.sh @@ -6,11 +6,11 @@ set -e # On MacOS, ensure that GNU sed is installed and available as `gsed`. SED_COMMAND=sed if [[ "${OSTYPE}" == 'darwin'* ]]; then - if ! command -v gsed &> /dev/null; then - echo "Error: gsed is not installed. Please install it using 'brew install gnu-sed'." - exit 1 - fi - SED_COMMAND=gsed + if ! command -v gsed &> /dev/null; then + echo "Error: gsed is not installed. Please install it using 'brew install gnu-sed'." + exit 1 + fi + SED_COMMAND=gsed fi # This script changes the binary name from `rippled` to `xrpld`, and reverses @@ -29,7 +29,7 @@ if [ ! -d "${DIRECTORY}" ]; then echo "Error: Directory '${DIRECTORY}' does not exist." exit 1 fi -pushd ${DIRECTORY} +pushd "${DIRECTORY}" # Remove the binary name override added by the cmake.sh script. ${SED_COMMAND} -z -i -E 's@\s+# For the time being.+"rippled"\)@@' cmake/XrplCore.cmake @@ -49,6 +49,7 @@ ${SED_COMMAND} -i -E 's@ripple/xrpld@XRPLF/rippled@g' BUILD.md ${SED_COMMAND} -i -E 's@XRPLF/xrpld@XRPLF/rippled@g' BUILD.md ${SED_COMMAND} -i -E 's@xrpld \(`xrpld`\)@xrpld@g' BUILD.md ${SED_COMMAND} -i -E 's@XRPLF/xrpld@XRPLF/rippled@g' CONTRIBUTING.md +${SED_COMMAND} -i -E 's@XRPLF/xrpld@XRPLF/rippled@g' docs/build/install.md popd echo "Processing complete." diff --git a/.github/scripts/rename/cmake.sh b/.github/scripts/rename/cmake.sh index 0f88fa5de2..9c91e8f277 100755 --- a/.github/scripts/rename/cmake.sh +++ b/.github/scripts/rename/cmake.sh @@ -8,16 +8,16 @@ set -e SED_COMMAND=sed HEAD_COMMAND=head if [[ "${OSTYPE}" == 'darwin'* ]]; then - if ! command -v gsed &> /dev/null; then - echo "Error: gsed is not installed. Please install it using 'brew install gnu-sed'." - exit 1 - fi - SED_COMMAND=gsed - if ! command -v ghead &> /dev/null; then - echo "Error: ghead is not installed. Please install it using 'brew install coreutils'." - exit 1 - fi - HEAD_COMMAND=ghead + if ! command -v gsed &> /dev/null; then + echo "Error: gsed is not installed. Please install it using 'brew install gnu-sed'." + exit 1 + fi + SED_COMMAND=gsed + if ! command -v ghead &> /dev/null; then + echo "Error: ghead is not installed. Please install it using 'brew install coreutils'." + exit 1 + fi + HEAD_COMMAND=ghead fi # This script renames CMake files from `RippleXXX.cmake` or `RippledXXX.cmake` @@ -38,16 +38,16 @@ if [ ! -d "${DIRECTORY}" ]; then echo "Error: Directory '${DIRECTORY}' does not exist." exit 1 fi -pushd ${DIRECTORY} +pushd "${DIRECTORY}" # Rename the files. find cmake -type f -name 'Rippled*.cmake' -exec bash -c 'mv "${1}" "${1/Rippled/Xrpl}"' - {} \; find cmake -type f -name 'Ripple*.cmake' -exec bash -c 'mv "${1}" "${1/Ripple/Xrpl}"' - {} \; if [ -e cmake/xrpl_add_test.cmake ]; then - mv cmake/xrpl_add_test.cmake cmake/XrplAddTest.cmake + mv cmake/xrpl_add_test.cmake cmake/XrplAddTest.cmake fi if [ -e include/xrpl/proto/ripple.proto ]; then - mv include/xrpl/proto/ripple.proto include/xrpl/proto/xrpl.proto + mv include/xrpl/proto/ripple.proto include/xrpl/proto/xrpl.proto fi # Rename inside the files. @@ -71,14 +71,14 @@ ${SED_COMMAND} -i 's@xrpl/validator-keys-tool@ripple/validator-keys-tool@' cmake # Ensure the name of the binary and config remain 'rippled' for now. ${SED_COMMAND} -i -E 's/xrpld(-example)?\.cfg/rippled\1.cfg/g' cmake/XrplInstall.cmake if grep -q '"xrpld"' cmake/XrplCore.cmake; then - # The script has been rerun, so just restore the name of the binary. - ${SED_COMMAND} -i 's/"xrpld"/"rippled"/' cmake/XrplCore.cmake + # The script has been rerun, so just restore the name of the binary. + ${SED_COMMAND} -i 's/"xrpld"/"rippled"/' cmake/XrplCore.cmake elif ! grep -q '"rippled"' cmake/XrplCore.cmake; then - ${HEAD_COMMAND} -n -1 cmake/XrplCore.cmake > cmake.tmp - echo ' # For the time being, we will keep the name of the binary as it was.' >> cmake.tmp - echo ' set_target_properties(xrpld PROPERTIES OUTPUT_NAME "rippled")' >> cmake.tmp - tail -1 cmake/XrplCore.cmake >> cmake.tmp - mv cmake.tmp cmake/XrplCore.cmake + ${HEAD_COMMAND} -n -1 cmake/XrplCore.cmake > cmake.tmp + echo ' # For the time being, we will keep the name of the binary as it was.' >> cmake.tmp + echo ' set_target_properties(xrpld PROPERTIES OUTPUT_NAME "rippled")' >> cmake.tmp + tail -1 cmake/XrplCore.cmake >> cmake.tmp + mv cmake.tmp cmake/XrplCore.cmake fi # Restore the symlink from 'xrpld' to 'rippled'. diff --git a/.github/scripts/rename/config.sh b/.github/scripts/rename/config.sh index f6e914f502..b7aff82cdf 100755 --- a/.github/scripts/rename/config.sh +++ b/.github/scripts/rename/config.sh @@ -6,11 +6,11 @@ set -e # On MacOS, ensure that GNU sed is installed and available as `gsed`. SED_COMMAND=sed if [[ "${OSTYPE}" == 'darwin'* ]]; then - if ! command -v gsed &> /dev/null; then - echo "Error: gsed is not installed. Please install it using 'brew install gnu-sed'." - exit 1 - fi - SED_COMMAND=gsed + if ! command -v gsed &> /dev/null; then + echo "Error: gsed is not installed. Please install it using 'brew install gnu-sed'." + exit 1 + fi + SED_COMMAND=gsed fi # This script renames the config from `rippled.cfg` to `xrpld.cfg`, and updates @@ -28,40 +28,39 @@ if [ ! -d "${DIRECTORY}" ]; then echo "Error: Directory '${DIRECTORY}' does not exist." exit 1 fi -pushd ${DIRECTORY} +pushd "${DIRECTORY}" # Add the xrpld.cfg to the .gitignore. if ! grep -q 'xrpld.cfg' .gitignore; then - ${SED_COMMAND} -i '/rippled.cfg/a\ + ${SED_COMMAND} -i '/rippled.cfg/a\ /xrpld.cfg' .gitignore fi # Rename the files. if [ -e rippled.cfg ]; then - mv rippled.cfg xrpld.cfg + mv rippled.cfg xrpld.cfg fi if [ -e cfg/rippled-example.cfg ]; then - mv cfg/rippled-example.cfg cfg/xrpld-example.cfg + mv cfg/rippled-example.cfg cfg/xrpld-example.cfg fi # Rename inside the files. DIRECTORIES=("cfg" "cmake" "include" "src") for DIRECTORY in "${DIRECTORIES[@]}"; do - echo "Processing directory: ${DIRECTORY}" + echo "Processing directory: ${DIRECTORY}" - find "${DIRECTORY}" -type f \( -name "*.h" -o -name "*.hpp" -o -name "*.ipp" -o -name "*.cpp" -o -name "*.cmake" -o -name "*.txt" -o -name "*.cfg" -o -name "*.md" \) | while read -r FILE; do - echo "Processing file: ${FILE}" - ${SED_COMMAND} -i -E 's/rippled(-example)?[ .]cfg/xrpld\1.cfg/g' "${FILE}" - done + find "${DIRECTORY}" -type f \( -name "*.h" -o -name "*.hpp" -o -name "*.ipp" -o -name "*.cpp" -o -name "*.cmake" -o -name "*.txt" -o -name "*.cfg" -o -name "*.md" \) | while read -r FILE; do + echo "Processing file: ${FILE}" + ${SED_COMMAND} -i -E 's/rippled(-example)?[ .]cfg/xrpld\1.cfg/g' "${FILE}" + ${SED_COMMAND} -i 's/rippleConfig/xrpldConfig/g' "${FILE}" + done done ${SED_COMMAND} -i 's/rippled/xrpld/g' cfg/xrpld-example.cfg ${SED_COMMAND} -i 's/rippled/xrpld/g' src/test/core/Config_test.cpp ${SED_COMMAND} -i 's/ripplevalidators/xrplvalidators/g' src/test/core/Config_test.cpp # cspell: disable-line -${SED_COMMAND} -i 's/rippleConfig/xrpldConfig/g' src/test/core/Config_test.cpp ${SED_COMMAND} -i 's@ripple/@xrpld/@g' src/test/core/Config_test.cpp ${SED_COMMAND} -i 's/Rippled/File/g' src/test/core/Config_test.cpp - # Restore the old config file name in the code that maintains support for now. ${SED_COMMAND} -i 's/configLegacyName = "xrpld.cfg"/configLegacyName = "rippled.cfg"/g' src/xrpld/core/detail/Config.cpp diff --git a/.github/scripts/rename/copyright.sh b/.github/scripts/rename/copyright.sh index c5a9fb2cd3..9ebdad1e89 100755 --- a/.github/scripts/rename/copyright.sh +++ b/.github/scripts/rename/copyright.sh @@ -6,11 +6,11 @@ set -e # On MacOS, ensure that GNU sed is installed and available as `gsed`. SED_COMMAND=sed if [[ "${OSTYPE}" == 'darwin'* ]]; then - if ! command -v gsed &> /dev/null; then - echo "Error: gsed is not installed. Please install it using 'brew install gnu-sed'." - exit 1 - fi - SED_COMMAND=gsed + if ! command -v gsed &> /dev/null; then + echo "Error: gsed is not installed. Please install it using 'brew install gnu-sed'." + exit 1 + fi + SED_COMMAND=gsed fi # This script removes superfluous copyright notices in source and header files @@ -31,7 +31,7 @@ if [ ! -d "${DIRECTORY}" ]; then echo "Error: Directory '${DIRECTORY}' does not exist." exit 1 fi -pushd ${DIRECTORY} +pushd "${DIRECTORY}" # Prevent sed and echo from removing newlines and tabs in string literals by # temporarily replacing them with placeholders. This only affects one file. @@ -43,56 +43,56 @@ ${SED_COMMAND} -i -E "s@\\\t@${PLACEHOLDER_TAB}@g" src/test/rpc/ValidatorInfo_te # Process the include/ and src/ directories. DIRECTORIES=("include" "src") for DIRECTORY in "${DIRECTORIES[@]}"; do - echo "Processing directory: ${DIRECTORY}" + echo "Processing directory: ${DIRECTORY}" - find "${DIRECTORY}" -type f \( -name "*.h" -o -name "*.hpp" -o -name "*.ipp" -o -name "*.cpp" -o -name "*.macro" \) | while read -r FILE; do - echo "Processing file: ${FILE}" - # Handle the cases where the copyright notice is enclosed in /* ... */ - # and usually surrounded by //---- and //======. - ${SED_COMMAND} -z -i -E 's@^//-------+\n+@@' "${FILE}" - ${SED_COMMAND} -z -i -E 's@^.*Copyright.+(Ripple|Bougalis|Falco|Hinnant|Null|Ritchford|XRPLF).+PERFORMANCE OF THIS SOFTWARE\.\n\*/\n+@@' "${FILE}" # cspell: ignore Bougalis Falco Hinnant Ritchford - ${SED_COMMAND} -z -i -E 's@^//=======+\n+@@' "${FILE}" + find "${DIRECTORY}" -type f \( -name "*.h" -o -name "*.hpp" -o -name "*.ipp" -o -name "*.cpp" -o -name "*.macro" \) | while read -r FILE; do + echo "Processing file: ${FILE}" + # Handle the cases where the copyright notice is enclosed in /* ... */ + # and usually surrounded by //---- and //======. + ${SED_COMMAND} -z -i -E 's@^//-------+\n+@@' "${FILE}" + ${SED_COMMAND} -z -i -E 's@^.*Copyright.+(Ripple|Bougalis|Falco|Hinnant|Null|Ritchford|XRPLF).+PERFORMANCE OF THIS SOFTWARE\.\n\*/\n+@@' "${FILE}" # cspell: ignore Bougalis Falco Hinnant Ritchford + ${SED_COMMAND} -z -i -E 's@^//=======+\n+@@' "${FILE}" - # Handle the cases where the copyright notice is commented out with //. - ${SED_COMMAND} -z -i -E 's@^//\n// Copyright.+Falco \(vinnie dot falco at gmail dot com\)\n//\n+@@' "${FILE}" # cspell: ignore Vinnie Falco - done + # Handle the cases where the copyright notice is commented out with //. + ${SED_COMMAND} -z -i -E 's@^//\n// Copyright.+Falco \(vinnie dot falco at gmail dot com\)\n//\n+@@' "${FILE}" # cspell: ignore Vinnie Falco + done done # Restore copyright notices that were removed from specific files, without # restoring the verbiage that is already present in LICENSE.md. Ensure that if # the script is run multiple times, duplicate notices are not added. if ! grep -q 'Raw Material Software' include/xrpl/beast/core/CurrentThreadName.h; then - echo -e "// Portions of this file are from JUCE (http://www.juce.com).\n// Copyright (c) 2013 - Raw Material Software Ltd.\n// Please visit http://www.juce.com\n\n$(cat include/xrpl/beast/core/CurrentThreadName.h)" > include/xrpl/beast/core/CurrentThreadName.h + echo -e "// Portions of this file are from JUCE (http://www.juce.com).\n// Copyright (c) 2013 - Raw Material Software Ltd.\n// Please visit http://www.juce.com\n\n$(cat include/xrpl/beast/core/CurrentThreadName.h)" > include/xrpl/beast/core/CurrentThreadName.h fi if ! grep -q 'Dev Null' src/test/app/NetworkID_test.cpp; then - echo -e "// Copyright (c) 2020 Dev Null Productions\n\n$(cat src/test/app/NetworkID_test.cpp)" > src/test/app/NetworkID_test.cpp + echo -e "// Copyright (c) 2020 Dev Null Productions\n\n$(cat src/test/app/NetworkID_test.cpp)" > src/test/app/NetworkID_test.cpp fi if ! grep -q 'Dev Null' src/test/app/tx/apply_test.cpp; then - echo -e "// Copyright (c) 2020 Dev Null Productions\n\n$(cat src/test/app/tx/apply_test.cpp)" > src/test/app/tx/apply_test.cpp + echo -e "// Copyright (c) 2020 Dev Null Productions\n\n$(cat src/test/app/tx/apply_test.cpp)" > src/test/app/tx/apply_test.cpp fi if ! grep -q 'Dev Null' src/test/rpc/ManifestRPC_test.cpp; then - echo -e "// Copyright (c) 2020 Dev Null Productions\n\n$(cat src/test/rpc/ManifestRPC_test.cpp)" > src/test/rpc/ManifestRPC_test.cpp + echo -e "// Copyright (c) 2020 Dev Null Productions\n\n$(cat src/test/rpc/ManifestRPC_test.cpp)" > src/test/rpc/ManifestRPC_test.cpp fi if ! grep -q 'Dev Null' src/test/rpc/ValidatorInfo_test.cpp; then - echo -e "// Copyright (c) 2020 Dev Null Productions\n\n$(cat src/test/rpc/ValidatorInfo_test.cpp)" > src/test/rpc/ValidatorInfo_test.cpp + echo -e "// Copyright (c) 2020 Dev Null Productions\n\n$(cat src/test/rpc/ValidatorInfo_test.cpp)" > src/test/rpc/ValidatorInfo_test.cpp fi -if ! grep -q 'Dev Null' src/xrpld/rpc/handlers/DoManifest.cpp; then - echo -e "// Copyright (c) 2019 Dev Null Productions\n\n$(cat src/xrpld/rpc/handlers/DoManifest.cpp)" > src/xrpld/rpc/handlers/DoManifest.cpp +if ! grep -q 'Dev Null' src/xrpld/rpc/handlers/server_info/Manifest.cpp; then + echo -e "// Copyright (c) 2019 Dev Null Productions\n\n$(cat src/xrpld/rpc/handlers/server_info/Manifest.cpp)" > src/xrpld/rpc/handlers/server_info/Manifest.cpp fi -if ! grep -q 'Dev Null' src/xrpld/rpc/handlers/ValidatorInfo.cpp; then - echo -e "// Copyright (c) 2019 Dev Null Productions\n\n$(cat src/xrpld/rpc/handlers/ValidatorInfo.cpp)" > src/xrpld/rpc/handlers/ValidatorInfo.cpp +if ! grep -q 'Dev Null' src/xrpld/rpc/handlers/admin/status/ValidatorInfo.cpp; then + echo -e "// Copyright (c) 2019 Dev Null Productions\n\n$(cat src/xrpld/rpc/handlers/admin/status/ValidatorInfo.cpp)" > src/xrpld/rpc/handlers/admin/status/ValidatorInfo.cpp fi if ! grep -q 'Bougalis' include/xrpl/basics/SlabAllocator.h; then - echo -e "// Copyright (c) 2022, Nikolaos D. Bougalis \n\n$(cat include/xrpl/basics/SlabAllocator.h)" > include/xrpl/basics/SlabAllocator.h # cspell: ignore Nikolaos Bougalis nikb + echo -e "// Copyright (c) 2022, Nikolaos D. Bougalis \n\n$(cat include/xrpl/basics/SlabAllocator.h)" > include/xrpl/basics/SlabAllocator.h # cspell: ignore Nikolaos Bougalis nikb fi if ! grep -q 'Bougalis' include/xrpl/basics/spinlock.h; then - echo -e "// Copyright (c) 2022, Nikolaos D. Bougalis \n\n$(cat include/xrpl/basics/spinlock.h)" > include/xrpl/basics/spinlock.h # cspell: ignore Nikolaos Bougalis nikb + echo -e "// Copyright (c) 2022, Nikolaos D. Bougalis \n\n$(cat include/xrpl/basics/spinlock.h)" > include/xrpl/basics/spinlock.h # cspell: ignore Nikolaos Bougalis nikb fi if ! grep -q 'Bougalis' include/xrpl/basics/tagged_integer.h; then - echo -e "// Copyright (c) 2014, Nikolaos D. Bougalis \n\n$(cat include/xrpl/basics/tagged_integer.h)" > include/xrpl/basics/tagged_integer.h # cspell: ignore Nikolaos Bougalis nikb + echo -e "// Copyright (c) 2014, Nikolaos D. Bougalis \n\n$(cat include/xrpl/basics/tagged_integer.h)" > include/xrpl/basics/tagged_integer.h # cspell: ignore Nikolaos Bougalis nikb fi if ! grep -q 'Ritchford' include/xrpl/beast/utility/Zero.h; then - echo -e "// Copyright (c) 2014, Tom Ritchford \n\n$(cat include/xrpl/beast/utility/Zero.h)" > include/xrpl/beast/utility/Zero.h # cspell: ignore Ritchford + echo -e "// Copyright (c) 2014, Tom Ritchford \n\n$(cat include/xrpl/beast/utility/Zero.h)" > include/xrpl/beast/utility/Zero.h # cspell: ignore Ritchford fi # Restore newlines and tabs in string literals in the affected file. diff --git a/.github/scripts/rename/definitions.sh b/.github/scripts/rename/definitions.sh index 403e5eab0d..5e004afe39 100755 --- a/.github/scripts/rename/definitions.sh +++ b/.github/scripts/rename/definitions.sh @@ -6,11 +6,11 @@ set -e # On MacOS, ensure that GNU sed is installed and available as `gsed`. SED_COMMAND=sed if [[ "${OSTYPE}" == 'darwin'* ]]; then - if ! command -v gsed &> /dev/null; then - echo "Error: gsed is not installed. Please install it using 'brew install gnu-sed'." - exit 1 - fi - SED_COMMAND=gsed + if ! command -v gsed &> /dev/null; then + echo "Error: gsed is not installed. Please install it using 'brew install gnu-sed'." + exit 1 + fi + SED_COMMAND=gsed fi # This script renames definitions, such as include guards, in this project. diff --git a/.github/scripts/rename/docs.sh b/.github/scripts/rename/docs.sh new file mode 100755 index 0000000000..59cc5665bf --- /dev/null +++ b/.github/scripts/rename/docs.sh @@ -0,0 +1,96 @@ +#!/bin/bash + +# Exit the script as soon as an error occurs. +set -e + +# On MacOS, ensure that GNU sed is installed and available as `gsed`. +SED_COMMAND=sed +if [[ "${OSTYPE}" == 'darwin'* ]]; then + if ! command -v gsed &> /dev/null; then + echo "Error: gsed is not installed. Please install it using 'brew install gnu-sed'." + exit 1 + fi + SED_COMMAND=gsed +fi + +# This script renames all remaining references to `ripple` and `rippled` to +# `xrpl` and `xrpld`, respectively, in code, comments, and documentation. +# Usage: .github/scripts/rename/docs.sh + +if [ "$#" -ne 1 ]; then + echo "Usage: $0 " + exit 1 +fi + +DIRECTORY=$1 +echo "Processing directory: ${DIRECTORY}" +if [ ! -d "${DIRECTORY}" ]; then + echo "Error: Directory '${DIRECTORY}' does not exist." + exit 1 +fi +pushd "${DIRECTORY}" + +find . -type f \( -name "*.h" -o -name "*.hpp" -o -name "*.ipp" -o -name "*.cpp" -o -name "*.txt" -o -name "*.cfg" -o -name "*.md" -o -name "*.proto" \) -not -path "./.github/scripts/*" | while read -r FILE; do + echo "Processing file: ${FILE}" + ${SED_COMMAND} -i 's/rippleLockEscrowMPT/lockEscrowMPT/g' "${FILE}" + ${SED_COMMAND} -i 's/rippleUnlockEscrowMPT/unlockEscrowMPT/g' "${FILE}" + ${SED_COMMAND} -i 's/rippleCredit/directSendNoFee/g' "${FILE}" + ${SED_COMMAND} -i 's/rippleSend/directSendNoLimit/g' "${FILE}" + ${SED_COMMAND} -i -E 's@([^/+-])rippled@\1xrpld@g' "${FILE}" + ${SED_COMMAND} -i -E 's@([^/+-])Rippled@\1Xrpld@g' "${FILE}" + ${SED_COMMAND} -i -E 's/^rippled/xrpld/g' "${FILE}" + ${SED_COMMAND} -i -E 's/^Rippled/Xrpld/g' "${FILE}" + # cspell: disable + ${SED_COMMAND} -i -E 's/(r|R)ipple (a|A)ddress/XRPL address/g' "${FILE}" + ${SED_COMMAND} -i -E 's/(r|R)ipple (a|A)ccount/XRPL account/g' "${FILE}" + ${SED_COMMAND} -i -E 's/(r|R)ipple (a|A)lgorithm/XRPL algorithm/g' "${FILE}" + ${SED_COMMAND} -i -E 's/(r|R)ipple (c|C)lient/XRPL client/g' "${FILE}" + ${SED_COMMAND} -i -E 's/(r|R)ipple (c|C)luster/XRPL cluster/g' "${FILE}" + ${SED_COMMAND} -i -E 's/(r|R)ipple (c|C)onsensus/XRPL consensus/g' "${FILE}" + ${SED_COMMAND} -i -E 's/(r|R)ipple (d|D)efault/XRPL default/g' "${FILE}" + ${SED_COMMAND} -i -E 's/(r|R)ipple (e|E)poch/XRPL epoch/g' "${FILE}" + ${SED_COMMAND} -i -E 's/(r|R)ipple (f|F)eature/XRPL feature/g' "${FILE}" + ${SED_COMMAND} -i -E 's/(r|R)ipple (n|N)etwork/XRPL network/g' "${FILE}" + ${SED_COMMAND} -i -E 's/(r|R)ipple (p|P)ayment/XRPL payment/g' "${FILE}" + ${SED_COMMAND} -i -E 's/(r|R)ipple (p|P)rotocol/XRPL protocol/g' "${FILE}" + ${SED_COMMAND} -i -E 's/(r|R)ipple (r|R)epository/XRPL repository/g' "${FILE}" + ${SED_COMMAND} -i -E 's/(r|R)ipple RPC/XRPL RPC/g' "${FILE}" + ${SED_COMMAND} -i -E 's/(r|R)ipple (s|S)erialization/XRPL serialization/g' "${FILE}" + ${SED_COMMAND} -i -E 's/(r|R)ipple (s|S)erver/XRPL server/g' "${FILE}" + ${SED_COMMAND} -i -E 's/(r|R)ipple (s|S)pecific/XRPL specific/g' "${FILE}" + ${SED_COMMAND} -i -E 's/(r|R)ipple Source/XRPL Source/g' "${FILE}" + ${SED_COMMAND} -i -E 's/(r|R)ipple (t|T)imestamp/XRPL timestamp/g' "${FILE}" + ${SED_COMMAND} -i -E 's/(r|R)ipple uses the consensus/XRPL uses the consensus/g' "${FILE}" + ${SED_COMMAND} -i -E 's/(r|R)ipple (v|V)alidator/XRPL validator/g' "${FILE}" + # cspell: enable + ${SED_COMMAND} -i 's/RippleLib/XrplLib/g' "${FILE}" + ${SED_COMMAND} -i 's/ripple-lib/XrplLib/g' "${FILE}" + ${SED_COMMAND} -i 's@opt/ripple/@opt/xrpld/@g' "${FILE}" + ${SED_COMMAND} -i 's@src/ripple/@src/xrpld/@g' "${FILE}" + ${SED_COMMAND} -i 's@ripple/app/@xrpld/app/@g' "${FILE}" + ${SED_COMMAND} -i 's@github.com/ripple/rippled@github.com/XRPLF/rippled@g' "${FILE}" + ${SED_COMMAND} -i 's/\ba xrpl/an xrpl/g' "${FILE}" + ${SED_COMMAND} -i 's/\ba XRPL/an XRPL/g' "${FILE}" +done +${SED_COMMAND} -i 's/ripple_libs/xrpl_libs/' BUILD.md +${SED_COMMAND} -i 's/Ripple integrators/XRPL developers/' README.md +${SED_COMMAND} -i 's/sanitizer-configuration-for-rippled/sanitizer-configuration-for-xrpld/' docs/build/sanitizers.md +${SED_COMMAND} -i 's/rippled/xrpld/g' .github/scripts/levelization/README.md +${SED_COMMAND} -i 's/rippled/xrpld/g' .github/scripts/strategy-matrix/generate.py +${SED_COMMAND} -i 's@/rippled@/xrpld@g' docs/build/install.md +${SED_COMMAND} -i 's@github.com/XRPLF/xrpld@github.com/XRPLF/rippled@g' docs/build/install.md +${SED_COMMAND} -i 's/rippled/xrpld/g' docs/Doxyfile +${SED_COMMAND} -i 's/ripple_basics/basics/' include/xrpl/basics/CountedObject.h +${SED_COMMAND} -i 's/ /dev/null; then - echo "Error: gsed is not installed. Please install it using 'brew install gnu-sed'." - exit 1 - fi - SED_COMMAND=gsed + if ! command -v gsed &> /dev/null; then + echo "Error: gsed is not installed. Please install it using 'brew install gnu-sed'." + exit 1 + fi + SED_COMMAND=gsed fi # This script renames the `ripple` namespace to `xrpl` in this project. @@ -31,18 +31,19 @@ if [ ! -d "${DIRECTORY}" ]; then echo "Error: Directory '${DIRECTORY}' does not exist." exit 1 fi -pushd ${DIRECTORY} +pushd "${DIRECTORY}" DIRECTORIES=("include" "src" "tests") for DIRECTORY in "${DIRECTORIES[@]}"; do - echo "Processing directory: ${DIRECTORY}" + echo "Processing directory: ${DIRECTORY}" - find "${DIRECTORY}" -type f \( -name "*.h" -o -name "*.hpp" -o -name "*.ipp" -o -name "*.cpp" \) | while read -r FILE; do - echo "Processing file: ${FILE}" - ${SED_COMMAND} -i 's/namespace ripple/namespace xrpl/g' "${FILE}" - ${SED_COMMAND} -i 's/ripple::/xrpl::/g' "${FILE}" - ${SED_COMMAND} -i -E 's/(BEAST_DEFINE_TESTSUITE.+)ripple(.+)/\1xrpl\2/g' "${FILE}" - done + find "${DIRECTORY}" -type f \( -name "*.h" -o -name "*.hpp" -o -name "*.ipp" -o -name "*.cpp" -o -name "*.macro" \) | while read -r FILE; do + echo "Processing file: ${FILE}" + ${SED_COMMAND} -i 's/namespace ripple/namespace xrpl/g' "${FILE}" + ${SED_COMMAND} -i 's/ripple::/xrpl::/g' "${FILE}" + ${SED_COMMAND} -i 's/"ripple:/"xrpl::/g' "${FILE}" + ${SED_COMMAND} -i -E 's/(BEAST_DEFINE_TESTSUITE.+)ripple(.+)/\1xrpl\2/g' "${FILE}" + done done # Special case for NuDBFactory that has ripple twice in the test suite name. diff --git a/.github/scripts/strategy-matrix/generate.py b/.github/scripts/strategy-matrix/generate.py index 61e3fde119..39696101ac 100755 --- a/.github/scripts/strategy-matrix/generate.py +++ b/.github/scripts/strategy-matrix/generate.py @@ -235,7 +235,7 @@ def generate_strategy_matrix(all: bool, config: Config) -> list: # so that they are easier to identify in the GitHub Actions UI, as long # names get truncated. # Add Address and Thread (both coupled with UB) sanitizers for specific bookworm distros. - # GCC-Asan rippled-embedded tests are failing because of https://github.com/google/sanitizers/issues/856 + # GCC-Asan xrpld-embedded tests are failing because of https://github.com/google/sanitizers/issues/856 if os[ "distro_version" ] == "bookworm" and f"{os['compiler_name']}-{os['compiler_version']}" in [ diff --git a/.github/workflows/check-pr-commits.yml b/.github/workflows/check-pr-commits.yml index 2697a3a40e..37e15a5648 100644 --- a/.github/workflows/check-pr-commits.yml +++ b/.github/workflows/check-pr-commits.yml @@ -10,4 +10,4 @@ permissions: jobs: check_commits: - uses: XRPLF/actions/.github/workflows/check-pr-commits.yml@481048b78b94ac3343d1292b4ef125a813879f2b + uses: XRPLF/actions/.github/workflows/check-pr-commits.yml@e2c7f400d1e85ae65dad552fd425169fbacca4a3 diff --git a/.github/workflows/check-pr-description.yml b/.github/workflows/check-pr-description.yml new file mode 100644 index 0000000000..f6eee50291 --- /dev/null +++ b/.github/workflows/check-pr-description.yml @@ -0,0 +1,30 @@ +name: Check PR description + +on: + merge_group: + types: + - checks_requested + pull_request: + types: [opened, edited, reopened, synchronize, ready_for_review] + branches: [develop] + +jobs: + check_description: + if: ${{ github.event.pull_request.draft != true }} + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Write PR body to file + env: + PR_BODY: ${{ github.event.pull_request.body }} + if: ${{ github.event_name == 'pull_request' }} + run: printenv PR_BODY > pr_body.md + + - name: Check PR description differs from template + if: ${{ github.event_name == 'pull_request' }} + run: > + python .github/scripts/check-pr-description.py + --template-file .github/pull_request_template.md + --pr-body-file pr_body.md diff --git a/.github/workflows/check-pr-title.yml b/.github/workflows/check-pr-title.yml index fc03cdf8e1..6d7bdefa08 100644 --- a/.github/workflows/check-pr-title.yml +++ b/.github/workflows/check-pr-title.yml @@ -11,4 +11,4 @@ on: jobs: check_title: if: ${{ github.event.pull_request.draft != true }} - uses: XRPLF/actions/.github/workflows/check-pr-title.yml@e2c7f400d1e85ae65dad552fd425169fbacca4a3 + uses: XRPLF/actions/.github/workflows/check-pr-title.yml@a5d8dd35be543365e90a11358447130c8763871d diff --git a/.github/workflows/on-pr.yml b/.github/workflows/on-pr.yml index 66893d19d3..28299a1264 100644 --- a/.github/workflows/on-pr.yml +++ b/.github/workflows/on-pr.yml @@ -46,7 +46,7 @@ jobs: # that Github considers any skipped jobs to have passed, and in # turn the required checks as well. id: changes - uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5 + uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6 with: files: | # These paths are unique to `on-pr.yml`. diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 5cc99d1804..89255f0e47 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -14,7 +14,7 @@ on: jobs: # Call the workflow in the XRPLF/actions repo that runs the pre-commit hooks. run-hooks: - uses: XRPLF/actions/.github/workflows/pre-commit.yml@e7896f15cc60d0da1a272c77ee5c4026b424f9c7 + uses: XRPLF/actions/.github/workflows/pre-commit.yml@9307df762265e15c745ddcdb38a581c989f7f349 with: runs_on: ubuntu-latest container: '{ "image": "ghcr.io/xrplf/ci/tools-rippled-pre-commit:sha-41ec7c1" }' diff --git a/.github/workflows/publish-docs.yml b/.github/workflows/publish-docs.yml index 637246ec40..d619be5543 100644 --- a/.github/workflows/publish-docs.yml +++ b/.github/workflows/publish-docs.yml @@ -47,7 +47,7 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Prepare runner - uses: XRPLF/actions/prepare-runner@2bbc2dc1abeec7bfaa886804ab86871ac201764e + uses: XRPLF/actions/prepare-runner@90f11ee655d1687824fb8793db770477d52afbab with: enable_ccache: false @@ -81,13 +81,13 @@ jobs: cmake --build . --target docs --parallel ${BUILD_NPROC} - name: Create documentation artifact - if: ${{ (github.repository_owner == 'XRPLF' || github.event.repository.visibility == 'public') && github.event_name == 'push' }} - uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b # v4.0.0 + if: ${{ github.event.repository.visibility == 'public' && github.event_name == 'push' }} + uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0 with: path: ${{ env.BUILD_DIR }}/docs/html deploy: - if: ${{ (github.repository_owner == 'XRPLF' || github.event.repository.visibility == 'public') && github.event_name == 'push' }} + if: ${{ github.repository == 'XRPLF/rippled' && github.event_name == 'push' }} needs: build runs-on: ubuntu-latest permissions: diff --git a/.github/workflows/reusable-build-test-config.yml b/.github/workflows/reusable-build-test-config.yml index 920b5b5278..c2c862d73f 100644 --- a/.github/workflows/reusable-build-test-config.yml +++ b/.github/workflows/reusable-build-test-config.yml @@ -107,7 +107,7 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Prepare runner - uses: XRPLF/actions/prepare-runner@2bbc2dc1abeec7bfaa886804ab86871ac201764e + uses: XRPLF/actions/prepare-runner@90f11ee655d1687824fb8793db770477d52afbab with: enable_ccache: ${{ inputs.ccache_enabled }} @@ -153,6 +153,32 @@ jobs: ${CMAKE_ARGS} \ .. + - name: Check protocol autogen files are up-to-date + working-directory: ${{ env.BUILD_DIR }} + env: + MESSAGE: | + + The generated protocol wrapper classes are out of date. + + This typically happens when the macro files or generator scripts + have changed but the generated files were not regenerated. + + To fix this: + 1. Run: cmake --build . --target setup_code_gen + 2. Run: cmake --build . --target code_gen + 3. Commit and push the regenerated files + run: | + set -e + cmake --build . --target setup_code_gen + cmake --build . --target code_gen + DIFF=$(git -C .. status --porcelain -- include/xrpl/protocol_autogen src/tests/libxrpl/protocol_autogen) + if [ -n "${DIFF}" ]; then + echo "::error::Generated protocol files are out of date" + git -C .. diff -- include/xrpl/protocol_autogen src/tests/libxrpl/protocol_autogen + echo "${MESSAGE}" + exit 1 + fi + - name: Build the binary working-directory: ${{ env.BUILD_DIR }} env: @@ -166,29 +192,6 @@ jobs: --parallel "${BUILD_NPROC}" \ --target "${CMAKE_TARGET}" - - name: Check protocol autogen files are up-to-date - env: - MESSAGE: | - - The generated protocol wrapper classes are out of date. - - This typically happens when your branch is behind develop and - the macro files or generator scripts have changed. - - To fix this: - 1. Update your branch from develop (merge or rebase) - 2. Build with code generation enabled (XRPL_NO_CODEGEN=OFF) - 3. Commit and push the regenerated files - run: | - set -e - DIFF=$(git status --porcelain -- include/xrpl/protocol_autogen src/tests/libxrpl/protocol_autogen) - if [ -n "${DIFF}" ]; then - echo "::error::Generated protocol files are out of date" - git diff -- include/xrpl/protocol_autogen src/tests/libxrpl/protocol_autogen - echo "${MESSAGE}" - exit 1 - fi - - name: Show ccache statistics if: ${{ inputs.ccache_enabled }} run: | @@ -199,14 +202,30 @@ jobs: fi - name: Upload the binary (Linux) - if: ${{ (github.repository_owner == 'XRPLF' || github.event.repository.visibility == 'public') && runner.os == 'Linux' }} - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + if: ${{ github.event.repository.visibility == 'public' && runner.os == 'Linux' }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: xrpld-${{ inputs.config_name }} path: ${{ env.BUILD_DIR }}/xrpld retention-days: 3 if-no-files-found: error + - name: Export server definitions + if: ${{ runner.os != 'Windows' && !inputs.build_only && env.VOIDSTAR_ENABLED != 'true' }} + working-directory: ${{ env.BUILD_DIR }} + run: | + set -o pipefail + ./xrpld --definitions | python3 -m json.tool > server_definitions.json + + - name: Upload server definitions + if: ${{ github.event.repository.visibility == 'public' && inputs.config_name == 'debian-bookworm-gcc-13-amd64-release' }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: server-definitions + path: ${{ env.BUILD_DIR }}/server_definitions.json + retention-days: 3 + if-no-files-found: error + - name: Check linking (Linux) if: ${{ runner.os == 'Linux' && env.SANITIZERS_ENABLED == 'false' }} working-directory: ${{ env.BUILD_DIR }} diff --git a/.github/workflows/reusable-check-rename.yml b/.github/workflows/reusable-check-rename.yml index 0e335ab9ca..56a1a3e637 100644 --- a/.github/workflows/reusable-check-rename.yml +++ b/.github/workflows/reusable-check-rename.yml @@ -33,6 +33,8 @@ jobs: run: .github/scripts/rename/config.sh . - name: Check include guards run: .github/scripts/rename/include.sh . + - name: Check documentation + run: .github/scripts/rename/docs.sh . - name: Check for differences env: MESSAGE: | diff --git a/.github/workflows/reusable-clang-tidy-files.yml b/.github/workflows/reusable-clang-tidy-files.yml index 81264cf24e..9b99f418b1 100644 --- a/.github/workflows/reusable-clang-tidy-files.yml +++ b/.github/workflows/reusable-clang-tidy-files.yml @@ -35,7 +35,7 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Prepare runner - uses: XRPLF/actions/prepare-runner@2bbc2dc1abeec7bfaa886804ab86871ac201764e + uses: XRPLF/actions/prepare-runner@90f11ee655d1687824fb8793db770477d52afbab with: enable_ccache: false @@ -80,18 +80,31 @@ jobs: env: TARGETS: ${{ inputs.files != '' && inputs.files || 'src tests' }} run: | - run-clang-tidy -j ${{ steps.nproc.outputs.nproc }} -p "${BUILD_DIR}" -quiet -allow-no-checks ${TARGETS} 2>&1 | tee clang-tidy-output.txt + run-clang-tidy -j ${{ steps.nproc.outputs.nproc }} -p "${BUILD_DIR}" -quiet -fix -allow-no-checks ${TARGETS} 2>&1 | tee clang-tidy-output.txt - name: Upload clang-tidy output - if: ${{ (github.repository_owner == 'XRPLF' || github.event.repository.visibility == 'public') && steps.run_clang_tidy.outcome != 'success' }} - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + if: ${{ github.event.repository.visibility == 'public' && steps.run_clang_tidy.outcome != 'success' }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: clang-tidy-results path: clang-tidy-output.txt retention-days: 30 + - name: Generate git diff + if: ${{ steps.run_clang_tidy.outcome != 'success' }} + run: | + git diff | tee clang-tidy-git-diff.txt + + - name: Upload clang-tidy diff output + if: ${{ github.event.repository.visibility == 'public' && steps.run_clang_tidy.outcome != 'success' }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: clang-tidy-git-diff + path: clang-tidy-git-diff.txt + retention-days: 30 + - name: Create an issue - if: steps.run_clang_tidy.outcome != 'success' && inputs.create_issue_on_failure + if: ${{ steps.run_clang_tidy.outcome != 'success' && inputs.create_issue_on_failure }} id: create_issue shell: bash env: @@ -156,7 +169,7 @@ jobs: rm -f create_issue.log issue.md clang-tidy-output.txt - name: Fail the workflow if clang-tidy failed - if: steps.run_clang_tidy.outcome != 'success' + if: ${{ steps.run_clang_tidy.outcome != 'success' }} run: | echo "Clang-tidy check failed!" exit 1 diff --git a/.github/workflows/reusable-clang-tidy.yml b/.github/workflows/reusable-clang-tidy.yml index 4c927dec9f..7a8bf6de57 100644 --- a/.github/workflows/reusable-clang-tidy.yml +++ b/.github/workflows/reusable-clang-tidy.yml @@ -31,7 +31,7 @@ jobs: - name: Get changed C++ files id: changed_files - uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5 + uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6 with: files: | **/*.cpp @@ -41,7 +41,7 @@ jobs: - name: Get changed clang-tidy configuration id: changed_clang_tidy - uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5 + uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6 with: files: | .clang-tidy diff --git a/.github/workflows/upload-conan-deps.yml b/.github/workflows/upload-conan-deps.yml index 832e455453..f14efde05b 100644 --- a/.github/workflows/upload-conan-deps.yml +++ b/.github/workflows/upload-conan-deps.yml @@ -70,7 +70,7 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Prepare runner - uses: XRPLF/actions/prepare-runner@2bbc2dc1abeec7bfaa886804ab86871ac201764e + uses: XRPLF/actions/prepare-runner@90f11ee655d1687824fb8793db770477d52afbab with: enable_ccache: false diff --git a/.gitignore b/.gitignore index 7ee6d0c70a..6bd34ece04 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ Debug/ Release/ /.build/ +/.venv/ /build/ /db/ /out.txt diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 59d42ccebc..1c0dc94550 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -17,10 +17,25 @@ repos: args: [--maxkb=400, --enforce-all] - id: trailing-whitespace - id: end-of-file-fixer - - id: mixed-line-ending - id: check-merge-conflict args: [--assume-in-merge] + - repo: local + hooks: + - id: clang-tidy + name: "clang-tidy (enable with: TIDY=1)" + entry: ./bin/pre-commit/clang_tidy_check.py + language: python + types_or: [c++, c] + exclude: ^include/xrpl/protocol_autogen + pass_filenames: false # script determines the staged files itself + - id: fix-include-style + name: fix include style + entry: ./bin/pre-commit/fix_include_style.py + language: python + types_or: [c++, c] + exclude: ^include/xrpl/protocol_autogen/(transactions|ledger_entries)/ + - repo: https://github.com/pre-commit/mirrors-clang-format rev: cd481d7b0bfb5c7b3090c21846317f9a8262e891 # frozen: v22.1.0 hooks: @@ -38,12 +53,19 @@ repos: rev: c2bc67fe8f8f549cc489e00ba8b45aa18ee713b1 # frozen: v3.8.1 hooks: - id: prettier + args: [--end-of-line=auto] - repo: https://github.com/psf/black-pre-commit-mirror rev: ea488cebbfd88a5f50b8bd95d5c829d0bb76feb8 # frozen: 26.1.0 hooks: - id: black + - repo: https://github.com/openstack/bashate + rev: 5798d24d571676fc407e81df574c1ef57b520f23 # frozen: 2.1.1 + hooks: + - id: bashate + args: ["--ignore=E006"] + - repo: https://github.com/streetsidesoftware/cspell-cli rev: a42085ade523f591dca134379a595e7859986445 # frozen: v9.7.0 hooks: diff --git a/API-CHANGELOG.md b/API-CHANGELOG.md index 64b61c2024..d5faaf70af 100644 --- a/API-CHANGELOG.md +++ b/API-CHANGELOG.md @@ -4,23 +4,23 @@ This changelog is intended to list all updates to the [public API methods](https For info about how [API versioning](https://xrpl.org/request-formatting.html#api-versioning) works, including examples, please view the [XLS-22d spec](https://github.com/XRPLF/XRPL-Standards/discussions/54). For details about the implementation of API versioning, view the [implementation PR](https://github.com/XRPLF/rippled/pull/3155). API versioning ensures existing integrations and users continue to receive existing behavior, while those that request a higher API version will experience new behavior. -The API version controls the API behavior you see. This includes what properties you see in responses, what parameters you're permitted to send in requests, and so on. You specify the API version in each of your requests. When a breaking change is introduced to the `rippled` API, a new version is released. To avoid breaking your code, you should set (or increase) your version when you're ready to upgrade. +The API version controls the API behavior you see. This includes what properties you see in responses, what parameters you're permitted to send in requests, and so on. You specify the API version in each of your requests. When a breaking change is introduced to the `xrpld` API, a new version is released. To avoid breaking your code, you should set (or increase) your version when you're ready to upgrade. The [commandline](https://xrpl.org/docs/references/http-websocket-apis/api-conventions/request-formatting/#commandline-format) always uses the latest API version. The command line is intended for ad-hoc usage by humans, not programs or automated scripts. The command line is not meant for use in production code. -For a log of breaking changes, see the **API Version [number]** headings. In general, breaking changes are associated with a particular API Version number. For non-breaking changes, scroll to the **XRP Ledger version [x.y.z]** headings. Non-breaking changes are associated with a particular XRP Ledger (`rippled`) release. +For a log of breaking changes, see the **API Version [number]** headings. In general, breaking changes are associated with a particular API Version number. For non-breaking changes, scroll to the **XRP Ledger version [x.y.z]** headings. Non-breaking changes are associated with a particular XRP Ledger (`xrpld`) release. ## API Version 3 (Beta) -API version 3 is currently a beta API. It requires enabling `[beta_rpc_api]` in the rippled configuration to use. See [API-VERSION-3.md](API-VERSION-3.md) for the full list of changes in API version 3. +API version 3 is currently a beta API. It requires enabling `[beta_rpc_api]` in the xrpld configuration to use. See [API-VERSION-3.md](API-VERSION-3.md) for the full list of changes in API version 3. ## API Version 2 -API version 2 is available in `rippled` version 2.0.0 and later. See [API-VERSION-2.md](API-VERSION-2.md) for the full list of changes in API version 2. +API version 2 is available in `xrpld` version 2.0.0 and later. See [API-VERSION-2.md](API-VERSION-2.md) for the full list of changes in API version 2. ## API Version 1 -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. +This version is supported by all `xrpld` versions. For WebSocket and HTTP JSON-RPC requests, it is currently the default API version used when no `api_version` is specified. ## Unreleased @@ -38,6 +38,8 @@ This section contains changes targeting a future version. ### Bugfixes - Peer Crawler: The `port` field in `overlay.active[]` now consistently returns an integer instead of a string for outbound peers. [#6318](https://github.com/XRPLF/rippled/pull/6318) +- `ping`: The `ip` field is no longer returned as an empty string for proxied connections without a forwarded-for header. It is now omitted, consistent with the behavior for identified connections. [#6730](https://github.com/XRPLF/rippled/pull/6730) +- gRPC `GetLedgerDiff`: Fixed error message that incorrectly said "base ledger not validated" when the desired ledger was not validated. [#6730](https://github.com/XRPLF/rippled/pull/6730) ## XRP Ledger server version 3.1.0 diff --git a/API-VERSION-2.md b/API-VERSION-2.md index 2296795271..eaf626099c 100644 --- a/API-VERSION-2.md +++ b/API-VERSION-2.md @@ -1,6 +1,6 @@ # API Version 2 -API version 2 is available in `rippled` version 2.0.0 and later. To use this API, clients specify `"api_version" : 2` in each request. +API version 2 is available in `xrpld` version 2.0.0 and later. To use this API, clients specify `"api_version" : 2` in each request. For info about how [API versioning](https://xrpl.org/request-formatting.html#api-versioning) works, including examples, please view the [XLS-22d spec](https://github.com/XRPLF/XRPL-Standards/discussions/54). For details about the implementation of API versioning, view the [implementation PR](https://github.com/XRPLF/rippled/pull/3155). API versioning ensures existing integrations and users continue to receive existing behavior, while those that request a higher API version will experience new behavior. diff --git a/API-VERSION-3.md b/API-VERSION-3.md index 46dc3f504d..57d42ce200 100644 --- a/API-VERSION-3.md +++ b/API-VERSION-3.md @@ -1,6 +1,6 @@ # API Version 3 -API version 3 is currently a **beta API**. It requires enabling `[beta_rpc_api]` in the rippled configuration to use. To use this API, clients specify `"api_version" : 3` in each request. +API version 3 is currently a **beta API**. It requires enabling `[beta_rpc_api]` in the xrpld configuration to use. To use this API, clients specify `"api_version" : 3` in each request. For info about how [API versioning](https://xrpl.org/request-formatting.html#api-versioning) works, including examples, please view the [XLS-22d spec](https://github.com/XRPLF/XRPL-Standards/discussions/54). For details about the implementation of API versioning, view the [implementation PR](https://github.com/XRPLF/rippled/pull/3155). API versioning ensures existing integrations and users continue to receive existing behavior, while those that request a higher API version will experience new behavior. diff --git a/BUILD.md b/BUILD.md index 757f76a716..cf0a685abd 100644 --- a/BUILD.md +++ b/BUILD.md @@ -459,6 +459,21 @@ install ccache --version 4.11.3 --allow-downgrade`. The location of `xrpld` binary in your build directory depends on your CMake generator. Pass `--help` to see the rest of the command line options. +## Code generation + +The protocol wrapper classes in `include/xrpl/protocol_autogen/` are generated +from macro definition files in `include/xrpl/protocol/detail/`. If you modify +the macro files (e.g. `transactions.macro`, `ledger_entries.macro`) or the +generation scripts/templates in `cmake/scripts/codegen/`, you need to regenerate the +files: + +``` +cmake --build . --target setup_code_gen # create venv and install dependencies (once) +cmake --build . --target code_gen # regenerate code +``` + +The regenerated files should be committed alongside your changes. + ## Coverage report The coverage report is intended for developers using compilers GCC @@ -603,8 +618,8 @@ If you want to experiment with a new package, follow these steps: `default_options` property (with syntax `'$package:$option': $value`). 3. Modify [`CMakeLists.txt`](./CMakeLists.txt): - Add a call to `find_package($package REQUIRED)`. - - Link a library from the package to the target `ripple_libs` - (search for the existing call to `target_link_libraries(ripple_libs INTERFACE ...)`). + - Link a library from the package to the target `xrpl_libs` + (search for the existing call to `target_link_libraries(xrpl_libs INTERFACE ...)`). 4. Start coding! Don't forget to include whatever headers you need from the package. [1]: https://github.com/conan-io/conan-center-index/issues/13168 diff --git a/CMakeLists.txt b/CMakeLists.txt index 33f68451c5..80ff8fec13 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -132,6 +132,7 @@ if(coverage) endif() include(XrplCore) +include(XrplProtocolAutogen) include(XrplInstall) include(XrplValidatorKeys) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d19222dbf0..56d3b48057 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -267,6 +267,26 @@ See the [environment setup guide](./docs/build/environment.md#clang-tidy) for pl 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. +#### Via pre-commit (recommended) + +If you have already installed the pre-commit hooks (see above), you can run clang-tidy on your staged files using: + +``` +TIDY=1 pre-commit run clang-tidy +``` + +This runs clang-tidy locally with the same configuration/flags as CI, scoped to your staged C++ files. The `TIDY=1` environment variable is required to opt in — without it the hook is skipped. + +You can also have clang-tidy run automatically on every `git commit` by setting `TIDY=1` in your shell environment: + +``` +export TIDY=1 +``` + +With this set, the hook will run as part of `git commit` alongside the other pre-commit checks. + +#### Manually + Then run clang-tidy on your local changes: ``` @@ -533,7 +553,7 @@ All releases, including release candidates and betas, are handled differently from typical PRs. Most importantly, never use the Github UI to merge a release. -Rippled uses a linear workflow model that can be summarized as: +Xrpld uses a linear workflow model that can be summarized as: 1. In between releases, developers work against the `develop` branch. 2. Periodically, a maintainer will build and tag a beta version from diff --git a/README.md b/README.md index dbc5ab078e..88c7943ebb 100644 --- a/README.md +++ b/README.md @@ -8,11 +8,11 @@ The [XRP Ledger](https://xrpl.org/) is a decentralized cryptographic ledger powe [XRP](https://xrpl.org/xrp.html) is a public, counterparty-free crypto-asset native to the XRP Ledger, and is designed as a gas token for network services and to bridge different currencies. XRP is traded on the open-market and is available for anyone to access. The XRP Ledger was created in 2012 with a finite supply of 100 billion units of XRP. -## rippled +## xrpld -The server software that powers the XRP Ledger is called `rippled` and is available in this repository under the permissive [ISC open-source license](LICENSE.md). The `rippled` server software is written primarily in C++ and runs on a variety of platforms. The `rippled` server software can run in several modes depending on its [configuration](https://xrpl.org/rippled-server-modes.html). +The server software that powers the XRP Ledger is called `xrpld` and is available in this repository under the permissive [ISC open-source license](LICENSE.md). The `xrpld` server software is written primarily in C++ and runs on a variety of platforms. The `xrpld` server software can run in several modes depending on its [configuration](https://xrpl.org/rippled-server-modes.html). -If you are interested in running an **API Server** (including a **Full History Server**), take a look at [Clio](https://github.com/XRPLF/clio). (rippled Reporting Mode has been replaced by Clio.) +If you are interested in running an **API Server** (including a **Full History Server**), take a look at [Clio](https://github.com/XRPLF/clio). (xrpld Reporting Mode has been replaced by Clio.) ### Build from Source @@ -41,19 +41,19 @@ If you are interested in running an **API Server** (including a **Full History S Here are some good places to start learning the source code: -- Read the markdown files in the source tree: `src/ripple/**/*.md`. +- Read the markdown files in the source tree: `src/xrpld/**/*.md`. - Read [the levelization document](.github/scripts/levelization) to get an idea of the internal dependency graph. - In the big picture, the `main` function constructs an `ApplicationImp` object, which implements the `Application` virtual interface. Almost every component in the application takes an `Application&` parameter in its constructor, typically named `app` and stored as a member variable `app_`. This allows most components to depend on any other component. ### Repository Contents -| Folder | Contents | -| :--------- | :----------------------------------------------- | -| `./bin` | Scripts and data files for Ripple integrators. | -| `./Builds` | Platform-specific guides for building `rippled`. | -| `./docs` | Source documentation files and doxygen config. | -| `./cfg` | Example configuration files. | -| `./src` | Source code. | +| Folder | Contents | +| :--------- | :--------------------------------------------- | +| `./bin` | Scripts and data files for XRPL developers. | +| `./Builds` | Platform-specific guides for building `xrpld`. | +| `./docs` | Source documentation files and doxygen config. | +| `./cfg` | Example configuration files. | +| `./src` | Source code. | Some of the directories under `src` are external repositories included using git-subtree. See those directories' README files for more details. diff --git a/SECURITY.md b/SECURITY.md index 1be412ae2a..2e0c43a134 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -6,7 +6,7 @@ For more details on operating an XRP Ledger server securely, please visit https: ## Supported Versions -Software constantly evolves. In order to focus resources, we only generally only accept vulnerability reports that affect recent and current versions of the software. We always accept reports for issues present in the **master**, **release** or **develop** branches, and with proposed, [open pull requests](https://github.com/ripple/rippled/pulls). +Software constantly evolves. In order to focus resources, we generally only accept vulnerability reports that affect recent and current versions of the software. We always accept reports for issues present in the **master**, **release** or **develop** branches, and with proposed, [open pull requests](https://github.com/XRPLF/rippled/pulls). ## Identifying and Reporting Vulnerabilities @@ -59,11 +59,11 @@ While we commit to responding with 24 hours of your initial report with our tria ## Bug Bounty Program -[Ripple](https://ripple.com) is generously sponsoring a bug bounty program for vulnerabilities in [`rippled`](https://github.com/XRPLF/rippled) (and other related projects, like [`xrpl.js`](https://github.com/XRPLF/xrpl.js), [`xrpl-py`](https://github.com/XRPLF/xrpl-py), [`xrpl4j`](https://github.com/XRPLF/xrpl4j)). +[Ripple](https://ripple.com) is generously sponsoring a bug bounty program for vulnerabilities in [`xrpld`](https://github.com/XRPLF/rippled) (and other related projects, like [`xrpl.js`](https://github.com/XRPLF/xrpl.js), [`xrpl-py`](https://github.com/XRPLF/xrpl-py), [`xrpl4j`](https://github.com/XRPLF/xrpl4j)). This program allows us to recognize and reward individuals or groups that identify and report bugs. In summary, in order to qualify for a bounty, the bug must be: -1. **In scope**. Only bugs in software under the scope of the program qualify. Currently, that means `rippled`, `xrpl.js`, `xrpl-py`, `xrpl4j`. +1. **In scope**. Only bugs in software under the scope of the program qualify. Currently, that means `xrpld`, `xrpl.js`, `xrpl-py`, `xrpl4j`. 2. **Relevant**. A security issue, posing a danger to user funds, privacy, or the operation of the XRP Ledger. 3. **Original and previously unknown**. Bugs that are already known and discussed in public do not qualify. Previously reported bugs, even if publicly unknown, are not eligible. 4. **Specific**. We welcome general security advice or recommendations, but we cannot pay bounties for that. diff --git a/bin/git/setup-upstreams.sh b/bin/git/setup-upstreams.sh index 61d8171569..57c3f935f9 100755 --- a/bin/git/setup-upstreams.sh +++ b/bin/git/setup-upstreams.sh @@ -1,14 +1,13 @@ #!/bin/bash -if [[ $# -ne 1 || "$1" == "--help" || "$1" == "-h" ]] -then - name=$( basename $0 ) - cat <<- USAGE - Usage: $name +if [[ $# -ne 1 || "$1" == "--help" || "$1" == "-h" ]]; then + name=$( basename $0 ) + cat <<- USAGE + Usage: $name - Where is the Github username of the upstream repo. e.g. XRPLF + Where is the Github username of the upstream repo. e.g. XRPLF USAGE - exit 0 + exit 0 fi # Create upstream remotes based on origin @@ -16,10 +15,9 @@ shift user="$1" # Get the origin URL. Expect it be an SSH-style URL origin=$( git remote get-url origin ) -if [[ "${origin}" == "" ]] -then - echo Invalid origin remote >&2 - exit 1 +if [[ "${origin}" == "" ]]; then + echo Invalid origin remote >&2 + exit 1 fi # echo "Origin: ${origin}" # Parse the origin @@ -30,11 +28,9 @@ IFS='@' read sshuser server <<< "${remote}" # echo "SSHUser: ${sshuser}, Server: ${server}" IFS='/' read originuser repo <<< "${originpath}" # echo "Originuser: ${originuser}, Repo: ${repo}" -if [[ "${sshuser}" == "" || "${server}" == "" || "${originuser}" == "" - || "${repo}" == "" ]] -then - echo "Can't parse origin URL: ${origin}" >&2 - exit 1 +if [[ "${sshuser}" == "" || "${server}" == "" || "${originuser}" == "" || "${repo}" == "" ]]; then + echo "Can't parse origin URL: ${origin}" >&2 + exit 1 fi upstream="https://${server}/${user}/${repo}" upstreampush="${remote}:${user}/${repo}" @@ -42,42 +38,34 @@ upstreamgroup="upstream upstream-push" current=$( git remote get-url upstream 2>/dev/null ) currentpush=$( git remote get-url upstream-push 2>/dev/null ) currentgroup=$( git config remotes.upstreams ) -if [[ "${current}" == "${upstream}" ]] -then - echo "Upstream already set up correctly. Skip" -elif [[ -n "${current}" && "${current}" != "${upstream}" && - "${current}" != "${upstreampush}" ]] -then - echo "Upstream already set up as: ${current}. Skip" +if [[ "${current}" == "${upstream}" ]]; then + echo "Upstream already set up correctly. Skip" +elif [[ -n "${current}" && "${current}" != "${upstream}" && "${current}" != "${upstreampush}" ]]; then + echo "Upstream already set up as: ${current}. Skip" else - if [[ "${current}" == "${upstreampush}" ]] - then - echo "Upstream set to dangerous push URL. Update." - _run git remote rename upstream upstream-push || \ - _run git remote remove upstream - currentpush=$( git remote get-url upstream-push 2>/dev/null ) - fi - _run git remote add upstream "${upstream}" + if [[ "${current}" == "${upstreampush}" ]]; then + echo "Upstream set to dangerous push URL. Update." + _run git remote rename upstream upstream-push || \ + _run git remote remove upstream + currentpush=$( git remote get-url upstream-push 2>/dev/null ) + fi + _run git remote add upstream "${upstream}" fi -if [[ "${currentpush}" == "${upstreampush}" ]] -then - echo "upstream-push already set up correctly. Skip" -elif [[ -n "${currentpush}" && "${currentpush}" != "${upstreampush}" ]] -then - echo "upstream-push already set up as: ${currentpush}. Skip" +if [[ "${currentpush}" == "${upstreampush}" ]]; then + echo "upstream-push already set up correctly. Skip" +elif [[ -n "${currentpush}" && "${currentpush}" != "${upstreampush}" ]]; then + echo "upstream-push already set up as: ${currentpush}. Skip" else - _run git remote add upstream-push "${upstreampush}" + _run git remote add upstream-push "${upstreampush}" fi -if [[ "${currentgroup}" == "${upstreamgroup}" ]] -then - echo "Upstreams group already set up correctly. Skip" -elif [[ -n "${currentgroup}" && "${currentgroup}" != "${upstreamgroup}" ]] -then - echo "Upstreams group already set up as: ${currentgroup}. Skip" +if [[ "${currentgroup}" == "${upstreamgroup}" ]]; then + echo "Upstreams group already set up correctly. Skip" +elif [[ -n "${currentgroup}" && "${currentgroup}" != "${upstreamgroup}" ]]; then + echo "Upstreams group already set up as: ${currentgroup}. Skip" else - _run git config --add remotes.upstreams "${upstreamgroup}" + _run git config --add remotes.upstreams "${upstreamgroup}" fi _run git fetch --jobs=$(nproc) upstreams diff --git a/bin/git/squash-branches.sh b/bin/git/squash-branches.sh index 4dcbf5aaa1..eb4aefe23c 100755 --- a/bin/git/squash-branches.sh +++ b/bin/git/squash-branches.sh @@ -1,17 +1,16 @@ #!/bin/bash -if [[ $# -lt 3 || "$1" == "--help" || "$1" = "-h" ]] -then - name=$( basename $0 ) - cat <<- USAGE - Usage: $name workbranch base/branch user/branch [user/branch [...]] +if [[ $# -lt 3 || "$1" == "--help" || "$1" = "-h" ]]; then + name=$( basename $0 ) + cat <<- USAGE + Usage: $name workbranch base/branch user/branch [user/branch [...]] - * workbranch will be created locally from base/branch - * base/branch and user/branch may be specified as user:branch to allow - easy copying from Github PRs - * Remotes for each user must already be set up + * workbranch will be created locally from base/branch + * base/branch and user/branch may be specified as user:branch to allow + easy copying from Github PRs + * Remotes for each user must already be set up USAGE -exit 0 + exit 0 fi work="$1" @@ -24,9 +23,8 @@ unset branches[0] set -e users=() -for b in "${branches[@]}" -do - users+=( $( echo $b | cut -d/ -f1 ) ) +for b in "${branches[@]}"; do + users+=( $( echo $b | cut -d/ -f1 ) ) done users=( $( printf '%s\n' "${users[@]}" | sort -u ) ) @@ -34,10 +32,9 @@ users=( $( printf '%s\n' "${users[@]}" | sort -u ) ) git fetch --multiple upstreams "${users[@]}" git checkout -B "$work" --no-track "$base" -for b in "${branches[@]}" -do - git merge --squash "${b}" - git commit -S # Use the commit message provided on the PR +for b in "${branches[@]}"; do + git merge --squash "${b}" + git commit -S # Use the commit message provided on the PR done # Make sure the commits look right @@ -47,13 +44,11 @@ parts=( $( echo $base | sed "s/\// /" ) ) repo="${parts[0]}" b="${parts[1]}" push=$repo -if [[ "$push" == "upstream" ]] -then - push="upstream-push" +if [[ "$push" == "upstream" ]]; then + push="upstream-push" fi -if [[ "$repo" == "upstream" ]] -then - repo="upstreams" +if [[ "$repo" == "upstream" ]]; then + repo="upstreams" fi cat << PUSH diff --git a/bin/git/update-version.sh b/bin/git/update-version.sh index c901a29e6a..f74b40ddf0 100755 --- a/bin/git/update-version.sh +++ b/bin/git/update-version.sh @@ -1,17 +1,16 @@ #!/bin/bash -if [[ $# -ne 3 || "$1" == "--help" || "$1" = "-h" ]] -then - name=$( basename $0 ) - cat <<- USAGE - Usage: $name workbranch base/branch version +if [[ $# -ne 3 || "$1" == "--help" || "$1" = "-h" ]]; then + name=$( basename $0 ) + cat <<- USAGE + Usage: $name workbranch base/branch version - * workbranch will be created locally from base/branch. If it exists, - it will be reused, so make sure you don't overwrite any work. - * base/branch may be specified as user:branch to allow easy copying - from Github PRs. + * workbranch will be created locally from base/branch. If it exists, + it will be reused, so make sure you don't overwrite any work. + * base/branch may be specified as user:branch to allow easy copying + from Github PRs. USAGE -exit 0 + exit 0 fi work="$1" @@ -30,10 +29,9 @@ git fetch upstreams git checkout -B "${work}" --no-track "${base}" push=$( git rev-parse --abbrev-ref --symbolic-full-name '@{push}' \ - 2>/dev/null ) || true -if [[ "${push}" != "" ]] -then - echo "Warning: ${push} may already exist." + 2>/dev/null ) || true +if [[ "${push}" != "" ]]; then + echo "Warning: ${push} may already exist." fi build=$( find -name BuildInfo.cpp ) diff --git a/bin/pre-commit/clang_tidy_check.py b/bin/pre-commit/clang_tidy_check.py new file mode 100755 index 0000000000..7fb51d1c46 --- /dev/null +++ b/bin/pre-commit/clang_tidy_check.py @@ -0,0 +1,206 @@ +#!/usr/bin/env python3 +"""Pre-commit hook that runs clang-tidy on changed files using run-clang-tidy.""" + +from __future__ import annotations + +import json +import os +import re +import shutil +import subprocess +import sys +from collections import defaultdict +from pathlib import Path + +HEADER_EXTENSIONS = {".h", ".hpp", ".ipp"} +SOURCE_EXTENSIONS = {".cpp"} +INCLUDE_RE = re.compile(r"^\s*#\s*include\s*[<\"]([^>\"]+)[>\"]") + + +def find_run_clang_tidy() -> str | None: + for candidate in ("run-clang-tidy-21", "run-clang-tidy"): + if path := shutil.which(candidate): + return path + return None + + +def find_build_dir(repo_root: Path) -> Path | None: + for name in (".build", "build"): + candidate = repo_root / name + if (candidate / "compile_commands.json").exists(): + return candidate + return None + + +def build_include_graph(build_dir: Path, repo_root: Path) -> tuple[dict, set]: + """ + Scan all files reachable from compile_commands.json and build an inverted include graph. + + Returns: + inverted: header_path -> set of files that include it + source_files: set of all TU paths from compile_commands.json + """ + with open(build_dir / "compile_commands.json") as f: + db = json.load(f) + + source_files = {Path(e["file"]).resolve() for e in db} + include_roots = [repo_root / "include", repo_root / "src"] + inverted: dict[Path, set[Path]] = defaultdict(set) + + to_scan: set[Path] = set(source_files) + scanned: set[Path] = set() + + while to_scan: + file = to_scan.pop() + if file in scanned or not file.exists(): + continue + scanned.add(file) + + content = file.read_text() + + for line in content.splitlines(): + m = INCLUDE_RE.match(line) + if not m: + continue + for root in include_roots: + candidate = (root / m.group(1)).resolve() + if candidate.exists(): + inverted[candidate].add(file) + if candidate not in scanned: + to_scan.add(candidate) + break + + return inverted, source_files + + +def find_tus_for_headers( + headers: list[Path], + inverted: dict[Path, set[Path]], + source_files: set[Path], +) -> set[Path]: + """ + For each header, pick one TU that transitively includes it. + Prefers a TU whose stem matches the header's stem, otherwise picks the first found. + """ + result: set[Path] = set() + + for header in headers: + preferred: Path | None = None + visited: set[Path] = {header} + stack: list[Path] = [header] + + while stack: + h = stack.pop() + for inc in inverted.get(h, ()): + if inc in source_files: + if inc.stem == header.stem: + preferred = inc + break + if preferred is None: + preferred = inc + if inc not in visited: + visited.add(inc) + stack.append(inc) + if preferred is not None and preferred.stem == header.stem: + break + + if preferred is not None: + result.add(preferred) + + return result + + +def resolve_files( + input_files: list[str], build_dir: Path, repo_root: Path +) -> list[str]: + """ + Split input into source files and headers. Source files are passed through; + headers are resolved to the TUs that transitively include them. + """ + sources: list[Path] = [] + headers: list[Path] = [] + + for f in input_files: + p = Path(f).resolve() + if p.suffix in SOURCE_EXTENSIONS: + sources.append(p) + elif p.suffix in HEADER_EXTENSIONS: + headers.append(p) + + if not headers: + return [str(p) for p in sources] + + print( + f"Resolving {len(headers)} header(s) to compilation units...", file=sys.stderr + ) + inverted, source_files = build_include_graph(build_dir, repo_root) + tus = find_tus_for_headers(headers, inverted, source_files) + + if not tus: + print( + "Warning: no compilation units found that include the modified headers; " + "skipping clang-tidy for headers.", + file=sys.stderr, + ) + + return sorted({str(p) for p in (*sources, *tus)}) + + +def staged_files(repo_root: Path) -> list[str]: + result = subprocess.run( + ["git", "diff", "--staged", "--name-only", "--diff-filter=d"], + capture_output=True, + text=True, + cwd=repo_root, + ) + if result.returncode != 0: + print( + "clang-tidy check failed: 'git diff --staged' command failed.", + file=sys.stderr, + ) + if result.stderr: + print(result.stderr, file=sys.stderr) + sys.exit(result.returncode or 1) + return [str(repo_root / p) for p in result.stdout.splitlines() if p] + + +def main(): + if not os.environ.get("TIDY"): + return 0 + + repo_root = Path(__file__).parent.parent + files = staged_files(repo_root) + if not files: + return 0 + + run_clang_tidy = find_run_clang_tidy() + if not run_clang_tidy: + print( + "clang-tidy check failed: TIDY is enabled but neither " + "'run-clang-tidy-21' nor 'run-clang-tidy' was found in PATH.", + file=sys.stderr, + ) + return 1 + + build_dir = find_build_dir(repo_root) + if not build_dir: + print( + "clang-tidy check failed: no build directory with compile_commands.json found " + "(looked for .build/ and build/)", + file=sys.stderr, + ) + return 1 + + tidy_files = resolve_files(files, build_dir, repo_root) + if not tidy_files: + return 0 + + result = subprocess.run( + [run_clang_tidy, "-quiet", "-p", str(build_dir), "-fix", "-allow-no-checks"] + + tidy_files + ) + return result.returncode + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/bin/pre-commit/fix_include_style.py b/bin/pre-commit/fix_include_style.py new file mode 100755 index 0000000000..ca59107271 --- /dev/null +++ b/bin/pre-commit/fix_include_style.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 + +""" +Converts quoted includes (#include "...") to angle-bracket includes +(#include <...>), which is the required style in this project. + +Usage: ./bin/pre-commit/fix_include_style.py ... +""" + +import re +import sys +from pathlib import Path + +PATTERN = re.compile(r'^(\s*#include\s*)"([^"]+)"', re.MULTILINE) + + +def fix_includes(path: Path) -> bool: + original = path.read_text(encoding="utf-8") + fixed = PATTERN.sub(r"\1<\2>", original) + if fixed != original: + path.write_text(fixed, encoding="utf-8") + return False + return True + + +def main() -> int: + files = [Path(f) for f in sys.argv[1:]] + success = True + + for path in files: + success &= fix_includes(path) + + return 0 if success else 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/cfg/validators-example.txt b/cfg/validators-example.txt index 6eb49da697..384db924f4 100644 --- a/cfg/validators-example.txt +++ b/cfg/validators-example.txt @@ -28,7 +28,7 @@ # https://vl.ripple.com # https://unl.xrplf.org # http://127.0.0.1:8000 -# file:///etc/opt/ripple/vl.txt +# file:///etc/opt/xrpld/vl.txt # # [validator_list_keys] # @@ -43,11 +43,11 @@ # ED307A760EE34F2D0CAA103377B1969117C38B8AA0AA1E2A24DAC1F32FC97087ED # -# The default validator list publishers that the rippled instance +# The default validator list publishers that the xrpld instance # trusts. # -# WARNING: Changing these values can cause your rippled instance to see a -# validated ledger that contradicts other rippled instances' +# WARNING: Changing these values can cause your xrpld instance to see a +# validated ledger that contradicts other xrpld instances' # validated ledgers (aka a ledger fork) if your validator list(s) # do not sufficiently overlap with the list(s) used by others. # See: https://arxiv.org/pdf/1802.07242.pdf diff --git a/cfg/xrpld-example.cfg b/cfg/xrpld-example.cfg index 995d4e65ff..4b17bf0500 100644 --- a/cfg/xrpld-example.cfg +++ b/cfg/xrpld-example.cfg @@ -9,7 +9,7 @@ # # 2. Peer Protocol # -# 3. Ripple Protocol +# 3. XRPL protocol # # 4. HTTPS Client # @@ -383,7 +383,7 @@ # # These settings control security and access attributes of the Peer to Peer # server section of the xrpld process. Peer Protocol implements the -# Ripple Payment protocol. It is over peer connections that transactions +# XRPL payment protocol. It is over peer connections that transactions # and validations are passed from to machine to machine, to determine the # contents of validated ledgers. # @@ -406,7 +406,7 @@ # # [ips] # -# List of hostnames or ips where the Ripple protocol is served. A default +# List of hostnames or ips where the XRPL protocol is served. A default # starter list is included in the code and used if no other hostnames are # available. # @@ -435,7 +435,7 @@ # List of IP addresses or hostnames to which xrpld should always attempt to # maintain peer connections with. This is useful for manually forming private # networks, for example to configure a validation server that connects to the -# Ripple network through a public-facing server, or for building a set +# XRPL network through a public-facing server, or for building a set # of cluster peers. # # One address or domain names per line is allowed. A port must be specified @@ -748,8 +748,8 @@ # the folder in which the xrpld.cfg file is located. # # Examples: -# /home/ripple/validators.txt -# C:/home/ripple/validators.txt +# /home/username/validators.txt +# C:/home/username/validators.txt # # Example content: # [validators] @@ -840,7 +840,7 @@ # # 0: Disable the ledger replay feature [default] # 1: Enable the ledger replay feature. With this feature enabled, when -# acquiring a ledger from the network, a xrpld node only downloads +# acquiring a ledger from the network, an xrpld node only downloads # the ledger header and the transactions instead of the whole ledger. # And the ledger is built by applying the transactions to the parent # ledger. @@ -853,7 +853,7 @@ # # The xrpld server instance uses HTTPS GET requests in a variety of # circumstances, including but not limited to contacting trusted domains to -# fetch information such as mapping an email address to a Ripple Payment +# fetch information such as mapping an email address to an XRPL payment # Network address. # # [ssl_verify] @@ -1227,7 +1227,7 @@ # #---------- # -# The vote settings configure settings for the entire Ripple network. +# The vote settings configure settings for the entire XRPL network. # While a single instance of xrpld cannot unilaterally enforce network-wide # settings, these choices become part of the instance's vote during the # consensus process for each voting ledger. @@ -1416,6 +1416,12 @@ # in this section to a comma-separated list of the addresses # of your Clio servers, in order to bypass xrpld's rate limiting. # +# TLS/SSL can be enabled for gRPC by specifying ssl_cert and ssl_key. +# Both parameters must be provided together. The ssl_cert_chain parameter +# is optional and provides intermediate CA certificates for the certificate +# chain. The ssl_client_ca parameter is optional and enables mutual TLS +# (client certificate verification). +# # This port is commented out but can be enabled by removing # the '#' from each corresponding line including the entry under [server] # @@ -1465,11 +1471,74 @@ admin = 127.0.0.1 protocol = ws send_queue_limit = 500 +# gRPC TLS/SSL Configuration +# +# The gRPC port supports optional TLS/SSL encryption. When TLS is not +# configured, the gRPC server will accept unencrypted connections. +# +# ssl_cert = +# ssl_key = +# +# To enable TLS for gRPC, both ssl_cert and ssl_key must be specified. +# If only one is provided, xrpld will fail to start. +# +# ssl_cert: Path to the server's SSL certificate file in PEM format. +# ssl_key: Path to the server's SSL private key file in PEM format. +# +# When configured, the gRPC server will only accept TLS-encrypted +# connections. Clients must use TLS (secure) channel credentials rather +# than plaintext / insecure connections. +# +# ssl_cert_chain = +# +# Optional. Path to intermediate CA certificate(s) in PEM format that +# complete the server's certificate chain. +# +# This file should contain the intermediate CA certificate(s) needed +# to build a trust chain from the server certificate (ssl_cert) to a +# root CA that clients trust. Multiple certificates should be +# concatenated in PEM format. +# +# This is needed when your server certificate was signed by an +# intermediate CA rather than directly by a root CA. Without this, +# clients may fail to verify your server certificate. +# +# If not specified, only the server certificate from ssl_cert will be +# presented to clients. +# +# ssl_client_ca = +# +# Optional. Path to a CA certificate file in PEM format for verifying +# client certificates (mutual TLS / mTLS). +# +# When specified, the gRPC server will verify client certificates +# against this CA. This enables mutual authentication where both the +# server and client verify each other's identity. +# +# This is typically NOT needed for public-facing gRPC servers. Only +# use this if you want to restrict access to clients with valid +# certificates signed by the specified CA. +# +# If not specified, the server will use one-way TLS (server +# authentication only) and will accept connections from any client. +# [port_grpc] port = 50051 ip = 127.0.0.1 secure_gateway = 127.0.0.1 +# Optional TLS/SSL configuration for gRPC +# To enable TLS, uncomment and configure both ssl_cert and ssl_key: +#ssl_cert = /etc/ssl/certs/grpc-server.crt +#ssl_key = /etc/ssl/private/grpc-server.key + +# Optional: Include intermediate CA certificates for complete certificate chain +#ssl_cert_chain = /etc/ssl/certs/grpc-intermediate-ca.crt + +# Optional: Enable mutual TLS (client certificate verification) +# Uncomment to require and verify client certificates: +#ssl_client_ca = /etc/ssl/certs/grpc-client-ca.crt + #[port_ws_public] #port = 6005 #ip = 127.0.0.1 diff --git a/cmake/XrplCore.cmake b/cmake/XrplCore.cmake index a50e30f660..9b1dc74049 100644 --- a/cmake/XrplCore.cmake +++ b/cmake/XrplCore.cmake @@ -108,24 +108,12 @@ target_link_libraries( ) # Level 05 -## Set up code generation for protocol_autogen module -include(XrplProtocolAutogen) -# Must call setup_protocol_autogen before add_module so that: -# 1. Stale generated files are cleared before GLOB runs -# 2. Output file list is known for custom commands -setup_protocol_autogen() - add_module(xrpl protocol_autogen) target_link_libraries( xrpl.libxrpl.protocol_autogen PUBLIC xrpl.libxrpl.protocol ) -# Ensure code generation runs before compiling protocol_autogen -if(TARGET protocol_autogen_generate) - add_dependencies(xrpl.libxrpl.protocol_autogen protocol_autogen_generate) -endif() - # Level 06 add_module(xrpl core) target_link_libraries( diff --git a/cmake/XrplProtocolAutogen.cmake b/cmake/XrplProtocolAutogen.cmake index e48d28959d..dd9ef6a9a4 100644 --- a/cmake/XrplProtocolAutogen.cmake +++ b/cmake/XrplProtocolAutogen.cmake @@ -2,308 +2,145 @@ Protocol Autogen - Code generation for protocol wrapper classes #]===================================================================] -# Options for code generation -option( - XRPL_NO_CODEGEN - "Disable code generation (use pre-generated files from repository)" - OFF -) set(CODEGEN_VENV_DIR - "" + "${CMAKE_CURRENT_SOURCE_DIR}/.venv" CACHE PATH - "Path to Python virtual environment for code generation. If provided, automatic venv setup is skipped." + "Path to a Python virtual environment for code generation. A venv will be created here by setup_code_gen and used to run generation scripts." ) -# Function to set up code generation for protocol_autogen module -# This runs at configure time to generate C++ wrapper classes from macro files -function(setup_protocol_autogen) - # Directory paths - set(MACRO_DIR "${CMAKE_CURRENT_SOURCE_DIR}/include/xrpl/protocol/detail") - set(AUTOGEN_HEADER_DIR - "${CMAKE_CURRENT_SOURCE_DIR}/include/xrpl/protocol_autogen" - ) - set(AUTOGEN_TEST_DIR - "${CMAKE_CURRENT_SOURCE_DIR}/src/tests/libxrpl/protocol_autogen" - ) - set(SCRIPTS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/scripts") +# Directory paths +set(MACRO_DIR "${CMAKE_CURRENT_SOURCE_DIR}/include/xrpl/protocol/detail") +set(AUTOGEN_HEADER_DIR + "${CMAKE_CURRENT_SOURCE_DIR}/include/xrpl/protocol_autogen" +) +set(AUTOGEN_TEST_DIR + "${CMAKE_CURRENT_SOURCE_DIR}/src/tests/libxrpl/protocol_autogen" +) +set(SCRIPTS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/cmake/scripts/codegen") - # Input macro files - set(TRANSACTIONS_MACRO "${MACRO_DIR}/transactions.macro") - set(LEDGER_ENTRIES_MACRO "${MACRO_DIR}/ledger_entries.macro") - set(SFIELDS_MACRO "${MACRO_DIR}/sfields.macro") +# Input macro files +set(TRANSACTIONS_MACRO "${MACRO_DIR}/transactions.macro") +set(LEDGER_ENTRIES_MACRO "${MACRO_DIR}/ledger_entries.macro") +set(SFIELDS_MACRO "${MACRO_DIR}/sfields.macro") - # Python scripts and templates - set(GENERATE_TX_SCRIPT "${SCRIPTS_DIR}/generate_tx_classes.py") - set(GENERATE_LEDGER_SCRIPT "${SCRIPTS_DIR}/generate_ledger_classes.py") - set(REQUIREMENTS_FILE "${SCRIPTS_DIR}/requirements.txt") - set(MACRO_PARSER_COMMON "${SCRIPTS_DIR}/macro_parser_common.py") - set(TX_TEMPLATE "${SCRIPTS_DIR}/templates/Transaction.h.mako") - set(TX_TEST_TEMPLATE "${SCRIPTS_DIR}/templates/TransactionTests.cpp.mako") - set(LEDGER_TEMPLATE "${SCRIPTS_DIR}/templates/LedgerEntry.h.mako") - set(LEDGER_TEST_TEMPLATE - "${SCRIPTS_DIR}/templates/LedgerEntryTests.cpp.mako" +# Python scripts and templates +set(GENERATE_TX_SCRIPT "${SCRIPTS_DIR}/generate_tx_classes.py") +set(GENERATE_LEDGER_SCRIPT "${SCRIPTS_DIR}/generate_ledger_classes.py") +set(REQUIREMENTS_FILE "${SCRIPTS_DIR}/requirements.txt") +set(MACRO_PARSER_COMMON "${SCRIPTS_DIR}/macro_parser_common.py") +set(TX_TEMPLATE "${SCRIPTS_DIR}/templates/Transaction.h.mako") +set(TX_TEST_TEMPLATE "${SCRIPTS_DIR}/templates/TransactionTests.cpp.mako") +set(LEDGER_TEMPLATE "${SCRIPTS_DIR}/templates/LedgerEntry.h.mako") +set(LEDGER_TEST_TEMPLATE "${SCRIPTS_DIR}/templates/LedgerEntryTests.cpp.mako") +set(ALL_INPUT_FILES + "${TRANSACTIONS_MACRO}" + "${LEDGER_ENTRIES_MACRO}" + "${SFIELDS_MACRO}" + "${GENERATE_TX_SCRIPT}" + "${GENERATE_LEDGER_SCRIPT}" + "${REQUIREMENTS_FILE}" + "${MACRO_PARSER_COMMON}" + "${TX_TEMPLATE}" + "${TX_TEST_TEMPLATE}" + "${LEDGER_TEMPLATE}" + "${LEDGER_TEST_TEMPLATE}" +) + +# Create output directories +file(MAKE_DIRECTORY "${AUTOGEN_HEADER_DIR}/transactions") +file(MAKE_DIRECTORY "${AUTOGEN_HEADER_DIR}/ledger_entries") +file(MAKE_DIRECTORY "${AUTOGEN_TEST_DIR}/ledger_entries") +file(MAKE_DIRECTORY "${AUTOGEN_TEST_DIR}/transactions") + +# Find Python3 +if(NOT Python3_EXECUTABLE) + find_package(Python3 COMPONENTS Interpreter QUIET) +endif() + +if(NOT Python3_EXECUTABLE) + find_program(Python3_EXECUTABLE NAMES python3 python) +endif() + +if(NOT Python3_EXECUTABLE) + message( + WARNING + "Python3 not found. The 'code_gen' and 'setup_code_gen' targets will not be available." ) + return() +endif() - # Check if code generation is disabled - if(XRPL_NO_CODEGEN) +# Warn if pip is configured with a non-default index (may need VPN). +execute_process( + COMMAND ${Python3_EXECUTABLE} -m pip config get global.index-url + OUTPUT_VARIABLE PIP_INDEX_URL + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET + RESULT_VARIABLE PIP_CONFIG_RESULT +) +if(PIP_CONFIG_RESULT EQUAL 0 AND PIP_INDEX_URL) + if( + NOT PIP_INDEX_URL STREQUAL "https://pypi.org/simple" + AND NOT PIP_INDEX_URL STREQUAL "https://pypi.python.org/simple" + ) message( WARNING - "Protocol autogen: Code generation is disabled (XRPL_NO_CODEGEN=ON). " - "Generated files may be out of date." + "Private pip index URL detected: ${PIP_INDEX_URL}\n" + "You may need to connect to VPN to access this URL." ) - return() endif() +endif() - # Create output directories - file(MAKE_DIRECTORY "${AUTOGEN_HEADER_DIR}/transactions") - file(MAKE_DIRECTORY "${AUTOGEN_HEADER_DIR}/ledger_entries") - file(MAKE_DIRECTORY "${AUTOGEN_TEST_DIR}/ledger_entries") - file(MAKE_DIRECTORY "${AUTOGEN_TEST_DIR}/transactions") - - # Find Python3 - check if already found by Conan or find it ourselves - if(NOT Python3_EXECUTABLE) - find_package(Python3 COMPONENTS Interpreter QUIET) - endif() - - if(NOT Python3_EXECUTABLE) - # Try finding python3 executable directly - find_program(Python3_EXECUTABLE NAMES python3 python) - endif() - - if(NOT Python3_EXECUTABLE) - message( - FATAL_ERROR - "Python3 not found. Code generation cannot proceed.\n" - "Please install Python 3, or set -DXRPL_NO_CODEGEN=ON to use existing generated files." - ) - return() - endif() - - message(STATUS "Using Python3 for code generation: ${Python3_EXECUTABLE}") - - # Set up Python virtual environment for code generation - if(CODEGEN_VENV_DIR) - # User-provided venv - skip automatic setup - set(VENV_DIR "${CODEGEN_VENV_DIR}") - message(STATUS "Using user-provided Python venv: ${VENV_DIR}") - else() - # Use default venv in build directory - set(VENV_DIR "${CMAKE_CURRENT_BINARY_DIR}/codegen_venv") - endif() - - # Determine the Python executable path in the venv +# Determine which Python interpreter to use for code generation. +if(CODEGEN_VENV_DIR) if(WIN32) - set(VENV_PYTHON "${VENV_DIR}/Scripts/python.exe") - set(VENV_PIP "${VENV_DIR}/Scripts/pip.exe") + set(CODEGEN_PYTHON "${CODEGEN_VENV_DIR}/Scripts/python.exe") else() - set(VENV_PYTHON "${VENV_DIR}/bin/python") - set(VENV_PIP "${VENV_DIR}/bin/pip") + set(CODEGEN_PYTHON "${CODEGEN_VENV_DIR}/bin/python") endif() - - # Only auto-setup venv if not user-provided - if(NOT CODEGEN_VENV_DIR) - # Check if venv needs to be created or updated - set(VENV_NEEDS_UPDATE FALSE) - if(NOT EXISTS "${VENV_PYTHON}") - set(VENV_NEEDS_UPDATE TRUE) - message( - STATUS - "Creating Python virtual environment for code generation..." - ) - elseif( - "${REQUIREMENTS_FILE}" - IS_NEWER_THAN - "${VENV_DIR}/.requirements_installed" - ) - set(VENV_NEEDS_UPDATE TRUE) - message( - STATUS - "Updating Python virtual environment (requirements changed)..." - ) - endif() - - # Create/update virtual environment if needed - if(VENV_NEEDS_UPDATE) - message( - STATUS - "Setting up Python virtual environment at ${VENV_DIR}" - ) - execute_process( - COMMAND ${Python3_EXECUTABLE} -m venv "${VENV_DIR}" - RESULT_VARIABLE VENV_RESULT - ERROR_VARIABLE VENV_ERROR - ) - if(NOT VENV_RESULT EQUAL 0) - message( - FATAL_ERROR - "Failed to create virtual environment: ${VENV_ERROR}" - ) - endif() - - # Check pip index URL configuration - execute_process( - COMMAND ${VENV_PIP} config get global.index-url - OUTPUT_VARIABLE PIP_INDEX_URL - OUTPUT_STRIP_TRAILING_WHITESPACE - ERROR_QUIET - ) - - # Default PyPI URL - set(DEFAULT_PIP_INDEX "https://pypi.org/simple") - - # Show warning if using non-default index - if(PIP_INDEX_URL AND NOT PIP_INDEX_URL STREQUAL "") - if(NOT PIP_INDEX_URL STREQUAL DEFAULT_PIP_INDEX) - message( - WARNING - "Private pip index URL detected: ${PIP_INDEX_URL}\n" - "You may need to connect to VPN to access this URL." - ) - endif() - endif() - - message(STATUS "Installing Python dependencies...") - execute_process( - COMMAND ${VENV_PIP} install --upgrade pip - RESULT_VARIABLE PIP_UPGRADE_RESULT - OUTPUT_QUIET - ERROR_VARIABLE PIP_UPGRADE_ERROR - ) - if(NOT PIP_UPGRADE_RESULT EQUAL 0) - message(WARNING "Failed to upgrade pip: ${PIP_UPGRADE_ERROR}") - endif() - - execute_process( - COMMAND ${VENV_PIP} install -r "${REQUIREMENTS_FILE}" - RESULT_VARIABLE PIP_INSTALL_RESULT - ERROR_VARIABLE PIP_INSTALL_ERROR - ) - if(NOT PIP_INSTALL_RESULT EQUAL 0) - message( - FATAL_ERROR - "Failed to install Python dependencies: ${PIP_INSTALL_ERROR}" - ) - endif() - - # Mark requirements as installed - file(TOUCH "${VENV_DIR}/.requirements_installed") - message(STATUS "Python virtual environment ready") - endif() - endif() - - # At configure time - get list of output files for transactions - execute_process( - COMMAND - ${VENV_PYTHON} "${GENERATE_TX_SCRIPT}" "${TRANSACTIONS_MACRO}" - --header-dir "${AUTOGEN_HEADER_DIR}/transactions" --test-dir - "${AUTOGEN_TEST_DIR}/transactions" --list-outputs - OUTPUT_VARIABLE TX_OUTPUT_FILES - OUTPUT_STRIP_TRAILING_WHITESPACE - RESULT_VARIABLE TX_LIST_RESULT - ERROR_VARIABLE TX_LIST_ERROR +else() + set(CODEGEN_PYTHON "${Python3_EXECUTABLE}") + message( + WARNING + "CODEGEN_VENV_DIR is not set. Dependencies will be installed globally.\n" + "If this is not intended, reconfigure with:\n" + " cmake . -UCODEGEN_VENV_DIR" ) - if(NOT TX_LIST_RESULT EQUAL 0) - message( - FATAL_ERROR - "Failed to list transaction output files:\n${TX_LIST_ERROR}" - ) - endif() - # Convert newline-separated list to CMake list - string(REPLACE "\\" "/" TX_OUTPUT_FILES "${TX_OUTPUT_FILES}") - string(REPLACE "\n" ";" TX_OUTPUT_FILES "${TX_OUTPUT_FILES}") +endif() - # At configure time - get list of output files for ledger entries - execute_process( - COMMAND - ${VENV_PYTHON} "${GENERATE_LEDGER_SCRIPT}" "${LEDGER_ENTRIES_MACRO}" - --header-dir "${AUTOGEN_HEADER_DIR}/ledger_entries" --test-dir - "${AUTOGEN_TEST_DIR}/ledger_entries" --list-outputs - OUTPUT_VARIABLE LEDGER_OUTPUT_FILES - OUTPUT_STRIP_TRAILING_WHITESPACE - RESULT_VARIABLE LEDGER_LIST_RESULT - ERROR_VARIABLE LEDGER_LIST_ERROR - ) - if(NOT LEDGER_LIST_RESULT EQUAL 0) - message( - FATAL_ERROR - "Failed to list ledger entry output files:\n${LEDGER_LIST_ERROR}" - ) - endif() - # Convert newline-separated list to CMake list - string(REPLACE "\\" "/" LEDGER_OUTPUT_FILES "${LEDGER_OUTPUT_FILES}") - string(REPLACE "\n" ";" LEDGER_OUTPUT_FILES "${LEDGER_OUTPUT_FILES}") - - # Custom command to generate transaction classes at build time - add_custom_command( - OUTPUT ${TX_OUTPUT_FILES} - COMMAND - ${VENV_PYTHON} "${GENERATE_TX_SCRIPT}" "${TRANSACTIONS_MACRO}" - --header-dir "${AUTOGEN_HEADER_DIR}/transactions" --test-dir - "${AUTOGEN_TEST_DIR}/transactions" --sfields-macro - "${SFIELDS_MACRO}" - WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" - DEPENDS - "${TRANSACTIONS_MACRO}" - "${SFIELDS_MACRO}" - "${GENERATE_TX_SCRIPT}" - "${MACRO_PARSER_COMMON}" - "${TX_TEMPLATE}" - "${TX_TEST_TEMPLATE}" - "${REQUIREMENTS_FILE}" - COMMENT "Generating transaction classes from transactions.macro..." - VERBATIM - ) - - # Custom command to generate ledger entry classes at build time - add_custom_command( - OUTPUT ${LEDGER_OUTPUT_FILES} - COMMAND - ${VENV_PYTHON} "${GENERATE_LEDGER_SCRIPT}" "${LEDGER_ENTRIES_MACRO}" - --header-dir "${AUTOGEN_HEADER_DIR}/ledger_entries" --test-dir - "${AUTOGEN_TEST_DIR}/ledger_entries" --sfields-macro - "${SFIELDS_MACRO}" - WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" - DEPENDS - "${LEDGER_ENTRIES_MACRO}" - "${SFIELDS_MACRO}" - "${GENERATE_LEDGER_SCRIPT}" - "${MACRO_PARSER_COMMON}" - "${LEDGER_TEMPLATE}" - "${LEDGER_TEST_TEMPLATE}" - "${REQUIREMENTS_FILE}" - COMMENT "Generating ledger entry classes from ledger_entries.macro..." - VERBATIM - ) - - # Create a custom target that depends on all generated files +# Custom target to create a venv and install Python dependencies. +# Run manually with: cmake --build . --target setup_code_gen +if(CODEGEN_VENV_DIR) add_custom_target( - protocol_autogen_generate - DEPENDS ${TX_OUTPUT_FILES} ${LEDGER_OUTPUT_FILES} - COMMENT "Protocol autogen code generation" + setup_code_gen + COMMAND ${Python3_EXECUTABLE} -m venv "${CODEGEN_VENV_DIR}" + COMMAND ${CODEGEN_PYTHON} -m pip install -r "${REQUIREMENTS_FILE}" + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + COMMENT "Creating venv and installing code generation dependencies..." ) +else() + add_custom_target( + setup_code_gen + COMMAND ${Python3_EXECUTABLE} -m pip install -r "${REQUIREMENTS_FILE}" + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + COMMENT "Installing code generation dependencies..." + ) +endif() - # Extract test files from output lists (files ending in Tests.cpp) - set(PROTOCOL_AUTOGEN_TEST_SOURCES "") - foreach(FILE ${TX_OUTPUT_FILES} ${LEDGER_OUTPUT_FILES}) - if(FILE MATCHES "Tests\\.cpp$") - list(APPEND PROTOCOL_AUTOGEN_TEST_SOURCES "${FILE}") - endif() - endforeach() - # Export test sources to parent scope for use in test CMakeLists.txt - set(PROTOCOL_AUTOGEN_TEST_SOURCES - "${PROTOCOL_AUTOGEN_TEST_SOURCES}" - CACHE INTERNAL - "Generated protocol_autogen test sources" - ) - - # Register dependencies so CMake reconfigures when macro files change - # (to update the list of output files) - set_property( - DIRECTORY - APPEND - PROPERTY - CMAKE_CONFIGURE_DEPENDS - "${TRANSACTIONS_MACRO}" - "${LEDGER_ENTRIES_MACRO}" - ) -endfunction() +# Custom target for code generation, excluded from ALL. +# Run manually with: cmake --build . --target code_gen +add_custom_target( + code_gen + COMMAND + ${CMAKE_COMMAND} -DCODEGEN_PYTHON=${CODEGEN_PYTHON} + -DGENERATE_TX_SCRIPT=${GENERATE_TX_SCRIPT} + -DGENERATE_LEDGER_SCRIPT=${GENERATE_LEDGER_SCRIPT} + -DTRANSACTIONS_MACRO=${TRANSACTIONS_MACRO} + -DLEDGER_ENTRIES_MACRO=${LEDGER_ENTRIES_MACRO} + -DSFIELDS_MACRO=${SFIELDS_MACRO} + -DAUTOGEN_HEADER_DIR=${AUTOGEN_HEADER_DIR} + -DAUTOGEN_TEST_DIR=${AUTOGEN_TEST_DIR} -P + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/XrplProtocolAutogenRun.cmake" + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + COMMENT "Running protocol code generation..." + SOURCES ${ALL_INPUT_FILES} +) diff --git a/cmake/XrplProtocolAutogenRun.cmake b/cmake/XrplProtocolAutogenRun.cmake new file mode 100644 index 0000000000..8bdb37a8e0 --- /dev/null +++ b/cmake/XrplProtocolAutogenRun.cmake @@ -0,0 +1,39 @@ +#[===================================================================[ + Protocol Autogen - Run script invoked by the 'code_gen' target +#]===================================================================] + +# Generate transaction classes. +execute_process( + COMMAND + ${CODEGEN_PYTHON} "${GENERATE_TX_SCRIPT}" "${TRANSACTIONS_MACRO}" + --header-dir "${AUTOGEN_HEADER_DIR}/transactions" --test-dir + "${AUTOGEN_TEST_DIR}/transactions" --sfields-macro "${SFIELDS_MACRO}" + RESULT_VARIABLE TX_RESULT + OUTPUT_VARIABLE TX_OUTPUT + ERROR_VARIABLE TX_ERROR +) +if(NOT TX_RESULT EQUAL 0) + message( + FATAL_ERROR + "Transaction code generation failed:\n${TX_OUTPUT}\n${TX_ERROR}\n${TX_RESULT}" + ) +endif() + +# Generate ledger entry classes. +execute_process( + COMMAND + ${CODEGEN_PYTHON} "${GENERATE_LEDGER_SCRIPT}" "${LEDGER_ENTRIES_MACRO}" + --header-dir "${AUTOGEN_HEADER_DIR}/ledger_entries" --test-dir + "${AUTOGEN_TEST_DIR}/ledger_entries" --sfields-macro "${SFIELDS_MACRO}" + RESULT_VARIABLE LEDGER_RESULT + OUTPUT_VARIABLE LEDGER_OUTPUT + ERROR_VARIABLE LEDGER_ERROR +) +if(NOT LEDGER_RESULT EQUAL 0) + message( + FATAL_ERROR + "Ledger entry code generation failed:\n${LEDGER_OUTPUT}\n${LEDGER_ERROR}\n${TX_RESULT}" + ) +endif() + +message(STATUS "Protocol autogen: code generation complete") diff --git a/scripts/generate_ledger_classes.py b/cmake/scripts/codegen/generate_ledger_classes.py similarity index 92% rename from scripts/generate_ledger_classes.py rename to cmake/scripts/codegen/generate_ledger_classes.py index ad773ab9af..f5513655de 100644 --- a/scripts/generate_ledger_classes.py +++ b/cmake/scripts/codegen/generate_ledger_classes.py @@ -138,28 +138,12 @@ def main(): "--sfields-macro", help="Path to sfields.macro (default: auto-detect from macro_path)", ) - parser.add_argument( - "--list-outputs", - action="store_true", - help="List output files without generating (one per line)", - ) - + parser.add_argument("--venv-dir", help=argparse.SUPPRESS) args = parser.parse_args() # Parse the macro file to get ledger entry names entries = parse_macro_file(args.macro_path) - # If --list-outputs, just print the output file paths and exit - if args.list_outputs: - header_dir = Path(args.header_dir) - for entry in entries: - print(header_dir / f"{entry['name']}.h") - if args.test_dir: - test_dir = Path(args.test_dir) - for entry in entries: - print(test_dir / f"{entry['name']}Tests.cpp") - return - # Auto-detect sfields.macro path if not provided if args.sfields_macro: sfields_path = Path(args.sfields_macro) diff --git a/scripts/generate_tx_classes.py b/cmake/scripts/codegen/generate_tx_classes.py similarity index 92% rename from scripts/generate_tx_classes.py rename to cmake/scripts/codegen/generate_tx_classes.py index f21b7101df..07baefd8b6 100644 --- a/scripts/generate_tx_classes.py +++ b/cmake/scripts/codegen/generate_tx_classes.py @@ -147,28 +147,12 @@ def main(): "--sfields-macro", help="Path to sfields.macro (default: auto-detect from macro_path)", ) - parser.add_argument( - "--list-outputs", - action="store_true", - help="List output files without generating (one per line)", - ) - + parser.add_argument("--venv-dir", help=argparse.SUPPRESS) args = parser.parse_args() # Parse the macro file to get transaction names transactions = parse_macro_file(args.macro_path) - # If --list-outputs, just print the output file paths and exit - if args.list_outputs: - header_dir = Path(args.header_dir) - for tx in transactions: - print(header_dir / f"{tx['name']}.h") - if args.test_dir: - test_dir = Path(args.test_dir) - for tx in transactions: - print(test_dir / f"{tx['name']}Tests.cpp") - return - # Auto-detect sfields.macro path if not provided if args.sfields_macro: sfields_path = Path(args.sfields_macro) diff --git a/scripts/macro_parser_common.py b/cmake/scripts/codegen/macro_parser_common.py similarity index 100% rename from scripts/macro_parser_common.py rename to cmake/scripts/codegen/macro_parser_common.py diff --git a/scripts/requirements.txt b/cmake/scripts/codegen/requirements.txt similarity index 100% rename from scripts/requirements.txt rename to cmake/scripts/codegen/requirements.txt diff --git a/scripts/templates/LedgerEntry.h.mako b/cmake/scripts/codegen/templates/LedgerEntry.h.mako similarity index 100% rename from scripts/templates/LedgerEntry.h.mako rename to cmake/scripts/codegen/templates/LedgerEntry.h.mako diff --git a/scripts/templates/LedgerEntryTests.cpp.mako b/cmake/scripts/codegen/templates/LedgerEntryTests.cpp.mako similarity index 100% rename from scripts/templates/LedgerEntryTests.cpp.mako rename to cmake/scripts/codegen/templates/LedgerEntryTests.cpp.mako diff --git a/scripts/templates/Transaction.h.mako b/cmake/scripts/codegen/templates/Transaction.h.mako similarity index 100% rename from scripts/templates/Transaction.h.mako rename to cmake/scripts/codegen/templates/Transaction.h.mako diff --git a/scripts/templates/TransactionTests.cpp.mako b/cmake/scripts/codegen/templates/TransactionTests.cpp.mako similarity index 100% rename from scripts/templates/TransactionTests.cpp.mako rename to cmake/scripts/codegen/templates/TransactionTests.cpp.mako diff --git a/conan/lockfile/regenerate.sh b/conan/lockfile/regenerate.sh index 30d38dc2d7..1aa47628f0 100755 --- a/conan/lockfile/regenerate.sh +++ b/conan/lockfile/regenerate.sh @@ -23,14 +23,14 @@ rm -f conan.lock # first create command will create a new lockfile, while the subsequent create # commands will merge any additional dependencies into the created lockfile. conan lock create . \ - --options '&:jemalloc=True' \ - --options '&:rocksdb=True' \ - --profile:all=conan/lockfile/linux.profile + --options '&:jemalloc=True' \ + --options '&:rocksdb=True' \ + --profile:all=conan/lockfile/linux.profile conan lock create . \ - --options '&:jemalloc=True' \ - --options '&:rocksdb=True' \ - --profile:all=conan/lockfile/macos.profile + --options '&:jemalloc=True' \ + --options '&:rocksdb=True' \ + --profile:all=conan/lockfile/macos.profile conan lock create . \ - --options '&:jemalloc=True' \ - --options '&:rocksdb=True' \ - --profile:all=conan/lockfile/windows.profile + --options '&:jemalloc=True' \ + --options '&:rocksdb=True' \ + --profile:all=conan/lockfile/windows.profile diff --git a/cspell.config.yaml b/cspell.config.yaml index 63edba587d..028f02191e 100644 --- a/cspell.config.yaml +++ b/cspell.config.yaml @@ -44,6 +44,10 @@ suggestWords: words: - abempty - AMMID + - AMMMPT + - AMMMPToken + - AMMMPTokens + - AMMXRP - amt - amts - asnode @@ -96,6 +100,7 @@ words: - distro - doxyfile - dxrpl + - enabled - endmacro - exceptioned - Falco @@ -148,6 +153,8 @@ words: - ltype - mcmodel - MEMORYSTATUSEX + - MPTAMM + - MPTDEX - Merkle - Metafuncton - misprediction @@ -157,6 +164,7 @@ words: - mptid - mptissuance - mptissuanceid + - mptissue - mptoken - mptokenid - mptokenissuance diff --git a/docs/0001-negative-unl/README.md b/docs/0001-negative-unl/README.md index c863cab9da..dd5f9af2ae 100644 --- a/docs/0001-negative-unl/README.md +++ b/docs/0001-negative-unl/README.md @@ -558,7 +558,7 @@ network delay. A test case specifies: 1. a UNL with different number of validators for different test cases, 1. a network with zero or more non-validator nodes, 1. a sequence of validator reliability change events (by killing/restarting - nodes, or by running modified rippled that does not send all validation + nodes, or by running modified xrpld that does not send all validation messages), 1. the correct outcomes. @@ -566,7 +566,7 @@ For all the test cases, the correct outcomes are verified by examining logs. We will grep the log to see if the correct negative UNLs are generated, and whether or not the network is making progress when it should be. The ripdtop tool will be helpful for monitoring validators' states and ledger progress. Some of the -timing parameters of rippled will be changed to have faster ledger time. Most if +timing parameters of xrpld will be changed to have faster ledger time. Most if not all test cases do not need client transactions. For example, the test cases for the prototype: @@ -583,7 +583,7 @@ For example, the test cases for the prototype: We considered testing with the current unit test framework, specifically the [Consensus Simulation -Framework](https://github.com/ripple/rippled/blob/develop/src/test/csf/README.md) +Framework](https://github.com/XRPLF/rippled/blob/develop/src/test/csf/README.md) (CSF). However, the CSF currently can only test the generic consensus algorithm as in the paper: [Analysis of the XRP Ledger Consensus Protocol](https://arxiv.org/abs/1802.07242). diff --git a/docs/0001-negative-unl/negativeUNLSqDiagram.puml b/docs/0001-negative-unl/negativeUNLSqDiagram.puml index d86b98c01f..5c8d0c1fb3 100644 --- a/docs/0001-negative-unl/negativeUNLSqDiagram.puml +++ b/docs/0001-negative-unl/negativeUNLSqDiagram.puml @@ -4,7 +4,7 @@ skinparam sequenceArrowThickness 2 skinparam roundcorner 20 skinparam maxmessagesize 160 -actor "Rippled Start" as RS +actor "Xrpld Start" as RS participant "Timer" as T participant "NetworkOPs" as NOP participant "ValidatorList" as VL #lightgreen diff --git a/docs/Docker.md b/docs/Docker.md index 9f67c87ee5..4c712148d6 100644 --- a/docs/Docker.md +++ b/docs/Docker.md @@ -1,5 +1,5 @@ -# `rippled` Docker Image +# `xrpld` Docker Image - Some info relating to Docker containers can be found here: [../Builds/containers](../Builds/containers) -- Images for building and testing rippled can be found here: [thejohnfreeman/rippled-docker](https://github.com/thejohnfreeman/rippled-docker/) - - These images do not have rippled. They have all the tools necessary to build rippled. +- Images for building and testing xrpld can be found here: [thejohnfreeman/rippled-docker](https://github.com/thejohnfreeman/rippled-docker/) + - These images do not have xrpld. They have all the tools necessary to build xrpld. diff --git a/docs/Doxyfile b/docs/Doxyfile index 750ae0fb64..caa1db30e7 100644 --- a/docs/Doxyfile +++ b/docs/Doxyfile @@ -2,7 +2,7 @@ # Project related configuration options #--------------------------------------------------------------------------- DOXYFILE_ENCODING = UTF-8 -PROJECT_NAME = "rippled" +PROJECT_NAME = "xrpld" PROJECT_NUMBER = PROJECT_BRIEF = PROJECT_LOGO = diff --git a/docs/HeapProfiling.md b/docs/HeapProfiling.md index 2871cccaba..6fcc9f9a67 100644 --- a/docs/HeapProfiling.md +++ b/docs/HeapProfiling.md @@ -1,4 +1,4 @@ -## Heap profiling of rippled with jemalloc +## Heap profiling of xrpld with jemalloc The jemalloc library provides a good API for doing heap analysis, including a mechanism to dump a description of the heap from within the @@ -7,26 +7,26 @@ activity in general, as well as how to acquire the software, are available on the jemalloc site: [https://github.com/jemalloc/jemalloc/wiki/Use-Case:-Heap-Profiling](https://github.com/jemalloc/jemalloc/wiki/Use-Case:-Heap-Profiling) -jemalloc is acquired separately from rippled, and is not affiliated +jemalloc is acquired separately from xrpld, and is not affiliated with Ripple Labs. If you compile and install jemalloc from the source release with default options, it will install the library and header under `/usr/local/lib` and `/usr/local/include`, respectively. Heap -profiling has been tested with rippled on a Linux platform. It should -work on platforms on which both rippled and jemalloc are available. +profiling has been tested with xrpld on a Linux platform. It should +work on platforms on which both xrpld and jemalloc are available. -To link rippled with jemalloc, the argument +To link xrpld with jemalloc, the argument `profile-jemalloc=` is provided after the optional target. The `` argument should be the same as that of the `--prefix` parameter passed to the jemalloc configure script when building. ## Examples: -Build rippled with jemalloc library under /usr/local/lib and +Build xrpld with jemalloc library under /usr/local/lib and header under /usr/local/include: $ scons profile-jemalloc=/usr/local -Build rippled using clang with the jemalloc library under /opt/local/lib +Build xrpld using clang with the jemalloc library under /opt/local/lib and header under /opt/local/include: $ scons clang profile-jemalloc=/opt/local diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index 35fcbba2d1..0000000000 --- a/docs/README.md +++ /dev/null @@ -1,61 +0,0 @@ -# Building documentation - -## Dependencies - -Install these dependencies: - -- [Doxygen](http://www.doxygen.nl): All major platforms have [official binary - distributions](http://www.doxygen.nl/download.html#srcbin), or you can - build from [source](http://www.doxygen.nl/download.html#srcbin). - - MacOS: We recommend installing via Homebrew: `brew install doxygen`. - The executable will be installed in `/usr/local/bin` which is already - in the default `PATH`. - - If you use the official binary distribution, then you'll need to make - Doxygen available to your command line. You can do this by adding - a symbolic link from `/usr/local/bin` to the `doxygen` executable. For - example, - - ``` - $ ln -s /Applications/Doxygen.app/Contents/Resources/doxygen /usr/local/bin/doxygen - ``` - -- [PlantUML](http://plantuml.com): - 1. Install a functioning Java runtime, if you don't already have one. - 2. Download [`plantuml.jar`](http://sourceforge.net/projects/plantuml/files/plantuml.jar/download). - -- [Graphviz](https://www.graphviz.org): - - Linux: Install from your package manager. - - Windows: Use an [official installer](https://graphviz.gitlab.io/_pages/Download/Download_windows.html). - - MacOS: Install via Homebrew: `brew install graphviz`. - -## Docker - -Instead of installing the above dependencies locally, you can use the official -build environment Docker image, which has all of them installed already. - -1. Install [Docker](https://docs.docker.com/engine/installation/) -2. Pull the image: - -``` -sudo docker pull rippleci/rippled-ci-builder:2944b78d22db -``` - -3. Run the image from the project folder: - -``` -sudo docker run -v $PWD:/opt/rippled --rm rippleci/rippled-ci-builder:2944b78d22db -``` - -## Build - -There is a `docs` target in the CMake configuration. - -``` -mkdir build -cd build -cmake -Donly_docs=ON .. -cmake --build . --target docs --parallel -``` - -The output will be in `build/docs/html`. diff --git a/docs/build/depend.md b/docs/build/depend.md index 2fa14378aa..f44519ddf9 100644 --- a/docs/build/depend.md +++ b/docs/build/depend.md @@ -20,7 +20,7 @@ CMakeToolchain ``` # If you want to depend on a version of libxrpl that is not in ConanCenter, -# then you can export the recipe from the rippled project. +# then you can export the recipe from the xrpld project. conan export ``` @@ -49,9 +49,9 @@ cmake --build . --parallel ## CMake subdirectory -The second method adds the [rippled][] project as a CMake +The second method adds the [xrpld][] project as a CMake [subdirectory][add_subdirectory]. -This method works well when you keep the rippled project as a Git +This method works well when you keep the xrpld project as a Git [submodule][]. It's good for when you want to make changes to libxrpl as part of your own project. @@ -90,6 +90,6 @@ cmake --build . --parallel [add_subdirectory]: https://cmake.org/cmake/help/latest/command/add_subdirectory.html [submodule]: https://git-scm.com/book/en/v2/Git-Tools-Submodules -[rippled]: https://github.com/ripple/rippled +[xrpld]: https://github.com/XRPLF/rippled [Conan]: https://docs.conan.io/ [CMake]: https://cmake.org/cmake/help/latest/ diff --git a/docs/build/environment.md b/docs/build/environment.md index 66bed06c26..fb1ebde8bc 100644 --- a/docs/build/environment.md +++ b/docs/build/environment.md @@ -55,7 +55,7 @@ clang --version ### Install Xcode Specific Version (Optional) If you develop other applications using XCode you might be consistently updating to the newest version of Apple Clang. -This will likely cause issues building rippled. You may want to install a specific version of Xcode: +This will likely cause issues building xrpld. You may want to install a specific version of Xcode: 1. **Download Xcode** - Visit [Apple Developer Downloads](https://developer.apple.com/download/more/) diff --git a/docs/build/install.md b/docs/build/install.md index 7be01ce726..d3ce1e9d87 100644 --- a/docs/build/install.md +++ b/docs/build/install.md @@ -1,4 +1,4 @@ -This document contains instructions for installing rippled. +This document contains instructions for installing xrpld. The APT package manager is common on Debian-based Linux distributions like Ubuntu, while the YUM package manager is common on Red Hat-based Linux distributions @@ -8,7 +8,7 @@ and the only supported option for installing custom builds. ## From source -From a source build, you can install rippled and libxrpl using CMake's +From a source build, you can install xrpld and libxrpl using CMake's `--install` mode: ``` @@ -16,7 +16,7 @@ cmake --install . --prefix /opt/local ``` The default [prefix][1] is typically `/usr/local` on Linux and macOS and -`C:/Program Files/rippled` on Windows. +`C:/Program Files/xrpld` on Windows. [1]: https://cmake.org/cmake/help/latest/variable/CMAKE_INSTALL_PREFIX.html @@ -50,9 +50,9 @@ The default [prefix][1] is typically `/usr/local` on Linux and macOS and In particular, make sure that the fingerprint matches. (In the above example, the fingerprint is on the third line, starting with `C001`.) -5. Add the appropriate Ripple repository for your operating system version: +5. Add the appropriate XRPL repository for your operating system version: - echo "deb [signed-by=/usr/local/share/keyrings/ripple-key.gpg] https://repos.ripple.com/repos/rippled-deb focal stable" | \ + echo "deb [signed-by=/usr/local/share/keyrings/ripple-key.gpg] https://repos.ripple.com/repos/xrpld-deb focal stable" | \ sudo tee -a /etc/apt/sources.list.d/ripple.list The above example is appropriate for **Ubuntu 20.04 Focal Fossa**. For other operating systems, replace the word `focal` with one of the following: @@ -61,33 +61,33 @@ The default [prefix][1] is typically `/usr/local` on Linux and macOS and - `bullseye` for **Debian 11 Bullseye** - `buster` for **Debian 10 Buster** - If you want access to development or pre-release versions of `rippled`, use one of the following instead of `stable`: - - `unstable` - Pre-release builds ([`release` branch](https://github.com/ripple/rippled/tree/release)) - - `nightly` - Experimental/development builds ([`develop` branch](https://github.com/ripple/rippled/tree/develop)) + If you want access to development or pre-release versions of `xrpld`, use one of the following instead of `stable`: + - `unstable` - Pre-release builds ([`release` branch](https://github.com/XRPLF/rippled/tree/release)) + - `nightly` - Experimental/development builds ([`develop` branch](https://github.com/XRPLF/rippled/tree/develop)) **Warning:** Unstable and nightly builds may be broken at any time. Do not use these builds for production servers. -6. Fetch the Ripple repository. +6. Fetch the XRPL repository. sudo apt -y update -7. Install the `rippled` software package: +7. Install the `xrpld` software package: - sudo apt -y install rippled + sudo apt -y install xrpld -8. Check the status of the `rippled` service: +8. Check the status of the `xrpld` service: - systemctl status rippled.service + systemctl status xrpld.service - The `rippled` service should start automatically. If not, you can start it manually: + The `xrpld` service should start automatically. If not, you can start it manually: - sudo systemctl start rippled.service + sudo systemctl start xrpld.service -9. Optional: allow `rippled` to bind to privileged ports. +9. Optional: allow `xrpld` to bind to privileged ports. This allows you to serve incoming API requests on port 80 or 443. (If you want to do so, you must also update the config file's port settings.) - sudo setcap 'cap_net_bind_service=+ep' /opt/ripple/bin/rippled + sudo setcap 'cap_net_bind_service=+ep' /opt/xrpld/bin/xrpld ## With the YUM package manager @@ -106,8 +106,8 @@ The default [prefix][1] is typically `/usr/local` on Linux and macOS and enabled=1 gpgcheck=0 repo_gpgcheck=1 - baseurl=https://repos.ripple.com/repos/rippled-rpm/stable/ - gpgkey=https://repos.ripple.com/repos/rippled-rpm/stable/repodata/repomd.xml.key + baseurl=https://repos.ripple.com/repos/xrpld-rpm/stable/ + gpgkey=https://repos.ripple.com/repos/xrpld-rpm/stable/repodata/repomd.xml.key REPOFILE _Unstable_ @@ -118,8 +118,8 @@ The default [prefix][1] is typically `/usr/local` on Linux and macOS and enabled=1 gpgcheck=0 repo_gpgcheck=1 - baseurl=https://repos.ripple.com/repos/rippled-rpm/unstable/ - gpgkey=https://repos.ripple.com/repos/rippled-rpm/unstable/repodata/repomd.xml.key + baseurl=https://repos.ripple.com/repos/xrpld-rpm/unstable/ + gpgkey=https://repos.ripple.com/repos/xrpld-rpm/unstable/repodata/repomd.xml.key REPOFILE _Nightly_ @@ -130,22 +130,22 @@ The default [prefix][1] is typically `/usr/local` on Linux and macOS and enabled=1 gpgcheck=0 repo_gpgcheck=1 - baseurl=https://repos.ripple.com/repos/rippled-rpm/nightly/ - gpgkey=https://repos.ripple.com/repos/rippled-rpm/nightly/repodata/repomd.xml.key + baseurl=https://repos.ripple.com/repos/xrpld-rpm/nightly/ + gpgkey=https://repos.ripple.com/repos/xrpld-rpm/nightly/repodata/repomd.xml.key REPOFILE 2. Fetch the latest repo updates: sudo yum -y update -3. Install the new `rippled` package: +3. Install the new `xrpld` package: - sudo yum install -y rippled + sudo yum install -y xrpld -4. Configure the `rippled` service to start on boot: +4. Configure the `xrpld` service to start on boot: - sudo systemctl enable rippled.service + sudo systemctl enable xrpld.service -5. Start the `rippled` service: +5. Start the `xrpld` service: - sudo systemctl start rippled.service + sudo systemctl start xrpld.service diff --git a/docs/build/sanitizers.md b/docs/build/sanitizers.md index ac3a0cc865..7677775a3d 100644 --- a/docs/build/sanitizers.md +++ b/docs/build/sanitizers.md @@ -1,9 +1,9 @@ -# Sanitizer Configuration for Rippled +# Sanitizer Configuration for Xrpld This document explains how to properly configure and run sanitizers (AddressSanitizer, undefinedbehaviorSanitizer, ThreadSanitizer) with the xrpld project. Corresponding suppression files are located in the `sanitizers/suppressions` directory. -- [Sanitizer Configuration for Rippled](#sanitizer-configuration-for-rippled) +- [Sanitizer Configuration for Xrpld](#sanitizer-configuration-for-xrpld) - [Building with Sanitizers](#building-with-sanitizers) - [Summary](#summary) - [Build steps:](#build-steps) @@ -100,7 +100,7 @@ export LSAN_OPTIONS="include=sanitizers/suppressions/runtime-lsan-options.txt:su - Boost intrusive containers (used in `aged_unordered_container`) trigger false positives - Boost context switching (used in `Workers.cpp`) confuses ASAN's stack tracking -- Since we usually don't build Boost (because we don't want to instrument Boost and detect issues in Boost code) with ASAN but use Boost containers in ASAN instrumented rippled code, it generates false positives. +- Since we usually don't build Boost (because we don't want to instrument Boost and detect issues in Boost code) with ASAN but use Boost containers in ASAN instrumented xrpld code, it generates false positives. - Building dependencies with ASAN instrumentation reduces false positives. But we don't want to instrument dependencies like Boost with ASAN because it is slow (to compile as well as run tests) and not necessary. - See: https://github.com/google/sanitizers/wiki/AddressSanitizerContainerOverflow - More such flags are detailed [here](https://github.com/google/sanitizers/wiki/AddressSanitizerFlags) diff --git a/docs/consensus.md b/docs/consensus.md index 23e5e7d5be..0da23b708a 100644 --- a/docs/consensus.md +++ b/docs/consensus.md @@ -5,9 +5,9 @@ Consensus is the task of reaching agreement within a distributed system in the presence of faulty or even malicious participants. This document outlines the [XRP Ledger Consensus Algorithm](https://arxiv.org/abs/1802.07242) -as implemented in [rippled](https://github.com/ripple/rippled), but +as implemented in [xrpld](https://github.com/XRPLF/rippled), but focuses on its utility as a generic consensus algorithm independent of the -detailed mechanics of the Ripple Consensus Ledger. Most notably, the algorithm +detailed mechanics of the XRPL consensus Ledger. Most notably, the algorithm does not require fully synchronous communication between all nodes in the network, or even a fixed network topology, but instead achieves consensus via collectively trusted subnetworks. @@ -15,7 +15,7 @@ collectively trusted subnetworks. ## Distributed Agreement A challenge for distributed systems is reaching agreement on changes in shared -state. For the Ripple network, the shared state is the current ledger--account +state. For the XRPL network, the shared state is the current ledger--account information, account balances, order books and other financial data. We will refer to shared distributed state as a /ledger/ throughout the remainder of this document. @@ -23,7 +23,7 @@ document. ![Ledger Chain](images/consensus/ledger_chain.png "Ledger Chain") As shown above, new ledgers are made by applying a set of transactions to the -prior ledger. For the Ripple network, transactions include payments, +prior ledger. For the XRPL network, transactions include payments, modification of account settings, updates to offers and more. In a centralized system, generating the next ledger is trivial since there is a @@ -33,10 +33,10 @@ the set of transactions to include, the order to apply those transactions, and even the resulting ledger after applying the transactions. This is even more difficult when some participants are faulty or malicious. -The Ripple network is a decentralized and **trust-full** network. Anyone is free +The XRPL network is a decentralized and **trust-full** network. Anyone is free to join and participants are free to choose a subset of peers that are collectively trusted to not collude in an attempt to defraud the participant. -Leveraging this network of trust, the Ripple algorithm has two main components. +Leveraging this network of trust, the XRPL algorithm has two main components. - _Consensus_ in which network participants agree on the transactions to apply to a prior ledger, based on the positions of their chosen peers. @@ -54,9 +54,9 @@ and was abandoned. The remainder of this section describes the Consensus and Validation algorithms in more detail and is meant as a companion guide to understanding the generic -implementation in `rippled`. The document **does not** discuss correctness, +implementation in `xrpld`. The document **does not** discuss correctness, fault-tolerance or liveness properties of the algorithms or the full details of -how they integrate within `rippled` to support the Ripple Consensus Ledger. +how they integrate within `xrpld` to support the XRPL consensus Ledger. ## Consensus Overview diff --git a/external/README.md b/external/README.md index f6e9f77d2d..4a4181e59e 100644 --- a/external/README.md +++ b/external/README.md @@ -1,6 +1,6 @@ # External Conan recipes -The subdirectories in this directory contain external libraries used by rippled. +The subdirectories in this directory contain external libraries used by xrpld. | Folder | Upstream | Description | | :--------------- | :------------------------------------------------------------- | :------------------------------------------------------------------------------------------- | diff --git a/external/antithesis-sdk/CMakeLists.txt b/external/antithesis-sdk/CMakeLists.txt index 46c7b4bf7a..645b73c530 100644 --- a/external/antithesis-sdk/CMakeLists.txt +++ b/external/antithesis-sdk/CMakeLists.txt @@ -1,11 +1,11 @@ cmake_minimum_required(VERSION 3.18) -# Note, version set explicitly by rippled project +# Note, version set explicitly by xrpld project project(antithesis-sdk-cpp VERSION 0.4.4 LANGUAGES CXX) add_library(antithesis-sdk-cpp INTERFACE antithesis_sdk.h) -# Note, both sections below created by rippled project +# Note, both sections below created by xrpld project target_include_directories(antithesis-sdk-cpp INTERFACE $ $ diff --git a/include/xrpl/basics/BasicConfig.h b/include/xrpl/basics/BasicConfig.h index eaa53f93d6..e1b0af516f 100644 --- a/include/xrpl/basics/BasicConfig.h +++ b/include/xrpl/basics/BasicConfig.h @@ -33,7 +33,7 @@ private: public: /** Create an empty section. */ - explicit Section(std::string const& name = ""); + explicit Section(std::string name = ""); /** Returns the name of this section. */ std::string const& @@ -67,9 +67,13 @@ public: legacy(std::string value) { if (lines_.empty()) + { lines_.emplace_back(std::move(value)); + } else + { lines_[0] = std::move(value); + } } /** @@ -84,8 +88,10 @@ public: if (lines_.empty()) return ""; if (lines_.size() > 1) + { Throw( "A legacy value must have exactly one line. Section: " + name_); + } return lines_[0]; } @@ -269,8 +275,7 @@ public: bool had_trailing_comments() const { - return std::any_of( - map_.cbegin(), map_.cend(), [](auto s) { return s.second.had_trailing_comments(); }); + return std::ranges::any_of(map_, [](auto s) { return s.second.had_trailing_comments(); }); } protected: @@ -296,7 +301,7 @@ set(T& target, std::string const& name, Section const& section) if ((found_and_valid = val.has_value())) target = *val; } - catch (boost::bad_lexical_cast&) + catch (boost::bad_lexical_cast const&) // NOLINT(bugprone-empty-catch) { } return found_and_valid; @@ -330,7 +335,7 @@ get(Section const& section, std::string const& name, T const& defaultValue = T{} { return section.value_or(name, defaultValue); } - catch (boost::bad_lexical_cast&) + catch (boost::bad_lexical_cast const&) // NOLINT(bugprone-empty-catch) { } return defaultValue; @@ -345,7 +350,7 @@ get(Section const& section, std::string const& name, char const* defaultValue) if (val.has_value()) return *val; } - catch (boost::bad_lexical_cast&) + catch (boost::bad_lexical_cast const&) // NOLINT(bugprone-empty-catch) { } return defaultValue; diff --git a/include/xrpl/basics/Buffer.h b/include/xrpl/basics/Buffer.h index 5192daf632..52c092981c 100644 --- a/include/xrpl/basics/Buffer.h +++ b/include/xrpl/basics/Buffer.h @@ -24,7 +24,8 @@ public: Buffer() = default; /** Create an uninitialized buffer with the given size. */ - explicit Buffer(std::size_t size) : p_(size ? new std::uint8_t[size] : nullptr), size_(size) + explicit Buffer(std::size_t size) + : p_((size != 0u) ? new std::uint8_t[size] : nullptr), size_(size) { } @@ -36,7 +37,7 @@ public: */ Buffer(void const* data, std::size_t size) : Buffer(size) { - if (size) + if (size != 0u) std::memcpy(p_.get(), data, size); } @@ -91,7 +92,7 @@ public: { // Ensure the slice isn't a subset of the buffer. XRPL_ASSERT( - s.size() == 0 || size_ == 0 || s.data() < p_.get() || s.data() >= p_.get() + size_, + s.empty() || size_ == 0 || s.data() < p_.get() || s.data() >= p_.get() + size_, "xrpl::Buffer::operator=(Slice) : input not a subset"); if (auto p = alloc(s.size())) @@ -114,7 +115,7 @@ public: operator Slice() const noexcept { - if (!size_) + if (size_ == 0u) return Slice{}; return Slice{p_.get(), size_}; } @@ -155,7 +156,7 @@ public: { if (n != size_) { - p_.reset(n ? new std::uint8_t[n] : nullptr); + p_.reset((n != 0u) ? new std::uint8_t[n] : nullptr); size_ = n; } return p_.get(); @@ -199,7 +200,7 @@ operator==(Buffer const& lhs, Buffer const& rhs) noexcept if (lhs.size() != rhs.size()) return false; - if (lhs.size() == 0) + if (lhs.empty()) return true; return std::memcmp(lhs.data(), rhs.data(), lhs.size()) == 0; diff --git a/include/xrpl/basics/CompressionAlgorithms.h b/include/xrpl/basics/CompressionAlgorithms.h index c549a58b93..e24c490337 100644 --- a/include/xrpl/basics/CompressionAlgorithms.h +++ b/include/xrpl/basics/CompressionAlgorithms.h @@ -9,9 +9,7 @@ #include #include -namespace xrpl { - -namespace compression_algorithms { +namespace xrpl::compression_algorithms { /** LZ4 block compression. * @tparam BufferFactory Callable object or lambda. @@ -68,12 +66,15 @@ lz4Decompress( if (decompressedSize <= 0) Throw("lz4Decompress: integer overflow (output)"); + // NOLINTNEXTLINE(readability-suspicious-call-argument) if (LZ4_decompress_safe( reinterpret_cast(in), reinterpret_cast(decompressed), inSize, decompressedSize) != decompressedSize) + { Throw("lz4Decompress: failed"); + } return decompressedSize; } @@ -138,6 +139,4 @@ lz4Decompress( return lz4Decompress(chunk, inSize, decompressed, decompressedSize); } -} // namespace compression_algorithms - -} // namespace xrpl +} // namespace xrpl::compression_algorithms diff --git a/include/xrpl/basics/CountedObject.h b/include/xrpl/basics/CountedObject.h index acf75360e1..675d1b163b 100644 --- a/include/xrpl/basics/CountedObject.h +++ b/include/xrpl/basics/CountedObject.h @@ -99,7 +99,7 @@ private: Derived classes have their instances counted automatically. This is used for reporting purposes. - @ingroup ripple_basics + @ingroup basics */ template class CountedObject @@ -112,7 +112,6 @@ private: return c; } -public: CountedObject() noexcept { getCounter().increment(); @@ -126,10 +125,13 @@ public: CountedObject& operator=(CountedObject const&) noexcept = default; +public: ~CountedObject() noexcept { getCounter().decrement(); } + + friend Object; }; } // namespace xrpl diff --git a/include/xrpl/basics/Expected.h b/include/xrpl/basics/Expected.h index 083a9233cf..6cba7106fb 100644 --- a/include/xrpl/basics/Expected.h +++ b/include/xrpl/basics/Expected.h @@ -61,7 +61,7 @@ template class Unexpected { public: - static_assert(!std::is_same::value, "E must not be void"); + static_assert(!std::is_same_v, "E must not be void"); Unexpected() = delete; diff --git a/include/xrpl/basics/IntrusivePointer.h b/include/xrpl/basics/IntrusivePointer.h index f816af1c05..230dec3ebb 100644 --- a/include/xrpl/basics/IntrusivePointer.h +++ b/include/xrpl/basics/IntrusivePointer.h @@ -59,7 +59,7 @@ concept CAdoptTag = std::is_same_v || still retaining the reference counts. For example, for SHAMapInnerNodes the children may be reset in that function. Note that std::shared_pointer WILL run the destructor when the strong count reaches zero, but may not free the - memory used by the object until the weak count reaches zero. In rippled, we + memory used by the object until the weak count reaches zero. In xrpld, we typically allocate shared pointers with the `make_shared` function. When that is used, the memory is not reclaimed until the weak count reaches zero. */ diff --git a/include/xrpl/basics/IntrusiveRefCounts.h b/include/xrpl/basics/IntrusiveRefCounts.h index 5a51b3c3bf..ea610a521e 100644 --- a/include/xrpl/basics/IntrusiveRefCounts.h +++ b/include/xrpl/basics/IntrusiveRefCounts.h @@ -33,7 +33,7 @@ enum class ReleaseWeakRefAction { noop, destroy }; /** Implement the strong count, weak count, and bit flags for an intrusive pointer. - A class can satisfy the requirements of a xrpl::IntrusivePointer by + A class can satisfy the requirements of an xrpl::IntrusivePointer by inheriting from this class. */ struct IntrusiveRefCounts @@ -247,7 +247,7 @@ IntrusiveRefCounts::releaseStrongRef() const using enum ReleaseStrongRefAction; auto prevIntVal = refCounts.load(std::memory_order_acquire); - while (1) + while (true) { RefCountPair const prevVal{prevIntVal}; XRPL_ASSERT( @@ -298,7 +298,7 @@ IntrusiveRefCounts::addWeakReleaseStrongRef() const // Note: If this becomes a perf bottleneck, the `partialDestroyStartedMask` // may be able to be set non-atomically. But it is easier to reason about // the code if the flag is set atomically. - while (1) + while (true) { RefCountPair const prevVal{prevIntVal}; // Converted the last strong pointer to a weak pointer. @@ -343,7 +343,7 @@ IntrusiveRefCounts::releaseWeakRef() const RefCountPair prev = prevIntVal; if (prev.weak == 1 && prev.strong == 0) { - if (!prev.partialDestroyStartedBit) + if (prev.partialDestroyStartedBit == 0u) { // This case should only be hit if the partialDestroyStartedBit is // set non-atomically (and even then very rarely). The code is kept @@ -352,7 +352,7 @@ IntrusiveRefCounts::releaseWeakRef() const prevIntVal = refCounts.load(std::memory_order_acquire); prev = RefCountPair{prevIntVal}; } - if (!prev.partialDestroyFinishedBit) + if (prev.partialDestroyFinishedBit == 0u) { // partial destroy MUST finish before running a full destroy (when // using weak pointers) @@ -372,7 +372,7 @@ IntrusiveRefCounts::checkoutStrongRefFromWeak() const noexcept while (!refCounts.compare_exchange_weak(curValue, desiredValue, std::memory_order_acq_rel)) { RefCountPair const prev{curValue}; - if (!prev.strong) + if (prev.strong == 0u) return false; desiredValue = curValue + strongDelta; diff --git a/include/xrpl/basics/LocalValue.h b/include/xrpl/basics/LocalValue.h index 4ac76b130d..421ea7af23 100644 --- a/include/xrpl/basics/LocalValue.h +++ b/include/xrpl/basics/LocalValue.h @@ -4,6 +4,7 @@ #include #include +#include namespace xrpl { @@ -28,7 +29,7 @@ struct LocalValues T t_; Value() = default; - explicit Value(T const& t) : t_(t) + explicit Value(T t) : t_(std::move(t)) { } @@ -42,10 +43,10 @@ struct LocalValues // Keys are the address of a LocalValue. std::unordered_map> values; - static inline void + static void cleanup(LocalValues* lvs) { - if (lvs && !lvs->onCoro) + if ((lvs != nullptr) && !lvs->onCoro) delete lvs; } }; @@ -89,7 +90,7 @@ T& LocalValue::operator*() { auto lvs = detail::getLocalValues().get(); - if (!lvs) + if (lvs == nullptr) { lvs = new detail::LocalValues(); lvs->onCoro = false; diff --git a/include/xrpl/basics/Log.h b/include/xrpl/basics/Log.h index 08da3e57b5..4efbec5199 100644 --- a/include/xrpl/basics/Log.h +++ b/include/xrpl/basics/Log.h @@ -38,7 +38,7 @@ private: std::string partition_; public: - Sink(std::string const& partition, beast::severities::Severity thresh, Logs& logs); + Sink(std::string partition, beast::severities::Severity thresh, Logs& logs); Sink(Sink const&) = delete; Sink& @@ -226,7 +226,7 @@ private: // expensive argument lists if the stream is not active. #ifndef JLOG #define JLOG(x) \ - if (!x) \ + if (!(x)) \ { \ } \ else \ @@ -235,7 +235,7 @@ private: #ifndef CLOG #define CLOG(ss) \ - if (!ss) \ + if (!(ss)) \ ; \ else \ *ss diff --git a/include/xrpl/basics/Number.h b/include/xrpl/basics/Number.h index c39aae2dd3..51ade0b5ea 100644 --- a/include/xrpl/basics/Number.h +++ b/include/xrpl/basics/Number.h @@ -78,7 +78,7 @@ struct MantissaRange } rep min; - rep max{min * 10 - 1}; + rep max{(min * 10) - 1}; int log; mantissa_scale scale; @@ -342,7 +342,9 @@ public: constexpr int signum() const noexcept { - return negative_ ? -1 : (mantissa_ ? 1 : 0); + if (negative_) + return -1; + return (mantissa_ != 0u) ? 1 : 0; } Number @@ -402,19 +404,19 @@ public: static void setMantissaScale(MantissaRange::mantissa_scale scale); - inline static internalrep + static internalrep minMantissa() { return range_.get().min; } - inline static internalrep + static internalrep maxMantissa() { return range_.get().max; } - inline static int + static int mantissaLog() { return range_.get().log; @@ -507,16 +509,12 @@ private: class Guard; }; -inline constexpr Number::Number( - bool negative, - internalrep mantissa, - int exponent, - unchecked) noexcept +constexpr Number::Number(bool negative, internalrep mantissa, int exponent, unchecked) noexcept : negative_(negative), mantissa_{mantissa}, exponent_{exponent} { } -inline constexpr Number::Number(internalrep mantissa, int exponent, unchecked) noexcept +constexpr Number::Number(internalrep mantissa, int exponent, unchecked) noexcept : Number(false, mantissa, exponent, unchecked{}) { } @@ -548,7 +546,7 @@ inline Number::Number(rep mantissa) : Number{mantissa, 0} * Please see the "---- External Interface ----" section of the class * documentation for an explanation of why the internal value may be modified. */ -inline constexpr Number::rep +constexpr Number::rep Number::mantissa() const noexcept { auto m = mantissa_; @@ -569,7 +567,7 @@ Number::mantissa() const noexcept * Please see the "---- External Interface ----" section of the class * documentation for an explanation of why the internal value may be modified. */ -inline constexpr int +constexpr int Number::exponent() const noexcept { auto e = exponent_; @@ -584,13 +582,13 @@ Number::exponent() const noexcept return e; } -inline constexpr Number +constexpr Number Number::operator+() const noexcept { return *this; } -inline constexpr Number +constexpr Number Number::operator-() const noexcept { if (mantissa_ == 0) @@ -705,17 +703,19 @@ Number::normalizeToRange(T minMantissa, T maxMantissa) const int exponent = exponent_; if constexpr (std::is_unsigned_v) + { XRPL_ASSERT_PARTS( !negative, "xrpl::Number::normalizeToRange", "Number is non-negative for unsigned range."); + } Number::normalize(negative, mantissa, exponent, minMantissa, maxMantissa); auto const sign = negative ? -1 : 1; return std::make_pair(static_cast(sign * mantissa), exponent); } -inline constexpr Number +constexpr Number abs(Number x) noexcept { if (x < Number{}) @@ -746,7 +746,7 @@ power(Number const& f, unsigned n, unsigned d); // Return 0 if abs(x) < limit, else returns x -inline constexpr Number +constexpr Number squelch(Number const& x, Number const& limit) noexcept { if (abs(x) < limit) diff --git a/include/xrpl/basics/README.md b/include/xrpl/basics/README.md index f8b19522cd..b20b837ed7 100644 --- a/include/xrpl/basics/README.md +++ b/include/xrpl/basics/README.md @@ -2,9 +2,9 @@ Utility functions and classes. -ripple/basic should contain no dependencies on other modules. +The module xrpl/basics should contain no dependencies on other modules. -# Choosing a rippled container. +# Choosing an xrpld container. - `std::vector` - For ordered containers with most insertions or erases at the end. diff --git a/include/xrpl/basics/RangeSet.h b/include/xrpl/basics/RangeSet.h index f6e03cac79..4e54624056 100644 --- a/include/xrpl/basics/RangeSet.h +++ b/include/xrpl/basics/RangeSet.h @@ -117,22 +117,32 @@ from_string(RangeSet& rs, std::string const& s) case 1: { T front; if (!beast::lexicalCastChecked(front, intervals.front())) + { result = false; + } else + { rs.insert(front); + } break; } case 2: { T front; if (!beast::lexicalCastChecked(front, intervals.front())) + { result = false; + } else { T back; if (!beast::lexicalCastChecked(back, intervals.back())) + { result = false; + } else + { rs.insert(range(front, back)); + } } break; } diff --git a/include/xrpl/basics/SlabAllocator.h b/include/xrpl/basics/SlabAllocator.h index 4ed88a32f7..5cc17858e2 100644 --- a/include/xrpl/basics/SlabAllocator.h +++ b/include/xrpl/basics/SlabAllocator.h @@ -60,18 +60,16 @@ class SlabAllocator { // Use memcpy to avoid unaligned UB // (will optimize to equivalent code) - std::memcpy(data, &l_, sizeof(std::uint8_t*)); + std::memcpy(data, static_cast(&l_), sizeof(std::uint8_t*)); l_ = data; data += item; } } - ~SlabBlock() - { - // Calling this destructor will release the allocated memory but - // will not properly destroy any objects that are constructed in - // the block itself. - } + // Calling this destructor will release the allocated memory but + // will not properly destroy any objects that are constructed in + // the block itself. + ~SlabBlock() = default; SlabBlock(SlabBlock const& other) = delete; SlabBlock& @@ -98,11 +96,11 @@ class SlabAllocator ret = l_; - if (ret) + if (ret != nullptr) { // Use memcpy to avoid unaligned UB // (will optimize to equivalent code) - std::memcpy(&l_, ret, sizeof(std::uint8_t*)); + std::memcpy(static_cast(&l_), ret, sizeof(std::uint8_t*)); } } @@ -127,7 +125,7 @@ class SlabAllocator // Use memcpy to avoid unaligned UB // (will optimize to equivalent code) - std::memcpy(ptr, &l_, sizeof(std::uint8_t*)); + std::memcpy(ptr, static_cast(&l_), sizeof(std::uint8_t*)); l_ = ptr; } }; @@ -159,7 +157,7 @@ public: std::size_t extra, std::size_t alloc = 0, std::size_t align = 0) - : itemAlignment_(align ? align : alignof(Type)) + : itemAlignment_((align != 0u) ? align : alignof(Type)) , itemSize_(boost::alignment::align_up(sizeof(Type) + extra, itemAlignment_)) , slabSize_(alloc) { @@ -176,12 +174,10 @@ public: SlabAllocator& operator=(SlabAllocator&& other) = delete; - ~SlabAllocator() - { - // FIXME: We can't destroy the memory blocks we've allocated, because - // we can't be sure that they are not being used. Cleaning the - // shutdown process up could make this possible. - } + // FIXME: We can't destroy the memory blocks we've allocated, because + // we can't be sure that they are not being used. Cleaning the + // shutdown process up could make this possible. + ~SlabAllocator() = default; /** Returns the size of the memory block this allocator returns. */ constexpr std::size_t @@ -215,7 +211,7 @@ public: // We want to allocate the memory at a 2 MiB boundary, to make it // possible to use hugepage mappings on Linux: auto buf = boost::alignment::aligned_alloc(megabytes(std::size_t(2)), size); - if (!buf) [[unlikely]] + if (buf == nullptr) [[unlikely]] return nullptr; #if BOOST_OS_LINUX @@ -235,7 +231,7 @@ public: // This operation is essentially guaranteed not to fail but // let's be careful anyways. - if (!boost::alignment::align(itemAlignment_, itemSize_, slabData, slabSize)) + if (boost::alignment::align(itemAlignment_, itemSize_, slabData, slabSize) == nullptr) { boost::alignment::aligned_free(buf); return nullptr; @@ -288,7 +284,7 @@ class SlabAllocatorSet { private: // The list of allocators that belong to this set - boost::container::static_vector, 64> allocators_; + boost::container::static_vector, 64> allocators_{}; std::size_t maxSize_ = 0; @@ -347,9 +343,7 @@ public: SlabAllocatorSet& operator=(SlabAllocatorSet&& other) = delete; - ~SlabAllocatorSet() - { - } + ~SlabAllocatorSet() = default; /** Returns a suitably aligned pointer, if one is available. diff --git a/include/xrpl/basics/Slice.h b/include/xrpl/basics/Slice.h index 6aa5446236..08ee9464ef 100644 --- a/include/xrpl/basics/Slice.h +++ b/include/xrpl/basics/Slice.h @@ -183,7 +183,7 @@ operator==(Slice const& lhs, Slice const& rhs) noexcept if (lhs.size() != rhs.size()) return false; - if (lhs.size() == 0) + if (lhs.empty()) return true; return std::memcmp(lhs.data(), rhs.data(), lhs.size()) == 0; @@ -211,14 +211,14 @@ operator<<(Stream& s, Slice const& v) } template -std::enable_if_t::value || std::is_same::value, Slice> +std::enable_if_t || std::is_same_v, Slice> makeSlice(std::array const& a) { return Slice(a.data(), a.size()); } template -std::enable_if_t::value || std::is_same::value, Slice> +std::enable_if_t || std::is_same_v, Slice> makeSlice(std::vector const& v) { return Slice(v.data(), v.size()); diff --git a/include/xrpl/basics/TaggedCache.h b/include/xrpl/basics/TaggedCache.h index 1a19c653bc..d20c850bad 100644 --- a/include/xrpl/basics/TaggedCache.h +++ b/include/xrpl/basics/TaggedCache.h @@ -251,7 +251,7 @@ private: } }; - typedef typename std::conditional::type Entry; + using Entry = std::conditional_t; using KeyOnlyCacheType = hardened_partitioned_hash_map; diff --git a/include/xrpl/basics/ToString.h b/include/xrpl/basics/ToString.h index a6254a1880..28f0245404 100644 --- a/include/xrpl/basics/ToString.h +++ b/include/xrpl/basics/ToString.h @@ -12,7 +12,7 @@ namespace xrpl { */ template -typename std::enable_if::value, std::string>::type +std::enable_if_t, std::string> to_string(T t) { return std::to_string(t); diff --git a/include/xrpl/basics/UptimeClock.h b/include/xrpl/basics/UptimeClock.h index 4edd38d274..3f2e09bbe3 100644 --- a/include/xrpl/basics/UptimeClock.h +++ b/include/xrpl/basics/UptimeClock.h @@ -26,7 +26,7 @@ public: explicit UptimeClock() = default; static time_point - now(); // seconds since rippled program start + now(); // seconds since xrpld program start private: static std::atomic now_; diff --git a/include/xrpl/basics/algorithm.h b/include/xrpl/basics/algorithm.h index b3fdd5453d..9dae731a89 100644 --- a/include/xrpl/basics/algorithm.h +++ b/include/xrpl/basics/algorithm.h @@ -23,8 +23,10 @@ generalized_set_intersection( { while (first1 != last1 && first2 != last2) { - if (comp(*first1, *first2)) // if *first1 < *first2 - ++first1; // then reduce first range + if (comp(*first1, *first2)) + { // if *first1 < *first2 + ++first1; // then reduce first range + } else { if (!comp(*first2, *first1)) // if *first1 == *first2 diff --git a/include/xrpl/basics/base_uint.h b/include/xrpl/basics/base_uint.h index 5fb13319ea..55b73bfb9b 100644 --- a/include/xrpl/basics/base_uint.h +++ b/include/xrpl/basics/base_uint.h @@ -183,11 +183,17 @@ private: return ParseResult::badChar; if (c >= 'a') + { nibble = static_cast(c - 'a' + 0xA); + } else if (c >= 'A') + { nibble = static_cast(c - 'A' + 0xA); + } else if (c <= '9') + { nibble = static_cast(c - '0'); + } if (nibble > 0xFu) return ParseResult::badChar; @@ -263,7 +269,7 @@ public: class Container, class = std::enable_if_t< detail::is_contiguous_container::value && - std::is_trivially_copyable::value>> + std::is_trivially_copyable_v>> explicit base_uint(Container const& c) { XRPL_ASSERT( @@ -275,7 +281,7 @@ public: template std::enable_if_t< detail::is_contiguous_container::value && - std::is_trivially_copyable::value, + std::is_trivially_copyable_v, base_uint&> operator=(Container const& c) { @@ -308,8 +314,10 @@ public: signum() const { for (int i = 0; i < WIDTH; i++) + { if (data_[i] != 0) return 1; + } return 0; } @@ -390,7 +398,7 @@ public: return *this; } - base_uint const + base_uint operator++(int) { // postfix operator @@ -415,7 +423,7 @@ public: return *this; } - base_uint const + base_uint operator--(int) { // postfix operator @@ -444,7 +452,7 @@ public: { std::uint64_t carry = 0; - for (int i = WIDTH; i--;) + for (int i = WIDTH - 1; i >= 0; i--) { std::uint64_t const n = carry + boost::endian::big_to_native(data_[i]) + boost::endian::big_to_native(b.data_[i]); @@ -532,7 +540,7 @@ using uint256 = base_uint<256>; using uint192 = base_uint<192>; template -[[nodiscard]] inline constexpr std::strong_ordering +[[nodiscard]] constexpr std::strong_ordering operator<=>(base_uint const& lhs, base_uint const& rhs) { // This comparison might seem wrong on a casual inspection because it @@ -553,7 +561,7 @@ operator<=>(base_uint const& lhs, base_uint const& rhs) } template -[[nodiscard]] inline constexpr bool +[[nodiscard]] constexpr bool operator==(base_uint const& lhs, base_uint const& rhs) { return (lhs <=> rhs) == 0; @@ -561,7 +569,7 @@ operator==(base_uint const& lhs, base_uint const& rhs) //------------------------------------------------------------------------------ template -inline constexpr bool +constexpr bool operator==(base_uint const& a, std::uint64_t b) { return a == base_uint(b); @@ -569,28 +577,28 @@ operator==(base_uint const& a, std::uint64_t b) //------------------------------------------------------------------------------ template -inline constexpr base_uint +constexpr base_uint operator^(base_uint const& a, base_uint const& b) { return base_uint(a) ^= b; } template -inline constexpr base_uint +constexpr base_uint operator&(base_uint const& a, base_uint const& b) { return base_uint(a) &= b; } template -inline constexpr base_uint +constexpr base_uint operator|(base_uint const& a, base_uint const& b) { return base_uint(a) |= b; } template -inline constexpr base_uint +constexpr base_uint operator+(base_uint const& a, base_uint const& b) { return base_uint(a) += b; diff --git a/include/xrpl/basics/contract.h b/include/xrpl/basics/contract.h index e2d6dafe55..0d9f567416 100644 --- a/include/xrpl/basics/contract.h +++ b/include/xrpl/basics/contract.h @@ -49,8 +49,7 @@ template Throw(Args&&... args) { static_assert( - std::is_convertible::value, - "Exception must derive from std::exception."); + std::is_convertible_v, "Exception must derive from std::exception."); E e(std::forward(args)...); LogThrow(std::string("Throwing exception of type " + beast::type_name() + ": ") + e.what()); diff --git a/include/xrpl/basics/hardened_hash.h b/include/xrpl/basics/hardened_hash.h index 05e6ab417f..3ff14c22c8 100644 --- a/include/xrpl/basics/hardened_hash.h +++ b/include/xrpl/basics/hardened_hash.h @@ -72,14 +72,12 @@ template class hardened_hash { private: - detail::seed_pair m_seeds; + detail::seed_pair m_seeds{detail::make_seed_pair<>()}; public: using result_type = typename HashAlgorithm::result_type; - hardened_hash() : m_seeds(detail::make_seed_pair<>()) - { - } + hardened_hash() = default; template result_type diff --git a/include/xrpl/basics/partitioned_unordered_map.h b/include/xrpl/basics/partitioned_unordered_map.h index f9e55d71a6..33fe63e91b 100644 --- a/include/xrpl/basics/partitioned_unordered_map.h +++ b/include/xrpl/basics/partitioned_unordered_map.h @@ -57,7 +57,7 @@ public: { using iterator_category = std::forward_iterator_tag; partition_map_type* map_{nullptr}; - typename partition_map_type::iterator ait_; + typename partition_map_type::iterator ait_{}; typename map_type::iterator mit_; iterator() = default; @@ -126,7 +126,7 @@ public: using iterator_category = std::forward_iterator_tag; partition_map_type* map_{nullptr}; - typename partition_map_type::iterator ait_; + typename partition_map_type::iterator ait_{}; typename map_type::iterator mit_; const_iterator() = default; @@ -231,7 +231,8 @@ public: { // Set partitions to the number of hardware threads if the parameter // is either empty or set to 0. - partitions_ = partitions && *partitions ? *partitions : std::thread::hardware_concurrency(); + partitions_ = + partitions && (*partitions != 0u) ? *partitions : std::thread::hardware_concurrency(); map_.resize(partitions_); XRPL_ASSERT( partitions_, diff --git a/include/xrpl/basics/random.h b/include/xrpl/basics/random.h index db66b303d4..adf579442e 100644 --- a/include/xrpl/basics/random.h +++ b/include/xrpl/basics/random.h @@ -15,15 +15,15 @@ namespace xrpl { #ifndef __INTELLISENSE__ static_assert( // NOLINTNEXTLINE(misc-redundant-expression) - std::is_integral::value && - std::is_unsigned::value, - "The Ripple default PRNG engine must return an unsigned integral type."); + std::is_integral_v && + std::is_unsigned_v, + "The XRPL default PRNG engine must return an unsigned integral type."); static_assert( // NOLINTNEXTLINE(misc-redundant-expression) std::numeric_limits::max() >= std::numeric_limits::max(), - "The Ripple default PRNG engine return must be at least 64 bits wide."); + "The XRPL default PRNG engine return must be at least 64 bits wide."); #endif namespace detail { @@ -91,7 +91,7 @@ default_prng() */ /** @{ */ template -std::enable_if_t::value && detail::is_engine::value, Integral> +std::enable_if_t && detail::is_engine::value, Integral> rand_int(Engine& engine, Integral min, Integral max) { XRPL_ASSERT(max > min, "xrpl::rand_int : max over min inputs"); @@ -103,35 +103,35 @@ rand_int(Engine& engine, Integral min, Integral max) } template -std::enable_if_t::value, Integral> +std::enable_if_t, Integral> rand_int(Integral min, Integral max) { return rand_int(default_prng(), min, max); } template -std::enable_if_t::value && detail::is_engine::value, Integral> +std::enable_if_t && detail::is_engine::value, Integral> rand_int(Engine& engine, Integral max) { return rand_int(engine, Integral(0), max); } template -std::enable_if_t::value, Integral> +std::enable_if_t, Integral> rand_int(Integral max) { return rand_int(default_prng(), max); } template -std::enable_if_t::value && detail::is_engine::value, Integral> +std::enable_if_t && detail::is_engine::value, Integral> rand_int(Engine& engine) { return rand_int(engine, std::numeric_limits::max()); } template -std::enable_if_t::value, Integral> +std::enable_if_t, Integral> rand_int() { return rand_int(default_prng(), std::numeric_limits::max()); @@ -142,7 +142,7 @@ rand_int() /** @{ */ template std::enable_if_t< - (std::is_same::value || std::is_same::value) && + (std::is_same_v || std::is_same_v) && detail::is_engine::value, Byte> rand_byte(Engine& engine) @@ -152,9 +152,7 @@ rand_byte(Engine& engine) } template -std::enable_if_t< - (std::is_same::value || std::is_same::value), - Byte> +std::enable_if_t<(std::is_same_v || std::is_same_v), Byte> rand_byte() { return rand_byte(default_prng()); diff --git a/include/xrpl/basics/safe_cast.h b/include/xrpl/basics/safe_cast.h index 1e33b9663a..c167e660ce 100644 --- a/include/xrpl/basics/safe_cast.h +++ b/include/xrpl/basics/safe_cast.h @@ -12,12 +12,12 @@ namespace xrpl { template concept SafeToCast = (std::is_integral_v && std::is_integral_v) && - (std::is_signed::value || std::is_unsigned::value) && - (std::is_signed::value != std::is_signed::value ? sizeof(Dest) > sizeof(Src) - : sizeof(Dest) >= sizeof(Src)); + (std::is_signed_v || std::is_unsigned_v) && + (std::is_signed_v != std::is_signed_v ? sizeof(Dest) > sizeof(Src) + : sizeof(Dest) >= sizeof(Src)); template -inline constexpr std::enable_if_t && std::is_integral_v, Dest> +constexpr std::enable_if_t && std::is_integral_v, Dest> safe_cast(Src s) noexcept { static_assert( @@ -30,14 +30,14 @@ safe_cast(Src s) noexcept } template -inline constexpr std::enable_if_t && std::is_integral_v, Dest> +constexpr std::enable_if_t && std::is_integral_v, Dest> safe_cast(Src s) noexcept { return static_cast(safe_cast>(s)); } template -inline constexpr std::enable_if_t && std::is_enum_v, Dest> +constexpr std::enable_if_t && std::is_enum_v, Dest> safe_cast(Src s) noexcept { return safe_cast(static_cast>(s)); @@ -48,7 +48,7 @@ safe_cast(Src s) noexcept // underlying types become safe, it can be converted to a safe_cast. template -inline constexpr std::enable_if_t && std::is_integral_v, Dest> +constexpr std::enable_if_t && std::is_integral_v, Dest> unsafe_cast(Src s) noexcept { static_assert( @@ -59,14 +59,14 @@ unsafe_cast(Src s) noexcept } template -inline constexpr std::enable_if_t && std::is_integral_v, Dest> +constexpr std::enable_if_t && std::is_integral_v, Dest> unsafe_cast(Src s) noexcept { return static_cast(unsafe_cast>(s)); } template -inline constexpr std::enable_if_t && std::is_enum_v, Dest> +constexpr std::enable_if_t && std::is_enum_v, Dest> unsafe_cast(Src s) noexcept { return unsafe_cast(static_cast>(s)); diff --git a/include/xrpl/basics/strHex.h b/include/xrpl/basics/strHex.h index cc07bf5b9d..9cae234f06 100644 --- a/include/xrpl/basics/strHex.h +++ b/include/xrpl/basics/strHex.h @@ -10,9 +10,9 @@ std::string strHex(FwdIt begin, FwdIt end) { static_assert( - std::is_convertible< + std::is_convertible_v< typename std::iterator_traits::iterator_category, - std::forward_iterator_tag>::value, + std::forward_iterator_tag>, "FwdIt must be a forward iterator"); std::string result; result.reserve(2 * std::distance(begin, end)); diff --git a/include/xrpl/basics/tagged_integer.h b/include/xrpl/basics/tagged_integer.h index 18d6a707a0..129c586126 100644 --- a/include/xrpl/basics/tagged_integer.h +++ b/include/xrpl/basics/tagged_integer.h @@ -44,8 +44,7 @@ public: template < class OtherInt, - class = typename std::enable_if< - std::is_integral::value && sizeof(OtherInt) <= sizeof(Int)>::type> + class = std::enable_if_t && sizeof(OtherInt) <= sizeof(Int)>> explicit constexpr tagged_integer(OtherInt value) noexcept : m_value(value) { static_assert(sizeof(tagged_integer) == sizeof(Int), "tagged_integer is adding padding"); diff --git a/include/xrpl/beast/asio/io_latency_probe.h b/include/xrpl/beast/asio/io_latency_probe.h index 2dc1fcba15..9a8a63de4e 100644 --- a/include/xrpl/beast/asio/io_latency_probe.h +++ b/include/xrpl/beast/asio/io_latency_probe.h @@ -184,7 +184,7 @@ private: void operator()() const { - if (!m_probe) + if (m_probe == nullptr) return; typename Clock::time_point const now(Clock::now()); typename Clock::duration const elapsed(now - m_start); @@ -202,7 +202,7 @@ private: // Calculate when we want to sample again, and // adjust for the expected latency. // - typename Clock::time_point const when(now + m_probe->m_period - 2 * elapsed); + typename Clock::time_point const when(now + m_probe->m_period - (2 * elapsed)); if (when <= now) { @@ -224,7 +224,7 @@ private: void operator()(boost::system::error_code const& ec) { - if (!m_probe) + if (m_probe == nullptr) return; typename Clock::time_point const now(Clock::now()); boost::asio::post( diff --git a/include/xrpl/beast/container/aged_container_utility.h b/include/xrpl/beast/container/aged_container_utility.h index cf6bae9990..47aa8a5e66 100644 --- a/include/xrpl/beast/container/aged_container_utility.h +++ b/include/xrpl/beast/container/aged_container_utility.h @@ -9,7 +9,7 @@ namespace beast { /** Expire aged container items past the specified age. */ template -typename std::enable_if::value, std::size_t>::type +std::enable_if_t::value, std::size_t> expire(AgedContainer& c, std::chrono::duration const& age) { std::size_t n(0); diff --git a/include/xrpl/beast/container/detail/aged_associative_container.h b/include/xrpl/beast/container/detail/aged_associative_container.h index 34e9560cbb..5d3534004c 100644 --- a/include/xrpl/beast/container/detail/aged_associative_container.h +++ b/include/xrpl/beast/container/detail/aged_associative_container.h @@ -1,7 +1,6 @@ #pragma once -namespace beast { -namespace detail { +namespace beast::detail { // Extracts the key portion of value template @@ -26,9 +25,8 @@ struct aged_associative_container_extract_t Value const& operator()(Value const& value) const { - return value; + return value; // NOLINT(bugprone-return-const-ref-from-parameter) } }; -} // namespace detail -} // namespace beast +} // namespace beast::detail diff --git a/include/xrpl/beast/container/detail/aged_container_iterator.h b/include/xrpl/beast/container/detail/aged_container_iterator.h index 99aab2a9a9..3f12a5610a 100644 --- a/include/xrpl/beast/container/detail/aged_container_iterator.h +++ b/include/xrpl/beast/container/detail/aged_container_iterator.h @@ -2,6 +2,7 @@ #include #include +#include namespace beast { @@ -16,10 +17,10 @@ class aged_container_iterator { public: using iterator_category = typename std::iterator_traits::iterator_category; - using value_type = typename std::conditional< + using value_type = std::conditional_t< is_const, typename Iterator::value_type::stashed::value_type const, - typename Iterator::value_type::stashed::value_type>::type; + typename Iterator::value_type::stashed::value_type>; using difference_type = typename std::iterator_traits::difference_type; using pointer = value_type*; using reference = value_type&; @@ -32,9 +33,9 @@ public: template < bool other_is_const, class OtherIterator, - class = typename std::enable_if< - (other_is_const == false || is_const == true) && - std::is_same::value == false>::type> + class = std::enable_if_t< + (!other_is_const || is_const) && + !static_cast(std::is_same_v)>> explicit aged_container_iterator( aged_container_iterator const& other) : m_iter(other.m_iter) @@ -42,9 +43,7 @@ public: } // Disable constructing a const_iterator from a non-const_iterator. - template < - bool other_is_const, - class = typename std::enable_if::type> + template > aged_container_iterator(aged_container_iterator const& other) : m_iter(other.m_iter) { @@ -53,8 +52,8 @@ public: // Disable assigning a const_iterator to a non-const iterator template auto - operator=(aged_container_iterator const& other) -> typename std:: - enable_if::type + operator=(aged_container_iterator const& other) + -> std::enable_if_t { m_iter = other.m_iter; return *this; @@ -133,7 +132,7 @@ private: friend class aged_container_iterator; template - aged_container_iterator(OtherIterator const& iter) : m_iter(iter) + aged_container_iterator(OtherIterator iter) : m_iter(std::move(iter)) { } diff --git a/include/xrpl/beast/container/detail/aged_ordered_container.h b/include/xrpl/beast/container/detail/aged_ordered_container.h index dad0d92e0b..b20639aec4 100644 --- a/include/xrpl/beast/container/detail/aged_ordered_container.h +++ b/include/xrpl/beast/container/detail/aged_ordered_container.h @@ -57,8 +57,7 @@ template < class T, class Clock = std::chrono::steady_clock, class Compare = std::less, - class Allocator = - std::allocator, Key>::type>> + class Allocator = std::allocator, Key>>> class aged_ordered_container { public: @@ -67,7 +66,7 @@ public: using duration = typename clock_type::duration; using key_type = Key; using mapped_type = T; - using value_type = typename std::conditional, Key>::type; + using value_type = std::conditional_t, Key>; using size_type = std::size_t; using difference_type = std::ptrdiff_t; @@ -110,8 +109,7 @@ private: template < class... Args, - class = - typename std::enable_if::value>::type> + class = std::enable_if_t>> element(time_point const& when_, Args&&... args) : value(std::forward(args)...), when(when_) { @@ -135,9 +133,7 @@ private: return Compare::operator()(lhs.first, rhs.first); } - pair_value_compare() - { - } + pair_value_compare() = default; pair_value_compare(pair_value_compare const& other) : Compare(other) { @@ -200,7 +196,7 @@ private: using list_type = typename boost::intrusive:: make_list>::type; - using cont_type = typename std::conditional< + using cont_type = std::conditional_t< IsMulti, typename boost::intrusive::make_multiset< element, @@ -209,7 +205,7 @@ private: typename boost::intrusive::make_set< element, boost::intrusive::constant_time_size, - boost::intrusive::compare>::type>::type; + boost::intrusive::compare>::type>; using ElementAllocator = typename std::allocator_traits::template rebind_alloc; @@ -257,7 +253,8 @@ private: config_t(config_t&& other) : KeyValueCompare(std::move(other.key_compare())) - , beast::detail::empty_base_optimization(std::move(other)) + , beast::detail::empty_base_optimization(std::move( + static_cast&>(other))) , clock(other.clock) { } @@ -373,7 +370,7 @@ private: public: using key_compare = Compare; - using value_compare = typename std::conditional::type; + using value_compare = std::conditional_t; using allocator_type = Allocator; using reference = value_type&; using const_reference = value_type const&; @@ -401,6 +398,8 @@ public: class chronological_t { + chronological_t() = default; + public: // A set iterator (IsMap==false) is always const // because the elements of a set are immutable. @@ -488,7 +487,7 @@ public: iterator iterator_to(value_type& value) { - static_assert(std::is_standard_layout::value, "must be standard layout"); + static_assert(std::is_standard_layout_v, "must be standard layout"); return list.iterator_to(*reinterpret_cast( reinterpret_cast(&value) - ((std::size_t)std::addressof(((element*)0)->member)))); @@ -497,20 +496,16 @@ public: const_iterator iterator_to(value_type const& value) const { - static_assert(std::is_standard_layout::value, "must be standard layout"); + static_assert(std::is_standard_layout_v, "must be standard layout"); return list.iterator_to(*reinterpret_cast( reinterpret_cast(&value) - ((std::size_t)std::addressof(((element*)0)->member)))); } - private: - chronological_t() - { - } - chronological_t(chronological_t const&) = delete; chronological_t(chronological_t&&) = delete; + private: friend class aged_ordered_container; list_type mutable list; } chronological; @@ -616,30 +611,30 @@ public: class K, bool maybe_multi = IsMulti, bool maybe_map = IsMap, - class = typename std::enable_if::type> - typename std::conditional::type& + class = std::enable_if_t> + std::conditional_t& at(K const& k); template < class K, bool maybe_multi = IsMulti, bool maybe_map = IsMap, - class = typename std::enable_if::type> + class = std::enable_if_t> typename std::conditional::type const& at(K const& k) const; template < bool maybe_multi = IsMulti, bool maybe_map = IsMap, - class = typename std::enable_if::type> - typename std::conditional::type& + class = std::enable_if_t> + std::conditional_t& operator[](Key const& key); template < bool maybe_multi = IsMulti, bool maybe_map = IsMap, - class = typename std::enable_if::type> - typename std::conditional::type& + class = std::enable_if_t> + std::conditional_t& operator[](Key&& key); //-------------------------------------------------------------------------- @@ -723,7 +718,7 @@ public: iterator iterator_to(value_type& value) { - static_assert(std::is_standard_layout::value, "must be standard layout"); + static_assert(std::is_standard_layout_v, "must be standard layout"); return m_cont.iterator_to(*reinterpret_cast( reinterpret_cast(&value) - ((std::size_t)std::addressof(((element*)0)->member)))); @@ -732,7 +727,7 @@ public: const_iterator iterator_to(value_type const& value) const { - static_assert(std::is_standard_layout::value, "must be standard layout"); + static_assert(std::is_standard_layout_v, "must be standard layout"); return m_cont.iterator_to(*reinterpret_cast( reinterpret_cast(&value) - ((std::size_t)std::addressof(((element*)0)->member)))); @@ -774,37 +769,35 @@ public: // map, set template auto - insert(value_type const& value) -> - typename std::enable_if>::type; + insert(value_type const& value) -> std::enable_if_t>; // multimap, multiset template auto - insert(value_type const& value) -> typename std::enable_if::type; + insert(value_type const& value) -> std::enable_if_t; // set template auto - insert(value_type&& value) -> - typename std::enable_if>::type; + insert(value_type&& value) + -> std::enable_if_t>; // multiset template auto - insert(value_type&& value) -> - typename std::enable_if::type; + insert(value_type&& value) -> std::enable_if_t; //--- // map, set template auto - insert(const_iterator hint, value_type const& value) -> - typename std::enable_if::type; + insert(const_iterator hint, value_type const& value) + -> std::enable_if_t; // multimap, multiset template - typename std::enable_if::type + std::enable_if_t insert(const_iterator /*hint*/, value_type const& value) { // VFALCO TODO Figure out how to utilize 'hint' @@ -814,12 +807,11 @@ public: // map, set template auto - insert(const_iterator hint, value_type&& value) -> - typename std::enable_if::type; + insert(const_iterator hint, value_type&& value) -> std::enable_if_t; // multimap, multiset template - typename std::enable_if::type + std::enable_if_t insert(const_iterator /*hint*/, value_type&& value) { // VFALCO TODO Figure out how to utilize 'hint' @@ -828,9 +820,9 @@ public: // map, multimap template - typename std::enable_if< - maybe_map && std::is_constructible::value, - typename std::conditional>::type>::type + std::enable_if_t< + maybe_map && std::is_constructible_v, + std::conditional_t>> insert(P&& value) { return emplace(std::forward

(value)); @@ -838,9 +830,9 @@ public: // map, multimap template - typename std::enable_if< - maybe_map && std::is_constructible::value, - typename std::conditional>::type>::type + std::enable_if_t< + maybe_map && std::is_constructible_v, + std::conditional_t>> insert(const_iterator hint, P&& value) { return emplace_hint(hint, std::forward

(value)); @@ -863,23 +855,22 @@ public: // map, set template auto - emplace(Args&&... args) -> - typename std::enable_if>::type; + emplace(Args&&... args) -> std::enable_if_t>; // multiset, multimap template auto - emplace(Args&&... args) -> typename std::enable_if::type; + emplace(Args&&... args) -> std::enable_if_t; // map, set template auto - emplace_hint(const_iterator hint, Args&&... args) -> - typename std::enable_if>::type; + emplace_hint(const_iterator hint, Args&&... args) + -> std::enable_if_t>; // multiset, multimap template - typename std::enable_if::type + std::enable_if_t emplace_hint(const_iterator /*hint*/, Args&&... args) { // VFALCO TODO Figure out how to utilize 'hint' @@ -1163,12 +1154,12 @@ private: template < bool maybe_propagate = std::allocator_traits::propagate_on_container_swap::value> - typename std::enable_if::type + std::enable_if_t swap_data(aged_ordered_container& other) noexcept; template < bool maybe_propagate = std::allocator_traits::propagate_on_container_swap::value> - typename std::enable_if::type + std::enable_if_t swap_data(aged_ordered_container& other) noexcept; private: @@ -1395,7 +1386,7 @@ aged_ordered_container::opera template template -typename std::conditional::type& +std::conditional_t& aged_ordered_container::at(K const& k) { auto const iter(m_cont.find(k, std::cref(m_config.key_compare()))); @@ -1417,7 +1408,7 @@ aged_ordered_container::at(K template template -typename std::conditional::type& +std::conditional_t& aged_ordered_container::operator[]( Key const& key) { @@ -1436,7 +1427,7 @@ aged_ordered_container::opera template template -typename std::conditional::type& +std::conditional_t& aged_ordered_container::operator[](Key&& key) { typename cont_type::insert_commit_data d; @@ -1471,8 +1462,7 @@ template auto aged_ordered_container::insert( - value_type const& value) -> - typename std::enable_if>::type + value_type const& value) -> std::enable_if_t> { typename cont_type::insert_commit_data d; auto const result(m_cont.insert_check(extract(value), std::cref(m_config.key_compare()), d)); @@ -1491,7 +1481,7 @@ template auto aged_ordered_container::insert( - value_type const& value) -> typename std::enable_if::type + value_type const& value) -> std::enable_if_t { auto const before(m_cont.upper_bound(extract(value), std::cref(m_config.key_compare()))); element* const p(new_element(value)); @@ -1505,8 +1495,7 @@ template auto aged_ordered_container::insert( - value_type&& value) -> - typename std::enable_if>::type + value_type&& value) -> std::enable_if_t> { typename cont_type::insert_commit_data d; auto const result(m_cont.insert_check(extract(value), std::cref(m_config.key_compare()), d)); @@ -1525,7 +1514,7 @@ template auto aged_ordered_container::insert( - value_type&& value) -> typename std::enable_if::type + value_type&& value) -> std::enable_if_t { auto const before(m_cont.upper_bound(extract(value), std::cref(m_config.key_compare()))); element* const p(new_element(std::move(value))); @@ -1542,7 +1531,7 @@ template auto aged_ordered_container::insert( const_iterator hint, - value_type const& value) -> typename std::enable_if::type + value_type const& value) -> std::enable_if_t { typename cont_type::insert_commit_data d; auto const result( @@ -1563,7 +1552,7 @@ template auto aged_ordered_container::insert( const_iterator hint, - value_type&& value) -> typename std::enable_if::type + value_type&& value) -> std::enable_if_t { typename cont_type::insert_commit_data d; auto const result( @@ -1583,7 +1572,7 @@ template auto aged_ordered_container::emplace(Args&&... args) - -> typename std::enable_if>::type + -> std::enable_if_t> { // VFALCO NOTE Its unfortunate that we need to // construct element here @@ -1605,7 +1594,7 @@ template auto aged_ordered_container::emplace(Args&&... args) - -> typename std::enable_if::type + -> std::enable_if_t { element* const p(new_element(std::forward(args)...)); auto const before(m_cont.upper_bound(extract(p->value), std::cref(m_config.key_compare()))); @@ -1620,7 +1609,7 @@ template auto aged_ordered_container::emplace_hint( const_iterator hint, - Args&&... args) -> typename std::enable_if>::type + Args&&... args) -> std::enable_if_t> { // VFALCO NOTE Its unfortunate that we need to // construct element here @@ -1770,7 +1759,7 @@ aged_ordered_container::touch template template -typename std::enable_if::type +std::enable_if_t aged_ordered_container::swap_data( aged_ordered_container& other) noexcept { @@ -1781,7 +1770,7 @@ aged_ordered_container::swap_ template template -typename std::enable_if::type +std::enable_if_t aged_ordered_container::swap_data( aged_ordered_container& other) noexcept { diff --git a/include/xrpl/beast/container/detail/aged_unordered_container.h b/include/xrpl/beast/container/detail/aged_unordered_container.h index dfc853f019..15565bbada 100644 --- a/include/xrpl/beast/container/detail/aged_unordered_container.h +++ b/include/xrpl/beast/container/detail/aged_unordered_container.h @@ -62,8 +62,7 @@ template < class Clock = std::chrono::steady_clock, class Hash = std::hash, class KeyEqual = std::equal_to, - class Allocator = - std::allocator, Key>::type>> + class Allocator = std::allocator, Key>>> class aged_unordered_container { public: @@ -72,7 +71,7 @@ public: using duration = typename clock_type::duration; using key_type = Key; using mapped_type = T; - using value_type = typename std::conditional, Key>::type; + using value_type = std::conditional_t, Key>; using size_type = std::size_t; using difference_type = std::ptrdiff_t; @@ -115,8 +114,7 @@ private: template < class... Args, - class = - typename std::enable_if::value>::type> + class = std::enable_if_t>> element(time_point const& when_, Args&&... args) : value(std::forward(args)...), when(when_) { @@ -133,9 +131,7 @@ private: using argument_type = element; using result_type = size_t; - ValueHash() - { - } + ValueHash() = default; ValueHash(Hash const& h) : Hash(h) { @@ -169,9 +165,7 @@ private: using second_argument_type = element; using result_type = bool; - KeyValueEqual() - { - } + KeyValueEqual() = default; KeyValueEqual(KeyEqual const& keyEqual) : KeyEqual(keyEqual) { @@ -211,7 +205,7 @@ private: using list_type = typename boost::intrusive:: make_list>::type; - using cont_type = typename std::conditional< + using cont_type = std::conditional_t< IsMulti, typename boost::intrusive::make_unordered_multiset< element, @@ -224,7 +218,7 @@ private: boost::intrusive::constant_time_size, boost::intrusive::hash, boost::intrusive::equal, - boost::intrusive::cache_begin>::type>::type; + boost::intrusive::cache_begin>::type>; using bucket_type = typename cont_type::bucket_type; using bucket_traits = typename cont_type::bucket_traits; @@ -662,7 +656,7 @@ public: iterator iterator_to(value_type& value) { - static_assert(std::is_standard_layout::value, "must be standard layout"); + static_assert(std::is_standard_layout_v, "must be standard layout"); return list.iterator_to(*reinterpret_cast( reinterpret_cast(&value) - ((std::size_t)std::addressof(((element*)0)->member)))); @@ -671,20 +665,17 @@ public: const_iterator iterator_to(value_type const& value) const { - static_assert(std::is_standard_layout::value, "must be standard layout"); + static_assert(std::is_standard_layout_v, "must be standard layout"); return list.iterator_to(*reinterpret_cast( reinterpret_cast(&value) - ((std::size_t)std::addressof(((element*)0)->member)))); } - private: - chronological_t() - { - } - chronological_t(chronological_t const&) = delete; chronological_t(chronological_t&&) = delete; + chronological_t() = default; + private: friend class aged_unordered_container; list_type mutable list; } chronological; @@ -862,30 +853,30 @@ public: class K, bool maybe_multi = IsMulti, bool maybe_map = IsMap, - class = typename std::enable_if::type> - typename std::conditional::type& + class = std::enable_if_t> + std::conditional_t& at(K const& k); template < class K, bool maybe_multi = IsMulti, bool maybe_map = IsMap, - class = typename std::enable_if::type> + class = std::enable_if_t> typename std::conditional::type const& at(K const& k) const; template < bool maybe_multi = IsMulti, bool maybe_map = IsMap, - class = typename std::enable_if::type> - typename std::conditional::type& + class = std::enable_if_t> + std::conditional_t& operator[](Key const& key); template < bool maybe_multi = IsMulti, bool maybe_map = IsMap, - class = typename std::enable_if::type> - typename std::conditional::type& + class = std::enable_if_t> + std::conditional_t& operator[](Key&& key); //-------------------------------------------------------------------------- @@ -933,7 +924,7 @@ public: iterator iterator_to(value_type& value) { - static_assert(std::is_standard_layout::value, "must be standard layout"); + static_assert(std::is_standard_layout_v, "must be standard layout"); return m_cont.iterator_to(*reinterpret_cast( reinterpret_cast(&value) - ((std::size_t)std::addressof(((element*)0)->member)))); @@ -942,7 +933,7 @@ public: const_iterator iterator_to(value_type const& value) const { - static_assert(std::is_standard_layout::value, "must be standard layout"); + static_assert(std::is_standard_layout_v, "must be standard layout"); return m_cont.iterator_to(*reinterpret_cast( reinterpret_cast(&value) - ((std::size_t)std::addressof(((element*)0)->member)))); @@ -984,29 +975,27 @@ public: // map, set template auto - insert(value_type const& value) -> - typename std::enable_if>::type; + insert(value_type const& value) -> std::enable_if_t>; // multimap, multiset template auto - insert(value_type const& value) -> typename std::enable_if::type; + insert(value_type const& value) -> std::enable_if_t; // map, set template auto - insert(value_type&& value) -> - typename std::enable_if>::type; + insert(value_type&& value) + -> std::enable_if_t>; // multimap, multiset template auto - insert(value_type&& value) -> - typename std::enable_if::type; + insert(value_type&& value) -> std::enable_if_t; // map, set template - typename std::enable_if::type + std::enable_if_t insert(const_iterator /*hint*/, value_type const& value) { // Hint is ignored but we provide the interface so @@ -1016,7 +1005,7 @@ public: // multimap, multiset template - typename std::enable_if::type + std::enable_if_t insert(const_iterator /*hint*/, value_type const& value) { // VFALCO TODO The hint could be used to let @@ -1026,7 +1015,7 @@ public: // map, set template - typename std::enable_if::type + std::enable_if_t insert(const_iterator /*hint*/, value_type&& value) { // Hint is ignored but we provide the interface so @@ -1036,7 +1025,7 @@ public: // multimap, multiset template - typename std::enable_if::type + std::enable_if_t insert(const_iterator /*hint*/, value_type&& value) { // VFALCO TODO The hint could be used to let @@ -1046,9 +1035,9 @@ public: // map, multimap template - typename std::enable_if< - maybe_map && std::is_constructible::value, - typename std::conditional>::type>::type + std::enable_if_t< + maybe_map && std::is_constructible_v, + std::conditional_t>> insert(P&& value) { return emplace(std::forward

(value)); @@ -1056,9 +1045,9 @@ public: // map, multimap template - typename std::enable_if< - maybe_map && std::is_constructible::value, - typename std::conditional>::type>::type + std::enable_if_t< + maybe_map && std::is_constructible_v, + std::conditional_t>> insert(const_iterator hint, P&& value) { return emplace_hint(hint, std::forward

(value)); @@ -1080,23 +1069,22 @@ public: // set, map template auto - emplace(Args&&... args) -> - typename std::enable_if>::type; + emplace(Args&&... args) -> std::enable_if_t>; // multiset, multimap template auto - emplace(Args&&... args) -> typename std::enable_if::type; + emplace(Args&&... args) -> std::enable_if_t; // set, map template auto - emplace_hint(const_iterator /*hint*/, Args&&... args) -> - typename std::enable_if>::type; + emplace_hint(const_iterator /*hint*/, Args&&... args) + -> std::enable_if_t>; // multiset, multimap template - typename std::enable_if::type + std::enable_if_t emplace_hint(const_iterator /*hint*/, Args&&... args) { // VFALCO TODO The hint could be used for multi, to let @@ -1328,7 +1316,7 @@ public: class OtherHash, class OtherAllocator, bool maybe_multi = IsMulti> - typename std::enable_if::type + std::enable_if_t operator==(aged_unordered_container< false, OtherIsMap, @@ -1347,7 +1335,7 @@ public: class OtherHash, class OtherAllocator, bool maybe_multi = IsMulti> - typename std::enable_if::type + std::enable_if_t operator==(aged_unordered_container< true, OtherIsMap, @@ -1401,14 +1389,13 @@ private: // map, set template auto - insert_unchecked(value_type const& value) -> - typename std::enable_if>::type; + insert_unchecked(value_type const& value) + -> std::enable_if_t>; // multimap, multiset template auto - insert_unchecked(value_type const& value) -> - typename std::enable_if::type; + insert_unchecked(value_type const& value) -> std::enable_if_t; template void @@ -1449,7 +1436,7 @@ private: template < bool maybe_propagate = std::allocator_traits::propagate_on_container_swap::value> - typename std::enable_if::type + std::enable_if_t swap_data(aged_unordered_container& other) noexcept { std::swap(m_config.key_compare(), other.m_config.key_compare()); @@ -1459,7 +1446,7 @@ private: template < bool maybe_propagate = std::allocator_traits::propagate_on_container_swap::value> - typename std::enable_if::type + std::enable_if_t swap_data(aged_unordered_container& other) noexcept { std::swap(m_config.key_compare(), other.m_config.key_compare()); @@ -2114,7 +2101,7 @@ template < class KeyEqual, class Allocator> template -typename std::conditional::type& +std::conditional_t& aged_unordered_container::at(K const& k) { auto const iter( @@ -2155,7 +2142,7 @@ template < class KeyEqual, class Allocator> template -typename std::conditional::type& +std::conditional_t& aged_unordered_container::operator[]( Key const& key) { @@ -2184,7 +2171,7 @@ template < class KeyEqual, class Allocator> template -typename std::conditional::type& +std::conditional_t& aged_unordered_container::operator[]( Key&& key) { @@ -2239,8 +2226,7 @@ template < template auto aged_unordered_container::insert( - value_type const& value) -> - typename std::enable_if>::type + value_type const& value) -> std::enable_if_t> { maybe_rehash(1); typename cont_type::insert_commit_data d; @@ -2272,7 +2258,7 @@ template < template auto aged_unordered_container::insert( - value_type const& value) -> typename std::enable_if::type + value_type const& value) -> std::enable_if_t { maybe_rehash(1); element* const p(new_element(value)); @@ -2294,8 +2280,7 @@ template < template auto aged_unordered_container::insert( - value_type&& value) -> - typename std::enable_if>::type + value_type&& value) -> std::enable_if_t> { maybe_rehash(1); typename cont_type::insert_commit_data d; @@ -2327,7 +2312,7 @@ template < template auto aged_unordered_container::insert( - value_type&& value) -> typename std::enable_if::type + value_type&& value) -> std::enable_if_t { maybe_rehash(1); element* const p(new_element(std::move(value))); @@ -2350,7 +2335,7 @@ template < template auto aged_unordered_container::emplace( - Args&&... args) -> typename std::enable_if>::type + Args&&... args) -> std::enable_if_t> { maybe_rehash(1); // VFALCO NOTE Its unfortunate that we need to @@ -2415,7 +2400,7 @@ template < template auto aged_unordered_container::emplace( - Args&&... args) -> typename std::enable_if::type + Args&&... args) -> std::enable_if_t { maybe_rehash(1); element* const p(new_element(std::forward(args)...)); @@ -2438,7 +2423,7 @@ template auto aged_unordered_container::emplace_hint( const_iterator /*hint*/, - Args&&... args) -> typename std::enable_if>::type + Args&&... args) -> std::enable_if_t> { maybe_rehash(1); // VFALCO NOTE Its unfortunate that we need to @@ -2590,7 +2575,7 @@ template < class OtherHash, class OtherAllocator, bool maybe_multi> -typename std::enable_if::type +std::enable_if_t aged_unordered_container::operator==( aged_unordered_container< false, @@ -2630,7 +2615,7 @@ template < class OtherHash, class OtherAllocator, bool maybe_multi> -typename std::enable_if::type +std::enable_if_t aged_unordered_container::operator==( aged_unordered_container< true, @@ -2677,8 +2662,8 @@ template < template auto aged_unordered_container:: - insert_unchecked(value_type const& value) -> - typename std::enable_if>::type + insert_unchecked(value_type const& value) + -> std::enable_if_t> { typename cont_type::insert_commit_data d; auto const result(m_cont.insert_check( @@ -2709,8 +2694,7 @@ template < template auto aged_unordered_container:: - insert_unchecked(value_type const& value) -> - typename std::enable_if::type + insert_unchecked(value_type const& value) -> std::enable_if_t { element* const p(new_element(value)); chronological.list.push_back(*p); diff --git a/include/xrpl/beast/container/detail/empty_base_optimization.h b/include/xrpl/beast/container/detail/empty_base_optimization.h index 21a9d13ce8..337f3cf434 100644 --- a/include/xrpl/beast/container/detail/empty_base_optimization.h +++ b/include/xrpl/beast/container/detail/empty_base_optimization.h @@ -11,12 +11,11 @@ #include #include -namespace beast { -namespace detail { +namespace beast::detail { template struct is_empty_base_optimization_derived - : std::integral_constant::value && !boost::is_final::value> + : std::integral_constant && !boost::is_final::value> { }; @@ -86,5 +85,4 @@ public: } }; -} // namespace detail -} // namespace beast +} // namespace beast::detail diff --git a/include/xrpl/beast/core/List.h b/include/xrpl/beast/core/List.h index 560467c8dd..ab88eae738 100644 --- a/include/xrpl/beast/core/List.h +++ b/include/xrpl/beast/core/List.h @@ -16,7 +16,7 @@ struct CopyConst { explicit CopyConst() = default; - using type = typename std::remove_const::type; + using type = std::remove_const_t; }; template @@ -35,9 +35,11 @@ struct CopyConst template class ListNode { -private: + ListNode() = default; + using value_type = T; + friend T; friend class List; template diff --git a/include/xrpl/beast/core/LockFreeStack.h b/include/xrpl/beast/core/LockFreeStack.h index cf512725fe..2c03e58f68 100644 --- a/include/xrpl/beast/core/LockFreeStack.h +++ b/include/xrpl/beast/core/LockFreeStack.h @@ -13,22 +13,18 @@ class LockFreeStackIterator { protected: using Node = typename Container::Node; - using NodePtr = typename std::conditional::type; + using NodePtr = std::conditional_t; public: using iterator_category = std::forward_iterator_tag; using value_type = typename Container::value_type; using difference_type = typename Container::difference_type; - using pointer = typename std:: - conditional::type; - using reference = typename std::conditional< - IsConst, - typename Container::const_reference, - typename Container::reference>::type; + using pointer = + std::conditional_t; + using reference = std:: + conditional_t; - LockFreeStackIterator() : m_node() - { - } + LockFreeStackIterator() = default; LockFreeStackIterator(NodePtr node) : m_node(node) { @@ -81,7 +77,7 @@ public: } private: - NodePtr m_node; + NodePtr m_node{}; }; //------------------------------------------------------------------------------ diff --git a/include/xrpl/beast/core/SemanticVersion.h b/include/xrpl/beast/core/SemanticVersion.h index f839ef8c53..244783234c 100644 --- a/include/xrpl/beast/core/SemanticVersion.h +++ b/include/xrpl/beast/core/SemanticVersion.h @@ -40,12 +40,12 @@ public: std::string print() const; - inline bool + bool isRelease() const noexcept { return preReleaseIdentifiers.empty(); } - inline bool + bool isPreRelease() const noexcept { return !isRelease(); diff --git a/include/xrpl/beast/hash/hash_append.h b/include/xrpl/beast/hash/hash_append.h index d456bb3a73..3161ab3ce2 100644 --- a/include/xrpl/beast/hash/hash_append.h +++ b/include/xrpl/beast/hash/hash_append.h @@ -69,7 +69,7 @@ template struct is_uniquely_represented : public std::integral_constant< bool, - std::is_integral::value || std::is_enum::value || std::is_pointer::value> + std::is_integral_v || std::is_enum_v || std::is_pointer_v> { explicit is_uniquely_represented() = default; }; @@ -203,13 +203,14 @@ template inline std::enable_if_t::value> hash_append(Hasher& h, T const& t) noexcept { - h(std::addressof(t), sizeof(t)); + // NOLINTNEXTLINE(bugprone-sizeof-expression) + h(static_cast(std::addressof(t)), sizeof(t)); } template inline std::enable_if_t< !is_contiguously_hashable::value && - (std::is_integral::value || std::is_pointer::value || std::is_enum::value)> + (std::is_integral_v || std::is_pointer_v || std::is_enum_v)> hash_append(Hasher& h, T t) noexcept { detail::reverse_bytes(t); @@ -217,7 +218,7 @@ hash_append(Hasher& h, T t) noexcept } template -inline std::enable_if_t::value> +inline std::enable_if_t> hash_append(Hasher& h, T t) noexcept { if (t == 0) diff --git a/include/xrpl/beast/hash/xxhasher.h b/include/xrpl/beast/hash/xxhasher.h index 7c6ae894fc..5cd060e465 100644 --- a/include/xrpl/beast/hash/xxhasher.h +++ b/include/xrpl/beast/hash/xxhasher.h @@ -64,7 +64,7 @@ private: void flushToState(void const* data, std::size_t len) { - if (!state_) + if (state_ == nullptr) { state_ = allocState(); if (seed_.has_value()) @@ -78,7 +78,7 @@ private: } XXH3_64bits_update(state_, readBuffer_.data(), readBuffer_.size()); resetBuffers(); - if (data && len) + if ((data != nullptr) && (len != 0u)) { XXH3_64bits_update(state_, data, len); } @@ -87,22 +87,18 @@ private: result_type retrieveHash() { - if (state_) + if (state_ != nullptr) { flushToState(nullptr, 0); return XXH3_64bits_digest(state_); } - else + + if (seed_.has_value()) { - if (seed_.has_value()) - { - return XXH3_64bits_withSeed(readBuffer_.data(), readBuffer_.size(), *seed_); - } - else - { - return XXH3_64bits(readBuffer_.data(), readBuffer_.size()); - } + return XXH3_64bits_withSeed(readBuffer_.data(), readBuffer_.size(), *seed_); } + + return XXH3_64bits(readBuffer_.data(), readBuffer_.size()); } public: @@ -119,19 +115,19 @@ public: ~xxhasher() noexcept { - if (state_) + if (state_ != nullptr) { XXH3_freeState(state_); } } - template ::value>* = nullptr> + template >* = nullptr> explicit xxhasher(Seed seed) : seed_(seed) { resetBuffers(); } - template ::value>* = nullptr> + template >* = nullptr> xxhasher(Seed seed, Seed) : seed_(seed) { resetBuffers(); diff --git a/include/xrpl/beast/insight/Collector.h b/include/xrpl/beast/insight/Collector.h index 89aa8c1cb5..1d18c7e15c 100644 --- a/include/xrpl/beast/insight/Collector.h +++ b/include/xrpl/beast/insight/Collector.h @@ -8,8 +8,7 @@ #include -namespace beast { -namespace insight { +namespace beast::insight { /** Interface for a manager that allows collection of metrics. @@ -117,5 +116,4 @@ public: /** @} */ }; -} // namespace insight -} // namespace beast +} // namespace beast::insight diff --git a/include/xrpl/beast/insight/Counter.h b/include/xrpl/beast/insight/Counter.h index f6722c4e03..c5b8178c2d 100644 --- a/include/xrpl/beast/insight/Counter.h +++ b/include/xrpl/beast/insight/Counter.h @@ -4,8 +4,7 @@ #include -namespace beast { -namespace insight { +namespace beast::insight { /** A metric for measuring an integral value. @@ -23,9 +22,7 @@ public: /** Create a null metric. A null metric reports no information. */ - Counter() - { - } + Counter() = default; /** Create the metric reference the specified implementation. Normally this won't be called directly. Instead, call the appropriate @@ -91,5 +88,4 @@ private: std::shared_ptr m_impl; }; -} // namespace insight -} // namespace beast +} // namespace beast::insight diff --git a/include/xrpl/beast/insight/CounterImpl.h b/include/xrpl/beast/insight/CounterImpl.h index 199315dcb8..8b16681759 100644 --- a/include/xrpl/beast/insight/CounterImpl.h +++ b/include/xrpl/beast/insight/CounterImpl.h @@ -3,8 +3,7 @@ #include #include -namespace beast { -namespace insight { +namespace beast::insight { class Counter; @@ -18,5 +17,4 @@ public: increment(value_type amount) = 0; }; -} // namespace insight -} // namespace beast +} // namespace beast::insight diff --git a/include/xrpl/beast/insight/Event.h b/include/xrpl/beast/insight/Event.h index bc0c0dd403..ada488f134 100644 --- a/include/xrpl/beast/insight/Event.h +++ b/include/xrpl/beast/insight/Event.h @@ -5,8 +5,7 @@ #include #include -namespace beast { -namespace insight { +namespace beast::insight { /** A metric for reporting event timing. @@ -25,9 +24,7 @@ public: /** Create a null metric. A null metric reports no information. */ - Event() - { - } + Event() = default; /** Create the metric reference the specified implementation. Normally this won't be called directly. Instead, call the appropriate @@ -58,5 +55,4 @@ private: std::shared_ptr m_impl; }; -} // namespace insight -} // namespace beast +} // namespace beast::insight diff --git a/include/xrpl/beast/insight/EventImpl.h b/include/xrpl/beast/insight/EventImpl.h index abd9741511..ede649d195 100644 --- a/include/xrpl/beast/insight/EventImpl.h +++ b/include/xrpl/beast/insight/EventImpl.h @@ -3,8 +3,7 @@ #include #include -namespace beast { -namespace insight { +namespace beast::insight { class Event; @@ -18,5 +17,4 @@ public: notify(value_type const& value) = 0; }; -} // namespace insight -} // namespace beast +} // namespace beast::insight diff --git a/include/xrpl/beast/insight/Gauge.h b/include/xrpl/beast/insight/Gauge.h index f2a88deda2..b75060face 100644 --- a/include/xrpl/beast/insight/Gauge.h +++ b/include/xrpl/beast/insight/Gauge.h @@ -4,8 +4,7 @@ #include -namespace beast { -namespace insight { +namespace beast::insight { /** A metric for measuring an integral value. @@ -25,9 +24,7 @@ public: /** Create a null metric. A null metric reports no information. */ - Gauge() - { - } + Gauge() = default; /** Create the metric reference the specified implementation. Normally this won't be called directly. Instead, call the appropriate @@ -121,5 +118,4 @@ private: std::shared_ptr m_impl; }; -} // namespace insight -} // namespace beast +} // namespace beast::insight diff --git a/include/xrpl/beast/insight/GaugeImpl.h b/include/xrpl/beast/insight/GaugeImpl.h index 29afbe6a4d..3b8afda5e7 100644 --- a/include/xrpl/beast/insight/GaugeImpl.h +++ b/include/xrpl/beast/insight/GaugeImpl.h @@ -3,8 +3,7 @@ #include #include -namespace beast { -namespace insight { +namespace beast::insight { class Gauge; @@ -21,5 +20,4 @@ public: increment(difference_type amount) = 0; }; -} // namespace insight -} // namespace beast +} // namespace beast::insight diff --git a/include/xrpl/beast/insight/Group.h b/include/xrpl/beast/insight/Group.h index c85fd1bfb6..2b0d692f25 100644 --- a/include/xrpl/beast/insight/Group.h +++ b/include/xrpl/beast/insight/Group.h @@ -5,8 +5,7 @@ #include #include -namespace beast { -namespace insight { +namespace beast::insight { /** A collector front-end that manages a group of metrics. */ class Group : public Collector @@ -19,5 +18,4 @@ public: name() const = 0; }; -} // namespace insight -} // namespace beast +} // namespace beast::insight diff --git a/include/xrpl/beast/insight/Groups.h b/include/xrpl/beast/insight/Groups.h index 8ac93454d3..abc1aa004b 100644 --- a/include/xrpl/beast/insight/Groups.h +++ b/include/xrpl/beast/insight/Groups.h @@ -6,8 +6,7 @@ #include #include -namespace beast { -namespace insight { +namespace beast::insight { /** A container for managing a set of metric groups. */ class Groups @@ -32,5 +31,4 @@ public: std::unique_ptr make_Groups(Collector::ptr const& collector); -} // namespace insight -} // namespace beast +} // namespace beast::insight diff --git a/include/xrpl/beast/insight/Hook.h b/include/xrpl/beast/insight/Hook.h index ea511862d9..d51a5d2300 100644 --- a/include/xrpl/beast/insight/Hook.h +++ b/include/xrpl/beast/insight/Hook.h @@ -4,8 +4,7 @@ #include -namespace beast { -namespace insight { +namespace beast::insight { /** A reference to a handler for performing polled collection. */ class Hook final @@ -14,9 +13,7 @@ public: /** Create a null hook. A null hook has no associated handler. */ - Hook() - { - } + Hook() = default; /** Create a hook referencing the specified implementation. Normally this won't be called directly. Instead, call the appropriate @@ -37,5 +34,4 @@ private: std::shared_ptr m_impl; }; -} // namespace insight -} // namespace beast +} // namespace beast::insight diff --git a/include/xrpl/beast/insight/HookImpl.h b/include/xrpl/beast/insight/HookImpl.h index 18208b554a..9a503ba0d2 100644 --- a/include/xrpl/beast/insight/HookImpl.h +++ b/include/xrpl/beast/insight/HookImpl.h @@ -3,8 +3,7 @@ #include #include -namespace beast { -namespace insight { +namespace beast::insight { class HookImpl : public std::enable_shared_from_this { @@ -14,5 +13,4 @@ public: virtual ~HookImpl() = 0; }; -} // namespace insight -} // namespace beast +} // namespace beast::insight diff --git a/include/xrpl/beast/insight/Meter.h b/include/xrpl/beast/insight/Meter.h index 193a1f1003..7685a0ec90 100644 --- a/include/xrpl/beast/insight/Meter.h +++ b/include/xrpl/beast/insight/Meter.h @@ -4,8 +4,7 @@ #include -namespace beast { -namespace insight { +namespace beast::insight { /** A metric for measuring an integral value. @@ -22,9 +21,7 @@ public: /** Create a null metric. A null metric reports no information. */ - Meter() - { - } + Meter() = default; /** Create the metric reference the specified implementation. Normally this won't be called directly. Instead, call the appropriate @@ -76,5 +73,4 @@ private: std::shared_ptr m_impl; }; -} // namespace insight -} // namespace beast +} // namespace beast::insight diff --git a/include/xrpl/beast/insight/MeterImpl.h b/include/xrpl/beast/insight/MeterImpl.h index 22efdbe647..2220650131 100644 --- a/include/xrpl/beast/insight/MeterImpl.h +++ b/include/xrpl/beast/insight/MeterImpl.h @@ -3,8 +3,7 @@ #include #include -namespace beast { -namespace insight { +namespace beast::insight { class Meter; @@ -18,5 +17,4 @@ public: increment(value_type amount) = 0; }; -} // namespace insight -} // namespace beast +} // namespace beast::insight diff --git a/include/xrpl/beast/insight/NullCollector.h b/include/xrpl/beast/insight/NullCollector.h index 1d16a11e17..e6404c8f2c 100644 --- a/include/xrpl/beast/insight/NullCollector.h +++ b/include/xrpl/beast/insight/NullCollector.h @@ -2,8 +2,7 @@ #include -namespace beast { -namespace insight { +namespace beast::insight { /** A Collector which does not collect metrics. */ class NullCollector : public Collector @@ -15,5 +14,4 @@ public: New(); }; -} // namespace insight -} // namespace beast +} // namespace beast::insight diff --git a/include/xrpl/beast/insight/StatsDCollector.h b/include/xrpl/beast/insight/StatsDCollector.h index ab09967483..161f351763 100644 --- a/include/xrpl/beast/insight/StatsDCollector.h +++ b/include/xrpl/beast/insight/StatsDCollector.h @@ -4,8 +4,7 @@ #include #include -namespace beast { -namespace insight { +namespace beast::insight { /** A Collector that reports metrics to a StatsD server. Reference: @@ -25,5 +24,4 @@ public: New(IP::Endpoint const& address, std::string const& prefix, Journal journal); }; -} // namespace insight -} // namespace beast +} // namespace beast::insight diff --git a/include/xrpl/beast/net/IPAddress.h b/include/xrpl/beast/net/IPAddress.h index 2ac4c3bc43..277a43e5d8 100644 --- a/include/xrpl/beast/net/IPAddress.h +++ b/include/xrpl/beast/net/IPAddress.h @@ -70,9 +70,13 @@ hash_append(Hasher& h, beast::IP::Address const& addr) noexcept { using beast::hash_append; if (addr.is_v4()) + { hash_append(h, addr.to_v4().to_bytes()); + } else if (addr.is_v6()) + { hash_append(h, addr.to_v6().to_bytes()); + } else { // LCOV_EXCL_START diff --git a/include/xrpl/beast/net/IPAddressConversion.h b/include/xrpl/beast/net/IPAddressConversion.h index 2ebd0a6eef..4e0328c113 100644 --- a/include/xrpl/beast/net/IPAddressConversion.h +++ b/include/xrpl/beast/net/IPAddressConversion.h @@ -4,8 +4,7 @@ #include -namespace beast { -namespace IP { +namespace beast::IP { /** Convert to Endpoint. The port is set to zero. @@ -27,8 +26,7 @@ to_asio_address(Endpoint const& endpoint); boost::asio::ip::tcp::endpoint to_asio_endpoint(Endpoint const& endpoint); -} // namespace IP -} // namespace beast +} // namespace beast::IP namespace beast { diff --git a/include/xrpl/beast/net/IPAddressV4.h b/include/xrpl/beast/net/IPAddressV4.h index 0d586716d8..15bb959f49 100644 --- a/include/xrpl/beast/net/IPAddressV4.h +++ b/include/xrpl/beast/net/IPAddressV4.h @@ -4,8 +4,7 @@ #include -namespace beast { -namespace IP { +namespace beast::IP { using AddressV4 = boost::asio::ip::address_v4; @@ -23,5 +22,4 @@ is_public(AddressV4 const& addr); char get_class(AddressV4 const& address); -} // namespace IP -} // namespace beast +} // namespace beast::IP diff --git a/include/xrpl/beast/net/IPAddressV6.h b/include/xrpl/beast/net/IPAddressV6.h index 2f9dedb748..83ac6d4dab 100644 --- a/include/xrpl/beast/net/IPAddressV6.h +++ b/include/xrpl/beast/net/IPAddressV6.h @@ -4,8 +4,7 @@ #include -namespace beast { -namespace IP { +namespace beast::IP { using AddressV6 = boost::asio::ip::address_v6; @@ -17,5 +16,4 @@ is_private(AddressV6 const& addr); bool is_public(AddressV6 const& addr); -} // namespace IP -} // namespace beast +} // namespace beast::IP diff --git a/include/xrpl/beast/net/IPEndpoint.h b/include/xrpl/beast/net/IPEndpoint.h index 7a0394cbd1..88f3d7669b 100644 --- a/include/xrpl/beast/net/IPEndpoint.h +++ b/include/xrpl/beast/net/IPEndpoint.h @@ -8,8 +8,7 @@ #include #include -namespace beast { -namespace IP { +namespace beast::IP { using Port = std::uint16_t; @@ -21,7 +20,7 @@ public: Endpoint(); /** Create an endpoint from the address and optional port. */ - explicit Endpoint(Address const& addr, Port port = 0); + explicit Endpoint(Address addr, Port port = 0); /** Create an Endpoint from a string. If the port is omitted, the endpoint will have a zero port. @@ -69,12 +68,12 @@ public: { return m_addr.is_v6(); } - AddressV4 const + AddressV4 to_v4() const { return m_addr.to_v4(); } - AddressV6 const + AddressV6 to_v6() const { return m_addr.to_v6(); @@ -184,8 +183,7 @@ operator<<(OutputStream& os, Endpoint const& endpoint) std::istream& operator>>(std::istream& is, Endpoint& endpoint); -} // namespace IP -} // namespace beast +} // namespace beast::IP //------------------------------------------------------------------------------ diff --git a/include/xrpl/beast/rfc2616.h b/include/xrpl/beast/rfc2616.h index b19c2c511a..7278119c3b 100644 --- a/include/xrpl/beast/rfc2616.h +++ b/include/xrpl/beast/rfc2616.h @@ -12,8 +12,7 @@ #include #include -namespace beast { -namespace rfc2616 { +namespace beast::rfc2616 { namespace detail { @@ -53,8 +52,9 @@ is_white(char c) case '\t': case '\v': return true; + default: + return false; }; - return false; } template @@ -349,8 +349,10 @@ bool token_in_list(boost::string_ref const& value, boost::string_ref const& token) { for (auto const& item : make_list(value)) + { if (ci_equal(item, token)) return true; + } return false; } @@ -359,11 +361,12 @@ bool is_keep_alive(boost::beast::http::message const& m) { if (m.version() <= 10) + { return boost::beast::http::token_list{m[boost::beast::http::field::connection]}.exists( "keep-alive"); + } return !boost::beast::http::token_list{m[boost::beast::http::field::connection]}.exists( "close"); } -} // namespace rfc2616 -} // namespace beast +} // namespace beast::rfc2616 diff --git a/include/xrpl/beast/test/yield_to.h b/include/xrpl/beast/test/yield_to.h index bf774b0eb5..4723fb49b8 100644 --- a/include/xrpl/beast/test/yield_to.h +++ b/include/xrpl/beast/test/yield_to.h @@ -15,8 +15,7 @@ #include #include -namespace beast { -namespace test { +namespace beast::test { /** Mix-in to support tests using asio coroutines. @@ -45,7 +44,9 @@ public: { threads_.reserve(concurrency); for (std::size_t i = 0; i < concurrency; ++i) + { threads_.emplace_back([&] { ios_.run(); }); + } } ~enable_yield_to() @@ -125,5 +126,4 @@ enable_yield_to::spawn(F0&& f, FN&&... fn) spawn(fn...); } -} // namespace test -} // namespace beast +} // namespace beast::test diff --git a/include/xrpl/beast/type_name.h b/include/xrpl/beast/type_name.h index 99b90d1757..d12f9c4b90 100644 --- a/include/xrpl/beast/type_name.h +++ b/include/xrpl/beast/type_name.h @@ -15,7 +15,7 @@ template std::string type_name() { - using TR = typename std::remove_reference::type; + using TR = std::remove_reference_t; std::string name = typeid(TR).name(); @@ -27,14 +27,18 @@ type_name() } #endif - if (std::is_const::value) + if (std::is_const_v) name += " const"; - if (std::is_volatile::value) + if (std::is_volatile_v) name += " volatile"; - if (std::is_lvalue_reference::value) + if (std::is_lvalue_reference_v) + { name += "&"; - else if (std::is_rvalue_reference::value) + } + else if (std::is_rvalue_reference_v) + { name += "&&"; + } return name; } diff --git a/include/xrpl/beast/unit_test/amount.h b/include/xrpl/beast/unit_test/amount.h index aedced15a7..ae1dd8fe31 100644 --- a/include/xrpl/beast/unit_test/amount.h +++ b/include/xrpl/beast/unit_test/amount.h @@ -8,8 +8,7 @@ #include #include -namespace beast { -namespace unit_test { +namespace beast::unit_test { /** Utility for producing nicely composed output of amounts with units. */ class amount @@ -42,5 +41,4 @@ operator<<(std::ostream& s, amount const& t) return s; } -} // namespace unit_test -} // namespace beast +} // namespace beast::unit_test diff --git a/include/xrpl/beast/unit_test/detail/const_container.h b/include/xrpl/beast/unit_test/detail/const_container.h index 7c52e0e1ec..6a32c61b61 100644 --- a/include/xrpl/beast/unit_test/detail/const_container.h +++ b/include/xrpl/beast/unit_test/detail/const_container.h @@ -4,9 +4,7 @@ #pragma once -namespace beast { -namespace unit_test { -namespace detail { +namespace beast::unit_test::detail { /** Adapter to constrain a container interface. The interface allows for limited read only operations. Derived classes @@ -82,6 +80,4 @@ public: /** @} */ }; -} // namespace detail -} // namespace unit_test -} // namespace beast +} // namespace beast::unit_test::detail diff --git a/include/xrpl/beast/unit_test/global_suites.h b/include/xrpl/beast/unit_test/global_suites.h index db5fee35dd..ca33ddb11c 100644 --- a/include/xrpl/beast/unit_test/global_suites.h +++ b/include/xrpl/beast/unit_test/global_suites.h @@ -6,8 +6,7 @@ #include -namespace beast { -namespace unit_test { +namespace beast::unit_test { namespace detail { @@ -42,5 +41,4 @@ global_suites() return detail::global_suites(); } -} // namespace unit_test -} // namespace beast +} // namespace beast::unit_test diff --git a/include/xrpl/beast/unit_test/match.h b/include/xrpl/beast/unit_test/match.h index 38816bb5c7..084e2bc464 100644 --- a/include/xrpl/beast/unit_test/match.h +++ b/include/xrpl/beast/unit_test/match.h @@ -8,8 +8,7 @@ #include -namespace beast { -namespace unit_test { +namespace beast::unit_test { // Predicate for implementing matches class selector @@ -163,5 +162,4 @@ match_library(std::string const& name) return selector(selector::library, name); } -} // namespace unit_test -} // namespace beast +} // namespace beast::unit_test diff --git a/include/xrpl/beast/unit_test/recorder.h b/include/xrpl/beast/unit_test/recorder.h index 8f956fda88..f101d5318f 100644 --- a/include/xrpl/beast/unit_test/recorder.h +++ b/include/xrpl/beast/unit_test/recorder.h @@ -7,8 +7,7 @@ #include #include -namespace beast { -namespace unit_test { +namespace beast::unit_test { /** A test runner that stores the results. */ class recorder : public runner @@ -29,49 +28,48 @@ public: } private: - virtual void + void on_suite_begin(suite_info const& info) override { m_suite = suite_results(info.full_name()); } - virtual void + void on_suite_end() override { m_results.insert(std::move(m_suite)); } - virtual void + void on_case_begin(std::string const& name) override { m_case = case_results(name); } - virtual void + void on_case_end() override { - if (m_case.tests.size() > 0) + if (!m_case.tests.empty()) m_suite.insert(std::move(m_case)); } - virtual void + void on_pass() override { m_case.tests.pass(); } - virtual void + void on_fail(std::string const& reason) override { m_case.tests.fail(reason); } - virtual void + void on_log(std::string const& s) override { m_case.log.insert(s); } }; -} // namespace unit_test -} // namespace beast +} // namespace beast::unit_test diff --git a/include/xrpl/beast/unit_test/reporter.h b/include/xrpl/beast/unit_test/reporter.h index 63ad90ff7c..066d628ea2 100644 --- a/include/xrpl/beast/unit_test/reporter.h +++ b/include/xrpl/beast/unit_test/reporter.h @@ -18,8 +18,7 @@ #include #include -namespace beast { -namespace unit_test { +namespace beast::unit_test { namespace detail { @@ -86,7 +85,7 @@ public: reporter& operator=(reporter const&) = delete; - ~reporter(); + ~reporter() override; explicit reporter(std::ostream& os = std::cout); @@ -94,42 +93,42 @@ private: static std::string fmtdur(typename clock_type::duration const& d); - virtual void + void on_suite_begin(suite_info const& info) override; - virtual void + void on_suite_end() override; - virtual void + void on_case_begin(std::string const& name) override; - virtual void + void on_case_end() override; - virtual void + void on_pass() override; - virtual void + void on_fail(std::string const& reason) override; - virtual void + void on_log(std::string const& s) override; }; //------------------------------------------------------------------------------ -template +template void -reporter<_>::suite_results::add(case_results const& r) +reporter::suite_results::add(case_results const& r) { ++cases; total += r.total; failed += r.failed; } -template +template void -reporter<_>::results::add(suite_results const& r) +reporter::results::add(suite_results const& r) { ++suites; total += r.total; @@ -160,13 +159,13 @@ reporter<_>::results::add(suite_results const& r) //------------------------------------------------------------------------------ -template -reporter<_>::reporter(std::ostream& os) : os_(os) +template +reporter::reporter(std::ostream& os) : os_(os) { } -template -reporter<_>::~reporter() +template +reporter::~reporter() { if (results_.top.size() > 0) { @@ -180,9 +179,9 @@ reporter<_>::~reporter() << amount{results_.failed, "failure"} << std::endl; } -template +template std::string -reporter<_>::fmtdur(typename clock_type::duration const& d) +reporter::fmtdur(typename clock_type::duration const& d) { using namespace std::chrono; auto const ms = duration_cast(d); @@ -193,46 +192,46 @@ reporter<_>::fmtdur(typename clock_type::duration const& d) return ss.str(); } -template +template void -reporter<_>::on_suite_begin(suite_info const& info) +reporter::on_suite_begin(suite_info const& info) { suite_results_ = suite_results{info.full_name()}; } -template +template void -reporter<_>::on_suite_end() +reporter::on_suite_end() { results_.add(suite_results_); } -template +template void -reporter<_>::on_case_begin(std::string const& name) +reporter::on_case_begin(std::string const& name) { case_results_ = case_results(name); os_ << suite_results_.name << (case_results_.name.empty() ? "" : (" " + case_results_.name)) << std::endl; } -template +template void -reporter<_>::on_case_end() +reporter::on_case_end() { suite_results_.add(case_results_); } -template +template void -reporter<_>::on_pass() +reporter::on_pass() { ++case_results_.total; } -template +template void -reporter<_>::on_fail(std::string const& reason) +reporter::on_fail(std::string const& reason) { ++case_results_.failed; ++case_results_.total; @@ -240,9 +239,9 @@ reporter<_>::on_fail(std::string const& reason) << std::endl; } -template +template void -reporter<_>::on_log(std::string const& s) +reporter::on_log(std::string const& s) { os_ << s; } @@ -251,5 +250,4 @@ reporter<_>::on_log(std::string const& s) using reporter = detail::reporter<>; -} // namespace unit_test -} // namespace beast +} // namespace beast::unit_test diff --git a/include/xrpl/beast/unit_test/results.h b/include/xrpl/beast/unit_test/results.h index b8a8e2aadf..5607071729 100644 --- a/include/xrpl/beast/unit_test/results.h +++ b/include/xrpl/beast/unit_test/results.h @@ -7,10 +7,10 @@ #include #include +#include #include -namespace beast { -namespace unit_test { +namespace beast::unit_test { /** Holds a set of test condition outcomes in a testcase. */ class case_results @@ -23,7 +23,7 @@ public: { } - test(bool pass_, std::string const& reason_) : pass(pass_), reason(reason_) + test(bool pass_, std::string reason_) : pass(pass_), reason(std::move(reason_)) { } @@ -38,9 +38,7 @@ private: std::size_t failed_{0}; public: - tests_t() - { - } + tests_t() = default; /** Returns the total number of test conditions. */ std::size_t @@ -86,7 +84,7 @@ private: std::string name_; public: - explicit case_results(std::string const& name = "") : name_(name) + explicit case_results(std::string name = "") : name_(std::move(name)) { } @@ -115,7 +113,7 @@ private: std::size_t failed_ = 0; public: - explicit suite_results(std::string const& name = "") : name_(name) + explicit suite_results(std::string name = "") : name_(std::move(name)) { } @@ -145,9 +143,9 @@ public: void insert(case_results&& r) { - cont().emplace_back(std::move(r)); total_ += r.tests.total(); failed_ += r.tests.failed(); + cont().emplace_back(std::move(r)); } void @@ -172,9 +170,7 @@ private: std::size_t failed_{0}; public: - results() - { - } + results() = default; /** Returns the total number of test cases. */ std::size_t @@ -219,5 +215,4 @@ public: /** @} */ }; -} // namespace unit_test -} // namespace beast +} // namespace beast::unit_test diff --git a/include/xrpl/beast/unit_test/runner.h b/include/xrpl/beast/unit_test/runner.h index c8a6956732..3443308675 100644 --- a/include/xrpl/beast/unit_test/runner.h +++ b/include/xrpl/beast/unit_test/runner.h @@ -11,8 +11,7 @@ #include #include -namespace beast { -namespace unit_test { +namespace beast::unit_test { /** Unit test runner interface. @@ -198,8 +197,10 @@ runner::run_if(FwdIter first, FwdIter last, Pred pred) { bool failed(false); for (; first != last; ++first) + { if (pred(*first)) failed = run(*first) || failed; + } return failed; } @@ -219,8 +220,10 @@ runner::run_each_if(SequenceContainer const& c, Pred pred) { bool failed(false); for (auto const& s : c) + { if (pred(s)) failed = run(s) || failed; + } return failed; } @@ -273,5 +276,4 @@ runner::log(std::string const& s) on_log(s); } -} // namespace unit_test -} // namespace beast +} // namespace beast::unit_test diff --git a/include/xrpl/beast/unit_test/suite.h b/include/xrpl/beast/unit_test/suite.h index fa5157e126..1719c519cf 100644 --- a/include/xrpl/beast/unit_test/suite.h +++ b/include/xrpl/beast/unit_test/suite.h @@ -14,8 +14,7 @@ #include #include -namespace beast { -namespace unit_test { +namespace beast::unit_test { namespace detail { @@ -36,7 +35,7 @@ make_reason(String const& reason, char const* file, int line) } // namespace detail -class thread; +class Thread; enum abort_t { no_abort_on_fail, abort_on_fail }; @@ -75,7 +74,7 @@ private: { } - ~log_buf() + ~log_buf() override { sync(); } @@ -295,7 +294,7 @@ public: } private: - friend class thread; + friend class Thread; static suite** p_this_suite() @@ -309,7 +308,7 @@ private: run() = 0; void - propagate_abort(); + propagate_abort() const; template void @@ -486,9 +485,13 @@ suite::unexpected(Condition shouldBeFalse, String const& reason) { bool const b = static_cast(shouldBeFalse); if (!b) + { pass(); + } else + { fail(reason); + } return !b; } @@ -522,7 +525,7 @@ suite::fail(String const& reason, char const* file, int line) } inline void -suite::propagate_abort() +suite::propagate_abort() const { if (abort_ && aborted_) BOOST_THROW_EXCEPTION(abort_exception()); @@ -538,7 +541,7 @@ suite::run(runner& r) { run(); } - catch (abort_exception const&) + catch (abort_exception const&) // NOLINT(bugprone-empty-catch) { // ends the suite } @@ -569,8 +572,7 @@ suite::run(runner& r) ((cond) ? (pass(), true) : (fail((reason), __FILE__, __LINE__), false)) #endif -} // namespace unit_test -} // namespace beast +} // namespace beast::unit_test //------------------------------------------------------------------------------ diff --git a/include/xrpl/beast/unit_test/suite_info.h b/include/xrpl/beast/unit_test/suite_info.h index 0ffb330422..e7fa80b70e 100644 --- a/include/xrpl/beast/unit_test/suite_info.h +++ b/include/xrpl/beast/unit_test/suite_info.h @@ -9,8 +9,7 @@ #include #include -namespace beast { -namespace unit_test { +namespace beast::unit_test { class runner; @@ -110,5 +109,4 @@ make_suite_info( }); } -} // namespace unit_test -} // namespace beast +} // namespace beast::unit_test diff --git a/include/xrpl/beast/unit_test/suite_list.h b/include/xrpl/beast/unit_test/suite_list.h index bbd2914c45..deffbdfe84 100644 --- a/include/xrpl/beast/unit_test/suite_list.h +++ b/include/xrpl/beast/unit_test/suite_list.h @@ -13,8 +13,7 @@ #include #include -namespace beast { -namespace unit_test { +namespace beast::unit_test { /// A container of test suites. class suite_list : public detail::const_container> @@ -62,5 +61,4 @@ suite_list::insert( cont().emplace(make_suite_info(name, module, library, manual, priority)); } -} // namespace unit_test -} // namespace beast +} // namespace beast::unit_test diff --git a/include/xrpl/beast/unit_test/thread.h b/include/xrpl/beast/unit_test/thread.h index b49f8ed36e..cc12380b0d 100644 --- a/include/xrpl/beast/unit_test/thread.h +++ b/include/xrpl/beast/unit_test/thread.h @@ -10,11 +10,10 @@ #include #include -namespace beast { -namespace unit_test { +namespace beast::unit_test { /** Replacement for std::thread that handles exceptions in unit tests. */ -class thread +class Thread { private: suite* s_ = nullptr; @@ -24,17 +23,17 @@ public: using id = std::thread::id; using native_handle_type = std::thread::native_handle_type; - thread() = default; - thread(thread const&) = delete; - thread& - operator=(thread const&) = delete; + Thread() = default; + Thread(Thread const&) = delete; + Thread& + operator=(Thread const&) = delete; - thread(thread&& other) : s_(other.s_), t_(std::move(other.t_)) + Thread(Thread&& other) : s_(other.s_), t_(std::move(other.t_)) { } - thread& - operator=(thread&& other) + Thread& + operator=(Thread&& other) { s_ = other.s_; t_ = std::move(other.t_); @@ -42,10 +41,10 @@ public: } template - explicit thread(suite& s, F&& f, Args&&... args) : s_(&s) + explicit Thread(suite& s, F&& f, Args&&... args) : s_(&s) { std::function b = std::bind(std::forward(f), std::forward(args)...); - t_ = std::thread(&thread::run, this, std::move(b)); + t_ = std::thread(&Thread::run, this, std::move(b)); } bool @@ -80,7 +79,7 @@ public: } void - swap(thread& other) + swap(Thread& other) { std::swap(s_, other.s_); std::swap(t_, other.t_); @@ -94,7 +93,7 @@ private: { f(); } - catch (suite::abort_exception const&) + catch (suite::abort_exception const&) // NOLINT(bugprone-empty-catch) { } catch (std::exception const& e) @@ -108,5 +107,4 @@ private: } }; -} // namespace unit_test -} // namespace beast +} // namespace beast::unit_test diff --git a/include/xrpl/beast/utility/Journal.h b/include/xrpl/beast/utility/Journal.h index 2ac5050b2d..975169cf5f 100644 --- a/include/xrpl/beast/utility/Journal.h +++ b/include/xrpl/beast/utility/Journal.h @@ -13,13 +13,13 @@ enum Severity { kAll = 0, kTrace = kAll, - kDebug, - kInfo, - kWarning, - kError, - kFatal, + kDebug = 1, + kInfo = 2, + kWarning = 3, + kError = 4, + kFatal = 5, - kDisabled, + kDisabled = 6, kNone = kDisabled }; } // namespace severities @@ -55,15 +55,16 @@ public: class Sink { protected: - Sink() = delete; explicit Sink(Sink const& sink) = default; Sink(Severity thresh, bool console); - Sink& - operator=(Sink const& lhs) = delete; public: virtual ~Sink() = 0; + Sink() = delete; + Sink& + operator=(Sink const& lhs) = delete; + /** Returns `true` if text at the passed severity produces output. */ virtual bool active(Severity level) const; @@ -109,12 +110,12 @@ public: }; #ifndef __INTELLISENSE__ - static_assert(std::is_default_constructible::value == false, ""); - static_assert(std::is_copy_constructible::value == false, ""); - static_assert(std::is_move_constructible::value == false, ""); - static_assert(std::is_copy_assignable::value == false, ""); - static_assert(std::is_move_assignable::value == false, ""); - static_assert(std::is_nothrow_destructible::value == true, ""); + static_assert(!std::is_default_constructible_v, ""); + static_assert(!std::is_copy_constructible_v, ""); + static_assert(!std::is_move_constructible_v, ""); + static_assert(!std::is_copy_assignable_v, ""); + static_assert(!std::is_move_assignable_v, ""); + static_assert(std::is_nothrow_destructible_v, ""); #endif /** Returns a Sink which does nothing. */ @@ -165,12 +166,12 @@ public: }; #ifndef __INTELLISENSE__ - static_assert(std::is_default_constructible::value == false, ""); - static_assert(std::is_copy_constructible::value == true, ""); - static_assert(std::is_move_constructible::value == true, ""); - static_assert(std::is_copy_assignable::value == false, ""); - static_assert(std::is_move_assignable::value == false, ""); - static_assert(std::is_nothrow_destructible::value == true, ""); + static_assert(!std::is_default_constructible_v, ""); + static_assert(std::is_copy_constructible_v, ""); + static_assert(std::is_move_constructible_v, ""); + static_assert(!std::is_copy_assignable_v, ""); + static_assert(!std::is_move_assignable_v, ""); + static_assert(std::is_nothrow_destructible_v, ""); #endif //-------------------------------------------------------------------------- @@ -247,12 +248,12 @@ public: }; #ifndef __INTELLISENSE__ - static_assert(std::is_default_constructible::value == true, ""); - static_assert(std::is_copy_constructible::value == true, ""); - static_assert(std::is_move_constructible::value == true, ""); - static_assert(std::is_copy_assignable::value == false, ""); - static_assert(std::is_move_assignable::value == false, ""); - static_assert(std::is_nothrow_destructible::value == true, ""); + static_assert(std::is_default_constructible_v, ""); + static_assert(std::is_copy_constructible_v, ""); + static_assert(std::is_move_constructible_v, ""); + static_assert(!std::is_copy_assignable_v, ""); + static_assert(!std::is_move_assignable_v, ""); + static_assert(std::is_nothrow_destructible_v, ""); #endif //-------------------------------------------------------------------------- @@ -330,12 +331,12 @@ public: }; #ifndef __INTELLISENSE__ -static_assert(std::is_default_constructible::value == false, ""); -static_assert(std::is_copy_constructible::value == true, ""); -static_assert(std::is_move_constructible::value == true, ""); -static_assert(std::is_copy_assignable::value == true, ""); -static_assert(std::is_move_assignable::value == true, ""); -static_assert(std::is_nothrow_destructible::value == true, ""); +static_assert(!std::is_default_constructible_v, ""); +static_assert(std::is_copy_constructible_v, ""); +static_assert(std::is_move_constructible_v, ""); +static_assert(std::is_copy_assignable_v, ""); +static_assert(std::is_move_assignable_v, ""); +static_assert(std::is_nothrow_destructible_v, ""); #endif //------------------------------------------------------------------------------ @@ -371,10 +372,6 @@ class logstream_buf : public std::basic_stringbuf { beast::Journal::Stream strm_; - template - void - write(T const*) = delete; - void write(char const* s) { @@ -394,7 +391,7 @@ public: { } - ~logstream_buf() + ~logstream_buf() override { sync(); } @@ -406,6 +403,10 @@ public: this->str(""); return 0; } + + template + void + write(T const*) = delete; }; } // namespace detail @@ -413,11 +414,11 @@ public: template > class basic_logstream : public std::basic_ostream { - typedef CharT char_type; - typedef Traits traits_type; - typedef typename traits_type::int_type int_type; - typedef typename traits_type::pos_type pos_type; - typedef typename traits_type::off_type off_type; + using char_type = CharT; + using traits_type = Traits; + using int_type = typename traits_type::int_type; + using pos_type = typename traits_type::pos_type; + using off_type = typename traits_type::off_type; detail::logstream_buf buf_; diff --git a/include/xrpl/beast/utility/PropertyStream.h b/include/xrpl/beast/utility/PropertyStream.h index 290730c1a2..b2bd8c7a35 100644 --- a/include/xrpl/beast/utility/PropertyStream.h +++ b/include/xrpl/beast/utility/PropertyStream.h @@ -174,7 +174,7 @@ private: std::ostringstream mutable m_ostream; public: - Proxy(Map const& map, std::string const& key); + Proxy(Map const& map, std::string key); Proxy(Proxy const& other); ~Proxy(); @@ -315,7 +315,7 @@ private: List children_; public: - explicit Source(std::string const& name); + explicit Source(std::string name); virtual ~Source(); Source(Source const&) = delete; diff --git a/include/xrpl/beast/utility/WrappedSink.h b/include/xrpl/beast/utility/WrappedSink.h index bb8a1a6994..7e36ce99d7 100644 --- a/include/xrpl/beast/utility/WrappedSink.h +++ b/include/xrpl/beast/utility/WrappedSink.h @@ -2,6 +2,8 @@ #include +#include + namespace beast { /** Wraps a Journal::Sink to prefix its output with a string. */ @@ -17,8 +19,8 @@ private: std::string prefix_; public: - explicit WrappedSink(beast::Journal::Sink& sink, std::string const& prefix = "") - : Sink(sink), sink_(sink), prefix_(prefix) + explicit WrappedSink(beast::Journal::Sink& sink, std::string prefix = "") + : Sink(sink), sink_(sink), prefix_(std::move(prefix)) { } diff --git a/include/xrpl/beast/utility/Zero.h b/include/xrpl/beast/utility/Zero.h index 3b50b3fe00..ff212b9c98 100644 --- a/include/xrpl/beast/utility/Zero.h +++ b/include/xrpl/beast/utility/Zero.h @@ -27,7 +27,7 @@ struct Zero }; namespace { -static constexpr Zero zero{}; +constexpr Zero zero{}; } // namespace /** Default implementation of signum calls the method on the class. */ @@ -38,8 +38,7 @@ signum(T const& t) return t.signum(); } -namespace detail { -namespace zero_helper { +namespace detail::zero_helper { // For argument dependent lookup to function properly, calls to signum must // be made from a namespace that does not include overloads of the function.. @@ -50,8 +49,7 @@ call_signum(T const& t) return signum(t); } -} // namespace zero_helper -} // namespace detail +} // namespace detail::zero_helper // Handle operators where T is on the left side using signum. diff --git a/include/xrpl/beast/utility/maybe_const.h b/include/xrpl/beast/utility/maybe_const.h index 3f6e1f95bd..bcfd7a7532 100644 --- a/include/xrpl/beast/utility/maybe_const.h +++ b/include/xrpl/beast/utility/maybe_const.h @@ -9,10 +9,8 @@ template struct maybe_const { explicit maybe_const() = default; - using type = typename std::conditional< - IsConst, - typename std::remove_const::type const, - typename std::remove_const::type>::type; + using type = std:: + conditional_t::type const, std::remove_const_t>; }; /** Alias for omitting `typename`. */ diff --git a/include/xrpl/beast/xor_shift_engine.h b/include/xrpl/beast/xor_shift_engine.h index 4fbe5ec165..85504f51aa 100644 --- a/include/xrpl/beast/xor_shift_engine.h +++ b/include/xrpl/beast/xor_shift_engine.h @@ -43,15 +43,15 @@ private: murmurhash3(result_type x); }; -template -xor_shift_engine<_>::xor_shift_engine(result_type val) +template +xor_shift_engine::xor_shift_engine(result_type val) { seed(val); } -template +template void -xor_shift_engine<_>::seed(result_type seed) +xor_shift_engine::seed(result_type seed) { if (seed == 0) throw std::domain_error("invalid seed"); @@ -59,9 +59,9 @@ xor_shift_engine<_>::seed(result_type seed) s_[1] = murmurhash3(s_[0]); } -template +template auto -xor_shift_engine<_>::operator()() -> result_type +xor_shift_engine::operator()() -> result_type { result_type s1 = s_[0]; result_type const s0 = s_[1]; @@ -70,9 +70,9 @@ xor_shift_engine<_>::operator()() -> result_type return (s_[1] = (s1 ^ s0 ^ (s1 >> 17) ^ (s0 >> 26))) + s0; } -template +template auto -xor_shift_engine<_>::murmurhash3(result_type x) -> result_type +xor_shift_engine::murmurhash3(result_type x) -> result_type { x ^= x >> 33; x *= 0xff51afd7ed558ccdULL; diff --git a/include/xrpl/conditions/Condition.h b/include/xrpl/conditions/Condition.h index 6b306a3982..4b24c5a289 100644 --- a/include/xrpl/conditions/Condition.h +++ b/include/xrpl/conditions/Condition.h @@ -7,8 +7,7 @@ #include #include -namespace xrpl { -namespace cryptoconditions { +namespace xrpl::cryptoconditions { enum class Type : std::uint8_t { preimageSha256 = 0, @@ -88,6 +87,4 @@ operator!=(Condition const& lhs, Condition const& rhs) return !(lhs == rhs); } -} // namespace cryptoconditions - -} // namespace xrpl +} // namespace xrpl::cryptoconditions diff --git a/include/xrpl/conditions/Fulfillment.h b/include/xrpl/conditions/Fulfillment.h index ab47804e45..71ff9d83ef 100644 --- a/include/xrpl/conditions/Fulfillment.h +++ b/include/xrpl/conditions/Fulfillment.h @@ -4,8 +4,7 @@ #include #include -namespace xrpl { -namespace cryptoconditions { +namespace xrpl::cryptoconditions { struct Fulfillment { @@ -119,5 +118,4 @@ validate(Fulfillment const& f, Condition const& c, Slice m); bool validate(Fulfillment const& f, Condition const& c); -} // namespace cryptoconditions -} // namespace xrpl +} // namespace xrpl::cryptoconditions diff --git a/include/xrpl/conditions/detail/PreimageSha256.h b/include/xrpl/conditions/detail/PreimageSha256.h index 8726473c2d..bfa59ab749 100644 --- a/include/xrpl/conditions/detail/PreimageSha256.h +++ b/include/xrpl/conditions/detail/PreimageSha256.h @@ -9,8 +9,7 @@ #include -namespace xrpl { -namespace cryptoconditions { +namespace xrpl::cryptoconditions { class PreimageSha256 final : public Fulfillment { @@ -127,5 +126,4 @@ public: } }; -} // namespace cryptoconditions -} // namespace xrpl +} // namespace xrpl::cryptoconditions diff --git a/include/xrpl/conditions/detail/error.h b/include/xrpl/conditions/detail/error.h index fdeed3d9ac..73cee4c9f3 100644 --- a/include/xrpl/conditions/detail/error.h +++ b/include/xrpl/conditions/detail/error.h @@ -2,8 +2,7 @@ #include -namespace xrpl { -namespace cryptoconditions { +namespace xrpl::cryptoconditions { enum class error { generic = 1, @@ -28,8 +27,7 @@ enum class error { std::error_code make_error_code(error ev); -} // namespace cryptoconditions -} // namespace xrpl +} // namespace xrpl::cryptoconditions namespace std { diff --git a/include/xrpl/conditions/detail/utils.h b/include/xrpl/conditions/detail/utils.h index 17d93d43b5..8629987b8b 100644 --- a/include/xrpl/conditions/detail/utils.h +++ b/include/xrpl/conditions/detail/utils.h @@ -8,15 +8,12 @@ #include -namespace xrpl { -namespace cryptoconditions { - // A collection of functions to decode binary blobs // encoded with X.690 Distinguished Encoding Rules. // // This is a very trivial decoder and only implements // the bare minimum needed to support PreimageSha256. -namespace der { +namespace xrpl::cryptoconditions::der { // The preamble encapsulates the DER identifier and // length octets: @@ -89,7 +86,7 @@ parsePreamble(Slice& s, std::error_code& ec) p.length = s[0]; s += 1; - if (p.length & 0x80) + if ((p.length & 0x80) != 0u) { // Long form length: std::size_t const cnt = p.length & 0x7F; @@ -204,6 +201,4 @@ parseInteger(Slice& s, std::size_t count, std::error_code& ec) return v; } -} // namespace der -} // namespace cryptoconditions -} // namespace xrpl +} // namespace xrpl::cryptoconditions::der diff --git a/include/xrpl/core/ClosureCounter.h b/include/xrpl/core/ClosureCounter.h index b1939b2e63..bcdfa7d673 100644 --- a/include/xrpl/core/ClosureCounter.h +++ b/include/xrpl/core/ClosureCounter.h @@ -34,9 +34,9 @@ template class ClosureCounter { private: - std::mutex mutable mutex_{}; - std::condition_variable allClosuresDoneCond_{}; // guard with mutex_ - bool waitForClosures_{false}; // guard with mutex_ + std::mutex mutable mutex_; + std::condition_variable allClosuresDoneCond_; // guard with mutex_ + bool waitForClosures_{false}; // guard with mutex_ std::atomic closureCount_{0}; // Increment the count. @@ -72,10 +72,10 @@ private: { private: ClosureCounter& counter_; - std::remove_reference_t closure_; + std::remove_reference_t closure_{}; static_assert( - std::is_same()...)), Ret_t>::value, + std::is_same_v()...)), Ret_t>, "Closure arguments don't match ClosureCounter Ret_t or Args_t"); public: @@ -86,7 +86,7 @@ private: ++counter_; } - Substitute(Substitute&& rhs) noexcept(std::is_nothrow_move_constructible::value) + Substitute(Substitute&& rhs) noexcept(std::is_nothrow_move_constructible_v) : counter_(rhs.counter_), closure_(std::move(rhs.closure_)) { ++counter_; diff --git a/include/xrpl/core/HashRouter.h b/include/xrpl/core/HashRouter.h index b4f07f6dc0..3bc87f9524 100644 --- a/include/xrpl/core/HashRouter.h +++ b/include/xrpl/core/HashRouter.h @@ -109,9 +109,7 @@ private: class Entry : public CountedObject { public: - Entry() - { - } + Entry() = default; void addPeer(PeerShortID peer) diff --git a/include/xrpl/core/JobQueue.h b/include/xrpl/core/JobQueue.h index 558e55cf31..3c1bde89c3 100644 --- a/include/xrpl/core/JobQueue.h +++ b/include/xrpl/core/JobQueue.h @@ -128,7 +128,7 @@ public: beast::Journal journal, Logs& logs, perf::PerfLog& perfLog); - ~JobQueue(); + ~JobQueue() override; /** Adds a job to the JobQueue. @@ -141,8 +141,7 @@ public: */ template < typename JobHandler, - typename = - std::enable_if_t()()), void>::value>> + typename = std::enable_if_t()()), void>>> bool addJob(JobType type, std::string const& name, JobHandler&& jobHandler) { diff --git a/include/xrpl/core/JobTypeData.h b/include/xrpl/core/JobTypeData.h index c180629af7..917f838990 100644 --- a/include/xrpl/core/JobTypeData.h +++ b/include/xrpl/core/JobTypeData.h @@ -4,6 +4,8 @@ #include #include +#include + namespace xrpl { struct JobTypeData @@ -33,9 +35,9 @@ public: JobTypeData( JobTypeInfo const& info_, - beast::insight::Collector::ptr const& collector, + beast::insight::Collector::ptr collector, Logs& logs) noexcept - : m_load(logs.journal("LoadMonitor")), m_collector(collector), info(info_) + : m_load(logs.journal("LoadMonitor")), m_collector(std::move(collector)), info(info_) { m_load.setTargetLatency(info.getAverageLatency(), info.getPeakLatency()); diff --git a/include/xrpl/core/JobTypes.h b/include/xrpl/core/JobTypes.h index 98de97a1b4..1fb6f61573 100644 --- a/include/xrpl/core/JobTypes.h +++ b/include/xrpl/core/JobTypes.h @@ -33,8 +33,7 @@ private: std::chrono::milliseconds avgLatency, std::chrono::milliseconds peakLatency) { XRPL_ASSERT( - m_map.find(jt) == m_map.end(), - "xrpl::JobTypes::JobTypes::add : unique job type input"); + !m_map.contains(jt), "xrpl::JobTypes::JobTypes::add : unique job type input"); [[maybe_unused]] auto const inserted = m_map diff --git a/include/xrpl/core/LoadEvent.h b/include/xrpl/core/LoadEvent.h index f94e1020bf..87d4a5563d 100644 --- a/include/xrpl/core/LoadEvent.h +++ b/include/xrpl/core/LoadEvent.h @@ -16,7 +16,7 @@ class LoadEvent { public: // VFALCO TODO remove the dependency on LoadMonitor. Is that possible? - LoadEvent(LoadMonitor& monitor, std::string const& name, bool shouldStart); + LoadEvent(LoadMonitor& monitor, std::string name, bool shouldStart); LoadEvent(LoadEvent const&) = delete; ~LoadEvent(); diff --git a/include/xrpl/core/PeerReservationTable.h b/include/xrpl/core/PeerReservationTable.h index fc943f0807..3fb85e392f 100644 --- a/include/xrpl/core/PeerReservationTable.h +++ b/include/xrpl/core/PeerReservationTable.h @@ -20,7 +20,7 @@ struct PeerReservation final { public: PublicKey nodeId; - std::string description{}; + std::string description = {}; // NOLINT(readability-redundant-member-init) auto toJson() const -> Json::Value; @@ -68,7 +68,7 @@ public: contains(PublicKey const& nodeId) { std::lock_guard const lock(this->mutex_); - return table_.find({nodeId}) != table_.end(); + return table_.contains({.nodeId = nodeId, .description = {}}); } // Because `ApplicationImp` has two-phase initialization, so must we. diff --git a/include/xrpl/core/detail/Workers.h b/include/xrpl/core/detail/Workers.h index dbc93ecf81..bd82f9d57b 100644 --- a/include/xrpl/core/detail/Workers.h +++ b/include/xrpl/core/detail/Workers.h @@ -93,7 +93,7 @@ public: explicit Workers( Callback& callback, perf::PerfLog* perfLog, - std::string const& threadNames = "Worker", + std::string threadNames = "Worker", int numberOfThreads = static_cast(std::thread::hardware_concurrency())); ~Workers(); @@ -164,7 +164,7 @@ private: public beast::LockFreeStack::Node { public: - Worker(Workers& workers, std::string const& threadName, int const instance); + Worker(Workers& workers, std::string threadName, int const instance); ~Worker(); diff --git a/include/xrpl/json/json_writer.h b/include/xrpl/json/json_writer.h index e49abcd81a..45961fe030 100644 --- a/include/xrpl/json/json_writer.h +++ b/include/xrpl/json/json_writer.h @@ -15,9 +15,7 @@ class Value; class WriterBase { public: - virtual ~WriterBase() - { - } + virtual ~WriterBase() = default; virtual std::string write(Value const& root) = 0; }; @@ -34,9 +32,7 @@ class FastWriter : public WriterBase { public: FastWriter() = default; - virtual ~FastWriter() - { - } + ~FastWriter() override = default; public: // overridden from Writer std::string @@ -71,9 +67,7 @@ class StyledWriter : public WriterBase { public: StyledWriter(); - virtual ~StyledWriter() - { - } + ~StyledWriter() override = default; public: // overridden from Writer /** \brief Serialize a Value in JSON @@ -136,9 +130,7 @@ class StyledStreamWriter { public: StyledStreamWriter(std::string indentation = "\t"); - ~StyledStreamWriter() - { - } + ~StyledStreamWriter() = default; public: /** \brief Serialize a Value in JSON diff --git a/include/xrpl/json/to_string.h b/include/xrpl/json/to_string.h index fb379f5759..c9820e2e55 100644 --- a/include/xrpl/json/to_string.h +++ b/include/xrpl/json/to_string.h @@ -1,12 +1,11 @@ #pragma once -#include +#include + #include namespace Json { -class Value; - /** Writes a Json::Value to an std::string. */ std::string to_string(Value const&); @@ -15,8 +14,4 @@ to_string(Value const&); std::string pretty(Value const&); -/** Output using the StyledStreamWriter. @see Json::operator>>(). */ -std::ostream& -operator<<(std::ostream&, Value const& root); - } // namespace Json diff --git a/include/xrpl/ledger/AmendmentTable.h b/include/xrpl/ledger/AmendmentTable.h index 6ecfe2a240..8df09f74c3 100644 --- a/include/xrpl/ledger/AmendmentTable.h +++ b/include/xrpl/ledger/AmendmentTable.h @@ -8,6 +8,7 @@ #include #include +#include namespace xrpl { @@ -23,8 +24,8 @@ public: struct FeatureInfo { FeatureInfo() = delete; - FeatureInfo(std::string const& n, uint256 const& f, VoteBehavior v) - : name(n), feature(f), vote(v) + FeatureInfo(std::string n, uint256 const& f, VoteBehavior v) + : name(std::move(n)), feature(f), vote(v) { } @@ -75,10 +76,12 @@ public: doValidatedLedger(std::shared_ptr const& lastValidatedLedger) { if (needValidatedLedger(lastValidatedLedger->seq())) + { doValidatedLedger( lastValidatedLedger->seq(), getEnabledAmendments(*lastValidatedLedger), getMajorityAmendments(*lastValidatedLedger)); + } } /** Called to determine whether the amendment logic needs to process diff --git a/include/xrpl/ledger/ApplyView.h b/include/xrpl/ledger/ApplyView.h index d5be5c9a1e..73161453db 100644 --- a/include/xrpl/ledger/ApplyView.h +++ b/include/xrpl/ledger/ApplyView.h @@ -213,11 +213,60 @@ public: // Called when a credit is made to an account // This is required to support PaymentSandbox virtual void - creditHook( + creditHookIOU( AccountID const& from, AccountID const& to, STAmount const& amount, STAmount const& preCreditBalance) + { + XRPL_ASSERT(amount.holds(), "creditHookIOU: amount is for Issue"); + } + + virtual void + creditHookMPT( + AccountID const& from, + AccountID const& to, + STAmount const& amount, + std::uint64_t preCreditBalanceHolder, + std::int64_t preCreditBalanceIssuer) + { + XRPL_ASSERT(amount.holds(), "creditHookMPT: amount is for MPTIssue"); + } + + /** Facilitate tracking of MPT sold by an issuer owning MPT sell offer. + * Unlike IOU, MPT doesn't have bi-directional relationship with an issuer, + * where a trustline limits an amount that can be issued to a holder. + * Consequently, the credit step (last MPTEndpointStep or + * BookStep buying MPT) might temporarily overflow OutstandingAmount. + * Limiting of a step's output amount in this case is delegated to + * the next step (in rev order). The next step always redeems when a holder + * account sells MPT (first MPTEndpointStep or BookStep selling MPT). + * In this case the holder account is only limited by the step's output + * and it's available funds since it's transferring the funds from one + * account to another account and doesn't change OutstandingAmount. + * This doesn't apply to an offer owned by an issuer. + * In this case the issuer sells or self debits and is increasing + * OutstandingAmount. Ability to issue is limited by the issuer + * originally available funds less already self sold MPT amounts (MPT sell + * offer). + * Consider an example: + * - GW creates MPT(USD) with 1,000USD MaximumAmount. + * - GW pays 950USD to A1. + * - A1 creates an offer 100XRP(buy)/100USD(sell). + * - GW creates an offer 100XRP(buy)/100USD(sell). + * - A2 pays 200USD to A3 with sendMax of 200XRP. + * Since the payment engine executes payments in reverse, + * OutstandingAmount overflows in MPTEndpointStep: 950 + 200 = 1,150USD. + * BookStep first consumes A1 offer. This reduces OutstandingAmount + * by 100USD: 1,150 - 100 = 1,050USD. GW offer can only be partially + * consumed because the initial available amount is 50USD = 1,000 - 950. + * BookStep limits it's output to 150USD. This in turn limits A3's send + * amount to 150XRP: A1 buys 100XRP and sells 100USD to A3. This doesn't + * change OutstandingAmount. GW buys 50XRP and sells 50USD to A3. This + * changes OutstandingAmount to 1,000USD. + */ + virtual void + issuerSelfDebitHookMPT(MPTIssue const& issue, std::uint64_t amount, std::int64_t origBalance) { } diff --git a/include/xrpl/ledger/BookListeners.h b/include/xrpl/ledger/BookListeners.h index 3ed267448b..c77271b20e 100644 --- a/include/xrpl/ledger/BookListeners.h +++ b/include/xrpl/ledger/BookListeners.h @@ -14,9 +14,7 @@ class BookListeners public: using pointer = std::shared_ptr; - BookListeners() - { - } + BookListeners() = default; /** Add a new subscription for this book */ diff --git a/include/xrpl/ledger/CachedView.h b/include/xrpl/ledger/CachedView.h index 7cab1dc1b3..5dad2598f4 100644 --- a/include/xrpl/ledger/CachedView.h +++ b/include/xrpl/ledger/CachedView.h @@ -132,7 +132,7 @@ template class CachedView : public detail::CachedViewImpl { private: - static_assert(std::is_base_of::value, ""); + static_assert(std::is_base_of_v, ""); std::shared_ptr sp_; diff --git a/include/xrpl/ledger/CanonicalTXSet.h b/include/xrpl/ledger/CanonicalTXSet.h index 45f58b5701..857b82a734 100644 --- a/include/xrpl/ledger/CanonicalTXSet.h +++ b/include/xrpl/ledger/CanonicalTXSet.h @@ -29,31 +29,31 @@ private: friend bool operator<(Key const& lhs, Key const& rhs); - inline friend bool + friend bool operator>(Key const& lhs, Key const& rhs) { return rhs < lhs; } - inline friend bool + friend bool operator<=(Key const& lhs, Key const& rhs) { return !(lhs > rhs); } - inline friend bool + friend bool operator>=(Key const& lhs, Key const& rhs) { return !(lhs < rhs); } - inline friend bool + friend bool operator==(Key const& lhs, Key const& rhs) { return lhs.txId_ == rhs.txId_; } - inline friend bool + friend bool operator!=(Key const& lhs, Key const& rhs) { return !(lhs == rhs); diff --git a/include/xrpl/ledger/Ledger.h b/include/xrpl/ledger/Ledger.h index 75a8f55dc0..69fb27975d 100644 --- a/include/xrpl/ledger/Ledger.h +++ b/include/xrpl/ledger/Ledger.h @@ -82,12 +82,12 @@ public: */ Ledger( create_genesis_t, - Rules const& rules, + Rules rules, Fees const& fees, std::vector const& amendments, Family& family); - Ledger(LedgerHeader const& info, Rules const& rules, Family& family); + Ledger(LedgerHeader const& info, Rules rules, Family& family); /** Used for ledgers loaded from JSON files @@ -100,7 +100,7 @@ public: LedgerHeader const& info, bool& loaded, bool acquire, - Rules const& rules, + Rules rules, Fees const& fees, Family& family, beast::Journal j); @@ -117,11 +117,11 @@ public: Ledger( std::uint32_t ledgerSeq, NetClock::time_point closeTime, - Rules const& rules, + Rules rules, Fees const& fees, Family& family); - ~Ledger() = default; + ~Ledger() override = default; // // ReadView diff --git a/include/xrpl/ledger/LedgerTiming.h b/include/xrpl/ledger/LedgerTiming.h index 33ccc671e2..8171beed3c 100644 --- a/include/xrpl/ledger/LedgerTiming.h +++ b/include/xrpl/ledger/LedgerTiming.h @@ -34,7 +34,7 @@ auto constexpr decreaseLedgerTimeResolutionEvery = 1; /** Calculates the close time resolution for the specified ledger. - The Ripple protocol uses binning to represent time intervals using only one + The XRPL protocol uses binning to represent time intervals using only one timestamp. This allows servers to derive a common time for the next ledger, without the need for perfectly synchronized clocks. The time resolution (i.e. the size of the intervals) is adjusted dynamically @@ -62,7 +62,7 @@ getNextLedgerTimeResolution( bool previousAgree, Seq ledgerSeq) { - XRPL_ASSERT(ledgerSeq != Seq{0}, "ripple:getNextLedgerTimeResolution : valid ledger sequence"); + XRPL_ASSERT(ledgerSeq != Seq{0}, "xrpl::getNextLedgerTimeResolution : valid ledger sequence"); using namespace std::chrono; // Find the current resolution: @@ -72,7 +72,7 @@ getNextLedgerTimeResolution( previousResolution); XRPL_ASSERT( iter != std::end(ledgerPossibleTimeResolutions), - "ripple:getNextLedgerTimeResolution : found time resolution"); + "xrpl::getNextLedgerTimeResolution : found time resolution"); // This should never happen, but just as a precaution if (iter == std::end(ledgerPossibleTimeResolutions)) diff --git a/include/xrpl/ledger/OpenView.h b/include/xrpl/ledger/OpenView.h index 5c942ce5e3..3420aa1df0 100644 --- a/include/xrpl/ledger/OpenView.h +++ b/include/xrpl/ledger/OpenView.h @@ -135,7 +135,7 @@ public: OpenView( open_ledger_t, ReadView const* base, - Rules const& rules, + Rules rules, std::shared_ptr hold = nullptr); OpenView(open_ledger_t, Rules const& rules, std::shared_ptr const& base) diff --git a/include/xrpl/ledger/OrderBookDB.h b/include/xrpl/ledger/OrderBookDB.h index 5bf66eb4ab..a0aee58e2a 100644 --- a/include/xrpl/ledger/OrderBookDB.h +++ b/include/xrpl/ledger/OrderBookDB.h @@ -3,8 +3,8 @@ #include #include #include +#include #include -#include #include #include @@ -53,30 +53,30 @@ public: issue. This is useful for pathfinding to find all possible next hops from a given currency. - @param issue The issue to search for + @param asset The asset to search for @param domain Optional domain restriction for the order book @return Vector of books that want this issue */ virtual std::vector - getBooksByTakerPays(Issue const& issue, std::optional const& domain = std::nullopt) = 0; + getBooksByTakerPays(Asset const& asset, std::optional const& domain = std::nullopt) = 0; /** Get the count of order books that want a specific issue. - @param issue The issue to search for + @param asset The asset to search for @param domain Optional domain restriction for the order book @return Number of books that want this issue */ virtual int - getBookSize(Issue const& issue, std::optional const& domain = std::nullopt) = 0; + getBookSize(Asset const& asset, std::optional const& domain = std::nullopt) = 0; /** Check if an order book to XRP exists for the given issue. - @param issue The issue to check + @param asset The asset to check @param domain Optional domain restriction for the order book @return true if a book from this issue to XRP exists */ virtual bool - isBookToXRP(Issue const& issue, std::optional const& domain = std::nullopt) = 0; + isBookToXRP(Asset const& asset, std::optional const& domain = std::nullopt) = 0; /** * Process a transaction for order book tracking. diff --git a/include/xrpl/ledger/PaymentSandbox.h b/include/xrpl/ledger/PaymentSandbox.h index 891e3ef27b..223a3c0c5a 100644 --- a/include/xrpl/ledger/PaymentSandbox.h +++ b/include/xrpl/ledger/PaymentSandbox.h @@ -6,6 +6,7 @@ #include #include +#include namespace xrpl { @@ -15,11 +16,56 @@ namespace detail { // into the PaymentSandbox class itself class DeferredCredits { -public: - struct Adjustment +private: + using KeyIOU = std::tuple; + struct ValueIOU { - Adjustment(STAmount const& d, STAmount const& c, STAmount const& b) - : debits(d), credits(c), origBalance(b) + explicit ValueIOU() = default; + + STAmount lowAcctCredits; + STAmount highAcctCredits; + STAmount lowAcctOrigBalance; + }; + + struct HolderValueMPT + { + HolderValueMPT() = default; + // Debit to issuer + std::uint64_t debit = 0; + std::uint64_t origBalance = 0; + }; + + struct IssuerValueMPT + { + IssuerValueMPT() = default; + std::map holders; + // Credit to holder + std::uint64_t credit = 0; + // OutstandingAmount might overflow when MPTs are credited to a holder. + // Consider A1 paying 100MPT to A2 and A1 already having maximum MPTs. + // Since the payment engine executes a payment in revers, A2 is + // credited first and OutstandingAmount is going to be equal + // to MaximumAmount + 100MPT. In the next step A1 redeems 100MPT + // to the issuer and OutstandingAmount balances out. + std::int64_t origBalance = 0; + // Self debit on offer selling MPT. Since the payment engine executes + // a payment in reverse, a crediting/buying step may overflow + // OutstandingAmount. A sell MPT offer owned by a holder can redeem any + // amount up to the offer's amount and holder's available funds, + // balancing out OutstandingAmount. But if the offer's owner is issuer + // then it issues more MPT. In this case the available amount to issue + // is the initial issuer's available amount less all offer sell amounts + // by the issuer. This is self-debit, where the offer's owner, + // issuer in this case, debits to self. + std::uint64_t selfDebit = 0; + }; + using AdjustmentMPT = IssuerValueMPT; + +public: + struct AdjustmentIOU + { + AdjustmentIOU(STAmount d, STAmount c, STAmount b) + : debits(std::move(d)), credits(std::move(c)), origBalance(std::move(b)) { } STAmount debits; @@ -29,16 +75,30 @@ public: // Get the adjustments for the balance between main and other. // Returns the debits, credits and the original balance - std::optional - adjustments(AccountID const& main, AccountID const& other, Currency const& currency) const; + std::optional + adjustmentsIOU(AccountID const& main, AccountID const& other, Currency const& currency) const; + + std::optional + adjustmentsMPT(MPTID const& mptID) const; void - credit( + creditIOU( AccountID const& sender, AccountID const& receiver, STAmount const& amount, STAmount const& preCreditSenderBalance); + void + creditMPT( + AccountID const& sender, + AccountID const& receiver, + STAmount const& amount, + std::uint64_t preCreditBalanceHolder, + std::int64_t preCreditBalanceIssuer); + + void + issuerSelfDebitMPT(MPTIssue const& issue, std::uint64_t amount, std::int64_t origBalance); + void ownerCount(AccountID const& id, std::uint32_t cur, std::uint32_t next); @@ -52,21 +112,11 @@ public: apply(DeferredCredits& to); private: - // lowAccount, highAccount - using Key = std::tuple; - struct Value - { - explicit Value() = default; + static KeyIOU + makeKeyIOU(AccountID const& a1, AccountID const& a2, Currency const& currency); - STAmount lowAcctCredits; - STAmount highAcctCredits; - STAmount lowAcctOrigBalance; - }; - - static Key - makeKey(AccountID const& a1, AccountID const& a2, Currency const& c); - - std::map credits_; + std::map creditsIOU_; + std::map creditsMPT_; std::map ownerCounts_; }; @@ -131,16 +181,35 @@ public: /** @} */ STAmount - balanceHook(AccountID const& account, AccountID const& issuer, STAmount const& amount) + balanceHookIOU(AccountID const& account, AccountID const& issuer, STAmount const& amount) const override; + STAmount + balanceHookMPT(AccountID const& account, MPTIssue const& issue, std::int64_t amount) + const override; + + STAmount + balanceHookSelfIssueMPT(MPTIssue const& issue, std::int64_t amount) const override; + void - creditHook( + creditHookIOU( AccountID const& from, AccountID const& to, STAmount const& amount, STAmount const& preCreditBalance) override; + void + creditHookMPT( + AccountID const& from, + AccountID const& to, + STAmount const& amount, + std::uint64_t preCreditBalanceHolder, + std::int64_t preCreditBalanceIssuer) override; + + void + issuerSelfDebitHookMPT(MPTIssue const& issue, std::uint64_t amount, std::int64_t origBalance) + override; + void adjustOwnerCountHook(AccountID const& account, std::uint32_t cur, std::uint32_t next) override; diff --git a/include/xrpl/ledger/PendingSaves.h b/include/xrpl/ledger/PendingSaves.h index 4e952547da..ff70d48bb9 100644 --- a/include/xrpl/ledger/PendingSaves.h +++ b/include/xrpl/ledger/PendingSaves.h @@ -65,7 +65,7 @@ public: pending(LedgerIndex seq) { std::lock_guard const lock(mutex_); - return map_.find(seq) != map_.end(); + return map_.contains(seq); } /** Check if a ledger should be dispatched diff --git a/include/xrpl/ledger/ReadView.h b/include/xrpl/ledger/ReadView.h index e3a5163418..debf01e85f 100644 --- a/include/xrpl/ledger/ReadView.h +++ b/include/xrpl/ledger/ReadView.h @@ -148,15 +148,35 @@ public: // Accounts in a payment are not allowed to use assets acquired during that // payment. The PaymentSandbox tracks the debits, credits, and owner count - // changes that accounts make during a payment. `balanceHook` adjusts + // changes that accounts make during a payment. `balanceHookIOU` adjusts // balances so newly acquired assets are not counted toward the balance. // This is required to support PaymentSandbox. virtual STAmount - balanceHook(AccountID const& account, AccountID const& issuer, STAmount const& amount) const + balanceHookIOU(AccountID const& account, AccountID const& issuer, STAmount const& amount) const { + XRPL_ASSERT(amount.holds(), "balanceHookIOU: amount is for Issue"); + return amount; } + // balanceHookMPT adjusts balances so newly acquired assets are not counted + // toward the balance. + virtual STAmount + balanceHookMPT(AccountID const& account, MPTIssue const& issue, std::int64_t amount) const + { + return STAmount{issue, amount}; + } + + // An offer owned by an issuer and selling MPT is limited by the issuer's + // funds available to issue, which are originally available funds less + // already self sold MPT amounts (MPT sell offer). This hook is used + // by issuerFundsToSelfIssue() function. + virtual STAmount + balanceHookSelfIssueMPT(MPTIssue const& issue, std::int64_t amount) const + { + return STAmount{issue, amount}; + } + // Accounts in a payment are not allowed to use assets acquired during that // payment. The PaymentSandbox tracks the debits, credits, and owner count // changes that accounts make during a payment. `ownerCountHook` adjusts the diff --git a/include/xrpl/ledger/View.h b/include/xrpl/ledger/View.h index 55be01d677..4958a89d8c 100644 --- a/include/xrpl/ledger/View.h +++ b/include/xrpl/ledger/View.h @@ -30,10 +30,10 @@ enum class SkipEntry : bool { No = false, Yes }; /** Determines whether the given expiration time has passed. In the XRP Ledger, expiration times are defined as the number of whole - seconds after the "Ripple Epoch" which, for historical reasons, is set + seconds after the "XRPL epoch" which, for historical reasons, is set to January 1, 2000 (00:00 UTC). - This is like the way the Unix epoch works, except the Ripple Epoch is + This is like the way the Unix epoch works, except the XRPL epoch is precisely 946,684,800 seconds after the Unix Epoch. See https://xrpl.org/basic-data-types.html#specifying-time @@ -63,8 +63,8 @@ isVaultPseudoAccountFrozen( isLPTokenFrozen( ReadView const& view, AccountID const& account, - Issue const& asset, - Issue const& asset2); + Asset const& asset, + Asset const& asset2); // Return the list of enabled amendments [[nodiscard]] std::set diff --git a/include/xrpl/ledger/detail/ApplyStateTable.h b/include/xrpl/ledger/detail/ApplyStateTable.h index 07af5247f6..0ded0aa273 100644 --- a/include/xrpl/ledger/detail/ApplyStateTable.h +++ b/include/xrpl/ledger/detail/ApplyStateTable.h @@ -10,8 +10,7 @@ #include -namespace xrpl { -namespace detail { +namespace xrpl::detail { // Helper class that buffers modifications class ApplyStateTable @@ -125,5 +124,4 @@ private: beast::Journal j); }; -} // namespace detail -} // namespace xrpl +} // namespace xrpl::detail diff --git a/include/xrpl/ledger/detail/ApplyViewBase.h b/include/xrpl/ledger/detail/ApplyViewBase.h index b3ec3c0fea..0e93ac5d2f 100644 --- a/include/xrpl/ledger/detail/ApplyViewBase.h +++ b/include/xrpl/ledger/detail/ApplyViewBase.h @@ -5,8 +5,7 @@ #include #include -namespace xrpl { -namespace detail { +namespace xrpl::detail { class ApplyViewBase : public ApplyView, public RawView { @@ -102,5 +101,4 @@ protected: detail::ApplyStateTable items_; }; -} // namespace detail -} // namespace xrpl +} // namespace xrpl::detail diff --git a/include/xrpl/ledger/detail/RawStateTable.h b/include/xrpl/ledger/detail/RawStateTable.h index 499b9204c6..b3307b3ea4 100644 --- a/include/xrpl/ledger/detail/RawStateTable.h +++ b/include/xrpl/ledger/detail/RawStateTable.h @@ -9,8 +9,7 @@ #include #include -namespace xrpl { -namespace detail { +namespace xrpl::detail { // Helper class that buffers raw modifications class RawStateTable @@ -108,5 +107,4 @@ private: XRPAmount dropsDestroyed_{0}; }; -} // namespace detail -} // namespace xrpl +} // namespace xrpl::detail diff --git a/include/xrpl/ledger/detail/ReadViewFwdRange.h b/include/xrpl/ledger/detail/ReadViewFwdRange.h index bf40b04a6a..26ed22f11d 100644 --- a/include/xrpl/ledger/detail/ReadViewFwdRange.h +++ b/include/xrpl/ledger/detail/ReadViewFwdRange.h @@ -102,7 +102,7 @@ public: private: ReadView const* view_ = nullptr; - std::unique_ptr impl_; + std::unique_ptr impl_{}; std::optional mutable cache_; }; diff --git a/include/xrpl/tx/transactors/dex/AMMHelpers.h b/include/xrpl/ledger/helpers/AMMHelpers.h similarity index 84% rename from include/xrpl/tx/transactors/dex/AMMHelpers.h rename to include/xrpl/ledger/helpers/AMMHelpers.h index 55f27a3f06..d261f9e018 100644 --- a/include/xrpl/tx/transactors/dex/AMMHelpers.h +++ b/include/xrpl/ledger/helpers/AMMHelpers.h @@ -1,8 +1,13 @@ #pragma once +#include #include #include #include +#include +#include +#include +#include #include #include #include @@ -11,6 +16,7 @@ #include #include #include +#include namespace xrpl { @@ -36,7 +42,7 @@ enum class IsDeposit : bool { No = false, Yes = true }; * @return LP Tokens as IOU */ STAmount -ammLPTokens(STAmount const& asset1, STAmount const& asset2, Issue const& lptIssue); +ammLPTokens(STAmount const& asset1, STAmount const& asset2, Asset const& lptIssue); /** Calculate LP Tokens given asset's deposit amount. * @param asset1Balance current AMM asset1 balance @@ -124,7 +130,8 @@ withinRelativeDistance(Quality const& calcQuality, Quality const& reqQuality, Nu template requires( std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v) + std::is_same_v || std::is_same_v || + std::is_same_v) bool withinRelativeDistance(Amt const& calc, Amt const& req, Number const& dist) { @@ -195,16 +202,16 @@ getAMMOfferStartWithTakerGets( // Round downward to minimize the offer and to maximize the quality. // This has the most impact when takerGets is XRP. auto const takerGets = - toAmount(getIssue(pool.out), nTakerGetsProposed, Number::downward); + toAmount(getAsset(pool.out), nTakerGetsProposed, Number::downward); return TAmounts{swapAssetOut(pool, takerGets, tfee), takerGets}; }; // Try to reduce the offer size to improve the quality. // The quality might still not match the targetQuality for a tiny offer. - if (auto amounts = getAmounts(*nTakerGets); Quality{amounts} < targetQuality) + auto amounts = getAmounts(*nTakerGets); + if (Quality{amounts} < targetQuality) return getAmounts(detail::reduceOffer(amounts.out)); - else - return amounts; + return amounts; } /** Generate AMM offer starting with takerPays when AMM pool @@ -262,16 +269,16 @@ getAMMOfferStartWithTakerPays( // Round downward to minimize the offer and to maximize the quality. // This has the most impact when takerPays is XRP. auto const takerPays = - toAmount(getIssue(pool.in), nTakerPaysProposed, Number::downward); + toAmount(getAsset(pool.in), nTakerPaysProposed, Number::downward); return TAmounts{takerPays, swapAssetIn(pool, takerPays, tfee)}; }; // Try to reduce the offer size to improve the quality. // The quality might still not match the targetQuality for a tiny offer. - if (auto amounts = getAmounts(*nTakerPays); Quality{amounts} < targetQuality) + auto amounts = getAmounts(*nTakerPays); + if (Quality{amounts} < targetQuality) return getAmounts(detail::reduceOffer(amounts.in)); - else - return amounts; + return amounts; } /** Generate AMM offer so that either updated Spot Price Quality (SPQ) @@ -311,9 +318,12 @@ changeSpotPriceQuality( auto const& a = f; auto const b = pool.in * (1 + f); Number const c = pool.in * pool.in - pool.in * pool.out * quality.rate(); - if (auto const res = b * b - 4 * a * c; res < 0) + auto const res = b * b - 4 * a * c; + if (res < 0) + { return std::nullopt; // LCOV_EXCL_LINE - else if (auto const nTakerPaysPropose = (-b + root2(res)) / (2 * a); nTakerPaysPropose > 0) + } + if (auto const nTakerPaysPropose = (-b + root2(res)) / (2 * a); nTakerPaysPropose > 0) { auto const nTakerPays = [&]() { // The fee might make the AMM offer quality less than CLOB @@ -331,7 +341,7 @@ changeSpotPriceQuality( << " " << to_string(pool.out) << " " << quality << " " << tfee; return std::nullopt; } - auto const takerPays = toAmount(getIssue(pool.in), nTakerPays, Number::upward); + auto const takerPays = toAmount(getAsset(pool.in), nTakerPays, Number::upward); // should not fail if (auto amounts = TAmounts{takerPays, swapAssetIn(pool, takerPays, tfee)}; Quality{amounts} < quality && @@ -360,7 +370,7 @@ changeSpotPriceQuality( // Generate the offer starting with XRP side. Return seated offer amounts // if the offer can be generated, otherwise nullopt. auto amounts = [&]() { - if (isXRP(getIssue(pool.out))) + if (isXRP(getAsset(pool.out))) return getAMMOfferStartWithTakerGets(pool, quality, tfee); return getAMMOfferStartWithTakerPays(pool, quality, tfee); }(); @@ -445,7 +455,7 @@ swapAssetIn(TAmounts const& pool, TIn const& assetIn, std::uint16_t t auto const denom = pool.in + assetIn * (1 - fee); if (denom.signum() <= 0) - return toAmount(getIssue(pool.out), 0); + return toAmount(getAsset(pool.out), 0); Number::setround(Number::upward); auto const ratio = numerator / denom; @@ -454,17 +464,15 @@ swapAssetIn(TAmounts const& pool, TIn const& assetIn, std::uint16_t t auto const swapOut = pool.out - ratio; if (swapOut.signum() < 0) - return toAmount(getIssue(pool.out), 0); + return toAmount(getAsset(pool.out), 0); - return toAmount(getIssue(pool.out), swapOut, Number::downward); - } - else - { - return toAmount( - getIssue(pool.out), - pool.out - (pool.in * pool.out) / (pool.in + assetIn * feeMult(tfee)), - Number::downward); + return toAmount(getAsset(pool.out), swapOut, Number::downward); } + + return toAmount( + getAsset(pool.out), + pool.out - (pool.in * pool.out) / (pool.in + assetIn * feeMult(tfee)), + Number::downward); } /** Swap assetOut out of the pool and swap in a proportional amount @@ -508,7 +516,7 @@ swapAssetOut(TAmounts const& pool, TOut const& assetOut, std::uint16_ auto const denom = pool.out - assetOut; if (denom.signum() <= 0) { - return toMaxAmount(getIssue(pool.in)); + return toMaxAmount(getAsset(pool.in)); } Number::setround(Number::upward); @@ -522,17 +530,15 @@ swapAssetOut(TAmounts const& pool, TOut const& assetOut, std::uint16_ Number::setround(Number::upward); auto const swapIn = numerator2 / feeMult; if (swapIn.signum() < 0) - return toAmount(getIssue(pool.in), 0); + return toAmount(getAsset(pool.in), 0); - return toAmount(getIssue(pool.in), swapIn, Number::upward); - } - else - { - return toAmount( - getIssue(pool.in), - ((pool.in * pool.out) / (pool.out - assetOut) - pool.in) / feeMult(tfee), - Number::upward); + return toAmount(getAsset(pool.in), swapIn, Number::upward); } + + return toAmount( + getAsset(pool.in), + ((pool.in * pool.out) / (pool.out - assetOut) - pool.in) / feeMult(tfee), + Number::upward); } /** Return square of n. @@ -616,9 +622,13 @@ getRoundedAsset(Rules const& rules, STAmount const& balance, A const& frac, IsDe if (!rules.enabled(fixAMMv1_3)) { if constexpr (std::is_same_v) - return multiply(balance, frac, balance.issue()); + { + return multiply(balance, frac, balance.asset()); + } else - return toSTAmount(balance.issue(), balance * frac); + { + return toSTAmount(balance.asset(), balance * frac); + } } auto const rm = detail::getAssetRounding(isDeposit); return multiply(balance, frac, rm); @@ -712,4 +722,94 @@ adjustFracByTokens( STAmount const& tokens, Number const& frac); +/** Get AMM pool balances. + */ +std::pair +ammPoolHolds( + ReadView const& view, + AccountID const& ammAccountID, + Asset const& asset1, + Asset const& asset2, + FreezeHandling freezeHandling, + AuthHandling authHandling, + beast::Journal const j); + +/** Get AMM pool and LP token balances. If both optIssue are + * provided then they are used as the AMM token pair issues. + * Otherwise the missing issues are fetched from ammSle. + */ +Expected, TER> +ammHolds( + ReadView const& view, + SLE const& ammSle, + std::optional const& optAsset1, + std::optional const& optAsset2, + FreezeHandling freezeHandling, + AuthHandling authHandling, + beast::Journal const j); + +/** Get the balance of LP tokens. + */ +STAmount +ammLPHolds( + ReadView const& view, + Asset const& asset1, + Asset const& asset2, + AccountID const& ammAccount, + AccountID const& lpAccount, + beast::Journal const j); + +STAmount +ammLPHolds( + ReadView const& view, + SLE const& ammSle, + AccountID const& lpAccount, + beast::Journal const j); + +/** Get AMM trading fee for the given account. The fee is discounted + * if the account is the auction slot owner or one of the slot's authorized + * accounts. + */ +std::uint16_t +getTradingFee(ReadView const& view, SLE const& ammSle, AccountID const& account); + +/** Returns total amount held by AMM for the given token. + */ +STAmount +ammAccountHolds(ReadView const& view, AccountID const& ammAccountID, Asset const& asset); + +/** Delete trustlines to AMM. If all trustlines are deleted then + * AMM object and account are deleted. Otherwise tecINCOMPLETE is returned. + */ +TER +deleteAMMAccount(Sandbox& view, Asset const& asset, Asset const& asset2, beast::Journal j); + +/** Initialize Auction and Voting slots and set the trading/discounted fee. + */ +void +initializeFeeAuctionVote( + ApplyView& view, + std::shared_ptr& ammSle, + AccountID const& account, + Asset const& lptAsset, + std::uint16_t tfee); + +/** Return true if the Liquidity Provider is the only AMM provider, false + * otherwise. Return tecINTERNAL if encountered an unexpected condition, + * for instance Liquidity Provider has more than one LPToken trustline. + */ +Expected +isOnlyLiquidityProvider(ReadView const& view, Issue const& ammIssue, AccountID const& lpAccount); + +/** Due to rounding, the LPTokenBalance of the last LP might + * not match the LP's trustline balance. If it's within the tolerance, + * update LPTokenBalance to match the LP's trustline balance. + */ +Expected +verifyAndAdjustLPTokenBalance( + Sandbox& sb, + STAmount const& lpTokens, + std::shared_ptr& ammSle, + AccountID const& account); + } // namespace xrpl diff --git a/include/xrpl/tx/transactors/delegate/DelegateUtils.h b/include/xrpl/ledger/helpers/DelegateHelpers.h similarity index 100% rename from include/xrpl/tx/transactors/delegate/DelegateUtils.h rename to include/xrpl/ledger/helpers/DelegateHelpers.h diff --git a/include/xrpl/ledger/helpers/DirectoryHelpers.h b/include/xrpl/ledger/helpers/DirectoryHelpers.h index 189dfcd263..2ae188182d 100644 --- a/include/xrpl/ledger/helpers/DirectoryHelpers.h +++ b/include/xrpl/ledger/helpers/DirectoryHelpers.h @@ -189,7 +189,7 @@ forEachItem( AccountID const& id, std::function const&)> const& f) { - return forEachItem(view, keylet::ownerDir(id), f); + forEachItem(view, keylet::ownerDir(id), f); } /** Iterate all items after an item in an owner directory. diff --git a/src/libxrpl/tx/transactors/escrow/EscrowHelpers.h b/include/xrpl/ledger/helpers/EscrowHelpers.h similarity index 92% rename from src/libxrpl/tx/transactors/escrow/EscrowHelpers.h rename to include/xrpl/ledger/helpers/EscrowHelpers.h index 8991fb06cc..b44601594d 100644 --- a/src/libxrpl/tx/transactors/escrow/EscrowHelpers.h +++ b/include/xrpl/ledger/helpers/EscrowHelpers.h @@ -10,7 +10,6 @@ #include #include #include -#include namespace xrpl { @@ -42,7 +41,8 @@ escrowUnlockApplyHelper( bool createAsset, beast::Journal journal) { - Keylet const trustLineKey = keylet::line(receiver, amount.issue()); + Issue const& issue = amount.get(); + Keylet const trustLineKey = keylet::line(receiver, issue); bool const recvLow = issuer > receiver; bool const senderIssuer = issuer == sender; bool const receiverIssuer = issuer == receiver; @@ -65,9 +65,9 @@ escrowUnlockApplyHelper( return tecNO_LINE_INSUF_RESERVE; } - Currency const currency = amount.getCurrency(); - STAmount initialBalance(amount.issue()); - initialBalance.setIssuer(noAccount()); + Currency const currency = issue.currency; + STAmount initialBalance(issue); + initialBalance.get().account = noAccount(); if (TER const ter = trustCreate( view, // payment sandbox @@ -114,7 +114,8 @@ escrowUnlockApplyHelper( if ((!senderIssuer && !receiverIssuer) && lockedRate != parityRate) { // compute transfer fee, if any - auto const xferFee = amount.value() - divideRound(amount, lockedRate, amount.issue(), true); + auto const xferFee = + amount.value() - divideRound(amount, lockedRate, amount.get(), true); // compute balance to transfer finalAmt = amount.value() - xferFee; } @@ -149,7 +150,7 @@ escrowUnlockApplyHelper( // if destination is not the issuer then transfer funds if (!receiverIssuer) { - auto const ter = rippleCredit(view, issuer, receiver, finalAmt, true, journal); + auto const ter = directSendNoFee(view, issuer, receiver, finalAmt, true, journal); if (!isTesSuccess(ter)) return ter; // LCOV_EXCL_LINE } @@ -183,8 +184,7 @@ escrowUnlockApplyHelper( return tecINSUFFICIENT_RESERVE; } - if (auto const ter = MPTokenAuthorize::createMPToken(view, mptID, receiver, 0); - !isTesSuccess(ter)) + if (auto const ter = createMPToken(view, mptID, receiver, 0); !isTesSuccess(ter)) { return ter; // LCOV_EXCL_LINE } @@ -218,7 +218,7 @@ escrowUnlockApplyHelper( // compute balance to transfer finalAmt = amount.value() - xferFee; } - return rippleUnlockEscrowMPT( + return unlockEscrowMPT( view, sender, receiver, diff --git a/include/xrpl/ledger/helpers/MPTokenHelpers.h b/include/xrpl/ledger/helpers/MPTokenHelpers.h index ab487280b9..6544b18dd1 100644 --- a/include/xrpl/ledger/helpers/MPTokenHelpers.h +++ b/include/xrpl/ledger/helpers/MPTokenHelpers.h @@ -80,6 +80,7 @@ authorizeMPToken( * requireAuth check is recursive for MPT shares in a vault, descending to * assets in the vault, up to maxAssetCheckDepth recursion depth. This is * purely defensive, as we currently do not allow such vaults to be created. + * WeakAuth intentionally allows missing MPTokens under MPToken V2. */ [[nodiscard]] TER requireAuth( @@ -114,6 +115,12 @@ canTransfer( AccountID const& from, AccountID const& to); +/** Check if Asset can be traded on DEX. return tecNO_PERMISSION + * if it doesn't and tesSUCCESS otherwise. + */ +[[nodiscard]] TER +canTrade(ReadView const& view, Asset const& asset); + //------------------------------------------------------------------------------ // // Empty holding operations (MPT-specific) @@ -142,14 +149,14 @@ removeEmptyHolding( //------------------------------------------------------------------------------ TER -rippleLockEscrowMPT( +lockEscrowMPT( ApplyView& view, AccountID const& uGrantorID, STAmount const& saAmount, beast::Journal j); TER -rippleUnlockEscrowMPT( +unlockEscrowMPT( ApplyView& view, AccountID const& uGrantorID, AccountID const& uGranteeID, @@ -157,4 +164,80 @@ rippleUnlockEscrowMPT( STAmount const& grossAmount, beast::Journal j); +TER +createMPToken( + ApplyView& view, + MPTID const& mptIssuanceID, + AccountID const& account, + std::uint32_t const flags); + +TER +checkCreateMPT( + xrpl::ApplyView& view, + xrpl::MPTIssue const& mptIssue, + xrpl::AccountID const& holder, + beast::Journal j); + +//------------------------------------------------------------------------------ +// +// MPT Overflow related +// +//------------------------------------------------------------------------------ + +// MaximumAmount doesn't exceed 2**63-1 +std::int64_t +maxMPTAmount(SLE const& sleIssuance); + +// OutstandingAmount may overflow and available amount might be negative. +// But available amount is always <= |MaximumAmount - OutstandingAmount|. +std::int64_t +availableMPTAmount(SLE const& sleIssuance); + +std::int64_t +availableMPTAmount(ReadView const& view, MPTID const& mptID); + +/** Checks for two types of OutstandingAmount overflow during a send operation. + * 1. **Direct directSendNoFee (Overflow: No):** A true overflow check when + * `OutstandingAmount > MaximumAmount`. This threshold is used for direct + * directSendNoFee transactions that bypass the payment engine. + * 2. **accountSend & Payment Engine (Overflow: Yes):** A temporary overflow + * check when `OutstandingAmount > UINT64_MAX`. This higher threshold is used + * for `accountSend` and payments processed via the payment engine. + */ +bool +isMPTOverflow( + std::int64_t sendAmount, + std::uint64_t outstandingAmount, + std::int64_t maximumAmount, + AllowMPTOverflow allowOverflow); + +/** + * Determine funds available for an issuer to sell in an issuer owned offer. + * Issuing step, which could be either MPTEndPointStep last step or BookStep's + * TakerPays may overflow OutstandingAmount. Redeeming step, in BookStep's + * TakerGets redeems the offer's owner funds, essentially balancing out + * the overflow, unless the offer's owner is the issuer. + */ +[[nodiscard]] STAmount +issuerFundsToSelfIssue(ReadView const& view, MPTIssue const& issue); + +/** Facilitate tracking of MPT sold by an issuer owning MPT sell offer. + * See ApplyView::issuerSelfDebitHookMPT(). + */ +void +issuerSelfDebitHookMPT(ApplyView& view, MPTIssue const& issue, std::uint64_t amount); + +//------------------------------------------------------------------------------ +// +// MPT DEX +// +//------------------------------------------------------------------------------ + +/* Return true if a transaction is allowed for the specified MPT/account. The + * function checks MPTokenIssuance and MPToken objects flags to determine if the + * transaction is allowed. + */ +TER +checkMPTTxAllowed(ReadView const& v, TxType tx, Asset const& asset, AccountID const& accountID); + } // namespace xrpl diff --git a/include/xrpl/tx/transactors/nft/NFTokenUtils.h b/include/xrpl/ledger/helpers/NFTokenHelpers.h similarity index 90% rename from include/xrpl/tx/transactors/nft/NFTokenUtils.h rename to include/xrpl/ledger/helpers/NFTokenHelpers.h index 33aab068c6..49fd520d51 100644 --- a/include/xrpl/tx/transactors/nft/NFTokenUtils.h +++ b/include/xrpl/ledger/helpers/NFTokenHelpers.h @@ -1,16 +1,16 @@ #pragma once +#include #include #include #include #include #include #include -#include -namespace xrpl { +#include -namespace nft { +namespace xrpl::nft { /** Delete up to a specified number of offers from the specified token offer * directory. */ @@ -20,10 +20,6 @@ removeTokenOffersWithLimit( Keylet const& directory, std::size_t maxDeletableOffers); -/** Returns tesSUCCESS if NFToken has few enough offers that it can be burned */ -TER -notTooManyOffers(ReadView const& view, uint256 const& nftokenID); - /** Finds the specified token in the owner's token directory. */ std::optional findToken(ReadView const& view, AccountID const& owner, uint256 const& nftokenID); @@ -34,8 +30,8 @@ struct TokenAndPage STObject token; std::shared_ptr page; - TokenAndPage(STObject const& token_, std::shared_ptr page_) - : token(token_), page(std::move(page_)) + TokenAndPage(STObject token_, std::shared_ptr page_) + : token(std::move(token_)), page(std::move(page_)) { } }; @@ -140,6 +136,4 @@ checkTrustlineDeepFrozen( beast::Journal const j, Issue const& issue); -} // namespace nft - -} // namespace xrpl +} // namespace xrpl::nft diff --git a/src/libxrpl/tx/transactors/payment_channel/PaymentChannelHelpers.h b/include/xrpl/ledger/helpers/PaymentChannelHelpers.h similarity index 77% rename from src/libxrpl/tx/transactors/payment_channel/PaymentChannelHelpers.h rename to include/xrpl/ledger/helpers/PaymentChannelHelpers.h index 5d1a4c0aa1..24838f1331 100644 --- a/src/libxrpl/tx/transactors/payment_channel/PaymentChannelHelpers.h +++ b/include/xrpl/ledger/helpers/PaymentChannelHelpers.h @@ -1,6 +1,8 @@ #pragma once +#include #include +#include #include namespace xrpl { diff --git a/include/xrpl/tx/transactors/dex/PermissionedDEXHelpers.h b/include/xrpl/ledger/helpers/PermissionedDEXHelpers.h similarity index 79% rename from include/xrpl/tx/transactors/dex/PermissionedDEXHelpers.h rename to include/xrpl/ledger/helpers/PermissionedDEXHelpers.h index 3992f8885e..04b12f2fc5 100644 --- a/include/xrpl/tx/transactors/dex/PermissionedDEXHelpers.h +++ b/include/xrpl/ledger/helpers/PermissionedDEXHelpers.h @@ -1,8 +1,7 @@ #pragma once #include -namespace xrpl { -namespace permissioned_dex { +namespace xrpl::permissioned_dex { // Check if an account is in a permissioned domain [[nodiscard]] bool @@ -16,6 +15,4 @@ offerInDomain( Domain const& domainID, beast::Journal j); -} // namespace permissioned_dex - -} // namespace xrpl +} // namespace xrpl::permissioned_dex diff --git a/include/xrpl/ledger/helpers/RippleStateHelpers.h b/include/xrpl/ledger/helpers/RippleStateHelpers.h index 3feba59d1f..17b0f7673e 100644 --- a/include/xrpl/ledger/helpers/RippleStateHelpers.h +++ b/include/xrpl/ledger/helpers/RippleStateHelpers.h @@ -252,4 +252,14 @@ deleteAMMTrustLine( std::optional const& ammAccountID, beast::Journal j); +/** Delete AMMs MPToken. The passed `sle` must be obtained from a prior + * call to view.peek(). + */ +[[nodiscard]] TER +deleteAMMMPToken( + ApplyView& view, + std::shared_ptr sleMPT, + AccountID const& ammAccountID, + beast::Journal j); + } // namespace xrpl diff --git a/include/xrpl/ledger/helpers/TokenHelpers.h b/include/xrpl/ledger/helpers/TokenHelpers.h index 74d1e4848e..c05308295a 100644 --- a/include/xrpl/ledger/helpers/TokenHelpers.h +++ b/include/xrpl/ledger/helpers/TokenHelpers.h @@ -31,6 +31,9 @@ enum SpendableHandling { shSIMPLE_BALANCE, shFULL_BALANCE }; enum class WaiveTransferFee : bool { No = false, Yes }; +/** Controls whether accountSend is allowed to overflow OutstandingAmount **/ +enum class AllowMPTOverflow : bool { No = false, Yes }; + /* Check if MPToken (for MPT) or trust line (for IOU) exists: * - StrongAuth - before checking if authorization is required * - WeakAuth @@ -176,6 +179,16 @@ accountFunds( FreezeHandling freezeHandling, beast::Journal j); +// Overload with AuthHandling to support IOU and MPT. +[[nodiscard]] STAmount +accountFunds( + ReadView const& view, + AccountID const& id, + STAmount const& saDefault, + FreezeHandling freezeHandling, + AuthHandling authHandling, + beast::Journal j); + /** Returns the transfer fee as Rate based on the type of token * @param view The ledger view * @param amount The amount to transfer @@ -235,11 +248,11 @@ canTransfer(ReadView const& view, Asset const& asset, AccountID const& from, Acc // --> bCheckIssuer : normally require issuer to be involved. // [[nodiscard]] // nodiscard commented out so DirectStep.cpp compiles. -/** Calls static rippleCreditIOU if saAmount represents Issue. - * Calls static rippleCreditMPT if saAmount represents MPTIssue. +/** Calls static directSendNoFeeIOU if saAmount represents Issue. + * Calls static directSendNoFeeMPT if saAmount represents MPTIssue. */ TER -rippleCredit( +directSendNoFee( ApplyView& view, AccountID const& uSenderID, AccountID const& uReceiverID, @@ -257,7 +270,8 @@ accountSend( AccountID const& to, STAmount const& saAmount, beast::Journal j, - WaiveTransferFee waiveFee = WaiveTransferFee::No); + WaiveTransferFee waiveFee = WaiveTransferFee::No, + AllowMPTOverflow allowOverflow = AllowMPTOverflow::No); using MultiplePaymentDestinations = std::vector>; /** Like accountSend, except one account is sending multiple payments (with the diff --git a/include/xrpl/net/AutoSocket.h b/include/xrpl/net/AutoSocket.h index 29cec23998..45e4919b8a 100644 --- a/include/xrpl/net/AutoSocket.h +++ b/include/xrpl/net/AutoSocket.h @@ -44,7 +44,7 @@ public: } bool - isSecure() + isSecure() const { return mSecure; } @@ -126,7 +126,9 @@ public: async_shutdown(ShutdownHandler handler) { if (isSecure()) + { mSocket->async_shutdown(handler); + } else { error_code ec; @@ -134,7 +136,7 @@ public: { lowest_layer().shutdown(plain_socket::shutdown_both); } - catch (boost::system::system_error& e) + catch (boost::system::system_error const& e) { ec = e.code(); } @@ -147,9 +149,13 @@ public: async_read_some(Seq const& buffers, Handler handler) { if (isSecure()) + { mSocket->async_read_some(buffers, handler); + } else + { PlainSocket().async_read_some(buffers, handler); + } } template @@ -157,9 +163,13 @@ public: async_read_until(Seq const& buffers, Condition condition, Handler handler) { if (isSecure()) + { boost::asio::async_read_until(*mSocket, buffers, condition, handler); + } else + { boost::asio::async_read_until(PlainSocket(), buffers, condition, handler); + } } template @@ -170,9 +180,13 @@ public: Handler handler) { if (isSecure()) + { boost::asio::async_read_until(*mSocket, buffers, delim, handler); + } else + { boost::asio::async_read_until(PlainSocket(), buffers, delim, handler); + } } template @@ -183,9 +197,13 @@ public: Handler handler) { if (isSecure()) + { boost::asio::async_read_until(*mSocket, buffers, cond, handler); + } else + { boost::asio::async_read_until(PlainSocket(), buffers, cond, handler); + } } template @@ -193,9 +211,13 @@ public: async_write(Buf const& buffers, Handler handler) { if (isSecure()) + { boost::asio::async_write(*mSocket, buffers, handler); + } else + { boost::asio::async_write(PlainSocket(), buffers, handler); + } } template @@ -203,9 +225,13 @@ public: async_write(boost::asio::basic_streambuf& buffers, Handler handler) { if (isSecure()) + { boost::asio::async_write(*mSocket, buffers, handler); + } else + { boost::asio::async_write(PlainSocket(), buffers, handler); + } } template @@ -213,9 +239,13 @@ public: async_read(Buf const& buffers, Condition cond, Handler handler) { if (isSecure()) + { boost::asio::async_read(*mSocket, buffers, cond, handler); + } else + { boost::asio::async_read(PlainSocket(), buffers, cond, handler); + } } template @@ -223,9 +253,13 @@ public: async_read(boost::asio::basic_streambuf& buffers, Condition cond, Handler handler) { if (isSecure()) + { boost::asio::async_read(*mSocket, buffers, cond, handler); + } else + { boost::asio::async_read(PlainSocket(), buffers, cond, handler); + } } template @@ -233,9 +267,13 @@ public: async_read(Buf const& buffers, Handler handler) { if (isSecure()) + { boost::asio::async_read(*mSocket, buffers, handler); + } else + { boost::asio::async_read(PlainSocket(), buffers, handler); + } } template @@ -243,9 +281,13 @@ public: async_write_some(Seq const& buffers, Handler handler) { if (isSecure()) + { mSocket->async_write_some(buffers, handler); + } else + { PlainSocket().async_write_some(buffers, handler); + } } protected: diff --git a/include/xrpl/net/HTTPClientSSLContext.h b/include/xrpl/net/HTTPClientSSLContext.h index 80e5835f5e..d211b21afe 100644 --- a/include/xrpl/net/HTTPClientSSLContext.h +++ b/include/xrpl/net/HTTPClientSSLContext.h @@ -30,8 +30,10 @@ public: registerSSLCerts(ssl_context_, ec, j_); if (ec && sslVerifyDir.empty()) + { Throw(boost::str( boost::format("Failed to set_default_verify_paths: %s") % ec.message())); + } } else { @@ -43,8 +45,10 @@ public: ssl_context_.add_verify_path(sslVerifyDir, ec); if (ec) + { Throw( boost::str(boost::format("Failed to add verify path: %s") % ec.message())); + } } } @@ -75,8 +79,8 @@ public: template < class T, class = std::enable_if_t< - std::is_same>::value || - std::is_same>::value>> + std::is_same_v> || + std::is_same_v>>> boost::system::error_code preConnectVerify(T& strm, std::string const& host) { @@ -95,8 +99,8 @@ public: template < class T, class = std::enable_if_t< - std::is_same>::value || - std::is_same>::value>> + std::is_same_v> || + std::is_same_v>>> /** * @brief invoked after connect/async_connect but before sending data * on an ssl stream - to setup name verification. diff --git a/include/xrpl/nodestore/Backend.h b/include/xrpl/nodestore/Backend.h index 36fd36ec00..d1b0ecb6dd 100644 --- a/include/xrpl/nodestore/Backend.h +++ b/include/xrpl/nodestore/Backend.h @@ -4,8 +4,7 @@ #include -namespace xrpl { -namespace NodeStore { +namespace xrpl::NodeStore { /** A backend used for the NodeStore. @@ -140,5 +139,4 @@ public: fdRequired() const = 0; }; -} // namespace NodeStore -} // namespace xrpl +} // namespace xrpl::NodeStore diff --git a/include/xrpl/nodestore/Database.h b/include/xrpl/nodestore/Database.h index e33b8a3a5c..c21c1e27dd 100644 --- a/include/xrpl/nodestore/Database.h +++ b/include/xrpl/nodestore/Database.h @@ -10,9 +10,7 @@ #include -namespace xrpl { - -namespace NodeStore { +namespace xrpl::NodeStore { /** Persistency layer for NodeObject @@ -274,5 +272,4 @@ private: threadEntry(); }; -} // namespace NodeStore -} // namespace xrpl +} // namespace xrpl::NodeStore diff --git a/include/xrpl/nodestore/DatabaseRotating.h b/include/xrpl/nodestore/DatabaseRotating.h index 23a749f972..a7deed294a 100644 --- a/include/xrpl/nodestore/DatabaseRotating.h +++ b/include/xrpl/nodestore/DatabaseRotating.h @@ -2,8 +2,7 @@ #include -namespace xrpl { -namespace NodeStore { +namespace xrpl::NodeStore { /* This class has two key-value store Backend objects for persisting SHAMap * records. This facilitates online deletion of data. New backends are @@ -36,5 +35,4 @@ public: f) = 0; }; -} // namespace NodeStore -} // namespace xrpl +} // namespace xrpl::NodeStore diff --git a/include/xrpl/nodestore/DummyScheduler.h b/include/xrpl/nodestore/DummyScheduler.h index 9fce4a6100..472684ff13 100644 --- a/include/xrpl/nodestore/DummyScheduler.h +++ b/include/xrpl/nodestore/DummyScheduler.h @@ -2,15 +2,14 @@ #include -namespace xrpl { -namespace NodeStore { +namespace xrpl::NodeStore { /** Simple NodeStore Scheduler that just performs the tasks synchronously. */ class DummyScheduler : public Scheduler { public: DummyScheduler() = default; - ~DummyScheduler() = default; + ~DummyScheduler() override = default; void scheduleTask(Task& task) override; void @@ -19,5 +18,4 @@ public: onBatchWrite(BatchWriteReport const& report) override; }; -} // namespace NodeStore -} // namespace xrpl +} // namespace xrpl::NodeStore diff --git a/include/xrpl/nodestore/Factory.h b/include/xrpl/nodestore/Factory.h index e01bdfabf2..1656e73840 100644 --- a/include/xrpl/nodestore/Factory.h +++ b/include/xrpl/nodestore/Factory.h @@ -7,9 +7,7 @@ #include -namespace xrpl { - -namespace NodeStore { +namespace xrpl::NodeStore { /** Base class for backend factories. */ class Factory @@ -59,5 +57,4 @@ public: } }; -} // namespace NodeStore -} // namespace xrpl +} // namespace xrpl::NodeStore diff --git a/include/xrpl/nodestore/Manager.h b/include/xrpl/nodestore/Manager.h index ff00ee3ee1..7f718d7835 100644 --- a/include/xrpl/nodestore/Manager.h +++ b/include/xrpl/nodestore/Manager.h @@ -3,9 +3,7 @@ #include #include -namespace xrpl { - -namespace NodeStore { +namespace xrpl::NodeStore { /** Singleton for managing NodeStore factories and back ends. */ class Manager @@ -81,5 +79,4 @@ public: beast::Journal journal) = 0; }; -} // namespace NodeStore -} // namespace xrpl +} // namespace xrpl::NodeStore diff --git a/include/xrpl/nodestore/README.md b/include/xrpl/nodestore/README.md index 4b228bfc9a..b78c99e27d 100644 --- a/include/xrpl/nodestore/README.md +++ b/include/xrpl/nodestore/README.md @@ -51,7 +51,7 @@ A blob containing the payload. Stored in the following format. --- The `NodeStore` provides an interface that stores, in a persistent database, a -collection of NodeObjects that rippled uses as its primary representation of +collection of NodeObjects that xrpld uses as its primary representation of ledger entries. All ledger entries are stored as NodeObjects and as such, need to be persisted between launches. If a NodeObject is accessed and is not in memory, it will be retrieved from the database. @@ -110,7 +110,7 @@ The `NodeStore.Timing` test is used to execute a set of read/write workloads to compare current available nodestore backends. It can be executed with: ``` -$rippled --unittest=NodeStoreTiming +$xrpld --unittest=NodeStoreTiming ``` It is also possible to use alternate DB config params by passing config strings @@ -143,10 +143,10 @@ Through various executions and profiling some conclusions are presented below. just after ledger close, then that would provide similar, but more predictable guarantees. It would also remove an unneeded thread and unnecessary memory usage. An alternative point of view is that because there will always be many - other rippled instances running there is no need for such guarantees. The nodes + other xrpld instances running there is no need for such guarantees. The nodes will always be available from another peer. -- Lookup in a block was previously using binary search. With rippled's use case +- Lookup in a block was previously using binary search. With xrpld's use case it is highly unlikely that two adjacent key/values will ever be requested one after the other. Therefore hash indexing of blocks makes much more sense. Rocksdb has a number of options for hash indexing both memtables and blocks and diff --git a/include/xrpl/nodestore/Scheduler.h b/include/xrpl/nodestore/Scheduler.h index dc3e1e3d15..bf256648aa 100644 --- a/include/xrpl/nodestore/Scheduler.h +++ b/include/xrpl/nodestore/Scheduler.h @@ -4,8 +4,7 @@ #include -namespace xrpl { -namespace NodeStore { +namespace xrpl::NodeStore { enum class FetchType { synchronous, async }; @@ -64,5 +63,4 @@ public: onBatchWrite(BatchWriteReport const& report) = 0; }; -} // namespace NodeStore -} // namespace xrpl +} // namespace xrpl::NodeStore diff --git a/include/xrpl/nodestore/Task.h b/include/xrpl/nodestore/Task.h index 4bfc88bd13..0695970a68 100644 --- a/include/xrpl/nodestore/Task.h +++ b/include/xrpl/nodestore/Task.h @@ -1,7 +1,6 @@ #pragma once -namespace xrpl { -namespace NodeStore { +namespace xrpl::NodeStore { /** Derived classes perform scheduled tasks. */ struct Task @@ -15,5 +14,4 @@ struct Task performScheduledTask() = 0; }; -} // namespace NodeStore -} // namespace xrpl +} // namespace xrpl::NodeStore diff --git a/include/xrpl/nodestore/Types.h b/include/xrpl/nodestore/Types.h index 5adbc40f70..851cbcfb8b 100644 --- a/include/xrpl/nodestore/Types.h +++ b/include/xrpl/nodestore/Types.h @@ -4,8 +4,7 @@ #include -namespace xrpl { -namespace NodeStore { +namespace xrpl::NodeStore { enum { // This is only used to pre-allocate the array for @@ -22,11 +21,11 @@ enum { /** Return codes from Backend operations. */ enum Status { - ok, - notFound, - dataCorrupt, - unknown, - backendError, + ok = 0, + notFound = 1, + dataCorrupt = 2, + unknown = 3, + backendError = 4, customCode = 100 }; @@ -34,6 +33,4 @@ enum Status { /** A batch of NodeObjects to write at once. */ using Batch = std::vector>; -} // namespace NodeStore - -} // namespace xrpl +} // namespace xrpl::NodeStore diff --git a/include/xrpl/nodestore/detail/BatchWriter.h b/include/xrpl/nodestore/detail/BatchWriter.h index 93993d1c3e..1112ccc324 100644 --- a/include/xrpl/nodestore/detail/BatchWriter.h +++ b/include/xrpl/nodestore/detail/BatchWriter.h @@ -7,8 +7,7 @@ #include #include -namespace xrpl { -namespace NodeStore { +namespace xrpl::NodeStore { /** Batch-writing assist logic. @@ -41,7 +40,7 @@ public: Anything pending in the batch is written out before this returns. */ - ~BatchWriter(); + ~BatchWriter() override; /** Store the object. @@ -76,5 +75,4 @@ private: Batch mWriteSet; }; -} // namespace NodeStore -} // namespace xrpl +} // namespace xrpl::NodeStore diff --git a/include/xrpl/nodestore/detail/DatabaseNodeImp.h b/include/xrpl/nodestore/detail/DatabaseNodeImp.h index 21f06fef8d..88fb994bee 100644 --- a/include/xrpl/nodestore/detail/DatabaseNodeImp.h +++ b/include/xrpl/nodestore/detail/DatabaseNodeImp.h @@ -4,8 +4,7 @@ #include #include -namespace xrpl { -namespace NodeStore { +namespace xrpl::NodeStore { class DatabaseNodeImp : public Database { @@ -29,7 +28,7 @@ public: "backend"); } - ~DatabaseNodeImp() + ~DatabaseNodeImp() override { stop(); } @@ -92,5 +91,4 @@ private: } }; -} // namespace NodeStore -} // namespace xrpl +} // namespace xrpl::NodeStore diff --git a/include/xrpl/nodestore/detail/DatabaseRotatingImp.h b/include/xrpl/nodestore/detail/DatabaseRotatingImp.h index 8d4cd9ddbc..7010c87ebf 100644 --- a/include/xrpl/nodestore/detail/DatabaseRotatingImp.h +++ b/include/xrpl/nodestore/detail/DatabaseRotatingImp.h @@ -4,8 +4,7 @@ #include -namespace xrpl { -namespace NodeStore { +namespace xrpl::NodeStore { class DatabaseRotatingImp : public DatabaseRotating { @@ -23,7 +22,7 @@ public: Section const& config, beast::Journal j); - ~DatabaseRotatingImp() + ~DatabaseRotatingImp() override { stop(); } @@ -69,5 +68,4 @@ private: for_each(std::function)> f) override; }; -} // namespace NodeStore -} // namespace xrpl +} // namespace xrpl::NodeStore diff --git a/include/xrpl/nodestore/detail/DecodedBlob.h b/include/xrpl/nodestore/detail/DecodedBlob.h index dc6704a08d..052c143009 100644 --- a/include/xrpl/nodestore/detail/DecodedBlob.h +++ b/include/xrpl/nodestore/detail/DecodedBlob.h @@ -2,8 +2,7 @@ #include -namespace xrpl { -namespace NodeStore { +namespace xrpl::NodeStore { /** Parsed key/value blob into NodeObject components. @@ -41,5 +40,4 @@ private: int m_dataBytes; }; -} // namespace NodeStore -} // namespace xrpl +} // namespace xrpl::NodeStore diff --git a/include/xrpl/nodestore/detail/EncodedBlob.h b/include/xrpl/nodestore/detail/EncodedBlob.h index b05583475e..343e1720a0 100644 --- a/include/xrpl/nodestore/detail/EncodedBlob.h +++ b/include/xrpl/nodestore/detail/EncodedBlob.h @@ -9,8 +9,7 @@ #include #include -namespace xrpl { -namespace NodeStore { +namespace xrpl::NodeStore { /** Convert a NodeObject from in-memory to database format. @@ -105,5 +104,4 @@ public: } }; -} // namespace NodeStore -} // namespace xrpl +} // namespace xrpl::NodeStore diff --git a/include/xrpl/nodestore/detail/ManagerImp.h b/include/xrpl/nodestore/detail/ManagerImp.h index cb10a740c1..46363f9dab 100644 --- a/include/xrpl/nodestore/detail/ManagerImp.h +++ b/include/xrpl/nodestore/detail/ManagerImp.h @@ -2,9 +2,7 @@ #include -namespace xrpl { - -namespace NodeStore { +namespace xrpl::NodeStore { class ManagerImp : public Manager { @@ -21,7 +19,7 @@ public: ManagerImp(); - ~ManagerImp() = default; + ~ManagerImp() override = default; Factory* find(std::string const& name) override; @@ -48,5 +46,4 @@ public: beast::Journal journal) override; }; -} // namespace NodeStore -} // namespace xrpl +} // namespace xrpl::NodeStore diff --git a/include/xrpl/nodestore/detail/codec.h b/include/xrpl/nodestore/detail/codec.h index 4e12c6b4db..c159558f83 100644 --- a/include/xrpl/nodestore/detail/codec.h +++ b/include/xrpl/nodestore/detail/codec.h @@ -17,8 +17,7 @@ #include #include -namespace xrpl { -namespace NodeStore { +namespace xrpl::NodeStore { template std::pair @@ -113,9 +112,11 @@ nodeobject_decompress(void const* in, std::size_t in_size, BufferFactory&& bf) { auto const hs = field::size; // Mask if (in_size < hs + 32) + { Throw( "nodeobject codec v1: short inner node size: " + std::string("in_size = ") + std::to_string(in_size) + " hs = " + std::to_string(hs)); + } istream is(p, in_size); std::uint16_t mask = 0; read(is, mask); // Mask @@ -136,10 +137,12 @@ nodeobject_decompress(void const* in, std::size_t in_size, BufferFactory&& bf) if (mask & bit) { if (in_size < 32) + { Throw( "nodeobject codec v1: short inner node subsize: " + std::string("in_size = ") + std::to_string(in_size) + " i = " + std::to_string(i)); + } std::memcpy(os.data(32), is(32), 32); in_size -= 32; } @@ -149,16 +152,20 @@ nodeobject_decompress(void const* in, std::size_t in_size, BufferFactory&& bf) } } if (in_size > 0) + { Throw( "nodeobject codec v1: long inner node, in_size = " + std::to_string(in_size)); + } break; } case 3: // full v1 inner node { - if (in_size != 16 * 32) // hashes + if (in_size != 16 * 32) + { // hashes Throw( "nodeobject codec v1: short full inner node, in_size = " + std::to_string(in_size)); + } istream is(p, in_size); result.second = 525; void* const out = bf(result.second); @@ -214,7 +221,7 @@ nodeobject_compress(void const* in, std::size_t in_size, BufferFactory&& bf) void const* const h = is(32); if (std::memcmp(h, zero32(), 32) == 0) continue; - std::memcpy(vh.data() + 32 * n, h, 32); + std::memcpy(vh.data() + (32 * n), h, 32); mask |= bit; ++n; } @@ -225,7 +232,7 @@ nodeobject_compress(void const* in, std::size_t in_size, BufferFactory&& bf) auto const type = 2U; auto const vs = size_varint(type); result.second = vs + field::size + // mask - n * 32; // hashes + (n * 32); // hashes std::uint8_t* out = reinterpret_cast(bf(result.second)); result.first = out; ostream os(out, result.second); @@ -237,7 +244,7 @@ nodeobject_compress(void const* in, std::size_t in_size, BufferFactory&& bf) // 3 = full v1 inner node auto const type = 3U; auto const vs = size_varint(type); - result.second = vs + n * 32; // hashes + result.second = vs + (n * 32); // hashes std::uint8_t* out = reinterpret_cast(bf(result.second)); result.first = out; ostream os(out, result.second); @@ -305,5 +312,4 @@ filter_inner(void* in, std::size_t in_size) } } -} // namespace NodeStore -} // namespace xrpl +} // namespace xrpl::NodeStore diff --git a/include/xrpl/nodestore/detail/varint.h b/include/xrpl/nodestore/detail/varint.h index 5ca6c27364..7c2a3756ff 100644 --- a/include/xrpl/nodestore/detail/varint.h +++ b/include/xrpl/nodestore/detail/varint.h @@ -5,8 +5,7 @@ #include #include -namespace xrpl { -namespace NodeStore { +namespace xrpl::NodeStore { // This is a variant of the base128 varint format from // google protocol buffers: @@ -18,7 +17,7 @@ struct varint; // Metafuncton to return largest // possible size of T represented as varint. // T must be unsigned -template ::value> +template > struct varint_traits; template @@ -42,8 +41,10 @@ read_varint(void const* buf, std::size_t buflen, std::size_t& t) std::uint8_t const* p = reinterpret_cast(buf); std::size_t n = 0; while (p[n] & 0x80) + { if (++n >= buflen) return 0; + } if (++n > buflen) return 0; // Special case for 0 @@ -66,7 +67,7 @@ read_varint(void const* buf, std::size_t buflen, std::size_t& t) return used; } -template ::value>* = nullptr> +template >* = nullptr> std::size_t size_varint(T v) { @@ -98,7 +99,7 @@ write_varint(void* p0, std::size_t v) // input stream -template ::value>* = nullptr> +template >* = nullptr> void read(nudb::detail::istream& is, std::size_t& u) { @@ -111,12 +112,11 @@ read(nudb::detail::istream& is, std::size_t& u) // output stream -template ::value>* = nullptr> +template >* = nullptr> void write(nudb::detail::ostream& os, std::size_t t) { write_varint(os.data(size_varint(t)), t); } -} // namespace NodeStore -} // namespace xrpl +} // namespace xrpl::NodeStore diff --git a/include/xrpl/proto/org/xrpl/rpc/v1/README.md b/include/xrpl/proto/org/xrpl/rpc/v1/README.md index e9b9b55841..e8566ec179 100644 --- a/include/xrpl/proto/org/xrpl/rpc/v1/README.md +++ b/include/xrpl/proto/org/xrpl/rpc/v1/README.md @@ -1,8 +1,8 @@ # Protocol buffer definitions for gRPC -This folder contains the protocol buffer definitions used by the rippled gRPC API. +This folder contains the protocol buffer definitions used by the xrpld gRPC API. The gRPC API attempts to mimic the JSON/Websocket API as much as possible. -As of April 2020, the gRPC API supports a subset of the full rippled API: +As of April 2020, the gRPC API supports a subset of the full xrpld API: tx, account_tx, account_info, fee and submit. ### Making Changes @@ -63,7 +63,7 @@ templated `CallData` class in GRPCServerImpl::setupListeners(). The template parameters should be the request type and the response type. Finally, define the handler itself in the appropriate file under the -src/ripple/rpc/handlers folder. If the method already has a JSON/Websocket +src/xrpld/rpc/handlers folder. If the method already has a JSON/Websocket equivalent, write the gRPC handler in the same file, and abstract common logic into helper functions (see Tx.cpp or AccountTx.cpp for an example). diff --git a/include/xrpl/proto/xrpl.proto b/include/xrpl/proto/xrpl.proto index 0af7deb35d..cd82ed24e6 100644 --- a/include/xrpl/proto/xrpl.proto +++ b/include/xrpl/proto/xrpl.proto @@ -36,7 +36,7 @@ enum MessageType { /* Provides the current ephemeral key for a validator. */ message TMManifest { - // A Manifest object in the Ripple serialization format. + // A Manifest object in the XRPL serialization format. required bytes stobject = 1; } diff --git a/include/xrpl/protocol/AMMCore.h b/include/xrpl/protocol/AMMCore.h index 1a0252d67a..7a835b44cd 100644 --- a/include/xrpl/protocol/AMMCore.h +++ b/include/xrpl/protocol/AMMCore.h @@ -2,7 +2,7 @@ #include #include -#include +#include #include #include @@ -31,12 +31,12 @@ class Rules; /** Calculate Liquidity Provider Token (LPT) Currency. */ Currency -ammLPTCurrency(Currency const& cur1, Currency const& cur2); +ammLPTCurrency(Asset const& asset1, Asset const& asset2); /** Calculate LPT Issue from AMM asset pair. */ Issue -ammLPTIssue(Currency const& cur1, Currency const& cur2, AccountID const& ammAccountID); +ammLPTIssue(Asset const& asset1, Asset const& asset2, AccountID const& ammAccountID); /** Validate the amount. * If validZero is false and amount is beast::zero then invalid amount. @@ -46,19 +46,19 @@ ammLPTIssue(Currency const& cur1, Currency const& cur2, AccountID const& ammAcco NotTEC invalidAMMAmount( STAmount const& amount, - std::optional> const& pair = std::nullopt, + std::optional> const& pair = std::nullopt, bool validZero = false); NotTEC invalidAMMAsset( - Issue const& issue, - std::optional> const& pair = std::nullopt); + Asset const& asset, + std::optional> const& pair = std::nullopt); NotTEC invalidAMMAssetPair( - Issue const& issue1, - Issue const& issue2, - std::optional> const& pair = std::nullopt); + Asset const& asset1, + Asset const& asset2, + std::optional> const& pair = std::nullopt); /** Get time slot of the auction slot. */ diff --git a/include/xrpl/protocol/AccountID.h b/include/xrpl/protocol/AccountID.h index bc57058d9e..641f2f15c1 100644 --- a/include/xrpl/protocol/AccountID.h +++ b/include/xrpl/protocol/AccountID.h @@ -2,7 +2,7 @@ #include // VFALCO Uncomment when the header issues are resolved -// #include +// #include #include #include #include diff --git a/include/xrpl/protocol/AmountConversions.h b/include/xrpl/protocol/AmountConversions.h index 7e41eff41a..9fb45e7bf8 100644 --- a/include/xrpl/protocol/AmountConversions.h +++ b/include/xrpl/protocol/AmountConversions.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -9,11 +10,12 @@ namespace xrpl { inline STAmount -toSTAmount(IOUAmount const& iou, Issue const& iss) +toSTAmount(IOUAmount const& iou, Asset const& asset) { + XRPL_ASSERT(asset.holds(), "xrpl::toSTAmount : is Issue"); bool const isNeg = iou.signum() < 0; std::uint64_t const umant = isNeg ? -iou.mantissa() : iou.mantissa(); - return STAmount(iss, umant, iou.exponent(), isNeg, STAmount::unchecked()); + return STAmount(asset, umant, iou.exponent(), isNeg, STAmount::unchecked()); } inline STAmount @@ -31,12 +33,25 @@ toSTAmount(XRPAmount const& xrp) } inline STAmount -toSTAmount(XRPAmount const& xrp, Issue const& iss) +toSTAmount(XRPAmount const& xrp, Asset const& asset) { - XRPL_ASSERT(isXRP(iss.account) && isXRP(iss.currency), "xrpl::toSTAmount : is XRP"); + XRPL_ASSERT(isXRP(asset), "xrpl::toSTAmount : is XRP"); return toSTAmount(xrp); } +inline STAmount +toSTAmount(MPTAmount const& mpt) +{ + return STAmount(mpt, noMPT()); +} + +inline STAmount +toSTAmount(MPTAmount const& mpt, Asset const& asset) +{ + XRPL_ASSERT(asset.holds(), "xrpl::toSTAmount : is MPT"); + return STAmount(mpt, asset.get()); +} + template T toAmount(STAmount const& amt) = delete; @@ -76,6 +91,21 @@ toAmount(STAmount const& amt) return XRPAmount(sMant); } +template <> +inline MPTAmount +toAmount(STAmount const& amt) +{ + XRPL_ASSERT( + amt.holds() && amt.mantissa() <= maxMPTokenAmount && amt.exponent() == 0, + "xrpl::toAmount : maximum mantissa"); + if (amt.mantissa() > maxMPTokenAmount || amt.exponent() != 0) + Throw("toAmount: invalid mantissa or exponent"); + bool const isNeg = amt.negative(); + std::int64_t const sMant = isNeg ? -std::int64_t(amt.mantissa()) : amt.mantissa(); + + return MPTAmount(sMant); +} + template T toAmount(IOUAmount const& amt) = delete; @@ -98,23 +128,42 @@ toAmount(XRPAmount const& amt) return amt; } +template +T +toAmount(MPTAmount const& amt) = delete; + +template <> +inline MPTAmount +toAmount(MPTAmount const& amt) +{ + return amt; +} + template T -toAmount(Issue const& issue, Number const& n, Number::rounding_mode mode = Number::getround()) +toAmount(Asset const& asset, Number const& n, Number::rounding_mode mode = Number::getround()) { saveNumberRoundMode const rm(Number::getround()); - if (isXRP(issue)) + if (isXRP(asset)) Number::setround(mode); if constexpr (std::is_same_v) + { return IOUAmount(n); + } else if constexpr (std::is_same_v) + { return XRPAmount(static_cast(n)); + } + else if constexpr (std::is_same_v) + { + return MPTAmount(static_cast(n)); + } else if constexpr (std::is_same_v) { - if (isXRP(issue)) - return STAmount(issue, static_cast(n)); - return STAmount(issue, n); + if (isXRP(asset)) + return STAmount(asset, static_cast(n)); + return STAmount(asset, n); } else { @@ -125,17 +174,29 @@ toAmount(Issue const& issue, Number const& n, Number::rounding_mode mode = Numbe template T -toMaxAmount(Issue const& issue) +toMaxAmount(Asset const& asset) { if constexpr (std::is_same_v) + { return IOUAmount(STAmount::cMaxValue, STAmount::cMaxOffset); + } else if constexpr (std::is_same_v) + { return XRPAmount(static_cast(STAmount::cMaxNativeN)); + } + else if constexpr (std::is_same_v) + { + return MPTAmount(maxMPTokenAmount); + } else if constexpr (std::is_same_v) { - if (isXRP(issue)) - return STAmount(issue, static_cast(STAmount::cMaxNativeN)); - return STAmount(issue, STAmount::cMaxValue, STAmount::cMaxOffset); + return asset.visit( + [](Issue const& issue) { + if (isXRP(issue)) + return STAmount(issue, static_cast(STAmount::cMaxNativeN)); + return STAmount(issue, STAmount::cMaxValue, STAmount::cMaxOffset); + }, + [](MPTIssue const& issue) { return STAmount(issue, maxMPTokenAmount); }); } else { @@ -145,21 +206,31 @@ toMaxAmount(Issue const& issue) } inline STAmount -toSTAmount(Issue const& issue, Number const& n, Number::rounding_mode mode = Number::getround()) +toSTAmount(Asset const& asset, Number const& n, Number::rounding_mode mode = Number::getround()) { - return toAmount(issue, n, mode); + return toAmount(asset, n, mode); } template -Issue -getIssue(T const& amt) +Asset +getAsset(T const& amt) { if constexpr (std::is_same_v) + { return noIssue(); + } else if constexpr (std::is_same_v) + { return xrpIssue(); + } + else if constexpr (std::is_same_v) + { + return noMPT(); + } else if constexpr (std::is_same_v) - return amt.issue(); + { + return amt.asset(); + } else { constexpr bool alwaysFalse = !std::is_same_v; @@ -172,11 +243,21 @@ constexpr T get(STAmount const& a) { if constexpr (std::is_same_v) + { return a.iou(); + } else if constexpr (std::is_same_v) + { return a.xrp(); + } + else if constexpr (std::is_same_v) + { + return a.mpt(); + } else if constexpr (std::is_same_v) + { return a; + } else { constexpr bool alwaysFalse = !std::is_same_v; diff --git a/include/xrpl/protocol/ApiVersion.h b/include/xrpl/protocol/ApiVersion.h index 8772b5a49d..653b4830bf 100644 --- a/include/xrpl/protocol/ApiVersion.h +++ b/include/xrpl/protocol/ApiVersion.h @@ -138,9 +138,11 @@ forApiVersions(Fn const& fn, Args&&... args) { constexpr auto size = maxVer + 1 - minVer; [&](std::index_sequence) { + // NOLINTBEGIN(bugprone-use-after-move) (((void)fn( std::integral_constant{}, std::forward(args)...)), ...); + // NOLINTEND(bugprone-use-after-move) }(std::make_index_sequence{}); } diff --git a/include/xrpl/protocol/Asset.h b/include/xrpl/protocol/Asset.h index d48bc94b50..b1f0338665 100644 --- a/include/xrpl/protocol/Asset.h +++ b/include/xrpl/protocol/Asset.h @@ -2,20 +2,37 @@ #include #include +#include #include #include +#include namespace xrpl { -class Asset; class STAmount; -template -concept ValidIssueType = std::is_same_v || std::is_same_v; +template + requires( + std::is_same_v || std::is_same_v || + std::is_same_v) +struct AmountType +{ + using amount_type = T; +}; -template -concept AssetType = std::is_convertible_v || std::is_convertible_v || - std::is_convertible_v || std::is_convertible_v; +/* Used to check for an asset with either badCurrency() + * or MPT with 0 account. + */ +struct BadAsset +{ +}; + +inline BadAsset const& +badAsset() +{ + static BadAsset const a; + return a; +} /* Asset is an abstraction of three different issue types: XRP, IOU, MPT. * For historical reasons, two issue types XRP and IOU are wrapped in Issue @@ -26,6 +43,9 @@ class Asset { public: using value_type = std::variant; + using token_type = std::variant; + using AmtType = + std::variant, AmountType, AmountType>; private: value_type issue_; @@ -69,36 +89,42 @@ public: constexpr value_type const& value() const; + constexpr token_type + token() const; + void setJson(Json::Value& jv) const; STAmount operator()(Number const&) const; - bool + constexpr AmtType + getAmountType() const; + + // Custom, generic visit implementation + template + constexpr auto + visit(Visitors&&... visitors) const -> decltype(auto) + { + // Simple delegation to the reusable utility, passing the internal + // variant data. + return detail::visit(issue_, std::forward(visitors)...); + } + + constexpr bool native() const { - return std::visit( - [&](TIss const& issue) { - if constexpr (std::is_same_v) - return issue.native(); - if constexpr (std::is_same_v) - return false; - }, - issue_); + return visit( + [&](Issue const& issue) { return issue.native(); }, + [&](MPTIssue const&) { return false; }); } bool integral() const { - return std::visit( - [&](TIss const& issue) { - if constexpr (std::is_same_v) - return issue.native(); - if constexpr (std::is_same_v) - return true; - }, - issue_); + return visit( + [&](Issue const& issue) { return issue.native(); }, + [&](MPTIssue const&) { return true; }); } friend constexpr bool @@ -110,6 +136,10 @@ public: friend constexpr bool operator==(Currency const& lhs, Asset const& rhs); + // rhs is either badCurrency() or MPT issuer is 0 + friend constexpr bool + operator==(BadAsset const& lhs, Asset const& rhs); + /** Return true if both assets refer to the same currency (regardless of * issuer) or MPT issuance. Otherwise return false. */ @@ -117,6 +147,12 @@ public: equalTokens(Asset const& lhs, Asset const& rhs); }; +template +constexpr bool is_issue_v = std::is_same_v; + +template +constexpr bool is_mptissue_v = std::is_same_v; + inline Json::Value to_json(Asset const& asset) { @@ -156,15 +192,42 @@ Asset::value() const return issue_; } +constexpr Asset::token_type +Asset::token() const +{ + return visit( + [&](Issue const& issue) -> Asset::token_type { return issue.currency; }, + [&](MPTIssue const& issue) -> Asset::token_type { return issue.getMptID(); }); +} + +constexpr Asset::AmtType +Asset::getAmountType() const +{ + return visit( + [&](Issue const& issue) -> Asset::AmtType { + constexpr AmountType xrp; + constexpr AmountType iou; + return native() ? AmtType(xrp) : AmtType(iou); + }, + [&](MPTIssue const& issue) -> Asset::AmtType { + constexpr AmountType mpt; + return AmtType(mpt); + }); +} + constexpr bool operator==(Asset const& lhs, Asset const& rhs) { return std::visit( [&](TLhs const& issLhs, TRhs const& issRhs) { if constexpr (std::is_same_v) + { return issLhs == issRhs; + } else + { return false; + } }, lhs.issue_, rhs.issue_); @@ -176,11 +239,17 @@ operator<=>(Asset const& lhs, Asset const& rhs) return std::visit( [](TLhs const& lhs_, TRhs const& rhs_) { if constexpr (std::is_same_v) + { return std::weak_ordering(lhs_ <=> rhs_); - else if constexpr (std::is_same_v && std::is_same_v) + } + else if constexpr (is_issue_v && is_mptissue_v) + { return std::weak_ordering::greater; + } else + { return std::weak_ordering::less; + } }, lhs.issue_, rhs.issue_); @@ -189,7 +258,17 @@ operator<=>(Asset const& lhs, Asset const& rhs) constexpr bool operator==(Currency const& lhs, Asset const& rhs) { - return rhs.holds() && rhs.get().currency == lhs; + return rhs.visit( + [&](Issue const& issue) { return issue.currency == lhs; }, + [](MPTIssue const& issue) { return false; }); +} + +constexpr bool +operator==(BadAsset const&, Asset const& rhs) +{ + return rhs.visit( + [](Issue const& issue) -> bool { return badCurrency() == issue.currency; }, + [](MPTIssue const& issue) -> bool { return issue.getIssuer() == xrpAccount(); }); } constexpr bool @@ -198,11 +277,17 @@ equalTokens(Asset const& lhs, Asset const& rhs) return std::visit( [&](TLhs const& issLhs, TRhs const& issRhs) { if constexpr (std::is_same_v && std::is_same_v) + { return issLhs.currency == issRhs.currency; + } else if constexpr (std::is_same_v && std::is_same_v) + { return issLhs.getMptID() == issRhs.getMptID(); + } else + { return false; + } }, lhs.issue_, rhs.issue_); @@ -223,4 +308,33 @@ validJSONAsset(Json::Value const& jv); Asset assetFromJson(Json::Value const& jv); +inline bool +isConsistent(Asset const& asset) +{ + return asset.visit( + [](Issue const& issue) { return isConsistent(issue); }, + [](MPTIssue const&) { return true; }); +} + +inline bool +validAsset(Asset const& asset) +{ + return asset.visit( + [](Issue const& issue) { return isConsistent(issue) && issue.currency != badCurrency(); }, + [](MPTIssue const& issue) { return issue.getIssuer() != xrpAccount(); }); +} + +template +void +hash_append(Hasher& h, Asset const& r) +{ + using beast::hash_append; + r.visit( + [&](Issue const& issue) { hash_append(h, issue); }, + [&](MPTIssue const& issue) { hash_append(h, issue); }); +} + +std::ostream& +operator<<(std::ostream& os, Asset const& x); + } // namespace xrpl diff --git a/include/xrpl/protocol/Book.h b/include/xrpl/protocol/Book.h index 83965cf701..cda4daf603 100644 --- a/include/xrpl/protocol/Book.h +++ b/include/xrpl/protocol/Book.h @@ -2,7 +2,7 @@ #include #include -#include +#include #include @@ -15,15 +15,13 @@ namespace xrpl { class Book final : public CountedObject { public: - Issue in; - Issue out; + Asset in; + Asset out; std::optional domain; - Book() - { - } + Book() = default; - Book(Issue const& in_, Issue const& out_, std::optional const& domain_) + Book(Asset const& in_, Asset const& out_, std::optional const& domain_) : in(in_), out(out_), domain(domain_) { } @@ -53,7 +51,7 @@ reversed(Book const& book); /** Equality comparison. */ /** @{ */ -[[nodiscard]] inline constexpr bool +[[nodiscard]] constexpr bool operator==(Book const& lhs, Book const& rhs) { return (lhs.in == rhs.in) && (lhs.out == rhs.out) && (lhs.domain == rhs.domain); @@ -62,7 +60,7 @@ operator==(Book const& lhs, Book const& rhs) /** Strict weak ordering. */ /** @{ */ -[[nodiscard]] inline constexpr std::weak_ordering +[[nodiscard]] constexpr std::weak_ordering operator<=>(Book const& lhs, Book const& rhs) { if (auto const c{lhs.in <=> rhs.in}; c != 0) @@ -112,16 +110,67 @@ public: } }; +template <> +struct hash : private boost::base_from_member, 0> +{ +private: + using id_hash_type = boost::base_from_member, 0>; + +public: + explicit hash() = default; + + using value_type = std::size_t; + using argument_type = xrpl::MPTIssue; + + value_type + operator()(argument_type const& value) const + { + value_type const result(id_hash_type::member(value.getMptID())); + return result; + } +}; + +template <> +struct hash +{ +private: + using value_type = std::size_t; + using argument_type = xrpl::Asset; + + using issue_hasher = std::hash; + using mptissue_hasher = std::hash; + + issue_hasher m_issue_hasher; + mptissue_hasher m_mptissue_hasher; + +public: + explicit hash() = default; + + value_type + operator()(argument_type const& asset) const + { + return asset.visit( + [&](xrpl::Issue const& issue) { + value_type const result(m_issue_hasher(issue)); + return result; + }, + [&](xrpl::MPTIssue const& issue) { + value_type const result(m_mptissue_hasher(issue)); + return result; + }); + } +}; + //------------------------------------------------------------------------------ template <> struct hash { private: - using issue_hasher = std::hash; + using asset_hasher = std::hash; using uint256_hasher = xrpl::uint256::hasher; - issue_hasher m_issue_hasher; + asset_hasher m_asset_hasher; uint256_hasher m_uint256_hasher; public: @@ -133,8 +182,8 @@ public: value_type operator()(argument_type const& value) const { - value_type result(m_issue_hasher(value.in)); - boost::hash_combine(result, m_issue_hasher(value.out)); + value_type result(m_asset_hasher(value.in)); + boost::hash_combine(result, m_asset_hasher(value.out)); if (value.domain) boost::hash_combine(result, m_uint256_hasher(*value.domain)); @@ -159,6 +208,22 @@ struct hash : std::hash // using Base::Base; // inherit ctors }; +template <> +struct hash : std::hash +{ + explicit hash() = default; + + using Base = std::hash; +}; + +template <> +struct hash : std::hash +{ + explicit hash() = default; + + using Base = std::hash; +}; + template <> struct hash : std::hash { diff --git a/include/xrpl/protocol/BuildInfo.h b/include/xrpl/protocol/BuildInfo.h index cc7633cfe1..47a27339a8 100644 --- a/include/xrpl/protocol/BuildInfo.h +++ b/include/xrpl/protocol/BuildInfo.h @@ -3,11 +3,9 @@ #include #include -namespace xrpl { - /** Versioning information for this build. */ // VFALCO The namespace is deprecated -namespace BuildInfo { +namespace xrpl::BuildInfo { /** Server version. Follows the Semantic Versioning Specification: @@ -33,7 +31,7 @@ getFullVersionString(); X: 16 bits identifying the particular implementation Y: 48 bits of data specific to the implementation - The rippled-specific format (implementation ID is: 0x18 0x3B) is: + The xrpld-specific format (implementation ID is: 0x18 0x3B) is: 00011000-00111011-MMMMMMMM-mmmmmmmm-pppppppp-TTNNNNNN-00000000-00000000 @@ -55,27 +53,25 @@ encodeSoftwareVersion(std::string_view versionStr); std::uint64_t getEncodedVersion(); -/** Check if the encoded software version is a rippled software version. +/** Check if the encoded software version is an xrpld software version. @param version another node's encoded software version - @return true if the version is a rippled software version, false otherwise + @return true if the version is an xrpld software version, false otherwise */ bool -isRippledVersion(std::uint64_t version); +isXrpldVersion(std::uint64_t version); -/** Check if the version is newer than the local node's rippled software +/** Check if the version is newer than the local node's xrpld software version. @param version another node's encoded software version - @return true if the version is newer than the local node's rippled software + @return true if the version is newer than the local node's xrpld software version, false otherwise. @note This function only understands version numbers that are generated by - rippled. Please see the encodeSoftwareVersion() function for detail. + xrpld. Please see the encodeSoftwareVersion() function for detail. */ bool isNewerVersion(std::uint64_t version); -} // namespace BuildInfo - -} // namespace xrpl +} // namespace xrpl::BuildInfo diff --git a/include/xrpl/protocol/Concepts.h b/include/xrpl/protocol/Concepts.h new file mode 100644 index 0000000000..5f2eb3c7c8 --- /dev/null +++ b/include/xrpl/protocol/Concepts.h @@ -0,0 +1,86 @@ +#pragma once + +#include + +#include + +namespace xrpl { + +class STAmount; +class Asset; +class Issue; +class MPTIssue; +class IOUAmount; +class XRPAmount; +class MPTAmount; + +template +concept StepAmount = + std::is_same_v || std::is_same_v || std::is_same_v; + +template +concept ValidIssueType = std::is_same_v || std::is_same_v; + +template +concept AssetType = std::is_convertible_v || std::is_convertible_v || + std::is_convertible_v || std::is_convertible_v; + +template +concept ValidPathAsset = (std::is_same_v || std::is_same_v); + +template +concept ValidTaker = + ((std::is_same_v || std::is_same_v || + std::is_same_v) && + (std::is_same_v || std::is_same_v || + std::is_same_v) && + (!std::is_same_v || !std::is_same_v)); + +namespace detail { + +// This template combines multiple callable objects (lambdas) into a single +// object that std::visit can use for overload resolution. +template +struct CombineVisitors : Ts... +{ + // Bring all operator() overloads from base classes into this scope. + // It's the mechanism that makes the CombineVisitors struct function + // as a single callable object with multiple overloads. + using Ts::operator()...; + + // Perfect forwarding constructor to correctly initialize the base class + // lambdas + constexpr CombineVisitors(Ts&&... ts) : Ts(std::forward(ts))... + { + } +}; + +// This function forces function template argument deduction, which is more +// robust than class template argument deduction (CTAD) via the deduction guide. +template +constexpr CombineVisitors...> +make_combine_visitors(Ts&&... ts) +{ + // std::decay_t is used to remove references/constness from the lambda + // types before they are passed as template arguments to the CombineVisitors + // struct. + return CombineVisitors...>{std::forward(ts)...}; +} + +// This function takes ANY variant and ANY number of visitors, and performs the +// visit. It is the reusable core logic. +template +constexpr auto +visit(Variant&& v, Visitors&&... visitors) -> decltype(auto) +{ + // Use the function template helper instead of raw CTAD. + auto visitor_set = make_combine_visitors(std::forward(visitors)...); + + // Delegate to std::visit, perfectly forwarding the variant and the visitor + // set. + return std::visit(visitor_set, std::forward(v)); +} + +} // namespace detail + +} // namespace xrpl diff --git a/include/xrpl/protocol/ErrorCodes.h b/include/xrpl/protocol/ErrorCodes.h index c138d5d39a..2f16415bdf 100644 --- a/include/xrpl/protocol/ErrorCodes.h +++ b/include/xrpl/protocol/ErrorCodes.h @@ -153,7 +153,7 @@ enum warning_code_i { warnRPC_AMENDMENT_BLOCKED = 1002, warnRPC_EXPIRED_VALIDATOR_LIST = 1003, // unused = 1004 - warnRPC_FIELDS_DEPRECATED = 2004, // rippled needs to maintain + warnRPC_FIELDS_DEPRECATED = 2004, // xrpld needs to maintain // compatibility with Clio on this code. }; diff --git a/include/xrpl/protocol/Feature.h b/include/xrpl/protocol/Feature.h index 0ff02fdac2..8f96935ca1 100644 --- a/include/xrpl/protocol/Feature.h +++ b/include/xrpl/protocol/Feature.h @@ -37,10 +37,10 @@ * 5) If a supported feature (`Supported::yes`) was _ever_ in a released * version, it can never be changed back to `Supported::no`, because * it _may_ still become enabled at any time. This would cause newer - * versions of `rippled` to become amendment blocked. + * versions of `xrpld` to become amendment blocked. * Instead, to prevent newer versions from voting on the feature, use * `VoteBehavior::Obsolete`. Obsolete features can not be voted for - * by any versions of `rippled` built with that setting, but will still + * by any versions of `xrpld` built with that setting, but will still * work correctly if they get enabled. If a feature remains obsolete * for long enough that _all_ clients that could vote for it are * amendment blocked, the feature can be removed from the code @@ -107,8 +107,8 @@ validFeatureName(auto fn) -> bool return true; } -enum class VoteBehavior : int { Obsolete = -1, DefaultNo = 0, DefaultYes }; -enum class AmendmentSupport : int { Retired = -1, Supported = 0, Unsupported }; +enum class VoteBehavior : int { Obsolete = -1, DefaultNo = 0, DefaultYes = 1 }; +enum class AmendmentSupport : int { Retired = -1, Supported = 0, Unsupported = 1 }; /** All amendments libxrpl knows about. */ std::map const& @@ -125,10 +125,12 @@ namespace detail { #pragma push_macro("XRPL_RETIRE_FIX") #undef XRPL_RETIRE_FIX +// NOLINTBEGIN(bugprone-macro-parentheses) #define XRPL_FEATURE(name, supported, vote) +1 #define XRPL_FIX(name, supported, vote) +1 #define XRPL_RETIRE_FEATURE(name) +1 #define XRPL_RETIRE_FIX(name) +1 +// NOLINTEND(bugprone-macro-parentheses) // This value SHOULD be equal to the number of amendments registered in // Feature.cpp. Because it's only used to reserve storage, and determine how @@ -373,8 +375,10 @@ void foreachFeature(FeatureBitset bs, F&& f) { for (size_t i = 0; i < bs.size(); ++i) + { if (bs[i]) f(bitsetIndexToFeature(i)); + } } #pragma push_macro("XRPL_FEATURE") diff --git a/include/xrpl/protocol/IOUAmount.h b/include/xrpl/protocol/IOUAmount.h index 47aa35e0e8..1744345a1b 100644 --- a/include/xrpl/protocol/IOUAmount.h +++ b/include/xrpl/protocol/IOUAmount.h @@ -151,7 +151,9 @@ operator bool() const noexcept inline int IOUAmount::signum() const noexcept { - return (mantissa_ < 0) ? -1 : (mantissa_ ? 1 : 0); + if (mantissa_ < 0) + return -1; + return (mantissa_ != 0) ? 1 : 0; } inline IOUAmount::exponent_type diff --git a/include/xrpl/protocol/Indexes.h b/include/xrpl/protocol/Indexes.h index 574bbfbde6..f4dd5e6816 100644 --- a/include/xrpl/protocol/Indexes.h +++ b/include/xrpl/protocol/Indexes.h @@ -374,14 +374,14 @@ struct keyletDesc // This list should include all of the keylet functions that take a single // AccountID parameter. std::array, 6> const directAccountKeylets{ - {{&keylet::account, jss::AccountRoot, false}, - {&keylet::ownerDir, jss::DirectoryNode, true}, - {&keylet::signers, jss::SignerList, true}, + {{.function = &keylet::account, .expectedLEName = jss::AccountRoot, .includeInTests = false}, + {.function = &keylet::ownerDir, .expectedLEName = jss::DirectoryNode, .includeInTests = true}, + {.function = &keylet::signers, .expectedLEName = jss::SignerList, .includeInTests = true}, // It's normally impossible to create an item at nftpage_min, but // test it anyway, since the invariant checks for it. - {&keylet::nftpage_min, jss::NFTokenPage, true}, - {&keylet::nftpage_max, jss::NFTokenPage, true}, - {&keylet::did, jss::DID, true}}}; + {.function = &keylet::nftpage_min, .expectedLEName = jss::NFTokenPage, .includeInTests = true}, + {.function = &keylet::nftpage_max, .expectedLEName = jss::NFTokenPage, .includeInTests = true}, + {.function = &keylet::did, .expectedLEName = jss::DID, .includeInTests = true}}}; MPTID makeMptID(std::uint32_t sequence, AccountID const& account); diff --git a/include/xrpl/protocol/Issue.h b/include/xrpl/protocol/Issue.h index 7bd01185c9..569b01725d 100644 --- a/include/xrpl/protocol/Issue.h +++ b/include/xrpl/protocol/Issue.h @@ -12,8 +12,8 @@ namespace xrpl { class Issue { public: - Currency currency{}; - AccountID account{}; + Currency currency; + AccountID account; Issue() = default; @@ -68,7 +68,7 @@ hash_append(Hasher& h, Issue const& r) /** Equality comparison. */ /** @{ */ -[[nodiscard]] inline constexpr bool +[[nodiscard]] constexpr bool operator==(Issue const& lhs, Issue const& rhs) { return (lhs.currency == rhs.currency) && (isXRP(lhs.currency) || lhs.account == rhs.account); diff --git a/include/xrpl/protocol/KnownFormats.h b/include/xrpl/protocol/KnownFormats.h index c454683e19..73bc463abe 100644 --- a/include/xrpl/protocol/KnownFormats.h +++ b/include/xrpl/protocol/KnownFormats.h @@ -38,7 +38,7 @@ public: { // Verify that KeyType is appropriate. static_assert( - std::is_enum::value || std::is_integral::value, + std::is_enum_v || std::is_integral_v, "KnownFormats KeyType must be integral or enum."); } @@ -74,10 +74,12 @@ public: Derived classes will load the object with all the known formats. */ +private: KnownFormats() : name_(beast::type_name()) { } +public: /** Destroy the known formats object. The defined formats are deleted. @@ -177,10 +179,11 @@ private: // One of the situations where a std::forward_list is useful. We want to // store each Item in a place where its address won't change. So a node- // based container is appropriate. But we don't need searchability. - std::forward_list formats_; + std::forward_list formats_{}; - boost::container::flat_map names_; - boost::container::flat_map types_; + boost::container::flat_map names_{}; + boost::container::flat_map types_{}; + friend Derived; }; } // namespace xrpl diff --git a/include/xrpl/protocol/LedgerFormats.h b/include/xrpl/protocol/LedgerFormats.h index a43f6a7134..009a96533a 100644 --- a/include/xrpl/protocol/LedgerFormats.h +++ b/include/xrpl/protocol/LedgerFormats.h @@ -187,7 +187,8 @@ enum LedgerEntryType : std::uint16_t { \ LEDGER_OBJECT(MPToken, \ LSF_FLAG2(lsfMPTLocked, 0x00000001) \ - LSF_FLAG(lsfMPTAuthorized, 0x00000002)) \ + LSF_FLAG(lsfMPTAuthorized, 0x00000002) \ + LSF_FLAG(lsfMPTAMM, 0x00000004)) \ \ LEDGER_OBJECT(Credential, \ LSF_FLAG(lsfAccepted, 0x00010000)) \ @@ -210,7 +211,7 @@ enum LedgerEntryType : std::uint16_t { // lsfRequireDestTag = 0x00020000, // ... // }; -#define TO_VALUE(name, value) name = value, +#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) }; diff --git a/include/xrpl/protocol/LedgerHeader.h b/include/xrpl/protocol/LedgerHeader.h index 6e22ad268d..1035e5e892 100644 --- a/include/xrpl/protocol/LedgerHeader.h +++ b/include/xrpl/protocol/LedgerHeader.h @@ -19,7 +19,7 @@ struct LedgerHeader // LedgerIndex seq = 0; - NetClock::time_point parentCloseTime = {}; + NetClock::time_point parentCloseTime; // // For closed ledgers @@ -49,7 +49,7 @@ struct LedgerHeader // closed. For open ledgers, the time the ledger // will close if there's no transactions. // - NetClock::time_point closeTime = {}; + NetClock::time_point closeTime; }; // ledger close flags diff --git a/include/xrpl/protocol/MPTAmount.h b/include/xrpl/protocol/MPTAmount.h index 5c1642ae5c..4a6297cc74 100644 --- a/include/xrpl/protocol/MPTAmount.h +++ b/include/xrpl/protocol/MPTAmount.h @@ -22,11 +22,12 @@ public: using value_type = std::int64_t; protected: - value_type value_; + value_type value_{}; public: MPTAmount() = default; constexpr MPTAmount(MPTAmount const& other) = default; + constexpr MPTAmount(beast::Zero); constexpr MPTAmount& operator=(MPTAmount const& other) = default; @@ -85,6 +86,11 @@ constexpr MPTAmount::MPTAmount(value_type value) : value_(value) { } +constexpr MPTAmount::MPTAmount(beast::Zero) +{ + *this = beast::zero; +} + constexpr MPTAmount& MPTAmount::operator=(beast::Zero) { @@ -103,7 +109,9 @@ operator bool() const noexcept constexpr int MPTAmount::signum() const noexcept { - return (value_ < 0) ? -1 : (value_ ? 1 : 0); + if (value_ < 0) + return -1; + return (value_ != 0) ? 1 : 0; } /** Returns the underlying value. Code SHOULD NOT call this @@ -116,6 +124,14 @@ MPTAmount::value() const return value_; } +// Output MPTAmount as just the value. +template +std::basic_ostream& +operator<<(std::basic_ostream& os, MPTAmount const& q) +{ + return os << q.value(); +} + inline std::string to_string(MPTAmount const& amount) { @@ -127,7 +143,7 @@ mulRatio(MPTAmount const& amt, std::uint32_t num, std::uint32_t den, bool roundU { using namespace boost::multiprecision; - if (!den) + if (den == 0u) Throw("division by zero"); int128_t const amt128(amt.value()); diff --git a/include/xrpl/protocol/MPTIssue.h b/include/xrpl/protocol/MPTIssue.h index d84a610418..727aef9008 100644 --- a/include/xrpl/protocol/MPTIssue.h +++ b/include/xrpl/protocol/MPTIssue.h @@ -17,7 +17,14 @@ private: public: MPTIssue() = default; - explicit MPTIssue(MPTID const& issuanceID); + MPTIssue(MPTID const& issuanceID); + + MPTIssue(std::uint32_t sequence, AccountID const& account); + + operator MPTID const&() const + { + return mptID_; + } AccountID const& getIssuer() const; @@ -40,14 +47,14 @@ public: friend constexpr std::weak_ordering operator<=>(MPTIssue const& lhs, MPTIssue const& rhs); - bool - native() const + static bool + native() { return false; } - bool - integral() const + static bool + integral() { return true; } @@ -73,6 +80,47 @@ isXRP(MPTID const&) return false; } +inline AccountID +getMPTIssuer(MPTID const& mptid) +{ + static_assert(sizeof(MPTID) == (sizeof(std::uint32_t) + sizeof(AccountID))); + // Extract the 20 bytes for the AccountID + std::array bytes{}; + std::copy_n(mptid.data() + sizeof(std::uint32_t), sizeof(AccountID), bytes.begin()); + + // bit_cast is a "magic" compiler intrinsic that is + // usually optimized away to nothing in the final assembly. + return std::bit_cast(bytes); +} + +// Disallow temporary +AccountID const& +getMPTIssuer(MPTID const&&) = delete; +AccountID const& +getMPTIssuer(MPTID&&) = delete; + +inline MPTID +noMPT() +{ + static MPTIssue const mpt{0, noAccount()}; + return mpt.getMptID(); +} + +inline MPTID +badMPT() +{ + static MPTIssue const mpt{0, xrpAccount()}; + return mpt.getMptID(); +} + +template +void +hash_append(Hasher& h, MPTIssue const& r) +{ + using beast::hash_append; + hash_append(h, r.getMptID()); +} + Json::Value to_json(MPTIssue const& mptIssue); @@ -82,4 +130,17 @@ to_string(MPTIssue const& mptIssue); MPTIssue mptIssueFromJson(Json::Value const& jv); +std::ostream& +operator<<(std::ostream& os, MPTIssue const& x); + } // namespace xrpl + +namespace std { + +template <> +struct hash : xrpl::MPTID::hasher +{ + explicit hash() = default; +}; + +} // namespace std diff --git a/include/xrpl/protocol/MultiApiJson.h b/include/xrpl/protocol/MultiApiJson.h index 1ebe72f15e..8d287767a6 100644 --- a/include/xrpl/protocol/MultiApiJson.h +++ b/include/xrpl/protocol/MultiApiJson.h @@ -74,10 +74,14 @@ struct MultiApiJson { int count = 0; for (auto& a : this->val) + { if (a.isMember(key)) count += 1; + } - return (count == 0 ? none : (count < size ? some : all)); + if (count == 0) + return none; + return count < size ? some : all; } static constexpr struct visitor_t final diff --git a/include/xrpl/protocol/NFTSyntheticSerializer.h b/include/xrpl/protocol/NFTSyntheticSerializer.h index c33a6edc7d..dcfd132ed8 100644 --- a/include/xrpl/protocol/NFTSyntheticSerializer.h +++ b/include/xrpl/protocol/NFTSyntheticSerializer.h @@ -6,9 +6,7 @@ #include -namespace xrpl { - -namespace RPC { +namespace xrpl::RPC { /** Adds common synthetic fields to transaction-related JSON responses @@ -19,5 +17,4 @@ void insertNFTSyntheticInJson(Json::Value&, std::shared_ptr const&, TxMeta const&); /** @} */ -} // namespace RPC -} // namespace xrpl +} // namespace xrpl::RPC diff --git a/include/xrpl/protocol/PathAsset.h b/include/xrpl/protocol/PathAsset.h new file mode 100644 index 0000000000..662e568bec --- /dev/null +++ b/include/xrpl/protocol/PathAsset.h @@ -0,0 +1,134 @@ +#pragma once + +#include +#include + +namespace xrpl { + +/* Represent STPathElement's asset, which can be Currency or MPTID. + */ +class PathAsset +{ +private: + std::variant easset_; + +public: + PathAsset() = default; + // Enables comparing Asset and PathAsset + PathAsset(Asset const& asset); + PathAsset(Currency const& currency) : easset_(currency) + { + } + PathAsset(MPTID const& mpt) : easset_(mpt) + { + } + + template + constexpr bool + holds() const; + + constexpr bool + isXRP() const; + + template + T const& + get() const; + + constexpr std::variant const& + value() const; + + // Custom, generic visit implementation + template + constexpr auto + visit(Visitors&&... visitors) const -> decltype(auto) + { + // Simple delegation to the reusable utility, passing the internal + // variant data. + return detail::visit(easset_, std::forward(visitors)...); + } + + friend constexpr bool + operator==(PathAsset const& lhs, PathAsset const& rhs); +}; + +template +constexpr bool is_currency_v = std::is_same_v; + +template +constexpr bool is_mptid_v = std::is_same_v; + +inline PathAsset::PathAsset(Asset const& asset) +{ + asset.visit( + [&](Issue const& issue) { easset_ = issue.currency; }, + [&](MPTIssue const& issue) { easset_ = issue.getMptID(); }); +} + +template +constexpr bool +PathAsset::holds() const +{ + return std::holds_alternative(easset_); +} + +template +T const& +PathAsset::get() const +{ + if (!holds()) + Throw("PathAsset doesn't hold requested asset."); + return std::get(easset_); +} + +constexpr std::variant const& +PathAsset::value() const +{ + return easset_; +} + +constexpr bool +PathAsset::isXRP() const +{ + return visit( + [&](Currency const& currency) { return xrpl::isXRP(currency); }, + [](MPTID const&) { return false; }); +} + +constexpr bool +operator==(PathAsset const& lhs, PathAsset const& rhs) +{ + return std::visit( + [](TLhs const& lhs_, TRhs const& rhs_) { + if constexpr (std::is_same_v) + { + return lhs_ == rhs_; + } + else + { + return false; + } + }, + lhs.value(), + rhs.value()); +} + +template +void +hash_append(Hasher& h, PathAsset const& pathAsset) +{ + std::visit([&](T const& e) { hash_append(h, e); }, pathAsset.value()); +} + +inline bool +isXRP(PathAsset const& asset) +{ + return asset.isXRP(); +} + +std::string +to_string(PathAsset const& asset); + +std::ostream& +operator<<(std::ostream& os, PathAsset const& x); + +} // namespace xrpl diff --git a/include/xrpl/protocol/Permissions.h b/include/xrpl/protocol/Permissions.h index 0ec4f04f1a..ea8bd77643 100644 --- a/include/xrpl/protocol/Permissions.h +++ b/include/xrpl/protocol/Permissions.h @@ -20,7 +20,7 @@ enum GranularPermissionType : std::uint32_t { #pragma push_macro("PERMISSION") #undef PERMISSION -#define PERMISSION(type, txType, value) type = value, +#define PERMISSION(type, txType, value) type = (value), #include diff --git a/include/xrpl/protocol/PublicKey.h b/include/xrpl/protocol/PublicKey.h index 67e55ca136..9003175f3d 100644 --- a/include/xrpl/protocol/PublicKey.h +++ b/include/xrpl/protocol/PublicKey.h @@ -21,7 +21,7 @@ namespace xrpl { Public keys are used in the public-key cryptography system used to verify signatures attached to messages. - The format of the public key is Ripple specific, + The format of the public key is XRPL specific, information needed to determine the cryptosystem parameters used is stored inside the key. @@ -69,8 +69,8 @@ public: return buf_; } - std::size_t - size() const noexcept + static std::size_t + size() noexcept { return size_; } diff --git a/include/xrpl/protocol/Quality.h b/include/xrpl/protocol/Quality.h index e9451d44ed..b0e3e65d6c 100644 --- a/include/xrpl/protocol/Quality.h +++ b/include/xrpl/protocol/Quality.h @@ -8,6 +8,7 @@ #include #include #include +#include namespace xrpl { @@ -29,7 +30,7 @@ struct TAmounts { } - TAmounts(In const& in_, Out const& out_) : in(in_), out(out_) + TAmounts(In in_, Out out_) : in(std::move(in_)), out(std::move(out_)) { } @@ -78,7 +79,7 @@ operator!=(TAmounts const& lhs, TAmounts const& rhs) noexcept //------------------------------------------------------------------------------ -// Ripple specific constant used for parsing qualities and other things +// XRPL specific constant used for parsing qualities and other things #define QUALITY_ONE 1'000'000'000 /** Represents the logical ratio of output currency to input currency. @@ -281,7 +282,7 @@ public: double const minVD = static_cast(minVMantissa); double const maxVD = - expDiff ? maxVMantissa * pow(10, expDiff) : static_cast(maxVMantissa); + (expDiff != 0) ? maxVMantissa * pow(10, expDiff) : static_cast(maxVMantissa); // maxVD and minVD are scaled so they have the same exponents. Dividing // cancels out the exponents, so we only need to deal with the (scaled) diff --git a/include/xrpl/protocol/README.md b/include/xrpl/protocol/README.md index a6e8d24982..d679c583d4 100644 --- a/include/xrpl/protocol/README.md +++ b/include/xrpl/protocol/README.md @@ -22,7 +22,7 @@ optional fields easier to read: - The operation `x[~sfFoo]` means "return the value of 'Foo' if it exists, or nothing if it doesn't." This usage of the tilde/bitwise NOT operator is not standard outside of the - `rippled` codebase. + `xrpld` codebase. - As a consequence of this, `x[~sfFoo] = y[~sfFoo]` assigns the value of Foo from y to x, including omitting Foo from x if it doesn't exist in y. @@ -33,7 +33,7 @@ or may not hold a value. For things not guaranteed to exist, you use `x[~sfFoo]` because you want such a container. It avoids having to look something up twice, once just to see if it exists and a second time to get/set its value. -([Real example](https://github.com/ripple/rippled/blob/35f4698aed5dce02f771b34cfbb690495cb5efcc/src/ripple/app/tx/impl/PayChan.cpp#L229-L236)) +([Real example](https://github.com/XRPLF/rippled/blob/35f4698aed5dce02f771b34cfbb690495cb5efcc/src/ripple/app/tx/impl/PayChan.cpp#L229-L236)) The source of this "type magic" is in [SField.h](./SField.h#L296-L302). diff --git a/include/xrpl/protocol/RPCErr.h b/include/xrpl/protocol/RPCErr.h index 34c4bf8f99..e42bf5e637 100644 --- a/include/xrpl/protocol/RPCErr.h +++ b/include/xrpl/protocol/RPCErr.h @@ -1,6 +1,7 @@ #pragma once #include +#include namespace xrpl { diff --git a/include/xrpl/protocol/SField.h b/include/xrpl/protocol/SField.h index 7a864b1b58..cbc2c12f4e 100644 --- a/include/xrpl/protocol/SField.h +++ b/include/xrpl/protocol/SField.h @@ -84,7 +84,7 @@ class STCurrency; #pragma push_macro("TO_MAP") #undef TO_MAP -#define TO_ENUM(name, value) name = value, +#define TO_ENUM(name, value) name = (value), #define TO_MAP(name, value) {#name, value}, enum SerializedTypeID { XMACRO(TO_ENUM) }; diff --git a/include/xrpl/protocol/STAccount.h b/include/xrpl/protocol/STAccount.h index 76c8f24b7b..b1f112fbb2 100644 --- a/include/xrpl/protocol/STAccount.h +++ b/include/xrpl/protocol/STAccount.h @@ -13,7 +13,7 @@ class STAccount final : public STBase, public CountedObject private: // The original implementation of STAccount kept the value in an STBlob. // But an STAccount is always 160 bits, so we can store it with less - // overhead in a xrpl::uint160. However, so the serialized format of the + // overhead in an xrpl::uint160. However, so the serialized format of the // STAccount stays unchanged, we serialize and deserialize like an STBlob. AccountID value_; bool default_; diff --git a/include/xrpl/protocol/STAmount.h b/include/xrpl/protocol/STAmount.h index df26f99b75..695bd3c0b1 100644 --- a/include/xrpl/protocol/STAmount.h +++ b/include/xrpl/protocol/STAmount.h @@ -42,13 +42,13 @@ private: public: using value_type = STAmount; - static int const cMinOffset = -96; - static int const cMaxOffset = 80; + constexpr static int cMinOffset = -96; + constexpr static int cMaxOffset = 80; // Maximum native value supported by the code constexpr static std::uint64_t cMinValue = 1'000'000'000'000'000ull; static_assert(isPowerOfTen(cMinValue)); - constexpr static std::uint64_t cMaxValue = cMinValue * 10 - 1; + constexpr static std::uint64_t cMaxValue = (cMinValue * 10) - 1; static_assert(cMaxValue == 9'999'999'999'999'999ull); constexpr static std::uint64_t cMaxNative = 9'000'000'000'000'000'000ull; @@ -164,12 +164,9 @@ public: constexpr TIss const& get() const; - Issue const& - issue() const; - - // These three are deprecated - Currency const& - getCurrency() const; + template + TIss& + get(); AccountID const& getIssuer() const; @@ -225,9 +222,6 @@ public: void clear(Asset const& asset); - void - setIssuer(AccountID const& uIssuer); - /** Set the Issue for this amount. */ void setIssue(Asset const& asset); @@ -365,9 +359,13 @@ inline STAmount::STAmount(IOUAmount const& amount, Issue const& issue) : mAsset(issue), mOffset(amount.exponent()), mIsNegative(amount < beast::zero) { if (mIsNegative) + { mValue = unsafe_cast(-amount.mantissa()); + } else + { mValue = unsafe_cast(amount.mantissa()); + } canonicalize(); } @@ -376,9 +374,13 @@ inline STAmount::STAmount(MPTAmount const& amount, MPTIssue const& mptIssue) : mAsset(mptIssue), mOffset(0), mIsNegative(amount < beast::zero) { if (mIsNegative) + { mValue = unsafe_cast(-amount.value()); + } else + { mValue = unsafe_cast(amount.value()); + } canonicalize(); } @@ -407,7 +409,7 @@ amountFromJsonNoThrow(STAmount& result, Json::Value const& jvSource); inline STAmount const& toSTAmount(STAmount const& a) { - return a; + return a; // NOLINT(bugprone-return-const-ref-from-parameter) } //------------------------------------------------------------------------------ @@ -466,16 +468,11 @@ STAmount::get() const return mAsset.get(); } -inline Issue const& -STAmount::issue() const +template +TIss& +STAmount::get() { - return get(); -} - -inline Currency const& -STAmount::getCurrency() const -{ - return mAsset.get().currency; + return mAsset.get(); } inline AccountID const& @@ -487,7 +484,9 @@ STAmount::getIssuer() const inline int STAmount::signum() const noexcept { - return mValue ? (mIsNegative ? -1 : 1) : 0; + if (mValue == 0u) + return 0; + return mIsNegative ? -1 : 1; } inline STAmount @@ -505,11 +504,13 @@ operator bool() const noexcept inline STAmount:: operator Number() const { - if (native()) - return xrp(); - if (mAsset.holds()) - return mpt(); - return iou(); + return asset().visit( + [&](Issue const& issue) -> Number { + if (issue.native()) + return xrp(); + return iou(); + }, + [&](MPTIssue const&) -> Number { return mpt(); }); } inline STAmount& @@ -568,12 +569,6 @@ STAmount::clear(Asset const& asset) clear(); } -inline void -STAmount::setIssuer(AccountID const& uIssuer) -{ - mAsset.get().account = uIssuer; -} - inline STAmount const& STAmount::value() const noexcept { @@ -739,6 +734,21 @@ canAdd(STAmount const& amt1, STAmount const& amt2); bool canSubtract(STAmount const& amt1, STAmount const& amt2); +/** Get the scale of a Number for a given asset. + * + * "scale" is similar to "exponent", but from the perspective of STAmount, which has different rules + * and mantissa ranges for determining the exponent than Number. + * + * @param number The Number to get the scale of. + * @param asset The asset to use for determining the scale. + * @return The scale of this Number for the given asset. + */ +inline int +scale(Number const& number, Asset const& asset) +{ + return STAmount{asset, number}.exponent(); +} + } // namespace xrpl //------------------------------------------------------------------------------ diff --git a/include/xrpl/protocol/STBitString.h b/include/xrpl/protocol/STBitString.h index 7bd270a98c..de038cce32 100644 --- a/include/xrpl/protocol/STBitString.h +++ b/include/xrpl/protocol/STBitString.h @@ -19,7 +19,7 @@ public: using value_type = base_uint; private: - value_type value_; + value_type value_{}; public: STBitString() = default; diff --git a/include/xrpl/protocol/STBlob.h b/include/xrpl/protocol/STBlob.h index 71d1a04478..b9dc78ffe4 100644 --- a/include/xrpl/protocol/STBlob.h +++ b/include/xrpl/protocol/STBlob.h @@ -95,7 +95,7 @@ STBlob::size() const inline std::uint8_t const* STBlob::data() const { - return reinterpret_cast(value_.data()); + return value_.data(); } inline STBlob& diff --git a/include/xrpl/protocol/STCurrency.h b/include/xrpl/protocol/STCurrency.h index dc5b079c6d..5fd4c08fbb 100644 --- a/include/xrpl/protocol/STCurrency.h +++ b/include/xrpl/protocol/STCurrency.h @@ -11,7 +11,7 @@ namespace xrpl { class STCurrency final : public STBase { private: - Currency currency_{}; + Currency currency_; public: using value_type = Currency; diff --git a/include/xrpl/protocol/STExchange.h b/include/xrpl/protocol/STExchange.h index 4b576991b7..c733df37cf 100644 --- a/include/xrpl/protocol/STExchange.h +++ b/include/xrpl/protocol/STExchange.h @@ -123,7 +123,7 @@ template void set(STObject& st, TypedField const& f, T&& t) { - st.set(STExchange::type>::set(f, std::forward(t))); + st.set(STExchange>::set(f, std::forward(t))); } /** Set a blob field using an init function. */ diff --git a/include/xrpl/protocol/STObject.h b/include/xrpl/protocol/STObject.h index 61a1cce05e..34bd19b9a0 100644 --- a/include/xrpl/protocol/STObject.h +++ b/include/xrpl/protocol/STObject.h @@ -62,7 +62,7 @@ class STObject : public STBase, public CountedObject public: using iterator = boost::transform_iterator; - virtual ~STObject() = default; + ~STObject() override = default; STObject(STObject const&) = default; template @@ -349,6 +349,8 @@ public: void setFieldH128(SField const& field, uint128 const&); void + setFieldH192(SField const& field, uint192 const&); + void setFieldH256(SField const& field, uint256 const&); void setFieldI32(SField const& field, std::int32_t); @@ -434,8 +436,7 @@ private: // by value. template < typename T, - typename V = typename std::remove_cv< - typename std::remove_reference().value())>::type>::type> + typename V = std::remove_cv_t().value())>>> V getFieldByValue(SField const& field) const; @@ -577,7 +578,7 @@ class STObject::OptionalProxy : public Proxy private: using value_type = typename T::value_type; - using optional_type = std::optional::type>; + using optional_type = std::optional>; public: OptionalProxy(OptionalProxy const&) = default; @@ -702,7 +703,7 @@ class STObject::FieldErr : public std::runtime_error template STObject::Proxy::Proxy(STObject* st, TypedField const* f) : st_(st), f_(f) { - if (st_->mType) + if (st_->mType != nullptr) { // STObject has associated template if (!st_->peekAtPField(*f_)) @@ -768,9 +769,13 @@ STObject::Proxy::assign(U&& u) } T* t = nullptr; if (style_ == soeINVALID) + { t = dynamic_cast(st_->getPField(*f_, true)); + } else + { t = dynamic_cast(st_->makeFieldPresent(*f_)); + } XRPL_ASSERT(t, "xrpl::STObject::Proxy::assign : type cast succeeded"); *t = std::forward(u); } @@ -856,9 +861,13 @@ STObject::OptionalProxy::operator=( -> OptionalProxy& { if (v) + { this->assign(std::move(*v)); + } else + { disengage(); + } return *this; } @@ -867,9 +876,13 @@ auto STObject::OptionalProxy::operator=(optional_type const& v) -> OptionalProxy& { if (v) + { this->assign(*v); + } else + { disengage(); + } return *this; } @@ -901,9 +914,13 @@ STObject::OptionalProxy::disengage() if (this->style_ == soeREQUIRED || this->style_ == soeDEFAULT) Throw("Template field error '" + this->f_->getName() + "'"); if (this->style_ == soeINVALID) + { this->st_->delField(*this->f_); + } else + { this->st_->makeFieldAbsent(*this->f_); + } } template @@ -1056,9 +1073,11 @@ STObject::at(TypedField const& f) const { auto const b = peekAtPField(f); if (!b) + { // This is a free object (no constraints) // with no template Throw("Missing field: " + f.getName()); + } if (auto const u = dynamic_cast(b)) return u->value(); @@ -1136,9 +1155,13 @@ STObject::setFieldH160(SField const& field, base_uint<160, Tag> const& v) using Bits = STBitString<160>; if (auto cf = dynamic_cast(rf)) + { cf->setValue(v); + } else + { Throw("Wrong field type"); + } } inline bool @@ -1186,7 +1209,10 @@ STObject::getFieldByConstRef(SField const& field, V const& empty) const SerializedTypeID const id = rf->getSType(); if (id == STI_NOTPRESENT) + { + // NOLINTNEXTLINE(bugprone-return-const-ref-from-parameter) return empty; // optional field not present + } T const* cf = dynamic_cast(rf); @@ -1201,7 +1227,7 @@ template void STObject::setFieldUsingSetValue(SField const& field, V value) { - static_assert(!std::is_lvalue_reference::value, ""); + static_assert(!std::is_lvalue_reference_v, ""); STBase* rf = getPField(field, true); diff --git a/include/xrpl/protocol/STPathSet.h b/include/xrpl/protocol/STPathSet.h index 2ccbc3d657..1d6fce5c18 100644 --- a/include/xrpl/protocol/STPathSet.h +++ b/include/xrpl/protocol/STPathSet.h @@ -3,6 +3,8 @@ #include #include #include +#include +#include #include #include #include @@ -16,7 +18,7 @@ class STPathElement final : public CountedObject { unsigned int mType; AccountID mAccountID; - Currency mCurrencyID; + PathAsset mAssetID; AccountID mIssuerID; bool is_offer_; @@ -28,8 +30,10 @@ public: typeAccount = 0x01, // Rippling through an account (vs taking an offer). typeCurrency = 0x10, // Currency follows. typeIssuer = 0x20, // Issuer follows. + typeMPT = 0x40, // MPT follows. typeBoundary = 0xFF, // Boundary between alternate paths. - typeAll = typeAccount | typeCurrency | typeIssuer, + typeAsset = typeCurrency | typeMPT, + typeAll = typeAccount | typeCurrency | typeIssuer | typeMPT, // Combination of all types. }; @@ -40,19 +44,19 @@ public: STPathElement( std::optional const& account, - std::optional const& currency, + std::optional const& asset, std::optional const& issuer); STPathElement( AccountID const& account, - Currency const& currency, + PathAsset const& asset, AccountID const& issuer, - bool forceCurrency = false); + bool forceAsset = false); STPathElement( unsigned int uType, AccountID const& account, - Currency const& currency, + PathAsset const& asset, AccountID const& issuer); auto @@ -70,6 +74,12 @@ public: bool hasCurrency() const; + bool + hasMPT() const; + + bool + hasAsset() const; + bool isNone() const; @@ -78,12 +88,21 @@ public: AccountID const& getAccountID() const; + PathAsset const& + getPathAsset() const; + Currency const& getCurrency() const; + MPTID const& + getMPTID() const; + AccountID const& getIssuerID() const; + bool + isType(Type const& pe) const; + bool operator==(STPathElement const& t) const; @@ -118,7 +137,7 @@ public: emplace_back(Args&&... args); bool - hasSeen(AccountID const& account, Currency const& currency, AccountID const& issuer) const; + hasSeen(AccountID const& account, PathAsset const& asset, AccountID const& issuer) const; Json::Value getJson(JsonOptions) const; @@ -221,7 +240,7 @@ inline STPathElement::STPathElement() : mType(typeNone), is_offer_(true) inline STPathElement::STPathElement( std::optional const& account, - std::optional const& currency, + std::optional const& asset, std::optional const& issuer) : mType(typeNone) { @@ -238,10 +257,10 @@ inline STPathElement::STPathElement( mAccountID != noAccount(), "xrpl::STPathElement::STPathElement : account is set"); } - if (currency) + if (asset) { - mCurrencyID = *currency; - mType |= typeCurrency; + mAssetID = *asset; + mType |= mAssetID.holds() ? typeCurrency : typeMPT; } if (issuer) @@ -256,20 +275,20 @@ inline STPathElement::STPathElement( inline STPathElement::STPathElement( AccountID const& account, - Currency const& currency, + PathAsset const& asset, AccountID const& issuer, - bool forceCurrency) + bool forceAsset) : mType(typeNone) , mAccountID(account) - , mCurrencyID(currency) + , mAssetID(asset) , mIssuerID(issuer) , is_offer_(isXRP(mAccountID)) { if (!is_offer_) mType |= typeAccount; - if (forceCurrency || !isXRP(currency)) - mType |= typeCurrency; + if (forceAsset || !isXRP(mAssetID)) + mType |= asset.holds() ? typeCurrency : typeMPT; if (!isXRP(issuer)) mType |= typeIssuer; @@ -280,14 +299,19 @@ inline STPathElement::STPathElement( inline STPathElement::STPathElement( unsigned int uType, AccountID const& account, - Currency const& currency, + PathAsset const& asset, AccountID const& issuer) : mType(uType) , mAccountID(account) - , mCurrencyID(currency) + , mAssetID(asset) , mIssuerID(issuer) , is_offer_(isXRP(mAccountID)) { + // uType could be assetType; i.e. either Currency or MPTID. + // Get the actual type. + mAssetID.visit( + [&](Currency const&) { mType = mType & (~Type::typeMPT); }, + [&](MPTID const&) { mType = mType & (~Type::typeCurrency); }); hash_value_ = get_hash(*this); } @@ -309,16 +333,34 @@ STPathElement::isAccount() const return !isOffer(); } +inline bool +STPathElement::isType(Type const& pe) const +{ + return (mType & pe) != 0u; +} + inline bool STPathElement::hasIssuer() const { - return getNodeType() & STPathElement::typeIssuer; + return isType(STPathElement::typeIssuer); } inline bool STPathElement::hasCurrency() const { - return getNodeType() & STPathElement::typeCurrency; + return isType(STPathElement::typeCurrency); +} + +inline bool +STPathElement::hasMPT() const +{ + return isType(STPathElement::typeMPT); +} + +inline bool +STPathElement::hasAsset() const +{ + return isType(STPathElement::typeAsset); } inline bool @@ -335,10 +377,22 @@ STPathElement::getAccountID() const return mAccountID; } +inline PathAsset const& +STPathElement::getPathAsset() const +{ + return mAssetID; +} + inline Currency const& STPathElement::getCurrency() const { - return mCurrencyID; + return mAssetID.get(); +} + +inline MPTID const& +STPathElement::getMPTID() const +{ + return mAssetID.get(); } inline AccountID const& @@ -351,7 +405,7 @@ inline bool STPathElement::operator==(STPathElement const& t) const { return (mType & typeAccount) == (t.mType & typeAccount) && hash_value_ == t.hash_value_ && - mAccountID == t.mAccountID && mCurrencyID == t.mCurrencyID && mIssuerID == t.mIssuerID; + mAccountID == t.mAccountID && mAssetID == t.mAssetID && mIssuerID == t.mIssuerID; } inline bool diff --git a/include/xrpl/protocol/STValidation.h b/include/xrpl/protocol/STValidation.h index 861e2152f8..c645336b59 100644 --- a/include/xrpl/protocol/STValidation.h +++ b/include/xrpl/protocol/STValidation.h @@ -36,7 +36,7 @@ class STValidation final : public STObject, public CountedObject // that use manifests this will be derived from the master public key. NodeID const nodeID_; - NetClock::time_point seenTime_ = {}; + NetClock::time_point seenTime_; public: /** Construct a STValidation from a peer from serialized data. diff --git a/include/xrpl/protocol/STVector256.h b/include/xrpl/protocol/STVector256.h index 815224def2..69b06ec1da 100644 --- a/include/xrpl/protocol/STVector256.h +++ b/include/xrpl/protocol/STVector256.h @@ -148,7 +148,7 @@ STVector256::size() const inline void STVector256::resize(std::size_t n) { - return mValue.resize(n); + mValue.resize(n); } inline bool @@ -220,7 +220,7 @@ STVector256::erase(std::vector::iterator position) inline void STVector256::clear() noexcept { - return mValue.clear(); + mValue.clear(); } } // namespace xrpl diff --git a/include/xrpl/protocol/SecretKey.h b/include/xrpl/protocol/SecretKey.h index dd5566915f..c17b3984e9 100644 --- a/include/xrpl/protocol/SecretKey.h +++ b/include/xrpl/protocol/SecretKey.h @@ -85,10 +85,10 @@ public: } }; -inline bool +bool operator==(SecretKey const& lhs, SecretKey const& rhs) = delete; -inline bool +bool operator!=(SecretKey const& lhs, SecretKey const& rhs) = delete; //------------------------------------------------------------------------------ @@ -118,7 +118,7 @@ derivePublicKey(KeyType type, SecretKey const& sk); /** Generate a key pair deterministically. - This algorithm is specific to Ripple: + This algorithm is specific to the XRPL: For secp256k1 key pairs, the seed is converted to a Generator and used to compute the key pair diff --git a/include/xrpl/protocol/Seed.h b/include/xrpl/protocol/Seed.h index 5b490be12e..04e8481c8f 100644 --- a/include/xrpl/protocol/Seed.h +++ b/include/xrpl/protocol/Seed.h @@ -80,7 +80,7 @@ randomSeed(); /** Generate a seed deterministically. - The algorithm is specific to Ripple: + The algorithm is specific to the XRPL: The seed is calculated as the first 128 bits of the SHA512-Half of the string text excluding diff --git a/include/xrpl/protocol/Serializer.h b/include/xrpl/protocol/Serializer.h index 2d3489fe7b..6ce60022e3 100644 --- a/include/xrpl/protocol/Serializer.h +++ b/include/xrpl/protocol/Serializer.h @@ -33,7 +33,7 @@ public: { mData.resize(size); - if (size) + if (size != 0u) { XRPL_ASSERT(data, "xrpl::Serializer::Serializer(void const*) : non-null input"); std::memcpy(mData.data(), data, size); diff --git a/include/xrpl/protocol/TER.h b/include/xrpl/protocol/TER.h index 64201881b0..efec25962d 100644 --- a/include/xrpl/protocol/TER.h +++ b/include/xrpl/protocol/TER.h @@ -121,6 +121,7 @@ enum TEMcodes : TERUnderlyingType { temARRAY_TOO_LARGE, temBAD_TRANSFER_FEE, temINVALID_INNER_BATCH, + temBAD_MPT, }; //------------------------------------------------------------------------------ @@ -208,6 +209,7 @@ enum TERcodes : TERUnderlyingType { terADDRESS_COLLISION, // Failed to allocate AccountID when trying to // create a pseudo-account terNO_DELEGATE_PERMISSION, // Delegate does not have permission + terLOCKED, // MPT is locked }; //------------------------------------------------------------------------------ @@ -342,10 +344,6 @@ enum TECcodes : TERUnderlyingType { tecLIMIT_EXCEEDED = 195, tecPSEUDO_ACCOUNT = 196, tecPRECISION_LOSS = 197, - // DEPRECATED: This error code tecNO_DELEGATE_PERMISSION is reserved for - // backward compatibility with historical data on non-prod networks, can be - // reclaimed after those networks reset. - tecNO_DELEGATE_PERMISSION = 198, }; //------------------------------------------------------------------------------ @@ -487,8 +485,7 @@ public: template constexpr auto operator==(L const& lhs, R const& rhs) -> std::enable_if_t< - std::is_same::value && - std::is_same::value, + std::is_same_v && std::is_same_v, bool> { return TERtoInt(lhs) == TERtoInt(rhs); @@ -497,8 +494,7 @@ operator==(L const& lhs, R const& rhs) -> std::enable_if_t< template constexpr auto operator!=(L const& lhs, R const& rhs) -> std::enable_if_t< - std::is_same::value && - std::is_same::value, + std::is_same_v && std::is_same_v, bool> { return TERtoInt(lhs) != TERtoInt(rhs); @@ -507,8 +503,7 @@ operator!=(L const& lhs, R const& rhs) -> std::enable_if_t< template constexpr auto operator<(L const& lhs, R const& rhs) -> std::enable_if_t< - std::is_same::value && - std::is_same::value, + std::is_same_v && std::is_same_v, bool> { return TERtoInt(lhs) < TERtoInt(rhs); @@ -517,8 +512,7 @@ operator<(L const& lhs, R const& rhs) -> std::enable_if_t< template constexpr auto operator<=(L const& lhs, R const& rhs) -> std::enable_if_t< - std::is_same::value && - std::is_same::value, + std::is_same_v && std::is_same_v, bool> { return TERtoInt(lhs) <= TERtoInt(rhs); @@ -527,8 +521,7 @@ operator<=(L const& lhs, R const& rhs) -> std::enable_if_t< template constexpr auto operator>(L const& lhs, R const& rhs) -> std::enable_if_t< - std::is_same::value && - std::is_same::value, + std::is_same_v && std::is_same_v, bool> { return TERtoInt(lhs) > TERtoInt(rhs); @@ -537,8 +530,7 @@ operator>(L const& lhs, R const& rhs) -> std::enable_if_t< template constexpr auto operator>=(L const& lhs, R const& rhs) -> std::enable_if_t< - std::is_same::value && - std::is_same::value, + std::is_same_v && std::is_same_v, bool> { return TERtoInt(lhs) >= TERtoInt(rhs); diff --git a/include/xrpl/protocol/TxFlags.h b/include/xrpl/protocol/TxFlags.h index 7c2085109f..7bbbd12707 100644 --- a/include/xrpl/protocol/TxFlags.h +++ b/include/xrpl/protocol/TxFlags.h @@ -235,7 +235,7 @@ XMACRO(NULL_NAME, TO_VALUE, NULL_OUTPUT, NULL_MASK_ADJ) // 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; + 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) diff --git a/include/xrpl/protocol/Units.h b/include/xrpl/protocol/Units.h index b377c80e26..b606ca2cdf 100644 --- a/include/xrpl/protocol/Units.h +++ b/include/xrpl/protocol/Units.h @@ -21,7 +21,7 @@ namespace unit { struct dropTag; /** "fee levels" are used by the transaction queue to compare the relative cost of transactions that require different levels of effort to process. - See also: src/ripple/app/misc/FeeEscalation.md#fee-level */ + See also: src/xrpld/app/misc/FeeEscalation.md#fee-level */ struct feelevelTag; /** unitless values are plain scalars wrapped in a ValueUnit. They are used for calculations in this header. */ @@ -267,7 +267,9 @@ public: constexpr int signum() const noexcept { - return (value_ < 0) ? -1 : (value_ ? 1 : 0); + if (value_ < 0) + return -1; + return value_ ? 1 : 0; } /** Returns the number of drops */ diff --git a/include/xrpl/protocol/XChainAttestations.h b/include/xrpl/protocol/XChainAttestations.h index 06eff0fcf2..44a2334ca0 100644 --- a/include/xrpl/protocol/XChainAttestations.h +++ b/include/xrpl/protocol/XChainAttestations.h @@ -15,6 +15,7 @@ #include #include +#include #include namespace xrpl { @@ -45,7 +46,7 @@ struct AttestationBase PublicKey const& publicKey_, Buffer signature_, AccountID const& sendingAccount_, - STAmount const& sendingAmount_, + STAmount sendingAmount_, AccountID const& rewardAccount_, bool wasLockingChainSend_); @@ -169,7 +170,7 @@ struct AttestationCreateAccount : AttestationBase Buffer signature_, AccountID const& sendingAccount_, STAmount const& sendingAmount_, - STAmount const& rewardAmount_, + STAmount rewardAmount_, AccountID const& rewardAccount_, bool wasLockingChainSend_, std::uint64_t createCount_, @@ -256,8 +257,8 @@ struct XChainClaimAttestation bool wasLockingChainSend; std::optional dst; MatchFields(TSignedAttestation const& att); - MatchFields(STAmount const& a, bool b, std::optional const& d) - : amount{a}, wasLockingChainSend{b}, dst{d} + MatchFields(STAmount a, bool b, std::optional const& d) + : amount{std::move(a)}, wasLockingChainSend{b}, dst{d} { } }; diff --git a/include/xrpl/protocol/XRPAmount.h b/include/xrpl/protocol/XRPAmount.h index 6e4133361b..0cb5121ef1 100644 --- a/include/xrpl/protocol/XRPAmount.h +++ b/include/xrpl/protocol/XRPAmount.h @@ -149,7 +149,9 @@ public: constexpr int signum() const noexcept { - return (drops_ < 0) ? -1 : (drops_ ? 1 : 0); + if (drops_ < 0) + return -1; + return (drops_ != 0) ? 1 : 0; } /** Returns the number of drops */ @@ -262,7 +264,7 @@ mulRatio(XRPAmount const& amt, std::uint32_t num, std::uint32_t den, bool roundU { using namespace boost::multiprecision; - if (!den) + if (den == 0u) Throw("division by zero"); int128_t const amt128(amt.drops()); diff --git a/include/xrpl/protocol/detail/STVar.h b/include/xrpl/protocol/detail/STVar.h index 1131437850..5526aed8fa 100644 --- a/include/xrpl/protocol/detail/STVar.h +++ b/include/xrpl/protocol/detail/STVar.h @@ -7,8 +7,7 @@ #include #include -namespace xrpl { -namespace detail { +namespace xrpl::detail { struct defaultObject_t { @@ -112,9 +111,13 @@ private: construct(Args&&... args) { if constexpr (sizeof(T) > max_size) + { p_ = new T(std::forward(args)...); + } else + { p_ = new (&d_) T(std::forward(args)...); + } } /** Construct requested Serializable Type according to id. @@ -154,5 +157,4 @@ operator!=(STVar const& lhs, STVar const& rhs) return !(lhs == rhs); } -} // namespace detail -} // namespace xrpl +} // namespace xrpl::detail diff --git a/include/xrpl/protocol/detail/b58_utils.h b/include/xrpl/protocol/detail/b58_utils.h index 5ad12d56e3..ec0e159230 100644 --- a/include/xrpl/protocol/detail/b58_utils.h +++ b/include/xrpl/protocol/detail/b58_utils.h @@ -17,8 +17,8 @@ template using Result = boost::outcome_v2::result; #ifndef _MSC_VER -namespace b58_fast { -namespace detail { + +namespace b58_fast::detail { // This optimizes to what hand written asm would do (single divide) [[nodiscard]] inline std::tuple @@ -33,7 +33,7 @@ carrying_mul(std::uint64_t a, std::uint64_t b, std::uint64_t carry) { unsigned __int128 const x = a; unsigned __int128 const y = b; - unsigned __int128 const c = x * y + carry; + unsigned __int128 const c = (x * y) + carry; return {c & 0xffff'ffff'ffff'ffff, c >> 64}; } @@ -64,13 +64,13 @@ inplace_bigint_add(std::span a, std::uint64_t b) for (auto& v : a.subspan(1)) { - if (!carry) + if (carry == 0u) { return TokenCodecErrc::success; } std::tie(v, carry) = carrying_add(v, 1); } - if (carry) + if (carry != 0u) { return TokenCodecErrc::overflowAdd; } @@ -105,7 +105,7 @@ inplace_bigint_mul(std::span a, std::uint64_t b) [[nodiscard]] inline std::uint64_t inplace_bigint_div_rem(std::span numerator, std::uint64_t divisor) { - if (numerator.size() == 0) + if (numerator.empty()) { // should never happen, but if it does then it seems natural to define // the a null set of numbers to be zero, so the remainder is also zero. @@ -170,8 +170,8 @@ b58_10_to_b58_be(std::uint64_t input) return result; } -} // namespace detail -} // namespace b58_fast +} // namespace b58_fast::detail + #endif } // namespace xrpl diff --git a/include/xrpl/protocol/detail/features.macro b/include/xrpl/protocol/detail/features.macro index c697629e59..494b3fa6cd 100644 --- a/include/xrpl/protocol/detail/features.macro +++ b/include/xrpl/protocol/detail/features.macro @@ -15,6 +15,7 @@ // Add new amendments to the top of this list. // Keep it sorted in reverse chronological order. +XRPL_FEATURE(MPTokensV2, Supported::no, VoteBehavior::DefaultNo) XRPL_FIX (Security3_1_3, Supported::no, VoteBehavior::DefaultNo) XRPL_FIX (PermissionedDomainInvariant, Supported::yes, VoteBehavior::DefaultNo) XRPL_FIX (ExpiredNFTokenOfferRemoval, Supported::yes, VoteBehavior::DefaultNo) diff --git a/include/xrpl/protocol/detail/ledger_entries.macro b/include/xrpl/protocol/detail/ledger_entries.macro index 216f404bec..e4182f0cba 100644 --- a/include/xrpl/protocol/detail/ledger_entries.macro +++ b/include/xrpl/protocol/detail/ledger_entries.macro @@ -161,8 +161,10 @@ LEDGER_ENTRY(ltDIR_NODE, 0x0064, DirectoryNode, directory, ({ {sfOwner, soeOPTIONAL}, // for owner directories {sfTakerPaysCurrency, soeOPTIONAL}, // order book directories {sfTakerPaysIssuer, soeOPTIONAL}, // order book directories + {sfTakerPaysMPT, soeOPTIONAL}, // order book directories {sfTakerGetsCurrency, soeOPTIONAL}, // order book directories {sfTakerGetsIssuer, soeOPTIONAL}, // order book directories + {sfTakerGetsMPT, soeOPTIONAL}, // order book directories {sfExchangeRate, soeOPTIONAL}, // order book directories {sfIndexes, soeREQUIRED}, {sfRootIndex, soeREQUIRED}, @@ -578,7 +580,7 @@ LEDGER_ENTRY(ltLOAN, 0x0089, Loan, loan, ({ // The unrounded true total value of the loan. // // - TrueTotalPrincipalOutstanding can be computed using the algorithm - // in the ripple::detail::loanPrincipalFromPeriodicPayment function. + // in the xrpl::detail::loanPrincipalFromPeriodicPayment function. // // - TrueTotalInterestOutstanding = TrueTotalLoanValue - // TrueTotalPrincipalOutstanding diff --git a/include/xrpl/protocol/detail/sfields.macro b/include/xrpl/protocol/detail/sfields.macro index a203f2fe8c..4f21207831 100644 --- a/include/xrpl/protocol/detail/sfields.macro +++ b/include/xrpl/protocol/detail/sfields.macro @@ -159,6 +159,8 @@ TYPED_SFIELD(sfTakerGetsIssuer, UINT160, 4) // 192-bit (common) TYPED_SFIELD(sfMPTokenIssuanceID, UINT192, 1) TYPED_SFIELD(sfShareMPTID, UINT192, 2) +TYPED_SFIELD(sfTakerPaysMPT, UINT192, 3) +TYPED_SFIELD(sfTakerGetsMPT, UINT192, 4) // 256-bit (common) TYPED_SFIELD(sfLedgerHash, UINT256, 1) diff --git a/include/xrpl/protocol/detail/token_errors.h b/include/xrpl/protocol/detail/token_errors.h index 54b283c41a..5511fb06b3 100644 --- a/include/xrpl/protocol/detail/token_errors.h +++ b/include/xrpl/protocol/detail/token_errors.h @@ -30,14 +30,14 @@ class TokenCodecErrcCategory : public std::error_category { public: // Return a short descriptive name for the category - virtual char const* - name() const noexcept override final + char const* + name() const noexcept final { return "TokenCodecError"; } // Return what each enum means in text - virtual std::string - message(int c) const override final + std::string + message(int c) const final { switch (static_cast(c)) { diff --git a/include/xrpl/protocol/detail/transactions.macro b/include/xrpl/protocol/detail/transactions.macro index a2f3c1be43..e52d28ce2a 100644 --- a/include/xrpl/protocol/detail/transactions.macro +++ b/include/xrpl/protocol/detail/transactions.macro @@ -27,7 +27,7 @@ TRANSACTION(ttPAYMENT, 0, Payment, Delegation::delegable, uint256{}, - createAcct, + createAcct | mayCreateMPT, ({ {sfDestination, soeREQUIRED}, {sfAmount, soeREQUIRED, soeMPTSupported}, @@ -129,10 +129,10 @@ TRANSACTION(ttREGULAR_KEY_SET, 5, SetRegularKey, TRANSACTION(ttOFFER_CREATE, 7, OfferCreate, Delegation::delegable, uint256{}, - noPriv, + mayCreateMPT, ({ - {sfTakerPays, soeREQUIRED}, - {sfTakerGets, soeREQUIRED}, + {sfTakerPays, soeREQUIRED, soeMPTSupported}, + {sfTakerGets, soeREQUIRED, soeMPTSupported}, {sfExpiration, soeOPTIONAL}, {sfOfferSequence, soeOPTIONAL}, {sfDomainID, soeOPTIONAL}, @@ -239,7 +239,7 @@ TRANSACTION(ttCHECK_CREATE, 16, CheckCreate, noPriv, ({ {sfDestination, soeREQUIRED}, - {sfSendMax, soeREQUIRED}, + {sfSendMax, soeREQUIRED, soeMPTSupported}, {sfExpiration, soeOPTIONAL}, {sfDestinationTag, soeOPTIONAL}, {sfInvoiceID, soeOPTIONAL}, @@ -252,11 +252,11 @@ TRANSACTION(ttCHECK_CREATE, 16, CheckCreate, TRANSACTION(ttCHECK_CASH, 17, CheckCash, Delegation::delegable, uint256{}, - noPriv, + mayCreateMPT, ({ {sfCheckID, soeREQUIRED}, - {sfAmount, soeOPTIONAL}, - {sfDeliverMin, soeOPTIONAL}, + {sfAmount, soeOPTIONAL, soeMPTSupported}, + {sfDeliverMin, soeOPTIONAL, soeMPTSupported}, })) /** This transaction type cancels an existing check. */ @@ -409,12 +409,12 @@ TRANSACTION(ttCLAWBACK, 30, Clawback, TRANSACTION(ttAMM_CLAWBACK, 31, AMMClawback, Delegation::delegable, featureAMMClawback, - mayDeleteAcct | overrideFreeze, + mayDeleteAcct | overrideFreeze | mayAuthorizeMPT, ({ {sfHolder, soeREQUIRED}, - {sfAsset, soeREQUIRED}, - {sfAsset2, soeREQUIRED}, - {sfAmount, soeOPTIONAL}, + {sfAsset, soeREQUIRED, soeMPTSupported}, + {sfAsset2, soeREQUIRED, soeMPTSupported}, + {sfAmount, soeOPTIONAL, soeMPTSupported}, })) /** This transaction type creates an AMM instance */ @@ -424,10 +424,10 @@ TRANSACTION(ttAMM_CLAWBACK, 31, AMMClawback, TRANSACTION(ttAMM_CREATE, 35, AMMCreate, Delegation::delegable, featureAMM, - createPseudoAcct, + createPseudoAcct | mayCreateMPT, ({ - {sfAmount, soeREQUIRED}, - {sfAmount2, soeREQUIRED}, + {sfAmount, soeREQUIRED, soeMPTSupported}, + {sfAmount2, soeREQUIRED, soeMPTSupported}, {sfTradingFee, soeREQUIRED}, })) @@ -440,10 +440,10 @@ TRANSACTION(ttAMM_DEPOSIT, 36, AMMDeposit, featureAMM, noPriv, ({ - {sfAsset, soeREQUIRED}, - {sfAsset2, soeREQUIRED}, - {sfAmount, soeOPTIONAL}, - {sfAmount2, soeOPTIONAL}, + {sfAsset, soeREQUIRED, soeMPTSupported}, + {sfAsset2, soeREQUIRED, soeMPTSupported}, + {sfAmount, soeOPTIONAL, soeMPTSupported}, + {sfAmount2, soeOPTIONAL, soeMPTSupported}, {sfEPrice, soeOPTIONAL}, {sfLPTokenOut, soeOPTIONAL}, {sfTradingFee, soeOPTIONAL}, @@ -456,12 +456,12 @@ TRANSACTION(ttAMM_DEPOSIT, 36, AMMDeposit, TRANSACTION(ttAMM_WITHDRAW, 37, AMMWithdraw, Delegation::delegable, featureAMM, - mayDeleteAcct, + mayDeleteAcct | mayAuthorizeMPT, ({ - {sfAsset, soeREQUIRED}, - {sfAsset2, soeREQUIRED}, - {sfAmount, soeOPTIONAL}, - {sfAmount2, soeOPTIONAL}, + {sfAsset, soeREQUIRED, soeMPTSupported}, + {sfAsset2, soeREQUIRED, soeMPTSupported}, + {sfAmount, soeOPTIONAL, soeMPTSupported}, + {sfAmount2, soeOPTIONAL, soeMPTSupported}, {sfEPrice, soeOPTIONAL}, {sfLPTokenIn, soeOPTIONAL}, })) @@ -475,8 +475,8 @@ TRANSACTION(ttAMM_VOTE, 38, AMMVote, featureAMM, noPriv, ({ - {sfAsset, soeREQUIRED}, - {sfAsset2, soeREQUIRED}, + {sfAsset, soeREQUIRED, soeMPTSupported}, + {sfAsset2, soeREQUIRED, soeMPTSupported}, {sfTradingFee, soeREQUIRED}, })) @@ -489,8 +489,8 @@ TRANSACTION(ttAMM_BID, 39, AMMBid, featureAMM, noPriv, ({ - {sfAsset, soeREQUIRED}, - {sfAsset2, soeREQUIRED}, + {sfAsset, soeREQUIRED, soeMPTSupported}, + {sfAsset2, soeREQUIRED, soeMPTSupported}, {sfBidMin, soeOPTIONAL}, {sfBidMax, soeOPTIONAL}, {sfAuthAccounts, soeOPTIONAL}, @@ -503,10 +503,10 @@ TRANSACTION(ttAMM_BID, 39, AMMBid, TRANSACTION(ttAMM_DELETE, 40, AMMDelete, Delegation::delegable, featureAMM, - mustDeleteAcct, + mustDeleteAcct | mayDeleteMPT, ({ - {sfAsset, soeREQUIRED}, - {sfAsset2, soeREQUIRED}, + {sfAsset, soeREQUIRED, soeMPTSupported}, + {sfAsset2, soeREQUIRED, soeMPTSupported}, })) /** This transactions creates a crosschain sequence number */ diff --git a/include/xrpl/protocol/digest.h b/include/xrpl/protocol/digest.h index 9e6606f51f..bbb38fa543 100644 --- a/include/xrpl/protocol/digest.h +++ b/include/xrpl/protocol/digest.h @@ -100,7 +100,7 @@ using sha512_hasher = openssl_sha512_hasher; /** Returns the RIPEMD-160 digest of the SHA256 hash of the message. This operation is used to compute the 160-bit identifier - representing a Ripple account, from a message. Typically the + representing an XRPL account, from a message. Typically the message is the public key of the account - which is not stored in the account root. @@ -177,12 +177,12 @@ public: } private: - inline void + void erase(std::false_type) { } - inline void + void erase(std::true_type) { secure_erase(&h_, sizeof(h_)); diff --git a/include/xrpl/protocol/json_get_or_throw.h b/include/xrpl/protocol/json_get_or_throw.h index 4406d9ff24..949fb64cf2 100644 --- a/include/xrpl/protocol/json_get_or_throw.h +++ b/include/xrpl/protocol/json_get_or_throw.h @@ -146,7 +146,7 @@ getOptional(Json::Value const& v, xrpl::SField const& field) { return getOrThrow(v, field); } - catch (...) + catch (...) // NOLINT(bugprone-empty-catch) { } return {}; diff --git a/include/xrpl/protocol/jss.h b/include/xrpl/protocol/jss.h index c12600fe61..0e94285be9 100644 --- a/include/xrpl/protocol/jss.h +++ b/include/xrpl/protocol/jss.h @@ -2,8 +2,7 @@ #include -namespace xrpl { -namespace jss { +namespace xrpl::jss { // JSON static strings @@ -401,6 +400,8 @@ JSS(min_ledger); // in: LedgerCleaner JSS(minimum_fee); // out: TxQ JSS(minimum_level); // out: TxQ JSS(missingCommand); // error +JSS(mpt_issuance_id_a); // out: BookChanges +JSS(mpt_issuance_id_b); // out: BookChanges JSS(name); // out: AmendmentTableImpl, PeerImp JSS(needed_state_hashes); // out: InboundLedger JSS(needed_transaction_hashes); // out: InboundLedger @@ -511,7 +512,7 @@ JSS(response); // websocket JSS(result); // RPC JSS(ripple_lines); // out: NetworkOPs JSS(ripple_state); // in: LedgerEntr -JSS(ripplerpc); // ripple RPC version +JSS(ripplerpc); // XRPL RPC version JSS(role); // out: Ping.cpp JSS(rpc); // JSS(rt_accounts); // in: Subscribe, Unsubscribe @@ -706,5 +707,4 @@ JSS(write_load); // out: GetCounts #undef JSS -} // namespace jss -} // namespace xrpl +} // namespace xrpl::jss diff --git a/include/xrpl/protocol/nft.h b/include/xrpl/protocol/nft.h index 821d146ff7..6dce8d1045 100644 --- a/include/xrpl/protocol/nft.h +++ b/include/xrpl/protocol/nft.h @@ -9,8 +9,7 @@ #include #include -namespace xrpl { -namespace nft { +namespace xrpl::nft { // Separate taxons from regular integers. struct TaxonTag @@ -102,5 +101,4 @@ getIssuer(uint256 const& id) return AccountID::fromVoid(id.data() + 4); } -} // namespace nft -} // namespace xrpl +} // namespace xrpl::nft diff --git a/include/xrpl/protocol/nftPageMask.h b/include/xrpl/protocol/nftPageMask.h index 2f7337c328..9889db5341 100644 --- a/include/xrpl/protocol/nftPageMask.h +++ b/include/xrpl/protocol/nftPageMask.h @@ -4,13 +4,11 @@ #include -namespace xrpl { -namespace nft { +namespace xrpl::nft { // NFT directory pages order their contents based only on the low 96 bits of // the NFToken value. This mask provides easy access to the necessary mask. uint256 constexpr pageMask( std::string_view("0000000000000000000000000000000000000000ffffffffffffffffffffffff")); -} // namespace nft -} // namespace xrpl +} // namespace xrpl::nft diff --git a/include/xrpl/protocol_autogen/README.md b/include/xrpl/protocol_autogen/README.md index be90788b05..860ed1a242 100644 --- a/include/xrpl/protocol_autogen/README.md +++ b/include/xrpl/protocol_autogen/README.md @@ -4,42 +4,33 @@ This directory contains auto-generated C++ wrapper classes for XRP Ledger protoc ## Generated Files -The files in this directory are automatically generated at **CMake configure time** from macro definition files: +The files in this directory are generated from macro definition files: -- **Transaction classes** (in `transactions/`): Generated from `include/xrpl/protocol/detail/transactions.macro` by `scripts/generate_tx_classes.py` -- **Ledger entry classes** (in `ledger_entries/`): Generated from `include/xrpl/protocol/detail/ledger_entries.macro` by `scripts/generate_ledger_classes.py` +- **Transaction classes** (in `transactions/`): Generated from `include/xrpl/protocol/detail/transactions.macro` by `cmake/scripts/codegen/generate_tx_classes.py` +- **Ledger entry classes** (in `ledger_entries/`): Generated from `include/xrpl/protocol/detail/ledger_entries.macro` by `cmake/scripts/codegen/generate_ledger_classes.py` ## Generation Process -The generation happens automatically when you **configure** the project (not during build). When you run CMake, the system: +Generation requires a one-time setup step to create a virtual environment +and install Python dependencies, followed by running the generation target: -1. Creates a Python virtual environment in the build directory (`codegen_venv`) -2. Installs Python dependencies from `scripts/requirements.txt` into the venv (only if needed) -3. Runs the Python generation scripts using the venv Python interpreter -4. Parses the macro files to extract type definitions -5. Generates type-safe C++ wrapper classes using Mako templates -6. Places the generated headers in this directory +```bash +cmake --build . --target setup_code_gen # create venv and install dependencies (once) +cmake --build . --target code_gen # generate code +``` -### When Regeneration Happens - -The code is regenerated when: - -- You run CMake configure for the first time -- The Python virtual environment doesn't exist -- `scripts/requirements.txt` has been modified - -To force regeneration, delete the build directory and reconfigure. +By default, `CODEGEN_VENV_DIR` points to `.venv` in the project root. The +`setup_code_gen` target creates a venv there and installs the required packages. +The `code_gen` target then uses the venv's Python interpreter to run generation. ### Python Dependencies -The code generation requires the following Python packages (automatically installed): +The code generation requires the following Python packages (installed by `setup_code_gen`): - `pcpp` - C preprocessor for Python - `pyparsing` - Parser combinator library - `Mako` - Template engine -These are isolated in a virtual environment and won't affect your system Python installation. - ## Version Control The generated `.h` files **are checked into version control**. This means: @@ -50,15 +41,15 @@ The generated `.h` files **are checked into version control**. This means: ## Modifying Generated Code -**Do not manually edit generated files.** Any changes will be overwritten the next time CMake configure runs. +**Do not manually edit generated files.** Any changes will be overwritten the next time `code_gen` is run. To modify the generated classes: - Edit the macro files in `include/xrpl/protocol/detail/` -- Edit the Mako templates in `scripts/templates/` -- Edit the generation scripts in `scripts/` -- Update Python dependencies in `scripts/requirements.txt` -- Run CMake configure to regenerate +- Edit the Mako templates in `cmake/scripts/codegen/templates/` +- Edit the generation scripts in `cmake/scripts/codegen/` +- Update Python dependencies in `cmake/scripts/codegen/requirements.txt` +- Run `cmake --build . --target code_gen` to regenerate ## Adding Common Fields @@ -73,7 +64,7 @@ Base classes: Templates (update to pass required common fields to base class constructors): -- `scripts/templates/Transaction.h.mako` -- `scripts/templates/LedgerEntry.h.mako` +- `cmake/scripts/codegen/templates/Transaction.h.mako` +- `cmake/scripts/codegen/templates/LedgerEntry.h.mako` These files are **not auto-generated** and must be updated by hand. diff --git a/include/xrpl/protocol_autogen/ledger_entries/DirectoryNode.h b/include/xrpl/protocol_autogen/ledger_entries/DirectoryNode.h index 545542ec6c..4c06ac5bbb 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/DirectoryNode.h +++ b/include/xrpl/protocol_autogen/ledger_entries/DirectoryNode.h @@ -117,6 +117,30 @@ public: return this->sle_->isFieldPresent(sfTakerPaysIssuer); } + /** + * @brief Get sfTakerPaysMPT (soeOPTIONAL) + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + protocol_autogen::Optional + getTakerPaysMPT() const + { + if (hasTakerPaysMPT()) + return this->sle_->at(sfTakerPaysMPT); + return std::nullopt; + } + + /** + * @brief Check if sfTakerPaysMPT is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasTakerPaysMPT() const + { + return this->sle_->isFieldPresent(sfTakerPaysMPT); + } + /** * @brief Get sfTakerGetsCurrency (soeOPTIONAL) * @return The field value, or std::nullopt if not present. @@ -165,6 +189,30 @@ public: return this->sle_->isFieldPresent(sfTakerGetsIssuer); } + /** + * @brief Get sfTakerGetsMPT (soeOPTIONAL) + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + protocol_autogen::Optional + getTakerGetsMPT() const + { + if (hasTakerGetsMPT()) + return this->sle_->at(sfTakerGetsMPT); + return std::nullopt; + } + + /** + * @brief Check if sfTakerGetsMPT is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasTakerGetsMPT() const + { + return this->sle_->isFieldPresent(sfTakerGetsMPT); + } + /** * @brief Get sfExchangeRate (soeOPTIONAL) * @return The field value, or std::nullopt if not present. @@ -427,6 +475,17 @@ public: return *this; } + /** + * @brief Set sfTakerPaysMPT (soeOPTIONAL) + * @return Reference to this builder for method chaining. + */ + DirectoryNodeBuilder& + setTakerPaysMPT(std::decay_t const& value) + { + object_[sfTakerPaysMPT] = value; + return *this; + } + /** * @brief Set sfTakerGetsCurrency (soeOPTIONAL) * @return Reference to this builder for method chaining. @@ -449,6 +508,17 @@ public: return *this; } + /** + * @brief Set sfTakerGetsMPT (soeOPTIONAL) + * @return Reference to this builder for method chaining. + */ + DirectoryNodeBuilder& + setTakerGetsMPT(std::decay_t const& value) + { + object_[sfTakerGetsMPT] = value; + return *this; + } + /** * @brief Set sfExchangeRate (soeOPTIONAL) * @return Reference to this builder for method chaining. diff --git a/include/xrpl/protocol_autogen/transactions/AMMBid.h b/include/xrpl/protocol_autogen/transactions/AMMBid.h index d3f8cba8f7..4e5c5d1d15 100644 --- a/include/xrpl/protocol_autogen/transactions/AMMBid.h +++ b/include/xrpl/protocol_autogen/transactions/AMMBid.h @@ -49,6 +49,7 @@ public: /** * @brief Get sfAsset (soeREQUIRED) + * @note This field supports MPT (Multi-Purpose Token) amounts. * @return The field value. */ [[nodiscard]] @@ -60,6 +61,7 @@ public: /** * @brief Get sfAsset2 (soeREQUIRED) + * @note This field supports MPT (Multi-Purpose Token) amounts. * @return The field value. */ [[nodiscard]] @@ -192,6 +194,7 @@ public: /** * @brief Set sfAsset (soeREQUIRED) + * @note This field supports MPT (Multi-Purpose Token) amounts. * @return Reference to this builder for method chaining. */ AMMBidBuilder& @@ -203,6 +206,7 @@ public: /** * @brief Set sfAsset2 (soeREQUIRED) + * @note This field supports MPT (Multi-Purpose Token) amounts. * @return Reference to this builder for method chaining. */ AMMBidBuilder& diff --git a/include/xrpl/protocol_autogen/transactions/AMMClawback.h b/include/xrpl/protocol_autogen/transactions/AMMClawback.h index 9603cad6ef..3a968f34d2 100644 --- a/include/xrpl/protocol_autogen/transactions/AMMClawback.h +++ b/include/xrpl/protocol_autogen/transactions/AMMClawback.h @@ -21,7 +21,7 @@ class AMMClawbackBuilder; * Type: ttAMM_CLAWBACK (31) * Delegable: Delegation::delegable * Amendment: featureAMMClawback - * Privileges: mayDeleteAcct | overrideFreeze + * Privileges: mayDeleteAcct | overrideFreeze | mayAuthorizeMPT * * Immutable wrapper around STTx providing type-safe field access. * Use AMMClawbackBuilder to construct new transactions. @@ -60,6 +60,7 @@ public: /** * @brief Get sfAsset (soeREQUIRED) + * @note This field supports MPT (Multi-Purpose Token) amounts. * @return The field value. */ [[nodiscard]] @@ -71,6 +72,7 @@ public: /** * @brief Get sfAsset2 (soeREQUIRED) + * @note This field supports MPT (Multi-Purpose Token) amounts. * @return The field value. */ [[nodiscard]] @@ -82,6 +84,7 @@ public: /** * @brief Get sfAmount (soeOPTIONAL) + * @note This field supports MPT (Multi-Purpose Token) amounts. * @return The field value, or std::nullopt if not present. */ [[nodiscard]] @@ -166,6 +169,7 @@ public: /** * @brief Set sfAsset (soeREQUIRED) + * @note This field supports MPT (Multi-Purpose Token) amounts. * @return Reference to this builder for method chaining. */ AMMClawbackBuilder& @@ -177,6 +181,7 @@ public: /** * @brief Set sfAsset2 (soeREQUIRED) + * @note This field supports MPT (Multi-Purpose Token) amounts. * @return Reference to this builder for method chaining. */ AMMClawbackBuilder& @@ -188,6 +193,7 @@ public: /** * @brief Set sfAmount (soeOPTIONAL) + * @note This field supports MPT (Multi-Purpose Token) amounts. * @return Reference to this builder for method chaining. */ AMMClawbackBuilder& diff --git a/include/xrpl/protocol_autogen/transactions/AMMCreate.h b/include/xrpl/protocol_autogen/transactions/AMMCreate.h index 311d19a3c4..ad6a253ffc 100644 --- a/include/xrpl/protocol_autogen/transactions/AMMCreate.h +++ b/include/xrpl/protocol_autogen/transactions/AMMCreate.h @@ -21,7 +21,7 @@ class AMMCreateBuilder; * Type: ttAMM_CREATE (35) * Delegable: Delegation::delegable * Amendment: featureAMM - * Privileges: createPseudoAcct + * Privileges: createPseudoAcct | mayCreateMPT * * Immutable wrapper around STTx providing type-safe field access. * Use AMMCreateBuilder to construct new transactions. @@ -49,6 +49,7 @@ public: /** * @brief Get sfAmount (soeREQUIRED) + * @note This field supports MPT (Multi-Purpose Token) amounts. * @return The field value. */ [[nodiscard]] @@ -60,6 +61,7 @@ public: /** * @brief Get sfAmount2 (soeREQUIRED) + * @note This field supports MPT (Multi-Purpose Token) amounts. * @return The field value. */ [[nodiscard]] @@ -129,6 +131,7 @@ public: /** * @brief Set sfAmount (soeREQUIRED) + * @note This field supports MPT (Multi-Purpose Token) amounts. * @return Reference to this builder for method chaining. */ AMMCreateBuilder& @@ -140,6 +143,7 @@ public: /** * @brief Set sfAmount2 (soeREQUIRED) + * @note This field supports MPT (Multi-Purpose Token) amounts. * @return Reference to this builder for method chaining. */ AMMCreateBuilder& diff --git a/include/xrpl/protocol_autogen/transactions/AMMDelete.h b/include/xrpl/protocol_autogen/transactions/AMMDelete.h index bc61434d0b..3325ee63b3 100644 --- a/include/xrpl/protocol_autogen/transactions/AMMDelete.h +++ b/include/xrpl/protocol_autogen/transactions/AMMDelete.h @@ -21,7 +21,7 @@ class AMMDeleteBuilder; * Type: ttAMM_DELETE (40) * Delegable: Delegation::delegable * Amendment: featureAMM - * Privileges: mustDeleteAcct + * Privileges: mustDeleteAcct | mayDeleteMPT * * Immutable wrapper around STTx providing type-safe field access. * Use AMMDeleteBuilder to construct new transactions. @@ -49,6 +49,7 @@ public: /** * @brief Get sfAsset (soeREQUIRED) + * @note This field supports MPT (Multi-Purpose Token) amounts. * @return The field value. */ [[nodiscard]] @@ -60,6 +61,7 @@ public: /** * @brief Get sfAsset2 (soeREQUIRED) + * @note This field supports MPT (Multi-Purpose Token) amounts. * @return The field value. */ [[nodiscard]] @@ -116,6 +118,7 @@ public: /** * @brief Set sfAsset (soeREQUIRED) + * @note This field supports MPT (Multi-Purpose Token) amounts. * @return Reference to this builder for method chaining. */ AMMDeleteBuilder& @@ -127,6 +130,7 @@ public: /** * @brief Set sfAsset2 (soeREQUIRED) + * @note This field supports MPT (Multi-Purpose Token) amounts. * @return Reference to this builder for method chaining. */ AMMDeleteBuilder& diff --git a/include/xrpl/protocol_autogen/transactions/AMMDeposit.h b/include/xrpl/protocol_autogen/transactions/AMMDeposit.h index 8e86339b0a..bb9a36d218 100644 --- a/include/xrpl/protocol_autogen/transactions/AMMDeposit.h +++ b/include/xrpl/protocol_autogen/transactions/AMMDeposit.h @@ -49,6 +49,7 @@ public: /** * @brief Get sfAsset (soeREQUIRED) + * @note This field supports MPT (Multi-Purpose Token) amounts. * @return The field value. */ [[nodiscard]] @@ -60,6 +61,7 @@ public: /** * @brief Get sfAsset2 (soeREQUIRED) + * @note This field supports MPT (Multi-Purpose Token) amounts. * @return The field value. */ [[nodiscard]] @@ -71,6 +73,7 @@ public: /** * @brief Get sfAmount (soeOPTIONAL) + * @note This field supports MPT (Multi-Purpose Token) amounts. * @return The field value, or std::nullopt if not present. */ [[nodiscard]] @@ -97,6 +100,7 @@ public: /** * @brief Get sfAmount2 (soeOPTIONAL) + * @note This field supports MPT (Multi-Purpose Token) amounts. * @return The field value, or std::nullopt if not present. */ [[nodiscard]] @@ -246,6 +250,7 @@ public: /** * @brief Set sfAsset (soeREQUIRED) + * @note This field supports MPT (Multi-Purpose Token) amounts. * @return Reference to this builder for method chaining. */ AMMDepositBuilder& @@ -257,6 +262,7 @@ public: /** * @brief Set sfAsset2 (soeREQUIRED) + * @note This field supports MPT (Multi-Purpose Token) amounts. * @return Reference to this builder for method chaining. */ AMMDepositBuilder& @@ -268,6 +274,7 @@ public: /** * @brief Set sfAmount (soeOPTIONAL) + * @note This field supports MPT (Multi-Purpose Token) amounts. * @return Reference to this builder for method chaining. */ AMMDepositBuilder& @@ -279,6 +286,7 @@ public: /** * @brief Set sfAmount2 (soeOPTIONAL) + * @note This field supports MPT (Multi-Purpose Token) amounts. * @return Reference to this builder for method chaining. */ AMMDepositBuilder& diff --git a/include/xrpl/protocol_autogen/transactions/AMMVote.h b/include/xrpl/protocol_autogen/transactions/AMMVote.h index a4e58c9aa4..3baa3f6d41 100644 --- a/include/xrpl/protocol_autogen/transactions/AMMVote.h +++ b/include/xrpl/protocol_autogen/transactions/AMMVote.h @@ -49,6 +49,7 @@ public: /** * @brief Get sfAsset (soeREQUIRED) + * @note This field supports MPT (Multi-Purpose Token) amounts. * @return The field value. */ [[nodiscard]] @@ -60,6 +61,7 @@ public: /** * @brief Get sfAsset2 (soeREQUIRED) + * @note This field supports MPT (Multi-Purpose Token) amounts. * @return The field value. */ [[nodiscard]] @@ -129,6 +131,7 @@ public: /** * @brief Set sfAsset (soeREQUIRED) + * @note This field supports MPT (Multi-Purpose Token) amounts. * @return Reference to this builder for method chaining. */ AMMVoteBuilder& @@ -140,6 +143,7 @@ public: /** * @brief Set sfAsset2 (soeREQUIRED) + * @note This field supports MPT (Multi-Purpose Token) amounts. * @return Reference to this builder for method chaining. */ AMMVoteBuilder& diff --git a/include/xrpl/protocol_autogen/transactions/AMMWithdraw.h b/include/xrpl/protocol_autogen/transactions/AMMWithdraw.h index da19c546ee..99f82b69c8 100644 --- a/include/xrpl/protocol_autogen/transactions/AMMWithdraw.h +++ b/include/xrpl/protocol_autogen/transactions/AMMWithdraw.h @@ -21,7 +21,7 @@ class AMMWithdrawBuilder; * Type: ttAMM_WITHDRAW (37) * Delegable: Delegation::delegable * Amendment: featureAMM - * Privileges: mayDeleteAcct + * Privileges: mayDeleteAcct | mayAuthorizeMPT * * Immutable wrapper around STTx providing type-safe field access. * Use AMMWithdrawBuilder to construct new transactions. @@ -49,6 +49,7 @@ public: /** * @brief Get sfAsset (soeREQUIRED) + * @note This field supports MPT (Multi-Purpose Token) amounts. * @return The field value. */ [[nodiscard]] @@ -60,6 +61,7 @@ public: /** * @brief Get sfAsset2 (soeREQUIRED) + * @note This field supports MPT (Multi-Purpose Token) amounts. * @return The field value. */ [[nodiscard]] @@ -71,6 +73,7 @@ public: /** * @brief Get sfAmount (soeOPTIONAL) + * @note This field supports MPT (Multi-Purpose Token) amounts. * @return The field value, or std::nullopt if not present. */ [[nodiscard]] @@ -97,6 +100,7 @@ public: /** * @brief Get sfAmount2 (soeOPTIONAL) + * @note This field supports MPT (Multi-Purpose Token) amounts. * @return The field value, or std::nullopt if not present. */ [[nodiscard]] @@ -220,6 +224,7 @@ public: /** * @brief Set sfAsset (soeREQUIRED) + * @note This field supports MPT (Multi-Purpose Token) amounts. * @return Reference to this builder for method chaining. */ AMMWithdrawBuilder& @@ -231,6 +236,7 @@ public: /** * @brief Set sfAsset2 (soeREQUIRED) + * @note This field supports MPT (Multi-Purpose Token) amounts. * @return Reference to this builder for method chaining. */ AMMWithdrawBuilder& @@ -242,6 +248,7 @@ public: /** * @brief Set sfAmount (soeOPTIONAL) + * @note This field supports MPT (Multi-Purpose Token) amounts. * @return Reference to this builder for method chaining. */ AMMWithdrawBuilder& @@ -253,6 +260,7 @@ public: /** * @brief Set sfAmount2 (soeOPTIONAL) + * @note This field supports MPT (Multi-Purpose Token) amounts. * @return Reference to this builder for method chaining. */ AMMWithdrawBuilder& diff --git a/include/xrpl/protocol_autogen/transactions/CheckCash.h b/include/xrpl/protocol_autogen/transactions/CheckCash.h index 2a2bc9c7af..34dc91c61b 100644 --- a/include/xrpl/protocol_autogen/transactions/CheckCash.h +++ b/include/xrpl/protocol_autogen/transactions/CheckCash.h @@ -21,7 +21,7 @@ class CheckCashBuilder; * Type: ttCHECK_CASH (17) * Delegable: Delegation::delegable * Amendment: uint256{} - * Privileges: noPriv + * Privileges: mayCreateMPT * * Immutable wrapper around STTx providing type-safe field access. * Use CheckCashBuilder to construct new transactions. @@ -60,6 +60,7 @@ public: /** * @brief Get sfAmount (soeOPTIONAL) + * @note This field supports MPT (Multi-Purpose Token) amounts. * @return The field value, or std::nullopt if not present. */ [[nodiscard]] @@ -86,6 +87,7 @@ public: /** * @brief Get sfDeliverMin (soeOPTIONAL) + * @note This field supports MPT (Multi-Purpose Token) amounts. * @return The field value, or std::nullopt if not present. */ [[nodiscard]] @@ -166,6 +168,7 @@ public: /** * @brief Set sfAmount (soeOPTIONAL) + * @note This field supports MPT (Multi-Purpose Token) amounts. * @return Reference to this builder for method chaining. */ CheckCashBuilder& @@ -177,6 +180,7 @@ public: /** * @brief Set sfDeliverMin (soeOPTIONAL) + * @note This field supports MPT (Multi-Purpose Token) amounts. * @return Reference to this builder for method chaining. */ CheckCashBuilder& diff --git a/include/xrpl/protocol_autogen/transactions/CheckCreate.h b/include/xrpl/protocol_autogen/transactions/CheckCreate.h index ba209a7fc4..f5e2ad23a9 100644 --- a/include/xrpl/protocol_autogen/transactions/CheckCreate.h +++ b/include/xrpl/protocol_autogen/transactions/CheckCreate.h @@ -60,6 +60,7 @@ public: /** * @brief Get sfSendMax (soeREQUIRED) + * @note This field supports MPT (Multi-Purpose Token) amounts. * @return The field value. */ [[nodiscard]] @@ -205,6 +206,7 @@ public: /** * @brief Set sfSendMax (soeREQUIRED) + * @note This field supports MPT (Multi-Purpose Token) amounts. * @return Reference to this builder for method chaining. */ CheckCreateBuilder& diff --git a/include/xrpl/protocol_autogen/transactions/OfferCreate.h b/include/xrpl/protocol_autogen/transactions/OfferCreate.h index a0eff385f6..2735eb85f7 100644 --- a/include/xrpl/protocol_autogen/transactions/OfferCreate.h +++ b/include/xrpl/protocol_autogen/transactions/OfferCreate.h @@ -21,7 +21,7 @@ class OfferCreateBuilder; * Type: ttOFFER_CREATE (7) * Delegable: Delegation::delegable * Amendment: uint256{} - * Privileges: noPriv + * Privileges: mayCreateMPT * * Immutable wrapper around STTx providing type-safe field access. * Use OfferCreateBuilder to construct new transactions. @@ -49,6 +49,7 @@ public: /** * @brief Get sfTakerPays (soeREQUIRED) + * @note This field supports MPT (Multi-Purpose Token) amounts. * @return The field value. */ [[nodiscard]] @@ -60,6 +61,7 @@ public: /** * @brief Get sfTakerGets (soeREQUIRED) + * @note This field supports MPT (Multi-Purpose Token) amounts. * @return The field value. */ [[nodiscard]] @@ -194,6 +196,7 @@ public: /** * @brief Set sfTakerPays (soeREQUIRED) + * @note This field supports MPT (Multi-Purpose Token) amounts. * @return Reference to this builder for method chaining. */ OfferCreateBuilder& @@ -205,6 +208,7 @@ public: /** * @brief Set sfTakerGets (soeREQUIRED) + * @note This field supports MPT (Multi-Purpose Token) amounts. * @return Reference to this builder for method chaining. */ OfferCreateBuilder& diff --git a/include/xrpl/protocol_autogen/transactions/Payment.h b/include/xrpl/protocol_autogen/transactions/Payment.h index 877286f90e..edf496dc6d 100644 --- a/include/xrpl/protocol_autogen/transactions/Payment.h +++ b/include/xrpl/protocol_autogen/transactions/Payment.h @@ -21,7 +21,7 @@ class PaymentBuilder; * Type: ttPAYMENT (0) * Delegable: Delegation::delegable * Amendment: uint256{} - * Privileges: createAcct + * Privileges: createAcct | mayCreateMPT * * Immutable wrapper around STTx providing type-safe field access. * Use PaymentBuilder to construct new transactions. diff --git a/include/xrpl/rdb/DatabaseCon.h b/include/xrpl/rdb/DatabaseCon.h index 7400593214..579f30516b 100644 --- a/include/xrpl/rdb/DatabaseCon.h +++ b/include/xrpl/rdb/DatabaseCon.h @@ -94,7 +94,7 @@ public: struct CheckpointerSetup { - JobQueue* jobQueue; + JobQueue* jobQueue{}; std::reference_wrapper registry; }; diff --git a/include/xrpl/rdb/SociDB.h b/include/xrpl/rdb/SociDB.h index 9c575359c1..43086ed931 100644 --- a/include/xrpl/rdb/SociDB.h +++ b/include/xrpl/rdb/SociDB.h @@ -39,7 +39,7 @@ class BasicConfig; class DBConfig { std::string connectionString_; - explicit DBConfig(std::string const& dbPath); + explicit DBConfig(std::string dbPath); public: DBConfig(BasicConfig const& config, std::string const& dbName); diff --git a/include/xrpl/resource/Charge.h b/include/xrpl/resource/Charge.h index 55b13731b9..436e87e158 100644 --- a/include/xrpl/resource/Charge.h +++ b/include/xrpl/resource/Charge.h @@ -2,8 +2,7 @@ #include -namespace xrpl { -namespace Resource { +namespace xrpl::Resource { /** A consumption charge. */ class Charge @@ -16,7 +15,7 @@ public: Charge() = delete; /** Create a charge with the specified cost and name. */ - Charge(value_type cost, std::string const& label = std::string()); + Charge(value_type cost, std::string label = std::string()); /** Return the human readable label associated with the charge. */ std::string const& @@ -47,5 +46,4 @@ private: std::ostream& operator<<(std::ostream& os, Charge const& v); -} // namespace Resource -} // namespace xrpl +} // namespace xrpl::Resource diff --git a/include/xrpl/resource/Consumer.h b/include/xrpl/resource/Consumer.h index 45cc221212..21d9f9c74f 100644 --- a/include/xrpl/resource/Consumer.h +++ b/include/xrpl/resource/Consumer.h @@ -5,8 +5,7 @@ #include #include -namespace xrpl { -namespace Resource { +namespace xrpl::Resource { struct Entry; class Logic; @@ -79,5 +78,4 @@ private: std::ostream& operator<<(std::ostream& os, Consumer const& v); -} // namespace Resource -} // namespace xrpl +} // namespace xrpl::Resource diff --git a/include/xrpl/resource/Disposition.h b/include/xrpl/resource/Disposition.h index 096afb6a6f..22830ad470 100644 --- a/include/xrpl/resource/Disposition.h +++ b/include/xrpl/resource/Disposition.h @@ -1,7 +1,6 @@ #pragma once -namespace xrpl { -namespace Resource { +namespace xrpl::Resource { /** The disposition of a consumer after applying a load charge. */ enum Disposition { @@ -17,5 +16,4 @@ enum Disposition { drop }; -} // namespace Resource -} // namespace xrpl +} // namespace xrpl::Resource diff --git a/include/xrpl/resource/Fees.h b/include/xrpl/resource/Fees.h index ebda0e98db..366b2d287f 100644 --- a/include/xrpl/resource/Fees.h +++ b/include/xrpl/resource/Fees.h @@ -2,8 +2,7 @@ #include -namespace xrpl { -namespace Resource { +namespace xrpl::Resource { /** Schedule of fees charged for imposing load on the server. */ /** @{ */ @@ -30,5 +29,4 @@ extern Charge const feeWarning; // The cost of receiving a warning. extern Charge const feeDrop; // The cost of being dropped for excess load. /** @} */ -} // namespace Resource -} // namespace xrpl +} // namespace xrpl::Resource diff --git a/include/xrpl/resource/Gossip.h b/include/xrpl/resource/Gossip.h index 9a70309cc4..e626af37c3 100644 --- a/include/xrpl/resource/Gossip.h +++ b/include/xrpl/resource/Gossip.h @@ -4,8 +4,7 @@ #include -namespace xrpl { -namespace Resource { +namespace xrpl::Resource { /** Data format for exchanging consumption information across peers. */ struct Gossip @@ -24,5 +23,4 @@ struct Gossip std::vector items; }; -} // namespace Resource -} // namespace xrpl +} // namespace xrpl::Resource diff --git a/include/xrpl/resource/README.md b/include/xrpl/resource/README.md index e525ce83e3..545d4e9ca0 100644 --- a/include/xrpl/resource/README.md +++ b/include/xrpl/resource/README.md @@ -17,7 +17,7 @@ performed, or simply disconnecting the endpoint. Currently, consumption endpoints include websocket connections used to service clients, and peer connections used to create the peer to peer -overlay network implementing the Ripple protocol. +overlay network implementing the XRPL protocol. The current "balance" of a Consumer represents resource consumption debt or credit. Debt is accrued when bad loads are imposed. Credit is @@ -72,6 +72,6 @@ drop connections to those IP addresses that occur commonly in the gossip. ## Access -In rippled, the Application holds a unique instance of Resource::Manager, +In xrpld, the Application holds a unique instance of Resource::Manager, which may be retrieved by calling the method `Application::getResourceManager()`. diff --git a/include/xrpl/resource/ResourceManager.h b/include/xrpl/resource/ResourceManager.h index 4aab017691..96e5255272 100644 --- a/include/xrpl/resource/ResourceManager.h +++ b/include/xrpl/resource/ResourceManager.h @@ -10,8 +10,7 @@ #include -namespace xrpl { -namespace Resource { +namespace xrpl::Resource { /** Tracks load and resource consumption. */ class Manager : public beast::PropertyStream::Source @@ -20,7 +19,7 @@ protected: Manager(); public: - virtual ~Manager() = 0; + ~Manager() override = 0; /** Create a new endpoint keyed by inbound IP address or the forwarded * IP if proxied. */ @@ -62,5 +61,4 @@ public: std::unique_ptr make_Manager(beast::insight::Collector::ptr const& collector, beast::Journal journal); -} // namespace Resource -} // namespace xrpl +} // namespace xrpl::Resource diff --git a/include/xrpl/resource/detail/Entry.h b/include/xrpl/resource/detail/Entry.h index 2a6414778c..5b2d8b1ba3 100644 --- a/include/xrpl/resource/detail/Entry.h +++ b/include/xrpl/resource/detail/Entry.h @@ -7,8 +7,7 @@ #include #include -namespace xrpl { -namespace Resource { +namespace xrpl::Resource { using clock_type = beast::abstract_clock; @@ -22,7 +21,7 @@ struct Entry : public beast::List::Node @param now Construction time of Entry. */ explicit Entry(clock_type::time_point const now) - : refcount(0), local_balance(now), remote_balance(0), lastWarningTime(), whenExpires() + : refcount(0), local_balance(now), remote_balance(0) { } @@ -87,5 +86,4 @@ operator<<(std::ostream& os, Entry const& v) return os; } -} // namespace Resource -} // namespace xrpl +} // namespace xrpl::Resource diff --git a/include/xrpl/resource/detail/Import.h b/include/xrpl/resource/detail/Import.h index bee90afbf0..6e2a7b9e7c 100644 --- a/include/xrpl/resource/detail/Import.h +++ b/include/xrpl/resource/detail/Import.h @@ -3,8 +3,7 @@ #include #include -namespace xrpl { -namespace Resource { +namespace xrpl::Resource { /** A set of imported consumer data from a gossip origin. */ struct Import @@ -18,7 +17,7 @@ struct Import }; // Dummy argument required for zero-copy construction - Import(int = 0) : whenExpires() + Import(int = 0) { } @@ -29,5 +28,4 @@ struct Import std::vector items; }; -} // namespace Resource -} // namespace xrpl +} // namespace xrpl::Resource diff --git a/include/xrpl/resource/detail/Key.h b/include/xrpl/resource/detail/Key.h index 0c5164a7d8..c9a233b634 100644 --- a/include/xrpl/resource/detail/Key.h +++ b/include/xrpl/resource/detail/Key.h @@ -4,8 +4,9 @@ #include #include -namespace xrpl { -namespace Resource { +#include + +namespace xrpl::Resource { // The consumer key struct Key @@ -15,7 +16,7 @@ struct Key Key() = delete; - Key(Kind k, beast::IP::Endpoint const& addr) : kind(k), address(addr) + Key(Kind k, beast::IP::Endpoint addr) : kind(k), address(std::move(addr)) { } @@ -45,5 +46,4 @@ struct Key }; }; -} // namespace Resource -} // namespace xrpl +} // namespace xrpl::Resource diff --git a/include/xrpl/resource/detail/Kind.h b/include/xrpl/resource/detail/Kind.h index fbb6b65e25..74115ba7f1 100644 --- a/include/xrpl/resource/detail/Kind.h +++ b/include/xrpl/resource/detail/Kind.h @@ -1,7 +1,6 @@ #pragma once -namespace xrpl { -namespace Resource { +namespace xrpl::Resource { /** * Kind of consumer. @@ -13,5 +12,4 @@ namespace Resource { */ enum Kind { kindInbound, kindOutbound, kindUnlimited }; -} // namespace Resource -} // namespace xrpl +} // namespace xrpl::Resource diff --git a/include/xrpl/resource/detail/Logic.h b/include/xrpl/resource/detail/Logic.h index 66c47bc538..b775f0a965 100644 --- a/include/xrpl/resource/detail/Logic.h +++ b/include/xrpl/resource/detail/Logic.h @@ -15,8 +15,7 @@ #include -namespace xrpl { -namespace Resource { +namespace xrpl::Resource { class Logic { @@ -352,7 +351,9 @@ public: iter = importTable_.erase(iter); } else + { ++iter; + } } } @@ -506,7 +507,7 @@ public: //-------------------------------------------------------------------------- - void + static void writeList( clock_type::time_point const now, beast::PropertyStream::Set& items, @@ -553,5 +554,4 @@ public: } }; -} // namespace Resource -} // namespace xrpl +} // namespace xrpl::Resource diff --git a/include/xrpl/resource/detail/Tuning.h b/include/xrpl/resource/detail/Tuning.h index d5be5cf0b6..1c903d8cd4 100644 --- a/include/xrpl/resource/detail/Tuning.h +++ b/include/xrpl/resource/detail/Tuning.h @@ -2,8 +2,7 @@ #include -namespace xrpl { -namespace Resource { +namespace xrpl::Resource { /** Tunable constants. */ enum { @@ -30,5 +29,4 @@ std::chrono::seconds constexpr secondsUntilExpiration{300}; // Number of seconds until imported gossip expires std::chrono::seconds constexpr gossipExpirationSeconds{30}; -} // namespace Resource -} // namespace xrpl +} // namespace xrpl::Resource diff --git a/include/xrpl/server/LoadFeeTrack.h b/include/xrpl/server/LoadFeeTrack.h index 226408dbde..6b9e96241c 100644 --- a/include/xrpl/server/LoadFeeTrack.h +++ b/include/xrpl/server/LoadFeeTrack.h @@ -60,8 +60,8 @@ public: return clusterTxnLoadFee_; } - std::uint32_t - getLoadBase() const + static std::uint32_t + getLoadBase() { return lftNormalFee; } diff --git a/include/xrpl/server/Manifest.h b/include/xrpl/server/Manifest.h index 02c370561a..ce97c57260 100644 --- a/include/xrpl/server/Manifest.h +++ b/include/xrpl/server/Manifest.h @@ -8,6 +8,7 @@ #include #include #include +#include namespace xrpl { @@ -15,9 +16,9 @@ namespace xrpl { Validator key manifests ----------------------- - Suppose the secret keys installed on a Ripple validator are compromised. Not + Suppose the secret keys installed on an XRPL validator are compromised. Not only do you have to generate and install new key pairs on each validator, - EVERY rippled needs to have its config updated with the new public keys, and + EVERY xrpld needs to have its config updated with the new public keys, and is vulnerable to forged validation signatures until this is done. The solution is a new layer of indirection: A master secret key under restrictive access control is used to sign a "manifest": essentially, a @@ -39,11 +40,11 @@ namespace xrpl { seen for that validator, if any. On startup, the [validator_token] config entry (which contains the manifest for this validator) is decoded and added to the manifest cache. Other manifests are added as "gossip" - received from rippled peers. + received from xrpld peers. When an ephemeral key is compromised, a new signing key pair is created, along with a new manifest vouching for it (with a higher sequence number), - signed by the master key. When a rippled peer receives the new manifest, + signed by the master key. When an xrpld peer receives the new manifest, it verifies it with the master key and (assuming it's valid) discards the old ephemeral key and stores the new one. If the master key itself gets compromised, a manifest with sequence number 0xFFFFFFFF will supersede a @@ -80,16 +81,16 @@ struct Manifest Manifest() = delete; Manifest( - std::string const& serialized_, + std::string serialized_, PublicKey const& masterKey_, std::optional const& signingKey_, std::uint32_t seq, - std::string const& domain_) - : serialized(serialized_) + std::string domain_) + : serialized(std::move(serialized_)) , masterKey(masterKey_) , signingKey(signingKey_) , sequence(seq) - , domain(domain_) + , domain(std::move(domain_)) { } @@ -154,7 +155,7 @@ deserializeManifest( template < class T, - class = std::enable_if_t::value || std::is_same::value>> + class = std::enable_if_t || std::is_same_v>> std::optional deserializeManifest( std::vector const& v, diff --git a/include/xrpl/server/NetworkOPs.h b/include/xrpl/server/NetworkOPs.h index 75f1e0e1b2..b8f50dcd42 100644 --- a/include/xrpl/server/NetworkOPs.h +++ b/include/xrpl/server/NetworkOPs.h @@ -63,8 +63,8 @@ enum class OperatingMode { needed. A backend application or local client can trust a local instance of - rippled / NetworkOPs. However, client software connecting to non-local - instances of rippled will need to be hardened to protect against hostile + xrpld / NetworkOPs. However, client software connecting to non-local + instances of xrpld will need to be hardened to protect against hostile or unreliable servers. */ class NetworkOPs : public InfoSub::Source @@ -73,7 +73,7 @@ public: using clock_type = beast::abstract_clock; enum class FailHard : unsigned char { no, yes }; - static inline FailHard + static FailHard doFailHard(bool noMeansDont) { return noMeansDont ? FailHard::yes : FailHard::no; diff --git a/include/xrpl/server/Port.h b/include/xrpl/server/Port.h index c7acabbc85..93652d422a 100644 --- a/include/xrpl/server/Port.h +++ b/include/xrpl/server/Port.h @@ -15,13 +15,9 @@ #include #include -namespace boost { -namespace asio { -namespace ssl { +namespace boost::asio::ssl { class context; -} // namespace ssl -} // namespace asio -} // namespace boost +} // namespace boost::asio::ssl namespace xrpl { diff --git a/include/xrpl/server/SimpleWriter.h b/include/xrpl/server/SimpleWriter.h index 87b94507a4..0c3b711187 100644 --- a/include/xrpl/server/SimpleWriter.h +++ b/include/xrpl/server/SimpleWriter.h @@ -48,7 +48,7 @@ public: std::vector result; result.reserve(std::distance(buf.begin(), buf.end())); for (auto const b : buf) - result.push_back(b); + result.emplace_back(b); return result; } }; diff --git a/include/xrpl/server/detail/BaseHTTPPeer.h b/include/xrpl/server/detail/BaseHTTPPeer.h index 878f6a1ddf..a0f8e1f318 100644 --- a/include/xrpl/server/detail/BaseHTTPPeer.h +++ b/include/xrpl/server/detail/BaseHTTPPeer.h @@ -23,6 +23,7 @@ #include #include #include +#include #include namespace xrpl { @@ -93,7 +94,7 @@ public: endpoint_type remote_address, ConstBufferSequence const& buffers); - virtual ~BaseHTTPPeer(); + ~BaseHTTPPeer() override; Session& session() @@ -195,7 +196,7 @@ BaseHTTPPeer::BaseHTTPPeer( , handler_(handler) , work_(boost::asio::make_work_guard(executor)) , strand_(boost::asio::make_strand(executor)) - , remote_address_(remote_address) + , remote_address_(std::move(remote_address)) , journal_(journal) { read_buf_.commit( @@ -219,10 +220,12 @@ void BaseHTTPPeer::close() { if (!strand_.running_in_this_thread()) + { return post( strand_, std::bind( (void (BaseHTTPPeer::*)(void))&BaseHTTPPeer::close, impl().shared_from_this())); + } boost::beast::get_lowest_layer(impl().stream_).close(); } @@ -398,11 +401,12 @@ BaseHTTPPeer::write(void const* buf, std::size_t bytes) }()) { if (!strand_.running_in_this_thread()) + { return post( strand_, std::bind(&BaseHTTPPeer::on_write, impl().shared_from_this(), error_code{}, 0)); - else - return on_write(error_code{}, 0); + } + return on_write(error_code{}, 0); } } @@ -436,8 +440,10 @@ void BaseHTTPPeer::complete() { if (!strand_.running_in_this_thread()) + { return post( strand_, std::bind(&BaseHTTPPeer::complete, impl().shared_from_this())); + } message_ = {}; complete_ = true; @@ -464,12 +470,14 @@ void BaseHTTPPeer::close(bool graceful) { if (!strand_.running_in_this_thread()) + { return post( strand_, std::bind( (void (BaseHTTPPeer::*)(bool))&BaseHTTPPeer::close, impl().shared_from_this(), graceful)); + } complete_ = true; if (graceful) diff --git a/include/xrpl/server/detail/BasePeer.h b/include/xrpl/server/detail/BasePeer.h index afa1ceb993..5bde6924a5 100644 --- a/include/xrpl/server/detail/BasePeer.h +++ b/include/xrpl/server/detail/BasePeer.h @@ -11,6 +11,7 @@ #include #include #include +#include namespace xrpl { @@ -34,6 +35,7 @@ protected: boost::asio::strand strand_; public: + // NOLINTNEXTLINE(bugprone-crtp-constructor-accessibility) BasePeer( Port const& port, Handler& handler, @@ -63,7 +65,7 @@ BasePeer::BasePeer( beast::Journal journal) : port_(port) , handler_(handler) - , remote_address_(remote_address) + , remote_address_(std::move(remote_address)) , sink_( journal.sink(), [] { diff --git a/include/xrpl/server/detail/BaseWSPeer.h b/include/xrpl/server/detail/BaseWSPeer.h index 9213a955f0..4251617262 100644 --- a/include/xrpl/server/detail/BaseWSPeer.h +++ b/include/xrpl/server/detail/BaseWSPeer.h @@ -15,6 +15,7 @@ #include #include +#include #include #include @@ -285,6 +286,7 @@ BaseWSPeer::on_write(error_code const& ec) return; start_timer(); if (!result.first) + { impl().ws_.async_write_some( static_cast(result.first), result.second, @@ -292,7 +294,9 @@ BaseWSPeer::on_write(error_code const& ec) strand_, std::bind( &BaseWSPeer::on_write, impl().shared_from_this(), std::placeholders::_1))); + } else + { impl().ws_.async_write_some( static_cast(result.first), result.second, @@ -300,6 +304,7 @@ BaseWSPeer::on_write(error_code const& ec) strand_, std::bind( &BaseWSPeer::on_write_fin, impl().shared_from_this(), std::placeholders::_1))); + } } template @@ -319,7 +324,9 @@ BaseWSPeer::on_write_fin(error_code const& ec) &BaseWSPeer::on_close, impl().shared_from_this(), std::placeholders::_1))); } else if (!wq_.empty()) + { on_write({}); + } } template @@ -346,7 +353,7 @@ BaseWSPeer::on_read(error_code const& ec) auto const& data = rb_.data(); std::vector b; b.reserve(std::distance(data.begin(), data.end())); - std::copy(data.begin(), data.end(), std::back_inserter(b)); + std::ranges::copy(data, std::back_inserter(b)); this->handler_.onWSMessage(impl().shared_from_this(), b); rb_.consume(rb_.size()); } @@ -392,7 +399,7 @@ BaseWSPeer::cancel_timer() { timer_.cancel(); } - catch (boost::system::system_error const&) + catch (boost::system::system_error const&) // NOLINT(bugprone-empty-catch) { // ignored } diff --git a/include/xrpl/server/detail/Door.h b/include/xrpl/server/detail/Door.h index 87baad42db..346309f078 100644 --- a/include/xrpl/server/detail/Door.h +++ b/include/xrpl/server/detail/Door.h @@ -33,6 +33,7 @@ #include #include #include +#include namespace xrpl { @@ -93,7 +94,7 @@ private: port_.protocol.count("wss2") > 0 || port_.protocol.count("peer") > 0}; bool plain_{ port_.protocol.count("http") > 0 || port_.protocol.count("ws") > 0 || - port_.protocol.count("ws2")}; + (port_.protocol.count("ws2") != 0u)}; static constexpr std::chrono::milliseconds INITIAL_ACCEPT_DELAY{50}; static constexpr std::chrono::milliseconds MAX_ACCEPT_DELAY{2000}; std::chrono::milliseconds accept_delay_{INITIAL_ACCEPT_DELAY}; @@ -163,7 +164,7 @@ Door::Detector::Detector( , ioc_(ioc) , stream_(std::move(stream)) , socket_(stream_.socket()) - , remote_address_(remote_address) + , remote_address_(std::move(remote_address)) , strand_(boost::asio::make_strand(ioc_)) , j_(j) { @@ -297,8 +298,10 @@ void Door::close() { if (!strand_.running_in_this_thread()) + { return boost::asio::post( strand_, std::bind(&Door::close, this->shared_from_this())); + } backoff_timer_.cancel(); error_code ec; acceptor_.close(ec); @@ -432,11 +435,7 @@ Door::should_throttle_for_fds() auto const& s = *stats; auto const free = (s.limit > s.used) ? (s.limit - s.used) : 0ull; double const free_ratio = static_cast(free) / static_cast(s.limit); - if (free_ratio < FREE_FD_THRESHOLD) - { - return true; - } - return false; + return free_ratio < FREE_FD_THRESHOLD; #endif } diff --git a/include/xrpl/server/detail/ServerImpl.h b/include/xrpl/server/detail/ServerImpl.h index 0b5d075d87..14d2b56fe4 100644 --- a/include/xrpl/server/detail/ServerImpl.h +++ b/include/xrpl/server/detail/ServerImpl.h @@ -81,7 +81,7 @@ private: public: ServerImpl(Handler& handler, boost::asio::io_context& io_context, beast::Journal journal); - ~ServerImpl(); + ~ServerImpl() override; beast::Journal journal() override @@ -155,7 +155,7 @@ ServerImpl::ports(std::vector const& ports) list_.push_back(sp); auto ep = sp->get_endpoint(); - if (!internalPort.port) + if (internalPort.port == 0u) internalPort.port = ep.port(); eps.emplace(port.name, std::move(ep)); diff --git a/include/xrpl/server/detail/io_list.h b/include/xrpl/server/detail/io_list.h index f1cc04f683..ef11d3cea6 100644 --- a/include/xrpl/server/detail/io_list.h +++ b/include/xrpl/server/detail/io_list.h @@ -189,7 +189,7 @@ template std::shared_ptr io_list::emplace(Args&&... args) { - static_assert(std::is_base_of::value, "T must derive from io_list::work"); + static_assert(std::is_base_of_v, "T must derive from io_list::work"); if (closed_) return nullptr; auto sp = std::make_shared(std::forward(args)...); @@ -223,8 +223,10 @@ io_list::close(Finisher&& f) f_ = std::forward(f); lock.unlock(); for (auto const& p : map) + { if (auto sp = p.second.lock()) sp->close(); + } } else { diff --git a/include/xrpl/shamap/README.md b/include/xrpl/shamap/README.md index 419918c0cb..ae37d23ac3 100644 --- a/include/xrpl/shamap/README.md +++ b/include/xrpl/shamap/README.md @@ -112,7 +112,7 @@ When a `SHAMap` decides that it is safe to share a node of its own, it sets the node's sequence number to 0 (a `SHAMap` never has a sequence number of 0). This is done for every node in the trie when `SHAMap::walkSubTree` is executed. -Note that other objects in rippled also have sequence numbers (e.g. ledgers). +Note that other objects in xrpld also have sequence numbers (e.g. ledgers). The `SHAMap` and node sequence numbers should not be confused with these other sequence numbers (no relation). diff --git a/include/xrpl/shamap/SHAMap.h b/include/xrpl/shamap/SHAMap.h index 213e7ce0ce..43b2734d65 100644 --- a/include/xrpl/shamap/SHAMap.h +++ b/include/xrpl/shamap/SHAMap.h @@ -94,10 +94,10 @@ private: public: /** Number of children each non-leaf node has (the 'radix tree' part of the * map) */ - static inline constexpr unsigned int branchFactor = SHAMapInnerNode::branchFactor; + static constexpr unsigned int branchFactor = SHAMapInnerNode::branchFactor; /** The depth of the hash map: data is only present in the leaves */ - static inline constexpr unsigned int leafDepth = 64; + static constexpr unsigned int leafDepth = 64; using DeltaItem = std::pair, boost::intrusive_ptr>; @@ -658,9 +658,13 @@ inline SHAMap::const_iterator& SHAMap::const_iterator::operator++() { if (auto temp = map_->peekNextItem(item_->key(), stack_)) + { item_ = temp->peekItem().get(); + } else + { item_ = nullptr; + } return *this; } diff --git a/include/xrpl/shamap/SHAMapAccountStateLeafNode.h b/include/xrpl/shamap/SHAMapAccountStateLeafNode.h index 98ac9776c3..2f71d10b0a 100644 --- a/include/xrpl/shamap/SHAMapAccountStateLeafNode.h +++ b/include/xrpl/shamap/SHAMapAccountStateLeafNode.h @@ -28,25 +28,25 @@ public: } intr_ptr::SharedPtr - clone(std::uint32_t cowid) const final override + clone(std::uint32_t cowid) const final { return intr_ptr::make_shared(item_, cowid, hash_); } SHAMapNodeType - getType() const final override + getType() const final { return SHAMapNodeType::tnACCOUNT_STATE; } void - updateHash() final override + updateHash() final { hash_ = SHAMapHash{sha512Half(HashPrefix::leafNode, item_->slice(), item_->key())}; } void - serializeForWire(Serializer& s) const final override + serializeForWire(Serializer& s) const final { s.addRaw(item_->slice()); s.addBitString(item_->key()); @@ -54,7 +54,7 @@ public: } void - serializeWithPrefix(Serializer& s) const final override + serializeWithPrefix(Serializer& s) const final { s.add32(HashPrefix::leafNode); s.addRaw(item_->slice()); diff --git a/include/xrpl/shamap/SHAMapInnerNode.h b/include/xrpl/shamap/SHAMapInnerNode.h index 73d59cc1fe..f2c34cbc1a 100644 --- a/include/xrpl/shamap/SHAMapInnerNode.h +++ b/include/xrpl/shamap/SHAMapInnerNode.h @@ -15,7 +15,7 @@ class SHAMapInnerNode final : public SHAMapTreeNode, public CountedObject const& @@ -53,7 +53,7 @@ public: setItem(boost::intrusive_ptr i); std::string - getString(SHAMapNodeID const&) const final override; + getString(SHAMapNodeID const&) const final; }; } // namespace xrpl diff --git a/include/xrpl/shamap/SHAMapTreeNode.h b/include/xrpl/shamap/SHAMapTreeNode.h index f837a8643b..55b18a2d12 100644 --- a/include/xrpl/shamap/SHAMapTreeNode.h +++ b/include/xrpl/shamap/SHAMapTreeNode.h @@ -40,11 +40,6 @@ protected: */ std::uint32_t cowid_; -protected: - SHAMapTreeNode(SHAMapTreeNode const&) = delete; - SHAMapTreeNode& - operator=(SHAMapTreeNode const&) = delete; - /** Construct a node @param cowid The identifier of a SHAMap. For more, see #cowid_ @@ -62,7 +57,11 @@ protected: /** @} */ public: - virtual ~SHAMapTreeNode() noexcept = default; + ~SHAMapTreeNode() noexcept override = default; + + SHAMapTreeNode(SHAMapTreeNode const&) = delete; + SHAMapTreeNode& + operator=(SHAMapTreeNode const&) = delete; // Needed to support weak intrusive pointers virtual void diff --git a/include/xrpl/shamap/SHAMapTxLeafNode.h b/include/xrpl/shamap/SHAMapTxLeafNode.h index dcedb26881..368aa2e21b 100644 --- a/include/xrpl/shamap/SHAMapTxLeafNode.h +++ b/include/xrpl/shamap/SHAMapTxLeafNode.h @@ -27,32 +27,32 @@ public: } intr_ptr::SharedPtr - clone(std::uint32_t cowid) const final override + clone(std::uint32_t cowid) const final { return intr_ptr::make_shared(item_, cowid, hash_); } SHAMapNodeType - getType() const final override + getType() const final { return SHAMapNodeType::tnTRANSACTION_NM; } void - updateHash() final override + updateHash() final { hash_ = SHAMapHash{sha512Half(HashPrefix::transactionID, item_->slice())}; } void - serializeForWire(Serializer& s) const final override + serializeForWire(Serializer& s) const final { s.addRaw(item_->slice()); s.add8(wireTypeTransaction); } void - serializeWithPrefix(Serializer& s) const final override + serializeWithPrefix(Serializer& s) const final { s.add32(HashPrefix::transactionID); s.addRaw(item_->slice()); diff --git a/include/xrpl/shamap/SHAMapTxPlusMetaLeafNode.h b/include/xrpl/shamap/SHAMapTxPlusMetaLeafNode.h index 60e645fccc..40be482ed9 100644 --- a/include/xrpl/shamap/SHAMapTxPlusMetaLeafNode.h +++ b/include/xrpl/shamap/SHAMapTxPlusMetaLeafNode.h @@ -40,13 +40,13 @@ public: } void - updateHash() final override + updateHash() final { hash_ = SHAMapHash{sha512Half(HashPrefix::txNode, item_->slice(), item_->key())}; } void - serializeForWire(Serializer& s) const final override + serializeForWire(Serializer& s) const final { s.addRaw(item_->slice()); s.addBitString(item_->key()); @@ -54,7 +54,7 @@ public: } void - serializeWithPrefix(Serializer& s) const final override + serializeWithPrefix(Serializer& s) const final { s.add32(HashPrefix::txNode); s.addRaw(item_->slice()); diff --git a/include/xrpl/tx/SignerEntries.h b/include/xrpl/tx/SignerEntries.h index 0b997b3e4d..91fc4bd030 100644 --- a/include/xrpl/tx/SignerEntries.h +++ b/include/xrpl/tx/SignerEntries.h @@ -42,10 +42,10 @@ public: } // For sorting to look for duplicate accounts - friend bool - operator<(SignerEntry const& lhs, SignerEntry const& rhs) + friend auto + operator<=>(SignerEntry const& lhs, SignerEntry const& rhs) { - return lhs.account < rhs.account; + return lhs.account <=> rhs.account; } friend bool diff --git a/include/xrpl/tx/Transactor.h b/include/xrpl/tx/Transactor.h index 287f785cd7..a3b0af821e 100644 --- a/include/xrpl/tx/Transactor.h +++ b/include/xrpl/tx/Transactor.h @@ -7,6 +7,8 @@ #include #include +#include + namespace xrpl { /** State information when preflighting a tx. */ @@ -24,12 +26,12 @@ public: ServiceRegistry& registry_, STTx const& tx_, uint256 parentBatchId_, - Rules const& rules_, + Rules rules_, ApplyFlags flags_, beast::Journal j_ = beast::Journal{beast::Journal::getNullSink()}) : registry(registry_) , tx(tx_) - , rules(rules_) + , rules(std::move(rules_)) , flags(flags_) , parentBatchId(parentBatchId_) , j(j_) @@ -40,10 +42,10 @@ public: PreflightContext( ServiceRegistry& registry_, STTx const& tx_, - Rules const& rules_, + Rules rules_, ApplyFlags flags_, beast::Journal j_ = beast::Journal{beast::Journal::getNullSink()}) - : registry(registry_), tx(tx_), rules(rules_), flags(flags_), j(j_) + : registry(registry_), tx(tx_), rules(std::move(rules_)), flags(flags_), j(j_) { XRPL_ASSERT((flags_ & tapBATCH) == 0, "Batch apply flag should not be set"); } @@ -116,14 +118,13 @@ protected: AccountID const account_; XRPAmount preFeeBalance_{}; // Balance before fees. +public: + virtual ~Transactor() = default; Transactor(Transactor const&) = delete; Transactor& operator=(Transactor const&) = delete; - -public: - virtual ~Transactor() = default; - enum ConsequencesFactoryType { Normal, Blocker, Custom }; + /** Process the transaction. */ ApplyResult operator()(); @@ -140,6 +141,20 @@ public: return ctx_.view(); } + /** Check all invariants for the current transaction. + * + * Runs transaction-specific invariants first (visitInvariantEntry + + * finalizeInvariants), then protocol-level invariants. Both layers + * always run; the worst failure code is returned. + * + * @param result the tentative TER from transaction processing. + * @param fee the fee consumed by the transaction. + * + * @return the final TER after all invariant checks. + */ + [[nodiscard]] TER + checkInvariants(TER result, XRPAmount fee); + ///////////////////////////////////////////////////// /* These static functions are called from invoke_preclaim @@ -230,6 +245,52 @@ protected: virtual TER doApply() = 0; + /** Inspect a single ledger entry modified by this transaction. + * + * Called once for every SLE created, modified, or deleted by the + * transaction, before finalizeInvariants. Implementations should + * accumulate whatever state they need to verify transaction-specific + * post-conditions. + * + * @param isDelete true if the entry was erased from the ledger. + * @param before the entry's state before the transaction (nullptr + * for newly created entries). + * @param after the entry's state as supplied by the apply logic + * for this transaction. For deletions, this is the + * SLE being erased and is not guaranteed to be null; + * callers must use isDelete rather than after == nullptr + * to detect deletions. + */ + virtual void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) = 0; + + /** Check transaction-specific post-conditions after all entries have + * been visited. + * + * Called once after every modified ledger entry has been passed to + * visitInvariantEntry. Returns true if all transaction-specific + * invariants hold, or false to fail the transaction with + * tecINVARIANT_FAILED. + * + * @param tx the transaction being applied. + * @param result the tentative TER result so far. + * @param fee the fee consumed by the transaction. + * @param view read-only view of the ledger after the transaction. + * @param j journal for logging invariant failures. + * + * @return true if all invariants pass; false otherwise. + */ + [[nodiscard]] virtual bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) = 0; + /** Compute the minimum fee required to process a transaction with a given baseFee based on the current server load. @@ -335,6 +396,21 @@ private: */ static NotTEC preflight2(PreflightContext const& ctx); + + /** Check transaction-specific invariants only. + * + * Walks every modified ledger entry via visitInvariantEntry, then + * calls finalizeInvariants on the derived transactor. Returns + * tecINVARIANT_FAILED if any transaction invariant is violated. + * + * @param result the tentative TER from transaction processing. + * @param fee the fee consumed by the transaction. + * + * @return the original result if all invariants pass, or + * tecINVARIANT_FAILED otherwise. + */ + [[nodiscard]] TER + checkTransactionInvariants(TER result, XRPAmount fee); }; inline bool diff --git a/include/xrpl/tx/applySteps.h b/include/xrpl/tx/applySteps.h index 9ce6dfb1e2..d42ca2c118 100644 --- a/include/xrpl/tx/applySteps.h +++ b/include/xrpl/tx/applySteps.h @@ -27,7 +27,7 @@ struct ApplyResult inline bool isTecClaimHardFail(TER ter, ApplyFlags flags) { - return isTecClaim(ter) && !(flags & tapRETRY); + return isTecClaim(ter) && ((flags & tapRETRY) == 0u); } /** Class describing the consequences to the account @@ -202,7 +202,7 @@ public: /// Success flag - whether the transaction is likely to /// claim a fee - bool const likelyToClaimFee; + bool const likelyToClaimFee{}; /// Constructor template diff --git a/include/xrpl/tx/invariants/AMMInvariant.h b/include/xrpl/tx/invariants/AMMInvariant.h index e872c61f76..b15b62cc93 100644 --- a/include/xrpl/tx/invariants/AMMInvariant.h +++ b/include/xrpl/tx/invariants/AMMInvariant.h @@ -20,9 +20,7 @@ class ValidAMM public: enum class ZeroAllowed : bool { No = false, Yes = true }; - ValidAMM() - { - } + ValidAMM() = default; void visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); diff --git a/include/xrpl/tx/invariants/InvariantCheck.h b/include/xrpl/tx/invariants/InvariantCheck.h index e7ced63785..ad4c5e16c4 100644 --- a/include/xrpl/tx/invariants/InvariantCheck.h +++ b/include/xrpl/tx/invariants/InvariantCheck.h @@ -398,7 +398,8 @@ using InvariantChecks = std::tuple< ValidPseudoAccounts, ValidLoanBroker, ValidLoan, - ValidVault>; + ValidVault, + ValidMPTPayment>; /** * @brief get a tuple of all invariant checks diff --git a/include/xrpl/tx/invariants/InvariantCheckPrivilege.h b/include/xrpl/tx/invariants/InvariantCheckPrivilege.h index 161b3572db..684e40c8cf 100644 --- a/include/xrpl/tx/invariants/InvariantCheckPrivilege.h +++ b/include/xrpl/tx/invariants/InvariantCheckPrivilege.h @@ -44,6 +44,7 @@ enum Privilege { 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 + mayCreateMPT = 0x2000, // The transaction MAY create an MPT object, except for issuer. }; constexpr Privilege diff --git a/include/xrpl/tx/invariants/MPTInvariant.h b/include/xrpl/tx/invariants/MPTInvariant.h index 68f866d362..dd064af396 100644 --- a/include/xrpl/tx/invariants/MPTInvariant.h +++ b/include/xrpl/tx/invariants/MPTInvariant.h @@ -28,4 +28,32 @@ public: finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&) const; }; +/** Verify: + * - OutstandingAmount <= MaximumAmount for any MPT + * - OutstandingAmount after = OutstandingAmount before + + * sum (MPT after - MPT before) - this is total MPT credit/debit + */ +class ValidMPTPayment +{ + enum Order { Before = 0, After = 1 }; + struct MPTData + { + std::array outstanding{}; + // sum (MPT after - MPT before) + std::int64_t mptAmount{0}; + }; + + // true if OutstandingAmount > MaximumAmount in after for any MPT + bool overflow_{false}; + // mptid:MPTData + hash_map data_; + +public: + void + visitEntry(bool, std::shared_ptr const&, std::shared_ptr const&); + + bool + finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); +}; + } // namespace xrpl diff --git a/include/xrpl/tx/invariants/PermissionedDEXInvariant.h b/include/xrpl/tx/invariants/PermissionedDEXInvariant.h index b4e06cd212..da187779b2 100644 --- a/include/xrpl/tx/invariants/PermissionedDEXInvariant.h +++ b/include/xrpl/tx/invariants/PermissionedDEXInvariant.h @@ -11,7 +11,8 @@ namespace xrpl { class ValidPermissionedDEX { bool regularOffers_ = false; - bool badHybrids_ = false; + bool badHybridsOld_ = false; // pre-fixSecurity3_1_3: missing field/domain or size > 1 + bool badHybrids_ = false; // post-fixSecurity3_1_3: also catches size == 0 (size != 1) hash_set domains_; public: diff --git a/include/xrpl/tx/invariants/VaultInvariant.h b/include/xrpl/tx/invariants/VaultInvariant.h index ded9e4618b..b9b3bf746f 100644 --- a/include/xrpl/tx/invariants/VaultInvariant.h +++ b/include/xrpl/tx/invariants/VaultInvariant.h @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -39,9 +40,9 @@ class ValidVault struct Vault final { uint256 key = beast::zero; - Asset asset = {}; - AccountID pseudoId = {}; - AccountID owner = {}; + Asset asset; + AccountID pseudoId; + AccountID owner; uint192 shareMPTID = beast::zero; Number assetsTotal = 0; Number assetsAvailable = 0; @@ -53,18 +54,30 @@ class ValidVault struct Shares final { - MPTIssue share = {}; + MPTIssue share; std::uint64_t sharesTotal = 0; std::uint64_t sharesMaximum = 0; Shares static make(SLE const&); }; - std::vector afterVault_ = {}; - std::vector afterMPTs_ = {}; - std::vector beforeVault_ = {}; - std::vector beforeMPTs_ = {}; - std::unordered_map deltas_ = {}; +public: + struct DeltaInfo final + { + Number delta = numZero; + std::optional scale; + + // Compute the delta between two Numbers, taking the coarsest scale + [[nodiscard]] static DeltaInfo + makeDelta(Number const& before, Number const& after, Asset const& asset); + }; + +private: + std::vector afterVault_; + std::vector afterMPTs_; + std::vector beforeVault_; + std::vector beforeMPTs_; + std::unordered_map deltas_; public: void @@ -72,6 +85,10 @@ public: bool finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&); + + // Compute the coarsest scale required to represent all numbers + [[nodiscard]] static std::int32_t + computeCoarsestScale(std::vector const& numbers); }; } // namespace xrpl diff --git a/include/xrpl/tx/paths/AMMLiquidity.h b/include/xrpl/tx/paths/AMMLiquidity.h index 71b8dbb12a..128052f851 100644 --- a/include/xrpl/tx/paths/AMMLiquidity.h +++ b/include/xrpl/tx/paths/AMMLiquidity.h @@ -3,14 +3,14 @@ #include #include #include +#include +#include #include #include -#include -#include namespace xrpl { -template +template class AMMOffer; /** AMMLiquidity class provides AMM offers to BookStep class. @@ -35,8 +35,8 @@ private: AMMContext& ammContext_; AccountID const ammAccountID_; std::uint32_t const tradingFee_; - Issue const issueIn_; - Issue const issueOut_; + Asset const assetIn_; + Asset const assetOut_; // Initial AMM pool balances TAmounts const initialBalances_; beast::Journal const j_; @@ -46,8 +46,8 @@ public: ReadView const& view, AccountID const& ammAccountID, std::uint32_t tradingFee, - Issue const& in, - Issue const& out, + Asset const& in, + Asset const& out, AMMContext& ammContext, beast::Journal j); ~AMMLiquidity() = default; @@ -87,16 +87,16 @@ public: return ammContext_; } - Issue const& - issueIn() const + Asset const& + assetIn() const { - return issueIn_; + return assetIn_; } - Issue const& - issueOut() const + Asset const& + assetOut() const { - return issueOut_; + return assetOut_; } private: diff --git a/include/xrpl/tx/paths/AMMOffer.h b/include/xrpl/tx/paths/AMMOffer.h index 4ef1b2048b..de583a60d6 100644 --- a/include/xrpl/tx/paths/AMMOffer.h +++ b/include/xrpl/tx/paths/AMMOffer.h @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -16,7 +17,7 @@ class QualityFunction; * methods for use in generic BookStep methods. AMMOffer amounts * are changed indirectly in BookStep limiting steps. */ -template +template class AMMOffer { private: @@ -52,8 +53,11 @@ public: return quality_; } - Issue const& - issueIn() const; + Asset const& + assetIn() const; + + Asset const& + assetOut() const; AccountID const& owner() const; @@ -99,7 +103,8 @@ public: static TER send(Args&&... args) { - return accountSend(std::forward(args)..., WaiveTransferFee::Yes); + return accountSend( + std::forward(args)..., WaiveTransferFee::Yes, AllowMPTOverflow::Yes); } bool diff --git a/include/xrpl/tx/paths/Flow.h b/include/xrpl/tx/paths/Flow.h index f37c9d56c3..c746249866 100644 --- a/include/xrpl/tx/paths/Flow.h +++ b/include/xrpl/tx/paths/Flow.h @@ -6,11 +6,9 @@ namespace xrpl { -namespace path { -namespace detail { +namespace path::detail { struct FlowDebugInfo; -} // namespace detail -} // namespace path +} // namespace path::detail /** Make a payment from the src account to the dst account diff --git a/include/xrpl/tx/paths/Offer.h b/include/xrpl/tx/paths/Offer.h index de3eed933a..f79f33658b 100644 --- a/include/xrpl/tx/paths/Offer.h +++ b/include/xrpl/tx/paths/Offer.h @@ -3,37 +3,27 @@ #include #include #include +#include +#include #include #include #include #include #include +#include namespace xrpl { -template -class TOfferBase -{ -protected: - Issue issIn_; - Issue issOut_; -}; - -template <> -class TOfferBase -{ -public: - explicit TOfferBase() = default; -}; - -template -class TOffer : private TOfferBase +template +class TOffer { private: SLE::pointer m_entry; Quality m_quality{}; AccountID m_account; + Asset assetIn_; + Asset assetOut_; TAmounts m_amounts{}; void @@ -42,7 +32,7 @@ private: public: TOffer() = default; - TOffer(SLE::pointer const& entry, Quality quality); + TOffer(SLE::pointer entry, Quality quality); /** Returns the quality of the offer. Conceptually, the quality is the ratio of output to input currency. @@ -113,10 +103,10 @@ public: return m_entry->key(); } - Issue const& - issueIn() const; - Issue const& - issueOut() const; + Asset const& + assetIn() const; + Asset const& + assetOut() const; TAmounts limitOut(TAmounts const& offerAmount, TOut const& limit, bool roundUp) const; @@ -131,8 +121,8 @@ public: bool isFunded() const { - // Offer owner is issuer; they have unlimited funds - return m_account == issueOut().account; + // Offer owner is issuer; they have unlimited funds if IOU + return m_account == assetOut_.getIssuer() && assetOut_.holds(); } static std::pair @@ -167,43 +157,42 @@ public: } }; -using Offer = TOffer<>; - -template -TOffer::TOffer(SLE::pointer const& entry, Quality quality) - : m_entry(entry), m_quality(quality), m_account(m_entry->getAccountID(sfAccount)) +template +TOffer::TOffer(SLE::pointer entry, Quality quality) + : m_entry(std::move(entry)), m_quality(quality), m_account(m_entry->getAccountID(sfAccount)) { auto const tp = m_entry->getFieldAmount(sfTakerPays); auto const tg = m_entry->getFieldAmount(sfTakerGets); m_amounts.in = toAmount(tp); m_amounts.out = toAmount(tg); - this->issIn_ = tp.issue(); - this->issOut_ = tg.issue(); + assetIn_ = tp.asset(); + assetOut_ = tg.asset(); } -template <> -inline TOffer::TOffer(SLE::pointer const& entry, Quality quality) - : m_entry(entry) - , m_quality(quality) - , m_account(m_entry->getAccountID(sfAccount)) - , m_amounts(m_entry->getFieldAmount(sfTakerPays), m_entry->getFieldAmount(sfTakerGets)) -{ -} - -template +template void TOffer::setFieldAmounts() { - // LCOV_EXCL_START -#ifdef _MSC_VER - UNREACHABLE("xrpl::TOffer::setFieldAmounts : must be specialized"); -#else - static_assert(sizeof(TOut) == -1, "Must be specialized"); -#endif - // LCOV_EXCL_STOP + if constexpr (std::is_same_v) + { + m_entry->setFieldAmount(sfTakerPays, toSTAmount(m_amounts.in)); + } + else + { + m_entry->setFieldAmount(sfTakerPays, toSTAmount(m_amounts.in, assetIn_)); + } + + if constexpr (std::is_same_v) + { + m_entry->setFieldAmount(sfTakerGets, toSTAmount(m_amounts.out)); + } + else + { + m_entry->setFieldAmount(sfTakerGets, toSTAmount(m_amounts.out, assetOut_)); + } } -template +template TAmounts TOffer::limitOut(TAmounts const& offerAmount, TOut const& limit, bool roundUp) const @@ -213,90 +202,46 @@ TOffer::limitOut(TAmounts const& offerAmount, TOut const& return quality().ceil_out_strict(offerAmount, limit, roundUp); } -template +template TAmounts TOffer::limitIn(TAmounts const& offerAmount, TIn const& limit, bool roundUp) const { if (auto const& rules = getCurrentTransactionRules(); rules && rules->enabled(fixReducedOffersV2)) + { // It turns out that the ceil_in implementation has some slop in // it. ceil_in_strict removes that slop. But removing that slop // affects transaction outcomes, so the change must be made using // an amendment. return quality().ceil_in_strict(offerAmount, limit, roundUp); + } return m_quality.ceil_in(offerAmount, limit); } -template +template template TER TOffer::send(Args&&... args) { - return accountSend(std::forward(args)...); + return accountSend(std::forward(args)..., WaiveTransferFee::No, AllowMPTOverflow::Yes); } -template <> -inline void -TOffer::setFieldAmounts() +template +Asset const& +TOffer::assetIn() const { - m_entry->setFieldAmount(sfTakerPays, m_amounts.in); - m_entry->setFieldAmount(sfTakerGets, m_amounts.out); + return assetIn_; } -template <> -inline void -TOffer::setFieldAmounts() +template +Asset const& +TOffer::assetOut() const { - m_entry->setFieldAmount(sfTakerPays, toSTAmount(m_amounts.in, issIn_)); - m_entry->setFieldAmount(sfTakerGets, toSTAmount(m_amounts.out, issOut_)); + return assetOut_; } -template <> -inline void -TOffer::setFieldAmounts() -{ - m_entry->setFieldAmount(sfTakerPays, toSTAmount(m_amounts.in, issIn_)); - m_entry->setFieldAmount(sfTakerGets, toSTAmount(m_amounts.out)); -} - -template <> -inline void -TOffer::setFieldAmounts() -{ - m_entry->setFieldAmount(sfTakerPays, toSTAmount(m_amounts.in)); - m_entry->setFieldAmount(sfTakerGets, toSTAmount(m_amounts.out, issOut_)); -} - -template -Issue const& -TOffer::issueIn() const -{ - return this->issIn_; -} - -template <> -inline Issue const& -TOffer::issueIn() const -{ - return m_amounts.in.issue(); -} - -template -Issue const& -TOffer::issueOut() const -{ - return this->issOut_; -} - -template <> -inline Issue const& -TOffer::issueOut() const -{ - return m_amounts.out.issue(); -} - -template +template inline std::ostream& operator<<(std::ostream& os, TOffer const& offer) { diff --git a/include/xrpl/tx/paths/OfferStream.h b/include/xrpl/tx/paths/OfferStream.h index ba553360ef..e40387e3d2 100644 --- a/include/xrpl/tx/paths/OfferStream.h +++ b/include/xrpl/tx/paths/OfferStream.h @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -11,7 +12,7 @@ namespace xrpl { -template +template class TOfferStreamBase { public: @@ -64,6 +65,7 @@ protected: permRmOffer(uint256 const& offerIndex) = 0; template + requires ValidTaker bool shouldRmSmallIncreasedQOffer() const; @@ -105,33 +107,6 @@ public: } }; -/** Presents and consumes the offers in an order book. - - Two `ApplyView` objects accumulate changes to the ledger. `view` - is applied when the calling transaction succeeds. If the calling - transaction fails, then `view_cancel` is applied. - - Certain invalid offers are automatically removed: - - Offers with missing ledger entries - - Offers that expired - - Offers found unfunded: - An offer is found unfunded when the corresponding balance is zero - and the caller has not modified the balance. This is accomplished - by also looking up the balance in the cancel view. - - When an offer is removed, it is removed from both views. This grooms the - order book regardless of whether or not the transaction is successful. -*/ -class OfferStream : public TOfferStreamBase -{ -protected: - void - permRmOffer(uint256 const& offerIndex) override; - -public: - using TOfferStreamBase::TOfferStreamBase; -}; - /** Presents and consumes the offers in an order book. The `view_' ` `ApplyView` accumulates changes to the ledger. @@ -149,7 +124,7 @@ public: and the caller has not modified the balance. This is accomplished by also looking up the balance in the cancel view. */ -template +template class FlowOfferStream : public TOfferStreamBase { private: diff --git a/include/xrpl/tx/paths/detail/AmountSpec.h b/include/xrpl/tx/paths/detail/AmountSpec.h index 1adee6a0a3..e69de29bb2 100644 --- a/include/xrpl/tx/paths/detail/AmountSpec.h +++ b/include/xrpl/tx/paths/detail/AmountSpec.h @@ -1,198 +0,0 @@ -#pragma once - -#include -#include -#include - -#include - -namespace xrpl { - -struct AmountSpec -{ - explicit AmountSpec() = default; - - bool native{}; - union - { - XRPAmount xrp; - IOUAmount iou = {}; - }; - std::optional issuer; - std::optional currency; - - friend std::ostream& - operator<<(std::ostream& stream, AmountSpec const& amt) - { - if (amt.native) - stream << to_string(amt.xrp); - else - stream << to_string(amt.iou); - if (amt.currency) - stream << "/(" << *amt.currency << ")"; - if (amt.issuer) - stream << "/" << *amt.issuer << ""; - return stream; - } -}; - -struct EitherAmount -{ -#ifndef NDEBUG - bool native = false; -#endif - - union - { - IOUAmount iou = {}; - XRPAmount xrp; - }; - - EitherAmount() = default; - - explicit EitherAmount(IOUAmount const& a) : iou(a) - { - } - -#if defined(__GNUC__) && !defined(__clang__) -#pragma GCC diagnostic push - // ignore warning about half of iou amount being uninitialized -#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" -#endif - explicit EitherAmount(XRPAmount const& a) : xrp(a) - { -#ifndef NDEBUG - native = true; -#endif - } -#if defined(__GNUC__) && !defined(__clang__) -#pragma GCC diagnostic pop -#endif - - explicit EitherAmount(AmountSpec const& a) - { -#ifndef NDEBUG - native = a.native; -#endif - if (a.native) - xrp = a.xrp; - else - iou = a.iou; - } - -#ifndef NDEBUG - friend std::ostream& - operator<<(std::ostream& stream, EitherAmount const& amt) - { - if (amt.native) - stream << to_string(amt.xrp); - else - stream << to_string(amt.iou); - return stream; - } -#endif -}; - -template -T& -get(EitherAmount& amt) -{ - static_assert(sizeof(T) == -1, "Must used specialized function"); - return T(0); -} - -template <> -inline IOUAmount& -get(EitherAmount& amt) -{ - XRPL_ASSERT(!amt.native, "xrpl::get(EitherAmount&) : is not XRP"); - return amt.iou; -} - -template <> -inline XRPAmount& -get(EitherAmount& amt) -{ - XRPL_ASSERT(amt.native, "xrpl::get(EitherAmount&) : is XRP"); - return amt.xrp; -} - -template -T const& -get(EitherAmount const& amt) -{ - static_assert(sizeof(T) == -1, "Must used specialized function"); - return T(0); -} - -template <> -inline IOUAmount const& -get(EitherAmount const& amt) -{ - XRPL_ASSERT(!amt.native, "xrpl::get(EitherAmount const&) : is not XRP"); - return amt.iou; -} - -template <> -inline XRPAmount const& -get(EitherAmount const& amt) -{ - XRPL_ASSERT(amt.native, "xrpl::get(EitherAmount const&) : is XRP"); - return amt.xrp; -} - -inline AmountSpec -toAmountSpec(STAmount const& amt) -{ - XRPL_ASSERT( - amt.mantissa() < std::numeric_limits::max(), - "xrpl::toAmountSpec(STAmount const&) : maximum mantissa"); - bool const isNeg = amt.negative(); - std::int64_t const sMant = isNeg ? -std::int64_t(amt.mantissa()) : amt.mantissa(); - AmountSpec result; - - result.native = isXRP(amt); - if (result.native) - { - result.xrp = XRPAmount(sMant); - } - else - { - result.iou = IOUAmount(sMant, amt.exponent()); - result.issuer = amt.issue().account; - result.currency = amt.issue().currency; - } - - return result; -} - -inline EitherAmount -toEitherAmount(STAmount const& amt) -{ - if (isXRP(amt)) - return EitherAmount{amt.xrp()}; - return EitherAmount{amt.iou()}; -} - -inline AmountSpec -toAmountSpec(EitherAmount const& ea, std::optional const& c) -{ - AmountSpec r; - r.native = (!c || isXRP(*c)); - r.currency = c; - XRPL_ASSERT( - ea.native == r.native, - "xrpl::toAmountSpec(EitherAmount const&&, std::optional) : " - "matching native"); - if (r.native) - { - r.xrp = ea.xrp; - } - else - { - r.iou = ea.iou; - } - return r; -} - -} // namespace xrpl diff --git a/include/xrpl/tx/paths/detail/EitherAmount.h b/include/xrpl/tx/paths/detail/EitherAmount.h new file mode 100644 index 0000000000..ffd90751b8 --- /dev/null +++ b/include/xrpl/tx/paths/detail/EitherAmount.h @@ -0,0 +1,54 @@ +#pragma once + +#include +#include +#include +#include + +namespace xrpl { + +struct EitherAmount +{ + std::variant amount; + + explicit EitherAmount() = default; + + template + explicit EitherAmount(T const& a) : amount(a) + { + } + + template + [[nodiscard]] bool + holds() const + { + return std::holds_alternative(amount); + } + + template + [[nodiscard]] T const& + get() const + { + if (!holds()) + Throw("EitherAmount doesn't hold requested amount"); + return std::get(amount); + } + +#ifndef NDEBUG + friend std::ostream& + operator<<(std::ostream& stream, EitherAmount const& amt) + { + std::visit([&](T const& a) { stream << to_string(a); }, amt.amount); + return stream; + } +#endif +}; + +template +T const& +get(EitherAmount const& amt) +{ + return amt.get(); +} + +} // namespace xrpl diff --git a/include/xrpl/tx/paths/detail/FlowDebugInfo.h b/include/xrpl/tx/paths/detail/FlowDebugInfo.h index d2d9dc8466..dd7a0bb9c5 100644 --- a/include/xrpl/tx/paths/detail/FlowDebugInfo.h +++ b/include/xrpl/tx/paths/detail/FlowDebugInfo.h @@ -11,9 +11,7 @@ #include #include -namespace xrpl { -namespace path { -namespace detail { +namespace xrpl::path::detail { // Track performance information of a single payment struct FlowDebugInfo { @@ -208,18 +206,19 @@ struct FlowDebugInfo auto writeXrpAmtList = [&write_list]( std::vector const& amts, char delim = ';') { auto get_val = [](EitherAmount const& a) -> std::string { - return xrpl::to_string(a.xrp); + return xrpl::to_string(a.get()); }; write_list(amts, get_val, delim); }; auto writeIouAmtList = [&write_list]( std::vector const& amts, char delim = ';') { auto get_val = [](EitherAmount const& a) -> std::string { - return xrpl::to_string(a.iou); + return xrpl::to_string(a.get()); }; write_list(amts, get_val, delim); }; auto writeIntList = [&write_list](std::vector const& vals, char delim = ';') { + // NOLINTNEXTLINE(bugprone-return-const-ref-from-parameter) auto get_val = [](size_t const& v) -> size_t const& { return v; }; write_list(vals, get_val); }; @@ -254,28 +253,44 @@ struct FlowDebugInfo ostr << ", in_pass: "; if (passInfo.nativeIn) + { writeXrpAmtList(passInfo.in); + } else + { writeIouAmtList(passInfo.in); + } ostr << ", out_pass: "; if (passInfo.nativeOut) + { writeXrpAmtList(passInfo.out); + } else + { writeIouAmtList(passInfo.out); + } ostr << ", num_active: "; writeIntList(passInfo.numActive); if (!passInfo.liquiditySrcIn.empty() && !passInfo.liquiditySrcIn.back().empty()) { ostr << ", l_src_in: "; if (passInfo.nativeIn) + { writeNestedXrpAmtList(passInfo.liquiditySrcIn); + } else + { writeNestedIouAmtList(passInfo.liquiditySrcIn); + } ostr << ", l_src_out: "; if (passInfo.nativeOut) + { writeNestedXrpAmtList(passInfo.liquiditySrcOut); + } else + { writeNestedIouAmtList(passInfo.liquiditySrcOut); + } } } @@ -335,6 +350,4 @@ balanceDiffsToString(std::optional const& bd) return ostr.str(); }; -} // namespace detail -} // namespace path -} // namespace xrpl +} // namespace xrpl::path::detail diff --git a/include/xrpl/tx/paths/detail/StepChecks.h b/include/xrpl/tx/paths/detail/StepChecks.h index c866705a4c..a1e6490781 100644 --- a/include/xrpl/tx/paths/detail/StepChecks.h +++ b/include/xrpl/tx/paths/detail/StepChecks.h @@ -50,8 +50,7 @@ checkFreeze( if (!sleAmm) return tecINTERNAL; // LCOV_EXCL_LINE - if (isLPTokenFrozen( - view, src, (*sleAmm)[sfAsset].get(), (*sleAmm)[sfAsset2].get())) + if (isLPTokenFrozen(view, src, (*sleAmm)[sfAsset], (*sleAmm)[sfAsset2])) { return terNO_LINE; } @@ -78,8 +77,8 @@ checkNoRipple( if (!sleIn || !sleOut) return terNO_LINE; - if ((*sleIn)[sfFlags] & ((cur > prev) ? lsfHighNoRipple : lsfLowNoRipple) && - (*sleOut)[sfFlags] & ((cur > next) ? lsfHighNoRipple : lsfLowNoRipple)) + if ((((*sleIn)[sfFlags] & ((cur > prev) ? lsfHighNoRipple : lsfLowNoRipple)) != 0u) && + (((*sleOut)[sfFlags] & ((cur > next) ? lsfHighNoRipple : lsfLowNoRipple)) != 0u)) { JLOG(j.info()) << "Path violates noRipple constraint between " << prev << ", " << cur << " and " << next; diff --git a/include/xrpl/tx/paths/detail/Steps.h b/include/xrpl/tx/paths/detail/Steps.h index 0b75974acb..c46cebca88 100644 --- a/include/xrpl/tx/paths/detail/Steps.h +++ b/include/xrpl/tx/paths/detail/Steps.h @@ -2,11 +2,11 @@ #include #include +#include #include #include -#include #include -#include +#include #include @@ -44,6 +44,7 @@ issues(DebtDirection dir) BookStepIX is an IOU/XRP offer book BookStepXI is an XRP/IOU offer book XRPEndpointStep is the source or destination account for XRP + MPTEndpointStep is the source or destination account for MPT Amounts may be transformed through a step in either the forward or the reverse direction. In the forward direction, the function `fwd` is used to @@ -287,10 +288,10 @@ private: inline std::pair, DebtDirection> Step::getQualityFunc(ReadView const& v, DebtDirection prevStepDir) const { - if (auto const res = qualityUpperBound(v, prevStepDir); res.first) + auto const res = qualityUpperBound(v, prevStepDir); + if (res.first) return {QualityFunction{*res.first, QualityFunction::CLOBLikeTag{}}, res.second}; - else - return {std::nullopt, res.second}; + return {std::nullopt, res.second}; } /// @cond INTERNAL @@ -316,8 +317,10 @@ operator==(Strand const& lhs, Strand const& rhs) if (lhs.size() != rhs.size()) return false; for (size_t i = 0, e = lhs.size(); i != e; ++i) + { if (*lhs[i] != *rhs[i]) return false; + } return true; } /// @endcond @@ -339,8 +342,8 @@ std::pair normalizePath( AccountID const& src, AccountID const& dst, - Issue const& deliver, - std::optional const& sendMaxIssue, + Asset const& deliver, + std::optional const& sendMaxAsset, STPath const& path); /** @@ -355,7 +358,7 @@ normalizePath( optimization. If, during direct offer crossing, the quality of the tip of the book drops below this value, then evaluating the strand can stop. - @param sendMaxIssue Optional asset to send. + @param sendMaxAsset Optional asset to send. @param path Liquidity sources to use for this strand of the payment. The path contains an ordered collection of the offer books to use and accounts to ripple through. @@ -372,9 +375,9 @@ toStrand( ReadView const& sb, AccountID const& src, AccountID const& dst, - Issue const& deliver, + Asset const& deliver, std::optional const& limitQuality, - std::optional const& sendMaxIssue, + std::optional const& sendMaxAsset, STPath const& path, bool ownerPaysTransferFee, OfferCrossing offerCrossing, @@ -413,9 +416,9 @@ toStrands( ReadView const& sb, AccountID const& src, AccountID const& dst, - Issue const& deliver, + Asset const& deliver, std::optional const& limitQuality, - std::optional const& sendMax, + std::optional const& sendMax, STPathSet const& paths, bool addDefaultPath, bool ownerPaysTransferFee, @@ -425,11 +428,13 @@ toStrands( beast::Journal j); /// @cond INTERNAL -template +template struct StepImp : public Step { +private: explicit StepImp() = default; +public: std::pair rev(PaymentSandbox& sb, ApplyView& afView, @@ -469,6 +474,7 @@ struct StepImp : public Step { return get(lhs) == get(rhs); } + friend TDerived; }; /// @endcond @@ -493,8 +499,16 @@ public: // Check equal with tolerance bool checkNear(IOUAmount const& expected, IOUAmount const& actual); -bool -checkNear(XRPAmount const& expected, XRPAmount const& actual); +inline bool +checkNear(MPTAmount const& expected, MPTAmount const& actual) +{ + return expected == actual; +} +inline bool +checkNear(XRPAmount const& expected, XRPAmount const& actual) +{ + return expected == actual; +} /// @endcond /** @@ -505,7 +519,7 @@ struct StrandContext ReadView const& view; ///< Current ReadView AccountID const strandSrc; ///< Strand source account AccountID const strandDst; ///< Strand destination account - Issue const strandDeliver; ///< Issue strand delivers + Asset const strandDeliver; ///< Asset strand delivers std::optional const limitQuality; ///< Worst accepted quality bool const isFirst; ///< true if Step is first in Strand bool const isLast = false; ///< true if Step is last in Strand @@ -522,11 +536,11 @@ struct StrandContext at most twice: once as a src and once as a dst (hence the two element array). The strandSrc and strandDst will only show up once each. */ - std::array, 2>& seenDirectIssues; + std::array, 2>& seenDirectAssets; /** A strand may not include an offer that output the same issue more than once */ - boost::container::flat_set& seenBookOuts; + boost::container::flat_set& seenBookOuts; AMMContext& ammContext; std::optional domainID; // the domain the order book will use beast::Journal const j; @@ -539,15 +553,15 @@ struct StrandContext // replicates the source or destination. AccountID const& strandSrc_, AccountID const& strandDst_, - Issue const& strandDeliver_, + Asset const& strandDeliver_, std::optional const& limitQuality_, bool isLast_, bool ownerPaysTransferFee_, OfferCrossing offerCrossing_, bool isDefaultPath_, - std::array, 2>& - seenDirectIssues_, ///< For detecting currency loops - boost::container::flat_set& seenBookOuts_, ///< For detecting book loops + std::array, 2>& + seenDirectAssets_, ///< For detecting currency loops + boost::container::flat_set& seenBookOuts_, ///< For detecting book loops AMMContext& ammContext_, std::optional const& domainID, beast::Journal j_); ///< Journal for logging @@ -563,6 +577,13 @@ directStepEqual( AccountID const& dst, Currency const& currency); +bool +mptEndpointStepEqual( + Step const& step, + AccountID const& src, + AccountID const& dst, + MPTID const& mptid); + bool xrpEndpointStepEqual(Step const& step, AccountID const& acc); @@ -577,6 +598,13 @@ make_DirectStepI( AccountID const& dst, Currency const& c); +std::pair> +make_MPTEndpointStep( + StrandContext const& ctx, + AccountID const& src, + AccountID const& dst, + MPTID const& a); + std::pair> make_BookStepII(StrandContext const& ctx, Issue const& in, Issue const& out); @@ -589,9 +617,34 @@ make_BookStepXI(StrandContext const& ctx, Issue const& out); std::pair> make_XRPEndpointStep(StrandContext const& ctx, AccountID const& acc); -template +std::pair> +make_BookStepMM(StrandContext const& ctx, MPTIssue const& in, MPTIssue const& out); + +std::pair> +make_BookStepMX(StrandContext const& ctx, MPTIssue const& in); + +std::pair> +make_BookStepXM(StrandContext const& ctx, MPTIssue const& out); + +std::pair> +make_BookStepMI(StrandContext const& ctx, MPTIssue const& in, Issue const& out); + +std::pair> +make_BookStepIM(StrandContext const& ctx, Issue const& in, MPTIssue const& out); + +template bool -isDirectXrpToXrp(Strand const& strand); +isDirectXrpToXrp(Strand const& strand) +{ + if constexpr (std::is_same_v && std::is_same_v) + { + return strand.size() == 2; + } + else + { + return false; + } +} /// @endcond } // namespace xrpl diff --git a/include/xrpl/tx/paths/detail/StrandFlow.h b/include/xrpl/tx/paths/detail/StrandFlow.h index fba631c695..52b7d94484 100644 --- a/include/xrpl/tx/paths/detail/StrandFlow.h +++ b/include/xrpl/tx/paths/detail/StrandFlow.h @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -13,7 +14,6 @@ #include #include #include -#include #include @@ -246,7 +246,7 @@ flow( EitherAmount stepIn(*strand[0]->cachedIn()); for (auto i = 0; i < s; ++i) { - bool valid; + bool valid = false; std::tie(valid, stepIn) = strand[i]->validFwd(checkSB, checkAfView, stepIn); if (!valid) { @@ -327,9 +327,13 @@ qualityUpperBound(ReadView const& v, Strand const& strand) for (auto const& step : strand) { if (std::tie(stepQ, dir) = step->qualityUpperBound(v, dir); stepQ) + { q = composed_quality(q, *stepQ); + } else + { return std::nullopt; + } } return q; }; @@ -360,12 +364,18 @@ limitOut( if (std::tie(stepQualityFunc, dir) = step->getQualityFunc(v, dir); stepQualityFunc) { if (!qf) + { qf = stepQualityFunc; + } else + { qf->combine(*stepQualityFunc); + } } else + { return remainingOut; + } } // QualityFunction is constant @@ -373,14 +383,25 @@ limitOut( return remainingOut; auto const out = [&]() { - if (auto const out = qf->outFromAvgQ(limitQuality); !out) + auto const out = qf->outFromAvgQ(limitQuality); + if (!out) return remainingOut; - else if constexpr (std::is_same_v) + if constexpr (std::is_same_v) + { return XRPAmount{*out}; + } else if constexpr (std::is_same_v) + { return IOUAmount{*out}; + } + else if constexpr (std::is_same_v) + { + return MPTAmount{*out}; + } else - return STAmount{remainingOut.issue(), out->mantissa(), out->exponent()}; + { + return STAmount{remainingOut.asset(), out->mantissa(), out->exponent()}; + } }(); // A tiny difference could be due to the round off if (withinRelativeDistance(out, remainingOut, Number(1, -9))) @@ -430,7 +451,7 @@ public: { for (Strand const* strand : next_) { - if (!strand) + if (strand == nullptr) { // should not happen continue; @@ -447,14 +468,14 @@ public: // an unusual corner case. continue; } - strandQualities.push_back({*qual, strand}); + strandQualities.emplace_back(*qual, strand); } } // must stable sort for deterministic order across different c++ // standard library implementations - std::stable_sort( - strandQualities.begin(), - strandQualities.end(), + std::ranges::stable_sort( + strandQualities, + [](auto const& lhs, auto const& rhs) { // higher qualities first return std::get(lhs) > std::get(rhs); @@ -534,7 +555,7 @@ public: @return Actual amount in and out from the strands, errors, and payment sandbox */ -template +template FlowResult flow( PaymentSandbox const& baseView, @@ -625,8 +646,10 @@ flow( // Limit only if one strand and limitQuality auto const limitRemainingOut = [&]() { if (activeStrands.size() == 1 && limitQuality) + { if (auto const strand = activeStrands.get(0)) return limitOut(sb, *strand, remainingOut, *limitQuality); + } return remainingOut; }(); auto const adjustedRemOut = limitRemainingOut != remainingOut; @@ -715,8 +738,10 @@ flow( remainingIn = *sendMax - sum(savedIns); if (flowDebugInfo) + { flowDebugInfo->pushPass( EitherAmount(best->in), EitherAmount(best->out), activeStrands.size()); + } JLOG(j.trace()) << "Best path: in: " << to_string(best->in) << " out: " << to_string(best->out) @@ -771,7 +796,7 @@ flow( { // Rounding in the payment engine is causing this assert to // sometimes fire with "dust" amounts. This is causing issues when - // running debug builds of rippled. While this issue still needs to + // running debug builds of xrpld. While this issue still needs to // be resolved, the assert is causing more harm than good at this // point. // UNREACHABLE("xrpl::flow : rounding error"); diff --git a/include/xrpl/tx/transactors/account/AccountDelete.h b/include/xrpl/tx/transactors/account/AccountDelete.h index 81a11152ed..23fc55da48 100644 --- a/include/xrpl/tx/transactors/account/AccountDelete.h +++ b/include/xrpl/tx/transactors/account/AccountDelete.h @@ -27,6 +27,20 @@ public: TER doApply() override; + + void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; }; } // namespace xrpl diff --git a/include/xrpl/tx/transactors/account/AccountSet.h b/include/xrpl/tx/transactors/account/AccountSet.h index 0940ab0739..fd2106e20c 100644 --- a/include/xrpl/tx/transactors/account/AccountSet.h +++ b/include/xrpl/tx/transactors/account/AccountSet.h @@ -31,6 +31,20 @@ public: TER doApply() override; + + void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; }; } // namespace xrpl diff --git a/include/xrpl/tx/transactors/account/SetRegularKey.h b/include/xrpl/tx/transactors/account/SetRegularKey.h index bb1dd48a68..54e574939a 100644 --- a/include/xrpl/tx/transactors/account/SetRegularKey.h +++ b/include/xrpl/tx/transactors/account/SetRegularKey.h @@ -21,6 +21,20 @@ public: TER doApply() override; + + void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; }; } // namespace xrpl diff --git a/include/xrpl/tx/transactors/account/SignerListSet.h b/include/xrpl/tx/transactors/account/SignerListSet.h index 2529f0d36c..1f1a68ccc8 100644 --- a/include/xrpl/tx/transactors/account/SignerListSet.h +++ b/include/xrpl/tx/transactors/account/SignerListSet.h @@ -41,6 +41,20 @@ public: void preCompute() override; + void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; + // Interface used by AccountDelete static TER removeFromLedger( diff --git a/include/xrpl/tx/transactors/bridge/XChainBridge.h b/include/xrpl/tx/transactors/bridge/XChainBridge.h index 0a2fccc18b..cd16c0b791 100644 --- a/include/xrpl/tx/transactors/bridge/XChainBridge.h +++ b/include/xrpl/tx/transactors/bridge/XChainBridge.h @@ -26,6 +26,20 @@ public: TER doApply() override; + + void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; }; class BridgeModify : public Transactor @@ -48,6 +62,20 @@ public: TER doApply() override; + + void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; }; using XChainModifyBridge = BridgeModify; @@ -81,6 +109,20 @@ public: TER doApply() override; + + void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; }; //------------------------------------------------------------------------------ @@ -108,6 +150,20 @@ public: TER doApply() override; + + void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; }; //------------------------------------------------------------------------------ @@ -137,6 +193,20 @@ public: TER doApply() override; + + void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; }; //------------------------------------------------------------------------------ @@ -166,6 +236,20 @@ public: TER doApply() override; + + void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; }; class XChainAddAccountCreateAttestation : public Transactor @@ -186,6 +270,20 @@ public: TER doApply() override; + + void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; }; //------------------------------------------------------------------------------ @@ -230,6 +328,20 @@ public: TER doApply() override; + + void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; }; using XChainAccountCreateCommit = XChainCreateAccountCommit; diff --git a/include/xrpl/tx/transactors/check/CheckCancel.h b/include/xrpl/tx/transactors/check/CheckCancel.h index f16c6d2827..14abeaa978 100644 --- a/include/xrpl/tx/transactors/check/CheckCancel.h +++ b/include/xrpl/tx/transactors/check/CheckCancel.h @@ -21,6 +21,20 @@ public: TER doApply() override; + + void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; }; } // namespace xrpl diff --git a/include/xrpl/tx/transactors/check/CheckCash.h b/include/xrpl/tx/transactors/check/CheckCash.h index c64d45061d..312c5a983e 100644 --- a/include/xrpl/tx/transactors/check/CheckCash.h +++ b/include/xrpl/tx/transactors/check/CheckCash.h @@ -13,6 +13,9 @@ public: { } + static bool + checkExtraFeatures(PreflightContext const& ctx); + static NotTEC preflight(PreflightContext const& ctx); @@ -21,6 +24,20 @@ public: TER doApply() override; + + void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; }; } // namespace xrpl diff --git a/include/xrpl/tx/transactors/check/CheckCreate.h b/include/xrpl/tx/transactors/check/CheckCreate.h index 2b5aa9123d..ff24d36e2c 100644 --- a/include/xrpl/tx/transactors/check/CheckCreate.h +++ b/include/xrpl/tx/transactors/check/CheckCreate.h @@ -13,6 +13,9 @@ public: { } + static bool + checkExtraFeatures(xrpl::PreflightContext const& ctx); + static NotTEC preflight(PreflightContext const& ctx); @@ -21,6 +24,20 @@ public: TER doApply() override; + + void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; }; } // namespace xrpl diff --git a/include/xrpl/tx/transactors/credentials/CredentialAccept.h b/include/xrpl/tx/transactors/credentials/CredentialAccept.h index 1895e54589..f3abc80666 100644 --- a/include/xrpl/tx/transactors/credentials/CredentialAccept.h +++ b/include/xrpl/tx/transactors/credentials/CredentialAccept.h @@ -24,6 +24,20 @@ public: TER doApply() override; + + void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; }; } // namespace xrpl diff --git a/include/xrpl/tx/transactors/credentials/CredentialCreate.h b/include/xrpl/tx/transactors/credentials/CredentialCreate.h index 500b034a9c..22247feb36 100644 --- a/include/xrpl/tx/transactors/credentials/CredentialCreate.h +++ b/include/xrpl/tx/transactors/credentials/CredentialCreate.h @@ -24,6 +24,20 @@ public: TER doApply() override; + + void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; }; } // namespace xrpl diff --git a/include/xrpl/tx/transactors/credentials/CredentialDelete.h b/include/xrpl/tx/transactors/credentials/CredentialDelete.h index 26305232ce..91ea4d05ad 100644 --- a/include/xrpl/tx/transactors/credentials/CredentialDelete.h +++ b/include/xrpl/tx/transactors/credentials/CredentialDelete.h @@ -24,6 +24,20 @@ public: TER doApply() override; + + void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; }; } // namespace xrpl diff --git a/include/xrpl/tx/transactors/delegate/DelegateSet.h b/include/xrpl/tx/transactors/delegate/DelegateSet.h index cc3a8fe4cb..25cb21e9b5 100644 --- a/include/xrpl/tx/transactors/delegate/DelegateSet.h +++ b/include/xrpl/tx/transactors/delegate/DelegateSet.h @@ -22,6 +22,20 @@ public: TER doApply() override; + void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; + // Interface used by AccountDelete static TER deleteDelegate( diff --git a/include/xrpl/tx/transactors/dex/AMMBid.h b/include/xrpl/tx/transactors/dex/AMMBid.h index b80bfe3bef..e872c0d12d 100644 --- a/include/xrpl/tx/transactors/dex/AMMBid.h +++ b/include/xrpl/tx/transactors/dex/AMMBid.h @@ -62,6 +62,20 @@ public: TER doApply() override; + + void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; }; } // namespace xrpl diff --git a/include/xrpl/tx/transactors/dex/AMMClawback.h b/include/xrpl/tx/transactors/dex/AMMClawback.h index 2bfccfa202..4f44da60d2 100644 --- a/include/xrpl/tx/transactors/dex/AMMClawback.h +++ b/include/xrpl/tx/transactors/dex/AMMClawback.h @@ -13,6 +13,9 @@ public: { } + static bool + checkExtraFeatures(PreflightContext const& ctx); + static std::uint32_t getFlagsMask(PreflightContext const& ctx); @@ -25,6 +28,20 @@ public: TER doApply() override; + void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; + private: TER applyGuts(Sandbox& view); diff --git a/include/xrpl/tx/transactors/dex/AMMCreate.h b/include/xrpl/tx/transactors/dex/AMMCreate.h index 5deaa129ed..a3ee083b9a 100644 --- a/include/xrpl/tx/transactors/dex/AMMCreate.h +++ b/include/xrpl/tx/transactors/dex/AMMCreate.h @@ -58,6 +58,20 @@ public: /** Attempt to create the AMM instance. */ TER doApply() override; + + void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; }; } // namespace xrpl diff --git a/include/xrpl/tx/transactors/dex/AMMDelete.h b/include/xrpl/tx/transactors/dex/AMMDelete.h index 1c0996f8a2..a1708eec88 100644 --- a/include/xrpl/tx/transactors/dex/AMMDelete.h +++ b/include/xrpl/tx/transactors/dex/AMMDelete.h @@ -30,6 +30,20 @@ public: TER doApply() override; + + void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; }; } // namespace xrpl diff --git a/include/xrpl/tx/transactors/dex/AMMDeposit.h b/include/xrpl/tx/transactors/dex/AMMDeposit.h index 287d46ff07..87f2871d27 100644 --- a/include/xrpl/tx/transactors/dex/AMMDeposit.h +++ b/include/xrpl/tx/transactors/dex/AMMDeposit.h @@ -63,6 +63,20 @@ public: TER doApply() override; + void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; + private: std::pair applyGuts(Sandbox& view); @@ -224,7 +238,7 @@ private: AccountID const& ammAccount, STAmount const& amount, STAmount const& amount2, - Issue const& lptIssue, + Asset const& lptIssue, std::uint16_t tfee); }; diff --git a/include/xrpl/tx/transactors/dex/AMMUtils.h b/include/xrpl/tx/transactors/dex/AMMUtils.h deleted file mode 100644 index 77ad18106e..0000000000 --- a/include/xrpl/tx/transactors/dex/AMMUtils.h +++ /dev/null @@ -1,106 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include - -namespace xrpl { - -class ReadView; -class ApplyView; -class Sandbox; -class NetClock; - -/** Get AMM pool balances. - */ -std::pair -ammPoolHolds( - ReadView const& view, - AccountID const& ammAccountID, - Issue const& issue1, - Issue const& issue2, - FreezeHandling freezeHandling, - beast::Journal const j); - -/** Get AMM pool and LP token balances. If both optIssue are - * provided then they are used as the AMM token pair issues. - * Otherwise the missing issues are fetched from ammSle. - */ -Expected, TER> -ammHolds( - ReadView const& view, - SLE const& ammSle, - std::optional const& optIssue1, - std::optional const& optIssue2, - FreezeHandling freezeHandling, - beast::Journal const j); - -/** Get the balance of LP tokens. - */ -STAmount -ammLPHolds( - ReadView const& view, - Currency const& cur1, - Currency const& cur2, - AccountID const& ammAccount, - AccountID const& lpAccount, - beast::Journal const j); - -STAmount -ammLPHolds( - ReadView const& view, - SLE const& ammSle, - AccountID const& lpAccount, - beast::Journal const j); - -/** Get AMM trading fee for the given account. The fee is discounted - * if the account is the auction slot owner or one of the slot's authorized - * accounts. - */ -std::uint16_t -getTradingFee(ReadView const& view, SLE const& ammSle, AccountID const& account); - -/** Returns total amount held by AMM for the given token. - */ -STAmount -ammAccountHolds(ReadView const& view, AccountID const& ammAccountID, Issue const& issue); - -/** Delete trustlines to AMM. If all trustlines are deleted then - * AMM object and account are deleted. Otherwise tecIMPCOMPLETE is returned. - */ -TER -deleteAMMAccount(Sandbox& view, Issue const& asset, Issue const& asset2, beast::Journal j); - -/** Initialize Auction and Voting slots and set the trading/discounted fee. - */ -void -initializeFeeAuctionVote( - ApplyView& view, - std::shared_ptr& ammSle, - AccountID const& account, - Issue const& lptIssue, - std::uint16_t tfee); - -/** Return true if the Liquidity Provider is the only AMM provider, false - * otherwise. Return tecINTERNAL if encountered an unexpected condition, - * for instance Liquidity Provider has more than one LPToken trustline. - */ -Expected -isOnlyLiquidityProvider(ReadView const& view, Issue const& ammIssue, AccountID const& lpAccount); - -/** Due to rounding, the LPTokenBalance of the last LP might - * not match the LP's trustline balance. If it's within the tolerance, - * update LPTokenBalance to match the LP's trustline balance. - */ -Expected -verifyAndAdjustLPTokenBalance( - Sandbox& sb, - STAmount const& lpTokens, - std::shared_ptr& ammSle, - AccountID const& account); - -} // namespace xrpl diff --git a/include/xrpl/tx/transactors/dex/AMMVote.h b/include/xrpl/tx/transactors/dex/AMMVote.h index ab04b30993..f83cdcfcd2 100644 --- a/include/xrpl/tx/transactors/dex/AMMVote.h +++ b/include/xrpl/tx/transactors/dex/AMMVote.h @@ -47,6 +47,20 @@ public: TER doApply() override; + + void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; }; } // namespace xrpl diff --git a/include/xrpl/tx/transactors/dex/AMMWithdraw.h b/include/xrpl/tx/transactors/dex/AMMWithdraw.h index 5328b03abb..a07cfca693 100644 --- a/include/xrpl/tx/transactors/dex/AMMWithdraw.h +++ b/include/xrpl/tx/transactors/dex/AMMWithdraw.h @@ -71,6 +71,20 @@ public: TER doApply() override; + void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; + /** Equal-asset withdrawal (LPTokens) of some AMM instance pools * shares represented by the number of LPTokens . * The trading fee is not charged. @@ -98,7 +112,8 @@ public: STAmount const& lpTokens, STAmount const& lpTokensWithdraw, std::uint16_t tfee, - FreezeHandling freezeHanding, + FreezeHandling freezeHandling, + AuthHandling authHandling, WithdrawAll withdrawAll, XRPAmount const& priorBalance, beast::Journal const& journal); @@ -132,6 +147,7 @@ public: STAmount const& lpTokensWithdraw, std::uint16_t tfee, FreezeHandling freezeHandling, + AuthHandling authHandling, WithdrawAll withdrawAll, XRPAmount const& priorBalance, beast::Journal const& journal); @@ -141,8 +157,8 @@ public: Sandbox& sb, std::shared_ptr const ammSle, STAmount const& lpTokenBalance, - Issue const& issue1, - Issue const& issue2, + Asset const& asset1, + Asset const& asset2, beast::Journal const& journal); private: diff --git a/include/xrpl/tx/transactors/dex/OfferCancel.h b/include/xrpl/tx/transactors/dex/OfferCancel.h index c4c57ff3b4..d18808d236 100644 --- a/include/xrpl/tx/transactors/dex/OfferCancel.h +++ b/include/xrpl/tx/transactors/dex/OfferCancel.h @@ -22,6 +22,20 @@ public: TER doApply() override; + + void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; }; } // namespace xrpl diff --git a/include/xrpl/tx/transactors/dex/OfferCreate.h b/include/xrpl/tx/transactors/dex/OfferCreate.h index 2179180054..09c9c0dd6f 100644 --- a/include/xrpl/tx/transactors/dex/OfferCreate.h +++ b/include/xrpl/tx/transactors/dex/OfferCreate.h @@ -40,6 +40,20 @@ public: TER doApply() override; + void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; + private: std::pair applyGuts(Sandbox& view, Sandbox& view_cancel); @@ -51,7 +65,7 @@ private: ApplyFlags const flags, AccountID const id, beast::Journal const j, - Issue const& issue); + Asset const& asset); // Use the payment flow code to perform offer crossing. std::pair diff --git a/include/xrpl/tx/transactors/did/DIDDelete.h b/include/xrpl/tx/transactors/did/DIDDelete.h index 037a57bb02..47b4112762 100644 --- a/include/xrpl/tx/transactors/did/DIDDelete.h +++ b/include/xrpl/tx/transactors/did/DIDDelete.h @@ -24,6 +24,20 @@ public: TER doApply() override; + + void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; }; } // namespace xrpl diff --git a/include/xrpl/tx/transactors/did/DIDSet.h b/include/xrpl/tx/transactors/did/DIDSet.h index fe1c476b47..2e88570065 100644 --- a/include/xrpl/tx/transactors/did/DIDSet.h +++ b/include/xrpl/tx/transactors/did/DIDSet.h @@ -18,6 +18,20 @@ public: TER doApply() override; + + void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; }; } // namespace xrpl diff --git a/include/xrpl/tx/transactors/escrow/EscrowCancel.h b/include/xrpl/tx/transactors/escrow/EscrowCancel.h index c8b97203f0..253d88613f 100644 --- a/include/xrpl/tx/transactors/escrow/EscrowCancel.h +++ b/include/xrpl/tx/transactors/escrow/EscrowCancel.h @@ -21,6 +21,20 @@ public: TER doApply() override; + + void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; }; } // namespace xrpl diff --git a/include/xrpl/tx/transactors/escrow/EscrowCreate.h b/include/xrpl/tx/transactors/escrow/EscrowCreate.h index 221ffee070..3cbeb47fa1 100644 --- a/include/xrpl/tx/transactors/escrow/EscrowCreate.h +++ b/include/xrpl/tx/transactors/escrow/EscrowCreate.h @@ -24,6 +24,20 @@ public: TER doApply() override; + + void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; }; } // namespace xrpl diff --git a/include/xrpl/tx/transactors/escrow/EscrowFinish.h b/include/xrpl/tx/transactors/escrow/EscrowFinish.h index 0917c35d93..6279b3a9af 100644 --- a/include/xrpl/tx/transactors/escrow/EscrowFinish.h +++ b/include/xrpl/tx/transactors/escrow/EscrowFinish.h @@ -30,6 +30,20 @@ public: TER doApply() override; + + void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; }; } // namespace xrpl diff --git a/include/xrpl/tx/transactors/lending/LendingHelpers.h b/include/xrpl/tx/transactors/lending/LendingHelpers.h index 897ca3995b..1c938bbc8a 100644 --- a/include/xrpl/tx/transactors/lending/LendingHelpers.h +++ b/include/xrpl/tx/transactors/lending/LendingHelpers.h @@ -171,7 +171,7 @@ getAssetsTotalScale(SLE::const_ref vaultSle) { if (!vaultSle) return Number::minExponent - 1; // LCOV_EXCL_LINE - return STAmount{vaultSle->at(sfAsset), vaultSle->at(sfAssetsTotal)}.exponent(); + return scale(vaultSle->at(sfAssetsTotal), vaultSle->at(sfAsset)); } TER diff --git a/include/xrpl/tx/transactors/lending/LoanBrokerCoverClawback.h b/include/xrpl/tx/transactors/lending/LoanBrokerCoverClawback.h index b1e539392f..9a028b6448 100644 --- a/include/xrpl/tx/transactors/lending/LoanBrokerCoverClawback.h +++ b/include/xrpl/tx/transactors/lending/LoanBrokerCoverClawback.h @@ -24,6 +24,20 @@ public: TER doApply() override; + + void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; }; //------------------------------------------------------------------------------ diff --git a/include/xrpl/tx/transactors/lending/LoanBrokerCoverDeposit.h b/include/xrpl/tx/transactors/lending/LoanBrokerCoverDeposit.h index 8dda417443..9b0cd9b176 100644 --- a/include/xrpl/tx/transactors/lending/LoanBrokerCoverDeposit.h +++ b/include/xrpl/tx/transactors/lending/LoanBrokerCoverDeposit.h @@ -24,6 +24,20 @@ public: TER doApply() override; + + void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; }; //------------------------------------------------------------------------------ diff --git a/include/xrpl/tx/transactors/lending/LoanBrokerCoverWithdraw.h b/include/xrpl/tx/transactors/lending/LoanBrokerCoverWithdraw.h index 52b14bfb67..74c35d5667 100644 --- a/include/xrpl/tx/transactors/lending/LoanBrokerCoverWithdraw.h +++ b/include/xrpl/tx/transactors/lending/LoanBrokerCoverWithdraw.h @@ -24,6 +24,20 @@ public: TER doApply() override; + + void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; }; //------------------------------------------------------------------------------ diff --git a/include/xrpl/tx/transactors/lending/LoanBrokerDelete.h b/include/xrpl/tx/transactors/lending/LoanBrokerDelete.h index b9c9851c41..3de0f6fec2 100644 --- a/include/xrpl/tx/transactors/lending/LoanBrokerDelete.h +++ b/include/xrpl/tx/transactors/lending/LoanBrokerDelete.h @@ -24,6 +24,20 @@ public: TER doApply() override; + + void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; }; //------------------------------------------------------------------------------ diff --git a/include/xrpl/tx/transactors/lending/LoanBrokerSet.h b/include/xrpl/tx/transactors/lending/LoanBrokerSet.h index ce1e069791..519ccd6ce9 100644 --- a/include/xrpl/tx/transactors/lending/LoanBrokerSet.h +++ b/include/xrpl/tx/transactors/lending/LoanBrokerSet.h @@ -27,6 +27,20 @@ public: TER doApply() override; + + void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; }; //------------------------------------------------------------------------------ diff --git a/include/xrpl/tx/transactors/lending/LoanDelete.h b/include/xrpl/tx/transactors/lending/LoanDelete.h index ff78d7db60..5c6ec46d33 100644 --- a/include/xrpl/tx/transactors/lending/LoanDelete.h +++ b/include/xrpl/tx/transactors/lending/LoanDelete.h @@ -24,6 +24,20 @@ public: TER doApply() override; + + void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; }; //------------------------------------------------------------------------------ diff --git a/include/xrpl/tx/transactors/lending/LoanManage.h b/include/xrpl/tx/transactors/lending/LoanManage.h index 810e9bbf65..98b7e38ba6 100644 --- a/include/xrpl/tx/transactors/lending/LoanManage.h +++ b/include/xrpl/tx/transactors/lending/LoanManage.h @@ -58,6 +58,20 @@ public: TER doApply() override; + + void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; }; //------------------------------------------------------------------------------ diff --git a/include/xrpl/tx/transactors/lending/LoanPay.h b/include/xrpl/tx/transactors/lending/LoanPay.h index 2e3cce75ed..83271cf4e7 100644 --- a/include/xrpl/tx/transactors/lending/LoanPay.h +++ b/include/xrpl/tx/transactors/lending/LoanPay.h @@ -30,6 +30,20 @@ public: TER doApply() override; + + void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; }; //------------------------------------------------------------------------------ diff --git a/include/xrpl/tx/transactors/lending/LoanSet.h b/include/xrpl/tx/transactors/lending/LoanSet.h index 86101d0735..c778582ab0 100644 --- a/include/xrpl/tx/transactors/lending/LoanSet.h +++ b/include/xrpl/tx/transactors/lending/LoanSet.h @@ -38,6 +38,20 @@ public: TER doApply() override; + void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; + public: static std::uint32_t constexpr minPaymentTotal = 1; static std::uint32_t constexpr defaultPaymentTotal = 1; diff --git a/include/xrpl/tx/transactors/nft/NFTokenAcceptOffer.h b/include/xrpl/tx/transactors/nft/NFTokenAcceptOffer.h index 60962fc9ca..7291db9a2e 100644 --- a/include/xrpl/tx/transactors/nft/NFTokenAcceptOffer.h +++ b/include/xrpl/tx/transactors/nft/NFTokenAcceptOffer.h @@ -34,6 +34,20 @@ public: TER doApply() override; + + void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; }; } // namespace xrpl diff --git a/include/xrpl/tx/transactors/nft/NFTokenBurn.h b/include/xrpl/tx/transactors/nft/NFTokenBurn.h index 8737997f03..2830b7c22a 100644 --- a/include/xrpl/tx/transactors/nft/NFTokenBurn.h +++ b/include/xrpl/tx/transactors/nft/NFTokenBurn.h @@ -21,6 +21,20 @@ public: TER doApply() override; + + void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; }; } // namespace xrpl diff --git a/include/xrpl/tx/transactors/nft/NFTokenCancelOffer.h b/include/xrpl/tx/transactors/nft/NFTokenCancelOffer.h index 6d60a0bebd..540258b8d6 100644 --- a/include/xrpl/tx/transactors/nft/NFTokenCancelOffer.h +++ b/include/xrpl/tx/transactors/nft/NFTokenCancelOffer.h @@ -21,6 +21,20 @@ public: TER doApply() override; + + void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; }; } // namespace xrpl diff --git a/include/xrpl/tx/transactors/nft/NFTokenCreateOffer.h b/include/xrpl/tx/transactors/nft/NFTokenCreateOffer.h index a48e53589d..532aa5c120 100644 --- a/include/xrpl/tx/transactors/nft/NFTokenCreateOffer.h +++ b/include/xrpl/tx/transactors/nft/NFTokenCreateOffer.h @@ -24,6 +24,20 @@ public: TER doApply() override; + + void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; }; } // namespace xrpl diff --git a/include/xrpl/tx/transactors/nft/NFTokenMint.h b/include/xrpl/tx/transactors/nft/NFTokenMint.h index d4eeba2bf0..6dcf8eaf12 100644 --- a/include/xrpl/tx/transactors/nft/NFTokenMint.h +++ b/include/xrpl/tx/transactors/nft/NFTokenMint.h @@ -1,8 +1,8 @@ #pragma once +#include #include #include -#include namespace xrpl { @@ -30,6 +30,20 @@ public: TER doApply() override; + void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; + // Public to support unit tests. static uint256 createNFTokenID( diff --git a/include/xrpl/tx/transactors/nft/NFTokenModify.h b/include/xrpl/tx/transactors/nft/NFTokenModify.h index a64df65783..a1076a96a9 100644 --- a/include/xrpl/tx/transactors/nft/NFTokenModify.h +++ b/include/xrpl/tx/transactors/nft/NFTokenModify.h @@ -21,6 +21,20 @@ public: TER doApply() override; + + void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; }; } // namespace xrpl diff --git a/include/xrpl/tx/transactors/oracle/OracleDelete.h b/include/xrpl/tx/transactors/oracle/OracleDelete.h index 9d1e6d3a44..5de69e980f 100644 --- a/include/xrpl/tx/transactors/oracle/OracleDelete.h +++ b/include/xrpl/tx/transactors/oracle/OracleDelete.h @@ -31,6 +31,20 @@ public: TER doApply() override; + void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; + static TER deleteOracle( ApplyView& view, diff --git a/include/xrpl/tx/transactors/oracle/OracleSet.h b/include/xrpl/tx/transactors/oracle/OracleSet.h index d879d14a84..426f46c2b4 100644 --- a/include/xrpl/tx/transactors/oracle/OracleSet.h +++ b/include/xrpl/tx/transactors/oracle/OracleSet.h @@ -30,6 +30,20 @@ public: TER doApply() override; + + void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; }; } // namespace xrpl diff --git a/include/xrpl/tx/transactors/payment/DepositPreauth.h b/include/xrpl/tx/transactors/payment/DepositPreauth.h index b397a81748..f74be24c56 100644 --- a/include/xrpl/tx/transactors/payment/DepositPreauth.h +++ b/include/xrpl/tx/transactors/payment/DepositPreauth.h @@ -25,6 +25,20 @@ public: TER doApply() override; + void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; + // Interface used by AccountDelete static TER removeFromLedger(ApplyView& view, uint256 const& delIndex, beast::Journal j); diff --git a/include/xrpl/tx/transactors/payment/Payment.h b/include/xrpl/tx/transactors/payment/Payment.h index bc5bc4fee3..3baf1fa55f 100644 --- a/include/xrpl/tx/transactors/payment/Payment.h +++ b/include/xrpl/tx/transactors/payment/Payment.h @@ -39,6 +39,20 @@ public: TER doApply() override; + + void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; }; } // namespace xrpl diff --git a/include/xrpl/tx/transactors/payment_channel/PaymentChannelClaim.h b/include/xrpl/tx/transactors/payment_channel/PaymentChannelClaim.h index 449e503bc4..6e9a269b8d 100644 --- a/include/xrpl/tx/transactors/payment_channel/PaymentChannelClaim.h +++ b/include/xrpl/tx/transactors/payment_channel/PaymentChannelClaim.h @@ -27,6 +27,20 @@ public: TER doApply() override; + + void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; }; } // namespace xrpl diff --git a/include/xrpl/tx/transactors/payment_channel/PaymentChannelCreate.h b/include/xrpl/tx/transactors/payment_channel/PaymentChannelCreate.h index 7fdacf9b3a..cd0250713a 100644 --- a/include/xrpl/tx/transactors/payment_channel/PaymentChannelCreate.h +++ b/include/xrpl/tx/transactors/payment_channel/PaymentChannelCreate.h @@ -24,6 +24,20 @@ public: TER doApply() override; + + void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; }; } // namespace xrpl diff --git a/include/xrpl/tx/transactors/payment_channel/PaymentChannelFund.h b/include/xrpl/tx/transactors/payment_channel/PaymentChannelFund.h index 69f74a4c53..befe9ac951 100644 --- a/include/xrpl/tx/transactors/payment_channel/PaymentChannelFund.h +++ b/include/xrpl/tx/transactors/payment_channel/PaymentChannelFund.h @@ -21,6 +21,20 @@ public: TER doApply() override; + + void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; }; } // namespace xrpl diff --git a/include/xrpl/tx/transactors/permissioned_domain/PermissionedDomainDelete.h b/include/xrpl/tx/transactors/permissioned_domain/PermissionedDomainDelete.h index b5c72413a2..93879c55b2 100644 --- a/include/xrpl/tx/transactors/permissioned_domain/PermissionedDomainDelete.h +++ b/include/xrpl/tx/transactors/permissioned_domain/PermissionedDomainDelete.h @@ -22,6 +22,20 @@ public: /** Attempt to delete the Permissioned Domain. */ TER doApply() override; + + void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; }; } // namespace xrpl diff --git a/include/xrpl/tx/transactors/permissioned_domain/PermissionedDomainSet.h b/include/xrpl/tx/transactors/permissioned_domain/PermissionedDomainSet.h index acf9194ee2..3187be08e1 100644 --- a/include/xrpl/tx/transactors/permissioned_domain/PermissionedDomainSet.h +++ b/include/xrpl/tx/transactors/permissioned_domain/PermissionedDomainSet.h @@ -25,6 +25,20 @@ public: /** Attempt to create the Permissioned Domain. */ TER doApply() override; + + void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; }; } // namespace xrpl diff --git a/include/xrpl/tx/transactors/system/Batch.h b/include/xrpl/tx/transactors/system/Batch.h index 0861deb094..1d9a66ec9a 100644 --- a/include/xrpl/tx/transactors/system/Batch.h +++ b/include/xrpl/tx/transactors/system/Batch.h @@ -33,6 +33,20 @@ public: TER doApply() override; + void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; + static constexpr auto disabledTxTypes = std::to_array({ ttVAULT_CREATE, ttVAULT_SET, diff --git a/include/xrpl/tx/transactors/system/Change.h b/include/xrpl/tx/transactors/system/Change.h index 1bf63ff0db..b966ef73ec 100644 --- a/include/xrpl/tx/transactors/system/Change.h +++ b/include/xrpl/tx/transactors/system/Change.h @@ -18,6 +18,20 @@ public: void preCompute() override; + void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; + static XRPAmount calculateBaseFee(ReadView const& view, STTx const& tx) { diff --git a/include/xrpl/tx/transactors/system/LedgerStateFix.h b/include/xrpl/tx/transactors/system/LedgerStateFix.h index 728f8c651d..a236a52c74 100644 --- a/include/xrpl/tx/transactors/system/LedgerStateFix.h +++ b/include/xrpl/tx/transactors/system/LedgerStateFix.h @@ -28,6 +28,20 @@ public: TER doApply() override; + + void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; }; } // namespace xrpl diff --git a/include/xrpl/tx/transactors/system/TicketCreate.h b/include/xrpl/tx/transactors/system/TicketCreate.h index 88d83c37a7..13b6e7eca5 100644 --- a/include/xrpl/tx/transactors/system/TicketCreate.h +++ b/include/xrpl/tx/transactors/system/TicketCreate.h @@ -59,6 +59,20 @@ public: /** Precondition: fee collection is likely. Attempt to create ticket(s). */ TER doApply() override; + + void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; }; } // namespace xrpl diff --git a/include/xrpl/tx/transactors/token/Clawback.h b/include/xrpl/tx/transactors/token/Clawback.h index a795115f7a..f7b77b872c 100644 --- a/include/xrpl/tx/transactors/token/Clawback.h +++ b/include/xrpl/tx/transactors/token/Clawback.h @@ -21,6 +21,20 @@ public: TER doApply() override; + + void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; }; } // namespace xrpl diff --git a/include/xrpl/tx/transactors/token/MPTokenAuthorize.h b/include/xrpl/tx/transactors/token/MPTokenAuthorize.h index b6a34d4f14..0cbc683e91 100644 --- a/include/xrpl/tx/transactors/token/MPTokenAuthorize.h +++ b/include/xrpl/tx/transactors/token/MPTokenAuthorize.h @@ -10,7 +10,7 @@ struct MPTAuthorizeArgs MPTID const& mptIssuanceID; AccountID const& account; std::uint32_t flags{}; - std::optional holderID{}; + std::optional holderID; }; class MPTokenAuthorize : public Transactor @@ -31,15 +31,22 @@ public: static TER preclaim(PreclaimContext const& ctx); - static TER - createMPToken( - ApplyView& view, - MPTID const& mptIssuanceID, - AccountID const& account, - std::uint32_t const flags); - TER doApply() override; + + void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; }; } // namespace xrpl diff --git a/include/xrpl/tx/transactors/token/MPTokenIssuanceCreate.h b/include/xrpl/tx/transactors/token/MPTokenIssuanceCreate.h index 5ef12df282..6718d28e4d 100644 --- a/include/xrpl/tx/transactors/token/MPTokenIssuanceCreate.h +++ b/include/xrpl/tx/transactors/token/MPTokenIssuanceCreate.h @@ -12,12 +12,16 @@ struct MPTCreateArgs AccountID const& account; std::uint32_t sequence = 0; std::uint32_t flags = 0; - std::optional maxAmount{}; - std::optional assetScale{}; - std::optional transferFee{}; + std::optional maxAmount = + std::nullopt; // NOLINT(readability-redundant-member-init) + std::optional assetScale = + std::nullopt; // NOLINT(readability-redundant-member-init) + std::optional transferFee = + std::nullopt; // NOLINT(readability-redundant-member-init) std::optional const& metadata{}; - std::optional domainId{}; - std::optional mutableFlags{}; + std::optional domainId = std::nullopt; // NOLINT(readability-redundant-member-init) + std::optional mutableFlags = + std::nullopt; // NOLINT(readability-redundant-member-init) }; class MPTokenIssuanceCreate : public Transactor @@ -41,6 +45,20 @@ public: TER doApply() override; + void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; + static Expected create(ApplyView& view, beast::Journal journal, MPTCreateArgs const& args); }; diff --git a/include/xrpl/tx/transactors/token/MPTokenIssuanceDestroy.h b/include/xrpl/tx/transactors/token/MPTokenIssuanceDestroy.h index 416708565a..aaac508cf5 100644 --- a/include/xrpl/tx/transactors/token/MPTokenIssuanceDestroy.h +++ b/include/xrpl/tx/transactors/token/MPTokenIssuanceDestroy.h @@ -21,6 +21,20 @@ public: TER doApply() override; + + void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; }; } // namespace xrpl diff --git a/include/xrpl/tx/transactors/token/MPTokenIssuanceSet.h b/include/xrpl/tx/transactors/token/MPTokenIssuanceSet.h index dccd4e4cee..8dc423376a 100644 --- a/include/xrpl/tx/transactors/token/MPTokenIssuanceSet.h +++ b/include/xrpl/tx/transactors/token/MPTokenIssuanceSet.h @@ -30,6 +30,20 @@ public: TER doApply() override; + + void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; }; } // namespace xrpl diff --git a/include/xrpl/tx/transactors/token/TrustSet.h b/include/xrpl/tx/transactors/token/TrustSet.h index 2e67aaeded..cf4f042515 100644 --- a/include/xrpl/tx/transactors/token/TrustSet.h +++ b/include/xrpl/tx/transactors/token/TrustSet.h @@ -28,6 +28,20 @@ public: TER doApply() override; + + void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; }; } // namespace xrpl diff --git a/include/xrpl/tx/transactors/vault/VaultClawback.h b/include/xrpl/tx/transactors/vault/VaultClawback.h index 131a1d87e7..9c69c88f75 100644 --- a/include/xrpl/tx/transactors/vault/VaultClawback.h +++ b/include/xrpl/tx/transactors/vault/VaultClawback.h @@ -22,6 +22,20 @@ public: TER doApply() override; + void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; + private: Expected, TER> assetsToClawback( diff --git a/include/xrpl/tx/transactors/vault/VaultCreate.h b/include/xrpl/tx/transactors/vault/VaultCreate.h index cc35cd765b..6861c9d164 100644 --- a/include/xrpl/tx/transactors/vault/VaultCreate.h +++ b/include/xrpl/tx/transactors/vault/VaultCreate.h @@ -27,6 +27,20 @@ public: TER doApply() override; + + void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; }; } // namespace xrpl diff --git a/include/xrpl/tx/transactors/vault/VaultDelete.h b/include/xrpl/tx/transactors/vault/VaultDelete.h index f881a692fd..33a86dd050 100644 --- a/include/xrpl/tx/transactors/vault/VaultDelete.h +++ b/include/xrpl/tx/transactors/vault/VaultDelete.h @@ -21,6 +21,20 @@ public: TER doApply() override; + + void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; }; } // namespace xrpl diff --git a/include/xrpl/tx/transactors/vault/VaultDeposit.h b/include/xrpl/tx/transactors/vault/VaultDeposit.h index 0943596f20..5a0c63a3b1 100644 --- a/include/xrpl/tx/transactors/vault/VaultDeposit.h +++ b/include/xrpl/tx/transactors/vault/VaultDeposit.h @@ -21,6 +21,20 @@ public: TER doApply() override; + + void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; }; } // namespace xrpl diff --git a/include/xrpl/tx/transactors/vault/VaultSet.h b/include/xrpl/tx/transactors/vault/VaultSet.h index fb69f132b1..6abbe80fec 100644 --- a/include/xrpl/tx/transactors/vault/VaultSet.h +++ b/include/xrpl/tx/transactors/vault/VaultSet.h @@ -24,6 +24,20 @@ public: TER doApply() override; + + void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; }; } // namespace xrpl diff --git a/include/xrpl/tx/transactors/vault/VaultWithdraw.h b/include/xrpl/tx/transactors/vault/VaultWithdraw.h index ffe14a7141..b8604d039e 100644 --- a/include/xrpl/tx/transactors/vault/VaultWithdraw.h +++ b/include/xrpl/tx/transactors/vault/VaultWithdraw.h @@ -21,6 +21,20 @@ public: TER doApply() override; + + void + visitInvariantEntry( + bool isDelete, + std::shared_ptr const& before, + std::shared_ptr const& after) override; + + [[nodiscard]] bool + finalizeInvariants( + STTx const& tx, + TER result, + XRPAmount fee, + ReadView const& view, + beast::Journal const& j) override; }; } // namespace xrpl diff --git a/sanitizers/suppressions/tsan.supp b/sanitizers/suppressions/tsan.supp index 74f3371e68..702eef1e3d 100644 --- a/sanitizers/suppressions/tsan.supp +++ b/sanitizers/suppressions/tsan.supp @@ -7,7 +7,7 @@ race:boost/context/ race:boost/asio/executor.hpp race:boost::asio -# Suppress tsan related issues in rippled code. +# Suppress tsan related issues in xrpld code. race:src/libxrpl/basics/make_SSLContext.cpp race:src/libxrpl/basics/Number.cpp race:src/libxrpl/json/json_value.cpp @@ -46,7 +46,7 @@ race:xrpl/server/detail/ServerImpl.h race:xrpl/nodestore/detail/DatabaseNodeImp.h race:src/libxrpl/beast/utility/beast_Journal.cpp race:src/test/beast/LexicalCast_test.cpp -race:ripple::ServerHandler +race:ServerHandler # More suppressions in external library code. race:crtstuff.c @@ -65,7 +65,7 @@ deadlock:src/xrpld/app/misc/detail/ValidatorSite.cpp signal:src/libxrpl/beast/utility/beast_Journal.cpp signal:src/xrpld/core/detail/Workers.cpp signal:src/xrpld/core/JobQueue.cpp -signal:ripple::Workers::Worker +signal:Workers::Worker # Aggressive suppressing of deadlock tsan errors deadlock:pthread_create diff --git a/src/libxrpl/basics/Archive.cpp b/src/libxrpl/basics/Archive.cpp index e77dabcd68..bba144ed04 100644 --- a/src/libxrpl/basics/Archive.cpp +++ b/src/libxrpl/basics/Archive.cpp @@ -1,4 +1,5 @@ #include + #include #include diff --git a/src/libxrpl/basics/BasicConfig.cpp b/src/libxrpl/basics/BasicConfig.cpp index ba10a575d9..8fed7e5345 100644 --- a/src/libxrpl/basics/BasicConfig.cpp +++ b/src/libxrpl/basics/BasicConfig.cpp @@ -1,9 +1,9 @@ #include + #include #include #include -#include #include #include @@ -14,7 +14,7 @@ namespace xrpl { -Section::Section(std::string const& name) : name_(name) +Section::Section(std::string name) : name_(std::move(name)) { } diff --git a/src/libxrpl/basics/CountedObject.cpp b/src/libxrpl/basics/CountedObject.cpp index bcdca9dfa4..8eb6ca9dc2 100644 --- a/src/libxrpl/basics/CountedObject.cpp +++ b/src/libxrpl/basics/CountedObject.cpp @@ -31,7 +31,7 @@ CountedObjects::getCounts(int minimumThreshold) const counts.emplace_back(ctr->getName(), ctr->getCount()); } - std::sort(counts.begin(), counts.end()); + std::ranges::sort(counts); return counts; } diff --git a/src/libxrpl/basics/FileUtilities.cpp b/src/libxrpl/basics/FileUtilities.cpp index 96ec3fa3ba..1a6e604724 100644 --- a/src/libxrpl/basics/FileUtilities.cpp +++ b/src/libxrpl/basics/FileUtilities.cpp @@ -1,6 +1,5 @@ #include -#include #include #include #include diff --git a/src/libxrpl/basics/Log.cpp b/src/libxrpl/basics/Log.cpp index f0a546ee75..e855e1006b 100644 --- a/src/libxrpl/basics/Log.cpp +++ b/src/libxrpl/basics/Log.cpp @@ -1,4 +1,5 @@ #include + #include #include #include @@ -19,8 +20,8 @@ namespace xrpl { -Logs::Sink::Sink(std::string const& partition, beast::severities::Severity thresh, Logs& logs) - : beast::Journal::Sink(thresh, false), logs_(logs), partition_(partition) +Logs::Sink::Sink(std::string partition, beast::severities::Severity thresh, Logs& logs) + : beast::Journal::Sink(thresh, false), logs_(logs), partition_(std::move(partition)) { } diff --git a/src/libxrpl/basics/MallocTrim.cpp b/src/libxrpl/basics/MallocTrim.cpp index ed20ac6c94..07b5b9ffc5 100644 --- a/src/libxrpl/basics/MallocTrim.cpp +++ b/src/libxrpl/basics/MallocTrim.cpp @@ -1,13 +1,11 @@ -#include #include +#include +#include + #include -#include -#include -#include -#include -#include +#include #if defined(__GLIBC__) && BOOST_OS_LINUX #include @@ -15,6 +13,14 @@ #include #include +#include +#include +#include +#include +#include +#include +#include + // Require RUSAGE_THREAD for thread-scoped page fault tracking #ifndef RUSAGE_THREAD #error "MallocTrim rusage instrumentation requires RUSAGE_THREAD on Linux/glibc" diff --git a/src/libxrpl/basics/Number.cpp b/src/libxrpl/basics/Number.cpp index a8adabd5de..73ab8f6307 100644 --- a/src/libxrpl/basics/Number.cpp +++ b/src/libxrpl/basics/Number.cpp @@ -1,11 +1,12 @@ #include -// Keep Number.h first to ensure it can build without hidden dependencies + #include #include #include #include #include +#include #include #include #include diff --git a/src/libxrpl/basics/ResolverAsio.cpp b/src/libxrpl/basics/ResolverAsio.cpp index 305bdb6451..fc71666768 100644 --- a/src/libxrpl/basics/ResolverAsio.cpp +++ b/src/libxrpl/basics/ResolverAsio.cpp @@ -1,15 +1,19 @@ +#include + #include #include -#include #include #include #include #include #include +#include #include #include #include +#include +#include #include #include @@ -22,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -121,7 +126,7 @@ public: HandlerType handler; template - Work(StringSequence const& names_, HandlerType const& handler_) : handler(handler_) + Work(StringSequence const& names_, HandlerType handler_) : handler(std::move(handler_)) { names.reserve(names_.size()); @@ -290,9 +295,10 @@ public: auto const find_whitespace = std::bind(&std::isspace, std::placeholders::_1, std::locale()); - auto host_first = std::find_if_not(str.begin(), str.end(), find_whitespace); + auto host_first = std::ranges::find_if_not(str, find_whitespace); - auto port_last = std::find_if_not(str.rbegin(), str.rend(), find_whitespace).base(); + auto port_last = + std::ranges::find_if_not(std::ranges::reverse_view(str), find_whitespace).base(); // This should only happen for all-whitespace strings if (host_first >= port_last) diff --git a/src/libxrpl/basics/StringUtilities.cpp b/src/libxrpl/basics/StringUtilities.cpp index 5e3b100b26..7bf4c8e743 100644 --- a/src/libxrpl/basics/StringUtilities.cpp +++ b/src/libxrpl/basics/StringUtilities.cpp @@ -1,5 +1,6 @@ -#include #include + +#include #include #include @@ -8,7 +9,6 @@ #include #include #include -#include #include #include diff --git a/src/libxrpl/basics/UptimeClock.cpp b/src/libxrpl/basics/UptimeClock.cpp index 521a6a1313..3d1664482b 100644 --- a/src/libxrpl/basics/UptimeClock.cpp +++ b/src/libxrpl/basics/UptimeClock.cpp @@ -9,14 +9,14 @@ namespace xrpl { std::atomic UptimeClock::now_{0}; // seconds since start std::atomic UptimeClock::stop_{false}; // stop update thread -// On rippled shutdown, cancel and wait for the update thread +// On xrpld shutdown, cancel and wait for the update thread UptimeClock::update_thread::~update_thread() { if (joinable()) { stop_ = true; // This join() may take up to a 1s, but happens only - // once at rippled shutdown. + // once at xrpld shutdown. join(); } } @@ -40,7 +40,7 @@ UptimeClock::start_clock() }}; } -// This actually measures time since first use, instead of since rippled start. +// This actually measures time since first use, instead of since xrpld start. // However the difference between these two epochs is a small fraction of a // second and unimportant. @@ -50,7 +50,7 @@ UptimeClock::now() // start the update thread on first use static auto const init = start_clock(); - // Return the number of seconds since rippled start + // Return the number of seconds since xrpld start return time_point{duration{now_}}; } diff --git a/src/libxrpl/basics/contract.cpp b/src/libxrpl/basics/contract.cpp index 562d3a0944..32628ac3d9 100644 --- a/src/libxrpl/basics/contract.cpp +++ b/src/libxrpl/basics/contract.cpp @@ -1,5 +1,6 @@ -#include #include + +#include #include #include diff --git a/src/libxrpl/basics/make_SSLContext.cpp b/src/libxrpl/basics/make_SSLContext.cpp index c5ff456d25..aa04f22191 100644 --- a/src/libxrpl/basics/make_SSLContext.cpp +++ b/src/libxrpl/basics/make_SSLContext.cpp @@ -1,6 +1,7 @@ -#include #include +#include + #include #include #include @@ -8,8 +9,9 @@ #include #include +#include #include -#include +#include // IWYU pragma: keep #include #include #include @@ -25,8 +27,8 @@ #include namespace xrpl { -namespace openssl { -namespace detail { + +namespace openssl::detail { /** The default strength of self-signed RSA certificates. @@ -344,8 +346,7 @@ get_context(std::string cipherList) return c; } -} // namespace detail -} // namespace openssl +} // namespace openssl::detail //------------------------------------------------------------------------------ std::shared_ptr diff --git a/src/libxrpl/basics/mulDiv.cpp b/src/libxrpl/basics/mulDiv.cpp index d8988b474e..4abdcc0f75 100644 --- a/src/libxrpl/basics/mulDiv.cpp +++ b/src/libxrpl/basics/mulDiv.cpp @@ -1,8 +1,6 @@ #include -#include -#include -#include +#include // IWYU pragma: keep #include #include diff --git a/src/libxrpl/beast/clock/basic_seconds_clock.cpp b/src/libxrpl/beast/clock/basic_seconds_clock.cpp index c928c8c579..2b7ce57742 100644 --- a/src/libxrpl/beast/clock/basic_seconds_clock.cpp +++ b/src/libxrpl/beast/clock/basic_seconds_clock.cpp @@ -1,4 +1,5 @@ #include + #include #include diff --git a/src/libxrpl/beast/core/CurrentThreadName.cpp b/src/libxrpl/beast/core/CurrentThreadName.cpp index 6f22687dcc..ccd3df5f74 100644 --- a/src/libxrpl/beast/core/CurrentThreadName.cpp +++ b/src/libxrpl/beast/core/CurrentThreadName.cpp @@ -1,5 +1,4 @@ #include -#include #include #include @@ -72,7 +71,8 @@ setCurrentThreadNameImpl(std::string_view name) #if BOOST_OS_LINUX #include -#include +#include +#include // IWYU pragma: keep namespace beast::detail { diff --git a/src/libxrpl/beast/core/SemanticVersion.cpp b/src/libxrpl/beast/core/SemanticVersion.cpp index 0601690560..0cf4ac3113 100644 --- a/src/libxrpl/beast/core/SemanticVersion.cpp +++ b/src/libxrpl/beast/core/SemanticVersion.cpp @@ -1,13 +1,16 @@ -#include #include + +#include #include #include #include #include #include +#include #include #include +#include namespace beast { @@ -58,9 +61,8 @@ chopUInt(int& value, int limit, std::string& input) if (input.empty()) return false; - auto left_iter = std::find_if_not(input.begin(), input.end(), [](std::string::value_type c) { - return std::isdigit(c, std::locale::classic()); - }); + auto left_iter = std::ranges::find_if_not( + input, [](std::string::value_type c) { return std::isdigit(c, std::locale::classic()); }); std::string const item(input.begin(), left_iter); @@ -148,13 +150,13 @@ bool 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) { - return std::isspace(c, std::locale::classic()); - }); + auto left_iter = std::ranges::find_if_not( + input, [](std::string::value_type c) { return std::isspace(c, std::locale::classic()); }); - auto right_iter = std::find_if_not(input.rbegin(), input.rend(), [](std::string::value_type c) { - return std::isspace(c, std::locale::classic()); - }).base(); + auto right_iter = + std::ranges::find_if_not(std::ranges::reverse_view(input), [](std::string::value_type c) { + return std::isspace(c, std::locale::classic()); + }).base(); // Must not be empty! if (left_iter >= right_iter) diff --git a/src/libxrpl/beast/insight/Collector.cpp b/src/libxrpl/beast/insight/Collector.cpp index d4a528473b..55abb27c21 100644 --- a/src/libxrpl/beast/insight/Collector.cpp +++ b/src/libxrpl/beast/insight/Collector.cpp @@ -1,8 +1,6 @@ #include -namespace beast { -namespace insight { +namespace beast::insight { Collector::~Collector() = default; -} // namespace insight -} // namespace beast +} // namespace beast::insight diff --git a/src/libxrpl/beast/insight/Groups.cpp b/src/libxrpl/beast/insight/Groups.cpp index 393deca101..c9e9453468 100644 --- a/src/libxrpl/beast/insight/Groups.cpp +++ b/src/libxrpl/beast/insight/Groups.cpp @@ -1,10 +1,11 @@ +#include + #include #include #include #include #include #include -#include #include #include #include @@ -14,8 +15,7 @@ #include #include -namespace beast { -namespace insight { +namespace beast::insight { namespace detail { @@ -25,12 +25,12 @@ public: std::string const m_name; Collector::ptr m_collector; - GroupImp(std::string const& name_, Collector::ptr const& collector) - : m_name(name_), m_collector(collector) + GroupImp(std::string name_, Collector::ptr collector) + : m_name(std::move(name_)), m_collector(std::move(collector)) { } - ~GroupImp() = default; + ~GroupImp() override = default; std::string const& name() const override @@ -74,9 +74,8 @@ public: return m_collector->make_meter(make_name(name)); } -private: GroupImp& - operator=(GroupImp const&); + operator=(GroupImp const&) = delete; }; //------------------------------------------------------------------------------ @@ -89,11 +88,11 @@ public: Collector::ptr m_collector; Items m_items; - explicit GroupsImp(Collector::ptr const& collector) : m_collector(collector) + explicit GroupsImp(Collector::ptr collector) : m_collector(std::move(collector)) { } - ~GroupsImp() = default; + ~GroupsImp() override = default; Group::ptr const& get(std::string const& name) override @@ -118,5 +117,4 @@ make_Groups(Collector::ptr const& collector) return std::make_unique(collector); } -} // namespace insight -} // namespace beast +} // namespace beast::insight diff --git a/src/libxrpl/beast/insight/Hook.cpp b/src/libxrpl/beast/insight/Hook.cpp index f74f3e8705..a20e33fad4 100644 --- a/src/libxrpl/beast/insight/Hook.cpp +++ b/src/libxrpl/beast/insight/Hook.cpp @@ -1,9 +1,8 @@ #include + #include -namespace beast { -namespace insight { +namespace beast::insight { HookImpl::~HookImpl() = default; -} // namespace insight -} // namespace beast +} // namespace beast::insight diff --git a/src/libxrpl/beast/insight/Metric.cpp b/src/libxrpl/beast/insight/Metric.cpp index fcad390576..b9ac569259 100644 --- a/src/libxrpl/beast/insight/Metric.cpp +++ b/src/libxrpl/beast/insight/Metric.cpp @@ -3,8 +3,7 @@ #include #include -namespace beast { -namespace insight { +namespace beast::insight { CounterImpl::~CounterImpl() = default; @@ -13,5 +12,4 @@ EventImpl::~EventImpl() = default; GaugeImpl::~GaugeImpl() = default; MeterImpl::~MeterImpl() = default; -} // namespace insight -} // namespace beast +} // namespace beast::insight diff --git a/src/libxrpl/beast/insight/NullCollector.cpp b/src/libxrpl/beast/insight/NullCollector.cpp index d8ba67a8e7..f5bb44bdba 100644 --- a/src/libxrpl/beast/insight/NullCollector.cpp +++ b/src/libxrpl/beast/insight/NullCollector.cpp @@ -1,3 +1,5 @@ +#include + #include #include #include @@ -9,13 +11,11 @@ #include #include #include -#include #include #include -namespace beast { -namespace insight { +namespace beast::insight { namespace detail { @@ -24,9 +24,8 @@ class NullHookImpl : public HookImpl public: explicit NullHookImpl() = default; -private: NullHookImpl& - operator=(NullHookImpl const&); + operator=(NullHookImpl const&) = delete; }; //------------------------------------------------------------------------------ @@ -41,9 +40,8 @@ public: { } -private: NullCounterImpl& - operator=(NullCounterImpl const&); + operator=(NullCounterImpl const&) = delete; }; //------------------------------------------------------------------------------ @@ -58,9 +56,8 @@ public: { } -private: NullEventImpl& - operator=(NullEventImpl const&); + operator=(NullEventImpl const&) = delete; }; //------------------------------------------------------------------------------ @@ -80,9 +77,8 @@ public: { } -private: NullGaugeImpl& - operator=(NullGaugeImpl const&); + operator=(NullGaugeImpl const&) = delete; }; //------------------------------------------------------------------------------ @@ -97,9 +93,8 @@ public: { } -private: NullMeterImpl& - operator=(NullMeterImpl const&); + operator=(NullMeterImpl const&) = delete; }; //------------------------------------------------------------------------------ @@ -110,7 +105,7 @@ private: public: NullCollectorImp() = default; - ~NullCollectorImp() = default; + ~NullCollectorImp() override = default; Hook make_hook(HookImpl::HandlerType const&) override @@ -153,5 +148,4 @@ NullCollector::New() return std::make_shared(); } -} // namespace insight -} // namespace beast +} // namespace beast::insight diff --git a/src/libxrpl/beast/insight/StatsDCollector.cpp b/src/libxrpl/beast/insight/StatsDCollector.cpp index 7d80e1a052..d11e85830f 100644 --- a/src/libxrpl/beast/insight/StatsDCollector.cpp +++ b/src/libxrpl/beast/insight/StatsDCollector.cpp @@ -1,3 +1,5 @@ +#include + #include #include #include @@ -5,7 +7,6 @@ #include #include #include -#include #include #include #include @@ -13,12 +14,14 @@ #include #include #include +#include #include #include #include #include #include #include +#include #include #include @@ -38,8 +41,7 @@ #define BEAST_STATSDCOLLECTOR_TRACING_ENABLED 0 #endif -namespace beast { -namespace insight { +namespace beast::insight { namespace detail { @@ -64,17 +66,17 @@ public: class StatsDHookImpl : public HookImpl, public StatsDMetricBase { public: - StatsDHookImpl(HandlerType const& handler, std::shared_ptr const& impl); + StatsDHookImpl(HandlerType handler, std::shared_ptr const& impl); ~StatsDHookImpl() override; void do_process() override; -private: StatsDHookImpl& - operator=(StatsDHookImpl const&); + operator=(StatsDHookImpl const&) = delete; +private: std::shared_ptr m_impl; HandlerType m_handler; }; @@ -84,7 +86,7 @@ private: class StatsDCounterImpl : public CounterImpl, public StatsDMetricBase { public: - StatsDCounterImpl(std::string const& name, std::shared_ptr const& impl); + StatsDCounterImpl(std::string name, std::shared_ptr const& impl); ~StatsDCounterImpl() override; @@ -98,10 +100,10 @@ public: void do_process() override; -private: StatsDCounterImpl& - operator=(StatsDCounterImpl const&); + operator=(StatsDCounterImpl const&) = delete; +private: std::shared_ptr m_impl; std::string m_name; CounterImpl::value_type m_value{0}; @@ -113,9 +115,9 @@ private: class StatsDEventImpl : public EventImpl { public: - StatsDEventImpl(std::string const& name, std::shared_ptr const& impl); + StatsDEventImpl(std::string name, std::shared_ptr const& impl); - ~StatsDEventImpl() = default; + ~StatsDEventImpl() override = default; void notify(EventImpl::value_type const& value) override; @@ -138,7 +140,7 @@ private: class StatsDGaugeImpl : public GaugeImpl, public StatsDMetricBase { public: - StatsDGaugeImpl(std::string const& name, std::shared_ptr const& impl); + StatsDGaugeImpl(std::string name, std::shared_ptr const& impl); ~StatsDGaugeImpl() override; @@ -156,10 +158,10 @@ public: void do_process() override; -private: StatsDGaugeImpl& - operator=(StatsDGaugeImpl const&); + operator=(StatsDGaugeImpl const&) = delete; +private: std::shared_ptr m_impl; std::string m_name; GaugeImpl::value_type m_last_value{0}; @@ -172,9 +174,7 @@ private: class StatsDMeterImpl : public MeterImpl, public StatsDMetricBase { public: - explicit StatsDMeterImpl( - std::string const& name, - std::shared_ptr const& impl); + explicit StatsDMeterImpl(std::string name, std::shared_ptr const& impl); ~StatsDMeterImpl() override; @@ -188,10 +188,10 @@ public: void do_process() override; -private: StatsDMeterImpl& - operator=(StatsDMeterImpl const&); + operator=(StatsDMeterImpl const&) = delete; +private: std::shared_ptr m_impl; std::string m_name; MeterImpl::value_type m_value{0}; @@ -231,10 +231,10 @@ private: } public: - StatsDCollectorImp(IP::Endpoint const& address, std::string const& prefix, Journal journal) + StatsDCollectorImp(IP::Endpoint address, std::string prefix, Journal journal) : m_journal(journal) - , m_address(address) - , m_prefix(prefix) + , m_address(std::move(address)) + , m_prefix(std::move(prefix)) , m_work(boost::asio::make_work_guard(m_io_context)) , m_strand(boost::asio::make_strand(m_io_context)) , m_timer(m_io_context) @@ -481,10 +481,8 @@ public: //------------------------------------------------------------------------------ -StatsDHookImpl::StatsDHookImpl( - HandlerType const& handler, - std::shared_ptr const& impl) - : m_impl(impl), m_handler(handler) +StatsDHookImpl::StatsDHookImpl(HandlerType handler, std::shared_ptr const& impl) + : m_impl(impl), m_handler(std::move(handler)) { m_impl->add(*this); } @@ -503,9 +501,9 @@ StatsDHookImpl::do_process() //------------------------------------------------------------------------------ StatsDCounterImpl::StatsDCounterImpl( - std::string const& name, + std::string name, std::shared_ptr const& impl) - : m_impl(impl), m_name(name) + : m_impl(impl), m_name(std::move(name)) { m_impl->add(*this); } @@ -555,10 +553,8 @@ StatsDCounterImpl::do_process() //------------------------------------------------------------------------------ -StatsDEventImpl::StatsDEventImpl( - std::string const& name, - std::shared_ptr const& impl) - : m_impl(impl), m_name(name) +StatsDEventImpl::StatsDEventImpl(std::string name, std::shared_ptr const& impl) + : m_impl(impl), m_name(std::move(name)) { } @@ -584,10 +580,8 @@ StatsDEventImpl::do_notify(EventImpl::value_type const& value) //------------------------------------------------------------------------------ -StatsDGaugeImpl::StatsDGaugeImpl( - std::string const& name, - std::shared_ptr const& impl) - : m_impl(impl), m_name(name) +StatsDGaugeImpl::StatsDGaugeImpl(std::string name, std::shared_ptr const& impl) + : m_impl(impl), m_name(std::move(name)) { m_impl->add(*this); } @@ -673,10 +667,8 @@ StatsDGaugeImpl::do_process() //------------------------------------------------------------------------------ -StatsDMeterImpl::StatsDMeterImpl( - std::string const& name, - std::shared_ptr const& impl) - : m_impl(impl), m_name(name) +StatsDMeterImpl::StatsDMeterImpl(std::string name, std::shared_ptr const& impl) + : m_impl(impl), m_name(std::move(name)) { m_impl->add(*this); } @@ -734,5 +726,4 @@ StatsDCollector::New(IP::Endpoint const& address, std::string const& prefix, Jou return std::make_shared(address, prefix, journal); } -} // namespace insight -} // namespace beast +} // namespace beast::insight diff --git a/src/libxrpl/beast/net/IPAddressConversion.cpp b/src/libxrpl/beast/net/IPAddressConversion.cpp index d34a973d8e..14cb4bd44d 100644 --- a/src/libxrpl/beast/net/IPAddressConversion.cpp +++ b/src/libxrpl/beast/net/IPAddressConversion.cpp @@ -1,11 +1,11 @@ #include + #include #include #include -namespace beast { -namespace IP { +namespace beast::IP { Endpoint from_asio(boost::asio::ip::address const& address) @@ -31,5 +31,4 @@ to_asio_endpoint(Endpoint const& endpoint) return boost::asio::ip::tcp::endpoint{endpoint.address(), endpoint.port()}; } -} // namespace IP -} // namespace beast +} // namespace beast::IP diff --git a/src/libxrpl/beast/net/IPAddressV4.cpp b/src/libxrpl/beast/net/IPAddressV4.cpp index c65e6fcf89..f39e511c4c 100644 --- a/src/libxrpl/beast/net/IPAddressV4.cpp +++ b/src/libxrpl/beast/net/IPAddressV4.cpp @@ -1,7 +1,6 @@ #include -namespace beast { -namespace IP { +namespace beast::IP { bool is_private(AddressV4 const& addr) @@ -25,5 +24,4 @@ get_class(AddressV4 const& addr) return table[(addr.to_uint() & 0xE0000000) >> 29]; } -} // namespace IP -} // namespace beast +} // namespace beast::IP diff --git a/src/libxrpl/beast/net/IPAddressV6.cpp b/src/libxrpl/beast/net/IPAddressV6.cpp index 30e2eefb96..3dbadf9779 100644 --- a/src/libxrpl/beast/net/IPAddressV6.cpp +++ b/src/libxrpl/beast/net/IPAddressV6.cpp @@ -1,10 +1,10 @@ -#include #include -#include +#include -namespace beast { -namespace IP { +#include + +namespace beast::IP { bool is_private(AddressV6 const& addr) @@ -22,5 +22,4 @@ is_public(AddressV6 const& addr) return !is_private(addr) && !addr.is_multicast(); } -} // namespace IP -} // namespace beast +} // namespace beast::IP diff --git a/src/libxrpl/beast/net/IPEndpoint.cpp b/src/libxrpl/beast/net/IPEndpoint.cpp index 7e2799b46c..e1c7394b42 100644 --- a/src/libxrpl/beast/net/IPEndpoint.cpp +++ b/src/libxrpl/beast/net/IPEndpoint.cpp @@ -1,9 +1,9 @@ -#include #include +#include + #include #include -#include #include #include @@ -12,15 +12,15 @@ #include #include #include +#include -namespace beast { -namespace IP { +namespace beast::IP { Endpoint::Endpoint() : m_port(0) { } -Endpoint::Endpoint(Address const& addr, Port port) : m_addr(addr), m_port(port) +Endpoint::Endpoint(Address addr, Port port) : m_addr(std::move(addr)), m_port(port) { } @@ -176,5 +176,4 @@ operator>>(std::istream& is, Endpoint& endpoint) return is; } -} // namespace IP -} // namespace beast +} // namespace beast::IP diff --git a/src/libxrpl/beast/utility/beast_PropertyStream.cpp b/src/libxrpl/beast/utility/beast_PropertyStream.cpp index 662d763ce0..ddfe075132 100644 --- a/src/libxrpl/beast/utility/beast_PropertyStream.cpp +++ b/src/libxrpl/beast/utility/beast_PropertyStream.cpp @@ -1,3 +1,4 @@ +#include #include #include @@ -15,7 +16,7 @@ namespace beast { // //------------------------------------------------------------------------------ -PropertyStream::Item::Item(Source* source) : ListNode(), m_source(source) +PropertyStream::Item::Item(Source* source) : m_source(source) { } @@ -43,7 +44,7 @@ PropertyStream::Item::operator*() const // //------------------------------------------------------------------------------ -PropertyStream::Proxy::Proxy(Map const& map, std::string const& key) : m_map(&map), m_key(key) +PropertyStream::Proxy::Proxy(Map const& map, std::string key) : m_map(&map), m_key(std::move(key)) { } @@ -151,7 +152,7 @@ PropertyStream::Set::stream() const // //------------------------------------------------------------------------------ -PropertyStream::Source::Source(std::string const& name) : m_name(name), item_(this) +PropertyStream::Source::Source(std::string name) : m_name(std::move(name)), item_(this) { } diff --git a/src/libxrpl/conditions/Condition.cpp b/src/libxrpl/conditions/Condition.cpp index 30beba3402..ce3b55a827 100644 --- a/src/libxrpl/conditions/Condition.cpp +++ b/src/libxrpl/conditions/Condition.cpp @@ -1,9 +1,18 @@ #include + +#include +#include #include +#include #include -namespace xrpl { -namespace cryptoconditions { +#include +#include +#include +#include +#include + +namespace xrpl::cryptoconditions { namespace detail { // The binary encoding of conditions differs based on their @@ -210,5 +219,4 @@ Condition::deserialize(Slice s, std::error_code& ec) return c; } -} // namespace cryptoconditions -} // namespace xrpl +} // namespace xrpl::cryptoconditions diff --git a/src/libxrpl/conditions/Fulfillment.cpp b/src/libxrpl/conditions/Fulfillment.cpp index 11581a8705..d1f48bfd7c 100644 --- a/src/libxrpl/conditions/Fulfillment.cpp +++ b/src/libxrpl/conditions/Fulfillment.cpp @@ -1,11 +1,16 @@ +#include + +#include #include #include -#include #include +#include #include -namespace xrpl { -namespace cryptoconditions { +#include +#include + +namespace xrpl::cryptoconditions { bool match(Fulfillment const& f, Condition const& c) @@ -128,5 +133,4 @@ Fulfillment::deserialize(Slice s, std::error_code& ec) return f; } -} // namespace cryptoconditions -} // namespace xrpl +} // namespace xrpl::cryptoconditions diff --git a/src/libxrpl/conditions/error.cpp b/src/libxrpl/conditions/error.cpp index 9c9d4658e4..15ac847118 100644 --- a/src/libxrpl/conditions/error.cpp +++ b/src/libxrpl/conditions/error.cpp @@ -1,10 +1,12 @@ -#include #include -#include +#include -namespace xrpl { -namespace cryptoconditions { +#include +#include +#include + +namespace xrpl::cryptoconditions { namespace detail { class cryptoconditions_error_category : public std::error_category @@ -109,9 +111,8 @@ std::error_code make_error_code(error ev) { return std::error_code{ - safe_cast::type>(ev), + safe_cast>(ev), detail::get_cryptoconditions_error_category()}; } -} // namespace cryptoconditions -} // namespace xrpl +} // namespace xrpl::cryptoconditions diff --git a/src/libxrpl/core/HashRouter.cpp b/src/libxrpl/core/HashRouter.cpp index f21daf84a2..4175d83340 100644 --- a/src/libxrpl/core/HashRouter.cpp +++ b/src/libxrpl/core/HashRouter.cpp @@ -1,5 +1,17 @@ #include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + namespace xrpl { auto diff --git a/src/libxrpl/core/detail/Job.cpp b/src/libxrpl/core/detail/Job.cpp index 7e5ca274b3..069cc7f216 100644 --- a/src/libxrpl/core/detail/Job.cpp +++ b/src/libxrpl/core/detail/Job.cpp @@ -1,6 +1,14 @@ -#include #include +#include +#include +#include + +#include +#include +#include +#include + namespace xrpl { Job::Job() : mType(jtINVALID), mJobIndex(0) diff --git a/src/libxrpl/core/detail/JobQueue.cpp b/src/libxrpl/core/detail/JobQueue.cpp index ddfc42c97e..d55160f285 100644 --- a/src/libxrpl/core/detail/JobQueue.cpp +++ b/src/libxrpl/core/detail/JobQueue.cpp @@ -1,8 +1,23 @@ -#include #include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include #include +#include +#include +#include namespace xrpl { @@ -164,9 +179,7 @@ JobQueue::addLoadEvents(JobType t, int count, std::chrono::milliseconds elapsed) bool JobQueue::isOverloaded() { - return std::any_of(m_jobData.begin(), m_jobData.end(), [](auto& entry) { - return entry.second.load().isOver(); - }); + return std::ranges::any_of(m_jobData, [](auto& entry) { return entry.second.load().isOver(); }); } Json::Value diff --git a/src/libxrpl/core/detail/LoadEvent.cpp b/src/libxrpl/core/detail/LoadEvent.cpp index 1370e9089f..a2f8ee7620 100644 --- a/src/libxrpl/core/detail/LoadEvent.cpp +++ b/src/libxrpl/core/detail/LoadEvent.cpp @@ -1,13 +1,18 @@ -#include #include + +#include #include +#include +#include +#include + namespace xrpl { -LoadEvent::LoadEvent(LoadMonitor& monitor, std::string const& name, bool shouldStart) +LoadEvent::LoadEvent(LoadMonitor& monitor, std::string name, bool shouldStart) : monitor_(monitor) , running_(shouldStart) - , name_(name) + , name_(std::move(name)) , mark_{std::chrono::steady_clock::now()} , timeWaiting_{} , timeRunning_{} diff --git a/src/libxrpl/core/detail/LoadMonitor.cpp b/src/libxrpl/core/detail/LoadMonitor.cpp index e613717ed8..2cf4e1f6ea 100644 --- a/src/libxrpl/core/detail/LoadMonitor.cpp +++ b/src/libxrpl/core/detail/LoadMonitor.cpp @@ -1,6 +1,12 @@ +#include + #include #include -#include +#include +#include + +#include +#include namespace xrpl { diff --git a/src/libxrpl/core/detail/Workers.cpp b/src/libxrpl/core/detail/Workers.cpp index ed9d09d1e8..e7d63c0900 100644 --- a/src/libxrpl/core/detail/Workers.cpp +++ b/src/libxrpl/core/detail/Workers.cpp @@ -1,17 +1,23 @@ -#include -#include #include +#include +#include +#include + +#include +#include +#include + namespace xrpl { Workers::Workers( Callback& callback, perf::PerfLog* perfLog, - std::string const& threadNames, + std::string threadNames, int numberOfThreads) : m_callback(callback) , perfLog_(perfLog) - , m_threadNames(threadNames) + , m_threadNames(std::move(threadNames)) , m_semaphore(0) , m_activeCount(0) , m_pauseCount(0) @@ -135,8 +141,8 @@ Workers::deleteWorkers(beast::LockFreeStack& stack) //------------------------------------------------------------------------------ -Workers::Worker::Worker(Workers& workers, std::string const& threadName, int const instance) - : m_workers{workers}, threadName_{threadName}, instance_{instance} +Workers::Worker::Worker(Workers& workers, std::string threadName, int const instance) + : m_workers{workers}, threadName_{std::move(threadName)}, instance_{instance} { thread_ = std::thread{&Workers::Worker::run, this}; diff --git a/src/libxrpl/crypto/RFC1751.cpp b/src/libxrpl/crypto/RFC1751.cpp index f7098f3833..8ea4e7007c 100644 --- a/src/libxrpl/crypto/RFC1751.cpp +++ b/src/libxrpl/crypto/RFC1751.cpp @@ -1,6 +1,7 @@ -#include #include +#include + #include #include #include diff --git a/src/libxrpl/crypto/csprng.cpp b/src/libxrpl/crypto/csprng.cpp index 343bed9be0..dc16c9ac64 100644 --- a/src/libxrpl/crypto/csprng.cpp +++ b/src/libxrpl/crypto/csprng.cpp @@ -1,8 +1,9 @@ -#include #include +#include + +#include #include -#include #include #include diff --git a/src/libxrpl/git/Git.cpp b/src/libxrpl/git/Git.cpp index 2992852632..e13b2ef693 100644 --- a/src/libxrpl/git/Git.cpp +++ b/src/libxrpl/git/Git.cpp @@ -1,4 +1,4 @@ -#include "xrpl/git/Git.h" +#include #include diff --git a/src/libxrpl/json/JsonPropertyStream.cpp b/src/libxrpl/json/JsonPropertyStream.cpp index fb5a7b32a4..ab94223956 100644 --- a/src/libxrpl/json/JsonPropertyStream.cpp +++ b/src/libxrpl/json/JsonPropertyStream.cpp @@ -1,4 +1,5 @@ #include + #include #include diff --git a/src/libxrpl/json/Output.cpp b/src/libxrpl/json/Output.cpp index 626dbeb4b8..e588d21d3c 100644 --- a/src/libxrpl/json/Output.cpp +++ b/src/libxrpl/json/Output.cpp @@ -1,4 +1,5 @@ #include + #include #include diff --git a/src/libxrpl/json/Writer.cpp b/src/libxrpl/json/Writer.cpp index ea7b4b51ae..75b5bc113c 100644 --- a/src/libxrpl/json/Writer.cpp +++ b/src/libxrpl/json/Writer.cpp @@ -1,11 +1,12 @@ +#include + #include #include -#include #include #include #include -#include +#include // IWYU pragma: keep #include #include #include @@ -62,7 +63,7 @@ lengthWithoutTrailingZeros(std::string const& s) class Writer::Impl { public: - explicit Impl(Output const& output) : output_(output) + explicit Impl(Output output) : output_(std::move(output)) { } ~Impl() = default; @@ -82,8 +83,7 @@ public: { char const ch = (ct == array) ? openBracket : openBrace; output({&ch, 1}); - stack_.push(Collection()); - stack_.top().type = ct; + stack_.emplace(Collection{.type = ct}); } void @@ -197,8 +197,6 @@ private: // JSON collections are either arrays, or objects. struct Collection { - explicit Collection() = default; - /** What type of collection are we in? */ Writer::CollectionType type = Writer::CollectionType::array; @@ -208,7 +206,7 @@ private: #ifndef NDEBUG /** What tags have we already seen in this collection? */ - std::set tags; + std::set tags{}; // NOLINT(readability-redundant-member-init) #endif }; diff --git a/src/libxrpl/json/json_reader.cpp b/src/libxrpl/json/json_reader.cpp index 71365ba6c1..1bb157afc3 100644 --- a/src/libxrpl/json/json_reader.cpp +++ b/src/libxrpl/json/json_reader.cpp @@ -1,5 +1,6 @@ -#include #include + +#include #include #include @@ -364,8 +365,7 @@ Reader::readNumber() { if (std::isdigit(static_cast(*current_)) == 0) { - auto ret = - std::find(std::begin(extended_tokens), std::end(extended_tokens), *current_); + auto ret = std::ranges::find(extended_tokens, *current_); if (ret == std::end(extended_tokens)) break; diff --git a/src/libxrpl/json/json_value.cpp b/src/libxrpl/json/json_value.cpp index d4351b23ef..61e0a0c7ee 100644 --- a/src/libxrpl/json/json_value.cpp +++ b/src/libxrpl/json/json_value.cpp @@ -1,13 +1,17 @@ +#include + +#include #include #include #include #include -#include #include #include +#include #include #include +#include #include #include @@ -18,7 +22,7 @@ Value const Value::null; class DefaultValueAllocator : public ValueAllocator { public: - virtual ~DefaultValueAllocator() = default; + ~DefaultValueAllocator() override = default; char* makeMemberName(char const* memberName) override @@ -1050,7 +1054,7 @@ Value::getMemberNames() const ObjectValues::const_iterator const itEnd = value_.map_->end(); for (; it != itEnd; ++it) - members.push_back(std::string((*it).first.c_str())); + members.emplace_back((*it).first.c_str()); return members; } diff --git a/src/libxrpl/json/json_valueiterator.cpp b/src/libxrpl/json/json_valueiterator.cpp index b3cf7e6538..595835d9c0 100644 --- a/src/libxrpl/json/json_valueiterator.cpp +++ b/src/libxrpl/json/json_valueiterator.cpp @@ -155,9 +155,7 @@ ValueIterator::ValueIterator(ValueConstIterator const& other) : ValueIteratorBas { } -ValueIterator::ValueIterator(ValueIterator const& other) : ValueIteratorBase(other) -{ -} +ValueIterator::ValueIterator(ValueIterator const& other) = default; ValueIterator& ValueIterator::operator=(SelfType const& other) diff --git a/src/libxrpl/json/json_writer.cpp b/src/libxrpl/json/json_writer.cpp index 150a7fe2e5..7846ed6ce1 100644 --- a/src/libxrpl/json/json_writer.cpp +++ b/src/libxrpl/json/json_writer.cpp @@ -1,7 +1,8 @@ +#include + #include #include #include -#include #include #include @@ -252,9 +253,7 @@ FastWriter::writeValue(Value const& value) // Class StyledWriter // ////////////////////////////////////////////////////////////////// -StyledWriter::StyledWriter() -{ -} +StyledWriter::StyledWriter() = default; std::string StyledWriter::write(Value const& root) @@ -486,7 +485,8 @@ StyledWriter::unindent() // Class StyledStreamWriter // ////////////////////////////////////////////////////////////////// -StyledStreamWriter::StyledStreamWriter(std::string indentation) : indentation_(indentation) +StyledStreamWriter::StyledStreamWriter(std::string indentation) + : indentation_(std::move(indentation)) { } diff --git a/src/libxrpl/json/to_string.cpp b/src/libxrpl/json/to_string.cpp index d3b35308a0..1d1a609366 100644 --- a/src/libxrpl/json/to_string.cpp +++ b/src/libxrpl/json/to_string.cpp @@ -1,6 +1,7 @@ -#include #include +#include + #include namespace Json { diff --git a/src/libxrpl/ledger/AcceptedLedgerTx.cpp b/src/libxrpl/ledger/AcceptedLedgerTx.cpp index 005d48e6f6..e609f5bdc8 100644 --- a/src/libxrpl/ledger/AcceptedLedgerTx.cpp +++ b/src/libxrpl/ledger/AcceptedLedgerTx.cpp @@ -1,11 +1,24 @@ -#include -#include #include -#include -#include -#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include + namespace xrpl { AcceptedLedgerTx::AcceptedLedgerTx( @@ -43,13 +56,14 @@ AcceptedLedgerTx::AcceptedLedgerTx( auto const amount = mTxn->getFieldAmount(sfTakerGets); // If the offer create is not self funded then add the owner balance - if (account != amount.issue().account) + if (account != amount.getIssuer()) { auto const ownerFunds = accountFunds( *ledger, account, amount, fhIGNORE_FREEZE, + ahIGNORE_AUTH, beast::Journal{beast::Journal::getNullSink()}); mJson[jss::transaction][jss::owner_funds] = ownerFunds.getText(); } diff --git a/src/libxrpl/ledger/ApplyStateTable.cpp b/src/libxrpl/ledger/ApplyStateTable.cpp index 9ebbca8ac5..d7cbcb06e8 100644 --- a/src/libxrpl/ledger/ApplyStateTable.cpp +++ b/src/libxrpl/ledger/ApplyStateTable.cpp @@ -1,12 +1,38 @@ -#include -#include -#include #include -#include -#include -namespace xrpl { -namespace detail { +#include +#include +#include +#include +#include +#include // IWYU pragma: keep +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl::detail { void ApplyStateTable::apply(RawView& to) const @@ -374,14 +400,14 @@ ApplyStateTable::erase(ReadView const& base, std::shared_ptr const& sle) { auto const iter = items_.find(sle->key()); if (iter == items_.end()) - LogicError("ApplyStateTable::erase: missing key"); + Throw("ApplyStateTable::erase: missing key"); auto& item = iter->second; if (item.second != sle) - LogicError("ApplyStateTable::erase: unknown SLE"); + Throw("ApplyStateTable::erase: unknown SLE"); switch (item.first) { case Action::erase: - LogicError("ApplyStateTable::erase: double erase"); + Throw("ApplyStateTable::erase: double erase"); break; case Action::insert: items_.erase(iter); @@ -405,7 +431,7 @@ ApplyStateTable::rawErase(ReadView const& base, std::shared_ptr const& sle) switch (item.first) { case Action::erase: - LogicError("ApplyStateTable::rawErase: double erase"); + Throw("ApplyStateTable::rawErase: double erase"); break; case Action::insert: items_.erase(result.first); @@ -436,11 +462,11 @@ ApplyStateTable::insert(ReadView const& base, std::shared_ptr const& sle) switch (item.first) { case Action::cache: - LogicError("ApplyStateTable::insert: already cached"); + Throw("ApplyStateTable::insert: already cached"); case Action::insert: - LogicError("ApplyStateTable::insert: already inserted"); + Throw("ApplyStateTable::insert: already inserted"); case Action::modify: - LogicError("ApplyStateTable::insert: already modified"); + Throw("ApplyStateTable::insert: already modified"); case Action::erase: break; } @@ -466,7 +492,7 @@ ApplyStateTable::replace(ReadView const& base, std::shared_ptr const& sle) switch (item.first) { case Action::erase: - LogicError("ApplyStateTable::replace: already erased"); + Throw("ApplyStateTable::replace: already erased"); case Action::cache: item.first = Action::modify; break; @@ -482,14 +508,14 @@ ApplyStateTable::update(ReadView const& base, std::shared_ptr const& sle) { auto const iter = items_.find(sle->key()); if (iter == items_.end()) - LogicError("ApplyStateTable::update: missing key"); + Throw("ApplyStateTable::update: missing key"); auto& item = iter->second; if (item.second != sle) - LogicError("ApplyStateTable::update: unknown SLE"); + Throw("ApplyStateTable::update: unknown SLE"); switch (item.first) { case Action::erase: - LogicError("ApplyStateTable::update: erased"); + Throw("ApplyStateTable::update: erased"); break; case Action::cache: item.first = Action::modify; @@ -642,5 +668,4 @@ ApplyStateTable::threadOwners( } } -} // namespace detail -} // namespace xrpl +} // namespace xrpl::detail diff --git a/src/libxrpl/ledger/ApplyView.cpp b/src/libxrpl/ledger/ApplyView.cpp index 476b635511..a3b138dc63 100644 --- a/src/libxrpl/ledger/ApplyView.cpp +++ b/src/libxrpl/ledger/ApplyView.cpp @@ -1,9 +1,25 @@ +#include + +#include #include #include -#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include #include namespace xrpl { @@ -40,9 +56,9 @@ findPreviousPage(ApplyView& view, Keylet const& directory, SLE::ref start) { node = view.peek(keylet::page(directory, page)); if (!node) - { // LCOV_EXCL_START - LogicError("Directory chain: root back-pointer broken."); - // LCOV_EXCL_STOP + { + Throw( + "Directory chain: root back-pointer broken."); // LCOV_EXCL_LINE } } @@ -61,22 +77,21 @@ insertKey( { if (preserveOrder) { - if (std::find(indexes.begin(), indexes.end(), key) != indexes.end()) - LogicError("dirInsert: double insertion"); // LCOV_EXCL_LINE + if (std::ranges::find(indexes, key) != indexes.end()) + Throw("dirInsert: double insertion"); // LCOV_EXCL_LINE indexes.push_back(key); } else { - // We can't be sure if this page is already sorted because - // it may be a legacy page we haven't yet touched. Take - // the time to sort it. - std::sort(indexes.begin(), indexes.end()); + // We can't be sure if this page is already sorted because it may be a + // legacy page we haven't yet touched. Take the time to sort it. + std::ranges::sort(indexes); - auto pos = std::lower_bound(indexes.begin(), indexes.end(), key); + auto pos = std::ranges::lower_bound(indexes, key); if (pos != indexes.end() && key == *pos) - LogicError("dirInsert: double insertion"); // LCOV_EXCL_LINE + Throw("dirInsert: double insertion"); // LCOV_EXCL_LINE indexes.insert(pos, key); } @@ -129,8 +144,7 @@ insertPage( node->setFieldH256(sfRootIndex, directory.key); node->setFieldV256(sfIndexes, indexes); - // Save some space by not specifying the value 0 since - // it's the default. + // Save some space by not specifying the value 0 since it's the default. if (page != 1) node->setFieldU64(sfIndexPrevious, page - 1); XRPL_ASSERT_PARTS(!nextPage, "xrpl::directory::insertPage", "nextPage has default value"); @@ -199,28 +213,24 @@ ApplyView::emptyDirDelete(Keylet const& directory) auto nextPage = node->getFieldU64(sfIndexNext); if (nextPage == rootPage && prevPage != rootPage) - LogicError("Directory chain: fwd link broken"); // LCOV_EXCL_LINE + Throw("Directory chain: fwd link broken"); // LCOV_EXCL_LINE if (prevPage == rootPage && nextPage != rootPage) - LogicError("Directory chain: rev link broken"); // LCOV_EXCL_LINE + Throw("Directory chain: rev link broken"); // LCOV_EXCL_LINE - // Older versions of the code would, in some cases, allow the last - // page to be empty. Remove such pages: + // Older versions of the code would, in some cases, allow the last page to + // be empty. Remove such pages: if (nextPage == prevPage && nextPage != rootPage) { auto last = peek(keylet::page(directory, nextPage)); if (!last) - { // LCOV_EXCL_START - LogicError("Directory chain: fwd link broken."); - // LCOV_EXCL_STOP - } + Throw("Directory chain: fwd link broken."); // LCOV_EXCL_LINE if (!last->getFieldV256(sfIndexes).empty()) return false; - // Update the first page's linked list and - // mark it as updated. + // Update the first page's linked list and mark it as updated. node->setFieldU64(sfIndexNext, rootPage); node->setFieldU64(sfIndexPrevious, rootPage); update(node); @@ -228,8 +238,7 @@ ApplyView::emptyDirDelete(Keylet const& directory) // And erase the empty last page: erase(last); - // Make sure our local values reflect the - // updated information: + // Make sure our local values reflect the updated information: nextPage = rootPage; prevPage = rootPage; } @@ -254,7 +263,7 @@ ApplyView::dirRemove(Keylet const& directory, std::uint64_t page, uint256 const& { auto entries = node->getFieldV256(sfIndexes); - auto it = std::find(entries.begin(), entries.end(), key); + auto it = std::ranges::find(entries, key); if (entries.end() == it) return false; @@ -269,46 +278,33 @@ ApplyView::dirRemove(Keylet const& directory, std::uint64_t page, uint256 const& return true; } - // The current page is now empty; check if it can be - // deleted, and, if so, whether the entire directory - // can now be removed. + // The current page is now empty; check if it can be deleted, and, if so, + // whether the entire directory can now be removed. auto prevPage = node->getFieldU64(sfIndexPrevious); auto nextPage = node->getFieldU64(sfIndexNext); - // The first page is the directory's root node and is - // treated specially: it can never be deleted even if - // it is empty, unless we plan on removing the entire - // directory. + // The first page is the directory's root node and is treated specially: it + // can never be deleted even if it is empty, unless we plan on removing the + // entire directory. if (page == rootPage) { if (nextPage == page && prevPage != page) - { // LCOV_EXCL_START - LogicError("Directory chain: fwd link broken"); - // LCOV_EXCL_STOP - } + Throw("Directory chain: fwd link broken"); // LCOV_EXCL_LINE if (prevPage == page && nextPage != page) - { // LCOV_EXCL_START - LogicError("Directory chain: rev link broken"); - // LCOV_EXCL_STOP - } + Throw("Directory chain: rev link broken"); // LCOV_EXCL_LINE - // Older versions of the code would, in some cases, - // allow the last page to be empty. Remove such - // pages if we stumble on them: + // Older versions of the code would, in some cases, allow the last page + // to be empty. Remove such pages if we stumble on them: if (nextPage == prevPage && nextPage != page) { auto last = peek(keylet::page(directory, nextPage)); if (!last) - { // LCOV_EXCL_START - LogicError("Directory chain: fwd link broken."); - // LCOV_EXCL_STOP - } + Throw("Directory chain: fwd link broken."); // LCOV_EXCL_LINE if (last->getFieldV256(sfIndexes).empty()) { - // Update the first page's linked list and - // mark it as updated. + // Update the first page's linked list and mark it as updated. node->setFieldU64(sfIndexNext, page); node->setFieldU64(sfIndexPrevious, page); update(node); @@ -316,8 +312,7 @@ ApplyView::dirRemove(Keylet const& directory, std::uint64_t page, uint256 const& // And erase the empty last page: erase(last); - // Make sure our local values reflect the - // updated information: + // Make sure our local values reflect the updated information: nextPage = page; prevPage = page; } @@ -335,25 +330,24 @@ ApplyView::dirRemove(Keylet const& directory, std::uint64_t page, uint256 const& // This can never happen for nodes other than the root: if (nextPage == page) - LogicError("Directory chain: fwd link broken"); // LCOV_EXCL_LINE + Throw("Directory chain: fwd link broken"); // LCOV_EXCL_LINE if (prevPage == page) - LogicError("Directory chain: rev link broken"); // LCOV_EXCL_LINE + Throw("Directory chain: rev link broken"); // LCOV_EXCL_LINE - // This node isn't the root, so it can either be in the - // middle of the list, or at the end. Unlink it first - // and then check if that leaves the list with only a - // root: + // This node isn't the root, so it can either be in the middle of the list, + // or at the end. Unlink it first and then check if that leaves the list + // with only a root: auto prev = peek(keylet::page(directory, prevPage)); if (!prev) - LogicError("Directory chain: fwd link broken."); // LCOV_EXCL_LINE + Throw("Directory chain: fwd link broken."); // LCOV_EXCL_LINE // Fix previous to point to its new next. prev->setFieldU64(sfIndexNext, nextPage); update(prev); auto next = peek(keylet::page(directory, nextPage)); if (!next) - LogicError("Directory chain: rev link broken."); // LCOV_EXCL_LINE + Throw("Directory chain: rev link broken."); // LCOV_EXCL_LINE // Fix next to point to its new previous. next->setFieldU64(sfIndexPrevious, prevPage); update(next); @@ -361,13 +355,12 @@ ApplyView::dirRemove(Keylet const& directory, std::uint64_t page, uint256 const& // The page is no longer linked. Delete it. erase(node); - // Check whether the next page is the last page and, if - // so, whether it's empty. If it is, delete it. + // Check whether the next page is the last page and, if so, whether it's + // empty. If it is, delete it. if (nextPage != rootPage && next->getFieldU64(sfIndexNext) == rootPage && next->getFieldV256(sfIndexes).empty()) { - // Since next doesn't point to the root, it - // can't be pointing to prev. + // Since next doesn't point to the root, it can't be pointing to prev. erase(next); // The previous page is now the last page: @@ -377,18 +370,16 @@ ApplyView::dirRemove(Keylet const& directory, std::uint64_t page, uint256 const& // And the root points to the last page: auto root = peek(keylet::page(directory, rootPage)); if (!root) - { // LCOV_EXCL_START - LogicError("Directory chain: root link broken."); - // LCOV_EXCL_STOP - } + Throw("Directory chain: root link broken."); // LCOV_EXCL_LINE + root->setFieldU64(sfIndexPrevious, prevPage); update(root); nextPage = rootPage; } - // If we're not keeping the root, then check to see if - // it's left empty. If so, delete it as well. + // If we're not keeping the root, then check to see if it's left empty. + // If so, delete it as well. if (!keepRoot && nextPage == rootPage && prevPage == rootPage) { if (prev->getFieldV256(sfIndexes).empty()) diff --git a/src/libxrpl/ledger/ApplyViewBase.cpp b/src/libxrpl/ledger/ApplyViewBase.cpp index a5bea68759..d617279a80 100644 --- a/src/libxrpl/ledger/ApplyViewBase.cpp +++ b/src/libxrpl/ledger/ApplyViewBase.cpp @@ -1,7 +1,19 @@ #include -namespace xrpl { -namespace detail { +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace xrpl::detail { ApplyViewBase::ApplyViewBase(ReadView const* base, ApplyFlags flags) : flags_(flags), base_(base) { @@ -152,5 +164,4 @@ ApplyViewBase::rawDestroyXRP(XRPAmount const& fee) items_.destroyXRP(fee); } -} // namespace detail -} // namespace xrpl +} // namespace xrpl::detail diff --git a/src/libxrpl/ledger/ApplyViewImpl.cpp b/src/libxrpl/ledger/ApplyViewImpl.cpp index eca9043db8..9650190a3e 100644 --- a/src/libxrpl/ledger/ApplyViewImpl.cpp +++ b/src/libxrpl/ledger/ApplyViewImpl.cpp @@ -1,5 +1,21 @@ #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + namespace xrpl { ApplyViewImpl::ApplyViewImpl(ReadView const* base, ApplyFlags flags) : ApplyViewBase(base, flags) diff --git a/src/libxrpl/ledger/BookDirs.cpp b/src/libxrpl/ledger/BookDirs.cpp index 2bdf6ac9a5..cbe93a159b 100644 --- a/src/libxrpl/ledger/BookDirs.cpp +++ b/src/libxrpl/ledger/BookDirs.cpp @@ -1,8 +1,15 @@ #include -#include + +#include +#include +#include +#include #include +#include #include +#include + namespace xrpl { BookDirs::BookDirs(ReadView const& view, Book const& book) diff --git a/src/libxrpl/ledger/BookListeners.cpp b/src/libxrpl/ledger/BookListeners.cpp index 8699d891a0..36d02427bc 100644 --- a/src/libxrpl/ledger/BookListeners.cpp +++ b/src/libxrpl/ledger/BookListeners.cpp @@ -1,5 +1,13 @@ #include +#include +#include +#include +#include + +#include +#include + namespace xrpl { void diff --git a/src/libxrpl/ledger/CachedView.cpp b/src/libxrpl/ledger/CachedView.cpp index aa075d69a4..6bbe2d828b 100644 --- a/src/libxrpl/ledger/CachedView.cpp +++ b/src/libxrpl/ledger/CachedView.cpp @@ -1,8 +1,17 @@ -#include #include -namespace xrpl { -namespace detail { +#include +#include // IWYU pragma: keep +#include +#include +#include +#include + +#include +#include +#include + +namespace xrpl::detail { bool CachedViewImpl::exists(Keylet const& k) const @@ -65,5 +74,4 @@ CachedViewImpl::read(Keylet const& k) const return sle; } -} // namespace detail -} // namespace xrpl +} // namespace xrpl::detail diff --git a/src/libxrpl/ledger/CanonicalTXSet.cpp b/src/libxrpl/ledger/CanonicalTXSet.cpp index 72f45731fb..be5006d33b 100644 --- a/src/libxrpl/ledger/CanonicalTXSet.cpp +++ b/src/libxrpl/ledger/CanonicalTXSet.cpp @@ -1,5 +1,15 @@ #include +#include +#include +#include +#include +#include + +#include +#include +#include + namespace xrpl { bool diff --git a/src/libxrpl/ledger/Dir.cpp b/src/libxrpl/ledger/Dir.cpp index a27171fe12..bf5eaf02fd 100644 --- a/src/libxrpl/ledger/Dir.cpp +++ b/src/libxrpl/ledger/Dir.cpp @@ -1,5 +1,16 @@ #include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + namespace xrpl { using const_iterator = Dir::const_iterator; diff --git a/src/libxrpl/ledger/Ledger.cpp b/src/libxrpl/ledger/Ledger.cpp index 299a82a1f2..ab09224914 100644 --- a/src/libxrpl/ledger/Ledger.cpp +++ b/src/libxrpl/ledger/Ledger.cpp @@ -1,17 +1,49 @@ -#include -#include -#include -#include #include + +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include #include +#include #include #include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include #include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include @@ -118,7 +150,7 @@ public: Ledger::Ledger( create_genesis_t, - Rules const& rules, + Rules rules, Fees const& fees, std::vector const& amendments, Family& family) @@ -126,7 +158,7 @@ Ledger::Ledger( , txMap_(SHAMapType::TRANSACTION, family) , stateMap_(SHAMapType::STATE, family) , fees_(fees) - , rules_(rules) + , rules_(std::move(rules)) , j_(beast::Journal(beast::Journal::getNullSink())) { header_.seq = 1; @@ -153,7 +185,7 @@ Ledger::Ledger( { auto sle = std::make_shared(keylet::fees()); // Whether featureXRPFees is supported will depend on startup options. - if (std::find(amendments.begin(), amendments.end(), featureXRPFees) != amendments.end()) + if (std::ranges::find(amendments, featureXRPFees) != amendments.end()) { sle->at(sfBaseFeeDrops) = fees.base; sle->at(sfReserveBaseDrops) = fees.reserve; @@ -180,7 +212,7 @@ Ledger::Ledger( LedgerHeader const& info, bool& loaded, bool acquire, - Rules const& rules, + Rules rules, Fees const& fees, Family& family, beast::Journal j) @@ -188,7 +220,7 @@ Ledger::Ledger( , txMap_(SHAMapType::TRANSACTION, info.txHash, family) , stateMap_(SHAMapType::STATE, info.accountHash, family) , fees_(fees) - , rules_(rules) + , rules_(std::move(rules)) , header_(info) , j_(j) { @@ -249,11 +281,11 @@ Ledger::Ledger(Ledger const& prevLedger, NetClock::time_point closeTime) } } -Ledger::Ledger(LedgerHeader const& info, Rules const& rules, Family& family) +Ledger::Ledger(LedgerHeader const& info, Rules rules, Family& family) : mImmutable(true) , txMap_(SHAMapType::TRANSACTION, info.txHash, family) , stateMap_(SHAMapType::STATE, info.accountHash, family) - , rules_(rules) + , rules_(std::move(rules)) , header_(info) , j_(beast::Journal(beast::Journal::getNullSink())) { @@ -263,14 +295,14 @@ Ledger::Ledger(LedgerHeader const& info, Rules const& rules, Family& family) Ledger::Ledger( std::uint32_t ledgerSeq, NetClock::time_point closeTime, - Rules const& rules, + Rules rules, Fees const& fees, Family& family) : mImmutable(false) , txMap_(SHAMapType::TRANSACTION, family) , stateMap_(SHAMapType::STATE, family) , fees_(fees) - , rules_(rules) + , rules_(std::move(rules)) , j_(beast::Journal(beast::Journal::getNullSink())) { header_.seq = ledgerSeq; @@ -461,14 +493,14 @@ void Ledger::rawErase(std::shared_ptr const& sle) { if (!stateMap_.delItem(sle->key())) - LogicError("Ledger::rawErase: key not found"); + Throw("Ledger::rawErase: key not found"); } void Ledger::rawErase(uint256 const& key) { if (!stateMap_.delItem(key)) - LogicError("Ledger::rawErase: key not found"); + Throw("Ledger::rawErase: key not found"); } void @@ -478,7 +510,7 @@ Ledger::rawInsert(std::shared_ptr const& sle) sle->add(ss); if (!stateMap_.addGiveItem( SHAMapNodeType::tnACCOUNT_STATE, make_shamapitem(sle->key(), ss.slice()))) - LogicError("Ledger::rawInsert: key already exists"); + Throw("Ledger::rawInsert: key already exists"); } void @@ -488,7 +520,7 @@ Ledger::rawReplace(std::shared_ptr const& sle) sle->add(ss); if (!stateMap_.updateGiveItem( SHAMapNodeType::tnACCOUNT_STATE, make_shamapitem(sle->key(), ss.slice()))) - LogicError("Ledger::rawReplace: key not found"); + Throw("Ledger::rawReplace: key not found"); } void @@ -504,7 +536,7 @@ Ledger::rawTxInsert( s.addVL(txn->peekData()); s.addVL(metaData->peekData()); if (!txMap_.addGiveItem(SHAMapNodeType::tnTRANSACTION_MD, make_shamapitem(key, s.slice()))) - LogicError("duplicate_tx: " + to_string(key)); + Throw("duplicate_tx: " + to_string(key)); } uint256 @@ -522,7 +554,7 @@ Ledger::rawTxInsertWithHash( auto item = make_shamapitem(key, s.slice()); auto hash = sha512Half(HashPrefix::txNode, item->slice(), item->key()); if (!txMap_.addGiveItem(SHAMapNodeType::tnTRANSACTION_MD, std::move(item))) - LogicError("duplicate_tx: " + to_string(key)); + Throw("duplicate_tx: " + to_string(key)); return hash; } diff --git a/src/libxrpl/ledger/OpenView.cpp b/src/libxrpl/ledger/OpenView.cpp index b5e358053c..089788d90b 100644 --- a/src/libxrpl/ledger/OpenView.cpp +++ b/src/libxrpl/ledger/OpenView.cpp @@ -1,6 +1,27 @@ -#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + namespace xrpl { class OpenView::txs_iter_impl : public txs_type::iter_base @@ -70,12 +91,12 @@ OpenView::OpenView(OpenView const& rhs) OpenView::OpenView( open_ledger_t, ReadView const* base, - Rules const& rules, + Rules rules, std::shared_ptr hold) : monotonic_resource_{ std::make_unique(initialBufferSize)} , txs_{monotonic_resource_.get()} - , rules_(rules) + , rules_(std::move(rules)) , header_(base->header()) , base_(base) , hold_(std::move(hold)) @@ -247,7 +268,7 @@ OpenView::rawTxInsert( auto const result = txs_.emplace( std::piecewise_construct, std::forward_as_tuple(key), std::forward_as_tuple(txn, metaData)); if (!result.second) - LogicError("rawTxInsert: duplicate TX id: " + to_string(key)); + Throw("rawTxInsert: duplicate TX id: " + to_string(key)); } } // namespace xrpl diff --git a/src/libxrpl/ledger/PaymentSandbox.cpp b/src/libxrpl/ledger/PaymentSandbox.cpp index a56d730b5a..6b38c4840c 100644 --- a/src/libxrpl/ledger/PaymentSandbox.cpp +++ b/src/libxrpl/ledger/PaymentSandbox.cpp @@ -1,14 +1,33 @@ -#include #include -#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include namespace xrpl { namespace detail { auto -DeferredCredits::makeKey(AccountID const& a1, AccountID const& a2, Currency const& c) -> Key +DeferredCredits::makeKeyIOU(AccountID const& a1, AccountID const& a2, Currency const& c) -> KeyIOU { if (a1 < a2) { @@ -19,21 +38,23 @@ DeferredCredits::makeKey(AccountID const& a1, AccountID const& a2, Currency cons } void -DeferredCredits::credit( +DeferredCredits::creditIOU( AccountID const& sender, AccountID const& receiver, STAmount const& amount, STAmount const& preCreditSenderBalance) { XRPL_ASSERT( - sender != receiver, "xrpl::detail::DeferredCredits::credit : sender is not receiver"); - XRPL_ASSERT(!amount.negative(), "xrpl::detail::DeferredCredits::credit : positive amount"); + sender != receiver, "xrpl::detail::DeferredCredits::creditIOU : sender is not receiver"); + XRPL_ASSERT(!amount.negative(), "xrpl::detail::DeferredCredits::creditIOU : positive amount"); + XRPL_ASSERT( + amount.holds(), "xrpl::detail::DeferredCredits::creditIOU : amount is for Issue"); - auto const k = makeKey(sender, receiver, amount.getCurrency()); - auto i = credits_.find(k); - if (i == credits_.end()) + auto const k = makeKeyIOU(sender, receiver, amount.get().currency); + auto i = creditsIOU_.find(k); + if (i == creditsIOU_.end()) { - Value v; + ValueIOU v; if (sender < receiver) { @@ -48,7 +69,7 @@ DeferredCredits::credit( v.lowAcctOrigBalance = -preCreditSenderBalance; } - credits_[k] = v; + creditsIOU_[k] = v; } else { @@ -65,11 +86,98 @@ DeferredCredits::credit( } } +void +DeferredCredits::creditMPT( + AccountID const& sender, + AccountID const& receiver, + STAmount const& amount, + std::uint64_t preCreditBalanceHolder, + std::int64_t preCreditBalanceIssuer) +{ + XRPL_ASSERT( + amount.holds(), + "xrpl::detail::DeferredCredits::creditMPT : amount is for MPTIssue"); + XRPL_ASSERT(!amount.negative(), "xrpl::detail::DeferredCredits::creditMPT : positive amount"); + XRPL_ASSERT( + sender != receiver, "xrpl::detail::DeferredCredits::creditMPT : sender is not receiver"); + + auto const mptAmtVal = amount.mpt().value(); + auto const& issuer = amount.getIssuer(); + auto const& mptIssue = amount.get(); + auto const& mptID = mptIssue.getMptID(); + bool const isSenderIssuer = sender == issuer; + + auto i = creditsMPT_.find(mptID); + if (i == creditsMPT_.end()) + { + IssuerValueMPT v; + if (isSenderIssuer) + { + v.credit = mptAmtVal; + v.holders[receiver].origBalance = preCreditBalanceHolder; + } + else + { + v.holders[sender].debit = mptAmtVal; + v.holders[sender].origBalance = preCreditBalanceHolder; + } + v.origBalance = preCreditBalanceIssuer; + creditsMPT_.emplace(mptID, std::move(v)); + } + else + { + // only record the balance the first time, do not record it here + auto& v = i->second; + if (isSenderIssuer) + { + v.credit += mptAmtVal; + if (!v.holders.contains(receiver)) + { + v.holders[receiver].origBalance = preCreditBalanceHolder; + } + } + else + { + if (!v.holders.contains(sender)) + { + v.holders[sender].debit = mptAmtVal; + v.holders[sender].origBalance = preCreditBalanceHolder; + } + else + { + v.holders[sender].debit += mptAmtVal; + } + } + } +} + +void +DeferredCredits::issuerSelfDebitMPT( + MPTIssue const& issue, + std::uint64_t amount, + std::int64_t origBalance) +{ + auto const& mptID = issue.getMptID(); + auto i = creditsMPT_.find(mptID); + + if (i == creditsMPT_.end()) + { + IssuerValueMPT v; + v.origBalance = origBalance; + v.selfDebit = amount; + creditsMPT_.emplace(mptID, std::move(v)); + } + else + { + i->second.selfDebit += amount; + } +} + void DeferredCredits::ownerCount(AccountID const& id, std::uint32_t cur, std::uint32_t next) { auto const v = std::max(cur, next); - auto r = ownerCounts_.emplace(std::make_pair(id, v)); + auto r = ownerCounts_.emplace(id, v); if (!r.second) { auto& mapVal = r.first->second; @@ -88,16 +196,16 @@ DeferredCredits::ownerCount(AccountID const& id) const // Get the adjustments for the balance between main and other. auto -DeferredCredits::adjustments( +DeferredCredits::adjustmentsIOU( AccountID const& main, AccountID const& other, - Currency const& currency) const -> std::optional + Currency const& currency) const -> std::optional { - std::optional result; + std::optional result; - Key const k = makeKey(main, other, currency); - auto i = credits_.find(k); - if (i == credits_.end()) + KeyIOU const k = makeKeyIOU(main, other, currency); + auto i = creditsIOU_.find(k); + if (i == creditsIOU_.end()) return result; auto const& v = i->second; @@ -112,12 +220,21 @@ DeferredCredits::adjustments( return result; } +auto +DeferredCredits::adjustmentsMPT(xrpl::MPTID const& mptID) const -> std::optional +{ + auto i = creditsMPT_.find(mptID); + if (i == creditsMPT_.end()) + return std::nullopt; + return i->second; +} + void DeferredCredits::apply(DeferredCredits& to) { - for (auto const& i : credits_) + for (auto const& i : creditsIOU_) { - auto r = to.credits_.emplace(i); + auto r = to.creditsIOU_.emplace(i); if (!r.second) { auto& toVal = r.first->second; @@ -128,6 +245,30 @@ DeferredCredits::apply(DeferredCredits& to) } } + for (auto const& i : creditsMPT_) + { + auto r = to.creditsMPT_.emplace(i); + if (!r.second) + { + auto& toVal = r.first->second; + auto const& fromVal = i.second; + toVal.credit += fromVal.credit; + toVal.selfDebit += fromVal.selfDebit; + for (auto& [k, v] : fromVal.holders) + { + if (!toVal.holders.contains(k)) + { + toVal.holders[k] = v; + } + else + { + toVal.holders[k].debit += v.debit; + } + } + // Do not update the orig balance, it's already correct + } + } + for (auto const& i : ownerCounts_) { auto r = to.ownerCounts_.emplace(i); @@ -143,11 +284,13 @@ DeferredCredits::apply(DeferredCredits& to) } // namespace detail STAmount -PaymentSandbox::balanceHook( +PaymentSandbox::balanceHookIOU( AccountID const& account, AccountID const& issuer, STAmount const& amount) const { + XRPL_ASSERT(amount.holds(), "balanceHookIOU: amount is for Issue"); + /* There are two algorithms here. The pre-switchover algorithm takes the current amount and subtracts the recorded credits. The post-switchover @@ -159,14 +302,14 @@ PaymentSandbox::balanceHook( magnitudes, (B+C)-C may not equal B. */ - auto const currency = amount.getCurrency(); + auto const& currency = amount.get().currency; auto delta = amount.zeroed(); auto lastBal = amount; auto minBal = amount; for (auto curSB = this; curSB != nullptr; curSB = curSB->ps_) { - if (auto adj = curSB->tab_.adjustments(account, issuer, currency)) + if (auto adj = curSB->tab_.adjustmentsIOU(account, issuer, currency)) { delta += adj->debits; lastBal = adj->origBalance; @@ -180,13 +323,13 @@ PaymentSandbox::balanceHook( // to compute usable balance just slightly above what the ledger // calculates (but always less than the actual balance). auto adjustedAmt = std::min({amount, lastBal - delta, minBal}); - adjustedAmt.setIssuer(amount.getIssuer()); + adjustedAmt.get().account = amount.getIssuer(); if (isXRP(issuer) && adjustedAmt < beast::zero) { // A calculated negative XRP balance is not an error case. Consider a // payment snippet that credits a large XRP amount and then debits the - // same amount. The credit can't be used but we subtract the debit and + // same amount. The credit can't be used, but we subtract the debit and // calculate a negative value. It's not an error case. adjustedAmt.clear(); } @@ -194,6 +337,64 @@ PaymentSandbox::balanceHook( return adjustedAmt; } +STAmount +PaymentSandbox::balanceHookMPT(AccountID const& account, MPTIssue const& issue, std::int64_t amount) + const +{ + auto const& issuer = issue.getIssuer(); + bool const accountIsHolder = account != issuer; + + std::int64_t delta = 0; + std::int64_t lastBal = amount; + std::int64_t minBal = amount; + for (auto curSB = this; curSB != nullptr; curSB = curSB->ps_) + { + if (auto adj = curSB->tab_.adjustmentsMPT(issue)) + { + if (accountIsHolder) + { + if (auto const i = adj->holders.find(account); i != adj->holders.end()) + { + delta += i->second.debit; + lastBal = i->second.origBalance; + } + } + else + { + delta += adj->credit; + lastBal = adj->origBalance; + } + minBal = std::min(lastBal, minBal); + } + } + + // The adjusted amount should never be larger than the balance. + + auto const adjustedAmt = std::min({amount, lastBal - delta, minBal}); + + return adjustedAmt > 0 ? STAmount{issue, adjustedAmt} : STAmount{issue}; +} + +STAmount +PaymentSandbox::balanceHookSelfIssueMPT(xrpl::MPTIssue const& issue, std::int64_t amount) const +{ + std::int64_t selfDebited = 0; + std::int64_t lastBal = amount; + for (auto curSB = this; curSB != nullptr; curSB = curSB->ps_) + { + if (auto adj = curSB->tab_.adjustmentsMPT(issue)) + { + selfDebited += adj->selfDebit; + lastBal = adj->origBalance; + } + } + + if (lastBal > selfDebited) + return STAmount{issue, lastBal - selfDebited}; + + return STAmount{issue}; +} + std::uint32_t PaymentSandbox::ownerCountHook(AccountID const& account, std::uint32_t count) const { @@ -207,13 +408,39 @@ PaymentSandbox::ownerCountHook(AccountID const& account, std::uint32_t count) co } void -PaymentSandbox::creditHook( +PaymentSandbox::creditHookIOU( AccountID const& from, AccountID const& to, STAmount const& amount, STAmount const& preCreditBalance) { - tab_.credit(from, to, amount, preCreditBalance); + XRPL_ASSERT(amount.holds(), "creditHookIOU: amount is for Issue"); + + tab_.creditIOU(from, to, amount, preCreditBalance); +} + +void +PaymentSandbox::creditHookMPT( + AccountID const& from, + AccountID const& to, + STAmount const& amount, + std::uint64_t preCreditBalanceHolder, + std::int64_t preCreditBalanceIssuer) +{ + XRPL_ASSERT(amount.holds(), "creditHookMPT: amount is for MPTIssue"); + + tab_.creditMPT(from, to, amount, preCreditBalanceHolder, preCreditBalanceIssuer); +} + +void +PaymentSandbox::issuerSelfDebitHookMPT( + MPTIssue const& issue, + std::uint64_t amount, + std::int64_t origBalance) +{ + XRPL_ASSERT(amount > 0, "PaymentSandbox::issuerSelfDebitHookMPT: amount must be > 0"); + + tab_.issuerSelfDebitMPT(issue, amount, origBalance); } void @@ -346,7 +573,7 @@ PaymentSandbox::balanceChanges(ReadView const& view) const } // The following are now set, put them in the map auto delta = newBalance - oldBalance; - auto const cur = newBalance.getCurrency(); + auto const cur = newBalance.get().currency; result[std::make_tuple(lowID, highID, cur)] = delta; auto r = result.emplace(std::make_tuple(lowID, lowID, cur), delta); if (r.second) diff --git a/src/libxrpl/ledger/RawStateTable.cpp b/src/libxrpl/ledger/RawStateTable.cpp index b411a25b00..f267d2f691 100644 --- a/src/libxrpl/ledger/RawStateTable.cpp +++ b/src/libxrpl/ledger/RawStateTable.cpp @@ -1,8 +1,21 @@ -#include #include -namespace xrpl { -namespace detail { +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace xrpl::detail { class RawStateTable::sles_iter_impl : public ReadView::sles_type::iter_base { @@ -22,7 +35,7 @@ public: items_t::const_iterator end1, ReadView::sles_type::iterator iter0, ReadView::sles_type::iterator end0) - : iter0_(iter0), end0_(end0), iter1_(iter1), end1_(end1) + : iter0_(std::move(iter0)), end0_(std::move(end0)), iter1_(iter1), end1_(end1) { if (iter0_ != end0_) sle0_ = *iter0_; @@ -241,7 +254,7 @@ RawStateTable::erase(std::shared_ptr const& sle) switch (item.action) { case Action::erase: - LogicError("RawStateTable::erase: already erased"); + Throw("RawStateTable::erase: already erased"); break; case Action::insert: items_.erase(result.first); @@ -270,10 +283,10 @@ RawStateTable::insert(std::shared_ptr const& sle) item.sle = sle; break; case Action::insert: - LogicError("RawStateTable::insert: already inserted"); + Throw("RawStateTable::insert: already inserted"); break; case Action::replace: - LogicError("RawStateTable::insert: already exists"); + Throw("RawStateTable::insert: already exists"); break; } } @@ -291,7 +304,7 @@ RawStateTable::replace(std::shared_ptr const& sle) switch (item.action) { case Action::erase: - LogicError("RawStateTable::replace: was erased"); + Throw("RawStateTable::replace: was erased"); break; case Action::insert: case Action::replace: @@ -343,5 +356,4 @@ RawStateTable::slesUpperBound(ReadView const& base, uint256 const& key) const items_.upper_bound(key), items_.end(), base.sles.upper_bound(key), base.sles.end()); } -} // namespace detail -} // namespace xrpl +} // namespace xrpl::detail diff --git a/src/libxrpl/ledger/ReadView.cpp b/src/libxrpl/ledger/ReadView.cpp index 8e6763410c..991dd95251 100644 --- a/src/libxrpl/ledger/ReadView.cpp +++ b/src/libxrpl/ledger/ReadView.cpp @@ -1,5 +1,16 @@ #include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + namespace xrpl { ReadView::sles_type::sles_type(ReadView const& view) : ReadViewFwdRange(view) diff --git a/src/libxrpl/ledger/View.cpp b/src/libxrpl/ledger/View.cpp index 1702d4243b..55735ec0e6 100644 --- a/src/libxrpl/ledger/View.cpp +++ b/src/libxrpl/ledger/View.cpp @@ -1,26 +1,39 @@ -#include -#include -#include -#include -#include #include + +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include #include +#include +#include +#include #include #include +#include +#include #include #include #include -#include +#include +#include +#include +#include #include -#include -#include -#include +#include -#include -#include +#include +#include +#include +#include namespace xrpl { @@ -84,11 +97,10 @@ bool isLPTokenFrozen( ReadView const& view, AccountID const& account, - Issue const& asset, - Issue const& asset2) + Asset const& asset, + Asset const& asset2) { - return isFrozen(view, account, asset.currency, asset.account) || - isFrozen(view, account, asset2.currency, asset2.account); + return isFrozen(view, account, asset) || isFrozen(view, account, asset2); } bool @@ -334,22 +346,19 @@ withdrawToDestExceedsLimit( if (from == to || to == issuer || isXRP(issuer)) return tesSUCCESS; - return std::visit( - [&](TIss const& issue) -> TER { - if constexpr (std::is_same_v) + return amount.asset().visit( + [&](Issue const& issue) -> TER { + auto const& currency = issue.currency; + auto const owed = creditBalance(view, to, issuer, currency); + if (owed <= beast::zero) { - auto const& currency = issue.currency; - auto const owed = creditBalance(view, to, issuer, currency); - if (owed <= beast::zero) - { - auto const limit = creditLimit(view, to, issuer, currency); - if (-owed >= limit || amount > (limit + owed)) - return tecNO_LINE; - } + auto const limit = creditLimit(view, to, issuer, currency); + if (-owed >= limit || amount > (limit + owed)) + return tecNO_LINE; } return tesSUCCESS; }, - amount.asset().value()); + [](MPTIssue const&) -> TER { return tesSUCCESS; }); } [[nodiscard]] TER diff --git a/src/libxrpl/ledger/helpers/AMMHelpers.cpp b/src/libxrpl/ledger/helpers/AMMHelpers.cpp new file mode 100644 index 0000000000..94ca2469f7 --- /dev/null +++ b/src/libxrpl/ledger/helpers/AMMHelpers.cpp @@ -0,0 +1,950 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl { + +STAmount +ammLPTokens(STAmount const& asset1, STAmount const& asset2, Asset const& lptIssue) +{ + // AMM invariant: sqrt(asset1 * asset2) >= LPTokensBalance + auto const rounding = isFeatureEnabled(fixAMMv1_3) ? Number::downward : Number::getround(); + NumberRoundModeGuard const g(rounding); + auto const tokens = root2(asset1 * asset2); + return toSTAmount(lptIssue, tokens); +} + +/* + * Equation 3: + * t = T * [(b/B - (sqrt(f2**2 - b/(B*f1)) - f2)) / + * (1 + sqrt(f2**2 - b/(B*f1)) - f2)] + * where f1 = 1 - tfee, f2 = (1 - tfee/2)/f1 + */ +STAmount +lpTokensOut( + STAmount const& asset1Balance, + STAmount const& asset1Deposit, + STAmount const& lptAMMBalance, + std::uint16_t tfee) +{ + auto const f1 = feeMult(tfee); + auto const f2 = feeMultHalf(tfee) / f1; + Number const r = asset1Deposit / asset1Balance; + auto const c = root2(f2 * f2 + r / f1) - f2; + if (!isFeatureEnabled(fixAMMv1_3)) + { + auto const t = lptAMMBalance * (r - c) / (1 + c); + return toSTAmount(lptAMMBalance.asset(), t); + } + + // minimize tokens out + auto const frac = (r - c) / (1 + c); + return multiply(lptAMMBalance, frac, Number::downward); +} + +/* Equation 4 solves equation 3 for b: + * Let f1 = 1 - tfee, f2 = (1 - tfee/2)/f1, t1 = t/T, t2 = 1 + t1, R = b/B + * then + * t1 = [R - sqrt(f2**2 + R/f1) + f2] / [1 + sqrt(f2**2 + R/f1] - f2] => + * sqrt(f2**2 + R/f1)*(t1 + 1) = R + f2 + t1*f2 - t1 => + * sqrt(f2**2 + R/f1)*t2 = R + t2*f2 - t1 => + * sqrt(f2**2 + R/f1) = R/t2 + f2 - t1/t2, let d = f2 - t1/t2 => + * sqrt(f2**2 + R/f1) = R/t2 + d => + * f2**2 + R/f1 = (R/t2)**2 +2*d*R/t2 + d**2 => + * (R/t2)**2 + R*(2*d/t2 - 1/f1) + d**2 - f2**2 = 0 + */ +STAmount +ammAssetIn( + STAmount const& asset1Balance, + STAmount const& lptAMMBalance, + STAmount const& lpTokens, + std::uint16_t tfee) +{ + auto const f1 = feeMult(tfee); + auto const f2 = feeMultHalf(tfee) / f1; + auto const t1 = lpTokens / lptAMMBalance; + auto const t2 = 1 + t1; + auto const d = f2 - t1 / t2; + auto const a = 1 / (t2 * t2); + auto const b = 2 * d / t2 - 1 / f1; + auto const c = d * d - f2 * f2; + if (!isFeatureEnabled(fixAMMv1_3)) + { + return toSTAmount(asset1Balance.asset(), asset1Balance * solveQuadraticEq(a, b, c)); + } + + // maximize deposit + auto const frac = solveQuadraticEq(a, b, c); + return multiply(asset1Balance, frac, Number::upward); +} + +/* Equation 7: + * t = T * (c - sqrt(c**2 - 4*R))/2 + * where R = b/B, c = R*fee + 2 - fee + */ +STAmount +lpTokensIn( + STAmount const& asset1Balance, + STAmount const& asset1Withdraw, + STAmount const& lptAMMBalance, + std::uint16_t tfee) +{ + Number const fr = asset1Withdraw / asset1Balance; + auto const f1 = getFee(tfee); + auto const c = fr * f1 + 2 - f1; + if (!isFeatureEnabled(fixAMMv1_3)) + { + auto const t = lptAMMBalance * (c - root2(c * c - 4 * fr)) / 2; + return toSTAmount(lptAMMBalance.asset(), t); + } + + // maximize tokens in + auto const frac = (c - root2(c * c - 4 * fr)) / 2; + return multiply(lptAMMBalance, frac, Number::upward); +} + +/* Equation 8 solves equation 7 for b: + * c - 2*t/T = sqrt(c**2 - 4*R) => + * c**2 - 4*c*t/T + 4*t**2/T**2 = c**2 - 4*R => + * -4*c*t/T + 4*t**2/T**2 = -4*R => + * -c*t/T + t**2/T**2 = -R -=> + * substitute c = R*f + 2 - f => + * -(t/T)*(R*f + 2 - f) + (t/T)**2 = -R, let t1 = t/T => + * -t1*R*f -2*t1 +t1*f +t1**2 = -R => + * R = (t1**2 + t1*(f - 2)) / (t1*f - 1) + */ +STAmount +ammAssetOut( + STAmount const& assetBalance, + STAmount const& lptAMMBalance, + STAmount const& lpTokens, + std::uint16_t tfee) +{ + auto const f = getFee(tfee); + Number const t1 = lpTokens / lptAMMBalance; + if (!isFeatureEnabled(fixAMMv1_3)) + { + auto const b = assetBalance * (t1 * t1 - t1 * (2 - f)) / (t1 * f - 1); + return toSTAmount(assetBalance.asset(), b); + } + + // minimize withdraw + auto const frac = (t1 * t1 - t1 * (2 - f)) / (t1 * f - 1); + return multiply(assetBalance, frac, Number::downward); +} + +Number +square(Number const& n) +{ + return n * n; +} + +STAmount +adjustLPTokens(STAmount const& lptAMMBalance, STAmount const& lpTokens, IsDeposit isDeposit) +{ + // Force rounding downward to ensure adjusted tokens are less or equal + // to requested tokens. + saveNumberRoundMode const rm(Number::setround(Number::rounding_mode::downward)); + if (isDeposit == IsDeposit::Yes) + return (lptAMMBalance + lpTokens) - lptAMMBalance; + return (lpTokens - lptAMMBalance) + lptAMMBalance; +} + +std::tuple, STAmount> +adjustAmountsByLPTokens( + STAmount const& amountBalance, + STAmount const& amount, + std::optional const& amount2, + STAmount const& lptAMMBalance, + STAmount const& lpTokens, + std::uint16_t tfee, + IsDeposit isDeposit) +{ + // AMMv1_3 amendment adjusts tokens and amounts in deposit/withdraw + if (isFeatureEnabled(fixAMMv1_3)) + return std::make_tuple(amount, amount2, lpTokens); + + auto const lpTokensActual = adjustLPTokens(lptAMMBalance, lpTokens, isDeposit); + + if (lpTokensActual == beast::zero) + { + auto const amount2Opt = amount2 ? std::make_optional(STAmount{}) : std::nullopt; + return std::make_tuple(STAmount{}, amount2Opt, lpTokensActual); + } + + if (lpTokensActual < lpTokens) + { + bool const ammRoundingEnabled = [&]() { + if (auto const& rules = getCurrentTransactionRules(); + rules && rules->enabled(fixAMMv1_1)) + return true; + return false; + }(); + + // Equal trade + if (amount2) + { + Number const fr = lpTokensActual / lpTokens; + auto const amountActual = toSTAmount(amount.asset(), fr * amount); + auto const amount2Actual = toSTAmount(amount2->asset(), fr * *amount2); + if (!ammRoundingEnabled) + { + return std::make_tuple( + amountActual < amount ? amountActual : amount, + amount2Actual < amount2 ? amount2Actual : amount2, + lpTokensActual); + } + + return std::make_tuple(amountActual, amount2Actual, lpTokensActual); + } + + // Single trade + auto const amountActual = [&]() { + if (isDeposit == IsDeposit::Yes) + { + return ammAssetIn(amountBalance, lptAMMBalance, lpTokensActual, tfee); + } + if (!ammRoundingEnabled) + { + return ammAssetOut(amountBalance, lptAMMBalance, lpTokens, tfee); + } + + return ammAssetOut(amountBalance, lptAMMBalance, lpTokensActual, tfee); + }(); + if (!ammRoundingEnabled) + { + return amountActual < amount + ? std::make_tuple(amountActual, std::nullopt, lpTokensActual) + : std::make_tuple(amount, std::nullopt, lpTokensActual); + } + + return std::make_tuple(amountActual, std::nullopt, lpTokensActual); + } + + XRPL_ASSERT( + lpTokensActual == lpTokens, "xrpl::adjustAmountsByLPTokens : LP tokens match actual"); + + return {amount, amount2, lpTokensActual}; +} + +Number +solveQuadraticEq(Number const& a, Number const& b, Number const& c) +{ + return (-b + root2(b * b - 4 * a * c)) / (2 * a); +} + +// Minimize takerGets or takerPays +std::optional +solveQuadraticEqSmallest(Number const& a, Number const& b, Number const& c) +{ + auto const d = b * b - 4 * a * c; + if (d < 0) + return std::nullopt; + // use numerically stable citardauq formula for quadratic equation solution + // https://people.csail.mit.edu/bkph/articles/Quadratics.pdf + if (b > 0) + { + return (2 * c) / (-b - root2(d)); + } + + return (2 * c) / (-b + root2(d)); +} + +STAmount +multiply(STAmount const& amount, Number const& frac, Number::rounding_mode rm) +{ + NumberRoundModeGuard const g(rm); + auto const t = amount * frac; + return toSTAmount(amount.asset(), t, rm); +} + +STAmount +getRoundedAsset( + Rules const& rules, + std::function const& noRoundCb, + STAmount const& balance, + std::function const& productCb, + IsDeposit isDeposit) +{ + if (!rules.enabled(fixAMMv1_3)) + return toSTAmount(balance.asset(), noRoundCb()); + + auto const rm = detail::getAssetRounding(isDeposit); + if (isDeposit == IsDeposit::Yes) + return multiply(balance, productCb(), rm); + NumberRoundModeGuard const g(rm); + return toSTAmount(balance.asset(), productCb(), rm); +} + +STAmount +getRoundedLPTokens( + Rules const& rules, + STAmount const& balance, + Number const& frac, + IsDeposit isDeposit) +{ + if (!rules.enabled(fixAMMv1_3)) + return toSTAmount(balance.asset(), balance * frac); + + auto const rm = detail::getLPTokenRounding(isDeposit); + auto const tokens = multiply(balance, frac, rm); + return adjustLPTokens(balance, tokens, isDeposit); +} + +STAmount +getRoundedLPTokens( + Rules const& rules, + std::function const& noRoundCb, + STAmount const& lptAMMBalance, + std::function const& productCb, + IsDeposit isDeposit) +{ + if (!rules.enabled(fixAMMv1_3)) + return toSTAmount(lptAMMBalance.asset(), noRoundCb()); + + auto const tokens = [&] { + auto const rm = detail::getLPTokenRounding(isDeposit); + if (isDeposit == IsDeposit::Yes) + { + NumberRoundModeGuard const g(rm); + return toSTAmount(lptAMMBalance.asset(), productCb(), rm); + } + return multiply(lptAMMBalance, productCb(), rm); + }(); + return adjustLPTokens(lptAMMBalance, tokens, isDeposit); +} + +std::pair +adjustAssetInByTokens( + Rules const& rules, + STAmount const& balance, + STAmount const& amount, + STAmount const& lptAMMBalance, + STAmount const& tokens, + std::uint16_t tfee) +{ + if (!rules.enabled(fixAMMv1_3)) + return {tokens, amount}; + auto assetAdj = ammAssetIn(balance, lptAMMBalance, tokens, tfee); + auto tokensAdj = tokens; + // Rounding didn't work the right way. + // Try to adjust the original deposit amount by difference + // in adjust and original amount. Then adjust tokens and deposit amount. + if (assetAdj > amount) + { + auto const adjAmount = amount - (assetAdj - amount); + auto const t = lpTokensOut(balance, adjAmount, lptAMMBalance, tfee); + tokensAdj = adjustLPTokens(lptAMMBalance, t, IsDeposit::Yes); + assetAdj = ammAssetIn(balance, lptAMMBalance, tokensAdj, tfee); + } + return {tokensAdj, std::min(amount, assetAdj)}; +} + +std::pair +adjustAssetOutByTokens( + Rules const& rules, + STAmount const& balance, + STAmount const& amount, + STAmount const& lptAMMBalance, + STAmount const& tokens, + std::uint16_t tfee) +{ + if (!rules.enabled(fixAMMv1_3)) + return {tokens, amount}; + auto assetAdj = ammAssetOut(balance, lptAMMBalance, tokens, tfee); + auto tokensAdj = tokens; + // Rounding didn't work the right way. + // Try to adjust the original deposit amount by difference + // in adjust and original amount. Then adjust tokens and deposit amount. + if (assetAdj > amount) + { + auto const adjAmount = amount - (assetAdj - amount); + auto const t = lpTokensIn(balance, adjAmount, lptAMMBalance, tfee); + tokensAdj = adjustLPTokens(lptAMMBalance, t, IsDeposit::No); + assetAdj = ammAssetOut(balance, lptAMMBalance, tokensAdj, tfee); + } + return {tokensAdj, std::min(amount, assetAdj)}; +} + +Number +adjustFracByTokens( + Rules const& rules, + STAmount const& lptAMMBalance, + STAmount const& tokens, + Number const& frac) +{ + if (!rules.enabled(fixAMMv1_3)) + return frac; + return tokens / lptAMMBalance; +} + +std::pair +ammPoolHolds( + ReadView const& view, + AccountID const& ammAccountID, + Asset const& asset1, + Asset const& asset2, + FreezeHandling freezeHandling, + AuthHandling authHandling, + beast::Journal const j) +{ + auto const assetInBalance = + accountHolds(view, ammAccountID, asset1, freezeHandling, authHandling, j); + auto const assetOutBalance = + accountHolds(view, ammAccountID, asset2, freezeHandling, authHandling, j); + return std::make_pair(assetInBalance, assetOutBalance); +} + +Expected, TER> +ammHolds( + ReadView const& view, + SLE const& ammSle, + std::optional const& optAsset1, + std::optional const& optAsset2, + FreezeHandling freezeHandling, + AuthHandling authHandling, + beast::Journal const j) +{ + auto const assets = [&]() -> std::optional> { + auto const asset1 = ammSle[sfAsset]; + auto const asset2 = ammSle[sfAsset2]; + if (optAsset1 && optAsset2) + { + if (invalidAMMAssetPair( + *optAsset1, *optAsset2, std::make_optional(std::make_pair(asset1, asset2)))) + { + // This error can only be hit if the AMM is corrupted + // LCOV_EXCL_START + JLOG(j.debug()) << "ammHolds: Invalid optAsset1 or optAsset2 " << *optAsset1 << " " + << *optAsset2; + return std::nullopt; + // LCOV_EXCL_STOP + } + return std::make_optional(std::make_pair(*optAsset1, *optAsset2)); + } + auto const singleAsset = [&asset1, &asset2, &j]( + Asset checkIssue, + char const* label) -> std::optional> { + if (checkIssue == asset1) + { + return std::make_optional(std::make_pair(asset1, asset2)); + } + if (checkIssue == asset2) + { + return std::make_optional(std::make_pair(asset2, asset1)); + } + // Unreachable unless AMM corrupted. + // LCOV_EXCL_START + JLOG(j.debug()) << "ammHolds: Invalid " << label << " " << checkIssue; + return std::nullopt; + // LCOV_EXCL_STOP + }; + if (optAsset1) + { + return singleAsset(*optAsset1, "optAsset1"); + } + if (optAsset2) + { + // Cannot have Amount2 without Amount. + return singleAsset(*optAsset2, "optAsset2"); // LCOV_EXCL_LINE + } + return std::make_optional(std::make_pair(asset1, asset2)); + }(); + if (!assets) + return Unexpected(tecAMM_INVALID_TOKENS); + auto const [amount1, amount2] = ammPoolHolds( + view, + ammSle.getAccountID(sfAccount), + assets->first, + assets->second, + freezeHandling, + authHandling, + j); + return std::make_tuple(amount1, amount2, ammSle[sfLPTokenBalance]); +} + +STAmount +ammLPHolds( + ReadView const& view, + Asset const& asset1, + Asset const& asset2, + AccountID const& ammAccount, + AccountID const& lpAccount, + beast::Journal const j) +{ + // This function looks similar to `accountHolds`. However, it only checks if + // a LPToken holder has enough balance. On the other hand, `accountHolds` + // checks if the underlying assets of LPToken are frozen with the + // fixFrozenLPTokenTransfer amendment + + auto const currency = ammLPTCurrency(asset1, asset2); + STAmount amount; + + auto const sle = view.read(keylet::line(lpAccount, ammAccount, currency)); + if (!sle) + { + amount.clear(Issue{currency, ammAccount}); + JLOG(j.trace()) << "ammLPHolds: no SLE " + << " lpAccount=" << to_string(lpAccount) + << " amount=" << amount.getFullText(); + } + else if (isFrozen(view, lpAccount, currency, ammAccount)) + { + amount.clear(Issue{currency, ammAccount}); + JLOG(j.trace()) << "ammLPHolds: frozen currency " + << " lpAccount=" << to_string(lpAccount) + << " amount=" << amount.getFullText(); + } + else + { + amount = sle->getFieldAmount(sfBalance); + if (lpAccount > ammAccount) + { + // Put balance in account terms. + amount.negate(); + } + amount.get().account = ammAccount; + + JLOG(j.trace()) << "ammLPHolds:" + << " lpAccount=" << to_string(lpAccount) + << " amount=" << amount.getFullText(); + } + + return view.balanceHookIOU(lpAccount, ammAccount, amount); +} + +STAmount +ammLPHolds( + ReadView const& view, + SLE const& ammSle, + AccountID const& lpAccount, + beast::Journal const j) +{ + return ammLPHolds(view, ammSle[sfAsset], ammSle[sfAsset2], ammSle[sfAccount], lpAccount, j); +} + +std::uint16_t +getTradingFee(ReadView const& view, SLE const& ammSle, AccountID const& account) +{ + using namespace std::chrono; + XRPL_ASSERT( + !view.rules().enabled(fixInnerObjTemplate) || ammSle.isFieldPresent(sfAuctionSlot), + "xrpl::getTradingFee : auction present"); + if (ammSle.isFieldPresent(sfAuctionSlot)) + { + auto const& auctionSlot = safe_downcast(ammSle.peekAtField(sfAuctionSlot)); + // Not expired + if (auto const expiration = auctionSlot[~sfExpiration]; + duration_cast(view.header().parentCloseTime.time_since_epoch()).count() < + expiration) + { + if (auctionSlot[~sfAccount] == account) + return auctionSlot[sfDiscountedFee]; + if (auctionSlot.isFieldPresent(sfAuthAccounts)) + { + for (auto const& acct : auctionSlot.getFieldArray(sfAuthAccounts)) + { + if (acct[~sfAccount] == account) + return auctionSlot[sfDiscountedFee]; + } + } + } + } + return ammSle[sfTradingFee]; +} + +STAmount +ammAccountHolds(ReadView const& view, AccountID const& ammAccountID, Asset const& asset) +{ + // Get the actual AMM balance without factoring in the balance hook + return asset.visit( + [&](MPTIssue const& issue) { + if (auto const sle = view.read(keylet::mptoken(issue, ammAccountID)); + sle && !isFrozen(view, ammAccountID, issue)) + return STAmount{issue, (*sle)[sfMPTAmount]}; + return STAmount{asset}; + }, + [&](Issue const& issue) { + if (isXRP(issue)) + { + 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)) + { + STAmount amount = (*sle)[sfBalance]; + if (ammAccountID > issue.account) + amount.negate(); + amount.get().account = issue.account; + return amount; + } + return STAmount{asset}; + }); +} + +static TER +deleteAMMTrustLines( + Sandbox& sb, + AccountID const& ammAccountID, + std::uint16_t maxTrustlinesToDelete, + beast::Journal j) +{ + return cleanupOnAccountDelete( + sb, + keylet::ownerDir(ammAccountID), + [&](LedgerEntryType nodeType, + uint256 const&, + std::shared_ptr& sleItem) -> std::pair { + // Skip AMM and MPToken + if (nodeType == ltAMM || nodeType == ltMPTOKEN) + return {tesSUCCESS, SkipEntry::Yes}; + + if (nodeType == ltRIPPLE_STATE) + { + // Trustlines must have zero balance + if (sleItem->getFieldAmount(sfBalance) != beast::zero) + { + // LCOV_EXCL_START + JLOG(j.error()) << "deleteAMMObjects: deleting trustline with " + "non-zero balance."; + return {tecINTERNAL, SkipEntry::No}; + // LCOV_EXCL_STOP + } + + return {deleteAMMTrustLine(sb, sleItem, ammAccountID, j), SkipEntry::No}; + } + // LCOV_EXCL_START + JLOG(j.error()) << "deleteAMMObjects: deleting non-trustline or non-MPT " << nodeType; + return {tecINTERNAL, SkipEntry::No}; + // LCOV_EXCL_STOP + }, + j, + maxTrustlinesToDelete); +} + +static TER +deleteAMMMPTokens(Sandbox& sb, AccountID const& ammAccountID, beast::Journal j) +{ + return cleanupOnAccountDelete( + sb, + keylet::ownerDir(ammAccountID), + [&](LedgerEntryType nodeType, + uint256 const&, + std::shared_ptr& sleItem) -> std::pair { + // Skip AMM + if (nodeType == ltAMM) + return {tesSUCCESS, SkipEntry::Yes}; + + if (nodeType == ltMPTOKEN) + { + // MPT must have zero balance + if (sleItem->getFieldU64(sfMPTAmount) != 0 || + (*sleItem)[~sfLockedAmount].value_or(0) != 0) + { + // LCOV_EXCL_START + JLOG(j.error()) << "deleteAMMObjects: deleting MPT with " + "non-zero balance."; + return {tecINTERNAL, SkipEntry::No}; + // LCOV_EXCL_STOP + } + + return {deleteAMMMPToken(sb, sleItem, ammAccountID, j), SkipEntry::No}; + } + if (nodeType == ltRIPPLE_STATE) + { + // Trustlines should have been deleted + // LCOV_EXCL_START + JLOG(j.error()) << "deleteAMMObjects: trustlines should have been deleted"; + return {tecINTERNAL, SkipEntry::No}; + // LCOV_EXCL_STOP + } + // LCOV_EXCL_START + JLOG(j.error()) << "deleteAMMObjects: deleting non-trustline or non-MPT " << nodeType; + return {tecINTERNAL, SkipEntry::No}; + // LCOV_EXCL_STOP + }, + j, + 3); // At most two MPToken plus AMM object +} + +TER +deleteAMMAccount(Sandbox& sb, Asset const& asset, Asset const& asset2, beast::Journal j) +{ + auto ammSle = sb.peek(keylet::amm(asset, asset2)); + if (!ammSle) + { + // LCOV_EXCL_START + JLOG(j.error()) << "deleteAMMAccount: AMM object does not exist " << asset << " " << asset2; + return tecINTERNAL; + // LCOV_EXCL_STOP + } + + auto const ammAccountID = (*ammSle)[sfAccount]; + auto sleAMMRoot = sb.peek(keylet::account(ammAccountID)); + if (!sleAMMRoot) + { + // LCOV_EXCL_START + JLOG(j.error()) << "deleteAMMAccount: AMM account does not exist " + << to_string(ammAccountID); + return tecINTERNAL; + // LCOV_EXCL_STOP + } + + if (auto const ter = deleteAMMTrustLines(sb, ammAccountID, maxDeletableAMMTrustLines, j); + !isTesSuccess(ter)) + return ter; + + // Delete AMM's MPTokens only if all trustlines are deleted. If trustlines + // are not deleted then AMM can be re-created with Deposit and + // AMM's MPToken(s) must exist. + if (auto const ter = deleteAMMMPTokens(sb, ammAccountID, j); !isTesSuccess(ter)) + return ter; + + auto const ownerDirKeylet = keylet::ownerDir(ammAccountID); + if (!sb.dirRemove(ownerDirKeylet, (*ammSle)[sfOwnerNode], ammSle->key(), false)) + { + // LCOV_EXCL_START + JLOG(j.error()) << "deleteAMMAccount: failed to remove dir link"; + return tecINTERNAL; + // LCOV_EXCL_STOP + } + if (sb.exists(ownerDirKeylet) && !sb.emptyDirDelete(ownerDirKeylet)) + { + // LCOV_EXCL_START + JLOG(j.error()) << "deleteAMMAccount: cannot delete root dir node of " + << toBase58(ammAccountID); + return tecINTERNAL; + // LCOV_EXCL_STOP + } + + sb.erase(ammSle); + sb.erase(sleAMMRoot); + + return tesSUCCESS; +} + +void +initializeFeeAuctionVote( + ApplyView& view, + std::shared_ptr& ammSle, + AccountID const& account, + Asset const& lptAsset, + std::uint16_t tfee) +{ + auto const& rules = view.rules(); + // AMM creator gets the voting slot. + STArray voteSlots; + STObject voteEntry = STObject::makeInnerObject(sfVoteEntry); + if (tfee != 0) + voteEntry.setFieldU16(sfTradingFee, tfee); + voteEntry.setFieldU32(sfVoteWeight, VOTE_WEIGHT_SCALE_FACTOR); + voteEntry.setAccountID(sfAccount, account); + voteSlots.push_back(voteEntry); + ammSle->setFieldArray(sfVoteSlots, voteSlots); + // AMM creator gets the auction slot for free. + // AuctionSlot is created on AMMCreate and updated on AMMDeposit + // when AMM is in an empty state + if (rules.enabled(fixInnerObjTemplate) && !ammSle->isFieldPresent(sfAuctionSlot)) + { + STObject auctionSlot = STObject::makeInnerObject(sfAuctionSlot); + ammSle->set(std::move(auctionSlot)); + } + STObject& auctionSlot = ammSle->peekFieldObject(sfAuctionSlot); + auctionSlot.setAccountID(sfAccount, account); + // current + sec in 24h + auto const expiration = std::chrono::duration_cast( + view.header().parentCloseTime.time_since_epoch()) + .count() + + TOTAL_TIME_SLOT_SECS; + auctionSlot.setFieldU32(sfExpiration, expiration); + auctionSlot.setFieldAmount(sfPrice, STAmount{lptAsset, 0}); + // Set the fee + if (tfee != 0) + { + ammSle->setFieldU16(sfTradingFee, tfee); + } + else if (ammSle->isFieldPresent(sfTradingFee)) + { + ammSle->makeFieldAbsent(sfTradingFee); // LCOV_EXCL_LINE + } + if (auto const dfee = tfee / AUCTION_SLOT_DISCOUNTED_FEE_FRACTION) + { + auctionSlot.setFieldU16(sfDiscountedFee, dfee); + } + else if (auctionSlot.isFieldPresent(sfDiscountedFee)) + { + auctionSlot.makeFieldAbsent(sfDiscountedFee); // LCOV_EXCL_LINE + } +} + +Expected +isOnlyLiquidityProvider(ReadView const& view, Issue const& ammIssue, AccountID const& lpAccount) +{ + // Liquidity Provider (LP) must have one LPToken trustline + std::uint8_t nLPTokenTrustLines = 0; + // AMM account has at most two IOU (pool tokens, not LPToken) trustlines. + // One or both trustlines could be to the LP if LP is the issuer, + // or a different account if LP is not an issuer. For instance, + // if AMM has two tokens USD and EUR and LP is not the issuer of the tokens + // then the trustlines are between AMM account and the issuer. + // There is one LPToken trustline for each LP. Only remaining LP has + // exactly one LPToken trustlines and at most two IOU trustline for each + // pool token. One or both tokens could be MPT. + std::uint8_t nIOUTrustLines = 0; + // There are at most two MPT objects, one for each side of the pool. + std::uint8_t nMPT = 0; + // There is only one AMM object + bool hasAMM = false; + // AMM LP has at most three trustlines, at most two MPTs, and only one + // AMM object must exist. If there are more than four objects then + // it's either an error or there are more than one LP. Ten pages should + // be sufficient to include four objects. + std::uint8_t limit = 10; + auto const root = keylet::ownerDir(ammIssue.account); + auto currentIndex = root; + + // Iterate over AMM owner directory objects. + while (limit-- >= 1) + { + auto const ownerDir = view.read(currentIndex); + if (!ownerDir) + return Unexpected(tecINTERNAL); // LCOV_EXCL_LINE + for (auto const& key : ownerDir->getFieldV256(sfIndexes)) + { + auto const sle = view.read(keylet::child(key)); + if (!sle) + return Unexpected(tecINTERNAL); // LCOV_EXCL_LINE + auto const entryType = sle->getFieldU16(sfLedgerEntryType); + // Only one AMM object + if (entryType == ltAMM) + { + if (hasAMM) + return Unexpected(tecINTERNAL); // LCOV_EXCL_LINE + hasAMM = true; + continue; + } + if (entryType == ltMPTOKEN) + { + ++nMPT; + continue; + } + if (entryType != ltRIPPLE_STATE) + return Unexpected(tecINTERNAL); // LCOV_EXCL_LINE + auto const lowLimit = sle->getFieldAmount(sfLowLimit); + auto const highLimit = sle->getFieldAmount(sfHighLimit); + auto const isLPTrustline = + lowLimit.getIssuer() == lpAccount || highLimit.getIssuer() == lpAccount; + auto const isLPTokenTrustline = + lowLimit.asset() == ammIssue || highLimit.asset() == ammIssue; + + // Liquidity Provider trustline + if (isLPTrustline) + { + // LPToken trustline + if (isLPTokenTrustline) + { + // LP has exactly one LPToken trustline + if (++nLPTokenTrustLines > 1) + return Unexpected(tecINTERNAL); // LCOV_EXCL_LINE + } + // AMM account has at most two IOU trustlines + else if (++nIOUTrustLines > 2) + { + return Unexpected(tecINTERNAL); // LCOV_EXCL_LINE + } + } + // Another Liquidity Provider LPToken trustline + else if (isLPTokenTrustline) + { + return false; + } + // AMM account has at most two IOU trustlines + else if (++nIOUTrustLines > 2) + { + return Unexpected(tecINTERNAL); // LCOV_EXCL_LINE + } + } + auto const uNodeNext = ownerDir->getFieldU64(sfIndexNext); + if (uNodeNext == 0) + { + if (nLPTokenTrustLines != 1 || (nIOUTrustLines == 0 && nMPT == 0) || + (nIOUTrustLines > 2 || nMPT > 2) || (nIOUTrustLines + nMPT) > 2) + return Unexpected(tecINTERNAL); // LCOV_EXCL_LINE + return true; + } + currentIndex = keylet::page(root, uNodeNext); + } + return Unexpected(tecINTERNAL); // LCOV_EXCL_LINE +} + +Expected +verifyAndAdjustLPTokenBalance( + Sandbox& sb, + STAmount const& lpTokens, + std::shared_ptr& ammSle, + AccountID const& account) +{ + auto const res = isOnlyLiquidityProvider(sb, lpTokens.get(), account); + if (!res.has_value()) + { + return Unexpected(res.error()); + } + + if (res.value()) + { + if (withinRelativeDistance( + lpTokens, ammSle->getFieldAmount(sfLPTokenBalance), Number{1, -3})) + { + ammSle->setFieldAmount(sfLPTokenBalance, lpTokens); + sb.update(ammSle); + } + else + { + return Unexpected(tecAMM_INVALID_TOKENS); + } + } + return true; +} + +} // namespace xrpl diff --git a/src/libxrpl/ledger/helpers/AccountRootHelpers.cpp b/src/libxrpl/ledger/helpers/AccountRootHelpers.cpp index 399494bc5f..0663fde1f4 100644 --- a/src/libxrpl/ledger/helpers/AccountRootHelpers.cpp +++ b/src/libxrpl/ledger/helpers/AccountRootHelpers.cpp @@ -1,13 +1,33 @@ #include -// + +#include #include +#include +#include +#include +#include #include +#include +#include +#include #include +#include #include +#include +#include +#include +#include +#include #include #include +#include #include +#include +#include +#include +#include +#include namespace xrpl { @@ -80,7 +100,7 @@ xrpLiquid(ReadView const& view, AccountID const& id, std::int32_t ownerCountAdj, auto const fullBalance = sle->getFieldAmount(sfBalance); - auto const balance = view.balanceHook(id, xrpAccount(), fullBalance); + auto const balance = view.balanceHookIOU(id, xrpAccount(), fullBalance); STAmount const amount = (balance < reserve) ? STAmount{0} : balance - reserve; @@ -153,7 +173,7 @@ getPseudoAccountFields() if (!ar) { // LCOV_EXCL_START - LogicError( + Throw( "xrpl::getPseudoAccountFields : unable to find account root " "ledger format"); // LCOV_EXCL_STOP diff --git a/src/libxrpl/ledger/helpers/CredentialHelpers.cpp b/src/libxrpl/ledger/helpers/CredentialHelpers.cpp index 32db285f1e..18cb44b461 100644 --- a/src/libxrpl/ledger/helpers/CredentialHelpers.cpp +++ b/src/libxrpl/ledger/helpers/CredentialHelpers.cpp @@ -1,11 +1,33 @@ #include -// -#include + +#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include +#include +#include +#include +#include #include +#include +#include namespace xrpl { namespace credentials { diff --git a/src/libxrpl/ledger/helpers/DirectoryHelpers.cpp b/src/libxrpl/ledger/helpers/DirectoryHelpers.cpp index dba71e0acd..8b4eeae7b7 100644 --- a/src/libxrpl/ledger/helpers/DirectoryHelpers.cpp +++ b/src/libxrpl/ledger/helpers/DirectoryHelpers.cpp @@ -1,6 +1,19 @@ #include -// + +#include +#include +#include +#include +#include +#include +#include #include +#include +#include + +#include +#include +#include namespace xrpl { diff --git a/src/libxrpl/ledger/helpers/MPTokenHelpers.cpp b/src/libxrpl/ledger/helpers/MPTokenHelpers.cpp index 31c1d543f5..d47b49e910 100644 --- a/src/libxrpl/ledger/helpers/MPTokenHelpers.cpp +++ b/src/libxrpl/ledger/helpers/MPTokenHelpers.cpp @@ -1,15 +1,40 @@ #include -// + #include +#include +#include +#include +#include +#include +#include #include #include #include #include +#include +#include #include #include +#include #include +#include +#include +#include #include +#include +#include +#include #include +#include +#include +#include + +#include +#include +#include +#include +#include +#include namespace xrpl { @@ -139,7 +164,9 @@ authorizeMPToken( { auto const mptokenKey = keylet::mptoken(mptIssuanceID, account); auto const sleMpt = view.peek(mptokenKey); - if (!sleMpt || (*sleMpt)[sfMPTAmount] != 0) + if (!sleMpt || (*sleMpt)[sfMPTAmount] != 0 || + (view.rules().enabled(fixSecurity3_1_3) && + (*sleMpt)[~sfLockedAmount].value_or(0) != 0)) return tecINTERNAL; // LCOV_EXCL_LINE if (!view.dirRemove( @@ -252,7 +279,8 @@ removeEmptyHolding( // balance, it can not just be deleted, because that will throw the issuance // accounting out of balance, so fail. Since this should be impossible // anyway, I'm not going to put any effort into it. - if (mptoken->at(sfMPTAmount) != 0) + if (mptoken->at(sfMPTAmount) != 0 || + (view.rules().enabled(fixSecurity3_1_3) && (*mptoken)[~sfLockedAmount].value_or(0) != 0)) return tecHAS_OBLIGATIONS; return authorizeMPToken( @@ -303,18 +331,11 @@ requireAuth( return tefINTERNAL; // LCOV_EXCL_LINE auto const asset = sleVault->at(sfAsset); - if (auto const err = std::visit( - [&](TIss const& issue) { - if constexpr (std::is_same_v) - { - return requireAuth(view, issue, account, authType); - } - else - { - return requireAuth(view, issue, account, authType, depth + 1); - } - }, - asset.value()); + if (auto const err = asset.visit( + [&](Issue const& issue) { return requireAuth(view, issue, account, authType); }, + [&](MPTIssue const& issue) { + return requireAuth(view, issue, account, authType, depth + 1); + }); !isTesSuccess(err)) return err; } @@ -485,25 +506,35 @@ canTransfer( } TER -rippleLockEscrowMPT( - ApplyView& view, - AccountID const& sender, - STAmount const& amount, - beast::Journal j) +canTrade(ReadView const& view, Asset const& asset) +{ + return asset.visit( + [&](Issue const&) -> TER { return tesSUCCESS; }, + [&](MPTIssue const& mptIssue) -> TER { + auto const sleIssuance = view.read(keylet::mptIssuance(mptIssue.getMptID())); + if (!sleIssuance) + return tecOBJECT_NOT_FOUND; + if (!sleIssuance->isFlag(lsfMPTCanTrade)) + return tecNO_PERMISSION; + return tesSUCCESS; + }); +} + +TER +lockEscrowMPT(ApplyView& view, AccountID const& sender, STAmount const& amount, beast::Journal j) { auto const mptIssue = amount.get(); auto const mptID = keylet::mptIssuance(mptIssue.getMptID()); auto sleIssuance = view.peek(mptID); if (!sleIssuance) { // LCOV_EXCL_START - JLOG(j.error()) << "rippleLockEscrowMPT: MPT issuance not found for " - << mptIssue.getMptID(); + JLOG(j.error()) << "lockEscrowMPT: MPT issuance not found for " << mptIssue.getMptID(); return tecOBJECT_NOT_FOUND; } // LCOV_EXCL_STOP if (amount.getIssuer() == sender) { // LCOV_EXCL_START - JLOG(j.error()) << "rippleLockEscrowMPT: sender is the issuer, cannot lock MPTs."; + JLOG(j.error()) << "lockEscrowMPT: sender is the issuer, cannot lock MPTs."; return tecINTERNAL; } // LCOV_EXCL_STOP @@ -514,7 +545,7 @@ rippleLockEscrowMPT( auto sle = view.peek(mptokenID); if (!sle) { // LCOV_EXCL_START - JLOG(j.error()) << "rippleLockEscrowMPT: MPToken not found for " << sender; + JLOG(j.error()) << "lockEscrowMPT: MPToken not found for " << sender; return tecOBJECT_NOT_FOUND; } // LCOV_EXCL_STOP @@ -524,8 +555,8 @@ rippleLockEscrowMPT( // Underflow check for subtraction if (!canSubtract(STAmount(mptIssue, amt), STAmount(mptIssue, pay))) { // LCOV_EXCL_START - JLOG(j.error()) << "rippleLockEscrowMPT: insufficient MPTAmount for " - << to_string(sender) << ": " << amt << " < " << pay; + JLOG(j.error()) << "lockEscrowMPT: insufficient MPTAmount for " << to_string(sender) + << ": " << amt << " < " << pay; return tecINTERNAL; } // LCOV_EXCL_STOP @@ -536,8 +567,8 @@ rippleLockEscrowMPT( if (!canAdd(STAmount(mptIssue, locked), STAmount(mptIssue, pay))) { // LCOV_EXCL_START - JLOG(j.error()) << "rippleLockEscrowMPT: overflow on locked amount for " - << to_string(sender) << ": " << locked << " + " << pay; + JLOG(j.error()) << "lockEscrowMPT: overflow on locked amount for " << to_string(sender) + << ": " << locked << " + " << pay; return tecINTERNAL; } // LCOV_EXCL_STOP @@ -562,7 +593,7 @@ rippleLockEscrowMPT( // Overflow check for addition if (!canAdd(STAmount(mptIssue, issuanceEscrowed), STAmount(mptIssue, pay))) { // LCOV_EXCL_START - JLOG(j.error()) << "rippleLockEscrowMPT: overflow on issuance " + JLOG(j.error()) << "lockEscrowMPT: overflow on issuance " "locked amount for " << mptIssue.getMptID() << ": " << issuanceEscrowed << " + " << pay; return tecINTERNAL; @@ -583,7 +614,7 @@ rippleLockEscrowMPT( } TER -rippleUnlockEscrowMPT( +unlockEscrowMPT( ApplyView& view, AccountID const& sender, AccountID const& receiver, @@ -593,8 +624,7 @@ rippleUnlockEscrowMPT( { if (!view.rules().enabled(fixTokenEscrowV1)) { - XRPL_ASSERT( - netAmount == grossAmount, "xrpl::rippleUnlockEscrowMPT : netAmount == grossAmount"); + XRPL_ASSERT(netAmount == grossAmount, "xrpl::unlockEscrowMPT : netAmount == grossAmount"); } auto const& issuer = netAmount.getIssuer(); @@ -603,8 +633,7 @@ rippleUnlockEscrowMPT( auto sleIssuance = view.peek(mptID); if (!sleIssuance) { // LCOV_EXCL_START - JLOG(j.error()) << "rippleUnlockEscrowMPT: MPT issuance not found for " - << mptIssue.getMptID(); + JLOG(j.error()) << "unlockEscrowMPT: MPT issuance not found for " << mptIssue.getMptID(); return tecOBJECT_NOT_FOUND; } // LCOV_EXCL_STOP @@ -612,7 +641,7 @@ rippleUnlockEscrowMPT( { if (!sleIssuance->isFieldPresent(sfLockedAmount)) { // LCOV_EXCL_START - JLOG(j.error()) << "rippleUnlockEscrowMPT: no locked amount in issuance for " + JLOG(j.error()) << "unlockEscrowMPT: no locked amount in issuance for " << mptIssue.getMptID(); return tecINTERNAL; } // LCOV_EXCL_STOP @@ -623,7 +652,7 @@ rippleUnlockEscrowMPT( // Underflow check for subtraction if (!canSubtract(STAmount(mptIssue, locked), STAmount(mptIssue, redeem))) { // LCOV_EXCL_START - JLOG(j.error()) << "rippleUnlockEscrowMPT: insufficient locked amount for " + JLOG(j.error()) << "unlockEscrowMPT: insufficient locked amount for " << mptIssue.getMptID() << ": " << locked << " < " << redeem; return tecINTERNAL; } // LCOV_EXCL_STOP @@ -647,7 +676,7 @@ rippleUnlockEscrowMPT( auto sle = view.peek(mptokenID); if (!sle) { // LCOV_EXCL_START - JLOG(j.error()) << "rippleUnlockEscrowMPT: MPToken not found for " << receiver; + JLOG(j.error()) << "unlockEscrowMPT: MPToken not found for " << receiver; return tecOBJECT_NOT_FOUND; } // LCOV_EXCL_STOP @@ -657,8 +686,8 @@ rippleUnlockEscrowMPT( // Overflow check for addition if (!canAdd(STAmount(mptIssue, current), STAmount(mptIssue, delta))) { // LCOV_EXCL_START - JLOG(j.error()) << "rippleUnlockEscrowMPT: overflow on MPTAmount for " - << to_string(receiver) << ": " << current << " + " << delta; + JLOG(j.error()) << "unlockEscrowMPT: overflow on MPTAmount for " << to_string(receiver) + << ": " << current << " + " << delta; return tecINTERNAL; } // LCOV_EXCL_STOP @@ -674,7 +703,7 @@ rippleUnlockEscrowMPT( // Underflow check for subtraction if (!canSubtract(STAmount(mptIssue, outstanding), STAmount(mptIssue, redeem))) { // LCOV_EXCL_START - JLOG(j.error()) << "rippleUnlockEscrowMPT: insufficient outstanding amount for " + JLOG(j.error()) << "unlockEscrowMPT: insufficient outstanding amount for " << mptIssue.getMptID() << ": " << outstanding << " < " << redeem; return tecINTERNAL; } // LCOV_EXCL_STOP @@ -685,7 +714,7 @@ rippleUnlockEscrowMPT( if (issuer == sender) { // LCOV_EXCL_START - JLOG(j.error()) << "rippleUnlockEscrowMPT: sender is the issuer, " + JLOG(j.error()) << "unlockEscrowMPT: sender is the issuer, " "cannot unlock MPTs."; return tecINTERNAL; } // LCOV_EXCL_STOP @@ -694,14 +723,13 @@ rippleUnlockEscrowMPT( auto sle = view.peek(mptokenID); if (!sle) { // LCOV_EXCL_START - JLOG(j.error()) << "rippleUnlockEscrowMPT: MPToken not found for " << sender; + JLOG(j.error()) << "unlockEscrowMPT: MPToken not found for " << sender; return tecOBJECT_NOT_FOUND; } // LCOV_EXCL_STOP if (!sle->isFieldPresent(sfLockedAmount)) { // LCOV_EXCL_START - JLOG(j.error()) << "rippleUnlockEscrowMPT: no locked amount in MPToken for " - << to_string(sender); + JLOG(j.error()) << "unlockEscrowMPT: no locked amount in MPToken for " << to_string(sender); return tecINTERNAL; } // LCOV_EXCL_STOP @@ -711,8 +739,8 @@ rippleUnlockEscrowMPT( // Underflow check for subtraction if (!canSubtract(STAmount(mptIssue, locked), STAmount(mptIssue, delta))) { // LCOV_EXCL_START - JLOG(j.error()) << "rippleUnlockEscrowMPT: insufficient locked amount for " - << to_string(sender) << ": " << locked << " < " << delta; + JLOG(j.error()) << "unlockEscrowMPT: insufficient locked amount for " << to_string(sender) + << ": " << locked << " < " << delta; return tecINTERNAL; } // LCOV_EXCL_STOP @@ -738,7 +766,7 @@ rippleUnlockEscrowMPT( // Underflow check for subtraction if (!canSubtract(STAmount(mptIssue, outstanding), STAmount(mptIssue, diff))) { // LCOV_EXCL_START - JLOG(j.error()) << "rippleUnlockEscrowMPT: insufficient outstanding amount for " + JLOG(j.error()) << "unlockEscrowMPT: insufficient outstanding amount for " << mptIssue.getMptID() << ": " << outstanding << " < " << diff; return tecINTERNAL; } // LCOV_EXCL_STOP @@ -749,4 +777,175 @@ rippleUnlockEscrowMPT( return tesSUCCESS; } +TER +createMPToken( + ApplyView& view, + MPTID const& mptIssuanceID, + AccountID const& account, + std::uint32_t const flags) +{ + auto const mptokenKey = keylet::mptoken(mptIssuanceID, account); + + auto const ownerNode = + view.dirInsert(keylet::ownerDir(account), mptokenKey, describeOwnerDir(account)); + + if (!ownerNode) + return tecDIR_FULL; // LCOV_EXCL_LINE + + auto mptoken = std::make_shared(mptokenKey); + (*mptoken)[sfAccount] = account; + (*mptoken)[sfMPTokenIssuanceID] = mptIssuanceID; + (*mptoken)[sfFlags] = flags; + (*mptoken)[sfOwnerNode] = *ownerNode; + + view.insert(mptoken); + + return tesSUCCESS; +} + +TER +checkCreateMPT( + xrpl::ApplyView& view, + xrpl::MPTIssue const& mptIssue, + xrpl::AccountID const& holder, + beast::Journal j) +{ + if (mptIssue.getIssuer() == holder) + return tesSUCCESS; + + auto const mptIssuanceID = keylet::mptIssuance(mptIssue.getMptID()); + auto const mptokenID = keylet::mptoken(mptIssuanceID.key, holder); + if (!view.exists(mptokenID)) + { + if (auto const err = createMPToken(view, mptIssue.getMptID(), holder, 0); + !isTesSuccess(err)) + { + return err; + } + auto const sleAcct = view.peek(keylet::account(holder)); + if (!sleAcct) + { + return tecINTERNAL; + } + adjustOwnerCount(view, sleAcct, 1, j); + } + return tesSUCCESS; +} + +std::int64_t +maxMPTAmount(SLE const& sleIssuance) +{ + return sleIssuance[~sfMaximumAmount].value_or(maxMPTokenAmount); +} + +std::int64_t +availableMPTAmount(SLE const& sleIssuance) +{ + auto const max = maxMPTAmount(sleIssuance); + auto const outstanding = sleIssuance[sfOutstandingAmount]; + return max - outstanding; +} + +std::int64_t +availableMPTAmount(ReadView const& view, MPTID const& mptID) +{ + auto const sle = view.read(keylet::mptIssuance(mptID)); + if (!sle) + Throw(transHuman(tecINTERNAL)); + return availableMPTAmount(*sle); +} + +bool +isMPTOverflow( + std::int64_t sendAmount, + std::uint64_t outstandingAmount, + std::int64_t maximumAmount, + AllowMPTOverflow allowOverflow) +{ + std::uint64_t const limit = (allowOverflow == AllowMPTOverflow::Yes) + ? std::numeric_limits::max() + : maximumAmount; + return (sendAmount > maximumAmount || outstandingAmount > (limit - sendAmount)); +} + +STAmount +issuerFundsToSelfIssue(ReadView const& view, MPTIssue const& issue) +{ + STAmount amount{issue}; + + auto const sle = view.read(keylet::mptIssuance(issue)); + if (!sle) + return amount; + auto const available = availableMPTAmount(*sle); + return view.balanceHookSelfIssueMPT(issue, available); +} + +void +issuerSelfDebitHookMPT(ApplyView& view, MPTIssue const& issue, std::uint64_t amount) +{ + auto const available = availableMPTAmount(view, issue); + view.issuerSelfDebitHookMPT(issue, amount, available); +} + +static TER +checkMPTAllowed(ReadView const& view, TxType txType, Asset const& asset, AccountID const& accountID) +{ + if (!asset.holds()) + return tesSUCCESS; + + auto const& issuanceID = asset.get().getMptID(); + auto const validTx = txType == ttAMM_CREATE || txType == ttAMM_DEPOSIT || + txType == ttAMM_WITHDRAW || txType == ttOFFER_CREATE || txType == ttCHECK_CREATE || + txType == ttCHECK_CASH || txType == ttPAYMENT; + XRPL_ASSERT(validTx, "xrpl::checkMPTAllowed : all MPT tx or DEX"); + if (!validTx) + return tefINTERNAL; // LCOV_EXCL_LINE + + auto const& issuer = asset.getIssuer(); + if (!view.exists(keylet::account(issuer))) + return tecNO_ISSUER; // LCOV_EXCL_LINE + + auto const issuanceKey = keylet::mptIssuance(issuanceID); + auto const issuanceSle = view.read(issuanceKey); + if (!issuanceSle) + return tecOBJECT_NOT_FOUND; // LCOV_EXCL_LINE + + auto const flags = issuanceSle->getFlags(); + + if ((flags & lsfMPTLocked) != 0u) + return tecLOCKED; // LCOV_EXCL_LINE + // Offer crossing and Payment + if ((flags & lsfMPTCanTrade) == 0) + return tecNO_PERMISSION; + + if (accountID != issuer) + { + if ((flags & lsfMPTCanTransfer) == 0) + return tecNO_PERMISSION; + + auto const mptSle = view.read(keylet::mptoken(issuanceKey.key, accountID)); + // Allow to succeed since some tx create MPToken if it doesn't exist. + // Tx's have their own check for missing MPToken. + if (!mptSle) + return tesSUCCESS; + + if (mptSle->isFlag(lsfMPTLocked)) + return tecLOCKED; + } + + return tesSUCCESS; +} + +TER +checkMPTTxAllowed( + ReadView const& view, + TxType txType, + Asset const& asset, + AccountID const& accountID) +{ + // use isDEXAllowed for payment/offer crossing + XRPL_ASSERT(txType != ttPAYMENT, "xrpl::checkMPTTxAllowed : not payment"); + return checkMPTAllowed(view, txType, asset, accountID); +} + } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/nft/NFTokenUtils.cpp b/src/libxrpl/ledger/helpers/NFTokenHelpers.cpp similarity index 94% rename from src/libxrpl/tx/transactors/nft/NFTokenUtils.cpp rename to src/libxrpl/ledger/helpers/NFTokenHelpers.cpp index 3368887d94..f57294a855 100644 --- a/src/libxrpl/tx/transactors/nft/NFTokenUtils.cpp +++ b/src/libxrpl/ledger/helpers/NFTokenHelpers.cpp @@ -1,22 +1,44 @@ -#include -#include -#include +#include + +#include +#include +#include +#include +#include +#include +#include #include #include #include #include +#include #include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include #include +#include +#include #include -#include +#include +#include +#include #include +#include #include +#include +#include +#include -namespace xrpl { - -namespace nft { +namespace xrpl::nft { static std::shared_ptr locatePage(ReadView const& view, AccountID const& owner, uint256 const& id) @@ -106,7 +128,7 @@ getPageForToken( // place to make the split. if (splitIter == narr.end()) { - splitIter = std::find_if(narr.begin(), narr.end(), [&cmp](STObject const& obj) { + splitIter = std::ranges::find_if(narr, [&cmp](STObject const& obj) { return (obj.getFieldH256(sfNFTokenID) & nft::pageMask) == cmp; }); } @@ -216,9 +238,8 @@ changeTokenURI( // Locate the NFT in the page STArray& arr = page->peekFieldArray(sfNFTokens); - auto const nftIter = std::find_if(arr.begin(), arr.end(), [&nftokenID](STObject const& obj) { - return (obj[sfNFTokenID] == nftokenID); - }); + auto const nftIter = std::ranges::find_if( + arr, [&nftokenID](STObject const& obj) { return (obj[sfNFTokenID] == nftokenID); }); if (nftIter == arr.end()) return tecINTERNAL; // LCOV_EXCL_LINE @@ -297,13 +318,8 @@ mergePages(ApplyView& view, std::shared_ptr const& p1, std::shared_ptr STArray x(p1arr.size() + p2arr.size()); - std::merge( - p1arr.begin(), - p1arr.end(), - p2arr.begin(), - p2arr.end(), - std::back_inserter(x), - [](STObject const& a, STObject const& b) { + std::ranges::merge( + p1arr, p2arr, std::back_inserter(x), [](STObject const& a, STObject const& b) { return compareTokens(a.getFieldH256(sfNFTokenID), b.getFieldH256(sfNFTokenID)); }); @@ -359,9 +375,8 @@ removeToken( auto arr = curr->getFieldArray(sfNFTokens); { - auto x = std::find_if(arr.begin(), arr.end(), [&nftokenID](STObject const& obj) { - return (obj[sfNFTokenID] == nftokenID); - }); + auto x = std::ranges::find_if( + arr, [&nftokenID](STObject const& obj) { return (obj[sfNFTokenID] == nftokenID); }); if (x == arr.end()) return tecNO_ENTRY; @@ -606,33 +621,6 @@ removeTokenOffersWithLimit(ApplyView& view, Keylet const& directory, std::size_t return deletedOffersCount; } -TER -notTooManyOffers(ReadView const& view, uint256 const& nftokenID) -{ - std::size_t totalOffers = 0; - - { - Dir const buys(view, keylet::nft_buys(nftokenID)); - for (auto iter = buys.begin(); iter != buys.end(); iter.next_page()) - { - totalOffers += iter.page_size(); - if (totalOffers > maxDeletableTokenOfferEntries) - return tefTOO_BIG; - } - } - - { - Dir const sells(view, keylet::nft_sells(nftokenID)); - for (auto iter = sells.begin(); iter != sells.end(); iter.next_page()) - { - totalOffers += iter.page_size(); - if (totalOffers > maxDeletableTokenOfferEntries) - return tefTOO_BIG; - } - } - return tesSUCCESS; -} - bool deleteTokenOffer(ApplyView& view, std::shared_ptr const& offer) { @@ -854,15 +842,15 @@ tokenOfferCreatePreclaim( if (view.rules().enabled(featureNFTokenMintOffer)) { if (nftIssuer != amount.getIssuer() && - !view.read(keylet::line(nftIssuer, amount.issue()))) + !view.read(keylet::line(nftIssuer, amount.get()))) return tecNO_LINE; } - else if (!view.exists(keylet::line(nftIssuer, amount.issue()))) + else if (!view.exists(keylet::line(nftIssuer, amount.get()))) { return tecNO_LINE; } - if (isFrozen(view, nftIssuer, amount.getCurrency(), amount.getIssuer())) + if (isFrozen(view, nftIssuer, amount.get().currency, amount.getIssuer())) return tecFROZEN; } @@ -875,7 +863,7 @@ tokenOfferCreatePreclaim( return tefNFTOKEN_IS_NOT_TRANSFERABLE; } - if (isFrozen(view, acctID, amount.getCurrency(), amount.getIssuer())) + if (isFrozen(view, acctID, amount.get().currency, amount.getIssuer())) return tecFROZEN; // If this is an offer to buy the token, the account must have the @@ -1106,5 +1094,4 @@ checkTrustlineDeepFrozen( return tesSUCCESS; } -} // namespace nft -} // namespace xrpl +} // namespace xrpl::nft diff --git a/src/libxrpl/ledger/helpers/OfferHelpers.cpp b/src/libxrpl/ledger/helpers/OfferHelpers.cpp index 3d63240fd0..d57066cfe2 100644 --- a/src/libxrpl/ledger/helpers/OfferHelpers.cpp +++ b/src/libxrpl/ledger/helpers/OfferHelpers.cpp @@ -1,9 +1,17 @@ #include -// + +#include +#include +#include +#include #include #include -#include -#include +#include +#include // IWYU pragma: keep +#include +#include + +#include namespace xrpl { diff --git a/src/libxrpl/tx/transactors/payment_channel/PaymentChannelHelpers.cpp b/src/libxrpl/ledger/helpers/PaymentChannelHelpers.cpp similarity index 81% rename from src/libxrpl/tx/transactors/payment_channel/PaymentChannelHelpers.cpp rename to src/libxrpl/ledger/helpers/PaymentChannelHelpers.cpp index 176b920e6b..31c206d85b 100644 --- a/src/libxrpl/tx/transactors/payment_channel/PaymentChannelHelpers.cpp +++ b/src/libxrpl/ledger/helpers/PaymentChannelHelpers.cpp @@ -1,9 +1,18 @@ -#include -#include -#include -#include +#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include namespace xrpl { diff --git a/src/libxrpl/ledger/helpers/PermissionedDEXHelpers.cpp b/src/libxrpl/ledger/helpers/PermissionedDEXHelpers.cpp new file mode 100644 index 0000000000..d65462f0cd --- /dev/null +++ b/src/libxrpl/ledger/helpers/PermissionedDEXHelpers.cpp @@ -0,0 +1,88 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace xrpl::permissioned_dex { + +bool +accountInDomain(ReadView const& view, AccountID const& account, Domain const& domainID) +{ + auto const sleDomain = view.read(keylet::permissionedDomain(domainID)); + if (!sleDomain) + return false; + + // domain owner is in the domain + if (sleDomain->getAccountID(sfOwner) == account) + return true; + + auto const& credentials = sleDomain->getFieldArray(sfAcceptedCredentials); + + bool const inDomain = std::ranges::any_of(credentials, [&](auto const& credential) { + auto const sleCred = view.read( + keylet::credential(account, credential[sfIssuer], credential[sfCredentialType])); + if (!sleCred || !sleCred->isFlag(lsfAccepted)) + return false; + + return !credentials::checkExpired(sleCred, view.header().parentCloseTime); + }); + + return inDomain; +} + +bool +offerInDomain( + ReadView const& view, + uint256 const& offerID, + Domain const& domainID, + beast::Journal j) +{ + auto const sleOffer = view.read(keylet::offer(offerID)); + + // The following are defensive checks that should never happen, since this + // function is used to check against the order book offers, which should not + // have any of the following wrong behavior + if (!sleOffer) + return false; // LCOV_EXCL_LINE + if (!sleOffer->isFieldPresent(sfDomainID)) + return false; // LCOV_EXCL_LINE + if (sleOffer->getFieldH256(sfDomainID) != domainID) + return false; // LCOV_EXCL_LINE + + if (view.rules().enabled(fixSecurity3_1_3)) + { + // post-fixSecurity3_1_3: also catches empty sfAdditionalBooks (size == 0) + if (sleOffer->isFlag(lsfHybrid) && + (!sleOffer->isFieldPresent(sfAdditionalBooks) || + sleOffer->getFieldArray(sfAdditionalBooks).size() != 1)) + { + JLOG(j.error()) << "Hybrid offer " << offerID + << " missing or malformed AdditionalBooks field"; + return false; // LCOV_EXCL_LINE + } + } + else + { + // pre-fixSecurity3_1_3: only check for missing sfAdditionalBooks + if (sleOffer->isFlag(lsfHybrid) && !sleOffer->isFieldPresent(sfAdditionalBooks)) + { + JLOG(j.error()) << "Hybrid offer " << offerID << " missing AdditionalBooks field"; + return false; // LCOV_EXCL_LINE + } + } + + return accountInDomain(view, sleOffer->getAccountID(sfAccount), domainID); +} + +} // namespace xrpl::permissioned_dex diff --git a/src/libxrpl/ledger/helpers/RippleStateHelpers.cpp b/src/libxrpl/ledger/helpers/RippleStateHelpers.cpp index 697b8fc293..f5a5c01060 100644 --- a/src/libxrpl/ledger/helpers/RippleStateHelpers.cpp +++ b/src/libxrpl/ledger/helpers/RippleStateHelpers.cpp @@ -1,15 +1,34 @@ #include -// + #include +#include +#include +#include +#include #include #include #include #include +#include +#include #include #include +#include #include +#include #include #include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include namespace xrpl { @@ -33,11 +52,14 @@ creditLimit( if (sleRippleState) { result = sleRippleState->getFieldAmount(account < issuer ? sfLowLimit : sfHighLimit); - result.setIssuer(account); + result.get().account = account; } XRPL_ASSERT(result.getIssuer() == account, "xrpl::creditLimit : result issuer match"); - XRPL_ASSERT(result.getCurrency() == currency, "xrpl::creditLimit : result currency match"); + XRPL_ASSERT( + result.get().currency == currency, + "xrpl::creditLimit : result currency " + "match"); return result; } @@ -63,11 +85,14 @@ creditBalance( result = sleRippleState->getFieldAmount(sfBalance); if (account < issuer) result.negate(); - result.setIssuer(account); + result.get().account = account; } XRPL_ASSERT(result.getIssuer() == account, "xrpl::creditBalance : result issuer match"); - XRPL_ASSERT(result.getCurrency() == currency, "xrpl::creditBalance : result currency match"); + XRPL_ASSERT( + result.get().currency == currency, + "xrpl::creditBalance : result currency " + "match"); return result; } @@ -222,7 +247,7 @@ trustCreate( sleRippleState->setFieldAmount(bSetHigh ? sfHighLimit : sfLowLimit, saLimit); sleRippleState->setFieldAmount( bSetHigh ? sfLowLimit : sfHighLimit, - STAmount(Issue{saBalance.getCurrency(), bSetDst ? uSrcAccountID : uDstAccountID})); + STAmount(Issue{saBalance.get().currency, bSetDst ? uSrcAccountID : uDstAccountID})); if (uQualityIn != 0u) sleRippleState->setFieldU32(bSetHigh ? sfHighQualityIn : sfLowQualityIn, uQualityIn); @@ -261,7 +286,7 @@ trustCreate( // ONLY: Create ripple balance. sleRippleState->setFieldAmount(sfBalance, bSetHigh ? -saBalance : saBalance); - view.creditHook(uSrcAccountID, uDstAccountID, saBalance, saBalance.zeroed()); + view.creditHookIOU(uSrcAccountID, uDstAccountID, saBalance, saBalance.zeroed()); return tesSUCCESS; } @@ -367,7 +392,7 @@ issueIOU( "xrpl::issueIOU : neither account nor issuer is XRP"); // Consistency check - XRPL_ASSERT(issue == amount.issue(), "xrpl::issueIOU : matching issue"); + XRPL_ASSERT(issue == amount.get(), "xrpl::issueIOU : matching issue"); // Can't send to self! XRPL_ASSERT(issue.account != account, "xrpl::issueIOU : not issuer account"); @@ -392,7 +417,7 @@ issueIOU( auto const must_delete = updateTrustLine( view, state, bSenderHigh, issue.account, start_balance, final_balance, j); - view.creditHook(issue.account, account, amount, start_balance); + view.creditHookIOU(issue.account, account, amount, start_balance); if (bSenderHigh) final_balance.negate(); @@ -422,7 +447,7 @@ issueIOU( STAmount const limit(Issue{issue.currency, account}); STAmount final_balance = amount; - final_balance.setIssuer(noAccount()); + final_balance.get().account = noAccount(); auto const receiverAccount = view.peek(keylet::account(account)); if (!receiverAccount) @@ -461,7 +486,7 @@ redeemIOU( "xrpl::redeemIOU : neither account nor issuer is XRP"); // Consistency check - XRPL_ASSERT(issue == amount.issue(), "xrpl::redeemIOU : matching issue"); + XRPL_ASSERT(issue == amount.get(), "xrpl::redeemIOU : matching issue"); // Can't send to self! XRPL_ASSERT(issue.account != account, "xrpl::redeemIOU : not issuer account"); @@ -484,7 +509,7 @@ redeemIOU( auto const must_delete = updateTrustLine(view, state, bSenderHigh, account, start_balance, final_balance, j); - view.creditHook(account, issue.account, amount, start_balance); + view.creditHookIOU(account, issue.account, amount, start_balance); if (bSenderHigh) final_balance.negate(); @@ -757,4 +782,20 @@ deleteAMMTrustLine( return tesSUCCESS; } +TER +deleteAMMMPToken( + ApplyView& view, + std::shared_ptr sleMpt, + AccountID const& ammAccountID, + beast::Journal j) +{ + if (!view.dirRemove( + keylet::ownerDir(ammAccountID), (*sleMpt)[sfOwnerNode], sleMpt->key(), false)) + return tefBAD_LEDGER; // LCOV_EXCL_LINE + + view.erase(sleMpt); + + return tesSUCCESS; +} + } // namespace xrpl diff --git a/src/libxrpl/ledger/helpers/TokenHelpers.cpp b/src/libxrpl/ledger/helpers/TokenHelpers.cpp index 8d2b0d5fff..d5f1d5eb39 100644 --- a/src/libxrpl/ledger/helpers/TokenHelpers.cpp +++ b/src/libxrpl/ledger/helpers/TokenHelpers.cpp @@ -1,19 +1,34 @@ #include -// + #include +#include +#include #include +#include +#include #include #include #include +#include +#include +#include #include #include +#include #include +#include #include -#include +#include +#include +#include +#include #include -#include +#include +#include -#include +#include +#include +#include #include namespace xrpl { @@ -23,8 +38,8 @@ bool isLPTokenFrozen( ReadView const& view, AccountID const& account, - Issue const& asset, - Issue const& asset2); + Asset const& asset, + Asset const& asset2); //------------------------------------------------------------------------------ // @@ -35,18 +50,9 @@ isLPTokenFrozen( bool isGlobalFrozen(ReadView const& view, Asset const& asset) { - return std::visit( - [&](TIss const& issue) { - if constexpr (std::is_same_v) - { - return isGlobalFrozen(view, issue.getIssuer()); - } - else - { - return isGlobalFrozen(view, issue); - } - }, - asset.value()); + return asset.visit( + [&](Issue const& issue) { return isGlobalFrozen(view, issue.getIssuer()); }, + [&](MPTIssue const& issue) { return isGlobalFrozen(view, issue); }); } bool @@ -103,18 +109,9 @@ isAnyFrozen( Asset const& asset, int depth) { - return std::visit( - [&](TIss const& issue) { - if constexpr (std::is_same_v) - { - return isAnyFrozen(view, accounts, issue); - } - else - { - return isAnyFrozen(view, accounts, issue, depth); - } - }, - asset.value()); + return asset.visit( + [&](Issue const& issue) { return isAnyFrozen(view, accounts, issue); }, + [&](MPTIssue const& issue) { return isAnyFrozen(view, accounts, issue, depth); }); } bool @@ -190,11 +187,7 @@ getLineIfUsable( auto const sleAmm = view.read(keylet::amm((*sleIssuer)[sfAMMID])); if (!sleAmm || - isLPTokenFrozen( - view, - account, - (*sleAmm)[sfAsset].get(), - (*sleAmm)[sfAsset2].get())) + isLPTokenFrozen(view, account, (*sleAmm)[sfAsset], (*sleAmm)[sfAsset2])) { return nullptr; } @@ -230,7 +223,7 @@ getTrustLineBalance( { amount += sle->getFieldAmount(oppositeField); } - amount.setIssuer(issuer); + amount.get().account = issuer; } else { @@ -240,7 +233,7 @@ getTrustLineBalance( JLOG(j.trace()) << "getTrustLineBalance:" << " account=" << to_string(account) << " amount=" << amount.getFullText(); - return view.balanceHook(account, issuer, amount); + return view.balanceHookIOU(account, issuer, amount); } STAmount @@ -298,6 +291,9 @@ accountHolds( SpendableHandling includeFullBalance) { bool const returnSpendable = (includeFullBalance == shFULL_BALANCE); + STAmount amount{mptIssue}; + auto const& issuer = mptIssue.getIssuer(); + bool const mptokensV2 = view.rules().enabled(featureMPTokensV2); if (returnSpendable && account == mptIssue.getIssuer()) { @@ -307,16 +303,14 @@ accountHolds( if (!issuance) { - return STAmount{mptIssue}; + return amount; } - return STAmount{ - mptIssue, - issuance->at(~sfMaximumAmount).value_or(maxMPTokenAmount) - - issuance->at(sfOutstandingAmount)}; + auto const available = availableMPTAmount(*issuance); + if (!mptokensV2) + return STAmount{mptIssue, available}; + return view.balanceHookMPT(issuer, mptIssue, available); } - STAmount amount; - auto const sleMpt = view.read(keylet::mptoken(mptIssue.getMptID(), account)); if (!sleMpt) @@ -352,6 +346,8 @@ accountHolds( } } + if (view.rules().enabled(featureMPTokensV2)) + return view.balanceHookMPT(account, mptIssue, amount.mpt().value()); return amount; } @@ -365,19 +361,14 @@ accountHolds( beast::Journal j, SpendableHandling includeFullBalance) { - return std::visit( - [&](TIss const& value) { - if constexpr (std::is_same_v) - { - return accountHolds(view, account, value, zeroIfFrozen, j, includeFullBalance); - } - else if constexpr (std::is_same_v) - { - return accountHolds( - view, account, value, zeroIfFrozen, zeroIfUnauthorized, j, includeFullBalance); - } + return asset.visit( + [&](Issue const& issue) { + return accountHolds(view, account, issue, zeroIfFrozen, j, includeFullBalance); }, - asset.value()); + [&](MPTIssue const& issue) { + return accountHolds( + view, account, issue, zeroIfFrozen, zeroIfUnauthorized, j, includeFullBalance); + }); } STAmount @@ -388,28 +379,38 @@ accountFunds( FreezeHandling freezeHandling, beast::Journal j) { + XRPL_ASSERT(saDefault.holds(), "xrpl::accountFunds: saDefault holds Issue"); + if (!saDefault.native() && saDefault.getIssuer() == id) return saDefault; return accountHolds( - view, id, saDefault.getCurrency(), saDefault.getIssuer(), freezeHandling, j); + view, id, saDefault.get().currency, saDefault.getIssuer(), freezeHandling, j); +} + +STAmount +accountFunds( + ReadView const& view, + AccountID const& id, + STAmount const& saDefault, + FreezeHandling freezeHandling, + AuthHandling authHandling, + beast::Journal j) +{ + return saDefault.asset().visit( + [&](Issue const&) { return accountFunds(view, id, saDefault, freezeHandling, j); }, + [&](MPTIssue const&) { + return accountHolds( + view, id, saDefault.asset(), freezeHandling, authHandling, j, shFULL_BALANCE); + }); } Rate transferRate(ReadView const& view, STAmount const& amount) { - return std::visit( - [&](TIss const& issue) { - if constexpr (std::is_same_v) - { - return transferRate(view, issue.getIssuer()); - } - else - { - return transferRate(view, issue.getMptID()); - } - }, - amount.asset().value()); + return amount.asset().visit( + [&](Issue const& issue) { return transferRate(view, issue.getIssuer()); }, + [&](MPTIssue const& issue) { return transferRate(view, issue.getMptID()); }); } //------------------------------------------------------------------------------ @@ -513,7 +514,7 @@ canTransfer(ReadView const& view, Asset const& asset, AccountID const& from, Acc // - Create trust line if needed. // --> bCheckIssuer : normally require issuer to be involved. static TER -rippleCreditIOU( +directSendNoFeeIOU( ApplyView& view, AccountID const& uSenderID, AccountID const& uReceiverID, @@ -522,25 +523,26 @@ rippleCreditIOU( beast::Journal j) { AccountID const& issuer = saAmount.getIssuer(); - Currency const& currency = saAmount.getCurrency(); + Currency const& currency = saAmount.get().currency; // Make sure issuer is involved. XRPL_ASSERT( !bCheckIssuer || uSenderID == issuer || uReceiverID == issuer, - "xrpl::rippleCreditIOU : matching issuer or don't care"); + "xrpl::directSendNoFeeIOU : matching issuer or don't care"); (void)issuer; // Disallow sending to self. - XRPL_ASSERT(uSenderID != uReceiverID, "xrpl::rippleCreditIOU : sender is not receiver"); + XRPL_ASSERT(uSenderID != uReceiverID, "xrpl::directSendNoFeeIOU : sender is not receiver"); bool const bSenderHigh = uSenderID > uReceiverID; auto const index = keylet::line(uSenderID, uReceiverID, currency); XRPL_ASSERT( - !isXRP(uSenderID) && uSenderID != noAccount(), "xrpl::rippleCreditIOU : sender is not XRP"); + !isXRP(uSenderID) && uSenderID != noAccount(), + "xrpl::directSendNoFeeIOU : sender is not XRP"); XRPL_ASSERT( !isXRP(uReceiverID) && uReceiverID != noAccount(), - "xrpl::rippleCreditIOU : receiver is not XRP"); + "xrpl::directSendNoFeeIOU : receiver is not XRP"); // If the line exists, modify it accordingly. if (auto const sleRippleState = view.peek(index)) @@ -550,13 +552,13 @@ rippleCreditIOU( if (bSenderHigh) saBalance.negate(); // Put balance in sender terms. - view.creditHook(uSenderID, uReceiverID, saAmount, saBalance); + view.creditHookIOU(uSenderID, uReceiverID, saAmount, saBalance); STAmount const saBefore = saBalance; saBalance -= saAmount; - JLOG(j.trace()) << "rippleCreditIOU: " << to_string(uSenderID) << " -> " + JLOG(j.trace()) << "directSendNoFeeIOU: " << to_string(uSenderID) << " -> " << to_string(uReceiverID) << " : before=" << saBefore.getFullText() << " amount=" << saAmount.getFullText() << " after=" << saBalance.getFullText(); @@ -602,7 +604,7 @@ rippleCreditIOU( // Want to reflect balance to zero even if we are deleting line. sleRippleState->setFieldAmount(sfBalance, saBalance); - // ONLY: Adjust ripple balance. + // ONLY: Adjust balance. if (bDelete) { @@ -621,9 +623,9 @@ rippleCreditIOU( STAmount const saReceiverLimit(Issue{currency, uReceiverID}); STAmount saBalance{saAmount}; - saBalance.setIssuer(noAccount()); + saBalance.get().account = noAccount(); - JLOG(j.debug()) << "rippleCreditIOU: " + JLOG(j.debug()) << "directSendNoFeeIOU: " "create line: " << to_string(uSenderID) << " -> " << to_string(uReceiverID) << " : " << saAmount.getFullText(); @@ -656,7 +658,7 @@ rippleCreditIOU( // --> saAmount: Amount/currency/issuer to deliver to receiver. // <-- saActual: Amount actually cost. Sender pays fees. static TER -rippleSendIOU( +directSendNoLimitIOU( ApplyView& view, AccountID const& uSenderID, AccountID const& uReceiverID, @@ -669,13 +671,13 @@ rippleSendIOU( XRPL_ASSERT( !isXRP(uSenderID) && !isXRP(uReceiverID), - "xrpl::rippleSendIOU : neither sender nor receiver is XRP"); - XRPL_ASSERT(uSenderID != uReceiverID, "xrpl::rippleSendIOU : sender is not receiver"); + "xrpl::directSendNoLimitIOU : neither sender nor receiver is XRP"); + XRPL_ASSERT(uSenderID != uReceiverID, "xrpl::directSendNoLimitIOU : sender is not receiver"); if (uSenderID == issuer || uReceiverID == issuer || issuer == noAccount()) { // Direct send: redeeming IOUs and/or sending own IOUs. - auto const ter = rippleCreditIOU(view, uSenderID, uReceiverID, saAmount, false, j); + auto const ter = directSendNoFeeIOU(view, uSenderID, uReceiverID, saAmount, false, j); if (!isTesSuccess(ter)) return ter; saActual = saAmount; @@ -689,14 +691,14 @@ rippleSendIOU( saActual = (waiveFee == WaiveTransferFee::Yes) ? saAmount : multiply(saAmount, transferRate(view, issuer)); - JLOG(j.debug()) << "rippleSendIOU> " << to_string(uSenderID) << " - > " + JLOG(j.debug()) << "directSendNoLimitIOU> " << to_string(uSenderID) << " - > " << to_string(uReceiverID) << " : deliver=" << saAmount.getFullText() << " cost=" << saActual.getFullText(); - TER terResult = rippleCreditIOU(view, issuer, uReceiverID, saAmount, true, j); + TER terResult = directSendNoFeeIOU(view, issuer, uReceiverID, saAmount, true, j); if (tesSUCCESS == terResult) - terResult = rippleCreditIOU(view, uSenderID, issuer, saActual, true, j); + terResult = directSendNoFeeIOU(view, uSenderID, issuer, saActual, true, j); return terResult; } @@ -705,7 +707,7 @@ rippleSendIOU( // --> receivers: Amount/currency/issuer to deliver to receivers. // <-- saActual: Amount actually cost to sender. Sender pays fees. static TER -rippleSendMultiIOU( +directSendNoLimitMultiIOU( ApplyView& view, AccountID const& senderID, Issue const& issue, @@ -716,7 +718,7 @@ rippleSendMultiIOU( { auto const& issuer = issue.getIssuer(); - XRPL_ASSERT(!isXRP(senderID), "xrpl::rippleSendMultiIOU : sender is not XRP"); + XRPL_ASSERT(!isXRP(senderID), "xrpl::directSendNoLimitMultiIOU : sender is not XRP"); // These may diverge STAmount takeFromSender{issue}; @@ -734,15 +736,15 @@ rippleSendMultiIOU( if (!amount || (senderID == receiverID)) continue; - XRPL_ASSERT(!isXRP(receiverID), "xrpl::rippleSendMultiIOU : receiver is not XRP"); + XRPL_ASSERT(!isXRP(receiverID), "xrpl::directSendNoLimitMultiIOU : receiver is not XRP"); if (senderID == issuer || receiverID == issuer || issuer == noAccount()) { // Direct send: redeeming IOUs and/or sending own IOUs. - if (auto const ter = rippleCreditIOU(view, senderID, receiverID, amount, false, j)) + if (auto const ter = directSendNoFeeIOU(view, senderID, receiverID, amount, false, j)) return ter; actual += amount; - // Do not add amount to takeFromSender, because rippleCreditIOU took + // Do not add amount to takeFromSender, because directSendNoFeeIOU took // it. continue; @@ -758,17 +760,18 @@ rippleSendMultiIOU( actual += actualSend; takeFromSender += actualSend; - JLOG(j.debug()) << "rippleSendMultiIOU> " << to_string(senderID) << " - > " + JLOG(j.debug()) << "directSendNoLimitMultiIOU> " << to_string(senderID) << " - > " << to_string(receiverID) << " : deliver=" << amount.getFullText() << " cost=" << actual.getFullText(); - if (TER const terResult = rippleCreditIOU(view, issuer, receiverID, amount, true, j)) + if (TER const terResult = directSendNoFeeIOU(view, issuer, receiverID, amount, true, j)) return terResult; } if (senderID != issuer && takeFromSender) { - if (TER const terResult = rippleCreditIOU(view, senderID, issuer, takeFromSender, true, j)) + if (TER const terResult = + directSendNoFeeIOU(view, senderID, issuer, takeFromSender, true, j)) return terResult; } @@ -813,7 +816,7 @@ accountSendIOU( JLOG(j.trace()) << "accountSendIOU: " << to_string(uSenderID) << " -> " << to_string(uReceiverID) << " : " << saAmount.getFullText(); - return rippleSendIOU(view, uSenderID, uReceiverID, saAmount, saActual, j, waiveFee); + return directSendNoLimitIOU(view, uSenderID, uReceiverID, saAmount, saActual, j, waiveFee); } /* XRP send which does not check reserve and can do pure adjustment. @@ -857,7 +860,7 @@ accountSendIOU( else { auto const sndBal = sender->getFieldAmount(sfBalance); - view.creditHook(uSenderID, xrpAccount(), saAmount, sndBal); + view.creditHookIOU(uSenderID, xrpAccount(), saAmount, sndBal); // Decrement XRP balance. sender->setFieldAmount(sfBalance, sndBal - saAmount); @@ -870,7 +873,7 @@ accountSendIOU( // Increment XRP balance. auto const rcvBal = receiver->getFieldAmount(sfBalance); receiver->setFieldAmount(sfBalance, rcvBal + saAmount); - view.creditHook(xrpAccount(), uReceiverID, saAmount, -rcvBal); + view.creditHookIOU(xrpAccount(), uReceiverID, saAmount, -rcvBal); view.update(receiver); } @@ -912,7 +915,7 @@ accountSendMultiIOU( JLOG(j.trace()) << "accountSendMultiIOU: " << to_string(senderID) << " sending " << receivers.size() << " IOUs"; - return rippleSendMultiIOU(view, senderID, issue, receivers, actual, j, waiveFee); + return directSendNoLimitMultiIOU(view, senderID, issue, receivers, actual, j, waiveFee); } /* XRP send which does not check reserve and can do pure adjustment. @@ -973,7 +976,7 @@ accountSendMultiIOU( // Increment XRP balance. auto const rcvBal = receiver->getFieldAmount(sfBalance); receiver->setFieldAmount(sfBalance, rcvBal + amount); - view.creditHook(xrpAccount(), receiverID, amount, -rcvBal); + view.creditHookIOU(xrpAccount(), receiverID, amount, -rcvBal); view.update(receiver); @@ -1001,7 +1004,7 @@ accountSendMultiIOU( return TER{tecFAILED_PROCESSING}; } auto const sndBal = sender->getFieldAmount(sfBalance); - view.creditHook(senderID, xrpAccount(), takeFromSender, sndBal); + view.creditHookIOU(senderID, xrpAccount(), takeFromSender, sndBal); // Decrement XRP balance. sender->setFieldAmount(sfBalance, sndBal - takeFromSender); @@ -1022,7 +1025,7 @@ accountSendMultiIOU( } static TER -rippleCreditMPT( +directSendNoFeeMPT( ApplyView& view, AccountID const& uSenderID, AccountID const& uReceiverID, @@ -1035,9 +1038,20 @@ rippleCreditMPT( auto sleIssuance = view.peek(mptID); if (!sleIssuance) return tecOBJECT_NOT_FOUND; + + auto const maxAmount = maxMPTAmount(*sleIssuance); + auto const outstanding = sleIssuance->getFieldU64(sfOutstandingAmount); + auto const available = availableMPTAmount(*sleIssuance); + auto const amt = saAmount.mpt().value(); + if (uSenderID == issuer) { - (*sleIssuance)[sfOutstandingAmount] += saAmount.mpt().value(); + if (view.rules().enabled(featureMPTokensV2)) + { + if (isMPTOverflow(amt, outstanding, maxAmount, AllowMPTOverflow::Yes)) + return tecPATH_DRY; + } + (*sleIssuance)[sfOutstandingAmount] += amt; view.update(sleIssuance); } else @@ -1045,11 +1059,11 @@ rippleCreditMPT( auto const mptokenID = keylet::mptoken(mptID.key, uSenderID); if (auto sle = view.peek(mptokenID)) { - auto const amt = sle->getFieldU64(sfMPTAmount); - auto const pay = saAmount.mpt().value(); - if (amt < pay) + auto const senderBalance = sle->getFieldU64(sfMPTAmount); + if (senderBalance < amt) return tecINSUFFICIENT_FUNDS; - (*sle)[sfMPTAmount] = amt - pay; + view.creditHookMPT(uSenderID, uReceiverID, saAmount, (*sle)[sfMPTAmount], available); + (*sle)[sfMPTAmount] = senderBalance - amt; view.update(sle); } else @@ -1060,11 +1074,9 @@ rippleCreditMPT( if (uReceiverID == issuer) { - auto const outstanding = sleIssuance->getFieldU64(sfOutstandingAmount); - auto const redeem = saAmount.mpt().value(); - if (outstanding >= redeem) + if (outstanding >= amt) { - sleIssuance->setFieldU64(sfOutstandingAmount, outstanding - redeem); + sleIssuance->setFieldU64(sfOutstandingAmount, outstanding - amt); view.update(sleIssuance); } else @@ -1077,7 +1089,8 @@ rippleCreditMPT( auto const mptokenID = keylet::mptoken(mptID.key, uReceiverID); if (auto sle = view.peek(mptokenID)) { - (*sle)[sfMPTAmount] += saAmount.mpt().value(); + view.creditHookMPT(uSenderID, uReceiverID, saAmount, (*sle)[sfMPTAmount], available); + (*sle)[sfMPTAmount] += amt; view.update(sle); } else @@ -1090,18 +1103,19 @@ rippleCreditMPT( } static TER -rippleSendMPT( +directSendNoLimitMPT( ApplyView& view, AccountID const& uSenderID, AccountID const& uReceiverID, STAmount const& saAmount, STAmount& saActual, beast::Journal j, - WaiveTransferFee waiveFee) + WaiveTransferFee waiveFee, + AllowMPTOverflow allowOverflow) { - XRPL_ASSERT(uSenderID != uReceiverID, "xrpl::rippleSendMPT : sender is not receiver"); + XRPL_ASSERT(uSenderID != uReceiverID, "xrpl::directSendNoLimitMPT : sender is not receiver"); - // Safe to get MPT since rippleSendMPT is only called by accountSendMPT + // Safe to get MPT since directSendNoLimitMPT is only called by accountSendMPT auto const& issuer = saAmount.getIssuer(); auto const sle = view.read(keylet::mptIssuance(saAmount.get().getMptID())); @@ -1115,14 +1129,18 @@ rippleSendMPT( if (uSenderID == issuer) { auto const sendAmount = saAmount.mpt().value(); - auto const maximumAmount = sle->at(~sfMaximumAmount).value_or(maxMPTokenAmount); - if (sendAmount > maximumAmount || - sle->getFieldU64(sfOutstandingAmount) > maximumAmount - sendAmount) + auto const maxAmount = maxMPTAmount(*sle); + auto const outstanding = sle->getFieldU64(sfOutstandingAmount); + auto const mptokensV2 = view.rules().enabled(featureMPTokensV2); + allowOverflow = (allowOverflow == AllowMPTOverflow::Yes && mptokensV2) + ? AllowMPTOverflow::Yes + : AllowMPTOverflow::No; + if (isMPTOverflow(sendAmount, outstanding, maxAmount, allowOverflow)) return tecPATH_DRY; } // Direct send: redeeming MPTs and/or sending own MPTs. - auto const ter = rippleCreditMPT(view, uSenderID, uReceiverID, saAmount, j); + auto const ter = directSendNoFeeMPT(view, uSenderID, uReceiverID, saAmount, j); if (!isTesSuccess(ter)) return ter; saActual = saAmount; @@ -1134,19 +1152,19 @@ rippleSendMPT( ? saAmount : multiply(saAmount, transferRate(view, saAmount.get().getMptID())); - JLOG(j.debug()) << "rippleSendMPT> " << to_string(uSenderID) << " - > " + JLOG(j.debug()) << "directSendNoLimitMPT> " << to_string(uSenderID) << " - > " << to_string(uReceiverID) << " : deliver=" << saAmount.getFullText() << " cost=" << saActual.getFullText(); - if (auto const terResult = rippleCreditMPT(view, issuer, uReceiverID, saAmount, j); + if (auto const terResult = directSendNoFeeMPT(view, issuer, uReceiverID, saAmount, j); !isTesSuccess(terResult)) return terResult; - return rippleCreditMPT(view, uSenderID, issuer, saActual, j); + return directSendNoFeeMPT(view, uSenderID, issuer, saActual, j); } static TER -rippleSendMultiMPT( +directSendNoLimitMultiMPT( ApplyView& view, AccountID const& senderID, MPTIssue const& mptIssue, @@ -1163,7 +1181,7 @@ rippleSendMultiMPT( // For the issuer-as-sender case, track the running total to validate // against MaximumAmount. The read-only SLE (view.read) is not updated - // by rippleCreditMPT, so a per-iteration SLE read would be stale. + // by directSendNoFeeMPT, so a per-iteration SLE read would be stale. // Use uint64_t, not STAmount, to keep MaximumAmount comparisons in exact // integer arithmetic. STAmount implicitly converts to Number, whose // small-scale mantissa (~16 digits) can lose precision for values near @@ -1195,7 +1213,7 @@ rippleSendMultiMPT( { XRPL_ASSERT_PARTS( takeFromSender == beast::zero, - "xrpl::rippleSendMultiMPT", + "xrpl::directSendNoLimitMultiMPT", "sender == issuer, takeFromSender == zero"); std::uint64_t const sendAmount = amount.mpt().value(); @@ -1231,11 +1249,11 @@ rippleSendMultiMPT( } // Direct send: redeeming MPTs and/or sending own MPTs. - if (auto const ter = rippleCreditMPT(view, senderID, receiverID, amount, j)) + if (auto const ter = directSendNoFeeMPT(view, senderID, receiverID, amount, j); + !isTesSuccess(ter)) return ter; actual += amount; - // Do not add amount to takeFromSender, because rippleCreditMPT - // took it. + // Do not add amount to takeFromSender, because directSendNoFeeMPT took it. continue; } @@ -1247,17 +1265,19 @@ rippleSendMultiMPT( actual += actualSend; takeFromSender += actualSend; - JLOG(j.debug()) << "rippleSendMultiMPT> " << to_string(senderID) << " - > " + JLOG(j.debug()) << "directSendNoLimitMultiMPT> " << to_string(senderID) << " - > " << to_string(receiverID) << " : deliver=" << amount.getFullText() << " cost=" << actualSend.getFullText(); - if (auto const terResult = rippleCreditMPT(view, issuer, receiverID, amount, j)) - return terResult; + if (auto const ter = directSendNoFeeMPT(view, issuer, receiverID, amount, j); + !isTesSuccess(ter)) + return ter; } if (senderID != issuer && takeFromSender) { - if (TER const terResult = rippleCreditMPT(view, senderID, issuer, takeFromSender, j)) - return terResult; + if (auto const ter = directSendNoFeeMPT(view, senderID, issuer, takeFromSender, j); + !isTesSuccess(ter)) + return ter; } return tesSUCCESS; @@ -1270,7 +1290,8 @@ accountSendMPT( AccountID const& uReceiverID, STAmount const& saAmount, beast::Journal j, - WaiveTransferFee waiveFee) + WaiveTransferFee waiveFee, + AllowMPTOverflow allowOverflow) { XRPL_ASSERT( saAmount >= beast::zero && saAmount.holds(), @@ -1284,7 +1305,8 @@ accountSendMPT( STAmount saActual{saAmount.asset()}; - return rippleSendMPT(view, uSenderID, uReceiverID, saAmount, saActual, j, waiveFee); + return directSendNoLimitMPT( + view, uSenderID, uReceiverID, saAmount, saActual, j, waiveFee, allowOverflow); } static TER @@ -1298,7 +1320,7 @@ accountSendMultiMPT( { STAmount actual; - return rippleSendMultiMPT(view, senderID, mptIssue, receivers, actual, j, waiveFee); + return directSendNoLimitMultiMPT(view, senderID, mptIssue, receivers, actual, j, waiveFee); } //------------------------------------------------------------------------------ @@ -1308,7 +1330,7 @@ accountSendMultiMPT( //------------------------------------------------------------------------------ TER -rippleCredit( +directSendNoFee( ApplyView& view, AccountID const& uSenderID, AccountID const& uReceiverID, @@ -1316,19 +1338,14 @@ rippleCredit( bool bCheckIssuer, beast::Journal j) { - return std::visit( - [&](TIss const& issue) { - if constexpr (std::is_same_v) - { - return rippleCreditIOU(view, uSenderID, uReceiverID, saAmount, bCheckIssuer, j); - } - else - { - XRPL_ASSERT(!bCheckIssuer, "xrpl::rippleCredit : not checking issuer"); - return rippleCreditMPT(view, uSenderID, uReceiverID, saAmount, j); - } + return saAmount.asset().visit( + [&](Issue const&) { + return directSendNoFeeIOU(view, uSenderID, uReceiverID, saAmount, bCheckIssuer, j); }, - saAmount.asset().value()); + [&](MPTIssue const&) { + XRPL_ASSERT(!bCheckIssuer, "xrpl::directSendNoFee : not checking issuer"); + return directSendNoFeeMPT(view, uSenderID, uReceiverID, saAmount, j); + }); } TER @@ -1338,20 +1355,17 @@ accountSend( AccountID const& uReceiverID, STAmount const& saAmount, beast::Journal j, - WaiveTransferFee waiveFee) + WaiveTransferFee waiveFee, + AllowMPTOverflow allowOverflow) { - return std::visit( - [&](TIss const& issue) { - if constexpr (std::is_same_v) - { - return accountSendIOU(view, uSenderID, uReceiverID, saAmount, j, waiveFee); - } - else - { - return accountSendMPT(view, uSenderID, uReceiverID, saAmount, j, waiveFee); - } + return saAmount.asset().visit( + [&](Issue const&) { + return accountSendIOU(view, uSenderID, uReceiverID, saAmount, j, waiveFee); }, - saAmount.asset().value()); + [&](MPTIssue const&) { + return accountSendMPT( + view, uSenderID, uReceiverID, saAmount, j, waiveFee, allowOverflow); + }); } TER @@ -1365,18 +1379,13 @@ accountSendMulti( { XRPL_ASSERT_PARTS( receivers.size() > 1, "xrpl::accountSendMulti", "multiple recipients provided"); - return std::visit( - [&](TIss const& issue) { - if constexpr (std::is_same_v) - { - return accountSendMultiIOU(view, senderID, issue, receivers, j, waiveFee); - } - else - { - return accountSendMultiMPT(view, senderID, issue, receivers, j, waiveFee); - } + return asset.visit( + [&](Issue const& issue) { + return accountSendMultiIOU(view, senderID, issue, receivers, j, waiveFee); }, - asset.value()); + [&](MPTIssue const& issue) { + return accountSendMultiMPT(view, senderID, issue, receivers, j, waiveFee); + }); } TER diff --git a/src/libxrpl/ledger/helpers/VaultHelpers.cpp b/src/libxrpl/ledger/helpers/VaultHelpers.cpp index 83a1b9fc4f..99500b36bf 100644 --- a/src/libxrpl/ledger/helpers/VaultHelpers.cpp +++ b/src/libxrpl/ledger/helpers/VaultHelpers.cpp @@ -1,8 +1,14 @@ #include -// + #include -#include -#include +#include +#include +#include +#include +#include // IWYU pragma: keep + +#include +#include namespace xrpl { diff --git a/src/libxrpl/net/HTTPClient.cpp b/src/libxrpl/net/HTTPClient.cpp index 5cb01cb54b..b39a605313 100644 --- a/src/libxrpl/net/HTTPClient.cpp +++ b/src/libxrpl/net/HTTPClient.cpp @@ -1,16 +1,35 @@ +#include + #include #include +#include #include -#include #include -#include +#include +#include +#include +#include +#include #include #include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include namespace xrpl { @@ -184,7 +203,7 @@ public: JLOG(j_.trace()) << "Deadline error: " << mDeqSites[0] << ": " << ecResult.message(); // Can't do anything sound. - abort(); + std::abort(); } else { diff --git a/src/libxrpl/net/RegisterSSLCerts.cpp b/src/libxrpl/net/RegisterSSLCerts.cpp index 1f21a76ddb..ff59b5971e 100644 --- a/src/libxrpl/net/RegisterSSLCerts.cpp +++ b/src/libxrpl/net/RegisterSSLCerts.cpp @@ -1,5 +1,10 @@ #include +#include + +#include +#include + #if BOOST_OS_WINDOWS #include #include diff --git a/src/libxrpl/nodestore/BatchWriter.cpp b/src/libxrpl/nodestore/BatchWriter.cpp index 75b9a8dcde..9da6f504a9 100644 --- a/src/libxrpl/nodestore/BatchWriter.cpp +++ b/src/libxrpl/nodestore/BatchWriter.cpp @@ -1,7 +1,17 @@ #include -namespace xrpl { -namespace NodeStore { +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace xrpl::NodeStore { BatchWriter::BatchWriter(Callback& callback, Scheduler& scheduler) : m_callback(callback), m_scheduler(scheduler) @@ -97,5 +107,4 @@ BatchWriter::waitForWriting() mWriteCondition.wait(sl); } -} // namespace NodeStore -} // namespace xrpl +} // namespace xrpl::NodeStore diff --git a/src/libxrpl/nodestore/Database.cpp b/src/libxrpl/nodestore/Database.cpp index 5d9d153f57..ee8d2f56aa 100644 --- a/src/libxrpl/nodestore/Database.cpp +++ b/src/libxrpl/nodestore/Database.cpp @@ -1,14 +1,35 @@ -#include -#include -#include #include -#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include -namespace xrpl { -namespace NodeStore { +namespace xrpl::NodeStore { Database::Database( Scheduler& scheduler, @@ -254,5 +275,4 @@ Database::getCountsJson(Json::Value& obj) obj[jss::node_reads_duration_us] = std::to_string(fetchDurationUs_); } -} // namespace NodeStore -} // namespace xrpl +} // namespace xrpl::NodeStore diff --git a/src/libxrpl/nodestore/DatabaseNodeImp.cpp b/src/libxrpl/nodestore/DatabaseNodeImp.cpp index a24379aea9..7722f711bb 100644 --- a/src/libxrpl/nodestore/DatabaseNodeImp.cpp +++ b/src/libxrpl/nodestore/DatabaseNodeImp.cpp @@ -1,7 +1,26 @@ #include -namespace xrpl { -namespace NodeStore { +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl::NodeStore { void DatabaseNodeImp::store(NodeObjectType type, Blob&& data, uint256 const& hash, std::uint32_t) @@ -91,5 +110,4 @@ DatabaseNodeImp::fetchBatch(std::vector const& hashes) return results; } -} // namespace NodeStore -} // namespace xrpl +} // namespace xrpl::NodeStore diff --git a/src/libxrpl/nodestore/DatabaseRotatingImp.cpp b/src/libxrpl/nodestore/DatabaseRotatingImp.cpp index aa04b17b33..ef7431f5a3 100644 --- a/src/libxrpl/nodestore/DatabaseRotatingImp.cpp +++ b/src/libxrpl/nodestore/DatabaseRotatingImp.cpp @@ -1,7 +1,27 @@ #include -namespace xrpl { -namespace NodeStore { +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl::NodeStore { DatabaseRotatingImp::DatabaseRotatingImp( Scheduler& scheduler, @@ -178,5 +198,4 @@ DatabaseRotatingImp::for_each(std::function)> f archive->for_each(f); } -} // namespace NodeStore -} // namespace xrpl +} // namespace xrpl::NodeStore diff --git a/src/libxrpl/nodestore/DecodedBlob.cpp b/src/libxrpl/nodestore/DecodedBlob.cpp index ad704cb84e..1d60f2af86 100644 --- a/src/libxrpl/nodestore/DecodedBlob.cpp +++ b/src/libxrpl/nodestore/DecodedBlob.cpp @@ -1,11 +1,16 @@ -#include -#include #include -#include +#include +#include +#include +#include +#include -namespace xrpl { -namespace NodeStore { +#include +#include +#include + +namespace xrpl::NodeStore { DecodedBlob::DecodedBlob(void const* key, void const* value, int valueBytes) { @@ -68,5 +73,4 @@ DecodedBlob::createObject() return object; } -} // namespace NodeStore -} // namespace xrpl +} // namespace xrpl::NodeStore diff --git a/src/libxrpl/nodestore/DummyScheduler.cpp b/src/libxrpl/nodestore/DummyScheduler.cpp index 21c1b5c92e..1f93ed3d0f 100644 --- a/src/libxrpl/nodestore/DummyScheduler.cpp +++ b/src/libxrpl/nodestore/DummyScheduler.cpp @@ -1,7 +1,9 @@ #include -namespace xrpl { -namespace NodeStore { +#include +#include + +namespace xrpl::NodeStore { void DummyScheduler::scheduleTask(Task& task) @@ -20,5 +22,4 @@ DummyScheduler::onBatchWrite(BatchWriteReport const& report) { } -} // namespace NodeStore -} // namespace xrpl +} // namespace xrpl::NodeStore diff --git a/src/libxrpl/nodestore/ManagerImp.cpp b/src/libxrpl/nodestore/ManagerImp.cpp index 7925ccdb91..40a48f0875 100644 --- a/src/libxrpl/nodestore/ManagerImp.cpp +++ b/src/libxrpl/nodestore/ManagerImp.cpp @@ -1,11 +1,27 @@ -#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + #include -namespace xrpl { +#include +#include +#include +#include +#include +#include +#include -namespace NodeStore { +namespace xrpl::NodeStore { ManagerImp& ManagerImp::instance() @@ -88,8 +104,8 @@ void ManagerImp::erase(Factory& factory) { std::lock_guard const _(mutex_); - auto const iter = std::find_if( - list_.begin(), list_.end(), [&factory](Factory* other) { return other == &factory; }); + auto const iter = + std::ranges::find_if(list_, [&factory](Factory* other) { return other == &factory; }); XRPL_ASSERT(iter != list_.end(), "xrpl::NodeStore::ManagerImp::erase : valid input"); list_.erase(iter); } @@ -98,9 +114,8 @@ Factory* ManagerImp::find(std::string const& name) { std::lock_guard const _(mutex_); - auto const iter = std::find_if(list_.begin(), list_.end(), [&name](Factory* other) { - return boost::iequals(name, other->getName()); - }); + auto const iter = std::ranges::find_if( + list_, [&name](Factory* other) { return boost::iequals(name, other->getName()); }); if (iter == list_.end()) return nullptr; return *iter; @@ -114,5 +129,4 @@ Manager::instance() return ManagerImp::instance(); } -} // namespace NodeStore -} // namespace xrpl +} // namespace xrpl::NodeStore diff --git a/src/libxrpl/nodestore/NodeObject.cpp b/src/libxrpl/nodestore/NodeObject.cpp index 2e40989e7c..b19d5d625a 100644 --- a/src/libxrpl/nodestore/NodeObject.cpp +++ b/src/libxrpl/nodestore/NodeObject.cpp @@ -1,6 +1,10 @@ #include +#include +#include + #include +#include namespace xrpl { diff --git a/src/libxrpl/nodestore/backend/MemoryFactory.cpp b/src/libxrpl/nodestore/backend/MemoryFactory.cpp index a245af3030..2f94783b8b 100644 --- a/src/libxrpl/nodestore/backend/MemoryFactory.cpp +++ b/src/libxrpl/nodestore/backend/MemoryFactory.cpp @@ -1,16 +1,30 @@ +#include +#include #include +#include +#include +#include #include #include +#include +#include +#include #include #include +#include +#include #include #include #include +#include +#include +#include +#include +#include -namespace xrpl { -namespace NodeStore { +namespace xrpl::NodeStore { struct MemoryDB { @@ -224,5 +238,4 @@ MemoryFactory::createInstance( return std::make_unique(keyBytes, keyValues, journal); } -} // namespace NodeStore -} // namespace xrpl +} // namespace xrpl::NodeStore diff --git a/src/libxrpl/nodestore/backend/NuDBFactory.cpp b/src/libxrpl/nodestore/backend/NuDBFactory.cpp index 424778f280..7fb3aec843 100644 --- a/src/libxrpl/nodestore/backend/NuDBFactory.cpp +++ b/src/libxrpl/nodestore/backend/NuDBFactory.cpp @@ -1,24 +1,50 @@ +#include +#include +#include #include #include +#include #include +#include #include #include +#include +#include +#include #include #include #include -#include +#include +#include +#include -#include +#include +#include // IWYU pragma: keep +#include +#include +#include +#include +#include +#include // IWYU pragma: keep +#include // IWYU pragma: keep +#include +#include #include #include #include #include +#include #include +#include +#include +#include +#include +#include +#include -namespace xrpl { -namespace NodeStore { +namespace xrpl::NodeStore { class NuDBBackend : public Backend { @@ -437,5 +463,4 @@ registerNuDBFactory(Manager& manager) static NuDBFactory const instance{manager}; } -} // namespace NodeStore -} // namespace xrpl +} // namespace xrpl::NodeStore diff --git a/src/libxrpl/nodestore/backend/NullFactory.cpp b/src/libxrpl/nodestore/backend/NullFactory.cpp index 617dfd893d..94cd71bd11 100644 --- a/src/libxrpl/nodestore/backend/NullFactory.cpp +++ b/src/libxrpl/nodestore/backend/NullFactory.cpp @@ -1,17 +1,28 @@ +#include +#include +#include +#include #include #include +#include +#include +#include +#include +#include #include +#include +#include +#include -namespace xrpl { -namespace NodeStore { +namespace xrpl::NodeStore { class NullBackend : public Backend { public: NullBackend() = default; - ~NullBackend() = default; + ~NullBackend() override = default; std::string getName() override @@ -120,5 +131,4 @@ registerNullFactory(Manager& manager) static NullFactory const instance{manager}; } -} // namespace NodeStore -} // namespace xrpl +} // namespace xrpl::NodeStore diff --git a/src/libxrpl/nodestore/backend/RocksDBFactory.cpp b/src/libxrpl/nodestore/backend/RocksDBFactory.cpp index 4ce7c3f10c..3e0957edea 100644 --- a/src/libxrpl/nodestore/backend/RocksDBFactory.cpp +++ b/src/libxrpl/nodestore/backend/RocksDBFactory.cpp @@ -1,4 +1,36 @@ -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include #if XRPL_ROCKSDB_AVAILABLE #include @@ -14,8 +46,7 @@ #include #include -namespace xrpl { -namespace NodeStore { +namespace xrpl::NodeStore { class RocksDBEnv : public rocksdb::EnvWrapper { @@ -458,7 +489,6 @@ registerRocksDBFactory(Manager& manager) static RocksDBFactory const instance{manager}; } -} // namespace NodeStore -} // namespace xrpl +} // namespace xrpl::NodeStore #endif diff --git a/src/libxrpl/protocol/AMMCore.cpp b/src/libxrpl/protocol/AMMCore.cpp index 9061d04689..41303e6a22 100644 --- a/src/libxrpl/protocol/AMMCore.cpp +++ b/src/libxrpl/protocol/AMMCore.cpp @@ -1,10 +1,14 @@ +#include + #include #include #include -#include #include +#include +#include #include #include +#include #include #include #include @@ -17,16 +21,28 @@ #include #include #include +#include namespace xrpl { Currency -ammLPTCurrency(Currency const& cur1, Currency const& cur2) +ammLPTCurrency(Asset const& asset1, Asset const& asset2) { // AMM LPToken is 0x03 plus 19 bytes of the hash std::int32_t constexpr AMMCurrencyCode = 0x03; - auto const [minC, maxC] = std::minmax(cur1, cur2); - auto const hash = sha512Half(minC, maxC); + auto const& [minA, maxA] = std::minmax(asset1, asset2); + uint256 const hash = std::visit( + [](auto&& issue1, auto&& issue2) { + auto fromIss = [](T const& issue) { + if constexpr (std::is_same_v) + return issue.currency; + if constexpr (std::is_same_v) + return issue.getMptID(); + }; + return sha512Half(fromIss(issue1), fromIss(issue2)); + }, + minA.value(), + maxA.value()); Currency currency; *currency.begin() = AMMCurrencyCode; std::copy(hash.begin(), hash.begin() + currency.size() - 1, currency.begin() + 1); @@ -34,34 +50,45 @@ ammLPTCurrency(Currency const& cur1, Currency const& cur2) } Issue -ammLPTIssue(Currency const& cur1, Currency const& cur2, AccountID const& ammAccountID) +ammLPTIssue(Asset const& asset1, Asset const& asset2, AccountID const& ammAccountID) { - return Issue(ammLPTCurrency(cur1, cur2), ammAccountID); + return Issue(ammLPTCurrency(asset1, asset2), ammAccountID); } NotTEC -invalidAMMAsset(Issue const& issue, std::optional> const& pair) +invalidAMMAsset(Asset const& asset, std::optional> const& pair) { - if (badCurrency() == issue.currency) - return temBAD_CURRENCY; - if (isXRP(issue) && issue.account.isNonZero()) - return temBAD_ISSUER; - if (pair && issue != pair->first && issue != pair->second) + auto const err = asset.visit( + [](MPTIssue const& issue) -> std::optional { + if (issue.getIssuer() == beast::zero) + return temBAD_MPT; + return std::nullopt; + }, + [](Issue const& issue) -> std::optional { + if (badCurrency() == issue.currency) + return temBAD_CURRENCY; + if (isXRP(issue) && issue.getIssuer().isNonZero()) + return temBAD_ISSUER; + return std::nullopt; + }); + if (err) + return *err; + if (pair && asset != pair->first && asset != pair->second) return temBAD_AMM_TOKENS; return tesSUCCESS; } NotTEC invalidAMMAssetPair( - Issue const& issue1, - Issue const& issue2, - std::optional> const& pair) + Asset const& asset1, + Asset const& asset2, + std::optional> const& pair) { - if (issue1 == issue2) + if (asset1 == asset2) return temBAD_AMM_TOKENS; - if (auto const res = invalidAMMAsset(issue1, pair)) + if (auto const res = invalidAMMAsset(asset1, pair)) return res; - if (auto const res = invalidAMMAsset(issue2, pair)) + if (auto const res = invalidAMMAsset(asset2, pair)) return res; return tesSUCCESS; } @@ -69,10 +96,10 @@ invalidAMMAssetPair( NotTEC invalidAMMAmount( STAmount const& amount, - std::optional> const& pair, + std::optional> const& pair, bool validZero) { - if (auto const res = invalidAMMAsset(amount.issue(), pair)) + if (auto const res = invalidAMMAsset(amount.asset(), pair)) return res; if (amount < beast::zero || (!validZero && amount == beast::zero)) return temBAD_AMOUNT; diff --git a/src/libxrpl/protocol/AccountID.cpp b/src/libxrpl/protocol/AccountID.cpp index 2c64457c87..a89dc8fd11 100644 --- a/src/libxrpl/protocol/AccountID.cpp +++ b/src/libxrpl/protocol/AccountID.cpp @@ -1,8 +1,9 @@ +#include + #include #include #include #include -#include #include #include #include diff --git a/src/libxrpl/protocol/Asset.cpp b/src/libxrpl/protocol/Asset.cpp index 0076da6570..3d163c5c6a 100644 --- a/src/libxrpl/protocol/Asset.cpp +++ b/src/libxrpl/protocol/Asset.cpp @@ -1,12 +1,16 @@ +#include + +#include #include #include #include -#include +#include #include #include #include #include +#include #include #include #include @@ -62,4 +66,11 @@ assetFromJson(Json::Value const& v) return mptIssueFromJson(v); } +std::ostream& +operator<<(std::ostream& os, Asset const& x) +{ + std::visit([&](TIss const& issue) { os << issue; }, x.value()); + return os; +} + } // namespace xrpl diff --git a/src/libxrpl/protocol/Book.cpp b/src/libxrpl/protocol/Book.cpp index 59ad739c11..f71800b786 100644 --- a/src/libxrpl/protocol/Book.cpp +++ b/src/libxrpl/protocol/Book.cpp @@ -1,5 +1,6 @@ #include -#include + +#include #include #include diff --git a/src/libxrpl/protocol/BuildInfo.cpp b/src/libxrpl/protocol/BuildInfo.cpp index 0fddb9ff19..c732e95a87 100644 --- a/src/libxrpl/protocol/BuildInfo.cpp +++ b/src/libxrpl/protocol/BuildInfo.cpp @@ -1,20 +1,19 @@ +#include + #include #include #include -#include -#include +#include // IWYU pragma: keep #include -#include +#include // IWYU pragma: keep #include #include #include #include -namespace xrpl { - -namespace BuildInfo { +namespace xrpl::BuildInfo { namespace { @@ -122,7 +121,7 @@ encodeSoftwareVersion(std::string_view versionStr) std::uint8_t hik) -> std::uint8_t { std::uint8_t ret = 0; - if (prefix != identifier.substr(0, prefix.length())) + if (!identifier.starts_with(prefix)) return 0; if (!beast::lexicalCastChecked( @@ -160,7 +159,7 @@ getEncodedVersion() } bool -isRippledVersion(std::uint64_t version) +isXrpldVersion(std::uint64_t version) { return (version & implementationVersionIdentifierMask) == implementationVersionIdentifier; } @@ -168,11 +167,9 @@ isRippledVersion(std::uint64_t version) bool isNewerVersion(std::uint64_t version) { - if (isRippledVersion(version)) + if (isXrpldVersion(version)) return version > getEncodedVersion(); return false; } -} // namespace BuildInfo - -} // namespace xrpl +} // namespace xrpl::BuildInfo diff --git a/src/libxrpl/protocol/ErrorCodes.cpp b/src/libxrpl/protocol/ErrorCodes.cpp index 407e1ab3f3..4e928deb75 100644 --- a/src/libxrpl/protocol/ErrorCodes.cpp +++ b/src/libxrpl/protocol/ErrorCodes.cpp @@ -1,6 +1,7 @@ +#include + #include #include -#include #include #include diff --git a/src/libxrpl/protocol/Feature.cpp b/src/libxrpl/protocol/Feature.cpp index 1762d7d22d..1686d0e52a 100644 --- a/src/libxrpl/protocol/Feature.cpp +++ b/src/libxrpl/protocol/Feature.cpp @@ -1,8 +1,9 @@ +#include + #include #include #include #include -#include #include #include @@ -70,8 +71,8 @@ class FeatureCollections uint256 feature; Feature() = delete; - explicit Feature(std::string const& name_, uint256 const& feature_) - : name(name_), feature(feature_) + explicit Feature(std::string name_, uint256 const& feature_) + : name(std::move(name_)), feature(feature_) { } @@ -428,6 +429,8 @@ enforceValidFeatureName(auto fn) -> char const* #include +#include + #undef XRPL_RETIRE_FEATURE #pragma pop_macro("XRPL_RETIRE_FEATURE") #undef XRPL_RETIRE_FIX diff --git a/src/libxrpl/protocol/IOUAmount.cpp b/src/libxrpl/protocol/IOUAmount.cpp index 338f2c2760..69b763dbab 100644 --- a/src/libxrpl/protocol/IOUAmount.cpp +++ b/src/libxrpl/protocol/IOUAmount.cpp @@ -1,20 +1,18 @@ #include -// Do not remove. Forces IOUAmount.h to stay first, to verify it can compile -// without any hidden dependencies + #include #include #include #include #include -#include - #include #include #include #include #include #include +#include #include namespace xrpl { @@ -209,7 +207,7 @@ mulRatio(IOUAmount const& amt, std::uint32_t num, std::uint32_t den, bool roundU static auto log10Floor = [](uint128_t const& v) { // Find the index of the first element >= the requested element, the // index is the log of the element in the log table. - auto const l = std::lower_bound(powerTable.begin(), powerTable.end(), v); + auto const l = std::ranges::lower_bound(powerTable, v); int index = std::distance(powerTable.begin(), l); // If we're not equal, subtract to get the floor if (*l != v) @@ -221,7 +219,7 @@ mulRatio(IOUAmount const& amt, std::uint32_t num, std::uint32_t den, bool roundU static auto log10Ceil = [](uint128_t const& v) { // Find the index of the first element >= the requested element, the // index is the log of the element in the log table. - auto const l = std::lower_bound(powerTable.begin(), powerTable.end(), v); + auto const l = std::ranges::lower_bound(powerTable, v); return int(std::distance(powerTable.begin(), l)); }; diff --git a/src/libxrpl/protocol/Indexes.cpp b/src/libxrpl/protocol/Indexes.cpp index 61a64b2a54..4bbf2d6f1f 100644 --- a/src/libxrpl/protocol/Indexes.cpp +++ b/src/libxrpl/protocol/Indexes.cpp @@ -1,3 +1,5 @@ +#include + #include #include #include @@ -5,8 +7,10 @@ #include #include #include -#include +#include +#include #include +#include #include #include #include @@ -23,6 +27,7 @@ #include #include #include +#include #include namespace xrpl { @@ -99,19 +104,36 @@ getBookBase(Book const& book) { XRPL_ASSERT(isConsistent(book), "xrpl::getBookBase : input is consistent"); - auto const index = book.domain ? indexHash( - LedgerNameSpace::BOOK_DIR, - book.in.currency, - book.out.currency, - book.in.account, - book.out.account, - *(book.domain)) - : indexHash( - LedgerNameSpace::BOOK_DIR, - book.in.currency, - book.out.currency, - book.in.account, - book.out.account); + auto getIndexHash = [&book](Args... args) { + if (book.domain) + return indexHash(std::forward(args)..., *book.domain); + return indexHash(std::forward(args)...); + }; + + auto const index = std::visit( + [&](TIn const& in, TOut const& out) { + if constexpr (std::is_same_v && std::is_same_v) + { + return getIndexHash( + LedgerNameSpace::BOOK_DIR, in.currency, out.currency, in.account, out.account); + } + else if constexpr (std::is_same_v && std::is_same_v) + { + return getIndexHash( + LedgerNameSpace::BOOK_DIR, in.currency, out.getMptID(), in.account); + } + else if constexpr (std::is_same_v && std::is_same_v) + { + return getIndexHash( + LedgerNameSpace::BOOK_DIR, in.getMptID(), out.currency, out.account); + } + else + { + return getIndexHash(LedgerNameSpace::BOOK_DIR, in.getMptID(), out.getMptID()); + } + }, + book.in.value(), + book.out.value()); // Return with quality 0. auto k = keylet::quality({ltDIR_NODE, index}, 0); @@ -401,11 +423,37 @@ nft_sells(uint256 const& id) noexcept } Keylet -amm(Asset const& issue1, Asset const& issue2) noexcept +amm(Asset const& asset1, Asset const& asset2) noexcept { - auto const& [minI, maxI] = std::minmax(issue1.get(), issue2.get()); - return amm( - indexHash(LedgerNameSpace::AMM, minI.account, minI.currency, maxI.account, maxI.currency)); + auto const& [minA, maxA] = std::minmax(asset1, asset2); + return std::visit( + [](TIss1 const& issue1, TIss2 const& issue2) { + if constexpr (std::is_same_v && std::is_same_v) + { + return amm(indexHash( + LedgerNameSpace::AMM, + issue1.account, + issue1.currency, + issue2.account, + issue2.currency)); + } + else if constexpr (std::is_same_v && std::is_same_v) + { + return amm(indexHash( + LedgerNameSpace::AMM, issue1.account, issue1.currency, issue2.getMptID())); + } + else if constexpr (std::is_same_v && std::is_same_v) + { + return amm(indexHash( + LedgerNameSpace::AMM, issue1.getMptID(), issue2.account, issue2.currency)); + } + else if constexpr (std::is_same_v && std::is_same_v) + { + return amm(indexHash(LedgerNameSpace::AMM, issue1.getMptID(), issue2.getMptID())); + } + }, + minA.value(), + maxA.value()); } Keylet diff --git a/src/libxrpl/protocol/InnerObjectFormats.cpp b/src/libxrpl/protocol/InnerObjectFormats.cpp index 8429c51aea..f4a88ec171 100644 --- a/src/libxrpl/protocol/InnerObjectFormats.cpp +++ b/src/libxrpl/protocol/InnerObjectFormats.cpp @@ -1,4 +1,5 @@ #include + #include #include diff --git a/src/libxrpl/protocol/Issue.cpp b/src/libxrpl/protocol/Issue.cpp index c73c6c8513..d5896bbd57 100644 --- a/src/libxrpl/protocol/Issue.cpp +++ b/src/libxrpl/protocol/Issue.cpp @@ -1,8 +1,9 @@ +#include + #include #include #include #include -#include #include #include diff --git a/src/libxrpl/protocol/Keylet.cpp b/src/libxrpl/protocol/Keylet.cpp index 6f9656c4ea..26bc98c1d6 100644 --- a/src/libxrpl/protocol/Keylet.cpp +++ b/src/libxrpl/protocol/Keylet.cpp @@ -1,5 +1,6 @@ -#include #include + +#include #include #include diff --git a/src/libxrpl/protocol/LedgerFormats.cpp b/src/libxrpl/protocol/LedgerFormats.cpp index 9f8bd6a2ba..7826435e51 100644 --- a/src/libxrpl/protocol/LedgerFormats.cpp +++ b/src/libxrpl/protocol/LedgerFormats.cpp @@ -1,7 +1,8 @@ #include + #include #include -#include +#include // IWYU pragma: keep #include diff --git a/src/libxrpl/protocol/LedgerHeader.cpp b/src/libxrpl/protocol/LedgerHeader.cpp index 38e7a7f6dd..f6a99be28a 100644 --- a/src/libxrpl/protocol/LedgerHeader.cpp +++ b/src/libxrpl/protocol/LedgerHeader.cpp @@ -1,10 +1,13 @@ +#include + #include #include #include -#include #include #include +#include + namespace xrpl { void diff --git a/src/libxrpl/protocol/MPTIssue.cpp b/src/libxrpl/protocol/MPTIssue.cpp index c2f7f1fa50..dac2f4e5fe 100644 --- a/src/libxrpl/protocol/MPTIssue.cpp +++ b/src/libxrpl/protocol/MPTIssue.cpp @@ -1,13 +1,16 @@ +#include + #include #include #include #include #include -#include +#include #include #include #include +#include #include #include @@ -17,6 +20,11 @@ MPTIssue::MPTIssue(MPTID const& issuanceID) : mptID_(issuanceID) { } +MPTIssue::MPTIssue(std::uint32_t sequence, AccountID const& account) + : MPTIssue(xrpl::makeMptID(sequence, account)) +{ +} + AccountID const& MPTIssue::getIssuer() const { @@ -86,4 +94,11 @@ mptIssueFromJson(Json::Value const& v) return MPTIssue{id}; } +std::ostream& +operator<<(std::ostream& os, MPTIssue const& x) +{ + os << to_string(x); + return os; +} + } // namespace xrpl diff --git a/src/libxrpl/protocol/NFTSyntheticSerializer.cpp b/src/libxrpl/protocol/NFTSyntheticSerializer.cpp index e4ce3b7611..9982f05b0f 100644 --- a/src/libxrpl/protocol/NFTSyntheticSerializer.cpp +++ b/src/libxrpl/protocol/NFTSyntheticSerializer.cpp @@ -1,5 +1,6 @@ -#include #include + +#include #include #include #include @@ -8,8 +9,7 @@ #include -namespace xrpl { -namespace RPC { +namespace xrpl::RPC { void insertNFTSyntheticInJson( @@ -21,5 +21,4 @@ insertNFTSyntheticInJson( insertNFTokenOfferID(response[jss::meta], transaction, transactionMeta); } -} // namespace RPC -} // namespace xrpl +} // namespace xrpl::RPC diff --git a/src/libxrpl/protocol/NFTokenID.cpp b/src/libxrpl/protocol/NFTokenID.cpp index a808ef1fcf..fc32a7b4e8 100644 --- a/src/libxrpl/protocol/NFTokenID.cpp +++ b/src/libxrpl/protocol/NFTokenID.cpp @@ -1,7 +1,8 @@ +#include + #include #include #include -#include #include #include #include @@ -55,11 +56,10 @@ getNFTokenIDFromPage(TxMeta const& transactionMeta) { STArray const& toAddPrevNFTs = node.peekAtField(sfNewFields).downcast().getFieldArray(sfNFTokens); - std::transform( - toAddPrevNFTs.begin(), - toAddPrevNFTs.end(), - std::back_inserter(finalIDs), - [](STObject const& nft) { return nft.getFieldH256(sfNFTokenID); }); + std::ranges::transform( + toAddPrevNFTs, std::back_inserter(finalIDs), [](STObject const& nft) { + return nft.getFieldH256(sfNFTokenID); + }); } else if (fName == sfModifiedNode) { @@ -70,7 +70,7 @@ getNFTokenIDFromPage(TxMeta const& transactionMeta) // field changing, but no NFTs within that page changing. In this // case, there will be no previous NFTs and we need to skip. // However, there will always be NFTs listed in the final fields, - // as rippled outputs all fields in final fields even if they were + // as xrpld outputs all fields in final fields even if they were // not changed. STObject const& previousFields = node.peekAtField(sfPreviousFields).downcast(); @@ -78,19 +78,17 @@ getNFTokenIDFromPage(TxMeta const& transactionMeta) continue; STArray const& toAddPrevNFTs = previousFields.getFieldArray(sfNFTokens); - std::transform( - toAddPrevNFTs.begin(), - toAddPrevNFTs.end(), - std::back_inserter(prevIDs), - [](STObject const& nft) { return nft.getFieldH256(sfNFTokenID); }); + std::ranges::transform( + toAddPrevNFTs, std::back_inserter(prevIDs), [](STObject const& nft) { + return nft.getFieldH256(sfNFTokenID); + }); STArray const& toAddFinalNFTs = node.peekAtField(sfFinalFields).downcast().getFieldArray(sfNFTokens); - std::transform( - toAddFinalNFTs.begin(), - toAddFinalNFTs.end(), - std::back_inserter(finalIDs), - [](STObject const& nft) { return nft.getFieldH256(sfNFTokenID); }); + std::ranges::transform( + toAddFinalNFTs, std::back_inserter(finalIDs), [](STObject const& nft) { + return nft.getFieldH256(sfNFTokenID); + }); } } @@ -101,15 +99,14 @@ getNFTokenIDFromPage(TxMeta const& transactionMeta) // Find the first NFT ID that doesn't match. We're looking for an // added NFT, so the one we want will be the mismatch in finalIDs. - auto const diff = - std::mismatch(finalIDs.begin(), finalIDs.end(), prevIDs.begin(), prevIDs.end()); + auto const diff = std::ranges::mismatch(finalIDs, prevIDs); // There should always be a difference so the returned finalIDs // iterator should never be end(). But better safe than sorry. - if (diff.first == finalIDs.end()) + if (diff.in1 == finalIDs.end()) return std::nullopt; - return *diff.first; + return *diff.in1; } std::vector @@ -129,8 +126,9 @@ getNFTokenIDFromDeletedOffer(TxMeta const& transactionMeta) // Deduplicate the NFT IDs because multiple offers could affect the same NFT // and hence we would get duplicate NFT IDs - sort(tokenIDResult.begin(), tokenIDResult.end()); - tokenIDResult.erase(unique(tokenIDResult.begin(), tokenIDResult.end()), tokenIDResult.end()); + std::ranges::sort(tokenIDResult); + auto const uniq = std::ranges::unique(tokenIDResult); + tokenIDResult.erase(uniq.begin(), uniq.end()); return tokenIDResult; } diff --git a/src/libxrpl/protocol/NFTokenOfferID.cpp b/src/libxrpl/protocol/NFTokenOfferID.cpp index 4af589a4e9..c1d426eef0 100644 --- a/src/libxrpl/protocol/NFTokenOfferID.cpp +++ b/src/libxrpl/protocol/NFTokenOfferID.cpp @@ -1,7 +1,8 @@ +#include + #include #include #include -#include #include #include #include diff --git a/src/libxrpl/protocol/PathAsset.cpp b/src/libxrpl/protocol/PathAsset.cpp new file mode 100644 index 0000000000..97011129e5 --- /dev/null +++ b/src/libxrpl/protocol/PathAsset.cpp @@ -0,0 +1,22 @@ +#include + +#include +#include +#include + +namespace xrpl { + +std::string +to_string(PathAsset const& asset) +{ + return std::visit([&](auto const& issue) { return to_string(issue); }, asset.value()); +} + +std::ostream& +operator<<(std::ostream& os, PathAsset const& x) +{ + os << to_string(x); + return os; +} + +} // namespace xrpl diff --git a/src/libxrpl/protocol/Permissions.cpp b/src/libxrpl/protocol/Permissions.cpp index 47fc0d28b6..21efe7a8cf 100644 --- a/src/libxrpl/protocol/Permissions.cpp +++ b/src/libxrpl/protocol/Permissions.cpp @@ -1,7 +1,15 @@ -#include -#include #include -#include + +#include +#include +#include // IWYU pragma: keep +#include +#include + +#include +#include +#include +#include namespace xrpl { @@ -67,6 +75,11 @@ Permission::Permission() #pragma pop_macro("PERMISSION") }; + XRPL_ASSERT( + txFeatureMap_.size() == delegableTx_.size(), + "xrpl::Permission : txFeatureMap_ and delegableTx_ must have same " + "size"); + for ([[maybe_unused]] auto const& permission : granularPermissionMap_) { XRPL_ASSERT( diff --git a/src/libxrpl/protocol/PublicKey.cpp b/src/libxrpl/protocol/PublicKey.cpp index fc63edd2fc..54430ed5d5 100644 --- a/src/libxrpl/protocol/PublicKey.cpp +++ b/src/libxrpl/protocol/PublicKey.cpp @@ -1,18 +1,19 @@ +#include + #include #include #include #include #include -#include #include #include #include #include -#include #include #include +#include #include #include diff --git a/src/libxrpl/protocol/Quality.cpp b/src/libxrpl/protocol/Quality.cpp index ba9807318a..5f38f2219b 100644 --- a/src/libxrpl/protocol/Quality.cpp +++ b/src/libxrpl/protocol/Quality.cpp @@ -1,11 +1,10 @@ -#include +#include + #include #include -#include #include #include -#include namespace xrpl { diff --git a/src/libxrpl/protocol/QualityFunction.cpp b/src/libxrpl/protocol/QualityFunction.cpp index 1774603a61..b460eb212f 100644 --- a/src/libxrpl/protocol/QualityFunction.cpp +++ b/src/libxrpl/protocol/QualityFunction.cpp @@ -1,8 +1,9 @@ +#include + #include #include #include #include -#include #include #include diff --git a/src/libxrpl/protocol/RPCErr.cpp b/src/libxrpl/protocol/RPCErr.cpp index e25b5e574b..99836f5e0f 100644 --- a/src/libxrpl/protocol/RPCErr.cpp +++ b/src/libxrpl/protocol/RPCErr.cpp @@ -1,6 +1,7 @@ +#include + #include #include -#include #include namespace xrpl { diff --git a/src/libxrpl/protocol/Rules.cpp b/src/libxrpl/protocol/Rules.cpp index fd54a1eff2..a09afa96d8 100644 --- a/src/libxrpl/protocol/Rules.cpp +++ b/src/libxrpl/protocol/Rules.cpp @@ -1,6 +1,5 @@ #include -// Do not remove. Forces Rules.h to stay first, to verify it can compile -// without any hidden dependencies + #include #include #include diff --git a/src/libxrpl/protocol/SField.cpp b/src/libxrpl/protocol/SField.cpp index 8015b34e73..fd6d39bbb0 100644 --- a/src/libxrpl/protocol/SField.cpp +++ b/src/libxrpl/protocol/SField.cpp @@ -1,8 +1,9 @@ -#include #include -#include +#include + #include +#include namespace xrpl { diff --git a/src/libxrpl/protocol/SOTemplate.cpp b/src/libxrpl/protocol/SOTemplate.cpp index b90bff6192..708fd465e2 100644 --- a/src/libxrpl/protocol/SOTemplate.cpp +++ b/src/libxrpl/protocol/SOTemplate.cpp @@ -1,12 +1,14 @@ +#include + #include #include -#include #include #include #include #include #include +#include #include namespace xrpl { diff --git a/src/libxrpl/protocol/STAccount.cpp b/src/libxrpl/protocol/STAccount.cpp index 2dc83cb591..4266b55c98 100644 --- a/src/libxrpl/protocol/STAccount.cpp +++ b/src/libxrpl/protocol/STAccount.cpp @@ -1,3 +1,5 @@ +#include + #include #include #include @@ -5,7 +7,6 @@ #include #include #include -#include #include #include diff --git a/src/libxrpl/protocol/STAmount.cpp b/src/libxrpl/protocol/STAmount.cpp index cc935cff61..89992b823c 100644 --- a/src/libxrpl/protocol/STAmount.cpp +++ b/src/libxrpl/protocol/STAmount.cpp @@ -1,16 +1,16 @@ -#include +#include + #include #include -#include #include #include -#include #include #include #include #include #include #include +#include #include #include #include @@ -19,7 +19,6 @@ #include #include #include -#include #include #include #include @@ -31,11 +30,6 @@ #include #include #include -#include -#include -#include -#include -#include #include #include @@ -44,9 +38,11 @@ #include #include #include +#include #include #include #include +#include #include namespace xrpl { @@ -90,11 +86,23 @@ getMPTValue(STAmount const& amount) static bool areComparable(STAmount const& v1, STAmount const& v2) { - if (v1.holds() && v2.holds()) - return v1.native() == v2.native() && v1.get().currency == v2.get().currency; - if (v1.holds() && v2.holds()) - return v1.get() == v2.get(); - return false; + return std::visit( + [&](TIss1 const& issue1, TIss2 const& issue2) { + if constexpr (is_issue_v && is_issue_v) + { + return v1.native() == v2.native() && issue1.currency == issue2.currency; + } + else if constexpr (is_mptissue_v && is_mptissue_v) + { + return issue1 == issue2; + } + else + { + return false; + } + }, + v1.asset().value(), + v2.asset().value()); } static_assert(INITIAL_XRP.drops() == STAmount::cMaxNativeN); @@ -513,26 +521,35 @@ canAdd(STAmount const& a, STAmount const& b) } // IOU case (precision check) - if (a.holds() && b.holds()) - { - static STAmount const one{IOUAmount{1, 0}, noIssue()}; - static STAmount const maxLoss{IOUAmount{1, -4}, noIssue()}; - STAmount const lhs = divide((a - b) + b, a, noIssue()) - one; - STAmount const rhs = divide((b - a) + a, b, noIssue()) - one; - return ((rhs.negative() ? -rhs : rhs) + (lhs.negative() ? -lhs : lhs)) <= maxLoss; - } + auto const ret = std::visit( + [&]( + TIss1 const&, TIss2 const&) -> std::optional { + if constexpr (is_issue_v && is_issue_v) + { + static STAmount const one{IOUAmount{1, 0}, noIssue()}; + static STAmount const maxLoss{IOUAmount{1, -4}, noIssue()}; + STAmount const lhs = divide((a - b) + b, a, noIssue()) - one; + STAmount const rhs = divide((b - a) + a, b, noIssue()) - one; + return ((rhs.negative() ? -rhs : rhs) + (lhs.negative() ? -lhs : lhs)) <= maxLoss; + } - // MPT (overflow & underflow check) - if (a.holds() && b.holds()) - { - MPTAmount const A = a.mpt(); - MPTAmount const B = b.mpt(); - return !( - (B > MPTAmount{0} && - A > MPTAmount{std::numeric_limits::max()} - B) || - (B < MPTAmount{0} && - A < MPTAmount{std::numeric_limits::min()} - B)); - } + // MPT (overflow & underflow check) + if constexpr (is_mptissue_v && is_mptissue_v) + { + MPTAmount const A = a.mpt(); + MPTAmount const B = b.mpt(); + return !( + (B > MPTAmount{0} && + A > MPTAmount{std::numeric_limits::max()} - B) || + (B < MPTAmount{0} && + A < MPTAmount{std::numeric_limits::min()} - B)); + } + return std::nullopt; + }, + a.asset().value(), + b.asset().value()); + if (ret) + return *ret; // LCOV_EXCL_START UNREACHABLE("STAmount::canAdd : unexpected STAmount type"); return false; @@ -585,27 +602,36 @@ canSubtract(STAmount const& a, STAmount const& b) } // IOU case (no underflow) - if (a.holds() && b.holds()) - { - return true; - } + auto const ret = std::visit( + [&]( + TIss1 const&, TIss2 const&) -> std::optional { + if constexpr (is_issue_v && is_issue_v) + { + return true; + } - // MPT case (underflow & overflow check) - if (a.holds() && b.holds()) - { - MPTAmount const A = a.mpt(); - MPTAmount const B = b.mpt(); + // MPT case (underflow & overflow check) + if constexpr (is_mptissue_v && is_mptissue_v) + { + MPTAmount const A = a.mpt(); + MPTAmount const B = b.mpt(); - // Underflow check - if (B > MPTAmount{0} && A < B) - return false; + // Underflow check + if (B > MPTAmount{0} && A < B) + return false; - // Overflow check - if (B < MPTAmount{0} && - A > MPTAmount{std::numeric_limits::max()} + B) - return false; - return true; - } + // Overflow check + if (B < MPTAmount{0} && + A > MPTAmount{std::numeric_limits::max()} + B) + return false; + return true; + } + return std::nullopt; + }, + a.asset().value(), + b.asset().value()); + if (ret) + return *ret; // LCOV_EXCL_START UNREACHABLE("STAmount::canSubtract : unexpected STAmount type"); return false; @@ -751,45 +777,49 @@ STAmount::getJson(JsonOptions) const void STAmount::add(Serializer& s) const { - if (native()) - { - XRPL_ASSERT(mOffset == 0, "xrpl::STAmount::add : zero offset"); - - if (!mIsNegative) - { - s.add64(mValue | cPositive); - } - else - { + mAsset.visit( + [&](MPTIssue const& issue) { + auto u8 = static_cast(cMPToken >> 56); + if (!mIsNegative) + u8 |= static_cast(cPositive >> 56); + s.add8(u8); s.add64(mValue); - } - } - else if (mAsset.holds()) - { - auto u8 = static_cast(cMPToken >> 56); - if (!mIsNegative) - u8 |= static_cast(cPositive >> 56); - s.add8(u8); - s.add64(mValue); - s.addBitString(mAsset.get().getMptID()); - } - else - { - if (*this == beast::zero) - { - s.add64(cIssuedCurrency); - } - else if (mIsNegative) - { // 512 = not native - s.add64(mValue | (static_cast(mOffset + 512 + 97) << (64 - 10))); - } - else - { // 256 = positive - s.add64(mValue | (static_cast(mOffset + 512 + 256 + 97) << (64 - 10))); - } - s.addBitString(mAsset.get().currency); - s.addBitString(mAsset.get().account); - } + s.addBitString(issue.getMptID()); + }, + [&](Issue const& issue) { + if (native()) + { + XRPL_ASSERT(mOffset == 0, "xrpl::STAmount::add : zero offset"); + + if (!mIsNegative) + { + s.add64(mValue | cPositive); + } + else + { + s.add64(mValue); + } + } + else + { + if (*this == beast::zero) + { + s.add64(cIssuedCurrency); + } + else if (mIsNegative) // 512 = not native + { + s.add64(mValue | (static_cast(mOffset + 512 + 97) << (64 - 10))); + } + else // 256 = positive + { + s.add64( + mValue | + (static_cast(mOffset + 512 + 256 + 97) << (64 - 10))); + } + s.addBitString(issue.currency); + s.addBitString(issue.account); + } + }); } bool @@ -1065,7 +1095,7 @@ amountFromJson(SField const& name, Json::Value const& v) if (isMPT) { // sequence (32 bits) + account (160 bits) - uint192 u; + MPTID u; if (!u.parseHex(currencyOrMPTID.asString())) Throw("invalid MPTokenIssuanceID"); asset = u; @@ -1255,7 +1285,7 @@ divide(STAmount const& num, STAmount const& den, Asset const& asset) int numOffset = num.exponent(); int denOffset = den.exponent(); - if (num.native() || num.holds()) + if (num.integral()) { while (numVal < STAmount::cMinValue) { @@ -1265,7 +1295,7 @@ divide(STAmount const& num, STAmount const& den, Asset const& asset) } } - if (den.native() || den.holds()) + if (den.integral()) { while (denVal < STAmount::cMinValue) { @@ -1330,7 +1360,7 @@ multiply(STAmount const& v1, STAmount const& v2, Asset const& asset) int offset1 = v1.exponent(); int offset2 = v2.exponent(); - if (v1.native() || v1.holds()) + if (v1.integral()) { while (value1 < STAmount::cMinValue) { @@ -1339,7 +1369,7 @@ multiply(STAmount const& v1, STAmount const& v2, Asset const& asset) } } - if (v2.native() || v2.holds()) + if (v2.integral()) { while (value2 < STAmount::cMinValue) { @@ -1363,7 +1393,7 @@ multiply(STAmount const& v1, STAmount const& v2, Asset const& asset) // for years, so it is deeply embedded in the behavior of cross-currency // transactions. // -// However in 2022 it was noticed that the rounding characteristics were +// However, in 2022 it was noticed that the rounding characteristics were // surprising. When the code converts from IOU-like to XRP-like there may // be a fraction of the IOU-like representation that is too small to be // represented in drops. `canonicalizeRound()` currently does some unusual @@ -1380,9 +1410,9 @@ multiply(STAmount const& v1, STAmount const& v2, Asset const& asset) // So an alternative rounding approach was introduced. You'll see that // alternative below. static void -canonicalizeRound(bool native, std::uint64_t& value, int& offset, bool) +canonicalizeRound(bool integral, std::uint64_t& value, int& offset, bool) { - if (native) + if (integral) { if (offset < 0) { @@ -1419,9 +1449,9 @@ canonicalizeRound(bool native, std::uint64_t& value, int& offset, bool) // rounding decisions. canonicalizeRoundStrict() tracks all of the bits in // the value being rounded. static void -canonicalizeRoundStrict(bool native, std::uint64_t& value, int& offset, bool roundUp) +canonicalizeRoundStrict(bool integral, std::uint64_t& value, int& offset, bool roundUp) { - if (native) + if (integral) { if (offset < 0) { @@ -1512,9 +1542,7 @@ mulRoundImpl(STAmount const& v1, STAmount const& v2, Asset const& asset, bool ro if (v1 == beast::zero || v2 == beast::zero) return {asset}; - bool const xrp = asset.native(); - - if (v1.native() && v2.native() && xrp) + if (v1.native() && v2.native() && asset.native()) { std::uint64_t const minV = std::min(getSNValue(v1), getSNValue(v2)); std::uint64_t const maxV = std::max(getSNValue(v1), getSNValue(v2)); @@ -1545,7 +1573,7 @@ mulRoundImpl(STAmount const& v1, STAmount const& v2, Asset const& asset, bool ro std::uint64_t value1 = v1.mantissa(), value2 = v2.mantissa(); int offset1 = v1.exponent(), offset2 = v2.exponent(); - if (v1.native() || v1.holds()) + if (v1.integral()) { while (value1 < STAmount::cMinValue) { @@ -1554,7 +1582,7 @@ mulRoundImpl(STAmount const& v1, STAmount const& v2, Asset const& asset, bool ro } } - if (v2.native() || v2.holds()) + if (v2.integral()) { while (value2 < STAmount::cMinValue) { @@ -1570,7 +1598,7 @@ mulRoundImpl(STAmount const& v1, STAmount const& v2, Asset const& asset, bool ro // range. Dividing their product by 10^14 maintains the // precision, by scaling the result to 10^16 to 10^18. // - // If the we're rounding up, we want to round up away + // If we're rounding up, we want to round up away // from zero, and if we're rounding down, truncation // is implicit. std::uint64_t amount = @@ -1579,7 +1607,7 @@ mulRoundImpl(STAmount const& v1, STAmount const& v2, Asset const& asset, bool ro int offset = offset1 + offset2 + 14; if (resultNegative != roundUp) { - CanonicalizeFunc(xrp, amount, offset, roundUp); + CanonicalizeFunc(asset.integral(), amount, offset, roundUp); } STAmount result = [&]() { // If appropriate, tell Number to round down. This gives the desired @@ -1590,7 +1618,7 @@ mulRoundImpl(STAmount const& v1, STAmount const& v2, Asset const& asset, bool ro if (roundUp && !resultNegative && !result) { - if (xrp) + if (asset.integral()) { // return the smallest value above zero amount = 1; @@ -1634,7 +1662,7 @@ divRoundImpl(STAmount const& num, STAmount const& den, Asset const& asset, bool std::uint64_t numVal = num.mantissa(), denVal = den.mantissa(); int numOffset = num.exponent(), denOffset = den.exponent(); - if (num.native() || num.holds()) + if (num.integral()) { while (numVal < STAmount::cMinValue) { @@ -1643,7 +1671,7 @@ divRoundImpl(STAmount const& num, STAmount const& den, Asset const& asset, bool } } - if (den.native() || den.holds()) + if (den.integral()) { while (denVal < STAmount::cMinValue) { @@ -1668,12 +1696,12 @@ divRoundImpl(STAmount const& num, STAmount const& den, Asset const& asset, bool int offset = numOffset - denOffset - 17; if (resultNegative != roundUp) - canonicalizeRound(asset.native() || asset.holds(), amount, offset, roundUp); + canonicalizeRound(asset.integral(), amount, offset, roundUp); STAmount result = [&]() { // If appropriate, tell Number the rounding mode we are using. // Note that "roundUp == true" actually means "round away from zero". - // Otherwise round toward zero. + // Otherwise, round toward zero. using enum Number::rounding_mode; MightSaveRound const savedRound(roundUp ^ resultNegative ? upward : downward); return STAmount(asset, amount, offset, resultNegative); @@ -1681,7 +1709,7 @@ divRoundImpl(STAmount const& num, STAmount const& den, Asset const& asset, bool if (roundUp && !resultNegative && !result) { - if (asset.native() || asset.holds()) + if (asset.integral()) { // return the smallest value above zero amount = 1; diff --git a/src/libxrpl/protocol/STArray.cpp b/src/libxrpl/protocol/STArray.cpp index 91acc9ddd3..8e841c1fbb 100644 --- a/src/libxrpl/protocol/STArray.cpp +++ b/src/libxrpl/protocol/STArray.cpp @@ -1,8 +1,9 @@ +#include + #include #include #include #include -#include #include #include @@ -174,7 +175,7 @@ STArray::isDefault() const void STArray::sort(bool (*compare)(STObject const&, STObject const&)) { - std::sort(v_.begin(), v_.end(), compare); + std::ranges::sort(v_, compare); } } // namespace xrpl diff --git a/src/libxrpl/protocol/STBase.cpp b/src/libxrpl/protocol/STBase.cpp index 3c5c34ae4e..b3558e1b08 100644 --- a/src/libxrpl/protocol/STBase.cpp +++ b/src/libxrpl/protocol/STBase.cpp @@ -1,7 +1,8 @@ +#include + #include #include #include -#include #include #include diff --git a/src/libxrpl/protocol/STBlob.cpp b/src/libxrpl/protocol/STBlob.cpp index e7d45a698d..3f44c9b529 100644 --- a/src/libxrpl/protocol/STBlob.cpp +++ b/src/libxrpl/protocol/STBlob.cpp @@ -1,8 +1,9 @@ +#include + #include #include #include #include -#include #include #include diff --git a/src/libxrpl/protocol/STCurrency.cpp b/src/libxrpl/protocol/STCurrency.cpp index 3ca7b60d6b..dc47624402 100644 --- a/src/libxrpl/protocol/STCurrency.cpp +++ b/src/libxrpl/protocol/STCurrency.cpp @@ -1,8 +1,9 @@ +#include + #include #include #include #include -#include #include #include diff --git a/src/libxrpl/protocol/STInteger.cpp b/src/libxrpl/protocol/STInteger.cpp index 1fccee2285..0530a784d9 100644 --- a/src/libxrpl/protocol/STInteger.cpp +++ b/src/libxrpl/protocol/STInteger.cpp @@ -1,3 +1,5 @@ +#include + #include #include #include @@ -6,7 +8,6 @@ #include #include #include -#include #include #include #include @@ -15,7 +16,6 @@ #include #include #include -#include namespace xrpl { diff --git a/src/libxrpl/protocol/STIssue.cpp b/src/libxrpl/protocol/STIssue.cpp index 5acf4abcb6..10f0ff4c32 100644 --- a/src/libxrpl/protocol/STIssue.cpp +++ b/src/libxrpl/protocol/STIssue.cpp @@ -1,3 +1,5 @@ +#include + #include #include #include @@ -6,7 +8,6 @@ #include #include #include -#include #include #include @@ -88,22 +89,19 @@ STIssue::getJson(JsonOptions) const void STIssue::add(Serializer& s) const { - if (holds()) - { - auto const& issue = asset_.get(); - s.addBitString(issue.currency); - if (!isXRP(issue.currency)) - s.addBitString(issue.account); - } - else - { - auto const& issue = asset_.get(); - s.addBitString(issue.getIssuer()); - s.addBitString(noAccount()); - std::uint32_t sequence = 0; - memcpy(&sequence, issue.getMptID().data(), sizeof(sequence)); - s.add32(sequence); - } + asset_.visit( + [&](Issue const& issue) { + s.addBitString(issue.currency); + if (!isXRP(issue.currency)) + s.addBitString(issue.account); + }, + [&](MPTIssue const& issue) { + s.addBitString(issue.getIssuer()); + s.addBitString(noAccount()); + std::uint32_t sequence = 0; + memcpy(&sequence, issue.getMptID().data(), sizeof(sequence)); + s.add32(sequence); + }); } bool @@ -116,7 +114,9 @@ STIssue::isEquivalent(STBase const& t) const bool STIssue::isDefault() const { - return holds() && asset_.get() == xrpIssue(); + return asset_.visit( + [](Issue const& issue) { return issue == xrpIssue(); }, + [](MPTIssue const&) { return false; }); } STBase* diff --git a/src/libxrpl/protocol/STLedgerEntry.cpp b/src/libxrpl/protocol/STLedgerEntry.cpp index 5a80559c98..1f4d9acfd4 100644 --- a/src/libxrpl/protocol/STLedgerEntry.cpp +++ b/src/libxrpl/protocol/STLedgerEntry.cpp @@ -1,9 +1,12 @@ +#include + #include #include #include #include #include -#include +#include +#include // IWYU pragma: keep #include #include #include @@ -11,7 +14,6 @@ #include #include #include -#include #include #include #include diff --git a/src/libxrpl/protocol/STNumber.cpp b/src/libxrpl/protocol/STNumber.cpp index 8899a76a35..25aa9eaa05 100644 --- a/src/libxrpl/protocol/STNumber.cpp +++ b/src/libxrpl/protocol/STNumber.cpp @@ -1,20 +1,26 @@ #include -// Do not remove. Keep STNumber.h first + #include -#include +#include #include -#include +#include +#include #include #include #include -#include +#include #include #include -#include +#include +#include +#include #include +#include +#include #include +#include #include #include @@ -207,7 +213,7 @@ partsFromString(std::string const& number) } } - return {mantissa, exponent, negative}; + return {.mantissa = mantissa, .exponent = exponent, .negative = negative}; } STNumber diff --git a/src/libxrpl/protocol/STObject.cpp b/src/libxrpl/protocol/STObject.cpp index f758760337..baece3ca56 100644 --- a/src/libxrpl/protocol/STObject.cpp +++ b/src/libxrpl/protocol/STObject.cpp @@ -1,3 +1,5 @@ +#include + #include #include #include @@ -22,7 +24,6 @@ #include #include #include -#include #include #include #include @@ -166,9 +167,8 @@ STObject::applyTemplate(SOTemplate const& type) v.reserve(type.size()); for (auto const& e : type) { - auto const iter = std::find_if(v_.begin(), v_.end(), [&](detail::STVar const& b) { - return b.get().getFName() == e.sField(); - }); + auto const iter = std::ranges::find_if( + v_, [&](detail::STVar const& b) { return b.get().getFName() == e.sField(); }); if (iter != v_.end()) { if ((e.style() == soeDEFAULT) && iter->get().isDefault()) @@ -260,10 +260,9 @@ STObject::set(SerialIter& sit, int depth) // duplicate fields. This is a key invariant: auto const sf = getSortedFields(*this, withAllFields); - auto const dup = - std::adjacent_find(sf.cbegin(), sf.cend(), [](STBase const* lhs, STBase const* rhs) { - return lhs->getFName() == rhs->getFName(); - }); + auto const dup = std::ranges::adjacent_find(sf, [](STBase const* lhs, STBase const* rhs) { + return lhs->getFName() == rhs->getFName(); + }); if (dup != sf.cend()) Throw("Duplicate field detected"); @@ -348,7 +347,7 @@ STObject::isEquivalent(STBase const& t) const if (mType != nullptr && v->mType == mType) { - return std::equal( + return std::ranges::equal( begin(), end(), v->begin(), v->end(), [](STBase const& st1, STBase const& st2) { return (st1.getSType() == st2.getSType()) && st1.isEquivalent(st2); }); @@ -357,10 +356,9 @@ STObject::isEquivalent(STBase const& t) const auto const sf1 = getSortedFields(*this, withAllFields); auto const sf2 = getSortedFields(*v, withAllFields); - return std::equal( - sf1.begin(), sf1.end(), sf2.begin(), sf2.end(), [](STBase const* st1, STBase const* st2) { - return (st1->getSType() == st2->getSType()) && st1->isEquivalent(*st2); - }); + return std::ranges::equal(sf1, sf2, [](STBase const* st1, STBase const* st2) { + return (st1->getSType() == st2->getSType()) && st1->isEquivalent(*st2); + }); } uint256 @@ -749,6 +747,12 @@ STObject::setFieldH128(SField const& field, uint128 const& v) setFieldUsingSetValue(field, v); } +void +STObject::setFieldH192(SField const& field, uint192 const& v) +{ + setFieldUsingSetValue(field, v); +} + void STObject::setFieldH256(SField const& field, uint256 const& v) { @@ -922,7 +926,7 @@ STObject::getSortedFields(STObject const& objToSort, WhichFields whichFields) } // Sort the fields by fieldCode. - std::sort(sf.begin(), sf.end(), [](STBase const* lhs, STBase const* rhs) { + std::ranges::sort(sf, [](STBase const* lhs, STBase const* rhs) { return lhs->getFName().fieldCode < rhs->getFName().fieldCode; }); diff --git a/src/libxrpl/protocol/STParsedJSON.cpp b/src/libxrpl/protocol/STParsedJSON.cpp index c928f49375..01a52c88ec 100644 --- a/src/libxrpl/protocol/STParsedJSON.cpp +++ b/src/libxrpl/protocol/STParsedJSON.cpp @@ -1,13 +1,18 @@ +#include + #include #include #include #include #include +#include #include #include #include #include #include +#include +#include #include #include #include @@ -19,7 +24,6 @@ #include #include #include -#include #include #include #include @@ -27,6 +31,7 @@ #include #include #include +#include #include #include @@ -45,7 +50,7 @@ namespace xrpl { namespace STParsedJSONDetail { template -constexpr std::enable_if_t::value && std::is_signed::value, U> +constexpr std::enable_if_t && std::is_signed_v, U> to_unsigned(S value) { if (value < 0 || std::numeric_limits::max() < value) @@ -54,7 +59,7 @@ to_unsigned(S value) } template -constexpr std::enable_if_t::value && std::is_unsigned::value, U1> +constexpr std::enable_if_t && std::is_unsigned_v, U1> to_unsigned(U2 value) { if (std::numeric_limits::max() < value) @@ -736,7 +741,7 @@ parseLeaf( std::string const element_name(json_name + "." + ss.str()); // each element in this path has some combination of - // account, currency, or issuer + // account, asset, or issuer Json::Value pathEl = value[i][j]; @@ -746,14 +751,22 @@ parseLeaf( return ret; } - Json::Value const& account = pathEl["account"]; - Json::Value const& currency = pathEl["currency"]; - Json::Value const& issuer = pathEl["issuer"]; - bool hasCurrency = false; - AccountID uAccount, uIssuer; - Currency uCurrency; + if (pathEl.isMember(jss::currency) && pathEl.isMember(jss::mpt_issuance_id)) + { + error = RPC::make_error(rpcINVALID_PARAMS, "Invalid Asset."); + return ret; + } - if (!account && !currency && !issuer) + bool const isMPT = pathEl.isMember(jss::mpt_issuance_id); + auto const assetName = isMPT ? jss::mpt_issuance_id : jss::currency; + Json::Value const& account = pathEl[jss::account]; + Json::Value const& asset = pathEl[assetName]; + Json::Value const& issuer = pathEl[jss::issuer]; + bool hasAsset = false; + AccountID uAccount, uIssuer; + PathAsset uAsset; + + if (!account && !asset && !issuer) { error = invalid_data(element_name); return ret; @@ -764,7 +777,7 @@ parseLeaf( // human account id if (!account.isString()) { - error = string_expected(element_name, "account"); + error = string_expected(element_name, jss::account.c_str()); return ret; } @@ -775,31 +788,51 @@ parseLeaf( auto const a = parseBase58(account.asString()); if (!a) { - error = invalid_data(element_name, "account"); + error = invalid_data(element_name, jss::account.c_str()); return ret; } uAccount = *a; } } - if (currency) + if (asset) { - // human currency - if (!currency.isString()) + // human asset + if (!asset.isString()) { - error = string_expected(element_name, "currency"); + error = string_expected(element_name, assetName.c_str()); return ret; } - hasCurrency = true; + hasAsset = true; - if (!uCurrency.parseHex(currency.asString())) + if (isMPT) { - if (!to_currency(uCurrency, currency.asString())) + MPTID u; + if (!u.parseHex(asset.asString())) { - error = invalid_data(element_name, "currency"); + error = invalid_data(element_name, assetName.c_str()); return ret; } + if (getMPTIssuer(u) == beast::zero) + { + error = invalid_data(element_name, jss::account.c_str()); + return ret; + } + uAsset = u; + } + else + { + Currency currency; + if (!currency.parseHex(asset.asString())) + { + if (!to_currency(currency, asset.asString())) + { + error = invalid_data(element_name, assetName.c_str()); + return ret; + } + } + uAsset = currency; } } @@ -808,7 +841,7 @@ parseLeaf( // human account id if (!issuer.isString()) { - error = string_expected(element_name, "issuer"); + error = string_expected(element_name, jss::issuer.c_str()); return ret; } @@ -817,14 +850,20 @@ parseLeaf( auto const a = parseBase58(issuer.asString()); if (!a) { - error = invalid_data(element_name, "issuer"); + error = invalid_data(element_name, jss::issuer.c_str()); return ret; } uIssuer = *a; } + + if (isMPT && uIssuer != getMPTIssuer(uAsset.get())) + { + error = invalid_data(element_name, jss::issuer.c_str()); + return ret; + } } - p.emplace_back(uAccount, uCurrency, uIssuer, hasCurrency); + p.emplace_back(uAccount, uAsset, uIssuer, hasAsset); } tail.push_back(p); diff --git a/src/libxrpl/protocol/STPathSet.cpp b/src/libxrpl/protocol/STPathSet.cpp index 2536c18e4e..04c8d3c8da 100644 --- a/src/libxrpl/protocol/STPathSet.cpp +++ b/src/libxrpl/protocol/STPathSet.cpp @@ -1,11 +1,14 @@ +#include + #include +#include #include +#include #include #include #include #include #include -#include #include #include #include @@ -31,8 +34,15 @@ STPathElement::get_hash(STPathElement const& element) for (auto const x : element.getAccountID()) hash_account += (hash_account * 257) ^ x; - for (auto const x : element.getCurrency()) - hash_currency += (hash_currency * 509) ^ x; + // Check pathAsset type instead of element's mType + // In some cases mType might be account but the asset + // is still set to either MPT or currency (see Pathfinder::addLink()) + element.getPathAsset().visit( + [&](MPTID const& mpt) { hash_currency += beast::uhash<>{}(mpt); }, + [&](Currency const& currency) { + for (auto const x : currency) + hash_currency += (hash_currency * 509) ^ x; + }); for (auto const x : element.getIssuerID()) hash_issuer += (hash_issuer * 911) ^ x; @@ -68,24 +78,30 @@ STPathSet::STPathSet(SerialIter& sit, SField const& name) : STBase(name) } else { - auto hasAccount = iType & STPathElement::typeAccount; - auto hasCurrency = iType & STPathElement::typeCurrency; - auto hasIssuer = iType & STPathElement::typeIssuer; + auto const hasAccount = (iType & STPathElement::typeAccount) != 0u; + auto const hasCurrency = (iType & STPathElement::typeCurrency) != 0u; + auto const hasIssuer = (iType & STPathElement::typeIssuer) != 0u; + auto const hasMPT = (iType & STPathElement::typeMPT) != 0u; AccountID account; - Currency currency; + PathAsset asset; AccountID issuer; - if (hasAccount != 0) + if (hasAccount) account = sit.get160(); - if (hasCurrency != 0) - currency = sit.get160(); + XRPL_ASSERT( + !(hasCurrency && hasMPT), "xrpl::STPathSet::STPathSet : not has Currency and MPT"); + if (hasCurrency) + asset = static_cast(sit.get160()); - if (hasIssuer != 0) + if (hasMPT) + asset = sit.get192(); + + if (hasIssuer) issuer = sit.get160(); - path.emplace_back(account, currency, issuer, hasCurrency); + path.emplace_back(account, asset, issuer, hasCurrency); } } } @@ -137,11 +153,11 @@ STPathSet::isDefault() const } bool -STPath::hasSeen(AccountID const& account, Currency const& currency, AccountID const& issuer) const +STPath::hasSeen(AccountID const& account, PathAsset const& asset, AccountID const& issuer) const { for (auto& p : mPath) { - if (p.getAccountID() == account && p.getCurrency() == currency && p.getIssuerID() == issuer) + if (p.getAccountID() == account && p.getPathAsset() == asset && p.getIssuerID() == issuer) return true; } @@ -163,9 +179,16 @@ STPath::getJson(JsonOptions) const if ((iType & STPathElement::typeAccount) != 0u) elem[jss::account] = to_string(it.getAccountID()); + XRPL_ASSERT( + ((iType & STPathElement::typeCurrency) == 0u) || + ((iType & STPathElement::typeMPT) == 0u), + "xrpl::STPath::getJson : not type Currency and MPT"); if ((iType & STPathElement::typeCurrency) != 0u) elem[jss::currency] = to_string(it.getCurrency()); + if ((iType & STPathElement::typeMPT) != 0u) + elem[jss::mpt_issuance_id] = to_string(it.getMPTID()); + if ((iType & STPathElement::typeIssuer) != 0u) elem[jss::issuer] = to_string(it.getIssuerID()); @@ -209,13 +232,16 @@ STPathSet::add(Serializer& s) const s.add8(iType); - if ((iType & STPathElement::typeAccount) != 0) + if ((iType & STPathElement::typeAccount) != 0u) s.addBitString(speElement.getAccountID()); - if ((iType & STPathElement::typeCurrency) != 0) + if ((iType & STPathElement::typeMPT) != 0u) + s.addBitString(speElement.getMPTID()); + + if ((iType & STPathElement::typeCurrency) != 0u) s.addBitString(speElement.getCurrency()); - if ((iType & STPathElement::typeIssuer) != 0) + if ((iType & STPathElement::typeIssuer) != 0u) s.addBitString(speElement.getIssuerID()); } diff --git a/src/libxrpl/protocol/STTakesAsset.cpp b/src/libxrpl/protocol/STTakesAsset.cpp index fcf4ad749c..1d37c65fc1 100644 --- a/src/libxrpl/protocol/STTakesAsset.cpp +++ b/src/libxrpl/protocol/STTakesAsset.cpp @@ -1,5 +1,10 @@ #include -// Do not remove. Force STTakesAsset.h first + +#include +#include +#include +#include +#include #include namespace xrpl { diff --git a/src/libxrpl/protocol/STTx.cpp b/src/libxrpl/protocol/STTx.cpp index f8600e167f..6da0d61b57 100644 --- a/src/libxrpl/protocol/STTx.cpp +++ b/src/libxrpl/protocol/STTx.cpp @@ -1,3 +1,5 @@ +#include + #include #include #include @@ -24,18 +26,14 @@ #include #include #include -#include -#include #include #include #include #include -#include #include #include #include -#include #include #include @@ -50,6 +48,7 @@ #include #include #include +#include namespace xrpl { diff --git a/src/libxrpl/protocol/STValidation.cpp b/src/libxrpl/protocol/STValidation.cpp index f4042844f4..14349d3f85 100644 --- a/src/libxrpl/protocol/STValidation.cpp +++ b/src/libxrpl/protocol/STValidation.cpp @@ -1,16 +1,16 @@ +#include + #include #include #include #include #include #include -#include #include #include #include #include #include -#include #include #include diff --git a/src/libxrpl/protocol/STVar.cpp b/src/libxrpl/protocol/STVar.cpp index 8b76d8a322..652328f613 100644 --- a/src/libxrpl/protocol/STVar.cpp +++ b/src/libxrpl/protocol/STVar.cpp @@ -1,3 +1,5 @@ +#include + #include #include #include @@ -16,14 +18,12 @@ #include #include #include -#include #include #include #include -namespace xrpl { -namespace detail { +namespace xrpl::detail { defaultObject_t defaultObject; nonPresentObject_t nonPresentObject; @@ -227,5 +227,4 @@ STVar::constructST(SerializedTypeID id, int depth, Args&&... args) } } -} // namespace detail -} // namespace xrpl +} // namespace xrpl::detail diff --git a/src/libxrpl/protocol/STVector256.cpp b/src/libxrpl/protocol/STVector256.cpp index 2a70dd1e05..038f88c226 100644 --- a/src/libxrpl/protocol/STVector256.cpp +++ b/src/libxrpl/protocol/STVector256.cpp @@ -1,10 +1,11 @@ +#include + #include #include #include #include #include #include -#include #include #include diff --git a/src/libxrpl/protocol/STXChainBridge.cpp b/src/libxrpl/protocol/STXChainBridge.cpp index 428e4655ff..a29bf3b6f9 100644 --- a/src/libxrpl/protocol/STXChainBridge.cpp +++ b/src/libxrpl/protocol/STXChainBridge.cpp @@ -1,3 +1,5 @@ +#include + #include #include #include @@ -6,7 +8,6 @@ #include #include #include -#include #include #include diff --git a/src/libxrpl/protocol/SecretKey.cpp b/src/libxrpl/protocol/SecretKey.cpp index f58bb81a88..01d3c29f9e 100644 --- a/src/libxrpl/protocol/SecretKey.cpp +++ b/src/libxrpl/protocol/SecretKey.cpp @@ -1,3 +1,5 @@ +#include + #include #include #include @@ -8,7 +10,6 @@ #include #include #include -#include #include #include #include @@ -17,6 +18,7 @@ #include #include +#include #include #include @@ -75,7 +77,7 @@ deriveDeterministicRootKey(Seed const& seed) // | seed | seq| std::array buf{}; - std::copy(seed.begin(), seed.end(), buf.begin()); + std::ranges::copy(seed, buf.begin()); // The odds that this loop executes more than once are negligible // but *just* in case someone managed to generate a key that required @@ -134,7 +136,7 @@ private: // | generator | seq| cnt| std::array buf{}; - std::copy(generator_.begin(), generator_.end(), buf.begin()); + std::ranges::copy(generator_, buf.begin()); copy_uint32(buf.data() + 33, seq); // The odds that this loop executes more than once are negligible diff --git a/src/libxrpl/protocol/Seed.cpp b/src/libxrpl/protocol/Seed.cpp index 6d33013f2f..0fe525a613 100644 --- a/src/libxrpl/protocol/Seed.cpp +++ b/src/libxrpl/protocol/Seed.cpp @@ -1,3 +1,5 @@ +#include + #include #include #include @@ -9,7 +11,6 @@ #include #include #include -#include #include #include diff --git a/src/libxrpl/protocol/Serializer.cpp b/src/libxrpl/protocol/Serializer.cpp index 4e6a49f572..576e3f3e5f 100644 --- a/src/libxrpl/protocol/Serializer.cpp +++ b/src/libxrpl/protocol/Serializer.cpp @@ -1,3 +1,5 @@ +#include + #include #include #include @@ -5,7 +7,6 @@ #include #include #include -#include #include #include @@ -442,7 +443,7 @@ template T SerialIter::getRawHelper(int size) { - static_assert(std::is_same::value || std::is_same::value, ""); + static_assert(std::is_same_v || std::is_same_v, ""); if (remain_ < size) Throw("invalid SerialIter getRaw"); T result(size); diff --git a/src/libxrpl/protocol/Sign.cpp b/src/libxrpl/protocol/Sign.cpp index 9105be8294..bee034d60e 100644 --- a/src/libxrpl/protocol/Sign.cpp +++ b/src/libxrpl/protocol/Sign.cpp @@ -1,3 +1,5 @@ +#include + #include #include #include @@ -7,7 +9,6 @@ #include #include #include -#include namespace xrpl { diff --git a/src/libxrpl/protocol/TER.cpp b/src/libxrpl/protocol/TER.cpp index 5d732a5876..1e3f64cc22 100644 --- a/src/libxrpl/protocol/TER.cpp +++ b/src/libxrpl/protocol/TER.cpp @@ -156,6 +156,7 @@ transResults() MAKE_ERROR(temBAD_FEE, "Invalid fee, negative or not XRP."), MAKE_ERROR(temBAD_ISSUER, "Malformed: Bad issuer."), MAKE_ERROR(temBAD_LIMIT, "Limits must be non-negative."), + MAKE_ERROR(temBAD_MPT, "Malformed: Bad MPT."), MAKE_ERROR(temBAD_OFFER, "Malformed: Bad offer."), MAKE_ERROR(temBAD_PATH, "Malformed: Bad path."), MAKE_ERROR(temBAD_PATH_LOOP, "Malformed: Loop in path."), @@ -214,6 +215,7 @@ transResults() MAKE_ERROR(terNO_AMM, "AMM doesn't exist for the asset pair."), MAKE_ERROR(terADDRESS_COLLISION, "Failed to allocate an unique account address."), MAKE_ERROR(terNO_DELEGATE_PERMISSION, "Delegated account lacks permission to perform this transaction."), + MAKE_ERROR(terLOCKED, "Fund is locked."), MAKE_ERROR(tesSUCCESS, "The transaction was applied. Only final in a validated ledger."), }; diff --git a/src/libxrpl/protocol/TxFormats.cpp b/src/libxrpl/protocol/TxFormats.cpp index 4492ae271b..a2a7c5facf 100644 --- a/src/libxrpl/protocol/TxFormats.cpp +++ b/src/libxrpl/protocol/TxFormats.cpp @@ -1,7 +1,9 @@ +#include + +#include // IWYU pragma: keep #include #include -#include -#include +#include // IWYU pragma: keep #include diff --git a/src/libxrpl/protocol/TxMeta.cpp b/src/libxrpl/protocol/TxMeta.cpp index 3c797a7c8c..08069ca83d 100644 --- a/src/libxrpl/protocol/TxMeta.cpp +++ b/src/libxrpl/protocol/TxMeta.cpp @@ -1,4 +1,7 @@ +#include + #include +#include #include #include #include @@ -10,13 +13,12 @@ #include #include #include -#include #include #include +#include #include -#include namespace xrpl { diff --git a/src/libxrpl/protocol/UintTypes.cpp b/src/libxrpl/protocol/UintTypes.cpp index 5ace6c29d6..4206ec8cf5 100644 --- a/src/libxrpl/protocol/UintTypes.cpp +++ b/src/libxrpl/protocol/UintTypes.cpp @@ -1,7 +1,8 @@ +#include + #include #include #include -#include #include #include @@ -77,7 +78,7 @@ to_currency(Currency& currency, std::string const& code) currency = beast::zero; - std::copy(code.begin(), code.end(), currency.begin() + detail::isoCodeOffset); + std::ranges::copy(code, currency.begin() + detail::isoCodeOffset); return true; } diff --git a/src/libxrpl/protocol/XChainAttestations.cpp b/src/libxrpl/protocol/XChainAttestations.cpp index c255f743e3..8d3e62b31e 100644 --- a/src/libxrpl/protocol/XChainAttestations.cpp +++ b/src/libxrpl/protocol/XChainAttestations.cpp @@ -1,3 +1,5 @@ +#include + #include #include #include @@ -11,7 +13,6 @@ #include #include #include -#include #include #include @@ -30,14 +31,14 @@ AttestationBase::AttestationBase( PublicKey const& publicKey_, Buffer signature_, AccountID const& sendingAccount_, - STAmount const& sendingAmount_, + STAmount sendingAmount_, AccountID const& rewardAccount_, bool wasLockingChainSend_) : attestationSignerAccount{attestationSignerAccount_} , publicKey{publicKey_} , signature{std::move(signature_)} , sendingAccount{sendingAccount_} - , sendingAmount{sendingAmount_} + , sendingAmount{std::move(sendingAmount_)} , rewardAccount{rewardAccount_} , wasLockingChainSend{wasLockingChainSend_} { @@ -260,7 +261,7 @@ AttestationCreateAccount::AttestationCreateAccount( Buffer signature_, AccountID const& sendingAccount_, STAmount const& sendingAmount_, - STAmount const& rewardAmount_, + STAmount rewardAmount_, AccountID const& rewardAccount_, bool wasLockingChainSend_, std::uint64_t createCount_, @@ -275,7 +276,7 @@ AttestationCreateAccount::AttestationCreateAccount( wasLockingChainSend_) , createCount{createCount_} , toCreate{toCreate_} - , rewardAmount{rewardAmount_} + , rewardAmount{std::move(rewardAmount_)} { } diff --git a/src/libxrpl/protocol/tokens.cpp b/src/libxrpl/protocol/tokens.cpp index e2a77fbf10..3cb0e3f3dd 100644 --- a/src/libxrpl/protocol/tokens.cpp +++ b/src/libxrpl/protocol/tokens.cpp @@ -7,13 +7,14 @@ * file COPYING or http://www.opensource.org/licenses/mit-license.php. */ +#include + #include #include #include #include #include #include -#include #include #include @@ -281,7 +282,7 @@ decodeBase58(std::string const& s) --remain; } // Skip leading zeroes in b256. - auto iter = std::find_if(b256.begin(), b256.end(), [](unsigned char c) { return c != 0; }); + auto iter = std::ranges::find_if(b256, [](unsigned char c) { return c != 0; }); std::string result; result.reserve(zeroes + (b256.end() - iter)); result.assign(zeroes, 0x00); diff --git a/src/libxrpl/protocol_autogen/placeholder.cpp b/src/libxrpl/protocol_autogen/placeholder.cpp index b48581e5b8..efc30fd904 100644 --- a/src/libxrpl/protocol_autogen/placeholder.cpp +++ b/src/libxrpl/protocol_autogen/placeholder.cpp @@ -1,5 +1 @@ // This file is a placeholder to ensure the protocol_autogen module can be built. -#include -#include -#include -#include diff --git a/src/libxrpl/rdb/DatabaseCon.cpp b/src/libxrpl/rdb/DatabaseCon.cpp index bca14f9bfa..5f4594b630 100644 --- a/src/libxrpl/rdb/DatabaseCon.cpp +++ b/src/libxrpl/rdb/DatabaseCon.cpp @@ -1,13 +1,18 @@ +#include + #include #include -#include #include -#include -#include - +#include +#include #include +#include +#include +#include +#include #include +#include namespace xrpl { diff --git a/src/libxrpl/rdb/SociDB.cpp b/src/libxrpl/rdb/SociDB.cpp index 780e05854f..c6e40ae90b 100644 --- a/src/libxrpl/rdb/SociDB.cpp +++ b/src/libxrpl/rdb/SociDB.cpp @@ -1,3 +1,21 @@ +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include #if defined(__clang__) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated" @@ -8,9 +26,7 @@ #include #include -#include - -#include +#include // IWYU pragma: keep #include @@ -50,7 +66,7 @@ getSociInit(BasicConfig const& config, std::string const& dbName) } // namespace detail -DBConfig::DBConfig(std::string const& dbPath) : connectionString_(dbPath) +DBConfig::DBConfig(std::string dbPath) : connectionString_(std::move(dbPath)) { } @@ -256,10 +272,10 @@ public: return; int log = 0, ckpt = 0; - int const ret = - sqlite3_wal_checkpoint_v2(conn, nullptr, SQLITE_CHECKPOINT_PASSIVE, &log, &ckpt); + int const ret = sqlite_api::sqlite3_wal_checkpoint_v2( + conn, nullptr, SQLITE_CHECKPOINT_PASSIVE, &log, &ckpt); - auto fname = sqlite3_db_filename(conn, "main"); + auto fname = sqlite_api::sqlite3_db_filename(conn, "main"); if (ret != SQLITE_OK) { auto jm = (ret == SQLITE_LOCKED) ? j_.trace() : j_.warn(); diff --git a/src/libxrpl/resource/Charge.cpp b/src/libxrpl/resource/Charge.cpp index eece5355f0..8626115c4c 100644 --- a/src/libxrpl/resource/Charge.cpp +++ b/src/libxrpl/resource/Charge.cpp @@ -4,11 +4,11 @@ #include #include #include +#include -namespace xrpl { -namespace Resource { +namespace xrpl::Resource { -Charge::Charge(value_type cost, std::string const& label) : m_cost(cost), m_label(label) +Charge::Charge(value_type cost, std::string label) : m_cost(cost), m_label(std::move(label)) { } @@ -57,5 +57,4 @@ Charge::operator*(value_type m) const return Charge(m_cost * m, m_label); } -} // namespace Resource -} // namespace xrpl +} // namespace xrpl::Resource diff --git a/src/libxrpl/resource/Consumer.cpp b/src/libxrpl/resource/Consumer.cpp index c1cb9d4367..3d03fa05db 100644 --- a/src/libxrpl/resource/Consumer.cpp +++ b/src/libxrpl/resource/Consumer.cpp @@ -1,8 +1,10 @@ +#include + #include #include #include +#include #include -#include #include #include #include @@ -10,8 +12,7 @@ #include #include -namespace xrpl { -namespace Resource { +namespace xrpl::Resource { Consumer::Consumer(Logic& logic, Entry& entry) : m_logic(&logic), m_entry(&entry) { @@ -141,5 +142,4 @@ operator<<(std::ostream& os, Consumer const& v) return os; } -} // namespace Resource -} // namespace xrpl +} // namespace xrpl::Resource diff --git a/src/libxrpl/resource/Fees.cpp b/src/libxrpl/resource/Fees.cpp index d5999458b7..e9f47b3e7d 100644 --- a/src/libxrpl/resource/Fees.cpp +++ b/src/libxrpl/resource/Fees.cpp @@ -1,8 +1,8 @@ -#include #include -namespace xrpl { -namespace Resource { +#include + +namespace xrpl::Resource { Charge const feeMalformedRequest(200, "malformed request"); Charge const feeRequestNoReply(10, "unsatisfiable request"); @@ -25,5 +25,4 @@ Charge const feeDrop(6000, "dropped"); // See also Resource::Logic::charge for log level cutoff values -} // namespace Resource -} // namespace xrpl +} // namespace xrpl::Resource diff --git a/src/libxrpl/resource/ResourceManager.cpp b/src/libxrpl/resource/ResourceManager.cpp index d0e45d4c0e..6652c1a66b 100644 --- a/src/libxrpl/resource/ResourceManager.cpp +++ b/src/libxrpl/resource/ResourceManager.cpp @@ -1,3 +1,5 @@ +#include + #include #include #include @@ -8,7 +10,6 @@ #include #include #include -#include #include #include @@ -22,8 +23,7 @@ #include #include -namespace xrpl { -namespace Resource { +namespace xrpl::Resource { class ManagerImp : public Manager { @@ -164,5 +164,4 @@ make_Manager(beast::insight::Collector::ptr const& collector, beast::Journal jou return std::make_unique(collector, journal); } -} // namespace Resource -} // namespace xrpl +} // namespace xrpl::Resource diff --git a/src/libxrpl/server/InfoSub.cpp b/src/libxrpl/server/InfoSub.cpp index ea70dc3a83..30686dfe7e 100644 --- a/src/libxrpl/server/InfoSub.cpp +++ b/src/libxrpl/server/InfoSub.cpp @@ -1,5 +1,13 @@ #include +#include +#include +#include + +#include +#include +#include + namespace xrpl { // This is the primary interface into the "client" portion of the program. diff --git a/src/libxrpl/server/JSONRPCUtil.cpp b/src/libxrpl/server/JSONRPCUtil.cpp index db2ea450a2..280aeeeebc 100644 --- a/src/libxrpl/server/JSONRPCUtil.cpp +++ b/src/libxrpl/server/JSONRPCUtil.cpp @@ -1,9 +1,10 @@ +#include + #include #include #include #include #include -#include #include #include diff --git a/src/libxrpl/server/LoadFeeTrack.cpp b/src/libxrpl/server/LoadFeeTrack.cpp index d698e2420e..53fbb8cb84 100644 --- a/src/libxrpl/server/LoadFeeTrack.cpp +++ b/src/libxrpl/server/LoadFeeTrack.cpp @@ -1,11 +1,15 @@ +#include + #include #include #include #include -#include +#include #include #include +#include +#include namespace xrpl { diff --git a/src/libxrpl/server/Manifest.cpp b/src/libxrpl/server/Manifest.cpp index f4634982d0..d2d7bd2e38 100644 --- a/src/libxrpl/server/Manifest.cpp +++ b/src/libxrpl/server/Manifest.cpp @@ -1,17 +1,43 @@ +#include + +#include #include +#include #include #include +#include +#include +#include +#include #include +#include +#include #include +#include +#include +#include +#include +#include #include +#include #include -#include #include #include +#include +#include +#include +#include +#include +#include #include +#include +#include #include +#include +#include +#include namespace xrpl { @@ -263,7 +289,10 @@ loadValidatorToken(std::vector const& blob, beast::Journal journal) auto const key = strUnHex(k.asString()); if (key && key->size() == 32) - return ValidatorToken{m.asString(), makeSlice(*key)}; + { + return ValidatorToken{ + .manifest = m.asString(), .validationSecret = makeSlice(*key)}; + } } } diff --git a/src/libxrpl/server/Port.cpp b/src/libxrpl/server/Port.cpp index 2d0b4ec257..99466446d6 100644 --- a/src/libxrpl/server/Port.cpp +++ b/src/libxrpl/server/Port.cpp @@ -1,10 +1,11 @@ +#include + #include #include #include #include #include #include -#include #include #include @@ -17,7 +18,6 @@ #include #include #include -#include #include #include diff --git a/src/libxrpl/server/State.cpp b/src/libxrpl/server/State.cpp index a7c0c0fca2..3d2ed37e98 100644 --- a/src/libxrpl/server/State.cpp +++ b/src/libxrpl/server/State.cpp @@ -1,5 +1,20 @@ #include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include +#include + namespace xrpl { void diff --git a/src/libxrpl/server/Vacuum.cpp b/src/libxrpl/server/Vacuum.cpp index bcf5941e95..d0e430ead9 100644 --- a/src/libxrpl/server/Vacuum.cpp +++ b/src/libxrpl/server/Vacuum.cpp @@ -1,8 +1,19 @@ #include -#include +#include +#include +#include +#include +#include +#include +#include // IWYU pragma: keep + +#include + +#include #include +#include namespace xrpl { @@ -12,7 +23,7 @@ doVacuumDB(DatabaseCon::Setup const& setup, beast::Journal j) boost::filesystem::path const dbPath = setup.dataDir / TxDBName; uintmax_t const dbSize = file_size(dbPath); - XRPL_ASSERT(dbSize != static_cast(-1), "ripple:doVacuumDB : file_size succeeded"); + XRPL_ASSERT(dbSize != static_cast(-1), "xrpl::doVacuumDB : file_size succeeded"); if (auto available = space(dbPath.parent_path()).available; available < dbSize) { @@ -36,7 +47,7 @@ doVacuumDB(DatabaseCon::Setup const& setup, beast::Journal j) std::cout << "VACUUM beginning. page_size: " << pageSize << std::endl; session << "VACUUM;"; - XRPL_ASSERT(setup.globalPragma, "ripple:doVacuumDB : non-null global pragma"); + XRPL_ASSERT(setup.globalPragma, "xrpl::doVacuumDB : non-null global pragma"); for (auto const& p : *setup.globalPragma) session << p; session << "PRAGMA page_size;", soci::into(pageSize); diff --git a/src/libxrpl/server/Wallet.cpp b/src/libxrpl/server/Wallet.cpp index 8da934a921..7906867654 100644 --- a/src/libxrpl/server/Wallet.cpp +++ b/src/libxrpl/server/Wallet.cpp @@ -1,7 +1,37 @@ -#include #include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include namespace xrpl { @@ -168,7 +198,7 @@ getPeerReservationTable(soci::session& session, beast::Journal j) JLOG(j.warn()) << "load: not a public key: " << valPubKey; continue; } - table.insert(PeerReservation{*optNodeId, *valDesc}); + table.insert(PeerReservation{.nodeId = *optNodeId, .description = *valDesc}); } return table; diff --git a/src/libxrpl/shamap/SHAMap.cpp b/src/libxrpl/shamap/SHAMap.cpp index 505d20651e..b638efc383 100644 --- a/src/libxrpl/shamap/SHAMap.cpp +++ b/src/libxrpl/shamap/SHAMap.cpp @@ -1,13 +1,43 @@ -#include +#include + +#include // IWYU pragma: keep +#include // IWYU pragma: keep +#include +#include +#include +#include // IWYU pragma: keep +#include #include #include -#include +#include +#include +#include +#include #include +#include +#include +#include +#include #include #include +#include #include #include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + namespace xrpl { [[nodiscard]] intr_ptr::SharedPtr @@ -110,7 +140,7 @@ SHAMap::walkTowardsKey(uint256 const& id, SharedPtrNodeStack* stack) const while (inNode->isInner()) { if (stack != nullptr) - stack->push({inNode, nodeID}); + stack->emplace(inNode, nodeID); auto const inner = intr_ptr::static_pointer_cast(inNode); auto const branch = selectBranch(nodeID, id); @@ -122,7 +152,7 @@ SHAMap::walkTowardsKey(uint256 const& id, SharedPtrNodeStack* stack) const } if (stack != nullptr) - stack->push({inNode, nodeID}); + stack->emplace(inNode, nodeID); return safe_downcast(inNode.get()); } @@ -419,11 +449,11 @@ SHAMap::belowHelper( auto inner = intr_ptr::static_pointer_cast(node); if (stack.empty()) { - stack.push({inner, SHAMapNodeID{}}); + stack.emplace(inner, SHAMapNodeID{}); } else { - stack.push({inner, stack.top().second.getChildNodeID(branch)}); + stack.emplace(inner, stack.top().second.getChildNodeID(branch)); } for (int i = init; cmp(i);) { @@ -438,7 +468,7 @@ SHAMap::belowHelper( return n.get(); } inner = intr_ptr::static_pointer_cast(node); - stack.push({inner, stack.top().second.getChildNodeID(branch)}); + stack.emplace(inner, stack.top().second.getChildNodeID(branch)); i = init; // descend and reset loop } else @@ -783,7 +813,7 @@ SHAMap::addGiveItem(SHAMapNodeType type, boost::intrusive_ptr while ((b1 = selectBranch(nodeID, tag)) == (b2 = selectBranch(nodeID, otherItem->key()))) { - stack.push({node, nodeID}); + stack.emplace(node, nodeID); // we need a new inner node, since both go on same branch at this // level @@ -1085,7 +1115,7 @@ SHAMap::dump(bool hash) const JLOG(journal_.info()) << " MAP Contains"; std::stack> stack; - stack.push({root_.get(), SHAMapNodeID()}); + stack.emplace(root_.get(), SHAMapNodeID()); do { @@ -1111,7 +1141,7 @@ SHAMap::dump(bool hash) const XRPL_ASSERT( child->getHash() == inner->getChildHash(i), "xrpl::SHAMap::dump : child hash do match"); - stack.push({child, nodeID.getChildNodeID(i)}); + stack.emplace(child, nodeID.getChildNodeID(i)); } } } diff --git a/src/libxrpl/shamap/SHAMapDelta.cpp b/src/libxrpl/shamap/SHAMapDelta.cpp index ded57760f1..d8162710be 100644 --- a/src/libxrpl/shamap/SHAMapDelta.cpp +++ b/src/libxrpl/shamap/SHAMapDelta.cpp @@ -1,10 +1,23 @@ -#include +#include // IWYU pragma: keep +#include // IWYU pragma: keep +#include #include #include +#include #include +#include +#include +#include +#include + +#include #include +#include +#include #include +#include +#include #include namespace xrpl { @@ -130,7 +143,7 @@ SHAMap::compare(SHAMap const& otherMap, Delta& differences, int maxCount) const using StackEntry = std::pair; std::stack> nodeStack; // track nodes we've pushed - nodeStack.push({root_.get(), otherMap.root_.get()}); + nodeStack.emplace(root_.get(), otherMap.root_.get()); while (!nodeStack.empty()) { auto [ourNode, otherNode] = nodeStack.top(); @@ -212,7 +225,7 @@ SHAMap::compare(SHAMap const& otherMap, Delta& differences, int maxCount) const } else { // The two trees have different non-empty branches - nodeStack.push({descendThrow(ours, i), otherMap.descendThrow(other, i)}); + nodeStack.emplace(descendThrow(ours, i), otherMap.descendThrow(other, i)); } } } @@ -302,52 +315,50 @@ SHAMap::walkMapParallel(std::vector& missingNodes, int maxMis nodeStacks[rootChildIndex].push(intr_ptr::static_pointer_cast(child)); JLOG(journal_.debug()) << "starting worker " << rootChildIndex; - workers.push_back( - std::thread( - [&m, &missingNodes, &maxMissing, &exceptions, this]( - std::stack> nodeStack) { - try + workers.emplace_back( + [&m, &missingNodes, &maxMissing, &exceptions, this]( + std::stack> nodeStack) { + try + { + while (!nodeStack.empty()) { - while (!nodeStack.empty()) + intr_ptr::SharedPtr const node = + std::move(nodeStack.top()); + XRPL_ASSERT(node, "xrpl::SHAMap::walkMapParallel : non-null node"); + nodeStack.pop(); + + for (int i = 0; i < 16; ++i) { - intr_ptr::SharedPtr const node = - std::move(nodeStack.top()); - XRPL_ASSERT(node, "xrpl::SHAMap::walkMapParallel : non-null node"); - nodeStack.pop(); + if (node->isEmptyBranch(i)) + continue; + intr_ptr::SharedPtr const nextNode = + descendNoStore(*node, i); - for (int i = 0; i < 16; ++i) + if (nextNode) { - if (node->isEmptyBranch(i)) - continue; - intr_ptr::SharedPtr const nextNode = - descendNoStore(*node, i); - - if (nextNode) + if (nextNode->isInner()) { - if (nextNode->isInner()) - { - nodeStack.push( - intr_ptr::static_pointer_cast( - nextNode)); - } - } - else - { - std::lock_guard const l{m}; - missingNodes.emplace_back(type_, node->getChildHash(i)); - if (--maxMissing <= 0) - return; + nodeStack.push( + intr_ptr::static_pointer_cast(nextNode)); } } + else + { + std::lock_guard const l{m}; + missingNodes.emplace_back(type_, node->getChildHash(i)); + if (--maxMissing <= 0) + return; + } } } - catch (SHAMapMissingNode const& e) - { - std::lock_guard const l(m); - exceptions.push_back(e); - } - }, - std::move(nodeStacks[rootChildIndex]))); + } + catch (SHAMapMissingNode const& e) + { + std::lock_guard const l(m); + exceptions.push_back(e); + } + }, + std::move(nodeStacks[rootChildIndex])); } for (std::thread& worker : workers) diff --git a/src/libxrpl/shamap/SHAMapInnerNode.cpp b/src/libxrpl/shamap/SHAMapInnerNode.cpp index 91249d139e..e501561ee4 100644 --- a/src/libxrpl/shamap/SHAMapInnerNode.cpp +++ b/src/libxrpl/shamap/SHAMapInnerNode.cpp @@ -1,13 +1,30 @@ -#include +#include + +#include // IWYU pragma: keep +#include // IWYU pragma: keep +#include #include +#include #include #include +#include #include +#include #include -#include +#include #include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include + namespace xrpl { SHAMapInnerNode::SHAMapInnerNode(std::uint32_t cowid, std::uint8_t numAllocatedChildren) diff --git a/src/libxrpl/shamap/SHAMapLeafNode.cpp b/src/libxrpl/shamap/SHAMapLeafNode.cpp index 3776de3a35..58646503aa 100644 --- a/src/libxrpl/shamap/SHAMapLeafNode.cpp +++ b/src/libxrpl/shamap/SHAMapLeafNode.cpp @@ -1,5 +1,18 @@ #include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + namespace xrpl { SHAMapLeafNode::SHAMapLeafNode(boost::intrusive_ptr item, std::uint32_t cowid) diff --git a/src/libxrpl/shamap/SHAMapNodeID.cpp b/src/libxrpl/shamap/SHAMapNodeID.cpp index e5dbebc871..c2d93f20ee 100644 --- a/src/libxrpl/shamap/SHAMapNodeID.cpp +++ b/src/libxrpl/shamap/SHAMapNodeID.cpp @@ -1,8 +1,15 @@ -#include +#include + +#include +#include #include #include #include -#include + +#include +#include +#include +#include namespace xrpl { diff --git a/src/libxrpl/shamap/SHAMapSync.cpp b/src/libxrpl/shamap/SHAMapSync.cpp index 2074b43cb4..e7f3447642 100644 --- a/src/libxrpl/shamap/SHAMapSync.cpp +++ b/src/libxrpl/shamap/SHAMapSync.cpp @@ -1,8 +1,33 @@ +#include +#include +#include +#include +#include #include #include +#include +#include #include +#include +#include +#include #include +#include #include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace xrpl { @@ -58,7 +83,7 @@ SHAMap::visitNodes(std::function const& function) const if (pos != 15) { // save next position to resume at - stack.push(std::make_pair(pos + 1, std::move(node))); + stack.emplace(pos + 1, std::move(node)); } // descend to the child's first position @@ -107,7 +132,7 @@ SHAMap::visitDifferences( using StackEntry = std::pair; std::stack> stack; - stack.push({safe_downcast(root_.get()), SHAMapNodeID{}}); + stack.emplace(safe_downcast(root_.get()), SHAMapNodeID{}); while (!stack.empty()) { @@ -130,7 +155,7 @@ SHAMap::visitDifferences( if (next->isInner()) { if ((have == nullptr) || !have->hasInnerNode(childID, childHash)) - stack.push({safe_downcast(next), childID}); + stack.emplace(safe_downcast(next), childID); } else if ( (have == nullptr) || @@ -362,7 +387,7 @@ SHAMap::getMissingNodes(int max, SHAMapSyncFilter* filter) for (auto const& [innerNode, nodeId] : mn.resumes_) { if (!innerNode->isFullBelow(mn.generation_)) - mn.stack_.push(std::make_tuple(innerNode, nodeId, rand_int(255), 0, true)); + mn.stack_.emplace(innerNode, nodeId, rand_int(255), 0, true); } mn.resumes_.clear(); @@ -438,7 +463,7 @@ SHAMap::getNodeFat( // Add this node to the reply s.erase(); node->serializeForWire(s); - data.emplace_back(std::make_pair(nodeID, s.getData())); + data.emplace_back(nodeID, s.getData()); if (node->isInner()) { @@ -468,7 +493,7 @@ SHAMap::getNodeFat( // Just include this node s.erase(); childNode->serializeForWire(s); - data.emplace_back(std::make_pair(childID, s.getData())); + data.emplace_back(childID, s.getData()); } } } @@ -636,7 +661,7 @@ SHAMap::deepCompare(SHAMap& other) const // Intended for debug/test only std::stack> stack; - stack.push({root_.get(), other.root_.get()}); + stack.emplace(root_.get(), other.root_.get()); while (!stack.empty()) { @@ -690,7 +715,7 @@ SHAMap::deepCompare(SHAMap& other) const JLOG(journal_.warn()) << "unable to fetch inner node"; return false; } - stack.push({next, otherNext}); + stack.emplace(next, otherNext); } } } diff --git a/src/libxrpl/shamap/SHAMapTreeNode.cpp b/src/libxrpl/shamap/SHAMapTreeNode.cpp index 9497b4dc54..6fc7bd81e4 100644 --- a/src/libxrpl/shamap/SHAMapTreeNode.cpp +++ b/src/libxrpl/shamap/SHAMapTreeNode.cpp @@ -1,15 +1,28 @@ -#include +#include + +#include // IWYU pragma: keep +#include // IWYU pragma: keep +#include #include +#include #include #include #include +#include #include #include #include -#include +#include +#include #include #include +#include +#include +#include +#include +#include + namespace xrpl { intr_ptr::SharedPtr diff --git a/src/libxrpl/tx/ApplyContext.cpp b/src/libxrpl/tx/ApplyContext.cpp index d503643662..fa17574616 100644 --- a/src/libxrpl/tx/ApplyContext.cpp +++ b/src/libxrpl/tx/ApplyContext.cpp @@ -1,10 +1,28 @@ #include -// + #include +#include +#include #include +#include #include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include + namespace xrpl { ApplyContext::ApplyContext( @@ -104,7 +122,7 @@ ApplyContext::checkInvariantsHelper( // call each check's finalizer to see that it passes if (!std::all_of(finalizers.cbegin(), finalizers.cend(), [](auto const& b) { return b; })) { - JLOG(journal.fatal()) << "Transaction has failed one or more invariants: " + JLOG(journal.fatal()) << "Transaction has failed one or more global invariants: " << to_string(tx.getJson(JsonOptions::none)); return failInvariantCheck(result); @@ -112,7 +130,7 @@ ApplyContext::checkInvariantsHelper( } catch (std::exception const& ex) { - JLOG(journal.fatal()) << "Transaction caused an exception in an invariant" + JLOG(journal.fatal()) << "Transaction caused an exception in a global invariant" << ", ex: " << ex.what() << ", tx: " << to_string(tx.getJson(JsonOptions::none)); @@ -130,7 +148,7 @@ ApplyContext::checkInvariants(TER const result, XRPAmount const fee) "xrpl::ApplyContext::checkInvariants : is tesSUCCESS or tecCLAIM"); return checkInvariantsHelper( - result, fee, std::make_index_sequence::value>{}); + result, fee, std::make_index_sequence>{}); } } // namespace xrpl diff --git a/src/libxrpl/tx/SignerEntries.cpp b/src/libxrpl/tx/SignerEntries.cpp index 75659a7f04..a1883024d9 100644 --- a/src/libxrpl/tx/SignerEntries.cpp +++ b/src/libxrpl/tx/SignerEntries.cpp @@ -1,11 +1,20 @@ +#include + +#include #include +#include +#include +#include +#include #include #include #include -#include +#include #include #include +#include +#include namespace xrpl { diff --git a/src/libxrpl/tx/Transactor.cpp b/src/libxrpl/tx/Transactor.cpp index 7d2ac06fc1..9791ee4c9b 100644 --- a/src/libxrpl/tx/Transactor.cpp +++ b/src/libxrpl/tx/Transactor.cpp @@ -1,23 +1,54 @@ +#include + +#include +#include +#include +#include #include +#include +#include #include -#include -#include +#include +#include // IWYU pragma: keep +#include +#include #include #include +#include +#include #include #include +#include #include +#include #include +#include #include +#include +#include +#include +#include +#include +#include // IWYU pragma: keep #include +#include #include -#include +#include +#include #include +#include #include -#include #include -#include -#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include namespace xrpl { @@ -363,6 +394,13 @@ Transactor::checkFee(PreclaimContext const& ctx, XRPAmount baseFee) auto const balance = (*sle)[sfBalance].xrp(); + // NOTE: Because preclaim evaluates against a static readview, it + // does not reflect fee deductions from other transactions paid by + // the same account within the current ledger. + // As a result, if an account's balance is over-committed across multiple + // transactions, this check may pass optimistically. + // The fee shortfall will be handled by the Transactor::reset mechanism, + // which caps the fee to the remaining actual balance. if (balance < feePaid) { JLOG(ctx.j.trace()) << "Insufficient balance:" << " balance=" << to_string(balance) @@ -999,6 +1037,26 @@ removeDeletedTrustLines( } } +static void +removeDeletedMPTs(ApplyView& view, std::vector const& mpts, beast::Journal viewJ) +{ + // There could be at most two MPTs - one for each side of AMM pool + if (mpts.size() > 2) + { + JLOG(viewJ.error()) << "removeDeletedMPTs: deleted mpts exceed 2 " << mpts.size(); + return; + } + + for (auto const& index : mpts) + { + if (auto const sleState = view.peek({ltMPTOKEN, index}); sleState && + deleteAMMMPToken(view, sleState, (*sleState)[sfIssuer], viewJ) != tesSUCCESS) + { + JLOG(viewJ.error()) << "removeDeletedMPTs: failed to delete AMM MPT"; + } + } +} + /** Reset the context, discarding any changes made and adjust the fee. @param fee The transaction fee to be charged. @@ -1061,6 +1119,59 @@ Transactor::trapTransaction(uint256 txHash) const JLOG(j_.debug()) << "Transaction trapped: " << txHash; } +[[nodiscard]] TER +Transactor::checkTransactionInvariants(TER result, XRPAmount fee) +{ + try + { + // Phase 1: visit modified entries + ctx_.visit( + [this](uint256 const&, bool isDelete, SLE::const_ref before, SLE::const_ref after) { + this->visitInvariantEntry(isDelete, before, after); + }); + + // Phase 2: finalize + if (!this->finalizeInvariants(ctx_.tx, result, fee, ctx_.view(), ctx_.journal)) + { + JLOG(ctx_.journal.fatal()) << // + "Transaction has failed one or more transaction invariants, tx: " << // + to_string(ctx_.tx.getJson(JsonOptions::none)); + return tecINVARIANT_FAILED; + } + } + catch (std::exception const& ex) + { + JLOG(ctx_.journal.fatal()) << // + "Exception while checking transaction invariants: " << // + ex.what() << // + ", tx: " << // + to_string(ctx_.tx.getJson(JsonOptions::none)); + + return tecINVARIANT_FAILED; + } + + return result; +} + +[[nodiscard]] TER +Transactor::checkInvariants(TER result, XRPAmount fee) +{ + // Transaction invariants first (more specific). These check post-conditions of the specific + // transaction. If these fail, the transaction's core logic is wrong. + auto const txResult = checkTransactionInvariants(result, fee); + + // Protocol invariants second (broader). These check properties that must hold regardless of + // transaction type. + auto const protoResult = ctx_.checkInvariants(result, fee); + + // Fail if either check failed. tef (fatal) takes priority over tec. + if (protoResult == tefINVARIANT_FAILED) + return tefINVARIANT_FAILED; + if (txResult == tecINVARIANT_FAILED || protoResult == tecINVARIANT_FAILED) + return tecINVARIANT_FAILED; + + return result; +} //------------------------------------------------------------------------------ ApplyResult Transactor::operator()() @@ -1137,19 +1248,21 @@ Transactor::operator()() // when transactions fail with a `tec` code. std::vector removedOffers; std::vector removedTrustLines; + std::vector removedMPTs; std::vector expiredNFTokenOffers; std::vector expiredCredentials; bool const doOffers = ((result == tecOVERSIZE) || (result == tecKILLED)); - bool const doLines = (result == tecINCOMPLETE); + bool const doLinesOrMPTs = (result == tecINCOMPLETE); bool const doNFTokenOffers = (result == tecEXPIRED); bool const doCredentials = (result == tecEXPIRED); - if (doOffers || doLines || doNFTokenOffers || doCredentials) + if (doOffers || doLinesOrMPTs || doNFTokenOffers || doCredentials) { ctx_.visit([doOffers, &removedOffers, - doLines, + doLinesOrMPTs, &removedTrustLines, + &removedMPTs, doNFTokenOffers, &expiredNFTokenOffers, doCredentials, @@ -1171,10 +1284,17 @@ Transactor::operator()() removedOffers.push_back(index); } - if (doLines && before && after && (before->getType() == ltRIPPLE_STATE)) + if (doLinesOrMPTs && before && after) { // Removal of obsolete AMM trust line - removedTrustLines.push_back(index); + if (before->getType() == ltRIPPLE_STATE) + { + removedTrustLines.push_back(index); + } + else if (before->getType() == ltMPTOKEN) + { + removedMPTs.push_back(index); + } } if (doNFTokenOffers && before && after && @@ -1212,6 +1332,7 @@ Transactor::operator()() { removeDeletedTrustLines( view(), removedTrustLines, ctx_.registry.get().getJournal("View")); + removeDeletedMPTs(view(), removedMPTs, ctx_.registry.get().getJournal("View")); } if (result == tecEXPIRED) @@ -1227,20 +1348,20 @@ Transactor::operator()() { // Check invariants: if `tecINVARIANT_FAILED` is not returned, we can // proceed to apply the tx - result = ctx_.checkInvariants(result, fee); - + result = checkInvariants(result, fee); if (result == tecINVARIANT_FAILED) { - // if invariants checking failed again, reset the context and - // attempt to only claim a fee. + // Reset to fee-claim only auto const resetResult = reset(fee); if (!isTesSuccess(resetResult.first)) result = resetResult.first; fee = resetResult.second; - // Check invariants again to ensure the fee claiming doesn't - // violate invariants. + // Check invariants again to ensure the fee claiming doesn't violate + // invariants. After reset, only protocol invariants are re-checked. + // Transaction invariants are not meaningful here — the transaction's + // effects have been rolled back. if (isTesSuccess(result) || isTecClaim(result)) result = ctx_.checkInvariants(result, fee); } diff --git a/src/libxrpl/tx/apply.cpp b/src/libxrpl/tx/apply.cpp index c8016002c2..65c9649035 100644 --- a/src/libxrpl/tx/apply.cpp +++ b/src/libxrpl/tx/apply.cpp @@ -1,11 +1,28 @@ +#include + #include +#include +#include +#include #include #include +#include +#include #include +#include +#include +#include +#include +#include +#include #include -#include +#include #include +#include +#include +#include + namespace xrpl { // These are the same flags defined as HashRouterFlags::PRIVATE1-4 in diff --git a/src/libxrpl/tx/applySteps.cpp b/src/libxrpl/tx/applySteps.cpp index 447ba685cc..eb058e5ca6 100644 --- a/src/libxrpl/tx/applySteps.cpp +++ b/src/libxrpl/tx/applySteps.cpp @@ -1,4 +1,27 @@ #include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include #pragma push_macro("TRANSACTION") #undef TRANSACTION @@ -17,8 +40,6 @@ #include #include -#include - namespace xrpl { namespace { @@ -95,7 +116,6 @@ with_txn_type(Rules const& rules, TxType txnType, F&& f) // For Transactor::Normal // -// Current formatter for rippled is based on clang-10, which does not handle `requires` clauses template requires(T::ConsequencesFactory == Transactor::Normal) TxConsequences @@ -293,6 +313,18 @@ invoke_apply(ApplyContext& ctx) } } +// Test-only factory — not part of the public API. +// The returned Transactor holds a raw reference to ctx; the caller must ensure +// the ApplyContext outlives the Transactor. +std::unique_ptr +makeTransactor(ApplyContext& ctx) +{ + return with_txn_type( + ctx.view().rules(), ctx.tx.getTxnType(), [&]() -> std::unique_ptr { + return std::make_unique(ctx); + }); +} + PreflightResult preflight( ServiceRegistry& registry, diff --git a/src/libxrpl/tx/invariants/AMMInvariant.cpp b/src/libxrpl/tx/invariants/AMMInvariant.cpp index 96df97016f..3cc888dea2 100644 --- a/src/libxrpl/tx/invariants/AMMInvariant.cpp +++ b/src/libxrpl/tx/invariants/AMMInvariant.cpp @@ -1,10 +1,26 @@ #include -// + #include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include -#include -#include +#include + +#include +#include namespace xrpl { @@ -128,15 +144,16 @@ ValidAMM::finalizeCreate( auto const [amount, amount2] = ammPoolHolds( view, *ammAccount_, - tx[sfAmount].get(), - tx[sfAmount2].get(), + tx[sfAmount].asset(), + tx[sfAmount2].asset(), fhIGNORE_FREEZE, + ahIGNORE_AUTH, 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_) + ammLPTokens(amount, amount2, lptAMMBalanceAfter_->get()) != *lptAMMBalanceAfter_) { JLOG(j.error()) << "AMMCreate invariant failed: " << amount << " " << amount2 << " " << *lptAMMBalanceAfter_; @@ -188,12 +205,7 @@ ValidAMM::generalInvariant( beast::Journal const& j) const { auto const [amount, amount2] = ammPoolHolds( - view, - *ammAccount_, - tx[sfAsset].get(), - tx[sfAsset2].get(), - fhIGNORE_FREEZE, - j); + view, *ammAccount_, tx[sfAsset], tx[sfAsset2], fhIGNORE_FREEZE, ahIGNORE_AUTH, j); // Deposit and Withdrawal invariant: // sqrt(amount * amount2) >= LPTokens // all balances are greater than zero diff --git a/src/libxrpl/tx/invariants/FreezeInvariant.cpp b/src/libxrpl/tx/invariants/FreezeInvariant.cpp index 0048da7a84..bf9ef81396 100644 --- a/src/libxrpl/tx/invariants/FreezeInvariant.cpp +++ b/src/libxrpl/tx/invariants/FreezeInvariant.cpp @@ -1,11 +1,24 @@ #include -// + #include +#include #include +#include +#include #include -#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include + namespace xrpl { void @@ -68,7 +81,7 @@ TransfersNotFrozen::finalize( { 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. + // just in case so xrpld doesn't crash in release. if (!issuerSle) { // The comment above starting with "assert(enforce)" explains this @@ -172,13 +185,17 @@ TransfersNotFrozen::recordBalanceChanges( STAmount const& balanceChange) { auto const balanceChangeSign = balanceChange.signum(); - auto const currency = after->at(sfBalance).getCurrency(); + auto const currency = after->at(sfBalance).get().currency; // Change from low account's perspective, which is trust line default - recordBalance({currency, after->at(sfHighLimit).getIssuer()}, {after, balanceChangeSign}); + recordBalance( + {currency, after->at(sfHighLimit).getIssuer()}, + {.line = after, .balanceChangeSign = balanceChangeSign}); // Change from high account's perspective, which reverses the sign. - recordBalance({currency, after->at(sfLowLimit).getIssuer()}, {after, -balanceChangeSign}); + recordBalance( + {currency, after->at(sfLowLimit).getIssuer()}, + {.line = after, .balanceChangeSign = -balanceChangeSign}); } std::shared_ptr diff --git a/src/libxrpl/tx/invariants/InvariantCheck.cpp b/src/libxrpl/tx/invariants/InvariantCheck.cpp index bf0f65f000..f2570d14e8 100644 --- a/src/libxrpl/tx/invariants/InvariantCheck.cpp +++ b/src/libxrpl/tx/invariants/InvariantCheck.cpp @@ -1,22 +1,40 @@ #include -// + #include +#include +#include +#include #include -#include +#include #include -#include +#include +#include #include #include +#include +#include #include +#include +#include #include #include -#include +#include +#include // IWYU pragma: keep +#include #include +#include #include +#include +#include #include +#include #include +#include +#include #include +#include +#include namespace xrpl { @@ -284,25 +302,29 @@ NoZeroEscrow::visitEntry( } else { - // IOU case - if (amount.holds()) - { - if (amount <= beast::zero) - return true; + return amount.asset().visit( + [&](Issue const& issue) { + // IOU case + if (amount <= beast::zero) + return true; - if (badCurrency() == amount.getCurrency()) - return true; - } + if (badCurrency() == issue.currency) + return true; - // MPT case - if (amount.holds()) - { - if (amount <= beast::zero) - return true; + return false; + } - if (amount.mpt() > MPTAmount{maxMPTokenAmount}) - return true; // LCOV_EXCL_LINE - } + // MPT case + , + [&](MPTIssue const&) { + if (amount <= beast::zero) + return true; + + if (amount.mpt() > MPTAmount{maxMPTokenAmount}) + return true; // LCOV_EXCL_LINE + + return false; + }); } return false; }; @@ -600,8 +622,8 @@ NoXRPTrustLines::visitEntry( // checking the issue directly here instead of // relying on .native() just in case native somehow // were systematically incorrect - xrpTrustLine_ = after->getFieldAmount(sfLowLimit).issue() == xrpIssue() || - after->getFieldAmount(sfHighLimit).issue() == xrpIssue(); + xrpTrustLine_ = after->getFieldAmount(sfLowLimit).asset() == xrpIssue() || + after->getFieldAmount(sfHighLimit).asset() == xrpIssue(); } } @@ -774,17 +796,23 @@ ValidClawback::finalize( return false; } - if (trustlinesChanged == 1) + bool const mptV2Enabled = view.rules().enabled(featureMPTokensV2); + if (trustlinesChanged == 1 || (mptV2Enabled && mptokensChanged == 1)) { AccountID const issuer = tx.getAccountID(sfAccount); STAmount const& amount = tx.getFieldAmount(sfAmount); AccountID const& holder = amount.getIssuer(); - STAmount const holderBalance = - accountHolds(view, holder, amount.getCurrency(), issuer, fhIGNORE_FREEZE, j); + STAmount const holderBalance = amount.asset().visit( + [&](Issue const& issue) { + return accountHolds(view, holder, issue.currency, issuer, fhIGNORE_FREEZE, j); + }, + [&](MPTIssue const& issue) { + return accountHolds(view, issuer, issue, fhIGNORE_FREEZE, ahIGNORE_AUTH, j); + }); if (holderBalance.signum() < 0) { - JLOG(j.fatal()) << "Invariant failed: trustline balance is negative"; + JLOG(j.fatal()) << "Invariant failed: trustline or MPT balance is negative"; return false; } } diff --git a/src/libxrpl/tx/invariants/LoanBrokerInvariant.cpp b/src/libxrpl/tx/invariants/LoanBrokerInvariant.cpp index 2bc9e622ad..b72f821f50 100644 --- a/src/libxrpl/tx/invariants/LoanBrokerInvariant.cpp +++ b/src/libxrpl/tx/invariants/LoanBrokerInvariant.cpp @@ -1,13 +1,21 @@ #include -// + #include -#include -#include -#include +#include +#include +#include +#include #include #include -#include +#include +#include +#include // IWYU pragma: keep +#include +#include #include +#include + +#include namespace xrpl { @@ -175,18 +183,32 @@ ValidLoanBroker::finalize( 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)) + auto const pseudoBalance = accountHolds( + view, + after->at(sfAccount), + vaultAsset, + FreezeHandling::fhIGNORE_FREEZE, + AuthHandling::ahIGNORE_AUTH, + j); + if (after->at(sfCoverAvailable) < pseudoBalance) { JLOG(j.fatal()) << "Invariant failed: Loan Broker cover available " "is less than pseudo-account asset balance"; return false; } + + if (view.rules().enabled(fixSecurity3_1_3)) + { + // Don't check the balance when LoanBroker is deleted, + // sfCoverAvailable is not zeroed + if (tx.getTxnType() != ttLOAN_BROKER_DELETE && + after->at(sfCoverAvailable) > pseudoBalance) + { + JLOG(j.fatal()) << "Invariant failed: Loan Broker cover available is greater " + "than pseudo-account asset balance"; + return false; + } + } } return true; } diff --git a/src/libxrpl/tx/invariants/LoanInvariant.cpp b/src/libxrpl/tx/invariants/LoanInvariant.cpp index 6ce1261612..7aea36296e 100644 --- a/src/libxrpl/tx/invariants/LoanInvariant.cpp +++ b/src/libxrpl/tx/invariants/LoanInvariant.cpp @@ -1,9 +1,18 @@ #include -// + #include -#include +#include +#include +#include #include -#include +#include +#include +#include // IWYU pragma: keep +#include +#include +#include + +#include namespace xrpl { diff --git a/src/libxrpl/tx/invariants/MPTInvariant.cpp b/src/libxrpl/tx/invariants/MPTInvariant.cpp index 7b76e70c86..e010b79b87 100644 --- a/src/libxrpl/tx/invariants/MPTInvariant.cpp +++ b/src/libxrpl/tx/invariants/MPTInvariant.cpp @@ -1,13 +1,26 @@ #include -// + #include +#include #include +#include +#include #include #include +#include #include +#include +#include +#include +#include +#include #include +#include #include +#include +#include + namespace xrpl { void @@ -52,9 +65,10 @@ ValidMPTIssuance::finalize( ReadView const& view, beast::Journal const& j) const { - if (isTesSuccess(result)) + auto const& rules = view.rules(); + bool const mptV2Enabled = rules.enabled(featureMPTokensV2); + if (isTesSuccess(result) || (mptV2Enabled && result == tecINCOMPLETE)) { - auto const& rules = view.rules(); [[maybe_unused]] bool const enforceCreatedByIssuer = rules.enabled(featureSingleAssetVault) || rules.enabled(featureLendingProtocol); @@ -112,12 +126,12 @@ ValidMPTIssuance::finalize( return mptIssuancesCreated_ == 0 && mptIssuancesDeleted_ == 1; } - bool const lendingProtocolEnabled = view.rules().enabled(featureLendingProtocol); + bool const lendingProtocolEnabled = 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); + (rules.enabled(featureSingleAssetVault) || lendingProtocolEnabled); if (hasPrivilege(tx, mustAuthorizeMPT | mayAuthorizeMPT) || enforceEscrowFinish) { bool const submittedByIssuer = tx.isFieldPresent(sfHolder); @@ -134,19 +148,42 @@ ValidMPTIssuance::finalize( "succeeded but deleted issuances"; return false; } - if (lendingProtocolEnabled && mptokensCreated_ + mptokensDeleted_ > 1) + if (mptV2Enabled && hasPrivilege(tx, mayAuthorizeMPT) && + (txnType == ttAMM_WITHDRAW || txnType == ttAMM_CLAWBACK)) + { + if (submittedByIssuer && txnType == ttAMM_WITHDRAW && mptokensCreated_ > 0) + { + JLOG(j.fatal()) << "Invariant failed: MPT authorize " + "submitted by issuer succeeded " + "but created bad number of mptokens"; + return false; + } + // At most one MPToken may be created on withdraw/clawback since: + // - Liquidity Provider must have at least one token in order + // participate in AMM pool liquidity. + // - At most two MPTokens may be deleted if AMM pool, which has exactly + // two tokens, is empty after withdraw/clawback. + if (mptokensCreated_ > 1 || mptokensDeleted_ > 2) + { + JLOG(j.fatal()) << "Invariant failed: MPT authorize succeeded " + "but created/deleted bad number of mptokens"; + return false; + } + } + else if (lendingProtocolEnabled && (mptokensCreated_ + mptokensDeleted_) > 1) { JLOG(j.fatal()) << "Invariant failed: MPT authorize succeeded " "but created/deleted bad number mptokens"; return false; } - if (submittedByIssuer && (mptokensCreated_ > 0 || mptokensDeleted_ > 0)) + else if (submittedByIssuer && (mptokensCreated_ > 0 || mptokensDeleted_ > 0)) { JLOG(j.fatal()) << "Invariant failed: MPT authorize submitted by issuer " "succeeded but created/deleted mptokens"; return false; } - if (!submittedByIssuer && hasPrivilege(tx, mustAuthorizeMPT) && + else if ( + !submittedByIssuer && hasPrivilege(tx, mustAuthorizeMPT) && (mptokensCreated_ + mptokensDeleted_ != 1)) { // if the holder submitted this tx, then a mptoken must be @@ -158,6 +195,52 @@ ValidMPTIssuance::finalize( return true; } + + if (hasPrivilege(tx, mayCreateMPT)) + { + bool const submittedByIssuer = tx.isFieldPresent(sfHolder); + + if (mptIssuancesCreated_ > 0) + { + JLOG(j.fatal()) << "Invariant failed: MPT authorize " + "succeeded but created MPT issuances"; + return false; + } + if (mptIssuancesDeleted_ > 0) + { + JLOG(j.fatal()) << "Invariant failed: MPT authorize " + "succeeded but deleted issuances"; + return false; + } + if (mptokensDeleted_ > 0) + { + JLOG(j.fatal()) << "Invariant failed: MPT authorize " + "succeeded but deleted MPTokens"; + return false; + } + // AMMCreate may auto-create up to two MPT objects: + // - one per asset side in an MPT/MPT AMM, or one in an IOU/MPT AMM. + // CheckCash may auto-create at most one MPT object for the receiver. + if ((txnType == ttAMM_CREATE && mptokensCreated_ > 2) || + (txnType == ttCHECK_CASH && mptokensCreated_ > 1)) + { + JLOG(j.fatal()) << "Invariant failed: MPT authorize " + "succeeded but created bad number of mptokens"; + return false; + } + if (submittedByIssuer) + { + JLOG(j.fatal()) << "Invariant failed: MPT authorize submitted by issuer " + "succeeded but created mptokens"; + return false; + } + + // Offer crossing or payment may consume multiple offers + // where takerPays is MPT amount. If the offer owner doesn't + // own MPT then MPT is created automatically. + return true; + } + if (txnType == ttESCROW_FINISH) { // ttESCROW_FINISH may authorize an MPT, but it can't have the @@ -168,8 +251,9 @@ ValidMPTIssuance::finalize( return true; } - if (hasPrivilege(tx, mayDeleteMPT) && mptokensDeleted_ == 1 && mptokensCreated_ == 0 && - mptIssuancesCreated_ == 0 && mptIssuancesDeleted_ == 0) + if (hasPrivilege(tx, mayDeleteMPT) && + ((txnType == ttAMM_DELETE && mptokensDeleted_ <= 2) || mptokensDeleted_ == 1) && + mptokensCreated_ == 0 && mptIssuancesCreated_ == 0 && mptIssuancesDeleted_ == 0) return true; } @@ -194,4 +278,107 @@ ValidMPTIssuance::finalize( mptokensDeleted_ == 0; } +void +ValidMPTPayment::visitEntry( + bool, + std::shared_ptr const& before, + std::shared_ptr const& after) +{ + if (overflow_) + return; + + auto makeKey = [](SLE const& sle) { + if (sle.getType() == ltMPTOKEN_ISSUANCE) + return makeMptID(sle[sfSequence], sle[sfIssuer]); + return sle[sfMPTokenIssuanceID]; + }; + + auto update = [&](SLE const& sle, Order order) -> bool { + auto const type = sle.getType(); + if (type == ltMPTOKEN_ISSUANCE) + { + auto const outstanding = sle[sfOutstandingAmount]; + if (outstanding > maxMPTokenAmount) + { + overflow_ = true; + return false; + } + data_[makeKey(sle)].outstanding[order] = outstanding; + } + else if (type == ltMPTOKEN) + { + auto const mptAmt = sle[sfMPTAmount]; + auto const lockedAmt = sle[~sfLockedAmount].value_or(0); + if (mptAmt > maxMPTokenAmount || lockedAmt > maxMPTokenAmount || + lockedAmt > (maxMPTokenAmount - mptAmt)) + { + overflow_ = true; + return false; + } + auto const res = static_cast(mptAmt + lockedAmt); + // subtract before from after + if (order == Before) + { + data_[makeKey(sle)].mptAmount -= res; + } + else + { + data_[makeKey(sle)].mptAmount += res; + } + } + return true; + }; + + if (before && !update(*before, Before)) + return; + + if (after) + { + if (after->getType() == ltMPTOKEN_ISSUANCE) + { + overflow_ = (*after)[sfOutstandingAmount] > maxMPTAmount(*after); + } + if (!update(*after, After)) + return; + } +} + +bool +ValidMPTPayment::finalize( + STTx const& tx, + TER const result, + XRPAmount const, + ReadView const& view, + beast::Journal const& j) +{ + if (isTesSuccess(result)) + { + bool const enforce = view.rules().enabled(featureMPTokensV2); + if (overflow_) + { + JLOG(j.fatal()) << "Invariant failed: OutstandingAmount overflow"; + return !enforce; + } + + auto const signedMax = static_cast(maxMPTokenAmount); + for (auto const& [id, data] : data_) + { + (void)id; + bool const addOverflows = + (data.mptAmount > 0 && data.outstanding[Before] > (signedMax - data.mptAmount)) || + (data.mptAmount < 0 && data.outstanding[Before] < (-signedMax - data.mptAmount)); + if (addOverflows || + data.outstanding[After] != (data.outstanding[Before] + data.mptAmount)) + { + JLOG(j.fatal()) << "Invariant failed: invalid OutstandingAmount balance " + << data.outstanding[Before] << " " << data.outstanding[After] << " " + << data.mptAmount; + return !enforce; + } + } + } + + return true; +} + } // namespace xrpl diff --git a/src/libxrpl/tx/invariants/NFTInvariant.cpp b/src/libxrpl/tx/invariants/NFTInvariant.cpp index cf00dc9290..90511b6d04 100644 --- a/src/libxrpl/tx/invariants/NFTInvariant.cpp +++ b/src/libxrpl/tx/invariants/NFTInvariant.cpp @@ -1,12 +1,27 @@ #include -// + #include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include #include #include -#include + +#include +#include +#include namespace xrpl { diff --git a/src/libxrpl/tx/invariants/PermissionedDEXInvariant.cpp b/src/libxrpl/tx/invariants/PermissionedDEXInvariant.cpp index e932a6ba09..6466812743 100644 --- a/src/libxrpl/tx/invariants/PermissionedDEXInvariant.cpp +++ b/src/libxrpl/tx/invariants/PermissionedDEXInvariant.cpp @@ -1,11 +1,20 @@ #include -// + #include -#include +#include +#include +#include #include #include +#include #include +#include +#include +#include #include +#include + +#include namespace xrpl { @@ -32,11 +41,17 @@ ValidPermissionedDEX::visitEntry( regularOffers_ = true; } - // if a hybrid offer is missing domain or additional book, there's - // something wrong + // pre-fixSecurity3_1_3: hybrid offer missing domain, missing + // sfAdditionalBooks, or sfAdditionalBooks has more than one entry if (after->isFlag(lsfHybrid) && (!after->isFieldPresent(sfDomainID) || !after->isFieldPresent(sfAdditionalBooks) || after->getFieldArray(sfAdditionalBooks).size() > 1)) + badHybridsOld_ = true; + + // post-fixSecurity3_1_3: same as above but also catches size == 0 + if (after->isFlag(lsfHybrid) && + (!after->isFieldPresent(sfDomainID) || !after->isFieldPresent(sfAdditionalBooks) || + after->getFieldArray(sfAdditionalBooks).size() != 1)) badHybrids_ = true; } } @@ -55,7 +70,8 @@ ValidPermissionedDEX::finalize( // For each offercreate transaction, check if // permissioned offers are valid - if (txType == ttOFFER_CREATE && badHybrids_) + bool const isMalformed = view.rules().enabled(fixSecurity3_1_3) ? badHybrids_ : badHybridsOld_; + if (txType == ttOFFER_CREATE && isMalformed) { JLOG(j.fatal()) << "Invariant failed: hybrid offer is malformed"; return false; diff --git a/src/libxrpl/tx/invariants/PermissionedDomainInvariant.cpp b/src/libxrpl/tx/invariants/PermissionedDomainInvariant.cpp index 7365fc7b1a..3c6f67a8d6 100644 --- a/src/libxrpl/tx/invariants/PermissionedDomainInvariant.cpp +++ b/src/libxrpl/tx/invariants/PermissionedDomainInvariant.cpp @@ -1,10 +1,22 @@ #include -// -#include + +#include +#include +#include #include #include +#include +#include +#include #include +#include +#include +#include #include +#include + +#include +#include namespace xrpl { @@ -23,7 +35,11 @@ ValidPermissionedDomain::visitEntry( auto const& credentials = sle->getFieldArray(sfAcceptedCredentials); auto const sorted = credentials::makeSorted(credentials); - SleStatus ss{credentials.size(), false, !sorted.empty(), isDel}; + SleStatus ss{ + .credentialsSize_ = credentials.size(), + .isSorted_ = false, + .isUnique_ = !sorted.empty(), + .isDelete_ = isDel}; // If array have duplicates then all the other checks are invalid if (ss.isUnique_) diff --git a/src/libxrpl/tx/invariants/VaultInvariant.cpp b/src/libxrpl/tx/invariants/VaultInvariant.cpp index c6b3295569..c0d082e14e 100644 --- a/src/libxrpl/tx/invariants/VaultInvariant.cpp +++ b/src/libxrpl/tx/invariants/VaultInvariant.cpp @@ -1,18 +1,33 @@ #include -// + #include +#include +#include #include -#include +#include #include #include #include +#include #include #include #include -#include +#include +#include +#include // IWYU pragma: keep +#include +#include #include +#include #include +#include +#include +#include +#include +#include +#include + namespace xrpl { ValidVault::Vault @@ -61,10 +76,12 @@ ValidVault::visitEntry( "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{}; + // state (zero if created) and "after" state (zero if destroyed), and + // preserves value scale (exponent) to round values to the same scale during + // validation. It is used to validate that the change in account + // balances matches the change in vault balances, stored to deltas_ at the + // end of this function. + DeltaInfo balanceDelta{.delta = numZero, .scale = std::nullopt}; std::int8_t sign = 0; if (before) @@ -78,18 +95,34 @@ ValidVault::visitEntry( // At this moment we have no way of telling if this object holds // vault shares or something else. Save it for finalize. beforeMPTs_.push_back(Shares::make(*before)); - balanceDelta = static_cast(before->getFieldU64(sfOutstandingAmount)); + balanceDelta.delta = + static_cast(before->getFieldU64(sfOutstandingAmount)); + // MPTs are ints, so the scale is always 0. + balanceDelta.scale = 0; sign = 1; break; case ltMPTOKEN: - balanceDelta = static_cast(before->getFieldU64(sfMPTAmount)); + balanceDelta.delta = static_cast(before->getFieldU64(sfMPTAmount)); + // MPTs are ints, so the scale is always 0. + balanceDelta.scale = 0; sign = -1; break; case ltACCOUNT_ROOT: - case ltRIPPLE_STATE: - balanceDelta = before->getFieldAmount(sfBalance); + balanceDelta.delta = before->getFieldAmount(sfBalance); + // Account balance is XRP, which is an int, so the scale is + // always 0. + balanceDelta.scale = 0; sign = -1; break; + case ltRIPPLE_STATE: { + auto const amount = before->getFieldAmount(sfBalance); + balanceDelta.delta = amount; + // Trust Line balances are STAmounts, so we can use the exponent + // directly to get the scale. + balanceDelta.scale = amount.exponent(); + sign = -1; + break; + } default:; } } @@ -105,19 +138,36 @@ ValidVault::visitEntry( // 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 -= + balanceDelta.delta -= Number(static_cast(after->getFieldU64(sfOutstandingAmount))); + // MPTs are ints, so the scale is always 0. + balanceDelta.scale = 0; sign = 1; break; case ltMPTOKEN: - balanceDelta -= Number(static_cast(after->getFieldU64(sfMPTAmount))); + balanceDelta.delta -= + Number(static_cast(after->getFieldU64(sfMPTAmount))); + // MPTs are ints, so the scale is always 0. + balanceDelta.scale = 0; sign = -1; break; case ltACCOUNT_ROOT: - case ltRIPPLE_STATE: - balanceDelta -= Number(after->getFieldAmount(sfBalance)); + balanceDelta.delta -= Number(after->getFieldAmount(sfBalance)); + // Account balance is XRP, which is an int, so the scale is + // always 0. + balanceDelta.scale = 0; sign = -1; break; + case ltRIPPLE_STATE: { + auto const amount = after->getFieldAmount(sfBalance); + balanceDelta.delta -= Number(amount); + // Trust Line balances are STAmounts, so we can use the exponent + // directly to get the scale. + if (amount.exponent() > balanceDelta.scale) + balanceDelta.scale = amount.exponent(); + sign = -1; + break; + } default:; } } @@ -129,7 +179,11 @@ ValidVault::visitEntry( // transferred to the account. We intentionally do not compare balanceDelta // against zero, to avoid missing such updates. if (sign != 0) - deltas_[key] = balanceDelta * sign; + { + XRPL_ASSERT_PARTS(balanceDelta.scale, "xrpl::ValidVault::visitEntry", "scale initialized"); + balanceDelta.delta *= sign; + deltas_[key] = balanceDelta; + } } bool @@ -391,13 +445,13 @@ ValidVault::finalize( } auto const& vaultAsset = afterVault.asset; - auto const deltaAssets = [&](AccountID const& id) -> std::optional { + auto const deltaAssets = [&](AccountID const& id) -> std::optional { auto const get = // - [&](auto const& it, std::int8_t sign = 1) -> std::optional { + [&](auto const& it, std::int8_t sign = 1) -> std::optional { if (it == deltas_.end()) return std::nullopt; - return it->second * sign; + return DeltaInfo{it->second.delta * sign, it->second.scale}; }; return std::visit( @@ -416,7 +470,7 @@ ValidVault::finalize( }, vaultAsset.value()); }; - auto const deltaAssetsTxAccount = [&]() -> std::optional { + auto const deltaAssetsTxAccount = [&]() -> std::optional { auto ret = deltaAssets(tx[sfAccount]); // Nothing returned or not XRP transaction if (!ret.has_value() || !vaultAsset.native()) @@ -427,20 +481,20 @@ ValidVault::finalize( delegate.has_value() && *delegate != tx[sfAccount]) return ret; - *ret += fee.drops(); - if (*ret == zero) + ret->delta += fee.drops(); + if (ret->delta == zero) return std::nullopt; return ret; }; - auto const deltaShares = [&](AccountID const& id) -> std::optional { + auto const deltaShares = [&](AccountID const& id) -> std::optional { auto const it = [&]() { if (id == afterVault.pseudoId) return deltas_.find(keylet::mptIssuance(afterVault.shareMPTID).key); return deltas_.find(keylet::mptoken(afterVault.shareMPTID, id).key); }(); - return it != deltas_.end() ? std::optional(it->second) : std::nullopt; + return it != deltas_.end() ? std::optional(it->second) : std::nullopt; }; auto const vaultHoldsNoAssets = [&](Vault const& vault) { @@ -567,16 +621,30 @@ ValidVault::finalize( !beforeVault_.empty(), "xrpl::ValidVault::finalize : deposit updated a vault"); auto const& beforeVault = beforeVault_[0]; - auto const vaultDeltaAssets = deltaAssets(afterVault.pseudoId); - - if (!vaultDeltaAssets) + auto const maybeVaultDeltaAssets = deltaAssets(afterVault.pseudoId); + if (!maybeVaultDeltaAssets) { JLOG(j.fatal()) << // "Invariant failed: deposit must change vault balance"; return false; // That's all we can do } - if (*vaultDeltaAssets > tx[sfAmount]) + // Get the coarsest scale to round calculations to + auto const totalDelta = DeltaInfo::makeDelta( + beforeVault.assetsTotal, afterVault.assetsTotal, vaultAsset); + auto const availableDelta = DeltaInfo::makeDelta( + beforeVault.assetsAvailable, afterVault.assetsAvailable, vaultAsset); + auto const minScale = computeCoarsestScale({ + *maybeVaultDeltaAssets, + totalDelta, + availableDelta, + }); + + auto const vaultDeltaAssets = + roundToAsset(vaultAsset, maybeVaultDeltaAssets->delta, minScale); + auto const txAmount = roundToAsset(vaultAsset, tx[sfAmount], minScale); + + if (vaultDeltaAssets > txAmount) { JLOG(j.fatal()) << // "Invariant failed: deposit must not change vault " @@ -584,7 +652,7 @@ ValidVault::finalize( result = false; } - if (*vaultDeltaAssets <= zero) + if (vaultDeltaAssets <= zero) { JLOG(j.fatal()) << // "Invariant failed: deposit must increase vault balance"; @@ -601,16 +669,23 @@ ValidVault::finalize( if (!issuerDeposit) { - auto const accountDeltaAssets = deltaAssetsTxAccount(); - if (!accountDeltaAssets) + auto const maybeAccDeltaAssets = deltaAssetsTxAccount(); + if (!maybeAccDeltaAssets) { JLOG(j.fatal()) << // "Invariant failed: deposit must change depositor " "balance"; return false; } + auto const localMinScale = + std::max(minScale, computeCoarsestScale({*maybeAccDeltaAssets})); - if (*accountDeltaAssets >= zero) + auto const accountDeltaAssets = + roundToAsset(vaultAsset, maybeAccDeltaAssets->delta, localMinScale); + auto const localVaultDeltaAssets = + roundToAsset(vaultAsset, vaultDeltaAssets, localMinScale); + + if (accountDeltaAssets >= zero) { JLOG(j.fatal()) << // "Invariant failed: deposit must decrease depositor " @@ -618,7 +693,7 @@ ValidVault::finalize( result = false; } - if (*accountDeltaAssets * -1 != *vaultDeltaAssets) + if (localVaultDeltaAssets * -1 != accountDeltaAssets) { JLOG(j.fatal()) << // "Invariant failed: deposit must change vault and " @@ -636,16 +711,17 @@ ValidVault::finalize( result = false; } - auto const accountDeltaShares = deltaShares(tx[sfAccount]); - if (!accountDeltaShares) + auto const maybeAccDeltaShares = deltaShares(tx[sfAccount]); + if (!maybeAccDeltaShares) { JLOG(j.fatal()) << // "Invariant failed: deposit must change depositor " "shares"; return false; // That's all we can do } - - if (*accountDeltaShares <= zero) + // We don't need to round shares, they are integral MPT + auto const& accountDeltaShares = *maybeAccDeltaShares; + if (accountDeltaShares.delta <= zero) { JLOG(j.fatal()) << // "Invariant failed: deposit must increase depositor " @@ -653,15 +729,17 @@ ValidVault::finalize( result = false; } - auto const vaultDeltaShares = deltaShares(afterVault.pseudoId); - if (!vaultDeltaShares || *vaultDeltaShares == zero) + auto const maybeVaultDeltaShares = deltaShares(afterVault.pseudoId); + if (!maybeVaultDeltaShares || maybeVaultDeltaShares->delta == zero) { JLOG(j.fatal()) << // "Invariant failed: deposit must change vault shares"; return false; // That's all we can do } - if (*vaultDeltaShares * -1 != *accountDeltaShares) + // We don't need to round shares, they are integral MPT + auto const& vaultDeltaShares = *maybeVaultDeltaShares; + if (vaultDeltaShares.delta * -1 != accountDeltaShares.delta) { JLOG(j.fatal()) << // "Invariant failed: deposit must change depositor and " @@ -669,13 +747,18 @@ ValidVault::finalize( result = false; } - if (beforeVault.assetsTotal + *vaultDeltaAssets != afterVault.assetsTotal) + auto const assetTotalDelta = roundToAsset( + vaultAsset, afterVault.assetsTotal - beforeVault.assetsTotal, minScale); + if (assetTotalDelta != vaultDeltaAssets) { JLOG(j.fatal()) << "Invariant failed: deposit and assets " "outstanding must add up"; result = false; } - if (beforeVault.assetsAvailable + *vaultDeltaAssets != afterVault.assetsAvailable) + + auto const assetAvailableDelta = roundToAsset( + vaultAsset, afterVault.assetsAvailable - beforeVault.assetsAvailable, minScale); + if (assetAvailableDelta != vaultDeltaAssets) { JLOG(j.fatal()) << "Invariant failed: deposit and assets " "available must add up"; @@ -693,16 +776,27 @@ ValidVault::finalize( "vault"); auto const& beforeVault = beforeVault_[0]; - auto const vaultDeltaAssets = deltaAssets(afterVault.pseudoId); + auto const maybeVaultDeltaAssets = deltaAssets(afterVault.pseudoId); - if (!vaultDeltaAssets) + if (!maybeVaultDeltaAssets) { JLOG(j.fatal()) << "Invariant failed: withdrawal must " "change vault balance"; return false; // That's all we can do } - if (*vaultDeltaAssets >= zero) + // Get the most coarse scale to round calculations to + auto const totalDelta = DeltaInfo::makeDelta( + beforeVault.assetsTotal, afterVault.assetsTotal, vaultAsset); + auto const availableDelta = DeltaInfo::makeDelta( + beforeVault.assetsAvailable, afterVault.assetsAvailable, vaultAsset); + auto const minScale = + computeCoarsestScale({*maybeVaultDeltaAssets, totalDelta, availableDelta}); + + auto const vaultPseudoDeltaAssets = + roundToAsset(vaultAsset, maybeVaultDeltaAssets->delta, minScale); + + if (vaultPseudoDeltaAssets >= zero) { JLOG(j.fatal()) << "Invariant failed: withdrawal must " "decrease vault balance"; @@ -720,15 +814,15 @@ ValidVault::finalize( if (!issuerWithdrawal) { - auto const accountDeltaAssets = deltaAssetsTxAccount(); - auto const otherAccountDelta = [&]() -> std::optional { + auto const maybeAccDelta = deltaAssetsTxAccount(); + auto const maybeOtherAccDelta = [&]() -> std::optional { if (auto const destination = tx[~sfDestination]; destination && *destination != tx[sfAccount]) return deltaAssets(*destination); return std::nullopt; }(); - if (accountDeltaAssets.has_value() == otherAccountDelta.has_value()) + if (maybeAccDelta.has_value() == maybeOtherAccDelta.has_value()) { JLOG(j.fatal()) << // "Invariant failed: withdrawal must change one " @@ -737,9 +831,17 @@ ValidVault::finalize( } auto const destinationDelta = // - accountDeltaAssets ? *accountDeltaAssets : *otherAccountDelta; + maybeAccDelta ? *maybeAccDelta : *maybeOtherAccDelta; - if (destinationDelta <= zero) + // the scale of destinationDelta can be coarser than + // minScale, so we take that into account when rounding + auto const localMinScale = + std::max(minScale, computeCoarsestScale({destinationDelta})); + + auto const roundedDestinationDelta = + roundToAsset(vaultAsset, destinationDelta.delta, localMinScale); + + if (roundedDestinationDelta <= zero) { JLOG(j.fatal()) << // "Invariant failed: withdrawal must increase " @@ -747,7 +849,9 @@ ValidVault::finalize( result = false; } - if (*vaultDeltaAssets * -1 != destinationDelta) + auto const localPseudoDeltaAssets = + roundToAsset(vaultAsset, vaultPseudoDeltaAssets, localMinScale); + if (localPseudoDeltaAssets * -1 != roundedDestinationDelta) { JLOG(j.fatal()) << // "Invariant failed: withdrawal must change vault " @@ -756,6 +860,7 @@ ValidVault::finalize( } } + // We don't need to round shares, they are integral MPT auto const accountDeltaShares = deltaShares(tx[sfAccount]); if (!accountDeltaShares) { @@ -765,7 +870,7 @@ ValidVault::finalize( return false; } - if (*accountDeltaShares >= zero) + if (accountDeltaShares->delta >= zero) { JLOG(j.fatal()) << // "Invariant failed: withdrawal must decrease depositor " @@ -773,15 +878,16 @@ ValidVault::finalize( result = false; } + // We don't need to round shares, they are integral MPT auto const vaultDeltaShares = deltaShares(afterVault.pseudoId); - if (!vaultDeltaShares || *vaultDeltaShares == zero) + if (!vaultDeltaShares || vaultDeltaShares->delta == zero) { JLOG(j.fatal()) << // "Invariant failed: withdrawal must change vault shares"; return false; // That's all we can do } - if (*vaultDeltaShares * -1 != *accountDeltaShares) + if (vaultDeltaShares->delta * -1 != accountDeltaShares->delta) { JLOG(j.fatal()) << // "Invariant failed: withdrawal must change depositor " @@ -789,15 +895,20 @@ ValidVault::finalize( result = false; } + auto const assetTotalDelta = roundToAsset( + vaultAsset, afterVault.assetsTotal - beforeVault.assetsTotal, minScale); // Note, vaultBalance is negative (see check above) - if (beforeVault.assetsTotal + *vaultDeltaAssets != afterVault.assetsTotal) + if (assetTotalDelta != vaultPseudoDeltaAssets) { JLOG(j.fatal()) << "Invariant failed: withdrawal and " "assets outstanding must add up"; result = false; } - if (beforeVault.assetsAvailable + *vaultDeltaAssets != afterVault.assetsAvailable) + auto const assetAvailableDelta = roundToAsset( + vaultAsset, afterVault.assetsAvailable - beforeVault.assetsAvailable, minScale); + + if (assetAvailableDelta != vaultPseudoDeltaAssets) { JLOG(j.fatal()) << "Invariant failed: withdrawal and " "assets available must add up"; @@ -828,10 +939,18 @@ ValidVault::finalize( } } - auto const vaultDeltaAssets = deltaAssets(afterVault.pseudoId); - if (vaultDeltaAssets) + auto const maybeVaultDeltaAssets = deltaAssets(afterVault.pseudoId); + if (maybeVaultDeltaAssets) { - if (*vaultDeltaAssets >= zero) + auto const totalDelta = DeltaInfo::makeDelta( + beforeVault.assetsTotal, afterVault.assetsTotal, vaultAsset); + auto const availableDelta = DeltaInfo::makeDelta( + beforeVault.assetsAvailable, afterVault.assetsAvailable, vaultAsset); + auto const minScale = + computeCoarsestScale({*maybeVaultDeltaAssets, totalDelta, availableDelta}); + auto const vaultDeltaAssets = + roundToAsset(vaultAsset, maybeVaultDeltaAssets->delta, minScale); + if (vaultDeltaAssets >= zero) { JLOG(j.fatal()) << // "Invariant failed: clawback must decrease vault " @@ -839,7 +958,9 @@ ValidVault::finalize( result = false; } - if (beforeVault.assetsTotal + *vaultDeltaAssets != afterVault.assetsTotal) + auto const assetsTotalDelta = roundToAsset( + vaultAsset, afterVault.assetsTotal - beforeVault.assetsTotal, minScale); + if (assetsTotalDelta != vaultDeltaAssets) { JLOG(j.fatal()) << // "Invariant failed: clawback and assets outstanding " @@ -847,8 +968,11 @@ ValidVault::finalize( result = false; } - if (beforeVault.assetsAvailable + *vaultDeltaAssets != - afterVault.assetsAvailable) + auto const assetAvailableDelta = roundToAsset( + vaultAsset, + afterVault.assetsAvailable - beforeVault.assetsAvailable, + minScale); + if (assetAvailableDelta != vaultDeltaAssets) { JLOG(j.fatal()) << // "Invariant failed: clawback and assets available " @@ -863,15 +987,15 @@ ValidVault::finalize( return false; // That's all we can do } - auto const accountDeltaShares = deltaShares(tx[sfHolder]); - if (!accountDeltaShares) + // We don't need to round shares, they are integral MPT + auto const maybeAccountDeltaShares = deltaShares(tx[sfHolder]); + if (!maybeAccountDeltaShares) { JLOG(j.fatal()) << // "Invariant failed: clawback must change holder shares"; return false; // That's all we can do } - - if (*accountDeltaShares >= zero) + if (maybeAccountDeltaShares->delta >= zero) { JLOG(j.fatal()) << // "Invariant failed: clawback must decrease holder " @@ -879,15 +1003,16 @@ ValidVault::finalize( result = false; } + // We don't need to round shares, they are integral MPT auto const vaultDeltaShares = deltaShares(afterVault.pseudoId); - if (!vaultDeltaShares || *vaultDeltaShares == zero) + if (!vaultDeltaShares || vaultDeltaShares->delta == zero) { JLOG(j.fatal()) << // "Invariant failed: clawback must change vault shares"; return false; // That's all we can do } - if (*vaultDeltaShares * -1 != *accountDeltaShares) + if (vaultDeltaShares->delta * -1 != maybeAccountDeltaShares->delta) { JLOG(j.fatal()) << // "Invariant failed: clawback must change holder and " @@ -924,4 +1049,25 @@ ValidVault::finalize( return true; } +[[nodiscard]] ValidVault::DeltaInfo +ValidVault::DeltaInfo::makeDelta(Number const& before, Number const& after, Asset const& asset) +{ + return { + .delta = after - before, + .scale = std::max(xrpl::scale(after, asset), xrpl::scale(before, asset))}; +} + +[[nodiscard]] std::int32_t +ValidVault::computeCoarsestScale(std::vector const& numbers) +{ + if (numbers.empty()) + return 0; + + auto const max = std::ranges::max_element( + numbers, [](auto const& a, auto const& b) -> bool { return a.scale < b.scale; }); + XRPL_ASSERT_PARTS( + max->scale, "xrpl::ValidVault::computeCoarsestScale", "scale set for destinationDelta"); + return max->scale.value_or(STAmount::cMaxOffset); +} + } // namespace xrpl diff --git a/src/libxrpl/tx/paths/AMMLiquidity.cpp b/src/libxrpl/tx/paths/AMMLiquidity.cpp index bb7f229374..0a4cfadb69 100644 --- a/src/libxrpl/tx/paths/AMMLiquidity.cpp +++ b/src/libxrpl/tx/paths/AMMLiquidity.cpp @@ -1,5 +1,30 @@ #include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include + +#include +#include +#include +#include namespace xrpl { @@ -8,15 +33,15 @@ AMMLiquidity::AMMLiquidity( ReadView const& view, AccountID const& ammAccountID, std::uint32_t tradingFee, - Issue const& in, - Issue const& out, + Asset const& in, + Asset const& out, AMMContext& ammContext, beast::Journal j) : ammContext_(ammContext) , ammAccountID_(ammAccountID) , tradingFee_(tradingFee) - , issueIn_(in) - , issueOut_(out) + , assetIn_(in) + , assetOut_(out) , initialBalances_{fetchBalances(view)} , j_(j) { @@ -26,13 +51,13 @@ template TAmounts AMMLiquidity::fetchBalances(ReadView const& view) const { - auto const assetIn = ammAccountHolds(view, ammAccountID_, issueIn_); - auto const assetOut = ammAccountHolds(view, ammAccountID_, issueOut_); + auto const amountIn = ammAccountHolds(view, ammAccountID_, assetIn_); + auto const amountOut = ammAccountHolds(view, ammAccountID_, assetOut_); // This should not happen. - if (assetIn < beast::zero || assetOut < beast::zero) + if (amountIn < beast::zero || amountOut < beast::zero) Throw("AMMLiquidity: invalid balances"); - return TAmounts{get(assetIn), get(assetOut)}; + return TAmounts{get(amountIn), get(amountOut)}; } template @@ -42,7 +67,7 @@ AMMLiquidity::generateFibSeqOffer(TAmounts const& balances TAmounts cur{}; cur.in = toAmount( - getIssue(balances.in), + getAsset(balances.in), InitialFibSeqPct * initialBalances_.in, Number::rounding_mode::upward); cur.out = swapAssetIn(initialBalances_, cur.in, tradingFee_); @@ -60,7 +85,7 @@ AMMLiquidity::generateFibSeqOffer(TAmounts const& balances "xrpl::AMMLiquidity::generateFibSeqOffer : maximum iterations"); cur.out = toAmount( - getIssue(balances.out), + getAsset(balances.out), cur.out * fib[ammContext_.curIters() - 1], Number::rounding_mode::downward); // swapAssetOut() returns negative in this case @@ -89,14 +114,18 @@ maxAmount() { return STAmount(STAmount::cMaxValue / 2, STAmount::cMaxOffset); } + else if constexpr (std::is_same_v) + { + return MPTAmount(maxMPTokenAmount); + } } template T -maxOut(T const& out, Issue const& iss) +maxOut(T const& out, Asset const& asset) { Number const res = out * Number{99, -2}; - return toAmount(iss, res, Number::rounding_mode::downward); + return toAmount(asset, res, Number::rounding_mode::downward); } } // namespace @@ -113,7 +142,7 @@ AMMLiquidity::maxOffer(TAmounts const& balances, Rules con Quality{balances}); } - auto const out = maxOut(balances.out, issueOut()); + auto const out = maxOut(balances.out, assetOut()); if (out <= TOut{0} || out >= balances.out) return std::nullopt; return AMMOffer( @@ -211,8 +240,8 @@ AMMLiquidity::getOffer(ReadView const& view, std::optional c if (offer->amount().in > beast::zero && offer->amount().out > beast::zero) { JLOG(j_.trace()) << "AMMLiquidity::getOffer, created " << to_string(offer->amount().in) - << "/" << issueIn_ << " " << to_string(offer->amount().out) << "/" - << issueOut_; + << "/" << assetIn_ << " " << to_string(offer->amount().out) << "/" + << assetOut_; return offer; } @@ -225,9 +254,13 @@ AMMLiquidity::getOffer(ReadView const& view, std::optional c return std::nullopt; } -template class AMMLiquidity; template class AMMLiquidity; template class AMMLiquidity; template class AMMLiquidity; +template class AMMLiquidity; +template class AMMLiquidity; +template class AMMLiquidity; +template class AMMLiquidity; +template class AMMLiquidity; } // namespace xrpl diff --git a/src/libxrpl/tx/paths/AMMOffer.cpp b/src/libxrpl/tx/paths/AMMOffer.cpp index 79071850c2..0a41411492 100644 --- a/src/libxrpl/tx/paths/AMMOffer.cpp +++ b/src/libxrpl/tx/paths/AMMOffer.cpp @@ -1,10 +1,28 @@ -#include -#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + namespace xrpl { -template +template AMMOffer::AMMOffer( AMMLiquidity const& ammLiquidity, TAmounts const& amounts, @@ -15,28 +33,35 @@ AMMOffer::AMMOffer( { } -template -Issue const& -AMMOffer::issueIn() const +template +Asset const& +AMMOffer::assetIn() const { - return ammLiquidity_.issueIn(); + return ammLiquidity_.assetIn(); } -template +template +Asset const& +AMMOffer::assetOut() const +{ + return ammLiquidity_.assetOut(); +} + +template AccountID const& AMMOffer::owner() const { return ammLiquidity_.ammAccount(); } -template +template TAmounts const& AMMOffer::amount() const { return amounts_; } -template +template void AMMOffer::consume(ApplyView& view, TAmounts const& consumed) { @@ -52,7 +77,7 @@ AMMOffer::consume(ApplyView& view, TAmounts const& consume ammLiquidity_.context().setAMMUsed(); } -template +template TAmounts AMMOffer::limitOut( TAmounts const& offerAmount, @@ -77,7 +102,7 @@ AMMOffer::limitOut( return {swapAssetOut(balances_, limit, ammLiquidity_.tradingFee()), limit}; } -template +template TAmounts AMMOffer::limitIn(TAmounts const& offerAmount, TIn const& limit, bool roundUp) const @@ -94,7 +119,7 @@ AMMOffer::limitIn(TAmounts const& offerAmount, TIn const& return {limit, swapAssetIn(balances_, limit, ammLiquidity_.tradingFee())}; } -template +template QualityFunction AMMOffer::getQualityFunc() const { @@ -103,7 +128,7 @@ AMMOffer::getQualityFunc() const return QualityFunction{balances_, ammLiquidity_.tradingFee(), QualityFunction::AMMTag{}}; } -template +template bool AMMOffer::checkInvariant(TAmounts const& consumed, beast::Journal j) const { @@ -133,9 +158,13 @@ AMMOffer::checkInvariant(TAmounts const& consumed, beast:: return false; } -template class AMMOffer; template class AMMOffer; template class AMMOffer; template class AMMOffer; +template class AMMOffer; +template class AMMOffer; +template class AMMOffer; +template class AMMOffer; +template class AMMOffer; } // namespace xrpl diff --git a/src/libxrpl/tx/paths/BookStep.cpp b/src/libxrpl/tx/paths/BookStep.cpp index ddba0c1ae9..f05460df61 100644 --- a/src/libxrpl/tx/paths/BookStep.cpp +++ b/src/libxrpl/tx/paths/BookStep.cpp @@ -1,24 +1,53 @@ #include +#include #include +#include +#include #include +#include #include +#include +#include #include +#include +#include +#include +#include #include +#include #include #include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include #include #include #include +#include #include +#include #include #include -#include #include +#include +#include #include +#include #include +#include +#include +#include +#include namespace xrpl { @@ -40,7 +69,7 @@ protected: /** Number of offers consumed or partially consumed the last time the step ran, including expired and unfunded offers. - N.B. This this not the total number offers consumed by this step for the + N.B. This is not the total number offers consumed by this step for the entire payment, it is only the number the last time it ran. Offers may be partially consumed multiple times during a payment. */ @@ -50,6 +79,7 @@ protected: // quality or there is no CLOB offer. std::optional> ammLiquidity_; beast::Journal const j_; + Asset const strandDeliver_; struct Cache { @@ -64,13 +94,14 @@ protected: std::optional cache_; private: - BookStep(StrandContext const& ctx, Issue const& in, Issue const& out) + BookStep(StrandContext const& ctx, Asset const& in, Asset const& out) : book_(in, out, ctx.domainID) , strandSrc_(ctx.strandSrc) , strandDst_(ctx.strandDst) , prevStep_(ctx.prevStep) , ownerPaysTransferFee_(ctx.ownerPaysTransferFee) , j_(ctx.j) + , strandDeliver_(ctx.strandDeliver) { if (auto const ammSle = ctx.view.read(keylet::amm(in, out)); ammSle && ammSle->getFieldAmount(sfLPTokenBalance) != beast::zero) @@ -163,11 +194,14 @@ protected: { std::ostringstream ostr; ostr << name << ": " - << "\ninIss: " << book_.in.account << "\noutIss: " << book_.out.account - << "\ninCur: " << book_.in.currency << "\noutCur: " << book_.out.currency; + << "\ninIss: " << book_.in.getIssuer() << "\noutIss: " << book_.out.getIssuer() + << "\ninCur: " << to_string(book_.in) << "\noutCur: " << to_string(book_.out); return ostr.str(); } + Rate + rate(ReadView const& view, Asset const& asset, AccountID const& dstAccount) const; + private: friend bool operator==(BookStep const& lhs, BookStep const& rhs) @@ -188,7 +222,7 @@ private: // Unfunded offers and bad offers are skipped (and returned). // callback is called with the offer SLE, taker pays, taker gets. // If callback returns false, don't process any more offers. - // Return the unfunded and bad offers and the number of offers consumed. + // Return the unfunded, bad offers and the number of offers consumed. template std::pair, std::uint32_t> forEachOffer( @@ -227,6 +261,11 @@ private: std::optional tipOfferQualityF(ReadView const& view) const; + // Check that takerPays/takerGets can be transferred/traded. + // Applies to MPT assets. + bool + checkMPTDEX(ReadView const& view, AccountID const& owner) const; + friend TDerived; }; @@ -245,7 +284,7 @@ class BookPaymentStep : public BookStep> public: explicit BookPaymentStep() = default; - BookPaymentStep(StrandContext const& ctx, Issue const& in, Issue const& out) + BookPaymentStep(StrandContext const& ctx, Asset const& in, Asset const& out) : BookStep>(ctx, in, out) { } @@ -310,17 +349,12 @@ public: // (the old code does not charge a fee) // Calculate amount that goes to the taker and the amount charged the // offer owner - auto rate = [&](AccountID const& id) { - if (isXRP(id) || id == this->strandDst_) - return parityRate; - return transferRate(v, id); - }; - - auto const trIn = redeems(prevStepDir) ? rate(this->book_.in.account) : parityRate; + auto const trIn = + redeems(prevStepDir) ? this->rate(v, this->book_.in, this->strandDst_) : parityRate; // Always charge the transfer fee, even if the owner is the issuer, // unless the fee is waived auto const trOut = (this->ownerPaysTransferFee_ && waiveFee == WaiveTransferFee::No) - ? rate(this->book_.out.account) + ? this->rate(v, this->book_.out, this->strandDst_) : parityRate; Quality const q1{getRate(STAmount(trOut.value), STAmount(trIn.value))}; @@ -355,7 +389,7 @@ private: } public: - BookOfferCrossingStep(StrandContext const& ctx, Issue const& in, Issue const& out) + BookOfferCrossingStep(StrandContext const& ctx, Asset const& in, Asset const& out) : BookStep>(ctx, in, out) , defaultPath_(ctx.isDefaultPath) , qualityThreshold_(getQuality(ctx.limitQuality)) @@ -454,7 +488,7 @@ public: auto const srcAcct = (prevStep != nullptr) ? prevStep->directStepSrcAcct() : std::nullopt; return owner == srcAcct // If offer crossing && prevStep is DirectI - ? QUALITY_ONE // && src is offer owner + ? QUALITY_ONE // or MPTEndpoint && src is offer owner : trIn; // then rate = QUALITY_ONE } @@ -502,13 +536,8 @@ public: return ofrQ; } - auto rate = [&](AccountID const& id) { - if (isXRP(id) || id == this->strandDst_) - return parityRate; - return transferRate(v, id); - }; - - auto const trIn = redeems(prevStepDir) ? rate(this->book_.in.account) : parityRate; + auto const trIn = + redeems(prevStepDir) ? this->rate(v, this->book_.in, this->strandDst_) : parityRate; // AMM doesn't pay the transfer fee on the out amount auto const trOut = parityRate; @@ -659,15 +688,11 @@ BookStep::forEachOffer( // (the old code does not charge a fee) // Calculate amount that goes to the taker and the amount charged the offer // owner - auto rate = [this, &sb](AccountID const& id) -> std::uint32_t { - if (isXRP(id) || id == this->strandDst_) - return QUALITY_ONE; - return transferRate(sb, id).value; - }; - - std::uint32_t const trIn = redeems(prevStepDir) ? rate(book_.in.account) : QUALITY_ONE; + std::uint32_t const trIn = + redeems(prevStepDir) ? rate(sb, book_.in, this->strandDst_).value : QUALITY_ONE; // Always charge the transfer fee, even if the owner is the issuer - std::uint32_t const trOut = ownerPaysTransferFee_ ? rate(book_.out.account) : QUALITY_ONE; + std::uint32_t const trOut = + ownerPaysTransferFee_ ? rate(sb, book_.out, this->strandDst_).value : QUALITY_ONE; typename FlowOfferStream::StepCounter counter(MaxOffersToConsume, j_); @@ -691,45 +716,49 @@ BookStep::forEachOffer( strandSrc_, strandDst_, offer, ofrQ, offers, offerAttempted)) return true; - // Make sure offer owner has authorization to own IOUs from issuer. - // An account can always own XRP or their own IOUs. - if (!isXRP(offer.issueIn().currency) && offer.owner() != offer.issueIn().account) + Asset const& assetIn = offer.assetIn(); + bool const isAssetInMPT = assetIn.holds(); + auto const& owner = offer.owner(); + + if (isAssetInMPT) { - auto const& issuerID = offer.issueIn().account; - auto const issuer = afView.read(keylet::account(issuerID)); - if (issuer && ((*issuer)[sfFlags] & lsfRequireAuth)) + // Create MPToken for the offer's owner. No need to check + // for the reserve since the offer is removed if it is consumed. + // Therefore, the owner count remains the same. + if (auto const err = checkCreateMPT(sb, assetIn.get(), owner, j_); + !isTesSuccess(err)) { - // Issuer requires authorization. See if offer owner has that. - auto const& ownerID = offer.owner(); - auto const authFlag = issuerID > ownerID ? lsfHighAuth : lsfLowAuth; - - auto const line = - afView.read(keylet::line(ownerID, issuerID, offer.issueIn().currency)); - - if (!line || (((*line)[sfFlags] & authFlag) == 0)) - { - // Offer owner not authorized to hold IOU from issuer. - // Remove this offer even if no crossing occurs. - if (auto const key = offer.key()) - offers.permRmOffer(*key); - if (!offerAttempted) - { - // Change quality only if no previous offers were tried. - ofrQ = std::nullopt; - } - // Returning true causes offers.step() to delete the offer. - return true; - } + return true; } } + // It shouldn't matter from auth point of view whether it's sb + // or afView. Amendment guard this change just in case. + auto& applyView = sb.rules().enabled(featureMPTokensV2) ? sb : afView; + // Make sure offer owner has authorization to own Assets from issuer + // and MPT assets can be traded/transferred. + // An account can always own XRP or their own Assets. + if (!isTesSuccess(requireAuth(applyView, assetIn, owner)) || !checkMPTDEX(sb, owner)) + { + // Offer owner not authorized to hold IOU/MPT from issuer. + // Remove this offer even if no crossing occurs. + if (auto const key = offer.key()) + offers.permRmOffer(*key); + if (!offerAttempted) + { + // Change quality only if no previous offers were tried. + ofrQ = std::nullopt; + } + // Returning true causes offers.step() to delete the offer. + return true; + } + if (!static_cast(this)->checkQualityThreshold(offer.quality())) return false; auto const [ofrInRate, ofrOutRate] = offer.adjustRates( - static_cast(this)->getOfrInRate(prevStep_, offer.owner(), trIn), - static_cast(this)->getOfrOutRate( - prevStep_, offer.owner(), strandDst_, trOut)); + static_cast(this)->getOfrInRate(prevStep_, owner, trIn), + static_cast(this)->getOfrOutRate(prevStep_, owner, strandDst_, trOut)); auto ofrAmt = offer.amount(); TAmounts stpAmt{mulRatio(ofrAmt.in, ofrInRate, QUALITY_ONE, /*roundUp*/ true), ofrAmt.out}; @@ -756,6 +785,26 @@ BookStep::forEachOffer( stpAmt.in = mulRatio(ofrAmt.in, ofrInRate, QUALITY_ONE, /*roundUp*/ true); } + // Limit offer's input if MPT, BookStep is the first step (an issuer + // is making a cross-currency payment), and this offer is not owned + // by the issuer. Otherwise, OutstandingAmount may overflow. + auto const& issuer = assetIn.getIssuer(); + if (isAssetInMPT && !prevStep_ && offer.owner() != issuer) + { + // Funds available to issue + auto const available = toAmount(accountFunds( + sb, + issuer, + assetIn, // STAmount{0}, but the default is not used + FreezeHandling::fhIGNORE_FREEZE, + AuthHandling::ahIGNORE_AUTH, + j_)); + if (stpAmt.in > available) + { + limitStepIn(offer, ofrAmt, stpAmt, ownerGives, ofrInRate, ofrOutRate, available); + } + } + offerAttempted = true; return callback(offer, ofrAmt, stpAmt, ownerGives, ofrInRate, ofrOutRate); }; @@ -820,8 +869,8 @@ BookStep::consumeOffer( // The offer owner gets the ofrAmt. The difference between ofrAmt and // stepAmt is a transfer fee that goes to book_.in.account { - auto const dr = - offer.send(sb, book_.in.account, offer.owner(), toSTAmount(ofrAmt.in, book_.in), j_); + auto const dr = offer.send( + sb, book_.in.getIssuer(), offer.owner(), toSTAmount(ofrAmt.in, book_.in), j_); if (!isTesSuccess(dr)) Throw(dr); } @@ -829,10 +878,16 @@ BookStep::consumeOffer( // The offer owner pays `ownerGives`. The difference between ownerGives and // stepAmt is a transfer fee that goes to book_.out.account { + auto const& issuer = book_.out.getIssuer(); auto const cr = - offer.send(sb, offer.owner(), book_.out.account, toSTAmount(ownerGives, book_.out), j_); + offer.send(sb, offer.owner(), issuer, toSTAmount(ownerGives, book_.out), j_); if (!isTesSuccess(cr)) Throw(cr); + if constexpr (std::is_same_v) + { + if (offer.owner() == issuer) + issuerSelfDebitHookMPT(sb, book_.out.get(), ofrAmt.out.value()); + } } offer.consume(sb, ofrAmt); @@ -1260,20 +1315,20 @@ BookStep::check(StrandContext const& ctx) const // Do not allow two books to output the same issue. This may cause offers on // one step to unfund offers in another step. if (!ctx.seenBookOuts.insert(book_.out).second || - (ctx.seenDirectIssues[0].count(book_.out) != 0u)) + (ctx.seenDirectAssets[0].count(book_.out) != 0u)) { JLOG(j_.debug()) << "BookStep: loop detected: " << *this; return temBAD_PATH_LOOP; } - if (ctx.seenDirectIssues[1].count(book_.out) != 0u) + if (ctx.seenDirectAssets[1].count(book_.out) != 0u) { JLOG(j_.debug()) << "BookStep: loop detected: " << *this; return temBAD_PATH_LOOP; } - auto issuerExists = [](ReadView const& view, Issue const& iss) -> bool { - return isXRP(iss.account) || view.read(keylet::account(iss.account)); + auto issuerExists = [](ReadView const& view, Asset const& iss) -> bool { + return isXRP(iss.getIssuer()) || view.exists(keylet::account(iss.getIssuer())); }; if (!issuerExists(ctx.view, book_.in) || !issuerExists(ctx.view, book_.out)) @@ -1287,19 +1342,101 @@ BookStep::check(StrandContext const& ctx) const if (auto const prev = ctx.prevStep->directStepSrcAcct()) { auto const& view = ctx.view; - auto const& cur = book_.in.account; + auto const& cur = book_.in.getIssuer(); - auto sle = view.read(keylet::line(*prev, cur, book_.in.currency)); - if (!sle) - return terNO_LINE; - if (((*sle)[sfFlags] & ((cur > *prev) ? lsfHighNoRipple : lsfLowNoRipple)) != 0u) - return terNO_RIPPLE; + auto const err = book_.in.visit( + [&](Issue const& issue) -> std::optional { + auto sle = view.read(keylet::line(*prev, cur, issue.currency)); + if (!sle) + return terNO_LINE; + if (((*sle)[sfFlags] & ((cur > *prev) ? lsfHighNoRipple : lsfLowNoRipple)) != + 0u) + return terNO_RIPPLE; + return std::nullopt; + }, + [&](MPTIssue const& issue) -> std::optional { + // Check if can trade on DEX. + if (auto const ter = canTrade(view, book_.in); !isTesSuccess(ter)) + return ter; + if (auto const ter = canTrade(view, book_.out); !isTesSuccess(ter)) + return ter; + return std::nullopt; + }); + if (err) + return *err; } } return tesSUCCESS; } +template +Rate +BookStep::rate( + ReadView const& view, + Asset const& asset, + AccountID const& dstAccount) const +{ + auto const& issuer = asset.getIssuer(); + if (isXRP(issuer) || issuer == dstAccount) + return parityRate; + return asset.visit( + [&](Issue const&) { return transferRate(view, issuer); }, + [&](MPTIssue const& issue) { return transferRate(view, issue.getMptID()); }); +}; + +template +bool +BookStep::checkMPTDEX(ReadView const& view, AccountID const& owner) const +{ + if (!isTesSuccess(canTrade(view, book_.in)) || !isTesSuccess(canTrade(view, book_.out))) + return false; + + if (book_.in.holds()) + { + auto ret = [&]() { + auto const& asset = book_.in; + // Strand's source is an issuer + if (!prevStep_) + return true; + // Offer's owner is an issuer + if (asset.getIssuer() == owner) + return true; + // The previous step could be MPTEndpointStep with non issuer account or + // BookStep. Fail both if in asset is locked. In the former case it is holder + // to locked holder transfer. In the latter case it is not possible to tell if + // it is issuer to holder or holder to holder transfer. + if (isFrozen(view, owner, book_.in.get())) + return false; + // Previous step is BookStep. BookStep only sends if CanTransfer is + // set and not locked or the offer is owned by an issuer + if (prevStep_->bookStepBook()) + return true; + // Previous step is MPTEndpointStep and offer's owner is not an + // issuer + return isTesSuccess(canTransfer(view, asset, owner, owner)); + }(); + if (!ret) + return false; + } + + if (book_.out.holds()) + { + auto const& asset = book_.out; + // Last step if the strand's destination is an issuer + if (strandDeliver_ == asset && strandDst_ == asset.getIssuer()) + return true; + // Offer's owner is an issuer + if (asset.getIssuer() == owner) + return true; + + // Next step is BookStep and offer's owner is not an issuer. + return isTesSuccess(canTransfer(view, asset, owner, owner)); + } + + return true; +} + //------------------------------------------------------------------------------ namespace test { @@ -1317,22 +1454,25 @@ equalHelper(Step const& step, xrpl::Book const& book) bool bookStepEqual(Step const& step, xrpl::Book const& book) { - bool const inXRP = isXRP(book.in.currency); - bool const outXRP = isXRP(book.out.currency); - if (inXRP && outXRP) - { - // LCOV_EXCL_START - UNREACHABLE("xrpl::test::bookStepEqual : no XRP to XRP book step"); - return false; // no such thing as xrp/xrp book step - // LCOV_EXCL_STOP - } - if (inXRP && !outXRP) - return equalHelper>(step, book); - if (!inXRP && outXRP) - return equalHelper>(step, book); - if (!inXRP && !outXRP) - return equalHelper>(step, book); - return false; + return std::visit( + [&](TIn const&, TOut const&) { + using TIn_ = typename TIn::amount_type; + using TOut_ = typename TOut::amount_type; + + if constexpr (ValidTaker) + { + return equalHelper>(step, book); + } + else + { + // LCOV_EXCL_START + UNREACHABLE("xrpl::bookStepEqual : invalid book step"); + return false; + // LCOV_EXCL_STOP + } + }, + book.in.getAmountType(), + book.out.getAmountType()); } } // namespace test @@ -1340,7 +1480,7 @@ bookStepEqual(Step const& step, xrpl::Book const& book) template static std::pair> -make_BookStepHelper(StrandContext const& ctx, Issue const& in, Issue const& out) +make_BookStepHelper(StrandContext const& ctx, Asset const& in, Asset const& out) { TER ter = tefINTERNAL; std::unique_ptr r; @@ -1380,4 +1520,35 @@ make_BookStepXI(StrandContext const& ctx, Issue const& out) return make_BookStepHelper(ctx, xrpIssue(), out); } +// MPT's +std::pair> +make_BookStepMM(StrandContext const& ctx, MPTIssue const& in, MPTIssue const& out) +{ + return make_BookStepHelper(ctx, in, out); +} + +std::pair> +make_BookStepMI(StrandContext const& ctx, MPTIssue const& in, Issue const& out) +{ + return make_BookStepHelper(ctx, in, out); +} + +std::pair> +make_BookStepIM(StrandContext const& ctx, Issue const& in, MPTIssue const& out) +{ + return make_BookStepHelper(ctx, in, out); +} + +std::pair> +make_BookStepMX(StrandContext const& ctx, MPTIssue const& in) +{ + return make_BookStepHelper(ctx, in, xrpIssue()); +} + +std::pair> +make_BookStepXM(StrandContext const& ctx, MPTIssue const& out) +{ + return make_BookStepHelper(ctx, xrpIssue(), out); +} + } // namespace xrpl diff --git a/src/libxrpl/tx/paths/BookTip.cpp b/src/libxrpl/tx/paths/BookTip.cpp index f9b700b7af..7c6dd85ee3 100644 --- a/src/libxrpl/tx/paths/BookTip.cpp +++ b/src/libxrpl/tx/paths/BookTip.cpp @@ -1,6 +1,14 @@ +#include + +#include +#include #include #include -#include +#include +#include +#include + +#include namespace xrpl { diff --git a/src/libxrpl/tx/paths/DirectStep.cpp b/src/libxrpl/tx/paths/DirectStep.cpp index 9cae103d8f..c4b2c51934 100644 --- a/src/libxrpl/tx/paths/DirectStep.cpp +++ b/src/libxrpl/tx/paths/DirectStep.cpp @@ -1,17 +1,36 @@ #include +#include +#include +#include +#include #include #include #include -#include +#include +#include +#include #include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include #include #include #include -#include +#include +#include +#include #include +#include +#include namespace xrpl { @@ -519,7 +538,7 @@ DirectStepI::revImp( { IOUAmount const in = mulRatio(srcToDst, srcQOut, QUALITY_ONE, /*roundUp*/ true); cache_.emplace(in, srcToDst, out, srcDebtDir); - rippleCredit( + directSendNoFee( sb, src_, dst_, @@ -536,7 +555,7 @@ DirectStepI::revImp( IOUAmount const in = mulRatio(maxSrcToDst, srcQOut, QUALITY_ONE, /*roundUp*/ true); IOUAmount const actualOut = mulRatio(maxSrcToDst, dstQIn, QUALITY_ONE, /*roundUp*/ false); cache_.emplace(in, maxSrcToDst, actualOut, srcDebtDir); - rippleCredit( + directSendNoFee( sb, src_, dst_, @@ -628,7 +647,7 @@ DirectStepI::fwdImp( { IOUAmount const out = mulRatio(srcToDst, dstQIn, QUALITY_ONE, /*roundUp*/ false); setCacheLimiting(in, srcToDst, out, srcDebtDir); - rippleCredit( + directSendNoFee( sb, src_, dst_, @@ -645,7 +664,7 @@ DirectStepI::fwdImp( IOUAmount const actualIn = mulRatio(maxSrcToDst, srcQOut, QUALITY_ONE, /*roundUp*/ true); IOUAmount const out = mulRatio(maxSrcToDst, dstQIn, QUALITY_ONE, /*roundUp*/ false); setCacheLimiting(actualIn, maxSrcToDst, out, srcDebtDir); - rippleCredit( + directSendNoFee( sb, src_, dst_, @@ -671,7 +690,7 @@ DirectStepI::validFwd(PaymentSandbox& sb, ApplyView& afView, EitherAmo auto const savCache = *cache_; - XRPL_ASSERT(!in.native, "xrpl::DirectStepI::validFwd : input is not XRP"); + XRPL_ASSERT(in.holds(), "xrpl::DirectStepI::validFwd : input is IOU"); auto const [maxSrcToDst, srcDebtDir] = static_cast(this)->maxFlow(sb, cache_->srcToDst); @@ -680,7 +699,7 @@ DirectStepI::validFwd(PaymentSandbox& sb, ApplyView& afView, EitherAmo try { boost::container::flat_set dummy; - fwdImp(sb, afView, dummy, in.iou); // changes cache + fwdImp(sb, afView, dummy, in.get()); // changes cache } catch (FlowException const&) { @@ -857,13 +876,13 @@ DirectStepI::check(StrandContext const& ctx) const // issue if (auto book = ctx.prevStep->bookStepBook()) { - if (book->out != srcIssue) + if (book->out.get() != srcIssue) return temBAD_PATH_LOOP; } } - if (!ctx.seenDirectIssues[0].insert(srcIssue).second || - !ctx.seenDirectIssues[1].insert(dstIssue).second) + if (!ctx.seenDirectAssets[0].insert(srcIssue).second || + !ctx.seenDirectAssets[1].insert(dstIssue).second) { JLOG(j_.debug()) << "DirectStepI: loop detected: Index: " << ctx.strandSize << ' ' << *this; diff --git a/src/libxrpl/tx/paths/Flow.cpp b/src/libxrpl/tx/paths/Flow.cpp index 5a706ea812..413c160c7f 100644 --- a/src/libxrpl/tx/paths/Flow.cpp +++ b/src/libxrpl/tx/paths/Flow.cpp @@ -1,18 +1,28 @@ -#include -#include -#include -#include #include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include #include +#include +#include + namespace xrpl { template static auto -finishFlow(PaymentSandbox& sb, Issue const& srcIssue, Issue const& dstIssue, FlowResult&& f) +finishFlow(PaymentSandbox& sb, Asset const& srcAsset, Asset const& dstAsset, FlowResult&& f) { path::RippleCalc::Output result; if (isTesSuccess(f.ter)) @@ -25,8 +35,8 @@ finishFlow(PaymentSandbox& sb, Issue const& srcIssue, Issue const& dstIssue, Flo } result.setResult(f.ter); - result.actualAmountIn = toSTAmount(f.in, srcIssue); - result.actualAmountOut = toSTAmount(f.out, dstIssue); + result.actualAmountIn = toSTAmount(f.in, srcAsset); + result.actualAmountOut = toSTAmount(f.out, dstAsset); return result; }; @@ -48,19 +58,23 @@ flow( beast::Journal j, path::detail::FlowDebugInfo* flowDebugInfo) { - Issue const srcIssue = [&] { + Asset const srcAsset = [&]() -> Asset { if (sendMax) - return sendMax->issue(); - if (!isXRP(deliver.issue().currency)) - return Issue(deliver.issue().currency, src); - return xrpIssue(); + return sendMax->asset(); + return deliver.asset().visit( + [&](Issue const& issue) -> Asset { + if (isXRP(issue)) + return xrpIssue(); + return Issue(issue.currency, src); + }, + [&](MPTIssue const&) { return deliver.asset(); }); }(); - Issue const dstIssue = deliver.issue(); + Asset const dstAsset = deliver.asset(); - std::optional sendMaxIssue; + std::optional sendMaxAsset; if (sendMax) - sendMaxIssue = sendMax->issue(); + sendMaxAsset = sendMax->asset(); AMMContext ammContext(src, false); @@ -71,9 +85,9 @@ flow( sb, src, dst, - dstIssue, + dstAsset, limitQuality, - sendMaxIssue, + sendMaxAsset, paths, defaultPaths, ownerPaysTransferFee, @@ -93,8 +107,8 @@ flow( if (j.trace()) { - j.trace() << "\nsrc: " << src << "\ndst: " << dst << "\nsrcIssue: " << srcIssue - << "\ndstIssue: " << dstIssue; + j.trace() << "\nsrc: " << src << "\ndst: " << dst << "\nsrcAsset: " << srcAsset + << "\ndstAsset: " << dstAsset; j.trace() << "\nNumStrands: " << strands.size(); for (auto const& curStrand : strands) { @@ -106,87 +120,32 @@ flow( } } - bool const srcIsXRP = isXRP(srcIssue.currency); - bool const dstIsXRP = isXRP(dstIssue.currency); - - auto const asDeliver = toAmountSpec(deliver); - - // The src account may send either xrp or iou. The dst account may receive - // either xrp or iou. Since XRP and IOU amounts are represented by different - // types, use templates to tell `flow` about the amount types. - if (srcIsXRP && dstIsXRP) - { - return finishFlow( - sb, - srcIssue, - dstIssue, - flow( + // The src account may send either xrp,iou,or mpt. The dst account may + // receive either xrp,iou, or mpt. Since XRP, IOU, and MPT amounts are + // represented by different types, use templates to tell `flow` about the + // amount types. + return std::visit( + [&, &strands_ = strands](TIn const&, TOut const&) { + using TIn_ = typename TIn::amount_type; + using TOut_ = typename TOut::amount_type; + return finishFlow( sb, - strands, - asDeliver.xrp, - partialPayment, - offerCrossing, - limitQuality, - sendMax, - j, - ammContext, - flowDebugInfo)); - } - - if (srcIsXRP && !dstIsXRP) - { - return finishFlow( - sb, - srcIssue, - dstIssue, - flow( - sb, - strands, - asDeliver.iou, - partialPayment, - offerCrossing, - limitQuality, - sendMax, - j, - ammContext, - flowDebugInfo)); - } - - if (!srcIsXRP && dstIsXRP) - { - return finishFlow( - sb, - srcIssue, - dstIssue, - flow( - sb, - strands, - asDeliver.xrp, - partialPayment, - offerCrossing, - limitQuality, - sendMax, - j, - ammContext, - flowDebugInfo)); - } - - XRPL_ASSERT(!srcIsXRP && !dstIsXRP, "xrpl::flow : neither is XRP"); - return finishFlow( - sb, - srcIssue, - dstIssue, - flow( - sb, - strands, - asDeliver.iou, - partialPayment, - offerCrossing, - limitQuality, - sendMax, - j, - ammContext, - flowDebugInfo)); + srcAsset, + dstAsset, + flow( + sb, + strands_, + get(deliver), + partialPayment, + offerCrossing, + limitQuality, + sendMax, + j, + ammContext, + flowDebugInfo)); + }, + srcAsset.getAmountType(), + dstAsset.getAmountType()); } } // namespace xrpl diff --git a/src/libxrpl/tx/paths/MPTEndpointStep.cpp b/src/libxrpl/tx/paths/MPTEndpointStep.cpp new file mode 100644 index 0000000000..7b063a39d3 --- /dev/null +++ b/src/libxrpl/tx/paths/MPTEndpointStep.cpp @@ -0,0 +1,937 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl { + +template +class MPTEndpointStep : public StepImp> +{ +protected: + AccountID const src_; + AccountID const dst_; + MPTIssue const mptIssue_; + + // Charge transfer fees when the prev step redeems + Step const* const prevStep_ = nullptr; + bool const isLast_; + // Direct payment between the holders + // Used by maxFlow's last step. + bool const isDirectBetweenHolders_ = false; + beast::Journal const j_; + + struct Cache + { + MPTAmount in; + MPTAmount srcToDst; + MPTAmount out; + DebtDirection srcDebtDir; + + Cache( + MPTAmount const& in_, + MPTAmount const& srcToDst_, + MPTAmount const& out_, + DebtDirection srcDebtDir_) + : in(in_), srcToDst(srcToDst_), out(out_), srcDebtDir(srcDebtDir_) + { + } + }; + + std::optional cache_; + + // Compute the maximum value that can flow from src->dst at + // the best available quality. + // return: first element is max amount that can flow, + // second is the debt direction of the source w.r.t. the dst + std::pair + maxPaymentFlow(ReadView const& sb) const; + + // Compute srcQOut and dstQIn when the source redeems. + std::pair + qualitiesSrcRedeems(ReadView const& sb) const; + + // Compute srcQOut and dstQIn when the source issues. + std::pair + qualitiesSrcIssues(ReadView const& sb, DebtDirection prevStepDebtDirection) const; + + // Returns srcQOut, dstQIn + std::pair + qualities(ReadView const& sb, DebtDirection srcDebtDir, StrandDirection strandDir) const; + + void + resetCache(DebtDirection dir); + +private: + MPTEndpointStep( + StrandContext const& ctx, + AccountID const& src, + AccountID const& dst, + MPTID const& mpt) + : src_(src) + , dst_(dst) + , mptIssue_(mpt) + , prevStep_(ctx.prevStep) + , isLast_(ctx.isLast) + , isDirectBetweenHolders_( + mptIssue_ == ctx.strandDeliver && ctx.strandSrc != mptIssue_.getIssuer() && + ctx.strandDst != mptIssue_.getIssuer() && + (ctx.isFirst || (ctx.prevStep != nullptr && !ctx.prevStep->bookStepBook()))) + , j_(ctx.j) + { + XRPL_ASSERT( + src_ == mptIssue_.getIssuer() || dst_ == mptIssue_.getIssuer(), + "MPTEndpointStep::MPTEndpointStep src or dst must be an issuer"); + } + +public: + AccountID const& + src() const + { + return src_; + } + AccountID const& + dst() const + { + return dst_; + } + MPTID const& + mptID() const + { + return mptIssue_.getMptID(); + } + + std::optional + cachedIn() const override + { + if (!cache_) + return std::nullopt; + return EitherAmount(cache_->in); + } + + std::optional + cachedOut() const override + { + if (!cache_) + return std::nullopt; + return EitherAmount(cache_->out); + } + + std::optional + directStepSrcAcct() const override + { + return src_; + } + + std::optional> + directStepAccts() const override + { + return std::make_pair(src_, dst_); + } + + DebtDirection + debtDirection(ReadView const& sb, StrandDirection dir) const override; + + std::uint32_t + lineQualityIn(ReadView const& v) const override; + + std::pair, DebtDirection> + qualityUpperBound(ReadView const& v, DebtDirection dir) const override; + + std::pair + revImp( + PaymentSandbox& sb, + ApplyView& afView, + boost::container::flat_set& ofrsToRm, + MPTAmount const& out); + + std::pair + fwdImp( + PaymentSandbox& sb, + ApplyView& afView, + boost::container::flat_set& ofrsToRm, + MPTAmount const& in); + + std::pair + validFwd(PaymentSandbox& sb, ApplyView& afView, EitherAmount const& in) override; + + // Check for error, existing liquidity, and violations of auth/frozen + // constraints. + TER + check(StrandContext const& ctx) const; + + void + setCacheLimiting( + MPTAmount const& fwdIn, + MPTAmount const& fwdSrcToDst, + MPTAmount const& fwdOut, + DebtDirection srcDebtDir); + + friend bool + operator==(MPTEndpointStep const& lhs, MPTEndpointStep const& rhs) + { + return lhs.src_ == rhs.src_ && lhs.dst_ == rhs.dst_ && lhs.mptIssue_ == rhs.mptIssue_; + } + + friend bool + operator!=(MPTEndpointStep const& lhs, MPTEndpointStep const& rhs) + { + return !(lhs == rhs); + } + +protected: + std::string + logStringImpl(char const* name) const + { + std::ostringstream ostr; + ostr << name << ": " + << "\nSrc: " << src_ << "\nDst: " << dst_; + return ostr.str(); + } + +private: + bool + equal(Step const& rhs) const override + { + if (auto ds = dynamic_cast(&rhs)) + { + return *this == *ds; + } + return false; + } + + friend TDerived; +}; + +//------------------------------------------------------------------------------ + +// Flow is used in two different circumstances for transferring funds: +// o Payments, and +// o Offer crossing. +// The rules for handling funds in these two cases are almost, but not +// quite, the same. + +// Payment MPTEndpointStep class (not offer crossing). +class MPTEndpointPaymentStep : public MPTEndpointStep +{ +public: + using MPTEndpointStep::MPTEndpointStep; + using MPTEndpointStep::check; + + MPTEndpointPaymentStep( + StrandContext const& ctx, + AccountID const& src, + AccountID const& dst, + MPTID const& mpt) + : MPTEndpointStep(ctx, src, dst, mpt) + { + } + + static bool + verifyPrevStepDebtDirection(DebtDirection) + { + // A payment doesn't care regardless of prevStepRedeems. + return true; + } + + // Verify the consistency of the step. These checks are specific to + // payments and assume that general checks were already performed. + TER + check(StrandContext const& ctx, std::shared_ptr const& sleSrc) const; + + std::string + logString() const override + { + return logStringImpl("MPTEndpointPaymentStep"); + } + + // Not applicable for payment + static TER + checkCreateMPT(ApplyView&, DebtDirection) + { + return tesSUCCESS; + } +}; + +// Offer crossing MPTEndpointStep class (not a payment). +class MPTEndpointOfferCrossingStep : public MPTEndpointStep +{ +public: + using MPTEndpointStep::MPTEndpointStep; + using MPTEndpointStep::check; + + MPTEndpointOfferCrossingStep( + StrandContext const& ctx, + AccountID const& src, + AccountID const& dst, + MPTID const& mpt) + : MPTEndpointStep(ctx, src, dst, mpt) + { + } + + static bool + verifyPrevStepDebtDirection(DebtDirection prevStepDir) + { + // During offer crossing we rely on the fact that prevStepRedeems + // will *always* issue. That's because: + // o If there's a prevStep_, it will always be a BookStep. + // o BookStep::debtDirection() always returns `issues` when offer + // crossing. + // An assert based on this return value will tell us if that + // behavior changes. + return issues(prevStepDir); + } + + // Verify the consistency of the step. These checks are specific to + // offer crossing and assume that general checks were already performed. + static TER + check(StrandContext const& ctx, std::shared_ptr const& sleSrc); + + std::string + logString() const override + { + return logStringImpl("MPTEndpointOfferCrossingStep"); + } + + // Can be created in rev or fwd (if limiting step) direction. + TER + checkCreateMPT(ApplyView& view, DebtDirection srcDebtDir); +}; + +//------------------------------------------------------------------------------ + +TER +MPTEndpointPaymentStep::check(StrandContext const& ctx, std::shared_ptr const& sleSrc) + const +{ + // Since this is a payment, MPToken must be present. Perform all + // MPToken related checks. + + // requireAuth checks if MPTIssuance exist. Note that issuer to issuer + // payment is invalid + auto const& issuer = mptIssue_.getIssuer(); + if (src_ != issuer) + { + if (auto const ter = requireAuth(ctx.view, mptIssue_, src_); !isTesSuccess(ter)) + return ter; + } + + if (dst_ != issuer) + { + if (auto const ter = requireAuth(ctx.view, mptIssue_, dst_); !isTesSuccess(ter)) + return ter; + } + + // Direct MPT payment, no DEX + if (mptIssue_ == ctx.strandDeliver && + (ctx.isFirst || (ctx.prevStep != nullptr && !ctx.prevStep->bookStepBook()))) + { + // Between holders + if (isDirectBetweenHolders_) + { + auto const& holder = ctx.isFirst ? src_ : dst_; + // Payment between the holders + if (isFrozen(ctx.view, holder, mptIssue_)) + return tecLOCKED; + + if (auto const ter = canTransfer(ctx.view, mptIssue_, holder, ctx.strandDst); + !isTesSuccess(ter)) + return ter; + } + // Don't need to check if a payment is between issuer and holder + // in either direction + } + // Cross-token MPT payment via DEX + else + { + if (auto const ter = canTrade(ctx.view, mptIssue_); !isTesSuccess(ter)) + return ter; + } + + // Can't check for creditBalance/Limit unless it's the first step. + // Otherwise, even if OutstandingAmount is equal to MaximumAmount + // a payment can still be successful. For instance, when a balance + // is shifted from one holder to another. + + if (prevStep_ == nullptr) + { + auto const owed = + accountFunds(ctx.view, src_, mptIssue_, fhIGNORE_FREEZE, ahIGNORE_AUTH, j_); + // Already at MaximumAmount + if (owed <= beast::zero) + return tecPATH_DRY; + } + + return tesSUCCESS; +} + +TER +MPTEndpointOfferCrossingStep::check(StrandContext const& ctx, std::shared_ptr const&) +{ + return tesSUCCESS; +} + +TER +MPTEndpointOfferCrossingStep::checkCreateMPT(ApplyView& view, xrpl::DebtDirection srcDebtDir) +{ + // TakerPays is the last step if offer crossing + if (isLast_) + { + // Create MPToken for the offer's owner. No need to check + // for the reserve since the offer doesn't go on the books + // if crossed. Insufficient reserve is allowed if the offer + // crossed. See CreateOffer::applyGuts() for reserve check. + if (auto const err = xrpl::checkCreateMPT(view, mptIssue_, dst_, j_); !isTesSuccess(err)) + { + JLOG(j_.trace()) << "MPTEndpointStep::checkCreateMPT: failed create MPT"; + resetCache(srcDebtDir); + return err; + } + } + return tesSUCCESS; +} + +//------------------------------------------------------------------------------ + +template +std::pair +MPTEndpointStep::maxPaymentFlow(ReadView const& sb) const +{ + auto const maxFlow = accountFunds(sb, src_, mptIssue_, fhIGNORE_FREEZE, ahIGNORE_AUTH, j_); + + // From a holder to an issuer + if (src_ != mptIssue_.getIssuer()) + return {toAmount(maxFlow), DebtDirection::redeems}; + + // From an issuer to a holder + if (auto const sle = sb.read(keylet::mptIssuance(mptIssue_))) + { + // If issuer is the source account, and it is direct payment then + // MPTEndpointStep is the only step. Provide available maxFlow. + if (prevStep_ == nullptr) + return {toAmount(maxFlow), DebtDirection::issues}; + + // MPTEndpointStep is the last step. It's always issuing in + // this case. Can't infer at this point what the maxFlow is, because + // the previous step may issue or redeem. Allow OutstandingAmount + // to temporarily overflow. Let the previous step figure out how + // to limit the flow. + std::int64_t const maxAmount = maxMPTAmount(*sle); + return {MPTAmount{maxAmount}, DebtDirection::issues}; + } + + return {MPTAmount{0}, DebtDirection::issues}; +} + +template +DebtDirection +MPTEndpointStep::debtDirection(ReadView const& sb, StrandDirection dir) const +{ + if (dir == StrandDirection::forward && cache_) + return cache_->srcDebtDir; + + return (src_ == mptIssue_.getIssuer()) ? DebtDirection::issues : DebtDirection::redeems; +} + +template +std::pair +MPTEndpointStep::revImp( + PaymentSandbox& sb, + ApplyView& /*afView*/, + boost::container::flat_set& /*ofrsToRm*/, + MPTAmount const& out) +{ + cache_.reset(); + + auto const [maxSrcToDst, srcDebtDir] = static_cast(this)->maxPaymentFlow(sb); + + auto const [srcQOut, dstQIn] = qualities(sb, srcDebtDir, StrandDirection::reverse); + (void)dstQIn; + + MPTIssue const srcToDstIss(mptIssue_); + + JLOG(j_.trace()) << "MPTEndpointStep::rev" + << " srcRedeems: " << redeems(srcDebtDir) << " outReq: " << to_string(out) + << " maxSrcToDst: " << to_string(maxSrcToDst) << " srcQOut: " << srcQOut + << " dstQIn: " << dstQIn; + + if (maxSrcToDst.signum() <= 0) + { + JLOG(j_.trace()) << "MPTEndpointStep::rev: dry"; + resetCache(srcDebtDir); + return {beast::zero, beast::zero}; + } + + if (auto const err = static_cast(this)->checkCreateMPT(sb, srcDebtDir); + !isTesSuccess(err)) + return {beast::zero, beast::zero}; + + // Don't have to factor in dstQIn since it is always QUALITY_ONE + MPTAmount const srcToDst = out; + + if (srcToDst <= maxSrcToDst) + { + MPTAmount const in = mulRatio(srcToDst, srcQOut, QUALITY_ONE, /*roundUp*/ true); + cache_.emplace(in, srcToDst, srcToDst, srcDebtDir); + auto const ter = directSendNoFee( + sb, + src_, + dst_, + toSTAmount(srcToDst, srcToDstIss), + /*checkIssuer*/ false, + j_); + if (!isTesSuccess(ter)) + { + JLOG(j_.trace()) << "MPTEndpointStep::rev: error " << ter; + resetCache(srcDebtDir); + return {beast::zero, beast::zero}; + } + JLOG(j_.trace()) << "MPTEndpointStep::rev: Non-limiting" + << " srcRedeems: " << redeems(srcDebtDir) << " in: " << to_string(in) + << " srcToDst: " << to_string(srcToDst) << " out: " << to_string(out); + return {in, out}; + } + + // limiting node + MPTAmount const in = mulRatio(maxSrcToDst, srcQOut, QUALITY_ONE, /*roundUp*/ true); + // Don't have to factor in dsqQIn since it's always QUALITY_ONE + MPTAmount const actualOut = maxSrcToDst; + cache_.emplace(in, maxSrcToDst, actualOut, srcDebtDir); + + auto const ter = directSendNoFee( + sb, + src_, + dst_, + toSTAmount(maxSrcToDst, srcToDstIss), + /*checkIssuer*/ false, + j_); + if (!isTesSuccess(ter)) + { + JLOG(j_.trace()) << "MPTEndpointStep::rev: error " << ter; + resetCache(srcDebtDir); + return {beast::zero, beast::zero}; + } + JLOG(j_.trace()) << "MPTEndpointStep::rev: Limiting" + << " srcRedeems: " << redeems(srcDebtDir) << " in: " << to_string(in) + << " srcToDst: " << to_string(maxSrcToDst) << " out: " << to_string(out); + return {in, actualOut}; +} + +// The forward pass should never have more liquidity than the reverse +// pass. But sometimes rounding differences cause the forward pass to +// deliver more liquidity. Use the cached values from the reverse pass +// to prevent this. +template +void +MPTEndpointStep::setCacheLimiting( + MPTAmount const& fwdIn, + MPTAmount const& fwdSrcToDst, + MPTAmount const& fwdOut, + DebtDirection srcDebtDir) +{ + if (cache_->in < fwdIn) + { + MPTAmount const smallDiff(1); + auto const diff = fwdIn - cache_->in; + if (diff > smallDiff) + { + if (!cache_->in.value() || + (Number(fwdIn.value()) / Number(cache_->in.value())) > Number(101, -2)) + { + // Detect large diffs on forward pass so they may be + // investigated + JLOG(j_.warn()) << "MPTEndpointStep::fwd: setCacheLimiting" + << " fwdIn: " << to_string(fwdIn) + << " cacheIn: " << to_string(cache_->in) + << " fwdSrcToDst: " << to_string(fwdSrcToDst) + << " cacheSrcToDst: " << to_string(cache_->srcToDst) + << " fwdOut: " << to_string(fwdOut) + << " cacheOut: " << to_string(cache_->out); + cache_.emplace(fwdIn, fwdSrcToDst, fwdOut, srcDebtDir); + return; + } + } + } + cache_->in = fwdIn; + if (fwdSrcToDst < cache_->srcToDst) + cache_->srcToDst = fwdSrcToDst; + if (fwdOut < cache_->out) + cache_->out = fwdOut; + cache_->srcDebtDir = srcDebtDir; +}; + +template +std::pair +MPTEndpointStep::fwdImp( + PaymentSandbox& sb, + ApplyView& /*afView*/, + boost::container::flat_set& /*ofrsToRm*/, + MPTAmount const& in) +{ + XRPL_ASSERT(cache_, "MPTEndpointStep::fwdImp : valid cache"); + + auto const [maxSrcToDst, srcDebtDir] = static_cast(this)->maxPaymentFlow(sb); + + auto const [srcQOut, dstQIn] = qualities(sb, srcDebtDir, StrandDirection::forward); + (void)dstQIn; + + MPTIssue const srcToDstIss(mptIssue_); + + JLOG(j_.trace()) << "MPTEndpointStep::fwd" + << " srcRedeems: " << redeems(srcDebtDir) << " inReq: " << to_string(in) + << " maxSrcToDst: " << to_string(maxSrcToDst) << " srcQOut: " << srcQOut + << " dstQIn: " << dstQIn; + + if (maxSrcToDst.signum() <= 0) + { + JLOG(j_.trace()) << "MPTEndpointStep::fwd: dry"; + resetCache(srcDebtDir); + return {beast::zero, beast::zero}; + } + + if (auto const err = static_cast(this)->checkCreateMPT(sb, srcDebtDir); + !isTesSuccess(err)) + return {beast::zero, beast::zero}; + + MPTAmount const srcToDst = mulRatio(in, QUALITY_ONE, srcQOut, /*roundUp*/ false); + + if (srcToDst <= maxSrcToDst) + { + // Don't have to factor in dstQIn since it's always QUALITY_ONE + MPTAmount const out = srcToDst; + setCacheLimiting(in, srcToDst, out, srcDebtDir); + auto const ter = directSendNoFee( + sb, + src_, + dst_, + toSTAmount(cache_->srcToDst, srcToDstIss), + /*checkIssuer*/ false, + j_); + if (!isTesSuccess(ter)) + { + JLOG(j_.trace()) << "MPTEndpointStep::fwd: error " << ter; + resetCache(srcDebtDir); + return {beast::zero, beast::zero}; + } + JLOG(j_.trace()) << "MPTEndpointStep::fwd: Non-limiting" + << " srcRedeems: " << redeems(srcDebtDir) << " in: " << to_string(in) + << " srcToDst: " << to_string(srcToDst) << " out: " << to_string(out); + } + else + { + // limiting node + MPTAmount const actualIn = mulRatio(maxSrcToDst, srcQOut, QUALITY_ONE, /*roundUp*/ true); + // Don't have to factor in dstQIn since it's always QUALITY_ONE + MPTAmount const out = maxSrcToDst; + setCacheLimiting(actualIn, maxSrcToDst, out, srcDebtDir); + auto const ter = directSendNoFee( + sb, + src_, + dst_, + toSTAmount(cache_->srcToDst, srcToDstIss), + /*checkIssuer*/ false, + j_); + if (!isTesSuccess(ter)) + { + JLOG(j_.trace()) << "MPTEndpointStep::fwd: error " << ter; + resetCache(srcDebtDir); + return {beast::zero, beast::zero}; + } + JLOG(j_.trace()) << "MPTEndpointStep::fwd: Limiting" + << " srcRedeems: " << redeems(srcDebtDir) << " in: " << to_string(actualIn) + << " srcToDst: " << to_string(srcToDst) << " out: " << to_string(out); + } + return {cache_->in, cache_->out}; +} + +template +std::pair +MPTEndpointStep::validFwd(PaymentSandbox& sb, ApplyView& afView, EitherAmount const& in) +{ + if (!cache_) + { + JLOG(j_.trace()) << "Expected valid cache in validFwd"; + return {false, EitherAmount(MPTAmount(beast::zero))}; + } + + auto const savCache = *cache_; + + XRPL_ASSERT(in.holds(), "MPTEndpoint::validFwd : is MPT"); + + auto const [maxSrcToDst, srcDebtDir] = static_cast(this)->maxPaymentFlow(sb); + (void)srcDebtDir; + + try + { + boost::container::flat_set dummy; + fwdImp(sb, afView, dummy, in.get()); // changes cache + } + catch (FlowException const&) + { + return {false, EitherAmount(MPTAmount(beast::zero))}; + } + + if (maxSrcToDst < cache_->srcToDst) + { + JLOG(j_.warn()) << "MPTEndpointStep: Strand re-execute check failed." + << " Exceeded max src->dst limit" + << " max src->dst: " << to_string(maxSrcToDst) + << " actual src->dst: " << to_string(cache_->srcToDst); + return {false, EitherAmount(cache_->out)}; + } + + if (!(checkNear(savCache.in, cache_->in) && checkNear(savCache.out, cache_->out))) + { + JLOG(j_.warn()) << "MPTEndpointStep: Strand re-execute check failed." + << " ExpectedIn: " << to_string(savCache.in) + << " CachedIn: " << to_string(cache_->in) + << " ExpectedOut: " << to_string(savCache.out) + << " CachedOut: " << to_string(cache_->out); + return {false, EitherAmount(cache_->out)}; + } + return {true, EitherAmount(cache_->out)}; +} + +// Returns srcQOut, dstQIn +template +std::pair +MPTEndpointStep::qualitiesSrcRedeems(ReadView const& sb) const +{ + if (prevStep_ == nullptr) + return {QUALITY_ONE, QUALITY_ONE}; + + auto const prevStepQIn = prevStep_->lineQualityIn(sb); + // Unlike trustline MPT doesn't have line quality field + auto srcQOut = QUALITY_ONE; + + srcQOut = std::max(prevStepQIn, srcQOut); + return {srcQOut, QUALITY_ONE}; +} + +// Returns srcQOut, dstQIn +template +std::pair +MPTEndpointStep::qualitiesSrcIssues( + ReadView const& sb, + DebtDirection prevStepDebtDirection) const +{ + // Charge a transfer rate when issuing and previous step redeems + + XRPL_ASSERT( + static_cast(this)->verifyPrevStepDebtDirection(prevStepDebtDirection), + "MPTEndpointStep::qualitiesSrcIssues : verify prev step debt " + "direction"); + + std::uint32_t const srcQOut = + redeems(prevStepDebtDirection) ? transferRate(sb, mptIssue_.getMptID()).value : QUALITY_ONE; + + // Unlike trustline, MPT doesn't have line quality field + return {srcQOut, QUALITY_ONE}; +} + +// Returns srcQOut, dstQIn +template +std::pair +MPTEndpointStep::qualities( + ReadView const& sb, + DebtDirection srcDebtDir, + StrandDirection strandDir) const +{ + if (redeems(srcDebtDir)) + { + return qualitiesSrcRedeems(sb); + } + + auto const prevStepDebtDirection = [&] { + if (prevStep_ != nullptr) + return prevStep_->debtDirection(sb, strandDir); + return DebtDirection::issues; + }(); + return qualitiesSrcIssues(sb, prevStepDebtDirection); +} + +template +std::uint32_t +MPTEndpointStep::lineQualityIn(ReadView const& v) const +{ + // dst quality in + return QUALITY_ONE; +} + +template +std::pair, DebtDirection> +MPTEndpointStep::qualityUpperBound(ReadView const& v, DebtDirection prevStepDir) const +{ + auto const dir = this->debtDirection(v, StrandDirection::forward); + + auto const [srcQOut, dstQIn] = + redeems(dir) ? qualitiesSrcRedeems(v) : qualitiesSrcIssues(v, prevStepDir); + (void)dstQIn; + + MPTIssue const iss{mptIssue_}; + // Be careful not to switch the parameters to `getRate`. The + // `getRate(offerOut, offerIn)` function is usually used for offers. It + // returns offerIn/offerOut. For a direct step, the rate is srcQOut/dstQIn + // (Input*dstQIn/srcQOut = Output; So rate = srcQOut/dstQIn). Although the + // first parameter is called `offerOut`, it should take the `dstQIn` + // variable. + return {Quality(getRate(STAmount(iss, QUALITY_ONE), STAmount(iss, srcQOut))), dir}; +} + +template +TER +MPTEndpointStep::check(StrandContext const& ctx) const +{ + // The following checks apply for both payments and offer crossing. + if (!src_ || !dst_) + { + JLOG(j_.debug()) << "MPTEndpointStep: specified bad account."; + return temBAD_PATH; + } + + if (src_ == dst_) + { + JLOG(j_.debug()) << "MPTEndpointStep: same src and dst."; + return temBAD_PATH; + } + + auto const sleSrc = ctx.view.read(keylet::account(src_)); + if (!sleSrc) + { + JLOG(j_.warn()) << "MPTEndpointStep: can't receive MPT from non-existent issuer: " << src_; + return terNO_ACCOUNT; + } + + // pure issue/redeem can't be frozen (issuer/holder) + if (!(ctx.isLast && ctx.isFirst)) + { + auto const& account = ctx.isFirst ? src_ : dst_; + if (isFrozen(ctx.view, account, mptIssue_)) + return terLOCKED; + } + + if (ctx.seenBookOuts.count(mptIssue_) > 0) + { + if (ctx.prevStep == nullptr) + { + UNREACHABLE( + "xrpl::MPTEndpointStep::check : prev seen book without a " + "prev step"); + return temBAD_PATH_LOOP; + } + + // This is OK if the previous step is a book step that outputs this + // issue + if (auto book = ctx.prevStep->bookStepBook()) + { + if (book->out.get() != mptIssue_) + return temBAD_PATH_LOOP; + } + } + + if ((ctx.isFirst && !ctx.seenDirectAssets[0].insert(mptIssue_).second) || + (ctx.isLast && !ctx.seenDirectAssets[1].insert(mptIssue_).second)) + { + JLOG(j_.debug()) << "MPTEndpointStep: loop detected: Index: " << ctx.strandSize << ' ' + << *this; + return temBAD_PATH_LOOP; + } + + // MPT can only be an endpoint + if (!ctx.isLast && !ctx.isFirst) + { + JLOG(j_.warn()) << "MPTEndpointStep: MPT can only be an endpoint"; + return temBAD_PATH; + } + + auto const& issuer = mptIssue_.getIssuer(); + if ((src_ != issuer && dst_ != issuer) || (src_ == issuer && dst_ == issuer)) + { + JLOG(j_.warn()) << "MPTEndpointStep: invalid src/dst"; + return temBAD_PATH; + } + + return static_cast(this)->check(ctx, sleSrc); +} + +template +void +MPTEndpointStep::resetCache(xrpl::DebtDirection dir) +{ + cache_.emplace(MPTAmount(beast::zero), MPTAmount(beast::zero), MPTAmount(beast::zero), dir); +} + +//------------------------------------------------------------------------------ + +std::pair> +make_MPTEndpointStep( + StrandContext const& ctx, + AccountID const& src, + AccountID const& dst, + MPTID const& mpt) +{ + TER ter = tefINTERNAL; + std::unique_ptr r; + if (ctx.offerCrossing != OfferCrossing::no) + { + auto offerCrossingStep = std::make_unique(ctx, src, dst, mpt); + ter = offerCrossingStep->check(ctx); + r = std::move(offerCrossingStep); + } + else // payment + { + auto paymentStep = std::make_unique(ctx, src, dst, mpt); + ter = paymentStep->check(ctx); + r = std::move(paymentStep); + } + if (!isTesSuccess(ter)) + return {ter, nullptr}; + + return {tesSUCCESS, std::move(r)}; +} + +namespace test { +// Needed for testing +bool +mptEndpointStepEqual( + Step const& step, + AccountID const& src, + AccountID const& dst, + MPTID const& mptid) +{ + if (auto ds = dynamic_cast const*>(&step)) + { + return ds->src() == src && ds->dst() == dst && ds->mptID() == mptid; + } + return false; +} +} // namespace test + +} // namespace xrpl diff --git a/src/libxrpl/tx/paths/OfferStream.cpp b/src/libxrpl/tx/paths/OfferStream.cpp index acb2df1429..c7e81ba203 100644 --- a/src/libxrpl/tx/paths/OfferStream.cpp +++ b/src/libxrpl/tx/paths/OfferStream.cpp @@ -1,11 +1,34 @@ -#include -#include -#include -#include -#include -#include #include -#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include namespace xrpl { @@ -13,14 +36,15 @@ namespace { bool checkIssuers(ReadView const& view, Book const& book) { - auto issuerExists = [](ReadView const& view, Issue const& iss) -> bool { - return isXRP(iss.account) || view.read(keylet::account(iss.account)); + auto issuerExists = [](ReadView const& view, Asset const& asset) -> bool { + auto const& issuer = asset.getIssuer(); + return isXRP(issuer) || view.exists(keylet::account(issuer)); }; return issuerExists(view, book.in) && issuerExists(view, book.out); } } // namespace -template +template TOfferStreamBase::TOfferStreamBase( ApplyView& view, ApplyView& cancelView, @@ -42,7 +66,7 @@ TOfferStreamBase::TOfferStreamBase( // Handle the case where a directory item with no corresponding ledger entry // is found. This shouldn't happen but if it does we clean it up. -template +template void TOfferStreamBase::erase(ApplyView& view) { @@ -59,7 +83,7 @@ TOfferStreamBase::erase(ApplyView& view) } auto v(p->getFieldV256(sfIndexes)); - auto it(std::find(v.begin(), v.end(), tip_.index())); + auto it(std::ranges::find(v, tip_.index())); if (it == v.end()) { @@ -75,67 +99,42 @@ TOfferStreamBase::erase(ApplyView& view) << tip_.dir(); } -static STAmount +template +static T accountFundsHelper( ReadView const& view, AccountID const& id, - STAmount const& saDefault, - Issue const&, + T const& amtDefault, + Asset const& asset, FreezeHandling freezeHandling, + AuthHandling authHandling, beast::Journal j) { - return accountFunds(view, id, saDefault, freezeHandling, j); -} - -static IOUAmount -accountFundsHelper( - ReadView const& view, - AccountID const& id, - IOUAmount const& amtDefault, - Issue const& issue, - FreezeHandling freezeHandling, - beast::Journal j) -{ - if (issue.account == id) + if constexpr (std::is_same_v) { - // self funded - return amtDefault; + if (id == asset.getIssuer()) + { + // self funded + return amtDefault; + } + } + else if constexpr (std::is_same_v) + { + if (id == asset.getIssuer()) + { + return toAmount(issuerFundsToSelfIssue(view, asset.get())); + } } - return toAmount( - accountHolds(view, id, issue.currency, issue.account, freezeHandling, j)); + return toAmount(accountHolds(view, id, asset, freezeHandling, authHandling, j)); } -static XRPAmount -accountFundsHelper( - ReadView const& view, - AccountID const& id, - XRPAmount const& amtDefault, - Issue const& issue, - FreezeHandling freezeHandling, - beast::Journal j) -{ - return toAmount( - accountHolds(view, id, issue.currency, issue.account, freezeHandling, j)); -} - -template +template template + requires ValidTaker bool TOfferStreamBase::shouldRmSmallIncreasedQOffer() const { - static_assert( - std::is_same_v || std::is_same_v, - "STAmount is not supported"); - - static_assert( - std::is_same_v || std::is_same_v, - "STAmount is not supported"); - - static_assert( - !std::is_same_v || !std::is_same_v, - "Cannot have XRP/XRP offers"); - // Consider removing the offer if: // o `TakerPays` is XRP (because of XRP drops granularity) or // o `TakerPays` and `TakerGets` are both IOU and `TakerPays`<`TakerGets` @@ -156,14 +155,14 @@ TOfferStreamBase::shouldRmSmallIncreasedQOffer() const if constexpr (!inIsXRP && !outIsXRP) { - if (ofrAmts.in >= ofrAmts.out) + if (Number(ofrAmts.in) >= Number(ofrAmts.out)) return false; } TTakerGets const ownerFunds = toAmount(*ownerFunds_); auto const effectiveAmounts = [&] { - if (offer_.owner() != offer_.issueOut().account && ownerFunds < ofrAmts.out) + if (offer_.owner() != offer_.assetOut().getIssuer() && ownerFunds < ofrAmts.out) { // adjust the amounts by owner funds. // @@ -185,7 +184,7 @@ TOfferStreamBase::shouldRmSmallIncreasedQOffer() const return effectiveQuality < offer_.quality(); } -template +template bool TOfferStreamBase::step() { @@ -240,9 +239,7 @@ TOfferStreamBase::step() continue; } - bool const deepFrozen = isDeepFrozen( - view_, offer_.owner(), offer_.issueIn().currency, offer_.issueIn().account); - if (deepFrozen) + if (isDeepFrozen(view_, offer_.owner(), offer_.assetIn())) { JLOG(j_.trace()) << "Removing deep frozen unfunded offer " << entry->key(); permRmOffer(entry->key()); @@ -262,7 +259,13 @@ TOfferStreamBase::step() // Calculate owner funds ownerFunds_ = accountFundsHelper( - view_, offer_.owner(), amount.out, offer_.issueOut(), fhZERO_IF_FROZEN, j_); + view_, + offer_.owner(), + amount.out, + offer_.assetOut(), + fhZERO_IF_FROZEN, + ahZERO_IF_UNAUTHORIZED, + j_); // Check for unfunded offer if (*ownerFunds_ <= beast::zero) @@ -271,7 +274,13 @@ TOfferStreamBase::step() // we haven't modified the balance and therefore the // offer is "found unfunded" versus "became unfunded" auto const original_funds = accountFundsHelper( - cancelView_, offer_.owner(), amount.out, offer_.issueOut(), fhZERO_IF_FROZEN, j_); + cancelView_, + offer_.owner(), + amount.out, + offer_.assetOut(), + fhZERO_IF_FROZEN, + ahZERO_IF_UNAUTHORIZED, + j_); if (original_funds == *ownerFunds_) { @@ -287,44 +296,16 @@ TOfferStreamBase::step() continue; } - bool const rmSmallIncreasedQOffer = [&] { - bool const inIsXRP = isXRP(offer_.issueIn()); - bool const outIsXRP = isXRP(offer_.issueOut()); - if (inIsXRP && !outIsXRP) - { - // Without the `if constexpr`, the - // `shouldRmSmallIncreasedQOffer` template will be instantiated - // even if it is never used. This can cause compiler errors in - // some cases, hence the `if constexpr` guard. - // Note that TIn can be XRPAmount or STAmount, and TOut can be - // IOUAmount or STAmount. - if constexpr (!(std::is_same_v || std::is_same_v)) - return shouldRmSmallIncreasedQOffer(); - } - if (!inIsXRP && outIsXRP) - { - // See comment above for `if constexpr` rationale - if constexpr (!(std::is_same_v || std::is_same_v)) - return shouldRmSmallIncreasedQOffer(); - } - if (!inIsXRP && !outIsXRP) - { - // See comment above for `if constexpr` rationale - if constexpr (!(std::is_same_v || std::is_same_v)) - return shouldRmSmallIncreasedQOffer(); - } - // LCOV_EXCL_START - UNREACHABLE( - "xrpl::TOfferStreamBase::step::rmSmallIncreasedQOffer : XRP " - "vs XRP offer"); - return false; - // LCOV_EXCL_STOP - }(); - - if (rmSmallIncreasedQOffer) + if (shouldRmSmallIncreasedQOffer()) { auto const original_funds = accountFundsHelper( - cancelView_, offer_.owner(), amount.out, offer_.issueOut(), fhZERO_IF_FROZEN, j_); + cancelView_, + offer_.owner(), + amount.out, + offer_.assetOut(), + fhZERO_IF_FROZEN, + ahZERO_IF_UNAUTHORIZED, + j_); if (original_funds == *ownerFunds_) { @@ -348,26 +329,28 @@ TOfferStreamBase::step() return true; } -void -OfferStream::permRmOffer(uint256 const& offerIndex) -{ - offerDelete(cancelView_, cancelView_.peek(keylet::offer(offerIndex)), j_); -} - -template +template void FlowOfferStream::permRmOffer(uint256 const& offerIndex) { permToRemove_.insert(offerIndex); } -template class FlowOfferStream; template class FlowOfferStream; template class FlowOfferStream; template class FlowOfferStream; +template class FlowOfferStream; +template class FlowOfferStream; +template class FlowOfferStream; +template class FlowOfferStream; +template class FlowOfferStream; -template class TOfferStreamBase; template class TOfferStreamBase; template class TOfferStreamBase; template class TOfferStreamBase; +template class TOfferStreamBase; +template class TOfferStreamBase; +template class TOfferStreamBase; +template class TOfferStreamBase; +template class TOfferStreamBase; } // namespace xrpl diff --git a/src/libxrpl/tx/paths/PaySteps.cpp b/src/libxrpl/tx/paths/PaySteps.cpp index 33b63f7714..d3fe366616 100644 --- a/src/libxrpl/tx/paths/PaySteps.cpp +++ b/src/libxrpl/tx/paths/PaySteps.cpp @@ -1,11 +1,33 @@ +#include +#include +#include #include +#include +#include #include #include +#include +#include #include -#include +#include +#include +#include +#include +#include +#include #include +#include + #include +#include +#include +#include +#include +#include +#include +#include +#include namespace xrpl { @@ -32,12 +54,6 @@ checkNear(IOUAmount const& expected, IOUAmount const& actual) return r <= ratTol; }; -bool -checkNear(XRPAmount const& expected, XRPAmount const& actual) -{ - return expected == actual; -}; - static bool isXRPAccount(STPathElement const& pe) { @@ -51,12 +67,12 @@ toStep( StrandContext const& ctx, STPathElement const* e1, STPathElement const* e2, - Issue const& curIssue) + Asset const& curAsset) { auto& j = ctx.j; if (ctx.isFirst && e1->isAccount() && - ((e1->getNodeType() & STPathElement::typeCurrency) != 0u) && isXRP(e1->getCurrency())) + ((e1->getNodeType() & STPathElement::typeCurrency) != 0u) && e1->getPathAsset().isXRP()) { return make_XRPEndpointStep(ctx, e1->getAccountID()); } @@ -64,9 +80,32 @@ toStep( if (ctx.isLast && isXRPAccount(*e1) && e2->isAccount()) return make_XRPEndpointStep(ctx, e2->getAccountID()); + // MPTEndpointStep is created in following cases: + // 1 Direct payment between an issuer and a holder + // e1 is issuer and e2 is holder or vise versa + // There is only one step in this case: holder->issuer or + // issuer->holder + // 2 Direct payment between the holders + // e1 is issuer and e2 is holder or vise versa + // There are two steps in this case: holder->issuer->holder1 + // 3 Cross-token payment with Amount or SendMax or both MPT + // If destination is an issuer then the last step is BookStep, + // otherwise the last step is MPTEndpointStep where e1 is + // the issuer and e2 is the holder. + // In all cases MPTEndpointStep is always first or last step, + // e1/e2 are always account types, and curAsset is always MPT. + if (e1->isAccount() && e2->isAccount()) { - return make_DirectStepI(ctx, e1->getAccountID(), e2->getAccountID(), curIssue.currency); + return curAsset.visit( + [&](MPTIssue const& issue) { + return make_MPTEndpointStep( + ctx, e1->getAccountID(), e2->getAccountID(), issue.getMptID()); + }, + [&](Issue const& issue) { + return make_DirectStepI( + ctx, e1->getAccountID(), e2->getAccountID(), issue.currency); + }); } if (e1->isOffer() && e2->isAccount()) @@ -80,17 +119,16 @@ toStep( } XRPL_ASSERT( - (e2->getNodeType() & STPathElement::typeCurrency) || + (e2->getNodeType() & STPathElement::typeAsset) || (e2->getNodeType() & STPathElement::typeIssuer), "xrpl::toStep : currency or issuer"); - auto const outCurrency = ((e2->getNodeType() & STPathElement::typeCurrency) != 0u) - ? e2->getCurrency() - : curIssue.currency; + PathAsset const outAsset = + ((e2->getNodeType() & STPathElement::typeAsset) != 0u) ? e2->getPathAsset() : curAsset; auto const outIssuer = ((e2->getNodeType() & STPathElement::typeIssuer) != 0u) ? e2->getIssuerID() - : curIssue.account; + : curAsset.getIssuer(); - if (isXRP(curIssue.currency) && isXRP(outCurrency)) + if (isXRP(curAsset) && outAsset.isXRP()) { JLOG(j.info()) << "Found xrp/xrp offer payment step"; return {temBAD_PATH, std::unique_ptr{}}; @@ -98,13 +136,35 @@ toStep( XRPL_ASSERT(e2->isOffer(), "xrpl::toStep : is offer"); - if (isXRP(outCurrency)) - return make_BookStepIX(ctx, curIssue); + if (outAsset.isXRP()) + { + return curAsset.visit( + [&](MPTIssue const& issue) { return make_BookStepMX(ctx, issue); }, + [&](Issue const& issue) { return make_BookStepIX(ctx, issue); }); + } - if (isXRP(curIssue.currency)) - return make_BookStepXI(ctx, {outCurrency, outIssuer}); + if (isXRP(curAsset)) + { + return outAsset.visit( + [&](MPTID const& mpt) { return make_BookStepXM(ctx, mpt); }, + [&](Currency const& currency) { return make_BookStepXI(ctx, {currency, outIssuer}); }); + } - return make_BookStepII(ctx, curIssue, {outCurrency, outIssuer}); + return curAsset.visit( + [&](MPTIssue const& issue) { + return outAsset.visit( + [&](Currency const& currency) { + return make_BookStepMI(ctx, issue, {currency, outIssuer}); + }, + [&](MPTID const& mpt) { return make_BookStepMM(ctx, issue, mpt); }); + }, + [&](Issue const& issue) { + return outAsset.visit( + [&](MPTID const& mpt) { return make_BookStepIM(ctx, issue, mpt); }, + [&](Currency const& currency) { + return make_BookStepII(ctx, issue, {currency, outIssuer}); + }); + }); } std::pair @@ -112,9 +172,9 @@ toStrand( ReadView const& view, AccountID const& src, AccountID const& dst, - Issue const& deliver, + Asset const& deliver, std::optional const& limitQuality, - std::optional const& sendMaxIssue, + std::optional const& sendMaxAsset, STPath const& path, bool ownerPaysTransferFee, OfferCrossing offerCrossing, @@ -123,15 +183,21 @@ toStrand( beast::Journal j) { if (isXRP(src) || isXRP(dst) || !isConsistent(deliver) || - (sendMaxIssue && !isConsistent(*sendMaxIssue))) + (sendMaxAsset && !isConsistent(*sendMaxAsset))) return {temBAD_PATH, Strand{}}; - if ((sendMaxIssue && sendMaxIssue->account == noAccount()) || (src == noAccount()) || - (dst == noAccount()) || (deliver.account == noAccount())) + if ((sendMaxAsset && sendMaxAsset->getIssuer() == noAccount()) || (src == noAccount()) || + (dst == noAccount()) || (deliver.getIssuer() == noAccount())) return {temBAD_PATH, Strand{}}; - for (auto const& pe : path) + if ((deliver.holds() && deliver.getIssuer() == beast::zero) || + (sendMaxAsset && sendMaxAsset->holds() && + sendMaxAsset->getIssuer() == beast::zero)) + return {temBAD_PATH, Strand{}}; + + for (std::size_t i = 0; i < path.size(); ++i) { + auto const& pe = path[i]; auto const t = pe.getNodeType(); if (((t & ~STPathElement::typeAll) != 0u) || (t == 0u)) @@ -140,6 +206,8 @@ toStrand( bool const hasAccount = (t & STPathElement::typeAccount) != 0u; bool const hasIssuer = (t & STPathElement::typeIssuer) != 0u; bool const hasCurrency = (t & STPathElement::typeCurrency) != 0u; + bool const hasMPT = (t & STPathElement::typeMPT) != 0u; + bool const hasAsset = (t & STPathElement::typeAsset) != 0u; if (hasAccount && (hasIssuer || hasCurrency)) return {temBAD_PATH, Strand{}}; @@ -158,17 +226,33 @@ toStrand( if (hasAccount && (pe.getAccountID() == noAccount())) return {temBAD_PATH, Strand{}}; + + if (hasMPT && (hasCurrency || hasAccount)) + return {temBAD_PATH, Strand{}}; + + if (hasMPT && hasIssuer && (pe.getIssuerID() != getMPTIssuer(pe.getMPTID()))) + return {temBAD_PATH, Strand{}}; + + // No rippling if MPT + if (i > 0 && path[i - 1].hasMPT() && (hasAccount || (hasIssuer && !hasAsset))) + return {temBAD_PATH, Strand{}}; } - Issue curIssue = [&] { - auto const& currency = sendMaxIssue ? sendMaxIssue->currency : deliver.currency; - if (isXRP(currency)) - return xrpIssue(); - return Issue{currency, src}; + Asset curAsset = [&]() -> Asset { + auto const& asset = sendMaxAsset ? *sendMaxAsset : deliver; + return asset.visit( + [&](MPTIssue const& issue) -> Asset { return asset; }, + [&](Issue const& issue) -> Asset { + if (isXRP(asset)) + return xrpIssue(); + // First step ripples from the source to the issuer. + return Issue{issue.currency, src}; + }); }(); - auto hasCurrency = [](STPathElement const pe) { - return pe.getNodeType() & STPathElement::typeCurrency; + // Currency or MPT + auto hasAsset = [](STPathElement const pe) { + return pe.getNodeType() & STPathElement::typeAsset; }; std::vector normPath; @@ -176,13 +260,28 @@ toStrand( // sendmax and deliver. normPath.reserve(4 + path.size()); { - normPath.emplace_back(STPathElement::typeAll, src, curIssue.currency, curIssue.account); + // The first step of a path is always implied to be the sender of the + // transaction, as defined by the transaction's Account field. The Asset + // is either SendMax or Deliver. + auto const t = [&]() { + auto const t = STPathElement::typeAccount | STPathElement::typeIssuer; + return curAsset.visit( + [&](MPTIssue const&) { return t | STPathElement::typeMPT; }, + [&](Issue const&) { return t | STPathElement::typeCurrency; }); + }(); + // If MPT then the issuer is the actual issuer, it is never the source + // account. + normPath.emplace_back(t, src, curAsset, curAsset.getIssuer()); - if (sendMaxIssue && sendMaxIssue->account != src && + // If transaction includes SendMax with the issuer, which is not + // the sender of the transaction, that issuer is implied to be + // the second step of the path. Unless the path starts at an address, + // which is the issuer of SendMax. + if (sendMaxAsset && sendMaxAsset->getIssuer() != src && (path.empty() || !path[0].isAccount() || - path[0].getAccountID() != sendMaxIssue->account)) + path[0].getAccountID() != sendMaxAsset->getIssuer())) { - normPath.emplace_back(sendMaxIssue->account, std::nullopt, std::nullopt); + normPath.emplace_back(sendMaxAsset->getIssuer(), std::nullopt, std::nullopt); } for (auto const& i : path) @@ -190,22 +289,32 @@ toStrand( { // Note that for offer crossing (only) we do use an offer book - // even if all that is changing is the Issue.account. - STPathElement const& lastCurrency = - *std::find_if(normPath.rbegin(), normPath.rend(), hasCurrency); - if ((lastCurrency.getCurrency() != deliver.currency) || - ((offerCrossing != 0u) && lastCurrency.getIssuerID() != deliver.account)) + // even if all that is changing is the Issue.account. Note + // that MPTIssue can't change the account. + STPathElement const& lastAsset = + *std::ranges::find_if(std::ranges::reverse_view(normPath), hasAsset); + if (lastAsset.getPathAsset() != deliver || + (offerCrossing != OfferCrossing::no && + lastAsset.getIssuerID() != deliver.getIssuer())) { - normPath.emplace_back(std::nullopt, deliver.currency, deliver.account); + normPath.emplace_back(std::nullopt, deliver, deliver.getIssuer()); } } - if (!((normPath.back().isAccount() && normPath.back().getAccountID() == deliver.account) || - (dst == deliver.account))) + // If the Amount field of the transaction includes an issuer that is not + // the same as the Destination of the transaction, that issuer is + // implied to be the second-to-last step of the path. If normPath.back + // is an offer, which sells MPT then the added path element account is + // the MPT's issuer. + if (!((normPath.back().isAccount() && + normPath.back().getAccountID() == deliver.getIssuer()) || + (dst == deliver.getIssuer()))) { - normPath.emplace_back(deliver.account, std::nullopt, std::nullopt); + normPath.emplace_back(deliver.getIssuer(), std::nullopt, std::nullopt); } + // Last step of a path is always implied to be the receiver of a + // transaction, as defined by the transaction's Destination field. if (!normPath.back().isAccount() || normPath.back().getAccountID() != dst) { normPath.emplace_back(dst, std::nullopt, std::nullopt); @@ -227,11 +336,11 @@ toStrand( at most twice: once as a src and once as a dst (hence the two element array). The strandSrc and strandDst will only show up once each. */ - std::array, 2> seenDirectIssues; + std::array, 2> seenDirectAssets; // A strand may not include the same offer book more than once - boost::container::flat_set seenBookOuts; - seenDirectIssues[0].reserve(normPath.size()); - seenDirectIssues[1].reserve(normPath.size()); + boost::container::flat_set seenBookOuts; + seenDirectAssets[0].reserve(normPath.size()); + seenDirectAssets[1].reserve(normPath.size()); seenBookOuts.reserve(normPath.size()); auto ctx = [&](bool isLast = false) { return StrandContext{ @@ -245,7 +354,7 @@ toStrand( ownerPaysTransferFee, offerCrossing, isDefaultPath, - seenDirectIssues, + seenDirectAssets, seenBookOuts, ammContext, domainID, @@ -265,58 +374,96 @@ toStrand( auto cur = &normPath[i]; auto const next = &normPath[i + 1]; - if (cur->isAccount()) + // Switch over from MPT to Currency. In this case curAsset account + // can be different from the issuer. If cur is MPT then curAsset + // is just set to MPTID. + if (curAsset.holds() && cur->hasCurrency()) { - curIssue.account = cur->getAccountID(); - } - else if (cur->hasIssuer()) - { - curIssue.account = cur->getIssuerID(); + curAsset = Issue{}; } + // Can only update the account for Issue since MPTIssue's account + // is immutable as it is part of MPTID. + curAsset.visit( + [&](Issue const&) { + if (cur->isAccount()) + { + curAsset.get().account = cur->getAccountID(); + } + else if (cur->hasIssuer()) + { + curAsset.get().account = cur->getIssuerID(); + } + }, + [](MPTIssue const&) {}); + if (cur->hasCurrency()) { - curIssue.currency = cur->getCurrency(); - if (isXRP(curIssue.currency)) - curIssue.account = xrpAccount(); + curAsset = Issue{cur->getCurrency(), curAsset.getIssuer()}; + if (isXRP(curAsset)) + curAsset.get().account = xrpAccount(); } + else if (cur->hasMPT()) + { + curAsset = cur->getPathAsset().get(); + } + + using ImpliedStepRet = std::pair>; + auto getImpliedStep = [&](AccountID const& src_, + AccountID const& dst_, + Asset const& asset_) -> ImpliedStepRet { + return asset_.visit( + [&](MPTIssue const&) -> ImpliedStepRet { + JLOG(j.error()) << "MPT is invalid with rippling"; + return {temBAD_PATH, nullptr}; + }, + [&](Issue const& issue) -> ImpliedStepRet { + return make_DirectStepI(ctx(), src_, dst_, issue.currency); + }); + }; if (cur->isAccount() && next->isAccount()) { - if (!isXRP(curIssue.currency) && curIssue.account != cur->getAccountID() && - curIssue.account != next->getAccountID()) + // This block doesn't execute + // since curAsset's account is set to cur's account above. + // It should not execute for MPT either because MPT rippling + // is invalid. Should this block be removed/amendment excluded? + if (!isXRP(curAsset) && curAsset.getIssuer() != cur->getAccountID() && + curAsset.getIssuer() != next->getAccountID()) { JLOG(j.trace()) << "Inserting implied account"; - auto msr = make_DirectStepI( - ctx(), cur->getAccountID(), curIssue.account, curIssue.currency); + auto msr = getImpliedStep(cur->getAccountID(), curAsset.getIssuer(), curAsset); if (!isTesSuccess(msr.first)) return {msr.first, Strand{}}; result.push_back(std::move(msr.second)); impliedPE.emplace( - STPathElement::typeAccount, curIssue.account, xrpCurrency(), xrpAccount()); + STPathElement::typeAccount, curAsset.getIssuer(), xrpCurrency(), xrpAccount()); cur = &*impliedPE; } } else if (cur->isAccount() && next->isOffer()) { - if (curIssue.account != cur->getAccountID()) + // Same as above, this block doesn't execute. + if (curAsset.getIssuer() != cur->getAccountID()) { JLOG(j.trace()) << "Inserting implied account before offer"; - auto msr = make_DirectStepI( - ctx(), cur->getAccountID(), curIssue.account, curIssue.currency); + auto msr = getImpliedStep(cur->getAccountID(), curAsset.getIssuer(), curAsset); if (!isTesSuccess(msr.first)) return {msr.first, Strand{}}; result.push_back(std::move(msr.second)); impliedPE.emplace( - STPathElement::typeAccount, curIssue.account, xrpCurrency(), xrpAccount()); + STPathElement::typeAccount, curAsset.getIssuer(), xrpCurrency(), xrpAccount()); cur = &*impliedPE; } } else if (cur->isOffer() && next->isAccount()) { - if (curIssue.account != next->getAccountID() && !isXRP(next->getAccountID())) + // If the offer sells MPT, then next's account is always the issuer. + // See how normPath step is added for second-to-last or last + // step. Therefore, this block never executes if MPT. + if (curAsset.getIssuer() != next->getAccountID() && !isXRP(next->getAccountID())) { - if (isXRP(curIssue)) + if (isXRP(curAsset)) { if (i != normPath.size() - 2) return {temBAD_PATH, Strand{}}; @@ -330,8 +477,7 @@ toStrand( else { JLOG(j.trace()) << "Inserting implied account after offer"; - auto msr = make_DirectStepI( - ctx(), curIssue.account, next->getAccountID(), curIssue.currency); + auto msr = getImpliedStep(curAsset.getIssuer(), next->getAccountID(), curAsset); if (!isTesSuccess(msr.first)) return {msr.first, Strand{}}; result.push_back(std::move(msr.second)); @@ -340,7 +486,7 @@ toStrand( continue; } - if (!next->isOffer() && next->hasCurrency() && next->getCurrency() != curIssue.currency) + if (!next->isOffer() && next->hasAsset() && next->getPathAsset() != curAsset) { // Should never happen // LCOV_EXCL_START @@ -349,7 +495,7 @@ toStrand( // LCOV_EXCL_STOP } - auto s = toStep(ctx(/*isLast*/ i == normPath.size() - 2), cur, next, curIssue); + auto s = toStep(ctx(/*isLast*/ i == normPath.size() - 2), cur, next, curAsset); if (isTesSuccess(s.first)) { result.emplace_back(std::move(s.second)); @@ -366,17 +512,21 @@ toStrand( if (auto r = s.directStepAccts()) return *r; if (auto const r = s.bookStepBook()) - return std::make_pair(r->in.account, r->out.account); + return std::make_pair(r->in.getIssuer(), r->out.getIssuer()); Throw(tefEXCEPTION, "Step should be either a direct or book step"); return std::make_pair(xrpAccount(), xrpAccount()); }; auto curAcc = src; - auto curIss = [&] { - auto& currency = sendMaxIssue ? sendMaxIssue->currency : deliver.currency; - if (isXRP(currency)) - return xrpIssue(); - return Issue{currency, src}; + auto curAsset = [&]() -> Asset { + auto const& asset = sendMaxAsset ? *sendMaxAsset : deliver; + return asset.visit( + [&](MPTIssue const&) -> Asset { return asset; }, + [&](Issue const& issue) -> Asset { + if (isXRP(asset)) + return xrpIssue(); + return Issue{issue.currency, src}; + }); }(); for (auto const& s : result) @@ -387,22 +537,25 @@ toStrand( if (auto const b = s->bookStepBook()) { - if (curIss != b->in) + if (curAsset != b->in) return false; - curIss = b->out; + curAsset = b->out; } - else + else if (curAsset.holds()) { - curIss.account = accts.second; + curAsset.get().account = accts.second; } curAcc = accts.second; } if (curAcc != dst) return false; - if (curIss.currency != deliver.currency) + if (curAsset.holds() != deliver.holds() || + (curAsset.holds() && + curAsset.get().currency != deliver.get().currency) || + (curAsset.holds() && curAsset.get() != deliver.get())) return false; - if (curIss.account != deliver.account && curIss.account != dst) + if (curAsset.getIssuer() != deliver.getIssuer() && curAsset.getIssuer() != dst) return false; return true; }; @@ -424,9 +577,9 @@ toStrands( ReadView const& view, AccountID const& src, AccountID const& dst, - Issue const& deliver, + Asset const& deliver, std::optional const& limitQuality, - std::optional const& sendMax, + std::optional const& sendMax, STPathSet const& paths, bool addDefaultPath, bool ownerPaysTransferFee, @@ -439,7 +592,7 @@ toStrands( result.reserve(1 + paths.size()); // Insert the strand into result if it is not already part of the vector auto insert = [&](Strand s) { - bool const hasStrand = std::find(result.begin(), result.end(), s) != result.end(); + bool const hasStrand = std::ranges::find(result, s) != result.end(); if (!hasStrand) result.emplace_back(std::move(s)); @@ -539,14 +692,14 @@ StrandContext::StrandContext( // replicates the source or destination. AccountID const& strandSrc_, AccountID const& strandDst_, - Issue const& strandDeliver_, + Asset const& strandDeliver_, std::optional const& limitQuality_, bool isLast_, bool ownerPaysTransferFee_, OfferCrossing offerCrossing_, bool isDefaultPath_, - std::array, 2>& seenDirectIssues_, - boost::container::flat_set& seenBookOuts_, + std::array, 2>& seenDirectAssets_, + boost::container::flat_set& seenBookOuts_, AMMContext& ammContext_, std::optional const& domainID_, beast::Journal j_) @@ -562,7 +715,7 @@ StrandContext::StrandContext( , isDefaultPath(isDefaultPath_) , strandSize(strand_.size()) , prevStep(!strand_.empty() ? strand_.back().get() : nullptr) - , seenDirectIssues(seenDirectIssues_) + , seenDirectAssets(seenDirectAssets_) , seenBookOuts(seenBookOuts_) , ammContext(ammContext_) , domainID(domainID_) @@ -570,25 +723,4 @@ StrandContext::StrandContext( { } -template -bool -isDirectXrpToXrp(Strand const& strand) -{ - return false; -} - -template <> -bool -isDirectXrpToXrp(Strand const& strand) -{ - return (strand.size() == 2); -} - -template bool -isDirectXrpToXrp(Strand const& strand); -template bool -isDirectXrpToXrp(Strand const& strand); -template bool -isDirectXrpToXrp(Strand const& strand); - } // namespace xrpl diff --git a/src/libxrpl/tx/paths/RippleCalc.cpp b/src/libxrpl/tx/paths/RippleCalc.cpp index c9f0c9e0fb..773a9bac3d 100644 --- a/src/libxrpl/tx/paths/RippleCalc.cpp +++ b/src/libxrpl/tx/paths/RippleCalc.cpp @@ -1,11 +1,23 @@ -#include -#include -#include #include -#include -namespace xrpl { -namespace path { +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace xrpl::path { RippleCalc::Output RippleCalc::rippleCalculate( @@ -54,7 +66,7 @@ RippleCalc::rippleCalculate( auto const sendMax = [&]() -> std::optional { if (saMaxAmountReq >= beast::zero || - saMaxAmountReq.getCurrency() != saDstAmountReq.getCurrency() || + !equalTokens(saMaxAmountReq.asset(), saDstAmountReq.asset()) || saMaxAmountReq.getIssuer() != uSrcAccountID) { return saMaxAmountReq; @@ -100,5 +112,4 @@ RippleCalc::rippleCalculate( return flowOut; } -} // namespace path -} // namespace xrpl +} // namespace xrpl::path diff --git a/src/libxrpl/tx/paths/XRPEndpointStep.cpp b/src/libxrpl/tx/paths/XRPEndpointStep.cpp index 7452f57ecd..6356eb8ded 100644 --- a/src/libxrpl/tx/paths/XRPEndpointStep.cpp +++ b/src/libxrpl/tx/paths/XRPEndpointStep.cpp @@ -1,18 +1,34 @@ #include +#include +#include +#include +#include #include #include -#include -#include +#include +#include #include +#include +#include +#include #include +#include +#include +#include #include #include +#include #include #include #include +#include +#include +#include #include +#include +#include namespace xrpl { @@ -179,13 +195,25 @@ private: // because the trust line was created after the XRP was removed.) // Return how much the reserve should be reduced. // - // Note that reduced reserve only happens if the trust line does not + // Note that reduced reserve only happens if the trust line or MPT does not // currently exist. static std::int32_t computeReserveReduction(StrandContext const& ctx, AccountID const& acc) { - if (ctx.isFirst && !ctx.view.read(keylet::line(acc, ctx.strandDeliver))) - return -1; + if (ctx.isFirst) + { + return ctx.strandDeliver.visit( + [&](Issue const& issue) { + if (!ctx.view.exists(keylet::line(acc, issue))) + return -1; + return 0; + }, + [&](MPTIssue const& issue) { + if (!ctx.view.exists(keylet::mptoken(issue.getMptID(), acc))) + return -1; + return 0; + }); + } return 0; } @@ -283,9 +311,9 @@ XRPEndpointStep::validFwd(PaymentSandbox& sb, ApplyView& afView, Eithe return {false, EitherAmount(XRPAmount(beast::zero))}; } - XRPL_ASSERT(in.native, "xrpl::XRPEndpointStep::validFwd : input is XRP"); + XRPL_ASSERT(in.holds(), "xrpl::XRPEndpointStep::validFwd : input is XRP"); - auto const& xrpIn = in.xrp; + auto const& xrpIn = in.get(); auto const balance = static_cast(this)->xrpLiquid(sb); if (!isLast_ && balance < xrpIn) @@ -336,7 +364,7 @@ XRPEndpointStep::check(StrandContext const& ctx) const return ter; auto const issuesIndex = isLast_ ? 0 : 1; - if (!ctx.seenDirectIssues[issuesIndex].insert(xrpIssue()).second) + if (!ctx.seenDirectAssets[issuesIndex].insert(xrpIssue()).second) { JLOG(j_.debug()) << "XRPEndpointStep: loop detected: Index: " << ctx.strandSize << ' ' << *this; diff --git a/src/libxrpl/tx/transactors/account/AccountDelete.cpp b/src/libxrpl/tx/transactors/account/AccountDelete.cpp index 8c2f23b754..596a8a1560 100644 --- a/src/libxrpl/tx/transactors/account/AccountDelete.cpp +++ b/src/libxrpl/tx/transactors/account/AccountDelete.cpp @@ -1,22 +1,40 @@ +#include + #include +#include +#include +#include #include +#include +#include +#include #include #include #include +#include #include +#include #include #include +#include +#include #include -#include -#include -#include +#include +#include +#include +#include +#include +#include #include #include #include -#include #include #include +#include +#include +#include + namespace xrpl { bool @@ -292,7 +310,7 @@ AccountDelete::preclaim(PreclaimContext const& ctx) if (!cdirFirst(ctx.view, ownerDirKeylet.key, sleDirNode, uDirEntry, dirEntry)) return tesSUCCESS; - std::int32_t deletableDirEntryCount{0}; + std::uint32_t deletableDirEntryCount{0}; do { // Make sure any directory node types that we find are the kind @@ -398,4 +416,23 @@ AccountDelete::doApply() return tesSUCCESS; } +void +AccountDelete::visitInvariantEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ +} + +bool +AccountDelete::finalizeInvariants( + STTx const&, + TER, + XRPAmount, + ReadView const&, + beast::Journal const&) +{ + return true; +} + } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/account/AccountSet.cpp b/src/libxrpl/tx/transactors/account/AccountSet.cpp index af3edb5768..a892bd045c 100644 --- a/src/libxrpl/tx/transactors/account/AccountSet.cpp +++ b/src/libxrpl/tx/transactors/account/AccountSet.cpp @@ -1,13 +1,33 @@ +#include + +#include #include -#include +#include +#include +#include +#include +#include #include #include #include +#include +#include +#include #include #include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include namespace xrpl { @@ -637,4 +657,18 @@ AccountSet::doApply() return tesSUCCESS; } +void +AccountSet::visitInvariantEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ +} + +bool +AccountSet::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&) +{ + return true; +} + } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/account/SetRegularKey.cpp b/src/libxrpl/tx/transactors/account/SetRegularKey.cpp index 471da26c88..bc59ed4fc1 100644 --- a/src/libxrpl/tx/transactors/account/SetRegularKey.cpp +++ b/src/libxrpl/tx/transactors/account/SetRegularKey.cpp @@ -1,8 +1,20 @@ -#include -#include -#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + namespace xrpl { XRPAmount @@ -68,4 +80,23 @@ SetRegularKey::doApply() return tesSUCCESS; } +void +SetRegularKey::visitInvariantEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ +} + +bool +SetRegularKey::finalizeInvariants( + STTx const&, + TER, + XRPAmount, + ReadView const&, + beast::Journal const&) +{ + return true; +} + } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/account/SignerListSet.cpp b/src/libxrpl/tx/transactors/account/SignerListSet.cpp index 90ab8daf6f..87eb69e706 100644 --- a/src/libxrpl/tx/transactors/account/SignerListSet.cpp +++ b/src/libxrpl/tx/transactors/account/SignerListSet.cpp @@ -1,16 +1,36 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + #include +#include #include +#include +#include +#include +#include namespace xrpl { @@ -238,10 +258,10 @@ SignerListSet::validateQuorumAndSignerEntries( // Make sure there are no duplicate signers. XRPL_ASSERT( - std::is_sorted(signers.begin(), signers.end()), + std::ranges::is_sorted(signers), "xrpl::SignerListSet::validateQuorumAndSignerEntries : sorted " "signers"); - if (std::adjacent_find(signers.begin(), signers.end()) != signers.end()) + if (std::ranges::adjacent_find(signers) != signers.end()) { JLOG(j.trace()) << "Duplicate signers in signer list"; return temBAD_SIGNER; @@ -385,4 +405,23 @@ SignerListSet::writeSignersToSLE(SLE::pointer const& ledgerEntry, std::uint32_t ledgerEntry->setFieldArray(sfSignerEntries, toLedger); } +void +SignerListSet::visitInvariantEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ +} + +bool +SignerListSet::finalizeInvariants( + STTx const&, + TER, + XRPAmount, + ReadView const&, + beast::Journal const&) +{ + return true; +} + } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/bridge/XChainBridge.cpp b/src/libxrpl/tx/transactors/bridge/XChainBridge.cpp index 9074122cdf..166e42560f 100644 --- a/src/libxrpl/tx/transactors/bridge/XChainBridge.cpp +++ b/src/libxrpl/tx/transactors/bridge/XChainBridge.cpp @@ -1,31 +1,52 @@ +#include + +#include #include #include -#include #include +#include #include +#include #include #include +#include +#include #include #include #include #include #include +#include +#include +#include +#include #include #include #include +#include #include +#include #include +#include +#include #include #include #include #include +#include #include #include #include -#include +#include +#include +#include +#include +#include +#include #include -#include +#include +#include namespace xrpl { @@ -308,7 +329,7 @@ onNewAttestations( j); if (!r.has_value()) - return {std::nullopt, changed}; + return {.rewardAccounts = std::nullopt, .changed = changed}; return {std::move(r.value()), changed}; }; @@ -642,7 +663,7 @@ finalizeClaimHelper( saveNumberRoundMode const _{Number::setround(round_mode)}; STAmount const den{rewardAccounts.size()}; - return divide(rewardPool, den, rewardPool.issue()); + return divide(rewardPool, den, rewardPool.asset()); }(); STAmount distributed = rewardPool.zeroed(); for (auto const& rewardAccount : rewardAccounts) @@ -1167,7 +1188,7 @@ attestationPreflight(PreflightContext const& ctx) if (att->sendingAmount.signum() <= 0) return temXCHAIN_BAD_PROOF; auto const expectedIssue = bridgeSpec.issue(STXChainBridge::srcChain(att->wasLockingChainSend)); - if (att->sendingAmount.issue() != expectedIssue) + if (att->sendingAmount.asset() != expectedIssue) return temXCHAIN_BAD_PROOF; return tesSUCCESS; @@ -1578,8 +1599,8 @@ XChainClaim::preflight(PreflightContext const& ctx) auto const amount = ctx.tx[sfAmount]; if (amount.signum() <= 0 || - (amount.issue() != bridgeSpec.lockingChainIssue() && - amount.issue() != bridgeSpec.issuingChainIssue())) + (amount.asset() != bridgeSpec.lockingChainIssue() && + amount.asset() != bridgeSpec.issuingChainIssue())) { return temBAD_AMOUNT; } @@ -1628,12 +1649,12 @@ XChainClaim::preclaim(PreclaimContext const& ctx) if (isLockingChain) { - if (bridgeSpec.lockingChainIssue() != thisChainAmount.issue()) + if (bridgeSpec.lockingChainIssue() != thisChainAmount.asset()) return tecXCHAIN_BAD_TRANSFER_ISSUE; } else { - if (bridgeSpec.issuingChainIssue() != thisChainAmount.issue()) + if (bridgeSpec.issuingChainIssue() != thisChainAmount.asset()) return tecXCHAIN_BAD_TRANSFER_ISSUE; } } @@ -1758,11 +1779,11 @@ XChainClaim::doApply() return Unexpected(claimR.error()); return ScopeResult{ - claimR.value(), - (*sleClaimID)[sfAccount], - sendingAmount, - srcChain, - (*sleClaimID)[sfSignatureReward], + .rewardAccounts = claimR.value(), + .rewardPoolSrc = (*sleClaimID)[sfAccount], + .sendingAmount = sendingAmount, + .srcChain = srcChain, + .signatureReward = (*sleClaimID)[sfSignatureReward], }; }(); @@ -1820,8 +1841,8 @@ XChainCommit::preflight(PreflightContext const& ctx) if (amount.signum() <= 0 || !isLegalNet(amount)) return temBAD_AMOUNT; - if (amount.issue() != bridgeSpec.lockingChainIssue() && - amount.issue() != bridgeSpec.issuingChainIssue()) + if (amount.asset() != bridgeSpec.lockingChainIssue() && + amount.asset() != bridgeSpec.issuingChainIssue()) return temBAD_ISSUER; return tesSUCCESS; @@ -1866,12 +1887,12 @@ XChainCommit::preclaim(PreclaimContext const& ctx) if (isLockingChain) { - if (bridgeSpec.lockingChainIssue() != ctx.tx[sfAmount].issue()) + if (bridgeSpec.lockingChainIssue() != ctx.tx[sfAmount].asset()) return tecXCHAIN_BAD_TRANSFER_ISSUE; } else { - if (bridgeSpec.issuingChainIssue() != ctx.tx[sfAmount].issue()) + if (bridgeSpec.issuingChainIssue() != ctx.tx[sfAmount].asset()) return tecXCHAIN_BAD_TRANSFER_ISSUE; } @@ -1899,7 +1920,9 @@ XChainCommit::doApply() // Support dipping into reserves to pay the fee TransferHelperSubmittingAccountInfo submittingAccountInfo{ - account_, preFeeBalance_, (*sleAccount)[sfBalance]}; + .account = account_, + .preFeeBalance_ = preFeeBalance_, + .postFeeBalance = (*sleAccount)[sfBalance]}; auto const thTer = transferHelper( psb, @@ -2083,7 +2106,7 @@ XChainCreateAccountCommit::preflight(PreflightContext const& ctx) if (reward.signum() < 0 || !reward.native()) return temBAD_AMOUNT; - if (reward.issue() != amount.issue()) + if (reward.asset() != amount.asset()) return temBAD_AMOUNT; return tesSUCCESS; @@ -2115,7 +2138,7 @@ XChainCreateAccountCommit::preclaim(PreclaimContext const& ctx) if (amount < *minCreateAmount) return tecXCHAIN_INSUFF_CREATE_AMOUNT; - if (minCreateAmount->issue() != amount.issue()) + if (minCreateAmount->asset() != amount.asset()) return tecXCHAIN_BAD_TRANSFER_ISSUE; AccountID const thisDoor = (*sleBridge)[sfAccount]; @@ -2143,7 +2166,7 @@ XChainCreateAccountCommit::preclaim(PreclaimContext const& ctx) } STXChainBridge::ChainType const dstChain = STXChainBridge::otherChain(srcChain); - if (bridgeSpec.issue(srcChain) != ctx.tx[sfAmount].issue()) + if (bridgeSpec.issue(srcChain) != ctx.tx[sfAmount].asset()) return tecXCHAIN_BAD_TRANSFER_ISSUE; if (!isXRP(bridgeSpec.issue(dstChain))) @@ -2174,7 +2197,7 @@ XChainCreateAccountCommit::doApply() // Support dipping into reserves to pay the fee TransferHelperSubmittingAccountInfo submittingAccountInfo{ - account_, preFeeBalance_, (*sle)[sfBalance]}; + .account = account_, .preFeeBalance_ = preFeeBalance_, .postFeeBalance = (*sle)[sfBalance]}; STAmount const toTransfer = amount + reward; auto const thTer = transferHelper( psb, @@ -2199,4 +2222,151 @@ XChainCreateAccountCommit::doApply() return tesSUCCESS; } +void +XChainCreateBridge::visitInvariantEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ +} + +bool +XChainCreateBridge::finalizeInvariants( + STTx const&, + TER, + XRPAmount, + ReadView const&, + beast::Journal const&) +{ + return true; +} + +void +BridgeModify::visitInvariantEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ +} + +bool +BridgeModify::finalizeInvariants( + STTx const&, + TER, + XRPAmount, + ReadView const&, + beast::Journal const&) +{ + return true; +} + +void +XChainClaim::visitInvariantEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ +} + +bool +XChainClaim::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&) +{ + return true; +} + +void +XChainCommit::visitInvariantEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ +} + +bool +XChainCommit::finalizeInvariants( + STTx const&, + TER, + XRPAmount, + ReadView const&, + beast::Journal const&) +{ + return true; +} + +void +XChainCreateClaimID::visitInvariantEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ +} + +bool +XChainCreateClaimID::finalizeInvariants( + STTx const&, + TER, + XRPAmount, + ReadView const&, + beast::Journal const&) +{ + return true; +} + +void +XChainAddClaimAttestation::visitInvariantEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ +} + +bool +XChainAddClaimAttestation::finalizeInvariants( + STTx const&, + TER, + XRPAmount, + ReadView const&, + beast::Journal const&) +{ + return true; +} + +void +XChainAddAccountCreateAttestation::visitInvariantEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ +} + +bool +XChainAddAccountCreateAttestation::finalizeInvariants( + STTx const&, + TER, + XRPAmount, + ReadView const&, + beast::Journal const&) +{ + return true; +} + +void +XChainCreateAccountCommit::visitInvariantEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ +} + +bool +XChainCreateAccountCommit::finalizeInvariants( + STTx const&, + TER, + XRPAmount, + ReadView const&, + beast::Journal const&) +{ + return true; +} + } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/check/CheckCancel.cpp b/src/libxrpl/tx/transactors/check/CheckCancel.cpp index be3b434fb6..c30c116e58 100644 --- a/src/libxrpl/tx/transactors/check/CheckCancel.cpp +++ b/src/libxrpl/tx/transactors/check/CheckCancel.cpp @@ -1,11 +1,20 @@ +#include + +#include #include #include #include -#include +#include #include +#include +#include +#include #include -#include -#include +#include +#include + +#include +#include namespace xrpl { @@ -92,4 +101,18 @@ CheckCancel::doApply() return tesSUCCESS; } +void +CheckCancel::visitInvariantEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ +} + +bool +CheckCancel::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&) +{ + return true; +} + } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/check/CheckCash.cpp b/src/libxrpl/tx/transactors/check/CheckCash.cpp index fa908c3aa3..4498a3cd8e 100644 --- a/src/libxrpl/tx/transactors/check/CheckCash.cpp +++ b/src/libxrpl/tx/transactors/check/CheckCash.cpp @@ -1,18 +1,52 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + #include +#include +#include +#include namespace xrpl { +bool +CheckCash::checkExtraFeatures(xrpl::PreflightContext const& ctx) +{ + auto const optAmount = ctx.tx[~sfAmount]; + auto const optDeliverMin = ctx.tx[~sfDeliverMin]; + + return ctx.rules.enabled(featureMPTokensV2) || + (!(optAmount && optAmount->holds()) && + !(optDeliverMin && optDeliverMin->holds())); +} + NotTEC CheckCash::preflight(PreflightContext const& ctx) { @@ -35,7 +69,7 @@ CheckCash::preflight(PreflightContext const& ctx) return temBAD_AMOUNT; } - if (badCurrency() == value.getCurrency()) + if (badAsset() == value.asset()) { JLOG(ctx.j.warn()) << "Malformed transaction: Bad currency."; return temBAD_CURRENCY; @@ -106,8 +140,7 @@ CheckCash::preclaim(PreclaimContext const& ctx) }(ctx.tx)}; STAmount const sendMax = sleCheck->at(sfSendMax); - Currency const currency{value.getCurrency()}; - if (currency != sendMax.getCurrency()) + if (!equalTokens(value.asset(), sendMax.asset())) { JLOG(ctx.j.warn()) << "Check cash does not match check currency."; return temMALFORMED; @@ -127,8 +160,13 @@ CheckCash::preclaim(PreclaimContext const& ctx) // Make sure the check owner holds at least value. If they have // less than value the check cannot be cashed. { - STAmount availableFunds{ - accountFunds(ctx.view, sleCheck->at(sfAccount), value, fhZERO_IF_FROZEN, ctx.j)}; + STAmount availableFunds{accountFunds( + ctx.view, + sleCheck->at(sfAccount), + value, + fhZERO_IF_FROZEN, + ahZERO_IF_UNAUTHORIZED, + ctx.j)}; // Note that src will have one reserve's worth of additional XRP // once the check is cashed, since the check's reserve will no @@ -147,51 +185,94 @@ CheckCash::preclaim(PreclaimContext const& ctx) // An issuer can always accept their own currency. if (!value.native() && (value.getIssuer() != dstId)) { - auto const sleIssuer = ctx.view.read(keylet::account(issuerId)); - if (!sleIssuer) - { - JLOG(ctx.j.warn()) - << "Can't receive IOUs from non-existent issuer: " << to_string(issuerId); - return tecNO_ISSUER; - } + return value.asset().visit( + [&](Issue const& issue) -> TER { + Currency const currency{issue.currency}; + auto const sleTrustLine = + ctx.view.read(keylet::line(dstId, issuerId, currency)); - if ((sleIssuer->at(sfFlags) & lsfRequireAuth) != 0u) - { - auto const sleTrustLine = ctx.view.read(keylet::line(dstId, issuerId, currency)); + auto const sleIssuer = ctx.view.read(keylet::account(issuerId)); + if (!sleIssuer) + { + JLOG(ctx.j.warn()) << "Can't receive IOUs from " + "non-existent issuer: " + << to_string(issuerId); + return tecNO_ISSUER; + } - if (!sleTrustLine) - { - // We can only create a trust line if the issuer does not - // have lsfRequireAuth set. - return tecNO_AUTH; - } + if ((sleIssuer->at(sfFlags) & lsfRequireAuth) != 0u) + { + if (!sleTrustLine) + { + // We can only create a trust line if the issuer + // does not have lsfRequireAuth set. + return tecNO_AUTH; + } - // Entries have a canonical representation, determined by a - // lexicographical "greater than" comparison employing strict - // weak ordering. Determine which entry we need to access. - bool const canonical_gt(dstId > issuerId); + // Entries have a canonical representation, + // determined by a lexicographical "greater than" + // comparison employing strict weak ordering. + // Determine which entry we need to access. + bool const canonical_gt(dstId > issuerId); - bool const is_authorized( - (sleTrustLine->at(sfFlags) & (canonical_gt ? lsfLowAuth : lsfHighAuth)) != 0u); + bool const is_authorized( + (sleTrustLine->at(sfFlags) & + (canonical_gt ? lsfLowAuth : lsfHighAuth)) != 0u); - if (!is_authorized) - { - JLOG(ctx.j.warn()) << "Can't receive IOUs from issuer without auth."; - return tecNO_AUTH; - } - } + if (!is_authorized) + { + JLOG(ctx.j.warn()) << "Can't receive IOUs from " + "issuer without auth."; + return tecNO_AUTH; + } + } - // The trustline from source to issuer does not need to - // be checked for freezing, since we already verified that the - // source has sufficient non-frozen funds available. + // The trustline from source to issuer does not need to + // be checked for freezing, since we already verified + // that the source has sufficient non-frozen funds + // available. - // However, the trustline from destination to issuer may not - // be frozen. - if (isFrozen(ctx.view, dstId, currency, issuerId)) - { - JLOG(ctx.j.warn()) << "Cashing a check to a frozen trustline."; - return tecFROZEN; - } + // However, the trustline from destination to issuer may + // not be frozen. + if (isFrozen(ctx.view, dstId, currency, issuerId)) + { + JLOG(ctx.j.warn()) << "Cashing a check to a frozen trustline."; + return tecFROZEN; + } + + return tesSUCCESS; + }, + [&](MPTIssue const& issue) -> TER { + auto const sleIssuer = ctx.view.read(keylet::account(issuerId)); + if (!sleIssuer) + { + JLOG(ctx.j.warn()) << "Can't receive MPTs from " + "non-existent issuer: " + << to_string(issuerId); + return tecNO_ISSUER; + } + + if (auto const err = requireAuth(ctx.view, issue, dstId, AuthType::WeakAuth); + !isTesSuccess(err)) + { + JLOG(ctx.j.warn()) << "Cashing a check to a MPT requiring auth."; + return err; + } + + if (isFrozen(ctx.view, dstId, issue)) + { + JLOG(ctx.j.warn()) << "Cashing a check to a frozen MPT."; + return tecLOCKED; + } + + if (auto const err = canTrade(ctx.view, value.asset()); !isTesSuccess(err)) + { + JLOG(ctx.j.warn()) << "MPT DEX is not allowed."; + return err; + } + + return tesSUCCESS; + }); } } return tesSUCCESS; @@ -287,94 +368,152 @@ CheckCash::doApply() // maximum possible currency because there might be a gateway // transfer rate to account for. Since the transfer rate cannot // exceed 200%, we use 1/2 maxValue as our limit. + auto const maxDeliverMin = [&]() { + return optDeliverMin->asset().visit( + [&](Issue const&) { + return STAmount( + optDeliverMin->asset(), STAmount::cMaxValue / 2, STAmount::cMaxOffset); + }, + [&](MPTIssue const&) { + return STAmount(optDeliverMin->asset(), maxMPTokenAmount / 2); + }); + }; STAmount const flowDeliver{ - optDeliverMin - ? STAmount( - optDeliverMin->issue(), STAmount::cMaxValue / 2, STAmount::cMaxOffset) - : ctx_.tx.getFieldAmount(sfAmount)}; + optDeliverMin ? maxDeliverMin() : ctx_.tx.getFieldAmount(sfAmount)}; - // If a trust line does not exist yet create one. - Issue const& trustLineIssue = flowDeliver.issue(); - AccountID const issuer = flowDeliver.getIssuer(); - AccountID const truster = issuer == account_ ? srcId : account_; - Keylet const trustLineKey = keylet::line(truster, trustLineIssue); - bool const destLow = issuer > account_; + // Check reserve. Return destination account SLE if enough reserve, + // otherwise return nullptr. + auto checkReserve = [&]() -> std::shared_ptr { + auto sleDst = psb.peek(keylet::account(account_)); - if (!psb.exists(trustLineKey)) - { - // 1. Can the check casher meet the reserve for the trust line? - // 2. Create trust line between destination (this) account - // and the issuer. - // 3. Apply correct noRipple settings on trust line. Use... - // a. this (destination) account and - // b. issuing account (not sending account). - - auto const sleDst = psb.peek(keylet::account(account_)); - - // Can the account cover the trust line's reserve? + // Can the account cover the trust line's or MPT reserve? if (std::uint32_t const ownerCount = {sleDst->at(sfOwnerCount)}; preFeeBalance_ < psb.fees().accountReserve(ownerCount + 1)) { JLOG(j_.trace()) << "Trust line does not exist. " - "Insufficent reserve to create line."; + "Insufficient reserve to create line."; - return tecNO_LINE_INSUF_RESERVE; + return nullptr; } + return sleDst; + }; - Currency const currency = flowDeliver.getCurrency(); - STAmount initialBalance(flowDeliver.issue()); - initialBalance.setIssuer(noAccount()); + std::optional trustLineKey; + STAmount savedLimit; + bool destLow = false; + AccountID const& deliverIssuer = flowDeliver.getIssuer(); + auto const err = flowDeliver.asset().visit( + [&](Issue const& issue) -> std::optional { + // If a trust line does not exist yet create one. + Issue const& trustLineIssue = issue; + AccountID const truster = deliverIssuer == account_ ? srcId : account_; + trustLineKey = keylet::line(truster, trustLineIssue); + destLow = deliverIssuer > account_; - if (TER const ter = trustCreate( - psb, // payment sandbox - destLow, // is dest low? - issuer, // source - account_, // destination - trustLineKey.key, // ledger index - sleDst, // Account to add to - false, // authorize account - (sleDst->getFlags() & lsfDefaultRipple) == 0, // - false, // freeze trust line - false, // deep freeze trust line - initialBalance, // zero initial balance - Issue(currency, account_), // limit of zero - 0, // quality in - 0, // quality out - viewJ); // journal - !isTesSuccess(ter)) + if (!psb.exists(*trustLineKey)) + { + // 1. Can the check casher meet the reserve for the + // trust line? + // 2. Create trust line between destination (this) + // account + // and the issuer. + // 3. Apply correct noRipple settings on trust line. + // Use... + // a. this (destination) account and + // b. issuing account (not sending account). + + auto const sleDst = checkReserve(); + if (sleDst == nullptr) + return tecNO_LINE_INSUF_RESERVE; + + Currency const& currency = issue.currency; + STAmount initialBalance(flowDeliver.asset()); + initialBalance.get().account = noAccount(); + + if (TER const ter = trustCreate( + psb, // payment sandbox + destLow, // is dest low? + deliverIssuer, // source + account_, // destination + trustLineKey->key, // ledger index + sleDst, // Account to add to + false, // authorize account + (sleDst->getFlags() & lsfDefaultRipple) == 0, // + false, // freeze trust line + false, // deep freeze trust line + initialBalance, // zero initial balance + Issue(currency, account_), // limit of zero + 0, // quality in + 0, // quality out + viewJ); // journal + !isTesSuccess(ter)) + { + return ter; + } + + psb.update(sleDst); + + // Note that we _don't_ need to be careful about + // destroying the trust line if the check cashing + // fails. The transaction machinery will + // automatically clean it up. + } + + // Since the destination is signing the check, they + // clearly want the funds even if their new total funds + // would exceed the limit on their trust line. So we + // tweak the trust line limits before calling flow and + // then restore the trust line limits afterwards. + auto const sleTrustLine = psb.peek(*trustLineKey); + if (!sleTrustLine) + return tecNO_LINE; + + SF_AMOUNT const& tweakedLimit = destLow ? sfLowLimit : sfHighLimit; + savedLimit = sleTrustLine->at(tweakedLimit); + + // Set the trust line limit to the highest possible + // value while flow runs. + STAmount const bigAmount( + trustLineIssue, STAmount::cMaxValue, STAmount::cMaxOffset); + sleTrustLine->at(tweakedLimit) = bigAmount; + + return std::nullopt; + }, + [&](MPTIssue const& issue) -> std::optional { + if (account_ != deliverIssuer) + { + auto const& mptID = issue.getMptID(); + // Create MPT if it doesn't exist + auto const mptokenKey = keylet::mptoken(mptID, account_); + if (!psb.exists(mptokenKey)) + { + auto sleDst = checkReserve(); + if (sleDst == nullptr) + return tecINSUFFICIENT_RESERVE; + + if (auto const err = checkCreateMPT(psb, mptID, account_, j_); + !isTesSuccess(err)) + { + return err; + } + } + } + + return std::nullopt; + }); + if (err) + return *err; + // Make sure the tweaked limits are restored when we leave + // scope. + scope_exit const fixup([&psb, &trustLineKey, destLow, &savedLimit]() { + if (trustLineKey) { - return ter; + SF_AMOUNT const& tweakedLimit = destLow ? sfLowLimit : sfHighLimit; + if (auto const sleTrustLine = psb.peek(*trustLineKey)) + sleTrustLine->at(tweakedLimit) = savedLimit; } - - psb.update(sleDst); - - // Note that we _don't_ need to be careful about destroying - // the trust line if the check cashing fails. The transaction - // machinery will automatically clean it up. - } - - // Since the destination is signing the check, they clearly want - // the funds even if their new total funds would exceed the limit - // on their trust line. So we tweak the trust line limits before - // calling flow and then restore the trust line limits afterwards. - auto const sleTrustLine = psb.peek(trustLineKey); - if (!sleTrustLine) - return tecNO_LINE; - - SF_AMOUNT const& tweakedLimit = destLow ? sfLowLimit : sfHighLimit; - STAmount const savedLimit = sleTrustLine->at(tweakedLimit); - - // Make sure the tweaked limits are restored when we leave scope. - scope_exit const fixup([&psb, &trustLineKey, &tweakedLimit, &savedLimit]() { - if (auto const sleTrustLine = psb.peek(trustLineKey)) - sleTrustLine->at(tweakedLimit) = savedLimit; }); - // Set the trust line limit to the highest possible value - // while flow runs. - STAmount const bigAmount(trustLineIssue, STAmount::cMaxValue, STAmount::cMaxOffset); - sleTrustLine->at(tweakedLimit) = bigAmount; - // Let flow() do the heavy lifting on a check for an IOU. auto const result = flow( psb, @@ -405,6 +544,7 @@ CheckCash::doApply() JLOG(ctx_.journal.warn()) << "flow did not produce DeliverMin."; return tecPATH_PARTIAL; } + ctx_.deliver(result.actualAmountOut); } // Set the delivered amount metadata in all cases, not just @@ -446,4 +586,18 @@ CheckCash::doApply() return tesSUCCESS; } +void +CheckCash::visitInvariantEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ +} + +bool +CheckCash::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&) +{ + return true; +} + } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/check/CheckCreate.cpp b/src/libxrpl/tx/transactors/check/CheckCreate.cpp index fde3373691..6a13819615 100644 --- a/src/libxrpl/tx/transactors/check/CheckCreate.cpp +++ b/src/libxrpl/tx/transactors/check/CheckCreate.cpp @@ -1,15 +1,41 @@ +#include + +#include +#include +#include #include #include #include -#include +#include +#include +#include +#include #include #include +#include +#include +#include +#include +#include +#include +#include +#include #include -#include -#include +#include +#include + +#include +#include +#include namespace xrpl { +bool +CheckCreate::checkExtraFeatures(xrpl::PreflightContext const& ctx) +{ + return ctx.rules.enabled(featureMPTokensV2) || !ctx.tx[sfSendMax].holds(); +} + NotTEC CheckCreate::preflight(PreflightContext const& ctx) { @@ -29,7 +55,7 @@ CheckCreate::preflight(PreflightContext const& ctx) return temBAD_AMOUNT; } - if (badCurrency() == sendMax.getCurrency()) + if (badAsset() == sendMax.asset()) { JLOG(ctx.j.warn()) << "Malformed transaction: Bad currency."; return temBAD_CURRENCY; @@ -52,6 +78,7 @@ TER CheckCreate::preclaim(PreclaimContext const& ctx) { AccountID const dstId{ctx.tx[sfDestination]}; + AccountID const srcId{ctx.tx[sfAccount]}; auto const sleDst = ctx.view.read(keylet::account(dstId)); if (!sleDst) { @@ -86,39 +113,56 @@ CheckCreate::preclaim(PreclaimContext const& ctx) { // The currency may not be globally frozen AccountID const& issuerId{sendMax.getIssuer()}; - if (isGlobalFrozen(ctx.view, issuerId)) + if (isGlobalFrozen(ctx.view, sendMax.asset())) { JLOG(ctx.j.warn()) << "Creating a check for frozen asset"; - return tecFROZEN; - } - // If this account has a trustline for the currency, that - // trustline may not be frozen. - // - // Note that we DO allow create check for a currency that the - // account does not yet have a trustline to. - AccountID const srcId{ctx.tx.getAccountID(sfAccount)}; - if (issuerId != srcId) - { - // Check if the issuer froze the line - auto const sleTrust = - ctx.view.read(keylet::line(srcId, issuerId, sendMax.getCurrency())); - if (sleTrust && sleTrust->isFlag((issuerId > srcId) ? lsfHighFreeze : lsfLowFreeze)) - { - JLOG(ctx.j.warn()) << "Creating a check for frozen trustline."; - return tecFROZEN; - } - } - if (issuerId != dstId) - { - // Check if dst froze the line. - auto const sleTrust = - ctx.view.read(keylet::line(issuerId, dstId, sendMax.getCurrency())); - if (sleTrust && sleTrust->isFlag((dstId > issuerId) ? lsfHighFreeze : lsfLowFreeze)) - { - JLOG(ctx.j.warn()) << "Creating a check for destination frozen trustline."; - return tecFROZEN; - } + return sendMax.asset().holds() ? tecLOCKED : tecFROZEN; } + auto const err = sendMax.asset().visit( + [&](Issue const& issue) -> std::optional { + // If this account has a trustline for the currency, + // that trustline may not be frozen. + // + // Note that we DO allow create check for a currency + // that the account does not yet have a trustline to. + if (issuerId != srcId) + { + // Check if the issuer froze the line + auto const sleTrust = + ctx.view.read(keylet::line(srcId, issuerId, issue.currency)); + if (sleTrust && + sleTrust->isFlag((issuerId > srcId) ? lsfHighFreeze : lsfLowFreeze)) + { + JLOG(ctx.j.warn()) << "Creating a check for frozen trustline."; + return tecFROZEN; + } + } + if (issuerId != dstId) + { + // Check if dst froze the line. + auto const sleTrust = + ctx.view.read(keylet::line(issuerId, dstId, issue.currency)); + if (sleTrust && + sleTrust->isFlag((dstId > issuerId) ? lsfHighFreeze : lsfLowFreeze)) + { + JLOG(ctx.j.warn()) << "Creating a check for " + "destination frozen trustline."; + return tecFROZEN; + } + } + + return std::nullopt; + }, + [&](MPTIssue const& issue) -> std::optional { + if (srcId != issuerId && isFrozen(ctx.view, srcId, issue)) + return tecLOCKED; + if (dstId != issuerId && isFrozen(ctx.view, dstId, issue)) + return tecLOCKED; + + return std::nullopt; + }); + if (err) + return *err; } } if (hasExpired(ctx.view, ctx.tx[~sfExpiration])) @@ -126,7 +170,8 @@ CheckCreate::preclaim(PreclaimContext const& ctx) JLOG(ctx.j.warn()) << "Creating a check that has already expired."; return tecEXPIRED; } - return tesSUCCESS; + + return canTrade(ctx.view, ctx.tx[sfSendMax].asset()); } TER @@ -202,4 +247,18 @@ CheckCreate::doApply() return tesSUCCESS; } +void +CheckCreate::visitInvariantEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ +} + +bool +CheckCreate::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&) +{ + return true; +} + } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/credentials/CredentialAccept.cpp b/src/libxrpl/tx/transactors/credentials/CredentialAccept.cpp index 9c0018a647..4f1bd80f26 100644 --- a/src/libxrpl/tx/transactors/credentials/CredentialAccept.cpp +++ b/src/libxrpl/tx/transactors/credentials/CredentialAccept.cpp @@ -1,13 +1,26 @@ +#include + #include #include #include #include +#include #include #include +#include +#include +#include +#include +#include +#include +#include +#include #include -#include +#include +#include -#include +#include +#include namespace xrpl { @@ -110,4 +123,22 @@ CredentialAccept::doApply() return tesSUCCESS; } +void +CredentialAccept::visitInvariantEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ +} + +bool +CredentialAccept::finalizeInvariants( + STTx const&, + TER, + XRPAmount, + ReadView const&, + beast::Journal const&) +{ + return true; +} } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/credentials/CredentialCreate.cpp b/src/libxrpl/tx/transactors/credentials/CredentialCreate.cpp index f8f0f01b63..402bddcfdd 100644 --- a/src/libxrpl/tx/transactors/credentials/CredentialCreate.cpp +++ b/src/libxrpl/tx/transactors/credentials/CredentialCreate.cpp @@ -1,14 +1,29 @@ +#include + #include +#include +#include #include #include -#include +#include // IWYU pragma: keep #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include #include -#include +#include +#include #include +#include +#include namespace xrpl { @@ -163,4 +178,22 @@ CredentialCreate::doApply() return tesSUCCESS; } +void +CredentialCreate::visitInvariantEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ +} + +bool +CredentialCreate::finalizeInvariants( + STTx const&, + TER, + XRPAmount, + ReadView const&, + beast::Journal const&) +{ + return true; +} } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/credentials/CredentialDelete.cpp b/src/libxrpl/tx/transactors/credentials/CredentialDelete.cpp index 5f78ba6757..acff3c6aa0 100644 --- a/src/libxrpl/tx/transactors/credentials/CredentialDelete.cpp +++ b/src/libxrpl/tx/transactors/credentials/CredentialDelete.cpp @@ -1,13 +1,22 @@ -#include -#include -#include -#include -#include -#include -#include #include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include namespace xrpl { @@ -87,4 +96,22 @@ CredentialDelete::doApply() return deleteSLE(view(), sleCred, j_); } +void +CredentialDelete::visitInvariantEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ +} + +bool +CredentialDelete::finalizeInvariants( + STTx const&, + TER, + XRPAmount, + ReadView const&, + beast::Journal const&) +{ + return true; +} } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/delegate/DelegateSet.cpp b/src/libxrpl/tx/transactors/delegate/DelegateSet.cpp index 9fb17c4d1f..9fd67f877d 100644 --- a/src/libxrpl/tx/transactors/delegate/DelegateSet.cpp +++ b/src/libxrpl/tx/transactors/delegate/DelegateSet.cpp @@ -1,11 +1,24 @@ +#include + #include -#include +#include +#include #include #include -#include +#include #include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include namespace xrpl { @@ -135,4 +148,18 @@ DelegateSet::deleteDelegate( return tesSUCCESS; } +void +DelegateSet::visitInvariantEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ +} + +bool +DelegateSet::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&) +{ + return true; +} + } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/delegate/DelegateUtils.cpp b/src/libxrpl/tx/transactors/delegate/DelegateUtils.cpp index d9d74a1212..4d3d97e443 100644 --- a/src/libxrpl/tx/transactors/delegate/DelegateUtils.cpp +++ b/src/libxrpl/tx/transactors/delegate/DelegateUtils.cpp @@ -1,12 +1,21 @@ +#include +#include +#include #include -#include +#include +#include +#include +#include + +#include +#include namespace xrpl { NotTEC checkTxPermission(std::shared_ptr const& delegate, STTx const& tx) { if (!delegate) - return terNO_DELEGATE_PERMISSION; // LCOV_EXCL_LINE + return terNO_DELEGATE_PERMISSION; auto const permissionArray = delegate->getFieldArray(sfPermissions); auto const txPermission = tx.getTxnType() + 1; @@ -28,7 +37,7 @@ loadGranularPermission( std::unordered_set& granularPermissions) { if (!delegate) - return; // LCOV_EXCL_LINE + return; auto const permissionArray = delegate->getFieldArray(sfPermissions); for (auto const& permission : permissionArray) diff --git a/src/libxrpl/tx/transactors/dex/AMMBid.cpp b/src/libxrpl/tx/transactors/dex/AMMBid.cpp index f5b9445ead..e878585d4c 100644 --- a/src/libxrpl/tx/transactors/dex/AMMBid.cpp +++ b/src/libxrpl/tx/transactors/dex/AMMBid.cpp @@ -1,26 +1,56 @@ -#include -#include -#include -#include -#include -#include #include -#include -#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include namespace xrpl { bool AMMBid::checkExtraFeatures(PreflightContext const& ctx) { - return ammEnabled(ctx.rules); + if (!ammEnabled(ctx.rules)) + return false; + + if (!ctx.rules.enabled(featureMPTokensV2) && + (ctx.tx[sfAsset].holds() || ctx.tx[sfAsset2].holds())) + return false; + + return true; } NotTEC AMMBid::preflight(PreflightContext const& ctx) { - if (auto const res = - invalidAMMAssetPair(ctx.tx[sfAsset].get(), ctx.tx[sfAsset2].get())) + if (auto const res = invalidAMMAssetPair(ctx.tx[sfAsset], ctx.tx[sfAsset2])) { JLOG(ctx.j.debug()) << "AMM Bid: Invalid asset pair."; return res; @@ -110,7 +140,7 @@ AMMBid::preclaim(PreclaimContext const& ctx) if (bidMin) { - if (bidMin->issue() != lpTokens.issue()) + if (bidMin->asset() != lpTokens.asset()) { JLOG(ctx.j.debug()) << "AMM Bid: Invalid LPToken."; return temBAD_AMM_TOKENS; @@ -125,7 +155,7 @@ AMMBid::preclaim(PreclaimContext const& ctx) auto const bidMax = ctx.tx[~sfBidMax]; if (bidMax) { - if (bidMax->issue() != lpTokens.issue()) + if (bidMax->asset() != lpTokens.asset()) { JLOG(ctx.j.debug()) << "AMM Bid: Invalid LPToken."; return temBAD_AMM_TOKENS; @@ -200,7 +230,7 @@ applyBid(ApplyContext& ctx_, Sandbox& sb, AccountID const& account_, beast::Jour { auctionSlot.makeFieldAbsent(sfDiscountedFee); } - auctionSlot.setFieldAmount(sfPrice, toSTAmount(lpTokens.issue(), minPrice)); + auctionSlot.setFieldAmount(sfPrice, toSTAmount(lpTokens.asset(), minPrice)); if (ctx_.tx.isFieldPresent(sfAuthAccounts)) { auctionSlot.setFieldArray(sfAuthAccounts, ctx_.tx.getFieldArray(sfAuthAccounts)); @@ -211,7 +241,7 @@ applyBid(ApplyContext& ctx_, Sandbox& sb, AccountID const& account_, beast::Jour } // Burn the remaining bid amount auto const saBurn = - adjustLPTokens(lptAMMBalance, toSTAmount(lptAMMBalance.issue(), burn), IsDeposit::No); + adjustLPTokens(lptAMMBalance, toSTAmount(lptAMMBalance.asset(), burn), IsDeposit::No); if (saBurn >= lptAMMBalance) { // This error case should never occur. @@ -221,7 +251,7 @@ applyBid(ApplyContext& ctx_, Sandbox& sb, AccountID const& account_, beast::Jour return tecINTERNAL; // LCOV_EXCL_STOP } - auto res = redeemIOU(sb, account_, saBurn, lpTokens.issue(), ctx_.journal); + auto res = redeemIOU(sb, account_, saBurn, lpTokens.get(), ctx_.journal); if (!isTesSuccess(res)) { JLOG(ctx_.journal.debug()) << "AMM Bid: failed to redeem."; @@ -321,7 +351,7 @@ applyBid(ApplyContext& ctx_, Sandbox& sb, AccountID const& account_, beast::Jour sb, account_, auctionSlot[sfAccount], - toSTAmount(lpTokens.issue(), refund), + toSTAmount(lpTokens.asset(), refund), ctx_.journal); if (!isTesSuccess(res)) { @@ -350,4 +380,18 @@ AMMBid::doApply() return result.first; } +void +AMMBid::visitInvariantEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ +} + +bool +AMMBid::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&) +{ + return true; +} + } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/dex/AMMClawback.cpp b/src/libxrpl/tx/transactors/dex/AMMClawback.cpp index b8f4fe4b1b..20ec184670 100644 --- a/src/libxrpl/tx/transactors/dex/AMMClawback.cpp +++ b/src/libxrpl/tx/transactors/dex/AMMClawback.cpp @@ -1,14 +1,34 @@ -#include -#include -#include -#include -#include -#include #include -#include -#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include #include namespace xrpl { @@ -19,6 +39,19 @@ AMMClawback::getFlagsMask(PreflightContext const& ctx) return tfAMMClawbackMask; } +bool +AMMClawback::checkExtraFeatures(xrpl::PreflightContext const& ctx) +{ + if (!ctx.rules.enabled(featureAMMClawback)) + return false; + + std::optional const clawAmount = ctx.tx[~sfAmount]; + + return ctx.rules.enabled(featureMPTokensV2) || + (!(clawAmount && clawAmount->holds()) && !ctx.tx[sfAsset].holds() && + !ctx.tx[sfAsset2].holds()); +} + NotTEC AMMClawback::preflight(PreflightContext const& ctx) { @@ -32,31 +65,31 @@ AMMClawback::preflight(PreflightContext const& ctx) } std::optional const clawAmount = ctx.tx[~sfAmount]; - auto const asset = ctx.tx[sfAsset].get(); - auto const asset2 = ctx.tx[sfAsset2].get(); + auto const asset = ctx.tx[sfAsset]; + auto const asset2 = ctx.tx[sfAsset2]; if (isXRP(asset)) return temMALFORMED; auto const flags = ctx.tx.getFlags(); - if (((flags & tfClawTwoAssets) != 0u) && asset.account != asset2.account) + if (((flags & tfClawTwoAssets) != 0u) && asset.getIssuer() != asset2.getIssuer()) { JLOG(ctx.j.trace()) << "AMMClawback: tfClawTwoAssets can only be enabled when two " "assets in the AMM pool are both issued by the issuer"; return temINVALID_FLAG; } - if (asset.account != issuer) + if (asset.getIssuer() != issuer) { JLOG(ctx.j.trace()) << "AMMClawback: Asset's account does not " "match Account field."; return temMALFORMED; } - if (clawAmount && clawAmount->get() != asset) + if (clawAmount && clawAmount->asset() != asset) { - JLOG(ctx.j.trace()) << "AMMClawback: Amount's issuer/currency subfield " + JLOG(ctx.j.trace()) << "AMMClawback: Amount's asset subfield " "does not match Asset field"; return temBAD_AMOUNT; } @@ -70,8 +103,8 @@ AMMClawback::preflight(PreflightContext const& ctx) TER AMMClawback::preclaim(PreclaimContext const& ctx) { - auto const asset = ctx.tx[sfAsset].get(); - auto const asset2 = ctx.tx[sfAsset2].get(); + auto const asset = ctx.tx[sfAsset]; + auto const asset2 = ctx.tx[sfAsset2]; auto const sleIssuer = ctx.view.read(keylet::account(ctx.tx[sfAccount])); if (!sleIssuer) return terNO_ACCOUNT; // LCOV_EXCL_LINE @@ -87,11 +120,38 @@ AMMClawback::preclaim(PreclaimContext const& ctx) } std::uint32_t const issuerFlagsIn = sleIssuer->getFieldU32(sfFlags); + if (!ctx.view.rules().enabled(featureMPTokensV2)) + { + // If AllowTrustLineClawback is not set or NoFreeze is set, return no + // permission + if (((issuerFlagsIn & lsfAllowTrustLineClawback) == 0u) || + ((issuerFlagsIn & lsfNoFreeze) != 0u)) + { + return tecNO_PERMISSION; + } + } - // If AllowTrustLineClawback is not set or NoFreeze is set, return no - // permission - if (((issuerFlagsIn & lsfAllowTrustLineClawback) == 0u) || - ((issuerFlagsIn & lsfNoFreeze) != 0u)) + auto const checkClawAsset = [&](Asset const asset) -> bool { + return asset.visit( + [&](Issue const& issue) { + if (issue.native()) + return false; // LCOV_EXCL_LINE + + return ((issuerFlagsIn & lsfAllowTrustLineClawback) != 0u) && + ((issuerFlagsIn & lsfNoFreeze) == 0u); + }, + [&](MPTIssue const& issue) { + auto const sleIssuance = ctx.view.read(keylet::mptIssuance(issue.getMptID())); + + return sleIssuance && sleIssuance->isFlag(lsfMPTCanClawback) && + sleIssuance->getAccountID(sfIssuer) == ctx.tx[sfAccount]; + }); + }; + + if (!checkClawAsset(asset)) + return tecNO_PERMISSION; + + if (ctx.tx.isFlag(tfClawTwoAssets) && !checkClawAsset(asset2)) return tecNO_PERMISSION; return tesSUCCESS; @@ -115,8 +175,8 @@ AMMClawback::applyGuts(Sandbox& sb) std::optional const clawAmount = ctx_.tx[~sfAmount]; AccountID const issuer = ctx_.tx[sfAccount]; AccountID const holder = ctx_.tx[sfHolder]; - Issue const asset = ctx_.tx[sfAsset].get(); - Issue const asset2 = ctx_.tx[sfAsset2].get(); + Asset const asset = ctx_.tx[sfAsset]; + Asset const asset2 = ctx_.tx[sfAsset2]; auto ammSle = sb.peek(keylet::amm(asset, asset2)); if (!ammSle) @@ -138,8 +198,15 @@ AMMClawback::applyGuts(Sandbox& sb) !res) return res.error(); // LCOV_EXCL_LINE } - auto const expected = - ammHolds(sb, *ammSle, asset, asset2, FreezeHandling::fhIGNORE_FREEZE, ctx_.journal); + + auto const expected = ammHolds( + sb, + *ammSle, + asset, + asset2, + FreezeHandling::fhIGNORE_FREEZE, + AuthHandling::ahIGNORE_AUTH, + ctx_.journal); if (!expected) return expected.error(); // LCOV_EXCL_LINE @@ -173,6 +240,7 @@ AMMClawback::applyGuts(Sandbox& sb) holdLPtokens, 0, FreezeHandling::fhIGNORE_FREEZE, + AuthHandling::ahIGNORE_AUTH, WithdrawAll::Yes, preFeeBalance_, ctx_.journal); @@ -204,7 +272,12 @@ AMMClawback::applyGuts(Sandbox& sb) << to_string(newLPTokenBalance.iou()) << " old balance: " << to_string(lptAMMBalance.iou()); - auto const ter = rippleCredit(sb, holder, issuer, amountWithdraw, true, j_); + auto sendAmount = [&](STAmount const& saAmount) -> TER { + bool const checkIssuer = saAmount.holds(); + return directSendNoFee(sb, holder, issuer, saAmount, checkIssuer, j_); + }; + + auto const ter = sendAmount(amountWithdraw); if (!isTesSuccess(ter)) return ter; // LCOV_EXCL_LINE @@ -217,7 +290,7 @@ AMMClawback::applyGuts(Sandbox& sb) auto const flags = ctx_.tx.getFlags(); if ((flags & tfClawTwoAssets) != 0u) - return rippleCredit(sb, holder, issuer, *amount2Withdraw, true, j_); + return sendAmount(*amount2Withdraw); return tesSUCCESS; } @@ -237,8 +310,7 @@ AMMClawback::equalWithdrawMatchingOneAmount( auto frac = Number{amount} / amountBalance; auto amount2Withdraw = amount2Balance * frac; - auto const lpTokensWithdraw = toSTAmount(lptAMMBalance.issue(), lptAMMBalance * frac); - + auto const lpTokensWithdraw = toSTAmount(lptAMMBalance.asset(), lptAMMBalance * frac); if (lpTokensWithdraw > holdLPtokens) { // if lptoken balance less than what the issuer intended to clawback, @@ -256,6 +328,7 @@ AMMClawback::equalWithdrawMatchingOneAmount( holdLPtokens, 0, FreezeHandling::fhIGNORE_FREEZE, + AuthHandling::ahIGNORE_AUTH, WithdrawAll::Yes, preFeeBalance_, ctx_.journal); @@ -288,6 +361,7 @@ AMMClawback::equalWithdrawMatchingOneAmount( tokensAdj, 0, FreezeHandling::fhIGNORE_FREEZE, + AuthHandling::ahIGNORE_AUTH, WithdrawAll::No, preFeeBalance_, ctx_.journal); @@ -302,14 +376,29 @@ AMMClawback::equalWithdrawMatchingOneAmount( holder, amountBalance, amount, - toSTAmount(amount2Balance.issue(), amount2Withdraw), + toSTAmount(amount2Balance.asset(), amount2Withdraw), lptAMMBalance, - toSTAmount(lptAMMBalance.issue(), lptAMMBalance * frac), + toSTAmount(lptAMMBalance.asset(), lptAMMBalance * frac), 0, FreezeHandling::fhIGNORE_FREEZE, + AuthHandling::ahIGNORE_AUTH, WithdrawAll::No, preFeeBalance_, ctx_.journal); } +void +AMMClawback::visitInvariantEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ +} + +bool +AMMClawback::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&) +{ + return true; +} + } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/dex/AMMCreate.cpp b/src/libxrpl/tx/transactors/dex/AMMCreate.cpp index 069fadf103..47363531d4 100644 --- a/src/libxrpl/tx/transactors/dex/AMMCreate.cpp +++ b/src/libxrpl/tx/transactors/dex/AMMCreate.cpp @@ -1,21 +1,55 @@ +#include + +#include +#include +#include #include +#include #include #include +#include #include +#include +#include #include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include #include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include namespace xrpl { bool AMMCreate::checkExtraFeatures(PreflightContext const& ctx) { - return ammEnabled(ctx.rules); + if (!ammEnabled(ctx.rules)) + return false; + + if (!ctx.rules.enabled(featureMPTokensV2) && + (ctx.tx[sfAmount].holds() || ctx.tx[sfAmount2].holds())) + return false; + + return true; } NotTEC @@ -24,9 +58,9 @@ AMMCreate::preflight(PreflightContext const& ctx) auto const amount = ctx.tx[sfAmount]; auto const amount2 = ctx.tx[sfAmount2]; - if (amount.issue() == amount2.issue()) + if (amount.asset() == amount2.asset()) { - JLOG(ctx.j.debug()) << "AMM Instance: tokens can not have the same currency/issuer."; + JLOG(ctx.j.debug()) << "AMM Instance: tokens can not have the same asset."; return temBAD_AMM_TOKENS; } @@ -66,44 +100,44 @@ AMMCreate::preclaim(PreclaimContext const& ctx) auto const amount2 = ctx.tx[sfAmount2]; // Check if AMM already exists for the token pair - if (auto const ammKeylet = keylet::amm(amount.issue(), amount2.issue()); + if (auto const ammKeylet = keylet::amm(amount.asset(), amount2.asset()); ctx.view.read(ammKeylet)) { JLOG(ctx.j.debug()) << "AMM Instance: ltAMM already exists."; return tecDUPLICATE; } - if (auto const ter = requireAuth(ctx.view, amount.issue(), accountID); !isTesSuccess(ter)) + if (auto const ter = requireAuth(ctx.view, amount.asset(), accountID); !isTesSuccess(ter)) { - JLOG(ctx.j.debug()) << "AMM Instance: account is not authorized, " << amount.issue(); + JLOG(ctx.j.debug()) << "AMM Instance: account is not authorized, " << amount.asset(); return ter; } - if (auto const ter = requireAuth(ctx.view, amount2.issue(), accountID); !isTesSuccess(ter)) + if (auto const ter = requireAuth(ctx.view, amount2.asset(), accountID); !isTesSuccess(ter)) { - JLOG(ctx.j.debug()) << "AMM Instance: account is not authorized, " << amount2.issue(); + JLOG(ctx.j.debug()) << "AMM Instance: account is not authorized, " << amount2.asset(); return ter; } // Globally or individually frozen - if (isFrozen(ctx.view, accountID, amount.issue()) || - isFrozen(ctx.view, accountID, amount2.issue())) + if (isFrozen(ctx.view, accountID, amount.asset()) || + isFrozen(ctx.view, accountID, amount2.asset())) { JLOG(ctx.j.debug()) << "AMM Instance: involves frozen asset."; return tecFROZEN; } - auto noDefaultRipple = [](ReadView const& view, Issue const& issue) { - if (isXRP(issue)) + auto noDefaultRipple = [](ReadView const& view, Asset const& asset) { + if (asset.holds() || isXRP(asset)) return false; - if (auto const issuerAccount = view.read(keylet::account(issue.account))) + if (auto const issuerAccount = view.read(keylet::account(asset.getIssuer()))) return (issuerAccount->getFlags() & lsfDefaultRipple) == 0; return false; }; - if (noDefaultRipple(ctx.view, amount.issue()) || noDefaultRipple(ctx.view, amount2.issue())) + if (noDefaultRipple(ctx.view, amount.asset()) || noDefaultRipple(ctx.view, amount2.asset())) { JLOG(ctx.j.debug()) << "AMM Instance: DefaultRipple not set"; return terNO_RIPPLE; @@ -118,13 +152,16 @@ AMMCreate::preclaim(PreclaimContext const& ctx) return tecINSUF_RESERVE_LINE; } - auto insufficientBalance = [&](STAmount const& asset) { - if (isXRP(asset)) - return xrpBalance < asset; - return accountID != asset.issue().account && - accountHolds( - ctx.view, accountID, asset.issue(), FreezeHandling::fhZERO_IF_FROZEN, ctx.j) < - asset; + auto insufficientBalance = [&](STAmount const& amount) { + if (isXRP(amount)) + return xrpBalance < amount; + return accountFunds( + ctx.view, + accountID, + amount, + FreezeHandling::fhZERO_IF_FROZEN, + AuthHandling::ahZERO_IF_UNAUTHORIZED, + ctx.j) < amount; }; if (insufficientBalance(amount) || insufficientBalance(amount2)) @@ -134,7 +171,7 @@ AMMCreate::preclaim(PreclaimContext const& ctx) } auto isLPToken = [&](STAmount const& amount) -> bool { - if (auto const sle = ctx.view.read(keylet::account(amount.issue().account))) + if (auto const sle = ctx.view.read(keylet::account(amount.asset().getIssuer()))) return sle->isFieldPresent(sfAMMID); return false; }; @@ -149,11 +186,18 @@ AMMCreate::preclaim(PreclaimContext const& ctx) if (ctx.view.rules().enabled(featureSingleAssetVault)) { if (auto const accountId = - pseudoAccountAddress(ctx.view, keylet::amm(amount.issue(), amount2.issue()).key); + pseudoAccountAddress(ctx.view, keylet::amm(amount.asset(), amount2.asset()).key); accountId == beast::zero) return terADDRESS_COLLISION; } + if (auto const ter = checkMPTTxAllowed(ctx.view, ttAMM_CREATE, amount.asset(), accountID); + !isTesSuccess(ter)) + return ter; + if (auto const ter = checkMPTTxAllowed(ctx.view, ttAMM_CREATE, amount2.asset(), accountID); + !isTesSuccess(ter)) + return ter; + // If featureAMMClawback is enabled, allow AMMCreate without checking // if the issuer has clawback enabled if (ctx.view.rules().enabled(featureAMMClawback)) @@ -161,20 +205,34 @@ AMMCreate::preclaim(PreclaimContext const& ctx) // Disallow AMM if the issuer has clawback enabled when featureAMMClawback // is not enabled - auto clawbackDisabled = [&](Issue const& issue) -> TER { - if (isXRP(issue)) - return tesSUCCESS; - auto const sle = ctx.view.read(keylet::account(issue.account)); - if (!sle) - return tecINTERNAL; // LCOV_EXCL_LINE - if (sle->getFlags() & lsfAllowTrustLineClawback) - return tecNO_PERMISSION; - return tesSUCCESS; + auto clawbackDisabled = [&](Asset const& asset) -> TER { + return asset.visit( + [&](MPTIssue const& issue) -> TER { + auto const sle = ctx.view.read(keylet::mptIssuance(issue.getMptID())); + if (!sle) + return tecINTERNAL; // LCOV_EXCL_LINE + if (sle->isFlag(lsfMPTCanClawback)) + return tecNO_PERMISSION; + return tesSUCCESS; + }, + [&](Issue const& issue) -> TER { + if (isXRP(issue)) + return tesSUCCESS; + auto const sle = ctx.view.read(keylet::account(issue.account)); + if (!sle) + return tecINTERNAL; // LCOV_EXCL_LINE + if (sle->isFlag(lsfAllowTrustLineClawback)) + return tecNO_PERMISSION; + return tesSUCCESS; + }); }; - if (auto const ter = clawbackDisabled(amount.issue()); !isTesSuccess(ter)) + if (auto const ter = clawbackDisabled(amount.asset()); !isTesSuccess(ter)) return ter; - return clawbackDisabled(amount2.issue()); + if (auto const ter = clawbackDisabled(amount2.asset()); !isTesSuccess(ter)) + return ter; + + return tesSUCCESS; } static std::pair @@ -183,7 +241,7 @@ applyCreate(ApplyContext& ctx_, Sandbox& sb, AccountID const& account_, beast::J auto const amount = ctx_.tx[sfAmount]; auto const amount2 = ctx_.tx[sfAmount2]; - auto const ammKeylet = keylet::amm(amount.issue(), amount2.issue()); + auto const ammKeylet = keylet::amm(amount.asset(), amount2.asset()); // Mitigate same account exists possibility auto const maybeAccount = createPseudoAccount(sb, ammKeylet.key, sfAMMID); @@ -197,7 +255,7 @@ applyCreate(ApplyContext& ctx_, Sandbox& sb, AccountID const& account_, beast::J auto const accountId = (*account)[sfAccount]; // LP Token already exists. (should not happen) - auto const lptIss = ammLPTIssue(amount.issue().currency, amount2.issue().currency, accountId); + auto const lptIss = ammLPTIssue(amount.asset(), amount2.asset(), accountId); if (sb.read(keylet::line(accountId, lptIss))) { JLOG(j_.error()) << "AMM Instance: LP Token already exists."; @@ -217,9 +275,9 @@ applyCreate(ApplyContext& ctx_, Sandbox& sb, AccountID const& account_, beast::J auto ammSle = std::make_shared(ammKeylet); ammSle->setAccountID(sfAccount, accountId); ammSle->setFieldAmount(sfLPTokenBalance, lpTokens); - auto const& [issue1, issue2] = std::minmax(amount.issue(), amount2.issue()); - ammSle->setFieldIssue(sfAsset, STIssue{sfAsset, issue1}); - ammSle->setFieldIssue(sfAsset2, STIssue{sfAsset2, issue2}); + auto const& [asset1, asset2] = std::minmax(amount.asset(), amount2.asset()); + ammSle->setFieldIssue(sfAsset, STIssue{sfAsset, asset1}); + ammSle->setFieldIssue(sfAsset2, STIssue{sfAsset2, asset2}); // AMM creator gets the auction slot and the voting slot. initializeFeeAuctionVote(ctx_.view(), ammSle, account_, lptIss, ctx_.tx[sfTradingFee]); @@ -239,28 +297,59 @@ applyCreate(ApplyContext& ctx_, Sandbox& sb, AccountID const& account_, beast::J return {res, false}; } - auto sendAndTrustSet = [&](STAmount const& amount) -> TER { - if (auto const res = - accountSend(sb, account_, accountId, amount, ctx_.journal, WaiveTransferFee::Yes)) - return res; - // Set AMM flag on AMM trustline - if (!isXRP(amount)) - { - SLE::pointer const sleRippleState = sb.peek(keylet::line(accountId, amount.issue())); - if (!sleRippleState) - { - return tecINTERNAL; // LCOV_EXCL_LINE - } + auto sendAndInitTrustOrMPT = [&](STAmount const& amount) -> TER { + // Authorize MPT + return amount.asset().visit( + [&](MPTIssue const& issue) -> TER { + // Authorize MPT + auto const& mptIssue = issue; + auto const& mptID = mptIssue.getMptID(); + std::uint32_t flags = lsfMPTAMM; + if (auto const err = + requireAuth(ctx_.view(), mptIssue, accountId, AuthType::WeakAuth); + !isTesSuccess(err)) + { + if (err == tecNO_AUTH) + { + flags |= lsfMPTAuthorized; + } + else + { + return err; + } + } - auto const flags = sleRippleState->getFlags(); - sleRippleState->setFieldU32(sfFlags, flags | lsfAMMNode); - sb.update(sleRippleState); - } - return tesSUCCESS; + if (auto const err = createMPToken(sb, mptID, accountId, flags); !isTesSuccess(err)) + return err; + // Don't adjust AMM owner count. + // It's irrelevant for pseudo-account like AMM. + return accountSend( + sb, account_, accountId, amount, ctx_.journal, WaiveTransferFee::Yes); + }, + // Set AMM flag on AMM trustline + [&](Issue const& issue) -> TER { + if (auto const res = accountSend( + sb, account_, accountId, amount, ctx_.journal, WaiveTransferFee::Yes)) + return res; + // Set AMM flag on AMM trustline + if (!isXRP(amount)) + { + SLE::pointer const sleRippleState = sb.peek(keylet::line(accountId, issue)); + if (!sleRippleState) + { + return tecINTERNAL; // LCOV_EXCL_LINE + } + + auto const flags = sleRippleState->getFlags(); + sleRippleState->setFieldU32(sfFlags, flags | lsfAMMNode); + sb.update(sleRippleState); + } + return tesSUCCESS; + }); }; // Send asset1. - res = sendAndTrustSet(amount); + res = sendAndInitTrustOrMPT(amount); if (!isTesSuccess(res)) { JLOG(j_.debug()) << "AMM Instance: failed to send " << amount; @@ -268,7 +357,7 @@ applyCreate(ApplyContext& ctx_, Sandbox& sb, AccountID const& account_, beast::J } // Send asset2. - res = sendAndTrustSet(amount2); + res = sendAndInitTrustOrMPT(amount2); if (!isTesSuccess(res)) { JLOG(j_.debug()) << "AMM Instance: failed to send " << amount2; @@ -277,14 +366,14 @@ applyCreate(ApplyContext& ctx_, Sandbox& sb, AccountID const& account_, beast::J JLOG(j_.debug()) << "AMM Instance: success " << accountId << " " << ammKeylet.key << " " << lpTokens << " " << amount << " " << amount2; - auto addOrderBook = [&](Issue const& issueIn, Issue const& issueOut, std::uint64_t uRate) { - Book const book{issueIn, issueOut, std::nullopt}; + auto addOrderBook = [&](Asset const& assetIn, Asset const& assetOut, std::uint64_t uRate) { + Book const book{assetIn, assetOut, std::nullopt}; auto const dir = keylet::quality(keylet::book(book), uRate); if (auto const bookExisted = static_cast(sb.read(dir)); !bookExisted) ctx_.registry.get().getOrderBookDB().addOrderBook(book); }; - addOrderBook(amount.issue(), amount2.issue(), getRate(amount2, amount)); - addOrderBook(amount2.issue(), amount.issue(), getRate(amount, amount2)); + addOrderBook(amount.asset(), amount2.asset(), getRate(amount2, amount)); + addOrderBook(amount2.asset(), amount.asset(), getRate(amount, amount2)); return {res, isTesSuccess(res)}; } @@ -303,4 +392,18 @@ AMMCreate::doApply() return result.first; } +void +AMMCreate::visitInvariantEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ +} + +bool +AMMCreate::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&) +{ + return true; +} + } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/dex/AMMDelete.cpp b/src/libxrpl/tx/transactors/dex/AMMDelete.cpp index 0fbaa4e23e..6d2c7b07c3 100644 --- a/src/libxrpl/tx/transactors/dex/AMMDelete.cpp +++ b/src/libxrpl/tx/transactors/dex/AMMDelete.cpp @@ -1,16 +1,32 @@ -#include -#include -#include -#include #include -#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include namespace xrpl { bool AMMDelete::checkExtraFeatures(PreflightContext const& ctx) { - return ammEnabled(ctx.rules); + if (!ammEnabled(ctx.rules)) + return false; + + return ctx.rules.enabled(featureMPTokensV2) || + (!ctx.tx[sfAsset].holds() && !ctx.tx[sfAsset2].holds()); } NotTEC @@ -43,12 +59,25 @@ AMMDelete::doApply() // as we go on processing transactions. Sandbox sb(&ctx_.view()); - auto const ter = - deleteAMMAccount(sb, ctx_.tx[sfAsset].get(), ctx_.tx[sfAsset2].get(), j_); + auto const ter = deleteAMMAccount(sb, ctx_.tx[sfAsset], ctx_.tx[sfAsset2], j_); if (isTesSuccess(ter) || ter == tecINCOMPLETE) sb.apply(ctx_.rawView()); return ter; } +void +AMMDelete::visitInvariantEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ +} + +bool +AMMDelete::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&) +{ + return true; +} + } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/dex/AMMDeposit.cpp b/src/libxrpl/tx/transactors/dex/AMMDeposit.cpp index eb3e61e80b..344e3535da 100644 --- a/src/libxrpl/tx/transactors/dex/AMMDeposit.cpp +++ b/src/libxrpl/tx/transactors/dex/AMMDeposit.cpp @@ -1,19 +1,51 @@ -#include -#include -#include -#include -#include -#include #include -#include -#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include namespace xrpl { bool AMMDeposit::checkExtraFeatures(PreflightContext const& ctx) { - return ammEnabled(ctx.rules); + if (!ammEnabled(ctx.rules)) + return false; + + auto const amount = ctx.tx[~sfAmount]; + auto const amount2 = ctx.tx[~sfAmount2]; + + return ctx.rules.enabled(featureMPTokensV2) || + (!ctx.tx[sfAsset].holds() && !ctx.tx[sfAsset2].holds() && + !(amount && amount->holds()) && !(amount2 && amount2->holds())); } std::uint32_t @@ -26,7 +58,6 @@ NotTEC AMMDeposit::preflight(PreflightContext const& ctx) { auto const flags = ctx.tx.getFlags(); - auto const amount = ctx.tx[~sfAmount]; auto const amount2 = ctx.tx[~sfAmount2]; auto const ePrice = ctx.tx[~sfEPrice]; @@ -78,18 +109,18 @@ AMMDeposit::preflight(PreflightContext const& ctx) return temMALFORMED; } - auto const asset = ctx.tx[sfAsset].get(); - auto const asset2 = ctx.tx[sfAsset2].get(); + auto const asset = ctx.tx[sfAsset]; + auto const asset2 = ctx.tx[sfAsset2]; if (auto const res = invalidAMMAssetPair(asset, asset2)) { JLOG(ctx.j.debug()) << "AMM Deposit: invalid asset pair."; return res; } - if (amount && amount2 && amount->issue() == amount2->issue()) + if (amount && amount2 && amount->asset() == amount2->asset()) { - JLOG(ctx.j.debug()) << "AMM Deposit: invalid tokens, same issue." << amount->issue() << " " - << amount2->issue(); + JLOG(ctx.j.debug()) << "AMM Deposit: invalid tokens, same issue." << amount->asset() << " " + << amount2->asset(); return temBAD_AMM_TOKENS; } @@ -119,11 +150,16 @@ AMMDeposit::preflight(PreflightContext const& ctx) } } - // must be amount issue if (amount && ePrice) { - if (auto const res = invalidAMMAmount( - *ePrice, std::make_optional(std::make_pair(amount->issue(), amount->issue())))) + auto assets = [&]() -> std::optional> { + // don't check ePrice issue + if (ctx.rules.enabled(featureMPTokensV2)) + return std::nullopt; + // must be amount issue + return std::make_optional(std::make_pair(amount->asset(), amount->asset())); + }(); + if (auto const res = invalidAMMAmount(*ePrice, assets)) { JLOG(ctx.j.debug()) << "AMM Deposit: invalid EPrice"; return res; @@ -152,7 +188,13 @@ AMMDeposit::preclaim(PreclaimContext const& ctx) } auto const expected = ammHolds( - ctx.view, *ammSle, std::nullopt, std::nullopt, FreezeHandling::fhIGNORE_FREEZE, ctx.j); + ctx.view, + *ammSle, + std::nullopt, + std::nullopt, + FreezeHandling::fhIGNORE_FREEZE, + AuthHandling::ahIGNORE_AUTH, + ctx.j); if (!expected) return expected.error(); // LCOV_EXCL_LINE auto const [amountBalance, amount2Balance, lptAMMBalance] = *expected; @@ -190,7 +232,7 @@ AMMDeposit::preclaim(PreclaimContext const& ctx) auto balance = [&](auto const& deposit) -> TER { if (isXRP(deposit)) { - auto const lpIssue = (*ammSle)[sfLPTokenBalance].issue(); + auto const lpIssue = (*ammSle)[sfLPTokenBalance].get(); // Adjust the reserve if LP doesn't have LPToken trustline auto const sle = ctx.view.read(keylet::line(accountID, lpIssue.account, lpIssue.currency)); @@ -200,10 +242,13 @@ AMMDeposit::preclaim(PreclaimContext const& ctx) return tecUNFUNDED_AMM; return tecINSUF_RESERVE_LINE; } - return (accountID == deposit.issue().account || - accountHolds( - ctx.view, accountID, deposit.issue(), FreezeHandling::fhIGNORE_FREEZE, ctx.j) >= - deposit) + return accountFunds( + ctx.view, + accountID, + deposit, + FreezeHandling::fhIGNORE_FREEZE, + AuthHandling::ahIGNORE_AUTH, + ctx.j) >= deposit ? TER(tesSUCCESS) : tecUNFUNDED_AMM; }; @@ -212,8 +257,11 @@ AMMDeposit::preclaim(PreclaimContext const& ctx) { // Check if either of the assets is frozen, AMMDeposit is not allowed // if either asset is frozen - auto checkAsset = [&](Issue const& asset) -> TER { - if (auto const ter = requireAuth(ctx.view, asset, accountID)) + auto checkAsset = [&](Asset const& asset) -> TER { + // WeakAuth - don't need to check if MPT object exists as might be + // depositing into non-MPT pool. It'll fail on send if MPT doesn't + // exist. + if (auto const ter = requireAuth(ctx.view, asset, accountID, AuthType::WeakAuth)) { JLOG(ctx.j.debug()) << "AMM Deposit: account is not authorized, " << asset; return ter; @@ -222,7 +270,7 @@ AMMDeposit::preclaim(PreclaimContext const& ctx) if (isFrozen(ctx.view, accountID, asset)) { JLOG(ctx.j.debug()) << "AMM Deposit: account or currency is frozen, " - << to_string(accountID) << " " << to_string(asset.currency); + << to_string(accountID) << " " << to_string(asset); return tecFROZEN; } @@ -230,10 +278,10 @@ AMMDeposit::preclaim(PreclaimContext const& ctx) return tesSUCCESS; }; - if (auto const ter = checkAsset(ctx.tx[sfAsset].get())) + if (auto const ter = checkAsset(ctx.tx[sfAsset])) return ter; - if (auto const ter = checkAsset(ctx.tx[sfAsset2].get())) + if (auto const ter = checkAsset(ctx.tx[sfAsset2])) return ter; } @@ -246,27 +294,27 @@ AMMDeposit::preclaim(PreclaimContext const& ctx) { // This normally should not happen. // Account is not authorized to hold the assets it's depositing, - // or it doesn't even have a trust line for them - if (auto const ter = requireAuth(ctx.view, amount->issue(), accountID)) + // or it doesn't even have a trust line or MPT for them. + if (auto const ter = requireAuth(ctx.view, amount->asset(), accountID)) { // LCOV_EXCL_START JLOG(ctx.j.debug()) - << "AMM Deposit: account is not authorized, " << amount->issue(); + << "AMM Deposit: account is not authorized, " << amount->asset(); return ter; // LCOV_EXCL_STOP } // AMM account or currency frozen - if (isFrozen(ctx.view, ammAccountID, amount->issue())) + if (isFrozen(ctx.view, ammAccountID, amount->asset())) { JLOG(ctx.j.debug()) << "AMM Deposit: AMM account or currency is frozen, " << to_string(accountID); return tecFROZEN; } // Account frozen - if (isIndividualFrozen(ctx.view, accountID, amount->issue())) + if (isIndividualFrozen(ctx.view, accountID, amount->asset())) { JLOG(ctx.j.debug()) << "AMM Deposit: account is frozen, " << to_string(accountID) - << " " << to_string(amount->issue().currency); + << " " << to_string(amount->asset()); return tecFROZEN; } if (checkBalance) @@ -301,7 +349,7 @@ AMMDeposit::preclaim(PreclaimContext const& ctx) // Equal deposit lp tokens if (auto const lpTokens = ctx.tx[~sfLPTokenOut]; - lpTokens && lpTokens->issue() != lptAMMBalance.issue()) + lpTokens && lpTokens->asset() != lptAMMBalance.asset()) { JLOG(ctx.j.debug()) << "AMM Deposit: invalid LPTokens."; return temBAD_AMM_TOKENS; @@ -320,6 +368,13 @@ AMMDeposit::preclaim(PreclaimContext const& ctx) } } + if (auto const ter = checkMPTTxAllowed(ctx.view, ttAMM_DEPOSIT, ctx.tx[sfAsset], accountID); + !isTesSuccess(ter)) + return ter; + if (auto const ter = checkMPTTxAllowed(ctx.view, ttAMM_DEPOSIT, ctx.tx[sfAsset2], accountID); + !isTesSuccess(ter)) + return ter; + return tesSUCCESS; } @@ -338,9 +393,10 @@ AMMDeposit::applyGuts(Sandbox& sb) auto const expected = ammHolds( sb, *ammSle, - amount ? amount->issue() : std::optional{}, - amount2 ? amount2->issue() : std::optional{}, + amount ? amount->asset() : std::optional{}, + amount2 ? amount2->asset() : std::optional{}, FreezeHandling::fhZERO_IF_FROZEN, + AuthHandling::ahZERO_IF_UNAUTHORIZED, ctx_.journal); if (!expected) return {expected.error(), false}; // LCOV_EXCL_LINE @@ -400,7 +456,7 @@ AMMDeposit::applyGuts(Sandbox& sb) if (subTxType & tfTwoAssetIfEmpty) { return equalDepositInEmptyState( - sb, ammAccountID, *amount, *amount2, lptAMMBalance.issue(), tfee); + sb, ammAccountID, *amount, *amount2, lptAMMBalance.asset(), tfee); } // should not happen. // LCOV_EXCL_START @@ -418,7 +474,7 @@ AMMDeposit::applyGuts(Sandbox& sb) // LP depositing into AMM empty state gets the auction slot // and the voting if (lptAMMBalance == beast::zero) - initializeFeeAuctionVote(sb, ammSle, account_, lptAMMBalance.issue(), tfee); + initializeFeeAuctionVote(sb, ammSle, account_, lptAMMBalance.asset(), tfee); sb.update(ammSle); } @@ -461,19 +517,19 @@ AMMDeposit::deposit( return temBAD_AMOUNT; if (isXRP(depositAmount)) { - auto const& lpIssue = lpTokensDeposit.issue(); + auto const& lpIssue = lpTokensDeposit.get(); // Adjust the reserve if LP doesn't have LPToken trustline auto const sle = view.read(keylet::line(account_, lpIssue.account, lpIssue.currency)); if (xrpLiquid(view, account_, !sle, j_) >= depositAmount) return tesSUCCESS; } else if ( - account_ == depositAmount.issue().account || - accountHolds( + accountFunds( view, account_, - depositAmount.issue(), + depositAmount, FreezeHandling::fhIGNORE_FREEZE, + AuthHandling::ahIGNORE_AUTH, ctx_.journal) >= depositAmount) { return tesSUCCESS; @@ -588,7 +644,7 @@ AMMDeposit::equalDepositTokens( auto const tokensAdj = adjustLPTokensOut(view.rules(), lptAMMBalance, lpTokensDeposit); if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero) return {tecAMM_INVALID_TOKENS, STAmount{}}; - auto const frac = divide(tokensAdj, lptAMMBalance, lptAMMBalance.issue()); + auto const frac = divide(tokensAdj, lptAMMBalance, lptAMMBalance.asset()); // amounts factor in the adjusted tokens auto const amountDeposit = getRoundedAsset(view.rules(), amountBalance, frac, IsDeposit::Yes); @@ -934,7 +990,7 @@ AMMDeposit::equalDepositInEmptyState( AccountID const& ammAccount, STAmount const& amount, STAmount const& amount2, - Issue const& lptIssue, + Asset const& lptIssue, std::uint16_t tfee) { return deposit( @@ -951,4 +1007,18 @@ AMMDeposit::equalDepositInEmptyState( tfee); } +void +AMMDeposit::visitInvariantEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ +} + +bool +AMMDeposit::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&) +{ + return true; +} + } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/dex/AMMHelpers.cpp b/src/libxrpl/tx/transactors/dex/AMMHelpers.cpp deleted file mode 100644 index 386608229b..0000000000 --- a/src/libxrpl/tx/transactors/dex/AMMHelpers.cpp +++ /dev/null @@ -1,379 +0,0 @@ -#include - -namespace xrpl { - -STAmount -ammLPTokens(STAmount const& asset1, STAmount const& asset2, Issue const& lptIssue) -{ - // AMM invariant: sqrt(asset1 * asset2) >= LPTokensBalance - auto const rounding = isFeatureEnabled(fixAMMv1_3) ? Number::downward : Number::getround(); - NumberRoundModeGuard const g(rounding); - auto const tokens = root2(asset1 * asset2); - return toSTAmount(lptIssue, tokens); -} - -/* - * Equation 3: - * t = T * [(b/B - (sqrt(f2**2 - b/(B*f1)) - f2)) / - * (1 + sqrt(f2**2 - b/(B*f1)) - f2)] - * where f1 = 1 - tfee, f2 = (1 - tfee/2)/f1 - */ -STAmount -lpTokensOut( - STAmount const& asset1Balance, - STAmount const& asset1Deposit, - STAmount const& lptAMMBalance, - std::uint16_t tfee) -{ - auto const f1 = feeMult(tfee); - auto const f2 = feeMultHalf(tfee) / f1; - Number const r = asset1Deposit / asset1Balance; - auto const c = root2(f2 * f2 + r / f1) - f2; - if (!isFeatureEnabled(fixAMMv1_3)) - { - auto const t = lptAMMBalance * (r - c) / (1 + c); - return toSTAmount(lptAMMBalance.issue(), t); - } - - // minimize tokens out - auto const frac = (r - c) / (1 + c); - return multiply(lptAMMBalance, frac, Number::downward); -} - -/* Equation 4 solves equation 3 for b: - * Let f1 = 1 - tfee, f2 = (1 - tfee/2)/f1, t1 = t/T, t2 = 1 + t1, R = b/B - * then - * t1 = [R - sqrt(f2**2 + R/f1) + f2] / [1 + sqrt(f2**2 + R/f1] - f2] => - * sqrt(f2**2 + R/f1)*(t1 + 1) = R + f2 + t1*f2 - t1 => - * sqrt(f2**2 + R/f1)*t2 = R + t2*f2 - t1 => - * sqrt(f2**2 + R/f1) = R/t2 + f2 - t1/t2, let d = f2 - t1/t2 => - * sqrt(f2**2 + R/f1) = R/t2 + d => - * f2**2 + R/f1 = (R/t2)**2 +2*d*R/t2 + d**2 => - * (R/t2)**2 + R*(2*d/t2 - 1/f1) + d**2 - f2**2 = 0 - */ -STAmount -ammAssetIn( - STAmount const& asset1Balance, - STAmount const& lptAMMBalance, - STAmount const& lpTokens, - std::uint16_t tfee) -{ - auto const f1 = feeMult(tfee); - auto const f2 = feeMultHalf(tfee) / f1; - auto const t1 = lpTokens / lptAMMBalance; - auto const t2 = 1 + t1; - auto const d = f2 - t1 / t2; - auto const a = 1 / (t2 * t2); - auto const b = 2 * d / t2 - 1 / f1; - auto const c = d * d - f2 * f2; - if (!isFeatureEnabled(fixAMMv1_3)) - { - return toSTAmount(asset1Balance.issue(), asset1Balance * solveQuadraticEq(a, b, c)); - } - - // maximize deposit - auto const frac = solveQuadraticEq(a, b, c); - return multiply(asset1Balance, frac, Number::upward); -} - -/* Equation 7: - * t = T * (c - sqrt(c**2 - 4*R))/2 - * where R = b/B, c = R*fee + 2 - fee - */ -STAmount -lpTokensIn( - STAmount const& asset1Balance, - STAmount const& asset1Withdraw, - STAmount const& lptAMMBalance, - std::uint16_t tfee) -{ - Number const fr = asset1Withdraw / asset1Balance; - auto const f1 = getFee(tfee); - auto const c = fr * f1 + 2 - f1; - if (!isFeatureEnabled(fixAMMv1_3)) - { - auto const t = lptAMMBalance * (c - root2(c * c - 4 * fr)) / 2; - return toSTAmount(lptAMMBalance.issue(), t); - } - - // maximize tokens in - auto const frac = (c - root2(c * c - 4 * fr)) / 2; - return multiply(lptAMMBalance, frac, Number::upward); -} - -/* Equation 8 solves equation 7 for b: - * c - 2*t/T = sqrt(c**2 - 4*R) => - * c**2 - 4*c*t/T + 4*t**2/T**2 = c**2 - 4*R => - * -4*c*t/T + 4*t**2/T**2 = -4*R => - * -c*t/T + t**2/T**2 = -R -=> - * substitute c = R*f + 2 - f => - * -(t/T)*(R*f + 2 - f) + (t/T)**2 = -R, let t1 = t/T => - * -t1*R*f -2*t1 +t1*f +t1**2 = -R => - * R = (t1**2 + t1*(f - 2)) / (t1*f - 1) - */ -STAmount -ammAssetOut( - STAmount const& assetBalance, - STAmount const& lptAMMBalance, - STAmount const& lpTokens, - std::uint16_t tfee) -{ - auto const f = getFee(tfee); - Number const t1 = lpTokens / lptAMMBalance; - if (!isFeatureEnabled(fixAMMv1_3)) - { - auto const b = assetBalance * (t1 * t1 - t1 * (2 - f)) / (t1 * f - 1); - return toSTAmount(assetBalance.issue(), b); - } - - // minimize withdraw - auto const frac = (t1 * t1 - t1 * (2 - f)) / (t1 * f - 1); - return multiply(assetBalance, frac, Number::downward); -} - -Number -square(Number const& n) -{ - return n * n; -} - -STAmount -adjustLPTokens(STAmount const& lptAMMBalance, STAmount const& lpTokens, IsDeposit isDeposit) -{ - // Force rounding downward to ensure adjusted tokens are less or equal - // to requested tokens. - saveNumberRoundMode const rm(Number::setround(Number::rounding_mode::downward)); - if (isDeposit == IsDeposit::Yes) - return (lptAMMBalance + lpTokens) - lptAMMBalance; - return (lpTokens - lptAMMBalance) + lptAMMBalance; -} - -std::tuple, STAmount> -adjustAmountsByLPTokens( - STAmount const& amountBalance, - STAmount const& amount, - std::optional const& amount2, - STAmount const& lptAMMBalance, - STAmount const& lpTokens, - std::uint16_t tfee, - IsDeposit isDeposit) -{ - // AMMv1_3 amendment adjusts tokens and amounts in deposit/withdraw - if (isFeatureEnabled(fixAMMv1_3)) - return std::make_tuple(amount, amount2, lpTokens); - - auto const lpTokensActual = adjustLPTokens(lptAMMBalance, lpTokens, isDeposit); - - if (lpTokensActual == beast::zero) - { - auto const amount2Opt = amount2 ? std::make_optional(STAmount{}) : std::nullopt; - return std::make_tuple(STAmount{}, amount2Opt, lpTokensActual); - } - - if (lpTokensActual < lpTokens) - { - bool const ammRoundingEnabled = [&]() { - if (auto const& rules = getCurrentTransactionRules(); - rules && rules->enabled(fixAMMv1_1)) - return true; - return false; - }(); - - // Equal trade - if (amount2) - { - Number const fr = lpTokensActual / lpTokens; - auto const amountActual = toSTAmount(amount.issue(), fr * amount); - auto const amount2Actual = toSTAmount(amount2->issue(), fr * *amount2); - if (!ammRoundingEnabled) - { - return std::make_tuple( - amountActual < amount ? amountActual : amount, - amount2Actual < amount2 ? amount2Actual : amount2, - lpTokensActual); - } - - return std::make_tuple(amountActual, amount2Actual, lpTokensActual); - } - - // Single trade - auto const amountActual = [&]() { - if (isDeposit == IsDeposit::Yes) - { - return ammAssetIn(amountBalance, lptAMMBalance, lpTokensActual, tfee); - } - if (!ammRoundingEnabled) - { - return ammAssetOut(amountBalance, lptAMMBalance, lpTokens, tfee); - } - - return ammAssetOut(amountBalance, lptAMMBalance, lpTokensActual, tfee); - }(); - if (!ammRoundingEnabled) - { - return amountActual < amount - ? std::make_tuple(amountActual, std::nullopt, lpTokensActual) - : std::make_tuple(amount, std::nullopt, lpTokensActual); - } - - return std::make_tuple(amountActual, std::nullopt, lpTokensActual); - } - - XRPL_ASSERT( - lpTokensActual == lpTokens, "xrpl::adjustAmountsByLPTokens : LP tokens match actual"); - - return {amount, amount2, lpTokensActual}; -} - -Number -solveQuadraticEq(Number const& a, Number const& b, Number const& c) -{ - return (-b + root2(b * b - 4 * a * c)) / (2 * a); -} - -// Minimize takerGets or takerPays -std::optional -solveQuadraticEqSmallest(Number const& a, Number const& b, Number const& c) -{ - auto const d = b * b - 4 * a * c; - if (d < 0) - return std::nullopt; - // use numerically stable citardauq formula for quadratic equation solution - // https://people.csail.mit.edu/bkph/articles/Quadratics.pdf - if (b > 0) - { - return (2 * c) / (-b - root2(d)); - } - - return (2 * c) / (-b + root2(d)); -} - -STAmount -multiply(STAmount const& amount, Number const& frac, Number::rounding_mode rm) -{ - NumberRoundModeGuard const g(rm); - auto const t = amount * frac; - return toSTAmount(amount.issue(), t, rm); -} - -STAmount -getRoundedAsset( - Rules const& rules, - std::function const& noRoundCb, - STAmount const& balance, - std::function const& productCb, - IsDeposit isDeposit) -{ - if (!rules.enabled(fixAMMv1_3)) - return toSTAmount(balance.issue(), noRoundCb()); - - auto const rm = detail::getAssetRounding(isDeposit); - if (isDeposit == IsDeposit::Yes) - return multiply(balance, productCb(), rm); - NumberRoundModeGuard const g(rm); - return toSTAmount(balance.issue(), productCb(), rm); -} - -STAmount -getRoundedLPTokens( - Rules const& rules, - STAmount const& balance, - Number const& frac, - IsDeposit isDeposit) -{ - if (!rules.enabled(fixAMMv1_3)) - return toSTAmount(balance.issue(), balance * frac); - - auto const rm = detail::getLPTokenRounding(isDeposit); - auto const tokens = multiply(balance, frac, rm); - return adjustLPTokens(balance, tokens, isDeposit); -} - -STAmount -getRoundedLPTokens( - Rules const& rules, - std::function const& noRoundCb, - STAmount const& lptAMMBalance, - std::function const& productCb, - IsDeposit isDeposit) -{ - if (!rules.enabled(fixAMMv1_3)) - return toSTAmount(lptAMMBalance.issue(), noRoundCb()); - - auto const tokens = [&] { - auto const rm = detail::getLPTokenRounding(isDeposit); - if (isDeposit == IsDeposit::Yes) - { - NumberRoundModeGuard const g(rm); - return toSTAmount(lptAMMBalance.issue(), productCb(), rm); - } - return multiply(lptAMMBalance, productCb(), rm); - }(); - return adjustLPTokens(lptAMMBalance, tokens, isDeposit); -} - -std::pair -adjustAssetInByTokens( - Rules const& rules, - STAmount const& balance, - STAmount const& amount, - STAmount const& lptAMMBalance, - STAmount const& tokens, - std::uint16_t tfee) -{ - if (!rules.enabled(fixAMMv1_3)) - return {tokens, amount}; - auto assetAdj = ammAssetIn(balance, lptAMMBalance, tokens, tfee); - auto tokensAdj = tokens; - // Rounding didn't work the right way. - // Try to adjust the original deposit amount by difference - // in adjust and original amount. Then adjust tokens and deposit amount. - if (assetAdj > amount) - { - auto const adjAmount = amount - (assetAdj - amount); - auto const t = lpTokensOut(balance, adjAmount, lptAMMBalance, tfee); - tokensAdj = adjustLPTokens(lptAMMBalance, t, IsDeposit::Yes); - assetAdj = ammAssetIn(balance, lptAMMBalance, tokensAdj, tfee); - } - return {tokensAdj, std::min(amount, assetAdj)}; -} - -std::pair -adjustAssetOutByTokens( - Rules const& rules, - STAmount const& balance, - STAmount const& amount, - STAmount const& lptAMMBalance, - STAmount const& tokens, - std::uint16_t tfee) -{ - if (!rules.enabled(fixAMMv1_3)) - return {tokens, amount}; - auto assetAdj = ammAssetOut(balance, lptAMMBalance, tokens, tfee); - auto tokensAdj = tokens; - // Rounding didn't work the right way. - // Try to adjust the original deposit amount by difference - // in adjust and original amount. Then adjust tokens and deposit amount. - if (assetAdj > amount) - { - auto const adjAmount = amount - (assetAdj - amount); - auto const t = lpTokensIn(balance, adjAmount, lptAMMBalance, tfee); - tokensAdj = adjustLPTokens(lptAMMBalance, t, IsDeposit::No); - assetAdj = ammAssetOut(balance, lptAMMBalance, tokensAdj, tfee); - } - return {tokensAdj, std::min(amount, assetAdj)}; -} - -Number -adjustFracByTokens( - Rules const& rules, - STAmount const& lptAMMBalance, - STAmount const& tokens, - Number const& frac) -{ - if (!rules.enabled(fixAMMv1_3)) - return frac; - return tokens / lptAMMBalance; -} - -} // namespace xrpl diff --git a/src/libxrpl/tx/transactors/dex/AMMUtils.cpp b/src/libxrpl/tx/transactors/dex/AMMUtils.cpp deleted file mode 100644 index 91891ce86f..0000000000 --- a/src/libxrpl/tx/transactors/dex/AMMUtils.cpp +++ /dev/null @@ -1,462 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include - -namespace xrpl { - -std::pair -ammPoolHolds( - ReadView const& view, - AccountID const& ammAccountID, - Issue const& issue1, - Issue const& issue2, - FreezeHandling freezeHandling, - beast::Journal const j) -{ - auto const assetInBalance = accountHolds(view, ammAccountID, issue1, freezeHandling, j); - auto const assetOutBalance = accountHolds(view, ammAccountID, issue2, freezeHandling, j); - return std::make_pair(assetInBalance, assetOutBalance); -} - -Expected, TER> -ammHolds( - ReadView const& view, - SLE const& ammSle, - std::optional const& optIssue1, - std::optional const& optIssue2, - FreezeHandling freezeHandling, - beast::Journal const j) -{ - auto const issues = [&]() -> std::optional> { - auto const issue1 = ammSle[sfAsset].get(); - auto const issue2 = ammSle[sfAsset2].get(); - if (optIssue1 && optIssue2) - { - if (invalidAMMAssetPair( - *optIssue1, *optIssue2, std::make_optional(std::make_pair(issue1, issue2)))) - { - // This error can only be hit if the AMM is corrupted - // LCOV_EXCL_START - JLOG(j.debug()) << "ammHolds: Invalid optIssue1 or optIssue2 " << *optIssue1 << " " - << *optIssue2; - return std::nullopt; - // LCOV_EXCL_STOP - } - return std::make_optional(std::make_pair(*optIssue1, *optIssue2)); - } - auto const singleIssue = [&issue1, &issue2, &j]( - Issue checkIssue, - char const* label) -> std::optional> { - if (checkIssue == issue1) - { - return std::make_optional(std::make_pair(issue1, issue2)); - } - if (checkIssue == issue2) - { - return std::make_optional(std::make_pair(issue2, issue1)); - } - // Unreachable unless AMM corrupted. - // LCOV_EXCL_START - JLOG(j.debug()) << "ammHolds: Invalid " << label << " " << checkIssue; - return std::nullopt; - // LCOV_EXCL_STOP - }; - if (optIssue1) - { - return singleIssue(*optIssue1, "optIssue1"); - } - if (optIssue2) - { - // Cannot have Amount2 without Amount. - return singleIssue(*optIssue2, "optIssue2"); // LCOV_EXCL_LINE - } - return std::make_optional(std::make_pair(issue1, issue2)); - }(); - if (!issues) - return Unexpected(tecAMM_INVALID_TOKENS); - auto const [asset1, asset2] = ammPoolHolds( - view, ammSle.getAccountID(sfAccount), issues->first, issues->second, freezeHandling, j); - return std::make_tuple(asset1, asset2, ammSle[sfLPTokenBalance]); -} - -STAmount -ammLPHolds( - ReadView const& view, - Currency const& cur1, - Currency const& cur2, - AccountID const& ammAccount, - AccountID const& lpAccount, - beast::Journal const j) -{ - // This function looks similar to `accountHolds`. However, it only checks if - // a LPToken holder has enough balance. On the other hand, `accountHolds` - // checks if the underlying assets of LPToken are frozen with the - // fixFrozenLPTokenTransfer amendment - - auto const currency = ammLPTCurrency(cur1, cur2); - STAmount amount; - - auto const sle = view.read(keylet::line(lpAccount, ammAccount, currency)); - if (!sle) - { - amount.clear(Issue{currency, ammAccount}); - JLOG(j.trace()) << "ammLPHolds: no SLE " - << " lpAccount=" << to_string(lpAccount) - << " amount=" << amount.getFullText(); - } - else if (isFrozen(view, lpAccount, currency, ammAccount)) - { - amount.clear(Issue{currency, ammAccount}); - JLOG(j.trace()) << "ammLPHolds: frozen currency " - << " lpAccount=" << to_string(lpAccount) - << " amount=" << amount.getFullText(); - } - else - { - amount = sle->getFieldAmount(sfBalance); - if (lpAccount > ammAccount) - { - // Put balance in account terms. - amount.negate(); - } - amount.setIssuer(ammAccount); - - JLOG(j.trace()) << "ammLPHolds:" - << " lpAccount=" << to_string(lpAccount) - << " amount=" << amount.getFullText(); - } - - return view.balanceHook(lpAccount, ammAccount, amount); -} - -STAmount -ammLPHolds( - ReadView const& view, - SLE const& ammSle, - AccountID const& lpAccount, - beast::Journal const j) -{ - return ammLPHolds( - view, - ammSle[sfAsset].get().currency, - ammSle[sfAsset2].get().currency, - ammSle[sfAccount], - lpAccount, - j); -} - -std::uint16_t -getTradingFee(ReadView const& view, SLE const& ammSle, AccountID const& account) -{ - using namespace std::chrono; - XRPL_ASSERT( - !view.rules().enabled(fixInnerObjTemplate) || ammSle.isFieldPresent(sfAuctionSlot), - "xrpl::getTradingFee : auction present"); - if (ammSle.isFieldPresent(sfAuctionSlot)) - { - auto const& auctionSlot = safe_downcast(ammSle.peekAtField(sfAuctionSlot)); - // Not expired - if (auto const expiration = auctionSlot[~sfExpiration]; - duration_cast(view.header().parentCloseTime.time_since_epoch()).count() < - expiration) - { - if (auctionSlot[~sfAccount] == account) - return auctionSlot[sfDiscountedFee]; - if (auctionSlot.isFieldPresent(sfAuthAccounts)) - { - for (auto const& acct : auctionSlot.getFieldArray(sfAuthAccounts)) - { - if (acct[~sfAccount] == account) - return auctionSlot[sfDiscountedFee]; - } - } - } - } - return ammSle[sfTradingFee]; -} - -STAmount -ammAccountHolds(ReadView const& view, AccountID const& ammAccountID, Issue const& issue) -{ - if (isXRP(issue)) - { - 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)) - { - auto amount = (*sle)[sfBalance]; - if (ammAccountID > issue.account) - amount.negate(); - amount.setIssuer(issue.account); - return amount; - } - - return STAmount{issue}; -} - -static TER -deleteAMMTrustLines( - Sandbox& sb, - AccountID const& ammAccountID, - std::uint16_t maxTrustlinesToDelete, - beast::Journal j) -{ - return cleanupOnAccountDelete( - sb, - keylet::ownerDir(ammAccountID), - [&](LedgerEntryType nodeType, - uint256 const&, - std::shared_ptr& sleItem) -> std::pair { - // Skip AMM - if (nodeType == LedgerEntryType::ltAMM) - return {tesSUCCESS, SkipEntry::Yes}; - // Should only have the trustlines - if (nodeType != LedgerEntryType::ltRIPPLE_STATE) - { - // LCOV_EXCL_START - JLOG(j.error()) << "deleteAMMTrustLines: deleting non-trustline " << nodeType; - return {tecINTERNAL, SkipEntry::No}; - // LCOV_EXCL_STOP - } - - // Trustlines must have zero balance - if (sleItem->getFieldAmount(sfBalance) != beast::zero) - { - // LCOV_EXCL_START - JLOG(j.error()) << "deleteAMMTrustLines: deleting trustline with " - "non-zero balance."; - return {tecINTERNAL, SkipEntry::No}; - // LCOV_EXCL_STOP - } - - return {deleteAMMTrustLine(sb, sleItem, ammAccountID, j), SkipEntry::No}; - }, - j, - maxTrustlinesToDelete); -} - -TER -deleteAMMAccount(Sandbox& sb, Issue const& asset, Issue const& asset2, beast::Journal j) -{ - auto ammSle = sb.peek(keylet::amm(asset, asset2)); - if (!ammSle) - { - // LCOV_EXCL_START - JLOG(j.error()) << "deleteAMMAccount: AMM object does not exist " << asset << " " << asset2; - return tecINTERNAL; - // LCOV_EXCL_STOP - } - - auto const ammAccountID = (*ammSle)[sfAccount]; - auto sleAMMRoot = sb.peek(keylet::account(ammAccountID)); - if (!sleAMMRoot) - { - // LCOV_EXCL_START - JLOG(j.error()) << "deleteAMMAccount: AMM account does not exist " - << to_string(ammAccountID); - return tecINTERNAL; - // LCOV_EXCL_STOP - } - - if (auto const ter = deleteAMMTrustLines(sb, ammAccountID, maxDeletableAMMTrustLines, j); - !isTesSuccess(ter)) - return ter; - - auto const ownerDirKeylet = keylet::ownerDir(ammAccountID); - if (!sb.dirRemove(ownerDirKeylet, (*ammSle)[sfOwnerNode], ammSle->key(), false)) - { - // LCOV_EXCL_START - JLOG(j.error()) << "deleteAMMAccount: failed to remove dir link"; - return tecINTERNAL; - // LCOV_EXCL_STOP - } - if (sb.exists(ownerDirKeylet) && !sb.emptyDirDelete(ownerDirKeylet)) - { - // LCOV_EXCL_START - JLOG(j.error()) << "deleteAMMAccount: cannot delete root dir node of " - << toBase58(ammAccountID); - return tecINTERNAL; - // LCOV_EXCL_STOP - } - - sb.erase(ammSle); - sb.erase(sleAMMRoot); - - return tesSUCCESS; -} - -void -initializeFeeAuctionVote( - ApplyView& view, - std::shared_ptr& ammSle, - AccountID const& account, - Issue const& lptIssue, - std::uint16_t tfee) -{ - auto const& rules = view.rules(); - // AMM creator gets the voting slot. - STArray voteSlots; - STObject voteEntry = STObject::makeInnerObject(sfVoteEntry); - if (tfee != 0) - voteEntry.setFieldU16(sfTradingFee, tfee); - voteEntry.setFieldU32(sfVoteWeight, VOTE_WEIGHT_SCALE_FACTOR); - voteEntry.setAccountID(sfAccount, account); - voteSlots.push_back(voteEntry); - ammSle->setFieldArray(sfVoteSlots, voteSlots); - // AMM creator gets the auction slot for free. - // AuctionSlot is created on AMMCreate and updated on AMMDeposit - // when AMM is in an empty state - if (rules.enabled(fixInnerObjTemplate) && !ammSle->isFieldPresent(sfAuctionSlot)) - { - STObject auctionSlot = STObject::makeInnerObject(sfAuctionSlot); - ammSle->set(std::move(auctionSlot)); - } - STObject& auctionSlot = ammSle->peekFieldObject(sfAuctionSlot); - auctionSlot.setAccountID(sfAccount, account); - // current + sec in 24h - auto const expiration = std::chrono::duration_cast( - view.header().parentCloseTime.time_since_epoch()) - .count() + - TOTAL_TIME_SLOT_SECS; - auctionSlot.setFieldU32(sfExpiration, expiration); - auctionSlot.setFieldAmount(sfPrice, STAmount{lptIssue, 0}); - // Set the fee - if (tfee != 0) - { - ammSle->setFieldU16(sfTradingFee, tfee); - } - else if (ammSle->isFieldPresent(sfTradingFee)) - { - ammSle->makeFieldAbsent(sfTradingFee); // LCOV_EXCL_LINE - } - if (auto const dfee = tfee / AUCTION_SLOT_DISCOUNTED_FEE_FRACTION) - { - auctionSlot.setFieldU16(sfDiscountedFee, dfee); - } - else if (auctionSlot.isFieldPresent(sfDiscountedFee)) - { - auctionSlot.makeFieldAbsent(sfDiscountedFee); // LCOV_EXCL_LINE - } -} - -Expected -isOnlyLiquidityProvider(ReadView const& view, Issue const& ammIssue, AccountID const& lpAccount) -{ - // Liquidity Provider (LP) must have one LPToken trustline - std::uint8_t nLPTokenTrustLines = 0; - // There are at most two IOU trustlines. One or both could be to the LP - // if LP is the issuer, or a different account if LP is not an issuer. - // For instance, if AMM has two tokens USD and EUR and LP is not the issuer - // of the tokens then the trustlines are between AMM account and the issuer. - std::uint8_t nIOUTrustLines = 0; - // There is only one AMM object - bool hasAMM = false; - // AMM LP has at most three trustlines and only one AMM object must exist. - // If there are more than five objects then it's either an error or - // there are more than one LP. Ten pages should be sufficient to include - // five objects. - std::uint8_t limit = 10; - auto const root = keylet::ownerDir(ammIssue.account); - auto currentIndex = root; - - // Iterate over AMM owner directory objects. - while (limit-- >= 1) - { - auto const ownerDir = view.read(currentIndex); - if (!ownerDir) - return Unexpected(tecINTERNAL); // LCOV_EXCL_LINE - for (auto const& key : ownerDir->getFieldV256(sfIndexes)) - { - auto const sle = view.read(keylet::child(key)); - if (!sle) - return Unexpected(tecINTERNAL); // LCOV_EXCL_LINE - // Only one AMM object - if (sle->getFieldU16(sfLedgerEntryType) == ltAMM) - { - if (hasAMM) - return Unexpected(tecINTERNAL); // LCOV_EXCL_LINE - hasAMM = true; - continue; - } - if (sle->getFieldU16(sfLedgerEntryType) != ltRIPPLE_STATE) - return Unexpected(tecINTERNAL); // LCOV_EXCL_LINE - auto const lowLimit = sle->getFieldAmount(sfLowLimit); - auto const highLimit = sle->getFieldAmount(sfHighLimit); - auto const isLPTrustline = - lowLimit.getIssuer() == lpAccount || highLimit.getIssuer() == lpAccount; - auto const isLPTokenTrustline = - lowLimit.issue() == ammIssue || highLimit.issue() == ammIssue; - - // Liquidity Provider trustline - if (isLPTrustline) - { - // LPToken trustline - if (isLPTokenTrustline) - { - if (++nLPTokenTrustLines > 1) - return Unexpected(tecINTERNAL); // LCOV_EXCL_LINE - } - else if (++nIOUTrustLines > 2) - { - return Unexpected(tecINTERNAL); // LCOV_EXCL_LINE - } - } - // Another Liquidity Provider LPToken trustline - else if (isLPTokenTrustline) - { - return false; - } - else if (++nIOUTrustLines > 2) - { - return Unexpected(tecINTERNAL); // LCOV_EXCL_LINE - } - } - auto const uNodeNext = ownerDir->getFieldU64(sfIndexNext); - if (uNodeNext == 0) - { - if (nLPTokenTrustLines != 1 || nIOUTrustLines == 0 || nIOUTrustLines > 2) - return Unexpected(tecINTERNAL); // LCOV_EXCL_LINE - return true; - } - currentIndex = keylet::page(root, uNodeNext); - } - return Unexpected(tecINTERNAL); // LCOV_EXCL_LINE -} - -Expected -verifyAndAdjustLPTokenBalance( - Sandbox& sb, - STAmount const& lpTokens, - std::shared_ptr& ammSle, - AccountID const& account) -{ - auto const res = isOnlyLiquidityProvider(sb, lpTokens.issue(), account); - if (!res.has_value()) - { - return Unexpected(res.error()); - } - - if (res.value()) - { - if (withinRelativeDistance( - lpTokens, ammSle->getFieldAmount(sfLPTokenBalance), Number{1, -3})) - { - ammSle->setFieldAmount(sfLPTokenBalance, lpTokens); - sb.update(ammSle); - } - else - { - return Unexpected(tecAMM_INVALID_TOKENS); - } - } - return true; -} - -} // namespace xrpl diff --git a/src/libxrpl/tx/transactors/dex/AMMVote.cpp b/src/libxrpl/tx/transactors/dex/AMMVote.cpp index 2096eca0f0..1287c8c428 100644 --- a/src/libxrpl/tx/transactors/dex/AMMVote.cpp +++ b/src/libxrpl/tx/transactors/dex/AMMVote.cpp @@ -1,23 +1,47 @@ -#include -#include -#include -#include -#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + namespace xrpl { bool AMMVote::checkExtraFeatures(PreflightContext const& ctx) { - return ammEnabled(ctx.rules); + if (!ammEnabled(ctx.rules)) + return false; + + return ctx.rules.enabled(featureMPTokensV2) || + (!ctx.tx[sfAsset].holds() && !ctx.tx[sfAsset2].holds()); } NotTEC AMMVote::preflight(PreflightContext const& ctx) { - if (auto const res = - invalidAMMAssetPair(ctx.tx[sfAsset].get(), ctx.tx[sfAsset2].get())) + if (auto const res = invalidAMMAssetPair(ctx.tx[sfAsset], ctx.tx[sfAsset2])) { JLOG(ctx.j.debug()) << "AMM Vote: invalid asset pair."; return res; @@ -221,4 +245,18 @@ AMMVote::doApply() return result.first; } +void +AMMVote::visitInvariantEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ +} + +bool +AMMVote::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&) +{ + return true; +} + } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/dex/AMMWithdraw.cpp b/src/libxrpl/tx/transactors/dex/AMMWithdraw.cpp index 7c87d53ea0..6ab78f9cb9 100644 --- a/src/libxrpl/tx/transactors/dex/AMMWithdraw.cpp +++ b/src/libxrpl/tx/transactors/dex/AMMWithdraw.cpp @@ -1,17 +1,58 @@ -#include -#include -#include -#include -#include -#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + namespace xrpl { bool AMMWithdraw::checkExtraFeatures(PreflightContext const& ctx) { - return ammEnabled(ctx.rules); + if (!ammEnabled(ctx.rules)) + return false; + + auto const amount = ctx.tx[~sfAmount]; + auto const amount2 = ctx.tx[~sfAmount2]; + + return ctx.rules.enabled(featureMPTokensV2) || + (!ctx.tx[sfAsset].holds() && !ctx.tx[sfAsset2].holds() && + !(amount && amount->holds()) && !(amount2 && amount2->holds())); } std::uint32_t @@ -78,18 +119,18 @@ AMMWithdraw::preflight(PreflightContext const& ctx) return temMALFORMED; } - auto const asset = ctx.tx[sfAsset].get(); - auto const asset2 = ctx.tx[sfAsset2].get(); + auto const asset = ctx.tx[sfAsset]; + auto const asset2 = ctx.tx[sfAsset2]; if (auto const res = invalidAMMAssetPair(asset, asset2)) { JLOG(ctx.j.debug()) << "AMM Withdraw: Invalid asset pair."; return res; } - if (amount && amount2 && amount->issue() == amount2->issue()) + if (amount && amount2 && amount->asset() == amount2->asset()) { - JLOG(ctx.j.debug()) << "AMM Withdraw: invalid tokens, same issue." << amount->issue() << " " - << amount2->issue(); + JLOG(ctx.j.debug()) << "AMM Withdraw: invalid tokens, same issue." << amount->asset() << " " + << amount2->asset(); return temBAD_AMM_TOKENS; } @@ -162,9 +203,10 @@ AMMWithdraw::preclaim(PreclaimContext const& ctx) auto const expected = ammHolds( ctx.view, *ammSle, - amount ? amount->issue() : std::optional{}, - amount2 ? amount2->issue() : std::optional{}, + amount ? amount->asset() : std::optional{}, + amount2 ? amount2->asset() : std::optional{}, FreezeHandling::fhIGNORE_FREEZE, + AuthHandling::ahIGNORE_AUTH, ctx.j); if (!expected) return expected.error(); @@ -191,26 +233,33 @@ AMMWithdraw::preclaim(PreclaimContext const& ctx) << "AMM Withdraw: withdrawing more than the balance, " << *amount; return tecAMM_BALANCE; } - if (auto const ter = requireAuth(ctx.view, amount->issue(), accountID)) + // WeakAuth - MPToken is created if it doesn't exist. + if (auto const ter = + requireAuth(ctx.view, amount->asset(), accountID, AuthType::WeakAuth)) { JLOG(ctx.j.debug()) - << "AMM Withdraw: account is not authorized, " << amount->issue(); + << "AMM Withdraw: account is not authorized, " << amount->asset(); return ter; } // AMM account or currency frozen - if (isFrozen(ctx.view, ammAccountID, amount->issue())) + if (isFrozen(ctx.view, ammAccountID, amount->asset())) { JLOG(ctx.j.debug()) << "AMM Withdraw: AMM account or currency is frozen, " << to_string(accountID); return tecFROZEN; } // Account frozen - if (isIndividualFrozen(ctx.view, accountID, amount->issue())) + if (isIndividualFrozen(ctx.view, accountID, amount->asset())) { JLOG(ctx.j.debug()) << "AMM Withdraw: account is frozen, " << to_string(accountID) - << " " << to_string(amount->issue().currency); + << " " << to_string(amount->asset()); return tecFROZEN; } + + if (auto const ter = + checkMPTTxAllowed(ctx.view, ttAMM_WITHDRAW, amount->asset(), accountID); + !isTesSuccess(ter)) + return ter; } return tesSUCCESS; }; @@ -230,7 +279,7 @@ AMMWithdraw::preclaim(PreclaimContext const& ctx) return tecAMM_BALANCE; } - if (lpTokensWithdraw && lpTokensWithdraw->issue() != lpTokens.issue()) + if (lpTokensWithdraw && lpTokensWithdraw->asset() != lpTokens.asset()) { JLOG(ctx.j.debug()) << "AMM Withdraw: invalid LPTokens."; return temBAD_AMM_TOKENS; @@ -242,7 +291,7 @@ AMMWithdraw::preclaim(PreclaimContext const& ctx) return tecAMM_INVALID_TOKENS; } - if (auto const ePrice = ctx.tx[~sfEPrice]; ePrice && ePrice->issue() != lpTokens.issue()) + if (auto const ePrice = ctx.tx[~sfEPrice]; ePrice && ePrice->asset() != lpTokens.asset()) { JLOG(ctx.j.debug()) << "AMM Withdraw: invalid EPrice."; return temBAD_AMM_TOKENS; @@ -289,9 +338,10 @@ AMMWithdraw::applyGuts(Sandbox& sb) auto const expected = ammHolds( sb, *ammSle, - amount ? amount->issue() : std::optional{}, - amount2 ? amount2->issue() : std::optional{}, + amount ? amount->asset() : std::optional{}, + amount2 ? amount2->asset() : std::optional{}, FreezeHandling::fhZERO_IF_FROZEN, + AuthHandling::ahZERO_IF_UNAUTHORIZED, ctx_.journal); if (!expected) return {expected.error(), false}; @@ -363,12 +413,7 @@ AMMWithdraw::applyGuts(Sandbox& sb) return {result, false}; auto const res = deleteAMMAccountIfEmpty( - sb, - ammSle, - newLPTokenBalance, - ctx_.tx[sfAsset].get(), - ctx_.tx[sfAsset2].get(), - j_); + sb, ammSle, newLPTokenBalance, ctx_.tx[sfAsset], ctx_.tx[sfAsset2], j_); // LCOV_EXCL_START if (!res.second) return {res.first, false}; @@ -421,6 +466,7 @@ AMMWithdraw::withdraw( lpTokensWithdraw, tfee, FreezeHandling::fhZERO_IF_FROZEN, + AuthHandling::ahZERO_IF_UNAUTHORIZED, isWithdrawAll(ctx_.tx), preFeeBalance_, j_); @@ -440,13 +486,14 @@ AMMWithdraw::withdraw( STAmount const& lpTokensWithdraw, std::uint16_t tfee, FreezeHandling freezeHandling, + AuthHandling authHandling, WithdrawAll withdrawAll, XRPAmount const& priorBalance, beast::Journal const& journal) { auto const lpTokens = ammLPHolds(view, ammSle, account, journal); - auto const expected = - ammHolds(view, ammSle, amountWithdraw.issue(), std::nullopt, freezeHandling, journal); + auto const expected = ammHolds( + view, ammSle, amountWithdraw.asset(), std::nullopt, freezeHandling, authHandling, journal); // LCOV_EXCL_START if (!expected) return {expected.error(), STAmount{}, STAmount{}, STAmount{}}; @@ -525,33 +572,102 @@ AMMWithdraw::withdraw( return {tecAMM_BALANCE, STAmount{}, STAmount{}, STAmount{}}; } - // Check the reserve in case a trustline has to be created - bool const enabledFixAMMv1_2 = view.rules().enabled(fixAMMv1_2); - auto sufficientReserve = [&](Issue const& issue) -> TER { - if (!enabledFixAMMv1_2 || isXRP(issue)) - return tesSUCCESS; - if (!view.exists(keylet::line(account, issue))) + // Updated pool state must be valid - either all balances are zero + // or all balances are non-zero. + if (view.rules().enabled(featureMPTokensV2)) + { + bool const newBalanceZero = (curBalance - amountWithdrawActual) == beast::zero; + bool const newBalance2Zero = + (curBalance2 - amount2WithdrawActual.value_or(curBalance2.asset())) == beast::zero; + bool const newLPTokensZero = (lpTokensAMMBalance - lpTokensWithdrawActual) == beast::zero; + // newBalance2Zero can be zero if that side of the pool is frozen. + // ignore newBalance2Zero if one-sided withdrawal. + bool const valid = [&]() { + if (!amount2WithdrawActual) + return newBalanceZero == newLPTokensZero; + return newBalanceZero == newBalance2Zero && newBalance2Zero == newLPTokensZero; + }(); + if (!valid) { - auto const sleAccount = view.read(keylet::account(account)); + JLOG(journal.debug()) << "AMM Withdraw: some balances are zero" + << " curBalance: " << curBalance << " " << amountWithdrawActual + << " curBalance2: " << curBalance2 << " " + << (amount2WithdrawActual ? *amount2WithdrawActual : STAmount{}) + << " lpTokensBalance: " << lpTokensWithdraw << " lptBalance " + << lpTokensAMMBalance; + return {tecAMM_BALANCE, STAmount{}, STAmount{}, STAmount{}}; + } + } + + // Check the reserve in case a trustline or MPT has to be created + bool const enabledFixAMMv1_2 = view.rules().enabled(fixAMMv1_2); + // If seated after a call to sufficientReserve() then MPToken must be + // authorized + std::optional mptokenKey; + auto sufficientReserve = [&](Asset const& asset) -> TER { + mptokenKey = std::nullopt; + if (!enabledFixAMMv1_2 || isXRP(asset)) + return tesSUCCESS; + bool const isIssue = asset.holds(); + bool const assetNotExists = [&] { + if (isIssue) + return !view.exists(keylet::line(account, asset.get())); + auto const issuanceKey = keylet::mptIssuance(asset.get()); + mptokenKey = keylet::mptoken(issuanceKey.key, account); + if (!view.exists(*mptokenKey)) + return true; + mptokenKey = std::nullopt; + return false; + }(); + if (assetNotExists) + { + auto sleAccount = view.peek(keylet::account(account)); if (!sleAccount) return tecINTERNAL; // LCOV_EXCL_LINE - auto const balance = (*sleAccount)[sfBalance].xrp(); + STAmount const balance = (*sleAccount)[sfBalance]; std::uint32_t const ownerCount = sleAccount->at(sfOwnerCount); - // See also TrustSet::doApply() + // See also TrustSet::doApply() and MPTokenAuthorize::authorize() XRPAmount const reserve( (ownerCount < 2) ? XRPAmount(beast::zero) : view.fees().accountReserve(ownerCount + 1)); - if (std::max(priorBalance, balance) < reserve) + auto const balance_ = isIssue ? std::max(priorBalance, balance.xrp()) : priorBalance; + if (balance_ < reserve) return tecINSUFFICIENT_RESERVE; + + // Update owner count. + if (!isIssue) + adjustOwnerCount(view, sleAccount, 1, journal); } return tesSUCCESS; }; - if (auto const err = sufficientReserve(amountWithdrawActual.issue())) + // Create MPToken if it doesn't exist + auto createMPToken = [&](Asset const& asset) -> TER { + // If mptoken is seated then must authorize + if (mptokenKey && account != asset.getIssuer()) + { + auto const& mptIssue = asset.get(); + if (auto const err = requireAuth(view, mptIssue, account, AuthType::WeakAuth); + !isTesSuccess(err)) + return err; + + if (auto const err = checkCreateMPT(view, mptIssue, account, journal); + !isTesSuccess(err)) + { + return err; + } + } + return tesSUCCESS; + }; + + if (auto const err = sufficientReserve(amountWithdrawActual.asset())) return {err, STAmount{}, STAmount{}, STAmount{}}; + if (auto const res = createMPToken(amountWithdrawActual.asset()); !isTesSuccess(res)) + return {res, STAmount{}, STAmount{}, STAmount{}}; + // Withdraw amountWithdraw auto res = accountSend( view, ammAccount, account, amountWithdrawActual, journal, WaiveTransferFee::Yes); @@ -566,9 +682,12 @@ AMMWithdraw::withdraw( // Withdraw amount2Withdraw if (amount2WithdrawActual) { - if (auto const err = sufficientReserve(amount2WithdrawActual->issue()); !isTesSuccess(err)) + if (auto const err = sufficientReserve(amount2WithdrawActual->asset()); !isTesSuccess(err)) return {err, STAmount{}, STAmount{}, STAmount{}}; + if (auto const res = createMPToken(amount2WithdrawActual->asset()); !isTesSuccess(res)) + return {res, STAmount{}, STAmount{}, STAmount{}}; + res = accountSend( view, ammAccount, account, *amount2WithdrawActual, journal, WaiveTransferFee::Yes); if (!isTesSuccess(res)) @@ -581,7 +700,8 @@ AMMWithdraw::withdraw( } // Withdraw LP tokens - res = redeemIOU(view, account, lpTokensWithdrawActual, lpTokensWithdrawActual.issue(), journal); + res = redeemIOU( + view, account, lpTokensWithdrawActual, lpTokensWithdrawActual.get(), journal); if (!isTesSuccess(res)) { // LCOV_EXCL_START @@ -637,6 +757,7 @@ AMMWithdraw::equalWithdrawTokens( lpTokensWithdraw, tfee, FreezeHandling::fhZERO_IF_FROZEN, + AuthHandling::ahZERO_IF_UNAUTHORIZED, isWithdrawAll(ctx_.tx), preFeeBalance_, ctx_.journal); @@ -648,15 +769,15 @@ AMMWithdraw::deleteAMMAccountIfEmpty( Sandbox& sb, std::shared_ptr const ammSle, STAmount const& lpTokenBalance, - Issue const& issue1, - Issue const& issue2, + Asset const& asset1, + Asset const& asset2, beast::Journal const& journal) { TER ter; bool updateBalance = true; if (lpTokenBalance == beast::zero) { - ter = deleteAMMAccount(sb, issue1, issue2, journal); + ter = deleteAMMAccount(sb, asset1, asset2, journal); if (!isTesSuccess(ter) && ter != tecINCOMPLETE) return {ter, false}; // LCOV_EXCL_LINE @@ -687,6 +808,7 @@ AMMWithdraw::equalWithdrawTokens( STAmount const& lpTokensWithdraw, std::uint16_t tfee, FreezeHandling freezeHandling, + AuthHandling authHandling, WithdrawAll withdrawAll, XRPAmount const& priorBalance, beast::Journal const& journal) @@ -708,6 +830,7 @@ AMMWithdraw::equalWithdrawTokens( lpTokensWithdraw, tfee, freezeHandling, + authHandling, WithdrawAll::Yes, priorBalance, journal); @@ -742,6 +865,7 @@ AMMWithdraw::equalWithdrawTokens( tokensAdj, tfee, freezeHandling, + authHandling, withdrawAll, priorBalance, journal); @@ -793,13 +917,12 @@ AMMWithdraw::equalWithdrawLimit( std::uint16_t tfee) { auto frac = Number{amount} / amountBalance; - auto amount2Withdraw = getRoundedAsset(view.rules(), amount2Balance, frac, IsDeposit::No); auto tokensAdj = getRoundedLPTokens(view.rules(), lptAMMBalance, frac, IsDeposit::No); if (view.rules().enabled(fixAMMv1_3) && tokensAdj == beast::zero) return {tecAMM_INVALID_TOKENS, STAmount{}}; // factor in the adjusted tokens frac = adjustFracByTokens(view.rules(), lptAMMBalance, tokensAdj, frac); - amount2Withdraw = getRoundedAsset(view.rules(), amount2Balance, frac, IsDeposit::No); + auto const amount2Withdraw = getRoundedAsset(view.rules(), amount2Balance, frac, IsDeposit::No); if (amount2Withdraw <= amount2) { return withdraw( @@ -1022,4 +1145,18 @@ AMMWithdraw::isWithdrawAll(STTx const& tx) return WithdrawAll::Yes; return WithdrawAll::No; } +void +AMMWithdraw::visitInvariantEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ +} + +bool +AMMWithdraw::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&) +{ + return true; +} + } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/dex/OfferCancel.cpp b/src/libxrpl/tx/transactors/dex/OfferCancel.cpp index f8164401b7..72682149a3 100644 --- a/src/libxrpl/tx/transactors/dex/OfferCancel.cpp +++ b/src/libxrpl/tx/transactors/dex/OfferCancel.cpp @@ -1,8 +1,17 @@ -#include -#include -#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + namespace xrpl { NotTEC @@ -60,4 +69,18 @@ OfferCancel::doApply() return tesSUCCESS; } +void +OfferCancel::visitInvariantEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ +} + +bool +OfferCancel::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&) +{ + return true; +} + } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/dex/OfferCreate.cpp b/src/libxrpl/tx/transactors/dex/OfferCreate.cpp index 20524f73ae..2d21cebbe3 100644 --- a/src/libxrpl/tx/transactors/dex/OfferCreate.cpp +++ b/src/libxrpl/tx/transactors/dex/OfferCreate.cpp @@ -1,19 +1,55 @@ +#include + +#include #include -#include +#include +#include +#include +#include #include #include +#include #include #include +#include #include -#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include #include #include -#include +#include +#include +#include +#include #include -#include -#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include namespace xrpl { TxConsequences @@ -30,7 +66,11 @@ OfferCreate::makeTxConsequences(PreflightContext const& ctx) bool OfferCreate::checkExtraFeatures(PreflightContext const& ctx) { - return !ctx.tx.isFieldPresent(sfDomainID) || ctx.rules.enabled(featurePermissionedDEX); + if (ctx.tx.isFieldPresent(sfDomainID) && !ctx.rules.enabled(featurePermissionedDEX)) + return false; + + return ctx.rules.enabled(featureMPTokensV2) || + (!ctx.tx[sfTakerPays].holds() && !ctx.tx[sfTakerGets].holds()); } std::uint32_t @@ -97,18 +137,18 @@ OfferCreate::preflight(PreflightContext const& ctx) } auto const& uPaysIssuerID = saTakerPays.getIssuer(); - auto const& uPaysCurrency = saTakerPays.getCurrency(); + auto const& uPaysAsset = saTakerPays.asset(); auto const& uGetsIssuerID = saTakerGets.getIssuer(); - auto const& uGetsCurrency = saTakerGets.getCurrency(); + auto const& uGetsAsset = saTakerGets.asset(); - if (uPaysCurrency == uGetsCurrency && uPaysIssuerID == uGetsIssuerID) + if (uPaysAsset == uGetsAsset) { JLOG(j.debug()) << "Malformed offer: redundant (IOU for IOU)"; return temREDUNDANT; } // We don't allow a non-native currency to use the currency code XRP. - if (badCurrency() == uPaysCurrency || badCurrency() == uGetsCurrency) + if (badAsset() == uPaysAsset || badAsset() == uGetsAsset) { JLOG(j.debug()) << "Malformed offer: bad currency"; return temBAD_CURRENCY; @@ -131,10 +171,7 @@ OfferCreate::preclaim(PreclaimContext const& ctx) auto saTakerPays = ctx.tx[sfTakerPays]; auto saTakerGets = ctx.tx[sfTakerGets]; - auto const& uPaysIssuerID = saTakerPays.getIssuer(); - auto const& uPaysCurrency = saTakerPays.getCurrency(); - - auto const& uGetsIssuerID = saTakerGets.getIssuer(); + auto const& uPaysAsset = saTakerPays.asset(); auto const cancelSequence = ctx.tx[~sfOfferSequence]; @@ -146,13 +183,17 @@ OfferCreate::preclaim(PreclaimContext const& ctx) auto viewJ = ctx.registry.get().getJournal("View"); - if (isGlobalFrozen(ctx.view, uPaysIssuerID) || isGlobalFrozen(ctx.view, uGetsIssuerID)) + if (isGlobalFrozen(ctx.view, saTakerPays.asset()) || + isGlobalFrozen(ctx.view, saTakerGets.asset())) { JLOG(ctx.j.debug()) << "Offer involves frozen asset"; return tecFROZEN; } - if (accountFunds(ctx.view, id, saTakerGets, fhZERO_IF_FROZEN, viewJ) <= beast::zero) + // Allow unfunded MPT for issuer (OutstandingAmount >= MaximumAmount) + if ((!saTakerGets.holds() || saTakerGets.getIssuer() != id) && + accountFunds(ctx.view, id, saTakerGets, fhZERO_IF_FROZEN, ahZERO_IF_UNAUTHORIZED, viewJ) <= + beast::zero) { JLOG(ctx.j.debug()) << "delay: Offers must be at least partially funded."; return tecUNFUNDED_OFFER; @@ -177,8 +218,7 @@ OfferCreate::preclaim(PreclaimContext const& ctx) // Make sure that we are authorized to hold what the taker will pay us. if (!saTakerPays.native()) { - auto result = - checkAcceptAsset(ctx.view, ctx.flags, id, ctx.j, Issue(uPaysCurrency, uPaysIssuerID)); + auto result = checkAcceptAsset(ctx.view, ctx.flags, id, ctx.j, uPaysAsset); if (!isTesSuccess(result)) return result; } @@ -191,6 +231,11 @@ OfferCreate::preclaim(PreclaimContext const& ctx) return tecNO_PERMISSION; } + if (auto const ter = canTrade(ctx.view, saTakerPays.asset()); !isTesSuccess(ter)) + return ter; + if (auto const ter = canTrade(ctx.view, saTakerGets.asset()); !isTesSuccess(ter)) + return ter; + return tesSUCCESS; } @@ -200,17 +245,17 @@ OfferCreate::checkAcceptAsset( ApplyFlags const flags, AccountID const id, beast::Journal const j, - Issue const& issue) + Asset const& asset) { // Only valid for custom currencies - XRPL_ASSERT(!isXRP(issue.currency), "xrpl::OfferCreate::checkAcceptAsset : input is not XRP"); + XRPL_ASSERT(!isXRP(asset), "xrpl::OfferCreate::checkAcceptAsset : input is not XRP"); - auto const issuerAccount = view.read(keylet::account(issue.account)); + auto const issuerAccount = view.read(keylet::account(asset.getIssuer())); if (!issuerAccount) { JLOG(j.debug()) << "delay: can't receive IOUs from non-existent issuer: " - << to_string(issue.account); + << to_string(asset.getIssuer()); return ((flags & tapRETRY) != 0u) ? TER{terNO_ACCOUNT} : TER{tecNO_ISSUER}; } @@ -218,51 +263,63 @@ OfferCreate::checkAcceptAsset( // An account cannot create a trustline to itself, so no line can exist // to be frozen. Additionally, an issuer can always accept its own // issuance. - if (issue.account == id) + if (asset.getIssuer() == id) return tesSUCCESS; - if (((*issuerAccount)[sfFlags] & lsfRequireAuth) != 0u) - { - auto const trustLine = view.read(keylet::line(id, issue.account, issue.currency)); + return asset.visit( + [&](Issue const& issue) -> TER { + auto const& issuer = issue.getIssuer(); + if (((*issuerAccount)[sfFlags] & lsfRequireAuth) != 0u) + { + auto const trustLine = view.read(keylet::line(id, issuer, issue.currency)); - if (!trustLine) - { - return ((flags & tapRETRY) != 0u) ? TER{terNO_LINE} : TER{tecNO_LINE}; - } + if (!trustLine) + { + return ((flags & tapRETRY) != 0u) ? TER{terNO_LINE} : TER{tecNO_LINE}; + } - // Entries have a canonical representation, determined by a - // lexicographical "greater than" comparison employing strict weak - // ordering. Determine which entry we need to access. - bool const canonical_gt(id > issue.account); + // Entries have a canonical representation, determined by a + // lexicographical "greater than" comparison employing + // strict weak ordering. Determine which entry we need to + // access. + bool const canonical_gt(id > issuer); - bool const is_authorized( - ((*trustLine)[sfFlags] & (canonical_gt ? lsfLowAuth : lsfHighAuth)) != 0u); + bool const is_authorized( + ((*trustLine)[sfFlags] & (canonical_gt ? lsfLowAuth : lsfHighAuth)) != 0u); - if (!is_authorized) - { - JLOG(j.debug()) << "delay: can't receive IOUs from issuer without auth."; + if (!is_authorized) + { + JLOG(j.debug()) << "delay: can't receive IOUs from " + "issuer without auth."; - return ((flags & tapRETRY) != 0u) ? TER{terNO_AUTH} : TER{tecNO_AUTH}; - } - } + return ((flags & tapRETRY) != 0u) ? TER{terNO_AUTH} : TER{tecNO_AUTH}; + } + } - auto const trustLine = view.read(keylet::line(id, issue.account, issue.currency)); + auto const trustLine = view.read(keylet::line(id, issue.account, issue.currency)); - if (!trustLine) - { - return tesSUCCESS; - } + if (!trustLine) + { + return tesSUCCESS; + } - // There's no difference which side enacted deep freeze, accepting - // tokens shouldn't be possible. - bool const deepFrozen = ((*trustLine)[sfFlags] & (lsfLowDeepFreeze | lsfHighDeepFreeze)) != 0u; + // There's no difference which side enacted deep freeze, accepting + // tokens shouldn't be possible. + bool const deepFrozen = + ((*trustLine)[sfFlags] & (lsfLowDeepFreeze | lsfHighDeepFreeze)) != 0u; - if (deepFrozen) - { - return tecFROZEN; - } + if (deepFrozen) + { + return tecFROZEN; + } - return tesSUCCESS; + return tesSUCCESS; + }, + [&](MPTIssue const& issue) -> TER { + // WeakAuth - don't check if MPToken exists since it's created + // if needed. + return requireAuth(view, issue, id, AuthType::WeakAuth); + }); } std::pair @@ -280,9 +337,12 @@ OfferCreate::flowCross( // We check this in preclaim, but when selling XRP charged fees can // cause a user's available balance to go to 0 (by causing it to dip // below the reserve) so we check this case again. - STAmount const inStartBalance = - accountFunds(psb, account_, takerAmount.in, fhZERO_IF_FROZEN, j_); - if (inStartBalance <= beast::zero) + STAmount const inStartBalance = accountFunds( + psb, account_, takerAmount.in, fhZERO_IF_FROZEN, ahZERO_IF_UNAUTHORIZED, j_); + // Allow unfunded MPT issuer + auto const disallowUnfunded = + !inStartBalance.holds() || inStartBalance.getIssuer() != account_; + if (disallowUnfunded && inStartBalance <= beast::zero) { // The account balance can't cover even part of the offer. JLOG(j_.debug()) << "Not crossing: taker is unfunded."; @@ -296,11 +356,11 @@ OfferCreate::flowCross( STAmount sendMax = takerAmount.in; if (!sendMax.native() && (account_ != sendMax.getIssuer())) { - gatewayXferRate = transferRate(psb, sendMax.getIssuer()); + gatewayXferRate = transferRate(psb, sendMax); if (gatewayXferRate.value != QUALITY_ONE) { sendMax = - multiplyRound(takerAmount.in, gatewayXferRate, takerAmount.in.issue(), true); + multiplyRound(takerAmount.in, gatewayXferRate, takerAmount.in.asset(), true); } } @@ -331,6 +391,7 @@ OfferCreate::flowCross( } // Special handling for the tfSell flag. STAmount deliver = takerAmount.out; + auto const& deliverAsset = deliver.asset(); OfferCrossing offerCrossing = OfferCrossing::yes; if ((txFlags & tfSell) != 0u) { @@ -338,19 +399,23 @@ OfferCreate::flowCross( // We are selling, so we will accept *more* than the offer // specified. Since we don't know how much they might offer, // we allow delivery of the largest possible amount. - if (deliver.native()) - { - deliver = STAmount{STAmount::cMaxNative}; - } - else - { - // We can't use the maximum possible currency here because - // there might be a gateway transfer rate to account for. - // Since the transfer rate cannot exceed 200%, we use 1/2 - // maxValue for our limit. - deliver = STAmount{ - takerAmount.out.issue(), STAmount::cMaxValue / 2, STAmount::cMaxOffset}; - } + deliver.asset().visit( + [&](Issue const& issue) { + if (issue.native()) + { + deliver = STAmount{STAmount::cMaxNative}; + } + // We can't use the maximum possible currency here because + // there might be a gateway transfer rate to account for. + // Since the transfer rate cannot exceed 200%, we use 1/2 + // maxValue for our limit. + else + { + deliver = + STAmount{deliverAsset, STAmount::cMaxValue / 2, STAmount::cMaxOffset}; + } + }, + [&](MPTIssue const&) { deliver = STAmount{deliverAsset, maxMPTokenAmount / 2}; }); } // Call the payment engine's flow() to do the actual work. @@ -382,10 +447,10 @@ OfferCreate::flowCross( auto afterCross = takerAmount; // If !tesSUCCESS offer unchanged if (isTesSuccess(result.result())) { - STAmount const takerInBalance = - accountFunds(psb, account_, takerAmount.in, fhZERO_IF_FROZEN, j_); + STAmount const takerInBalance = accountFunds( + psb, account_, takerAmount.in, fhZERO_IF_FROZEN, ahZERO_IF_UNAUTHORIZED, j_); - if (takerInBalance <= beast::zero) + if (disallowUnfunded && takerInBalance <= beast::zero) { // If offer crossing exhausted the account's funds don't // create the offer. @@ -410,7 +475,7 @@ OfferCreate::flowCross( if (gatewayXferRate.value != QUALITY_ONE) { nonGatewayAmountIn = divideRound( - result.actualAmountIn, gatewayXferRate, takerAmount.in.issue(), true); + result.actualAmountIn, gatewayXferRate, takerAmount.in.asset(), true); } afterCross.in -= nonGatewayAmountIn; @@ -425,7 +490,7 @@ OfferCreate::flowCross( } afterCross.out = - divRoundStrict(afterCross.in, rate, takerAmount.out.issue(), false); + divRoundStrict(afterCross.in, rate, takerAmount.out.asset(), false); } else { @@ -438,7 +503,7 @@ OfferCreate::flowCross( "xrpl::OfferCreate::flowCross : minimum offer"); if (afterCross.out < beast::zero) afterCross.out.clear(); - afterCross.in = mulRound(afterCross.out, rate, takerAmount.in.issue(), true); + afterCross.in = mulRound(afterCross.out, rate, takerAmount.in.asset(), true); } } } @@ -458,7 +523,9 @@ OfferCreate::format_amount(STAmount const& amount) { std::string txt = amount.getText(); txt += "/"; - txt += to_string(amount.issue().currency); + amount.asset().visit( + [&](Issue const& issue) { txt += to_string(issue.currency); }, + [&](MPTIssue const& issue) { txt += to_string(issue); }); return txt; } @@ -478,7 +545,7 @@ OfferCreate::applyHybrid( sleOffer->setFlag(lsfHybrid); // if offer is hybrid, need to also place into open offer dir - Book const book{saTakerPays.issue(), saTakerGets.issue(), std::nullopt}; + Book const book{saTakerPays.asset(), saTakerGets.asset(), std::nullopt}; auto dir = keylet::quality(keylet::book(book), getRate(saTakerGets, saTakerPays)); bool const bookExists = sb.exists(dir); @@ -527,7 +594,7 @@ OfferCreate::applyGuts(Sandbox& sb, Sandbox& sbCancel) auto const cancelSequence = ctx_.tx[~sfOfferSequence]; - // Note that we we use the value from the sequence or ticket as the + // Note that we use the value from the sequence or ticket as the // offer sequence. For more explanation see comments in SeqProxy.h. auto const offerSequence = ctx_.tx.getSeqValue(); @@ -574,13 +641,15 @@ OfferCreate::applyGuts(Sandbox& sb, Sandbox& sbCancel) auto const& uGetsIssuerID = saTakerGets.getIssuer(); std::uint8_t uTickSize = Quality::maxTickSize; - if (!isXRP(uPaysIssuerID)) + // Not XRP or MPT + if (!saTakerPays.integral()) { auto const sle = sb.read(keylet::account(uPaysIssuerID)); if (sle && sle->isFieldPresent(sfTickSize)) uTickSize = std::min(uTickSize, (*sle)[sfTickSize]); } - if (!isXRP(uGetsIssuerID)) + // Not XRP or MPT + if (!saTakerGets.integral()) { auto const sle = sb.read(keylet::account(uGetsIssuerID)); if (sle && sle->isFieldPresent(sfTickSize)) @@ -596,12 +665,13 @@ OfferCreate::applyGuts(Sandbox& sb, Sandbox& sbCancel) if (bSell) { // this is a sell, round taker pays - saTakerPays = multiply(saTakerGets, rate, saTakerPays.issue()); + if (!saTakerPays.holds()) + saTakerPays = multiply(saTakerGets, rate, saTakerPays.asset()); } - else + else if (!saTakerGets.holds()) { // this is a buy, round taker gets - saTakerGets = divide(saTakerPays, rate, saTakerGets.issue()); + saTakerGets = divide(saTakerPays, rate, saTakerGets.asset()); } if (!saTakerGets || !saTakerPays) { @@ -615,8 +685,8 @@ OfferCreate::applyGuts(Sandbox& sb, Sandbox& sbCancel) // We reverse pays and gets because during crossing we are taking. Amounts const takerAmount(saTakerGets, saTakerPays); - JLOG(j_.debug()) << "Attempting cross: " << to_string(takerAmount.in.issue()) << " -> " - << to_string(takerAmount.out.issue()); + JLOG(j_.debug()) << "Attempting cross: " << to_string(takerAmount.in.asset()) << " -> " + << to_string(takerAmount.out.asset()); if (auto stream = j_.trace()) { @@ -660,10 +730,10 @@ OfferCreate::applyGuts(Sandbox& sb, Sandbox& sbCancel) } XRPL_ASSERT( - saTakerGets.issue() == place_offer.in.issue(), + saTakerGets.asset() == place_offer.in.asset(), "xrpl::OfferCreate::applyGuts : taker gets issue match"); XRPL_ASSERT( - saTakerPays.issue() == place_offer.out.issue(), + saTakerPays.asset() == place_offer.out.asset(), "xrpl::OfferCreate::applyGuts : taker pays issue match"); if (takerAmount != place_offer) @@ -726,7 +796,7 @@ OfferCreate::applyGuts(Sandbox& sb, Sandbox& sbCancel) { // Any ImmediateOrCancel offer that transfers absolutely no funds // returns tecKILLED rather than tesSUCCESS. Motivation for the - // change is here: https://github.com/ripple/rippled/issues/4115 + // change is here: https://github.com/XRPLF/rippled/issues/4115 return {tecKILLED, false}; } return {tesSUCCESS, true}; @@ -775,11 +845,11 @@ OfferCreate::applyGuts(Sandbox& sb, Sandbox& sbCancel) // Update owner count. adjustOwnerCount(sb, sleCreator, 1, viewJ); - JLOG(j_.trace()) << "adding to book: " << to_string(saTakerPays.issue()) << " : " - << to_string(saTakerGets.issue()) + JLOG(j_.trace()) << "adding to book: " << to_string(saTakerPays.asset()) << " : " + << to_string(saTakerGets.asset()) << (domainID ? (" : " + to_string(*domainID)) : ""); - Book const book{saTakerPays.issue(), saTakerGets.issue(), domainID}; + Book const book{saTakerPays.asset(), saTakerGets.asset(), domainID}; // Add offer to order book, using the original rate // before any crossing occurred. @@ -796,10 +866,18 @@ OfferCreate::applyGuts(Sandbox& sb, Sandbox& sbCancel) bool const bookExisted = static_cast(sb.peek(dir)); auto setBookDir = [&](SLE::ref sle, std::optional const& maybeDomain) { - sle->setFieldH160(sfTakerPaysCurrency, saTakerPays.issue().currency); - sle->setFieldH160(sfTakerPaysIssuer, saTakerPays.issue().account); - sle->setFieldH160(sfTakerGetsCurrency, saTakerGets.issue().currency); - sle->setFieldH160(sfTakerGetsIssuer, saTakerGets.issue().account); + saTakerPays.asset().visit( + [&](Issue const& issue) { + sle->setFieldH160(sfTakerPaysCurrency, issue.currency); + sle->setFieldH160(sfTakerPaysIssuer, issue.account); + }, + [&](MPTIssue const& issue) { sle->setFieldH192(sfTakerPaysMPT, issue.getMptID()); }); + saTakerGets.asset().visit( + [&](Issue const& issue) { + sle->setFieldH160(sfTakerGetsCurrency, issue.currency); + sle->setFieldH160(sfTakerGetsIssuer, issue.account); + }, + [&](MPTIssue const& issue) { sle->setFieldH192(sfTakerGetsMPT, issue.getMptID()); }); sle->setFieldU64(sfExchangeRate, uRate); if (maybeDomain) sle->setFieldH256(sfDomainID, *maybeDomain); @@ -878,4 +956,18 @@ OfferCreate::doApply() return result.first; } +void +OfferCreate::visitInvariantEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ +} + +bool +OfferCreate::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&) +{ + return true; +} + } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/dex/PermissionedDEXHelpers.cpp b/src/libxrpl/tx/transactors/dex/PermissionedDEXHelpers.cpp deleted file mode 100644 index d857795e39..0000000000 --- a/src/libxrpl/tx/transactors/dex/PermissionedDEXHelpers.cpp +++ /dev/null @@ -1,63 +0,0 @@ -#include -#include - -namespace xrpl { -namespace permissioned_dex { - -bool -accountInDomain(ReadView const& view, AccountID const& account, Domain const& domainID) -{ - auto const sleDomain = view.read(keylet::permissionedDomain(domainID)); - if (!sleDomain) - return false; - - // domain owner is in the domain - if (sleDomain->getAccountID(sfOwner) == account) - return true; - - auto const& credentials = sleDomain->getFieldArray(sfAcceptedCredentials); - - bool const inDomain = - std::any_of(credentials.begin(), credentials.end(), [&](auto const& credential) { - auto const sleCred = view.read( - keylet::credential(account, credential[sfIssuer], credential[sfCredentialType])); - if (!sleCred || !sleCred->isFlag(lsfAccepted)) - return false; - - return !credentials::checkExpired(sleCred, view.header().parentCloseTime); - }); - - return inDomain; -} - -bool -offerInDomain( - ReadView const& view, - uint256 const& offerID, - Domain const& domainID, - beast::Journal j) -{ - auto const sleOffer = view.read(keylet::offer(offerID)); - - // The following are defensive checks that should never happen, since this - // function is used to check against the order book offers, which should not - // have any of the following wrong behavior - if (!sleOffer) - return false; // LCOV_EXCL_LINE - if (!sleOffer->isFieldPresent(sfDomainID)) - return false; // LCOV_EXCL_LINE - if (sleOffer->getFieldH256(sfDomainID) != domainID) - return false; // LCOV_EXCL_LINE - - if (sleOffer->isFlag(lsfHybrid) && !sleOffer->isFieldPresent(sfAdditionalBooks)) - { - JLOG(j.error()) << "Hybrid offer " << offerID << " missing AdditionalBooks field"; - return false; // LCOV_EXCL_LINE - } - - return accountInDomain(view, sleOffer->getAccountID(sfAccount), domainID); -} - -} // namespace permissioned_dex - -} // namespace xrpl diff --git a/src/libxrpl/tx/transactors/did/DIDDelete.cpp b/src/libxrpl/tx/transactors/did/DIDDelete.cpp index 0d5b63635f..a323822b9c 100644 --- a/src/libxrpl/tx/transactors/did/DIDDelete.cpp +++ b/src/libxrpl/tx/transactors/did/DIDDelete.cpp @@ -1,9 +1,23 @@ -#include -#include -#include -#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + namespace xrpl { NotTEC @@ -56,4 +70,17 @@ DIDDelete::doApply() return deleteSLE(ctx_, keylet::did(account_), account_); } +void +DIDDelete::visitInvariantEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ +} + +bool +DIDDelete::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&) +{ + return true; +} } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/did/DIDSet.cpp b/src/libxrpl/tx/transactors/did/DIDSet.cpp index cd5c9bbc96..3aa9966570 100644 --- a/src/libxrpl/tx/transactors/did/DIDSet.cpp +++ b/src/libxrpl/tx/transactors/did/DIDSet.cpp @@ -1,11 +1,25 @@ -#include +#include + +#include #include +#include #include #include +#include #include #include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include namespace xrpl { @@ -135,4 +149,18 @@ DIDSet::doApply() return addSLE(ctx_, sleDID, account_); } +void +DIDSet::visitInvariantEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ +} + +bool +DIDSet::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&) +{ + return true; +} + } // namespace xrpl diff --git a/src/xrpld/app/tx/detail/Taker.h b/src/libxrpl/tx/transactors/escrow/Escrow.cpp similarity index 100% rename from src/xrpld/app/tx/detail/Taker.h rename to src/libxrpl/tx/transactors/escrow/Escrow.cpp diff --git a/src/libxrpl/tx/transactors/escrow/EscrowCancel.cpp b/src/libxrpl/tx/transactors/escrow/EscrowCancel.cpp index e47f008357..edc07fa0f7 100644 --- a/src/libxrpl/tx/transactors/escrow/EscrowCancel.cpp +++ b/src/libxrpl/tx/transactors/escrow/EscrowCancel.cpp @@ -1,15 +1,30 @@ +#include + #include #include #include #include +#include #include #include +#include +#include +#include #include #include +#include +#include #include -#include +#include +#include +#include +#include +#include +#include +#include -#include +#include +#include namespace xrpl { @@ -33,13 +48,13 @@ escrowCancelPreclaimHelper( AccountID const& account, STAmount const& amount) { - AccountID const issuer = amount.getIssuer(); + AccountID const& issuer = amount.getIssuer(); // If the issuer is the same as the account, return tecINTERNAL if (issuer == account) return tecINTERNAL; // LCOV_EXCL_LINE // If the issuer has requireAuth set, check if the account is authorized - if (auto const ter = requireAuth(ctx.view, amount.issue(), account); !isTesSuccess(ter)) + if (auto const ter = requireAuth(ctx.view, amount.get(), account); !isTesSuccess(ter)) return ter; return tesSUCCESS; @@ -204,4 +219,22 @@ EscrowCancel::doApply() return tesSUCCESS; } +void +EscrowCancel::visitInvariantEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ +} + +bool +EscrowCancel::finalizeInvariants( + STTx const&, + TER, + XRPAmount, + ReadView const&, + beast::Journal const&) +{ + return true; +} } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/escrow/EscrowCreate.cpp b/src/libxrpl/tx/transactors/escrow/EscrowCreate.cpp index ed0bbeea44..9d16d41914 100644 --- a/src/libxrpl/tx/transactors/escrow/EscrowCreate.cpp +++ b/src/libxrpl/tx/transactors/escrow/EscrowCreate.cpp @@ -1,19 +1,40 @@ +#include + #include #include +#include #include +#include #include #include #include #include #include #include +#include +#include +#include #include #include +#include +#include #include +#include +#include #include -#include +#include +#include +#include +#include +#include +#include #include -#include +#include +#include + +#include +#include +#include namespace xrpl { @@ -72,7 +93,7 @@ escrowCreatePreflightHelper(PreflightContext const& ctx) if (amount.native() || amount <= beast::zero) return temBAD_AMOUNT; - if (badCurrency() == amount.getCurrency()) + if (badCurrency() == amount.get().currency) return temBAD_CURRENCY; return tesSUCCESS; @@ -163,7 +184,8 @@ escrowCreatePreclaimHelper( AccountID const& dest, STAmount const& amount) { - AccountID const issuer = amount.getIssuer(); + Issue const& issue = amount.get(); + AccountID const& issuer = amount.getIssuer(); // If the issuer is the same as the account, return tecNO_PERMISSION if (issuer == account) return tecNO_PERMISSION; @@ -176,7 +198,7 @@ escrowCreatePreclaimHelper( return tecNO_PERMISSION; // If the account does not have a trustline to the issuer, return tecNO_LINE - auto const sleRippleState = ctx.view.read(keylet::line(account, issuer, amount.getCurrency())); + auto const sleRippleState = ctx.view.read(keylet::line(account, issuer, issue.currency)); if (!sleRippleState) return tecNO_LINE; @@ -191,23 +213,23 @@ escrowCreatePreclaimHelper( return tecNO_PERMISSION; // LCOV_EXCL_LINE // If the issuer has requireAuth set, check if the account is authorized - if (auto const ter = requireAuth(ctx.view, amount.issue(), account); !isTesSuccess(ter)) + if (auto const ter = requireAuth(ctx.view, issue, account); !isTesSuccess(ter)) return ter; // If the issuer has requireAuth set, check if the destination is authorized - if (auto const ter = requireAuth(ctx.view, amount.issue(), dest); !isTesSuccess(ter)) + if (auto const ter = requireAuth(ctx.view, issue, dest); !isTesSuccess(ter)) return ter; // If the issuer has frozen the account, return tecFROZEN - if (isFrozen(ctx.view, account, amount.issue())) + if (isFrozen(ctx.view, account, issue)) return tecFROZEN; // If the issuer has frozen the destination, return tecFROZEN - if (isFrozen(ctx.view, dest, amount.issue())) + if (isFrozen(ctx.view, dest, issue)) return tecFROZEN; STAmount const spendableAmount = - accountHolds(ctx.view, account, amount.getCurrency(), issuer, fhIGNORE_FREEZE, ctx.j); + accountHolds(ctx.view, account, issue.currency, issuer, fhIGNORE_FREEZE, ctx.j); // If the balance is less than or equal to 0, return tecINSUFFICIENT_FUNDS if (spendableAmount <= beast::zero) @@ -353,7 +375,8 @@ escrowLockApplyHelper( if (issuer == sender) return tecINTERNAL; // LCOV_EXCL_LINE - auto const ter = rippleCredit(view, sender, issuer, amount, !amount.holds(), journal); + auto const ter = + directSendNoFee(view, sender, issuer, amount, !amount.holds(), journal); if (!isTesSuccess(ter)) return ter; // LCOV_EXCL_LINE return tesSUCCESS; @@ -372,7 +395,7 @@ escrowLockApplyHelper( if (issuer == sender) return tecINTERNAL; // LCOV_EXCL_LINE - auto const ter = rippleLockEscrowMPT(view, sender, amount, journal); + auto const ter = lockEscrowMPT(view, sender, amount, journal); if (!isTesSuccess(ter)) return ter; // LCOV_EXCL_LINE return tesSUCCESS; @@ -418,7 +441,7 @@ EscrowCreate::doApply() return tecDST_TAG_NEEDED; } - // Create escrow in ledger. Note that we we use the value from the + // Create escrow in ledger. Note that we use the value from the // sequence or ticket. For more explanation see comments in SeqProxy.h. Keylet const escrowKeylet = keylet::escrow(account_, ctx_.tx.getSeqValue()); auto const slep = std::make_shared(escrowKeylet); @@ -502,4 +525,22 @@ EscrowCreate::doApply() return tesSUCCESS; } +void +EscrowCreate::visitInvariantEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ +} + +bool +EscrowCreate::finalizeInvariants( + STTx const&, + TER, + XRPAmount, + ReadView const&, + beast::Journal const&) +{ + return true; +} } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/escrow/EscrowFinish.cpp b/src/libxrpl/tx/transactors/escrow/EscrowFinish.cpp index e05ba87bbb..c8697fbaa8 100644 --- a/src/libxrpl/tx/transactors/escrow/EscrowFinish.cpp +++ b/src/libxrpl/tx/transactors/escrow/EscrowFinish.cpp @@ -1,21 +1,38 @@ +#include + #include +#include #include #include #include #include #include +#include #include +#include #include +#include #include #include +#include +#include +#include #include #include +#include +#include #include -#include +#include +#include +#include +#include +#include #include -#include +#include -#include +#include +#include +#include namespace xrpl { @@ -127,17 +144,17 @@ escrowFinishPreclaimHelper( AccountID const& dest, STAmount const& amount) { - AccountID const issuer = amount.getIssuer(); + AccountID const& issuer = amount.getIssuer(); // If the issuer is the same as the account, return tesSUCCESS if (issuer == dest) return tesSUCCESS; // If the issuer has requireAuth set, check if the destination is authorized - if (auto const ter = requireAuth(ctx.view, amount.issue(), dest); !isTesSuccess(ter)) + if (auto const ter = requireAuth(ctx.view, amount.get(), dest); !isTesSuccess(ter)) return ter; // If the issuer has deep frozen the destination, return tecFROZEN - if (isDeepFrozen(ctx.view, dest, amount.getCurrency(), amount.getIssuer())) + if (isDeepFrozen(ctx.view, dest, amount.get().currency, amount.getIssuer())) return tecFROZEN; return tesSUCCESS; @@ -150,7 +167,7 @@ escrowFinishPreclaimHelper( AccountID const& dest, STAmount const& amount) { - AccountID const issuer = amount.getIssuer(); + AccountID const& issuer = amount.getIssuer(); // If the issuer is the same as the dest, return tesSUCCESS if (issuer == dest) return tesSUCCESS; @@ -382,4 +399,22 @@ EscrowFinish::doApply() return tesSUCCESS; } +void +EscrowFinish::visitInvariantEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ +} + +bool +EscrowFinish::finalizeInvariants( + STTx const&, + TER, + XRPAmount, + ReadView const&, + beast::Journal const&) +{ + return true; +} } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/lending/LendingHelpers.cpp b/src/libxrpl/tx/transactors/lending/LendingHelpers.cpp index bad55e9222..dea8dbbd34 100644 --- a/src/libxrpl/tx/transactors/lending/LendingHelpers.cpp +++ b/src/libxrpl/tx/transactors/lending/LendingHelpers.cpp @@ -1,7 +1,30 @@ #include -// DO NOT REMOVE forces header file include to sort first + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include + namespace xrpl { bool diff --git a/src/libxrpl/tx/transactors/lending/LoanBrokerCoverClawback.cpp b/src/libxrpl/tx/transactors/lending/LoanBrokerCoverClawback.cpp index 627b16794b..16f95dd357 100644 --- a/src/libxrpl/tx/transactors/lending/LoanBrokerCoverClawback.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanBrokerCoverClawback.cpp @@ -1,9 +1,36 @@ #include -// + +#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include #include +#include +#include +#include + namespace xrpl { bool @@ -123,7 +150,7 @@ determineAsset( { // We want the asset to match the vault asset, so use the account as the // issuer - return Issue{amount.getCurrency(), account}; + return Issue{amount.get().currency, account}; } return Unexpected(tecWRONG_ASSET); @@ -328,6 +355,25 @@ LoanBrokerCoverClawback::doApply() return accountSend(view(), brokerPseudoID, account, clawAmount, j_, WaiveTransferFee::Yes); } +void +LoanBrokerCoverClawback::visitInvariantEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ +} + +bool +LoanBrokerCoverClawback::finalizeInvariants( + STTx const&, + TER, + XRPAmount, + ReadView const&, + beast::Journal const&) +{ + return true; +} + //------------------------------------------------------------------------------ } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/lending/LoanBrokerCoverDeposit.cpp b/src/libxrpl/tx/transactors/lending/LoanBrokerCoverDeposit.cpp index 4630e6a360..36b5cab618 100644 --- a/src/libxrpl/tx/transactors/lending/LoanBrokerCoverDeposit.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanBrokerCoverDeposit.cpp @@ -1,9 +1,21 @@ #include -// + +#include +#include #include +#include +#include +#include +#include #include +#include +#include +#include +#include #include +#include + namespace xrpl { bool @@ -121,6 +133,25 @@ LoanBrokerCoverDeposit::doApply() return tesSUCCESS; } +void +LoanBrokerCoverDeposit::visitInvariantEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ +} + +bool +LoanBrokerCoverDeposit::finalizeInvariants( + STTx const&, + TER, + XRPAmount, + ReadView const&, + beast::Journal const&) +{ + return true; +} + //------------------------------------------------------------------------------ } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/lending/LoanBrokerCoverWithdraw.cpp b/src/libxrpl/tx/transactors/lending/LoanBrokerCoverWithdraw.cpp index d03edad0a2..c8479e941d 100644 --- a/src/libxrpl/tx/transactors/lending/LoanBrokerCoverWithdraw.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanBrokerCoverWithdraw.cpp @@ -1,11 +1,25 @@ #include -// + +#include +#include +#include +#include #include -#include #include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include #include -#include + +#include namespace xrpl { @@ -123,7 +137,7 @@ LoanBrokerCoverWithdraw::preclaim(PreclaimContext const& ctx) return roundToAsset( vaultAsset, tenthBipsOfValue(currentDebtTotal, TenthBips32(sleBroker->at(sfCoverRateMinimum))), - currentDebtTotal.exponent()); + scale(currentDebtTotal, vaultAsset)); }(); if (coverAvail < amount) return tecINSUFFICIENT_FUNDS; @@ -172,6 +186,25 @@ LoanBrokerCoverWithdraw::doApply() return doWithdraw(view(), tx, account_, dstAcct, brokerPseudoID, preFeeBalance_, amount, j_); } +void +LoanBrokerCoverWithdraw::visitInvariantEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ +} + +bool +LoanBrokerCoverWithdraw::finalizeInvariants( + STTx const&, + TER, + XRPAmount, + ReadView const&, + beast::Journal const&) +{ + return true; +} + //------------------------------------------------------------------------------ } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/lending/LoanBrokerDelete.cpp b/src/libxrpl/tx/transactors/lending/LoanBrokerDelete.cpp index a755db7942..6f774eaeae 100644 --- a/src/libxrpl/tx/transactors/lending/LoanBrokerDelete.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanBrokerDelete.cpp @@ -1,10 +1,24 @@ #include -// + +#include +#include +#include #include #include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include #include +#include + namespace xrpl { bool @@ -176,6 +190,25 @@ LoanBrokerDelete::doApply() return tesSUCCESS; } +void +LoanBrokerDelete::visitInvariantEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ +} + +bool +LoanBrokerDelete::finalizeInvariants( + STTx const&, + TER, + XRPAmount, + ReadView const&, + beast::Journal const&) +{ + return true; +} + //------------------------------------------------------------------------------ } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/lending/LoanBrokerSet.cpp b/src/libxrpl/tx/transactors/lending/LoanBrokerSet.cpp index f8813ddbef..3322df9fe3 100644 --- a/src/libxrpl/tx/transactors/lending/LoanBrokerSet.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanBrokerSet.cpp @@ -1,10 +1,28 @@ #include -// + +#include +#include +#include +#include +#include #include #include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include #include +#include +#include + namespace xrpl { bool @@ -260,6 +278,25 @@ LoanBrokerSet::doApply() return tesSUCCESS; } +void +LoanBrokerSet::visitInvariantEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ +} + +bool +LoanBrokerSet::finalizeInvariants( + STTx const&, + TER, + XRPAmount, + ReadView const&, + beast::Journal const&) +{ + return true; +} + //------------------------------------------------------------------------------ } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/lending/LoanDelete.cpp b/src/libxrpl/tx/transactors/lending/LoanDelete.cpp index 39b28f5110..28948a4139 100644 --- a/src/libxrpl/tx/transactors/lending/LoanDelete.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanDelete.cpp @@ -1,9 +1,23 @@ #include -// + +#include +#include // IWYU pragma: keep +#include +#include #include +#include +#include +#include // IWYU pragma: keep +#include #include +#include +#include +#include +#include #include +#include + namespace xrpl { bool @@ -127,6 +141,20 @@ LoanDelete::doApply() return tesSUCCESS; } +void +LoanDelete::visitInvariantEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ +} + +bool +LoanDelete::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&) +{ + return true; +} + //------------------------------------------------------------------------------ } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/lending/LoanManage.cpp b/src/libxrpl/tx/transactors/lending/LoanManage.cpp index adef5374b9..1b87421e71 100644 --- a/src/libxrpl/tx/transactors/lending/LoanManage.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanManage.cpp @@ -1,10 +1,33 @@ #include -// + +#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include #include +#include +#include +#include #include +#include +#include +#include + namespace xrpl { bool @@ -386,21 +409,43 @@ LoanManage::doApply() return tefBAD_LEDGER; // LCOV_EXCL_LINE auto const vaultAsset = vaultSle->at(sfAsset); - // Valid flag combinations are checked in preflight. No flags is valid - - // just a noop. - if (tx.isFlag(tfLoanDefault)) - return defaultLoan(view, loanSle, brokerSle, vaultSle, vaultAsset, j_); - if (tx.isFlag(tfLoanImpair)) - return impairLoan(view, loanSle, vaultSle, vaultAsset, j_); - if (tx.isFlag(tfLoanUnimpair)) - return unimpairLoan(view, loanSle, vaultSle, vaultAsset, j_); - // Noop, as described above. + auto const result = [&]() -> TER { + // Valid flag combinations are checked in preflight. No flags is valid - + // just a noop. + if (tx.isFlag(tfLoanDefault)) + return defaultLoan(view, loanSle, brokerSle, vaultSle, vaultAsset, j_); + if (tx.isFlag(tfLoanImpair)) + return impairLoan(view, loanSle, vaultSle, vaultAsset, j_); + if (tx.isFlag(tfLoanUnimpair)) + return unimpairLoan(view, loanSle, vaultSle, vaultAsset, j_); + // Noop, as described above. + return tesSUCCESS; + }(); - associateAsset(*loanSle, vaultAsset); - associateAsset(*brokerSle, vaultAsset); - associateAsset(*vaultSle, vaultAsset); + // Pre-amendment, associateAsset was only called on the noop (no flags) + // path. Post-amendment, we call associateAsset on all successful paths. + if (view.rules().enabled(fixSecurity3_1_3) && isTesSuccess(result)) + { + associateAsset(*loanSle, vaultAsset); + associateAsset(*brokerSle, vaultAsset); + associateAsset(*vaultSle, vaultAsset); + } - return tesSUCCESS; + return result; +} + +void +LoanManage::visitInvariantEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ +} + +bool +LoanManage::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&) +{ + return true; } //------------------------------------------------------------------------------ diff --git a/src/libxrpl/tx/transactors/lending/LoanPay.cpp b/src/libxrpl/tx/transactors/lending/LoanPay.cpp index f748a670fd..8360303fd8 100644 --- a/src/libxrpl/tx/transactors/lending/LoanPay.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanPay.cpp @@ -1,14 +1,35 @@ #include -// + +#include +#include +#include +#include +#include #include +#include +#include #include +#include +#include +#include #include +#include +#include +#include #include +#include +#include #include +#include +#include +#include #include #include +#include #include +#include +#include namespace xrpl { @@ -155,7 +176,7 @@ LoanPay::preclaim(PreclaimContext const& ctx) if (tx.isFlag(tfLoanOverpayment) && !loanSle->isFlag(lsfLoanOverpayment)) { JLOG(ctx.j.warn()) << "Requested overpayment on a loan that doesn't allow it"; - return temINVALID_FLAG; + return ctx.view.rules().enabled(fixSecurity3_1_3) ? TER{tecNO_PERMISSION} : temINVALID_FLAG; } auto const principalOutstanding = loanSle->at(sfPrincipalOutstanding); @@ -624,6 +645,20 @@ LoanPay::doApply() return tesSUCCESS; } +void +LoanPay::visitInvariantEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ +} + +bool +LoanPay::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&) +{ + return true; +} + //------------------------------------------------------------------------------ } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/lending/LoanSet.cpp b/src/libxrpl/tx/transactors/lending/LoanSet.cpp index 9cc4042365..1027fdea9f 100644 --- a/src/libxrpl/tx/transactors/lending/LoanSet.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanSet.cpp @@ -1,11 +1,40 @@ #include -// + +#include +#include +#include +#include +#include +#include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include #include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include + namespace xrpl { bool @@ -617,6 +646,20 @@ LoanSet::doApply() return tesSUCCESS; } +void +LoanSet::visitInvariantEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ +} + +bool +LoanSet::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&) +{ + return true; +} + //------------------------------------------------------------------------------ } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/nft/NFTokenAcceptOffer.cpp b/src/libxrpl/tx/transactors/nft/NFTokenAcceptOffer.cpp index e061dbe7ec..296020ff95 100644 --- a/src/libxrpl/tx/transactors/nft/NFTokenAcceptOffer.cpp +++ b/src/libxrpl/tx/transactors/nft/NFTokenAcceptOffer.cpp @@ -1,10 +1,29 @@ +#include + +#include +#include +#include +#include #include +#include #include #include +#include +#include +#include #include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include namespace xrpl { @@ -83,7 +102,7 @@ NFTokenAcceptOffer::preclaim(PreclaimContext const& ctx) return tecNFTOKEN_BUY_SELL_MISMATCH; // The two offers being brokered must be for the same asset: - if ((*bo)[sfAmount].issue() != (*so)[sfAmount].issue()) + if ((*bo)[sfAmount].asset() != (*so)[sfAmount].asset()) return tecNFTOKEN_BUY_SELL_MISMATCH; // The two offers may not form a loop. A broker may not sell the @@ -116,7 +135,7 @@ NFTokenAcceptOffer::preclaim(PreclaimContext const& ctx) // cut, if any). if (auto const brokerFee = ctx.tx[~sfNFTokenBrokerFee]) { - if (brokerFee->issue() != (*bo)[sfAmount].issue()) + if (brokerFee->asset() != (*bo)[sfAmount].asset()) return tecNFTOKEN_BUY_SELL_MISMATCH; if (brokerFee >= (*bo)[sfAmount]) @@ -289,7 +308,7 @@ NFTokenAcceptOffer::preclaim(PreclaimContext const& ctx) if (ctx.view.rules().enabled(fixEnforceNFTokenTrustline) && (nft::getFlags(tokenID) & nft::flagCreateTrustLines) == 0 && nftMinter != amount.getIssuer() && - !ctx.view.read(keylet::line(nftMinter, amount.issue()))) + !ctx.view.read(keylet::line(nftMinter, amount.get()))) return tecNO_LINE; // Check that the issuer is allowed to receive IOUs. @@ -550,4 +569,23 @@ NFTokenAcceptOffer::doApply() return tecINTERNAL; // LCOV_EXCL_LINE } +void +NFTokenAcceptOffer::visitInvariantEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ +} + +bool +NFTokenAcceptOffer::finalizeInvariants( + STTx const&, + TER, + XRPAmount, + ReadView const&, + beast::Journal const&) +{ + return true; +} + } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/nft/NFTokenBurn.cpp b/src/libxrpl/tx/transactors/nft/NFTokenBurn.cpp index 5a31e35470..a0d10ca07d 100644 --- a/src/libxrpl/tx/transactors/nft/NFTokenBurn.cpp +++ b/src/libxrpl/tx/transactors/nft/NFTokenBurn.cpp @@ -1,8 +1,19 @@ -#include -#include -#include #include -#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include namespace xrpl { @@ -83,4 +94,18 @@ NFTokenBurn::doApply() return tesSUCCESS; } +void +NFTokenBurn::visitInvariantEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ +} + +bool +NFTokenBurn::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&) +{ + return true; +} + } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/nft/NFTokenCancelOffer.cpp b/src/libxrpl/tx/transactors/nft/NFTokenCancelOffer.cpp index df0561e076..9d25a7335f 100644 --- a/src/libxrpl/tx/transactors/nft/NFTokenCancelOffer.cpp +++ b/src/libxrpl/tx/transactors/nft/NFTokenCancelOffer.cpp @@ -1,10 +1,22 @@ -#include -#include -#include #include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include namespace xrpl { @@ -18,8 +30,8 @@ NFTokenCancelOffer::preflight(PreflightContext const& ctx) // In order to prevent unnecessarily overlarge transactions, we // disallow duplicates in the list of offers to cancel. STVector256 ids = ctx.tx.getFieldV256(sfNFTokenOffers); - std::sort(ids.begin(), ids.end()); - if (std::adjacent_find(ids.begin(), ids.end()) != ids.end()) + std::ranges::sort(ids); + if (std::ranges::adjacent_find(ids) != ids.end()) return temMALFORMED; return tesSUCCESS; @@ -32,7 +44,7 @@ NFTokenCancelOffer::preclaim(PreclaimContext const& ctx) auto const& ids = ctx.tx[sfNFTokenOffers]; - auto ret = std::find_if(ids.begin(), ids.end(), [&ctx, &account](uint256 const& id) { + auto ret = std::ranges::find_if(ids, [&ctx, &account](uint256 const& id) { auto const offer = ctx.view.read(keylet::child(id)); // If id is not in the ledger we assume the offer was consumed @@ -85,4 +97,23 @@ NFTokenCancelOffer::doApply() return tesSUCCESS; } +void +NFTokenCancelOffer::visitInvariantEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ +} + +bool +NFTokenCancelOffer::finalizeInvariants( + STTx const&, + TER, + XRPAmount, + ReadView const&, + beast::Journal const&) +{ + return true; +} + } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/nft/NFTokenCreateOffer.cpp b/src/libxrpl/tx/transactors/nft/NFTokenCreateOffer.cpp index f5fdc89550..9ca5220a8c 100644 --- a/src/libxrpl/tx/transactors/nft/NFTokenCreateOffer.cpp +++ b/src/libxrpl/tx/transactors/nft/NFTokenCreateOffer.cpp @@ -1,8 +1,20 @@ -#include -#include -#include #include -#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include namespace xrpl { @@ -79,4 +91,23 @@ NFTokenCreateOffer::doApply() ctx_.tx.getFlags()); } +void +NFTokenCreateOffer::visitInvariantEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ +} + +bool +NFTokenCreateOffer::finalizeInvariants( + STTx const&, + TER, + XRPAmount, + ReadView const&, + beast::Journal const&) +{ + return true; +} + } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/nft/NFTokenMint.cpp b/src/libxrpl/tx/transactors/nft/NFTokenMint.cpp index 7bebbd0070..6043d918c2 100644 --- a/src/libxrpl/tx/transactors/nft/NFTokenMint.cpp +++ b/src/libxrpl/tx/transactors/nft/NFTokenMint.cpp @@ -1,14 +1,34 @@ -#include -#include -#include -#include -#include -#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + #include #include +#include +#include +#include // IWYU pragma: keep +#include +#include namespace xrpl { @@ -323,4 +343,18 @@ NFTokenMint::doApply() return tesSUCCESS; } +void +NFTokenMint::visitInvariantEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ +} + +bool +NFTokenMint::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&) +{ + return true; +} + } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/nft/NFTokenModify.cpp b/src/libxrpl/tx/transactors/nft/NFTokenModify.cpp index 8ccd4e9552..392d928f02 100644 --- a/src/libxrpl/tx/transactors/nft/NFTokenModify.cpp +++ b/src/libxrpl/tx/transactors/nft/NFTokenModify.cpp @@ -1,7 +1,20 @@ -#include -#include #include -#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include namespace xrpl { @@ -55,4 +68,23 @@ NFTokenModify::doApply() return nft::changeTokenURI(view(), owner, nftokenID, ctx_.tx[~sfURI]); } +void +NFTokenModify::visitInvariantEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ +} + +bool +NFTokenModify::finalizeInvariants( + STTx const&, + TER, + XRPAmount, + ReadView const&, + beast::Journal const&) +{ + return true; +} + } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/oracle/OracleDelete.cpp b/src/libxrpl/tx/transactors/oracle/OracleDelete.cpp index bde403f821..c6896b78d4 100644 --- a/src/libxrpl/tx/transactors/oracle/OracleDelete.cpp +++ b/src/libxrpl/tx/transactors/oracle/OracleDelete.cpp @@ -1,10 +1,20 @@ -#include -#include -#include -#include -#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + namespace xrpl { NotTEC @@ -78,4 +88,23 @@ OracleDelete::doApply() return tecINTERNAL; // LCOV_EXCL_LINE } +void +OracleDelete::visitInvariantEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ +} + +bool +OracleDelete::finalizeInvariants( + STTx const&, + TER, + XRPAmount, + ReadView const&, + beast::Journal const&) +{ + return true; +} + } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/oracle/OracleSet.cpp b/src/libxrpl/tx/transactors/oracle/OracleSet.cpp index 772756ad6d..4304427646 100644 --- a/src/libxrpl/tx/transactors/oracle/OracleSet.cpp +++ b/src/libxrpl/tx/transactors/oracle/OracleSet.cpp @@ -1,12 +1,31 @@ -#include -#include +#include + +#include +#include #include #include #include +#include #include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include namespace xrpl { @@ -308,4 +327,18 @@ OracleSet::doApply() return tesSUCCESS; } +void +OracleSet::visitInvariantEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ +} + +bool +OracleSet::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&) +{ + return true; +} + } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/payment/DepositPreauth.cpp b/src/libxrpl/tx/transactors/payment/DepositPreauth.cpp index 0f4681a3ad..f8dca6bb58 100644 --- a/src/libxrpl/tx/transactors/payment/DepositPreauth.cpp +++ b/src/libxrpl/tx/transactors/payment/DepositPreauth.cpp @@ -1,14 +1,31 @@ +#include + #include -#include +#include +#include +#include #include #include #include +#include #include #include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include namespace xrpl { @@ -279,4 +296,23 @@ DepositPreauth::removeFromLedger(ApplyView& view, uint256 const& preauthIndex, b return tesSUCCESS; } +void +DepositPreauth::visitInvariantEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ +} + +bool +DepositPreauth::finalizeInvariants( + STTx const&, + TER, + XRPAmount, + ReadView const&, + beast::Journal const&) +{ + return true; +} + } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/payment/Payment.cpp b/src/libxrpl/tx/transactors/payment/Payment.cpp index 5f2a78917c..644d5210c0 100644 --- a/src/libxrpl/tx/transactors/payment/Payment.cpp +++ b/src/libxrpl/tx/transactors/payment/Payment.cpp @@ -1,17 +1,47 @@ -#include +#include + +#include +#include +#include +#include +#include +#include #include #include +#include #include +#include #include +#include +#include #include +#include +#include +#include +#include +#include #include #include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include #include +#include +#include #include -#include -#include -#include + +#include +#include +#include +#include +#include namespace xrpl { @@ -39,16 +69,17 @@ getMaxSourceAmount( { return *sendMax; } - if (dstAmount.native() || dstAmount.holds()) - { - return dstAmount; - } - - return STAmount( - Issue{dstAmount.get().currency, account}, - dstAmount.mantissa(), - dstAmount.exponent(), - dstAmount < beast::zero); + return dstAmount.asset().visit( + [&](MPTIssue const& issue) { return dstAmount; }, + [&](Issue const& issue) { + if (issue.native()) + return dstAmount; + return STAmount( + Issue{issue.currency, account}, + dstAmount.mantissa(), + dstAmount.exponent(), + dstAmount < beast::zero); + }); } bool @@ -68,9 +99,14 @@ Payment::getFlagsMask(PreflightContext const& ctx) auto& tx = ctx.tx; STAmount const dstAmount(tx.getFieldAmount(sfAmount)); - bool const mptDirect = dstAmount.holds(); + bool const isDstMPT = dstAmount.holds(); + bool const MPTokensV2 = ctx.rules.enabled(featureMPTokensV2); - return mptDirect ? tfMPTPaymentMask : tfPaymentMask; + constexpr std::uint32_t tfMPTPaymentMaskV1 = ~(tfUniversal | tfPartialPayment); + std::uint32_t const paymentMask = + (isDstMPT && !MPTokensV2) ? tfMPTPaymentMaskV1 : tfPaymentMask; + + return paymentMask; } NotTEC @@ -80,14 +116,15 @@ Payment::preflight(PreflightContext const& ctx) auto& j = ctx.j; STAmount const dstAmount(tx.getFieldAmount(sfAmount)); - bool const mptDirect = dstAmount.holds(); + bool const isDstMPT = dstAmount.holds(); + bool const MPTokensV2 = ctx.rules.enabled(featureMPTokensV2); - if (mptDirect && !ctx.rules.enabled(featureMPTokensV1)) + if (!ctx.rules.enabled(featureMPTokensV1) && isDstMPT) return temDISABLED; std::uint32_t const txFlags = tx.getFlags(); - if (mptDirect && ctx.tx.isFieldPresent(sfPaths)) + if (!MPTokensV2 && isDstMPT && ctx.tx.isFieldPresent(sfPaths)) return temMALFORMED; bool const partialPaymentAllowed = (txFlags & tfPartialPayment) != 0u; @@ -101,8 +138,9 @@ Payment::preflight(PreflightContext const& ctx) auto const account = tx.getAccountID(sfAccount); STAmount const maxSourceAmount = getMaxSourceAmount(account, dstAmount, tx[~sfSendMax]); - if ((mptDirect && dstAmount.asset() != maxSourceAmount.asset()) || - (!mptDirect && maxSourceAmount.holds())) + if (!MPTokensV2 && + ((isDstMPT && dstAmount.asset() != maxSourceAmount.asset()) || + (!isDstMPT && maxSourceAmount.holds()))) { JLOG(j.trace()) << "Malformed transaction: inconsistent issues: " << dstAmount.getFullText() << " " << maxSourceAmount.getFullText() << " " @@ -137,7 +175,12 @@ Payment::preflight(PreflightContext const& ctx) JLOG(j.trace()) << "Malformed transaction: bad dst amount: " << dstAmount.getFullText(); return temBAD_AMOUNT; } - if (badCurrency() == srcAsset || badCurrency() == dstAsset) + auto bad = [&](auto const& asset) { + if (ctx.rules.enabled(featureMPTokensV2)) + return badAsset() == asset; + return badCurrency() == asset; + }; + if (bad(srcAsset) || bad(dstAsset)) { JLOG(j.trace()) << "Malformed transaction: Bad currency."; return temBAD_CURRENCY; @@ -158,7 +201,7 @@ Payment::preflight(PreflightContext const& ctx) << "SendMax specified for XRP to XRP."; return temBAD_SEND_XRP_MAX; } - if ((xrpDirect || mptDirect) && hasPaths) + if ((xrpDirect || (!MPTokensV2 && isDstMPT)) && hasPaths) { // XRP is sent without paths. JLOG(j.trace()) << "Malformed transaction: " @@ -172,14 +215,14 @@ Payment::preflight(PreflightContext const& ctx) << "Partial payment specified for XRP to XRP."; return temBAD_SEND_XRP_PARTIAL; } - if ((xrpDirect || mptDirect) && limitQuality) + if ((xrpDirect || (!MPTokensV2 && isDstMPT)) && limitQuality) { // Consistent but redundant transaction. JLOG(j.trace()) << "Malformed transaction: " << "Limit quality specified for XRP to XRP or MPT to MPT."; return temBAD_SEND_XRP_LIMIT; } - if ((xrpDirect || mptDirect) && !defaultPathsAllowed) + if ((xrpDirect || (!MPTokensV2 && isDstMPT)) && !defaultPathsAllowed) { // Consistent but redundant transaction. JLOG(j.trace()) << "Malformed transaction: " @@ -252,6 +295,7 @@ Payment::checkPermission(ReadView const& view, STTx const& tx) tx.isFieldPresent(sfPaths)) return terNO_DELEGATE_PERMISSION; + // PaymentMint and PaymentBurn apply to both IOU and MPT direct payments. if (granularPermissions.contains(PaymentMint) && !isXRP(amountAsset) && amountAsset.getIssuer() == tx[sfAccount]) return tesSUCCESS; @@ -332,8 +376,7 @@ Payment::preclaim(PreclaimContext const& ctx) { STPathSet const& paths = ctx.tx.getFieldPathSet(sfPaths); - if (paths.size() > MaxPathSize || - std::any_of(paths.begin(), paths.end(), [](STPath const& path) { + if (paths.size() > MaxPathSize || std::ranges::any_of(paths, [](STPath const& path) { return path.size() > MaxPathLength; })) { @@ -372,7 +415,7 @@ Payment::doApply() AccountID const dstAccountID(ctx_.tx.getAccountID(sfDestination)); STAmount const dstAmount(ctx_.tx.getFieldAmount(sfAmount)); - bool const mptDirect = dstAmount.holds(); + bool const isDstMPT = dstAmount.holds(); STAmount const maxSourceAmount = getMaxSourceAmount(account_, dstAmount, sendMax); JLOG(j_.trace()) << "maxSourceAmount=" << maxSourceAmount.getFullText() @@ -399,11 +442,14 @@ Payment::doApply() view().update(sleDst); } - bool const ripple = (hasPaths || sendMax || !dstAmount.native()) && !mptDirect; + bool const MPTokensV2 = view().rules().enabled(featureMPTokensV2); + + // Direct MPT payment is handled by payment engine if MPTokensV2 is enabled + bool const ripple = (hasPaths || sendMax || !dstAmount.native()) && (!isDstMPT || MPTokensV2); if (ripple) { - // Ripple payment with at least one intermediate step and uses + // XRPL payment with at least one intermediate step and uses // transitive balances. // An account that requires authorization has two ways to get an @@ -466,7 +512,7 @@ Payment::doApply() terResult = tecPATH_DRY; return terResult; } - if (mptDirect) + if (isDstMPT) { JLOG(j_.trace()) << " dstAmount=" << dstAmount.getFullText(); auto const& mptIssue = dstAmount.get(); @@ -559,18 +605,23 @@ Payment::doApply() // This is the total reserve in drops. auto const reserve = view().fees().accountReserve(ownerCount); - // preFeeBalance_ is the balance on the sending account BEFORE the - // fees were charged. We want to make sure we have enough reserve - // to send. Allow final spend to use reserve for fee. - auto const mmm = std::max(reserve, ctx_.tx.getFieldAmount(sfFee).xrp()); + // In a delegated payment, the fee payer is the delegated account, + // not the source account (account_). + bool const accountIsPayer = (ctx_.tx.getFeePayer() == account_); - if (preFeeBalance_ < dstAmount.xrp() + mmm) + // preFeeBalance_ is the balance on the source account (account_) BEFORE the fees + // were charged. If source account is the fee payer, it must also cover the fee. + // The final spend may use the reserve to cover fees. + auto const minRequiredFunds = + accountIsPayer ? std::max(reserve, ctx_.tx.getFieldAmount(sfFee).xrp()) : reserve; + + if (preFeeBalance_ < dstAmount.xrp() + minRequiredFunds) { // Vote no. However the transaction might succeed, if applied in // a different order. JLOG(j_.trace()) << "Delay transaction: Insufficient funds: " << to_string(preFeeBalance_) - << " / " << to_string(dstAmount.xrp() + mmm) << " (" << to_string(reserve) - << ")"; + << " / " << to_string(dstAmount.xrp() + minRequiredFunds) << " (" + << to_string(reserve) << ")"; return tecUNFUNDED_PAYMENT; } @@ -626,4 +677,18 @@ Payment::doApply() return tesSUCCESS; } +void +Payment::visitInvariantEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ +} + +bool +Payment::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&) +{ + return true; +} + } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/payment_channel/PaymentChannelClaim.cpp b/src/libxrpl/tx/transactors/payment_channel/PaymentChannelClaim.cpp index 91de511883..256d1e87ca 100644 --- a/src/libxrpl/tx/transactors/payment_channel/PaymentChannelClaim.cpp +++ b/src/libxrpl/tx/transactors/payment_channel/PaymentChannelClaim.cpp @@ -1,14 +1,30 @@ -#include -#include -#include -#include -#include -#include -#include -#include #include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include namespace xrpl { @@ -185,4 +201,22 @@ PaymentChannelClaim::doApply() return tesSUCCESS; } +void +PaymentChannelClaim::visitInvariantEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ +} + +bool +PaymentChannelClaim::finalizeInvariants( + STTx const&, + TER, + XRPAmount, + ReadView const&, + beast::Journal const&) +{ + return true; +} } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/payment_channel/PaymentChannelCreate.cpp b/src/libxrpl/tx/transactors/payment_channel/PaymentChannelCreate.cpp index c12169fc0e..4ced2d10bc 100644 --- a/src/libxrpl/tx/transactors/payment_channel/PaymentChannelCreate.cpp +++ b/src/libxrpl/tx/transactors/payment_channel/PaymentChannelCreate.cpp @@ -1,13 +1,27 @@ +#include + #include +#include +#include #include #include #include #include #include #include +#include +#include #include +#include +#include +#include +#include +#include #include -#include +#include +#include + +#include namespace xrpl { @@ -123,7 +137,7 @@ PaymentChannelCreate::doApply() // Create PayChan in ledger. // - // Note that we we use the value from the sequence or ticket as the + // Note that we use the value from the sequence or ticket as the // payChan sequence. For more explanation see comments in SeqProxy.h. Keylet const payChanKeylet = keylet::payChan(account, dst, ctx_.tx.getSeqValue()); auto const slep = std::make_shared(payChanKeylet); @@ -172,4 +186,22 @@ PaymentChannelCreate::doApply() return tesSUCCESS; } +void +PaymentChannelCreate::visitInvariantEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ +} + +bool +PaymentChannelCreate::finalizeInvariants( + STTx const&, + TER, + XRPAmount, + ReadView const&, + beast::Journal const&) +{ + return true; +} } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/payment_channel/PaymentChannelFund.cpp b/src/libxrpl/tx/transactors/payment_channel/PaymentChannelFund.cpp index c42e8545f5..4e588eda1e 100644 --- a/src/libxrpl/tx/transactors/payment_channel/PaymentChannelFund.cpp +++ b/src/libxrpl/tx/transactors/payment_channel/PaymentChannelFund.cpp @@ -1,9 +1,24 @@ -#include -#include -#include #include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include namespace xrpl { @@ -91,4 +106,22 @@ PaymentChannelFund::doApply() return tesSUCCESS; } +void +PaymentChannelFund::visitInvariantEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ +} + +bool +PaymentChannelFund::finalizeInvariants( + STTx const&, + TER, + XRPAmount, + ReadView const&, + beast::Journal const&) +{ + return true; +} } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/permissioned_domain/PermissionedDomainDelete.cpp b/src/libxrpl/tx/transactors/permissioned_domain/PermissionedDomainDelete.cpp index 565631b3fd..87794b8dc2 100644 --- a/src/libxrpl/tx/transactors/permissioned_domain/PermissionedDomainDelete.cpp +++ b/src/libxrpl/tx/transactors/permissioned_domain/PermissionedDomainDelete.cpp @@ -1,8 +1,19 @@ -#include -#include -#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + namespace xrpl { NotTEC @@ -62,4 +73,23 @@ PermissionedDomainDelete::doApply() return tesSUCCESS; } +void +PermissionedDomainDelete::visitInvariantEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ +} + +bool +PermissionedDomainDelete::finalizeInvariants( + STTx const&, + TER, + XRPAmount, + ReadView const&, + beast::Journal const&) +{ + return true; +} + } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/permissioned_domain/PermissionedDomainSet.cpp b/src/libxrpl/tx/transactors/permissioned_domain/PermissionedDomainSet.cpp index 30f24241aa..e7853b529b 100644 --- a/src/libxrpl/tx/transactors/permissioned_domain/PermissionedDomainSet.cpp +++ b/src/libxrpl/tx/transactors/permissioned_domain/PermissionedDomainSet.cpp @@ -1,11 +1,24 @@ #include -// -#include + +#include +#include #include #include #include +#include +#include +#include +#include +#include +#include #include -#include +#include +#include +#include +#include + +#include +#include namespace xrpl { @@ -118,4 +131,23 @@ PermissionedDomainSet::doApply() return tesSUCCESS; } +void +PermissionedDomainSet::visitInvariantEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ +} + +bool +PermissionedDomainSet::finalizeInvariants( + STTx const&, + TER, + XRPAmount, + ReadView const&, + beast::Journal const&) +{ + return true; +} + } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/system/Batch.cpp b/src/libxrpl/tx/transactors/system/Batch.cpp index cd3ac9a16c..1218f0d59f 100644 --- a/src/libxrpl/tx/transactors/system/Batch.cpp +++ b/src/libxrpl/tx/transactors/system/Batch.cpp @@ -1,13 +1,32 @@ +#include + #include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include -#include -#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include namespace xrpl { @@ -262,10 +281,8 @@ Batch::preflight(PreflightContext const& ctx) return temINVALID; } - if (std::any_of( - disabledTxTypes.begin(), disabledTxTypes.end(), [txType](auto const& disabled) { - return txType == disabled; - })) + if (std::ranges::any_of( + disabledTxTypes, [txType](auto const& disabled) { return txType == disabled; })) { return temINVALID_INNER_BATCH; } @@ -500,4 +517,18 @@ Batch::doApply() return tesSUCCESS; } +void +Batch::visitInvariantEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ +} + +bool +Batch::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&) +{ + return true; +} + } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/system/Change.cpp b/src/libxrpl/tx/transactors/system/Change.cpp index a1d9183ca4..60dbb06a17 100644 --- a/src/libxrpl/tx/transactors/system/Change.cpp +++ b/src/libxrpl/tx/transactors/system/Change.cpp @@ -1,13 +1,31 @@ -#include -#include -#include -#include -#include -#include -#include #include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include namespace xrpl { @@ -156,7 +174,7 @@ Change::applyAmendment() STVector256 amendments = amendmentObject->getFieldV256(sfAmendments); - if (std::find(amendments.begin(), amendments.end(), amendment) != amendments.end()) + if (std::ranges::find(amendments, amendment) != amendments.end()) return tefALREADY; auto flags = ctx_.tx.getFlags(); @@ -392,4 +410,18 @@ Change::applyUNLModify() return tesSUCCESS; } +void +Change::visitInvariantEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ +} + +bool +Change::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&) +{ + return true; +} + } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/system/LedgerStateFix.cpp b/src/libxrpl/tx/transactors/system/LedgerStateFix.cpp index 0ce0720ba0..27cbad2e88 100644 --- a/src/libxrpl/tx/transactors/system/LedgerStateFix.cpp +++ b/src/libxrpl/tx/transactors/system/LedgerStateFix.cpp @@ -1,10 +1,19 @@ -#include -#include -#include -#include -#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + namespace xrpl { NotTEC @@ -63,4 +72,23 @@ LedgerStateFix::doApply() return tecINTERNAL; // LCOV_EXCL_LINE } +void +LedgerStateFix::visitInvariantEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ +} + +bool +LedgerStateFix::finalizeInvariants( + STTx const&, + TER, + XRPAmount, + ReadView const&, + beast::Journal const&) +{ + return true; +} + } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/system/TicketCreate.cpp b/src/libxrpl/tx/transactors/system/TicketCreate.cpp index c4e281c357..fd6b947c5e 100644 --- a/src/libxrpl/tx/transactors/system/TicketCreate.cpp +++ b/src/libxrpl/tx/transactors/system/TicketCreate.cpp @@ -1,10 +1,23 @@ -#include +#include + +#include +#include +#include +#include #include #include -#include #include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include namespace xrpl { @@ -121,4 +134,23 @@ TicketCreate::doApply() return tesSUCCESS; } +void +TicketCreate::visitInvariantEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ +} + +bool +TicketCreate::finalizeInvariants( + STTx const&, + TER, + XRPAmount, + ReadView const&, + beast::Journal const&) +{ + return true; +} + } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/token/Clawback.cpp b/src/libxrpl/tx/transactors/token/Clawback.cpp index 57175ba427..c9e6d32f6c 100644 --- a/src/libxrpl/tx/transactors/token/Clawback.cpp +++ b/src/libxrpl/tx/transactors/token/Clawback.cpp @@ -1,12 +1,31 @@ -#include +#include + +#include +#include #include #include +#include +#include #include #include +#include +#include #include +#include #include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include namespace xrpl { @@ -95,7 +114,7 @@ preclaimHelper( return tecNO_PERMISSION; auto const sleRippleState = - ctx.view.read(keylet::line(holder, issuer, clawAmount.getCurrency())); + ctx.view.read(keylet::line(holder, issuer, clawAmount.get().currency)); if (!sleRippleState) return tecNO_LINE; @@ -118,7 +137,8 @@ preclaimHelper( // We can't directly check the balance of trustline because // the available balance of a trustline is prone to new changes (eg. // XLS-34). So we must use `accountHolds`. - if (accountHolds(ctx.view, holder, clawAmount.getCurrency(), issuer, fhIGNORE_FREEZE, ctx.j) <= + if (accountHolds( + ctx.view, holder, clawAmount.get().currency, issuer, fhIGNORE_FREEZE, ctx.j) <= beast::zero) return tecINSUFFICIENT_FUNDS; @@ -199,7 +219,7 @@ applyHelper(ApplyContext& ctx) AccountID const holder = clawAmount.getIssuer(); // cannot be reference // Replace the `issuer` field with issuer's account - clawAmount.setIssuer(issuer); + clawAmount.get().account = issuer; if (holder == issuer) return tecINTERNAL; // LCOV_EXCL_LINE @@ -207,12 +227,12 @@ applyHelper(ApplyContext& ctx) STAmount const spendableAmount = accountHolds( ctx.view(), holder, - clawAmount.getCurrency(), + clawAmount.get().currency, clawAmount.getIssuer(), fhIGNORE_FREEZE, ctx.journal); - return rippleCredit( + return directSendNoFee( ctx.view(), holder, issuer, std::min(spendableAmount, clawAmount), true, ctx.journal); } @@ -233,7 +253,7 @@ applyHelper(ApplyContext& ctx) ahIGNORE_AUTH, ctx.journal); - return rippleCredit( + return directSendNoFee( ctx.view(), holder, issuer, @@ -250,4 +270,18 @@ Clawback::doApply() ctx_.tx[sfAmount].asset().value()); } +void +Clawback::visitInvariantEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ +} + +bool +Clawback::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&) +{ + return true; +} + } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/token/MPTokenAuthorize.cpp b/src/libxrpl/tx/transactors/token/MPTokenAuthorize.cpp index 3ddc6d2c05..c0fbb7f10b 100644 --- a/src/libxrpl/tx/transactors/token/MPTokenAuthorize.cpp +++ b/src/libxrpl/tx/transactors/token/MPTokenAuthorize.cpp @@ -1,11 +1,21 @@ -#include +#include + +#include #include -#include #include #include +#include +#include +#include +#include +#include +#include #include -#include -#include +#include +#include + +#include +#include namespace xrpl { @@ -132,32 +142,6 @@ MPTokenAuthorize::preclaim(PreclaimContext const& ctx) return tesSUCCESS; } -TER -MPTokenAuthorize::createMPToken( - ApplyView& view, - MPTID const& mptIssuanceID, - AccountID const& account, - std::uint32_t const flags) -{ - auto const mptokenKey = keylet::mptoken(mptIssuanceID, account); - - auto const ownerNode = - view.dirInsert(keylet::ownerDir(account), mptokenKey, describeOwnerDir(account)); - - if (!ownerNode) - return tecDIR_FULL; // LCOV_EXCL_LINE - - auto mptoken = std::make_shared(mptokenKey); - (*mptoken)[sfAccount] = account; - (*mptoken)[sfMPTokenIssuanceID] = mptIssuanceID; - (*mptoken)[sfFlags] = flags; - (*mptoken)[sfOwnerNode] = *ownerNode; - - view.insert(mptoken); - - return tesSUCCESS; -} - TER MPTokenAuthorize::doApply() { @@ -172,4 +156,23 @@ MPTokenAuthorize::doApply() tx[~sfHolder]); } +void +MPTokenAuthorize::visitInvariantEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ +} + +bool +MPTokenAuthorize::finalizeInvariants( + STTx const&, + TER, + XRPAmount, + ReadView const&, + beast::Journal const&) +{ + return true; +} + } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/token/MPTokenIssuanceCreate.cpp b/src/libxrpl/tx/transactors/token/MPTokenIssuanceCreate.cpp index 6bcb1175e8..42fc037431 100644 --- a/src/libxrpl/tx/transactors/token/MPTokenIssuanceCreate.cpp +++ b/src/libxrpl/tx/transactors/token/MPTokenIssuanceCreate.cpp @@ -1,9 +1,27 @@ -#include +#include + +#include +#include +#include +#include +#include +#include #include #include #include +#include +#include +#include +#include +#include +#include #include -#include +#include +#include +#include + +#include +#include namespace xrpl { @@ -154,4 +172,23 @@ MPTokenIssuanceCreate::doApply() return result ? tesSUCCESS : result.error(); } +void +MPTokenIssuanceCreate::visitInvariantEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ +} + +bool +MPTokenIssuanceCreate::finalizeInvariants( + STTx const&, + TER, + XRPAmount, + ReadView const&, + beast::Journal const&) +{ + return true; +} + } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/token/MPTokenIssuanceDestroy.cpp b/src/libxrpl/tx/transactors/token/MPTokenIssuanceDestroy.cpp index 8ec1f37886..80b185641a 100644 --- a/src/libxrpl/tx/transactors/token/MPTokenIssuanceDestroy.cpp +++ b/src/libxrpl/tx/transactors/token/MPTokenIssuanceDestroy.cpp @@ -1,9 +1,16 @@ -#include -#include -#include -#include #include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + namespace xrpl { NotTEC @@ -51,4 +58,23 @@ MPTokenIssuanceDestroy::doApply() return tesSUCCESS; } +void +MPTokenIssuanceDestroy::visitInvariantEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ +} + +bool +MPTokenIssuanceDestroy::finalizeInvariants( + STTx const&, + TER, + XRPAmount, + ReadView const&, + beast::Journal const&) +{ + return true; +} + } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/token/MPTokenIssuanceSet.cpp b/src/libxrpl/tx/transactors/token/MPTokenIssuanceSet.cpp index fc09a53ae1..540dd6ef0c 100644 --- a/src/libxrpl/tx/transactors/token/MPTokenIssuanceSet.cpp +++ b/src/libxrpl/tx/transactors/token/MPTokenIssuanceSet.cpp @@ -1,9 +1,31 @@ -#include -#include -#include -#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + namespace xrpl { bool @@ -31,12 +53,24 @@ struct MPTMutabilityFlags }; static constexpr std::array mptMutabilityFlags = { - {{tmfMPTSetCanLock, tmfMPTClearCanLock, lsmfMPTCanMutateCanLock}, - {tmfMPTSetRequireAuth, tmfMPTClearRequireAuth, lsmfMPTCanMutateRequireAuth}, - {tmfMPTSetCanEscrow, tmfMPTClearCanEscrow, lsmfMPTCanMutateCanEscrow}, - {tmfMPTSetCanTrade, tmfMPTClearCanTrade, lsmfMPTCanMutateCanTrade}, - {tmfMPTSetCanTransfer, tmfMPTClearCanTransfer, lsmfMPTCanMutateCanTransfer}, - {tmfMPTSetCanClawback, tmfMPTClearCanClawback, lsmfMPTCanMutateCanClawback}}}; + {{.setFlag = tmfMPTSetCanLock, + .clearFlag = tmfMPTClearCanLock, + .canMutateFlag = lsmfMPTCanMutateCanLock}, + {.setFlag = tmfMPTSetRequireAuth, + .clearFlag = tmfMPTClearRequireAuth, + .canMutateFlag = lsmfMPTCanMutateRequireAuth}, + {.setFlag = tmfMPTSetCanEscrow, + .clearFlag = tmfMPTClearCanEscrow, + .canMutateFlag = lsmfMPTCanMutateCanEscrow}, + {.setFlag = tmfMPTSetCanTrade, + .clearFlag = tmfMPTClearCanTrade, + .canMutateFlag = lsmfMPTCanMutateCanTrade}, + {.setFlag = tmfMPTSetCanTransfer, + .clearFlag = tmfMPTClearCanTransfer, + .canMutateFlag = lsmfMPTCanMutateCanTransfer}, + {.setFlag = tmfMPTSetCanClawback, + .clearFlag = tmfMPTClearCanClawback, + .canMutateFlag = lsmfMPTCanMutateCanClawback}}}; NotTEC MPTokenIssuanceSet::preflight(PreflightContext const& ctx) @@ -92,12 +126,9 @@ MPTokenIssuanceSet::preflight(PreflightContext const& ctx) return temINVALID_FLAG; // Can not set and clear the same flag - if (std::any_of( - mptMutabilityFlags.begin(), - mptMutabilityFlags.end(), - [mutableFlags](auto const& f) { - return (*mutableFlags & f.setFlag) && (*mutableFlags & f.clearFlag); - })) + if (std::ranges::any_of(mptMutabilityFlags, [mutableFlags](auto const& f) { + return (*mutableFlags & f.setFlag) && (*mutableFlags & f.clearFlag); + })) return temINVALID_FLAG; // Trying to set a non-zero TransferFee and clear MPTCanTransfer @@ -205,13 +236,16 @@ MPTokenIssuanceSet::preclaim(PreclaimContext const& ctx) if (auto const mutableFlags = ctx.tx[~sfMutableFlags]) { - if (std::any_of( - mptMutabilityFlags.begin(), - mptMutabilityFlags.end(), - [mutableFlags, &isMutableFlag](auto const& f) { - return !isMutableFlag(f.canMutateFlag) && - ((*mutableFlags & (f.setFlag | f.clearFlag))); - })) + if (std::ranges::any_of(mptMutabilityFlags, [mutableFlags, &isMutableFlag](auto const& f) { + return !isMutableFlag(f.canMutateFlag) && + ((*mutableFlags & (f.setFlag | f.clearFlag))); + })) + return tecNO_PERMISSION; + + // Clearing lsfMPTRequireAuth is invalid when the issuance already has + // a DomainID set, because a DomainID requires RequireAuth to be active. + if ((*mutableFlags & tmfMPTClearRequireAuth) != 0u && + sleMptIssuance->isFieldPresent(sfDomainID)) return tecNO_PERMISSION; } @@ -343,4 +377,23 @@ MPTokenIssuanceSet::doApply() return tesSUCCESS; } +void +MPTokenIssuanceSet::visitInvariantEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ +} + +bool +MPTokenIssuanceSet::finalizeInvariants( + STTx const&, + TER, + XRPAmount, + ReadView const&, + beast::Journal const&) +{ + return true; +} + } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/token/TrustSet.cpp b/src/libxrpl/tx/transactors/token/TrustSet.cpp index feefa6a7ac..1610b2e5b9 100644 --- a/src/libxrpl/tx/transactors/token/TrustSet.cpp +++ b/src/libxrpl/tx/transactors/token/TrustSet.cpp @@ -1,14 +1,34 @@ -#include +#include + +#include +#include +#include +#include +#include #include +#include #include #include +#include #include #include +#include +#include #include #include +#include +#include +#include #include -#include -#include +#include +#include +#include +#include +#include + +#include +#include +#include namespace { @@ -82,7 +102,7 @@ TrustSet::preflight(PreflightContext const& ctx) return temBAD_LIMIT; } - if (badCurrency() == saLimitAmount.getCurrency()) + if (badCurrency() == saLimitAmount.get().currency) { JLOG(j.trace()) << "Malformed transaction: specifies XRP as IOU"; return temBAD_CURRENCY; @@ -135,7 +155,8 @@ TrustSet::checkPermission(ReadView const& view, STTx const& tx) auto const saLimitAmount = tx.getFieldAmount(sfLimitAmount); auto const sleRippleState = view.read( - keylet::line(tx[sfAccount], saLimitAmount.getIssuer(), saLimitAmount.getCurrency())); + keylet::line( + tx[sfAccount], saLimitAmount.getIssuer(), saLimitAmount.get().currency)); // if the trustline does not exist, granular permissions are // not allowed to create trustline @@ -159,7 +180,7 @@ TrustSet::checkPermission(ReadView const& view, STTx const& tx) : sleRippleState->getFieldAmount(sfLowLimit); STAmount saLimitAllow = saLimitAmount; - saLimitAllow.setIssuer(tx[sfAccount]); + saLimitAllow.get().account = tx[sfAccount]; if (curLimit != saLimitAllow) return terNO_DELEGATE_PERMISSION; @@ -188,7 +209,7 @@ TrustSet::preclaim(PreclaimContext const& ctx) auto const saLimitAmount = ctx.tx[sfLimitAmount]; - auto const currency = saLimitAmount.getCurrency(); + auto const currency = saLimitAmount.get().currency; auto const uDstAccountID = saLimitAmount.getIssuer(); if (id == uDstAccountID) @@ -241,7 +262,7 @@ TrustSet::preclaim(PreclaimContext const& ctx) { return tecAMM_EMPTY; } - if (lpTokens.getCurrency() != saLimitAmount.getCurrency()) + if (lpTokens.get().currency != saLimitAmount.get().currency) { return tecNO_PERMISSION; } @@ -317,7 +338,7 @@ TrustSet::doApply() bool const bQualityIn(ctx_.tx.isFieldPresent(sfQualityIn)); bool const bQualityOut(ctx_.tx.isFieldPresent(sfQualityOut)); - Currency const currency(saLimitAmount.getCurrency()); + Currency const currency(saLimitAmount.get().currency); AccountID const uDstAccountID(saLimitAmount.getIssuer()); // true, if current is high account. @@ -336,7 +357,7 @@ TrustSet::doApply() // items. // // We do this because being able to exchange currencies, - // which needs trust lines, is a powerful Ripple feature. + // which needs trust lines, is a powerful XRPL feature. // So we want to make it easy for a gateway to fund the // accounts of its users without fear of being tricked. // @@ -377,7 +398,7 @@ TrustSet::doApply() } STAmount saLimitAllow = saLimitAmount; - saLimitAllow.setIssuer(account_); + saLimitAllow.get().account = account_; SLE::pointer const sleRippleState = view().peek(keylet::line(account_, uDstAccountID, currency)); @@ -654,4 +675,18 @@ TrustSet::doApply() return terResult; } +void +TrustSet::visitInvariantEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ +} + +bool +TrustSet::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&) +{ + return true; +} + } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/vault/VaultClawback.cpp b/src/libxrpl/tx/transactors/vault/VaultClawback.cpp index 6a4d6a579e..da22b82578 100644 --- a/src/libxrpl/tx/transactors/vault/VaultClawback.cpp +++ b/src/libxrpl/tx/transactors/vault/VaultClawback.cpp @@ -1,18 +1,36 @@ +#include + +#include +#include +#include +#include +#include #include -#include +#include #include #include #include +#include +#include +#include +#include +#include #include #include #include -#include +#include +#include // IWYU pragma: keep #include +#include #include -#include -#include +#include +#include +#include +#include #include +#include +#include namespace xrpl { NotTEC @@ -162,44 +180,43 @@ VaultClawback::preclaim(PreclaimContext const& ctx) return tecNO_PERMISSION; } - return std::visit( - [&](TIss const& issue) -> TER { - if constexpr (std::is_same_v) - { - auto const mptIssue = ctx.view.read(keylet::mptIssuance(issue.getMptID())); - if (mptIssue == nullptr) - return tecOBJECT_NOT_FOUND; + return vaultAsset.visit( + [&](MPTIssue const& issue) -> TER { + auto const mptIssue = ctx.view.read(keylet::mptIssuance(issue.getMptID())); + if (mptIssue == nullptr) + return tecOBJECT_NOT_FOUND; - std::uint32_t const issueFlags = mptIssue->getFieldU32(sfFlags); - if (!(issueFlags & lsfMPTCanClawback)) - { - JLOG(ctx.j.debug()) << "VaultClawback: cannot clawback " - "MPT vault asset."; - return tecNO_PERMISSION; - } - } - else if constexpr (std::is_same_v) + std::uint32_t const issueFlags = mptIssue->getFieldU32(sfFlags); + if ((issueFlags & lsfMPTCanClawback) == 0u) { - auto const issuerSle = ctx.view.read(keylet::account(account)); - if (!issuerSle) - { - // LCOV_EXCL_START - JLOG(ctx.j.error()) << "VaultClawback: missing submitter account."; - return tefINTERNAL; - // LCOV_EXCL_STOP - } - - std::uint32_t const issuerFlags = issuerSle->getFieldU32(sfFlags); - if (!(issuerFlags & lsfAllowTrustLineClawback) || (issuerFlags & lsfNoFreeze)) - { - JLOG(ctx.j.debug()) << "VaultClawback: cannot clawback " - "IOU vault asset."; - return tecNO_PERMISSION; - } + JLOG(ctx.j.debug()) << "VaultClawback: cannot clawback " + "MPT vault asset."; + return tecNO_PERMISSION; } + return tesSUCCESS; }, - vaultAsset.value()); + [&](Issue const&) -> TER { + auto const issuerSle = ctx.view.read(keylet::account(account)); + if (!issuerSle) + { + // LCOV_EXCL_START + JLOG(ctx.j.error()) << "VaultClawback: missing submitter account."; + return tefINTERNAL; + // LCOV_EXCL_STOP + } + + std::uint32_t const issuerFlags = issuerSle->getFieldU32(sfFlags); + if (((issuerFlags & lsfAllowTrustLineClawback) == 0u) || + ((issuerFlags & lsfNoFreeze) != 0u)) + { + JLOG(ctx.j.debug()) << "VaultClawback: cannot clawback " + "IOU vault asset."; + return tecNO_PERMISSION; + } + + return tesSUCCESS; + }); } // Invalid asset @@ -226,7 +243,11 @@ VaultClawback::assetsToClawback( auto const mptIssuanceID = *vault->at(sfShareMPTID); MPTIssue const share{mptIssuanceID}; - if (clawbackAmount == beast::zero) + // Pre-fixSecurity3_1_3: zero-amount clawback returned early without + // clamping to assetsAvailable, allowing more assets to be recovered + // than available when there was an outstanding loan. Retained for + // ledger replay compatibility. + if (!ctx_.view().rules().enabled(fixSecurity3_1_3) && clawbackAmount == beast::zero) { auto const sharesDestroyed = accountHolds( view(), @@ -243,22 +264,40 @@ VaultClawback::assetsToClawback( } STAmount sharesDestroyed; - STAmount assetsRecovered = clawbackAmount; + STAmount assetsRecovered; + try { + if (clawbackAmount == beast::zero) + { + sharesDestroyed = accountHolds( + view(), + holder, + share, + FreezeHandling::fhIGNORE_FREEZE, + AuthHandling::ahIGNORE_AUTH, + j_); + auto const maybeAssets = + sharesToAssetsWithdraw(vault, sleShareIssuance, sharesDestroyed); + if (!maybeAssets) + return Unexpected(tecINTERNAL); // LCOV_EXCL_LINE + + assetsRecovered = *maybeAssets; + } + else { auto const maybeShares = - assetsToSharesWithdraw(vault, sleShareIssuance, assetsRecovered); + assetsToSharesWithdraw(vault, sleShareIssuance, clawbackAmount); if (!maybeShares) return Unexpected(tecINTERNAL); // LCOV_EXCL_LINE sharesDestroyed = *maybeShares; + + auto const maybeAssets = + sharesToAssetsWithdraw(vault, sleShareIssuance, sharesDestroyed); + if (!maybeAssets) + return Unexpected(tecINTERNAL); // LCOV_EXCL_LINE + assetsRecovered = *maybeAssets; } - - auto const maybeAssets = sharesToAssetsWithdraw(vault, sleShareIssuance, sharesDestroyed); - if (!maybeAssets) - return Unexpected(tecINTERNAL); // LCOV_EXCL_LINE - assetsRecovered = *maybeAssets; - // Clamp to maximum. if (assetsRecovered > *assetsAvailable) { @@ -431,4 +470,23 @@ VaultClawback::doApply() return tesSUCCESS; } +void +VaultClawback::visitInvariantEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ +} + +bool +VaultClawback::finalizeInvariants( + STTx const&, + TER, + XRPAmount, + ReadView const&, + beast::Journal const&) +{ + return true; +} + } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/vault/VaultCreate.cpp b/src/libxrpl/tx/transactors/vault/VaultCreate.cpp index 02f8ecb57b..cc28abfe65 100644 --- a/src/libxrpl/tx/transactors/vault/VaultCreate.cpp +++ b/src/libxrpl/tx/transactors/vault/VaultCreate.cpp @@ -1,3 +1,8 @@ +#include + +#include +#include +#include #include #include #include @@ -6,16 +11,23 @@ #include #include #include +#include #include #include #include -#include +#include +#include // IWYU pragma: keep #include +#include #include #include -#include +#include +#include #include -#include + +#include +#include +#include namespace xrpl { @@ -181,8 +193,10 @@ VaultCreate::doApply() .sequence = 1, .flags = mptFlags, .assetScale = scale, + .transferFee = std::nullopt, .metadata = tx[~sfMPTokenMetadata], .domainId = tx[~sfDomainID], + .mutableFlags = std::nullopt, }); if (!maybeShare) return maybeShare.error(); // LCOV_EXCL_LINE @@ -235,4 +249,18 @@ VaultCreate::doApply() return tesSUCCESS; } +void +VaultCreate::visitInvariantEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ +} + +bool +VaultCreate::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&) +{ + return true; +} + } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/vault/VaultDelete.cpp b/src/libxrpl/tx/transactors/vault/VaultDelete.cpp index 5a60c0032c..e4542d524f 100644 --- a/src/libxrpl/tx/transactors/vault/VaultDelete.cpp +++ b/src/libxrpl/tx/transactors/vault/VaultDelete.cpp @@ -1,14 +1,23 @@ -#include +#include + +#include +#include +#include #include #include #include -#include +#include +#include #include -#include -#include +#include +#include +#include // IWYU pragma: keep +#include #include -#include -#include +#include +#include + +#include namespace xrpl { @@ -203,4 +212,18 @@ VaultDelete::doApply() return tesSUCCESS; } +void +VaultDelete::visitInvariantEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ +} + +bool +VaultDelete::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&) +{ + return true; +} + } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/vault/VaultDeposit.cpp b/src/libxrpl/tx/transactors/vault/VaultDeposit.cpp index 04b249d211..fbb8908ddb 100644 --- a/src/libxrpl/tx/transactors/vault/VaultDeposit.cpp +++ b/src/libxrpl/tx/transactors/vault/VaultDeposit.cpp @@ -1,19 +1,27 @@ -#include +#include + +#include +#include +#include #include #include #include #include -#include #include +#include #include #include #include -#include +#include +#include // IWYU pragma: keep #include +#include #include -#include -#include -#include +#include +#include + +#include +#include namespace xrpl { @@ -270,4 +278,23 @@ VaultDeposit::doApply() return tesSUCCESS; } +void +VaultDeposit::visitInvariantEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ +} + +bool +VaultDeposit::finalizeInvariants( + STTx const&, + TER, + XRPAmount, + ReadView const&, + beast::Journal const&) +{ + return true; +} + } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/vault/VaultSet.cpp b/src/libxrpl/tx/transactors/vault/VaultSet.cpp index b54389bcde..a5b0b20699 100644 --- a/src/libxrpl/tx/transactors/vault/VaultSet.cpp +++ b/src/libxrpl/tx/transactors/vault/VaultSet.cpp @@ -1,13 +1,21 @@ -#include -#include +#include + +#include +#include #include #include +#include +#include #include -#include +#include +#include // IWYU pragma: keep #include +#include #include -#include -#include +#include +#include + +#include namespace xrpl { @@ -171,4 +179,18 @@ VaultSet::doApply() return tesSUCCESS; } +void +VaultSet::visitInvariantEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ +} + +bool +VaultSet::finalizeInvariants(STTx const&, TER, XRPAmount, ReadView const&, beast::Journal const&) +{ + return true; +} + } // namespace xrpl diff --git a/src/libxrpl/tx/transactors/vault/VaultWithdraw.cpp b/src/libxrpl/tx/transactors/vault/VaultWithdraw.cpp index f6b8c98788..3e608733b6 100644 --- a/src/libxrpl/tx/transactors/vault/VaultWithdraw.cpp +++ b/src/libxrpl/tx/transactors/vault/VaultWithdraw.cpp @@ -1,15 +1,28 @@ +#include + +#include +#include +#include +#include #include -#include #include #include #include #include +#include +#include +#include #include -#include +#include +#include // IWYU pragma: keep #include +#include #include -#include -#include +#include +#include + +#include +#include namespace xrpl { @@ -128,7 +141,7 @@ VaultWithdraw::preclaim(PreclaimContext const& ctx) // Cannot return shares to the vault, if the underlying asset was frozen for // the submitter - if (auto const ret = checkFrozen(ctx.view, account, vaultShare)) + if (auto const ret = checkFrozen(ctx.view, account, Asset{vaultShare})) return ret; return tesSUCCESS; @@ -282,4 +295,23 @@ VaultWithdraw::doApply() view(), ctx_.tx, account_, dstAcct, vaultAccount, preFeeBalance_, assetsWithdrawn, j_); } +void +VaultWithdraw::visitInvariantEntry( + bool, + std::shared_ptr const&, + std::shared_ptr const&) +{ +} + +bool +VaultWithdraw::finalizeInvariants( + STTx const&, + TER, + XRPAmount, + ReadView const&, + beast::Journal const&) +{ + return true; +} + } // namespace xrpl diff --git a/src/test/README.md b/src/test/README.md index b012607f58..179d75941b 100644 --- a/src/test/README.md +++ b/src/test/README.md @@ -2,10 +2,10 @@ ## Running Tests -Unit tests are bundled in the `rippled` executable and can be executed using the +Unit tests are bundled in the `xrpld` executable and can be executed using the `--unittest` parameter. Without any arguments to this option, all non-manual unit tests will be executed. If you want to run one or more manual tests, you -must specify it by suite or full-name (e.g. `ripple.app.NoRippleCheckLimits` or +must specify it by suite or full-name (e.g. `xrpl.app.NoRippleCheckLimits` or just `NoRippleCheckLimits`). More than one suite or group of suites can be specified as a comma separated diff --git a/src/test/app/AMMCalc_test.cpp b/src/test/app/AMMCalc_test.cpp index c3091a166d..6c32c6118d 100644 --- a/src/test/app/AMMCalc_test.cpp +++ b/src/test/app/AMMCalc_test.cpp @@ -1,12 +1,35 @@ -#include +#include +#include +#include + +#include +#include +#include +#include +#include #include -#include +#include +#include +#include -#include +#include +#include +#include +#include -namespace xrpl { -namespace test { +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl::test { /** AMM Calculator. Uses AMM formulas to simulate the payment engine * expected results. Assuming the formulas are correct some unit-tests can @@ -164,9 +187,7 @@ class AMMCalc_test : public beast::unit_test::suite static std::string toString(STAmount const& a) { - std::stringstream str; - str << a.getText() << "/" << to_string(a.issue().currency); - return str.str(); + return (boost::format("%s/%s") % a.getText() % to_string(a.get().currency)).str(); } static STAmount @@ -175,8 +196,8 @@ class AMMCalc_test : public beast::unit_test::suite if (a == b) return amt; if (amt.native()) - return toSTAmount(mulRatio(amt.xrp(), a, b, round), amt.issue()); - return toSTAmount(mulRatio(amt.iou(), a, b, round), amt.issue()); + return toSTAmount(mulRatio(amt.xrp(), a, b, round), amt.asset()); + return toSTAmount(mulRatio(amt.iou(), a, b, round), amt.asset()); } static void @@ -191,9 +212,9 @@ class AMMCalc_test : public beast::unit_test::suite STAmount sin{}; int limitingStep = vp.size(); STAmount limitStepOut{}; - auto transferRate = [&](auto const& amt) { - auto const currency = to_string(amt.issue().currency); - return rates.find(currency) != rates.end() ? rates.at(currency) : QUALITY_ONE; + auto transferRate = [&](STAmount const& amt) { + auto const currency = to_string(amt.get().currency); + return rates.contains(currency) ? rates.at(currency) : QUALITY_ONE; }; // swap out reverse sin = sout; @@ -254,9 +275,9 @@ class AMMCalc_test : public beast::unit_test::suite STAmount sout{}; int limitingStep = 0; STAmount limitStepIn{}; - auto transferRate = [&](auto const& amt) { - auto const currency = to_string(amt.issue().currency); - return rates.find(currency) != rates.end() ? rates.at(currency) : QUALITY_ONE; + auto transferRate = [&](STAmount const& amt) { + auto const currency = to_string(amt.get().currency); + return rates.contains(currency) ? rates.at(currency) : QUALITY_ONE; }; // Swap in forward for (auto it = vp.begin(); it != vp.end(); ++it) @@ -436,5 +457,4 @@ class AMMCalc_test : public beast::unit_test::suite BEAST_DEFINE_TESTSUITE_MANUAL(AMMCalc, app, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/app/AMMClawbackMPT_test.cpp b/src/test/app/AMMClawbackMPT_test.cpp new file mode 100644 index 0000000000..393c0b062c --- /dev/null +++ b/src/test/app/AMMClawbackMPT_test.cpp @@ -0,0 +1,1840 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace xrpl::test { +class AMMClawbackMPT_test : public beast::unit_test::suite +{ + void + testInvalidRequest(FeatureBitset features) + { + testcase("test invalid request"); + using namespace jtx; + + for (auto const& feature : {features, features - featureSingleAssetVault}) + { + Env env(*this, feature); + Account const gw{"gateway"}; + Account const alice{"alice"}; + Account const bob{"bob"}; + env.fund(XRP(100000), gw, alice, bob); + env.close(); + + env(fset(gw, asfAllowTrustLineClawback)); + env.close(); + + MPT const BTC = MPTTester( + {.env = env, + .issuer = gw, + .holders = {alice}, + .pay = 40'000, + .flags = tfMPTCanClawback | MPTDEXFlags}); + + auto const USD = gw["USD"]; + env.trust(USD(10000), alice); + env(pay(gw, alice, USD(100))); + env.close(); + + AMM amm(env, gw, BTC(100), USD(100)); + + // holder does not exist + env(amm::ammClawback(gw, Account("unknown"), USD, BTC, std::nullopt), + ter(terNO_ACCOUNT)); + + // can not clawback from self. + env(amm::ammClawback(gw, gw, USD, BTC, std::nullopt), ter(temMALFORMED)); + + // provided Asset does not match issuer gw + { + env(amm::ammClawback( + gw, alice, Issue{gw["USD"].currency, alice.id()}, BTC, std::nullopt), + ter(temMALFORMED)); + env(amm::ammClawback(gw, alice, MPTIssue{makeMptID(1, alice)}, USD, std::nullopt), + ter(temMALFORMED)); + } + + // Amount does not match asset + { + env(amm::ammClawback( + gw, alice, USD, BTC, STAmount{Issue{gw["USD"].currency, alice.id()}, 1}), + ter(temBAD_AMOUNT)); + env(amm::ammClawback( + gw, alice, BTC, USD, STAmount{MPTIssue{makeMptID(1, alice)}, 10}), + ter(temBAD_AMOUNT)); + } + + // Amount is not greater than 0 + { + env(amm::ammClawback(gw, alice, BTC, USD, BTC(-1)), ter(temBAD_AMOUNT)); + env(amm::ammClawback(gw, alice, BTC, USD, BTC(0)), ter(temBAD_AMOUNT)); + } + + // clawback from account not holding lptoken + env(amm::ammClawback(gw, bob, BTC, USD, BTC(1000)), ter(tecAMM_BALANCE)); + + // can not perform regular claw from amm pool + { + Issue const usd(USD.currency, amm.ammAccount()); + auto amount = amountFromString(usd, "10"); + auto const err = + feature[featureSingleAssetVault] ? tecPSEUDO_ACCOUNT : tecAMM_ACCOUNT; + env(claw(gw, amount), ter(err)); + } + + // AMM does not exist + { + // withdraw all tokens will delete the AMM + amm.withdrawAll(gw); + BEAST_EXPECT(!amm.ammExists()); + env.close(); + env(amm::ammClawback(gw, alice, USD, BTC, std::nullopt), ter(terNO_AMM)); + } + } + + // tfMPTCanClawback is not enabled + { + Env env(*this, features); + Account const gw{"gateway"}; + Account const alice{"alice"}; + env.fund(XRP(100000), gw, alice); + env.close(); + + env(fset(gw, asfAllowTrustLineClawback)); + env.close(); + + MPT const BTC = + MPTTester({.env = env, .issuer = gw, .holders = {alice}, .pay = 40'000}); + + auto const USD = gw["USD"]; + env.trust(USD(10000), alice); + env(pay(gw, alice, USD(10000))); + env.close(); + + AMM amm(env, gw, BTC(100), USD(100)); + env.close(); + amm.deposit(alice, 1'000); + env.close(); + + // can not clawback when tfMPTCanClawback is not enabled + env(amm::ammClawback(gw, alice, BTC, USD, std::nullopt), ter(tecNO_PERMISSION)); + } + + // can not claw with tfClawTwoAssets if the assets are not issued by the + // same issuer + { + Env env(*this, features); + Account const gw{"gateway"}; + Account const gw2{"gateway2"}; + Account const alice{"alice"}; + env.fund(XRP(100000), gw, gw2, alice); + env.close(); + + env(fset(gw, asfAllowTrustLineClawback)); + env.close(); + + auto const USD = gw["USD"]; + env.trust(USD(10000), alice); + env(pay(gw, alice, USD(10000))); + env.close(); + + // todo: check tfMPTCanTransfer in xrpl.org + MPT const BTC = MPTTester( + {.env = env, + .issuer = gw2, + .holders = {alice}, + .pay = 40'000, + .flags = tfMPTCanClawback | MPTDEXFlags}); + + AMM const amm(env, alice, BTC(100), USD(100)); + env.close(); + + { + // Return temINVALID_FLAG because the issuer set + // tfClawTwoAssets, but the issuer only issues USD in the pool. + // The issuer is not allowed to set tfClawTwoAssets flag if he + // did not issue both assets in the pool. + env(amm::ammClawback(gw, alice, USD, BTC, std::nullopt), + txflags(tfClawTwoAssets), + ter(temINVALID_FLAG)); + } + } + + // Test if the issuer did not set asfAllowTrustLineClawback, but the MPT + // is set tfMPTCanClawback, the issuer can claw MPT. + { + Env env(*this, features); + Account const gw{"gateway"}; + Account const alice{"alice"}; + env.fund(XRP(10000), gw, alice); + env.close(); + + MPT const BTC = MPTTester( + {.env = env, + .issuer = gw, + .holders = {alice}, + .pay = 40'000, + .flags = tfMPTCanClawback | MPTDEXFlags}); + + AMM const amm(env, alice, BTC(100), XRP(100)); + env.close(); + + // If asfAllowTrustLineClawback is not set, the issuer can + // still claw MPT because the MPT's tfMPTCanClawback is set. + env(amm::ammClawback(gw, alice, BTC, XRP, std::nullopt)); + } + } + + void + testFeatureDisabled(FeatureBitset features) + { + testcase("test feature disabled."); + using namespace jtx; + Env env{*this, features}; + Account const gw("gateway"), alice("alice"); + env.fund(XRP(30'000), gw, alice); + env.close(); + env(fset(gw, asfAllowTrustLineClawback)); + env.close(); + + MPT const BTC = MPTTester( + {.env = env, + .issuer = gw, + .holders = {alice}, + .pay = 10'000, + .flags = tfMPTCanClawback | MPTDEXFlags}); + + AMM const amm(env, alice, XRP(1'000), BTC(1'000)); + + // disable featureAMMClawback + env.disableFeature(featureAMMClawback); + env(amm::ammClawback(gw, alice, BTC, XRP, std::nullopt), ter(temDISABLED)); + + // enable featureAMMClawback and disable featureMPTokensV2 + env.enableFeature(featureAMMClawback); + env.disableFeature(featureMPTokensV2); + env(amm::ammClawback(gw, alice, BTC, XRP, BTC(100)), ter(temDISABLED)); + + // enable featureMPTokensV2 + env.enableFeature(featureMPTokensV2); + env(amm::ammClawback(gw, alice, BTC, XRP, BTC(200))); + } + + void + testAMMClawbackAmount(FeatureBitset features) + { + testcase("test AMMClawback specific amount"); + using namespace jtx; + + // AMMClawback from MPT/IOU issued by different issuers + { + Env env(*this, features); + Account const gw{"gateway"}; + Account const gw2{"gateway2"}; + Account const alice{"alice"}; + env.fund(XRP(100000), gw, gw2, alice); + env.close(); + + env(fset(gw, asfAllowTrustLineClawback)); + env(fset(gw2, asfAllowTrustLineClawback)); + env.close(); + + auto const USD = gw["USD"]; + env.trust(USD(100000), alice); + env(pay(gw, alice, USD(50000))); + env.close(); + + MPT const BTC = MPTTester( + {.env = env, + .issuer = gw2, + .holders = {alice}, + .pay = 40'000'000000, + .flags = tfMPTCanClawback | MPTDEXFlags}); + + AMM const amm(env, alice, BTC(1000000000), USD(2000)); + env.close(); + BEAST_EXPECT(amm.expectBalances( + BTC(1'000'000000), USD(2000), IOUAmount{1414'213'562373095, -9})); + + // can not set tfClawTwoAssets because the assets are not issued by + // the same issuer. + env(amm::ammClawback(gw2, alice, BTC, USD, BTC(1000)), + txflags(tfClawTwoAssets), + ter(temINVALID_FLAG)); + + auto aliceUSD = env.balance(alice, USD); + auto aliceBTC = env.balance(alice, BTC); + // gw clawback 1000 USD from alice + env(amm::ammClawback(gw, alice, USD, BTC, USD(1000))); + env.close(); + + BEAST_EXPECT( + amm.expectBalances(BTC(500'000000), USD(1000), IOUAmount{707'106'7811865475, -10})); + // USD is clawed back, + env.require(balance(alice, aliceUSD)); + // a proportional amount of BTC is returned to alice + env.require(balance(alice, aliceBTC + BTC(500'000000))); + aliceBTC = env.balance(alice, BTC); + + // gw2 clawback 250'000000 BTC from alice + env(amm::ammClawback(gw2, alice, BTC, USD, BTC(250'000000))); + env.close(); + BEAST_EXPECT( + amm.expectBalances(BTC(250'000000), USD(500), IOUAmount{353'553'3905932737, -10})); + env.require(balance(alice, aliceUSD + USD(500))); + env.require(balance(alice, aliceBTC)); + aliceUSD = env.balance(alice, USD); + + // gw2 clawback 500'000000 BTC which exceeds the balance, + // this will clawback all and the amm will be deleted. + env(amm::ammClawback(gw2, alice, BTC, USD, BTC(500'000000))); + env.close(); + BEAST_EXPECT(!amm.ammExists()); + env.require(balance(alice, aliceUSD + USD(500))); + env.require(balance(alice, aliceBTC)); + } + + // AMMClawback from MPT/XRP pool + { + Env env(*this, features); + Account const gw{"gateway"}; + Account const alice{"alice"}; + Account const bob{"bob"}; + env.fund(XRP(100000), gw, alice, bob); + env.close(); + + env(fset(gw, asfAllowTrustLineClawback)); + env.close(); + + MPT const BTC = MPTTester( + {.env = env, + .issuer = gw, + .holders = {alice, bob}, + .pay = 40'000'000000, + .flags = tfMPTCanClawback | MPTDEXFlags}); + + AMM amm(env, alice, BTC(1000000000), XRP(2000)); + env.close(); + BEAST_EXPECT(amm.expectBalances( + BTC(1'000'000000), XRP(2000), IOUAmount{1'414'213'562'373095, -6})); + + amm.deposit(bob, BTC(2'000'000000), XRP(4000)); + BEAST_EXPECT(amm.expectBalances( + BTC(3'000'000000), XRP(6000), IOUAmount{4'242'640'687'119285, -6})); + + auto aliceXRP = env.balance(alice, XRP); + auto aliceBTC = env.balance(alice, BTC); + auto bobXRP = env.balance(bob, XRP); + auto bobBTC = env.balance(bob, BTC); + + // can not claw XRP + env(amm::ammClawback(gw, alice, XRP, BTC, XRP(1000)), ter(temMALFORMED)); + // can not set tfClawTwoAssets + env(amm::ammClawback(gw, alice, BTC, XRP, BTC(1000)), + txflags(tfClawTwoAssets), + ter(temINVALID_FLAG)); + + // gw clawback 500 BTC from alice + env(amm::ammClawback(gw, alice, BTC, XRP, BTC(500))); + env.close(); + BEAST_EXPECT(amm.expectBalances( + BTC(2'999'999501), + STAmount{XRP, UINT64_C(5'999'999001)}, + IOUAmount{4'242'639'980'012504, -6})); + env.require(balance(alice, aliceXRP + drops(999))); + env.require(balance(alice, aliceBTC)); + env.require(balance(bob, bobXRP)); + env.require(balance(bob, bobBTC)); + BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount{1'414'212'855'266314, -6})); + BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount{2'828'427'124'74619, -5})); + aliceXRP = env.balance(alice, XRP); + + // gw clawback 1000'000000 BTC from bob + env(amm::ammClawback(gw, bob, BTC, XRP, BTC(1'000'000000))); + env.close(); + BEAST_EXPECT(amm.expectBalances( + BTC(1'999'999501), + STAmount{XRP, UINT64_C(3'999'999002)}, + IOUAmount{2828426418'110813, -6})); + env.require(balance(alice, aliceXRP)); + env.require(balance(alice, aliceBTC)); + env.require(balance(bob, bobXRP + XRPAmount(1999999999))); + env.require(balance(bob, bobBTC)); + BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount{1'414'212'855'266314, -6})); + BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount{1'414'213'562'844499, -6})); + bobXRP = env.balance(bob, XRP); + + // gw clawback 1000'000000 BTC from alice, which exceeds her balance + // will clawback all her balance + env(amm::ammClawback(gw, alice, BTC, XRP, BTC(1'000'000000))); + env.close(); + BEAST_EXPECT(amm.expectBalances( + BTC(1'000'000001), XRPAmount(2'000'000002), IOUAmount{1'414'213'562'844499, -6})); + env.require(balance(alice, aliceXRP + STAmount{XRP, UINT64_C(1'999'999000)})); + env.require(balance(alice, aliceBTC)); + env.require(balance(bob, bobXRP)); + env.require(balance(bob, bobBTC)); + BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0))); + BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount{1'414'213'562'844499, -6})); + aliceXRP = env.balance(alice, XRP); + + // gw clawback from bob, which exceeds his balance + env(amm::ammClawback(gw, bob, BTC, XRP, BTC(2'000'000000))); + env.close(); + // amm is empty and deleted + BEAST_EXPECT(!amm.ammExists()); + env.require(balance(alice, aliceXRP)); + env.require(balance(alice, aliceBTC)); + env.require(balance(bob, bobXRP + XRPAmount(2000000002))); + env.require(balance(bob, bobBTC)); + } + + // AMMClawback from MPT/MPT pool, different issuers + { + Env env(*this, features); + Account const gw{"gateway"}; + Account const gw2{"gateway2"}; + Account const alice{"alice"}; + Account const bob{"bob"}; + env.fund(XRP(100000), gw, gw2, alice, bob); + env.close(); + + env(fset(gw, asfAllowTrustLineClawback)); + env(fset(gw2, asfAllowTrustLineClawback)); + env.close(); + + MPT const BTC = MPTTester( + {.env = env, + .issuer = gw, + .holders = {alice, bob}, + .pay = 40'000'000000, + .flags = tfMPTCanClawback | MPTDEXFlags}); + + MPT const ETH = MPTTester( + {.env = env, + .issuer = gw2, + .holders = {alice, bob}, + .pay = 30'000'000000, + .flags = tfMPTCanClawback | MPTDEXFlags}); + + AMM amm(env, alice, BTC(2'000'000000), ETH(3'000'000000)); + env.close(); + BEAST_EXPECT(amm.expectBalances( + BTC(2'000'000000), ETH(3'000'000000), IOUAmount{2'449'489'742'783178, -6})); + + amm.deposit(bob, BTC(4'000'000000), ETH(6'000'000000)); + BEAST_EXPECT(amm.expectBalances( + BTC(6'000'000000), ETH(9'000'000000), IOUAmount{7'348'469'228'349534, -6})); + + auto aliceBTC = env.balance(alice, BTC); + auto aliceETH = env.balance(alice, ETH); + auto bobBTC = env.balance(bob, BTC); + auto bobETH = env.balance(bob, ETH); + + // gw clawback BTC from alice + env(amm::ammClawback(gw, alice, BTC, ETH, BTC(1'000'000000))); + env.close(); + BEAST_EXPECT(amm.expectBalances( + BTC(5'000'000000), ETH(7'500'000000), IOUAmount{6'123'724'356'957944, -6})); + env.require(balance(alice, aliceBTC)); + env.require(balance(alice, aliceETH + ETH(1'500'000000))); + env.require(balance(bob, bobBTC)); + env.require(balance(bob, bobETH)); + BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount{1'224'744'871'391588, -6})); + BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount{4'898'979'485'566356, -6})); + aliceETH = env.balance(alice, ETH); + + // gw2 clawback ETH from bob + env(amm::ammClawback(gw2, bob, ETH, BTC, ETH(3'000'000000))); + env.close(); + BEAST_EXPECT(amm.expectBalances( + BTC(3'000'000000), ETH(4'500'000000), IOUAmount{3'674'234'614'174766, -6})); + env.require(balance(alice, aliceBTC)); + env.require(balance(alice, aliceETH)); + env.require(balance(bob, bobBTC + BTC(2'000'000000))); + env.require(balance(bob, bobETH)); + BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount{1'224'744'871'391588, -6})); + BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount{2'449'489'742'783178, -6})); + bobBTC = env.balance(bob, BTC); + + // gw2 clawback ETH from alice, which exceeds her balance + env(amm::ammClawback(gw2, alice, ETH, BTC, ETH(4'000'000000))); + env.close(); + BEAST_EXPECT(amm.expectBalances( + BTC(2'000'000001), ETH(3'000'000001), IOUAmount{2'449'489'742'783178, -6})); + env.require(balance(alice, aliceBTC + BTC(999'999999))); + env.require(balance(alice, aliceETH)); + env.require(balance(bob, bobBTC)); + env.require(balance(bob, bobETH)); + aliceBTC = env.balance(alice, BTC); + + // gw clawback BTC from bob, which exceeds his balance + env(amm::ammClawback(gw, bob, BTC, ETH, BTC(3'000'000000))); + env.close(); + // amm is empty and deleted + BEAST_EXPECT(!amm.ammExists()); + env.require(balance(alice, aliceBTC)); + env.require(balance(alice, aliceETH)); + env.require(balance(bob, bobBTC)); + env.require(balance(bob, bobETH + ETH(3'000'000001))); + } + } + + void + testAMMClawbackAll(FeatureBitset features) + { + testcase("test AMMClawback all"); + using namespace jtx; + + // AMMClawback all from MPT/IOU issued by different issuers + { + Env env(*this, features); + Account const gw{"gateway"}; + Account const gw2{"gateway2"}; + Account const alice{"alice"}; + Account const bob{"bob"}; + env.fund(XRP(100000), gw, gw2, alice, bob); + env.close(); + + env(fset(gw, asfAllowTrustLineClawback)); + env(fset(gw2, asfAllowTrustLineClawback)); + env.close(); + + auto const USD = gw["USD"]; + env.trust(USD(100000), alice); + env(pay(gw, alice, USD(50000))); + env.trust(USD(200000), bob); + env(pay(gw, bob, USD(60000))); + env.close(); + + MPT const BTC = MPTTester( + {.env = env, + .issuer = gw2, + .holders = {alice, bob}, + .pay = 40'000'000000, + .flags = tfMPTCanClawback | MPTDEXFlags}); + + AMM amm(env, alice, BTC(2000000000), USD(2000)); + env.close(); + BEAST_EXPECT(amm.expectBalances(BTC(2'000'000000), USD(2000), IOUAmount(2000000))); + + // gw clawback all BTC from alice + amm.deposit(bob, BTC(1'000'000000), USD(2000)); + env.close(); + BEAST_EXPECT(amm.expectBalances(BTC(3'000'000000), USD(3000), IOUAmount(3000000))); + + auto aliceBTC = env.balance(alice, BTC); + auto aliceUSD = env.balance(alice, USD); + auto bobBTC = env.balance(bob, BTC); + auto bobUSD = env.balance(bob, USD); + + // gw2 clawback all BTC from alice + env(amm::ammClawback(gw2, alice, BTC, USD, std::nullopt)); + env.close(); + BEAST_EXPECT(amm.expectBalances(BTC(1'000'000000), USD(1000), IOUAmount(1000000))); + env.require(balance(alice, aliceBTC)); + env.require(balance(alice, aliceUSD + USD(2000))); + env.require(balance(bob, bobBTC)); + env.require(balance(bob, bobUSD)); + aliceUSD = env.balance(alice, USD); + + // gw clawback all USD from bob + env(amm::ammClawback(gw, bob, USD, BTC, std::nullopt)); + env.close(); + // amm is empty and deleted + BEAST_EXPECT(!amm.ammExists()); + env.require(balance(alice, aliceBTC)); + env.require(balance(alice, aliceUSD)); + env.require(balance(bob, bobBTC + BTC(1'000'000000))); + env.require(balance(bob, bobUSD)); + } + + // AMMClawback all from MPT/XRP pool + { + Env env(*this, features); + Account const gw{"gateway"}; + Account const alice{"alice"}; + Account const bob{"bob"}; + env.fund(XRP(100000), gw, alice, bob); + env.close(); + + env(fset(gw, asfAllowTrustLineClawback)); + env.close(); + + MPT const BTC = MPTTester( + {.env = env, + .issuer = gw, + .holders = {alice, bob}, + .pay = 40'000'000000, + .flags = tfMPTCanClawback | MPTDEXFlags}); + + AMM amm(env, alice, BTC(5000), XRP(10'000)); + env.close(); + BEAST_EXPECT( + amm.expectBalances(BTC(5'000), XRP(10'000), IOUAmount{7'071'067'811865475, -9})); + + amm.deposit(bob, BTC(10'000), XRP(20'000)); + BEAST_EXPECT( + amm.expectBalances(BTC(15'000), XRP(30'000), IOUAmount{21'213'203'43559642, -8})); + + auto aliceXRP = env.balance(alice, XRP); + auto aliceBTC = env.balance(alice, BTC); + auto bobXRP = env.balance(bob, XRP); + auto bobBTC = env.balance(bob, BTC); + + // gw clawback all BTC from alice + env(amm::ammClawback(gw, alice, BTC, XRP, std::nullopt)); + env.close(); + BEAST_EXPECT( + amm.expectBalances(BTC(10'000), XRP(20'000), IOUAmount{14'142'135'62373094, -8})); + env.require(balance(alice, aliceBTC)); + env.require(balance(alice, aliceXRP + XRP(10'000))); + env.require(balance(bob, bobBTC)); + env.require(balance(bob, bobXRP)); + aliceXRP = env.balance(alice, XRP); + + // gw clawback all BTC from bob + env(amm::ammClawback(gw, bob, BTC, XRP, std::nullopt)); + env.close(); + // amm is empty and deleted + BEAST_EXPECT(!amm.ammExists()); + env.require(balance(alice, aliceBTC)); + env.require(balance(alice, aliceXRP)); + env.require(balance(bob, bobBTC)); + env.require(balance(bob, bobXRP + XRP(20'000))); + } + + // AMMClawback all from MPT/MPT pool, different issuers + { + Env env(*this, features); + Account const gw{"gateway"}; + Account const gw2{"gateway2"}; + Account const alice{"alice"}; + Account const bob{"bob"}; + env.fund(XRP(100000), gw, gw2, alice, bob); + env.close(); + + env(fset(gw, asfAllowTrustLineClawback)); + env(fset(gw2, asfAllowTrustLineClawback)); + env.close(); + + MPT const BTC = MPTTester( + {.env = env, + .issuer = gw, + .holders = {alice, bob}, + .pay = 40'000'000000, + .flags = tfMPTCanClawback | MPTDEXFlags}); + + MPT const ETH = MPTTester( + {.env = env, + .issuer = gw2, + .holders = {alice, bob}, + .pay = 30'000'000000, + .flags = tfMPTCanClawback | MPTDEXFlags}); + + AMM amm(env, alice, BTC(20'000), ETH(50'000)); + env.close(); + BEAST_EXPECT( + amm.expectBalances(BTC(20'000), ETH(50'000), IOUAmount{31'622'77660168379, -11})); + + amm.deposit(bob, BTC(40'000), ETH(100'000)); + BEAST_EXPECT( + amm.expectBalances(BTC(60'000), ETH(150'000), IOUAmount{94'868'32980505137, -11})); + + auto aliceBTC = env.balance(alice, BTC); + auto aliceETH = env.balance(alice, ETH); + auto bobBTC = env.balance(bob, BTC); + auto bobETH = env.balance(bob, ETH); + + // gw clawback all BTC from bob + env(amm::ammClawback(gw, bob, BTC, ETH, std::nullopt)); + env.close(); + BEAST_EXPECT( + amm.expectBalances(BTC(20'000), ETH(50'000), IOUAmount{31'622'77660168379, -11})); + env.require(balance(alice, aliceBTC)); + env.require(balance(alice, aliceETH)); + env.require(balance(bob, bobBTC)); + env.require(balance(bob, bobETH + ETH(100'000))); + bobETH = env.balance(bob, ETH); + + // gw2 clawback all ETH from alice + env(amm::ammClawback(gw2, alice, ETH, BTC, std::nullopt)); + env.close(); + // amm is empty and deleted + BEAST_EXPECT(!amm.ammExists()); + env.require(balance(alice, aliceBTC + BTC(20'000))); + env.require(balance(alice, aliceETH)); + env.require(balance(bob, bobBTC)); + env.require(balance(bob, bobETH)); + } + } + + void + testAMMClawbackAmountSameIssuer(FeatureBitset features) + { + testcase("test AMMClawback specific amount, assets have the same issuer"); + using namespace jtx; + + // AMMClawback from MPT/IOU issued by the same issuer + { + Env env(*this, features); + Account const gw{"gateway"}; + Account const alice{"alice"}; + Account const bob{"bob"}; + env.fund(XRP(100000), gw, alice, bob); + env.close(); + + env(fset(gw, asfAllowTrustLineClawback)); + env.close(); + + auto const USD = gw["USD"]; + env.trust(USD(100000), alice); + env(pay(gw, alice, USD(50000))); + env.trust(USD(100000), bob); + env(pay(gw, bob, USD(40000))); + env.close(); + + MPT const BTC = MPTTester( + {.env = env, + .issuer = gw, + .holders = {alice, bob}, + .pay = 40'000'000000, + .flags = tfMPTCanClawback | MPTDEXFlags}); + + AMM amm(env, alice, BTC(1'000'000000), USD(2000)); + env.close(); + BEAST_EXPECT(amm.expectBalances( + BTC(1'000'000000), USD(2000), IOUAmount{1414'213'562373095, -9})); + + amm.deposit(bob, BTC(500'000000), USD(1000)); + BEAST_EXPECT(amm.expectBalances( + BTC(1'500'000000), + STAmount{USD, UINT64_C(2'999'999999999999), -12}, + IOUAmount{2'121'320'343559642, -9})); + + auto aliceUSD = env.balance(alice, USD); + auto aliceBTC = env.balance(alice, BTC); + auto bobUSD = env.balance(bob, USD); + auto bobBTC = env.balance(bob, BTC); + + // gw clawback 500 USD from alice. + env(amm::ammClawback(gw, alice, USD, BTC, USD(500))); + env.close(); + BEAST_EXPECT(amm.expectBalances( + BTC(1250'000001), USD(2500), IOUAmount{1'767'766'952966369, -9})); + env.require(balance(alice, aliceUSD)); + env.require(balance(alice, aliceBTC + BTC(249'999999))); + env.require(balance(bob, bobUSD)); + env.require(balance(bob, bobBTC)); + aliceBTC = env.balance(alice, BTC); + // gw clawback 250'000000 BTC and 500 USD from bob + // with tfClawTwoAssets + env(amm::ammClawback(gw, bob, BTC, USD, BTC(250'000000)), txflags(tfClawTwoAssets)); + env.close(); + BEAST_EXPECT(amm.expectBalances( + BTC(1'000'000002), + STAmount{USD, UINT64_C(2000'0000004), -7}, + IOUAmount{1'414'213'562655938, -9})); + env.require(balance(alice, aliceUSD)); + env.require(balance(alice, aliceBTC)); + env.require(balance(bob, bobUSD)); + env.require(balance(bob, bobBTC)); + BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount{1'060'660'171779822, -9})); + BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount{353'553'390876116, -9})); + + // gw clawback USD from alice exceeding her balance + env(amm::ammClawback(gw, alice, USD, BTC, USD(5'000))); + env.close(); + BEAST_EXPECT(amm.expectBalances( + BTC(250'000001), + STAmount{USD, UINT64_C(500'0000004), -7}, + IOUAmount{353'553'390876116, -9})); + env.require(balance(alice, aliceUSD)); + env.require(balance(alice, aliceBTC + BTC(750'000001))); + env.require(balance(bob, bobUSD)); + env.require(balance(bob, bobBTC)); + BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0))); + BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount{353'553'390876116, -9})); + aliceBTC = env.balance(alice, BTC); + + // gw clawback BTC from bob which exceeds his balance with + // tfClawTwoAssets + env(amm::ammClawback(gw, bob, BTC, USD, BTC(300'000000)), txflags(tfClawTwoAssets)); + env.close(); + // amm is empty and deleted + BEAST_EXPECT(!amm.ammExists()); + env.require(balance(alice, aliceUSD)); + env.require(balance(alice, aliceBTC)); + // USD is also clawed back from bob because of tfClawTwoAssets, + // bob's USD balance will not change + env.require(balance(bob, bobUSD)); + env.require(balance(bob, bobBTC)); + } + + // AMMClawback from MPT/MPT issued by the same issuer + { + Env env(*this, features); + Account const gw{"gateway"}; + Account const alice{"alice"}; + Account const bob{"bob"}; + env.fund(XRP(100000), gw, alice, bob); + env.close(); + + env(fset(gw, asfAllowTrustLineClawback)); + env.close(); + + MPT const BTC = MPTTester( + {.env = env, + .issuer = gw, + .holders = {alice, bob}, + .pay = 40'000'000000, + .flags = tfMPTCanClawback | MPTDEXFlags}); + + MPT const ETH = MPTTester( + {.env = env, + .issuer = gw, + .holders = {alice, bob}, + .pay = 30'000'000000, + .flags = tfMPTCanClawback | MPTDEXFlags}); + + AMM amm(env, alice, BTC(2'000'000000), ETH(3'000'000000)); + env.close(); + BEAST_EXPECT(amm.expectBalances( + BTC(2'000'000000), ETH(3'000'000000), IOUAmount{2'449'489'742'783178, -6})); + + amm.deposit(bob, BTC(4'000'000000), ETH(6'000'000000)); + BEAST_EXPECT(amm.expectBalances( + BTC(6'000'000000), ETH(9'000'000000), IOUAmount{7'348'469'228'349534, -6})); + + auto aliceBTC = env.balance(alice, BTC); + auto aliceETH = env.balance(alice, ETH); + auto bobBTC = env.balance(bob, BTC); + auto bobETH = env.balance(bob, ETH); + + // gw clawback BTC from alice + env(amm::ammClawback(gw, alice, BTC, ETH, BTC(1'000'000000))); + env.close(); + BEAST_EXPECT(amm.expectBalances( + BTC(5'000'000000), ETH(7'500'000000), IOUAmount{6'123'724'356'957944, -6})); + env.require(balance(alice, aliceBTC)); + env.require(balance(alice, aliceETH + ETH(1'500'000000))); + env.require(balance(bob, bobBTC)); + env.require(balance(bob, bobETH)); + BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount{1'224'744'871'391588, -6})); + BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount{4'898'979'485'566356, -6})); + aliceETH = env.balance(alice, ETH); + + // gw clawback ETH and BTC from bob with tfClawTwoAssets + env(amm::ammClawback(gw, bob, ETH, BTC, ETH(3'000'000000)), txflags(tfClawTwoAssets)); + env.close(); + BEAST_EXPECT(amm.expectBalances( + BTC(3'000'000000), ETH(4'500'000000), IOUAmount{3'674'234'614'174766, -6})); + env.require(balance(alice, aliceBTC)); + env.require(balance(alice, aliceETH)); + env.require(balance(bob, bobBTC)); + env.require(balance(bob, bobETH)); + BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount{1'224'744'871'391588, -6})); + BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount{2'449'489'742'783178, -6})); + + // gw clawback BTC from alice, which exceeds her balance with + // tfClawTwoAssets + env(amm::ammClawback(gw, alice, BTC, ETH, BTC(3'000'000000)), txflags(tfClawTwoAssets)); + env.close(); + BEAST_EXPECT(amm.expectBalances( + BTC(2'000'000001), ETH(3'000'000001), IOUAmount{2'449'489'742'783178, -6})); + env.require(balance(alice, aliceBTC)); + env.require(balance(alice, aliceETH)); + env.require(balance(bob, bobBTC)); + env.require(balance(bob, bobETH)); + BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0))); + BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount{2'449'489'742'783178, -6})); + + // gw clawback ETH from bob, which is the same as his balance + env(amm::ammClawback(gw, bob, ETH, BTC, ETH(3'000'000001))); + env.close(); + // amm is empty and deleted + BEAST_EXPECT(!amm.ammExists()); + env.require(balance(alice, aliceBTC)); + env.require(balance(alice, aliceETH)); + env.require(balance(bob, bobBTC + BTC(2'000'000001))); + env.require(balance(bob, bobETH)); + } + } + + void + testAMMClawbackAllSameIssuer(FeatureBitset features) + { + testcase("test AMMClawback all, assets have the same issuer"); + using namespace jtx; + + // AMMClawback all from MPT/IOU issued by the same issuer + { + Env env(*this, features); + Account const gw{"gateway"}; + Account const alice{"alice"}; + Account const bob{"bob"}; + env.fund(XRP(100000), gw, alice, bob); + env.close(); + + env(fset(gw, asfAllowTrustLineClawback)); + env.close(); + + auto const USD = gw["USD"]; + env.trust(USD(100000), alice); + env(pay(gw, alice, USD(50000))); + env.trust(USD(200000), bob); + env(pay(gw, bob, USD(60000))); + env.close(); + + MPT const BTC = MPTTester( + {.env = env, + .issuer = gw, + .holders = {alice, bob}, + .pay = 40'000'000000, + .flags = tfMPTCanClawback | MPTDEXFlags}); + + AMM amm(env, alice, BTC(2'000'000000), USD(8'000)); + env.close(); + BEAST_EXPECT(amm.expectBalances(BTC(2'000'000000), USD(8'000), IOUAmount(4'000'000))); + + amm.deposit(bob, BTC(1'000'000000), USD(4'000)); + env.close(); + BEAST_EXPECT(amm.expectBalances(BTC(3'000'000000), USD(12'000), IOUAmount(6'000'000))); + + auto aliceBTC = env.balance(alice, BTC); + auto aliceUSD = env.balance(alice, USD); + auto bobBTC = env.balance(bob, BTC); + auto bobUSD = env.balance(bob, USD); + + // gw clawback all BTC and USD from alice + env(amm::ammClawback(gw, alice, BTC, USD, std::nullopt), txflags(tfClawTwoAssets)); + env.close(); + + BEAST_EXPECT(amm.expectBalances(BTC(1'000'000000), USD(4'000), IOUAmount(2'000'000))); + BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(2'000'000))); + BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0))); + env.require(balance(alice, aliceBTC)); + env.require(balance(alice, aliceUSD)); + env.require(balance(bob, bobBTC)); + env.require(balance(bob, bobUSD)); + + // gw clawback all USD from bob + env(amm::ammClawback(gw, bob, USD, BTC, std::nullopt)); + env.close(); + // amm is empty and deleted + BEAST_EXPECT(!amm.ammExists()); + env.require(balance(alice, aliceBTC)); + env.require(balance(alice, aliceUSD)); + env.require(balance(bob, bobBTC + BTC(1'000'000000))); + env.require(balance(bob, bobUSD)); + } + + // AMMClawback all from MPT/MPT issued by the same issuer + { + Env env(*this, features); + Account const gw{"gateway"}; + Account const alice{"alice"}; + Account const bob{"bob"}; + env.fund(XRP(100000), gw, alice, bob); + env.close(); + + env(fset(gw, asfAllowTrustLineClawback)); + env.close(); + + MPT const BTC = MPTTester( + {.env = env, + .issuer = gw, + .holders = {alice, bob}, + .pay = 40'000'000000, + .flags = tfMPTCanClawback | MPTDEXFlags}); + + MPT const ETH = MPTTester( + {.env = env, + .issuer = gw, + .holders = {alice, bob}, + .pay = 30'000'000000, + .flags = tfMPTCanClawback | MPTDEXFlags}); + + AMM amm(env, alice, BTC(20'000), ETH(10'000)); + env.close(); + BEAST_EXPECT( + amm.expectBalances(BTC(20'000), ETH(10'000), IOUAmount{14'142'13562373095, -11})); + + amm.deposit(bob, BTC(40'000), ETH(20'000)); + BEAST_EXPECT( + amm.expectBalances(BTC(60'000), ETH(30'000), IOUAmount{42'426'40687119285, -11})); + + auto aliceBTC = env.balance(alice, BTC); + auto aliceETH = env.balance(alice, ETH); + auto bobBTC = env.balance(bob, BTC); + auto bobETH = env.balance(bob, ETH); + + // gw clawback all ETH from bob + env(amm::ammClawback(gw, bob, ETH, BTC, std::nullopt)); + env.close(); + BEAST_EXPECT( + amm.expectBalances(BTC(20'000), ETH(10'000), IOUAmount{14'142'13562373095, -11})); + env.require(balance(alice, aliceBTC)); + env.require(balance(alice, aliceETH)); + env.require(balance(bob, bobBTC + BTC(40'000))); + env.require(balance(bob, bobETH)); + BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount{14'142'13562373095, -11})); + BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(0))); + bobBTC = env.balance(bob, BTC); + + // gw clawback all ETH and BTC from alice with tfClawTwoAssets + env(amm::ammClawback(gw, alice, ETH, BTC, std::nullopt), txflags(tfClawTwoAssets)); + env.close(); + + // amm is empty and deleted + BEAST_EXPECT(!amm.ammExists()); + env.require(balance(alice, aliceBTC)); + env.require(balance(alice, aliceETH)); + env.require(balance(bob, bobBTC)); + env.require(balance(bob, bobETH)); + } + } + + void + testAMMClawbackIssuesEachOther(FeatureBitset features) + { + testcase("test AMMClawback when issuing token for each other"); + using namespace jtx; + + // AMMClawback from MPT/IOU issued by each other + { + Env env(*this, features); + Account const gw{"gateway"}; + Account const gw2{"gateway2"}; + Account const alice{"alice"}; + env.fund(XRP(1000000), gw, gw2, alice); + env.close(); + + env(fset(gw, asfAllowTrustLineClawback)); + env(fset(gw2, asfAllowTrustLineClawback)); + env.close(); + + auto const USD = gw["USD"]; + env.trust(USD(100000), gw2); + env(pay(gw, gw2, USD(5000))); + env.trust(USD(100000), alice); + env(pay(gw, alice, USD(5000))); + + MPT const BTC = MPTTester( + {.env = env, + .issuer = gw2, + .holders = {alice, gw}, + .pay = 40'000'000000, + .flags = tfMPTCanClawback | MPTDEXFlags}); + + AMM amm(env, gw, USD(1000), BTC(2000)); + env.close(); + BEAST_EXPECT( + amm.expectBalances(USD(1000), BTC(2000), IOUAmount{1414'213562373095, -12})); + + amm.deposit(gw2, USD(2000), BTC(4000)); + BEAST_EXPECT( + amm.expectBalances(USD(3000), BTC(6000), IOUAmount{4242'640687119285, -12})); + + amm.deposit(alice, USD(3000), BTC(6000)); + BEAST_EXPECT( + amm.expectBalances(USD(6000), BTC(12000), IOUAmount{8485'281374238570, -12})); + + BEAST_EXPECT(amm.expectLPTokens(gw, IOUAmount{1414'213562373095, -12})); + BEAST_EXPECT(amm.expectLPTokens(gw2, IOUAmount{2828'427124746190, -12})); + BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount{4242'640687119285, -12})); + + auto aliceBTC = env.balance(alice, BTC); + auto aliceUSD = env.balance(alice, USD); + auto gwBTC = env.balance(gw, BTC); + auto gw2USD = env.balance(gw2, USD); + + // gw claws back 1000 USD from gw2. + env(amm::ammClawback(gw, gw2, USD, BTC, USD(1000))); + env.close(); + BEAST_EXPECT( + amm.expectBalances(USD(5000), BTC(10000), IOUAmount{7071'067811865474, -12})); + BEAST_EXPECT(amm.expectLPTokens(gw, IOUAmount{1414'213562373095, -12})); + BEAST_EXPECT(amm.expectLPTokens(gw2, IOUAmount{1414'213562373094, -12})); + BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount{4242'640687119285, -12})); + env.require(balance(alice, aliceBTC)); + env.require(balance(alice, aliceUSD)); + env.require(balance(gw, gwBTC)); + env.require(balance(gw2, gw2USD)); + + // gw2 claws back 1000 BTC from gw. + env(amm::ammClawback(gw2, gw, BTC, USD, BTC(1000)), ter(tesSUCCESS)); + env.close(); + BEAST_EXPECT( + amm.expectBalances(USD(4500), BTC(9001), IOUAmount{6363'961030678927, -12})); + + BEAST_EXPECT(amm.expectLPTokens(gw, IOUAmount{707'1067811865480, -13})); + BEAST_EXPECT(amm.expectLPTokens(gw2, IOUAmount{1414'213562373094, -12})); + BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount{4242'640687119285, -12})); + env.require(balance(alice, aliceBTC)); + env.require(balance(alice, aliceUSD)); + env.require(balance(gw, gwBTC)); + env.require(balance(gw2, gw2USD)); + + // gw2 claws back 4000 BTC from alice + env(amm::ammClawback(gw2, alice, BTC, USD, BTC(4000))); + env.close(); + BEAST_EXPECT(amm.expectBalances( + STAmount{USD, UINT64_C(2500'222197533607), -12}, + BTC(5001), + IOUAmount{3535'84814069829, -11})); + + BEAST_EXPECT(amm.expectLPTokens(gw, IOUAmount{707'1067811865480, -13})); + BEAST_EXPECT(amm.expectLPTokens(gw2, IOUAmount{1414'213562373094, -12})); + BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount{1414'527797138648, -12})); + env.require(balance(alice, aliceBTC)); + env.require(balance(alice, aliceUSD + STAmount{USD, UINT64_C(1999'777802466393), -12})); + env.require(balance(gw, gwBTC)); + env.require(balance(gw2, gw2USD)); + } + + // AMMClawback from MPT/MPT issued by each other + { + Env env(*this, features); + Account const gw{"gateway"}; + Account const gw2{"gateway2"}; + Account const alice{"alice"}; + env.fund(XRP(100000), gw, gw2, alice); + env.close(); + + env(fset(gw, asfAllowTrustLineClawback)); + env(fset(gw2, asfAllowTrustLineClawback)); + env.close(); + + MPT const BTC = MPTTester( + {.env = env, + .issuer = gw, + .holders = {gw2, alice}, + .pay = 40'000'000000, + .flags = tfMPTCanClawback | MPTDEXFlags}); + + MPT const ETH = MPTTester( + {.env = env, + .issuer = gw2, + .holders = {gw, alice}, + .pay = 30'000'000000, + .flags = tfMPTCanClawback | MPTDEXFlags}); + + AMM amm(env, gw, BTC(10'000), ETH(50'000)); + env.close(); + BEAST_EXPECT( + amm.expectBalances(BTC(10'000), ETH(50'000), IOUAmount{22'360'67977499789, -11})); + + amm.deposit(gw2, BTC(20'000), ETH(100'000)); + BEAST_EXPECT( + amm.expectBalances(BTC(30'000), ETH(150'000), IOUAmount{67'082'03932499367, -11})); + + amm.deposit(alice, BTC(40'000), ETH(200'000)); + BEAST_EXPECT( + amm.expectBalances(BTC(70'000), ETH(350'000), IOUAmount{156'524'7584249852, -10})); + + auto aliceBTC = env.balance(alice, BTC); + auto aliceETH = env.balance(alice, ETH); + auto gw2BTC = env.balance(gw2, BTC); + auto gwETH = env.balance(gw, ETH); + + // gw claws back 1000 BTC from gw2. + env(amm::ammClawback(gw, gw2, BTC, ETH, BTC(1000))); + env.close(); + BEAST_EXPECT( + amm.expectBalances(BTC(69'001), ETH(345'001), IOUAmount{154'288'6904474855, -10})); + env.require(balance(alice, aliceBTC)); + env.require(balance(alice, aliceETH)); + env.require(balance(gw, gwETH)); + env.require(balance(gw2, gw2BTC)); + + // gw2 claws back all ETH from gw + env(amm::ammClawback(gw2, gw, ETH, BTC, std::nullopt)); + env.close(); + BEAST_EXPECT( + amm.expectBalances(BTC(59'001), ETH(295'001), IOUAmount{131'928'0106724876, -10})); + env.require(balance(alice, aliceBTC)); + env.require(balance(alice, aliceETH)); + env.require(balance(gw, gwETH)); + env.require(balance(gw2, gw2BTC)); + + // gw claws back all BTC from alice + env(amm::ammClawback(gw, alice, BTC, ETH, std::nullopt)); + env.close(); + BEAST_EXPECT( + amm.expectBalances(BTC(19'001), ETH(95'001), IOUAmount{42'485'29157249607, -11})); + env.require(balance(alice, aliceBTC)); + env.require(balance(alice, aliceETH + ETH(200'000))); + env.require(balance(gw, gwETH)); + env.require(balance(gw2, gw2BTC)); + } + } + + void + testAssetFrozenOrLocked(FeatureBitset features) + { + testcase("test AMMClawback when asset is frozen or locked"); + using namespace jtx; + + // test AMMClawback when MPT globally locked or IOU globally frozen + { + Env env{*this, features}; + Account const gw{"gateway"}; + Account const alice{"alice"}; + env.fund(XRP(1'000'000), gw, alice); + + env(fset(gw, asfAllowTrustLineClawback)); + env.close(); + auto const USD = gw["USD"]; + env.trust(USD(1'000'000), alice); + env(pay(gw, alice, USD(500'000))); + + MPTTester BTC( + {.env = env, + .issuer = gw, + .holders = {alice}, + .pay = 30'000, + .flags = tfMPTCanClawback | tfMPTCanLock | MPTDEXFlags}); + AMM const ammAlice(env, alice, USD(10'000), BTC(10'000)); + BEAST_EXPECT(ammAlice.expectBalances(USD(10'000), BTC(10'000), IOUAmount(10'000))); + env.close(); + + auto aliceBTC = env.balance(alice, MPT(BTC)); + auto aliceUSD = env.balance(alice, USD); + + // globally locked and claw back 1000 BTC. + // this should be successful + BTC.set({.flags = tfMPTLock}); + env(amm::ammClawback(gw, alice, MPT(BTC), USD, BTC(1'000))); + BEAST_EXPECT(ammAlice.expectBalances(USD(9'000), BTC(9'000), IOUAmount(9'000))); + env.require(balance(alice, aliceBTC)); + env.require(balance(alice, aliceUSD + USD(1'000))); + aliceUSD = env.balance(alice, USD); + + // unlock and claw back 2000 BTC + BTC.set({.flags = tfMPTUnlock}); + env(amm::ammClawback(gw, alice, MPT(BTC), USD, BTC(2'000))); + BEAST_EXPECT(ammAlice.expectBalances( + STAmount(USD, UINT64_C(7'000'000000000001), -12), BTC(7'001), IOUAmount(7'000))); + env.require(balance(alice, aliceBTC)); + env.require(balance(alice, aliceUSD + USD(2'000))); + aliceUSD = env.balance(alice, USD); + + // globally freeze trustline and claw back 1000 USD. + // this should be successful + env(trust(gw, alice["USD"](0), tfSetFreeze)); + env.close(); + env(amm::ammClawback(gw, alice, USD, MPT(BTC), USD(1'000))); + BEAST_EXPECT(ammAlice.expectBalances( + STAmount(USD, UINT64_C(6000'000000000002), -12), + BTC(6'001), + IOUAmount(6'000'000000000001, -12))); + env.require(balance(alice, aliceBTC + BTC(1'000))); + env.require(balance(alice, aliceUSD)); + aliceBTC = env.balance(alice, MPT(BTC)); + + // globally unfreeze trustline and claw back 2000 USD + // and 2000 BTC with tfClawTwoAssets + env(fset(gw, asfGlobalFreeze)); + env.close(); + env(amm::ammClawback(gw, alice, USD, MPT(BTC), USD(2'000)), txflags(tfClawTwoAssets)); + BEAST_EXPECT(ammAlice.expectBalances( + STAmount(USD, UINT64_C(4'000'000000000002), -12), + BTC(4'001), + IOUAmount(4'000'000000000001, -12))); + env.require(balance(alice, aliceBTC)); + env.require(balance(alice, aliceUSD)); + } + + // test AMMClawback when MPT individually locked or IOU individually + // frozen + { + Env env{*this, features}; + Account const gw{"gateway"}; + Account const alice{"alice"}; + env.fund(XRP(1'000'000), gw, alice); + + env(fset(gw, asfAllowTrustLineClawback)); + env.close(); + auto const USD = gw["USD"]; + env.trust(USD(1'000'000), alice); + env(pay(gw, alice, USD(500'000))); + + MPTTester BTC( + {.env = env, + .issuer = gw, + .holders = {alice}, + .pay = 30'000, + .flags = tfMPTCanClawback | tfMPTCanLock | MPTDEXFlags}); + AMM const ammAlice(env, alice, USD(10'000), BTC(10'000)); + BEAST_EXPECT(ammAlice.expectBalances(USD(10'000), BTC(10'000), IOUAmount(10'000))); + env.close(); + + auto aliceBTC = env.balance(alice, MPT(BTC)); + auto aliceUSD = env.balance(alice, USD); + + // individually locked and claw back 2000 BTC from alice + BTC.set({.holder = alice, .flags = tfMPTLock}); + env(amm::ammClawback(gw, alice, MPT(BTC), USD, BTC(2'000))); + BEAST_EXPECT(ammAlice.expectBalances(USD(8'000), BTC(8'000), IOUAmount(8'000))); + env.require(balance(alice, aliceBTC)); + env.require(balance(alice, aliceUSD + USD(2'000))); + aliceUSD = env.balance(alice, USD); + + // individually freeze trustline and claw back 1000 USD from alice + env(trust(gw, alice["USD"](0), tfSetFreeze)); + env.close(); + env(amm::ammClawback(gw, alice, USD, MPT(BTC), USD(1'000))); + BEAST_EXPECT(ammAlice.expectBalances(USD(7'000), BTC(7'000), IOUAmount(7'000))); + env.require(balance(alice, aliceBTC + BTC(1'000))); + env.require(balance(alice, aliceUSD)); + aliceBTC = env.balance(alice, MPT(BTC)); + + // unlock MPT and claw back 3000 BTC from alice + BTC.set({.holder = alice, .flags = tfMPTUnlock}); + env(amm::ammClawback(gw, alice, MPT(BTC), USD, BTC(3'000))); + BEAST_EXPECT(ammAlice.expectBalances( + STAmount{USD, UINT64_C(4000'000000000001), -12}, BTC(4'001), IOUAmount(4'000))); + env.require(balance(alice, aliceBTC)); + env.require(balance(alice, aliceUSD + USD(3'000))); + aliceUSD = env.balance(alice, USD); + + // unlock trustline and claw back 1000 USD from alice + env(trust(gw, alice["USD"](0), tfClearFreeze)); + env.close(); + env(amm::ammClawback(gw, alice, USD, MPT(BTC), USD(1'000))); + BEAST_EXPECT(ammAlice.expectBalances( + STAmount(USD, UINT64_C(3'000'000000000002), -12), + BTC(3'001), + IOUAmount(3000'000000000001, -12))); + env.require(balance(alice, aliceBTC + BTC(1'000))); + env.require(balance(alice, aliceUSD)); + } + } + + void + testSingleDepositAndClawback(FeatureBitset features) + { + testcase("test single depoit and clawback"); + using namespace jtx; + + // MPT/XRP + { + Env env(*this, features); + Account const gw{"gateway"}; + Account const alice{"alice"}; + env.fund(XRP(1000000000), gw, alice); + env.close(); + + MPT const BTC = MPTTester( + {.env = env, + .issuer = gw, + .holders = {alice}, + .pay = 40'000'000000, + .flags = tfMPTCanClawback | MPTDEXFlags}); + + // gw creates AMM pool of BTC/XRP. + AMM amm(env, gw, XRP(100), BTC(400), ter(tesSUCCESS)); + env.close(); + BEAST_EXPECT(amm.expectBalances(XRP(100), BTC(400), IOUAmount(200000))); + amm.deposit(alice, BTC(400)); + env.close(); + BEAST_EXPECT(amm.expectBalances(XRP(100), BTC(800), IOUAmount{282842'712474619, -9})); + + auto aliceBTC = env.balance(alice, MPT(BTC)); + auto aliceXRP = env.balance(alice, XRP); + + // gw clawback 100 BTC from alice + env(amm::ammClawback(gw, alice, MPT(BTC), XRP, BTC(100))); + BEAST_EXPECT(amm.expectBalances( + XRPAmount(87500001), BTC(701), IOUAmount{247'487'3734152917, -10})); + + env.require(balance(alice, aliceBTC)); + env.require(balance(alice, aliceXRP + XRPAmount(12'499999))); + } + + // MPT/IOU + { + Env env(*this, features); + Account const gw{"gateway"}; + Account const alice{"alice"}; + env.fund(XRP(1000000000), gw, alice); + env.close(); + + // gw sets asfAllowTrustLineClawback. + env(fset(gw, asfAllowTrustLineClawback)); + env.close(); + env.require(flags(gw, asfAllowTrustLineClawback)); + + // gw issues 1000 USD to Alice. + auto const USD = gw["USD"]; + env.trust(USD(100000), alice); + env(pay(gw, alice, USD(1000))); + env.close(); + + MPT const BTC = MPTTester( + {.env = env, + .issuer = gw, + .holders = {alice}, + .pay = 40'000'000000, + .flags = tfMPTCanClawback | MPTDEXFlags}); + + // gw creates AMM pool of BTC/USD. + AMM amm(env, gw, USD(100), BTC(400), ter(tesSUCCESS)); + env.close(); + BEAST_EXPECT(amm.expectBalances(USD(100), BTC(400), IOUAmount(200))); + amm.deposit(alice, BTC(400)); + env.close(); + BEAST_EXPECT(amm.expectBalances(USD(100), BTC(800), IOUAmount{282'842712474619, -12})); + + auto aliceBTC = env.balance(alice, MPT(BTC)); + auto aliceUSD = env.balance(alice, USD); + + // gw clawback 100 BTC from alice + env(amm::ammClawback(gw, alice, MPT(BTC), USD, BTC(100))); + BEAST_EXPECT(amm.expectBalances( + STAmount{USD, UINT64_C(87'50000000000003), -14}, + BTC(701), + IOUAmount{247'4873734152917, -13})); + + env.require(balance(alice, aliceBTC)); + env.require(balance(alice, aliceUSD + USD(12.5))); + aliceUSD = env.balance(alice, USD); + + // gw clawback 30 USD from alice with tfClawTwoAssets, which exceeds + // her balance + env(amm::ammClawback(gw, alice, USD, MPT(BTC), USD(30)), txflags(tfClawTwoAssets)); + BEAST_EXPECT(amm.expectBalances( + STAmount{USD, UINT64_C(70'71067811865476), -14}, BTC(567), IOUAmount(200))); + env.require(balance(alice, aliceBTC)); + env.require(balance(alice, aliceUSD)); + BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0))); + BEAST_EXPECT(amm.expectLPTokens(gw, IOUAmount(200))); + } + + // MPT/MPT + { + Env env(*this, features); + Account const gw{"gateway"}; + Account const alice{"alice"}; + env.fund(XRP(1000000000), gw, alice); + env.close(); + + MPT const USD = MPTTester( + {.env = env, + .issuer = gw, + .holders = {alice}, + .pay = 40'000'000000, + .flags = tfMPTCanClawback | MPTDEXFlags}); + + MPT const BTC = MPTTester( + {.env = env, + .issuer = gw, + .holders = {alice}, + .pay = 40'000'000000, + .flags = tfMPTCanClawback | MPTDEXFlags}); + + // gw creates AMM pool of BTC/USD. + AMM amm(env, gw, USD(100), BTC(400), ter(tesSUCCESS)); + env.close(); + BEAST_EXPECT(amm.expectBalances(USD(100), BTC(400), IOUAmount(200))); + amm.deposit(alice, BTC(400)); + env.close(); + BEAST_EXPECT(amm.expectBalances(USD(100), BTC(800), IOUAmount{282'842712474619, -12})); + + auto aliceBTC = env.balance(alice, MPT(BTC)); + auto aliceUSD = env.balance(alice, USD); + + // gw clawback 100 BTC from alice + env(amm::ammClawback(gw, alice, MPT(BTC), USD, BTC(100))); + BEAST_EXPECT(amm.expectBalances(USD(88), BTC(701), IOUAmount{247'4873734152917, -13})); + + env.require(balance(alice, aliceBTC)); + env.require(balance(alice, aliceUSD + USD(12))); + aliceUSD = env.balance(alice, USD); + + // gw clawback 30 USD from alice with tfClawTwoAssets, which exceeds + // her balance + env(amm::ammClawback(gw, alice, USD, MPT(BTC), USD(30)), txflags(tfClawTwoAssets)); + BEAST_EXPECT(amm.expectBalances(USD(72), BTC(567), IOUAmount(200))); + env.require(balance(alice, aliceBTC)); + env.require(balance(alice, aliceUSD)); + BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0))); + BEAST_EXPECT(amm.expectLPTokens(gw, IOUAmount(200))); + } + } + + void + testLastHolderLPTokenBalance(FeatureBitset features) + { + testcase( + "test last holder's lptoken balance not equal to AMM's lptoken " + "balance before clawback"); + using namespace jtx; + std::string logs; + + // MPT/IOU + { + Env env(*this, features, std::make_unique(&logs)); + Account const gw{"gateway"}, alice{"alice"}, bob{"bob"}; + env.fund(XRP(100000), gw, alice, bob); + env.close(); + env(fset(gw, asfAllowTrustLineClawback)); + env.close(); + + auto const USD = gw["USD"]; + env.trust(USD(100000), alice); + env(pay(gw, alice, USD(50000))); + env.trust(USD(100000), bob); + env(pay(gw, bob, USD(40000))); + env.close(); + + MPT const EUR = MPTTester( + {.env = env, + .issuer = gw, + .holders = {alice, bob}, + .pay = 40'000'000000, + .flags = tfMPTCanClawback | MPTDEXFlags}); + + AMM amm(env, alice, USD(2), EUR(1)); + amm.deposit(alice, IOUAmount{1'576123487565916, -15}); + amm.deposit(bob, IOUAmount{1'000}); + amm.withdraw(alice, IOUAmount{1'576123487565916, -15}); + amm.withdrawAll(bob); + + auto const lpToken = + getAccountLines(env, alice, amm.lptIssue())[jss::lines][0u][jss::balance] + .asString(); + auto const lpTokenBalance = + amm.ammRpcInfo()[jss::amm][jss::lp_token][jss::value].asString(); + if (features[featureSingleAssetVault] || features[featureLendingProtocol]) + { + BEAST_EXPECT(lpToken == "1.414213562374011" && lpTokenBalance == "1.4142135623741"); + } + else + { + BEAST_EXPECT(lpToken == "1.414213562374011" && lpTokenBalance == "1.414213562374"); + } + + auto res = isOnlyLiquidityProvider(*env.current(), amm.lptIssue(), alice); + BEAST_EXPECT(res && res.value()); + + if (features[fixAMMv1_3] && features[fixAMMClawbackRounding]) + { + env(amm::ammClawback(gw, alice, USD, EUR, std::nullopt)); + BEAST_EXPECT(!amm.ammExists()); + } + else if ( + features[fixAMMv1_3] && + (features[featureSingleAssetVault] || features[featureLendingProtocol])) + { + env(amm::ammClawback(gw, alice, USD, EUR, std::nullopt)); + // Without the Rounding feature and with new Number a dust pool + // amount remains + BEAST_EXPECT(amm.ammExists()); + } + else if (!features[featureSingleAssetVault] && !features[featureLendingProtocol]) + { + env(amm::ammClawback(gw, alice, USD, EUR, std::nullopt), ter(tecINTERNAL)); + BEAST_EXPECT(amm.ammExists()); + } + else + { + env(amm::ammClawback(gw, alice, USD, EUR, std::nullopt), ter(tecAMM_BALANCE)); + BEAST_EXPECT(amm.ammExists()); + } + } + + // MPT/MPT + { + Env env(*this, features, std::make_unique(&logs)); + Account const gw{"gateway"}, alice{"alice"}, bob{"bob"}; + env.fund(XRP(100000), gw, alice, bob); + env.close(); + + MPT const USD = MPTTester( + {.env = env, + .issuer = gw, + .holders = {alice, bob}, + .pay = 40'000'000000, + .flags = tfMPTCanClawback | MPTDEXFlags}); + + MPT const EUR = MPTTester( + {.env = env, + .issuer = gw, + .holders = {alice, bob}, + .pay = 40'000'000000, + .flags = tfMPTCanClawback | MPTDEXFlags}); + + AMM amm(env, alice, USD(2), EUR(1)); + amm.deposit(alice, IOUAmount{1'576123487565916, -15}); + amm.deposit(bob, IOUAmount{1'000}); + amm.withdraw(alice, IOUAmount{1'576123487565916, -15}); + amm.withdrawAll(bob); + + auto const lpToken = + getAccountLines(env, alice, amm.lptIssue())[jss::lines][0u][jss::balance] + .asString(); + auto const lpTokenBalance = + amm.ammRpcInfo()[jss::amm][jss::lp_token][jss::value].asString(); + if (!features[featureSingleAssetVault] && !features[featureLendingProtocol]) + { + BEAST_EXPECT(lpToken == "1.414213562374011" && lpTokenBalance == "1.414213562374"); + } + else + { + BEAST_EXPECT(lpToken == "1.414213562374011" && lpTokenBalance == "1.4142135623741"); + } + + auto res = isOnlyLiquidityProvider(*env.current(), amm.lptIssue(), alice); + BEAST_EXPECT(res && res.value()); + + if (features[fixAMMv1_3] && features[fixAMMClawbackRounding]) + { + env(amm::ammClawback(gw, alice, USD, EUR, std::nullopt)); + BEAST_EXPECT(!amm.ammExists()); + } + else if ( + features[fixAMMv1_3] && + (features[featureSingleAssetVault] || features[featureLendingProtocol])) + { + // Without the Rounding feature and with new Number a dust pool + // amount remains + env(amm::ammClawback(gw, alice, USD, EUR, std::nullopt)); + BEAST_EXPECT(amm.ammExists()); + } + else if (!features[featureSingleAssetVault] && !features[featureLendingProtocol]) + { + env(amm::ammClawback(gw, alice, USD, EUR, std::nullopt), ter(tecINTERNAL)); + BEAST_EXPECT(amm.ammExists()); + } + else if (features[featureMPTokensV2]) + { + env(amm::ammClawback(gw, alice, USD, EUR, std::nullopt), ter(tecAMM_BALANCE)); + BEAST_EXPECT(amm.ammExists()); + } + } + } + + void + testClawAssetCheck(FeatureBitset features) + { + testcase("claw asset check for MPT and IOU"); + using namespace jtx; + + // IOU/MPT, MPT not clawable + { + Env env(*this, features); + Account const gw{"gateway"}; + Account const alice{"alice"}; + env.fund(XRP(100000), gw, alice); + env.close(); + + env(fset(gw, asfAllowTrustLineClawback)); + env.close(); + + auto const USD = gw["USD"]; + env.trust(USD(100000), alice); + env(pay(gw, alice, USD(1000))); + env.close(); + + MPT const BTC = + MPTTester({.env = env, .issuer = gw, .holders = {alice}, .pay = 40'000}); + + AMM const amm(env, alice, USD(200), BTC(100)); + // Asset BTC is not clawable without tfMPTCanClawback. + env(amm::ammClawback(gw, alice, BTC, USD, std::nullopt), ter(tecNO_PERMISSION)); + + // Although USD is clawable with asfAllowTrustLineClawback. + // When tfClawTwoAssets is set, we will claw Asser2 as well. + // But Asset2 is not clawable. tfMPTCanClawback was not set for BTC. + env(amm::ammClawback(gw, alice, USD, BTC, std::nullopt), + txflags(tfClawTwoAssets), + ter(tecNO_PERMISSION)); + + // Can only claw the other asset + env(amm::ammClawback(gw, alice, USD, BTC, std::nullopt)); + } + + // IOU/MPT, IOU not clawable + { + Env env(*this, features); + Account const gw{"gateway"}; + Account const alice{"alice"}; + env.fund(XRP(100000), gw, alice); + env.close(); + + auto const USD = gw["USD"]; + env.trust(USD(100000), alice); + env(pay(gw, alice, USD(1000))); + env.close(); + + MPT const BTC = MPTTester( + {.env = env, + .issuer = gw, + .holders = {alice}, + .pay = 40'000, + .flags = tfMPTCanClawback | MPTDEXFlags}); + + // Asset USD is not clawable without asfAllowTrustLineClawback. + AMM const amm(env, alice, USD(200), BTC(100)); + env(amm::ammClawback(gw, alice, USD, BTC, std::nullopt), ter(tecNO_PERMISSION)); + + // Although BTC is clawable with tfMPTCanClawback. + // When tfClawTwoAssets is set, we will claw Asset2 as well. + // But Asset2 is not clawable. asfAllowTrustLineClawback was not set + // by the issuer. + env(amm::ammClawback(gw, alice, BTC, USD, std::nullopt), + txflags(tfClawTwoAssets), + ter(tecNO_PERMISSION)); + + // Can only claw the other asset + env(amm::ammClawback(gw, alice, BTC, USD, std::nullopt)); + } + + // IOU/MPT both clawable + { + Env env(*this, features); + Account const gw{"gateway"}; + Account const gw2{"gateway2"}; + Account const alice{"alice"}; + env.fund(XRP(100000), gw, gw2, alice); + env.close(); + + env(fset(gw, asfAllowTrustLineClawback)); + env.close(); + + auto const USD = gw["USD"]; + env.trust(USD(100000), alice); + env(pay(gw, alice, USD(1000))); + env.close(); + + MPT const BTC = MPTTester( + {.env = env, + .issuer = gw2, + .holders = {alice}, + .pay = 40'000, + .flags = tfMPTCanClawback | MPTDEXFlags}); + + AMM const amm(env, alice, USD(200), BTC(100)); + + // the account trying to claw MPT is not its issuer + // will return temMALFORMED in preflight. + env(amm::ammClawback(gw, alice, BTC, USD, std::nullopt), ter(temMALFORMED)); + } + + // only issuer can claw. IOU/MPT mix + { + auto test = [&](auto&& issue1, auto&& issue2) { + Env env(*this); + Account const gw("gateway"), alice("alice"), bob("bob"); + env.fund(XRP(30'000), alice, bob, gw); + env.close(); + auto const USD = issue1( + {.env = env, + .token = "USD", + .issuer = gw, + .holders = {alice}, + .limit = 1'000'000}); + auto const BTC = issue2( + {.env = env, + .token = "BTC", + .issuer = bob, + .holders = {alice}, + .limit = 1'000'000}); + env(pay(gw, alice, USD(50000))); + env(pay(bob, alice, BTC(50000))); + env.close(); + + auto ammAlice = AMM(env, alice, USD(10000), BTC(10100)); + // BTC's issuer is bob, alice can not clawback + env(amm::ammClawback(gw, alice, BTC, USD, std::nullopt), ter(temMALFORMED)); + }; + testHelper2TokensMix(test); + } + + // set tfClawTwoAssets, but the two assets are from different issuer. + { + auto test = [&](auto&& issue1, auto&& issue2) { + Env env(*this); + Account const gw("gateway"), alice("alice"), bob("bob"); + env.fund(XRP(30'000), alice, bob, gw); + env.close(); + auto const USD = issue1( + {.env = env, + .token = "USD", + .issuer = gw, + .holders = {alice}, + .limit = 1'000'000}); + auto const BTC = issue2( + {.env = env, + .token = "BTC", + .issuer = bob, + .holders = {alice}, + .limit = 1'000'000}); + env(pay(gw, alice, USD(50000))); + env(pay(bob, alice, BTC(50000))); + env.close(); + + auto ammAlice = AMM(env, alice, USD(10000), BTC(10100)); + // BTC's issuer is bob. But with tfClawTwoAssets, we will claw + // both. It will fail because the other asset USD's issuer is + // gw. + env(amm::ammClawback(bob, alice, BTC, USD, std::nullopt), + txflags(tfClawTwoAssets), + ter(temINVALID_FLAG)); + }; + testHelper2TokensMix(test); + } + } + + void + run() override + { + FeatureBitset const all{jtx::testable_amendments() | fixAMMClawbackRounding}; + + testInvalidRequest(all); + testFeatureDisabled(all); + testAMMClawbackAmount(all); + testAMMClawbackAll(all); + testAMMClawbackAmountSameIssuer(all); + testAMMClawbackAllSameIssuer(all); + testAMMClawbackIssuesEachOther(all); + testAssetFrozenOrLocked(all); + testSingleDepositAndClawback(all); + testLastHolderLPTokenBalance(all); + testLastHolderLPTokenBalance(all - fixAMMv1_3 - fixAMMClawbackRounding); + testLastHolderLPTokenBalance( + all - fixAMMv1_3 - fixAMMClawbackRounding - featureSingleAssetVault - + featureLendingProtocol); + testLastHolderLPTokenBalance(all - fixAMMClawbackRounding); + testClawAssetCheck(all); + } +}; + +BEAST_DEFINE_TESTSUITE(AMMClawbackMPT, app, xrpl); + +} // namespace xrpl::test diff --git a/src/test/app/AMMClawback_test.cpp b/src/test/app/AMMClawback_test.cpp index 245ee38ac2..bf08b57bf3 100644 --- a/src/test/app/AMMClawback_test.cpp +++ b/src/test/app/AMMClawback_test.cpp @@ -1,23 +1,41 @@ -#include #include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include -#include +#include +#include +#include +#include -namespace xrpl { -namespace test { +#include +#include +#include +#include + +namespace xrpl::test { class AMMClawback_test : public beast::unit_test::suite { void - testInvalidRequest() + testInvalidRequest(FeatureBitset features) { testcase("test invalid request"); using namespace jtx; // Test if holder does not exist. { - Env env(*this); + Env env(*this, features); Account const gw{"gateway"}; Account const alice{"alice"}; env.fund(XRP(100000), gw, alice); @@ -42,8 +60,8 @@ class AMMClawback_test : public beast::unit_test::suite // Test if asset pair provided does not exist. This should // return terNO_AMM error. { - Env env(*this); - Account gw{"gateway"}; + Env env(*this, features); + Account const gw{"gateway"}; Account const alice{"alice"}; env.fund(XRP(100000), gw, alice); env.close(); @@ -74,7 +92,7 @@ class AMMClawback_test : public beast::unit_test::suite // Test if the issuer field and holder field is the same. This should // return temMALFORMED error. { - Env env(*this); + Env env(*this, features); Account const gw{"gateway"}; Account const alice{"alice"}; env.fund(XRP(10000), gw, alice); @@ -102,7 +120,7 @@ class AMMClawback_test : public beast::unit_test::suite // Test if the Asset field matches the Account field. { - Env env(*this); + Env env(*this, features); Account const gw{"gateway"}; Account const alice{"alice"}; env.fund(XRP(10000), gw, alice); @@ -130,7 +148,7 @@ class AMMClawback_test : public beast::unit_test::suite // Test if the Amount field matches the Asset field. { - Env env(*this); + Env env(*this, features); Account const gw{"gateway"}; Account const alice{"alice"}; env.fund(XRP(10000), gw, alice); @@ -159,7 +177,7 @@ class AMMClawback_test : public beast::unit_test::suite // Test if the Amount is invalid, which is less than zero. { - Env env(*this); + Env env(*this, features); Account const gw{"gateway"}; Account const alice{"alice"}; env.fund(XRP(10000), gw, alice); @@ -192,7 +210,7 @@ class AMMClawback_test : public beast::unit_test::suite // Test if the issuer did not set asfAllowTrustLineClawback, AMMClawback // transaction is prohibited. { - Env env(*this); + Env env(*this, features); Account const gw{"gateway"}; Account const alice{"alice"}; env.fund(XRP(10000), gw, alice); @@ -216,7 +234,7 @@ class AMMClawback_test : public beast::unit_test::suite // Test invalid flag. { - Env env(*this); + Env env(*this, features); Account const gw{"gateway"}; Account const alice{"alice"}; env.fund(XRP(10000), gw, alice); @@ -244,7 +262,7 @@ class AMMClawback_test : public beast::unit_test::suite // Test if tfClawTwoAssets is set when the two assets in the AMM pool // are not issued by the same issuer. { - Env env(*this); + Env env(*this, features); Account const gw{"gateway"}; Account const alice{"alice"}; env.fund(XRP(10000), gw, alice); @@ -275,7 +293,7 @@ class AMMClawback_test : public beast::unit_test::suite // Test clawing back XRP is being prohibited. { - Env env(*this); + Env env(*this, features); Account const gw{"gateway"}; Account const alice{"alice"}; env.fund(XRP(1000000), gw, alice); @@ -674,8 +692,8 @@ class AMMClawback_test : public beast::unit_test::suite Env env(*this, features); Account const gw{"gateway"}; Account const gw2{"gateway2"}; - Account alice{"alice"}; - Account bob{"bob"}; + Account const alice{"alice"}; + Account const bob{"bob"}; env.fund(XRP(1000000), gw, gw2, alice, bob); env.close(); @@ -1085,8 +1103,8 @@ class AMMClawback_test : public beast::unit_test::suite Account const gw{"gateway"}; Account const gw2{"gateway2"}; Account const alice{"alice"}; - Account bob{"bob"}; - Account carol{"carol"}; + Account const bob{"bob"}; + Account const carol{"carol"}; env.fund(XRP(1000000), gw, gw2, alice, bob, carol); env.close(); @@ -1289,8 +1307,8 @@ class AMMClawback_test : public beast::unit_test::suite { Env env(*this, features); Account const gw{"gateway"}; - Account alice{"alice"}; - Account bob{"bob"}; + Account const alice{"alice"}; + Account const bob{"bob"}; env.fund(XRP(1000000), gw, alice, bob); env.close(); @@ -1402,8 +1420,8 @@ class AMMClawback_test : public beast::unit_test::suite Env env(*this, features); Account const gw{"gateway"}; Account const alice{"alice"}; - Account bob{"bob"}; - Account carol{"carol"}; + Account const bob{"bob"}; + Account const carol{"carol"}; env.fund(XRP(1000000), gw, alice, bob, carol); env.close(); @@ -1537,7 +1555,7 @@ class AMMClawback_test : public beast::unit_test::suite Account const gw{"gateway"}; Account const gw2{"gateway2"}; Account const alice{"alice"}; - Account bob{"bob"}; + Account const bob{"bob"}; env.fund(XRP(1000000), gw, gw2, alice, bob); env.close(); @@ -1630,8 +1648,8 @@ class AMMClawback_test : public beast::unit_test::suite // each other. Env env(*this, features); Account const gw{"gateway"}; - Account gw2{"gateway2"}; - Account alice{"alice"}; + Account const gw2{"gateway2"}; + Account const alice{"alice"}; env.fund(XRP(1000000), gw, gw2, alice); env.close(); @@ -2012,8 +2030,8 @@ class AMMClawback_test : public beast::unit_test::suite Env env(*this, features); Account const gw{"gateway"}; Account const alice{"alice"}; - Account bob{"bob"}; - Account carol{"carol"}; + Account const bob{"bob"}; + Account const carol{"carol"}; env.fund(XRP(1000000), gw, alice, bob, carol); env.close(); @@ -2150,7 +2168,7 @@ class AMMClawback_test : public beast::unit_test::suite // to the holder. Env env(*this, features, std::make_unique(&logs)); Account const gw{"gateway"}; - Account alice{"alice"}; + Account const alice{"alice"}; env.fund(XRP(1000000000), gw, alice); env.close(); @@ -2212,21 +2230,22 @@ class AMMClawback_test : public beast::unit_test::suite using namespace jtx; std::string logs; - auto setupAccounts = [&](Env& env, Account& gw, Account& alice, Account& bob) { - env.fund(XRP(100000), gw, alice, bob); - env.close(); - env(fset(gw, asfAllowTrustLineClawback)); - env.close(); + auto setupAccounts = + [&](Env& env, Account const& gw, Account const& alice, Account const& bob) { + env.fund(XRP(100000), gw, alice, bob); + env.close(); + env(fset(gw, asfAllowTrustLineClawback)); + env.close(); - auto const USD = gw["USD"]; - env.trust(USD(100000), alice); - env(pay(gw, alice, USD(50000))); - env.trust(USD(100000), bob); - env(pay(gw, bob, USD(40000))); - env.close(); + auto const USD = gw["USD"]; + env.trust(USD(100000), alice); + env(pay(gw, alice, USD(50000))); + env.trust(USD(100000), bob); + env(pay(gw, bob, USD(40000))); + env.close(); - return USD; - }; + return USD; + }; auto getLPTokenBalances = [&](auto& env, auto const& amm, @@ -2242,7 +2261,7 @@ class AMMClawback_test : public beast::unit_test::suite // IOU/XRP pool. AMMClawback almost last holder's USD balance { Env env(*this, features, std::make_unique(&logs)); - Account gw{"gateway"}, alice{"alice"}, bob{"bob"}; + Account const gw{"gateway"}, alice{"alice"}, bob{"bob"}; auto const USD = setupAccounts(env, gw, alice, bob); AMM amm(env, alice, XRP(2), USD(1)); @@ -2275,7 +2294,7 @@ class AMMClawback_test : public beast::unit_test::suite // IOU/XRP pool. AMMClawback part of last holder's USD balance { Env env(*this, features, std::make_unique(&logs)); - Account gw{"gateway"}, alice{"alice"}, bob{"bob"}; + Account const gw{"gateway"}, alice{"alice"}, bob{"bob"}; auto const USD = setupAccounts(env, gw, alice, bob); AMM amm(env, alice, XRP(2), USD(1)); @@ -2317,7 +2336,7 @@ class AMMClawback_test : public beast::unit_test::suite // IOU/XRP pool. AMMClawback all of last holder's USD balance { Env env(*this, features, std::make_unique(&logs)); - Account gw{"gateway"}, alice{"alice"}, bob{"bob"}; + Account const gw{"gateway"}, alice{"alice"}, bob{"bob"}; auto const USD = setupAccounts(env, gw, alice, bob); AMM amm(env, alice, XRP(2), USD(1)); @@ -2354,7 +2373,7 @@ class AMMClawback_test : public beast::unit_test::suite // IOU/IOU pool, different issuers { Env env(*this, features, std::make_unique(&logs)); - Account gw{"gateway"}, alice{"alice"}, bob{"bob"}; + Account const gw{"gateway"}, alice{"alice"}, bob{"bob"}; auto const USD = setupAccounts(env, gw, alice, bob); Account const gw2{"gateway2"}; @@ -2394,7 +2413,7 @@ class AMMClawback_test : public beast::unit_test::suite // IOU/IOU pool, same issuer { Env env(*this, features, std::make_unique(&logs)); - Account gw{"gateway"}, alice{"alice"}, bob{"bob"}; + Account const gw{"gateway"}, alice{"alice"}, bob{"bob"}; auto const USD = setupAccounts(env, gw, alice, bob); auto const EUR = gw["EUR"]; @@ -2433,7 +2452,7 @@ class AMMClawback_test : public beast::unit_test::suite // IOU/IOU pool, larger asset ratio { Env env(*this, features, std::make_unique(&logs)); - Account gw{"gateway"}, alice{"alice"}, bob{"bob"}; + Account const gw{"gateway"}, alice{"alice"}, bob{"bob"}; auto const USD = setupAccounts(env, gw, alice, bob); auto const EUR = gw["EUR"]; @@ -2490,10 +2509,14 @@ class AMMClawback_test : public beast::unit_test::suite FeatureBitset const all = jtx::testable_amendments() - featureSingleAssetVault - featureLendingProtocol; - testInvalidRequest(); + testInvalidRequest(all); + testInvalidRequest(all - featureMPTokensV2); testFeatureDisabled(all - featureAMMClawback); for (auto const& features : - {all - fixAMMv1_3 - fixAMMClawbackRounding, all - fixAMMClawbackRounding, all}) + {all - fixAMMv1_3 - fixAMMClawbackRounding - featureMPTokensV2, + all - fixAMMClawbackRounding - featureMPTokensV2, + all - featureMPTokensV2, + all}) { testAMMClawbackSpecificAmount(features); testAMMClawbackExceedBalance(features); @@ -2509,5 +2532,4 @@ class AMMClawback_test : public beast::unit_test::suite } }; BEAST_DEFINE_TESTSUITE(AMMClawback, app, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/app/AMMExtendedMPT_test.cpp b/src/test/app/AMMExtendedMPT_test.cpp new file mode 100644 index 0000000000..44ced2edc2 --- /dev/null +++ b/src/test/app/AMMExtendedMPT_test.cpp @@ -0,0 +1,3661 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace xrpl::test { + +/** + * Tests of AMM MPT that use offers. + */ +struct AMMExtendedMPT_test : public jtx::AMMTest +{ +private: + void + testRmFundedOffer(FeatureBitset features) + { + testcase("Incorrect Removal of Funded Offers"); + + // We need at least two paths. One at good quality and one at bad + // quality. The bad quality path needs two offer books in a row. + // Each offer book should have two offers at the same quality, the + // offers should be completely consumed, and the payment should + // require both offers to be satisfied. The first offer must + // be "taker gets" XRP. Ensure that the payment engine does not remove + // the first "taker gets" xrp offer, because the offer is still + // funded and not used for the payment. + + using namespace jtx; + Env env{*this, features}; + + fund(env, gw, {alice, bob, carol}, XRP(10'000)); + + MPTTester const ETH( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol}, + .pay = 200'000'000'000'000'000, + .flags = MPTDEXFlags}); + + MPTTester const BTC( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol}, + .pay = 2'000'000'000'000'000, + .flags = MPTDEXFlags}); + + // Must be two offers at the same quality + // "taker gets" must be XRP + // (Different amounts so I can distinguish the offers) + env(offer(carol, BTC(49'000'000'000'000), XRP(49))); + env(offer(carol, BTC(51'000'000'000'000), XRP(51))); + + // Offers for the poor quality path + // Must be two offers at the same quality + env(offer(carol, XRP(50), ETH(50'000'000'000'000))); + env(offer(carol, XRP(50), ETH(50'000'000'000'000))); + + // Good quality path + AMM const ammCarol(env, carol, BTC(1'000'000'000'000'000), ETH(100'100'000'000'000'000)); + + PathSet const paths(Path(XRP, MPT(ETH)), Path(MPT(ETH))); + + env(pay(alice, bob, ETH(100'000'000'000'000)), + json(paths.json()), + sendmax(BTC(1'000'000'000'000'000)), + txflags(tfPartialPayment)); + + BEAST_EXPECT(ammCarol.expectBalances( + BTC(1'001'000'000'374'816), ETH(100'000'000'000'000'000), ammCarol.tokens())); + + env.require(balance(bob, ETH(200'100'000'000'000'000))); + BEAST_EXPECT(isOffer(env, carol, BTC(49'000'000'000'000), XRP(49))); + } + + void + testFillModes(FeatureBitset features) + { + testcase("Fill Modes"); + using namespace jtx; + + auto const startBalance = XRP(1'000'000); + + // Fill or Kill - unless we fully cross, just charge a fee and don't + // place the offer on the books. But also clean up expired offers + // that are discovered along the way. + testAMM( + [&](AMM& ammAlice, Env& env) { + auto const& BTC = MPT(ammAlice[1]); + auto const baseFee = env.current()->fees().base; + auto carolBTC = env.balance(carol, BTC); + auto carolXRP = env.balance(carol, XRP); + // Order that can't be filled + env(offer(carol, BTC(100), XRP(100)), txflags(tfFillOrKill), ter(tecKILLED)); + env.close(); + BEAST_EXPECT(ammAlice.expectBalances(XRP(10'100), BTC(10'000), ammAlice.tokens())); + // fee = AMM + env.require(balance(carol, carolXRP - baseFee)); + env.require(balance(carol, carolBTC)); + + BEAST_EXPECT(expectOffers(env, carol, 0)); + carolXRP = env.balance(carol, XRP); + + // Order that can be filled + env(offer(carol, XRP(100), BTC(100)), txflags(tfFillOrKill), ter(tesSUCCESS)); + BEAST_EXPECT(ammAlice.expectBalances(XRP(10'000), BTC(10'100), ammAlice.tokens())); + env.require(balance(carol, carolXRP + XRP(100) - baseFee)); + env.require(balance(carol, carolBTC - BTC(100))); + BEAST_EXPECT(expectOffers(env, carol, 0)); + }, + {{XRP(10'100), AMMMPT(10'000)}}, + 0, + std::nullopt, + {features}); + + // Immediate or Cancel - cross as much as possible + // and add nothing on the books. + testAMM( + [&](AMM& ammAlice, Env& env) { + auto const& BTC = MPT(ammAlice[1]); + auto const baseFee = env.current()->fees().base; + auto carolBTC = env.balance(carol, BTC); + auto carolXRP = env.balance(carol, XRP); + env(offer(carol, XRP(200), BTC(200)), + txflags(tfImmediateOrCancel), + ter(tesSUCCESS)); + + // AMM generates a synthetic offer of 100BTC/100XRP + // to match the CLOB offer quality. + BEAST_EXPECT(ammAlice.expectBalances(XRP(10'000), BTC(10'100), ammAlice.tokens())); + // +AMM - offer * fee + env.require(balance(carol, carolXRP + XRP(100) - baseFee)); + env.require(balance(carol, carolBTC - BTC(100))); + BEAST_EXPECT(expectOffers(env, carol, 0)); + }, + {{XRP(10'100), AMMMPT(10'000)}}, + 0, + std::nullopt, + {features}); + + // tfPassive -- place the offer without crossing it. + testAMM( + [&](AMM& ammAlice, Env& env) { + // Carol creates a passive offer that could cross AMM. + // Carol's offer should stay in the ledger. + auto const& BTC = MPT(ammAlice[1]); + env(offer(carol, XRP(100), BTC(100), tfPassive)); + env.close(); + BEAST_EXPECT(ammAlice.expectBalances(XRP(10'100), BTC(10'000), ammAlice.tokens())); + BEAST_EXPECT(expectOffers(env, carol, 1, {{{XRP(100), BTC(100)}}})); + }, + {{XRP(10'100), AMMMPT(10'000)}}, + 0, + std::nullopt, + {features}); + + // tfPassive -- cross only offers of better quality. + testAMM( + [&](AMM& ammAlice, Env& env) { + auto const& BTC = MPT(ammAlice[1]); + env(offer(alice, BTC(110), XRP(100))); + env.close(); + + // Carol creates a passive offer. That offer should cross + // AMM and leave Alice's offer untouched. + env(offer(carol, XRP(100), BTC(100), tfPassive)); + env.close(); + BEAST_EXPECT(ammAlice.expectBalances(XRP(10'900), BTC(9083), ammAlice.tokens())); + BEAST_EXPECT(expectOffers(env, carol, 0)); + BEAST_EXPECT(expectOffers(env, alice, 1)); + }, + {{XRP(11'000), AMMMPT(9'000)}}, + 0, + std::nullopt, + {features}); + } + + void + testOfferCrossWithXRP(FeatureBitset features) + { + testcase("Offer Crossing with XRP, Normal order"); + + using namespace jtx; + + Env env{*this, features}; + + fund(env, gw, {bob, alice}, XRP(300'000)); + + MPTTester const BTC( + {.env = env, + .issuer = gw, + .holders = {alice, bob}, + .pay = 100'000'000, + .flags = MPTDEXFlags}); + + AMM const ammAlice(env, alice, XRP(150'000), BTC(50'000'000)); + + // Existing offer pays better than this wants. + // Partially consume existing offer. + // Pay 1'000'000 BTC, get 3061224490 Drops. + auto const xrpTransferred = XRPAmount{3'061'224'490}; + env(offer(bob, BTC(1'000'000), XRP(4'000))); + + BEAST_EXPECT(ammAlice.expectBalances( + XRP(150'000) + xrpTransferred, BTC(49'000'000), IOUAmount{273'861'278752583, -5})); + + env.require(balance(bob, BTC(101'000'000))); + BEAST_EXPECT( + expectLedgerEntryRoot(env, bob, XRP(300'000) - xrpTransferred - 2 * txfee(env, 1))); + BEAST_EXPECT(expectOffers(env, bob, 0)); + } + + void + testOfferCrossWithLimitOverride(FeatureBitset features) + { + testcase("Offer Crossing with Limit Override"); + + using namespace jtx; + + Env env{*this, features}; + + env.fund(XRP(200'000), gw, alice, bob); + env.close(); + + MPTTester const BTC( + {.env = env, .issuer = gw, .holders = {alice, bob}, .flags = MPTDEXFlags}); + env(pay(gw, alice, BTC(500'000'000))); + + AMM const ammAlice(env, alice, XRP(150'000), BTC(51'000'000)); + env(offer(bob, BTC(1'000'000), XRP(3'000))); + + BEAST_EXPECT(ammAlice.expectBalances(XRP(153'000), BTC(50'000'000), ammAlice.tokens())); + + env.require(balance(bob, BTC(1'000'000))); + env.require(balance(bob, XRP(200'000) - XRP(3'000) - env.current()->fees().base * 2)); + } + + void + testCurrencyConversionEntire(FeatureBitset features) + { + testcase("Currency Conversion: Entire Offer"); + + using namespace jtx; + + Env env{*this, features}; + + fund(env, gw, {alice, bob}, XRP(10'000)); + env.require(owners(bob, 0)); + + MPTTester const BTC( + {.env = env, .issuer = gw, .holders = {alice, bob}, .flags = MPTDEXFlags}); + env(pay(gw, bob, BTC(1'000'000'000))); + + env.require(owners(alice, 1), owners(bob, 1)); + + env(pay(gw, alice, BTC(100'000'000))); + AMM const ammBob(env, bob, BTC(200'000'000), XRP(1'500)); + + env(pay(alice, alice, XRP(500)), sendmax(BTC(100'000'000))); + + BEAST_EXPECT(ammBob.expectBalances(BTC(300'000'000), XRP(1'000), ammBob.tokens())); + env.require(balance(alice, BTC(0))); + + auto jrr = ledgerEntryRoot(env, alice); + env.require(balance(alice, XRP(10'000) + XRP(500) - env.current()->fees().base * 2)); + } + + void + testCurrencyConversionInParts(FeatureBitset features) + { + testcase("Currency Conversion: In Parts"); + + using namespace jtx; + + Env env{*this, features}; + env.fund(XRP(30'000), gw, bob); + env.fund(XRP(40'000), alice); + + MPTTester const BTC( + {.env = env, + .issuer = gw, + .holders = {alice, bob}, + .pay = 30'000'000'000, + .flags = MPTDEXFlags}); + env(pay(gw, alice, BTC(10'000'000'000))); + + AMM const ammAlice(env, alice, XRP(10'000), BTC(10'000'000'000)); + env.close(); + + // Alice converts BTC to XRP which should fail + // due to PartialPayment. + env(pay(alice, alice, XRP(100)), sendmax(BTC(100'000'000)), ter(tecPATH_PARTIAL)); + + // Alice converts BTC to XRP, should succeed because + // we permit partial payment + env(pay(alice, alice, XRP(100)), sendmax(BTC(100'000'000)), txflags(tfPartialPayment)); + env.close(); + BEAST_EXPECT(ammAlice.expectBalances( + XRPAmount{9'900'990'100}, BTC(10'100'000'000), ammAlice.tokens())); + // initial 40,000'000'000 - 10,000'000'000AMM - 100'000'000pay + env.require(balance(alice, BTC(29'900'000'000))); + // initial 40,000 - 10,0000AMM + 99.009900pay - fee*3 + BEAST_EXPECT(expectLedgerEntryRoot( + env, + alice, + XRP(40'000) - XRP(10'000) + XRPAmount{99'009'900} - ammCrtFee(env) - txfee(env, 3))); + } + + void + testCrossCurrencyStartXRP(FeatureBitset features) + { + testcase("Cross Currency Payment: Start with XRP"); + + using namespace jtx; + + Env env{*this, features}; + env.fund(XRP(30'000), gw); + env.fund(XRP(40'000), alice); + env.fund(XRP(1'000), bob); + + MPTTester const BTC( + {.env = env, .issuer = gw, .holders = {alice, bob}, .flags = MPTDEXFlags}); + env(pay(gw, alice, BTC(10'100'000'000))); + + AMM const ammAlice(env, alice, XRP(10'000), BTC(10'100'000'000)); + env.close(); + + env(pay(alice, bob, BTC(100'000'000)), sendmax(XRP(100))); + BEAST_EXPECT(ammAlice.expectBalances(XRP(10'100), BTC(10'000'000'000), ammAlice.tokens())); + env.require(balance(bob, BTC(100'000'000))); + } + + void + testCrossCurrencyEndXRP(FeatureBitset features) + { + testcase("Cross Currency Payment: End with XRP"); + + using namespace jtx; + + Env env{*this, features}; + env.fund(XRP(30'000), gw); + env.fund(XRP(40'100), alice); + env.fund(XRP(1'000), bob); + + MPTTester const BTC( + {.env = env, .issuer = gw, .holders = {alice, bob}, .flags = MPTDEXFlags}); + env(pay(gw, alice, BTC(40'000'000'000))); + + AMM const ammAlice(env, alice, XRP(10'100), BTC(10'000'000'000)); + env.close(); + + env(pay(alice, bob, XRP(100)), sendmax(BTC(100'000'000))); + BEAST_EXPECT(ammAlice.expectBalances(XRP(10'000), BTC(10'100'000'000), ammAlice.tokens())); + BEAST_EXPECT(expectLedgerEntryRoot(env, bob, XRP(1'000) + XRP(100) - txfee(env, 1))); + } + + void + testCrossCurrencyBridged(FeatureBitset features) + { + testcase("Cross Currency Payment: Bridged"); + + using namespace jtx; + + auto test = [&](auto&& issue1, auto&& issue2) { + Env env(*this); + auto const dan = Account{"dan"}; + env.fund(XRP(60'000), alice, bob, carol, gw, dan); + env.close(); + auto const ETH = issue1( + {.env = env, + .token = "ETH", + .issuer = gw, + .holders = {alice, bob, carol, dan}, + .limit = 10'000'000'000'000'000}); + auto const BTC = issue2( + {.env = env, + .token = "BTC", + .issuer = gw, + .holders = {alice, bob, carol, dan}, + .limit = 10'000'000'000'000'000}); + env(pay(gw, alice, BTC(500'000'000'000'000))); + env(pay(gw, carol, BTC(6'000'000'000'000'000))); + env(pay(gw, dan, ETH(400'000'000'000'000))); + env.close(); + env.close(); + AMM const ammCarol(env, carol, BTC(5'000'000'000'000'000), XRP(50'000)); + + env(offer(dan, XRP(500), ETH(50'000'000'000'000))); + env.close(); + + Json::Value jtp{Json::arrayValue}; + jtp[0u][0u][jss::currency] = "XRP"; + env(pay(alice, bob, ETH(30'000'000'000'000)), + json(jss::Paths, jtp), + sendmax(BTC(333'000'000'000'000))); + env.close(); + BEAST_EXPECT(ammCarol.expectBalances( + XRP(49'700), BTC(5'030'181'086'519'115), ammCarol.tokens())); + BEAST_EXPECT(expectOffers(env, dan, 1, {{Amounts{XRP(200), ETH(20'000'000'000'000)}}})); + env.require(balance(bob, ETH(30'000'000'000'000))); + }; + testHelper2TokensMix(test); + } + + void + testOfferFeesConsumeFunds(FeatureBitset features) + { + testcase("Offer Fees Consume Funds"); + + using namespace jtx; + + Env env{*this, features}; + + // Provide micro amounts to compensate for fees to make results round + // nice. + auto const starting_xrp = + XRP(100) + env.current()->fees().accountReserve(2) + env.current()->fees().base * 3; + + env.fund(starting_xrp, gw, alice); + env.fund(XRP(2'000), bob); + env.close(); + + MPTTester const BTC( + {.env = env, .issuer = gw, .holders = {alice, bob}, .flags = MPTDEXFlags}); + + // Created only to increase one reserve count for alice + MPTTester const ETH({.env = env, .issuer = gw, .holders = {alice}, .flags = MPTDEXFlags}); + + env(pay(gw, bob, BTC(1'200'000'000'000'000))); + + AMM const ammBob(env, bob, XRP(1'000), BTC(1'200'000'000'000'000)); + // Alice has 400 - (2 reserve of 50 = 300 reserve) = 100 available. + // Ask for more than available to prove reserve works. + env(offer(alice, BTC(200'000'000'000'000), XRP(200))); + + // The pool gets only 100XRP for ~109.09e12BTC, even though + // it can exchange more. + BEAST_EXPECT( + ammBob.expectBalances(XRP(1'100), BTC(1'090'909'090'909'091), ammBob.tokens())); + + env.require(balance(alice, BTC(109'090'909'090'909))); + env.require(balance(alice, XRP(300))); + } + + void + testOfferCreateThenCross(FeatureBitset features) + { + testcase("Offer Create, then Cross"); + + using namespace jtx; + + Env env{*this, features}; + + fund(env, gw, {alice, bob}, XRP(200'000)); + + MPTTester const BTC( + {.env = env, + .issuer = gw, + .holders = {alice, bob}, + .transferFee = 500, + .flags = MPTDEXFlags}); + + env(pay(gw, bob, BTC(1'000'000'000'000))); + env(pay(gw, alice, BTC(200'000'000'000'000))); + + AMM const ammAlice(env, alice, BTC(150'000'000'000'000), XRP(150'100)); + env(offer(bob, XRP(100), BTC(100'000'000'000))); + + BEAST_EXPECT( + ammAlice.expectBalances(BTC(150'100'000'000'000), XRP(150'000), ammAlice.tokens())); + + // Bob pays 0.005 transfer fee. + env.require(balance(bob, BTC(899'500'000'000))); + } + + void + testSellFlagBasic(FeatureBitset features) + { + testcase("Offer tfSell: Basic Sell"); + + using namespace jtx; + + Env env{*this, features}; + env.fund(XRP(30'000), gw, bob, carol); + env.fund(XRP(39'900), alice); + + MPTTester const BTC( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol}, + .pay = 30'000, + .flags = MPTDEXFlags}); + env(pay(gw, alice, BTC(10'100))); + + AMM const ammAlice(env, alice, XRP(9'900), BTC(10'100)); + + env(offer(carol, BTC(100), XRP(100)), json(jss::Flags, tfSell)); + env.close(); + BEAST_EXPECT(ammAlice.expectBalances(XRP(10'000), BTC(9'999), ammAlice.tokens())); + BEAST_EXPECT(expectOffers(env, carol, 0)); + env.require(balance(carol, BTC(30'101))); + BEAST_EXPECT(expectLedgerEntryRoot(env, carol, XRP(30'000) - XRP(100) - 2 * txfee(env, 1))); + } + + void + testSellFlagExceedLimit(FeatureBitset features) + { + testcase("Offer tfSell: 2x Sell Exceed Limit"); + + using namespace jtx; + + Env env{*this, features}; + + auto const starting_xrp = XRP(100) + reserve(env, 1) + env.current()->fees().base * 2; + + env.fund(starting_xrp, gw, alice); + env.fund(XRP(2'000), bob); + env.close(); + + MPTTester const BTC( + {.env = env, .issuer = gw, .holders = {alice, bob}, .flags = MPTDEXFlags}); + env(pay(gw, bob, BTC(2'200'000'000))); + + AMM const ammBob(env, bob, XRP(1'000), BTC(2'200'000'000)); + // Alice has 350 fees - a reserve of 50 = 250 reserve = 100 available. + // Ask for more than available to prove reserve works. + // Taker pays 100'000'000 BTC for 100 XRP. + // Selling XRP. + // Will sell all 100 XRP and get more BTC than asked for. + env(offer(alice, BTC(100'000'000), XRP(200)), json(jss::Flags, tfSell)); + BEAST_EXPECT(ammBob.expectBalances(XRP(1'100), BTC(2'000'000'000), ammBob.tokens())); + env.require(balance(alice, BTC(200'000'000))); + BEAST_EXPECT(expectLedgerEntryRoot(env, alice, XRP(250))); + BEAST_EXPECT(expectOffers(env, alice, 0)); + } + + void + testGatewayCrossCurrency(FeatureBitset features) + { + testcase("Client Issue: Gateway Cross Currency"); + + using namespace jtx; + + Env env{*this, features}; + + auto const starting_xrp = XRP(100.1) + reserve(env, 1) + env.current()->fees().base * 2; + env.fund(starting_xrp, gw, alice, bob); + + MPTTester const XTS( + {.env = env, + .issuer = gw, + .holders = {alice, bob}, + .pay = 1'000'000'000'000'000, + .flags = MPTDEXFlags}); + MPTTester const XXX( + {.env = env, + .issuer = gw, + .holders = {alice, bob}, + .pay = 1'000'000'000'000'000, + .flags = MPTDEXFlags}); + + AMM const ammAlice(env, alice, XTS(1'000'000'000'000'000), XXX(1'000'000'000'000'000)); + + Json::Value payment; + payment[jss::secret] = toBase58(generateSeed("bob")); + payment[jss::id] = env.seq(bob); + payment[jss::build_path] = true; + payment[jss::tx_json] = pay(bob, bob, XXX(10'000'000'000'000)); + payment[jss::tx_json][jss::Sequence] = + env.current()->read(keylet::account(bob.id()))->getFieldU32(sfSequence); + payment[jss::tx_json][jss::Fee] = to_string(env.current()->fees().base); + payment[jss::tx_json][jss::SendMax] = + XTS(15'000'000'000'000).value().getJson(JsonOptions::none); + payment[jss::tx_json][jss::Flags] = tfPartialPayment; + auto const jrr = env.rpc("json", "submit", to_string(payment)); + BEAST_EXPECT(jrr[jss::result][jss::status] == "success"); + BEAST_EXPECT(jrr[jss::result][jss::engine_result] == "tesSUCCESS"); + + BEAST_EXPECT(ammAlice.expectBalances( + XTS(1'010'101'010'101'011), XXX(990'000'000'000'000), ammAlice.tokens())); + env.require(balance(bob, XTS(989'898'989'898'989))); + env.require(balance(bob, XXX(1'010'000'000'000'000))); + } + + void + testBridgedCross(FeatureBitset features) + { + testcase("Bridged Crossing"); + + using namespace jtx; + + { + Env env{*this, features}; + env.fund(XRP(30'000), gw, alice, bob, carol); + + MPTTester const BTC( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol}, + .pay = 15'000'000'000, + .flags = MPTDEXFlags}); + MPTTester const ETH( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol}, + .pay = 15'000'000'000, + .flags = MPTDEXFlags}); + + // The scenario: + // o BTC/XRP AMM is created. + // o ETH/XRP AMM is created. + // o carol has ETH but wants BTC. + // Note that carol's offer must come last. If carol's offer is + // placed before AMM is created, then autobridging will not occur. + AMM const ammAlice(env, alice, XRP(10'000), BTC(10'100'000'000)); + AMM const ammBob(env, bob, ETH(10'000'000'000), XRP(10'100)); + + // Carol makes an offer that consumes AMM liquidity and + // fully consumes Carol's offer. + env(offer(carol, BTC(100'000'000), ETH(100'000'000))); + env.close(); + + BEAST_EXPECT( + ammAlice.expectBalances(XRP(10'100), BTC(10'000'000'000), ammAlice.tokens())); + BEAST_EXPECT(ammBob.expectBalances(XRP(10'000), ETH(10'100'000'000), ammBob.tokens())); + env.require(balance(carol, BTC(15'100'000'000))); + env.require(balance(carol, ETH(14'900'000'000))); + BEAST_EXPECT(expectOffers(env, carol, 0)); + } + + { + Env env{*this, features}; + env.fund(XRP(30'000), gw, alice, bob, carol); + + MPTTester const BTC( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol}, + .pay = 15'000'000'000, + .flags = MPTDEXFlags}); + MPTTester const ETH( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol}, + .pay = 15'000'000'000, + .flags = MPTDEXFlags}); + + // The scenario: + // o BTC/XRP AMM is created. + // o ETH/XRP offer is created. + // o carol has ETH but wants BTC. + // Note that carol's offer must come last. If carol's offer is + // placed before AMM and bob's offer are created, then autobridging + // will not occur. + AMM const ammAlice(env, alice, XRP(10'000), BTC(10'100'000'000)); + env(offer(bob, ETH(100'000'000), XRP(100))); + env.close(); + + // Carol makes an offer that consumes AMM liquidity and + // fully consumes Carol's offer. + env(offer(carol, BTC(100'000'000), ETH(100'000'000))); + env.close(); + + BEAST_EXPECT( + ammAlice.expectBalances(XRP(10'100), BTC(10'000'000'000), ammAlice.tokens())); + env.require(balance(carol, BTC(15'100'000'000))); + env.require(balance(carol, ETH(14'900'000'000))); + BEAST_EXPECT(expectOffers(env, carol, 0)); + BEAST_EXPECT(expectOffers(env, bob, 0)); + } + + { + Env env{*this, features}; + env.fund(XRP(30'000), gw, alice, bob, carol); + + MPTTester const BTC( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol}, + .pay = 15'000'000'000, + .flags = MPTDEXFlags}); + MPTTester const ETH( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol}, + .pay = 15'000'000'000, + .flags = MPTDEXFlags}); + + // The scenario: + // o BTC/XRP offer is created. + // o ETH/XRP AMM is created. + // o carol has ETH but wants BTC. + // Note that carol's offer must come last. If carol's offer is + // placed before AMM and alice's offer are created, then + // autobridging will not occur. + env(offer(alice, XRP(100), BTC(100'000'000))); + env.close(); + AMM const ammBob(env, bob, ETH(10'000'000'000), XRP(10'100)); + + // Carol makes an offer that consumes AMM liquidity and + // fully consumes Carol's offer. + env(offer(carol, BTC(100'000'000), ETH(100'000'000))); + env.close(); + + BEAST_EXPECT(ammBob.expectBalances(XRP(10'000), ETH(10'100'000'000), ammBob.tokens())); + env.require(balance(carol, BTC(15'100'000'000))); + env.require(balance(carol, ETH(14'900'000'000))); + BEAST_EXPECT(expectOffers(env, carol, 0)); + BEAST_EXPECT(expectOffers(env, alice, 0)); + } + } + + void + testSellWithFillOrKill(FeatureBitset features) + { + // Test a number of different corner cases regarding offer crossing + // when both the tfSell flag and tfFillOrKill flags are set. + testcase("Combine tfSell with tfFillOrKill"); + + using namespace jtx; + + { + Env env{*this, features}; + env.fund(XRP(30'000), gw, alice, bob); + + MPTTester const BTC( + {.env = env, + .issuer = gw, + .holders = {alice, bob}, + .pay = 20'000'000'000, + .flags = MPTDEXFlags}); + AMM const ammBob(env, bob, XRP(20'000), BTC(200'000'000)); + // alice submits a tfSell | tfFillOrKill offer that does not cross. + env(offer(alice, BTC(2'100'000), XRP(210), tfSell | tfFillOrKill), ter(tecKILLED)); + + BEAST_EXPECT(ammBob.expectBalances(XRP(20'000), BTC(200'000'000), ammBob.tokens())); + BEAST_EXPECT(expectOffers(env, bob, 0)); + } + { + Env env{*this, features}; + env.fund(XRP(30'000), gw, alice, bob); + + MPTTester const BTC( + {.env = env, + .issuer = gw, + .holders = {alice, bob}, + .pay = 1'000'000'000'000'000, + .flags = MPTDEXFlags}); + AMM const ammBob(env, bob, XRP(20'000), BTC(200'000'000'000'000)); + // alice submits a tfSell | tfFillOrKill offer that crosses. + // Even though tfSell is present it doesn't matter this time. + env(offer(alice, BTC(2'000'000'000'000), XRP(220), tfSell | tfFillOrKill)); + env.close(); + BEAST_EXPECT( + ammBob.expectBalances(XRP(20'220), BTC(197'823'936'696'341), ammBob.tokens())); + env.require(balance(alice, BTC(1'002'176'063'303'659))); + BEAST_EXPECT(expectOffers(env, alice, 0)); + } + { + // alice submits a tfSell | tfFillOrKill offer that crosses and + // returns more than was asked for (because of the tfSell flag). + Env env{*this, features}; + env.fund(XRP(30'000), gw, alice, bob); + + MPTTester const BTC( + {.env = env, + .issuer = gw, + .holders = {alice, bob}, + .pay = 1'000'000'000'000'000, + .flags = MPTDEXFlags}); + AMM const ammBob(env, bob, XRP(20'000), BTC(200'000'000'000'000)); + + env(offer(alice, BTC(10'000'000'000'000), XRP(1'500), tfSell | tfFillOrKill)); + env.close(); + + BEAST_EXPECT( + ammBob.expectBalances(XRP(21'500), BTC(186'046'511'627'907), ammBob.tokens())); + env.require(balance(alice, BTC(1'013'953'488'372'093))); + BEAST_EXPECT(expectOffers(env, alice, 0)); + } + { + // alice submits a tfSell | tfFillOrKill offer that doesn't cross. + // This would have succeeded with a regular tfSell, but the + // fillOrKill prevents the transaction from crossing since not + // all of the offer is consumed because AMM generated offer, + // which matches alice's offer quality is ~ 10XRP/0.01996e3BTC. + Env env{*this, features}; + env.fund(XRP(30'000), gw, alice, bob); + + MPTTester const BTC( + {.env = env, + .issuer = gw, + .holders = {alice, bob}, + .pay = 10'000'000'000, + .flags = MPTDEXFlags}); + AMM const ammBob(env, bob, XRP(5000), BTC(10'000'000)); + + env(offer(alice, BTC(1'000'000), XRP(501), tfSell | tfFillOrKill), ter(tecKILLED)); + env.close(); + BEAST_EXPECT(expectOffers(env, alice, 0)); + BEAST_EXPECT(expectOffers(env, bob, 0)); + } + } + + void + testTransferRateOffer(FeatureBitset features) + { + testcase("Transfer Rate Offer"); + + using namespace jtx; + + // AMM XRP/BTC. Alice places BTC/XRP offer. + { + Env env(*this, features); + env.fund(XRP(30'000), gw, bob, carol); + env.fund(XRP(40'000), alice); + + MPTTester const BTC( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol}, + .transferFee = 25'000, + .pay = 30'000'000, + .flags = MPTDEXFlags}); + env(pay(gw, alice, BTC(10'100'000))); + + AMM const ammAlice(env, alice, XRP(10'000), BTC(10'100'000)); + env.close(); + + env(offer(carol, BTC(100'000), XRP(100))); + env.close(); + + // AMM doesn't pay the transfer fee + BEAST_EXPECT(ammAlice.expectBalances(XRP(10'100), BTC(10'000'000), ammAlice.tokens())); + env.require(balance(carol, BTC(30'100'000))); + BEAST_EXPECT(expectOffers(env, carol, 0)); + } + + { + Env env(*this, features); + env.fund(XRP(30'000), gw, bob, carol); + env.fund(XRP(40'100), alice); + + MPTTester const BTC( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol}, + .transferFee = 25'000, + .pay = 30'000'000, + .flags = MPTDEXFlags}); + env(pay(gw, alice, BTC(10'000'000))); + + AMM const ammAlice(env, alice, XRP(10'100), BTC(10'000'000)); + env.close(); + + env(offer(carol, XRP(100), BTC(100'000))); + env.close(); + + BEAST_EXPECT(ammAlice.expectBalances(XRP(10'000), BTC(10'100'000), ammAlice.tokens())); + // Carol pays 25% transfer fee + env.require(balance(carol, BTC(29'875'000))); + BEAST_EXPECT(expectOffers(env, carol, 0)); + } + + { + // Bridged crossing. + Env env{*this, features}; + env.fund(XRP(30'000), gw, alice, bob, carol); + + MPTTester const BTC( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol}, + .transferFee = 25'000, + .pay = 15'000'000, + .flags = MPTDEXFlags}); + MPTTester const ETH( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol}, + .transferFee = 25'000, + .pay = 15'000'000, + .flags = MPTDEXFlags}); + + // The scenario: + // o BTC/XRP AMM is created. + // o ETH/XRP Offer is created. + // o carol has ETH but wants BTC. + // Note that Carol's offer must come last. If Carol's offer is + // placed before AMM is created, then autobridging will not occur. + AMM const ammAlice(env, alice, XRP(10'000), BTC(10'100'000)); + env(offer(bob, ETH(100'000), XRP(100))); + env.close(); + + // Carol makes an offer that consumes AMM liquidity and + // fully consumes Bob's offer. + env(offer(carol, BTC(100'000), ETH(100'000))); + env.close(); + + // AMM doesn't pay the transfer fee + BEAST_EXPECT(ammAlice.expectBalances(XRP(10'100), BTC(10'000'000), ammAlice.tokens())); + env.require(balance(carol, BTC(15'100'000))); + // Carol pays 25% transfer fee. + env.require(balance(carol, ETH(14'875'000))); + BEAST_EXPECT(expectOffers(env, carol, 0)); + BEAST_EXPECT(expectOffers(env, bob, 0)); + } + + { + // Bridged crossing. The transfer fee is paid on the step not + // involving AMM as src/dst. + Env env{*this, features}; + env.fund(XRP(30'000), gw, alice, bob, carol); + + MPTTester const BTC( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol}, + .transferFee = 25'000, + .pay = 15'000'000, + .flags = MPTDEXFlags}); + MPTTester const ETH( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol}, + .transferFee = 25'000, + .pay = 15'000'000, + .flags = MPTDEXFlags}); + + // The scenario: + // o BTC/XRP AMM is created. + // o ETH/XRP Offer is created. + // o carol has ETH but wants BTC. + // Note that Carol's offer must come last. If Carol's offer is + // placed before AMM is created, then autobridging will not occur. + AMM const ammAlice(env, alice, XRP(10'000), BTC(10'050'000)); + env(offer(bob, ETH(100'000), XRP(100))); + env.close(); + + // Carol makes an offer that consumes AMM liquidity and + // partially consumes Bob's offer. + env(offer(carol, BTC(50'000), ETH(50'000))); + env.close(); + // This test verifies that the amount removed from an offer + // accounts for the transfer fee that is removed from the + // account but not from the remaining offer. + + // AMM doesn't pay the transfer fee + BEAST_EXPECT(ammAlice.expectBalances(XRP(10'050), BTC(10'000'000), ammAlice.tokens())); + env.require(balance(carol, BTC(15'050'000))); + // Carol pays 25% transfer fee. + env.require(balance(carol, ETH(14'937'500))); + BEAST_EXPECT(expectOffers(env, carol, 0)); + BEAST_EXPECT(expectOffers(env, bob, 1, {{Amounts{ETH(50'000), XRP(50)}}})); + } + } + + void + testSelfIssueOffer(FeatureBitset features) + { + // This test is not the same as corresponding testSelfIssueOffer() + // in the Offer_test. It simply tests AMM with self issue and + // offer crossing. + using namespace jtx; + + Env env{*this, features}; + + auto const f = env.current()->fees().base; + + env.fund(XRP(30'000) + f, alice, bob); + env.close(); + + MPTTester const BTC({.env = env, .issuer = bob, .holders = {alice}, .flags = MPTDEXFlags}); + + AMM const ammBob(env, bob, XRP(10'000), BTC(10'100)); + + env(offer(alice, BTC(100), XRP(100))); + env.close(); + + BEAST_EXPECT(ammBob.expectBalances(XRP(10'100), BTC(10'000), ammBob.tokens())); + BEAST_EXPECT(expectOffers(env, alice, 0)); + env.require(balance(alice, BTC(100))); + } + + void + testDirectToDirectPath(FeatureBitset features) + { + // The offer crossing code expects that a DirectStep is always + // preceded by a BookStep. In one instance the default path + // was not matching that assumption. Here we recreate that case + // so we can prove the bug stays fixed. + testcase("Direct to Direct path"); + + using namespace jtx; + + Env env{*this, features}; + + auto const ann = Account("ann"); + auto const bob = Account("bob"); + auto const cam = Account("cam"); + auto const carol = Account("carol"); + + auto const fee = env.current()->fees().base; + env.fund(XRP(1'000), carol); + env.fund(reserve(env, 4) + (fee * 5), ann, bob, cam); + env.close(); + + MPTTester const A_BUX( + {.env = env, .issuer = ann, .holders = {bob, cam, carol}, .flags = MPTDEXFlags}); + + MPTTester const B_BUX( + {.env = env, .issuer = bob, .holders = {ann, cam, carol}, .flags = MPTDEXFlags}); + + env(pay(ann, cam, A_BUX(350'000'000'000'000))); + env(pay(bob, cam, B_BUX(350'000'000'000'000))); + env(pay(bob, carol, B_BUX(4'000'000'000'000'000))); + env(pay(ann, carol, A_BUX(4'000'000'000'000'000))); + + AMM const ammCarol(env, carol, A_BUX(3'000'000'000'000'000), B_BUX(3'300'000'000'000'000)); + + // cam puts an offer on the books that her upcoming offer could cross. + // But this offer should be deleted, not crossed, by her upcoming + // offer. + env(offer(cam, A_BUX(290'000'000'000'000), B_BUX(300'000'000'000'000), tfPassive)); + env.close(); + env.require(balance(cam, A_BUX(350'000'000'000'000))); + env.require(balance(cam, B_BUX(350'000'000'000'000))); + env.require(offers(cam, 1)); + + // This offer caused the assert. + env(offer(cam, B_BUX(300'000'000'000'000), A_BUX(300'000'000'000'000))); + + // AMM is consumed up to the first cam Offer quality + BEAST_EXPECT(ammCarol.expectBalances( + A_BUX(3'093'541'659'651'604), B_BUX(3'200'215'509'984'418), ammCarol.tokens())); + BEAST_EXPECT(expectOffers( + env, cam, 1, {{Amounts{B_BUX(200'215'509'984'418), A_BUX(200'215'509'984'419)}}})); + } + + void + testRequireAuth(FeatureBitset features) + { + testcase("RequireAuth"); + + using namespace jtx; + + Env env{*this, features}; + env.fund(XRP(400'000), gw, alice, bob); + + MPTTester BTC( + {.env = env, + .issuer = gw, + .holders = {alice, bob}, + .flags = tfMPTRequireAuth | MPTDEXFlags}); + + // Authorize bob and alice + BTC.authorize({.holder = alice}); + BTC.authorize({.holder = bob}); + + env(pay(gw, alice, BTC(1'000))); + env.close(); + + // Alice is able to create AMM since the GW has authorized her + AMM const ammAlice(env, alice, BTC(1'000), XRP(1'050)); + + env(pay(gw, bob, BTC(50))); + env.close(); + + env.require(balance(bob, BTC(50))); + + // Bob's offer should cross Alice's AMM + env(offer(bob, XRP(50), BTC(50))); + env.close(); + + BEAST_EXPECT(ammAlice.expectBalances(BTC(1'050), XRP(1'000), ammAlice.tokens())); + BEAST_EXPECT(expectOffers(env, bob, 0)); + env.require(balance(bob, BTC(0))); + } + + void + testMissingAuth(FeatureBitset features) + { + testcase("Missing Auth"); + + using namespace jtx; + + Env env{*this, features}; + + env.fund(XRP(400'000), gw, alice, bob); + env.close(); + + MPTTester BTC( + {.env = env, + .issuer = gw, + .holders = {alice, bob}, + .flags = tfMPTRequireAuth | MPTDEXFlags}); + + // Alice doesn't have the funds + { + AMM const ammAlice(env, alice, BTC(1'000), XRP(1'000), ter(tecNO_AUTH)); + } + + BTC.authorize({.holder = bob}); + env(pay(gw, bob, BTC(50))); + env.close(); + env.require(balance(bob, BTC(50))); + + // Alice should not be able to create AMM without authorization. + { + AMM const ammAlice(env, alice, BTC(1'000), XRP(1'000), ter(tecNO_AUTH)); + } + + // Finally, authorize alice. Now alice's AMM create should succeed. + BTC.authorize({.holder = alice}); + env(pay(gw, alice, BTC(1'000))); + env.close(); + + AMM const ammAlice(env, alice, BTC(1'000), XRP(1'050)); + + // Authorize AMM. + // BTC.authorize({.account = ammAlice.ammAccount()}); + // env.close(); + + // Now bob creates his offer again, which crosses with alice's AMM. + env(offer(bob, XRP(50), BTC(50))); + env.close(); + + BEAST_EXPECT(ammAlice.expectBalances(BTC(1'050), XRP(1'000), ammAlice.tokens())); + BEAST_EXPECT(expectOffers(env, bob, 0)); + env.require(balance(bob, BTC(0))); + } + + void + testOffers() + { + using namespace jtx; + FeatureBitset const all{testable_amendments()}; + testRmFundedOffer(all); + testFillModes(all); + testOfferCrossWithXRP(all); + testOfferCrossWithLimitOverride(all); + testCurrencyConversionEntire(all); + testCurrencyConversionInParts(all); + testCrossCurrencyStartXRP(all); + testCrossCurrencyEndXRP(all); + testCrossCurrencyBridged(all); + testOfferFeesConsumeFunds(all); + testOfferCreateThenCross(all); + testSellFlagExceedLimit(all); + testGatewayCrossCurrency(all); + testBridgedCross(all); + testSellWithFillOrKill(all); + testTransferRateOffer(all); + testSelfIssueOffer(all); + testSellFlagBasic(all); + testDirectToDirectPath(all); + testRequireAuth(all); + testMissingAuth(all); + } + + void + path_find_consume_all() + { + testcase("path find consume all"); + using namespace jtx; + + Env env = pathTestEnv(); + env.fund(XRP(100'000'260), alice); + env.fund(XRP(30'000), gw, bob, carol); + + MPTTester const ETH( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol}, + .pay = 100'000'000'000'000, + .flags = MPTDEXFlags}); + + AMM const ammCarol(env, carol, XRP(100), ETH(100'000'000'000'000)); + + STPathSet st; + STAmount sa; + STAmount da; + std::tie(st, sa, da) = + find_paths(env, alice, bob, bob["AUD"](-1), std::optional(XRP(100'000'000))); + BEAST_EXPECT(st.empty()); + std::tie(st, sa, da) = + find_paths(env, alice, bob, ETH(-1), std::optional(XRP(100'000'000))); + // Alice sends all requested 100,000,000XRP + BEAST_EXPECT(sa == XRP(100'000'000)); + // Bob gets ~99.99e12ETH. This is the amount Bob + // can get out of AMM for 100,000,000XRP. + BEAST_EXPECT(equal(da, ETH(99'999'900'000'100))); + } + + // carol holds ETH, sells ETH for XRP + // bob will hold ETH + // alice pays bob ETH using XRP + void + via_offers_via_gateway() + { + testcase("via gateway"); + using namespace jtx; + + Env env = pathTestEnv(); + env.fund(XRP(10'000), alice, bob, carol, gw); + env.close(); + + MPTTester const ETH( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol}, + .transferFee = 10'000, + .flags = MPTDEXFlags}); + + MPTTester const BTC( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol}, + .transferFee = 10'000, + .flags = MPTDEXFlags}); + + env(pay(gw, carol, ETH(51))); + env.close(); + AMM const ammCarol(env, carol, XRP(40), ETH(51)); + env(pay(alice, bob, ETH(10)), sendmax(XRP(100)), paths(XRP)); + env.close(); + // AMM offer is 51.282052XRP/11ETH, 11ETH/1.1 = 10ETH to bob + BEAST_EXPECT(ammCarol.expectBalances(XRP(51), ETH(40), ammCarol.tokens())); + env.require(balance(bob, ETH(10))); + + auto const result = find_paths(env, alice, bob, BTC(25)); + BEAST_EXPECT(std::get<0>(result).empty()); + } + + void + receive_max() + { + testcase("Receive max"); + using namespace jtx; + auto const charlie = Account("charlie"); + { + // XRP -> MPT receive max + Env env = pathTestEnv(); + env.fund(XRP(30'000), alice, bob, charlie, gw); + + MPTTester const ETH( + {.env = env, + .issuer = gw, + .holders = {alice, bob, charlie}, + .pay = 11'000'000'000'000, + .flags = MPTDEXFlags}); + + AMM const ammCharlie(env, charlie, XRP(10), ETH(11'000'000'000'000)); + auto [st, sa, da] = find_paths(env, alice, bob, ETH(-1), XRP(1).value()); + BEAST_EXPECT(sa == XRP(1)); + BEAST_EXPECT(equal(da, ETH(1'000'000'000'000))); + if (BEAST_EXPECT(st.size() == 1 && st[0].size() == 1)) + { + auto const& pathElem = st[0][0]; + BEAST_EXPECT( + pathElem.isOffer() && pathElem.getIssuerID() == gw.id() && + pathElem.getMPTID() == ETH.issuanceID()); + } + } + { + // MPT -> XRP receive max + Env env = pathTestEnv(); + env.fund(XRP(30'000), alice, bob, charlie, gw); + + MPTTester const ETH( + {.env = env, + .issuer = gw, + .holders = {alice, bob, charlie}, + .pay = 11'000'000'000'000, + .flags = MPTDEXFlags}); + + AMM const ammCharlie(env, charlie, XRP(11), ETH(10'000'000'000'000)); + env.close(); + auto [st, sa, da] = + find_paths(env, alice, bob, drops(-1), ETH(1'000'000'000'000).value()); + BEAST_EXPECT(sa == ETH(1'000'000'000'000)); + BEAST_EXPECT(equal(da, XRP(1))); + if (BEAST_EXPECT(st.size() == 1 && st[0].size() == 1)) + { + auto const& pathElem = st[0][0]; + BEAST_EXPECT( + pathElem.isOffer() && pathElem.getIssuerID() == xrpAccount() && + pathElem.getCurrency() == xrpCurrency()); + } + } + } + + void + path_find_01() + { + testcase("Path Find: XRP -> XRP and XRP -> MPT"); + using namespace jtx; + Env env = pathTestEnv(); + Account A1{"A1"}; + Account A2{"A2"}; + Account A3{"A3"}; + Account const G1{"G1"}; + Account const G2{"G2"}; + Account G3{"G3"}; + Account M1{"M1"}; + + env.fund(XRP(100'000), A1); + env.fund(XRP(10'000), A2); + env.fund(XRP(1'000), A3, G1, G2, G3); + env.fund(XRP(20'000), M1); + env.close(); + + MPTTester const XYZ_G1( + {.env = env, .issuer = G1, .holders = {A1, M1, A2}, .flags = MPTDEXFlags}); + + MPTTester const XYZ_G2( + {.env = env, .issuer = G2, .holders = {A2, M1, A1}, .flags = MPTDEXFlags}); + + MPTTester const ABC_G3( + {.env = env, .issuer = G3, .holders = {A1, A2, M1, A3}, .flags = MPTDEXFlags}); + + MPTTester const ABC_A2( + {.env = env, .issuer = A2, .holders = {G3, A1}, .flags = MPTDEXFlags}); + + env(pay(G1, A1, XYZ_G1(3'500'000'000))); + env(pay(G3, A1, ABC_G3(1'200'000'000))); + env(pay(G1, M1, XYZ_G1(25'000'000'000))); + env(pay(G2, M1, XYZ_G2(25'000'000'000))); + env(pay(G3, M1, ABC_G3(25'000'000'000))); + env(pay(A2, G3, ABC_A2(101'000'000))); + env.close(); + + AMM const ammM1_XYZ_G1_XYZ_G2(env, M1, XYZ_G1(1'000'000'000), XYZ_G2(1'000'000'000)); + AMM const ammM1_XRP_ABC_G3(env, M1, XRP(10'000), ABC_G3(1'000'000'000)); + AMM const ammG3_ABC_G3_ABC_A2(env, G3, ABC_G3(100'000'000), ABC_A2(101'000'000)); + env.close(); + + STPathSet st; + STAmount sa, da; + + { + auto const& send_amt = XRP(10); + std::tie(st, sa, da) = find_paths(env, A1, A2, send_amt, std::nullopt, xrpCurrency()); + BEAST_EXPECT(equal(da, send_amt)); + BEAST_EXPECT(st.empty()); + } + + { + // no path should exist for this since dest account + // does not exist. + auto const& send_amt = XRP(200); + std::tie(st, sa, da) = + find_paths(env, A1, Account{"A0"}, send_amt, std::nullopt, xrpCurrency()); + BEAST_EXPECT(equal(da, send_amt)); + BEAST_EXPECT(st.empty()); + } + + { + auto const& send_amt = ABC_G3(10'000'000); + std::tie(st, sa, da) = find_paths(env, A2, G3, send_amt, std::nullopt, xrpCurrency()); + BEAST_EXPECT(equal(da, send_amt)); + BEAST_EXPECT(equal(sa, XRPAmount{101'010'102})); + BEAST_EXPECT(same(st, stpath(IPE(MPT(ABC_G3))))); + } + + { + auto const& send_amt = ABC_A2(1'000'000); + std::tie(st, sa, da) = find_paths(env, A1, A2, send_amt, std::nullopt, xrpCurrency()); + BEAST_EXPECT(equal(da, send_amt)); + BEAST_EXPECT(equal(sa, XRPAmount{10'010'011})); + BEAST_EXPECT(same(st, stpath(IPE(MPT(ABC_G3)), IPE(MPT(ABC_A2))))); + } + } + + void + path_find_02() + { + testcase("Path Find: non-XRP -> XRP"); + using namespace jtx; + Env env = pathTestEnv(); + Account A1{"A1"}; + Account A2{"A2"}; + Account const G3{"G3"}; + Account M1{"M1"}; + + env.fund(XRP(1'000), A1, A2, G3); + env.fund(XRP(11'000), M1); + env.close(); + + MPTTester const ETH( + {.env = env, + .issuer = G3, + .holders = {A1, A2, M1}, + .pay = 1'000'000'000, + .flags = MPTDEXFlags}); + + AMM const ammM1(env, M1, ETH(1'000'000'000), XRP(10'010)); + + STPathSet st; + STAmount sa, da; + + auto const& send_amt = XRP(10); + + std::tie(st, sa, da) = + find_paths_by_element(env, A1, A2, send_amt, std::nullopt, IPE(MPT(ETH))); + BEAST_EXPECT(equal(da, send_amt)); + BEAST_EXPECT(equal(sa, ETH(1'000'000))); + BEAST_EXPECT(same(st, stpath(IPE(xrpIssue())))); + } + + void + path_find_06() + { + testcase("Path Find: non-XRP -> non-XRP, same issuanceID"); + using namespace jtx; + { + Env env = pathTestEnv(); + Account A1{"A1"}; + Account A2{"A2"}; + Account const A3{"A3"}; + Account const G1{"G1"}; + Account const G2{"G2"}; + Account M1{"M1"}; + + env.fund(XRP(11'000), M1); + env.fund(XRP(1'000), A1, A2, A3, G1, G2); + env.close(); + + MPTTester const HKD_G1( + {.env = env, + .issuer = G1, + .holders = {A1, M1}, + .pay = 5'000'000'000, + .flags = MPTDEXFlags}); + + MPTTester const HKD_G2( + {.env = env, + .issuer = G2, + .holders = {A2, M1}, + .pay = 5'000'000'000, + .flags = MPTDEXFlags}); + + AMM const ammM1(env, M1, HKD_G1(1'000'000'000), HKD_G2(1'010'000'000)); + + auto const& send_amt = HKD_G2(10'000'000); + STPathSet st; + STAmount sa, da; + std::tie(st, sa, da) = jtx::find_paths( + env, + G1, + A2, + send_amt, + std::nullopt, + HKD_G1.issuanceID(), + std::nullopt, + std::nullopt); + BEAST_EXPECT(equal(da, send_amt)); + BEAST_EXPECT(equal(sa, HKD_G1(10'000'000))); + BEAST_EXPECT(same(st, stpath(IPE(MPT(HKD_G2))))); + } + } + + void + testFalseDry(FeatureBitset features) + { + testcase("falseDryChanges"); + + using namespace jtx; + + Env env(*this, features); + env.memoize(bob); + + env.fund(XRP(10'000), alice, gw); + fund(env, gw, {carol}, XRP(10'000), {}, Fund::Acct); + auto const AMMXRPPool = env.current()->fees().increment * 2; + env.fund(reserve(env, 5) + ammCrtFee(env) + AMMXRPPool, bob); + env.close(); + + MPTTester const ETH( + {.env = env, .issuer = gw, .holders = {alice, bob, carol}, .flags = MPTDEXFlags}); + + MPTTester const BTC( + {.env = env, .issuer = gw, .holders = {alice, bob, carol}, .flags = MPTDEXFlags}); + + env(pay(gw, alice, ETH(50'000))); + env(pay(gw, bob, BTC(150'000))); + + // Bob has _just_ slightly less than 50 xrp available + // If his owner count changes, he will have more liquidity. + // This is one error case to test (when Flow is used). + // Computing the incoming xrp to the XRP/BTC offer will require two + // recursive calls to the ETH/XRP offer. The second call will return + // tecPATH_DRY, but the entire path should not be marked as dry. + // This is the second error case to test (when flowV1 is used). + env(offer(bob, ETH(50'000), XRP(50))); + AMM const ammBob(env, bob, AMMXRPPool, BTC(150'000)); + + env(pay(alice, carol, BTC(1'000'000'000)), + path(~XRP, ~MPT(BTC)), + sendmax(ETH(500'000)), + txflags(tfNoRippleDirect | tfPartialPayment)); + + auto const carolBTC = env.balance(carol, MPT(BTC)); + BEAST_EXPECT(carolBTC > BTC(0) && carolBTC < BTC(50'000)); + } + + void + testBookStep(FeatureBitset features) + { + testcase("Book Step"); + + using namespace jtx; + + // simple MPT/IOU mix offer + { + auto test = [&](auto&& issue1, auto&& issue2) { + Env env(*this); + env.fund(XRP(30'000), alice, bob, carol, gw); + env.close(); + auto const ETH = issue1( + {.env = env, + .token = "ETH", + .issuer = gw, + .holders = {alice, bob, carol}, + .limit = 100'000'000}); + auto const BTC = issue2( + {.env = env, + .token = "BTC", + .issuer = gw, + .holders = {alice, bob, carol}, + .limit = 100'000'000}); + env(pay(gw, alice, BTC(500000))); + env(pay(gw, bob, BTC(500000))); + env(pay(gw, carol, BTC(500000))); + env(pay(gw, alice, ETH(500000))); + env(pay(gw, bob, ETH(500000))); + env(pay(gw, carol, ETH(500000))); + env.close(); + AMM const ammBob(env, bob, BTC(100'000), ETH(150'000)); + + env(pay(alice, carol, ETH(50'000)), path(~ETH), sendmax(BTC(50'000))); + + env.require(balance(alice, BTC(450'000))); + env.require(balance(bob, BTC(400'000))); + env.require(balance(bob, ETH(350'000))); + env.require(balance(carol, ETH(550'000))); + BEAST_EXPECT(ammBob.expectBalances(BTC(150'000), ETH(100'000), ammBob.tokens())); + }; + testHelper2TokensMix(test); + } + + { + // simple MPT/XRP XRP/MPT offer + Env env(*this, features); + env.fund(XRP(10'000), gw, alice, bob, carol); + + MPTTester const BTC( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol}, + .pay = 100'000, + .flags = MPTDEXFlags}); + + MPTTester const ETH( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol}, + .pay = 150'000, + .flags = MPTDEXFlags}); + + AMM const ammBobBTC_XRP(env, bob, BTC(100'000), XRP(150)); + AMM const ammBobXRP_ETH(env, bob, XRP(100), ETH(150'000)); + + env(pay(alice, carol, ETH(50'000)), path(~XRP, ~MPT(ETH)), sendmax(BTC(50'000))); + + env.require(balance(alice, BTC(50'000))); + env.require(balance(bob, BTC(0))); + env.require(balance(bob, ETH(0))); + env.require(balance(carol, ETH(200'000))); + BEAST_EXPECT( + ammBobBTC_XRP.expectBalances(BTC(150'000), XRP(100), ammBobBTC_XRP.tokens())); + BEAST_EXPECT( + ammBobXRP_ETH.expectBalances(XRP(150), ETH(100'000), ammBobXRP_ETH.tokens())); + } + { + // simple XRP -> MPT through offer and sendmax + Env env(*this, features); + XRPAmount const baseFee{env.current()->fees().base}; + env.fund(XRP(10'000), gw, alice, bob, carol); + + MPTTester const ETH( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol}, + .pay = 150'000, + .flags = MPTDEXFlags}); + + AMM const ammBob(env, bob, XRP(100), ETH(150'000)); + + env(pay(alice, carol, ETH(50'000)), path(~MPT(ETH)), sendmax(XRP(50))); + BEAST_EXPECT(expectLedgerEntryRoot(env, alice, XRP(10'000) - XRP(50) - 2 * baseFee)); + BEAST_EXPECT( + expectLedgerEntryRoot(env, bob, XRP(10'000) - XRP(100) - ammCrtFee(env) - baseFee)); + env.require(balance(bob, ETH(0))); + env.require(balance(carol, ETH(200'000))); + BEAST_EXPECT(ammBob.expectBalances(XRP(150), ETH(100'000), ammBob.tokens())); + } + { + // simple MPT -> XRP through offer and sendmax + Env env(*this, features); + XRPAmount const baseFee{env.current()->fees().base}; + env.fund(XRP(10'000), gw, alice, bob, carol); + + MPTTester const ETH( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol}, + .pay = 100'000, + .flags = MPTDEXFlags}); + + AMM const ammBob(env, bob, ETH(100'000), XRP(150)); + + env(pay(alice, carol, XRP(50)), path(~XRP), sendmax(ETH(50'000))); + + env.require(balance(alice, ETH(50'000))); + BEAST_EXPECT( + expectLedgerEntryRoot(env, bob, XRP(10'000) - XRP(150) - ammCrtFee(env) - baseFee)); + env.require(balance(bob, ETH(0))); + BEAST_EXPECT(expectLedgerEntryRoot(env, carol, XRP(10'000 + 50) - baseFee)); + BEAST_EXPECT(ammBob.expectBalances(ETH(150'000), XRP(100), ammBob.tokens())); + } + + // test unfunded offers are removed when payment succeeds + { + auto test = [&](auto&& issue1, auto&& issue2, auto&& issue3) { + Env env(*this, features); + env.fund(XRP(10'000), alice, bob, carol, gw); + env.close(); + auto const BTC = issue1( + {.env = env, + .token = "BTC", + .issuer = gw, + .holders = {alice, bob, carol}, + .limit = 1000'000000}); + auto const ETH = issue2( + {.env = env, + .token = "ETH", + .issuer = gw, + .holders = {alice, bob, carol}, + .limit = 1000'000000}); + auto const GBP = issue3( + {.env = env, + .token = "GBP", + .issuer = gw, + .holders = {alice, bob, carol}, + .limit = 1000'000000}); + + env(pay(gw, alice, BTC(60'000))); + env(pay(gw, bob, ETH(200'000))); + env(pay(gw, bob, GBP(150'000))); + env(offer(bob, BTC(50'000), ETH(50'000))); + env(offer(bob, BTC(40'000), GBP(50'000))); + env.close(); + AMM const ammBob(env, bob, GBP(100'000), ETH(150'000)); + + // unfund offer + env(pay(bob, gw, GBP(50'000))); + BEAST_EXPECT(isOffer(env, bob, BTC(50'000), ETH(50'000))); + BEAST_EXPECT(isOffer(env, bob, BTC(40'000), GBP(50'000))); + env(pay(alice, carol, ETH(50'000)), + path(~ETH), + path(~GBP, ~ETH), + sendmax(BTC(60'000))); + env.require(balance(alice, BTC(10'000))); + env.require(balance(bob, BTC(50'000))); + env.require(balance(bob, ETH(0))); + env.require(balance(bob, GBP(0))); + env.require(balance(carol, ETH(50'000))); + // used in the payment + BEAST_EXPECT(!isOffer(env, bob, BTC(50'000), ETH(50'000))); + // found unfunded + BEAST_EXPECT(!isOffer(env, bob, BTC(40'000), GBP(50'000))); + // unchanged + BEAST_EXPECT(ammBob.expectBalances(GBP(100'000), ETH(150'000), ammBob.tokens())); + }; + testHelper3TokensMix(test); + } + + { + // test unfunded offers are removed when the payment fails. + // bob makes two offers: a funded 50'000'000 ETH for 50'000'000 BTC + // and an unfunded 50'000'000 GBP for 60'000'000 BTC. alice pays + // carol 61'000'000 ETH with 61'000'000 BTC. alice only has + // 60'000'000 BTC, so the payment will fail. The payment uses two + // paths: one through bob's funded offer and one through his + // unfunded offer. When the payment fails `flow` should return the + // unfunded offer. This test is intentionally similar to the one + // that removes unfunded offers when the payment succeeds. + Env env(*this, features); + + env.fund(XRP(10'000), bob, carol, gw); + env.close(); + // Sets rippling on, this is different from + // the original test + fund(env, gw, {alice}, XRP(10'000), {}, Fund::Acct); + + MPTTester BTC( + {.env = env, .issuer = gw, .holders = {alice, bob, carol}, .flags = MPTDEXFlags}); + + MPTTester ETH( + {.env = env, .issuer = gw, .holders = {alice, bob, carol}, .flags = MPTDEXFlags}); + + MPTTester GBP( + {.env = env, .issuer = gw, .holders = {alice, bob, carol}, .flags = MPTDEXFlags}); + + env(pay(gw, alice, BTC(60'000'000))); + env(pay(gw, bob, BTC(100'000'000))); + env(pay(gw, bob, ETH(100'000'000))); + env(pay(gw, bob, GBP(50'000'000))); + env(pay(gw, carol, GBP(1'000'000))); + env.close(); + + // This is multiplath, which generates limited # of offers + AMM const ammBobBTC_ETH(env, bob, BTC(50'000'000), ETH(50'000'000)); + env(offer(bob, BTC(60'000'000), GBP(50'000'000))); + env(offer(carol, BTC(1'000'000'000), GBP(1'000'000))); + env(offer(bob, GBP(50'000'000), ETH(50'000'000))); + + // unfund offer + env(pay(bob, gw, GBP(50'000'000))); + BEAST_EXPECT(ammBobBTC_ETH.expectBalances( + BTC(50'000'000), ETH(50'000'000), ammBobBTC_ETH.tokens())); + BEAST_EXPECT(isOffer(env, bob, BTC(60'000'000), GBP(50'000'000))); + BEAST_EXPECT(isOffer(env, carol, BTC(1'000'000'000), GBP(1'000'000))); + BEAST_EXPECT(isOffer(env, bob, GBP(50'000'000), ETH(50'000'000))); + + auto flowJournal = env.app().getLogs().journal("Flow"); + auto const flowResult = [&] { + STAmount const deliver(ETH(51'000'000)); + STAmount smax(BTC(61'000'000)); + PaymentSandbox sb(env.current().get(), tapNONE); + STPathSet paths; + auto IPE = [](MPTTester const& iss) { + return STPathElement( + STPathElement::typeMPT | STPathElement::typeIssuer, + xrpAccount(), + PathAsset{iss.issuanceID()}, + iss.issuer()); + }; + { + // BTC -> ETH + STPath const p1({IPE(ETH)}); + paths.push_back(p1); + // BTC -> GBP -> ETH + STPath const p2({IPE(GBP), IPE(ETH)}); + paths.push_back(p2); + } + + return flow( + sb, + deliver, + alice, + carol, + paths, + false, + false, + true, + OfferCrossing::no, + std::nullopt, + smax, + std::nullopt, + flowJournal); + }(); + + BEAST_EXPECT(flowResult.removableOffers.size() == 1); + env.app().getOpenLedger().modify([&](OpenView& view, beast::Journal j) { + if (flowResult.removableOffers.empty()) + return false; + Sandbox sb(&view, tapNONE); + for (auto const& o : flowResult.removableOffers) + { + if (auto ok = sb.peek(keylet::offer(o))) + { + offerDelete(sb, ok, flowJournal); + } + } + sb.apply(view); + return true; + }); + + // used in payment, but since payment failed should be untouched + BEAST_EXPECT(ammBobBTC_ETH.expectBalances( + BTC(50'000'000), ETH(50'000'000), ammBobBTC_ETH.tokens())); + BEAST_EXPECT(isOffer(env, carol, BTC(1'000'000'000), GBP(1'000'000))); + // found unfunded + BEAST_EXPECT(!isOffer(env, bob, BTC(60'000'000), GBP(50'000'000))); + } + { + // Do not produce more in the forward pass than the reverse pass + // This test uses a path that whose reverse pass will compute a + // 500 ETH input required for a 1'000 BTC output. It sets a sendmax + // of 400 ETH, so the payment engine will need to do a forward + // pass. Without limits, the 400 ETH would produce 1'000 BTC in + // the forward pass. This test checks that the payment produces + // 1'000 BTC, as expected. + + auto test = [&](auto&& issue1, auto&& issue2) { + Env env(*this); + env.fund(XRP(30'000), alice, bob, carol, gw); + env.close(); + auto const ETH = issue1( + {.env = env, + .token = "ETH", + .issuer = gw, + .holders = {alice, bob, carol}, + .limit = 10'000'000}); + auto const BTC = issue2( + {.env = env, + .token = "BTC", + .issuer = gw, + .holders = {alice, bob, carol}, + .limit = 10'000'000}); + + env(pay(gw, alice, ETH(1'000'000))); + env(pay(gw, bob, BTC(1'000'000))); + env(pay(gw, bob, ETH(1'000'000))); + env.close(); + + AMM const ammBob(env, bob, ETH(8'000), XRPAmount{21}); + env(offer(bob, drops(1), BTC(1'000'000)), txflags(tfPassive)); + + env(pay(alice, carol, BTC(1'000)), + path(~XRP, ~BTC), + sendmax(ETH(400)), + txflags(tfNoRippleDirect | tfPartialPayment)); + + env.require(balance(carol, BTC(1'000))); + BEAST_EXPECT(ammBob.expectBalances(ETH(8400), XRPAmount{20}, ammBob.tokens())); + }; + testHelper2TokensMix(test); + } + } + + void + testTransferRateNoOwnerFee(FeatureBitset features) + { + testcase("No Owner Fee"); + using namespace jtx; + + { + // payment via AMM + Env env(*this, features); + env.fund(XRP(1'000), gw, alice, bob, carol); + + MPTTester const GBP( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol}, + .transferFee = 25'000, + .pay = 1'000'000'000'000'000, + .flags = MPTDEXFlags}); + + MPTTester const BTC( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol}, + .transferFee = 25'000, + .pay = 1'000'000'000'000'000, + .flags = MPTDEXFlags}); + + AMM const amm(env, bob, GBP(1'000'000'000'000'000), BTC(1'000'000'000'000'000)); + + env(pay(alice, carol, BTC(100'000'000'000'000)), + path(~MPT(BTC)), + sendmax(GBP(150'000'000'000'000)), + txflags(tfNoRippleDirect | tfPartialPayment)); + env.close(); + + // alice buys 107.1428e12BTC with 120e12GBP and pays 25% tr fee on + // 120e12GBP 1,000e12 - 120e12*1.25 = 850e12GBP + env.require(balance(alice, GBP(850'000'000'000'000))); + + BEAST_EXPECT(amm.expectBalances( + GBP(1'120'000'000'000'000), BTC(892'857'142'857'143), amm.tokens())); + + // 25% of 85.7142e12BTC is paid in tr fee + // 85.7142e12*1.25 = 107.1428e12BTC + env.require(balance(carol, BTC(1'085'714'285'714'285))); + } + { + // Payment via offer and AMM + Env env(*this, features); + Account const ed("ed"); + + env.fund(XRP(1'000), gw, alice, bob, carol, ed); + + MPTTester const GBP( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol, ed}, + .transferFee = 25'000, + .pay = 1'000'000'000'000'000, + .flags = MPTDEXFlags}); + + MPTTester const BTC( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol, ed}, + .transferFee = 25'000, + .pay = 1'000'000'000'000'000, + .flags = MPTDEXFlags}); + + MPTTester const ETH( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol, ed}, + .transferFee = 25'000, + .pay = 1'000'000'000'000'000, + .flags = MPTDEXFlags}); + + env(offer(ed, GBP(1'000'000'000'000'000), ETH(1'000'000'000'000'000)), + txflags(tfPassive)); + env.close(); + + AMM const amm(env, bob, ETH(1'000'000'000'000'000), BTC(1'000'000'000'000'000)); + + env(pay(alice, carol, BTC(100'000'000'000'000)), + path(~MPT(ETH), ~MPT(BTC)), + sendmax(GBP(150'000'000'000'000)), + txflags(tfNoRippleDirect | tfPartialPayment)); + env.close(); + + // alice buys 120e12ETH with 120e12GBP via the offer + // and pays 25% tr fee on 120e12GBP + // 1,000e12 - 120e12*1.25 = 850e12GBP + env.require(balance(alice, GBP(850'000'000'000'000))); + // consumed offer is 120e12GBP/120e12ETH + // ed doesn't pay tr fee + env.require(balance(ed, ETH(880'000'000'000'000))); + env.require(balance(ed, GBP(1'120'000'000'000'000))); + BEAST_EXPECT(expectOffers( + env, ed, 1, {Amounts{GBP(880'000'000'000'000), ETH(880'000'000'000'000)}})); + // 25% on 96e12ETH is paid in tr fee 96e12*1.25 = 120e12ETH + // 96e12ETH is swapped in for 87.5912e12BTC + BEAST_EXPECT(amm.expectBalances( + ETH(1'096'000'000'000'000), BTC(912'408'759'124'088), amm.tokens())); + // 25% on 70.0729e12BTC is paid in tr fee 70.0729e12*1.25 + // = 87.5912e12BTC + env.require(balance(carol, BTC(1'070'072'992'700'729))); + } + { + // Payment via AMM, AMM + Env env(*this, features); + Account const ed("ed"); + + env.fund(XRP(1'000), gw, alice, bob, carol, ed); + + MPTTester const GBP( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol, ed}, + .transferFee = 25'000, + .pay = 1'000'000'000'000'000, + .flags = MPTDEXFlags}); + + MPTTester const BTC( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol, ed}, + .transferFee = 25'000, + .pay = 1'000'000'000'000'000, + .flags = MPTDEXFlags}); + + MPTTester const ETH( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol, ed}, + .transferFee = 25'000, + .pay = 1'000'000'000'000'000, + .flags = MPTDEXFlags}); + + AMM const amm1(env, bob, GBP(1'000'000'000'000'000), ETH(1'000'000'000'000'000)); + AMM const amm2(env, ed, ETH(1'000'000'000'000'000), BTC(1'000'000'000'000'000)); + + env(pay(alice, carol, BTC(100'000'000'000'000)), + path(~MPT(ETH), ~MPT(BTC)), + sendmax(GBP(150'000'000'000'000)), + txflags(tfNoRippleDirect | tfPartialPayment)); + env.close(); + + env.require(balance(alice, GBP(850'000'000'000'000))); + + // alice buys 107.1428e12ETH with 120e12GBP and pays 25% tr fee on + // 120e12GBP 1,000e12 - 120e12*1.25 = 850e12GBP 120e12GBP is swapped + // in for 107.1428e12ETH + BEAST_EXPECT(amm1.expectBalances( + GBP(1'120'000'000'000'000), ETH(892'857'142'857'143), amm1.tokens())); + // 25% on 85.7142e12ETH is paid in tr fee 85.7142e12*1.25 = + // 107.1428e12ETH 85.7142e12ETH is swapped in for 78.9473e12BTC + BEAST_EXPECT(amm2.expectBalances( + ETH(1'085'714'285'714'285), BTC(921'052'631'578'948), amm2.tokens())); + + // 25% on 63.1578e12BTC is paid in tr fee 63.1578e12*1.25 + // = 78.9473e12BTC + env.require(balance(carol, BTC(1'063'157'894'736'841))); + } + { + // AMM offer crossing + Env env(*this, features); + + env.fund(XRP(1'000), gw, alice, bob); + + MPTTester const BTC( + {.env = env, + .issuer = gw, + .holders = {alice, bob}, + .transferFee = 25'000, + .pay = 1'100'000, + .flags = MPTDEXFlags}); + + MPTTester const ETH( + {.env = env, + .issuer = gw, + .holders = {alice, bob}, + .transferFee = 25'000, + .pay = 1'100'000, + .flags = MPTDEXFlags}); + + AMM const amm(env, bob, BTC(1'000'000), ETH(1'100'000)); + env(offer(alice, ETH(100'000), BTC(100'000))); + env.close(); + + // 100e3BTC is swapped in for 100e3ETH + BEAST_EXPECT(amm.expectBalances(BTC(1'100'000), ETH(1'000'000), amm.tokens())); + // alice pays 25% tr fee on 100e3BTC 1100e3-100e3*1.25 = 975e3BTC + env.require(balance(alice, BTC(975'000))); + env.require(balance(alice, ETH(1'200'000))); + BEAST_EXPECT(expectOffers(env, alice, 0)); + } + { + // Payment via AMM with limit quality + Env env(*this, features); + + env.fund(XRP(1'000), gw, alice, bob, carol); + + MPTTester const BTC( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol}, + .transferFee = 25'000, + .pay = 1'000'000'000'000'000, + .flags = MPTDEXFlags}); + + MPTTester const GBP( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol}, + .transferFee = 25'000, + .pay = 1'000'000'000'000'000, + .flags = MPTDEXFlags}); + + AMM const amm(env, bob, GBP(1'000'000'000'000'000), BTC(1'000'000'000'000'000)); + + // requested quality limit is 100e12BTC/178.58e12GBP = 0.55997 + // trade quality is 100e12BTC/178.5714 = 0.55999e12 + env(pay(alice, carol, BTC(100'000'000'000'000)), + path(~MPT(BTC)), + sendmax(GBP(178'580'000'000'000)), + txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality)); + env.close(); + + // alice buys 125e12BTC with 142.8571e12GBP and pays 25% tr fee + // on 142.8571e12GBP + // 1,000e12 - 142.8571e12*1.25 = 821.4285e12GBP + env.require(balance(alice, GBP(821'428'571'428'571))); + // 142.8571e12GBP is swapped in for 125e12BTC + BEAST_EXPECT(amm.expectBalances( + GBP(1'142'857'142'857'143), BTC(875'000'000'000'000), amm.tokens())); + // 25% on 100e12BTC is paid in tr fee + // 100e12*1.25 = 125e12BTC + env.require(balance(carol, BTC(1'100'000'000'000'000))); + } + { + // Payment via AMM with limit quality, deliver less + // than requested + Env env(*this, features); + + env.fund(XRP(1'000), gw, alice, bob, carol); + + MPTTester const BTC( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol}, + .transferFee = 25'000, + .pay = 1'200'000'000'000'000, + .flags = MPTDEXFlags}); + + MPTTester const GBP( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol}, + .transferFee = 25'000, + .pay = 1'200'000'000'000'000, + .flags = MPTDEXFlags}); + + AMM const amm(env, bob, GBP(1'000'000'000'000'000), BTC(1'200'000'000'000'000)); + + // requested quality limit is 90e12BTC/120e12GBP = 0.75 + // trade quality is 22.5e12BTC/30e12GBP = 0.75 + env(pay(alice, carol, BTC(90'000'000'000'000)), + path(~MPT(BTC)), + sendmax(GBP(120'000'000'000'000)), + txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality)); + env.close(); + + // alice buys 28.125e12BTC with 24e12GBP and pays 25% tr fee + // on 24e12GBP + // 1,200e12 - 24e12*1.25 =~ 1,170e12GBP + env.require(balance(alice, GBP(1'170'000'000'000'000))); + // 24e12GBP is swapped in for 28.125e12BTC + BEAST_EXPECT(amm.expectBalances( + GBP(1'024'000'000'000'000), BTC(1'171'875'000'000'000), amm.tokens())); + + // 25% on 22.5e12BTC is paid in tr fee + // 22.5*1.25 = 28.125e12BTC + env.require(balance(carol, BTC(1'222'500'000'000'000))); + } + { + // Payment via offer and AMM with limit quality, deliver less + // than requested + Env env(*this, features); + Account const ed("ed"); + + env.fund(XRP(1'000), gw, alice, bob, carol, ed); + + MPTTester const BTC( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol, ed}, + .transferFee = 25'000, + .pay = 1'400'000'000'000'000, + .flags = MPTDEXFlags}); + + MPTTester const GBP( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol, ed}, + .transferFee = 25'000, + .pay = 1'400'000'000'000'000, + .flags = MPTDEXFlags}); + + MPTTester const ETH( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol, ed}, + .transferFee = 25'000, + .pay = 1'400'000'000'000'000, + .flags = MPTDEXFlags}); + + env(offer(ed, GBP(1'000'000'000'000'000), ETH(1'000'000'000'000'000)), + txflags(tfPassive)); + env.close(); + + AMM const amm(env, bob, ETH(1'000'000'000'000'000), BTC(1'400'000'000'000'000)); + + // requested quality limit is 95e12BTC/140e12GBP = 0.6785 + // trade quality is 59.7321e12BTC/88.0262e12GBP = 0.6785 + env(pay(alice, carol, BTC(95'000'000'000'000)), + path(~MPT(ETH), ~MPT(BTC)), + sendmax(GBP(140'000'000'000'000)), + txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality)); + env.close(); + + // alice buys 70.4210e12ETH with 70.4210e12GBP via the offer + // and pays 25% tr fee on 70.4210e12GBP + // 1,400e12 - 70.4210e12*1.25 = 1400e12 - 88.0262e12 = + // 1311.9736e12GBP + env.require(balance(alice, GBP(1'311'973'684'210'525))); + // ed doesn't pay tr fee, the balances reflect consumed offer + // 70.4210e12GBP/70.4210e12ETH + env.require(balance(ed, ETH(1'329'578'947'368'420))); + env.require(balance(ed, GBP(1'470'421'052'631'580))); + BEAST_EXPECT(expectOffers( + env, ed, 1, {Amounts{GBP(929'578'947'368'420), ETH(929'578'947'368'420)}})); + // 25% on 56.3368e12ETH is paid in tr fee 56.3368e12*1.25 + // = 70.4210e12ETH 56.3368e12ETH is swapped in for 74.6651e12BTC + BEAST_EXPECT(amm.expectBalances( + ETH(1'056'336'842'105'264), BTC(1'325'334'821'428'571), amm.tokens())); + + // 25% on 59.7321e12BTC is paid in tr fee 59.7321e12*1.25 + // = 74.6651e12BTC + env.require(balance(carol, BTC(1'459'732'142'857'143))); + } + { + // Payment via AMM and offer with limit quality, deliver less + // than requested + Env env(*this, features); + Account const ed("ed"); + + env.fund(XRP(1'000), gw, alice, bob, carol, ed); + + MPTTester const BTC( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol, ed}, + .transferFee = 25'000, + .pay = 1'400'000'000'000'000, + .flags = MPTDEXFlags}); + + MPTTester const GBP( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol, ed}, + .transferFee = 25'000, + .pay = 1'400'000'000'000'000, + .flags = MPTDEXFlags}); + + MPTTester const ETH( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol, ed}, + .transferFee = 25'000, + .pay = 1'400'000'000'000'000, + .flags = MPTDEXFlags}); + + AMM const amm(env, bob, GBP(1'000'000'000'000'000), ETH(1'000'000'000'000'000)); + + env(offer(ed, ETH(1'000'000'000'000'000), BTC(1'400'000'000'000'000)), + txflags(tfPassive)); + env.close(); + + // requested quality limit is 95e12BTC/140e12GBP = 0.6785 + // trade quality is 47.7857e12BTC/70.4210e12GBP = 0.6785 + env(pay(alice, carol, BTC(95'000'000'000'000)), + path(~MPT(ETH), ~MPT(BTC)), + sendmax(GBP(140'000'000'000'000)), + txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality)); + env.close(); + + // alice buys 53.3322e12ETH with 56.3368e12GBP via the amm + // and pays 25% tr fee on 56.3368e12GBP + // 1,400e12 - 56.3368e12*1.25 = 1400e12 - 70.4210e12 = + // 1329.5789e12GBP + env.require(balance(alice, GBP(1'329'578'947'368'420))); + //// 25% on 56.3368e12ETH is paid in tr fee 56.3368e12*1.25 + ///= 70.4210e12ETH + // 56.3368e12GBP is swapped in for 53.3322e12ETH + BEAST_EXPECT(amm.expectBalances( + GBP(1'056'336'842'105'264), ETH(946'667'729'591'836), amm.tokens())); + + // 25% on 42.6658e12ETH is paid in tr fee 42.6658e12*1.25 + // = 53.3322e12ETH 42.6658e12ETH/59.7321e12BTC + env.require(balance(ed, BTC(1'340'267'857'142'857))); + env.require(balance(ed, ETH(1'442'665'816'326'531))); + BEAST_EXPECT(expectOffers( + env, ed, 1, {Amounts{ETH(957'334'183'673'469), BTC(1'340'267'857'142'857)}})); + // 25% on 47.7857e12BTC is paid in tr fee 47.7857e12*1.25 + // = 59.7321e12BTC + env.require(balance(carol, BTC(1'447'785714285714))); + } + { + // Payment via AMM, AMM with limit quality, deliver less + // than requested + Env env(*this, features); + Account const ed("ed"); + + env.fund(XRP(1'000), gw, alice, bob, carol, ed); + + MPTTester const BTC( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol, ed}, + .transferFee = 25'000, + .pay = 1'400'000'000'000'000, + .flags = MPTDEXFlags}); + + MPTTester const GBP( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol, ed}, + .transferFee = 25'000, + .pay = 1'400'000'000'000'000, + .flags = MPTDEXFlags}); + + MPTTester const ETH( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol, ed}, + .transferFee = 25'000, + .pay = 1'400'000'000'000'000, + .flags = MPTDEXFlags}); + + AMM const amm1(env, bob, GBP(1'000'000'000'000'000), ETH(1'000'000'000'000'000)); + AMM const amm2(env, ed, ETH(1'000'000'000'000'000), BTC(1'400'000'000'000'000)); + + // requested quality limit is 90e12BTC/145e12GBP = 0.6206 + // trade quality is 66.7432e12BTC/107.5308e12GBP = 0.6206 + env(pay(alice, carol, BTC(90'000'000'000'000)), + path(~MPT(ETH), ~MPT(BTC)), + sendmax(GBP(145'000'000'000'000)), + txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality)); + env.close(); + + // alice buys 53.3322e12ETH with 107.5308e12GBP + // 25% on 86.0246e12GBP is paid in tr fee + // 1,400e12 - 86.0246e12*1.25 = 1400e12 - 107.5308e12 = + // 1229.4691e12GBP + env.require(balance(alice, GBP(1'292'469'135'802'465))); + // 86.0246e12GBP is swapped in for 79.2106e12ETH + BEAST_EXPECT(amm1.expectBalances( + GBP(1'086'024'691'358'028), ETH(920'789'377'955'618), amm1.tokens())); + // 25% on 63.3684e12ETH is paid in tr fee 63.3684e12*1.25 + // = 79.2106e12ETH 63.3684e12ETH is swapped in for 83.4291e12BTC + BEAST_EXPECT(amm2.expectBalances( + ETH(1'063'368'497'635'505), BTC(1'316'570'881'226'053), amm2.tokens())); + + // 25% on 66.7432e12BTC is paid in tr fee 66.7432e12*1.25 + // = 83.4291e12BTC + env.require(balance(carol, BTC(1'466'743'295'019'157))); + } + { + // Payment by the issuer via AMM, AMM with limit quality, + // deliver less than requested + Env env(*this, features); + + env.fund(XRP(1'000), gw, alice, bob, carol); + + MPTTester const BTC( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol}, + .transferFee = 25'000, + .pay = 1'400'000'000'000'000, + .flags = MPTDEXFlags}); + + MPTTester const GBP( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol}, + .transferFee = 25'000, + .pay = 1'400'000'000'000'000, + .flags = MPTDEXFlags}); + + MPTTester const ETH( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol}, + .transferFee = 25'000, + .pay = 1'400'000'000'000'000, + .flags = MPTDEXFlags}); + + AMM const amm1(env, alice, GBP(1'000'000'000'000'000), ETH(1'000'000'000'000'000)); + AMM const amm2(env, bob, ETH(1'000'000'000'000'000), BTC(1'400'000'000'000'000)); + + // requested quality limit is 90e12BTC/120e12GBP = 0.75 + // trade quality is 81.1111e12BTC/108.1481e12GBP = 0.75 + env(pay(gw, carol, BTC(90'000'000'000'000)), + path(~MPT(ETH), ~MPT(BTC)), + sendmax(GBP(120'000'000'000'000)), + txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality)); + env.close(); + + // 108.1481e12GBP is swapped in for 97.5935e12ETH + BEAST_EXPECT(amm1.expectBalances( + GBP(1'108'148'148'148'150), ETH(902'406'417'112'298), amm1.tokens())); + // 25% on 78.0748e12ETH is paid in tr fee 78.0748e12*1.25 + // = 97.5935e12ETH 78.0748e12ETH is swapped in for 101.3888e12BTC + BEAST_EXPECT(amm2.expectBalances( + ETH(1'078'074'866'310'161), BTC(1'298'611'111'111'111), amm2.tokens())); + + // 25% on 81.1111e12BTC is paid in tr fee 81.1111e12*1.25 = + // 101.3888e12BTC + env.require(balance(carol, BTC(1'481'111'111'111'111))); + } + } + + void + testLimitQuality() + { + // Single path with amm, offer, and limit quality. The quality limit + // is such that the first offer should be taken but the second + // should not. The total amount delivered should be the sum of the + // two offers and sendMax should be more than the first offer. + testcase("limitQuality"); + using namespace jtx; + + { + Env env(*this); + env.fund(XRP(10'000), gw, alice, bob, carol); + + MPTTester const ETH( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol}, + .pay = 2'000'000, + .flags = MPTDEXFlags}); + + AMM const ammBob(env, bob, XRP(1'000), ETH(1'050'000)); + env(offer(bob, XRP(100), ETH(50'000))); + + env(pay(alice, carol, ETH(100'000)), + path(~MPT(ETH)), + sendmax(XRP(100)), + txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality)); + + BEAST_EXPECT(ammBob.expectBalances(XRP(1'050), ETH(1'000'000), ammBob.tokens())); + env.require(balance(carol, ETH(2'050'000))); + BEAST_EXPECT(expectOffers(env, bob, 1, {{{XRP(100), ETH(50'000)}}})); + } + } + + void + testXRPPathLoop() + { + testcase("Circular XRP"); + + using namespace jtx; + + // Payment path starting with XRP + { + auto test = [&](auto&& issue1, auto&& issue2) { + Env env(*this); + env.fund(XRP(30'000), alice, bob, gw); + env.close(); + auto const ETH = issue1( + {.env = env, + .token = "ETH", + .issuer = gw, + .holders = {alice, bob}, + .limit = 2000'000}); + auto const BTC = issue2( + {.env = env, + .token = "BTC", + .issuer = gw, + .holders = {alice, bob}, + .limit = 2000'000}); + + env(pay(gw, alice, BTC(200'000))); + env(pay(gw, bob, BTC(200'000))); + env(pay(gw, alice, ETH(200'000))); + env(pay(gw, bob, ETH(200'000))); + env.close(); + + AMM const ammAliceXRP_BTC(env, alice, XRP(100), BTC(101'000)); + AMM const ammAliceXRP_ETH(env, alice, XRP(100), ETH(101'000)); + env(pay(alice, bob, ETH(1'000)), + path(~BTC, ~XRP, ~ETH), + sendmax(XRP(1)), + txflags(tfNoRippleDirect), + ter(temBAD_PATH_LOOP)); + }; + testHelper2TokensMix(test); + } + + // Payment path ending with XRP + { + auto test = [&](auto&& issue1, auto&& issue2) { + Env env(*this); + env.fund(XRP(30'000), alice, bob, gw); + env.close(); + auto const ETH = issue1( + {.env = env, + .token = "ETH", + .issuer = gw, + .holders = {alice, bob}, + .limit = 2000'000}); + auto const BTC = issue2( + {.env = env, + .token = "BTC", + .issuer = gw, + .holders = {alice, bob}, + .limit = 2000'000}); + + env(pay(gw, alice, BTC(200'000))); + env(pay(gw, bob, BTC(200'000))); + env(pay(gw, alice, ETH(200'000))); + env(pay(gw, bob, ETH(200'000))); + env.close(); + + AMM const ammAliceXRP_BTC(env, alice, XRP(100), BTC(100'000)); + AMM const ammAliceXRP_ETH(env, alice, XRP(100), ETH(100'000)); + // ETH -> //XRP -> //BTC ->XRP + env(pay(alice, bob, XRP(1)), + path(~XRP, ~BTC, ~XRP), + sendmax(ETH(1'000)), + txflags(tfNoRippleDirect), + ter(temBAD_PATH_LOOP)); + }; + testHelper2TokensMix(test); + } + + // Payment where loop is formed in the middle of the path, not + // on an endpoint + { + auto test = [&](auto&& issue1, auto&& issue2, auto&& issue3) { + Env env(*this); + env.fund(XRP(10'000), gw, alice, bob); + env.close(); + auto const ETH = issue1( + {.env = env, + .token = "ETH", + .issuer = gw, + .holders = {alice, bob}, + .limit = 2000'000}); + auto const BTC = issue2( + {.env = env, + .token = "BTC", + .issuer = gw, + .holders = {alice, bob}, + .limit = 2000'000}); + auto const JPY = issue2( + {.env = env, + .token = "JPY", + .issuer = gw, + .holders = {alice, bob}, + .limit = 2000'000}); + + env(pay(gw, alice, BTC(200'000))); + env(pay(gw, bob, BTC(200'000))); + env(pay(gw, alice, ETH(200'000))); + env(pay(gw, bob, ETH(200'000))); + env(pay(gw, alice, JPY(200'000))); + env(pay(gw, bob, JPY(200'000))); + env.close(); + + AMM const ammAliceXRP_BTC(env, alice, XRP(100), BTC(100'000)); + AMM const ammAliceXRP_ETH(env, alice, XRP(100), ETH(100'000)); + AMM const ammAliceXRP_JPY(env, alice, XRP(100), JPY(100'000)); + + env(pay(alice, bob, JPY(1'000)), + path(~XRP, ~ETH, ~XRP, ~JPY), + sendmax(BTC(1'000)), + txflags(tfNoRippleDirect), + ter(temBAD_PATH_LOOP)); + }; + testHelper3TokensMix(test); + } + } + + void + testStepLimit(FeatureBitset features) + { + testcase("Step Limit"); + + using namespace jtx; + { + Env env(*this, features); + auto const dan = Account("dan"); + auto const ed = Account("ed"); + + env.fund(XRP(100'000'000), gw, alice, bob, carol, dan, ed); + + MPTTester const BTC( + {.env = env, .issuer = gw, .holders = {bob, dan, ed}, .flags = MPTDEXFlags}); + + env(pay(gw, ed, BTC(11'000'000'000'000))); + env(pay(gw, bob, BTC(1'000'000'000'000))); + env(pay(gw, dan, BTC(1'000'000'000'000))); + + n_offers(env, 2'000, bob, XRP(1), BTC(1'000'000'000'000)); + n_offers(env, 1, dan, XRP(1), BTC(1'000'000'000'000)); + AMM const ammEd(env, ed, XRP(9), BTC(11'000'000'000'000)); + + // Alice offers to buy 1000 XRP for 1000e12 BTC. She takes Bob's + // first offer, removes 999 more as unfunded, then hits the step + // limit. + env(offer(alice, BTC(1'000'000'000'000'000), XRP(1'000))); + env.require(balance(alice, BTC(2'050'125'257'867))); + env.require(owners(alice, 2)); + env.require(balance(bob, BTC(0))); + env.require(owners(bob, 1'001)); + env.require(balance(dan, BTC(1'000'000'000'000))); + env.require(owners(dan, 2)); + + // Carol offers to buy 1000 XRP for 1000e12 BTC. She removes Bob's + // next 1000 offers as unfunded and hits the step limit. + env(offer(carol, BTC(1'000'000'000'000'000), XRP(1'000))); + env.require(balance(carol, MPT(BTC)(none))); + env.require(owners(carol, 1)); + env.require(balance(bob, BTC(0))); + env.require(owners(bob, 1)); + env.require(balance(dan, BTC(1'000'000'000'000))); + env.require(owners(dan, 2)); + } + + // MPT/IOU, similar to the case above + { + Env env(*this, features); + auto const dan = Account("dan"); + auto const ed = Account("ed"); + + env.fund(XRP(100'000), gw, alice, bob, carol, dan, ed); + env.close(); + + MPTTester const USD( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol, dan, ed}, + .pay = 10000'000'000, + .flags = MPTDEXFlags}); + + env.trust(BTC(11'000'000'000'000), ed); + env(pay(gw, ed, BTC(11'000'000'000'000))); + env.trust(BTC(1'000'000'000'000), bob); + env(pay(gw, bob, BTC(1'000'000'000'000))); + env.trust(BTC(1'000'000'000'000), dan); + env(pay(gw, dan, BTC(1'000'000'000'000))); + env.close(); + + n_offers(env, 2'000, bob, USD(1000000), BTC(1'000'000'000'000)); + n_offers(env, 1, dan, USD(1000000), BTC(1'000'000'000'000)); + AMM const ammEd(env, ed, USD(9000000), BTC(11'000'000'000'000)); + env(offer(alice, BTC(1'000'000'000'000'000), USD(1'000000000))); + + env.require(balance(alice, STAmount{BTC, UINT64_C(2050125257867'587), -3})); + env.require(owners(alice, 3)); + env.require(balance(bob, BTC(0))); + env.require(owners(bob, 1'002)); + env.require(balance(dan, BTC(1000000000000))); + env.require(owners(dan, 3)); + } + + // IOU/MPT, similar to the case above + { + Env env(*this, features); + auto const dan = Account("dan"); + auto const ed = Account("ed"); + + env.fund(XRP(100'000), gw, alice, bob, carol, dan, ed); + env.close(); + + env.trust(USD(10000'000'000), alice); + env(pay(gw, alice, USD(10000'000'000))); + env.trust(USD(10000'000'000), bob); + env(pay(gw, bob, USD(10000'000'000))); + env.trust(USD(10000'000'000), carol); + env(pay(gw, carol, USD(10000'000'000))); + env.trust(USD(10000'000'000), dan); + env(pay(gw, dan, USD(10000'000'000))); + env.trust(USD(10000'000'000), ed); + env(pay(gw, ed, USD(10000'000'000))); + env.close(); + + MPTTester const BTC( + {.env = env, .issuer = gw, .holders = {bob, dan, ed}, .flags = MPTDEXFlags}); + + env(pay(gw, ed, BTC(11'000'000'000'000))); + env(pay(gw, bob, BTC(1'000'000'000'000))); + env(pay(gw, dan, BTC(1'000'000'000'000))); + env.close(); + + n_offers(env, 2'000, bob, USD(1000000), BTC(1'000'000'000'000)); + n_offers(env, 1, dan, USD(1000000), BTC(1'000'000'000'000)); + AMM const ammEd(env, ed, USD(9000000), BTC(11'000'000'000'000)); + env(offer(alice, BTC(1'000'000'000'000'000), USD(1'000000000))); + + env.require(balance(alice, BTC(2050125628933))); + env.require(owners(alice, 3)); + env.require(balance(bob, BTC(0))); + env.require(owners(bob, 1'002)); + env.require(balance(dan, BTC(1000000000000))); + env.require(owners(dan, 3)); + } + + // MPT/MPT, similar to the case above + { + Env env(*this, features); + auto const dan = Account("dan"); + auto const ed = Account("ed"); + + env.fund(XRP(100'000), gw, alice, bob, carol, dan, ed); + env.close(); + + MPTTester const BTC( + {.env = env, .issuer = gw, .holders = {bob, dan, ed}, .flags = MPTDEXFlags}); + MPTTester const USD( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol, dan, ed}, + .pay = 10000'000'000, + .flags = MPTDEXFlags}); + + env(pay(gw, ed, BTC(11'000'000'000'000))); + env(pay(gw, bob, BTC(1'000'000'000'000))); + env(pay(gw, dan, BTC(1'000'000'000'000))); + env.close(); + + n_offers(env, 2'000, bob, USD(1000000), BTC(1'000'000'000'000)); + n_offers(env, 1, dan, USD(1000000), BTC(1'000'000'000'000)); + AMM const ammEd(env, ed, USD(9000000), BTC(11'000'000'000'000)); + env(offer(alice, BTC(1'000'000'000'000'000), USD(1'000000000))); + + env.require(balance(alice, BTC(2050125257867))); + env.require(owners(alice, 3)); + env.require(balance(bob, BTC(0))); + env.require(owners(bob, 1'002)); + env.require(balance(dan, BTC(1000000000000))); + env.require(owners(dan, 3)); + } + } + + void + test_convert_all_of_an_asset(FeatureBitset features) + { + testcase("Convert all of an asset using DeliverMin"); + + using namespace jtx; + + { + Env env(*this, features); + fund(env, gw, {alice, bob, carol}, XRP(10'000)); + + MPTTester const BTC( + {.env = env, .issuer = gw, .holders = {alice, bob, carol}, .flags = MPTDEXFlags}); + + env(pay(alice, bob, BTC(10'000)), deliver_min(BTC(10'000)), ter(temBAD_AMOUNT)); + env(pay(alice, bob, BTC(10'000)), + deliver_min(BTC(-5'000)), + txflags(tfPartialPayment), + ter(temBAD_AMOUNT)); + env(pay(alice, bob, BTC(10'000)), + deliver_min(XRP(5)), + txflags(tfPartialPayment), + ter(temBAD_AMOUNT)); + env(pay(alice, bob, BTC(10'000)), + deliver_min(BTC(5'000)), + txflags(tfPartialPayment), + ter(tecPATH_DRY)); + env(pay(alice, bob, BTC(10'000)), + deliver_min(BTC(15'000)), + txflags(tfPartialPayment), + ter(temBAD_AMOUNT)); + env(pay(gw, carol, BTC(50'000))); + AMM const ammCarol(env, carol, XRP(10), BTC(15'000)); + env(pay(alice, bob, BTC(10'000)), + paths(XRP), + deliver_min(BTC(7'000)), + txflags(tfPartialPayment), + sendmax(XRP(5)), + ter(tecPATH_PARTIAL)); + env.require( + balance(alice, drops(10'000'000'000 - (3 * env.current()->fees().base.drops())))); + env.require(balance(bob, drops(10'000'000'000 - env.current()->fees().base.drops()))); + } + + { + Env env(*this, features); + fund(env, gw, {alice, bob}, XRP(10'000)); + + MPTTester const BTC( + {.env = env, .issuer = gw, .holders = {alice, bob}, .flags = MPTDEXFlags}); + + env(pay(gw, bob, BTC(1'100'000))); + AMM const ammBob(env, bob, XRP(1'000), BTC(1'100'000)); + env(pay(alice, alice, BTC(10'000'000)), + paths(XRP), + deliver_min(BTC(100'000)), + txflags(tfPartialPayment), + sendmax(XRP(100))); + env.require(balance(alice, BTC(100'000))); + } + + // IOU/MPT mix, similar to the above case + { + auto test = [&](auto&& issue1, auto&& issue2) { + Env env(*this); + env.fund(XRP(30'000), alice, bob, carol, gw); + env.close(); + auto const USD = issue1( + {.env = env, + .token = "USD", + .issuer = gw, + .holders = {alice, bob, carol}, + .limit = 3000'000}); + auto const BTC = issue2( + {.env = env, + .token = "BTC", + .issuer = gw, + .holders = {alice, bob, carol}, + .limit = 1'000'000}); + + env(pay(gw, alice, USD(10'000))); + env(pay(gw, bob, USD(10'000))); + env(pay(gw, bob, BTC(1'200))); + env.close(); + + AMM const ammBob(env, bob, USD(1'000), BTC(1'100)); + env(pay(alice, alice, BTC(10'000)), + paths(USD), + deliver_min(BTC(100)), + txflags(tfPartialPayment), + sendmax(USD(100))); + env.require(balance(alice, BTC(100))); + }; + testHelper2TokensMix(test); + } + + { + Env env(*this, features); + fund(env, gw, {alice, bob, carol}, XRP(10'000)); + + MPTTester const BTC( + {.env = env, .issuer = gw, .holders = {bob, carol}, .flags = MPTDEXFlags}); + + env(pay(gw, bob, BTC(1'200'000))); + AMM const ammBob(env, bob, XRP(5'500), BTC(1'200'000)); + env(pay(alice, carol, BTC(10'000'000)), + paths(XRP), + deliver_min(BTC(200'000)), + txflags(tfPartialPayment), + sendmax(XRP(1'000)), + ter(tecPATH_PARTIAL)); + env(pay(alice, carol, BTC(10'000'000)), + paths(XRP), + deliver_min(BTC(200'000)), + txflags(tfPartialPayment), + sendmax(XRP(1'100))); + BEAST_EXPECT(ammBob.expectBalances(XRP(6'600), BTC(1'000'000), ammBob.tokens())); + env.require(balance(carol, BTC(200'000))); + } + + // IOU/MPT mix, similar to the above case + { + auto test = [&](auto&& issue1, auto&& issue2) { + Env env(*this); + env.fund(XRP(30'000), alice, bob, carol, gw); + env.close(); + auto const USD = issue1( + {.env = env, + .token = "USD", + .issuer = gw, + .holders = {alice, bob, carol}, + .limit = 3000'000}); + auto const BTC = issue2( + {.env = env, + .token = "BTC", + .issuer = gw, + .holders = {alice, bob, carol}, + .limit = 1'000'000}); + + env(pay(gw, alice, USD(100'000))); + env(pay(gw, bob, USD(100'000))); + env(pay(gw, carol, USD(100'000))); + + env(pay(gw, bob, BTC(1'200))); + env.close(); + + AMM const ammBob(env, bob, USD(5'500), BTC(1'200)); + env(pay(alice, carol, BTC(10'000)), + paths(USD), + deliver_min(BTC(200)), + txflags(tfPartialPayment), + sendmax(USD(1'000)), + ter(tecPATH_PARTIAL)); + env(pay(alice, carol, BTC(10'000)), + paths(USD), + deliver_min(BTC(200)), + txflags(tfPartialPayment), + sendmax(USD(1'100))); + BEAST_EXPECT(ammBob.expectBalances(USD(6'600), BTC(1'000), ammBob.tokens())); + env.require(balance(carol, BTC(200))); + }; + testHelper2TokensMix(test); + } + + { + auto const dan = Account("dan"); + Env env(*this, features); + fund(env, gw, {alice, bob, carol, dan}, XRP(10'000)); + + MPTTester const BTC( + {.env = env, .issuer = gw, .holders = {bob, carol, dan}, .flags = MPTDEXFlags}); + + env(pay(gw, bob, BTC(100'000'000))); + env(pay(gw, dan, BTC(1'100'000'000))); + env(offer(bob, XRP(100), BTC(100'000'000))); + env(offer(bob, XRP(1'000), BTC(100'000'000))); + AMM const ammDan(env, dan, XRP(1'000), BTC(1'100'000'000)); + + env(pay(alice, carol, BTC(10'000'000'000)), + paths(XRP), + deliver_min(BTC(200'000'000)), + txflags(tfPartialPayment), + sendmax(XRPAmount(200'000'001))); + env.require(balance(bob, BTC(0))); + env.require(balance(carol, BTC(200'000'000))); + BEAST_EXPECT( + ammDan.expectBalances(XRPAmount{1'100'000'001}, BTC(1000'000000), ammDan.tokens())); + } + } + + void + testPayment(FeatureBitset features) + { + testcase("Payment"); + + using namespace jtx; + Account const becky{"becky"}; + + Env env(*this, features); + fund(env, gw, {alice, becky}, XRP(5'000)); + + MPTTester const BTC( + {.env = env, .issuer = gw, .holders = {alice, becky}, .flags = MPTDEXFlags}); + + env(pay(gw, alice, BTC(500'000))); + env.close(); + + AMM const ammAlice(env, alice, XRP(100), BTC(140'000)); + + // becky pays herself BTC (10'000) by consuming part of alice's offer. + // Make sure the payment works if PaymentAuth is not involved. + env(pay(becky, becky, BTC(10'000)), path(~MPT(BTC)), sendmax(XRP(10))); + env.close(); + BEAST_EXPECT( + ammAlice.expectBalances(XRPAmount(107'692'308), BTC(130'000), ammAlice.tokens())); + + // becky decides to require authorization for deposits. + env(fset(becky, asfDepositAuth)); + env.close(); + + // becky pays herself again. + env(pay(becky, becky, BTC(10'000)), path(~MPT(BTC)), sendmax(XRP(10)), ter(tesSUCCESS)); + + env.close(); + } + + void + testPayMPT() + { + // Exercise MPT payments and non-direct XRP payments to an account + // that has the lsfDepositAuth flag set. + testcase("Pay MPT"); + + using namespace jtx; + + Env env(*this); + + fund(env, gw, {alice, bob, carol}, XRP(10'000)); + + MPTTester BTC( + {.env = env, .issuer = gw, .holders = {alice, bob, carol}, .flags = MPTDEXFlags}); + + env(pay(gw, alice, BTC(150'000))); + env(pay(gw, carol, BTC(150'000))); + AMM const ammCarol(env, carol, BTC(100'000), XRPAmount(101)); + + env(pay(alice, bob, BTC(50'000))); + env.close(); + + // bob sets the lsfDepositAuth flag. + env(fset(bob, asfDepositAuth), require(flags(bob, asfDepositAuth))); + env.close(); + + // None of the following payments should succeed. + auto failedMptPayments = [this, &env, &BTC]() { + env.require(flags(bob, asfDepositAuth)); + + // Capture bob's balances before hand to confirm they don't + // change. + PrettyAmount const bobXrpBalance{env.balance(bob, XRP)}; + PrettyAmount const bobBTCBalance{env.balance(bob, MPT(BTC))}; + + env(pay(alice, bob, BTC(50'000)), ter(tecNO_PERMISSION)); + env.close(); + + // Note that even though alice is paying bob in XRP, the payment + // is still not allowed since the payment passes through an + // offer. + env(pay(alice, bob, drops(1)), sendmax(BTC(1'000)), ter(tecNO_PERMISSION)); + env.close(); + + BEAST_EXPECT(bobXrpBalance == env.balance(bob, XRP)); + BEAST_EXPECT(bobBTCBalance == env.balance(bob, MPT(BTC))); + }; + + // Test when bob has an XRP balance > base reserve. + failedMptPayments(); + + // Set bob's XRP balance == base reserve. Also demonstrate that + // bob can make payments while his lsfDepositAuth flag is set. + env(pay(bob, alice, BTC(25'000))); + env.close(); + + { + STAmount const bobPaysXRP{env.balance(bob, XRP) - reserve(env, 1)}; + XRPAmount const bobPaysFee{reserve(env, 1) - reserve(env, 0)}; + env(pay(bob, alice, bobPaysXRP), fee(bobPaysFee)); + env.close(); + } + + // Test when bob's XRP balance == base reserve. + BEAST_EXPECT(env.balance(bob, XRP) == reserve(env, 0)); + BEAST_EXPECT(env.balance(bob, MPT(BTC)) == BTC(25'000)); + failedMptPayments(); + + // Test when bob has an XRP balance == 0. + env(noop(bob), fee(reserve(env, 0))); + env.close(); + + BEAST_EXPECT(env.balance(bob, XRP) == XRP(0)); + failedMptPayments(); + + // Give bob enough XRP for the fee to clear the lsfDepositAuth flag. + env(pay(alice, bob, drops(env.current()->fees().base))); + + // bob clears the lsfDepositAuth and the next payment succeeds. + env(fclear(bob, asfDepositAuth)); + env.close(); + + env(pay(alice, bob, BTC(50'000))); + env.close(); + + env(pay(alice, bob, drops(1)), sendmax(BTC(1'000))); + env.close(); + BEAST_EXPECT(ammCarol.expectBalances(BTC(101'000), XRPAmount(100), ammCarol.tokens())); + } + + void + testIndividualLock(FeatureBitset features) + { + testcase("Individual Lock"); + + using namespace test::jtx; + Env env(*this, features); + + Account const G1{"G1"}; + Account const alice{"alice"}; + Account const bob{"bob"}; + + env.fund(XRP(1'000), G1, alice, bob); + + MPTTester BTC( + {.env = env, + .issuer = G1, + .holders = {alice, bob}, + .flags = tfMPTCanLock | MPTDEXFlags}); + + env(pay(G1, bob, BTC(10))); + env(pay(G1, alice, BTC(205))); + env.close(); + + AMM const ammAlice(env, alice, XRP(500), BTC(105)); + + env.require(balance(bob, BTC(10))); + env.require(balance(alice, BTC(100))); + + // Account with MPT unlocked (proving operations normally work) + // can make Payment + env(pay(alice, bob, BTC(1))); + + // can receive Payment + env(pay(bob, alice, BTC(1))); + env.close(); + + // Lock MPT for bob + BTC.set({.holder = bob, .flags = tfMPTLock}); + + { + // different from IOU. The offer is created but not crossed. + env(offer(bob, BTC(5), XRP(25))); + env.close(); + BEAST_EXPECT(expectOffers(env, bob, 1, {{{BTC(5), XRP(25)}}})); + BEAST_EXPECT(ammAlice.expectBalances(XRP(500), BTC(105), ammAlice.tokens())); + } + + { + // can not sell assets + env(offer(bob, XRP(1), BTC(5)), ter(tecUNFUNDED_OFFER)); + + // different from IOU + // can not receive Payment when locked + env(pay(alice, bob, BTC(1)), ter(tecPATH_DRY)); + + // can not make Payment when locked + env(pay(bob, alice, BTC(1)), ter(tecPATH_DRY)); + + env.require(balance(bob, BTC(10))); + } + + { + // Unlock + BTC.set({.holder = bob, .flags = tfMPTUnlock}); + env(offer(bob, XRP(1), BTC(5))); + env(pay(bob, alice, BTC(1))); + env(pay(alice, bob, BTC(1))); + env.close(); + } + } + + void + testGlobalLock(FeatureBitset features) + { + testcase("Global Lock"); + + using namespace test::jtx; + Env env(*this, features); + + Account const G1{"G1"}; + Account A1{"A1"}; + Account A2{"A2"}; + Account A3{"A3"}; + Account A4{"A4"}; + + env.fund(XRP(12'000), G1); + env.fund(XRP(1'000), A1); + env.fund(XRP(20'000), A2, A3, A4); + + MPTTester const ETH( + {.env = env, + .issuer = G1, + .holders = {A1, A2, A3, A4}, + .flags = tfMPTCanLock | MPTDEXFlags}); + + MPTTester BTC( + {.env = env, + .issuer = G1, + .holders = {A1, A2, A3, A4}, + .flags = tfMPTCanLock | MPTDEXFlags}); + + env(pay(G1, A1, ETH(1'000))); + env(pay(G1, A2, ETH(100))); + env(pay(G1, A3, BTC(100))); + env(pay(G1, A4, BTC(100))); + env.close(); + + AMM const ammG1(env, G1, XRP(10'000), ETH(100)); + env(offer(A1, XRP(10'000), ETH(100)), txflags(tfPassive)); + env(offer(A2, ETH(100), XRP(10'000)), txflags(tfPassive)); + env.close(); + + { + // Account without Global Lock (proving operations normally + // work) + // visible offers where taker_pays is unlocked issuer + auto offers = getAccountOffers(env, A2)[jss::offers]; + if (!BEAST_EXPECT(checkArraySize(offers, 1u))) + return; + + // visible offers where taker_gets is unlocked issuer + offers = getAccountOffers(env, A1)[jss::offers]; + if (!BEAST_EXPECT(checkArraySize(offers, 1u))) + return; + } + + { + // Offers/Payments + // assets can be bought on the market + AMM ammA3(env, A3, BTC(1), XRP(1)); + + // assets can be sold on the market + // AMM is bidirectional + env(pay(G1, A2, ETH(1))); + env(pay(A2, G1, ETH(1))); + env(pay(A2, A1, ETH(1))); + env(pay(A1, A2, ETH(1))); + ammA3.withdrawAll(std::nullopt); + } + + { + // Account with Global Lock + // set Global Lock first + BTC.set({.flags = tfMPTLock}); + + // assets can't be bought on the market + AMM const ammA3(env, A3, BTC(1), XRP(1), ter(tecFROZEN)); + + // direct issues can be sent + env(pay(G1, A2, BTC(1))); + env(pay(A2, G1, BTC(1))); + // locked + env(pay(A2, A1, BTC(1)), ter(tecPATH_DRY)); + env(pay(A1, A2, BTC(1)), ter(tecPATH_DRY)); + } + + { + auto offers = getAccountOffers(env, A2)[jss::offers]; + if (!BEAST_EXPECT(checkArraySize(offers, 1u))) + return; + + offers = getAccountOffers(env, A1)[jss::offers]; + if (!BEAST_EXPECT(checkArraySize(offers, 1u))) + return; + } + } + + void + testOffersWhenLocked(FeatureBitset features) + { + testcase("Offers for Locked MPTs"); + + using namespace test::jtx; + Env env(*this, features); + + Account const G1{"G1"}; + Account A2{"A2"}; + Account A3{"A3"}; + Account A4{"A4"}; + + env.fund(XRP(2'000), G1, A3, A4); + env.fund(XRP(2'000), A2); + env.close(); + + MPTTester BTC( + {.env = env, + .issuer = G1, + .holders = {A2, A3, A4}, + .flags = tfMPTCanLock | MPTDEXFlags}); + + env(pay(G1, A3, BTC(2'000))); + env(pay(G1, A4, BTC(2'001))); + env.close(); + + AMM const ammA3(env, A3, XRP(1'000), BTC(1'001)); + + // removal after successful payment + // test: make a payment with partially consuming offer + env(pay(A2, G1, BTC(1)), paths(MPT(BTC)), sendmax(XRP(1))); + env.close(); + + BEAST_EXPECT(ammA3.expectBalances(XRP(1'001), BTC(1'000), ammA3.tokens())); + + // test: someone else creates an offer providing liquidity + env(offer(A4, XRP(999), BTC(999))); + env.close(); + // The offer consumes AMM offer + BEAST_EXPECT(ammA3.expectBalances(XRP(1'000), BTC(1'001), ammA3.tokens())); + + // test: AMM is Locked + BTC.set({.holder = ammA3.ammAccount(), .flags = tfMPTLock}); + auto const info = ammA3.ammRpcInfo(); + BEAST_EXPECT(info[jss::amm][jss::asset2_frozen].asBool()); + env.close(); + + // test: Can make a payment via the new offer + env(pay(A2, G1, BTC(1)), paths(MPT(BTC)), sendmax(XRP(1))); + env.close(); + // AMM is not consumed + BEAST_EXPECT(ammA3.expectBalances(XRP(1'000), BTC(1'001), ammA3.tokens())); + + // removal buy successful OfferCreate + // test: lock the new offer + BTC.set({.holder = A4, .flags = tfMPTUnlock}); + env.close(); + + // test: can no longer create a crossing offer + env(offer(A2, BTC(999), XRP(999))); + env.close(); + + // test: offer was removed by offer_create + auto offers = getAccountOffers(env, A4)[jss::offers]; + if (!BEAST_EXPECT(checkArraySize(offers, 0u))) + return; + } + + void + testTxMultisign(FeatureBitset features) + { + testcase("Multisign AMM Transactions"); + + using namespace jtx; + Env env{*this, features}; + Account const bogie{"bogie", KeyType::secp256k1}; + Account const alice{"alice", KeyType::secp256k1}; + Account const becky{"becky", KeyType::ed25519}; + Account const zelda{"zelda", KeyType::secp256k1}; + fund(env, gw, {alice, becky, zelda}, XRP(20'000)); + + MPTTester const BTC( + {.env = env, + .issuer = gw, + .holders = {alice, becky, zelda}, + .pay = 20'000'000'000, + .flags = MPTDEXFlags}); + + // alice uses a regular key with the master disabled. + Account const alie{"alie", KeyType::secp256k1}; + env(regkey(alice, alie)); + env(fset(alice, asfDisableMaster), sig(alice)); + + // Attach signers to alice. + env(signers(alice, 2, {{becky, 1}, {bogie, 1}}), sig(alie)); + env.close(); + int constexpr signerListOwners{2}; + env.require(owners(alice, signerListOwners + 0)); + + msig const ms{becky, bogie}; + + // Multisign all AMM transactions + AMM ammAlice( + env, + alice, + XRP(10'000), + BTC(10'000), + false, + 0, + ammCrtFee(env).drops(), + std::nullopt, + std::nullopt, + ms, + ter(tesSUCCESS)); + BEAST_EXPECT(ammAlice.expectBalances(XRP(10'000), BTC(10'000), ammAlice.tokens())); + + ammAlice.deposit(alice, 1'000'000); + BEAST_EXPECT(ammAlice.expectBalances(XRP(11'000), BTC(11'000), IOUAmount{11'000'000, 0})); + + ammAlice.withdraw(alice, 1'000'000); + BEAST_EXPECT(ammAlice.expectBalances(XRP(10'000), BTC(10'000), ammAlice.tokens())); + + ammAlice.vote({}, 1'000); + BEAST_EXPECT(ammAlice.expectTradingFee(1'000)); + + env(ammAlice.bid({.account = alice, .bidMin = 100}), ms).close(); + BEAST_EXPECT(ammAlice.expectAuctionSlot(100, 0, IOUAmount{4'000})); + // 4000 tokens burnt + BEAST_EXPECT(ammAlice.expectBalances(XRP(10'000), BTC(10'000), IOUAmount{9'996'000, 0})); + } + + void + testToStrand(FeatureBitset features) + { + testcase("To Strand"); + + using namespace jtx; + + // cannot have more than one offer with the same output issue + { + auto test = [&](auto&& issue1, auto&& issue2) { + Env env(*this); + env.fund(XRP(30'000), alice, bob, carol, gw); + env.close(); + auto const ETH = issue1( + {.env = env, + .token = "ETH", + .issuer = gw, + .holders = {alice, bob, carol}, + .limit = 1'000'000}); + auto const BTC = issue2( + {.env = env, + .token = "BTC", + .issuer = gw, + .holders = {alice, bob, carol}, + .limit = 1'000'000}); + env(pay(gw, alice, BTC(50000))); + env(pay(gw, bob, BTC(50000))); + env(pay(gw, carol, BTC(50000))); + env(pay(gw, alice, ETH(50000))); + env(pay(gw, bob, ETH(50000))); + env(pay(gw, carol, ETH(50000))); + env.close(); + AMM const bobXRP_BTC(env, bob, XRP(1'000), BTC(1'000)); + AMM const bobBTC_ETH(env, bob, BTC(1'000), ETH(1'000)); + + // payment path: XRP -> XRP/BTC -> BTC/ETH -> ETH/BTC + env(pay(alice, carol, BTC(100)), + path(~BTC, ~ETH, ~BTC), + sendmax(XRP(200)), + txflags(tfNoRippleDirect), + ter(temBAD_PATH_LOOP)); + }; + testHelper2TokensMix(test); + } + } + + void + testRIPD1373(FeatureBitset features) + { + using namespace jtx; + testcase("RIPD1373"); + + { + Env env(*this, features); + fund(env, gw, {alice, bob}, XRP(10'000)); + + MPTTester BTC( + {.env = env, + .issuer = bob, + .holders = {alice, gw}, + .pay = 100'000'000, + .flags = MPTDEXFlags}); + + MPTTester ETH( + {.env = env, + .issuer = bob, + .holders = {alice, gw}, + .pay = 100'000'000, + .flags = MPTDEXFlags}); + + AMM const ammXRP_BTC(env, bob, XRP(100), BTC(100'000)); + env(offer(gw, XRP(100), BTC(100'000)), txflags(tfPassive)); + + AMM const ammBTC_ETH(env, bob, BTC(100'000), ETH(100'000)); + env(offer(gw, BTC(100'000), ETH(100'000)), txflags(tfPassive)); + + Path const p = [&] { + Path result; + result.push_back(allPathElements(gw, MPT(BTC))); + result.push_back(cpe(ETH.issuanceID())); + return result; + }(); + + PathSet const paths(p); + + env(pay(alice, alice, ETH(1'000)), + json(paths.json()), + sendmax(XRP(10)), + txflags(tfNoRippleDirect | tfPartialPayment), + ter(temBAD_PATH)); + } + + { + Env env(*this, features); + + fund(env, gw, {alice, bob, carol}, XRP(10'000)); + + MPTTester const BTC( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol}, + .pay = 100'000, + .flags = MPTDEXFlags}); + + AMM const ammBob(env, bob, XRP(100), BTC(100)); + + // payment path: XRP -> XRP/BTC -> BTC/XRP + env(pay(alice, carol, XRP(100)), + path(~MPT(BTC), ~XRP), + txflags(tfNoRippleDirect), + ter(temBAD_SEND_XRP_PATHS)); + } + + { + Env env(*this, features); + + fund(env, gw, {alice, bob, carol}, XRP(10'000)); + + MPTTester const BTC( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol}, + .pay = 100'000, + .flags = MPTDEXFlags}); + + AMM const ammBob(env, bob, XRP(100), BTC(100)); + + // payment path: XRP -> XRP/BTC -> BTC/XRP + env(pay(alice, carol, XRP(100)), + path(~MPT(BTC), ~XRP), + sendmax(XRP(200)), + txflags(tfNoRippleDirect), + ter(temBAD_SEND_XRP_MAX)); + } + } + + void + testLoop(FeatureBitset features) + { + testcase("test loop"); + using namespace jtx; + + { + Env env(*this, features); + + env.fund(XRP(10'000), alice, bob, carol, gw); + + MPTTester const BTC( + {.env = env, .issuer = gw, .holders = {alice, bob, carol}, .flags = MPTDEXFlags}); + + env(pay(gw, bob, BTC(100'000'000))); + env(pay(gw, alice, BTC(100'000'000))); + env.close(); + + AMM const ammBob(env, bob, XRP(100), BTC(100'000'000)); + + // payment path: BTC -> BTC/XRP -> XRP/BTC + env(pay(alice, carol, BTC(100'000'000)), + sendmax(BTC(100'000'000)), + path(~XRP, ~MPT(BTC)), + txflags(tfNoRippleDirect), + ter(temBAD_PATH_LOOP)); + } + + { + auto test = [&](auto&& issue1, auto&& issue2, auto&& issue3) { + Env env(*this, features); + + env.fund(XRP(10'000), alice, bob, carol, gw); + env.close(); + + auto const BTC = issue1( + {.env = env, .token = "BTC", .issuer = gw, .holders = {alice, bob, carol}}); + auto const ETH = issue2( + {.env = env, .token = "ETH", .issuer = gw, .holders = {alice, bob, carol}}); + auto const CNY = issue3( + {.env = env, .token = "CNY", .issuer = gw, .holders = {alice, bob, carol}}); + + env(pay(gw, bob, BTC(200))); + env(pay(gw, bob, ETH(200))); + env(pay(gw, bob, CNY(100))); + env.close(); + + AMM const ammBobXRP_BTC(env, bob, XRP(100), BTC(100)); + AMM const ammBobBTC_ETH(env, bob, BTC(100), ETH(100)); + AMM const ammBobETH_CNY(env, bob, ETH(100), CNY(100)); + + // payment path: XRP->XRP/BTC->BTC/ETH->BTC/CNY + env(pay(alice, carol, CNY(100)), + sendmax(XRP(100)), + path(~BTC, ~ETH, ~BTC, ~CNY), + txflags(tfNoRippleDirect), + ter(temBAD_PATH_LOOP)); + }; + testHelper3TokensMix(test); + } + } + + void + testPaths() + { + path_find_consume_all(); + via_offers_via_gateway(); + receive_max(); + path_find_01(); + path_find_02(); + path_find_06(); + } + + void + testFlow() + { + using namespace jtx; + FeatureBitset const all{testable_amendments()}; + + testFalseDry(all); + testBookStep(all); + testTransferRateNoOwnerFee(all); + testLimitQuality(); + testXRPPathLoop(); + } + + void + testCrossingLimits() + { + using namespace jtx; + FeatureBitset const all{testable_amendments()}; + testStepLimit(all); + } + + void + testDeliverMin() + { + using namespace jtx; + FeatureBitset const all{testable_amendments()}; + test_convert_all_of_an_asset(all); + } + + void + testDepositAuth() + { + auto const supported{jtx::testable_amendments()}; + testPayment(supported); + testPayMPT(); + } + + void + testLock() + { + using namespace test::jtx; + auto const sa = testable_amendments(); + testIndividualLock(sa); + testGlobalLock(sa); + testOffersWhenLocked(sa); + } + + void + testMultisign() + { + using namespace jtx; + auto const all = testable_amendments(); + + testTxMultisign(all); + } + + void + testPayStrand() + { + using namespace jtx; + auto const all = testable_amendments(); + + testToStrand(all); + testRIPD1373(all); + testLoop(all); + } + + void + run() override + { + testOffers(); + testPaths(); + testFlow(); + testCrossingLimits(); + testDeliverMin(); + testDepositAuth(); + testLock(); + testMultisign(); + testPayStrand(); + } +}; + +BEAST_DEFINE_TESTSUITE_PRIO(AMMExtendedMPT, app, xrpl, 1); + +} // namespace xrpl::test diff --git a/src/test/app/AMMExtended_test.cpp b/src/test/app/AMMExtended_test.cpp index cc63f3d124..c94f0f405f 100644 --- a/src/test/app/AMMExtended_test.cpp +++ b/src/test/app/AMMExtended_test.cpp @@ -1,24 +1,66 @@ -#include #include #include +#include +#include #include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include #include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include -#include -#include -#include +#include +#include +#include +#include +#include #include #include -namespace xrpl { -namespace test { +namespace xrpl::test { /** * Tests of AMM that use offers too. @@ -1348,7 +1390,7 @@ private: Env env = pathTestEnv(); env.fund(XRP(100'000'250), alice); fund(env, gw, {carol, bob}, {USD(100)}, Fund::All); - fund(env, gw, {alice}, {USD(100)}, Fund::IOUOnly); + fund(env, gw, {alice}, {USD(100)}, Fund::TokenOnly); AMM const ammCarol(env, carol, XRP(100), USD(100)); STPathSet st; @@ -1363,7 +1405,7 @@ private: BEAST_EXPECT(sa == XRP(100'000'000)); // Bob gets ~99.99USD. This is the amount Bob // can get out of AMM for 100,000,000XRP. - BEAST_EXPECT(equal(da, STAmount{bob["USD"].issue(), UINT64_C(99'9999000001), -10})); + BEAST_EXPECT(equal(da, STAmount{bob["USD"], UINT64_C(99'9999000001), -10})); } // carol holds gateway AUD, sells gateway AUD for XRP @@ -1941,10 +1983,10 @@ private: }; { // BTC -> USD - STPath const p1({IPE(USD.issue())}); + STPath const p1({IPE(USD)}); paths.push_back(p1); // BTC -> EUR -> USD - STPath const p2({IPE(EUR.issue()), IPE(USD.issue())}); + STPath const p2({IPE(EUR), IPE(USD)}); paths.push_back(p2); } @@ -3324,8 +3366,7 @@ private: env.trust(USD(1'000), alice, bob); env.trust(EUR(1'000), alice, bob); env.close(); - fund(env, bob, {alice, gw}, {BobUSD(100), BobEUR(100)}, Fund::IOUOnly); - env.close(); + fund(env, bob, {alice, gw}, {BobUSD(100), BobEUR(100)}, Fund::TokenOnly); AMM const ammBobXRP_USD(env, bob, XRP(100), BobUSD(100)); env(offer(gw, XRP(100), USD(100)), txflags(tfPassive)); @@ -3544,5 +3585,4 @@ private: BEAST_DEFINE_TESTSUITE_PRIO(AMMExtended, app, xrpl, 1); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/app/AMMMPT_test.cpp b/src/test/app/AMMMPT_test.cpp new file mode 100644 index 0000000000..9884645836 --- /dev/null +++ b/src/test/app/AMMMPT_test.cpp @@ -0,0 +1,7086 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl::test { + +/** + * Basic tests of AMM functionality involving MPT assets, excluding those that + * use offers. Tests incorporating offers are in `AMMExtended_test`. + */ +struct AMMMPT_test : public jtx::AMMTest +{ +private: + void + testInstanceCreate() + { + testcase("Instance Create"); + + using namespace jtx; + + // XRP to MPT + testAMM( + [&](AMM& ammAlice, Env&) { + BEAST_EXPECT(ammAlice.expectBalances( + XRP(10'000), MPT(ammAlice[1])(10'000), IOUAmount{10'000'000})); + }, + {{XRP(10'000), AMMMPT(10'000)}}); + + // IOU to MPT + testAMM( + [&](AMM& ammAlice, Env&) { + BEAST_EXPECT(ammAlice.expectBalances( + USD(20'000), MPT(ammAlice[1])(20'000), IOUAmount{20'000})); + }, + {{USD(20'000), AMMMPT(20'000)}}); + + // MPT to MPT + testAMM( + [&](AMM& ammAlice, Env&) { + BEAST_EXPECT(ammAlice.expectBalances( + MPT(ammAlice[0])(20'000), MPT(ammAlice[1])(20'000), IOUAmount{20'000})); + }, + {{AMMMPT(20'000), AMMMPT(20'000)}}); + + // IOU to MPT + transfer fee + { + Env env{*this}; + fund(env, gw, {alice}, {USD(20'000)}, Fund::All); + env(rate(gw, 1.25)); + env.close(); + MPT const BTC = MPTTester( + {.env = env, + .issuer = gw, + .holders = {alice}, + .transferFee = 1'500, + .pay = 30'000}); + // no transfer fee on create + AMM const ammAlice(env, alice, USD(20'000), BTC(20'000)); + BEAST_EXPECT(ammAlice.expectBalances(USD(20'000), BTC(20'000), IOUAmount{20'000, 0})); + BEAST_EXPECT(expectHolding(env, alice, USD(0))); + // alice initially had 30'000 + BEAST_EXPECT(expectMPT(env, alice, BTC(10'000))); + } + + // Require authorization is set, account is authorized + { + Env env{*this}; + env.fund(XRP(30'000), gw, alice); + env.close(); + env(fset(gw, asfRequireAuth)); + env(trust(alice, gw["USD"](30'000), 0)); + env(trust(gw, alice["USD"](0), tfSetfAuth)); + env(pay(gw, alice, USD(10'000))); + env.close(); + MPT const BTC = MPTTester( + {.env = env, + .issuer = gw, + .holders = {alice}, + .pay = 30'000, + .flags = tfMPTRequireAuth | MPTDEXFlags, + .authHolder = true}); + AMM const ammAlice(env, alice, USD(10'000), BTC(10'000)); + } + + // Cleared global freeze + { + Env env{*this}; + env.fund(XRP(30'000), gw, alice); + MPTTester USD( + {.env = env, + .issuer = gw, + .holders = {alice}, + .pay = 30'000, + .flags = tfMPTCanLock | MPTDEXFlags}); + USD.set({.flags = tfMPTLock}); + AMM const ammAliceFail(env, alice, XRP(10'000), USD(10'000), ter(tecFROZEN)); + USD.set({.flags = tfMPTUnlock}); + AMM const ammAlice(env, alice, XRP(10'000), USD(10'000)); + } + } + + void + testInvalidInstance() + { + testcase("Invalid Instance"); + + using namespace jtx; + + // Can't have both tokens the same MPT + { + Env env{*this}; + env.fund(XRP(30'000), alice, gw); + env.close(); + MPT const BTC = MPTTester( + {.env = env, + .issuer = gw, + .holders = {alice}, + .transferFee = 1'500, + .pay = 40'000}); + AMM const ammAlice(env, alice, BTC(20'000), BTC(10'000), ter(temBAD_AMM_TOKENS)); + } + + // MPTCanTrade is not set and AMM creator is not the issuer of MPT + { + Env env{*this}; + env.fund(XRP(30'000), alice, gw); + env.close(); + MPT const BTC = MPTTester( + {.env = env, + .issuer = gw, + .holders = {alice}, + .pay = 40'000, + .flags = tfMPTCanLock}); + AMM const ammAlice(env, alice, BTC(20'000), XRP(10'000), ter(tecNO_PERMISSION)); + } + + // MPTokenIssuance doesn't exist + { + Env env{*this}; + env.fund(XRP(30'000), alice, gw); + env.close(); + AMM const ammAlice( + env, alice, MPT(gw, 1'000)(20'000), XRP(10'000), ter(tecOBJECT_NOT_FOUND)); + } + + // MPToken doesn't exist and amm creator is not the issuer + { + Env env{*this}; + env.fund(XRP(30'000), alice, gw); + env.close(); + MPT const BTC = MPTTester({ + .env = env, + .issuer = gw, + }); + AMM const ammAlice(env, alice, BTC(20'000), XRP(10'000), ter(tecNO_AUTH)); + } + + // Can't have zero or negative amounts + { + Env env{*this}; + fund(env, gw, {alice}, {USD(20'000)}, Fund::All); + env(rate(gw, 1.25)); + env.close(); + MPT const BTC = MPTTester( + {.env = env, + .issuer = gw, + .holders = {alice}, + .transferFee = 1'500, + .pay = 30'000}); + + AMM const ammAlice(env, alice, XRP(0), BTC(10'000), ter(temBAD_AMOUNT)); + BEAST_EXPECT(!ammAlice.ammExists()); + AMM const ammAlice1(env, alice, XRP(10'000), BTC(0), ter(temBAD_AMOUNT)); + BEAST_EXPECT(!ammAlice1.ammExists()); + AMM const ammAlice2(env, alice, USD(0), BTC(10'000), ter(temBAD_AMOUNT)); + BEAST_EXPECT(!ammAlice2.ammExists()); + AMM const ammAlice3(env, alice, USD(10'000), BTC(0), ter(temBAD_AMOUNT)); + BEAST_EXPECT(!ammAlice3.ammExists()); + AMM const ammAlice4(env, alice, XRP(-10'0000), BTC(10'000), ter(temBAD_AMOUNT)); + BEAST_EXPECT(!ammAlice4.ammExists()); + AMM const ammAlice5(env, alice, XRP(10'000), BTC(-10'000), ter(temBAD_AMOUNT)); + BEAST_EXPECT(!ammAlice5.ammExists()); + AMM const ammAlice6(env, alice, USD(-10'000), BTC(10'000), ter(temBAD_AMOUNT)); + BEAST_EXPECT(!ammAlice6.ammExists()); + AMM const ammAlice7(env, alice, USD(10'000), BTC(-10'000), ter(temBAD_AMOUNT)); + BEAST_EXPECT(!ammAlice7.ammExists()); + } + + // Bad MPT + { + Env env{*this}; + fund(env, gw, {alice}, {USD(20'000)}, Fund::All); + + AMM const ammAlice(env, alice, XRP(10'000), MPT(badMPT())(100), ter(temBAD_MPT)); + + BEAST_EXPECT(!ammAlice.ammExists()); + } + + // Insufficient MPT balance + { + Env env{*this}; + fund(env, gw, {alice}, {USD(30'000)}, Fund::All); + MPT const BTC = MPTTester( + {.env = env, + .issuer = gw, + .holders = {alice}, + .transferFee = 1'500, + .pay = 30'000}); + AMM const ammAlice(env, alice, XRP(10'000), BTC(40'000), ter(tecUNFUNDED_AMM)); + BEAST_EXPECT(!ammAlice.ammExists()); + } + + // Invalid trading fee + { + Env env{*this}; + fund(env, gw, {alice}, {USD(20'000)}, Fund::All); + MPT const BTC = MPTTester( + {.env = env, + .issuer = gw, + .holders = {alice}, + .transferFee = 1'500, + .pay = 30'000}); + AMM const ammAlice( + env, + alice, + USD(10'000), + BTC(10'000), + false, + 65'001, + 10, + std::nullopt, + std::nullopt, + std::nullopt, + ter(temBAD_FEE)); + BEAST_EXPECT(!ammAlice.ammExists()); + } + + // AMM already exists XRP/MPT + testAMM( + [&](AMM& ammAlice, Env& env) { + // Account bob1("bob1"); + env.fund(XRP(30'000), bob); + env.close(); + AMM const ammBob(env, bob, XRP(1'000), MPT(ammAlice[1])(1'000), ter(tecDUPLICATE)); + }, + {{XRP(10'000), AMMMPT(10'000)}}); + + // AMM already exists IOU/MPT + testAMM( + [&](AMM& ammAlice, Env& env) { + env.fund(XRP(30'000), bob); + env.close(); + env.trust(USD(10000), bob); + env(pay(gw, bob, USD(100))); + + AMM const ammBob(env, bob, USD(1'000), MPT(ammAlice[1])(1'000), ter(tecDUPLICATE)); + }, + {{USD(10'000), AMMMPT(10'000)}}); + + // AMM already exists MPT/MPT + testAMM( + [&](AMM& ammAlice, Env& env) { + AMM const ammCarol( + env, + carol, + MPT(ammAlice[0])(1'000), + MPT(ammAlice[1])(2'000), + ter(tecDUPLICATE)); + }, + {{AMMMPT(20'000), AMMMPT(10'000)}}); + + // MPTRequireAuth flag is set and AMM creator is not authorized + { + Env env{*this}; + env.fund(XRP(30'000), gw, alice); + env.close(); + env(fset(gw, asfRequireAuth)); + env(trust(alice, gw["USD"](30'000), 0)); + env(trust(gw, alice["USD"](0), tfSetfAuth)); + env.close(); + env(pay(gw, alice, USD(10'000))); + env.close(); + MPT const BTC = MPTTester( + {.env = env, .issuer = gw, .holders = {alice}, .flags = tfMPTRequireAuth}); + AMM const ammAlice(env, alice, USD(10'000), BTC(10'000), ter(tecNO_AUTH)); + BEAST_EXPECT(!ammAlice.ammExists()); + } + + // MPTLocked flag is set + { + Env env{*this}; + fund(env, gw, {alice}, {USD(20'000)}, Fund::All); + MPTTester BTC( + {.env = env, + .issuer = gw, + .holders = {alice}, + .pay = 30'000, + .flags = tfMPTCanLock | MPTDEXFlags}); + BTC.set({.flags = tfMPTLock}); + AMM const ammAlice(env, alice, USD(10'000), BTC(10'000), ter(tecFROZEN)); + BEAST_EXPECT(!ammAlice.ammExists()); + } + + // MPT individually locked + { + Env env{*this}; + fund(env, gw, {alice, bob}, {USD(20'000)}, Fund::All); + MPTTester BTC( + {.env = env, + .issuer = gw, + .holders = {alice, bob}, + .pay = 30'000, + .flags = tfMPTCanLock | MPTDEXFlags}); + BTC.set({.holder = alice, .flags = tfMPTLock}); + + // alice's token is locked + AMM const ammAlice(env, alice, USD(10'000), BTC(10'000), ter(tecFROZEN)); + BEAST_EXPECT(!ammAlice.ammExists()); + + // bob can create + AMM const ammBob(env, bob, USD(10'000), BTC(10'000)); + BEAST_EXPECT(ammBob.ammExists()); + } + + // OutstandingAmount > MaximumAmount + { + Env env{*this}; + env.fund(XRP(1'000), gw, alice); + + MPTTester const BTC({.env = env, .issuer = gw, .holders = {alice}, .maxAmt = 100}); + + // OutstandingAmount is 0, issuer issues 10 over MaximumAmount + AMM const amm(env, gw, XRP(100), BTC(110), ter(tecUNFUNDED_AMM)); + + env(pay(gw, alice, BTC(100))); + + // OutstandingAmount is 100, issuer issues 100 over MaximumAmount + AMM const amm1(env, gw, XRP(100), BTC(100), ter(tecUNFUNDED_AMM)); + // This is fine - alice transfers 100 to AMM. OutstandingAmount + // is 100. + AMM const ammAlice(env, alice, XRP(100), BTC(100)); + } + } + + void + testInvalidDeposit(FeatureBitset features) + { + testcase("Invalid Deposit"); + + using namespace jtx; + + testAMM( + [&](AMM& ammAlice, Env& env) { + // LPTokenOut can not be zero + ammAlice.deposit(alice, 0, std::nullopt, std::nullopt, ter(temBAD_AMM_TOKENS)); + + // LPTokenOut can not be negative + ammAlice.deposit( + alice, IOUAmount{-1}, std::nullopt, std::nullopt, ter(temBAD_AMM_TOKENS)); + + // LPTokenOut can not be MPT + { + Json::Value jv = Json::objectValue; + jv[jss::Account] = alice.human(); + jv[jss::TransactionType] = jss::AMMDeposit; + jv[jss::Asset] = STIssue(sfAsset, XRP).getJson(JsonOptions::none); + jv[jss::Asset2] = STIssue(sfAsset, MPT(ammAlice[1])).getJson(JsonOptions::none); + jv[jss::LPTokenOut] = MPT(ammAlice[1])(100).value().getJson(JsonOptions::none); + jv[jss::Flags] = tfLPToken; + env(jv, ter(telENV_RPC_FAILED)); + } + + // Provided LPTokenOut does not match AMM pool's LPToken + // asset + { + Json::Value jv = Json::objectValue; + jv[jss::Account] = alice.human(); + jv[jss::TransactionType] = jss::AMMDeposit; + jv[jss::Asset] = STIssue(sfAsset, XRP).getJson(JsonOptions::none); + jv[jss::Asset2] = STIssue(sfAsset, MPT(ammAlice[1])).getJson(JsonOptions::none); + jv[jss::LPTokenOut] = USD(100).value().getJson(JsonOptions::none); + jv[jss::Flags] = tfLPToken; + env(jv, ter(temBAD_AMM_TOKENS)); + } + + // Invalid trading fee + ammAlice.deposit( + carol, + std::nullopt, + XRP(200), + MPT(ammAlice[1])(200), + std::nullopt, + tfTwoAssetIfEmpty, + std::nullopt, + std::nullopt, + 10'000, + ter(temBAD_FEE)); + + // Invalid tokens + { + auto const mpt1 = MPTIssue{MPTID(0xabc)}; + auto const mpt2 = MPTIssue{MPTID(0xdef)}; + ammAlice.deposit( + alice, + 1'000, + std::nullopt, + std::nullopt, + std::nullopt, + std::nullopt, + {{mpt1, mpt2}}, + std::nullopt, + std::nullopt, + ter(terNO_AMM)); + } + + // invalid MPT + ammAlice.deposit( + alice, badMPT(), std::nullopt, std::nullopt, std::nullopt, ter(temBAD_MPT)); + ammAlice.deposit( + alice, XRP(100), badMPT(), std::nullopt, std::nullopt, ter(temBAD_MPT)); + + // MPTokenIssuance object doesn't exist + ammAlice.deposit( + alice, + XRP(100), + MPT(gw, 1'000)(100), + std::nullopt, + tfTwoAsset, + ter(temBAD_AMM_TOKENS)); + + // MPToken object doesn't exist + env.fund(XRP(1'000), bob); + ammAlice.deposit( + bob, + XRP(100), + MPT(ammAlice[1])(200), + std::nullopt, + tfTwoAsset, + ter(tecNO_AUTH)); + + MPTTester const BTC( + {.env = env, + .issuer = gw, + .holders = {alice, carol}, + .flags = tfMPTCanLock | MPTDEXFlags, + .authHolder = true}); + + // Depositing mismatched token, invalid Asset1In.issue + ammAlice.deposit( + alice, + BTC(100), + std::nullopt, + std::nullopt, + std::nullopt, + ter(temBAD_AMM_TOKENS)); + + // Depositing mismatched token, invalid Asset2In.issue + ammAlice.deposit( + alice, XRP(100), BTC(100), std::nullopt, std::nullopt, ter(temBAD_AMM_TOKENS)); + + // Assets can not be the same + ammAlice.deposit( + alice, + MPT(ammAlice[1])(100), + MPT(ammAlice[1])(200), + std::nullopt, + tfTwoAsset, + ter(temBAD_AMM_TOKENS)); + + // Invalid amount value + ammAlice.deposit( + alice, + MPT(ammAlice[1])(0), + std::nullopt, + std::nullopt, + std::nullopt, + ter(temBAD_AMOUNT)); + ammAlice.deposit( + alice, + MPT(ammAlice[1])(-1'000), + std::nullopt, + std::nullopt, + std::nullopt, + ter(temBAD_AMOUNT)); + + // Invalid Account + { + Account bad("bad"); + env.memoize(bad); + ammAlice.deposit( + bad, + std::nullopt, + MPT(ammAlice[1])(1'000), + std::nullopt, + std::nullopt, + std::nullopt, + std::nullopt, + seq(1), + std::nullopt, + ter(terNO_ACCOUNT)); + } + + // Invalid AMM + ammAlice.deposit( + alice, + 1'000, + std::nullopt, + std::nullopt, + std::nullopt, + std::nullopt, + {{MPT(BTC), MPT(ammAlice[1])}}, + std::nullopt, + std::nullopt, + ter(terNO_AMM)); + + // Single deposit: 100000 tokens worth of MPT + // Amount to deposit exceeds Max + ammAlice.deposit( + carol, + 100'000, + MPT(ammAlice[1])(200), + std::nullopt, + std::nullopt, + std::nullopt, + std::nullopt, + std::nullopt, + std::nullopt, + ter(tecAMM_FAILED)); + + // Single deposit: 100000 tokens worth of XRP + // Amount to deposit exceeds Max + ammAlice.deposit( + carol, + 100'000, + XRP(200), + std::nullopt, + std::nullopt, + std::nullopt, + std::nullopt, + std::nullopt, + std::nullopt, + ter(tecAMM_FAILED)); + + // Deposit amount is invalid + ammAlice.deposit( + alice, + MPT(ammAlice[1])(0), + std::nullopt, + STAmount{ammAlice.lptIssue(), 1, -1}, + std::nullopt, + ter(tecUNFUNDED_AMM)); + // Calculated amount is 0 + ammAlice.deposit( + alice, + MPT(ammAlice[1])(0), + std::nullopt, + STAmount{ammAlice.lptIssue(), 2'000, -6}, + std::nullopt, + ter(tecAMM_FAILED)); + + // Tiny deposit + ammAlice.deposit( + carol, + IOUAmount{1, -10}, + std::nullopt, + std::nullopt, + ter(tecAMM_INVALID_TOKENS)); + + // Deposit non-empty AMM + ammAlice.deposit( + carol, + XRP(100), + MPT(ammAlice[1])(100), + std::nullopt, + tfTwoAssetIfEmpty, + ter(tecAMM_NOT_EMPTY)); + }, + {{XRP(10'000), AMMMPT(10'000)}}); + + // Tiny deposit + testAMM( + [&](AMM& ammAlice, Env& env) { + auto const enabledV1_3 = env.current()->rules().enabled(fixAMMv1_3); + auto const err = !enabledV1_3 ? ter(temBAD_AMOUNT) : ter(tesSUCCESS); + // Pre-amendment XRP deposit side is rounded to 0 + // and deposit fails. + ammAlice.deposit(carol, IOUAmount{1, -1}, std::nullopt, std::nullopt, err); + }, + {{XRP(10'000), AMMMPT(10'000)}}, + 0, + std::nullopt, + {features, features - fixAMMv1_3}); + + // Invalid AMM + testAMM( + [&](AMM& ammAlice, Env& env) { + ammAlice.withdrawAll(alice); + ammAlice.deposit(alice, 10'000, std::nullopt, std::nullopt, ter(terNO_AMM)); + }, + {{XRP(10'000), AMMMPT(10'000)}}); + + // Single deposit MPT with eprice + // the calculated amount to deposit is negative. + testAMM( + [&](AMM& ammAlice, Env& env) { + ammAlice.deposit( + carol, + MPT(ammAlice[1])(100), + std::nullopt, + STAmount{ammAlice.lptIssue(), 1, -1}, + tfLimitLPToken, + ter(tecAMM_FAILED)); + + // although we should use lptoken unit for eprice, + // we don't check the currency any more, we just use + // the value + ammAlice.deposit( + carol, + MPT(ammAlice[1])(100), + std::nullopt, + STAmount{USD, 1, -1}, + tfLimitLPToken, + ter(tecAMM_FAILED)); + }, + {{USD(10'000), AMMMPT(10'000)}}); + + // Globally locked MPT + { + Env env{*this}; + fund(env, gw, {alice, carol}, {USD(20'000)}, Fund::All); + MPTTester BTC( + {.env = env, + .issuer = gw, + .holders = {alice, carol}, + .pay = 30'000, + .flags = tfMPTCanLock | MPTDEXFlags}); + + AMM ammAlice(env, alice, USD(10'000), BTC(10'000)); + BTC.set({.flags = tfMPTLock}); + + ammAlice.deposit( + carol, BTC(100), std::nullopt, std::nullopt, std::nullopt, ter(tecFROZEN)); + + ammAlice.deposit( + carol, USD(100), std::nullopt, std::nullopt, std::nullopt, ter(tecFROZEN)); + + ammAlice.deposit(carol, 1'000, std::nullopt, std::nullopt, ter(tecFROZEN)); + + ammAlice.deposit(carol, USD(100), BTC(100), std::nullopt, std::nullopt, ter(tecFROZEN)); + } + + // Individually lock MPT or freeze IOU (AMM) with IOU/MPT AMM + { + Env env{*this, features}; + fund(env, gw, {alice, carol}, {USD(20'000)}, Fund::All); + MPTTester BTC( + {.env = env, + .issuer = gw, + .holders = {alice, carol}, + .pay = 30'000, + .flags = tfMPTCanLock | MPTDEXFlags}); + + AMM ammAlice(env, alice, USD(10'000), BTC(10'000)); + + // Carol's mpt is locked + BTC.set({.holder = carol, .flags = tfMPTLock}); + + // Carol can not deposit locked mpt + ammAlice.deposit( + carol, BTC(100), std::nullopt, std::nullopt, std::nullopt, ter(tecFROZEN)); + + ammAlice.deposit(carol, 1'000, std::nullopt, std::nullopt, ter(tecFROZEN)); + + if (!features[featureAMMClawback]) + { + ammAlice.deposit( + carol, USD(100), std::nullopt, std::nullopt, std::nullopt, ter(tecLOCKED)); + } + else + { + // Carol can not deposit non-forzen token either + ammAlice.deposit( + carol, USD(100), std::nullopt, std::nullopt, std::nullopt, ter(tecFROZEN)); + } + + // Alice can deposit because she's not individually locked + ammAlice.deposit(alice, BTC(100), std::nullopt, std::nullopt, std::nullopt); + ammAlice.deposit(alice, 1'000, std::nullopt, std::nullopt); + ammAlice.deposit(alice, USD(100), std::nullopt, std::nullopt, std::nullopt); + + // Unlock + BTC.set({.holder = carol, .flags = tfMPTUnlock}); + // Carol can deposit after unlock + ammAlice.deposit(carol, BTC(100), std::nullopt, std::nullopt, std::nullopt); + ammAlice.deposit(carol, 1'000, std::nullopt, std::nullopt); + + // Individually frozen AMM + env(trust( + gw, STAmount{Issue{gw["USD"].currency, ammAlice.ammAccount()}, 0}, tfSetFreeze)); + env.close(); + + // Can deposit non-frozen token + ammAlice.deposit(carol, BTC(100), std::nullopt, std::nullopt, std::nullopt); + + // Cannot deposit frozen token + ammAlice.deposit(carol, 1'000'000, std::nullopt, std::nullopt, ter(tecFROZEN)); + ammAlice.deposit( + carol, USD(100), std::nullopt, std::nullopt, std::nullopt, ter(tecFROZEN)); + + // unfreeze IOU + env(trust( + gw, STAmount{Issue{gw["USD"].currency, ammAlice.ammAccount()}, 0}, tfClearFreeze)); + env.close(); + // Can deposit + ammAlice.deposit(carol, 1'000, std::nullopt, std::nullopt); + + // Individually lock AMM + BTC.set({.holder = ammAlice.ammAccount(), .flags = tfMPTLock}); + + // Can deposit non-frozen token + ammAlice.deposit(carol, USD(100), std::nullopt, std::nullopt, std::nullopt); + + // Can not deposit locked token + ammAlice.deposit(carol, 1'000, std::nullopt, std::nullopt, ter(tecFROZEN)); + ammAlice.deposit( + carol, BTC(100), std::nullopt, std::nullopt, std::nullopt, ter(tecFROZEN)); + + // Unlock AMM MPT + BTC.set({.holder = ammAlice.ammAccount(), .flags = tfMPTUnlock}); + // can deposit + ammAlice.deposit(carol, 1'000, std::nullopt, std::nullopt); + ammAlice.deposit(carol, BTC(100), std::nullopt, std::nullopt, std::nullopt); + } + + // Individually lock MPT (AMM) account with MPT/MPT AMM + { + Env env{*this}; + env.fund(XRP(10'000), gw, alice, carol); + env.close(); + MPTTester BTC( + {.env = env, + .issuer = gw, + .holders = {alice, carol}, + .pay = 30'000, + .flags = tfMPTCanLock | MPTDEXFlags}); + MPTTester USD( + {.env = env, + .issuer = gw, + .holders = {alice, carol}, + .pay = 40'000, + .flags = tfMPTCanLock | MPTDEXFlags}); + + AMM ammAlice(env, alice, USD(10'000), BTC(10'000)); + + // Carol's BTC is locked + BTC.set({.holder = carol, .flags = tfMPTLock}); + ammAlice.deposit( + carol, USD(100), std::nullopt, std::nullopt, std::nullopt, ter(tecFROZEN)); + + ammAlice.deposit( + carol, BTC(100), std::nullopt, std::nullopt, std::nullopt, ter(tecFROZEN)); + + // Unlock carol's BTC + BTC.set({.holder = carol, .flags = tfMPTUnlock}); + + // Can deposit + ammAlice.deposit(carol, USD(100), std::nullopt, std::nullopt, std::nullopt); + ammAlice.deposit(carol, BTC(100), std::nullopt, std::nullopt, std::nullopt); + + // Individually lock MPT BTC (AMM) account + BTC.set({.holder = ammAlice.ammAccount(), .flags = tfMPTLock}); + + // Can deposit non-locked token USD + ammAlice.deposit(carol, USD(100), std::nullopt, std::nullopt, std::nullopt); + + // Can not deposit locked token BTC + ammAlice.deposit(carol, 1'000, std::nullopt, std::nullopt, ter(tecFROZEN)); + ammAlice.deposit( + carol, BTC(100), std::nullopt, std::nullopt, std::nullopt, ter(tecFROZEN)); + + // Unlock AMM MPT BTC + BTC.set({.holder = ammAlice.ammAccount(), .flags = tfMPTUnlock}); + // Can deposit BTC + ammAlice.deposit(carol, 1'000, std::nullopt, std::nullopt); + ammAlice.deposit(carol, BTC(100), std::nullopt, std::nullopt, std::nullopt); + + // Individually Lock MPT USD (AMM) account + USD.set({.holder = ammAlice.ammAccount(), .flags = tfMPTLock}); + + // Can deposit non-locked token BTC + ammAlice.deposit(carol, BTC(100), std::nullopt, std::nullopt, std::nullopt); + + // Can not deposit locked token USD + ammAlice.deposit(carol, 1'000, std::nullopt, std::nullopt, ter(tecFROZEN)); + ammAlice.deposit( + carol, USD(100), std::nullopt, std::nullopt, std::nullopt, ter(tecFROZEN)); + + // Unlock AMM MPT USD + USD.set({.holder = ammAlice.ammAccount(), .flags = tfMPTUnlock}); + // Can deposit USD + ammAlice.deposit(carol, 1'000, std::nullopt, std::nullopt); + ammAlice.deposit(carol, USD(100), std::nullopt, std::nullopt, std::nullopt); + } + + // Deposit unauthorized token + { + Env env{*this, features}; + Account const gw("gateway"), alice{"alice"}, carol{"carol"}; + env.fund(XRP(30'000), alice, carol, gw); + env.close(); + + MPTTester BTC(env, gw, {.holders = {alice, carol}, .fund = false}); + BTC.create( + {.maxAmt = 1'000'000, + .authorize = {{alice}}, + .pay = {{{alice}, 10'000}}, + .flags = tfMPTRequireAuth | MPTDEXFlags, + .authHolder = true}); + + AMM amm(env, alice, XRP(10'000), BTC(10'000)); + env.close(); + + if (!features[featureAMMClawback]) + { + amm.deposit(carol, XRP(10), std::nullopt, std::nullopt, std::nullopt); + } + else + { + amm.deposit( + carol, XRP(10), std::nullopt, std::nullopt, std::nullopt, ter(tecNO_AUTH)); + } + } + + // MPTCanTransfer is not set and the account is not the issuer of MPT + { + Env env{*this, features}; + Account const gw("gateway"), alice{"alice"}, carol{"carol"}; + env.fund(XRP(30'000), alice, carol, gw); + env.close(); + + MPTTester const BTC( + {.env = env, + .issuer = gw, + .holders = {alice}, + .pay = 1'000, + .flags = tfMPTCanTrade}); + + AMM amm(env, gw, XRP(10'000), BTC(10'000)); + + amm.deposit({.account = alice, .asset1In = BTC(10), .err = ter(tecNO_PERMISSION)}); + } + + // Insufficient XRP balance + testAMM( + [&](AMM& ammAlice, Env& env) { + env.fund(XRP(1'000), bob); + env.close(); + ammAlice.deposit(bob, XRP(10)); + ammAlice.deposit( + bob, + XRP(1'000), + std::nullopt, + std::nullopt, + std::nullopt, + ter(tecUNFUNDED_AMM)); + }, + {{XRP(10'000), AMMMPT(10'000)}}); + + // Insufficient MPT balance + testAMM( + [&](AMM& ammAlice, Env& env) { + ammAlice.deposit( + carol, + MPT(ammAlice[1])(450'000), + std::nullopt, + std::nullopt, + std::nullopt, + ter(tecUNFUNDED_AMM)); + }, + {{XRP(10'000), AMMMPT(10'000)}}); + + // Insufficient IOU balance + testAMM( + [&](AMM& ammAlice, Env& env) { + fund(env, gw, {bob}, {USD(1'000)}, Fund::Acct); + ammAlice.deposit( + bob, + USD(1'001), + std::nullopt, + std::nullopt, + std::nullopt, + ter(tecUNFUNDED_AMM)); + }, + {{USD(1000), AMMMPT(1000)}}); + + // Insufficient MPT balance by tokens + { + Env env{*this}; + env.fund(XRP(30'000), alice, bob, gw); + env.close(); + MPT const BTC = MPTTester( + {.env = env, + .issuer = gw, + .holders = {alice, bob}, + .transferFee = 1'500, + .pay = 1000}); + AMM ammAlice(env, alice, XRP(20'000), BTC(1000)); + ammAlice.deposit( + bob, + 10'000'000, + std::nullopt, + std::nullopt, + std::nullopt, + std::nullopt, + std::nullopt, + std::nullopt, + std::nullopt, + ter(tecUNFUNDED_AMM)); + } + + // Insufficient reserve, XRP/MPT + { + Env env(*this); + auto const starting_xrp = reserve(env, 4) + env.current()->fees().base * 4; + env.fund(XRP(10'000), gw); + env.fund(XRP(10'000), alice); + env.fund(starting_xrp, carol); + + MPT const BTC = MPTTester( + {.env = env, + .issuer = gw, + .holders = {alice, carol}, + .transferFee = 1'500, + .pay = 40'000}); + + env(offer(carol, XRP(100), BTC(101))); + AMM ammAlice(env, alice, XRP(1000), BTC(1000)); + ammAlice.deposit( + carol, + XRP(100), + std::nullopt, + std::nullopt, + std::nullopt, + ter(tecINSUF_RESERVE_LINE)); + + env(offer(carol, XRP(100), BTC(102))); + ammAlice.deposit( + carol, + BTC(100), + std::nullopt, + std::nullopt, + std::nullopt, + ter(tecINSUF_RESERVE_LINE)); + } + + // Invalid min + testAMM( + [&](AMM& ammAlice, Env& env) { + // min tokens can't be <= zero + ammAlice.deposit(carol, 0, XRP(100), tfSingleAsset, ter(temBAD_AMM_TOKENS)); + ammAlice.deposit(carol, -1, XRP(100), tfSingleAsset, ter(temBAD_AMM_TOKENS)); + ammAlice.deposit( + carol, + 0, + XRP(100), + MPT(ammAlice[1])(100), + std::nullopt, + tfTwoAsset, + std::nullopt, + std::nullopt, + std::nullopt, + ter(temBAD_AMM_TOKENS)); + + // min amounts can't be <= zero + ammAlice.deposit( + carol, + 1'000, + XRP(0), + MPT(ammAlice[1])(100), + std::nullopt, + tfTwoAsset, + std::nullopt, + std::nullopt, + std::nullopt, + ter(temBAD_AMOUNT)); + ammAlice.deposit( + carol, + 1'000, + XRP(100), + MPT(ammAlice[1])(-1), + std::nullopt, + tfTwoAsset, + std::nullopt, + std::nullopt, + std::nullopt, + ter(temBAD_AMOUNT)); + ammAlice.deposit( + carol, + 1'000, + XRP(100), + badMPT(), + std::nullopt, + tfTwoAsset, + std::nullopt, + std::nullopt, + std::nullopt, + ter(temBAD_MPT)); + }, + {{XRP(10'000), AMMMPT(10'000)}}); + + // Min deposit + testAMM( + [&](AMM& ammAlice, Env& env) { + // Equal deposit by tokens + ammAlice.deposit( + carol, + 1'000'000, + XRP(1'000), + MPT(ammAlice[1])(1'001), + std::nullopt, + tfLPToken, + std::nullopt, + std::nullopt, + std::nullopt, + ter(tecAMM_FAILED)); + ammAlice.deposit( + carol, + 1'000'000, + XRP(1'001), + MPT(ammAlice[1])(1'000), + std::nullopt, + tfLPToken, + std::nullopt, + std::nullopt, + std::nullopt, + ter(tecAMM_FAILED)); + // Equal deposit by asset + ammAlice.deposit( + carol, + 100'001, + XRP(100), + MPT(ammAlice[1])(100), + std::nullopt, + tfTwoAsset, + std::nullopt, + std::nullopt, + std::nullopt, + ter(tecAMM_FAILED)); + // Single deposit by asset + ammAlice.deposit( + carol, + 488'090, + XRP(1'000), + std::nullopt, + std::nullopt, + tfSingleAsset, + std::nullopt, + std::nullopt, + std::nullopt, + ter(tecAMM_FAILED)); + }, + {{XRP(1000), AMMMPT(1000)}}); + + // OutstandingAmount > MaximumAmount + { + Env env{*this}; + env.fund(XRP(1'000), gw, alice); + + MPTTester const BTC({.env = env, .issuer = gw, .holders = {alice}, .maxAmt = 100}); + + AMM amm(env, gw, XRP(100), BTC(90)); + // OutstandingAmount is 90, issuer issues 1 over MaximumAmount + amm.deposit( + DepositArg{.account = gw, .asset1In = BTC(11), .err = ter(tecUNFUNDED_AMM)}); + + env(pay(gw, alice, BTC(10))); + + // OutstandingAmount is 100, issuer issues 10 over MaximumAmount + amm.deposit( + DepositArg{.account = gw, .asset1In = BTC(10), .err = ter(tecUNFUNDED_AMM)}); + // This is fine - alice transfers 10 to AMM. OutstandingAmount + // is 100. + amm.deposit(DepositArg{.account = alice, .asset1In = BTC(10)}); + } + } + + void + testDeposit() + { + testcase("Deposit"); + + using namespace jtx; + + // Equal deposit: 1000000 tokens. XRP/MPT AMM. + testAMM( + [&](AMM& ammAlice, Env& env) { + XRPAmount const baseFee{env.current()->fees().base}; + auto carolXRP = env.balance(carol, XRP); + auto carolMPT = env.balance(carol, MPT(ammAlice[1])); + + ammAlice.deposit(carol, 1'000'000); + BEAST_EXPECT(ammAlice.expectBalances( + XRP(11'000), MPT(ammAlice[1])(11'000), IOUAmount{11'000'000, 0})); + + // carol deposited 1000 XRP and pays the transaction fee + env.require(balance(carol, carolXRP - XRP(1000) - drops(baseFee))); + // carol deposited 1000 MPT + env.require(balance(carol, carolMPT - MPT(ammAlice[1])(1000))); + }, + {{XRP(10'000), AMMMPT(10'000)}}); + + // single deposit MPT with eprice + testAMM( + [&](AMM& ammAlice, Env& env) { + ammAlice.deposit( + carol, + MPT(ammAlice[1])(100), + std::nullopt, + STAmount{ammAlice.lptIssue(), 1, -1}, + tfLimitLPToken); + + // although we should use lptoken unit for eprice, + // we don't check the currency any more, we just use + // the value + ammAlice.deposit( + carol, + MPT(ammAlice[1])(100), + std::nullopt, + STAmount{USD, 1, -1}, + tfLimitLPToken); + }, + {{USD(10'000'000), AMMMPT(10'000)}}); + + // Equal deposit: 1000000 tokens. IOU/MPT combination + { + auto test = [&](auto&& issue1, auto&& issue2) { + Env env(*this); + env.fund(XRP(30'000), alice, carol, gw); + env.close(); + auto const USD = issue1( + {.env = env, + .token = "USD", + .issuer = gw, + .holders = {alice, carol}, + .limit = 1'000'000}); + auto const BTC = issue2( + {.env = env, + .token = "BTC", + .issuer = gw, + .holders = {alice, carol}, + .limit = 1'000'000}); + env(pay(gw, alice, BTC(50000))); + env(pay(gw, carol, BTC(50000))); + env(pay(gw, alice, USD(50000))); + env(pay(gw, carol, USD(50000))); + env.close(); + + auto ammAlice = AMM(env, alice, USD(10000), BTC(10000)); + auto carolBTC = env.balance(carol, BTC); + auto carolUSD = env.balance(carol, USD); + + ammAlice.deposit(carol, 1'000); + BEAST_EXPECT(ammAlice.expectBalances(BTC(11'000), USD(11'000), IOUAmount(11'000))); + + env.require(balance(carol, carolBTC - BTC(1000))); + env.require(balance(carol, carolUSD - USD(1000))); + }; + testHelper2TokensMix(test); + } + + // Deposit 100MPT/100XRP. XRP/MPT AMM. + testAMM( + [&](AMM& ammAlice, Env& env) { + XRPAmount const baseFee{env.current()->fees().base}; + auto carolXRP = env.balance(carol, XRP); + auto carolMPT = env.balance(carol, MPT(ammAlice[1])); + + ammAlice.deposit(carol, MPT(ammAlice[1])(100), XRP(100)); + BEAST_EXPECT(ammAlice.expectBalances( + XRP(10'100), MPT(ammAlice[1])(10'100), IOUAmount{10'100'000, 0})); + + env.require(balance(carol, carolXRP - XRP(100) - drops(baseFee))); + env.require(balance(carol, carolMPT - MPT(ammAlice[1])(100))); + }, + {{XRP(10'000), AMMMPT(10'000)}}); + + // Deposit MPT/IOU combination + { + auto test = [&](auto&& issue1, auto&& issue2) { + Env env(*this); + env.fund(XRP(30'000), alice, carol, gw); + env.close(); + auto const USD = issue1( + {.env = env, + .token = "USD", + .issuer = gw, + .holders = {alice, carol}, + .limit = 1'000'000}); + auto const BTC = issue2( + {.env = env, + .token = "BTC", + .issuer = gw, + .holders = {alice, carol}, + .limit = 1'000'000}); + env(pay(gw, alice, BTC(50000))); + env(pay(gw, carol, BTC(50000))); + env(pay(gw, alice, USD(50000))); + env(pay(gw, carol, USD(50000))); + env.close(); + + auto ammAlice = AMM(env, alice, USD(10000), BTC(10000)); + auto carolBTC = env.balance(carol, BTC); + auto carolUSD = env.balance(carol, USD); + ammAlice.deposit(carol, BTC(100), USD(100)); + + BEAST_EXPECT(ammAlice.expectBalances(BTC(10'100), USD(10'100), IOUAmount(10'100))); + + env.require(balance(carol, carolBTC - BTC(100))); + env.require(balance(carol, carolUSD - USD(100))); + }; + testHelper2TokensMix(test); + } + + // Equal limit deposit. + // Try to deposit 200MPT/100XRP. Is truncated to 100MPT/100XRP. + testAMM( + [&](AMM& ammAlice, Env& env) { + XRPAmount const baseFee{env.current()->fees().base}; + auto carolXRP = env.balance(carol, XRP); + auto carolMPT = env.balance(carol, MPT(ammAlice[1])); + + ammAlice.deposit(carol, MPT(ammAlice[1])(200), XRP(100)); + BEAST_EXPECT(ammAlice.expectBalances( + XRP(10'100), MPT(ammAlice[1])(10'100), IOUAmount{10'100'000, 0})); + + env.require(balance(carol, carolXRP - XRP(100) - drops(baseFee))); + env.require(balance(carol, carolMPT - MPT(ammAlice[1])(100))); + }, + {{XRP(10'000), AMMMPT(10'000)}}); + + // Equal limit deposit. MPT/IOU combination. + { + auto test = [&](auto&& issue1, auto&& issue2) { + Env env(*this); + env.fund(XRP(30'000), alice, carol, gw); + env.close(); + auto const USD = issue1( + {.env = env, + .token = "USD", + .issuer = gw, + .holders = {alice, carol}, + .limit = 1'000'000}); + auto const BTC = issue2( + {.env = env, + .token = "BTC", + .issuer = gw, + .holders = {alice, carol}, + .limit = 1'000'000}); + env(pay(gw, alice, BTC(50000))); + env(pay(gw, carol, BTC(50000))); + env(pay(gw, alice, USD(50000))); + env(pay(gw, carol, USD(50000))); + env.close(); + + auto ammAlice = AMM(env, alice, USD(10000), BTC(10000)); + auto carolBTC = env.balance(carol, BTC); + auto carolUSD = env.balance(carol, USD); + ammAlice.deposit(carol, BTC(200), USD(100)); + BEAST_EXPECT(ammAlice.expectBalances(BTC(10'100), USD(10'100), IOUAmount(10'100))); + + env.require(balance(carol, carolBTC - BTC(100))); + env.require(balance(carol, carolUSD - USD(100))); + }; + testHelper2TokensMix(test); + } + + // Single deposit: 1000 MPT into MPT/XRP + testAMM( + [&](AMM& ammAlice, Env& env) { + XRPAmount const baseFee{env.current()->fees().base}; + auto carolXRP = env.balance(carol, XRP); + auto carolMPT = env.balance(carol, MPT(ammAlice[1])); + + ammAlice.deposit(carol, MPT(ammAlice[1])(1000)); + BEAST_EXPECT(ammAlice.expectBalances( + XRP(10'000), MPT(ammAlice[1])(11'000), IOUAmount{10'488'088'48170151, -8})); + + env.require(balance(carol, carolXRP - drops(baseFee))); + env.require(balance(carol, carolMPT - MPT(ammAlice[1])(1000))); + }, + {{XRP(10'000), AMMMPT(10'000)}}); + + // Single deposit: 1000 XRP into MPT/XRP + testAMM( + [&](AMM& ammAlice, Env& env) { + XRPAmount const baseFee{env.current()->fees().base}; + auto carolXRP = env.balance(carol, XRP); + auto carolMPT = env.balance(carol, MPT(ammAlice[1])); + + ammAlice.deposit(carol, XRP(1000)); + BEAST_EXPECT(ammAlice.expectBalances( + XRP(11'000), MPT(ammAlice[1])(10'000), IOUAmount{10'488'088'48170151, -8})); + + env.require(balance(carol, carolXRP - XRP(1000) - drops(baseFee))); + env.require(balance(carol, carolMPT)); + }, + {{XRP(10'000), AMMMPT(10'000)}}); + + // Single deposit: 1000 MPT0 into MPT/MPT + testAMM( + [&](AMM& ammAlice, Env& env) { + auto carolMPT0 = env.balance(carol, MPT(ammAlice[0])); + auto carolMPT1 = env.balance(carol, MPT(ammAlice[1])); + + ammAlice.deposit(carol, MPT(ammAlice[0])(1000)); + BEAST_EXPECT(ammAlice.expectBalances( + MPT(ammAlice[0])(11'000), + MPT(ammAlice[1])(10'000), + IOUAmount{10'488'08848170151, -11})); + + env.require(balance(carol, carolMPT0 - MPT(ammAlice[0])(1000))); + env.require(balance(carol, carolMPT1)); + }, + {{AMMMPT(10'000), AMMMPT(10'000)}}); + + // Single deposit: 1000 MPT into MPT/IOU + testAMM( + [&](AMM& ammAlice, Env& env) { + auto carolMPT = env.balance(carol, MPT(ammAlice[0])); + auto carolUSD = env.balance(carol, USD); + + ammAlice.deposit(carol, MPT(ammAlice[0])(1000)); + BEAST_EXPECT(ammAlice.expectBalances( + MPT(ammAlice[0])(11'000), USD(10'000), IOUAmount{10'488'08848170151, -11})); + + env.require(balance(carol, carolMPT - MPT(ammAlice[0])(1000))); + env.require(balance(carol, carolUSD)); + }, + {{AMMMPT(10'000), USD(10'000)}}); + + // Single deposit: 1000 IOU into MPT/IOU + testAMM( + [&](AMM& ammAlice, Env& env) { + auto carolMPT = env.balance(carol, MPT(ammAlice[0])); + auto carolUSD = env.balance(carol, USD); + + ammAlice.deposit(carol, USD(1000)); + BEAST_EXPECT(ammAlice.expectBalances( + MPT(ammAlice[0])(10'000), + STAmount{USD, UINT64_C(10999'99999999999), -11}, + IOUAmount{10'488'08848170151, -11})); + + env.require(balance(carol, carolMPT)); + env.require( + balance(carol, carolUSD - STAmount{USD, UINT64_C(999'99999999999), -11})); + }, + {{AMMMPT(10'000), USD(10'000)}}); + + // Single deposit: 100000 tokens worth of MPT into XRP/MPT + testAMM( + [&](AMM& ammAlice, Env& env) { + XRPAmount const baseFee{env.current()->fees().base}; + auto carolXRP = env.balance(carol, XRP); + auto carolMPT = env.balance(carol, MPT(ammAlice[1])); + + ammAlice.deposit(carol, 100'000, MPT(ammAlice[1])(205)); + BEAST_EXPECT(ammAlice.expectBalances( + XRP(10'000), MPT(ammAlice[1])(10'201), IOUAmount{10'100'000, 0})); + + env.require(balance(carol, carolXRP - drops(baseFee))); + env.require(balance(carol, carolMPT - MPT(ammAlice[1])(201))); + }, + {{XRP(10'000), AMMMPT(10'000)}}); + + // Single deposit: 100000 tokens worth of XRP into XRP/MPT + testAMM( + [&](AMM& ammAlice, Env& env) { + XRPAmount const baseFee{env.current()->fees().base}; + auto carolXRP = env.balance(carol, XRP); + auto carolMPT = env.balance(carol, MPT(ammAlice[1])); + + ammAlice.deposit(carol, 100'000, XRP(205)); + BEAST_EXPECT(ammAlice.expectBalances( + XRP(10'201), MPT(ammAlice[1])(10'000), IOUAmount{10'100'000, 0})); + + env.require(balance(carol, carolXRP - XRP(201) - drops(baseFee))); + env.require(balance(carol, carolMPT)); + }, + {{XRP(10'000), AMMMPT(10'000)}}); + + // Single deposit: 100 tokens worth of MPT/IOU into pool of MPT/IOU + // combination + { + auto test = [&](auto&& issue1, auto&& issue2) { + Env env(*this); + env.fund(XRP(30'000), alice, carol, gw); + env.close(); + auto const USD = issue1( + {.env = env, + .token = "USD", + .issuer = gw, + .holders = {alice, carol}, + .limit = 1'000'000}); + auto const BTC = issue2( + {.env = env, + .token = "BTC", + .issuer = gw, + .holders = {alice, carol}, + .limit = 1'000'000}); + env(pay(gw, alice, BTC(50000))); + env(pay(gw, carol, BTC(50000))); + env(pay(gw, alice, USD(50000))); + env(pay(gw, carol, USD(50000))); + env.close(); + + auto ammAlice = AMM(env, alice, USD(10000), BTC(10000)); + auto carolBTC = env.balance(carol, BTC); + auto carolUSD = env.balance(carol, USD); + + ammAlice.deposit(carol, 100, USD(205)); + auto deltaUSD = [&]() { + if constexpr (std::is_same_v>) + return USD(202); + return USD(201); + }(); + BEAST_EXPECT(ammAlice.expectBalances( + BTC(10'000), USD(10'000) + deltaUSD, IOUAmount{10'100, 0})); + + env.require(balance(carol, carolBTC)); + env.require(balance(carol, carolUSD - deltaUSD)); + }; + testHelper2TokensMix(test); + } + + // Single deposit with EP not exceeding specified: + // 100 MPT with EP not to exceed 0.1 (AssetIn/TokensOut) + // for XRP/MPT + testAMM( + [&](AMM& ammAlice, Env& env) { + ammAlice.deposit( + carol, + MPT(ammAlice[1])(100), + std::nullopt, + STAmount{ammAlice.lptIssue(), 1, -1}); + + BEAST_EXPECT(ammAlice.expectBalances( + XRP(10'000), MPT(ammAlice[1])(10100), IOUAmount{10'049'875'62112089, -8})); + }, + {{XRP(10'000), AMMMPT(10'000)}}); + + // Single deposit with EP not exceeding specified: + // 100 MPT with EP not to exceed 0.002004 (AssetIn/TokensOut) + testAMM( + [&](AMM& ammAlice, Env& env) { + XRPAmount const baseFee{env.current()->fees().base}; + auto carolXRP = env.balance(carol, XRP); + auto carolMPT = env.balance(carol, MPT(ammAlice[1])); + + ammAlice.deposit( + carol, + MPT(ammAlice[1])(100), + std::nullopt, + STAmount{ammAlice.lptIssue(), 2004, -6}); + + BEAST_EXPECT(ammAlice.expectBalances( + XRP(10'000), MPT(ammAlice[1])(10'081), IOUAmount{10'039'920'31840891, -8})); + + env.require(balance(carol, carolXRP - drops(baseFee))); + env.require(balance(carol, carolMPT - MPT(ammAlice[1])(81))); + }, + {{XRP(10'000), AMMMPT(10'000)}}); + + // Single deposit with EP not exceeding specified: + // 0 MPT with EP not to exceed 0.002004 (AssetIn/TokensOut) + testAMM( + [&](AMM& ammAlice, Env& env) { + XRPAmount const baseFee{env.current()->fees().base}; + auto carolXRP = env.balance(carol, XRP); + auto carolMPT = env.balance(carol, MPT(ammAlice[1])); + + ammAlice.deposit( + carol, + MPT(ammAlice[1])(0), + std::nullopt, + STAmount{ammAlice.lptIssue(), 2004, -6}); + + BEAST_EXPECT(ammAlice.expectBalances( + XRP(10'000), MPT(ammAlice[1])(10'081), IOUAmount{10'039'920'31840891, -8})); + + env.require(balance(carol, carolXRP - drops(baseFee))); + env.require(balance(carol, carolMPT - MPT(ammAlice[1])(81))); + }, + {{XRP(10'000), AMMMPT(10'000)}}); + + // Single deposit with EP not exceeding specified: + // 100 MPT with EP not to exceed 0.1 (AssetIn/TokensOut) + // for IOU/MPT + testAMM( + [&](AMM& ammAlice, Env& env) { + ammAlice.deposit( + carol, + MPT(ammAlice[1])(100), + std::nullopt, + STAmount{ammAlice.lptIssue(), 1, -1}); + + BEAST_EXPECT(ammAlice.expectBalances( + USD(10'000'000'000), + MPT(ammAlice[1])(10100), + IOUAmount{10'049'875'62112089, -8})); + }, + {{USD(10'000'000'000), AMMMPT(10'000)}}); + + // Single deposit with EP not exceeding specified: + // 100 IOU with EP not to exceed 0.1 (AssetIn/TokensOut) + // for IOU/MPT + testAMM( + [&](AMM& ammAlice, Env& env) { + ammAlice.deposit(carol, USD(100), std::nullopt, STAmount{USD, 1, -1}); + + BEAST_EXPECT(ammAlice.expectBalances( + MPT(ammAlice[1])(10'000'000'000), + USD(10100), + IOUAmount{10'049'875'62112089, -8})); + }, + {{USD(10'000), AMMMPT(10'000'000'000)}}); + + // Single deposit with EP not exceeding specified: + // 100 IOU with EP not to exceed 0.1 (AssetIn/TokensOut) + // for MPT/MPT + testAMM( + [&](AMM& ammAlice, Env& env) { + ammAlice.deposit( + carol, + MPT(ammAlice[0])(100), + std::nullopt, + STAmount{ammAlice.lptIssue(), 1, -1}); + + BEAST_EXPECT(ammAlice.expectBalances( + MPT(ammAlice[1])(10'000'000'000), + MPT(ammAlice[0])(10100), + IOUAmount{10'049'875'62112089, -8})); + }, + {{AMMMPT(10'000), AMMMPT(10'000'000'000)}}); + + // MPT/MPT with transfer fee + { + Env env(*this); + env.fund(XRP(30'000), gw, alice, carol); + env.close(); + + MPT const BTC = MPTTester( + {.env = env, + .issuer = gw, + .holders = {alice, carol}, + .transferFee = 25'000, + .pay = 400'000}); + + MPT const USD = MPTTester( + {.env = env, + .issuer = gw, + .holders = {alice, carol}, + .transferFee = 25'000, + .pay = 400'000}); + + AMM ammAlice(env, alice, USD(200'000), BTC(5)); + BEAST_EXPECT(ammAlice.expectBalances(USD(200'000), BTC(5), IOUAmount{1000, 0})); + + ammAlice.deposit(carol, 100, std::nullopt, std::nullopt); + BEAST_EXPECT(ammAlice.expectBalances(USD(220'000), BTC(6), IOUAmount{1100, 0})); + } + + // IOU/MPT with transfer fee + { + Env env(*this); + env.fund(XRP(30'000), gw, alice, bob, carol); + env.close(); + + env(rate(gw, 1.25)); + env.close(); + + MPT const BTC = MPTTester( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol}, + .transferFee = 25'000, + .pay = 400'000}); + + auto const USD = gw["USD"]; + env.trust(USD(1000000), alice); + env(pay(gw, alice, USD(1000000))); + env.trust(USD(1000000), bob); + env(pay(gw, bob, USD(1000000))); + env.trust(USD(1000000), carol); + env(pay(gw, carol, USD(1000000))); + env.close(); + + // IOU/MPT + AMM ammAlice(env, alice, USD(200'000), BTC(5)); + BEAST_EXPECT(ammAlice.expectBalances(USD(200'000), BTC(5), IOUAmount{1000, 0})); + ammAlice.deposit(carol, 100, std::nullopt, std::nullopt); + BEAST_EXPECT(ammAlice.expectBalances(USD(220'000), BTC(6), IOUAmount{1100, 0})); + + MPT const ETH = MPTTester( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol}, + .transferFee = 25'000, + .pay = 400'000}); + + // MPT/IOU + AMM ammBob(env, bob, ETH(20'000), USD(0.5)); + BEAST_EXPECT(ammBob.expectBalances(ETH(20'000), USD(0.5), IOUAmount{100, 0})); + ammBob.deposit(carol, 10); + BEAST_EXPECT(ammBob.expectBalances(ETH(22'000), USD(0.55), IOUAmount{110, 0})); + } + + // Tiny deposits for IOU/MPT + testAMM( + [&](AMM& ammAlice, Env& env) { + // tiny amount causes MPT to deposit rounded to 0 + ammAlice.deposit(carol, IOUAmount{1, -3}, std::nullopt, std::nullopt); + BEAST_EXPECT(ammAlice.expectBalances( + STAmount{USD, UINT64_C(10'000'001), -3}, + MPT(ammAlice[1])(10'001), + IOUAmount{10'000'001, -3})); + + ammAlice.deposit(carol, IOUAmount{1}); + BEAST_EXPECT(ammAlice.expectBalances( + STAmount{USD, UINT64_C(10'001'001), -3}, + MPT(ammAlice[1])(10'003), + IOUAmount{10'001'001, -3})); + }, + {{USD(10'000), AMMMPT(10'000)}}); + testAMM( + [&](AMM& ammAlice, Env& env) { + ammAlice.deposit(carol, STAmount{USD, 1, -10}); + BEAST_EXPECT(ammAlice.expectBalances( + STAmount{USD, UINT64_C(10'000'00000000008), -11}, + MPT(ammAlice[1])(10'000), + IOUAmount{1'000'000'000000004, -11})); + + ammAlice.deposit(carol, MPT(ammAlice[1])(1)); + BEAST_EXPECT(ammAlice.expectBalances( + STAmount{USD, UINT64_C(10'000'00000000008), -11}, + MPT(ammAlice[1])(10'001), + IOUAmount{10'000'49998750066, -11})); + }, + {{USD(10'000), AMMMPT(10'000)}}); + + // Tiny deposits for XRP/MPT + testAMM( + [&](AMM& ammAlice, Env& env) { + ammAlice.deposit(carol, XRPAmount{1}); + BEAST_EXPECT(ammAlice.expectBalances( + XRPAmount{10'000'000'001}, + MPT(ammAlice[1])(10'000), + IOUAmount{1'000'000'000049999, -8})); + }, + {{XRP(10'000), AMMMPT(10'000)}}); + testAMM( + [&](AMM& ammAlice, Env& env) { + ammAlice.deposit(carol, MPT(ammAlice[1])(1)); + BEAST_EXPECT(ammAlice.expectBalances( + XRP(10'000), MPT(ammAlice[1])(10'001), IOUAmount{10'000'499'98750062, -8})); + }, + {{XRP(10'000), AMMMPT(10'000)}}); + + // Tiny deposits for MPT/MPT + testAMM( + [&](AMM& ammAlice, Env& env) { + ammAlice.deposit(carol, MPT(ammAlice[1])(1)); + + BEAST_EXPECT(ammAlice.expectBalances( + MPT(ammAlice[0])(10'000), + MPT(ammAlice[1])(10'001), + IOUAmount{1'000'049'998750062, -11})); + }, + {{AMMMPT(10'000), AMMMPT(10'000)}}); + + // MPT Issuer create/deposit + { + Env env(*this); + env.fund(XRP(30'000), gw); + env.close(); + + MPT const BTC = MPTTester({.env = env, .issuer = gw, .holders = {}}); + + AMM ammGw(env, gw, XRP(10'000), BTC(10'000'000'000)); + BEAST_EXPECT( + ammGw.expectBalances(XRP(10'000), BTC(10'000'000'000), IOUAmount{10'000'000'000})); + + ammGw.deposit(gw, 1'000'000); + BEAST_EXPECT( + ammGw.expectBalances(XRP(10'001), BTC(10'001000000), IOUAmount{10'001000000})); + + ammGw.deposit(gw, BTC(1'000000000)); + BEAST_EXPECT(ammGw.expectBalances( + XRP(10'001), BTC(11'001000000), IOUAmount{1048'908'961731188, -5})); + } + + // Issuer deposit in MPT/MPT pool + testAMM( + [&](AMM& ammAlice, Env& env) { + ammAlice.deposit(gw, 1'000'000); + BEAST_EXPECT(ammAlice.expectBalances( + MPT(ammAlice[0])(1'010'000), + MPT(ammAlice[1])(1'010'000), + IOUAmount{1'010'000})); + + ammAlice.deposit(gw, MPT(ammAlice[0])(1000)); + BEAST_EXPECT(ammAlice.expectBalances( + MPT(ammAlice[0])(1'010'999), + MPT(ammAlice[1])(1'010'000), + IOUAmount{1'010'499'376546071, -9})); + }, + {{AMMMPT(10'000), AMMMPT(10'000)}}); + + // Issuer deposit in MPT/XRP pool + testAMM( + [&](AMM& ammAlice, Env& env) { + ammAlice.deposit(gw, 1'000'000); + BEAST_EXPECT(ammAlice.expectBalances( + XRP(11'000), MPT(ammAlice[1])(11'000), IOUAmount{11'000'000})); + ammAlice.deposit(gw, MPT(ammAlice[1])(1'000)); + BEAST_EXPECT(ammAlice.expectBalances( + XRP(11'000), MPT(ammAlice[1])(12'000), IOUAmount{11'489'125'29307605, -8})); + }, + {{XRP(10'000), AMMMPT(10'000)}}); + + // Equal deposit by tokens MPT/XRP + testAMM( + [&](AMM& ammAlice, Env& env) { + ammAlice.deposit( + carol, + 1'000'000, + XRP(1'000), + MPT(ammAlice[1])(1'000), + std::nullopt, + tfLPToken, + std::nullopt, + std::nullopt); + BEAST_EXPECT(ammAlice.expectBalances( + XRP(11'000), MPT(ammAlice[1])(11'000), IOUAmount{11'000'000, 0})); + }, + {{XRP(10'000), AMMMPT(10'000)}}); + + // Equal deposit by tokens MPT/IOU combination + { + auto test = [&](auto&& issue1, auto&& issue2) { + Env env(*this); + env.fund(XRP(30'000), alice, carol, gw); + env.close(); + auto const USD = issue1( + {.env = env, + .token = "USD", + .issuer = gw, + .holders = {alice, carol}, + .limit = 1'000'000}); + auto const BTC = issue2( + {.env = env, + .token = "BTC", + .issuer = gw, + .holders = {alice, carol}, + .limit = 1'000'000}); + env(pay(gw, alice, BTC(50000))); + env(pay(gw, carol, BTC(50000))); + env(pay(gw, alice, USD(50000))); + env(pay(gw, carol, USD(50000))); + env.close(); + + auto ammAlice = AMM(env, alice, USD(10000), BTC(10000)); + auto carolBTC = env.balance(carol, BTC); + auto carolUSD = env.balance(carol, USD); + + ammAlice.deposit( + carol, + 1'000, + USD(1'000), + BTC(1'000), + std::nullopt, + tfLPToken, + std::nullopt, + std::nullopt); + BEAST_EXPECT(ammAlice.expectBalances(USD(11'000), BTC(11'000), IOUAmount{11'000})); + + env.require(balance(carol, carolBTC - BTC(1000))); + env.require(balance(carol, carolUSD - USD(1000))); + }; + testHelper2TokensMix(test); + } + + // Equal deposit by asset XRP/MPT + testAMM( + [&](AMM& ammAlice, Env& env) { + ammAlice.deposit( + carol, + 1'000'000, + XRP(1'000), + MPT(ammAlice[1])(1'000), + std::nullopt, + tfTwoAsset, + std::nullopt, + std::nullopt); + BEAST_EXPECT(ammAlice.expectBalances( + XRP(11'000), MPT(ammAlice[1])(11'000), IOUAmount{11'000'000, 0})); + }, + {{XRP(10'000), AMMMPT(10'000)}}); + + // Equal deposit by asset IOU/MPT combination + { + auto test = [&](auto&& issue1, auto&& issue2) { + Env env(*this); + env.fund(XRP(30'000), alice, carol, gw); + env.close(); + auto const USD = issue1( + {.env = env, + .token = "USD", + .issuer = gw, + .holders = {alice, carol}, + .limit = 1'000'000}); + auto const BTC = issue2( + {.env = env, + .token = "BTC", + .issuer = gw, + .holders = {alice, carol}, + .limit = 1'000'000}); + env(pay(gw, alice, BTC(50000))); + env(pay(gw, carol, BTC(50000))); + env(pay(gw, alice, USD(50000))); + env(pay(gw, carol, USD(50000))); + env.close(); + auto ammAlice = AMM(env, alice, USD(10000), BTC(10000)); + auto carolBTC = env.balance(carol, BTC); + auto carolUSD = env.balance(carol, USD); + + ammAlice.deposit( + carol, + 1'000, + USD(1'000), + BTC(1'000), + std::nullopt, + tfTwoAsset, + std::nullopt, + std::nullopt); + BEAST_EXPECT( + ammAlice.expectBalances(USD(11'000), BTC(11'000), IOUAmount{11'000, 0})); + + env.require(balance(carol, carolBTC - BTC(1000))); + env.require(balance(carol, carolUSD - USD(1000))); + }; + testHelper2TokensMix(test); + } + + // Single deposit XRP by asset MPT/XRP + testAMM( + [&](AMM& ammAlice, Env& env) { + ammAlice.deposit( + carol, + 488'088, + XRP(1'000), + std::nullopt, + std::nullopt, + tfSingleAsset, + std::nullopt, + std::nullopt); + BEAST_EXPECT(ammAlice.expectBalances( + XRP(11'000), MPT(ammAlice[1])(10'000), IOUAmount{10'488'088'48170151, -8})); + }, + {{XRP(10'000), AMMMPT(10'000)}}); + + // Single deposit MPT by asset MPT/XRP + testAMM( + [&](AMM& ammAlice, Env& env) { + ammAlice.deposit( + carol, + 488'088, + MPT(ammAlice[1])(1'000), + std::nullopt, + std::nullopt, + tfSingleAsset, + std::nullopt, + std::nullopt); + BEAST_EXPECT(ammAlice.expectBalances( + XRP(10'000), MPT(ammAlice[1])(11'000), IOUAmount{10'488'088'48170151, -8})); + }, + {{XRP(10'000), AMMMPT(10'000)}}); + + // Single deposit IOU by asset MPT/IOU + testAMM( + [&](AMM& ammAlice, Env& env) { + ammAlice.deposit( + carol, + 488, + USD(1'000), + std::nullopt, + std::nullopt, + tfSingleAsset, + std::nullopt, + std::nullopt); + BEAST_EXPECT(ammAlice.expectBalances( + STAmount{USD, UINT64_C(10'999'99999999999), -11}, + MPT(ammAlice[1])(10'000), + IOUAmount{10'488'08848170151, -11})); + }, + {{USD(10'000), AMMMPT(10'000)}}); + + // Single deposit MPT by asset MPT/IOU + testAMM( + [&](AMM& ammAlice, Env& env) { + ammAlice.deposit( + carol, + 488, + MPT(ammAlice[1])(1'000), + std::nullopt, + std::nullopt, + tfSingleAsset, + std::nullopt, + std::nullopt); + BEAST_EXPECT(ammAlice.expectBalances( + USD(10'000), MPT(ammAlice[1])(11'000), IOUAmount{10'488'088'48170151, -11})); + }, + {{USD(10'000), AMMMPT(10'000)}}); + + // Single deposit MPT by asset MPT/MPT + testAMM( + [&](AMM& ammAlice, Env& env) { + ammAlice.deposit( + carol, + 488, + MPT(ammAlice[1])(1'000), + std::nullopt, + std::nullopt, + tfSingleAsset, + std::nullopt, + std::nullopt); + BEAST_EXPECT(ammAlice.expectBalances( + MPT(ammAlice[0])(10'000), + MPT(ammAlice[1])(11'000), + IOUAmount{10'488'088'48170151, -11})); + }, + {{AMMMPT(10'000), AMMMPT(10'000)}}); + } + + void + testInvalidWithdraw() + { + testcase("Invalid AMMWithdraw"); + + using namespace jtx; + auto const all = testable_amendments(); + + testAMM( + [&](AMM& ammAlice, Env& env) { + WithdrawArg const args{ + .asset1Out = XRP(100), + .err = ter(tecAMM_BALANCE), + }; + ammAlice.withdraw(args); + }, + {{XRP(99), AMMMPT(99)}}); + + testAMM( + [&](AMM& ammAlice, Env& env) { + WithdrawArg const args{ + .asset1Out = MPT(ammAlice[1])(100), + .err = ter(tecAMM_BALANCE), + }; + ammAlice.withdraw(args); + }, + {{XRP(99), AMMMPT(99)}}); + + { + Env env{*this}; + env.fund(XRP(30'000), gw, alice, bob); + env.close(); + // alice is authorized to hold gw MPT, bob is not authorized + MPTTester const BTC( + {.env = env, + .issuer = gw, + .holders = {alice}, + .pay = 30'000, + .flags = tfMPTRequireAuth | MPTDEXFlags, + .authHolder = true}); + AMM ammAlice(env, alice, XRP(10'000), BTC(10'000)); + WithdrawArg const args{ + .account = bob, + .asset1Out = BTC(100), + .err = ter(tecNO_AUTH), + }; + ammAlice.withdraw(args); + } + + testAMM( + [&](AMM& ammAlice, Env& env) { + MPTTester const BTC( + {.env = env, + .issuer = gw, + .holders = {alice, carol}, + .pay = 2'000, + .flags = tfMPTCanLock | MPTDEXFlags}); + + // Invalid tokens + ammAlice.withdraw(alice, 0, std::nullopt, std::nullopt, ter(temBAD_AMM_TOKENS)); + ammAlice.withdraw( + alice, IOUAmount{-1}, std::nullopt, std::nullopt, ter(temBAD_AMM_TOKENS)); + + // Mismatched token, invalid Asset1Out issue + ammAlice.withdraw( + alice, BTC(100), std::nullopt, std::nullopt, ter(temBAD_AMM_TOKENS)); + ammAlice.withdraw( + alice, MPT(badMPT())(100), std::nullopt, std::nullopt, ter(temBAD_MPT)); + + // Mismatched token, invalid Asset2Out issue + ammAlice.withdraw(alice, XRP(100), BTC(100), std::nullopt, ter(temBAD_AMM_TOKENS)); + + // Mismatched token, Asset1Out.issue == Asset2Out.issue + ammAlice.withdraw( + alice, + MPT(ammAlice[1])(100), + MPT(ammAlice[1])(100), + std::nullopt, + ter(temBAD_AMM_TOKENS)); + + // Invalid amount value + ammAlice.withdraw( + alice, MPT(ammAlice[1])(0), std::nullopt, std::nullopt, ter(temBAD_AMOUNT)); + ammAlice.withdraw( + alice, MPT(ammAlice[1])(-100), std::nullopt, std::nullopt, ter(temBAD_AMOUNT)); + ammAlice.withdraw( + alice, MPT(ammAlice[1])(10), std::nullopt, IOUAmount{-1}, ter(temBAD_AMOUNT)); + + // Invalid amount/token value, withdraw all tokens from one side + // of the pool. + ammAlice.withdraw( + alice, + MPT(ammAlice[1])(10'000), + std::nullopt, + std::nullopt, + ter(tecAMM_BALANCE)); + ammAlice.withdraw( + alice, XRP(10'000), std::nullopt, std::nullopt, ter(tecAMM_BALANCE)); + ammAlice.withdraw( + alice, + std::nullopt, + MPT(ammAlice[1])(0), + std::nullopt, + std::nullopt, + tfOneAssetWithdrawAll, + std::nullopt, + std::nullopt, + ter(tecAMM_BALANCE)); + + // Bad MPT + ammAlice.withdraw( + alice, XRP(100), MPT(badMPT())(100), std::nullopt, ter(temBAD_MPT)); + + // Specified MPToken doesn't match the pool assets + ammAlice.withdraw( + alice, XRP(100), MPT(noMPT())(100), std::nullopt, ter(temBAD_AMM_TOKENS)); + + // Invalid Account + Account bad("bad"); + env.memoize(bad); + ammAlice.withdraw( + bad, + 1'000'000, + std::nullopt, + std::nullopt, + std::nullopt, + std::nullopt, + std::nullopt, + seq(1), + ter(terNO_ACCOUNT)); + + // Invalid AMM + ammAlice.withdraw( + alice, + 1'000, + std::nullopt, + std::nullopt, + std::nullopt, + std::nullopt, + {{MPT(ammAlice[1]), GBP}}, + std::nullopt, + ter(terNO_AMM)); + + // Carol is not a Liquidity Provider + ammAlice.withdraw(carol, 10'000, std::nullopt, std::nullopt, ter(tecAMM_BALANCE)); + }, + {{XRP(10'000), AMMMPT(10'000)}}); + + testAMM( + [&](AMM& ammAlice, Env& env) { + // Withdraw entire one side of the pool. + // Pre-fixAMMv1_3: + // Equal withdraw but due to MPT precision limit, + // this results in full withdraw of MPT pool only, + // while leaving a tiny amount in USD pool. + // Post-fixAMMv1_3: + // Most of the pool is withdrawn with remaining tiny amounts + auto err = env.enabled(fixAMMv1_3) ? ter(tesSUCCESS) : ter(tecAMM_BALANCE); + ammAlice.withdraw( + alice, IOUAmount{9'999'999'9999, -4}, std::nullopt, std::nullopt, err); + if (env.enabled(fixAMMv1_3)) + { + BEAST_EXPECT(ammAlice.expectBalances( + MPT(ammAlice[0])(1), STAmount{USD, 1, -7}, IOUAmount{1, -4})); + } + }, + {{AMMMPT(10'000'000'000), USD(10'000)}}, + 0, + std::nullopt, + {all, all - fixAMMv1_3}); + + testAMM( + [&](AMM& ammAlice, Env& env) { + // Similar to above with even smaller remaining amount + // Pre-fixAMMv1_3: results in full withdraw of MPT pool only, + // returning tecAMM_BALANCE. Post-fixAMMv1_3: most of the pool + // is withdrawn with remaining tiny amounts + auto err = env.enabled(fixAMMv1_3) ? ter(tesSUCCESS) : ter(tecAMM_BALANCE); + ammAlice.withdraw( + alice, IOUAmount{9'999'999'999999999, -9}, std::nullopt, std::nullopt, err); + if (env.enabled(fixAMMv1_3)) + { + BEAST_EXPECT(ammAlice.expectBalances( + MPT(ammAlice[0])(1), STAmount{USD, 1, -11}, IOUAmount{1, -8})); + } + }, + {{AMMMPT(10'000'000'000), USD(10'000)}}, + 0, + std::nullopt, + {all, all - fixAMMv1_3}); + + // Invalid AMM + testAMM( + [&](AMM& ammAlice, Env& env) { + ammAlice.withdrawAll(alice); + ammAlice.withdraw(alice, 10'000, std::nullopt, std::nullopt, ter(terNO_AMM)); + }, + {{XRP(10'000), AMMMPT(10'000)}}); + + // MPTokenIssuance object doesn't exist + { + Env env{*this}; + env.fund(XRP(30'000), gw, alice); + env.close(); + MPT const BTC = + MPTTester({.env = env, .issuer = gw, .holders = {alice}, .pay = 30'000}); + + AMM ammAlice(env, alice, XRP(10'000), BTC(10'000)); + + ammAlice.withdraw( + WithdrawArg{ + .account = alice, + .asset1Out = MPT(gw, 1'000)(10), + .assets = {{XRP, MPT(gw, 1'000)}}, + .err = ter(terNO_AMM)}); + } + + // MPTRequireAuth flag is set and the account is not authorized + { + Env env{*this}; + env.fund(XRP(30'000), gw, alice); + env.close(); + auto BTCM = MPTTester( + {.env = env, + .issuer = gw, + .holders = {alice}, + .pay = 30'000, + .flags = tfMPTRequireAuth | MPTDEXFlags, + .authHolder = true}); + MPT const BTC = BTCM; + + AMM amm(env, alice, XRP(10'000), BTC(10'000)); + + BTCM.authorize({.account = gw, .holder = alice, .flags = tfMPTUnauthorize}); + + amm.withdraw( + WithdrawArg{ + .account = alice, + .asset1Out = BTC(100), + .assets = {{XRP, BTC}}, + .err = ter(tecNO_AUTH)}); + } + + // MPTCanTransfer is not set and the account is not the issuer of MPT + { + Env env{*this}; + env.fund(XRP(30'000), gw, alice); + env.close(); + auto BTCM = MPTTester( + {.env = env, + .issuer = gw, + .holders = {alice}, + .pay = 30'000, + .flags = tfMPTCanTrade, + .authHolder = true}); + MPT const BTC = BTCM; + + AMM amm(env, gw, XRP(10'000), BTC(10'000)); + + amm.withdraw( + WithdrawArg{ + .account = alice, + .asset1Out = BTC(100), + .assets = {{XRP, BTC}}, + .err = ter(tecNO_PERMISSION)}); + } + + // Globally locked MPT + // MPTLocked flag is set and the account is not the issuer of MPT + { + Env env{*this}; + env.fund(XRP(30'000), gw, alice); + env.close(); + MPTTester BTC( + {.env = env, + .issuer = gw, + .holders = {alice}, + .pay = 30'000, + .flags = tfMPTCanLock | MPTDEXFlags, + .authHolder = true}); + + AMM ammAlice(env, alice, XRP(10'000), BTC(10'000)); + BTC.set({.flags = tfMPTLock}); + + ammAlice.withdraw( + alice, MPT(ammAlice[1])(100), std::nullopt, std::nullopt, ter(tecFROZEN)); + ammAlice.withdraw(alice, 1'000, std::nullopt, std::nullopt, ter(tecFROZEN)); + + // can single withdraw the other asset + ammAlice.withdraw({.account = alice, .asset1Out = XRP(100)}); + } + + // Individually frozen (AMM) account with MPT/MPT AMM + { + Env env{*this}; + env.fund(XRP(10'000), gw, alice); + env.close(); + MPTTester BTC( + {.env = env, + .issuer = gw, + .holders = {alice}, + .pay = 30'000, + .flags = tfMPTCanLock | MPTDEXFlags}); + MPTTester const USD( + {.env = env, + .issuer = gw, + .holders = {alice}, + .pay = 40'000, + .flags = tfMPTCanLock | MPTDEXFlags}); + + AMM ammAlice(env, alice, USD(10'000), BTC(10'000)); + + // Alice's BTC is locked + BTC.set({.holder = alice, .flags = tfMPTLock}); + ammAlice.withdraw(alice, 1000, std::nullopt, std::nullopt, ter(tecFROZEN)); + ammAlice.withdraw(alice, BTC(100), std::nullopt, std::nullopt, ter(tecFROZEN)); + + // can withdraw the other asset + ammAlice.withdraw(alice, USD(100), std::nullopt, std::nullopt); + + // Unlock and then alice can withdraw + BTC.set({.holder = alice, .flags = tfMPTUnlock}); + ammAlice.withdraw(alice, 1000, std::nullopt, std::nullopt); + ammAlice.withdraw(alice, BTC(100), std::nullopt, std::nullopt); + ammAlice.withdraw(alice, USD(100), std::nullopt, std::nullopt); + } + + // Individually lock MPT or freeze IOU (AMM) + { + Env env{*this}; + fund(env, gw, {alice}, {USD(20'000)}, Fund::All); + MPTTester BTC( + {.env = env, + .issuer = gw, + .holders = {alice}, + .pay = 30'000, + .flags = tfMPTCanLock | MPTDEXFlags}); + + AMM ammAlice(env, alice, USD(10'000), BTC(10'000)); + + // Alice's BTC is locked + BTC.set({.holder = alice, .flags = tfMPTLock}); + + ammAlice.withdraw(alice, 1'000, std::nullopt, std::nullopt, ter(tecFROZEN)); + ammAlice.withdraw(alice, BTC(100), std::nullopt, std::nullopt, ter(tecFROZEN)); + // can still single withdraw the unlocked other asset + ammAlice.withdraw(alice, USD(100), std::nullopt, std::nullopt); + + // Unlock alice's BTC + BTC.set({.holder = alice, .flags = tfMPTUnlock}); + + // Now alice can withdraw + ammAlice.withdraw(alice, USD(100), std::nullopt, std::nullopt); + ammAlice.withdraw(alice, 1'000, std::nullopt, std::nullopt); + ammAlice.withdraw(alice, BTC(100), std::nullopt, std::nullopt); + + // Individually lock MPT BTC (AMM) account + BTC.set({.holder = ammAlice.ammAccount(), .flags = tfMPTLock}); + + // Can withdraw non-frozen token USD + ammAlice.withdraw(alice, USD(100), std::nullopt, std::nullopt); + + // Can not withdraw locked token BTC + ammAlice.withdraw(alice, 1'000, std::nullopt, std::nullopt, ter(tecFROZEN)); + ammAlice.withdraw(alice, BTC(100), std::nullopt, std::nullopt, ter(tecFROZEN)); + + // Unlock AMM MPT + BTC.set({.holder = ammAlice.ammAccount(), .flags = tfMPTUnlock}); + // Can withdraw + ammAlice.withdraw(alice, 1'000, std::nullopt, std::nullopt); + ammAlice.withdraw(alice, BTC(100), std::nullopt, std::nullopt); + + // Individually frozen AMM + env(trust( + gw, STAmount{Issue{gw["USD"].currency, ammAlice.ammAccount()}, 0}, tfSetFreeze)); + env.close(); + + // Can withdraw non-locked token BTC + ammAlice.withdraw(alice, BTC(100), std::nullopt, std::nullopt); + + // Can not withdraw frozen token USD + ammAlice.withdraw(alice, 1'000, std::nullopt, std::nullopt, ter(tecFROZEN)); + ammAlice.withdraw(alice, USD(100), std::nullopt, std::nullopt, ter(tecFROZEN)); + + // Unfreeze + env(trust( + gw, STAmount{Issue{gw["USD"].currency, ammAlice.ammAccount()}, 0}, tfClearFreeze)); + env.close(); + + // Can withdraw + ammAlice.withdraw(alice, 1'000, std::nullopt, std::nullopt); + ammAlice.withdraw(alice, USD(100), std::nullopt, std::nullopt); + } + + // Carol withdraws more than she owns + testAMM( + [&](AMM& ammAlice, Env&) { + // Single deposit of 100000 worth of tokens + // which is 10% of the pool. Carol is LP now. + ammAlice.deposit(carol, 1'000'000); + BEAST_EXPECT(ammAlice.expectBalances( + XRP(11'000), MPT(ammAlice[1])(11'000), IOUAmount{11'000'000, 0})); + + ammAlice.withdraw( + carol, 2'000'000, std::nullopt, std::nullopt, ter(tecAMM_INVALID_TOKENS)); + BEAST_EXPECT(ammAlice.expectBalances( + XRP(11'000), MPT(ammAlice[1])(11'000), IOUAmount{11'000'000, 0})); + }, + {{XRP(10'000), AMMMPT(10'000)}}); + + // Withdraw with EPrice limit. Fails to withdraw, calculated tokens + // to withdraw are 0. + testAMM( + [&](AMM& ammAlice, Env& env) { + ammAlice.deposit(carol, 1'000'000); + auto const err = + env.enabled(fixAMMv1_3) ? ter(tecAMM_INVALID_TOKENS) : ter(tecAMM_FAILED); + ammAlice.withdraw( + carol, MPT(ammAlice[1])(100), std::nullopt, IOUAmount{500, 0}, err); + }, + {{XRP(10'000), AMMMPT(10'000)}}, + 0, + std::nullopt, + {all, all - fixAMMv1_3}); + + // Withdraw with EPrice limit. Fails to withdraw, calculated tokens + // to withdraw are greater than the LP shares. + testAMM( + [&](AMM& ammAlice, Env&) { + ammAlice.deposit(carol, 1'000'000); + ammAlice.withdraw( + carol, + MPT(ammAlice[1])(100), + std::nullopt, + IOUAmount{600, 0}, + ter(tecAMM_INVALID_TOKENS)); + }, + {{XRP(10'000), AMMMPT(10'000)}}); + + // Withdraw with EPrice limit. Fails to withdraw, amount1 + // to withdraw is less than 1700 MPT. + testAMM( + [&](AMM& ammAlice, Env&) { + ammAlice.deposit(carol, 1'000'000); + ammAlice.withdraw( + carol, + MPT(ammAlice[1])(1'700), + std::nullopt, + IOUAmount{520, 0}, + ter(tecAMM_FAILED)); + }, + {{XRP(10'000), AMMMPT(10'000)}}); + + // Deposit/Withdraw the same amount with the trading fee + testAMM( + [&](AMM& ammAlice, Env&) { + ammAlice.deposit(carol, MPT(ammAlice[1])(1'000)); + ammAlice.withdraw( + carol, + MPT(ammAlice[1])(1'000), + std::nullopt, + std::nullopt, + ter(tecAMM_INVALID_TOKENS)); + }, + {{XRP(10'000), AMMMPT(10'000)}}, + 1'000); + testAMM( + [&](AMM& ammAlice, Env&) { + ammAlice.deposit(carol, XRP(1'000)); + ammAlice.withdraw( + carol, XRP(1'000), std::nullopt, std::nullopt, ter(tecAMM_INVALID_TOKENS)); + }, + {{XRP(10'000), AMMMPT(10'000)}}, + 1'000); + + // Deposit/Withdraw the same amount fails due to the tokens adjustment + testAMM( + [&](AMM& ammAlice, Env&) { + ammAlice.deposit(carol, STAmount{USD, 1, -6}); + ammAlice.withdraw( + carol, + STAmount{USD, 1, -6}, + std::nullopt, + std::nullopt, + ter(tecAMM_INVALID_TOKENS)); + }, + {{AMMMPT(10'000'000'000), USD(10'000)}}); + + // Withdraw close to one side of the pool. Account's LP tokens + // are rounded to all LP tokens. + testAMM( + [&](AMM& ammAlice, Env&) { + ammAlice.withdraw( + alice, + STAmount{MPT(ammAlice[1]), UINT64_C(9'999'999999999999), -12}, + std::nullopt, + std::nullopt, + ter(tecAMM_BALANCE)); + }, + {{XRP(10'000), AMMMPT(10'000)}}); + + // Tiny withdraw + testAMM( + [&](AMM& ammAlice, Env&) { + // XRP amount to withdraw is 0 + ammAlice.withdraw( + alice, IOUAmount{1, -5}, std::nullopt, std::nullopt, ter(tecAMM_FAILED)); + // Calculated tokens to withdraw are 0 + ammAlice.withdraw( + alice, + std::nullopt, + STAmount{USD, 1, -11}, + std::nullopt, + ter(tecAMM_INVALID_TOKENS)); + ammAlice.deposit(carol, STAmount{USD, 1, -10}); + ammAlice.withdraw( + carol, + std::nullopt, + STAmount{USD, 1, -9}, + std::nullopt, + ter(tecAMM_INVALID_TOKENS)); + ammAlice.withdraw( + carol, + std::nullopt, + MPT(ammAlice[0])(1), + std::nullopt, + ter(tecAMM_INVALID_TOKENS)); + }, + {{AMMMPT(10'000'000'000), USD(10'000)}}); + } + + void + testWithdraw() + { + testcase("Withdraw"); + + using namespace jtx; + + // Equal withdrawal by Carol: 1'000'000 of tokens, 10% of the current + // pool + testAMM( + [&](AMM& ammAlice, Env& env) { + // XRP/MPT + XRPAmount const baseFee{env.current()->fees().base}; + auto carolXRP = env.balance(carol, XRP); + auto carolMPT = env.balance(carol, MPT(ammAlice[1])); + + // Single deposit of 1'000'000 worth of tokens, + // which is 10% of the pool. Carol is LP now. + ammAlice.deposit(carol, 1'000'000); + BEAST_EXPECT(ammAlice.expectBalances( + XRP(11'000), MPT(ammAlice[1])(11'000), IOUAmount{11'000'000, 0})); + BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{1'000'000, 0})); + env.require(balance(carol, carolMPT - MPT(ammAlice[1])(1'000))); + env.require(balance(carol, carolXRP - XRP(1'000) - drops(baseFee))); + + // Carol withdraws all tokens + ammAlice.withdraw(carol, 1'000'000); + BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount(beast::Zero()))); + env.require(balance(carol, carolMPT)); + env.require(balance(carol, carolXRP - drops(2 * baseFee))); + }, + {{XRP(10'000), AMMMPT(10'000)}}); + + // Equal withdrawal by tokens 1000000, 10% + // of the current pool, XRP/MPT + testAMM( + [&](AMM& ammAlice, Env&) { + // XRP/MPT + ammAlice.withdraw(alice, 1'000'000); + BEAST_EXPECT(ammAlice.expectBalances( + XRP(9'000), MPT(ammAlice[1])(9'000), IOUAmount{9'000'000, 0})); + }, + {{XRP(10'000), AMMMPT(10'000)}}); + + // Equal withdrawal by tokens, 10% of the current pool, IOU/MPT + // combination + { + auto test = [&](auto&& issue1, auto&& issue2) { + Env env(*this); + env.fund(XRP(30'000), alice, gw); + env.close(); + auto const USD = issue1( + {.env = env, + .token = "USD", + .issuer = gw, + .holders = {alice}, + .limit = 1'000'000}); + auto const BTC = issue2( + {.env = env, + .token = "BTC", + .issuer = gw, + .holders = {alice}, + .limit = 1'000'000}); + env(pay(gw, alice, BTC(50000))); + env(pay(gw, alice, USD(50000))); + env.close(); + auto ammAlice = AMM(env, alice, USD(10000), BTC(10000)); + auto aliceBTC = env.balance(alice, BTC); + auto aliceUSD = env.balance(alice, USD); + ammAlice.withdraw(alice, 1'000); + BEAST_EXPECT(ammAlice.expectBalances(BTC(9'000), USD(9'000), IOUAmount(9'000))); + env.require(balance(alice, aliceBTC + BTC(1000))); + env.require(balance(alice, aliceUSD + USD(1000))); + }; + testHelper2TokensMix(test); + } + + // Equal withdrawal with a limit. Withdraw XRP200. + // If proportional withdraw of MPT is less than 100 + // then withdraw that amount, otherwise withdraw MPT100 + // and proportionally withdraw XRP. It's the latter + // in this case - XRP100/MPT100. + testAMM( + [&](AMM& ammAlice, Env&) { + // XRP/MPT + ammAlice.withdraw(alice, XRP(200), MPT(ammAlice[1])(100)); + BEAST_EXPECT(ammAlice.expectBalances( + XRP(9'900), MPT(ammAlice[1])(9'900), IOUAmount{9'900'000, 0})); + }, + {{XRP(10'000), AMMMPT(10'000)}}); + + // Equal withdrawal with a limit. XRP100/MPT200 truncated to + // XRP100/MPT100 + testAMM( + [&](AMM& ammAlice, Env&) { + // XRP/MPT + ammAlice.withdraw(alice, XRP(100), MPT(ammAlice[1])(200)); + BEAST_EXPECT(ammAlice.expectBalances( + XRP(9'900), MPT(ammAlice[1])(9'900), IOUAmount{9'900'000, 0})); + }, + {{XRP(10'000), AMMMPT(10'000)}}); + + // Equal withdrawal with a limit. IOU/MPT combination. + { + auto test = [&](auto&& issue1, auto&& issue2) { + Env env(*this); + env.fund(XRP(30'000), alice, gw); + env.close(); + auto const USD = issue1( + {.env = env, + .token = "USD", + .issuer = gw, + .holders = {alice}, + .limit = 1'000'000}); + auto const BTC = issue2( + {.env = env, + .token = "BTC", + .issuer = gw, + .holders = {alice}, + .limit = 1'000'000}); + env(pay(gw, alice, BTC(50000))); + env(pay(gw, alice, USD(50000))); + env.close(); + auto ammAlice = AMM(env, alice, USD(10000), BTC(10000)); + auto aliceBTC = env.balance(alice, BTC); + auto aliceUSD = env.balance(alice, USD); + ammAlice.withdraw(alice, BTC(200), USD(100)); + BEAST_EXPECT(ammAlice.expectBalances(BTC(9'900), USD(9'900), IOUAmount(9'900))); + env.require(balance(alice, aliceBTC + BTC(100))); + env.require(balance(alice, aliceUSD + USD(100))); + }; + testHelper2TokensMix(test); + } + + // Single withdrawal by amount + testAMM( + [&](AMM& ammAlice, Env&) { + // single withdraw XRP from XRP/MPT + ammAlice.withdraw(alice, XRP(1'000)); + BEAST_EXPECT(ammAlice.expectBalances( + XRPAmount(9000'000001), + MPT(ammAlice[1])(10'000), + IOUAmount{9'486'832'98050514, -8})); + }, + {{XRP(10'000), AMMMPT(10'000)}}); + testAMM( + [&](AMM& ammAlice, Env&) { + // single withdraw MPT from XRP/MPT + ammAlice.withdraw(alice, MPT(ammAlice[1])(1'000)); + BEAST_EXPECT(ammAlice.expectBalances( + XRP(10000), MPT(ammAlice[1])(9001), IOUAmount{9'486'832'98050514, -8})); + }, + {{XRP(10'000), AMMMPT(10'000)}}); + testAMM( + [&](AMM& ammAlice, Env&) { + // single withdraw IOU from IOU/MPT + ammAlice.withdraw(alice, USD(1'000)); + BEAST_EXPECT(ammAlice.expectBalances( + STAmount{USD, UINT64_C(9000'000000000004), -12}, + MPT(ammAlice[1])(10'000), + IOUAmount{9486'83298050514, -11})); + }, + {{USD(10'000), AMMMPT(10'000)}}); + testAMM( + [&](AMM& ammAlice, Env&) { + // single withdraw MPT from IOU/MPT + ammAlice.withdraw(alice, MPT(ammAlice[1])(1'000)); + BEAST_EXPECT(ammAlice.expectBalances( + USD(10'000), MPT(ammAlice[1])(9001), IOUAmount{9486'83298050514, -11})); + }, + {{USD(10'000), AMMMPT(10'000)}}); + testAMM( + [&](AMM& ammAlice, Env&) { + // single withdraw MPT from MPT/MPT + ammAlice.withdraw(alice, MPT(ammAlice[0])(1'000)); + BEAST_EXPECT(ammAlice.expectBalances( + MPT(ammAlice[0])(9001), + MPT(ammAlice[1])(10'000), + IOUAmount{9486'83298050514, -11})); + }, + {{AMMMPT(10'000), AMMMPT(10'000)}}); + + // Single withdrawal MPT by tokens 10000. XRP/MPT + testAMM( + [&](AMM& ammAlice, Env&) { + ammAlice.withdraw(alice, 10'000, MPT(ammAlice[1])(0)); + BEAST_EXPECT(ammAlice.expectBalances( + XRP(10'000), MPT(ammAlice[1])(9981), IOUAmount{9'990'000, 0})); + }, + {{XRP(10'000), AMMMPT(10'000)}}); + + // Single withdrawal XRP by tokens 10000. XRP/MPT + testAMM( + [&](AMM& ammAlice, Env&) { + ammAlice.withdraw(alice, 10'000, XRP(0)); + BEAST_EXPECT(ammAlice.expectBalances( + XRPAmount(9980010000), MPT(ammAlice[1])(10'000), IOUAmount{9'990'000, 0})); + }, + {{XRP(10'000), AMMMPT(10'000)}}); + + // Single withdrawal by tokens 10000. MPT/IOU combination. + { + auto test = [&](auto&& issue1, auto&& issue2) { + Env env(*this); + env.fund(XRP(30'000), alice, gw); + env.close(); + auto const USD = issue1( + {.env = env, + .token = "USD", + .issuer = gw, + .holders = {alice}, + .limit = 1'000'000}); + auto const BTC = issue2( + {.env = env, + .token = "BTC", + .issuer = gw, + .holders = {alice}, + .limit = 1'000'000}); + env(pay(gw, alice, BTC(50000))); + env(pay(gw, alice, USD(50000))); + env.close(); + auto ammAlice = AMM(env, alice, USD(10000), BTC(10000)); + auto aliceBTC = env.balance(alice, BTC); + auto aliceUSD = env.balance(alice, USD); + ammAlice.withdraw(alice, 1000, BTC(0)); + BEAST_EXPECT(ammAlice.expectBalances(USD(10'000), BTC(8100), IOUAmount{9000, 0})); + env.require(balance(alice, aliceBTC + BTC(1900))); + env.require(balance(alice, aliceUSD)); + }; + testHelper2TokensMix(test); + } + + // Withdraw all tokens. + testAMM( + [&](AMM& ammAlice, Env& env) { + env(trust(carol, STAmount{ammAlice.lptIssue(), 10'000})); + // Can SetTrust only for AMM LP tokens + env(trust(carol, STAmount{Issue{EUR.currency, ammAlice.ammAccount()}, 10'000}), + ter(tecNO_PERMISSION)); + env.close(); + ammAlice.withdrawAll(alice); + BEAST_EXPECT(!ammAlice.ammExists()); + + BEAST_EXPECT(!env.le(keylet::ownerDir(ammAlice.ammAccount()))); + + // Can create AMM for the XRP/MPT pair + AMM const ammCarol(env, carol, XRP(10'000), MPT(ammAlice[1])(10'000)); + BEAST_EXPECT(ammCarol.expectBalances( + XRP(10'000), MPT(ammAlice[1])(10'000), IOUAmount{10'000'000, 0})); + }, + {{XRP(10'000), AMMMPT(10'000)}}); + + // Single deposit 1000MPT, withdraw all tokens in MPT from XRP/MPT + testAMM( + [&](AMM& ammAlice, Env& env) { + ammAlice.deposit(carol, MPT(ammAlice[1])(1'000)); + ammAlice.withdrawAll(carol, MPT(ammAlice[1])(0)); + BEAST_EXPECT(ammAlice.expectBalances( + XRP(10'000), MPT(ammAlice[1])(10'001), IOUAmount{10'000'000, 0})); + BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount(beast::Zero()))); + }, + {{XRP(10'000), AMMMPT(10'000)}}); + + // Single deposit 1000MPT, withdraw all tokens in XRP from XRP/MPT + testAMM( + [&](AMM& ammAlice, Env&) { + ammAlice.deposit(carol, MPT(ammAlice[1])(1'000)); + ammAlice.withdrawAll(carol, XRP(0)); + BEAST_EXPECT(ammAlice.expectBalances( + XRPAmount(9'090'909'091), MPT(ammAlice[1])(11000), IOUAmount{10'000'000, 0})); + }, + {{XRP(10'000), AMMMPT(10'000)}}); + + // Single deposit 1000MPT, withdraw all tokens in MPT from USD/MPT + testAMM( + [&](AMM& ammAlice, Env& env) { + // USD/MPT + ammAlice.deposit(carol, MPT(ammAlice[1])(1'000)); + ammAlice.withdrawAll(carol, MPT(ammAlice[1])(0)); + BEAST_EXPECT(ammAlice.expectBalances( + USD(10'000), MPT(ammAlice[1])(10'001), IOUAmount{10'000, 0})); + BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount(beast::Zero()))); + }, + {{USD(10'000), AMMMPT(10'000)}}); + + // Single deposit 1000USD, withdraw all tokens in USD from USD/MPT + testAMM( + [&](AMM& ammAlice, Env& env) { + // USD/MPT + ammAlice.deposit(carol, USD(1'000)); + ammAlice.withdrawAll(carol, USD(0)); + BEAST_EXPECT(ammAlice.expectBalances( + USD(10'000), MPT(ammAlice[1])(10'000), IOUAmount{10'000, 0})); + BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount(beast::Zero()))); + }, + {{USD(10'000), AMMMPT(10'000)}}); + + // Single deposit 1000MPT, withdraw all tokens in MPT from MPT/MPT + testAMM( + [&](AMM& ammAlice, Env& env) { + // MPT/MPT + ammAlice.deposit(carol, MPT(ammAlice[1])(1'000)); + ammAlice.withdrawAll(carol, MPT(ammAlice[1])(0)); + BEAST_EXPECT(ammAlice.expectBalances( + MPT(ammAlice[0])(10'000), MPT(ammAlice[1])(10'001), IOUAmount{10'000, 0})); + BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount(beast::Zero()))); + }, + {{AMMMPT(10'000), AMMMPT(10'000)}}); + + // Single deposit 1000MPT, withdraw all tokens in MPT + testAMM( + [&](AMM& ammAlice, Env&) { + ammAlice.deposit(carol, MPT(ammAlice[1])(1'000)); + ammAlice.withdrawAll(carol, MPT(ammAlice[1])(0)); + BEAST_EXPECT(ammAlice.expectBalances( + XRP(10'000), MPT(ammAlice[1])(10001), IOUAmount{10'000'000, 0})); + }, + {{XRP(10'000), AMMMPT(10'000)}}); + + // Single deposit 1000MPT, withdraw all tokens in USD + testAMM( + [&](AMM& ammAlice, Env&) { + ammAlice.deposit(carol, MPT(ammAlice[1])(1'000)); + ammAlice.withdrawAll(carol, USD(0)); + BEAST_EXPECT(ammAlice.expectBalances( + STAmount{USD, UINT64_C(9'090'9090909091), -10}, + MPT(ammAlice[1])(11000), + IOUAmount{10'000, 0})); + }, + {{USD(10'000), AMMMPT(10'000)}}); + + // Single deposit 1000USD, withdraw all tokens in MPT + testAMM( + [&](AMM& ammAlice, Env&) { + ammAlice.deposit(carol, USD(1'000)); + ammAlice.withdrawAll(carol, MPT(ammAlice[1])(0)); + BEAST_EXPECT(ammAlice.expectBalances( + STAmount{USD, UINT64_C(10'999'99999999999), -11}, + MPT(ammAlice[1])(9091), + IOUAmount{10'000, 0})); + }, + {{USD(10'000), AMMMPT(10'000)}}); + + // Single deposit/withdraw by the same account + testAMM( + [&](AMM& ammAlice, Env&) { + auto lpTokens = ammAlice.deposit(carol, USD(1'000)); + ammAlice.withdraw(carol, lpTokens, USD(0)); + lpTokens = ammAlice.deposit(carol, STAmount(USD, 1, -6)); + ammAlice.withdraw(carol, lpTokens, USD(0)); + lpTokens = ammAlice.deposit(carol, MPT(ammAlice[0])(1)); + ammAlice.withdraw(carol, lpTokens, MPT(ammAlice[0])(0)); + BEAST_EXPECT(ammAlice.expectBalances( + MPT(ammAlice[0])(10'000'000'001), USD(10'000), ammAlice.tokens())); + BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{0})); + }, + {{AMMMPT(10'000'000'000), USD(10'000)}}); + testAMM( + [&](AMM& ammAlice, Env&) { + auto const& BTC = MPT(ammAlice[1]); + auto lpTokens = ammAlice.deposit(carol, BTC(1'000)); + ammAlice.withdraw(carol, lpTokens, BTC(0)); + lpTokens = ammAlice.deposit(carol, BTC(1)); + ammAlice.withdraw(carol, lpTokens, BTC(0)); + lpTokens = ammAlice.deposit(carol, BTC(1)); + ammAlice.withdraw(carol, lpTokens, BTC(0)); + BEAST_EXPECT(ammAlice.expectBalances(BTC(10'003), XRP(10'000), ammAlice.tokens())); + BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{0})); + }, + {{XRP(10'000), AMMMPT(10'000)}}); + + // Single deposit by different accounts and then withdraw + // in reverse. + testAMM( + [&](AMM& ammAlice, Env&) { + auto const carolTokens = ammAlice.deposit(carol, MPT(ammAlice[1])(1'000)); + auto const aliceTokens = ammAlice.deposit(alice, MPT(ammAlice[1])(1'000)); + ammAlice.withdraw(alice, aliceTokens, MPT(ammAlice[1])(0)); + ammAlice.withdraw(carol, carolTokens, MPT(ammAlice[1])(0)); + BEAST_EXPECT(ammAlice.expectBalances( + XRP(10'000), MPT(ammAlice[1])(10'001), ammAlice.tokens())); + BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{0})); + BEAST_EXPECT(ammAlice.expectLPTokens(alice, ammAlice.tokens())); + }, + {{XRP(10'000), AMMMPT(10'000)}}); + + // Equal deposit 10%, withdraw all tokens. XRP/MPT + testAMM( + [&](AMM& ammAlice, Env&) { + ammAlice.deposit(carol, 1'000'000); + ammAlice.withdrawAll(carol); + BEAST_EXPECT(ammAlice.expectBalances( + XRP(10'000), MPT(ammAlice[1])(10'000), IOUAmount{10'000'000, 0})); + }, + {{XRP(10'000), AMMMPT(10'000)}}); + // Equal deposit 10%, withdraw all tokens. IOU/MPT combination. + { + auto test = [&](auto&& issue1, auto&& issue2) { + Env env(*this); + env.fund(XRP(30'000), alice, carol, gw); + env.close(); + auto const USD = issue1( + {.env = env, + .token = "USD", + .issuer = gw, + .holders = {alice, carol}, + .limit = 1'000'000}); + auto const BTC = issue2( + {.env = env, + .token = "BTC", + .issuer = gw, + .holders = {alice, carol}, + .limit = 1'000'000}); + env(pay(gw, alice, BTC(50000))); + env(pay(gw, carol, BTC(50000))); + env(pay(gw, alice, USD(50000))); + env(pay(gw, carol, USD(50000))); + env.close(); + auto ammAlice = AMM(env, alice, USD(10000), BTC(10000)); + auto carolBTC = env.balance(carol, BTC); + auto carolUSD = env.balance(carol, USD); + ammAlice.deposit(carol, 1'000); + ammAlice.withdrawAll(carol); + BEAST_EXPECT( + ammAlice.expectBalances(USD(10'000), BTC(10'000), IOUAmount{10'000, 0})); + env.require(balance(carol, carolBTC)); + env.require(balance(carol, carolUSD)); + }; + testHelper2TokensMix(test); + } + + // Equal deposit 10%, withdraw all tokens in MPT from XRP/MPT + testAMM( + [&](AMM& ammAlice, Env&) { + ammAlice.deposit(carol, 1'000'000); + ammAlice.withdrawAll(carol, MPT(ammAlice[1])(0)); + BEAST_EXPECT(ammAlice.expectBalances( + XRP(11'000), + STAmount{MPT(ammAlice[1]), UINT64_C(9'090'909090909092), -12}, + IOUAmount{10'000'000, 0})); + }, + {{XRP(10'000), AMMMPT(10'000)}}); + + // Equal deposit 10%, withdraw all tokens in XRP from XRP/MPT + testAMM( + [&](AMM& ammAlice, Env&) { + ammAlice.deposit(carol, 1'000'000); + ammAlice.withdrawAll(carol, XRP(0)); + BEAST_EXPECT(ammAlice.expectBalances( + XRPAmount(9'090'909'091), MPT(ammAlice[1])(11'000), IOUAmount{10'000'000, 0})); + }, + {{XRP(10'000), AMMMPT(10'000)}}); + + // Equal deposit 10%, withdraw all tokens in USD from USD/MPT + testAMM( + [&](AMM& ammAlice, Env&) { + ammAlice.deposit(carol, 1'000); + ammAlice.withdrawAll(carol, USD(0)); + BEAST_EXPECT(ammAlice.expectBalances( + STAmount{USD, UINT64_C(9'090'909090909092), -12}, + MPT(ammAlice[1])(11'000), + IOUAmount{10'000})); + }, + {{USD(10'000), AMMMPT(10'000)}}); + // Equal deposit 10%, withdraw all tokens in MPT from MPT/MPT + testAMM( + [&](AMM& ammAlice, Env&) { + ammAlice.deposit(carol, 1'000); + ammAlice.withdrawAll(carol, MPT(ammAlice[1])(0)); + BEAST_EXPECT(ammAlice.expectBalances( + MPT(ammAlice[0])(11'000), MPT(ammAlice[1])(9'091), IOUAmount{10'000})); + }, + {{AMMMPT(10'000), AMMMPT(10'000)}}); + + auto const all = testable_amendments(); + + // Withdraw with EPrice limit. + testAMM( + [&](AMM& ammAlice, Env& env) { + ammAlice.deposit(carol, 1'000'000'000'000); + ammAlice.withdraw( + carol, MPT(ammAlice[1])(100'000000), std::nullopt, IOUAmount{520, 0}); + if (!env.enabled(fixAMMv1_1) && !env.enabled(fixAMMv1_3)) + { + BEAST_EXPECT( + ammAlice.expectBalances( + XRP(11'000'000000), + MPT(ammAlice[1])(9372781065), + IOUAmount{10'153'846'15384616, -2}) && + ammAlice.expectLPTokens(carol, IOUAmount{153'846'15384616, -2})); + } + else if (env.enabled(fixAMMv1_1) && !env.enabled(fixAMMv1_3)) + { + BEAST_EXPECT( + ammAlice.expectBalances( + XRP(11'000'000000), + MPT(ammAlice[1])(9372781065), + IOUAmount{10'153'846'15384616, -2}) && + ammAlice.expectLPTokens(carol, IOUAmount{153'846'15384616, -2})); + } + else if (env.enabled(fixAMMv1_3)) + { + BEAST_EXPECT( + ammAlice.expectBalances( + XRP(11'000'000000), + MPT(ammAlice[1])(9372781066), + IOUAmount{10'153'846'15384616, -2}) && + ammAlice.expectLPTokens(carol, IOUAmount{153'846'15384616, -2})); + } + ammAlice.withdrawAll(carol); + BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{0})); + }, + {{XRP(10'000'000'000), AMMMPT(10'000'000'000)}}, + 0, + std::nullopt, + {all, all - fixAMMv1_3, all - fixAMMv1_1 - fixAMMv1_3}); + + // Withdraw with EPrice limit. AssetOut is 0. + testAMM( + [&](AMM& ammAlice, Env& env) { + ammAlice.deposit(carol, 1'000'000'000'000); + ammAlice.withdraw(carol, MPT(ammAlice[1])(0), std::nullopt, IOUAmount{520, 0}); + if (!env.enabled(fixAMMv1_1) && !env.enabled(fixAMMv1_3)) + { + BEAST_EXPECT( + ammAlice.expectBalances( + XRP(11'000'000000), + MPT(ammAlice[1])(9372781065), + IOUAmount{10'153'846'15384616, -2}) && + ammAlice.expectLPTokens(carol, IOUAmount{153'846'15384616, -2})); + } + else if (env.enabled(fixAMMv1_1) && !env.enabled(fixAMMv1_3)) + { + BEAST_EXPECT( + ammAlice.expectBalances( + XRP(11'000'000000), + MPT(ammAlice[1])(9372781065), + IOUAmount{10'153'846'15384616, -2}) && + ammAlice.expectLPTokens(carol, IOUAmount{153'846'15384616, -2})); + } + else if (env.enabled(fixAMMv1_3)) + { + BEAST_EXPECT( + ammAlice.expectBalances( + XRP(11'000'000000), + MPT(ammAlice[1])(9372781066), + IOUAmount{10'153'846'15384616, -2}) && + ammAlice.expectLPTokens(carol, IOUAmount{153'846'15384616, -2})); + } + ammAlice.withdrawAll(carol); + BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{0})); + }, + {{XRP(10'000'000'000), AMMMPT(10'000'000'000)}}, + 0, + std::nullopt, + {all, all - fixAMMv1_3, all - fixAMMv1_1 - fixAMMv1_3}); + + // IOU/MPT combination + transfer fee + { + auto test = [&](auto&& issue1, auto&& issue2) { + Env env(*this); + env.fund(XRP(30'000), alice, bob, carol, gw); + env.close(); + auto const USD = issue1( + {.env = env, + .token = "USD", + .issuer = gw, + .holders = {alice, bob, carol}, + .limit = 1'000'000, + .transferFee = 25'000}); + auto const BTC = issue2( + {.env = env, + .token = "BTC", + .issuer = gw, + .holders = {alice, bob, carol}, + .limit = 1'000'000, + .transferFee = 25'000}); + env(pay(gw, alice, BTC(10000))); + env(pay(gw, bob, BTC(10000))); + env(pay(gw, carol, BTC(10000))); + env(pay(gw, alice, USD(10000))); + env(pay(gw, bob, USD(10000))); + env(pay(gw, carol, USD(10000))); + env.close(); + // no transfer fee on create + AMM ammAlice(env, alice, BTC(2'000), USD(5)); + BEAST_EXPECT(ammAlice.expectBalances(BTC(2'000), USD(5), IOUAmount{100, 0})); + env.require(balance(alice, BTC(8000))); + env.require(balance(alice, USD(9995))); + + // no transfer fee on deposit + ammAlice.deposit(carol, 100); + BEAST_EXPECT(ammAlice.expectBalances(BTC(4000), USD(10), IOUAmount{200, 0})); + env.require(balance(carol, BTC(8000))); + env.require(balance(carol, USD(9995))); + + // no transfer fee on withdraw + ammAlice.withdraw(carol, 100); + BEAST_EXPECT(ammAlice.expectBalances(BTC(2'000), USD(5), IOUAmount{100, 0})); + BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{0, 0})); + env.require(balance(carol, BTC(10000))); + env.require(balance(carol, USD(10000))); + }; + testHelper2TokensMix(test); + } + + // Tiny withdraw + testAMM( + [&](AMM& ammAlice, Env&) { + // By tokens + ammAlice.withdraw(alice, IOUAmount{1, -3}); + BEAST_EXPECT(ammAlice.expectBalances( + MPT(ammAlice[0])(9'999'999'999), + STAmount{USD, UINT64_C(9'999'999999), -6}, + IOUAmount{9'999'999'999, -3})); + }, + {{AMMMPT(10'000'000'000), USD(10'000)}}); + testAMM( + [&](AMM& ammAlice, Env&) { + // Single withdraw MPT from MPT/IOU + ammAlice.withdraw(alice, std::nullopt, MPT(ammAlice[0])(1)); + BEAST_EXPECT(ammAlice.expectBalances( + MPT(ammAlice[0])(10000'000000), USD(10'000), IOUAmount{9'999'999'9995, -4})); + }, + {{AMMMPT(10'000'000'000), USD(10'000)}}); + testAMM( + [&](AMM& ammAlice, Env&) { + // Single withdraw IOU from MPT/IOU + ammAlice.withdraw(alice, std::nullopt, STAmount{USD, 1, -10}); + BEAST_EXPECT(ammAlice.expectBalances( + MPT(ammAlice[0])(10'000'000'000), + STAmount{USD, UINT64_C(9'999'9999999999), -10}, + IOUAmount{9'999'999'99999995, -8})); + }, + {{AMMMPT(10'000'000'000), USD(10'000)}}); + testAMM( + [&](AMM& ammAlice, Env&) { + // Single withdraw XRP from MPT/XRP + ammAlice.withdraw(alice, std::nullopt, XRPAmount(1)); + BEAST_EXPECT(ammAlice.expectBalances( + MPT(ammAlice[1])(10'000), XRP(10'000), IOUAmount{9999999'9995, -4})); + }, + {{XRP(10'000), AMMMPT(10'000)}}); + + // Withdraw close to entire pool + // Equal by tokens + testAMM( + [&](AMM& ammAlice, Env&) { + ammAlice.withdraw(alice, IOUAmount{9'999'999'999, -3}); + BEAST_EXPECT(ammAlice.expectBalances( + MPT(ammAlice[0])(1), STAmount{USD, 1, -6}, IOUAmount{1, -3})); + }, + {{AMMMPT(10'000'000'000), USD(10'000)}}); + // USD by tokens + testAMM( + [&](AMM& ammAlice, Env&) { + ammAlice.withdraw(alice, IOUAmount{9'999'999}, USD(0)); + BEAST_EXPECT(ammAlice.expectBalances( + MPT(ammAlice[0])(10'000'000'000), STAmount{USD, 1, -10}, IOUAmount{1})); + }, + {{AMMMPT(10'000'000'000), USD(10'000)}}); + // MPT by tokens + testAMM( + [&](AMM& ammAlice, Env&) { + ammAlice.withdraw(alice, IOUAmount{9'999'900}, MPT(ammAlice[0])(0)); + BEAST_EXPECT( + ammAlice.expectBalances(MPT(ammAlice[0])(1), USD(10'000), IOUAmount{100})); + }, + {{AMMMPT(10'000'000'000), USD(10'000)}}); + // XRP by tokens + testAMM( + [&](AMM& ammAlice, Env&) { + ammAlice.withdraw(alice, IOUAmount{9'999'900}, XRP(0)); + BEAST_EXPECT( + ammAlice.expectBalances(MPT(ammAlice[1])(10000), XRPAmount(1), IOUAmount{100})); + }, + {{XRP(10'000), AMMMPT(10'000)}}); + // USD + testAMM( + [&](AMM& ammAlice, Env&) { + ammAlice.withdraw(alice, STAmount{USD, UINT64_C(9'999'99999999999), -11}); + BEAST_EXPECT(ammAlice.expectBalances( + MPT(ammAlice[0])(10'000'000'000), + STAmount{USD, 1, -11}, + IOUAmount{316227765, -9})); + }, + {{AMMMPT(10'000'000'000), USD(10'000)}}); + // XRP + testAMM( + [&](AMM& ammAlice, Env&) { + ammAlice.withdraw(alice, XRPAmount{9'999'999'999}); + BEAST_EXPECT( + ammAlice.expectBalances(MPT(ammAlice[1])(10000), XRPAmount(1), IOUAmount{100})); + }, + {{XRP(10'000), AMMMPT(10'000)}}); + // MPT + testAMM( + [&](AMM& ammAlice, Env&) { + ammAlice.withdraw(alice, MPT(ammAlice[0])(9'999'999'999)); + BEAST_EXPECT( + ammAlice.expectBalances(MPT(ammAlice[0])(1), USD(10'000), IOUAmount{100})); + }, + {{AMMMPT(10'000'000'000), USD(10'000)}}); + testAMM( + [&](AMM& ammAlice, Env&) { + ammAlice.withdraw(alice, MPT(ammAlice[1])(9'999)); + BEAST_EXPECT( + ammAlice.expectBalances(MPT(ammAlice[1])(1), XRP(10'000), IOUAmount{100000})); + }, + {{XRP(10'000), AMMMPT(10'000)}}); + } + + void + testInvalidFeeVote() + { + testcase("Invalid Fee Vote"); + using namespace jtx; + + testAMM( + [&](AMM& ammAlice, Env& env) { + // Invalid Account + Account bad("bad"); + env.memoize(bad); + ammAlice.vote(bad, 1'000, std::nullopt, seq(1), std::nullopt, ter(terNO_ACCOUNT)); + + // Invalid AMM + ammAlice.vote( + alice, + 1'000, + std::nullopt, + std::nullopt, + {{MPT(ammAlice[1]), GBP}}, + ter(terNO_AMM)); + + // Account is not LP + ammAlice.vote( + carol, + 1'000, + std::nullopt, + std::nullopt, + std::nullopt, + ter(tecAMM_INVALID_TOKENS)); + + // Invalid asset pair + ammAlice.vote( + alice, + 1'000, + std::nullopt, + std::nullopt, + {{MPT(ammAlice[1]), MPT(ammAlice[1])}}, + ter(temBAD_AMM_TOKENS)); + }, + {{XRP(10'000), AMMMPT(10'000)}}); + + // Invalid AMM + testAMM( + [&](AMM& ammAlice, Env& env) { + ammAlice.withdrawAll(alice); + ammAlice.vote( + alice, 1'000, std::nullopt, std::nullopt, std::nullopt, ter(terNO_AMM)); + }, + {{XRP(10'000), AMMMPT(10'000)}}); + + // MPTokenInstance object doesn't exist + { + Env env(*this); + env.fund(XRP(1'000), alice); + env(AMM::voteJv({.account = alice, .tfee = 1'000, .assets = {{XRP, MPT(alice, 0)}}}), + ter(terNO_AMM)); + } + } + + void + testFeeVote() + { + testcase("Fee Vote"); + using namespace jtx; + + // One vote sets fee to 1%. + testAMM( + [&](AMM& ammAlice, Env& env) { + BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{0})); + ammAlice.vote({}, 1'000); + BEAST_EXPECT(ammAlice.expectTradingFee(1'000)); + // Discounted fee is 1/10 of trading fee. + BEAST_EXPECT(ammAlice.expectAuctionSlot(100, 0, IOUAmount{0})); + }, + {{XRP(10'000), AMMMPT(10'000)}}); + + auto vote = [&](AMM& ammAlice, + Env& env, + int i, + std::uint32_t tokens = 10'000'000, + std::vector* accounts = nullptr) { + Account a(std::to_string(i)); + ammAlice.deposit(a, tokens); + ammAlice.vote(a, 50 * (i + 1)); + if (accounts) + accounts->push_back(std::move(a)); + }; + + { + // Eight votes fill all voting slots, set fee 0.175%. + // New vote, same account, sets fee 0.225% + Env env{*this}; + env.fund(XRP(30'000), gw, alice); + std::vector holders = {alice}; + for (int i = 0; i <= 7; ++i) + { + Account const a(std::to_string(i)); + holders.push_back(a); + env.fund(XRP(30'000), a); + } + env.close(); + // create MPT and pay 30'000 to all the accounts + MPTTester const BTC({.env = env, .issuer = gw, .holders = holders, .pay = 30'000}); + AMM ammAlice(env, alice, XRP(10'000), BTC(10'000)); + for (int i = 0; i < 7; ++i) + vote(ammAlice, env, i); + BEAST_EXPECT(ammAlice.expectTradingFee(175)); + Account const a("0"); + ammAlice.vote(a, 450); + BEAST_EXPECT(ammAlice.expectTradingFee(225)); + } + + { + // Eight votes fill all voting slots, set fee 0.175%. + // New vote, new account, higher vote weight, set higher fee 0.244% + Env env{*this}; + env.fund(XRP(30'000), gw, alice); + std::vector holders = {alice}; + for (int i = 0; i < 8; ++i) + { + Account const a(std::to_string(i)); + holders.push_back(a); + env.fund(XRP(30'000), a); + } + env.close(); + MPTTester const BTC({.env = env, .issuer = gw, .holders = holders, .pay = 30'000}); + AMM ammAlice(env, alice, XRP(10'000), BTC(10'000)); + for (int i = 0; i < 7; ++i) + vote(ammAlice, env, i); + BEAST_EXPECT(ammAlice.expectTradingFee(175)); + vote(ammAlice, env, 7, 20'000'000); + BEAST_EXPECT(ammAlice.expectTradingFee(244)); + } + + { + // Eight votes fill all voting slots, set fee 0.219%. + // New vote, new account, higher vote weight, set smaller fee 0.206% + Env env{*this}; + env.fund(XRP(30'000), gw, alice); + std::vector holders = {alice}; + for (int i = 0; i < 8; ++i) + { + Account const a(std::to_string(i)); + holders.push_back(a); + env.fund(XRP(30'000), a); + } + env.close(); + MPTTester const BTC({.env = env, .issuer = gw, .holders = holders, .pay = 30'000}); + AMM ammAlice(env, alice, XRP(10'000), BTC(10'000)); + for (int i = 7; i > 0; --i) + vote(ammAlice, env, i); + BEAST_EXPECT(ammAlice.expectTradingFee(219)); + vote(ammAlice, env, 0, 20'000'000); + BEAST_EXPECT(ammAlice.expectTradingFee(206)); + } + + { + // Eight votes fill all voting slots. The accounts then withdraw all + // tokens. An account sets a new fee and the previous slots are + // deleted. + Env env{*this}; + env.fund(XRP(30'000), gw, alice, carol); + std::vector holders = {alice, carol}; + for (int i = 0; i < 7; ++i) + { + Account const a(std::to_string(i)); + holders.push_back(a); + env.fund(XRP(30'000), a); + } + env.close(); + MPTTester const BTC({.env = env, .issuer = gw, .holders = holders, .pay = 30'000}); + AMM ammAlice(env, alice, XRP(10'000), BTC(10'000)); + std::vector accounts; + for (int i = 0; i < 7; ++i) + vote(ammAlice, env, i, 10'000'000, &accounts); + BEAST_EXPECT(ammAlice.expectTradingFee(175)); + for (int i = 0; i < 7; ++i) + ammAlice.withdrawAll(accounts[i]); + ammAlice.deposit(carol, 10'000'000); + ammAlice.vote(carol, 1'000); + // The initial LP set the fee to 1000. Carol gets 50% voting + // power, and the new fee is 500. + BEAST_EXPECT(ammAlice.expectTradingFee(500)); + } + { + // Eight votes fill all voting slots. The accounts then withdraw + // some tokens. The new vote doesn't get the voting power but the + // slots are refreshed and the fee is updated. + Env env{*this}; + env.fund(XRP(30'000), gw, alice, carol); + std::vector holders = {alice, carol}; + for (int i = 0; i < 7; ++i) + { + Account const a(std::to_string(i)); + holders.push_back(a); + env.fund(XRP(30'000), a); + } + env.close(); + MPTTester const BTC({.env = env, .issuer = gw, .holders = holders, .pay = 30'000}); + AMM ammAlice(env, alice, XRP(10'000), BTC(10'000)); + std::vector accounts; + for (int i = 0; i < 7; ++i) + vote(ammAlice, env, i, 10'000'000, &accounts); + BEAST_EXPECT(ammAlice.expectTradingFee(175)); + for (int i = 0; i < 7; ++i) + ammAlice.withdraw(accounts[i], 9'000'000); + ammAlice.deposit(carol, 1'000); + // The vote is not added to the slots + ammAlice.vote(carol, 1'000); + auto const info = ammAlice.ammRpcInfo()[jss::amm][jss::vote_slots]; + for (auto i = 0; i < info.size(); ++i) + BEAST_EXPECT(info[i][jss::account] != carol.human()); + // But the slots are refreshed and the fee is changed + BEAST_EXPECT(ammAlice.expectTradingFee(82)); + } + } + + void + testInvalidBid() + { + testcase("Invalid Bid"); + using namespace jtx; + using namespace std::chrono; + + // burn all the LPTokens through a AMMBid transaction + { + Env env(*this); + env.fund(XRP(2'000), gw, alice); + MPTTester const BTC( + {.env = env, .issuer = gw, .holders = {alice}, .pay = 2'000, .flags = MPTDEXFlags}); + AMM amm(env, gw, XRP(1'000), BTC(1'000), false, 1'000); + + // auction slot is owned by the creator of the AMM i.e. gw + BEAST_EXPECT(amm.expectAuctionSlot(100, 0, IOUAmount{0})); + + // gw attempts to burn all her LPTokens through a bid transaction + // this transaction fails because AMMBid transaction can not burn + // all the outstanding LPTokens + env(amm.bid({ + .account = gw, + .bidMin = 1'000'000, + }), + ter(tecAMM_INVALID_TOKENS)); + } + + // burn all the LPTokens through a AMMBid transaction + { + Env env(*this); + env.fund(XRP(2'000), gw, alice); + MPTTester const BTC( + {.env = env, .issuer = gw, .holders = {alice}, .pay = 2'000, .flags = MPTDEXFlags}); + AMM amm(env, gw, XRP(1'000), BTC(1'000), false, 1'000); + + // auction slot is owned by the creator of the AMM i.e. gw + BEAST_EXPECT(amm.expectAuctionSlot(100, 0, IOUAmount{0})); + + // gw burns all but one of its LPTokens through a bid transaction + // this transaction succeeds because the bid price is less than + // the total outstanding LPToken balance + env(amm.bid({ + .account = gw, + .bidMin = STAmount{amm.lptIssue(), UINT64_C(999'999)}, + }), + ter(tesSUCCESS)) + .close(); + + // gw must own the auction slot + BEAST_EXPECT(amm.expectAuctionSlot(100, 0, IOUAmount{999'999})); + + // 999'999 tokens are burned, only 1 LPToken is owned by gw + BEAST_EXPECT(amm.expectBalances(XRP(1'000), BTC(1'000), IOUAmount{1})); + + // gw owns only 1 LPToken in its balance + BEAST_EXPECT(Number{amm.getLPTokensBalance(gw)} == 1); + + // gw attempts to burn the last of its LPTokens in an AMMBid + // transaction. This transaction fails because it would burn all + // the remaining LPTokens + env(amm.bid({ + .account = gw, + .bidMin = 1, + }), + ter(tecAMM_INVALID_TOKENS)); + } + + testAMM( + [&](AMM& ammAlice, Env& env) { + ammAlice.deposit(carol, 1'000'000); + // Invlaid Min/Max combination + env(ammAlice.bid({ + .account = carol, + .bidMin = 200, + .bidMax = 100, + }), + ter(tecAMM_INVALID_TOKENS)); + + // Invalid Account + Account bad("bad"); + env.memoize(bad); + env(ammAlice.bid({ + .account = bad, + .bidMax = 100, + }), + seq(1), + ter(terNO_ACCOUNT)); + + // Account is not LP + Account const dan("dan"); + env.fund(XRP(1'000), dan); + env(ammAlice.bid({ + .account = dan, + .bidMin = 100, + }), + ter(tecAMM_INVALID_TOKENS)); + env(ammAlice.bid({ + .account = dan, + }), + ter(tecAMM_INVALID_TOKENS)); + + // Auth account is invalid. + env(ammAlice.bid({ + .account = carol, + .bidMin = 100, + .authAccounts = {bob}, + }), + ter(terNO_ACCOUNT)); + + // Invalid Assets + env(ammAlice.bid({ + .account = alice, + .bidMax = 100, + .assets = {{MPT(ammAlice[1]), GBP}}, + }), + ter(terNO_AMM)); + }, + {{XRP(10'000), AMMMPT(10'000)}}); + + // Invalid AMM + testAMM( + [&](AMM& ammAlice, Env& env) { + ammAlice.withdrawAll(alice); + env(ammAlice.bid({ + .account = alice, + .bidMax = 100, + }), + ter(terNO_AMM)); + }, + {{XRP(10'000), AMMMPT(10'000)}}); + + // Bid price exceeds LP owned tokens + { + Env env(*this); + fund(env, gw, {alice, bob, carol}, XRP(1'000), {USD(30'000)}, Fund::All); + MPTTester const BTC( + {.env = env, + .issuer = gw, + .holders = {alice, carol, bob}, + .pay = 30'000'000'000, + .flags = MPTDEXFlags}); + + AMM ammAlice(env, alice, BTC(10'000'000'000), USD(10'000)); + ammAlice.deposit(carol, 1'000'000); + ammAlice.deposit(bob, 10); + env(ammAlice.bid({ + .account = carol, + .bidMin = 1'000'001, + }), + ter(tecAMM_INVALID_TOKENS)); + env(ammAlice.bid({ + .account = carol, + .bidMax = 1'000'001, + }), + ter(tecAMM_INVALID_TOKENS)); + env(ammAlice.bid({ + .account = carol, + .bidMin = 1'000, + })); + BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{1'000})); + // Slot purchase price is more than 1000 but bob only has 10 tokens + env(ammAlice.bid({ + .account = bob, + }), + ter(tecAMM_INVALID_TOKENS)); + } + + // Bid all tokens, still own the slot + { + Env env(*this); + env.fund(XRP(1'000), gw, alice, bob); + MPTTester const BTC( + {.env = env, + .issuer = gw, + .holders = {alice, bob}, + .pay = 1'000, + .flags = MPTDEXFlags}); + AMM amm(env, gw, XRP(10), BTC(1'000)); + auto const lpIssue = amm.lptIssue(); + env.trust(STAmount{lpIssue, 100}, alice); + env.trust(STAmount{lpIssue, 50}, bob); + env(pay(gw, alice, STAmount{lpIssue, 100})); + env(pay(gw, bob, STAmount{lpIssue, 50})); + env(amm.bid({.account = alice, .bidMin = 100})); + // Alice doesn't have any more tokens, but + // she still owns the slot. + env(amm.bid({ + .account = bob, + .bidMax = 50, + }), + ter(tecAMM_FAILED)); + } + } + + void + testBid(FeatureBitset features) + { + testcase("Bid"); + using namespace jtx; + using namespace std::chrono; + + // Auction slot initially is owned by AMM creator, who pays 0 price. + + // Bid 110 tokens. Pay bidMin. + testAMM( + [&](AMM& ammAlice, Env& env) { + ammAlice.deposit(carol, 1'000'000); + env(ammAlice.bid({.account = carol, .bidMin = 110})); + BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{110})); + // 110 tokens are burned. + BEAST_EXPECT(ammAlice.expectBalances( + XRP(11'000), MPT(ammAlice[1])(11'000), IOUAmount{10'999'890, 0})); + }, + {{XRP(10'000), AMMMPT(10'000)}}); + + // Bid with min/max when the pay price is less than min. + testAMM( + [&](AMM& ammAlice, Env& env) { + ammAlice.deposit(carol, 1'000'000); + // Bid exactly 110. Pay 110 because the pay price is < 110. + env(ammAlice.bid({.account = carol, .bidMin = 110, .bidMax = 110})); + BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{110})); + BEAST_EXPECT(ammAlice.expectBalances( + XRP(11'000), MPT(ammAlice[1])(11'000), IOUAmount{10'999'890})); + // Bid exactly 180-200. Pay 180 because the pay price is < 180. + env(ammAlice.bid({.account = alice, .bidMin = 180, .bidMax = 200})); + BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{180})); + BEAST_EXPECT(ammAlice.expectBalances( + XRP(11'000), MPT(ammAlice[1])(11'000), IOUAmount{10'999'814'5, -1})); + }, + {{XRP(10'000), AMMMPT(10'000)}}); + + // Start bid at bidMin 110. + { + Env env(*this); + env.fund(XRP(30'000), gw, alice, carol, bob); + MPTTester const BTC( + {.env = env, + .issuer = gw, + .holders = {alice, carol, bob}, + .pay = 30'000, + .flags = MPTDEXFlags}); + AMM ammAlice(env, alice, XRP(10'000), BTC(10'000)); + + ammAlice.deposit(carol, 1'000'000); + // Bid, pay bidMin. + env(ammAlice.bid({.account = carol, .bidMin = 110})); + BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{110})); + + ammAlice.deposit(bob, 1'000'000); + // Bid, pay the computed price. + env(ammAlice.bid({.account = bob})); + BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount(1155, -1))); + + // Bid bidMax fails because the computed price is higher. + env(ammAlice.bid({ + .account = carol, + .bidMax = 120, + }), + ter(tecAMM_FAILED)); + // Bid MaxSlotPrice succeeds - pay computed price + env(ammAlice.bid({.account = carol, .bidMax = 600})); + BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{121'275, -3})); + + // Bid Min/MaxSlotPrice fails because the computed price is not + // in range + env(ammAlice.bid({ + .account = carol, + .bidMin = 10, + .bidMax = 100, + }), + ter(tecAMM_FAILED)); + // Bid Min/MaxSlotPrice succeeds - pay computed price + env(ammAlice.bid({.account = carol, .bidMin = 100, .bidMax = 600})); + BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{127'33875, -5})); + } + + // Slot states. + { + Env env(*this); + env.fund(XRP(30'000), gw, alice, carol, bob); + MPTTester const BTC( + {.env = env, + .issuer = gw, + .holders = {alice, carol, bob}, + .pay = 30'000, + .flags = MPTDEXFlags}); + AMM ammAlice(env, alice, XRP(10'000), BTC(10'000)); + + ammAlice.deposit(carol, 1'000'000); + ammAlice.deposit(bob, 1'000'000); + BEAST_EXPECT(ammAlice.expectBalances( + XRPAmount(12'000'000001), BTC(12'001), IOUAmount{12'000'000, 0})); + + // Initial state. Pay bidMin. + env(ammAlice.bid({.account = carol, .bidMin = 110})).close(); + BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{110})); + + // 1st Interval after close, price for 0th interval. + env(ammAlice.bid({.account = bob})); + env.close(seconds(AUCTION_SLOT_INTERVAL_DURATION + 1)); + BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 1, IOUAmount{1'155, -1})); + + // 10th Interval after close, price for 1st interval. + env(ammAlice.bid({.account = carol})); + env.close(seconds((10 * AUCTION_SLOT_INTERVAL_DURATION) + 1)); + BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 10, IOUAmount{121'275, -3})); + + // 20th Interval (expired) after close, price for 10th interval. + env(ammAlice.bid({.account = bob})); + env.close(seconds((AUCTION_SLOT_TIME_INTERVALS * AUCTION_SLOT_INTERVAL_DURATION) + 1)); + BEAST_EXPECT(ammAlice.expectAuctionSlot(0, std::nullopt, IOUAmount{127'33875, -5})); + + // 0 Interval. + env(ammAlice.bid({.account = carol, .bidMin = 110})).close(); + BEAST_EXPECT(ammAlice.expectAuctionSlot(0, std::nullopt, IOUAmount{110})); + // ~321.09 tokens burnt on bidding fees. + BEAST_EXPECT(ammAlice.expectBalances( + XRPAmount(12'000'000001), BTC(12'001), IOUAmount{11'999'678'91000001, -8})); + } + + // Pool's fee 1%. Bid bidMin. + // Auction slot owner and auth account trade at discounted fee - + // 1/10 of the trading fee. + // Other accounts trade at 1% fee. + { + Env env(*this); + Account const dan("dan"); + Account const ed("ed"); + env.fund(XRP(2'000), gw, alice, bob, carol, dan, ed); + MPTTester const BTC( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol, dan, ed}, + .pay = 30'000'000'000}); + fund(env, gw, {alice, carol}, {USD(30'000)}, Fund::TokenOnly); + fund(env, gw, {bob, dan, ed}, {USD(20'000)}, Fund::TokenOnly); + + AMM ammAlice(env, alice, BTC(10'000'000'000), USD(10'000), CreateArg{.tfee = 1'000}); + ammAlice.deposit(bob, 1'000'000); + ammAlice.deposit(ed, 1'000'000); + ammAlice.deposit(carol, 500'000); + ammAlice.deposit(dan, 500'000); + auto ammTokens = ammAlice.getLPTokensBalance(); + env(ammAlice.bid({ + .account = carol, + .bidMin = 120, + .authAccounts = {bob, ed}, + })); + auto const slotPrice = IOUAmount{5'200}; + ammTokens -= slotPrice; + BEAST_EXPECT(ammAlice.expectAuctionSlot(100, 0, slotPrice)); + BEAST_EXPECT(ammAlice.expectBalances(BTC(13'000'000'003), USD(13'000), ammTokens)); + // Discounted trade + for (int i = 0; i < 10; ++i) + { + auto tokens = ammAlice.deposit(carol, USD(100)); + ammAlice.withdraw(carol, tokens, USD(0)); + tokens = ammAlice.deposit(bob, USD(100)); + ammAlice.withdraw(bob, tokens, USD(0)); + tokens = ammAlice.deposit(ed, USD(100)); + ammAlice.withdraw(ed, tokens, USD(0)); + } + // carol, bob, and ed pay ~0.99USD in fees. + BEAST_EXPECT( + env.balance(carol, USD) == STAmount(USD, UINT64_C(29'499'00572620545), -11)); + BEAST_EXPECT(env.balance(bob, USD) == STAmount(USD, UINT64_C(18'999'00572616195), -11)); + BEAST_EXPECT(env.balance(ed, USD) == STAmount(USD, UINT64_C(18'999'00572611841), -11)); + // USD pool is slightly higher because of the fees. + BEAST_EXPECT(ammAlice.expectBalances( + BTC(13'000'000'003), STAmount(USD, UINT64_C(13'002'98282151419), -11), ammTokens)); + + ammTokens = ammAlice.getLPTokensBalance(); + // Trade with the fee + for (int i = 0; i < 10; ++i) + { + auto const tokens = ammAlice.deposit(dan, USD(100)); + ammAlice.withdraw(dan, tokens, USD(0)); + } + // dan pays ~9.94USD, which is ~10 times more in fees than + // carol, bob, ed. the discounted fee is 10 times less + // than the trading fee. + + BEAST_EXPECT(env.balance(dan, USD) == STAmount(USD, UINT64_C(19'490'05672274398), -11)); + // USD pool gains more in dan's fees. + BEAST_EXPECT(ammAlice.expectBalances( + BTC(13'000'000'003), STAmount{USD, UINT64_C(13'012'92609877021), -11}, ammTokens)); + // Discounted fee payment + ammAlice.deposit(carol, USD(100)); + ammTokens = ammAlice.getLPTokensBalance(); + BEAST_EXPECT(ammAlice.expectBalances( + MPT(ammAlice[0])(13'000'000'003), + STAmount{USD, UINT64_C(13'112'92609877019), -11}, + ammTokens)); + env(pay(carol, bob, USD(100)), path(~USD), sendmax(BTC(110'000'000))); + env.close(); + // carol pays 100000 drops in fees + // 99900668MPT swapped in for 100USD + BEAST_EXPECT(ammAlice.expectBalances( + BTC(13'100'000'671), STAmount{USD, UINT64_C(13'012'92609877019), -11}, ammTokens)); + + // Payment with the trading fee + env(pay(alice, carol, BTC(100'000'000)), path(~MPT(ammAlice[0])), sendmax(USD(110))); + env.close(); + // alice pays ~1.011USD in fees, which is ~10 times more + // than carol's fee + // 100.099431529USD swapped in for 100MPT + + BEAST_EXPECT(ammAlice.expectBalances( + BTC(13'000'000'671), STAmount{USD, UINT64_C(13'114'03663044931), -11}, ammTokens)); + + // Auction slot expired, no discounted fee + env.close(seconds(TOTAL_TIME_SLOT_SECS + 1)); + // clock is parent's based + env.close(); + + BEAST_EXPECT( + env.balance(carol, USD) == STAmount(USD, UINT64_C(29'399'00572620547), -11)); + ammTokens = ammAlice.getLPTokensBalance(); + for (int i = 0; i < 10; ++i) + { + auto const tokens = ammAlice.deposit(carol, USD(100)); + ammAlice.withdraw(carol, tokens, USD(0)); + } + // carol pays ~9.94USD in fees, which is ~10 times more in + // trading fees vs discounted fee. + + BEAST_EXPECT( + env.balance(carol, USD) == STAmount(USD, UINT64_C(29'389'06197177122), -11)); + BEAST_EXPECT(ammAlice.expectBalances( + BTC(13'000'000'671), STAmount{USD, UINT64_C(13'123'98038488356), -11}, ammTokens)); + + env(pay(carol, bob, USD(100)), path(~USD), sendmax(BTC(110'000'000))); + env.close(); + // carol pays ~1.008MPT in trading fee, which is + // ~10 times more than the discounted fee. + // 99.815876MPT is swapped in for 100USD + BEAST_EXPECT(ammAlice.expectBalances( + BTC(13'100'824'793), STAmount{USD, UINT64_C(13'023'98038488356), -11}, ammTokens)); + } + + // Bid tiny amount + testAMM( + [&](AMM& ammAlice, Env& env) { + // Bid a tiny amount + auto const tiny = Number{STAmount::cMinValue, STAmount::cMinOffset}; + env(ammAlice.bid({.account = alice, .bidMin = IOUAmount{tiny}})); + // Auction slot purchase price is equal to the tiny amount + // since the minSlotPrice is 0 with no trading fee. + BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{tiny})); + // The purchase price is too small to affect the total tokens + BEAST_EXPECT(ammAlice.expectBalances( + MPT(ammAlice[0])(10'000'000'000), USD(10'000), ammAlice.tokens())); + // Bid the tiny amount + env(ammAlice.bid({ + .account = alice, + .bidMin = IOUAmount{STAmount::cMinValue, STAmount::cMinOffset}, + })); + // Pay slightly higher price + BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{tiny * Number{105, -2}})); + // The purchase price is still too small to affect the total + // tokens + BEAST_EXPECT(ammAlice.expectBalances( + MPT(ammAlice[0])(10'000'000'000), USD(10'000), ammAlice.tokens())); + }, + {{AMMMPT(10'000'000'000), USD(10'000)}}); + + // Reset auth account + testAMM( + [&](AMM& ammAlice, Env& env) { + env(ammAlice.bid({ + .account = alice, + .bidMin = IOUAmount{100}, + .authAccounts = {carol}, + })); + BEAST_EXPECT(ammAlice.expectAuctionSlot({carol})); + env(ammAlice.bid({.account = alice, .bidMin = IOUAmount{100}})); + BEAST_EXPECT(ammAlice.expectAuctionSlot({})); + Account bob("bob"); + Account dan("dan"); + fund(env, {bob, dan}, XRP(1'000)); + env(ammAlice.bid({ + .account = alice, + .bidMin = IOUAmount{100}, + .authAccounts = {bob, dan}, + })); + BEAST_EXPECT(ammAlice.expectAuctionSlot({bob, dan})); + }, + {{AMMMPT(10'000'000'000), USD(10'000)}}); + + // Bid all tokens, still own the slot and trade at a discount + { + Env env(*this); + env.fund(XRP(2'000), gw, alice, bob); + MPTTester const BTC( + {.env = env, + .issuer = gw, + .holders = {alice, bob}, + .pay = 2'000'000'000, + .flags = MPTDEXFlags}); + fund(env, gw, {alice, bob}, {USD(2'000)}, Fund::TokenOnly); + AMM amm(env, gw, BTC(1'000'000'000), USD(1'010), false, 1'000); + auto const lpIssue = amm.lptIssue(); + env.trust(STAmount{lpIssue, 500}, alice); + env.trust(STAmount{lpIssue, 50}, bob); + env(pay(gw, alice, STAmount{lpIssue, 500})); + env(pay(gw, bob, STAmount{lpIssue, 50})); + // Alice doesn't have anymore lp tokens + env(amm.bid({.account = alice, .bidMin = 500})); + BEAST_EXPECT(amm.expectAuctionSlot(100, 0, IOUAmount{500})); + BEAST_EXPECT(expectHolding(env, alice, STAmount{lpIssue, 0})); + // But trades with the discounted fee since she still owns the slot. + // Alice pays ~10011 MPT in fees + env(pay(alice, bob, USD(10)), path(~USD), sendmax(BTC(11'000'000))); + BEAST_EXPECT(amm.expectBalances( + BTC(1'010'010'011), USD(1'000), IOUAmount{1'004'487'562112089, -9})); + + // Bob pays the full fee ~0.1USD + env(pay(bob, alice, BTC(10'000'000)), path(~MPT(BTC)), sendmax(USD(11))); + + BEAST_EXPECT(amm.expectBalances( + BTC(1'000'010'011), + STAmount{USD, UINT64_C(1'010'10090898081), -11}, + IOUAmount{1'004'487'562112089, -9})); + } + + // preflight tests + { + Env env(*this, features); + env.fund(XRP(2'000), gw, alice, bob); + MPTTester const BTC( + {.env = env, + .issuer = gw, + .holders = {alice, bob}, + .pay = 2'000, + .flags = MPTDEXFlags}); + AMM amm(env, gw, XRP(1'000), BTC(1'010), false, 1'000); + Json::Value const tx = amm.bid({.account = alice, .bidMin = 500}); + + { + auto jtx = env.jt(tx, seq(1), fee(10)); + env.app().config().features.erase(featureMPTokensV2); + PreflightContext const pfctx( + env.app(), *jtx.stx, env.current()->rules(), tapNONE, env.journal); + auto pf = AMMBid::checkExtraFeatures(pfctx); + BEAST_EXPECT(pf == false); + env.app().config().features.insert(featureMPTokensV2); + } + + { + auto jtx = env.jt(tx, seq(1), fee(10)); + jtx.jv["Asset2"]["currency"] = "XRP"; + jtx.jv["Asset2"].removeMember("mpt_issuance_id"); + jtx.stx = env.ust(jtx); + PreflightContext const pfctx( + env.app(), *jtx.stx, env.current()->rules(), tapNONE, env.journal); + auto pf = AMMBid::preflight(pfctx); + BEAST_EXPECT(pf == temBAD_AMM_TOKENS); + } + } + } + + void + testClawback() + { + testcase("Clawback"); + using namespace jtx; + + Env env(*this); + env.fund(XRP(2'000), gw, alice); + MPT const BTC = MPTTester( + {.env = env, .issuer = gw, .holders = {alice}, .transferFee = 1'500, .pay = 40'000}); + + // alice creates AMM + AMM const amm(env, alice, XRP(1'000), BTC(1'000)); + + // gw owns MPTIssuance, not allowed to set asfAllowTrustLineClawback + env(fset(gw, asfAllowTrustLineClawback), ter(tecOWNERS)); + } + + void + testClawbackFromAMMAccount(FeatureBitset features) + { + testcase("test clawback from AMM account"); + using namespace jtx; + + Env env(*this, features); + env.fund(XRP(1'000), gw); + env(fset(gw, asfAllowTrustLineClawback)); + fund(env, gw, {alice}, XRP(1'000), {USD(1'000)}, Fund::Acct); + + MPTTester const BTC( + {.env = env, + .issuer = gw, + .holders = {alice}, + .pay = 30'000, + .flags = tfMPTCanLock | MPTDEXFlags}); + + // to clawback from AMM account, must use AMMClawback instead of + // Clawback + auto const err = features[featureSingleAssetVault] ? tecPSEUDO_ACCOUNT : tecAMM_ACCOUNT; + AMM const amm(env, gw, XRP(100), BTC(100)); + auto amount = amountFromString(amm.lptIssue(), "10"); + env(claw(gw, amount), ter(err)); + + AMM const amm1(env, alice, USD(100), BTC(200)); + auto amount1 = amountFromString(amm1.lptIssue(), "10"); + env(claw(gw, amount1), ter(err)); + } + + void + testInvalidAMMPayment() + { + testcase("Invalid AMM Payment"); + using namespace jtx; + using namespace jtx::paychan; + using namespace std::chrono; + using namespace std::literals::chrono_literals; + + // Can't pay into AMM account. + // Can't pay out since there is no keys + for (auto const& acct : {gw, alice}) + { + { + Env env(*this); + fund(env, gw, {alice, carol}, XRP(1'000)); + MPTTester const BTC( + {.env = env, + .issuer = gw, + .holders = {alice, carol}, + .pay = 100, + .flags = MPTDEXFlags}); + // XRP balance is below reserve + AMM const ammAlice(env, acct, XRP(10), BTC(10)); + // Pay below reserve + env(pay(carol, ammAlice.ammAccount(), XRP(10)), ter(tecNO_PERMISSION)); + // Pay above reserve + env(pay(carol, ammAlice.ammAccount(), XRP(300)), ter(tecNO_PERMISSION)); + // Pay MPT + env(pay(carol, ammAlice.ammAccount(), BTC(10)), ter(tecNO_PERMISSION)); + } + + { + Env env(*this); + fund(env, gw, {alice, carol}, XRP(10'000'000)); + MPTTester const BTC( + {.env = env, + .issuer = gw, + .holders = {alice, carol}, + .pay = 20'000, + .flags = MPTDEXFlags}); + // XRP balance is above reserve + AMM const ammAlice(env, acct, XRP(1'000'000), BTC(10'000)); + // Pay below reserve + env(pay(carol, ammAlice.ammAccount(), XRP(10)), ter(tecNO_PERMISSION)); + // Pay above reserve + env(pay(carol, ammAlice.ammAccount(), XRP(1'000'000)), ter(tecNO_PERMISSION)); + // Pay MPT + env(pay(carol, ammAlice.ammAccount(), BTC(1'000)), ter(tecNO_PERMISSION)); + } + } + + // Can't pay into AMM with escrow. + testAMM( + [&](AMM& ammAlice, Env& env) { + env(escrow::create(carol, ammAlice.ammAccount(), MPT(ammAlice[1])(1)), + escrow::condition(escrow::cb1), + escrow::finish_time(env.now() + 1s), + escrow::cancel_time(env.now() + 2s), + fee(1'500), + ter(tecNO_PERMISSION)); + + env(escrow::create(carol, ammAlice.ammAccount(), XRP(1)), + escrow::condition(escrow::cb1), + escrow::finish_time(env.now() + 1s), + escrow::cancel_time(env.now() + 2s), + fee(1'500), + ter(tecNO_PERMISSION)); + }, + {{XRP(10'000), AMMMPT(10'000)}}); + + // Can't pay into AMM with paychan. + testAMM( + [&](AMM& ammAlice, Env& env) { + auto const pk = carol.pk(); + auto const settleDelay = 10s; + NetClock::time_point const cancelAfter = + env.current()->header().parentCloseTime + 20s; + env(create( + carol, + ammAlice.ammAccount(), + MPT(ammAlice[1])(1'000), + settleDelay, + pk, + cancelAfter), + ter(telENV_RPC_FAILED)); + + env(create(carol, ammAlice.ammAccount(), XRP(1'000), settleDelay, pk, cancelAfter), + ter(tecNO_PERMISSION)); + }, + {{XRP(10'000), AMMMPT(10'000)}}); + + // Can't pay into AMM with checks. + testAMM( + [&](AMM& ammAlice, Env& env) { + env(check::create(env.master.id(), ammAlice.ammAccount(), XRP(100)), + ter(tecNO_PERMISSION)); + + env(check::create(env.master.id(), ammAlice.ammAccount(), MPT(ammAlice[1])(100)), + ter(tecNO_PERMISSION)); + }, + {{XRP(10'000), AMMMPT(10'000)}}); + + // Pay amounts close to one side of the pool + testAMM( + [&](AMM& ammAlice, Env& env) { + auto const& BTC = MPT(ammAlice[1]); + // Can't consume whole pool + env(pay(alice, carol, USD(100)), + path(~USD), + sendmax(BTC(1'000'000'000)), + ter(tecPATH_PARTIAL)); + env(pay(alice, carol, BTC(100'000'000)), + path(~BTC), + sendmax(USD(1'000'000'000)), + ter(tecPATH_PARTIAL)); + // Overflow + env(pay(alice, carol, STAmount{USD, UINT64_C(99'999999999), -9}), + path(~USD), + sendmax(BTC(1'000'000'000)), + ter(tecPATH_PARTIAL)); + env(pay(alice, carol, STAmount{USD, UINT64_C(999'99999999), -8}), + path(~USD), + sendmax(BTC(1'000'000'000)), + ter(tecPATH_PARTIAL)); + env(pay(alice, carol, BTC(99'999'999)), + path(~BTC), + sendmax(USD(1'000'000'000)), + ter(tecPATH_PARTIAL)); + // Sender doesn't have enough funds + env(pay(alice, carol, USD(99.99)), + path(~USD), + sendmax(BTC(1'000'000'000)), + ter(tecPATH_PARTIAL)); + env(pay(alice, carol, BTC(99'990'000)), + path(~BTC), + sendmax(USD(1'000'000'000)), + ter(tecPATH_PARTIAL)); + }, + {{USD(100), AMMMPT(100'000'000)}}); + + // Globally locked MPT. + { + Env env(*this); + env.fund(XRP(30'000), gw, alice, carol); + MPTTester BTC( + {.env = env, + .issuer = gw, + .holders = {alice, carol}, + .pay = 30'000, + .flags = tfMPTCanLock | MPTDEXFlags}); + + AMM const ammAlice(env, alice, XRP(10'000), BTC(10'000)); + BTC.set({.flags = tfMPTLock}); + + env(pay(alice, carol, BTC(1)), + path(~static_cast(BTC)), + txflags(tfPartialPayment | tfNoRippleDirect), + sendmax(XRP(10)), + ter(tecPATH_DRY)); + env(pay(alice, carol, XRP(1)), + path(~XRP), + txflags(tfPartialPayment | tfNoRippleDirect), + sendmax(BTC(10)), + ter(tecPATH_DRY)); + } + + // Individually locked MPT destination account. + { + Env env(*this); + env.fund(XRP(30'000), gw, alice, carol); + MPTTester BTC( + {.env = env, + .issuer = gw, + .holders = {alice, carol}, + .pay = 30'000, + .flags = tfMPTCanLock | MPTDEXFlags}); + + AMM const ammAlice(env, alice, XRP(10'000), BTC(10'000)); + BTC.set({.holder = carol, .flags = tfMPTLock}); + + env(pay(alice, carol, BTC(1)), + path(~static_cast(BTC)), + txflags(tfPartialPayment | tfNoRippleDirect), + sendmax(XRP(10)), + ter(tecPATH_DRY)); + } + + // Individually locked MPT source account + { + Env env(*this); + env.fund(XRP(30'000), gw, alice, carol); + MPTTester BTC( + {.env = env, + .issuer = gw, + .holders = {alice, carol}, + .pay = 30'000, + .flags = tfMPTCanLock | MPTDEXFlags}); + + AMM const ammAlice(env, alice, XRP(10'000), BTC(10'000)); + BTC.set({.holder = alice, .flags = tfMPTLock}); + + env(pay(alice, carol, XRP(1)), + path(~XRP), + txflags(tfPartialPayment | tfNoRippleDirect), + sendmax(BTC(10)), + ter(tecPATH_DRY)); + } + + // lock on both sides + { + Env env(*this); + env.fund(XRP(30'000), gw, alice, carol); + MPTTester BTC( + {.env = env, + .issuer = gw, + .holders = {alice, carol}, + .pay = 30'000, + .flags = tfMPTCanLock | MPTDEXFlags}); + MPTTester ETH( + {.env = env, + .issuer = gw, + .holders = {alice, carol}, + .pay = 30'000, + .flags = tfMPTCanLock | MPTDEXFlags}); + + AMM const ammAlice(env, alice, ETH(10'000), BTC(10'000)); + BTC.set({.holder = carol, .flags = tfMPTLock}); + BTC.set({.holder = alice, .flags = tfMPTLock}); + ETH.set({.holder = carol, .flags = tfMPTLock}); + ETH.set({.holder = alice, .flags = tfMPTLock}); + + env(pay(alice, carol, ETH(1)), + path(~MPT(ETH)), + txflags(tfPartialPayment | tfNoRippleDirect), + sendmax(BTC(10)), + ter(tecPATH_DRY)); + + env(pay(alice, carol, BTC(1)), + path(~MPT(BTC)), + txflags(tfPartialPayment | tfNoRippleDirect), + sendmax(ETH(10)), + ter(tecPATH_DRY)); + } + + // Individually locked AMM MPT + { + Env env(*this); + env.fund(XRP(30'000), gw, alice, carol); + MPTTester BTC( + {.env = env, + .issuer = gw, + .holders = {alice, carol}, + .pay = 30'000, + .flags = tfMPTCanLock | MPTDEXFlags}); + + AMM const ammAlice(env, alice, XRP(10'000), BTC(10'000)); + BTC.set({.holder = ammAlice.ammAccount(), .flags = tfMPTLock}); + + env(pay(alice, carol, XRP(1)), + path(~XRP), + txflags(tfPartialPayment | tfNoRippleDirect), + sendmax(BTC(10)), + ter(tecPATH_DRY)); + } + } + + void + testBasicPaymentEngine() + { + testcase("Basic Payment"); + using namespace jtx; + + // Payment 100MPT for 100XRP. + // Force one path with tfNoRippleDirect. + testAMM( + [&](AMM& ammAlice, Env& env) { + auto carolMPT = env.balance(carol, MPT(ammAlice[1])); + env.fund(XRP(30'000), bob); + env.close(); + env(pay(bob, carol, MPT(ammAlice[1])(100)), + path(~MPT(ammAlice[1])), + sendmax(XRP(100)), + txflags(tfNoRippleDirect)); + env.close(); + BEAST_EXPECT(ammAlice.expectBalances( + XRP(10'100), MPT(ammAlice[1])(10'000), ammAlice.tokens())); + // Initial balance + 100 + env.require(balance(carol, carolMPT + MPT(ammAlice[1])(100))); + // Initial balance 30,000 - 100(sendmax) - 10(tx fee) + BEAST_EXPECT( + expectLedgerEntryRoot(env, bob, XRP(30'000) - XRP(100) - txfee(env, 1))); + }, + {{XRP(10'000), AMMMPT(10'100)}}); + + // Payment 100IOU/MPT for 100IOU/MPT. Test IOU/MPT mix. + // Force one path with tfNoRippleDirect. + { + auto test = [&](auto&& issue1, auto&& issue2) { + Env env(*this); + env.fund(XRP(30'000), alice, bob, carol, gw); + env.close(); + + auto const USD = issue1( + {.env = env, + .token = "USD", + .issuer = gw, + .holders = {alice, bob, carol}, + .limit = 1'000'000}); + auto const BTC = issue2( + {.env = env, + .token = "BTC", + .issuer = gw, + .holders = {alice, bob, carol}, + .limit = 1'000'000}); + + env(pay(gw, alice, BTC(50000))); + env(pay(gw, bob, BTC(50000))); + env(pay(gw, carol, BTC(50000))); + env(pay(gw, alice, USD(50000))); + env(pay(gw, bob, USD(50000))); + env(pay(gw, carol, USD(50000))); + env.close(); + + auto ammAlice = AMM(env, alice, USD(10000), BTC(10100)); + auto carolBTC = env.balance(carol, BTC); + auto bobUSD = env.balance(bob, USD); + env(pay(bob, carol, BTC(100)), + path(~BTC), + sendmax(USD(100)), + txflags(tfNoRippleDirect | tfPartialPayment)); + env.close(); + BEAST_EXPECT(ammAlice.expectBalances(USD(10'100), BTC(10'000), ammAlice.tokens())); + env.require(balance(carol, carolBTC + BTC(100))); + env.require(balance(bob, bobUSD - USD(100))); + }; + testHelper2TokensMix(test); + } + + // Payment 100MPT for 100XRP, use default path. + testAMM( + [&](AMM& ammAlice, Env& env) { + auto carolMPT = env.balance(carol, MPT(ammAlice[1])); + env.fund(XRP(30'000), bob); + env.close(); + env(pay(bob, carol, MPT(ammAlice[1])(100)), sendmax(XRP(100))); + env.close(); + BEAST_EXPECT(ammAlice.expectBalances( + XRP(10'100), MPT(ammAlice[1])(10'000), ammAlice.tokens())); + // Initial balance + 100 + env.require(balance(carol, carolMPT + MPT(ammAlice[1])(100))); + // Initial balance 30,000 - 100(sendmax) - 10(tx fee) + BEAST_EXPECT( + expectLedgerEntryRoot(env, bob, XRP(30'000) - XRP(100) - txfee(env, 1))); + }, + {{XRP(10'000), AMMMPT(10'100)}}); + + // Payment 100IOU/MPT for 100IOU/MPT using default path. + // Test IOU/MPT mix. + { + auto test = [&](auto&& issue1, auto&& issue2) { + Env env(*this); + env.fund(XRP(30'000), alice, bob, carol, gw); + env.close(); + + auto const USD = issue1( + {.env = env, + .token = "USD", + .issuer = gw, + .holders = {alice, bob, carol}, + .limit = 1'000'000}); + auto const BTC = issue2( + {.env = env, + .token = "BTC", + .issuer = gw, + .holders = {alice, bob, carol}, + .limit = 1'000'000}); + + env(pay(gw, alice, BTC(50000))); + env(pay(gw, bob, BTC(50000))); + env(pay(gw, carol, BTC(50000))); + env(pay(gw, alice, USD(50000))); + env(pay(gw, bob, USD(50000))); + env(pay(gw, carol, USD(50000))); + env.close(); + + auto ammAlice = AMM(env, alice, USD(10000), BTC(10100)); + auto carolBTC = env.balance(carol, BTC); + auto bobUSD = env.balance(bob, USD); + env(pay(bob, carol, BTC(100)), sendmax(USD(100))); + env.close(); + BEAST_EXPECT(ammAlice.expectBalances(USD(10'100), BTC(10'000), ammAlice.tokens())); + env.require(balance(carol, carolBTC + BTC(100))); + env.require(balance(bob, bobUSD - USD(100))); + }; + testHelper2TokensMix(test); + } + + // This payment is identical to above. While it has + // both default path and path, activeStrands has one path. + testAMM( + [&](AMM& ammAlice, Env& env) { + auto carolMPT = env.balance(carol, MPT(ammAlice[1])); + env.fund(XRP(30'000), bob); + env.close(); + env(pay(bob, carol, MPT(ammAlice[1])(100)), + path(~MPT(ammAlice[1])), + sendmax(XRP(100))); + env.close(); + BEAST_EXPECT(ammAlice.expectBalances( + XRP(10'100), MPT(ammAlice[1])(10'000), ammAlice.tokens())); + // Initial balance + 100 + env.require(balance(carol, carolMPT + MPT(ammAlice[1])(100))); + // Initial balance 30,000 - 100(sendmax) - 10(tx fee) + BEAST_EXPECT( + expectLedgerEntryRoot(env, bob, XRP(30'000) - XRP(100) - txfee(env, 1))); + }, + {{XRP(10'000), AMMMPT(10'100)}}); + + // Test MPT/IOU combination for the case above. + { + auto test = [&](auto&& issue1, auto&& issue2) { + Env env(*this); + env.fund(XRP(30'000), alice, bob, carol, gw); + env.close(); + + auto const USD = issue1( + {.env = env, + .token = "USD", + .issuer = gw, + .holders = {alice, bob, carol}, + .limit = 1'000'000}); + auto const BTC = issue2( + {.env = env, + .token = "BTC", + .issuer = gw, + .holders = {alice, bob, carol}, + .limit = 1'000'000}); + + env(pay(gw, alice, BTC(50000))); + env(pay(gw, bob, BTC(50000))); + env(pay(gw, carol, BTC(50000))); + env(pay(gw, alice, USD(50000))); + env(pay(gw, bob, USD(50000))); + env(pay(gw, carol, USD(50000))); + env.close(); + + auto ammAlice = AMM(env, alice, USD(10000), BTC(10100)); + auto carolBTC = env.balance(carol, BTC); + auto bobUSD = env.balance(bob, USD); + env(pay(bob, carol, BTC(100)), path(~BTC), sendmax(USD(100))); + env.close(); + BEAST_EXPECT(ammAlice.expectBalances(USD(10'100), BTC(10'000), ammAlice.tokens())); + env.require(balance(carol, carolBTC + BTC(100))); + env.require(balance(bob, bobUSD - USD(100))); + }; + testHelper2TokensMix(test); + } + + // Payment with limitQuality set. + testAMM( + [&](AMM& ammAlice, Env& env) { + auto carolMPT = env.balance(carol, MPT(ammAlice[1])); + env.fund(jtx::XRP(30'000), bob); + env.close(); + // Pays 10MPT for 10XRP. A larger payment of ~99.11MPT/100XRP + // would have been sent has it not been for limitQuality. + env(pay(bob, carol, MPT(ammAlice[1])(100)), + path(~MPT(ammAlice[1])), + sendmax(XRP(100)), + txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality)); + env.close(); + BEAST_EXPECT(ammAlice.expectBalances( + XRP(10'010), MPT(ammAlice[1])(10'000), ammAlice.tokens())); + // Initial balance + 10(limited by limitQuality) + env.require(balance(carol, carolMPT + MPT(ammAlice[1])(10))); + // Initial balance 30,000 - 10(limited by limitQuality) - 10(tx + // fee) + BEAST_EXPECT( + expectLedgerEntryRoot(env, bob, XRP(30'000) - XRP(10) - txfee(env, 1))); + + // Fails because of limitQuality. Would have sent + // ~98.91MPT/110XRP has it not been for limitQuality. + env(pay(bob, carol, MPT(ammAlice[1])(100)), + path(~MPT(ammAlice[1])), + sendmax(XRP(100)), + txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality), + ter(tecPATH_DRY)); + env.close(); + }, + {{XRP(10'000), AMMMPT(10'010)}}); + + // Payment with limitQuality set. MPT/IOU combination. + { + auto test = [&](auto&& issue1, auto&& issue2) { + Env env(*this); + env.fund(XRP(30'000), alice, bob, carol, gw); + env.close(); + + auto const USD = issue1( + {.env = env, + .token = "USD", + .issuer = gw, + .holders = {alice, bob, carol}, + .limit = 1'000'000}); + auto const BTC = issue2( + {.env = env, + .token = "BTC", + .issuer = gw, + .holders = {alice, bob, carol}, + .limit = 1'000'000}); + + env(pay(gw, alice, BTC(50000))); + env(pay(gw, bob, BTC(50000))); + env(pay(gw, carol, BTC(50000))); + env(pay(gw, alice, USD(50000))); + env(pay(gw, bob, USD(50000))); + env(pay(gw, carol, USD(50000))); + env.close(); + + auto ammAlice = AMM(env, alice, USD(10000), BTC(10010)); + auto carolBTC = env.balance(carol, BTC); + auto bobUSD = env.balance(bob, USD); + env(pay(bob, carol, BTC(100)), + path(~BTC), + sendmax(USD(100)), + txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality)); + env.close(); + BEAST_EXPECT(ammAlice.expectBalances(USD(10'010), BTC(10'000), ammAlice.tokens())); + env.require(balance(carol, carolBTC + BTC(10))); + env.require(balance(bob, bobUSD - USD(10))); + + env(pay(bob, carol, BTC(100)), + path(~BTC), + sendmax(USD(100)), + txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality), + ter(tecPATH_DRY)); + env.close(); + }; + testHelper2TokensMix(test); + } + + // Payment with limitQuality and transfer fee set. + { + Env env(*this); + env.fund(XRP(30'000), gw, alice, bob, carol); + env.close(); + MPTTester const BTC( + {.env = env, + .issuer = gw, + .holders = {alice, carol}, + .transferFee = 10'000, + .pay = 30'000'000'000'000'000, + .flags = MPTDEXFlags}); + auto ammAlice = AMM(env, alice, XRP(10'000), BTC(10'010'000'000'000'000)); + env.close(); + auto carolMPT = env.balance(carol, MPT(BTC)); + // Pays 10'000'000'000'000MPT for 10XRP. A larger payment of + // ~99'110'000'000'000MPT/100XRP would have been sent has it not + // been for limitQuality and the transfer fee. + env(pay(bob, carol, BTC(100'000'000'000'000)), + path(~MPT(BTC)), + sendmax(XRP(110)), + txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality)); + env.close(); + BEAST_EXPECT(ammAlice.expectBalances( + XRP(10'010), BTC(10'000'000'000'000'000), ammAlice.tokens())); + // 10'000'000'000'000MPT - 10% transfer fee + env.require(balance(carol, carolMPT + BTC(9'090'909'090'909))); + BEAST_EXPECT(expectLedgerEntryRoot(env, bob, XRP(30'000) - XRP(10) - txfee(env, 1))); + } + + // Payment with limitQuality and transfer fee set. MPT/IOU combination. + { + auto test = [&](auto&& issue1, auto&& issue2) { + Env env(*this); + env.fund(XRP(30'000), alice, bob, carol, gw); + env.close(); + + auto const USD = issue1({ + .env = env, + .token = "USD", + .issuer = gw, + .holders = {alice, bob, carol}, + .limit = 1'000'000 + //.transferFee = 10'000 + }); + auto const BTC = issue2( + {.env = env, + .token = "BTC", + .issuer = gw, + .holders = {alice, bob, carol}, + .limit = 300'000'000'000'000'000, + .transferFee = 10'000}); + + env(pay(gw, alice, BTC(30'000'000'000'000'000))); + env(pay(gw, bob, BTC(30'000'000'000'000'000))); + env(pay(gw, carol, BTC(30'000'000'000'000'000))); + env(pay(gw, alice, USD(50000))); + env(pay(gw, bob, USD(50000))); + env(pay(gw, carol, USD(50000))); + env.close(); + + auto ammAlice = AMM(env, alice, USD(10'000), BTC(10'010'000'000'000'000)); + auto carolBTC = env.balance(carol, BTC); + auto bobUSD = env.balance(bob, USD); + env(pay(bob, carol, BTC(100'000'000'000'000)), + path(~BTC), + sendmax(USD(110)), + txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality)); + env.close(); + BEAST_EXPECT(ammAlice.expectBalances( + USD(10'010), BTC(10'000'000'000'000'000), ammAlice.tokens())); + env.require(balance(carol, carolBTC + BTC(9'090'909'090'909))); + env.require(balance(bob, bobUSD - USD(10))); + }; + testHelper2TokensMix(test); + } + + // Fail when partial payment is not set. + testAMM( + [&](AMM& ammAlice, Env& env) { + env.fund(jtx::XRP(30'000), bob); + env.close(); + env(pay(bob, carol, MPT(ammAlice[1])(100)), + path(~MPT(ammAlice[1])), + sendmax(XRP(100)), + txflags(tfNoRippleDirect), + ter(tecPATH_PARTIAL)); + }, + {{XRP(10'000), AMMMPT(10'000)}}); + // Fail when partial payment is not set. MPT/IOU combination. + { + auto test = [&](auto&& issue1, auto&& issue2) { + Env env(*this); + env.fund(XRP(30'000), alice, bob, carol, gw); + env.close(); + + auto const USD = issue1( + {.env = env, + .token = "USD", + .issuer = gw, + .holders = {alice, bob, carol}, + .limit = 1'000'000}); + auto const BTC = issue2( + {.env = env, + .token = "BTC", + .issuer = gw, + .holders = {alice, bob, carol}, + .limit = 1'000'000}); + + env(pay(gw, alice, BTC(50000))); + env(pay(gw, bob, BTC(50000))); + env(pay(gw, carol, BTC(50000))); + env(pay(gw, alice, USD(50000))); + env(pay(gw, bob, USD(50000))); + env(pay(gw, carol, USD(50000))); + env.close(); + + auto ammAlice = AMM(env, alice, USD(10000), BTC(10000)); + env(pay(bob, carol, BTC(100)), + path(~BTC), + sendmax(USD(100)), + txflags(tfNoRippleDirect), + ter(tecPATH_PARTIAL)); + }; + testHelper2TokensMix(test); + } + + // Non-default path (with AMM) has a better quality than default path. + // The max possible liquidity is taken out of non-default + // path ~29.9e14XRP/29.9e14ETH, ~29.9e14ETH/~29.99e14btc. The rest + // is taken from the offer. + { + Env env(*this); + env.fund(XRP(30'000), gw, alice, carol); + MPTTester const BTC( + {.env = env, + .issuer = gw, + .holders = {alice, carol}, + .pay = 3'000'000'000'000'000'000, + .flags = MPTDEXFlags}); + MPTTester const ETH( + {.env = env, + .issuer = gw, + .holders = {alice, carol}, + .pay = 3'000'000'000'000'000'000, + .flags = MPTDEXFlags}); + env.fund(XRP(1'000), bob); + env.close(); + auto ammETH_XRP = AMM(env, alice, XRP(10'000), ETH(1'000'000'000'000'000'000)); + auto ammBTC_ETH = + AMM(env, alice, ETH(1'000'000'000'000'000'000), BTC(1'000'000'000'000'000'000)); + env(offer(alice, XRP(101), BTC(10'000'000'000'000'000)), txflags(tfPassive)); + env.close(); + env(pay(bob, carol, BTC(10'000'000'000'000'000)), + path(~MPT(ETH), ~MPT(BTC)), + sendmax(XRP(102)), + txflags(tfPartialPayment)); + env.close(); + BEAST_EXPECT(ammETH_XRP.expectBalances( + XRPAmount(10'030'082'730), ETH(9'970'00749812546872), ammETH_XRP.tokens())); + + BEAST_EXPECT(ammBTC_ETH.expectBalances( + BTC(9'970'09727766213961), ETH(10'029'99250187453128), ammBTC_ETH.tokens())); + + Amounts const expectedAmounts = Amounts{XRPAmount(30'201'749), BTC(29'90272233786039)}; + + BEAST_EXPECT(expectOffers(env, alice, 1, {{expectedAmounts}})); + + // Initial (30,000 + 100)e14 + env.require(balance(carol, BTC(3'010'000'000'000'000'000))); + // Initial 1,000 - 30082730(AMM pool) - 70798251(offer) - 10(tx fee) + BEAST_EXPECT(expectLedgerEntryRoot( + env, + bob, + XRP(1'000) - XRPAmount{30'082'730} - XRPAmount{70'798'251} - txfee(env, 1))); + } + + // Default path (with AMM) has a better quality than a + // non-default path. + // The max possible liquidity is taken out of default + // path ~49XRP/49e14BTC. The rest is taken from the offer. + { + Env env(*this); + env.fund(XRP(30'000), gw, alice, carol); + MPTTester const BTC( + {.env = env, + .issuer = gw, + .holders = {alice, carol}, + .pay = 3'000'000'000'000'000'000, + .flags = MPTDEXFlags}); + MPTTester const ETH( + {.env = env, + .issuer = gw, + .holders = {alice}, + .pay = 1'000'000'000'000'000'000, + .flags = MPTDEXFlags}); + auto ammAlice = AMM(env, alice, XRP(10'000), BTC(1'000'000'000'000'000'000)); + env.fund(XRP(1'000), bob); + env.close(); + env(offer(alice, XRP(101), ETH(10'000'000'000'000'000)), txflags(tfPassive)); + env.close(); + env(offer(alice, ETH(10'000'000'000'000'000), BTC(10'000'000'000'000'000)), + txflags(tfPassive)); + env.close(); + env(pay(bob, carol, BTC(10'000'000'000'000'000)), + path(~MPT(ETH), ~MPT(BTC)), + sendmax(XRP(102)), + txflags(tfPartialPayment)); + env.close(); + BEAST_EXPECT(ammAlice.expectBalances( + XRPAmount(10'050'238'637), BTC(9'950'01249687578120), ammAlice.tokens())); + BEAST_EXPECT(expectOffers( + env, + alice, + 2, + {{Amounts{XRPAmount(50'487'378), ETH(49'98750312421880)}, + Amounts{ETH(49'98750312421880), BTC(49'98750312421880)}}})); + // Initial (30,000 + 100)e14 + env.require(balance(carol, BTC(30'100'00000000000000))); + // Initial 1,000 - 50238637(AMM pool) - 50512622(offer) - 10(tx + // fee) + BEAST_EXPECT(expectLedgerEntryRoot( + env, + bob, + XRP(1'000) - XRPAmount{50'238'637} - XRPAmount{50'512'622} - txfee(env, 1))); + } + + // Default path with AMM and Order Book offer. AMM is consumed first, + // remaining amount is consumed by the offer. + { + Env env(*this); + fund(env, gw, {alice, bob, carol}, XRP(30'000)); + MPTTester const BTC( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol}, + .pay = 30'000'000'000'000'000, + .flags = MPTDEXFlags}); + AMM ammAlice(env, alice, XRP(10'000), BTC(10'100'000'000'000'000)); + env(offer(bob, XRP(100), MPT(ammAlice[1])(100'000'000'000'000)), txflags(tfPassive)); + env.close(); + env(pay(alice, carol, MPT(ammAlice[1])(200'000'000'000'000)), + sendmax(XRP(200)), + txflags(tfPartialPayment)); + env.close(); + + BEAST_EXPECT(ammAlice.expectBalances( + XRP(10'100), MPT(ammAlice[1])(10'000'000000000001), ammAlice.tokens())); + env.require(balance(carol, MPT(ammAlice[1])(30'199'999999999999))); + + // Initial 30,000 - 10000(AMM pool LP) - 100(AMMoffer) - + // - 100(offer) - 10(tx fee) - 10(tx fee of MPTTester init as + // holder) - one reserve + BEAST_EXPECT(expectLedgerEntryRoot( + env, + alice, + XRP(30'000) - XRP(10'000) - XRP(100) - XRP(100) - ammCrtFee(env) - + 2 * txfee(env, 1))); + BEAST_EXPECT(expectOffers(env, bob, 0)); + } + + // Default path with AMM and Order Book offer. + // Order Book offer is consumed first. + // Remaining amount is consumed by AMM. + { + Env env(*this); + fund(env, gw, {alice, bob, carol}, XRP(20'000)); + MPTTester const BTC( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol}, + .pay = 2'000, + .flags = MPTDEXFlags}); + env(offer(bob, XRP(50), BTC(150)), txflags(tfPassive)); + env.close(); + AMM const ammAlice(env, alice, XRP(1'000), BTC(1'050)); + env(pay(alice, carol, BTC(200)), sendmax(XRP(200)), txflags(tfPartialPayment)); + BEAST_EXPECT(ammAlice.expectBalances(XRP(1'050), BTC(1'000), ammAlice.tokens())); + env.require(balance(carol, BTC(2'200))); + BEAST_EXPECT(expectOffers(env, bob, 0)); + } + + // Offer crossing XRP/MPT + { + Env env(*this); + fund(env, gw, {alice, bob, carol}, XRP(30'000)); + MPTTester const BTC( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol}, + .pay = 30'000, + .flags = MPTDEXFlags}); + AMM ammAlice(env, alice, XRP(10'000), BTC(10'100)); + env(offer(bob, MPT(ammAlice[1])(100), XRP(100))); + env.close(); + BEAST_EXPECT( + ammAlice.expectBalances(XRP(10'100), MPT(ammAlice[1])(10'000), ammAlice.tokens())); + // Initial 30,000 + 100 + env.require(balance(bob, MPT(ammAlice[1])(30'100))); + // Initial 30,000 - 100(offer) - 10(tx fee) - 1(tx fee for MPTTester + // holder) + BEAST_EXPECT( + expectLedgerEntryRoot(env, bob, XRP(30'000) - XRP(100) - 2 * txfee(env, 1))); + BEAST_EXPECT(expectOffers(env, bob, 0)); + } + + // Offer crossing MPT/MPT and transfer rate + // Single path AMM offer + { + Env env(*this); + env.fund(XRP(30'000), gw, alice, carol); + MPTTester const BTC( + {.env = env, + .issuer = gw, + .holders = {alice, carol}, + .transferFee = 25'000, + .pay = 30'000'000'000'000'000, + .flags = MPTDEXFlags}); + MPTTester const ETH( + {.env = env, + .issuer = gw, + .holders = {alice, carol}, + .transferFee = 25'000, + .pay = 30'000'000'000'000'000, + .flags = MPTDEXFlags}); + AMM const ammAlice(env, alice, BTC(1'000'000'000'000'000), ETH(1'100'000'000'000'000)); + // This offer succeeds to cross pre- and post-amendment + // because the strand's out amount is small enough to match + // limitQuality value and limitOut() function in StrandFlow + // doesn't require an adjustment to out value. + env(offer(carol, ETH(100'000'000'000'000), BTC(100'000'000'000'000))); + env.close(); + // No transfer fee + BEAST_EXPECT(ammAlice.expectBalances( + BTC(1'100'000'000'000'000), ETH(1'000'000'000'000'000), ammAlice.tokens())); + // Initial 30,000'000'000'000'000 - 100'000'000'000'000(offer)-25 % + // transfer fee + env.require(balance(carol, BTC(29'875'000'000'000'000))); + // Initial 30,000'000'000'000'000 + 100'000'000'000'000(offer) + env.require(balance(carol, ETH(30'100'000'000'000'000))); + BEAST_EXPECT(expectOffers(env, carol, 0)); + } + + // Single-path AMM offer + { + Env env(*this); + env.fund(XRP(30'000), gw, alice, bob, carol); + MPTTester const BTC( + {.env = env, + .issuer = gw, + .holders = {alice, carol}, + .transferFee = 100, + .pay = 30'000'000'000'000'000, + .flags = MPTDEXFlags}); + AMM const amm(env, alice, XRP(1'000), BTC(500'000'000'000'000)); + env(offer(carol, XRP(100), BTC(55'000'000'000'000))); + env.close(); + + BEAST_EXPECT( + amm.expectBalances(XRPAmount(909'090'909), BTC(550'000000055001), amm.tokens())); + // Offer ~91XRP/49.99e12BTC + BEAST_EXPECT(expectOffers( + env, carol, 1, {{Amounts{XRPAmount{9'090'909}, BTC(4'999999950000)}}})); + // Carol pays 0.1% fee on 50'000000055000BTC = 50'000000055BTC + env.require(balance(carol, BTC(29'949'949'999'944'943))); + } + + { + Env env(*this); + env.fund(XRP(30'000), gw, alice, carol); + MPTTester const BTC( + {.env = env, + .issuer = gw, + .holders = {alice, carol}, + .transferFee = 100, + .pay = 3'000'000'000'000'000'000, + .flags = MPTDEXFlags}); + AMM const amm(env, alice, XRP(1'000), BTC(50'000'000'000'000'000)); + env(offer(carol, XRP(10), BTC(5'500'000'000'000'000))); + env.close(); + + BEAST_EXPECT(amm.expectBalances(XRP(990), BTC(505'05050505050506), amm.tokens())); + BEAST_EXPECT(expectOffers(env, carol, 0)); + } + + // Multi-path AMM offer + { + Env env(*this); + Account const ed("ed"); + env.fund(XRP(30'000), gw, alice, bob, carol, ed); + MPTTester const BTC( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol, ed}, + .transferFee = 25'000, + .pay = 20'000'000'000'000'000, + .flags = MPTDEXFlags}); + MPTTester const ETH( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol, ed}, + .transferFee = 25'000, + .pay = 20'000'000'000'000'000, + .flags = MPTDEXFlags}); + AMM const ammAlice( + env, alice, BTC(10'000'000'000'000'000), ETH(11'000'000'000'000'000)); + + env(offer(bob, BTC(100'000'000'000'000), XRP(10)), txflags(tfPassive)); + env(offer(ed, XRP(10), ETH(100'000'000'000'000)), txflags(tfPassive)); + env.close(); + env(offer(carol, ETH(1'000'000'000'000'000), BTC(1'000'000'000'000'000))); + env.close(); + + BEAST_EXPECT(ammAlice.expectBalances( + BTC(1'060'6848287928033), ETH(1'037'0658372213574), ammAlice.tokens())); + // Consumed offer ~72.93e13ETH/72.93e13BTC + BEAST_EXPECT(expectOffers( + env, carol, 1, {Amounts{ETH(27'0658372213574), BTC(27'0658372213575)}})); + BEAST_EXPECT(expectOffers(env, bob, 0)); + BEAST_EXPECT(expectOffers(env, ed, 0)); + + env.require(balance(carol, BTC(19'116'439'640'089'955))); + env.require(balance(carol, ETH(20'729'341'627'786'426))); + env.require(balance(bob, BTC(20'100'000'000'000'000))); + env.require(balance(ed, ETH(19'875'000'000'000'000))); + } + + // Payment and transfer fee + // Scenario: + // Bob sends 125BTC to pay 80EUR to Carol + // Payment execution: + // bob's 125BTC/1.25 = 100BTC + // 100BTC/100EUR AMM offer + // 100EUR/1.25 = 80EUR paid to carol + { + Env env(*this); + env.fund(XRP(30'000), gw, alice, bob, carol); + MPTTester const BTC( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol}, + .transferFee = 25'000, + .pay = 30'000, + .flags = MPTDEXFlags}); + MPTTester const ETH( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol}, + .transferFee = 25'000, + .pay = 30'000, + .flags = MPTDEXFlags}); + AMM const ammAlice(env, alice, BTC(1'000), ETH(1'100)); + env(rate(gw, 1.25)); + env.close(); + env(pay(bob, carol, ETH(100)), + path(~MPT(ETH)), + sendmax(BTC(125)), + txflags(tfPartialPayment)); + env.close(); + BEAST_EXPECT(ammAlice.expectBalances(BTC(1'100), ETH(1'000), ammAlice.tokens())); + env.require(balance(bob, BTC(29'875))); + env.require(balance(carol, ETH(30'080))); + } + + // Payment and transfer fee, multiple steps + // Scenario: + // Dan's offer 200CAN/200GBP + // AMM 1000GBP/10125ETH + // Ed's offer 200ETH/BTC + // Bob sends 195.3125CAN to pay 100BTC to Carol + // Payment execution: + // bob's 195.3125CAN/1.25 = 156.25CAN -> dan's offer + // 156.25CAN/156.25GBP 156.25GBP/1.25 = 125GBP -> AMM's offer + // 125GBP/125ETH 125ETH/1.25 = 100ETH -> ed's offer + // 100ETH/100BTC 100BTC/1.25 = 80BTC paid to carol + { + Env env(*this); + Account const dan("dan"); + Account const ed("ed"); + env.fund(XRP(30'000), gw, alice, bob, carol, dan, ed); + MPTTester const BTC( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol, dan, ed}, + .transferFee = 25'000, + .pay = 30'000, + .flags = MPTDEXFlags}); + MPTTester const ETH( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol, dan, ed}, + .transferFee = 25'000, + .pay = 30'000, + .flags = MPTDEXFlags}); + MPTTester const CAN( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol, dan, ed}, + .transferFee = 25'000, + .pay = 2'000'000, + .flags = MPTDEXFlags}); + MPTTester const GBP( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol, dan, ed}, + .transferFee = 25'000, + .pay = 3'000'000, + .flags = MPTDEXFlags}); + AMM const ammAlice(env, alice, GBP(1'000'000), ETH(10'125)); + env(pay(gw, bob, CAN(1'953'125))); + env.close(); + env(offer(dan, CAN(2'000'000), GBP(20'000))); + env(offer(ed, ETH(200), BTC(200))); + env.close(); + env(pay(bob, carol, BTC(100)), + path(~MPT(GBP), ~MPT(ETH), ~MPT(BTC)), + sendmax(CAN(1'953'125)), + txflags(tfPartialPayment)); + env.close(); + env.require(balance(bob, CAN(2'000'000))); + env.require(balance(dan, CAN(3'562'500))); + env.require(balance(dan, GBP(2'984'375))); + + BEAST_EXPECT(ammAlice.expectBalances(GBP(1'012'500), ETH(10'000), ammAlice.tokens())); + env.require(balance(ed, ETH(30'100))); + env.require(balance(ed, BTC(29'900))); + env.require(balance(carol, BTC(30'080))); + } + + // Pay amounts close to one side of the pool + testAMM( + [&](AMM& ammAlice, Env& env) { + auto const& BTC = MPT(ammAlice[1]); + env(pay(alice, carol, BTC(9999)), + path(~BTC), + sendmax(XRP(1)), + txflags(tfPartialPayment), + ter(tesSUCCESS)); + env(pay(alice, carol, BTC(10'000)), + path(~BTC), + sendmax(XRP(1)), + txflags(tfPartialPayment), + ter(tesSUCCESS)); + env(pay(alice, carol, XRP(100)), + path(~XRP), + sendmax(BTC(100)), + txflags(tfPartialPayment), + ter(tesSUCCESS)); + env(pay(alice, carol, STAmount{xrpIssue(), 99'999'900}), + path(~XRP), + sendmax(BTC(100)), + txflags(tfPartialPayment), + ter(tesSUCCESS)); + }, + {{XRP(100), AMMMPT(10'000)}}); + + // Multiple paths/steps + { + Env env(*this); + env.fund(XRP(100'000), gw, alice); + env.fund(XRP(1'000), bob, carol); + MPTTester const BTC( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol}, + .pay = 500'000'000'000'000'000, + .flags = MPTDEXFlags}); + MPTTester const ETH( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol}, + .pay = 500'000'000'000'000'000, + .flags = MPTDEXFlags}); + MPTTester const USD( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol}, + .pay = 500'000'000'000'000'000, + .flags = MPTDEXFlags}); + MPTTester const EUR( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol}, + .pay = 500'000'000'000'000'000, + .flags = MPTDEXFlags}); + AMM const xrp_eur(env, alice, XRP(10'100), EUR(100'000'000'000'000'000)); + AMM const eur_btc( + env, alice, EUR(100'000'000'000'000'000), BTC(102'000'000'000'000'000)); + AMM const btc_usd( + env, alice, BTC(101'000'000'000'000'000), USD(100'000'000'000'000'000)); + AMM const xrp_usd(env, alice, XRP(10'150), USD(102'000'000'000'000'000)); + AMM const xrp_eth(env, alice, XRP(10'000), ETH(101'000'000'000'000'000)); + AMM const eth_eur( + env, alice, ETH(109'000'000'000'000'000), EUR(110'000'000'000'000'000)); + AMM const eur_usd( + env, alice, EUR(101'000'000'000'000'000), USD(100'000'000'000'000'000)); + env(pay(bob, carol, USD(1'000'000'000'000'000)), + path(~MPT(EUR), ~MPT(BTC), ~MPT(USD)), + path(~MPT(USD)), + path(~MPT(ETH), ~MPT(EUR), ~MPT(USD)), + sendmax(XRP(200))); + + BEAST_EXPECT(xrp_eth.expectBalances( + XRPAmount(10'026'208'900), ETH(10'073'6577924447994), xrp_eth.tokens())); + BEAST_EXPECT(eth_eur.expectBalances( + ETH(10'926'3422075552006), EUR(10'973'5423207873690), eth_eur.tokens())); + BEAST_EXPECT(eur_usd.expectBalances( + EUR(10'126'4576792126310), USD(9'973'9315171207179), eur_usd.tokens())); + // XRP-USD path + // This path provides ~73.9e12USD/74.1XRP + BEAST_EXPECT(xrp_usd.expectBalances( + XRPAmount(10'224'106'246), USD(10'126'0684828792821), xrp_usd.tokens())); + + // XRP-EUR-BTC-USD + // This path doesn't provide any liquidity due to how + // offers are generated in multi-path. + // Analytical solution shows a different distribution: + // XRP-EUR-BTC-USD 11.6e12USD/11.64XRP, + // XRP-USD 60.7e12USD/60.8XRP, + // XRP-ETH-EUR-USD 27.6e12USD/27.6XRP + BEAST_EXPECT(xrp_eur.expectBalances( + XRP(10'100), EUR(100'000'000'000'000'000), xrp_eur.tokens())); + BEAST_EXPECT(eur_btc.expectBalances( + EUR(100'000'000'000'000'000), BTC(102'000'000'000'000'000), eur_btc.tokens())); + BEAST_EXPECT(btc_usd.expectBalances( + BTC(101'000'000'000'000'000), USD(100'000'000'000'000'000), btc_usd.tokens())); + env.require(balance(carol, USD(501'000'000'000'000'000))); + } + + // Dependent AMM + { + Env env(*this); + env.fund(XRP(40'000), gw, alice); + env.fund(XRP(1'000), bob, carol); + MPTTester const BTC( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol}, + .pay = 50'000'000'000'000'000, + .flags = MPTDEXFlags}); + MPTTester const ETH( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol}, + .pay = 50'000'000'000'000'000, + .flags = MPTDEXFlags}); + MPTTester const USD( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol}, + .pay = 50'000'000'000'000'000, + .flags = MPTDEXFlags}); + MPTTester const EUR( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol}, + .pay = 50'000'000'000'000'000, + .flags = MPTDEXFlags}); + AMM const xrp_eur(env, alice, XRP(10'100), EUR(10'000'000'000'000'000)); + AMM const eur_btc(env, alice, EUR(10'000'000'000'000'000), BTC(10'200'000'000'000'000)); + AMM const btc_usd(env, alice, BTC(10'100'000'000'000'000), USD(10'000'000'000'000'000)); + AMM const xrp_eth(env, alice, XRP(10'000), ETH(10'100'000'000'000'000)); + AMM const eth_eur(env, alice, ETH(10'900'000'000'000'000), EUR(11'000'000'000'000'000)); + env(pay(bob, carol, USD(100'000'000'000'000)), + path(~MPT(EUR), ~MPT(BTC), ~MPT(USD)), + path(~MPT(ETH), ~MPT(EUR), ~MPT(BTC), ~MPT(USD)), + sendmax(XRP(200))); + + BEAST_EXPECT(xrp_eur.expectBalances( + XRPAmount(10'118'738'472), EUR(9'981'544436337981), xrp_eur.tokens())); + BEAST_EXPECT(eur_btc.expectBalances( + EUR(10'101'160967851758), BTC(10'097'914269680647), eur_btc.tokens())); + BEAST_EXPECT(btc_usd.expectBalances( + BTC(10'202'085730319353), USD(9'900'000'000'000'000), btc_usd.tokens())); + BEAST_EXPECT(xrp_eth.expectBalances( + XRPAmount(10'082'446'397), ETH(10'017'410727780081), xrp_eth.tokens())); + BEAST_EXPECT(eth_eur.expectBalances( + ETH(10'982'589272219919), EUR(10'917'294595810261), eth_eur.tokens())); + env.require(balance(carol, USD(50'100'000'000'000'000))); + } + + // AMM offers limit + // Consuming 30 CLOB offers, results in hitting 30 AMM offers limit. + { + Env env(*this); + env.fund(XRP(30'000), gw, alice, carol); + env.fund(XRP(1'000), bob); + MPTTester const BTC( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol}, + .pay = 30'000'000'000'000'000, + .flags = MPTDEXFlags}); + MPTTester const ETH( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol}, + .pay = 400'000'000'000'000, + .flags = MPTDEXFlags}); + AMM const ammAlice(env, alice, XRP(10'000), BTC(10'000'000'000'000'000)); + + for (int i = 0; i < 30; ++i) + env(offer(alice, ETH(1'000'000'000'000 + (10'000'000'000 * i)), XRP(1))); + // This is worse quality offer than 30 offers above. + // It will not be consumed because of AMM offers limit. + env(offer(alice, ETH(140'000'000'000'000), XRP(100))); + env(pay(bob, carol, BTC(100'000'000'000'000)), + path(~XRP, ~MPT(BTC)), + sendmax(ETH(400'000'000'000'000)), + txflags(tfPartialPayment | tfNoRippleDirect)); + + BEAST_EXPECT( + ammAlice.expectBalances(XRP(10'030), BTC(9'970'089730807592), ammAlice.tokens())); + + env.require(balance(carol, BTC(30'029'910269192408))); + BEAST_EXPECT(expectOffers(env, alice, 1, {{{ETH(140'000'000'000'000), XRP(100)}}})); + } + + // This payment is fulfilled + { + Env env(*this); + env.fund(XRP(30'000), gw, alice, carol); + env.fund(XRP(1'000), bob); + MPTTester const BTC( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol}, + .pay = 30'000'000'000'000'000, + .flags = MPTDEXFlags}); + MPTTester const ETH( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol}, + .pay = 400'000'000'000'000, + .flags = MPTDEXFlags}); + AMM const ammAlice(env, alice, XRP(10'000), BTC(10'000'000'000'000'000)); + + for (int i = 0; i < 29; ++i) + env(offer(alice, ETH(1'000'000'000'000 + (10'000'000'000 * i)), XRP(1))); + // This is worse quality offer than 30 offers above. + // It will not be consumed because of AMM offers limit. + env(offer(alice, ETH(140'000'000'000'000), XRP(100))); + env(pay(bob, carol, BTC(100'000'000'000'000)), + path(~XRP, ~MPT(BTC)), + sendmax(ETH(400'000'000'000'000)), + txflags(tfPartialPayment | tfNoRippleDirect)); + BEAST_EXPECT(ammAlice.expectBalances( + XRPAmount{10'101'010'102}, BTC(9'900'000'000'000'000), ammAlice.tokens())); + + env.require(balance(carol, BTC(30'100'000'000'000'000))); + BEAST_EXPECT( + expectOffers(env, alice, 1, {{{ETH(39'185857200000), XRPAmount{27'989'898}}}})); + } + + // Offer crossing with AMM and another offer. + // AMM has a better quality and is consumed first. + { + Env env(*this); + env.fund(XRP(30'000), gw, alice, carol); + env.fund(XRP(1'000), bob); + MPTTester const BTC( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol}, + .pay = 30'000'000'000'000'000, + .flags = MPTDEXFlags}); + + env(offer(bob, XRP(100), BTC(100'001'000'000'000))); + AMM const ammAlice(env, alice, XRP(10'000), BTC(10'100'000'000'000'000)); + env(offer(carol, BTC(100'000'000'000'000), XRP(100))); + + BEAST_EXPECT(ammAlice.expectBalances( + XRPAmount{10'049'825'372}, BTC(10'049'925870493027), ammAlice.tokens())); + BEAST_EXPECT( + expectOffers(env, bob, 1, {{{XRPAmount{50'074'628}, BTC(50'075129506973)}}})); + + env.require(balance(carol, BTC(30'100'000'000'000'000))); + } + + // Individually locked MPT destination account + { + Env env(*this); + env.fund(XRP(30'000), gw, alice, carol); + MPTTester BTC( + {.env = env, + .issuer = gw, + .holders = {alice, carol}, + .pay = 30'000, + .flags = tfMPTCanLock | MPTDEXFlags}); + AMM const ammAlice(env, alice, XRP(10'000), BTC(10'000)); + + BTC.set({.holder = carol, .flags = tfMPTLock}); + + env(pay(alice, carol, XRP(1)), + path(~XRP), + sendmax(BTC(10)), + txflags(tfNoRippleDirect | tfPartialPayment), + ter(tesSUCCESS)); + } + + // Individually locked MPT source account + { + Env env(*this); + env.fund(XRP(30'000), gw, alice, carol); + MPTTester BTC( + {.env = env, + .issuer = gw, + .holders = {alice, carol}, + .pay = 30'000, + .flags = tfMPTCanLock | MPTDEXFlags}); + AMM const ammAlice(env, alice, XRP(10'000), BTC(10'000)); + + BTC.set({.holder = alice, .flags = tfMPTLock}); + + env(pay(alice, carol, BTC(1)), + path(~MPT(BTC)), + sendmax(XRP(10)), + txflags(tfNoRippleDirect | tfPartialPayment), + ter(tesSUCCESS)); + } + } + + void + testAMMTokens() + { + testcase("AMM Tokens"); + using namespace jtx; + + // Offer crossing with AMM LPTokens and XRP. + // AMM LPTokens come from MPT/XRP pool. + testAMM( + [&](AMM& ammAlice, Env& env) { + auto const token1 = ammAlice.lptIssue(); + auto priceXRP = ammAssetOut( + STAmount{XRPAmount{10'000'000'000}}, + STAmount{token1, 10'000'000}, + STAmount{token1, 5'000'000}, + 0); + // Carol places an order to buy LPTokens + env(offer(carol, STAmount{token1, 5'000'000}, priceXRP)); + // Alice places an order to sell LPTokens + env(offer(alice, priceXRP, STAmount{token1, 5'000'000})); + // Pool's LPTokens balance doesn't change + BEAST_EXPECT(ammAlice.expectBalances( + XRP(10'000), MPT(ammAlice[1])(10'000), IOUAmount{10'000'000})); + BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{5'000'000})); + BEAST_EXPECT(ammAlice.expectLPTokens(alice, IOUAmount{5'000'000})); + // Carol votes + ammAlice.vote(carol, 1'000); + BEAST_EXPECT(ammAlice.expectTradingFee(500)); + ammAlice.vote(carol, 0); + BEAST_EXPECT(ammAlice.expectTradingFee(0)); + // Carol bids + auto const baseFee = env.current()->fees().base; + auto const carolXRP = env.balance(carol); + env(ammAlice.bid({.account = carol, .bidMin = 100})); + BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{4'999'900})); + BEAST_EXPECT(ammAlice.expectAuctionSlot(0, 0, IOUAmount{100})); + BEAST_EXPECT(env.balance(carol) == (carolXRP - baseFee)); + priceXRP = ammAssetOut( + STAmount{XRPAmount{10'000'000'000}}, + STAmount{token1, 9'999'900}, + STAmount{token1, 4'999'900}, + 0); + // Carol withdraws + ammAlice.withdrawAll(carol, XRP(0)); + BEAST_EXPECT(env.balance(carol) == (carolXRP - baseFee * 2 + priceXRP)); + BEAST_EXPECT(ammAlice.expectBalances( + XRPAmount{10'000'000'000} - priceXRP, + MPT(ammAlice[1])(10'000), + IOUAmount{5'000'000})); + BEAST_EXPECT(ammAlice.expectLPTokens(alice, IOUAmount{5'000'000})); + BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{0})); + }, + {{XRP(10000), AMMMPT(10000)}}); + + // Offer crossing with two AMM LPTokens. + // token1 comes from MPT/XRP pool. + // token2 comes from XRP/IOU pool. + testAMM( + [&](AMM& ammAlice, Env& env) { + ammAlice.deposit(carol, 1'000'000); + fund(env, gw, {alice, carol}, {EUR(10'000)}, Fund::TokenOnly); + AMM ammAlice1(env, alice, XRP(10'000), EUR(10'000)); + ammAlice1.deposit(carol, 1'000'000); + auto const token1 = ammAlice.lptIssue(); + auto const token2 = ammAlice1.lptIssue(); + env(offer(alice, STAmount{token1, 100}, STAmount{token2, 100}), txflags(tfPassive)); + env.close(); + BEAST_EXPECT(expectOffers(env, alice, 1)); + env(offer(carol, STAmount{token2, 100}, STAmount{token1, 100})); + env.close(); + BEAST_EXPECT( + expectHolding(env, alice, STAmount{token1, 10'000'100}) && + expectHolding(env, alice, STAmount{token2, 9'999'900})); + BEAST_EXPECT( + expectHolding(env, carol, STAmount{token2, 1'000'100}) && + expectHolding(env, carol, STAmount{token1, 999'900})); + BEAST_EXPECT(expectOffers(env, alice, 0) && expectOffers(env, carol, 0)); + }, + {{XRP(10000), AMMMPT(10000)}}); + + // LPs pay LPTokens directly. Must trust set because the trust line + // is checked for the limit, which is 0 in the AMM auto-created + // trust line. + testAMM( + [&](AMM& ammAlice, Env& env) { + auto const token1 = ammAlice.lptIssue(); + env.trust(STAmount{token1, 2'000'000}, carol); + env.close(); + ammAlice.deposit(carol, 1'000'000); + BEAST_EXPECT( + ammAlice.expectLPTokens(alice, IOUAmount{10'000'000, 0}) && + ammAlice.expectLPTokens(carol, IOUAmount{1'000'000, 0})); + // Pool balance doesn't change, only tokens moved from + // one line to another. + env(pay(alice, carol, STAmount{token1, 100})); + env.close(); + BEAST_EXPECT( + // Alice initial token1 10,000,000 - 100 + ammAlice.expectLPTokens(alice, IOUAmount{9'999'900, 0}) && + // Carol initial token1 1,000,000 + 100 + ammAlice.expectLPTokens(carol, IOUAmount{1'000'100, 0})); + + env.trust(STAmount{token1, 20'000'000}, alice); + env.close(); + env(pay(carol, alice, STAmount{token1, 100})); + env.close(); + // Back to the original balance + BEAST_EXPECT( + ammAlice.expectLPTokens(alice, IOUAmount{10'000'000, 0}) && + ammAlice.expectLPTokens(carol, IOUAmount{1'000'000, 0})); + }, + {{XRP(10000), AMMMPT(10000)}}); + } + + void + testAmendment() + { + testcase("Amendment"); + using namespace jtx; + FeatureBitset const feature{testable_amendments() - featureMPTokensV2}; + Env env{*this, feature}; + + env.fund(XRP(30'000), gw, alice); + env.close(); + MPT const BTC = MPTTester( + {.env = env, + .issuer = gw, + .holders = {alice}, + .pay = 10'000, + .flags = tfMPTCanClawback | tfMPTCanTransfer}); + + AMM amm(env, alice, XRP(1'000), BTC(1'000), ter(temDISABLED)); + + env(amm.bid({.bidMax = 1000}), ter(temMALFORMED)); + env(amm.bid({}), ter(temDISABLED)); + amm.vote(VoteArg{.tfee = 100, .err = ter(temDISABLED)}); + amm.withdraw(WithdrawArg{.tokens = 100, .err = ter(temMALFORMED)}); + amm.withdraw(WithdrawArg{.err = ter(temDISABLED)}); + amm.deposit(DepositArg{.asset1In = USD(100), .err = ter(temDISABLED)}); + amm.ammDelete(alice, ter(temDISABLED)); + } + + void + testAMMAndCLOB(FeatureBitset features) + { + testcase("AMMAndCLOB, offer quality change"); + using namespace jtx; + auto const gw = Account("gw"); + auto const LP1 = Account("LP1"); + auto const LP2 = Account("LP2"); + + auto prep = [&](auto const& offerCb, auto const& expectCb) { + Env env(*this, features); + env.fund(XRP(30'000'000'000), gw); + env.fund(XRP(10'000), LP1); + env.fund(XRP(10'000), LP2); + MPTTester const TST( + {.env = env, .issuer = gw, .holders = {LP1, LP2}, .flags = MPTDEXFlags}); + + env(offer(gw, XRP(11'500'000'000), TST(1'000'000'000'000'000))); + + env(offer(LP1, TST(25'000'000), XRPAmount(287'500'000))); + + // Either AMM or CLOB offer + offerCb(env, TST); + + env(offer(LP2, TST(25'000'000), XRPAmount(287'500'000))); + + expectCb(env, TST); + }; + + // If we replace AMM with an equivalent CLOB offer, which AMM generates + // when it is consumed, then the result must be equivalent, too. + STAmount lp2TSTBalance; + std::string lp2TakerGets; + std::string lp2TakerPays; + // Execute with AMM first + prep( + [&](Env& env, MPTTester TST) { AMM const amm(env, LP1, TST(25'000'000), XRP(250)); }, + [&](Env& env, MPTTester TST) { + lp2TSTBalance = env.balance(LP2, MPT(TST)); + auto const offer = getAccountOffers(env, LP2)["offers"][0u]; + lp2TakerGets = offer["taker_gets"].asString(); + lp2TakerPays = offer["taker_pays"]["value"].asString(); + }); + // Execute with CLOB offer + prep( + [&](Env& env, MPTTester TST) { + env(offer(LP1, XRPAmount{18'095'131}, TST(1'687'379)), txflags(tfPassive)); + }, + [&](Env& env, MPTTester TST) { + BEAST_EXPECT(lp2TSTBalance == env.balance(LP2, MPT(TST))); + auto const offer = getAccountOffers(env, LP2)["offers"][0u]; + BEAST_EXPECT(lp2TakerGets == offer["taker_gets"].asString()); + BEAST_EXPECT(lp2TakerPays == offer["taker_pays"]["value"].asString()); + }); + } + + void + testTradingFee(FeatureBitset features) + { + testcase("Trading Fee"); + using namespace jtx; + + // Single Deposit, 1% fee + testAMM( + [&](AMM& ammAlice, Env& env) { + // No fee + ammAlice.deposit(carol, MPT(ammAlice[1])(3'000)); + BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{1'000})); + ammAlice.withdrawAll(carol, MPT(ammAlice[1])(3'000)); + BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{0})); + env.require(balance(carol, MPT(ammAlice[1])(30'000))); + // Set fee to 1% + ammAlice.vote(alice, 1'000); + BEAST_EXPECT(ammAlice.expectTradingFee(1'000)); + + // Carol gets fewer LPToken ~994, because of the single deposit + // fee + ammAlice.deposit(carol, MPT(ammAlice[1])(3'000)); + BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{994'981155689671, -12})); + env.require(balance(carol, MPT(ammAlice[1])(27'000))); + + // Set fee to 0 + ammAlice.vote(alice, 0); + ammAlice.withdrawAll(carol, MPT(ammAlice[1])(0)); + + // Carol gets back less than the original deposit + if (!features[fixAMMv1_3]) + { + env.require(balance(carol, MPT(ammAlice[1])(29'995))); + } + else + { + env.require(balance(carol, MPT(ammAlice[1])(29'994))); + } + }, + {{USD(1000), AMMMPT(1000)}}, + 0, + std::nullopt, + {features}); + + // Single deposit with EP not exceeding specified: + // 100MPT with EP not to exceed 0.1 (AssetIn/TokensOut). 1% fee. + testAMM( + [&](AMM& ammAlice, Env& env) { + BEAST_EXPECT(ammAlice.expectTradingFee(1'000)); + auto const balance = env.balance(carol, MPT(ammAlice[1])); + auto const tokensFee = ammAlice.deposit( + carol, + MPT(ammAlice[1])(1000), + std::nullopt, + STAmount{ammAlice.lptIssue(), 1, -1}); + auto const deposit = balance - env.balance(carol, MPT(ammAlice[1])); + ammAlice.withdrawAll(carol, MPT(ammAlice[1])(0)); + ammAlice.vote(alice, 0); + BEAST_EXPECT(ammAlice.expectTradingFee(0)); + auto const tokensNoFee = ammAlice.deposit(carol, deposit); + BEAST_EXPECT(tokensFee == IOUAmount(485636'0611129, -7)); + if (!features[fixAMMv1_3]) + { + BEAST_EXPECT(tokensNoFee == IOUAmount(487659'8005807, -7)); + } + else + { + BEAST_EXPECT(tokensNoFee == IOUAmount(487612'21584827, -8)); + } + }, + {{XRP(10'000), AMMMPT(10'000)}}, + 1'000, + std::nullopt, + {features}); + + // Single deposit with EP not exceeding specified: + // 200MPT with EP not to exceed 0.002020 (AssetIn/TokensOut). 1% fee + testAMM( + [&](AMM& ammAlice, Env& env) { + BEAST_EXPECT(ammAlice.expectTradingFee(1'000)); + auto const balance = env.balance(carol, MPT(ammAlice[1])); + auto const tokensFee = ammAlice.deposit( + carol, + MPT(ammAlice[1])(200), + std::nullopt, + STAmount{ammAlice.lptIssue(), 2020, -6}); + auto const deposit = balance - env.balance(carol, MPT(ammAlice[1])); + ammAlice.withdrawAll(carol, MPT(ammAlice[1])(0)); + ammAlice.vote(alice, 0); + BEAST_EXPECT(ammAlice.expectTradingFee(0)); + auto const tokensNoFee = ammAlice.deposit(carol, deposit); + if (!features[fixAMMv1_3]) + { + BEAST_EXPECT(tokensFee == IOUAmount(98'019'80198019, -8)); + BEAST_EXPECT(tokensNoFee == IOUAmount(98'495'13933556, -8)); + } + else + { + BEAST_EXPECT(tokensFee == IOUAmount(97527'05893345, -8)); + BEAST_EXPECT(tokensNoFee == IOUAmount(98000'10293049, -8)); + } + }, + {{XRP(10'000), AMMMPT(10'000)}}, + 1'000, + std::nullopt, + {features}); + + // Single Withdrawal, 1% fee + testAMM( + [&](AMM& ammAlice, Env& env) { + // No fee + ammAlice.deposit(carol, MPT(ammAlice[1])(3'000)); + BEAST_EXPECT(ammAlice.expectLPTokens(carol, IOUAmount{1'000})); + env.require(balance(carol, MPT(ammAlice[1])(27'000))); + // Set fee to 1% + ammAlice.vote(alice, 1'000); + BEAST_EXPECT(ammAlice.expectTradingFee(1'000)); + // Single withdrawal. Carol gets ~5USD less than deposited. + ammAlice.withdrawAll(carol, MPT(ammAlice[1])(0)); + if (!features[fixAMMv1_3]) + { + env.require(balance(carol, MPT(ammAlice[1])(29'995))); + } + else + { + env.require(balance(carol, MPT(ammAlice[1])(29'994))); + } + }, + {{USD(1000), AMMMPT(1000)}}, + 0, + std::nullopt, + {features}); + + // Withdraw with EPrice limit, 1% fee. + testAMM( + [&](AMM& ammAlice, Env& env) { + ammAlice.deposit(carol, 1'000'000); + auto const tokensFee = ammAlice.withdraw( + carol, MPT(ammAlice[1])(100), std::nullopt, IOUAmount{520, 0}); + env.require(balance(carol, MPT(ammAlice[1])(30443))); + + // Set to original pool size + auto const deposit = + env.balance(carol, MPT(ammAlice[1])) - MPT(ammAlice[1])(29'000); + ammAlice.deposit(carol, deposit); + // fee 0% + ammAlice.vote(alice, 0); + BEAST_EXPECT(ammAlice.expectTradingFee(0)); + auto const tokensNoFee = ammAlice.withdraw(carol, deposit); + if (!features[fixAMMv1_3]) + { + env.require(balance(carol, MPT(ammAlice[1])(30443))); + } + else + { + env.require(balance(carol, MPT(ammAlice[1])(30442))); + } + BEAST_EXPECT(tokensNoFee == IOUAmount(746'327'46496649, -8)); + BEAST_EXPECT(tokensFee == IOUAmount(750'588'23529411, -8)); + }, + {{XRP(10'000), AMMMPT(10'000)}}, + 1'000, + std::nullopt, + {features}); + + // Payment, 1% fee + { + Env env{*this, features}; + env.fund(XRP(30'000), gw, alice, bob, carol); + env.close(); + MPT const BTC = MPTTester( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol}, + .pay = 30'000, + .flags = MPTDEXFlags}); + + auto const USD = gw["USD"]; + env.trust(USD(30'000), alice); + env(pay(gw, alice, USD(30'000))); + env.trust(USD(30'000), bob); + env(pay(gw, bob, USD(1'000))); + env.trust(USD(30'000), carol); + env(pay(gw, carol, USD(30'000))); + env.close(); + + AMM amm(env, alice, BTC(1000), USD(1010)); + + env.require(balance(alice, BTC(29'000))); + env.require(balance(alice, USD(28'990))); + env.require(balance(carol, BTC(30'000))); + + // Carol pays to Alice with no fee + env(pay(carol, alice, USD(10)), + path(~USD), + sendmax(BTC(10)), + txflags(tfNoRippleDirect)); + env.close(); + + // Alice has 10USD more and Carol has 10BTC less + env.require(balance(alice, BTC(29'000))); + env.require(balance(alice, USD(29'000))); + env.require(balance(carol, BTC(29'990))); + + // Set fee to 1% + amm.vote(alice, 1'000); + BEAST_EXPECT(amm.expectTradingFee(1'000)); + // Bob pays to Carol with 1% fee + env(pay(bob, carol, BTC(10)), path(~BTC), sendmax(USD(15)), txflags(tfNoRippleDirect)); + env.close(); + // Bob sends 10.1~USD to pay 10BTC + env.require(balance(bob, STAmount{USD, UINT64_C(989'8989898989899), -13})); + // Carol got 10BTC + env.require(balance(carol, BTC(30'000))); + BEAST_EXPECT(amm.expectBalances( + BTC(1'000), STAmount{USD, UINT64_C(1'010'10101010101), -11}, amm.tokens())); + } + + // Offer crossing, 0.05% fee MPT/XRP + testAMM( + [&](AMM& ammAlice, Env& env) { + auto const BTC = MPT(ammAlice[1]); + auto const carolXRP = env.balance(carol); + auto const baseFee = env.current()->fees().base; + env(offer(carol, BTC(10), XRP(10))); + env.close(); + env.require(balance(carol, BTC(30'010))); + env.require(balance(carol, carolXRP - baseFee - XRP(10))); + + // Change pool composition back + env(offer(carol, XRP(10), BTC(10))); + env.close(); + env.require(balance(carol, BTC(30'000))); + env.require(balance(carol, carolXRP - baseFee * 2)); + + // set fee to 0.05% + ammAlice.vote(alice, 50); + BEAST_EXPECT(ammAlice.expectTradingFee(50)); + env(offer(carol, BTC(10), XRP(10))); + env.close(); + env.require(balance(carol, BTC(30'009))); + env.require(balance(carol, carolXRP - baseFee * 3 - XRPAmount(8'995'507))); + BEAST_EXPECT(expectOffers(env, carol, 1, {{Amounts{BTC(1), XRP(1)}}})); + }, + {{XRP(1000), AMMMPT(1010)}}, + 0, + std::nullopt, + {features}); + + // Offer crossing, 0.5% fee MPT/IOU + testAMM( + [&](AMM& ammAlice, Env& env) { + auto const BTC = MPT(ammAlice[1]); + env(offer(carol, BTC(10'000'000), USD(10))); + env.close(); + env.require(balance(carol, BTC(1'020'001'000))); + env.require(balance(carol, USD(29'990))); + + // Change pool composition back + env(offer(carol, USD(10), BTC(10'000'000))); + env.close(); + env.require(balance(carol, BTC(1'010'001'000))); + env.require(balance(carol, USD(30'000))); + + // set fee to 0.5% + ammAlice.vote(alice, 500); + BEAST_EXPECT(ammAlice.expectTradingFee(500)); + env(offer(carol, BTC(10'000'000), USD(10))); + env.close(); + env.require(balance(carol, BTC(1'014'975'874))); + env.require(balance(carol, STAmount{USD, UINT64_C(29'995'02512600184), -11})); + BEAST_EXPECT(expectOffers( + env, + carol, + 1, + {{Amounts{BTC(5'025126), STAmount{USD, UINT64_C(5'025126), -6}}}})); + }, + {{USD(1000), AMMMPT(1010000000)}}, + 0, + std::nullopt, + {features}); + + // Payment with AMM and CLOB offer, 0 fee + // AMM liquidity is consumed first up to CLOB offer quality + // CLOB offer is fully consumed next + // Remaining amount is consumed via AMM liquidity + { + Env env{*this, features}; + Account const ed("ed"); + fund(env, gw, {alice, bob, carol, ed}, XRP(30'000), {USD(2'000)}); + MPT const BTC = MPTTester( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol, ed}, + .pay = 30'000'000000, + .flags = MPTDEXFlags}); + + env(offer(carol, BTC(5'000000), USD(5))); + AMM const ammAlice(env, alice, USD(1'005), BTC(1'000'000000)); + env(pay(bob, ed, USD(10)), + path(~USD), + sendmax(BTC(15'000000)), + txflags(tfNoRippleDirect)); + + env.require(balance(ed, USD(2'010))); + env.require(balance(bob, BTC(29'989'999999))); + BEAST_EXPECT(ammAlice.expectBalances( + BTC(1'005'000001), + STAmount{USD, UINT64_C(999'9999999999999), -13}, + ammAlice.tokens())); + BEAST_EXPECT(expectOffers(env, carol, 0)); + } + + // Payment with AMM and CLOB offer. Same as above but with 0.25% + // fee. + { + Env env{*this, features}; + Account const ed("ed"); + fund(env, gw, {alice, bob, carol, ed}, XRP(30'000), {USD(2'000)}); + MPT const BTC = MPTTester( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol, ed}, + .pay = 30'000'000000, + .flags = MPTDEXFlags}); + + env(offer(carol, BTC(5'000000), USD(5))); + // Set 0.25% fee + AMM const ammAlice(env, alice, USD(1'005), BTC(1'000'000000), false, 250); + env(pay(bob, ed, USD(10)), + path(~USD), + sendmax(BTC(15'000000)), + txflags(tfNoRippleDirect)); + env.require(balance(ed, USD(2'010))); + env.require(balance(bob, BTC(29'989'987453))); + BEAST_EXPECT(ammAlice.expectBalances(BTC(1'005'012547), USD(1'000), ammAlice.tokens())); + BEAST_EXPECT(expectOffers(env, carol, 0)); + } + + // Payment with AMM and CLOB offer. AMM has a better + // spot price quality, but 1% fee offsets that. As the result + // the entire trade is executed via LOB. + { + Env env{*this, features}; + Account const ed("ed"); + fund(env, gw, {alice, bob, carol, ed}, XRP(30'000), {USD(2'000)}); + MPT const BTC = MPTTester( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol, ed}, + .pay = 30'000'000000, + .flags = MPTDEXFlags}); + + env(offer(carol, BTC(10'000000), USD(10))); + // Set 1% fee + AMM const ammAlice(env, alice, USD(1'005), BTC(1'000'000000), false, 1'000); + env(pay(bob, ed, USD(10)), + path(~USD), + sendmax(BTC(15'000000)), + txflags(tfNoRippleDirect)); + env.require(balance(ed, USD(2'010))); + env.require(balance(bob, BTC(29'990'000000))); + BEAST_EXPECT(ammAlice.expectBalances(BTC(1'000'000000), USD(1'005), ammAlice.tokens())); + BEAST_EXPECT(expectOffers(env, carol, 0)); + } + + // Payment with AMM and CLOB offer. AMM has a better + // spot price quality, but 1% fee offsets that. + // The CLOB offer is consumed first and the remaining + // amount is consumed via AMM liquidity. + { + Env env{*this, features}; + Account const ed("ed"); + fund(env, gw, {alice, bob, carol, ed}, XRP(30'000), {USD(2'000)}); + MPT const BTC = MPTTester( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol, ed}, + .pay = 30'000'000000, + .flags = MPTDEXFlags}); + + env(offer(carol, BTC(9'000000), USD(9))); + // Set 1% fee + AMM const ammAlice(env, alice, USD(1'005), BTC(1'000'000000), false, 1'000); + env(pay(bob, ed, USD(10)), + path(~USD), + sendmax(BTC(15'000000)), + txflags(tfNoRippleDirect)); + env.require(balance(ed, USD(2'010))); + env.require(balance(bob, BTC(29'989'993923))); + BEAST_EXPECT(ammAlice.expectBalances(BTC(1'001'006077), USD(1'004), ammAlice.tokens())); + BEAST_EXPECT(expectOffers(env, carol, 0)); + } + } + + void + testAdjustedTokens(FeatureBitset features) + { + testcase("Adjusted Deposit/Withdraw Tokens"); + using namespace jtx; + + // Deposit/Withdraw USD from USD/MPT pool + { + Env env(*this); + Account const gw{"gateway"}; + Account const alice{"alice"}; + Account const bob("bob"); + Account const carol("carol"); + Account const ed("ed"); + Account const paul("paul"); + Account const dan("dan"); + Account const chris("chris"); + Account const simon("simon"); + Account const ben("ben"); + Account const natalie("natalie"); + std::vector const holders{ + alice, bob, carol, ed, paul, dan, chris, simon, ben, natalie}; + env.fund(XRP(100000), gw, alice, bob, carol, ed, paul, dan, chris, simon, ben, natalie); + env.close(); + + MPT const BTC = MPTTester( + {.env = env, + .issuer = gw, + .holders = holders, + .pay = 40'000'000000, + .flags = MPTDEXFlags}); + + auto const USD = gw["USD"]; + for (auto const& holder : holders) + { + env.trust(USD(1'500'000), holder); + env(pay(gw, holder, USD(1'500'000))); + } + env.close(); + + auto aliceUSD = env.balance(alice, USD); + auto bobUSD = env.balance(bob, USD); + auto carolUSD = env.balance(carol, USD); + auto edUSD = env.balance(ed, USD); + auto paulUSD = env.balance(paul, USD); + auto danUSD = env.balance(dan, USD); + auto chrisUSD = env.balance(chris, USD); + auto simonUSD = env.balance(simon, USD); + auto benUSD = env.balance(ben, USD); + auto natalieUSD = env.balance(natalie, USD); + + AMM ammAlice(env, alice, BTC(10'000'000000), USD(10000)); + BEAST_EXPECT( + ammAlice.expectBalances(BTC(10'000'000000), USD(10'000), IOUAmount{10'000'000})); + + for (int i = 0; i < 10; ++i) + { + ammAlice.deposit(ben, STAmount{USD, 1, -10}); + ammAlice.withdrawAll(ben, USD(0)); + ammAlice.deposit(simon, USD(0.1)); + ammAlice.withdrawAll(simon, USD(0)); + ammAlice.deposit(chris, USD(1)); + ammAlice.withdrawAll(chris, USD(0)); + ammAlice.deposit(dan, USD(10)); + ammAlice.withdrawAll(dan, USD(0)); + ammAlice.deposit(bob, USD(100)); + ammAlice.withdrawAll(bob, USD(0)); + ammAlice.deposit(carol, USD(1'000)); + ammAlice.withdrawAll(carol, USD(0)); + ammAlice.deposit(ed, USD(10'000)); + ammAlice.withdrawAll(ed, USD(0)); + ammAlice.deposit(paul, USD(100'000)); + ammAlice.withdrawAll(paul, USD(0)); + ammAlice.deposit(natalie, USD(1'000'000)); + ammAlice.withdrawAll(natalie, USD(0)); + } + + BEAST_EXPECT(ammAlice.expectBalances( + BTC(10'000'000000), + STAmount{USD, UINT64_C(10000'0000000001), -10}, + IOUAmount{10'000'000})); + + env.require(balance(bob, bobUSD)); + env.require(balance(carol, carolUSD)); + env.require(balance(ed, edUSD)); + env.require(balance(paul, paulUSD)); + env.require(balance(dan, danUSD)); + env.require(balance(chris, chrisUSD)); + env.require(balance(simon, simonUSD)); + env.require(balance(ben, benUSD)); + env.require(balance(natalie, natalieUSD)); + + ammAlice.withdrawAll(alice); + BEAST_EXPECT(!ammAlice.ammExists()); + env.require(balance(alice, aliceUSD)); + } + + // Same as above but deposit/withdraw MPT from USD/MPT pool + { + Env env(*this); + Account const gw{"gateway"}; + Account const alice{"alice"}; + Account const bob("bob"); + Account const carol("carol"); + Account const ed("ed"); + Account const paul("paul"); + Account const dan("dan"); + Account const chris("chris"); + Account const simon("simon"); + Account const ben("ben"); + Account const natalie("natalie"); + std::vector const holders{ + alice, bob, carol, ed, paul, dan, chris, simon, ben, natalie}; + env.fund(XRP(100000), gw, alice, bob, carol, ed, paul, dan, chris, simon, ben, natalie); + env.close(); + + MPT const BTC = MPTTester( + {.env = env, + .issuer = gw, + .holders = holders, + .pay = 40'000'000000, + .flags = MPTDEXFlags}); + + auto const USD = gw["USD"]; + for (auto const& holder : holders) + { + env.trust(USD(1'500'000), holder); + env(pay(gw, holder, USD(1'500'000))); + } + env.close(); + + auto aliceBTC = env.balance(alice, BTC); + auto bobBTC = env.balance(bob, BTC); + auto carolBTC = env.balance(carol, BTC); + auto edBTC = env.balance(ed, BTC); + auto paulBTC = env.balance(paul, BTC); + auto danBTC = env.balance(dan, BTC); + auto chrisBTC = env.balance(chris, BTC); + auto simonBTC = env.balance(simon, BTC); + auto benBTC = env.balance(ben, BTC); + auto natalieBTC = env.balance(natalie, BTC); + + AMM ammAlice(env, alice, BTC(10'000'000000), USD(10000)); + BEAST_EXPECT( + ammAlice.expectBalances(BTC(10'000'000000), USD(10'000), IOUAmount{10'000'000})); + + for (int i = 0; i < 10; ++i) + { + ammAlice.deposit(ben, BTC(1)); + ammAlice.withdrawAll(ben, BTC(0)); + ammAlice.deposit(simon, BTC(1'000)); + ammAlice.withdrawAll(simon, BTC(0)); + ammAlice.deposit(chris, BTC(1)); + ammAlice.withdrawAll(chris, BTC(0)); + ammAlice.deposit(dan, BTC(10)); + ammAlice.withdrawAll(dan, BTC(0)); + ammAlice.deposit(bob, BTC(100)); + ammAlice.withdrawAll(bob, BTC(0)); + ammAlice.deposit(carol, BTC(1'000)); + ammAlice.withdrawAll(carol, BTC(0)); + ammAlice.deposit(ed, BTC(10'000)); + ammAlice.withdrawAll(ed, BTC(0)); + ammAlice.deposit(paul, BTC(100'000)); + ammAlice.withdrawAll(paul, BTC(0)); + ammAlice.deposit(natalie, BTC(1'000'000)); + ammAlice.withdrawAll(natalie, BTC(0)); + } + + BEAST_EXPECT( + ammAlice.expectBalances(BTC(10'000'000090), USD(10'000), IOUAmount{10'000'000})); + + env.require(balance(bob, bobBTC - BTC(10))); + env.require(balance(carol, carolBTC - BTC(10))); + env.require(balance(ed, edBTC - BTC(10))); + env.require(balance(paul, paulBTC - BTC(10))); + env.require(balance(dan, danBTC - BTC(10))); + env.require(balance(chris, chrisBTC - BTC(10))); + env.require(balance(simon, simonBTC - BTC(10))); + env.require(balance(ben, benBTC - BTC(10))); + env.require(balance(natalie, natalieBTC - BTC(10))); + + ammAlice.withdrawAll(alice); + BEAST_EXPECT(!ammAlice.ammExists()); + env.require(balance(alice, aliceBTC + BTC(90))); + } + } + + void + testAMMID() + { + testcase("AMMID"); + using namespace jtx; + + // MPT/XRP + testAMM( + [&](AMM& amm, Env& env) { + amm.setClose(false); + auto const info = env.rpc( + "json", + "account_info", + std::string("{\"account\": \"" + to_string(amm.ammAccount()) + "\"}")); + try + { + BEAST_EXPECT( + info[jss::result][jss::account_data][jss::AMMID].asString() == + to_string(amm.ammID())); + } + catch (...) + { + fail(); + } + + amm.deposit(carol, 1'000); + + auto affected = env.meta()->getJson(JsonOptions::none)[sfAffectedNodes.fieldName]; + try + { + bool found = false; + for (auto const& node : affected) + { + if (node.isMember(sfModifiedNode.fieldName) && + node[sfModifiedNode.fieldName][sfLedgerEntryType.fieldName] + .asString() == "AccountRoot" && + node[sfModifiedNode.fieldName][sfFinalFields.fieldName][jss::Account] + .asString() == to_string(amm.ammAccount())) + { + found = + node[sfModifiedNode.fieldName][sfFinalFields.fieldName][jss::AMMID] + .asString() == to_string(amm.ammID()); + break; + } + } + BEAST_EXPECT(found); + } + catch (...) + { + fail(); + } + }, + {{XRP(1000), AMMMPT(1000'000)}}); + } + + void + testSelection(FeatureBitset features) + { + testcase("Offer/Strand Selection"); + using namespace jtx; + Account const ed("ed"); + Account const gw1("gw1"); + + // These tests are expected to fail if the OwnerPaysFee feature + // is ever supported. Updates will need to be made to AMM handling + // in the payment engine, and these tests will need to be updated. + + struct MPTList + { + MPTTester const USD; + MPTTester const ETH; + MPTTester const CAN; + }; + + auto prep = [&](Env& env, uint16_t gwTransferFee, uint16_t gw1TransferFee) -> MPTList { + env.fund(XRP(2'000), gw, gw1, alice, bob, carol, ed); + MPTTester USD( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol, ed}, + .transferFee = gwTransferFee, + .pay = 2'000'000'000, + .flags = MPTDEXFlags}); + MPTTester ETH( + {.env = env, + .issuer = gw1, + .holders = {alice, bob, carol, ed}, + .transferFee = gw1TransferFee, + .pay = 2'000'000'000, + .flags = MPTDEXFlags}); + MPTTester CAN( + {.env = env, + .issuer = gw1, + .holders = {alice, bob, carol, ed}, + .transferFee = gw1TransferFee, + .pay = 2'000'000'000, + .flags = MPTDEXFlags}); + env.close(); + + return MPTList{ + .USD = std::move(USD), + .ETH = std::move(ETH), + .CAN = std::move(CAN), + }; + }; + + std::uint32_t constexpr lowRate = 10'000; + std::uint32_t constexpr highRate = 50'000; + for (auto const& rates : + {std::make_pair(lowRate, highRate), std::make_pair(highRate, lowRate)}) + { + // Offer Selection + + // Cross-currency payment: AMM has the same spot price quality + // as CLOB's offer and can't generate a better quality offer. + // The transfer fee in this case doesn't change the CLOB quality + // because trIn is ignored on adjustment and trOut on payment is + // also ignored because ownerPaysTransferFee is false in this + // case. Run test for 0) offer, 1) AMM, 2) offer and AMM to + // verify that the quality is better in the first case, and CLOB + // is selected in the second case. + { + std::array q{}; + for (auto i = 0; i < 3; ++i) + { + Env env(*this, features); + auto mpts = prep(env, rates.first, rates.second); + auto USD = mpts.USD; + auto ETH = mpts.ETH; + auto CAN = mpts.CAN; + std::optional amm; + + if (i == 0 || i == 2) + { + env(offer(ed, ETH(400'000'000), USD(400'000'000)), txflags(tfPassive)); + env.close(); + } + if (i > 0) + amm.emplace(env, ed, USD(1'000'000'000), ETH(1'000'000'000)); + env(pay(carol, bob, USD(100'000'000)), + path(~MPT(USD)), + sendmax(ETH(500'000'000))); + env.close(); + // CLOB and AMM, AMM is not selected + if (i == 2) + { + BEAST_EXPECT(amm->expectBalances( + USD(1'000'000'000), ETH(1'000'000'000), amm->tokens())); + } + env.require(balance(bob, USD(2'100'000'000))); + q[i] = Quality( + Amounts{ + ETH(2'000'000'000) - env.balance(carol, MPT(ETH)), + env.balance(bob, MPT(USD)) - USD(2'000'000'000)}); + } + // CLOB is better quality than AMM + BEAST_EXPECT(q[0] > q[1]); + // AMM is not selected with CLOB + BEAST_EXPECT(q[0] == q[2]); + } + // Offer crossing: AMM has the same spot price quality + // as CLOB's offer and can't generate a better quality offer. + // The transfer fee in this case doesn't change the CLOB quality + // because the quality adjustment is ignored for the offer + // crossing. + for (auto i = 0; i < 3; ++i) + { + Env env(*this, features); + auto mpts = prep(env, rates.first, rates.second); + auto USD = mpts.USD; + auto ETH = mpts.ETH; + auto CAN = mpts.CAN; + std::optional amm; + if (i == 0 || i == 2) + { + env(offer(ed, ETH(400'000'000), USD(400'000'000)), txflags(tfPassive)); + env.close(); + } + if (i > 0) + amm.emplace(env, ed, USD(1'000'000'000), ETH(1'000'000'000)); + env(offer(alice, USD(400'000'000), ETH(400'000'000))); + env.close(); + // AMM is not selected + if (i > 0) + { + BEAST_EXPECT( + amm->expectBalances(USD(1'000'000'000), ETH(1'000'000'000), amm->tokens())); + } + if (i == 0 || i == 2) + { + // Fully crosses + BEAST_EXPECT(expectOffers(env, alice, 0)); + } + // Fails to cross because AMM is not selected + else + { + BEAST_EXPECT( + expectOffers(env, alice, 1, {Amounts{USD(400'000'000), ETH(400'000'000)}})); + } + BEAST_EXPECT(expectOffers(env, ed, 0)); + } + + // Show that the CLOB quality reduction + // results in AMM offer selection. + + // Same as the payment but reduced offer quality + { + std::array q{}; + for (auto i = 0; i < 3; ++i) + { + Env env(*this, features); + auto mpts = prep(env, rates.first, rates.second); + auto USD = mpts.USD; + auto ETH = mpts.ETH; + auto CAN = mpts.CAN; + std::optional amm; + if (i == 0 || i == 2) + { + env(offer(ed, ETH(400'000'000), USD(330'000'000)), txflags(tfPassive)); + env.close(); + } + if (i > 0) + amm.emplace(env, ed, USD(1'000'000'000), ETH(1'000'000'000)); + env(pay(carol, bob, USD(100'000'000)), + path(~MPT(USD)), + sendmax(ETH(500'000'000))); + env.close(); + // AMM and CLOB are selected + if (i > 0) + { + BEAST_EXPECT(!amm->expectBalances( + USD(1'000'000'000), ETH(1'000'000'000), amm->tokens())); + } + + if (i == 2) + { + if (rates.first == lowRate) + { + BEAST_EXPECT(expectOffers( + env, + ed, + 1, + {{Amounts{ + ETH(377'824'111), + USD(311'704'892), + }}})); + } + else + { + BEAST_EXPECT(expectOffers( + env, + ed, + 1, + {{Amounts{ + ETH(329'339'263), + USD(271'704'892), + }}})); + } + } + env.require(balance(bob, USD(2'100'000'000))); + q[i] = Quality( + Amounts{ + ETH(2'000'000'000) - env.balance(carol, MPT(ETH)), + env.balance(bob, MPT(USD)) - USD(2'000'000'000)}); + } + // AMM is better quality + BEAST_EXPECT(q[1] > q[0]); + // AMM and CLOB produce better quality + BEAST_EXPECT(q[2] > q[1]); + } + + // Same as the offer-crossing but reduced offer quality + for (auto i = 0; i < 3; ++i) + { + Env env(*this, features); + auto mpts = prep(env, rates.first, rates.second); + auto USD = mpts.USD; + auto ETH = mpts.ETH; + auto CAN = mpts.CAN; + std::optional amm; + if (i == 0 || i == 2) + { + env(offer(ed, ETH(400'000'000), USD(325'000'002)), txflags(tfPassive)); + env.close(); + } + if (i > 0) + amm.emplace(env, ed, USD(1'000'000'000), ETH(1'000'000'000)); + env(offer(alice, USD(325'000'000), ETH(400'000'000))); + env.close(); + // AMM is selected in both cases + if (i > 0) + { + BEAST_EXPECT(!amm->expectBalances( + USD(1'000'000'000), ETH(1'000'000'000), amm->tokens())); + } + // Partially crosses, AMM is selected, CLOB fails + // limitQuality + if (i == 2) + { + if (rates.first == lowRate) + { + // Ed offer is partially crossed. + // The updated rounding makes limitQuality + // work if both amendments are enabled + BEAST_EXPECT(expectOffers( + env, + ed, + 1, + {{Amounts{ + ETH(121'368'836), + USD(98'612'180), + }}})); + BEAST_EXPECT(expectOffers(env, alice, 0)); + } + else + { + // Ed offer is partially crossed. + BEAST_EXPECT(expectOffers( + env, + ed, + 1, + {{Amounts{ + ETH(121'368'836), + USD(98'612'180), + }}})); + BEAST_EXPECT(expectOffers(env, alice, 0)); + } + } + } + + // Strand selection + + // Two book steps strand quality is 1. + // AMM strand's best quality is equal to AMM's spot price + // quality, which is 1. Both strands (steps) are adjusted + // for the transfer fee in qualityUpperBound. In case + // of two strands, AMM offers have better quality and are + // consumed first, remaining liquidity is generated by CLOB + // offers. Liquidity from two strands is better in this case + // than in case of one strand with two book steps. Liquidity + // from one strand with AMM has better quality than either one + // strand with two book steps or two strands. It may appear + // unintuitive, but one strand with AMM is optimized and + // generates one AMM offer, while in case of two strands, + // multiple AMM offers are generated, which results in slightly + // worse overall quality. + { + std::array q{}; + for (auto i = 0; i < 3; ++i) + { + Env env(*this, features); + auto mpts = prep(env, rates.first, rates.second); + auto USD = mpts.USD; + auto ETH = mpts.ETH; + auto CAN = mpts.CAN; + std::optional amm; + + if (i == 0 || i == 2) + { + env(offer(ed, ETH(400'000'000), CAN(375'000'000)), txflags(tfPassive)); + env(offer(ed, CAN(375'000'000), USD(338'000'000))), txflags(tfPassive); + } + + if (i > 0) + amm.emplace(env, ed, ETH(1'000'000'000), USD(1'000'000'000)); + + env(pay(carol, bob, USD(100'000'000)), + path(~MPT(USD)), + path(~MPT(CAN), ~MPT(USD)), + sendmax(ETH(600'000'000))); + env.close(); + + env.require(balance(bob, USD(2'100'000'000))); + + if (i == 2) + { + if (rates.first == lowRate) + { + // Liquidity is consumed from AMM strand only + BEAST_EXPECT(amm->expectBalances( + ETH(1'124'584'936), USD(889'999'993), amm->tokens())); + } + else + { + BEAST_EXPECT(amm->expectBalances( + ETH(1'103'723'909), USD(906'023'688), amm->tokens())); + BEAST_EXPECT(expectOffers( + env, + ed, + 2, + {{Amounts{ + ETH(327'069'745), + CAN(306'627'886), + }, + Amounts{ + CAN(312'843'533), + USD(281'976'305), + }}})); + } + } + q[i] = Quality( + Amounts{ + ETH(2'000'000'000) - env.balance(carol, MPT(ETH)), + env.balance(bob, MPT(USD)) - USD(2'000'000'000)}); + } + BEAST_EXPECT(q[1] > q[0]); + BEAST_EXPECT(q[2] > q[0] && q[2] < q[1]); + } + } + } + + void + testMalformed() + { + testcase("Malformed"); + using namespace jtx; + + testAMM( + [&](AMM& ammAlice, Env& env) { + WithdrawArg const args{ + .flags = tfSingleAsset, + .err = ter(temMALFORMED), + }; + ammAlice.withdraw(args); + }, + {{XRP(10'000), AMMMPT(10'000)}}); + + testAMM( + [&](AMM& ammAlice, Env& env) { + WithdrawArg const args{ + .flags = tfOneAssetLPToken, + .err = ter(temMALFORMED), + }; + ammAlice.withdraw(args); + }, + {{XRP(10'000), AMMMPT(10'000)}}); + + testAMM( + [&](AMM& ammAlice, Env& env) { + WithdrawArg const args{ + .flags = tfLimitLPToken, + .err = ter(temMALFORMED), + }; + ammAlice.withdraw(args); + }, + {{XRP(10'000), AMMMPT(10'000)}}); + + testAMM( + [&](AMM& ammAlice, Env& env) { + WithdrawArg const args{ + .asset1Out = MPT(ammAlice[1])(100), + .asset2Out = MPT(ammAlice[1])(100), + .err = ter(temBAD_AMM_TOKENS), + }; + ammAlice.withdraw(args); + }, + {{XRP(10'000), AMMMPT(10'000)}}); + + testAMM( + [&](AMM& ammAlice, Env& env) { + MPTTester const BTC( + {.env = env, + .issuer = gw, + .holders = {alice, carol}, + .pay = 2'000, + .flags = tfMPTCanLock | MPTDEXFlags}); + WithdrawArg const args{ + .asset1Out = XRP(100), + .asset2Out = BTC(100), + .err = ter(temBAD_AMM_TOKENS), + }; + ammAlice.withdraw(args); + }, + {{XRP(10'000), AMMMPT(10'000)}}); + + testAMM( + [&](AMM& ammAlice, Env& env) { + Json::Value jv; + jv[jss::TransactionType] = jss::AMMWithdraw; + jv[jss::Flags] = tfLimitLPToken; + jv[jss::Account] = alice.human(); + ammAlice.setTokens(jv); + XRP(100).value().setJson(jv[jss::Amount]); + MPTTester const BTC( + {.env = env, + .issuer = gw, + .holders = {alice, carol}, + .pay = 2'000, + .flags = tfMPTCanLock | MPTDEXFlags}); + BTC(100).value().setJson(jv[jss::EPrice]); + env(jv, ter(telENV_RPC_FAILED)); + }, + {{XRP(10'000), AMMMPT(10'000)}}); + } + + void + testFixAMMOfferBlockedByLOB(FeatureBitset features) + { + testcase("AMM Offer Blocked By LOB"); + using namespace jtx; + + // Low quality LOB offer blocks AMM liquidity + + // USD/MPT crosses AMM despite of low quality LOB + { + Env env(*this, features); + + fund(env, gw, {alice, carol}, XRP(1'000'000), {USD(1'000'000)}); + MPT const BTC = MPTTester( + {.env = env, + .issuer = gw, + .holders = {alice, carol}, + .pay = 40'000'000000, + .flags = MPTDEXFlags}); + + env(offer(alice, BTC(1), USD(0.01))); + env.close(); + AMM const amm(env, gw, BTC(200'000), USD(100'000)); + env(offer(carol, USD(0.49), BTC(1))); + env.close(); + + if (!features[fixAMMv1_1] && !features[fixAMMv1_3]) + { + BEAST_EXPECT(amm.expectBalances(BTC(200'000), USD(100'000), amm.tokens())); + BEAST_EXPECT(expectOffers(env, alice, 1, {{Amounts{BTC(1), USD(0.01)}}})); + BEAST_EXPECT(expectOffers(env, carol, 1, {{Amounts{USD(0.49), BTC(1)}}})); + } + + if (features[fixAMMv1_1] && features[fixAMMv1_3]) + { + BEAST_EXPECT(amm.expectBalances(BTC(200'001), USD(99'999.51), amm.tokens())); + BEAST_EXPECT(expectOffers(env, alice, 1, {{Amounts{BTC(1), USD(0.01)}}})); + // Carol's offer crosses AMM + BEAST_EXPECT(expectOffers(env, carol, 0)); + } + } + + // XRP/MPT crosses AMM despite of low quality LOB + { + Env env(*this, features); + + fund(env, gw, {alice, carol}, XRP(1'000'000), {USD(1'000'000)}); + MPT const BTC = MPTTester( + {.env = env, + .issuer = gw, + .holders = {alice, carol}, + .pay = 40'000'000000, + .flags = MPTDEXFlags}); + + env(offer(alice, BTC(1), XRP(0.01))); + env.close(); + AMM const amm(env, gw, BTC(200'000), XRP(100'000)); + env(offer(carol, XRP(0.49), BTC(1))); + env.close(); + + if (!features[fixAMMv1_1] && !features[fixAMMv1_3]) + { + BEAST_EXPECT(amm.expectBalances(BTC(200'000), XRP(100'000), amm.tokens())); + BEAST_EXPECT(expectOffers(env, alice, 1, {{Amounts{BTC(1), XRP(0.01)}}})); + BEAST_EXPECT(expectOffers(env, carol, 1, {{Amounts{XRP(0.49), BTC(1)}}})); + } + + if (features[fixAMMv1_1] && features[fixAMMv1_3]) + { + BEAST_EXPECT(amm.expectBalances(BTC(200'001), XRP(99'999.51), amm.tokens())); + BEAST_EXPECT(expectOffers(env, alice, 1, {{Amounts{BTC(1), XRP(0.01)}}})); + // Carol's offer crosses AMM + BEAST_EXPECT(expectOffers(env, carol, 0)); + } + } + } + + void + testLPTokenBalance(FeatureBitset features) + { + testcase("LPToken Balance"); + using namespace jtx; + + Env env(*this, features); + Account const gw{"gateway"}, alice{"alice"}, bob{"bob"}; + env.fund(XRP(100000), gw, alice, bob); + env.close(); + env(fset(gw, asfAllowTrustLineClawback)); + env.close(); + + auto const USD = gw["USD"]; + env.trust(USD(100000), alice); + env(pay(gw, alice, USD(50000))); + env.trust(USD(100000), bob); + env(pay(gw, bob, USD(40000))); + env.close(); + + MPT const BTC = MPTTester( + {.env = env, + .issuer = gw, + .holders = {alice, bob}, + .pay = 40'000'000000, + .flags = tfMPTCanClawback | tfMPTCanLock | MPTDEXFlags}); + + AMM amm(env, alice, BTC(2), USD(1)); + amm.deposit(alice, IOUAmount{1'876123487565916, -15}); + amm.deposit(bob, IOUAmount{1'000}); + amm.withdraw(alice, IOUAmount{1'876123487565916, -15}); + amm.withdrawAll(bob); + + auto const lpToken = + getAccountLines(env, alice, amm.lptIssue())[jss::lines][0u][jss::balance].asString(); + auto const lpTokenBalance = + amm.ammRpcInfo()[jss::amm][jss::lp_token][jss::value].asString(); + + BEAST_EXPECT(lpToken == "1.414213562374011" && lpTokenBalance == "1.4142135623741"); + + auto res = isOnlyLiquidityProvider(*env.current(), amm.lptIssue(), alice); + BEAST_EXPECT(res && res.value()); + + amm.withdrawAll(alice); + BEAST_EXPECT(!amm.ammExists()); + } + + void + testAMMDepositWithFrozenAssets() + { + testcase("test AMMDeposit with frozen assets"); + using namespace jtx; + + // This lambda function is used to create trustline, MPT. + // and create an AMM account. + // And also test the callback function. + auto testAMMDeposit = [&](Env& env, std::function cb) { + env.fund(XRP(1'000), gw, alice); + env.close(); + MPTTester BTC( + {.env = env, + .issuer = gw, + .holders = {alice}, + .pay = 30'000, + .flags = tfMPTCanLock | MPTDEXFlags}); + + AMM amm(env, alice, BTC(100), XRP(100)); + env.close(); + BTC.set({.holder = alice, .flags = tfMPTLock}); + cb(amm, BTC); + }; + + // Deposit two assets, one of which is frozen, + // then we should get tecFROZEN error. + { + Env env(*this); + testAMMDeposit(env, [&](AMM& amm, MPTTester& BTC) { + amm.deposit(alice, BTC(100), XRP(100), std::nullopt, tfTwoAsset, ter(tecFROZEN)); + }); + } + + // Deposit one asset, which is the frozen token, + // then we should get tecFROZEN error. + { + Env env(*this); + testAMMDeposit(env, [&](AMM& amm, MPTTester& BTC) { + amm.deposit( + alice, BTC(100), std::nullopt, std::nullopt, tfSingleAsset, ter(tecFROZEN)); + }); + } + + // Deposit one asset which is not the frozen token, + // but the other asset is frozen. We should get tecFROZEN error + // when feature AMMClawback is enabled. + { + Env env(*this); + testAMMDeposit(env, [&](AMM& amm, MPTTester& BTC) { + amm.deposit( + alice, XRP(100), std::nullopt, std::nullopt, tfSingleAsset, ter(tecFROZEN)); + }); + } + } + + void + testAutoDelete() + { + testcase("Auto Delete"); + + using namespace jtx; + FeatureBitset const all{testable_amendments()}; + + { + Env env( + *this, + envconfig([](std::unique_ptr cfg) { + cfg->FEES.reference_fee = XRPAmount(1); + return cfg; + }), + all); + env.fund(XRP(1'000), gw, alice); + MPTTester const USD({.env = env, .issuer = gw, .holders = {alice}, .pay = 20'000}); + MPTTester const BTC({.env = env, .issuer = gw, .holders = {alice}, .pay = 20'000}); + AMM amm(env, gw, USD(10'000), BTC(10'000)); + for (auto i = 0; i < maxDeletableAMMTrustLines + 10; ++i) + { + Account const a{std::to_string(i)}; + env.fund(XRP(1'000), a); + env(trust(a, STAmount{amm.lptIssue(), 10'000})); + env.close(); + } + // The trustlines are partially deleted, + // AMM is set to an empty state. + amm.withdrawAll(gw); + BEAST_EXPECT(amm.ammExists()); + + // Bid,Vote,Deposit,Withdraw,SetTrust failing with + // tecAMM_EMPTY. Deposit succeeds with tfTwoAssetIfEmpty option. + env(amm.bid({ + .account = alice, + .bidMin = 1000, + }), + ter(tecAMM_EMPTY)); + amm.vote({.account = alice, .tfee = 100, .err = ter(tecAMM_EMPTY)}); + amm.withdraw({.account = alice, .tokens = 100, .err = ter(tecAMM_EMPTY)}); + amm.deposit({.account = alice, .asset1In = USD(100), .err = ter(tecAMM_EMPTY)}); + env(trust(alice, STAmount{amm.lptIssue(), 10'000}), ter(tecAMM_EMPTY)); + + // Can deposit with tfTwoAssetIfEmpty option + amm.deposit( + {.account = alice, + .asset1In = USD(1'000), + .asset2In = BTC(1'000), + .flags = tfTwoAssetIfEmpty, + .tfee = 1'000}); + BEAST_EXPECT(amm.expectBalances(USD(1'000), BTC(1'000), IOUAmount{1'000})); + BEAST_EXPECT(amm.expectTradingFee(1'000)); + BEAST_EXPECT(amm.expectAuctionSlot(100, 0, IOUAmount{0})); + + // Withdrawing all tokens deletes AMM since the number + // of remaining trustlines is less than max + amm.withdrawAll(alice); + BEAST_EXPECT(!amm.ammExists()); + BEAST_EXPECT(!env.le(keylet::ownerDir(amm.ammAccount()))); + } + + { + Env env( + *this, + envconfig([](std::unique_ptr cfg) { + cfg->FEES.reference_fee = XRPAmount(1); + return cfg; + }), + all); + env.fund(XRP(1'000), gw, alice); + MPTTester const USD({.env = env, .issuer = gw, .holders = {alice}, .pay = 20'000}); + MPTTester const BTC({.env = env, .issuer = gw, .holders = {alice}, .pay = 20'000}); + AMM amm(env, gw, USD(10'000), BTC(10'000)); + for (auto i = 0; i < (maxDeletableAMMTrustLines * 2) + 10; ++i) + { + Account const a{std::to_string(i)}; + env.fund(XRP(1'000), a); + env(trust(a, STAmount{amm.lptIssue(), 10'000})); + env.close(); + } + // The trustlines are partially deleted. + amm.withdrawAll(gw); + BEAST_EXPECT(amm.ammExists()); + + // AMMDelete has to be called twice to delete AMM. + amm.ammDelete(alice, ter(tecINCOMPLETE)); + BEAST_EXPECT(amm.ammExists()); + // Deletes remaining trustlines and deletes AMM. + amm.ammDelete(alice); + BEAST_EXPECT(!amm.ammExists()); + BEAST_EXPECT(!env.le(keylet::ownerDir(amm.ammAccount()))); + + // Try redundant delete + amm.ammDelete(alice, ter(terNO_AMM)); + } + + { + Env env( + *this, + envconfig([](std::unique_ptr cfg) { + cfg->FEES.reference_fee = XRPAmount(1); + return cfg; + }), + all); + env.fund(XRP(1'000), gw, alice, carol); + MPTTester const USD( + {.env = env, .issuer = gw, .holders = {alice, carol}, .pay = 20'000}); + MPTTester const BTC( + {.env = env, .issuer = gw, .holders = {alice, carol}, .pay = 20'000}); + AMM amm(env, gw, USD(10'000), BTC(10'000)); + amm.deposit({.account = alice, .tokens = 1'000}); + amm.deposit({.account = carol, .tokens = 1'000}); + amm.withdrawAll(alice); + amm.withdrawAll(carol); + amm.withdrawAll(gw); + BEAST_EXPECT(!amm.ammExists()); + } + + // This test validates both invariant changes work together for + // the specific case of MPT/MPT pools with > maxDeletableAMMTrustLines. + { + Env env( + *this, + envconfig([](std::unique_ptr cfg) { + cfg->FEES.reference_fee = XRPAmount(1); + return cfg; + }), + all); + + env.fund(XRP(1'000), gw, alice); + MPT const USD = + MPTTester({.env = env, .issuer = gw, .holders = {alice}, .pay = 20'000}); + MPT const BTC = + MPTTester({.env = env, .issuer = gw, .holders = {alice}, .pay = 20'000}); + + // MPT/MPT pool with MANY trustlines + AMM amm(env, gw, USD(10'000), BTC(10'000)); + for (auto i = 0; i < (maxDeletableAMMTrustLines * 2) + 10; ++i) + { + Account const a{std::to_string(i)}; + env.fund(XRP(1'000), a); + env(trust(a, STAmount{amm.lptIssue(), 10'000})); + env.close(); + } + + amm.withdrawAll(gw); + // AMM is in empty state, but can't be auto-deleted because of the LPTokens trustlines. + BEAST_EXPECT(amm.expectBalances(USD(0), BTC(0), IOUAmount(0))); + BEAST_EXPECT(amm.ammExists()); + + // Critical: MPT/MPT pool + tecINCOMPLETE + amm.ammDelete(alice, ter(tecINCOMPLETE)); + BEAST_EXPECT(amm.ammExists()); + + amm.ammDelete(alice); + BEAST_EXPECT(!amm.ammExists()); + } + } + + void + run() override + { + FeatureBitset const all{jtx::testable_amendments()}; + testInstanceCreate(); + testInvalidInstance(); + testInvalidDeposit(all); + testInvalidDeposit(all - featureAMMClawback); + testDeposit(); + testInvalidWithdraw(); + testWithdraw(); + testInvalidFeeVote(); + testFeeVote(); + testInvalidBid(); + testBid(all); + testClawback(); + testClawbackFromAMMAccount(all); + testClawbackFromAMMAccount(all - featureSingleAssetVault); + testInvalidAMMPayment(); + testBasicPaymentEngine(); + testAMMTokens(); + testAmendment(); + testAMMAndCLOB(all); + testTradingFee(all); + testTradingFee(all - fixAMMv1_3); + testAdjustedTokens(all); + testAMMID(); + testSelection(all); + testMalformed(); + testFixAMMOfferBlockedByLOB(all - fixAMMv1_1 - fixAMMv1_3); + testFixAMMOfferBlockedByLOB(all); + testLPTokenBalance(all); + testLPTokenBalance(all - fixAMMv1_3); + testAMMDepositWithFrozenAssets(); + testAutoDelete(); + } +}; + +BEAST_DEFINE_TESTSUITE_PRIO(AMMMPT, app, xrpl, 1); + +} // namespace xrpl::test diff --git a/src/test/app/AMM_test.cpp b/src/test/app/AMM_test.cpp index c19eb971a7..d2d7fbf2c7 100644 --- a/src/test/app/AMM_test.cpp +++ b/src/test/app/AMM_test.cpp @@ -1,28 +1,70 @@ -#include #include #include #include #include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include #include #include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include #include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include -namespace xrpl { -namespace test { +namespace xrpl::test { /** * Basic tests of AMM that do not use offers. @@ -135,11 +177,11 @@ private: // Make sure asset comparison works. BEAST_EXPECT( - STIssue(sfAsset, STAmount(XRP(2'000)).issue()) == - STIssue(sfAsset, STAmount(XRP(2'000)).issue())); + STIssue(sfAsset, STAmount(XRP(2'000)).asset()) == + STIssue(sfAsset, STAmount(XRP(2'000)).asset())); BEAST_EXPECT( - STIssue(sfAsset, STAmount(XRP(2'000)).issue()) != - STIssue(sfAsset, STAmount(USD(2'000)).issue())); + STIssue(sfAsset, STAmount(XRP(2'000)).asset()) != + STIssue(sfAsset, STAmount(USD(2'000)).asset())); } void @@ -288,8 +330,11 @@ private: env.close(); env(trust(gw, alice["USD"](30'000))); env.close(); - AMM const ammAlice(env, alice, XRP(10'000), USD(10'000), ter(tecFROZEN)); - BEAST_EXPECT(!ammAlice.ammExists()); + for (auto const& account : {alice, gw}) + { + AMM const amm(env, account, XRP(10'000), USD(10'000), ter(tecFROZEN)); + BEAST_EXPECT(!amm.ammExists()); + } } // Individually frozen @@ -303,6 +348,8 @@ private: env.close(); AMM const ammAlice(env, alice, XRP(10'000), USD(10'000), ter(tecFROZEN)); BEAST_EXPECT(!ammAlice.ammExists()); + // issuer can create + AMM const amm(env, gw, XRP(10'000), USD(10'000)); } // Insufficient reserve, XRP/IOU @@ -359,7 +406,7 @@ private: // AMM with one LPToken from another AMM. testAMM([&](AMM& ammAlice, Env& env) { - fund(env, gw, {alice}, {EUR(10'000)}, Fund::IOUOnly); + fund(env, gw, {alice}, {EUR(10'000)}, Fund::TokenOnly); AMM const ammAMMToken( env, alice, @@ -376,7 +423,7 @@ private: // AMM with two LPTokens from other AMMs. testAMM([&](AMM& ammAlice, Env& env) { - fund(env, gw, {alice}, {EUR(10'000)}, Fund::IOUOnly); + fund(env, gw, {alice}, {EUR(10'000)}, Fund::TokenOnly); AMM const ammAlice1(env, alice, XRP(10'000), EUR(10'000)); auto const token1 = ammAlice.lptIssue(); auto const token2 = ammAlice1.lptIssue(); @@ -585,7 +632,7 @@ private: alice, BAD(100), std::nullopt, std::nullopt, std::nullopt, ter(temBAD_CURRENCY)); // Invalid Account - Account bad("bad"); + Account const bad("bad"); env.memoize(bad); ammAlice.deposit( bad, @@ -713,11 +760,20 @@ private: ammAlice.deposit( carol, XRP(100), std::nullopt, std::nullopt, std::nullopt, ter(tecFROZEN)); } - ammAlice.deposit( - carol, USD(100), std::nullopt, std::nullopt, std::nullopt, ter(tecFROZEN)); - ammAlice.deposit(carol, 1'000'000, std::nullopt, std::nullopt, ter(tecFROZEN)); - ammAlice.deposit( - carol, XRP(100), USD(100), std::nullopt, std::nullopt, ter(tecFROZEN)); + for (auto const& account : {carol, gw}) + { + ammAlice.deposit( + account, + USD(100), + std::nullopt, + std::nullopt, + std::nullopt, + ter(tecFROZEN)); + ammAlice.deposit( + account, 1'000'000, std::nullopt, std::nullopt, ter(tecFROZEN)); + ammAlice.deposit( + account, XRP(100), USD(100), std::nullopt, std::nullopt, ter(tecFROZEN)); + } }, std::nullopt, 0, @@ -1579,7 +1635,7 @@ private: ammAlice.withdraw(alice, BAD(100), std::nullopt, std::nullopt, ter(temBAD_CURRENCY)); // Invalid Account - Account bad("bad"); + Account const bad("bad"); env.memoize(bad); ammAlice.withdraw( bad, @@ -1678,12 +1734,16 @@ private: // Globally frozen asset testAMM([&](AMM& ammAlice, Env& env) { + ammAlice.deposit({.account = gw, .asset1In = USD(1'000), .asset2In = XRP(1'000)}); env(fset(gw, asfGlobalFreeze)); env.close(); // Can withdraw non-frozen token - ammAlice.withdraw(alice, XRP(100)); - ammAlice.withdraw(alice, USD(100), std::nullopt, std::nullopt, ter(tecFROZEN)); - ammAlice.withdraw(alice, 1'000, std::nullopt, std::nullopt, ter(tecFROZEN)); + for (auto const& account : {alice, gw}) + { + ammAlice.withdraw(account, XRP(100)); + ammAlice.withdraw(account, USD(100), std::nullopt, std::nullopt, ter(tecFROZEN)); + ammAlice.withdraw(account, 1'000, std::nullopt, std::nullopt, ter(tecFROZEN)); + } }); // Individually frozen (AMM) account @@ -2192,7 +2252,7 @@ private: BEAST_EXPECT(ammAlice.expectTradingFee(0)); // Invalid Account - Account bad("bad"); + Account const bad("bad"); env.memoize(bad); ammAlice.vote(bad, 1'000, std::nullopt, seq(1), std::nullopt, ter(terNO_ACCOUNT)); @@ -2426,7 +2486,7 @@ private: ter(tecAMM_INVALID_TOKENS)); // Invalid Account - Account bad("bad"); + Account const bad("bad"); env.memoize(bad); env(ammAlice.bid({ .account = bad, @@ -2489,10 +2549,10 @@ private: // More than four Auth accounts. testAMM([&](AMM& ammAlice, Env& env) { - Account ed("ed"); - Account bill("bill"); - Account scott("scott"); - Account james("james"); + Account const ed("ed"); + Account const bill("bill"); + Account const scott("scott"); + Account const james("james"); env.fund(XRP(1'000), bob, ed, bill, scott, james); env.close(); ammAlice.deposit(carol, 1'000'000); @@ -3026,8 +3086,8 @@ private: BEAST_EXPECT(ammAlice.expectAuctionSlot({carol})); env(ammAlice.bid({.account = alice, .bidMin = IOUAmount{100}})); BEAST_EXPECT(ammAlice.expectAuctionSlot({})); - Account bob("bob"); - Account dan("dan"); + Account const bob("bob"); + Account const dan("dan"); fund(env, {bob, dan}, XRP(1'000)); env(ammAlice.bid({ .account = alice, @@ -4046,7 +4106,7 @@ private: testAMM( [&](AMM& ammAlice, Env& env) { env.fund(XRP(1'000), bob); - fund(env, gw, {bob}, {EUR(400)}, Fund::IOUOnly); + fund(env, gw, {bob}, {EUR(400)}, Fund::TokenOnly); env(trust(alice, EUR(200))); for (int i = 0; i < 30; ++i) env(offer(alice, EUR(1.0 + (0.01 * i)), XRP(1))); @@ -4086,7 +4146,7 @@ private: testAMM( [&](AMM& ammAlice, Env& env) { env.fund(XRP(1'000), bob); - fund(env, gw, {bob}, {EUR(400)}, Fund::IOUOnly); + fund(env, gw, {bob}, {EUR(400)}, Fund::TokenOnly); env(trust(alice, EUR(200))); for (int i = 0; i < 29; ++i) env(offer(alice, EUR(1.0 + (0.01 * i)), XRP(1))); @@ -4224,7 +4284,7 @@ private: // Offer crossing with two AMM LPTokens. testAMM([&](AMM& ammAlice, Env& env) { ammAlice.deposit(carol, 1'000'000); - fund(env, gw, {alice, carol}, {EUR(10'000)}, Fund::IOUOnly); + fund(env, gw, {alice, carol}, {EUR(10'000)}, Fund::TokenOnly); AMM ammAlice1(env, alice, XRP(10'000), EUR(10'000)); ammAlice1.deposit(carol, 1'000'000); auto const token1 = ammAlice.lptIssue(); @@ -5194,7 +5254,7 @@ private: auto prep = [&](Env& env, auto gwRate, auto gw1Rate) { fund(env, gw, {alice, carol, bob, ed}, XRP(2'000), {USD(2'000)}); env.fund(XRP(2'000), gw1); - fund(env, gw1, {alice, carol, bob, ed}, {ETH(2'000), CAN(2'000)}, Fund::IOUOnly); + fund(env, gw1, {alice, carol, bob, ed}, {ETH(2'000), CAN(2'000)}, Fund::TokenOnly); env(rate(gw, gwRate)); env(rate(gw1, gw1Rate)); env.close(); @@ -5824,7 +5884,7 @@ private: takerGets}; } auto const takerPays = - toAmount(getIssue(poolIn), Number{1, -10} * poolIn); + toAmount(getAsset(poolIn), Number{1, -10} * poolIn); return Amounts{ takerPays, swapAssetIn(Amounts{poolIn, poolOut}, takerPays, tfee)}; }(); @@ -5915,6 +5975,7 @@ private: } void + // NOLINTNEXTLINE(readability-convert-member-functions-to-static) testFixOverflowOffer(FeatureBitset featuresInitial) { using namespace jtx; @@ -6472,7 +6533,7 @@ private: Account const gw1("gw1"); auto const YAN = gw1["YAN"]; fund(env, gw, {gw1}, XRP(1'000), {USD(1'000)}); - fund(env, gw1, {gw}, XRP(1'000), {YAN(1'000)}, Fund::IOUOnly); + fund(env, gw1, {gw}, XRP(1'000), {YAN(1'000)}, Fund::TokenOnly); AMM amm(env, gw1, YAN(10), USD(10)); amm.deposit(gw, 1'000); auto res = isOnlyLiquidityProvider(*env.current(), amm.lptIssue(), gw); @@ -6652,7 +6713,7 @@ private: STAmount const amount = XRP(10'000); STAmount const amount2 = USD(10'000); - auto const keylet = keylet::amm(amount.issue(), amount2.issue()); + auto const keylet = keylet::amm(amount.asset(), amount2.asset()); for (int i = 0; i < 256; ++i) { AccountID const accountId = xrpl::pseudoAccountAddress(*env.current(), keylet.key); @@ -6930,7 +6991,7 @@ private: // tfSingleAsset withdraw mode // Note: This test fails with 0 trading fees, but doesn't fail if // trading fees is set to 1'000 -- I suspect the compound operations - // in AMMHelpers.cpp:withdrawByTokens compensate for the rounding + // in AMMHelpers.cpp:ammAssetOut compensate for the rounding // errors testAMM( [&](AMM& ammAlice, Env& env) { @@ -7074,5 +7135,4 @@ private: BEAST_DEFINE_TESTSUITE_PRIO(AMM, app, xrpl, 1); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/app/AccountDelete_test.cpp b/src/test/app/AccountDelete_test.cpp index 4382fb27c7..c2ecb91a84 100644 --- a/src/test/app/AccountDelete_test.cpp +++ b/src/test/app/AccountDelete_test.cpp @@ -1,10 +1,50 @@ -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include #include -namespace xrpl { -namespace test { +#include +#include +#include +#include + +namespace xrpl::test { class AccountDelete_test : public beast::unit_test::suite { @@ -1050,5 +1090,4 @@ public: BEAST_DEFINE_TESTSUITE_PRIO(AccountDelete, app, xrpl, 2); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/app/AccountSet_test.cpp b/src/test/app/AccountSet_test.cpp index 246f18c445..15012abe36 100644 --- a/src/test/app/AccountSet_test.cpp +++ b/src/test/app/AccountSet_test.cpp @@ -1,13 +1,54 @@ -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include +#include +#include #include #include +#include +#include +#include +#include +#include +#include #include #include +#include +#include +#include +#include +#include +#include +#include + namespace xrpl { class AccountSet_test : public beast::unit_test::suite @@ -84,7 +125,7 @@ public: continue; } - if (std::find(goodFlags.begin(), goodFlags.end(), flag) != goodFlags.end()) + if (std::ranges::find(goodFlags, flag) != goodFlags.end()) { // Good flag env.require(nflags(alice, flag)); @@ -320,13 +361,13 @@ public: doTests( testable_amendments(), - {{1.0, tesSUCCESS, 1.0}, - {1.1, tesSUCCESS, 1.1}, - {2.0, tesSUCCESS, 2.0}, - {2.1, temBAD_TRANSFER_RATE, 2.0}, - {0.0, tesSUCCESS, 1.0}, - {2.0, tesSUCCESS, 2.0}, - {0.9, temBAD_TRANSFER_RATE, 2.0}}); + {{.set = 1.0, .code = tesSUCCESS, .get = 1.0}, + {.set = 1.1, .code = tesSUCCESS, .get = 1.1}, + {.set = 2.0, .code = tesSUCCESS, .get = 2.0}, + {.set = 2.1, .code = temBAD_TRANSFER_RATE, .get = 2.0}, + {.set = 0.0, .code = tesSUCCESS, .get = 1.0}, + {.set = 2.0, .code = tesSUCCESS, .get = 2.0}, + {.set = 0.9, .code = temBAD_TRANSFER_RATE, .get = 2.0}}); } void diff --git a/src/test/app/AccountTxPaging_test.cpp b/src/test/app/AccountTxPaging_test.cpp index 1f2e909927..d5c299b782 100644 --- a/src/test/app/AccountTxPaging_test.cpp +++ b/src/test/app/AccountTxPaging_test.cpp @@ -1,7 +1,13 @@ -#include -#include -#include +#include +#include +#include +#include +#include + +#include +#include +#include #include #include diff --git a/src/test/app/AmendmentTable_test.cpp b/src/test/app/AmendmentTable_test.cpp index 007715f9d1..7edaef3821 100644 --- a/src/test/app/AmendmentTable_test.cpp +++ b/src/test/app/AmendmentTable_test.cpp @@ -1,21 +1,44 @@ #include +#include #include +#include +#include #include #include -#include +#include +#include #include -#include +#include +#include +#include #include +#include #include +#include #include +#include +#include #include #include #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + namespace xrpl { class AmendmentTable_test final : public beast::unit_test::suite @@ -419,7 +442,7 @@ public: BEAST_EXPECT(table->unVeto(unvetoedID)); std::vector const desired = table->getDesired(); - BEAST_EXPECT(std::find(desired.begin(), desired.end(), unvetoedID) != desired.end()); + BEAST_EXPECT(std::ranges::find(desired, unvetoedID) != desired.end()); } // Veto all supported amendments. Now desired should be empty. @@ -954,10 +977,9 @@ public: // We need a hash_set to pass to trustChanged. hash_set trustedValidators; trustedValidators.reserve(validators.size()); - std::for_each( - validators.begin(), validators.end(), [&trustedValidators](auto const& val) { - trustedValidators.insert(val.first); - }); + std::ranges::for_each(validators, [&trustedValidators](auto const& val) { + trustedValidators.insert(val.first); + }); // Tell the AmendmentTable that the UNL changed. table->trustChanged(trustedValidators); @@ -1151,9 +1173,8 @@ public: BEAST_EXPECT(table->needValidatedLedger(1)); std::set enabled; - std::for_each(unsupported_.begin(), unsupported_.end(), [&enabled](auto const& s) { - enabled.insert(amendmentId(s)); - }); + std::ranges::for_each( + unsupported_, [&enabled](auto const& s) { enabled.insert(amendmentId(s)); }); majorityAmendments_t majority; table->doValidatedLedger(1, enabled, majority); @@ -1161,12 +1182,9 @@ public: BEAST_EXPECT(!table->firstUnsupportedExpected()); NetClock::duration t{1000s}; - std::for_each( - unsupportedMajority_.begin(), - unsupportedMajority_.end(), - [&majority, &t](auto const& s) { - majority[amendmentId(s)] = NetClock::time_point{--t}; - }); + std::ranges::for_each(unsupportedMajority_, [&majority, &t](auto const& s) { + majority[amendmentId(s)] = NetClock::time_point{--t}; + }); table->doValidatedLedger(1, enabled, majority); BEAST_EXPECT(table->hasUnsupportedEnabled()); diff --git a/src/test/app/Batch_test.cpp b/src/test/app/Batch_test.cpp index f301b6d60f..6d8e1f2de3 100644 --- a/src/test/app/Batch_test.cpp +++ b/src/test/app/Batch_test.cpp @@ -1,22 +1,78 @@ -#include +#include +#include #include +#include +#include +#include // IWYU pragma: keep +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include #include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include #include #include +#include +#include +#include +#include +#include +#include #include -#include +#include +#include +#include +#include +#include #include +#include +#include +#include #include #include #include #include -namespace xrpl { -namespace test { +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl::test { class Batch_test : public beast::unit_test::suite { @@ -2601,10 +2657,9 @@ class Batch_test : public beast::unit_test::suite { testcase("loan"); - bool const lendingBatchEnabled = !std::any_of( - Batch::disabledTxTypes.begin(), Batch::disabledTxTypes.end(), [](auto const& disabled) { - return disabled == ttLOAN_BROKER_SET; - }); + bool const lendingBatchEnabled = !std::ranges::any_of( + Batch::disabledTxTypes, + [](auto const& disabled) { return disabled == ttLOAN_BROKER_SET; }); using namespace test::jtx; @@ -4155,8 +4210,12 @@ class Batch_test : public beast::unit_test::suite std::vector const testCases = { {0, "Batch", "tesSUCCESS", batchID, std::nullopt}, {1, "TrustSet", "tesSUCCESS", txIDs[0], batchID}, + // jv2 fails with terNO_DELEGATE_PERMISSION. }; validateClosedLedger(env, testCases); + + // verify jv2 is not present in the closed ledger. + BEAST_EXPECT(env.rpc("tx", txIDs[1])[jss::result][jss::error] == "txnNotFound"); } } @@ -4363,5 +4422,4 @@ public: BEAST_DEFINE_TESTSUITE(Batch, app, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/app/CheckMPT_test.cpp b/src/test/app/CheckMPT_test.cpp new file mode 100644 index 0000000000..fb9d413e6e --- /dev/null +++ b/src/test/app/CheckMPT_test.cpp @@ -0,0 +1,2205 @@ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl { + +class CheckMPT_test : public beast::unit_test::suite +{ + // Helper function that returns the Checks on an account. + static std::vector> + checksOnAccount(test::jtx::Env& env, test::jtx::Account account) + { + std::vector> result; + forEachItem(*env.current(), account, [&result](std::shared_ptr const& sle) { + if (sle && sle->getType() == ltCHECK) + result.push_back(sle); + }); + return result; + } + + // Helper function that verifies the expected DeliveredAmount is present. + // + // NOTE: the function _infers_ the transaction to operate on by calling + // env.tx(), which returns the result from the most recent transaction. + void + verifyDeliveredAmount(test::jtx::Env& env, STAmount const& amount) + { + // Get the hash for the most recent transaction. + std::string const txHash{env.tx()->getJson(JsonOptions::none)[jss::hash].asString()}; + + // Verify DeliveredAmount and delivered_amount metadata are correct. + env.close(); + Json::Value const meta = env.rpc("tx", txHash)[jss::result][jss::meta]; + + // Expect there to be a DeliveredAmount field. + if (!BEAST_EXPECT(meta.isMember(sfDeliveredAmount.jsonName))) + return; + + // DeliveredAmount and delivered_amount should both be present and + // equal amount. + BEAST_EXPECT(meta[sfDeliveredAmount.jsonName] == amount.getJson(JsonOptions::none)); + BEAST_EXPECT(meta[jss::delivered_amount] == amount.getJson(JsonOptions::none)); + } + + void + testCreateValid(FeatureBitset features) + { + // Explore many of the valid ways to create a check. + testcase("Create valid"); + + using namespace test::jtx; + + Account const gw{"gateway"}; + Account const alice{"alice"}; + Account const bob{"bob"}; + + Env env{*this, features}; + + STAmount const startBalance{XRP(1'000).value()}; + env.fund(startBalance, gw, alice, bob); + + MPT const USD = MPTTester({.env = env, .issuer = gw}); + + // Note that no MPToken has been set up for alice, but alice can + // still write a check for USD. You don't have to have the funds + // necessary to cover a check in order to write a check. + auto writeTwoChecks = [&env, &USD, this](Account const& from, Account const& to) { + std::uint32_t const fromOwnerCount{ownerCount(env, from)}; + std::uint32_t const toOwnerCount{ownerCount(env, to)}; + + std::size_t const fromCkCount{checksOnAccount(env, from).size()}; + std::size_t const toCkCount{checksOnAccount(env, to).size()}; + + env(check::create(from, to, XRP(2000))); + env.close(); + + env(check::create(from, to, USD(50))); + env.close(); + + BEAST_EXPECT(checksOnAccount(env, from).size() == fromCkCount + 2); + BEAST_EXPECT(checksOnAccount(env, to).size() == toCkCount + 2); + + env.require(owners(from, fromOwnerCount + 2)); + env.require(owners(to, to == from ? fromOwnerCount + 2 : toOwnerCount)); + }; + // from to + writeTwoChecks(alice, bob); + writeTwoChecks(gw, alice); + writeTwoChecks(alice, gw); + + // Now try adding the various optional fields. There's no + // expected interaction between these optional fields; other than + // the expiration, they are just plopped into the ledger. So I'm + // not looking at interactions. + using namespace std::chrono_literals; + std::size_t const aliceCount{checksOnAccount(env, alice).size()}; + std::size_t const bobCount{checksOnAccount(env, bob).size()}; + env(check::create(alice, bob, USD(50)), expiration(env.now() + 1s)); + env.close(); + + env(check::create(alice, bob, USD(50)), source_tag(2)); + env.close(); + env(check::create(alice, bob, USD(50)), dest_tag(3)); + env.close(); + env(check::create(alice, bob, USD(50)), invoice_id(uint256{4})); + env.close(); + env(check::create(alice, bob, USD(50)), + expiration(env.now() + 1s), + source_tag(12), + dest_tag(13), + invoice_id(uint256{4})); + env.close(); + + BEAST_EXPECT(checksOnAccount(env, alice).size() == aliceCount + 5); + BEAST_EXPECT(checksOnAccount(env, bob).size() == bobCount + 5); + + // Use a regular key and also multisign to create a check. + Account const alie{"alie", KeyType::ed25519}; + env(regkey(alice, alie)); + env.close(); + + Account const bogie{"bogie", KeyType::secp256k1}; + Account const demon{"demon", KeyType::ed25519}; + env(signers(alice, 2, {{bogie, 1}, {demon, 1}}), sig(alie)); + env.close(); + + // alice uses her regular key to create a check. + env(check::create(alice, bob, USD(50)), sig(alie)); + env.close(); + BEAST_EXPECT(checksOnAccount(env, alice).size() == aliceCount + 6); + BEAST_EXPECT(checksOnAccount(env, bob).size() == bobCount + 6); + + // alice uses multisigning to create a check. + XRPAmount const baseFeeDrops{env.current()->fees().base}; + env(check::create(alice, bob, USD(50)), msig(bogie, demon), fee(3 * baseFeeDrops)); + env.close(); + BEAST_EXPECT(checksOnAccount(env, alice).size() == aliceCount + 7); + BEAST_EXPECT(checksOnAccount(env, bob).size() == bobCount + 7); + } + + void + testCreateDisallowIncoming(FeatureBitset features) + { + testcase("Create valid with disallow incoming"); + + using namespace test::jtx; + + Account const gw{"gateway"}; + Account const alice{"alice"}; + Account const bob{"bob"}; + + Env env{*this, features}; + + STAmount const startBalance{XRP(1'000).value()}; + env.fund(startBalance, gw, alice, bob); + + MPT const USD = MPTTester({.env = env, .issuer = gw}); + + /* + * Attempt to create two checks from `from` to `to` and + * require they both result in error/success code `expected` + */ + auto writeTwoChecksDI = [&env, &USD, this]( + Account const& from, Account const& to, TER expected) { + std::uint32_t const fromOwnerCount{ownerCount(env, from)}; + std::uint32_t const toOwnerCount{ownerCount(env, to)}; + + std::size_t const fromCkCount{checksOnAccount(env, from).size()}; + std::size_t const toCkCount{checksOnAccount(env, to).size()}; + + env(check::create(from, to, XRP(2000)), ter(expected)); + env.close(); + + env(check::create(from, to, USD(50)), ter(expected)); + env.close(); + + if (expected == tesSUCCESS) + { + BEAST_EXPECT(checksOnAccount(env, from).size() == fromCkCount + 2); + BEAST_EXPECT(checksOnAccount(env, to).size() == toCkCount + 2); + + env.require(owners(from, fromOwnerCount + 2)); + env.require(owners(to, to == from ? fromOwnerCount + 2 : toOwnerCount)); + return; + } + + BEAST_EXPECT(checksOnAccount(env, from).size() == fromCkCount); + BEAST_EXPECT(checksOnAccount(env, to).size() == toCkCount); + + env.require(owners(from, fromOwnerCount)); + env.require(owners(to, to == from ? fromOwnerCount : toOwnerCount)); + }; + + // enable the DisallowIncoming flag on both bob and alice + env(fset(bob, asfDisallowIncomingCheck)); + env(fset(alice, asfDisallowIncomingCheck)); + env.close(); + + // both alice and bob can't receive checks + writeTwoChecksDI(alice, bob, tecNO_PERMISSION); + writeTwoChecksDI(gw, alice, tecNO_PERMISSION); + + // remove the flag from alice but not from bob + env(fclear(alice, asfDisallowIncomingCheck)); + env.close(); + + // now bob can send alice a cheque but not visa-versa + writeTwoChecksDI(bob, alice, tesSUCCESS); + writeTwoChecksDI(alice, bob, tecNO_PERMISSION); + + // remove bob's flag too + env(fclear(bob, asfDisallowIncomingCheck)); + env.close(); + + // now they can send checks freely + writeTwoChecksDI(bob, alice, tesSUCCESS); + writeTwoChecksDI(alice, bob, tesSUCCESS); + } + + void + testCreateInvalid(FeatureBitset features) + { + // Explore many of the invalid ways to create a check. + testcase("Create invalid"); + + using namespace test::jtx; + + Account const gw1{"gateway1"}; + Account const gwF{"gatewayFrozen"}; + Account const alice{"alice"}; + Account const bob{"bob"}; + + Env env{*this, features}; + + STAmount const startBalance{XRP(1'000).value()}; + env.fund(startBalance, gw1, gwF, alice, bob); + + auto USDM = MPTTester({.env = env, .issuer = gw1, .flags = MPTDEXFlags | tfMPTCanLock}); + MPT const USD = USDM; + + // Bad fee. + env(check::create(alice, bob, USD(50)), fee(drops(-10)), ter(temBAD_FEE)); + env.close(); + + // Bad flags. + env(check::create(alice, bob, USD(50)), txflags(tfImmediateOrCancel), ter(temINVALID_FLAG)); + env.close(); + + // Check to self. + env(check::create(alice, alice, XRP(10)), ter(temREDUNDANT)); + env.close(); + + // Bad amount. + env(check::create(alice, bob, drops(-1)), ter(temBAD_AMOUNT)); + env.close(); + + env(check::create(alice, bob, drops(0)), ter(temBAD_AMOUNT)); + env.close(); + + env(check::create(alice, bob, drops(1))); + env.close(); + + env(check::create(alice, bob, USD(-1)), ter(temBAD_AMOUNT)); + env.close(); + + env(check::create(alice, bob, USD(0)), ter(temBAD_AMOUNT)); + env.close(); + + env(check::create(alice, bob, USD(1))); + env.close(); + { + MPT const BAD(makeMptID(0, xrpAccount())); + env(check::create(alice, bob, BAD(2)), ter(temBAD_CURRENCY)); + env.close(); + } + + // Bad expiration. + env(check::create(alice, bob, USD(50)), + expiration(NetClock::time_point{}), + ter(temBAD_EXPIRATION)); + env.close(); + + // Destination does not exist. + Account const bogie{"bogie"}; + env(check::create(alice, bogie, USD(50)), ter(tecNO_DST)); + env.close(); + + // Require destination tag. + env(fset(bob, asfRequireDest)); + env.close(); + + env(check::create(alice, bob, USD(50)), ter(tecDST_TAG_NEEDED)); + env.close(); + + env(check::create(alice, bob, USD(50)), dest_tag(11)); + env.close(); + + env(fclear(bob, asfRequireDest)); + env.close(); + { + // Globally frozen asset. + env.close(); + auto USFM = MPTTester({.env = env, .issuer = gwF, .flags = MPTDEXFlags | tfMPTCanLock}); + MPT const USF = USFM; + USFM.set({.flags = tfMPTLock}); + + env(check::create(alice, bob, USF(50)), ter(tecLOCKED)); + env.close(); + + USFM.set({.flags = tfMPTUnlock}); + + env(check::create(alice, bob, USF(50))); + env.close(); + } + { + // Frozen MPT. Check creation should be similar to payment + // behavior in the face of locked MPT. + USDM.authorizeHolders({alice, bob}); + env(pay(gw1, alice, USD(25))); + env(pay(gw1, bob, USD(25))); + env.close(); + + USDM.set({.holder = alice, .flags = tfMPTLock}); + // Setting MPT locked prevents alice from + // creating a check for USD ore receiving a check. This is different + // from IOU where alice can receive checks from bob or gw. + env.close(); + env(check::create(alice, bob, USD(50)), ter(tecLOCKED)); + env.close(); + // Note that IOU returns tecPATH_DRY in this case. + // IOU's internal error is terNO_LINE, which is + // considered ter re-triable and changed to tecPATH_DRY. + env(pay(alice, bob, USD(1)), ter(tecPATH_DRY)); + env.close(); + env(check::create(bob, alice, USD(50)), ter(tecLOCKED)); + env.close(); + env(pay(bob, alice, USD(1)), ter(tecPATH_DRY)); + env.close(); + env(check::create(gw1, alice, USD(50)), ter(tecLOCKED)); + env.close(); + env(pay(gw1, alice, USD(1))); + env.close(); + + // Clear that lock. Now check creation works. + USDM.set({.holder = alice, .flags = tfMPTUnlock}); + env(check::create(alice, bob, USD(50))); + env.close(); + env(check::create(bob, alice, USD(50))); + env.close(); + env(check::create(gw1, alice, USD(50))); + env.close(); + } + + // Expired expiration. + env(check::create(alice, bob, USD(50)), expiration(env.now()), ter(tecEXPIRED)); + env.close(); + + using namespace std::chrono_literals; + env(check::create(alice, bob, USD(50)), expiration(env.now() + 1s)); + env.close(); + + // Insufficient reserve. + Account const cheri{"cheri"}; + env.fund(env.current()->fees().accountReserve(1) - drops(1), cheri); + + env(check::create(cheri, bob, USD(50)), + fee(drops(env.current()->fees().base)), + ter(tecINSUFFICIENT_RESERVE)); + env.close(); + + env(pay(bob, cheri, drops(env.current()->fees().base + 1))); + env.close(); + + env(check::create(cheri, bob, USD(50))); + env.close(); + } + + void + testCashMPT(FeatureBitset features) + { + // Explore many of the valid ways to cash a check for an MPT. + testcase("Cash MPT"); + + using namespace test::jtx; + + Account const gw{"gateway"}; + Account const alice{"alice"}; + Account const bob{"bob"}; + { + // Simple MPT check cashed with Amount (with failures). + Env env{*this, features}; + + env.fund(XRP(1'000), gw, alice, bob); + + MPT const USD = + MPTTester({.env = env, .issuer = gw, .holders = {alice}, .maxAmt = 105}); + + // alice writes the check before she gets the funds. + uint256 const chkId1{getCheckIndex(alice, env.seq(alice))}; + env(check::create(alice, bob, USD(100))); + env.close(); + + // bob attempts to cash the check. Should fail. + env(check::cash(bob, chkId1, USD(100)), ter(tecPATH_PARTIAL)); + env.close(); + + // alice gets almost enough funds. bob tries and fails again. + env(pay(gw, alice, USD(95))); + env.close(); + env(check::cash(bob, chkId1, USD(100)), ter(tecPATH_PARTIAL)); + env.close(); + + // alice gets the last of the necessary funds. + env(pay(gw, alice, USD(5))); + env.close(); + + // bob for more than the check's SendMax. + env.close(); + env(check::cash(bob, chkId1, USD(105)), ter(tecPATH_PARTIAL)); + env.close(); + + // bob asks for exactly the check amount and the check clears. + // MPT is authorized automatically + env(check::cash(bob, chkId1, USD(100))); + env.close(); + env.require(balance(alice, USD(0))); + env.require(balance(bob, USD(100))); + BEAST_EXPECT(checksOnAccount(env, alice).empty()); + BEAST_EXPECT(checksOnAccount(env, bob).empty()); + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(ownerCount(env, bob) == 1); + + // bob tries to cash the same check again, which fails. + env(check::cash(bob, chkId1, USD(100)), ter(tecNO_ENTRY)); + env.close(); + + // bob pays alice USD(70) so he can try another case. + env(pay(bob, alice, USD(70))); + env.close(); + + uint256 const chkId2{getCheckIndex(alice, env.seq(alice))}; + env(check::create(alice, bob, USD(70))); + env.close(); + BEAST_EXPECT(checksOnAccount(env, alice).size() == 1); + BEAST_EXPECT(checksOnAccount(env, bob).size() == 1); + + // bob cashes the check for less than the face amount. That works, + // consumes the check, and bob receives as much as he asked for. + env(check::cash(bob, chkId2, USD(50))); + env.close(); + env.require(balance(alice, USD(20))); + env.require(balance(bob, USD(80))); + BEAST_EXPECT(checksOnAccount(env, alice).empty()); + BEAST_EXPECT(checksOnAccount(env, bob).empty()); + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(ownerCount(env, bob) == 1); + + // alice writes two checks for USD(20), although she only has + // USD(20). + uint256 const chkId3{getCheckIndex(alice, env.seq(alice))}; + env(check::create(alice, bob, USD(20))); + env.close(); + uint256 const chkId4{getCheckIndex(alice, env.seq(alice))}; + env(check::create(alice, bob, USD(20))); + env.close(); + BEAST_EXPECT(checksOnAccount(env, alice).size() == 2); + BEAST_EXPECT(checksOnAccount(env, bob).size() == 2); + + // bob cashes the second check for the face amount. + env(check::cash(bob, chkId4, USD(20))); + env.close(); + env.require(balance(alice, USD(0))); + env.require(balance(bob, USD(100))); + BEAST_EXPECT(checksOnAccount(env, alice).size() == 1); + BEAST_EXPECT(checksOnAccount(env, bob).size() == 1); + BEAST_EXPECT(ownerCount(env, alice) == 2); + BEAST_EXPECT(ownerCount(env, bob) == 1); + + // bob is not allowed to cash the last check for USD(0), he must + // use check::cancel instead. + env(check::cash(bob, chkId3, USD(0)), ter(temBAD_AMOUNT)); + env.close(); + env.require(balance(alice, USD(0))); + env.require(balance(bob, USD(100))); + BEAST_EXPECT(checksOnAccount(env, alice).size() == 1); + BEAST_EXPECT(checksOnAccount(env, bob).size() == 1); + BEAST_EXPECT(ownerCount(env, alice) == 2); + BEAST_EXPECT(ownerCount(env, bob) == 1); + + { + // Unlike IOU, cashing a check exceeding the MPT limit doesn't + // work. Show that at work. + // + // MPT limit is USD(105). Show that + // neither a payment to bob or caching can exceed that limit. + + // Payment of 200 USD fails. + env(pay(gw, bob, USD(200)), ter(tecPATH_PARTIAL)); + env.close(); + + uint256 const chkId20{getCheckIndex(gw, env.seq(gw))}; + env(check::create(gw, bob, USD(200))); + env.close(); + + // Cashing a check for 200 USD fails. + env(check::cash(bob, chkId20, USD(200)), ter(tecPATH_PARTIAL)); + env.close(); + env.require(balance(bob, USD(100))); + + // Clean up this most recent experiment so the rest of the + // tests work. + env(pay(bob, gw, USD(100))); + env(check::cancel(bob, chkId20)); + } + + // ... so bob cancels alice's remaining check. + env(check::cancel(bob, chkId3)); + env.close(); + env.require(balance(alice, USD(0))); + env.require(balance(bob, USD(0))); + BEAST_EXPECT(checksOnAccount(env, alice).empty()); + BEAST_EXPECT(checksOnAccount(env, bob).empty()); + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(ownerCount(env, bob) == 1); + } + { + // Simple MPT check cashed with DeliverMin (with failures). + Env env{*this, features}; + + env.fund(XRP(1'000), gw, alice, bob); + + MPT const USD = + MPTTester({.env = env, .issuer = gw, .holders = {alice, bob}, .maxAmt = 20}); + + env(pay(gw, alice, USD(8))); + env.close(); + + // alice creates several checks ahead of time. + uint256 const chkId9{getCheckIndex(alice, env.seq(alice))}; + env(check::create(alice, bob, USD(9))); + env.close(); + uint256 const chkId8{getCheckIndex(alice, env.seq(alice))}; + env(check::create(alice, bob, USD(8))); + env.close(); + uint256 const chkId7{getCheckIndex(alice, env.seq(alice))}; + env(check::create(alice, bob, USD(7))); + env.close(); + uint256 const chkId6{getCheckIndex(alice, env.seq(alice))}; + env(check::create(alice, bob, USD(6))); + env.close(); + + // bob attempts to cash a check for the amount on the check. + // Should fail, since alice doesn't have the funds. + env(check::cash(bob, chkId9, check::DeliverMin(USD(9))), ter(tecPATH_PARTIAL)); + env.close(); + + // bob sets a DeliverMin of 7 and gets all that alice has. + env(check::cash(bob, chkId9, check::DeliverMin(USD(7)))); + verifyDeliveredAmount(env, USD(8)); + env.require(balance(alice, USD(0))); + env.require(balance(bob, USD(8))); + BEAST_EXPECT(checksOnAccount(env, alice).size() == 3); + BEAST_EXPECT(checksOnAccount(env, bob).size() == 3); + BEAST_EXPECT(ownerCount(env, alice) == 4); + BEAST_EXPECT(ownerCount(env, bob) == 1); + + // bob pays alice USD(7) so he can use another check. + env(pay(bob, alice, USD(7))); + env.close(); + + // Using DeliverMin for the SendMax value of the check (and no + // transfer fees) should work just like setting Amount. + env(check::cash(bob, chkId7, check::DeliverMin(USD(7)))); + verifyDeliveredAmount(env, USD(7)); + env.require(balance(alice, USD(0))); + env.require(balance(bob, USD(8))); + BEAST_EXPECT(checksOnAccount(env, alice).size() == 2); + BEAST_EXPECT(checksOnAccount(env, bob).size() == 2); + BEAST_EXPECT(ownerCount(env, alice) == 3); + BEAST_EXPECT(ownerCount(env, bob) == 1); + + // bob pays alice USD(8) so he can use the last two checks. + env(pay(bob, alice, USD(8))); + env.close(); + + // alice has USD(8). If bob uses the check for USD(6) and uses a + // DeliverMin of 4, he should get the SendMax value of the check. + env(check::cash(bob, chkId6, check::DeliverMin(USD(4)))); + verifyDeliveredAmount(env, USD(6)); + env.require(balance(alice, USD(2))); + env.require(balance(bob, USD(6))); + BEAST_EXPECT(checksOnAccount(env, alice).size() == 1); + BEAST_EXPECT(checksOnAccount(env, bob).size() == 1); + BEAST_EXPECT(ownerCount(env, alice) == 2); + BEAST_EXPECT(ownerCount(env, bob) == 1); + + // bob cashes the last remaining check setting a DeliverMin. + // of exactly alice's remaining USD. + env(check::cash(bob, chkId8, check::DeliverMin(USD(2)))); + verifyDeliveredAmount(env, USD(2)); + env.require(balance(alice, USD(0))); + env.require(balance(bob, USD(8))); + BEAST_EXPECT(checksOnAccount(env, alice).empty()); + BEAST_EXPECT(checksOnAccount(env, bob).empty()); + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(ownerCount(env, bob) == 1); + } + { + // Examine the effects of the asfRequireAuth flag. + Env env(*this, features); + + env.fund(XRP(1000), gw, alice, bob); + auto USDM = MPTTester( + {.env = env, + .issuer = gw, + .holders = {alice}, + .flags = MPTDEXFlags | tfMPTRequireAuth, + .maxAmt = 20}); + MPT const USD = USDM; + USDM.authorize({.holder = alice}); + env.close(); + env(pay(gw, alice, USD(8))); + env.close(); + + // alice writes a check to bob for USD. bob can't cash it + // because he is not authorized to hold gw["USD"]. + uint256 const chkId{getCheckIndex(alice, env.seq(alice))}; + env(check::create(alice, bob, USD(7))); + env.close(); + + env(check::cash(bob, chkId, USD(7)), ter(tecNO_AUTH)); + env.close(); + + // Now give bob MPT for USD. bob still can't cash the + // check because he is not authorized. + USDM.authorize({.account = bob}); + env.close(); + + env(check::cash(bob, chkId, USD(7)), ter(tecNO_AUTH)); + env.close(); + + // bob gets authorization to hold USD. + USDM.authorize({.holder = bob}); + env.close(); + + env(check::cash(bob, chkId, check::DeliverMin(USD(4)))); + STAmount const bobGot = USD(7); + verifyDeliveredAmount(env, bobGot); + env.require(balance(alice, USD(8) - bobGot)); + env.require(balance(bob, bobGot)); + + BEAST_EXPECT(checksOnAccount(env, alice).empty()); + BEAST_EXPECT(checksOnAccount(env, bob).empty()); + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(ownerCount(env, bob) == 1); + } + + { + Env env{*this, features}; + + env.fund(XRP(1'000), gw, alice, bob); + + MPT const USD = + MPTTester({.env = env, .issuer = gw, .holders = {alice, bob}, .maxAmt = 20}); + + // alice creates her checks ahead of time. + uint256 const chkId1{getCheckIndex(alice, env.seq(alice))}; + env(check::create(alice, bob, USD(1))); + env.close(); + + uint256 const chkId2{getCheckIndex(alice, env.seq(alice))}; + env(check::create(alice, bob, USD(2))); + env.close(); + + env(pay(gw, alice, USD(8))); + env.close(); + + // Give bob a regular key and signers + Account const bobby{"bobby", KeyType::secp256k1}; + env(regkey(bob, bobby)); + env.close(); + + Account const bogie{"bogie", KeyType::secp256k1}; + Account const demon{"demon", KeyType::ed25519}; + env(signers(bob, 2, {{bogie, 1}, {demon, 1}}), sig(bobby)); + env.close(); + + int const signersCount = 1; + BEAST_EXPECT(ownerCount(env, bob) == signersCount + 1); + + // bob uses his regular key to cash a check. + env(check::cash(bob, chkId1, (USD(1))), sig(bobby)); + env.close(); + env.require(balance(alice, USD(7))); + env.require(balance(bob, USD(1))); + BEAST_EXPECT(checksOnAccount(env, alice).size() == 1); + BEAST_EXPECT(checksOnAccount(env, bob).size() == 1); + BEAST_EXPECT(ownerCount(env, alice) == 2); + BEAST_EXPECT(ownerCount(env, bob) == signersCount + 1); + + // bob uses multisigning to cash a check. + XRPAmount const baseFeeDrops{env.current()->fees().base}; + env(check::cash(bob, chkId2, (USD(2))), msig(bogie, demon), fee(3 * baseFeeDrops)); + env.close(); + env.require(balance(alice, USD(5))); + env.require(balance(bob, USD(3))); + BEAST_EXPECT(checksOnAccount(env, alice).empty()); + BEAST_EXPECT(checksOnAccount(env, bob).empty()); + BEAST_EXPECT(ownerCount(env, alice) == 1); + BEAST_EXPECT(ownerCount(env, bob) == signersCount + 1); + } + } + + void + testCashXferFee(FeatureBitset features) + { + // Look at behavior when the issuer charges a transfer fee. + testcase("Cash with transfer fee"); + + using namespace test::jtx; + + Account const gw{"gateway"}; + Account const alice{"alice"}; + Account const bob{"bob"}; + + Env env{*this, features}; + + env.fund(XRP(1'000), gw, alice, bob); + + // Set gw's transfer rate and see the consequences when cashing a check. + MPT const USD = MPTTester( + {.env = env, + .issuer = gw, + .holders = {alice, bob}, + .transferFee = 25'000, + .maxAmt = 1'000}); + + env.close(); + env(pay(gw, alice, USD(1'000))); + env.close(); + + // alice writes a check with a SendMax of USD(125). The most bob + // can get is USD(100) because of the transfer rate. + uint256 const chkId125{getCheckIndex(alice, env.seq(alice))}; + env(check::create(alice, bob, USD(125))); + env.close(); + + // alice writes another check that won't get cashed until the transfer + // rate changes so we can see the rate applies when the check is + // cashed, not when it is created. +#if 0 + uint256 const chkId120{getCheckIndex(alice, env.seq(alice))}; + env(check::create(alice, bob, USD(120))); + env.close(); +#endif + + // bob attempts to cash the check for face value. Should fail. + env(check::cash(bob, chkId125, USD(125)), ter(tecPATH_PARTIAL)); + env.close(); + env(check::cash(bob, chkId125, check::DeliverMin(USD(101))), ter(tecPATH_PARTIAL)); + env.close(); + + // bob decides that he'll accept anything USD(75) or up. + // He gets USD(100). + env(check::cash(bob, chkId125, check::DeliverMin(USD(75)))); + verifyDeliveredAmount(env, USD(100)); + env.require(balance(alice, USD(1'000 - 125))); + env.require(balance(bob, USD(0 + 100))); + BEAST_EXPECT(checksOnAccount(env, alice).empty()); + BEAST_EXPECT(checksOnAccount(env, bob).empty()); + +#if 0 + // Adjust gw's rate... + env(rate(gw, 1.2)); + env.close(); + + // bob cashes the second check for less than the face value. The new + // rate applies to the actual value transferred. + env(check::cash(bob, chkId120, USD(50))); + env.close(); + env.require(balance(alice, USD(1000 - 125 - 60))); + env.require(balance(bob, USD(0 + 100 + 50))); + BEAST_EXPECT(checksOnAccount(env, alice).size() == 0); + BEAST_EXPECT(checksOnAccount(env, bob).size() == 0); +#endif + } + + void + testCashInvalid(FeatureBitset features) + { + // Explore many of the ways to fail at cashing a check. + testcase("Cash invalid"); + + using namespace test::jtx; + + Account const gw{"gateway"}; + Account const alice{"alice"}; + Account const bob{"bob"}; + Account const zoe{"zoe"}; + std::int64_t maxAmt{20}; + + Env env(*this, features); + + env.fund(XRP(1000), gw, alice, bob, zoe); + + auto USDM = MPTTester( + {.env = env, + .issuer = gw, + .holders = {alice}, + .flags = MPTDEXFlags | tfMPTCanLock, + .maxAmt = maxAmt}); + MPT const USD = USDM; + + env(pay(gw, alice, USD(20))); + env.close(); + + USDM.authorize({.account = bob}); + + // bob tries to cash a non-existent check from alice. + { + uint256 const chkId{getCheckIndex(alice, env.seq(alice))}; + env(check::cash(bob, chkId, USD(20)), ter(tecNO_ENTRY)); + env.close(); + } + + // alice creates her checks ahead of time. + uint256 const chkIdU{getCheckIndex(alice, env.seq(alice))}; + env(check::create(alice, bob, USD(20))); + env.close(); + + uint256 const chkIdX{getCheckIndex(alice, env.seq(alice))}; + env(check::create(alice, bob, XRP(10))); + env.close(); + + using namespace std::chrono_literals; + uint256 const chkIdExp{getCheckIndex(alice, env.seq(alice))}; + env(check::create(alice, bob, XRP(10)), expiration(env.now() + 1s)); + env.close(); + + uint256 const chkIdFroz1{getCheckIndex(alice, env.seq(alice))}; + env(check::create(alice, bob, USD(1))); + env.close(); + + uint256 const chkIdFroz2{getCheckIndex(alice, env.seq(alice))}; + env(check::create(alice, bob, USD(2))); + env.close(); + + uint256 const chkIdFroz3{getCheckIndex(alice, env.seq(alice))}; + env(check::create(alice, bob, USD(3))); + env.close(); + + uint256 const chkIdNoDest1{getCheckIndex(alice, env.seq(alice))}; + env(check::create(alice, bob, USD(1))); + env.close(); + + uint256 const chkIdHasDest2{getCheckIndex(alice, env.seq(alice))}; + env(check::create(alice, bob, USD(2)), dest_tag(7)); + env.close(); + + // Same set of failing cases for both MPT and XRP check cashing. + auto failingCases = [&env, &gw, &alice, &bob]( + uint256 const& chkId, STAmount const& amount) { + // Bad fee. + env(check::cash(bob, chkId, amount), fee(drops(-10)), ter(temBAD_FEE)); + env.close(); + + // Bad flags. + env(check::cash(bob, chkId, amount), + txflags(tfImmediateOrCancel), + ter(temINVALID_FLAG)); + env.close(); + + // Missing both Amount and DeliverMin. + { + Json::Value tx{check::cash(bob, chkId, amount)}; + tx.removeMember(sfAmount.jsonName); + env(tx, ter(temMALFORMED)); + env.close(); + } + // Both Amount and DeliverMin present. + { + Json::Value tx{check::cash(bob, chkId, amount)}; + tx[sfDeliverMin.jsonName] = amount.getJson(JsonOptions::none); + env(tx, ter(temMALFORMED)); + env.close(); + } + + // Negative or zero amount. + { + STAmount neg{amount}; + neg.negate(); + env(check::cash(bob, chkId, neg), ter(temBAD_AMOUNT)); + env.close(); + env(check::cash(bob, chkId, amount.zeroed()), ter(temBAD_AMOUNT)); + env.close(); + } + + // Bad currency. + if (!amount.native()) + { + Issue const badIssue{badCurrency(), amount.getIssuer()}; + STAmount badAmount{amount}; + badAmount.setIssue(Issue{badCurrency(), amount.getIssuer()}); + env(check::cash(bob, chkId, badAmount), ter(temBAD_CURRENCY)); + env.close(); + } + + // Not destination cashing check. + env(check::cash(alice, chkId, amount), ter(tecNO_PERMISSION)); + env.close(); + env(check::cash(gw, chkId, amount), ter(tecNO_PERMISSION)); + env.close(); + + // Currency mismatch. + { + MPT const EUR = MPTTester({.env = env, .issuer = gw}); + STAmount const badAmount{EUR, amount}; + env(check::cash(bob, chkId, badAmount), ter(temMALFORMED)); + env.close(); + } + + // Issuer mismatch. + // Every MPT is unique. There is no USD MPT with different issuers. + + // Amount bigger than SendMax. + env(check::cash(bob, chkId, amount + amount), ter(tecPATH_PARTIAL)); + env.close(); + + // DeliverMin bigger than SendMax. + env(check::cash(bob, chkId, check::DeliverMin(amount + amount)), ter(tecPATH_PARTIAL)); + env.close(); + }; + + failingCases(chkIdX, XRP(10)); + failingCases(chkIdU, USD(20)); + + // Verify that those two checks really were cashable. + env(check::cash(bob, chkIdU, USD(20))); + env.close(); + env(check::cash(bob, chkIdX, check::DeliverMin(XRP(10)))); + verifyDeliveredAmount(env, XRP(10)); + + // Try to cash an expired check. + env(check::cash(bob, chkIdExp, XRP(10)), ter(tecEXPIRED)); + env.close(); + + // Cancel the expired check. Anyone can cancel an expired check. + env(check::cancel(zoe, chkIdExp)); + env.close(); + + // Can we cash a check with frozen MPT? + { + env(pay(bob, alice, USD(20))); + env.close(); + env.require(balance(alice, USD(20))); + env.require(balance(bob, USD(0))); + + // Global freeze + USDM.set({.flags = tfMPTLock}); + + // MPTLocked flag is set and the account is not the issuer of MPT + env(check::cash(bob, chkIdFroz1, USD(1)), ter(tecPATH_PARTIAL)); + env.close(); + env(check::cash(bob, chkIdFroz1, check::DeliverMin(USD(1))), ter(tecPATH_PARTIAL)); + env.close(); + + USDM.set({.flags = tfMPTUnlock}); + + // No longer frozen. Success. + env(check::cash(bob, chkIdFroz1, USD(1))); + env.close(); + env.require(balance(alice, USD(19))); + env.require(balance(bob, USD(1))); + + // Freeze individual MPT. + USDM.set({.holder = alice, .flags = tfMPTLock}); + env(check::cash(bob, chkIdFroz2, USD(2)), ter(tecPATH_PARTIAL)); + env.close(); + env(check::cash(bob, chkIdFroz2, check::DeliverMin(USD(1))), ter(tecPATH_PARTIAL)); + env.close(); + + // Clear that freeze. Now check cashing works. + USDM.set({.holder = alice, .flags = tfMPTUnlock}); + env(check::cash(bob, chkIdFroz2, USD(2))); + env.close(); + env.require(balance(alice, USD(17))); + env.require(balance(bob, USD(3))); + + // Freeze bob's MPT. bob can't cash the check. + USDM.set({.holder = bob, .flags = tfMPTLock}); + env(check::cash(bob, chkIdFroz3, USD(3)), ter(tecLOCKED)); + env.close(); + env(check::cash(bob, chkIdFroz3, check::DeliverMin(USD(1))), ter(tecLOCKED)); + env.close(); + + // Clear that freeze. Now check cashing works again. + USDM.set({.holder = bob, .flags = tfMPTUnlock}); + env.close(); + env(check::cash(bob, chkIdFroz3, check::DeliverMin(USD(1)))); + verifyDeliveredAmount(env, USD(3)); + env.require(balance(alice, USD(14))); + env.require(balance(bob, USD(6))); + } + { + // Set the RequireDest flag on bob's account (after the check + // was created) then cash a check without a destination tag. + env(fset(bob, asfRequireDest)); + env.close(); + env(check::cash(bob, chkIdNoDest1, USD(1)), ter(tecDST_TAG_NEEDED)); + env.close(); + env(check::cash(bob, chkIdNoDest1, check::DeliverMin(USD(1))), ter(tecDST_TAG_NEEDED)); + env.close(); + + // bob can cash a check with a destination tag. + env(check::cash(bob, chkIdHasDest2, USD(2))); + env.close(); + + env.require(balance(alice, USD(12))); + env.require(balance(bob, USD(8))); + + // Clear the RequireDest flag on bob's account so he can + // cash the check with no DestinationTag. + env(fclear(bob, asfRequireDest)); + env.close(); + env(check::cash(bob, chkIdNoDest1, USD(1))); + env.close(); + env.require(balance(alice, USD(11))); + env.require(balance(bob, USD(9))); + } + + // OutstandingAmount exceeds MaximumAmount + { + // Already at maximum + BEAST_EXPECT(env.balance(gw, USDM) == USDM(-maxAmt)); + + uint256 const chkId{getCheckIndex(gw, env.seq(gw))}; + env(check::create(gw, bob, USDM(10))); + env.close(); + + // Exceeds MaximumAmount (20 + 10) = 30 > 20 + env(check::cash(bob, chkId, USDM(10)), ter(tecPATH_PARTIAL)); + env.close(); + + // Redeem some tokens (20 - 9) = 11 + env(pay(alice, gw, USDM(9))); + env.close(); + + // Still exceeds MaximumAmount (11 + 10) = 21 > 20 + env(check::cash(bob, chkId, USDM(10)), ter(tecPATH_PARTIAL)); + env.close(); + } + } + + void + testCancelValid(FeatureBitset features) + { + // Explore many of the ways to cancel a check. + testcase("Cancel valid"); + + using namespace test::jtx; + + Account const gw{"gateway"}; + Account const alice{"alice"}; + Account const bob{"bob"}; + Account const zoe{"zoe"}; + + { + Env env{*this, features}; + + env.fund(XRP(1'000), gw, alice, bob, zoe); + + MPT const USD = MPTTester({.env = env, .issuer = gw}); + + // alice creates her checks ahead of time. + // Three ordinary checks with no expiration. + uint256 const chkId1{getCheckIndex(alice, env.seq(alice))}; + env(check::create(alice, bob, USD(10))); + env.close(); + + uint256 const chkId2{getCheckIndex(alice, env.seq(alice))}; + env(check::create(alice, bob, XRP(10))); + env.close(); + + uint256 const chkId3{getCheckIndex(alice, env.seq(alice))}; + env(check::create(alice, bob, USD(10))); + env.close(); + + // Three checks that expire in 10 minutes. + using namespace std::chrono_literals; + uint256 const chkIdNotExp1{getCheckIndex(alice, env.seq(alice))}; + env(check::create(alice, bob, XRP(10)), expiration(env.now() + 600s)); + env.close(); + + uint256 const chkIdNotExp2{getCheckIndex(alice, env.seq(alice))}; + env(check::create(alice, bob, USD(10)), expiration(env.now() + 600s)); + env.close(); + + uint256 const chkIdNotExp3{getCheckIndex(alice, env.seq(alice))}; + env(check::create(alice, bob, XRP(10)), expiration(env.now() + 600s)); + env.close(); + + // Three checks that expire in one second. + uint256 const chkIdExp1{getCheckIndex(alice, env.seq(alice))}; + env(check::create(alice, bob, USD(10)), expiration(env.now() + 1s)); + env.close(); + + uint256 const chkIdExp2{getCheckIndex(alice, env.seq(alice))}; + env(check::create(alice, bob, XRP(10)), expiration(env.now() + 1s)); + env.close(); + + uint256 const chkIdExp3{getCheckIndex(alice, env.seq(alice))}; + env(check::create(alice, bob, USD(10)), expiration(env.now() + 1s)); + env.close(); + + // Two checks to cancel using a regular key and using multisigning. + uint256 const chkIdReg{getCheckIndex(alice, env.seq(alice))}; + env(check::create(alice, bob, USD(10))); + env.close(); + + uint256 const chkIdMSig{getCheckIndex(alice, env.seq(alice))}; + env(check::create(alice, bob, XRP(10))); + env.close(); + BEAST_EXPECT(checksOnAccount(env, alice).size() == 11); + BEAST_EXPECT(ownerCount(env, alice) == 11); + + // Creator, destination, and an outsider cancel the checks. + env(check::cancel(alice, chkId1)); + env.close(); + BEAST_EXPECT(checksOnAccount(env, alice).size() == 10); + BEAST_EXPECT(ownerCount(env, alice) == 10); + + env(check::cancel(bob, chkId2)); + env.close(); + BEAST_EXPECT(checksOnAccount(env, alice).size() == 9); + BEAST_EXPECT(ownerCount(env, alice) == 9); + + env(check::cancel(zoe, chkId3), ter(tecNO_PERMISSION)); + env.close(); + BEAST_EXPECT(checksOnAccount(env, alice).size() == 9); + BEAST_EXPECT(ownerCount(env, alice) == 9); + + // Creator, destination, and an outsider cancel unexpired checks. + env(check::cancel(alice, chkIdNotExp1)); + env.close(); + BEAST_EXPECT(checksOnAccount(env, alice).size() == 8); + BEAST_EXPECT(ownerCount(env, alice) == 8); + + env(check::cancel(bob, chkIdNotExp2)); + env.close(); + BEAST_EXPECT(checksOnAccount(env, alice).size() == 7); + BEAST_EXPECT(ownerCount(env, alice) == 7); + + env(check::cancel(zoe, chkIdNotExp3), ter(tecNO_PERMISSION)); + env.close(); + BEAST_EXPECT(checksOnAccount(env, alice).size() == 7); + BEAST_EXPECT(ownerCount(env, alice) == 7); + + // Creator, destination, and an outsider cancel expired checks. + env(check::cancel(alice, chkIdExp1)); + env.close(); + BEAST_EXPECT(checksOnAccount(env, alice).size() == 6); + BEAST_EXPECT(ownerCount(env, alice) == 6); + + env(check::cancel(bob, chkIdExp2)); + env.close(); + BEAST_EXPECT(checksOnAccount(env, alice).size() == 5); + BEAST_EXPECT(ownerCount(env, alice) == 5); + + env(check::cancel(zoe, chkIdExp3)); + env.close(); + BEAST_EXPECT(checksOnAccount(env, alice).size() == 4); + BEAST_EXPECT(ownerCount(env, alice) == 4); + + // Use a regular key and also multisign to cancel checks. + Account const alie{"alie", KeyType::ed25519}; + env(regkey(alice, alie)); + env.close(); + + Account const bogie{"bogie", KeyType::secp256k1}; + Account const demon{"demon", KeyType::ed25519}; + env(signers(alice, 2, {{bogie, 1}, {demon, 1}}), sig(alie)); + env.close(); + + int const signersCount{1}; + + // alice uses her regular key to cancel a check. + env(check::cancel(alice, chkIdReg), sig(alie)); + env.close(); + BEAST_EXPECT(checksOnAccount(env, alice).size() == 3); + BEAST_EXPECT(ownerCount(env, alice) == signersCount + 3); + + // alice uses multisigning to cancel a check. + XRPAmount const baseFeeDrops{env.current()->fees().base}; + env(check::cancel(alice, chkIdMSig), msig(bogie, demon), fee(3 * baseFeeDrops)); + env.close(); + BEAST_EXPECT(checksOnAccount(env, alice).size() == 2); + BEAST_EXPECT(ownerCount(env, alice) == signersCount + 2); + + // Creator and destination cancel the remaining unexpired checks. + env(check::cancel(alice, chkId3), sig(alice)); + env.close(); + BEAST_EXPECT(checksOnAccount(env, alice).size() == 1); + BEAST_EXPECT(ownerCount(env, alice) == signersCount + 1); + + env(check::cancel(bob, chkIdNotExp3)); + env.close(); + BEAST_EXPECT(checksOnAccount(env, alice).empty()); + BEAST_EXPECT(ownerCount(env, alice) == signersCount + 0); + } + } + + void + testWithTickets(FeatureBitset features) + { + testcase("With Tickets"); + + using namespace test::jtx; + + Account const gw{"gw"}; + Account const alice{"alice"}; + Account const bob{"bob"}; + + Env env{*this, features}; + env.fund(XRP(1'000), gw, alice, bob); + env.close(); + + MPT const USD = + MPTTester({.env = env, .issuer = gw, .holders = {alice, bob}, .maxAmt = 1'000}); + + // alice and bob grab enough tickets for all the following + // transactions. Note that once the tickets are acquired alice's + // and bob's account sequence numbers should not advance. + std::uint32_t aliceTicketSeq{env.seq(alice) + 1}; + env(ticket::create(alice, 10)); + std::uint32_t const aliceSeq{env.seq(alice)}; + + std::uint32_t bobTicketSeq{env.seq(bob) + 1}; + env(ticket::create(bob, 10)); + std::uint32_t const bobSeq{env.seq(bob)}; + + env.close(); + // MPT + 10 tickets + env.require(owners(alice, 11)); + env.require(owners(bob, 11)); + + env.require(tickets(alice, env.seq(alice) - aliceTicketSeq)); + BEAST_EXPECT(env.seq(alice) == aliceSeq); + + env.require(tickets(bob, env.seq(bob) - bobTicketSeq)); + BEAST_EXPECT(env.seq(bob) == bobSeq); + + env(pay(gw, alice, USD(900))); + env.close(); + + // alice creates four checks; two XRP, two MPT. Bob will cash + // one of each and cancel one of each. + uint256 const chkIdXrp1{getCheckIndex(alice, aliceTicketSeq)}; + env(check::create(alice, bob, XRP(200)), ticket::use(aliceTicketSeq++)); + + uint256 const chkIdXrp2{getCheckIndex(alice, aliceTicketSeq)}; + env(check::create(alice, bob, XRP(300)), ticket::use(aliceTicketSeq++)); + + uint256 const chkIdUsd1{getCheckIndex(alice, aliceTicketSeq)}; + env(check::create(alice, bob, USD(200)), ticket::use(aliceTicketSeq++)); + + uint256 const chkIdUsd2{getCheckIndex(alice, aliceTicketSeq)}; + env(check::create(alice, bob, USD(300)), ticket::use(aliceTicketSeq++)); + + env.close(); + // Alice used four tickets but created four checks. + env.require(owners(alice, 11)); + env.require(tickets(alice, env.seq(alice) - aliceTicketSeq)); + BEAST_EXPECT(checksOnAccount(env, alice).size() == 4); + BEAST_EXPECT(env.seq(alice) == aliceSeq); + + env.require(owners(bob, 11)); + BEAST_EXPECT(env.seq(bob) == bobSeq); + + // Bob cancels two of alice's checks. + env(check::cancel(bob, chkIdXrp1), ticket::use(bobTicketSeq++)); + env(check::cancel(bob, chkIdUsd2), ticket::use(bobTicketSeq++)); + env.close(); + + env.require(owners(alice, 9)); + env.require(tickets(alice, env.seq(alice) - aliceTicketSeq)); + BEAST_EXPECT(checksOnAccount(env, alice).size() == 2); + BEAST_EXPECT(env.seq(alice) == aliceSeq); + + env.require(owners(bob, 9)); + BEAST_EXPECT(env.seq(bob) == bobSeq); + + // Bob cashes alice's two remaining checks. + env(check::cash(bob, chkIdXrp2, XRP(300)), ticket::use(bobTicketSeq++)); + env(check::cash(bob, chkIdUsd1, USD(200)), ticket::use(bobTicketSeq++)); + env.close(); + + auto const baseFee = env.current()->fees().base; + env.require(owners(alice, 7)); + env.require(tickets(alice, env.seq(alice) - aliceTicketSeq)); + BEAST_EXPECT(checksOnAccount(env, alice).empty()); + BEAST_EXPECT(env.seq(alice) == aliceSeq); + env.require(balance(alice, USD(700))); + env.require(balance(alice, XRP(700) - 6 * baseFee)); + env.require(owners(bob, 7)); + BEAST_EXPECT(env.seq(bob) == bobSeq); + env.require(balance(bob, USD(200))); + env.require(balance(bob, XRP(1'300) - 6 * baseFee)); + } + + void + testMPTCreation(FeatureBitset features) + { + // Explore automatic MPT creation when a check is cashed. + + testcase("MPT Creation"); + + using namespace test::jtx; + + Env env{*this, features}; + + // An account that independently tracks its owner count. + struct AccountOwns + { + using iterator = hash_map::iterator; + beast::unit_test::suite& suite; + Env& env; + Account const acct; + std::size_t owners{0}; + hash_map mpts; + bool const isIssuer; + bool const requireAuth; + + AccountOwns( + beast::unit_test::suite& s, + Env& e, + Account a, + bool isIssuer_, + bool requireAuth_ = false) + : suite(s) + , env(e) + , acct(std::move(a)) + , isIssuer(isIssuer_) + , requireAuth(requireAuth_) + { + } + + void + verifyOwners(std::uint32_t line, bool print = false) const + { + if (print) + { + std::cout << acct.name() << " " << ownerCount(env, acct) << " " << owners + << std::endl; + } + suite.expect( + ownerCount(env, acct) == owners, "Owner count mismatch", __FILE__, line); + } + + // Operators to make using the class more convenient. + operator Account() const + { + return acct; + } + + operator xrpl::AccountID() const + { + return acct.id(); + } + + /** Create MPTTester if it doesn't exist for the given MPT. + * Increment owners if created since it creates MPTokenIssuance + */ + MPT + operator[](std::string const& s) + { + if (!isIssuer) + Throw("AccountOwns: must be issuer"); + if (auto const& it = mpts.find(s); it != mpts.end()) + return it->second[s]; + auto flags = MPTDEXFlags | tfMPTCanLock; + if (requireAuth) + flags |= tfMPTRequireAuth; + auto [it, _] = + mpts.emplace(s, MPTTester({.env = env, .issuer = acct, .flags = flags})); + (void)_; + ++owners; + + return it->second[s]; + } + + iterator + getIt(MPT const& mpt) + { + if (!isIssuer) + Throw("AccountOwns::set must be issuer"); + auto it = mpts.find(mpt.name); + if (it == mpts.end()) + Throw("AccountOwns::set mpt doesn't exist"); + return it; + } + + void + set(MPT const& mpt, std::uint32_t flag) + { + auto it = getIt(mpt); + it->second.set({.flags = flag}); + } + + void + authorize(MPT const& mpt, AccountOwns& id) + { + auto it = getIt(mpt); + it->second.authorize({.account = id}); + ++id.owners; + } + + void + cleanup(MPT const& mpt, AccountOwns& id) + { + auto it = getIt(mpt); + // redeem to the issuer + if (auto const redeem = it->second.getBalance(id)) + pay(it, id, acct, redeem); + // delete mptoken + it->second.authorize({.account = id, .flags = tfMPTUnauthorize}); + --id.owners; + } + + void + pay(iterator& it, Account const& src, Account const& dst, std::uint64_t amount) + { + if (env.le(keylet::account(dst))->isFlag(lsfDepositAuth)) + { + env(fclear(dst, asfDepositAuth)); + it->second.pay(src, dst, amount); + env(fset(dst, asfDepositAuth)); + } + else + { + it->second.pay(src, dst, amount); + } + } + + void + pay(Account const& src, Account const& dst, PrettyAmount amount) + { + auto it = getIt(amount.name()); + pay(it, src, dst, amount.value().mpt().value()); + } + }; + + AccountOwns alice{*this, env, "alice", false}; + AccountOwns bob{*this, env, "bob", false}; + AccountOwns gw1{*this, env, "gw1", true}; + + // Fund with noripple so the accounts do not have any flags set. + env.fund(XRP(5000), noripple(alice, bob)); + env.close(); + + // Automatic MPT creation should fail if the check destination + // can't afford the reserve for the trust line. + { + // Fund gw1 with noripple (even though that's atypical for a + // gateway) so it does not have any flags set. We'll set flags + // on gw1 later. + env.fund(XRP(5'000), noripple(gw1)); + env.close(); + + MPT const CK8 = gw1["CK8"]; + gw1.verifyOwners(__LINE__); + + Account const yui{"yui"}; + + // Note the reserve in unit tests is 200 XRP, not 20. So here + // we're just barely giving yui enough XRP to meet the + // account reserve. + env.fund(XRP(200), yui); + env.close(); + + uint256 const chkId{getCheckIndex(gw1, env.seq(gw1))}; + env(check::create(gw1, yui, CK8(99))); + env.close(); + + env(check::cash(yui, chkId, CK8(99)), ter(tecINSUFFICIENT_RESERVE)); + env.close(); + alice.verifyOwners(__LINE__); + + // Give yui enough XRP to meet the trust line's reserve. Cashing + // the check succeeds and creates the trust line. + env(pay(env.master, yui, XRP(51))); + env.close(); + env(check::cash(yui, chkId, CK8(99))); + verifyDeliveredAmount(env, CK8(99)); + env.close(); + BEAST_EXPECT(ownerCount(env, yui) == 1); + + // The automatic trust line does not take a reserve from gw1. + // Since gw1's check was consumed it has no owners. + gw1.verifyOwners(__LINE__); + } + + // We'll be looking at the effects of various account root flags and + // MPT flags. + + // Automatically create MPT using + // o Offers and + // o Check cashing + + //----------- No account root flags, check written by issuer ----------- + { + // No account root flags on any participant. + // Automatic trust line from issuer to destination. + + BEAST_EXPECT((*env.le(gw1))[sfFlags] == 0); + BEAST_EXPECT((*env.le(alice))[sfFlags] == 0); + BEAST_EXPECT((*env.le(bob))[sfFlags] == 0); + + // Use offers to automatically create MPT + MPT const OF1 = gw1["OF1"]; + env(offer(gw1, XRP(98), OF1(98))); + env.close(); + BEAST_EXPECT(env.le(keylet::mptoken(OF1.issuanceID, alice)) == nullptr); + env(offer(alice, OF1(98), XRP(98))); + ++alice.owners; + env.close(); + + // Both offers should be consumed. + // Since gw1's offer was consumed and the trust line was not + // created by gw1, gw1's owner count should be 0. + gw1.verifyOwners(__LINE__); + + // alice's automatically created MPT bumps her owner count. + alice.verifyOwners(__LINE__); + + // Use check cashing to automatically create the trust line. + MPT const CK1 = gw1["CK1"]; + uint256 const chkId{getCheckIndex(gw1, env.seq(gw1))}; + env(check::create(gw1, alice, CK1(98))); + env.close(); + BEAST_EXPECT(env.le(keylet::mptoken(CK1.issuanceID, alice)) == nullptr); + env(check::cash(alice, chkId, CK1(98))); + ++alice.owners; + verifyDeliveredAmount(env, CK1(98)); + env.close(); + + // gw1's check should be consumed. + // Since gw1's check was consumed and the trust line was not + // created by gw1, gw1's owner count should be 0. + gw1.verifyOwners(__LINE__); + + // alice's automatically created trust line bumps her owner count. + alice.verifyOwners(__LINE__); + + // cmpTrustLines(gw1, alice, OF1, CK1); + } + //--------- No account root flags, check written by non-issuer --------- + { + // No account root flags on any participant. + + // Use offers to automatically create MPT. + // Transfer of assets using offers does not require rippling. + // So bob's offer is successfully crossed which creates MPT. + MPT const OF1 = gw1["OF1"]; + env(offer(alice, XRP(97), OF1(97))); + env.close(); + BEAST_EXPECT(env.le(keylet::mptoken(OF1, bob)) == nullptr); + env(offer(bob, OF1(97), XRP(97))); + ++bob.owners; + env.close(); + + // Both offers should be consumed. + env.require(balance(alice, OF1(1))); + env.require(balance(bob, OF1(97))); + + // bob now has an owner count of 1 due to new MPT. + gw1.verifyOwners(__LINE__); + alice.verifyOwners(__LINE__); + bob.verifyOwners(__LINE__); + + // Use check cashing to automatically create MPT. + // + // Unlike IOU where cashing a check (unlike crossing offers) + // requires rippling through the currency's issuer, rippling doesn't + // impact MPT. Even though gw1 does not have rippling enabled, the + // check cash succeeds for MPT and MPT is created. + MPT const CK1 = gw1["CK1"]; + uint256 const chkId{getCheckIndex(alice, env.seq(alice))}; + env(check::create(alice, bob, CK1(97))); + env.close(); + BEAST_EXPECT(env.le(keylet::mptoken(CK1, bob)) == nullptr); + env(check::cash(bob, chkId, CK1(97))); + ++bob.owners; + env.close(); + + BEAST_EXPECT(env.le(keylet::mptoken(OF1, bob)) != nullptr); + + gw1.verifyOwners(__LINE__); + alice.verifyOwners(__LINE__); + bob.verifyOwners(__LINE__); + } + + //------------- lsfDefaultRipple, check written by issuer -------------- + { + // gw1 enables rippling. + // This doesn't impact automatic MPT creation. + env(fset(gw1, asfDefaultRipple)); + env.close(); + + // Use offers to automatically create the trust line. + MPT const OF2 = gw1["OF2"]; + env(offer(gw1, XRP(96), OF2(96))); + env.close(); + BEAST_EXPECT(env.le(keylet::mptoken(OF2, alice)) == nullptr); + env(offer(alice, OF2(96), XRP(96))); + ++alice.owners; + env.close(); + + // Both offers should be consumed. + // Since gw1's offer was consumed, gw1 owner count doesn't change. + gw1.verifyOwners(__LINE__); + + // alice's automatically created MPT bumps her owner count. + alice.verifyOwners(__LINE__); + + // Use check cashing to automatically create MPT. + MPT const CK2 = gw1["CK2"]; + uint256 const chkId{getCheckIndex(gw1, env.seq(gw1))}; + env(check::create(gw1, alice, CK2(96))); + env.close(); + BEAST_EXPECT(env.le(keylet::mptoken(CK2, alice)) == nullptr); + env(check::cash(alice, chkId, CK2(96))); + ++alice.owners; + verifyDeliveredAmount(env, CK2(96)); + env.close(); + + // gw1's check should be consumed. + // Since gw1's check was consumed and MPT was not + // created by gw1, gw1's owner count doesn't change. + gw1.verifyOwners(__LINE__); + + // alice's automatically created trust line bumps her owner count. + alice.verifyOwners(__LINE__); + } + + //----------- lsfDefaultRipple, check written by non-issuer ------------ + { + // gw1 enabled rippling doesn't impact MPT, so automatic MPT from + // non-issuer to non-issuer should work. + + // Use offers to automatically create MPT. + MPT const OF2 = gw1["OF2"]; + env(offer(alice, XRP(95), OF2(95))); + env.close(); + // alice already has OF2 MPT + BEAST_EXPECT(env.le(keylet::mptoken(OF2, alice)) != nullptr); + env(offer(bob, OF2(95), XRP(95))); + ++bob.owners; + env.close(); + + // bob's owner count should increase due to the new MPT. + gw1.verifyOwners(__LINE__); + alice.verifyOwners(__LINE__); + bob.verifyOwners(__LINE__); + + // Use check cashing to automatically create MPT. + MPT const CK2 = gw1["CK2"]; + uint256 const chkId{getCheckIndex(alice, env.seq(alice))}; + env(check::create(alice, bob, CK2(95))); + env.close(); + BEAST_EXPECT(env.le(keylet::mptoken(CK2, bob)) == nullptr); + env(check::cash(bob, chkId, CK2(95))); + ++bob.owners; + verifyDeliveredAmount(env, CK2(95)); + env.close(); + + // bob's owner count should increase due to the new MPT. + gw1.verifyOwners(__LINE__); + alice.verifyOwners(__LINE__); + bob.verifyOwners(__LINE__); + } + + //-------------- lsfDepositAuth, check written by issuer --------------- + { + // Both offers and checks ignore the lsfDepositAuth flag, since + // the destination signs the transaction that delivers their funds. + // So setting lsfDepositAuth on all the participants should not + // change any outcomes. + // + // Automatic MPT from issuer to non-issuer should still work. + env(fset(gw1, asfDepositAuth)); + env(fset(alice, asfDepositAuth)); + env(fset(bob, asfDepositAuth)); + env.close(); + + // Use offers to automatically create MPT. + MPT const OF3 = gw1["OF3"]; + env(offer(gw1, XRP(94), OF3(94))); + env.close(); + BEAST_EXPECT(env.le(keylet::mptoken(OF3, alice)) == nullptr); + env(offer(alice, OF3(94), XRP(94))); + ++alice.owners; + env.close(); + + // Both offers should be consumed. + // Since gw1's offer was consumed and MPT was not + // created by gw1, gw1's owner count doesn't change. + gw1.verifyOwners(__LINE__); + + // alice's automatically created MPT bumps her owner count. + alice.verifyOwners(__LINE__); + + // Use check cashing to automatically create MPT. + MPT const CK3 = gw1["CK3"]; + uint256 const chkId{getCheckIndex(gw1, env.seq(gw1))}; + env(check::create(gw1, alice, CK3(94))); + env.close(); + BEAST_EXPECT(env.le(keylet::mptoken(CK3, alice)) == nullptr); + env(check::cash(alice, chkId, CK3(94))); + ++alice.owners; + verifyDeliveredAmount(env, CK3(94)); + env.close(); + + // gw1's check should be consumed. + // Since gw1's check was consumed and MPT was not + // created by gw1, gw1's owner count doesn't change. + gw1.verifyOwners(__LINE__); + + // alice's automatically created trust line bumps her owner count. + alice.verifyOwners(__LINE__); + } + + //------------ lsfDepositAuth, check written by non-issuer ------------- + { + // The presence of the lsfDepositAuth flag should not affect + // automatic MPT creation. + + // Use offers to automatically create MPT. + MPT const OF3 = gw1["OF3"]; + env(offer(alice, XRP(93), OF3(93))); + env.close(); + BEAST_EXPECT(env.le(keylet::mptoken(OF3, alice)) != nullptr); + env(offer(bob, OF3(93), XRP(93))); + ++bob.owners; + env.close(); + + // bob's owner count should increase due to the new MPT. + gw1.verifyOwners(__LINE__); + alice.verifyOwners(__LINE__); + bob.verifyOwners(__LINE__); + + // Use check cashing to automatically create MPT. + MPT const CK3 = gw1["CK3"]; + uint256 const chkId{getCheckIndex(alice, env.seq(alice))}; + env(check::create(alice, bob, CK3(93))); + env.close(); + BEAST_EXPECT(env.le(keylet::mptoken(CK3, bob)) == nullptr); + env(check::cash(bob, chkId, CK3(93))); + ++bob.owners; + verifyDeliveredAmount(env, CK3(93)); + env.close(); + + // bob's owner count should increase due to the new MPT. + gw1.verifyOwners(__LINE__); + alice.verifyOwners(__LINE__); + bob.verifyOwners(__LINE__); + } + + //-------------- lsfGlobalFreeze, check written by issuer -------------- + { + // Set lsfGlobalFreeze on gw1. That should not stop any automatic + // MPT from being created. + env(fset(gw1, asfGlobalFreeze)); + env.close(); + + // Use offers to automatically create MPT. + MPT const OF4 = gw1["OF4"]; + env(offer(gw1, XRP(92), OF4(92))); + env.close(); + BEAST_EXPECT(env.le(keylet::mptoken(OF4, alice)) == nullptr); + env(offer(alice, OF4(92), XRP(92))); + ++alice.owners; + env.close(); + + // alice's owner count should increase do to the new MPT. + gw1.verifyOwners(__LINE__); + alice.verifyOwners(__LINE__); + bob.verifyOwners(__LINE__); + + // Use check cashing to automatically create MPT. + MPT const CK4 = gw1["CK4"]; + uint256 const chkId{getCheckIndex(gw1, env.seq(gw1))}; + env(check::create(gw1, bob, CK4(92))); + env.close(); + BEAST_EXPECT(env.le(keylet::mptoken(CK4, bob)) == nullptr); + env(check::cash(bob, chkId, CK4(92))); + verifyDeliveredAmount(env, CK4(92)); + ++bob.owners; + env.close(); + + // bob's owner count should increase due to the new MPT. + gw1.verifyOwners(__LINE__); + alice.verifyOwners(__LINE__); + bob.verifyOwners(__LINE__); + + // clean up + gw1.cleanup(OF4, alice); + gw1.cleanup(CK4, bob); + } + + //-------------- lsfMPTLock, check written by issuer -------------- + { + // Set lsfMPTLock on gw1. That should stop any automatic + // MPT from being created. + + // Use offers to automatically create MPT. + MPT const OF4 = gw1["OF4"]; + gw1.set(OF4, tfMPTLock); + env(offer(gw1, XRP(92), OF4(92)), ter(tecFROZEN)); + env.close(); + BEAST_EXPECT(env.le(keylet::mptoken(OF4, alice)) == nullptr); + env(offer(alice, OF4(92), XRP(92)), ter(tecFROZEN)); + env.close(); + + // No one's owner count should have changed. + gw1.verifyOwners(__LINE__); + alice.verifyOwners(__LINE__); + bob.verifyOwners(__LINE__); + + // Use check cashing to automatically create MPT. + MPT const CK4 = gw1["CK4"]; + gw1.set(CK4, tfMPTLock); + uint256 const chkId{getCheckIndex(gw1, env.seq(gw1))}; + env(check::create(gw1, alice, CK4(92)), ter(tecLOCKED)); + env.close(); + BEAST_EXPECT(env.le(keylet::mptoken(CK4, alice)) == nullptr); + env(check::cash(alice, chkId, CK4(92)), ter(tecNO_ENTRY)); + env.close(); + + // No one's owner count should have changed. + gw1.verifyOwners(__LINE__); + alice.verifyOwners(__LINE__); + bob.verifyOwners(__LINE__); + + // Because gw1 has set tfMPTLock, neither MPT + // is created. + BEAST_EXPECT(env.le(keylet::mptoken(OF4, alice)) == nullptr); + BEAST_EXPECT(env.le(keylet::mptoken(CK4, alice)) == nullptr); + + // clear global freeze + gw1.set(OF4, tfMPTUnlock); + gw1.set(CK4, tfMPTUnlock); + } + + //------------ lsfGlobalFreeze, check written by non-issuer ------------ + { + // lsfGlobalFreeze flag set on gw1 should not stop + // automatic MPT creation between non-issuers. + + // Use offers to automatically create MPT. + MPT const OF4 = gw1["OF4"]; + gw1.authorize(OF4, alice); + gw1.pay(gw1, alice, OF4(91)); + env(offer(alice, XRP(91), OF4(91))); + env.close(); + BEAST_EXPECT(env.le(keylet::mptoken(OF4, alice)) != nullptr); + env(offer(bob, OF4(91), XRP(91))); + ++bob.owners; + env.close(); + + // alice's owner count should increase since it created MPT. + // bob's owner count should increase due to the new MPT. + gw1.verifyOwners(__LINE__); + alice.verifyOwners(__LINE__); + bob.verifyOwners(__LINE__); + + // Use check cashing to automatically create the trust line. + MPT const CK4 = gw1["CK4"]; + uint256 const chkId{getCheckIndex(alice, env.seq(alice))}; + env(check::create(alice, bob, CK4(91))); + env.close(); + BEAST_EXPECT(env.le(keylet::mptoken(CK4, bob)) == nullptr); + gw1.authorize(CK4, alice); + gw1.pay(gw1, alice, CK4(91)); + env(check::cash(bob, chkId, CK4(91))); + ++bob.owners; + env.close(); + + // alice's owner count should increase since it created MPT. + // bob's owner count should increase due to the new MPT. + gw1.verifyOwners(__LINE__); + alice.verifyOwners(__LINE__); + bob.verifyOwners(__LINE__); + + // cleanup + gw1.cleanup(OF4, alice); + gw1.cleanup(CK4, alice); + gw1.cleanup(OF4, bob); + gw1.cleanup(CK4, bob); + } + + //------------ lsfMPTLock, check written by non-issuer ------------ + { + // Since gw1 has the lsfMPTLock flag set, there should be + // no automatic MPT creation between non-issuers. + + // Use offers to automatically create MPT. + MPT const OF4 = gw1["OF4"]; + gw1.set(OF4, tfMPTLock); + env(offer(alice, XRP(91), OF4(91)), ter(tecFROZEN)); + env.close(); + BEAST_EXPECT(env.le(keylet::mptoken(OF4, alice)) == nullptr); + env(offer(bob, OF4(91), XRP(91)), ter(tecFROZEN)); + env.close(); + + // No one's owner count should have changed. + gw1.verifyOwners(__LINE__); + alice.verifyOwners(__LINE__); + bob.verifyOwners(__LINE__); + + // Use check cashing to automatically create the trust line. + MPT const CK4 = gw1["CK4"]; + gw1.set(CK4, tfMPTLock); + uint256 const chkId{getCheckIndex(alice, env.seq(alice))}; + env(check::create(alice, bob, CK4(91)), ter(tecLOCKED)); + env.close(); + BEAST_EXPECT(env.le(keylet::mptoken(CK4, bob)) == nullptr); + env(check::cash(bob, chkId, CK4(91)), ter(tecNO_ENTRY)); + env.close(); + + // No one's owner count should have changed. + gw1.verifyOwners(__LINE__); + alice.verifyOwners(__LINE__); + bob.verifyOwners(__LINE__); + + // Because gw1 has set lsfGlobalFreeze, neither trust line + // is created. + BEAST_EXPECT(env.le(keylet::mptoken(OF4, bob)) == nullptr); + BEAST_EXPECT(env.le(keylet::mptoken(CK4, bob)) == nullptr); + + gw1.set(OF4, tfMPTUnlock); + gw1.set(CK4, tfMPTUnlock); + } + + //-------------- lsfRequireAuth, check written by issuer --------------- + + // We want to test the lsfRequireAuth flag, but we can't set that + // flag on an account that already has MPT. So we'll fund + // a new gateway and use that. + AccountOwns gw2{*this, env, "gw2", true}; + { + env.fund(XRP(5'000), gw2); + env.close(); + + // Set lsfRequireAuth on gw2. That should not stop any automatic + // MPT from being created. + env(fset(gw2, asfRequireAuth)); + env.close(); + + // Use offers to automatically create MPT. + MPT const OF5 = gw2["OF5"]; + env(offer(gw2, XRP(92), OF5(92))); + env.close(); + BEAST_EXPECT(env.le(keylet::mptoken(OF5, alice)) == nullptr); + env(offer(alice, OF5(92), XRP(92))); + ++alice.owners; + env.close(); + + // alice's owner count should increase due to the new MPT. + gw2.verifyOwners(__LINE__); + alice.verifyOwners(__LINE__); + bob.verifyOwners(__LINE__); + + // Use check cashing to automatically create MPT. + MPT const CK5 = gw2["CK5"]; + uint256 const chkId{getCheckIndex(gw2, env.seq(gw2))}; + env(check::create(gw2, alice, CK5(92))); + env.close(); + BEAST_EXPECT(env.le(keylet::mptoken(CK5, alice)) == nullptr); + env(check::cash(alice, chkId, CK5(92))); + verifyDeliveredAmount(env, CK5(92)); + ++alice.owners; + env.close(); + + // alice's owner count should increase due to the new MPT. + gw2.verifyOwners(__LINE__); + alice.verifyOwners(__LINE__); + bob.verifyOwners(__LINE__); + + // cleanup + gw2.cleanup(OF5, alice); + gw2.cleanup(CK5, alice); + } + + // Fund new gw to test since gw2 has MPTokenIssuance already created. + // Set RequireAuth flag. + AccountOwns gw3{*this, env, "gw3", true, true}; + { + env.fund(XRP(5'000), gw3); + env.close(); + // Use offers to automatically create the trust line. + MPT const OF5 = gw3["OF5"]; + std::uint32_t const gw3OfferSeq = {env.seq(gw3)}; + env(offer(gw3, XRP(92), OF5(92))); + ++gw3.owners; + env.close(); + BEAST_EXPECT(env.le(keylet::mptoken(OF5, alice)) == nullptr); + env(offer(alice, OF5(92), XRP(92)), ter(tecNO_AUTH)); + env.close(); + + // gw3 should still own the offer, but no one else's owner + // count should have changed. + gw3.verifyOwners(__LINE__); + alice.verifyOwners(__LINE__); + bob.verifyOwners(__LINE__); + + // Since we don't need it anymore, remove gw3's offer. + env(offer_cancel(gw3, gw3OfferSeq)); + --gw3.owners; + env.close(); + gw3.verifyOwners(__LINE__); + + // Use check cashing to automatically create the trust line. + MPT const CK5 = gw3["CK5"]; + uint256 const chkId{getCheckIndex(gw3, env.seq(gw3))}; + env(check::create(gw3, alice, CK5(92))); + ++gw3.owners; + env.close(); + BEAST_EXPECT(env.le(keylet::mptoken(CK5, alice)) == nullptr); + env(check::cash(alice, chkId, CK5(92)), ter(tecNO_AUTH)); + env.close(); + + // gw3 should still own the check, but no one else's owner + // count should have changed. + gw3.verifyOwners(__LINE__); + alice.verifyOwners(__LINE__); + bob.verifyOwners(__LINE__); + + // Because gw3 has set lsfRequireAuth, neither trust line + // is created. + BEAST_EXPECT(env.le(keylet::mptoken(OF5, alice)) == nullptr); + BEAST_EXPECT(env.le(keylet::mptoken(CK5, alice)) == nullptr); + + // Since we don't need it anymore, remove gw3's check. + env(check::cancel(gw3, chkId)); + --gw3.owners; + env.close(); + gw3.verifyOwners(__LINE__); + } + + //------------ lsfRequireAuth, check written by non-issuer ------------- + { + // gw2 lsfRequireAuth flag set should not affect + // automatic MPT creation between non-issuers. + + // Use offers to automatically create MPT. + MPT const OF5 = gw2["OF5"]; + gw2.authorize(OF5, alice); + gw2.pay(gw2, alice, OF5(91)); + env(offer(alice, XRP(91), OF5(91))); + env.close(); + env(offer(bob, OF5(91), XRP(91))); + ++bob.owners; + env.close(); + + // bob's owner count should increase due to the new MPT. + gw2.verifyOwners(__LINE__); + alice.verifyOwners(__LINE__); + bob.verifyOwners(__LINE__); + + // Use check cashing to automatically create the trust line. + MPT const CK5 = gw2["CK5"]; + gw2.authorize(CK5, alice); + gw2.pay(gw2, alice, CK5(91)); + uint256 const chkId{getCheckIndex(alice, env.seq(alice))}; + env(check::create(alice, bob, CK5(91))); + env.close(); + BEAST_EXPECT(env.le(keylet::mptoken(CK5, bob)) == nullptr); + env(check::cash(bob, chkId, CK5(91))); + ++bob.owners; + env.close(); + + // bob's owner count should increase due to the new MPT. + gw2.verifyOwners(__LINE__); + alice.verifyOwners(__LINE__); + bob.verifyOwners(__LINE__); + } + + //------------ lsfMPTRequireAuth, check written by non-issuer + //------------- + { + // Since gw3 has the lsfMPTRequireAuth flag set, there should be + // no automatic MPT creation between non-issuers. + + // Use offers to automatically create the trust line. + MPT const OF5 = gw3["OF5"]; + env(offer(alice, XRP(91), OF5(91)), ter(tecUNFUNDED_OFFER)); + env.close(); + env(offer(bob, OF5(91), XRP(91)), ter(tecNO_AUTH)); + BEAST_EXPECT(env.le(keylet::mptoken(OF5, bob)) == nullptr); + env.close(); + + gw3.verifyOwners(__LINE__); + alice.verifyOwners(__LINE__); + bob.verifyOwners(__LINE__); + + // Use check cashing to automatically create the trust line. + MPT const CK5 = gw3["CK5"]; + uint256 const chkId{getCheckIndex(alice, env.seq(alice))}; + env(check::create(alice, bob, CK5(91))); + env.close(); + BEAST_EXPECT(env.le(keylet::mptoken(CK5, bob)) == nullptr); + env(check::cash(bob, chkId, CK5(91)), ter(tecPATH_PARTIAL)); + env.close(); + + // Delete alice's check since it is no longer needed. + env(check::cancel(alice, chkId)); + env.close(); + + // No one's owner count should have changed. + gw3.verifyOwners(__LINE__); + alice.verifyOwners(__LINE__); + bob.verifyOwners(__LINE__); + + // Because gw3 has set lsfRequireAuth, neither trust line + // is created. + BEAST_EXPECT(env.le(keylet::mptoken(OF5, bob)) == nullptr); + BEAST_EXPECT(env.le(keylet::mptoken(CK5, bob)) == nullptr); + } + } + + void + testWithFeats(FeatureBitset features) + { + testCreateValid(features); + testCreateDisallowIncoming(features); + testCreateInvalid(features); + testCashMPT(features); + testCashXferFee(features); + testCashInvalid(features); + testCancelValid(features); + testWithTickets(features); + } + +public: + void + run() override + { + using namespace test::jtx; + auto const sa = testable_amendments(); + testWithFeats(sa); + + testMPTCreation(sa); + } +}; + +BEAST_DEFINE_TESTSUITE(CheckMPT, tx, xrpl); + +} // namespace xrpl diff --git a/src/test/app/Check_test.cpp b/src/test/app/Check_test.cpp index 1e5d90f650..f2c7ab3a38 100644 --- a/src/test/app/Check_test.cpp +++ b/src/test/app/Check_test.cpp @@ -1,70 +1,53 @@ -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include #include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include + namespace xrpl { -namespace test { -namespace jtx { - -/** Set Expiration on a JTx. */ -class expiration -{ -private: - std::uint32_t const expiry_; - -public: - explicit expiration(NetClock::time_point const& expiry) - : expiry_{expiry.time_since_epoch().count()} - { - } - - void - operator()(Env&, JTx& jt) const - { - jt[sfExpiration.jsonName] = expiry_; - } -}; - -/** Set SourceTag on a JTx. */ -class source_tag -{ -private: - std::uint32_t const tag_; - -public: - explicit source_tag(std::uint32_t tag) : tag_{tag} - { - } - - void - operator()(Env&, JTx& jt) const - { - jt[sfSourceTag.jsonName] = tag_; - } -}; - -/** Set DestinationTag on a JTx. */ -class dest_tag -{ -private: - std::uint32_t const tag_; - -public: - explicit dest_tag(std::uint32_t tag) : tag_{tag} - { - } - - void - operator()(Env&, JTx& jt) const - { - jt[sfDestinationTag.jsonName] = tag_; - } -}; - -} // namespace jtx -} // namespace test class Check_test : public beast::unit_test::suite { @@ -404,11 +387,17 @@ class Check_test : public beast::unit_test::suite env(check::create(alice, bob, USF(50)), ter(tecFROZEN)); env.close(); + env(check::create(gwF, bob, USF(50)), ter(tecFROZEN)); + env.close(); + env(fclear(gwF, asfGlobalFreeze)); env.close(); env(check::create(alice, bob, USF(50))); env.close(); + + env(check::create(gwF, bob, USF(50))); + env.close(); } { // Frozen trust line. Check creation should be similar to payment @@ -1064,8 +1053,8 @@ class Check_test : public beast::unit_test::suite double pct, double amount) { // Capture bob's and alice's balances so we can test at the end. - STAmount const aliceStart{env.balance(alice, USD.issue()).value()}; - STAmount const bobStart{env.balance(bob, USD.issue()).value()}; + STAmount const aliceStart{env.balance(alice, USD).value()}; + STAmount const bobStart{env.balance(bob, USD).value()}; // Set the modified quality. env(trust(truster, iou(1000)), inOrOut(pct)); @@ -1089,8 +1078,8 @@ class Check_test : public beast::unit_test::suite double pct, double amount) { // Capture bob's and alice's balances so we can test at the end. - STAmount const aliceStart{env.balance(alice, USD.issue()).value()}; - STAmount const bobStart{env.balance(bob, USD.issue()).value()}; + STAmount const aliceStart{env.balance(alice, USD).value()}; + STAmount const bobStart{env.balance(bob, USD).value()}; // Set the modified quality. env(trust(truster, iou(1000)), inOrOut(pct)); @@ -1153,7 +1142,7 @@ class Check_test : public beast::unit_test::suite double max2) { // Capture alice's balance so we can test at the end. It doesn't // make any sense to look at the balance of a gateway. - STAmount const aliceStart{env.balance(alice, USD.issue()).value()}; + STAmount const aliceStart{env.balance(alice, USD).value()}; // Set the modified quality. env(trust(truster, iou(1000)), inOrOut(pct)); @@ -1186,7 +1175,7 @@ class Check_test : public beast::unit_test::suite double max2) { // Capture alice's balance so we can test at the end. It doesn't // make any sense to look at the balance of the issuer. - STAmount const aliceStart{env.balance(alice, USD.issue()).value()}; + STAmount const aliceStart{env.balance(alice, USD).value()}; // Set the modified quality. env(trust(truster, iou(1000)), inOrOut(pct)); @@ -1297,6 +1286,14 @@ class Check_test : public beast::unit_test::suite env(check::create(alice, bob, USD(4))); env.close(); + uint256 const chkIdFroz4ToIssuer{getCheckIndex(alice, env.seq(alice))}; + env(check::create(alice, gw, USD(4))); + env.close(); + + uint256 const chkIdFroz4Issuer{getCheckIndex(gw, env.seq(gw))}; + env(check::create(gw, alice, USD(4))); + env.close(); + uint256 const chkIdNoDest1{getCheckIndex(alice, env.seq(alice))}; env(check::create(alice, bob, USD(1))); env.close(); @@ -1363,7 +1360,7 @@ class Check_test : public beast::unit_test::suite { IOU const wrongCurrency{gw["EUR"]}; STAmount badAmount{amount}; - badAmount.setIssue(wrongCurrency.issue()); + badAmount.setIssue(wrongCurrency); env(check::cash(bob, chkId, badAmount), ter(temMALFORMED)); env.close(); } @@ -1372,7 +1369,7 @@ class Check_test : public beast::unit_test::suite { IOU const wrongIssuer{alice["USD"]}; STAmount badAmount{amount}; - badAmount.setIssue(wrongIssuer.issue()); + badAmount.setIssue(wrongIssuer); env(check::cash(bob, chkId, badAmount), ter(temMALFORMED)); env.close(); } @@ -1419,6 +1416,17 @@ class Check_test : public beast::unit_test::suite env(check::cash(bob, chkIdFroz1, check::DeliverMin(USD(0.5))), ter(tecPATH_PARTIAL)); env.close(); + env(check::cash(gw, chkIdFroz4ToIssuer, USD(1)), ter(tecPATH_PARTIAL)); + env.close(); + env(check::cash(gw, chkIdFroz4ToIssuer, check::DeliverMin(USD(0.5))), + ter(tecPATH_PARTIAL)); + env.close(); + + env(check::cash(alice, chkIdFroz4Issuer, USD(1)), ter(tecFROZEN)); + env.close(); + env(check::cash(alice, chkIdFroz4Issuer, check::DeliverMin(USD(0.5))), ter(tecFROZEN)); + env.close(); + env(fclear(gw, asfGlobalFreeze)); env.close(); @@ -1428,6 +1436,9 @@ class Check_test : public beast::unit_test::suite env.require(balance(alice, USD(19))); env.require(balance(bob, USD(1))); + env(check::cash(gw, chkIdFroz4ToIssuer, USD(1))); + env.close(); + // Freeze individual trustlines. env(trust(gw, alice["USD"](0), tfSetFreeze)); env.close(); @@ -1441,7 +1452,7 @@ class Check_test : public beast::unit_test::suite env.close(); env(check::cash(bob, chkIdFroz2, USD(2))); env.close(); - env.require(balance(alice, USD(17))); + env.require(balance(alice, USD(16))); env.require(balance(bob, USD(3))); // Freeze bob's trustline. bob can't cash the check. @@ -1457,7 +1468,7 @@ class Check_test : public beast::unit_test::suite env.close(); env(check::cash(bob, chkIdFroz3, check::DeliverMin(USD(1)))); verifyDeliveredAmount(env, USD(3)); - env.require(balance(alice, USD(14))); + env.require(balance(alice, USD(13))); env.require(balance(bob, USD(6))); // Set bob's freeze bit in the other direction. Check @@ -1474,7 +1485,7 @@ class Check_test : public beast::unit_test::suite env.close(); env(check::cash(bob, chkIdFroz4, USD(4))); env.close(); - env.require(balance(alice, USD(10))); + env.require(balance(alice, USD(9))); env.require(balance(bob, USD(10))); } { @@ -1491,7 +1502,7 @@ class Check_test : public beast::unit_test::suite // bob can cash a check with a destination tag. env(check::cash(bob, chkIdHasDest2, USD(2))); env.close(); - env.require(balance(alice, USD(8))); + env.require(balance(alice, USD(7))); env.require(balance(bob, USD(12))); // Clear the RequireDest flag on bob's account so he can @@ -1500,7 +1511,7 @@ class Check_test : public beast::unit_test::suite env.close(); env(check::cash(bob, chkIdNoDest1, USD(1))); env.close(); - env.require(balance(alice, USD(7))); + env.require(balance(alice, USD(6))); env.require(balance(bob, USD(13))); } } @@ -1870,8 +1881,8 @@ class Check_test : public beast::unit_test::suite } }; - AccountOwns alice{*this, env, "alice", 0}; - AccountOwns bob{*this, env, "bob", 0}; + AccountOwns alice{.suite = *this, .env = env, .acct = "alice", .owners = 0}; + AccountOwns bob{.suite = *this, .env = env, .acct = "bob", .owners = 0}; // Fund with noripple so the accounts do not have any flags set. env.fund(XRP(5000), noripple(alice, bob)); @@ -1880,7 +1891,7 @@ class Check_test : public beast::unit_test::suite // Automatic trust line creation should fail if the check destination // can't afford the reserve for the trust line. { - AccountOwns const gw1{*this, env, "gw1", 0}; + AccountOwns const gw1{.suite = *this, .env = env, .acct = "gw1", .owners = 0}; // Fund gw1 with noripple (even though that's atypical for a // gateway) so it does not have any flags set. We'll set flags @@ -1960,7 +1971,7 @@ class Check_test : public beast::unit_test::suite if (!BEAST_EXPECT(!offerAmount.native() && !checkAmount.native())) return; - BEAST_EXPECT(offerAmount.issue().account == checkAmount.issue().account); + BEAST_EXPECT(offerAmount.getIssuer() == checkAmount.getIssuer()); BEAST_EXPECT(offerAmount.negative() == checkAmount.negative()); BEAST_EXPECT(offerAmount.mantissa() == checkAmount.mantissa()); BEAST_EXPECT(offerAmount.exponent() == checkAmount.exponent()); @@ -2000,7 +2011,7 @@ class Check_test : public beast::unit_test::suite { // No account root flags on any participant. // Automatic trust line from issuer to destination. - AccountOwns const gw1{*this, env, "gw1", 0}; + AccountOwns const gw1{.suite = *this, .env = env, .acct = "gw1", .owners = 0}; BEAST_EXPECT((*env.le(gw1))[sfFlags] == 0); BEAST_EXPECT((*env.le(alice))[sfFlags] == 0); @@ -2053,7 +2064,7 @@ class Check_test : public beast::unit_test::suite // Transfer of assets using offers does not require rippling. // So bob's offer is successfully crossed which creates the // trust line. - AccountOwns const gw1{*this, env, "gw1", 0}; + AccountOwns const gw1{.suite = *this, .env = env, .acct = "gw1", .owners = 0}; IOU const OF1 = gw1["OF1"]; env(offer(alice, XRP(97), OF1(97))); env.close(); @@ -2102,7 +2113,7 @@ class Check_test : public beast::unit_test::suite { // gw1 enables rippling. // Automatic trust line from issuer to non-issuer should still work. - AccountOwns const gw1{*this, env, "gw1", 0}; + AccountOwns const gw1{.suite = *this, .env = env, .acct = "gw1", .owners = 0}; env(fset(gw1, asfDefaultRipple)); env.close(); @@ -2150,7 +2161,7 @@ class Check_test : public beast::unit_test::suite // to non-issuer should work. // Use offers to automatically create the trust line. - AccountOwns const gw1{*this, env, "gw1", 0}; + AccountOwns const gw1{.suite = *this, .env = env, .acct = "gw1", .owners = 0}; IOU const OF2 = gw1["OF2"]; env(offer(alice, XRP(95), OF2(95))); env.close(); @@ -2191,7 +2202,7 @@ class Check_test : public beast::unit_test::suite // change any outcomes. // // Automatic trust line from issuer to non-issuer should still work. - AccountOwns const gw1{*this, env, "gw1", 0}; + AccountOwns const gw1{.suite = *this, .env = env, .acct = "gw1", .owners = 0}; env(fset(gw1, asfDepositAuth)); env(fset(alice, asfDepositAuth)); env(fset(bob, asfDepositAuth)); @@ -2241,7 +2252,7 @@ class Check_test : public beast::unit_test::suite // automatic trust line creation. // Use offers to automatically create the trust line. - AccountOwns const gw1{*this, env, "gw1", 0}; + AccountOwns const gw1{.suite = *this, .env = env, .acct = "gw1", .owners = 0}; IOU const OF3 = gw1["OF3"]; env(offer(alice, XRP(93), OF3(93))); env.close(); @@ -2278,7 +2289,7 @@ class Check_test : public beast::unit_test::suite { // Set lsfGlobalFreeze on gw1. That should stop any automatic // trust lines from being created. - AccountOwns const gw1{*this, env, "gw1", 0}; + AccountOwns const gw1{.suite = *this, .env = env, .acct = "gw1", .owners = 0}; env(fset(gw1, asfGlobalFreeze)); env.close(); @@ -2320,7 +2331,7 @@ class Check_test : public beast::unit_test::suite // no automatic trust line creation between non-issuers. // Use offers to automatically create the trust line. - AccountOwns const gw1{*this, env, "gw1", 0}; + AccountOwns const gw1{.suite = *this, .env = env, .acct = "gw1", .owners = 0}; IOU const OF4 = gw1["OF4"]; env(offer(alice, XRP(91), OF4(91)), ter(tecFROZEN)); env.close(); @@ -2359,7 +2370,7 @@ class Check_test : public beast::unit_test::suite // flag on an account that already has trust lines. So we'll fund // a new gateway and use that. { - AccountOwns gw2{*this, env, "gw2", 0}; + AccountOwns gw2{.suite = *this, .env = env, .acct = "gw2", .owners = 0}; env.fund(XRP(5000), gw2); env.close(); @@ -2423,7 +2434,7 @@ class Check_test : public beast::unit_test::suite // no automatic trust line creation between non-issuers. // Use offers to automatically create the trust line. - AccountOwns const gw2{*this, env, "gw2", 0}; + AccountOwns const gw2{.suite = *this, .env = env, .acct = "gw2", .owners = 0}; IOU const OF5 = gw2["OF5"]; env(offer(alice, XRP(91), OF5(91)), ter(tecUNFUNDED_OFFER)); env.close(); diff --git a/src/test/app/Clawback_test.cpp b/src/test/app/Clawback_test.cpp index 902acf3222..8efcece295 100644 --- a/src/test/app/Clawback_test.cpp +++ b/src/test/app/Clawback_test.cpp @@ -1,7 +1,28 @@ -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include + +#include +#include +#include namespace xrpl { diff --git a/src/test/app/Credentials_test.cpp b/src/test/app/Credentials_test.cpp index db09bfc0ca..3c9a658aca 100644 --- a/src/test/app/Credentials_test.cpp +++ b/src/test/app/Credentials_test.cpp @@ -1,18 +1,38 @@ -#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include #include #include +#include #include #include +#include #include +#include +#include #include #include +#include +#include #include -namespace xrpl { -namespace test { +namespace xrpl::test { struct Credentials_test : public beast::unit_test::suite { @@ -471,7 +491,7 @@ struct Credentials_test : public beast::unit_test::suite { testcase("Credentials fail, expiration in the past."); auto jv = credentials::create(subject, issuer, credType); - // current time in ripple epoch - 1s + // current time in XRPL epoch - 1s uint32_t const t = env.current()->header().parentCloseTime.time_since_epoch().count() - 1; jv[sfExpiration.jsonName] = t; @@ -812,7 +832,7 @@ struct Credentials_test : public beast::unit_test::suite testcase("CredentialsDelete fail, time not expired yet."); auto jv = credentials::create(subject, issuer, credType); - // current time in ripple epoch + 1000s + // current time in XRPL epoch + 1000s uint32_t const t = env.current()->header().parentCloseTime.time_since_epoch().count() + 1000; jv[sfExpiration.jsonName] = t; @@ -1028,5 +1048,4 @@ struct Credentials_test : public beast::unit_test::suite BEAST_DEFINE_TESTSUITE(Credentials, app, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/app/CrossingLimitsMPT_test.cpp b/src/test/app/CrossingLimitsMPT_test.cpp new file mode 100644 index 0000000000..8efacaae21 --- /dev/null +++ b/src/test/app/CrossingLimitsMPT_test.cpp @@ -0,0 +1,442 @@ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace xrpl::test { + +class CrossingLimitsMPT_test : public beast::unit_test::suite +{ +public: + void + testStepLimit(FeatureBitset features) + { + testcase("Step Limit"); + + using namespace jtx; + Env env(*this, features); + + auto const gw = Account("gateway"); + + env.fund(XRP(100'000'000), gw, "alice", "bob", "carol", "dan"); + MPT const USD = + MPTTester({.env = env, .issuer = gw, .holders = {"bob", "dan"}, .maxAmt = 2}); + env(pay(gw, "bob", USD(1))); + env(pay(gw, "dan", USD(1))); + n_offers(env, 2'000, "bob", XRP(1), USD(1)); + n_offers(env, 1, "dan", XRP(1), USD(1)); + + // Alice offers to buy 1000 XRP for 1000 USD. She takes Bob's first + // offer, removes 999 more as unfunded, then hits the step limit. + env(offer("alice", USD(1'000), XRP(1'000))); + env.require(balance("alice", USD(1))); + env.require(owners("alice", 2)); + env.require(balance("bob", USD(0))); + env.require(owners("bob", 1'001)); + env.require(balance("dan", USD(1))); + env.require(owners("dan", 2)); + + // Carol offers to buy 1000 XRP for 1000 USD. She removes Bob's next + // 1000 offers as unfunded and hits the step limit. + env(offer("carol", USD(1'000), XRP(1'000))); + env.require(balance("carol", USD(none))); + env.require(owners("carol", 1)); + env.require(balance("bob", USD(0))); + env.require(owners("bob", 1)); + env.require(balance("dan", USD(1))); + env.require(owners("dan", 2)); + } + + void + testCrossingLimit(FeatureBitset features) + { + testcase("Crossing Limit"); + + using namespace jtx; + Env env(*this, features); + + auto const gw = Account("gateway"); + + int const maxConsumed = 1'000; + + env.fund(XRP(100'000'000), gw, "alice", "bob", "carol"); + int const bobsOfferCount = maxConsumed + 150; + MPT const USD = + MPTTester({.env = env, .issuer = gw, .holders = {"bob"}, .maxAmt = bobsOfferCount}); + env(pay(gw, "bob", USD(bobsOfferCount))); + env.close(); + n_offers(env, bobsOfferCount, "bob", XRP(1), USD(1)); + + // Alice offers to buy Bob's offers. However, she hits the offer + // crossing limit, so she can't buy them all at once. + env(offer("alice", USD(bobsOfferCount), XRP(bobsOfferCount))); + env.close(); + env.require(balance("alice", USD(maxConsumed))); + env.require(balance("bob", USD(150))); + env.require(owners("bob", 150 + 1)); + + // Carol offers to buy 1000 XRP for 1000 USD. She takes Bob's + // remaining 150 offers without hitting a limit. + env(offer("carol", USD(1'000), XRP(1'000))); + env.close(); + env.require(balance("carol", USD(150))); + env.require(balance("bob", USD(0))); + env.require(owners("bob", 1)); + } + + void + testStepAndCrossingLimit(FeatureBitset features) + { + testcase("Step And Crossing Limit"); + + using namespace jtx; + Env env(*this, features); + + auto const gw = Account("gateway"); + + env.fund(XRP(100'000'000), gw, "alice", "bob", "carol", "dan", "evita"); + + int const maxConsumed = 1'000; + int const evitaOfferCount{maxConsumed + 49}; + + MPT const USD = MPTTester( + {.env = env, + .issuer = gw, + .holders = {"bob", "alice", "carol", "evita"}, + .maxAmt = 2'000 + evitaOfferCount + 1}); + + env(pay(gw, "alice", USD(1000))); + env(pay(gw, "carol", USD(1))); + env(pay(gw, "evita", USD(evitaOfferCount + 1))); + + // Give carol an extra 150 (unfunded) offers when we're using Taker + // to accommodate that difference. + int const carolOfferCount{700}; + n_offers(env, 400, "alice", XRP(1), USD(1)); + n_offers(env, carolOfferCount, "carol", XRP(1), USD(1)); + n_offers(env, evitaOfferCount, "evita", XRP(1), USD(1)); + + // Bob offers to buy 1000 XRP for 1000 USD. He takes all 400 USD from + // Alice's offers, 1 USD from Carol's and then removes 599 of Carol's + // offers as unfunded, before hitting the step limit. + env(offer("bob", USD(1000), XRP(1000))); + env.require(balance("bob", USD(401))); + env.require(balance("alice", USD(600))); + env.require(owners("alice", 1)); + env.require(balance("carol", USD(0))); + env.require(owners("carol", carolOfferCount - 599)); + env.require(balance("evita", USD(evitaOfferCount + 1))); + env.require(owners("evita", evitaOfferCount + 1)); + + // Dan offers to buy maxConsumed + 50 XRP USD. He removes all of + // Carol's remaining offers as unfunded, then takes + // (maxConsumed - 100) USD from Evita's, hitting the crossing limit. + env(offer("dan", USD(maxConsumed + 50), XRP(maxConsumed + 50))); + env.require(balance("dan", USD(maxConsumed - 100))); + env.require(owners("dan", 2)); + env.require(balance("alice", USD(600))); + env.require(owners("alice", 1)); + env.require(balance("carol", USD(0))); + env.require(owners("carol", 1)); + env.require(balance("evita", USD(150))); + env.require(owners("evita", 150)); + } + + void + testAutoBridgedLimits(FeatureBitset features) + { + testcase("Auto Bridged Limits"); + + // Extracts as much as possible in one book at one Quality + // before proceeding to the other book. This reduces the number of + // times we change books. + + // If any book step in a payment strand consumes 1000 offers, the + // liquidity from the offers is used, but that strand will be marked as + // dry for the remainder of the transaction. + + using namespace jtx; + + auto const gw = Account("gateway"); + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const carol = Account("carol"); + + // There are two almost identical tests. There is a strand with a large + // number of unfunded offers that will cause the strand to be marked dry + // even though there will still be liquidity available on that strand. + // In the first test, the strand has the best initial quality. In the + // second test the strand does not have the best quality (the + // implementation has to handle this case correct and not mark the + // strand dry until the liquidity is actually used) + + // The implementation allows any single step to consume at most 1000 + // offers. With the `FlowSortStrands` feature enabled, if the total + // number of offers consumed by all the steps combined exceeds 1500, the + // payment stops. + { + auto test = [&](auto&& issue1, auto&& issue2) { + Env env(*this, features); + + env.fund(XRP(100'000'000), gw, alice, bob, carol); + + auto const USD = issue1( + {.env = env, + .token = "USD", + .issuer = gw, + .holders = {alice, carol}, + .limit = maxMPTokenAmount}); + auto const EUR = issue2( + {.env = env, + .token = "EUR", + .issuer = gw, + .holders = {bob}, + .limit = maxMPTokenAmount}); + + env(pay(gw, alice, USD(4'000))); + env(pay(gw, carol, USD(3))); + + // Notice the strand with the 800 unfunded offers has the + // initial best quality + n_offers(env, 2'000, alice, EUR(2), XRP(1)); + n_offers(env, 100, alice, XRP(1), USD(4)); + n_offers(env, 801, carol, XRP(1), + USD(3)); // only one offer is funded + n_offers(env, 1'000, alice, XRP(1), USD(3)); + + n_offers(env, 1, alice, EUR(500), USD(500)); + + // Bob offers to buy 2000 USD for 2000 EUR; He starts with 2000 + // EUR + // 1. The best quality is the autobridged offers that take 2 + // EUR and give 4 USD. + // Bob spends 200 EUR and receives 400 USD. + // 100 EUR->XRP offers consumed. + // 100 XRP->USD offers consumed. + // 200 total offers consumed. + // + // 2. The best quality is the autobridged offers that take 2 + // EUR and give 3 USD. + // a. One of Carol's offers is taken. This leaves her other + // offers unfunded. + // b. Carol's remaining 800 offers are consumed as unfunded. + // c. 199 of alice's XRP(1) to USD(3) offers are consumed. + // A book step is allowed to consume a maximum of 1000 + // offers at a given quality, and that limit is now + // reached. + // d. Now the strand is dry, even though there are still + // funded XRP(1) to USD(3) offers available. + // Bob has spent 400 EUR and received 600 USD in this + // step. 200 EUR->XRP offers consumed 800 unfunded + // XRP->USD offers consumed 200 funded XRP->USD offers + // consumed (1 carol, 199 alice) 1400 total offers + // consumed so far (100 left before the limit) + // 3. The best is the non-autobridged offers that takes 500 EUR + // and gives 500 USD. + // Bob started with 2000 EUR + // Bob spent 500 EUR (100+400) + // Bob has 1500 EUR left + // In this step: + // Bob spends 500 EUR and receives 500 USD. + // In total: + // Bob spent 1100 EUR (200 + 400 + 500) + // Bob has 900 EUR remaining (2000 - 1100) + // Bob received 1500 USD (400 + 600 + 500) + // Alice spent 1497 USD (100*4 + 199*3 + 500) + // Alice has 2503 remaining (4000 - 1497) + // Alice received 1100 EUR (200 + 400 + 500) + env(pay(gw, bob, EUR(2'000))); + env.close(); + env(offer(bob, USD(4'000), EUR(4'000))); + env.close(); + + env.require(balance(bob, USD(1'500))); + env.require(balance(bob, EUR(900))); + env.require(offers(bob, 1)); + env.require(owners(bob, 3)); + + env.require(balance(alice, USD(2'503))); + env.require(balance(alice, EUR(1'100))); + auto const numAOffers = 2'000 + 100 + 1'000 + 1 - (2 * 100 + 2 * 199 + 1 + 1); + env.require(offers(alice, numAOffers)); + env.require(owners(alice, numAOffers + 2)); + + env.require(offers(carol, 0)); + }; + testHelper2TokensMix(test); + } + { + auto test = [&](auto&& issue1, auto&& issue2) { + Env env(*this, features); + + env.fund(XRP(100'000'000), gw, alice, bob, carol); + + auto const USD = issue1( + {.env = env, + .token = "USD", + .issuer = gw, + .holders = {alice, carol}, + .limit = maxMPTokenAmount}); + auto const EUR = issue2( + {.env = env, + .token = "EUR", + .issuer = gw, + .holders = {bob}, + .limit = maxMPTokenAmount}); + + env(pay(gw, alice, USD(4'000))); + env(pay(gw, carol, USD(3))); + + // Notice the strand with the 800 unfunded offers does not have + // the initial best quality + n_offers(env, 1, alice, EUR(1), USD(10)); + n_offers(env, 2'000, alice, EUR(2), XRP(1)); + n_offers(env, 100, alice, XRP(1), USD(4)); + n_offers(env, 801, carol, XRP(1), + USD(3)); // only one offer is funded + n_offers(env, 1'000, alice, XRP(1), USD(3)); + + n_offers(env, 1, alice, EUR(499), USD(499)); + + // Bob offers to buy 2000 USD for 2000 EUR; He starts with 2000 + // EUR + // 1. The best quality is the offer that takes 1 EUR and gives + // 10 USD + // Bob spends 1 EUR and receives 10 USD. + // + // 2. The best quality is the autobridged offers that takes 2 + // EUR and gives 4 USD. + // Bob spends 200 EUR and receives 400 USD. + // + // 3. The best quality is the autobridged offers that takes 2 + // EUR and gives 3 USD. + // a. One of Carol's offers is taken. This leaves her other + // offers unfunded. + // b. Carol's remaining 800 offers are consumed as unfunded. + // c. 199 of alice's XRP(1) to USD(3) offers are consumed. + // A book step is allowed to consume a maximum of 1000 + // offers at a given quality, and that limit is now + // reached. + // d. Now the strand is dry, even though there are still + // funded XRP(1) to USD(3) offers available. Bob has spent + // 400 EUR and received 600 USD in this step. (200 funded + // offers consumed 800 unfunded offers) + // 4. The best is the non-autobridged offers that takes 499 EUR + // and gives 499 USD. + // Bob has 2000 EUR, and has spent 1+200+400=601 EUR. He has + // 1399 left. Bob spent 499 EUR and receives 499 USD. + // In total: Bob spent EUR(1 + 200 + 400 + 499) = EUR(1100). He + // started with 2000 so has 900 remaining + // Bob received USD(10 + 400 + 600 + 499) = USD(1509). + // Alice spent 10 + 100*4 + 199*3 + 499 = 1506 USD. + // She started with 4000 so has 2494 USD remaining. + // Alice received 200 + 400 + 500 = 1100 EUR + env.close(); + env(pay(gw, bob, EUR(2'000))); + env.close(); + env(offer(bob, USD(4'000), EUR(4'000))); + env.close(); + + env.require(balance(bob, USD(1'509))); + env.require(balance(bob, EUR(900))); + env.require(offers(bob, 1)); + env.require(owners(bob, 3)); + + env.require(balance(alice, USD(2'494))); + env.require(balance(alice, EUR(1'100))); + auto const numAOffers = + 1 + 2'000 + 100 + 1'000 + 1 - (1 + 2 * 100 + 2 * 199 + 1 + 1); + env.require(offers(alice, numAOffers)); + env.require(owners(alice, numAOffers + 2)); + + env.require(offers(carol, 0)); + }; + testHelper2TokensMix(test); + } + } + + void + testOfferOverflow(FeatureBitset features) + { + testcase("Offer Overflow"); + + using namespace jtx; + + auto const gw = Account("gateway"); + auto const alice = Account("alice"); + auto const bob = Account("bob"); + + Env env(*this, features); + + env.fund(XRP(100'000'000), gw, alice, bob); + + MPT const USD = MPTTester({.env = env, .issuer = gw, .holders = {alice, bob}}); + + env(pay(gw, alice, USD(8'000))); + env.close(); + + // The new flow cross handles consuming excessive offers differently + // than the old offer crossing code. In the old code, the total number + // of consumed offers is tracked, and the crossings will stop after this + // limit is hit. In the new code, the number of offers is tracked per + // offerbook and per quality. This test shows how they can differ. Set + // up a book with many offers. At each quality keep the number of offers + // below the limit. However, if all the offers are consumed it would + // create a tecOVERSIZE error. + + // The featureFlowSortStrands introduces a way of tracking the total + // number of consumed offers; with this feature the transaction no + // longer fails with a tecOVERSIZE error. + // The implementation allows any single step to consume at most 1000 + // offers. With the `FlowSortStrands` feature enabled, if the total + // number of offers consumed by all the steps combined exceeds 1500, the + // payment stops. Since the first set of offers consumes 998 offers, the + // second set will consume 998, which is not over the limit and the + // payment stops. So 2*998, or 1996 is the expected value when + // `FlowSortStrands` is enabled. + n_offers(env, 998, alice, XRP(1.00), USD(1)); + n_offers(env, 998, alice, XRP(0.99), USD(1)); + n_offers(env, 998, alice, XRP(0.98), USD(1)); + n_offers(env, 998, alice, XRP(0.97), USD(1)); + n_offers(env, 998, alice, XRP(0.96), USD(1)); + n_offers(env, 998, alice, XRP(0.95), USD(1)); + + auto const expectedTER = tesSUCCESS; + + env(offer(bob, USD(8'000), XRP(8'000)), ter(expectedTER)); + env.close(); + + auto const expectedUSD = USD(1'996); + + env.require(balance(bob, expectedUSD)); + } + + void + run() override + { + using namespace jtx; + auto const features = testable_amendments(); + testStepLimit(features); + testCrossingLimit(features); + testStepAndCrossingLimit(features); + testAutoBridgedLimits(features); + testOfferOverflow(features); + } +}; + +BEAST_DEFINE_TESTSUITE_MANUAL_PRIO(CrossingLimitsMPT, tx, xrpl, 10); + +} // namespace xrpl::test diff --git a/src/test/app/CrossingLimits_test.cpp b/src/test/app/CrossingLimits_test.cpp index 0451822492..0cbe57a464 100644 --- a/src/test/app/CrossingLimits_test.cpp +++ b/src/test/app/CrossingLimits_test.cpp @@ -1,10 +1,20 @@ -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include #include +#include -namespace xrpl { -namespace test { +namespace xrpl::test { class CrossingLimits_test : public beast::unit_test::suite { @@ -143,107 +153,15 @@ public: env.require(owners("evita", 150)); } - void - testAutoBridgedLimitsTaker(FeatureBitset features) - { - testcase("Auto Bridged Limits Taker"); - - using namespace jtx; - Env env(*this, features); - - auto const gw = Account("gateway"); - auto const USD = gw["USD"]; - auto const EUR = gw["EUR"]; - - env.fund(XRP(100000000), gw, "alice", "bob", "carol", "dan", "evita"); - - env.trust(USD(2000), "alice"); - env(pay(gw, "alice", USD(2000))); - env.trust(USD(1000), "carol"); - env(pay(gw, "carol", USD(3))); - env.trust(USD(1000), "evita"); - env(pay(gw, "evita", USD(1000))); - - n_offers(env, 302, "alice", EUR(2), XRP(1)); - n_offers(env, 300, "alice", XRP(1), USD(4)); - n_offers(env, 497, "carol", XRP(1), USD(3)); - n_offers(env, 1001, "evita", EUR(1), USD(1)); - - // Bob offers to buy 2000 USD for 2000 EUR, even though he only has - // 1000 EUR. - // 1. He spends 600 EUR taking Alice's auto-bridged offers and - // gets 1200 USD for that. - // 2. He spends another 2 EUR taking one of Alice's EUR->XRP and - // one of Carol's XRP-USD offers. He gets 3 USD for that. - // 3. The remainder of Carol's offers are now unfunded. We've - // consumed 602 offers so far. We now chew through 398 more - // of Carol's unfunded offers until we hit the 1000 offer limit. - // This sets have_bridge to false -- we will handle no more - // bridged offers. - // 4. However, have_direct is still true. So we go around one more - // time and take one of Evita's offers. - // 5. After taking one of Evita's offers we notice (again) that our - // offer count was exceeded. So we completely stop after taking - // one of Evita's offers. - env.trust(EUR(10000), "bob"); - env.close(); - env(pay(gw, "bob", EUR(1000))); - env.close(); - env(offer("bob", USD(2000), EUR(2000))); - env.require(balance("bob", USD(1204))); - env.require(balance("bob", EUR(397))); - - env.require(balance("alice", USD(800))); - env.require(balance("alice", EUR(602))); - env.require(offers("alice", 1)); - env.require(owners("alice", 3)); - - env.require(balance("carol", USD(0))); - env.require(balance("carol", EUR(none))); - env.require(offers("carol", 100)); - env.require(owners("carol", 101)); - - env.require(balance("evita", USD(999))); - env.require(balance("evita", EUR(1))); - env.require(offers("evita", 1000)); - env.require(owners("evita", 1002)); - - // Dan offers to buy 900 EUR for 900 USD. - // 1. He removes all 100 of Carol's remaining unfunded offers. - // 2. Then takes 850 USD from Evita's offers. - // 3. Consuming 850 of Evita's funded offers hits the crossing - // limit. So Dan's offer crossing stops even though he would - // be willing to take another 50 of Evita's offers. - env.trust(EUR(10000), "dan"); - env.close(); - env(pay(gw, "dan", EUR(1000))); - env.close(); - - env(offer("dan", USD(900), EUR(900))); - env.require(balance("dan", USD(850))); - env.require(balance("dan", EUR(150))); - - env.require(balance("alice", USD(800))); - env.require(balance("alice", EUR(602))); - env.require(offers("alice", 1)); - env.require(owners("alice", 3)); - - env.require(balance("carol", USD(0))); - env.require(balance("carol", EUR(none))); - env.require(offers("carol", 0)); - env.require(owners("carol", 1)); - - env.require(balance("evita", USD(149))); - env.require(balance("evita", EUR(851))); - env.require(offers("evita", 150)); - env.require(owners("evita", 152)); - } - void testAutoBridgedLimits(FeatureBitset features) { testcase("Auto Bridged Limits"); + // Extracts as much as possible in one book at one Quality + // before proceeding to the other book. This reduces the number of + // times we change books. + // If any book step in a payment strand consumes 1000 offers, the // liquidity from the offers is used, but that strand will be marked as // dry for the remainder of the transaction. @@ -490,5 +408,4 @@ public: BEAST_DEFINE_TESTSUITE_MANUAL_PRIO(CrossingLimits, app, xrpl, 10); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/app/DID_test.cpp b/src/test/app/DID_test.cpp index 20c367d64f..bd3ea59971 100644 --- a/src/test/app/DID_test.cpp +++ b/src/test/app/DID_test.cpp @@ -1,12 +1,21 @@ -#include +#include +#include +#include +#include +#include // IWYU pragma: keep +#include +#include +#include +#include + +#include #include #include +#include +#include -#include - -namespace xrpl { -namespace test { +namespace xrpl::test { struct DID_test : public beast::unit_test::suite { @@ -369,5 +378,4 @@ struct DID_test : public beast::unit_test::suite BEAST_DEFINE_TESTSUITE(DID, app, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/app/DNS_test.cpp b/src/test/app/DNS_test.cpp index 2273a77e09..3997da60db 100644 --- a/src/test/app/DNS_test.cpp +++ b/src/test/app/DNS_test.cpp @@ -1,14 +1,22 @@ -#include +#include + +#include #include #include +#include + +#include +#include #include +#include #include +#include +#include -namespace xrpl { -namespace test { +namespace xrpl::test { class DNS_test : public beast::unit_test::suite { @@ -111,5 +119,4 @@ public: BEAST_DEFINE_TESTSUITE_MANUAL_PRIO(DNS, app, xrpl, 20); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/app/Delegate_test.cpp b/src/test/app/Delegate_test.cpp index 3f6d5e6b47..5ed8c79146 100644 --- a/src/test/app/Delegate_test.cpp +++ b/src/test/app/Delegate_test.cpp @@ -1,12 +1,56 @@ -#include +#include #include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include -namespace xrpl { -namespace test { +#include +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl::test { class Delegate_test : public beast::unit_test::suite { void @@ -274,37 +318,42 @@ class Delegate_test : public beast::unit_test::suite testcase("test fee"); using namespace jtx; - Env env(*this); - Account const alice{"alice"}; - Account const bob{"bob"}; - Account const carol{"carol"}; - env.fund(XRP(10000), alice, carol); - env.fund(XRP(1000), bob); - env.close(); + // Common setup: fund alice, bob, carol with 1000 XRP. + auto setup = [&](Env& env) { + Account const alice{"alice"}; + Account const bob{"bob"}; + Account const carol{"carol"}; + env.fund(XRP(1000), alice, bob, carol); + env.close(); + return std::make_tuple(alice, bob, carol); + }; + // No fee deduction for terNO_DELEGATE_PERMISSION. { - auto aliceBalance = env.balance(alice); - auto bobBalance = env.balance(bob); - auto carolBalance = env.balance(carol); + Env env(*this); + auto [alice, bob, carol] = setup(env); - env(pay(alice, carol, XRP(100)), - fee(XRP(2000)), - delegate::as(bob), - ter(terNO_DELEGATE_PERMISSION)); + auto const aliceBalance = env.balance(alice); + auto const bobBalance = env.balance(bob); + auto const carolBalance = env.balance(carol); + + env(pay(alice, carol, XRP(100)), delegate::as(bob), ter(terNO_DELEGATE_PERMISSION)); env.close(); BEAST_EXPECT(env.balance(alice) == aliceBalance); BEAST_EXPECT(env.balance(bob) == bobBalance); BEAST_EXPECT(env.balance(carol) == carolBalance); } - env(delegate::set(alice, bob, {"Payment"})); - env.close(); - + // Delegate pays the fee successfully. { - // Delegate pays the fee - auto aliceBalance = env.balance(alice); - auto bobBalance = env.balance(bob); - auto carolBalance = env.balance(carol); + Env env(*this); + auto [alice, bob, carol] = setup(env); + env(delegate::set(alice, bob, {"Payment"})); + env.close(); + + auto const aliceBalance = env.balance(alice); + auto const bobBalance = env.balance(bob); + auto const carolBalance = env.balance(carol); auto const sendAmt = XRP(100); auto const feeAmt = XRP(10); @@ -315,11 +364,16 @@ class Delegate_test : public beast::unit_test::suite BEAST_EXPECT(env.balance(carol) == carolBalance + sendAmt); } + // Bob has insufficient balance to pay the fee, will get terINSUF_FEE_B. { - // insufficient balance to pay fee - auto aliceBalance = env.balance(alice); - auto bobBalance = env.balance(bob); - auto carolBalance = env.balance(carol); + Env env(*this); + auto [alice, bob, carol] = setup(env); + env(delegate::set(alice, bob, {"Payment"})); + env.close(); + + auto const aliceBalance = env.balance(alice); + auto const bobBalance = env.balance(bob); + auto const carolBalance = env.balance(carol); env(pay(alice, carol, XRP(100)), fee(XRP(2000)), @@ -331,22 +385,143 @@ class Delegate_test : public beast::unit_test::suite BEAST_EXPECT(env.balance(carol) == carolBalance); } + // The delegated account has enough balance to pay and delegator has enough reserve { - // fee is paid by Delegate - // on context reset (tec error) - auto aliceBalance = env.balance(alice); - auto bobBalance = env.balance(bob); - auto carolBalance = env.balance(carol); - auto const feeAmt = XRP(10); + // Common setup: fund accounts and grant Bob permission to pay on Alice's behalf. + // Alice is funded with exactly (paymentAmount + reserve + baseFee): baseFee covers + // the DelegateSet tx cost, leaving Alice with exactly (paymentAmount + reserve). + // highFee = reserve + baseFee, strictly greater than reserve, so that + // max(reserve, highFee) = highFee — making the direct payment check fail. + auto setup = [&](Env& env) { + Account const alice{"alice"}; + Account const bob{"bob"}; + Account const carol{"carol"}; - env(pay(alice, carol, XRP(20000)), - fee(feeAmt), - delegate::as(bob), - ter(tecUNFUNDED_PAYMENT)); + auto const baseFee = env.current()->fees().base; + auto const reserve = env.current()->fees().accountReserve(1); + auto const paymentAmount = XRP(1); + auto const highFee = reserve + baseFee; + BEAST_EXPECT(highFee > reserve); + + env.fund(paymentAmount + reserve + baseFee, alice); + env.fund(XRP(1000), bob); + env.fund(XRP(1000), carol); + env.close(); + + env(delegate::set(alice, bob, {"Payment"})); + env.close(); + + env.require(balance(alice, paymentAmount + reserve)); + + return std::make_tuple(alice, bob, carol, paymentAmount, highFee, reserve); + }; + + // Alice's balance (paymentAmount + reserve) is insufficient to cover both + // the payment and highFee directly. Even though fees are allowed to dip + // below reserve, when Alice pays the fee herself the required funds = + // paymentAmount + max(reserve, highFee) = paymentAmount + highFee + // (since highFee > reserve), which still exceeds her balance. + // tec: highFee is consumed from Alice's balance. + { + Env env(*this); + auto [alice, bob, carol, paymentAmount, highFee, reserve] = setup(env); + auto const aliceBalance = env.balance(alice); + auto const bobBalance = env.balance(bob); + auto const carolBalance = env.balance(carol); + + env(pay(alice, carol, paymentAmount), fee(highFee), ter(tecUNFUNDED_PAYMENT)); + + // tec consumes the fee from Alice; carol and bob are unaffected. + BEAST_EXPECT(env.balance(alice) == aliceBalance - highFee); + BEAST_EXPECT(env.balance(bob) == bobBalance); + BEAST_EXPECT(env.balance(carol) == carolBalance); + } + + // The payment succeeds because the delegated account pays the fee. + // Alice only needs (paymentAmount + reserve). + { + Env env(*this); + auto [alice, bob, carol, paymentAmount, highFee, reserve] = setup(env); + + auto const alicePrePay = env.balance(alice, XRP); + auto const bobPrePay = env.balance(bob, XRP); + auto const carolPrePay = env.balance(carol, XRP); + + env(pay(alice, carol, paymentAmount), delegate::as(bob), fee(highFee)); + env.close(); + + env.require(balance(alice, alicePrePay - paymentAmount)); + env.require(balance(bob, bobPrePay - highFee)); + env.require(balance(carol, carolPrePay + paymentAmount)); + } + } + + // Delegated account can pay the fee even if it dips below reserve. + { + Env env(*this); + Account const alice{"alice"}; + Account const bob{"bob"}; + Account const carol{"carol"}; + + auto const baseFee = env.current()->fees().base; + auto const baseReserve = env.current()->fees().accountReserve(0); + + env.fund(env.current()->fees().accountReserve(1) + baseFee + XRP(1), alice); + env.fund(baseReserve, bob); + env.fund(XRP(1000), carol); env.close(); - BEAST_EXPECT(env.balance(alice) == aliceBalance); - BEAST_EXPECT(env.balance(bob) == bobBalance - feeAmt); - BEAST_EXPECT(env.balance(carol) == carolBalance); + + env(delegate::set(alice, bob, {"Payment"})); + env.close(); + + auto const alicePreTx = env.balance(alice, XRP); + auto const bobPreTx = env.balance(bob, XRP); + + // After paying for this transaction, bob's balance will + // dip below the base reserve + env(pay(alice, carol, XRP(1)), delegate::as(bob)); + env.close(); + + // Bob's balance is now less than the base reserve. + BEAST_EXPECT(env.balance(bob, XRP) < baseReserve); + env.require(balance(bob, bobPreTx - drops(baseFee))); + + // Alice's balance only decreased by the 1.0 XRP she sent. + env.require(balance(alice, alicePreTx - XRP(1))); + } + + // The delegated account has enough balance for the fee, but delegator + // runs into tecUNFUNDED_PAYMENT. + { + Env env(*this); + Account const alice{"alice"}; + Account const bob{"bob"}; + Account const carol{"carol"}; + + auto const baseFee = env.current()->fees().base; + auto const reserve = env.current()->fees().accountReserve(1); + + // Alice is funded with (reserve + baseFee): after DelegateSet she has + // exactly 'reserve', which is insufficient to send XRP(10) while keeping + // reserve. Bob has plenty to pay the fee. + env.fund(reserve + baseFee, alice); + env.fund(XRP(1000), bob); + env.fund(XRP(1000), carol); + env.close(); + + env(delegate::set(alice, bob, {"Payment"})); + env.close(); + + auto const alicePrePay = env.balance(alice, XRP); + auto const bobPrePay = env.balance(bob, XRP); + auto const carolPrePay = env.balance(carol, XRP); + + // Bob pays the fee, but Alice has insufficient balance to send XRP(10). + env(pay(alice, carol, XRP(10)), delegate::as(bob), ter(tecUNFUNDED_PAYMENT)); + + env.require(balance(alice, alicePrePay)); + env.require(balance(bob, bobPrePay - drops(baseFee))); + env.require(balance(carol, carolPrePay)); } } @@ -1238,8 +1413,8 @@ class Delegate_test : public beast::unit_test::suite // test MPTokenIssuanceUnlock and MPTokenIssuanceLock permissions { Env env(*this); - Account alice{"alice"}; - Account bob{"bob"}; + Account const alice{"alice"}; + Account const bob{"bob"}; env.fund(XRP(100000), alice, bob); env.close(); @@ -1285,8 +1460,8 @@ class Delegate_test : public beast::unit_test::suite // test mix of granular and transaction level permission { Env env(*this); - Account alice{"alice"}; - Account bob{"bob"}; + Account const alice{"alice"}; + Account const bob{"bob"}; env.fund(XRP(100000), alice, bob); env.close(); @@ -1332,8 +1507,8 @@ class Delegate_test : public beast::unit_test::suite // tfFullyCanonicalSig won't block delegated transaction { Env env(*this); - Account alice{"alice"}; - Account bob{"bob"}; + Account const alice{"alice"}; + Account const bob{"bob"}; env.fund(XRP(100000), alice, bob); env.close(); @@ -1410,11 +1585,9 @@ class Delegate_test : public beast::unit_test::suite { Env env(*this); - Account const alice{"alice"}; Account const bob{"bob"}; Account const carol{"carol"}; - env.fund(XRP(100000), alice, bob, carol); env.close(); @@ -1448,11 +1621,9 @@ class Delegate_test : public beast::unit_test::suite { Env env(*this); - Account const alice{"alice"}; Account const bob{"bob"}; Account const carol{"carol"}; - env.fund(XRP(100000), alice, bob, carol); env.close(); @@ -1492,8 +1663,8 @@ class Delegate_test : public beast::unit_test::suite Account const alice{"alice"}; Account const bob{"bob"}; Account const carol{"carol"}; - Account daria{"daria"}; - Account edward{"edward"}; + Account const daria{"daria"}; + Account const edward{"edward"}; env.fund(XRP(100000), alice, bob, carol, daria, edward); env.close(); @@ -1528,9 +1699,9 @@ class Delegate_test : public beast::unit_test::suite Account const alice{"alice"}; Account const bob{"bob"}; Account const carol{"carol"}; - Account daria = Account{"daria"}; - Account edward = Account{"edward"}; - Account fred = Account{"fred"}; + Account const daria{"daria"}; + Account const edward{"edward"}; + Account const fred{"fred"}; env.fund(XRP(100000), alice, bob, carol, daria, edward, fred); env.close(); @@ -1567,8 +1738,8 @@ class Delegate_test : public beast::unit_test::suite Env env(*this, features); - Account alice{"alice"}; - Account bob{"bob"}; + Account const alice{"alice"}; + Account const bob{"bob"}; env.fund(XRP(100000), alice, bob); env.close(); @@ -1729,6 +1900,21 @@ class Delegate_test : public beast::unit_test::suite "\n Action: Verify security requirements to interact with Delegation feature"); } + void + testDelegateUtilsNullptrCheck() + { + testcase("DelegateUtils nullptr check"); + + // checkTxPermission nullptr check + STTx const tx{ttPAYMENT, [](STObject&) {}}; + BEAST_EXPECT(checkTxPermission(nullptr, tx) == terNO_DELEGATE_PERMISSION); + + // loadGranularPermission nullptr check + std::unordered_set granularPermissions; + loadGranularPermission(nullptr, ttPAYMENT, granularPermissions); + BEAST_EXPECT(granularPermissions.empty()); + } + void run() override { @@ -1754,8 +1940,8 @@ class Delegate_test : public beast::unit_test::suite testPermissionValue(all); testTxRequireFeatures(all); testTxDelegableCount(); + testDelegateUtilsNullptrCheck(); } }; BEAST_DEFINE_TESTSUITE(Delegate, app, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/app/DeliverMin_test.cpp b/src/test/app/DeliverMin_test.cpp index fe044353b7..4b3b6d34ad 100644 --- a/src/test/app/DeliverMin_test.cpp +++ b/src/test/app/DeliverMin_test.cpp @@ -1,10 +1,22 @@ -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include #include +#include +#include -namespace xrpl { -namespace test { +namespace xrpl::test { class DeliverMin_test : public beast::unit_test::suite { @@ -128,5 +140,4 @@ public: BEAST_DEFINE_TESTSUITE(DeliverMin, app, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/app/DepositAuth_test.cpp b/src/test/app/DepositAuth_test.cpp index 7a8404aad8..c9773cba8b 100644 --- a/src/test/app/DepositAuth_test.cpp +++ b/src/test/app/DepositAuth_test.cpp @@ -1,11 +1,50 @@ -#include +#include +#include +#include +#include +#include // IWYU pragma: keep +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include -namespace xrpl { -namespace test { +namespace xrpl::test { // Helper function that returns the reserve on an account based on // the passed in number of owners. @@ -1045,7 +1084,7 @@ struct DepositPreauth_test : public beast::unit_test::suite // Create credentials auto jv = credentials::create(alice, issuer, credType); - // Current time in ripple epoch. + // Current time in XRPL epoch. // Every time ledger close, unittest timer increase by 10s uint32_t const t = env.current()->header().parentCloseTime.time_since_epoch().count() + 60; @@ -1369,5 +1408,4 @@ struct DepositPreauth_test : public beast::unit_test::suite BEAST_DEFINE_TESTSUITE(DepositAuth, app, xrpl); BEAST_DEFINE_TESTSUITE(DepositPreauth, app, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/app/Discrepancy_test.cpp b/src/test/app/Discrepancy_test.cpp index d8c14df64e..b01b11aa11 100644 --- a/src/test/app/Discrepancy_test.cpp +++ b/src/test/app/Discrepancy_test.cpp @@ -1,13 +1,25 @@ -#include +#include #include #include +#include +#include +#include +#include +#include +#include +#include #include -#include +#include +#include +#include #include #include +#include #include +#include + namespace xrpl { class Discrepancy_test : public beast::unit_test::suite diff --git a/src/test/app/EscrowToken_test.cpp b/src/test/app/EscrowToken_test.cpp index 6e08c3eddf..d77043a624 100644 --- a/src/test/app/EscrowToken_test.cpp +++ b/src/test/app/EscrowToken_test.cpp @@ -1,19 +1,47 @@ -#include +#include +#include +#include +#include // IWYU pragma: keep +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include #include +#include #include +#include #include #include +#include +#include +#include +#include #include +#include #include +#include #include -#include #include +#include +#include #include +#include -namespace xrpl { -namespace test { +namespace xrpl::test { struct EscrowToken_test : public beast::unit_test::suite { @@ -985,13 +1013,17 @@ struct EscrowToken_test : public beast::unit_test::suite { xrpl::Dir const aod(*env.current(), keylet::ownerDir(alice.id())); BEAST_EXPECT(std::distance(aod.begin(), aod.end()) == 2); - BEAST_EXPECT(std::find(aod.begin(), aod.end(), aa) != aod.end()); + BEAST_EXPECT( + // NOLINTNEXTLINE(modernize-use-ranges) + std::find(aod.begin(), aod.end(), aa) != aod.end()); } { xrpl::Dir const iod(*env.current(), keylet::ownerDir(gw.id())); BEAST_EXPECT(std::distance(iod.begin(), iod.end()) == 4); - BEAST_EXPECT(std::find(iod.begin(), iod.end(), aa) != iod.end()); + BEAST_EXPECT( + // NOLINTNEXTLINE(modernize-use-ranges) + std::find(iod.begin(), iod.end(), aa) != iod.end()); } env(escrow::create(bob, bob, USD(1'000)), @@ -1006,13 +1038,17 @@ struct EscrowToken_test : public beast::unit_test::suite { xrpl::Dir const bod(*env.current(), keylet::ownerDir(bob.id())); BEAST_EXPECT(std::distance(bod.begin(), bod.end()) == 2); - BEAST_EXPECT(std::find(bod.begin(), bod.end(), bb) != bod.end()); + BEAST_EXPECT( + // NOLINTNEXTLINE(modernize-use-ranges) + std::find(bod.begin(), bod.end(), bb) != bod.end()); } { xrpl::Dir const iod(*env.current(), keylet::ownerDir(gw.id())); BEAST_EXPECT(std::distance(iod.begin(), iod.end()) == 5); - BEAST_EXPECT(std::find(iod.begin(), iod.end(), bb) != iod.end()); + BEAST_EXPECT( + // NOLINTNEXTLINE(modernize-use-ranges) + std::find(iod.begin(), iod.end(), bb) != iod.end()); } env.close(5s); @@ -1024,15 +1060,21 @@ struct EscrowToken_test : public beast::unit_test::suite xrpl::Dir const aod(*env.current(), keylet::ownerDir(alice.id())); BEAST_EXPECT(std::distance(aod.begin(), aod.end()) == 1); - BEAST_EXPECT(std::find(aod.begin(), aod.end(), aa) == aod.end()); + BEAST_EXPECT( + // NOLINTNEXTLINE(modernize-use-ranges) + std::find(aod.begin(), aod.end(), aa) == aod.end()); xrpl::Dir const bod(*env.current(), keylet::ownerDir(bob.id())); BEAST_EXPECT(std::distance(bod.begin(), bod.end()) == 2); - BEAST_EXPECT(std::find(bod.begin(), bod.end(), bb) != bod.end()); + BEAST_EXPECT( + // NOLINTNEXTLINE(modernize-use-ranges) + std::find(bod.begin(), bod.end(), bb) != bod.end()); xrpl::Dir const iod(*env.current(), keylet::ownerDir(gw.id())); BEAST_EXPECT(std::distance(iod.begin(), iod.end()) == 4); - BEAST_EXPECT(std::find(iod.begin(), iod.end(), bb) != iod.end()); + BEAST_EXPECT( + // NOLINTNEXTLINE(modernize-use-ranges) + std::find(iod.begin(), iod.end(), bb) != iod.end()); } env.close(5s); @@ -1044,11 +1086,15 @@ struct EscrowToken_test : public beast::unit_test::suite xrpl::Dir const bod(*env.current(), keylet::ownerDir(bob.id())); BEAST_EXPECT(std::distance(bod.begin(), bod.end()) == 1); - BEAST_EXPECT(std::find(bod.begin(), bod.end(), bb) == bod.end()); + BEAST_EXPECT( + // NOLINTNEXTLINE(modernize-use-ranges) + std::find(bod.begin(), bod.end(), bb) == bod.end()); xrpl::Dir const iod(*env.current(), keylet::ownerDir(gw.id())); BEAST_EXPECT(std::distance(iod.begin(), iod.end()) == 3); - BEAST_EXPECT(std::find(iod.begin(), iod.end(), bb) == iod.end()); + BEAST_EXPECT( + // NOLINTNEXTLINE(modernize-use-ranges) + std::find(iod.begin(), iod.end(), bb) == iod.end()); } } { @@ -1087,21 +1133,33 @@ struct EscrowToken_test : public beast::unit_test::suite { xrpl::Dir const aod(*env.current(), keylet::ownerDir(alice.id())); BEAST_EXPECT(std::distance(aod.begin(), aod.end()) == 2); - BEAST_EXPECT(std::find(aod.begin(), aod.end(), ab) != aod.end()); + BEAST_EXPECT( + // NOLINTNEXTLINE(modernize-use-ranges) + std::find(aod.begin(), aod.end(), ab) != aod.end()); xrpl::Dir const bod(*env.current(), keylet::ownerDir(bob.id())); BEAST_EXPECT(std::distance(bod.begin(), bod.end()) == 3); - BEAST_EXPECT(std::find(bod.begin(), bod.end(), ab) != bod.end()); - BEAST_EXPECT(std::find(bod.begin(), bod.end(), bc) != bod.end()); + BEAST_EXPECT( + // NOLINTNEXTLINE(modernize-use-ranges) + std::find(bod.begin(), bod.end(), ab) != bod.end()); + BEAST_EXPECT( + // NOLINTNEXTLINE(modernize-use-ranges) + std::find(bod.begin(), bod.end(), bc) != bod.end()); xrpl::Dir const cod(*env.current(), keylet::ownerDir(carol.id())); BEAST_EXPECT(std::distance(cod.begin(), cod.end()) == 2); - BEAST_EXPECT(std::find(cod.begin(), cod.end(), bc) != cod.end()); + BEAST_EXPECT( + // NOLINTNEXTLINE(modernize-use-ranges) + std::find(cod.begin(), cod.end(), bc) != cod.end()); xrpl::Dir const iod(*env.current(), keylet::ownerDir(gw.id())); BEAST_EXPECT(std::distance(iod.begin(), iod.end()) == 5); - BEAST_EXPECT(std::find(iod.begin(), iod.end(), ab) != iod.end()); - BEAST_EXPECT(std::find(iod.begin(), iod.end(), bc) != iod.end()); + BEAST_EXPECT( + // NOLINTNEXTLINE(modernize-use-ranges) + std::find(iod.begin(), iod.end(), ab) != iod.end()); + BEAST_EXPECT( + // NOLINTNEXTLINE(modernize-use-ranges) + std::find(iod.begin(), iod.end(), bc) != iod.end()); } env.close(5s); @@ -1112,20 +1170,30 @@ struct EscrowToken_test : public beast::unit_test::suite xrpl::Dir const aod(*env.current(), keylet::ownerDir(alice.id())); BEAST_EXPECT(std::distance(aod.begin(), aod.end()) == 1); - BEAST_EXPECT(std::find(aod.begin(), aod.end(), ab) == aod.end()); + BEAST_EXPECT( + // NOLINTNEXTLINE(modernize-use-ranges) + std::find(aod.begin(), aod.end(), ab) == aod.end()); xrpl::Dir const bod(*env.current(), keylet::ownerDir(bob.id())); BEAST_EXPECT(std::distance(bod.begin(), bod.end()) == 2); - BEAST_EXPECT(std::find(bod.begin(), bod.end(), ab) == bod.end()); - BEAST_EXPECT(std::find(bod.begin(), bod.end(), bc) != bod.end()); + BEAST_EXPECT( + // NOLINTNEXTLINE(modernize-use-ranges) + std::find(bod.begin(), bod.end(), ab) == bod.end()); + BEAST_EXPECT( + // NOLINTNEXTLINE(modernize-use-ranges) + std::find(bod.begin(), bod.end(), bc) != bod.end()); xrpl::Dir const cod(*env.current(), keylet::ownerDir(carol.id())); BEAST_EXPECT(std::distance(cod.begin(), cod.end()) == 2); xrpl::Dir const iod(*env.current(), keylet::ownerDir(gw.id())); BEAST_EXPECT(std::distance(iod.begin(), iod.end()) == 4); - BEAST_EXPECT(std::find(iod.begin(), iod.end(), ab) == iod.end()); - BEAST_EXPECT(std::find(iod.begin(), iod.end(), bc) != iod.end()); + BEAST_EXPECT( + // NOLINTNEXTLINE(modernize-use-ranges) + std::find(iod.begin(), iod.end(), ab) == iod.end()); + BEAST_EXPECT( + // NOLINTNEXTLINE(modernize-use-ranges) + std::find(iod.begin(), iod.end(), bc) != iod.end()); } env.close(5s); @@ -1136,20 +1204,30 @@ struct EscrowToken_test : public beast::unit_test::suite xrpl::Dir const aod(*env.current(), keylet::ownerDir(alice.id())); BEAST_EXPECT(std::distance(aod.begin(), aod.end()) == 1); - BEAST_EXPECT(std::find(aod.begin(), aod.end(), ab) == aod.end()); + BEAST_EXPECT( + // NOLINTNEXTLINE(modernize-use-ranges) + std::find(aod.begin(), aod.end(), ab) == aod.end()); xrpl::Dir const bod(*env.current(), keylet::ownerDir(bob.id())); BEAST_EXPECT(std::distance(bod.begin(), bod.end()) == 1); - BEAST_EXPECT(std::find(bod.begin(), bod.end(), ab) == bod.end()); - BEAST_EXPECT(std::find(bod.begin(), bod.end(), bc) == bod.end()); + BEAST_EXPECT( + // NOLINTNEXTLINE(modernize-use-ranges) + std::find(bod.begin(), bod.end(), ab) == bod.end()); + BEAST_EXPECT( + // NOLINTNEXTLINE(modernize-use-ranges) + std::find(bod.begin(), bod.end(), bc) == bod.end()); xrpl::Dir const cod(*env.current(), keylet::ownerDir(carol.id())); BEAST_EXPECT(std::distance(cod.begin(), cod.end()) == 1); xrpl::Dir const iod(*env.current(), keylet::ownerDir(gw.id())); BEAST_EXPECT(std::distance(iod.begin(), iod.end()) == 3); - BEAST_EXPECT(std::find(iod.begin(), iod.end(), ab) == iod.end()); - BEAST_EXPECT(std::find(iod.begin(), iod.end(), bc) == iod.end()); + BEAST_EXPECT( + // NOLINTNEXTLINE(modernize-use-ranges) + std::find(iod.begin(), iod.end(), ab) == iod.end()); + BEAST_EXPECT( + // NOLINTNEXTLINE(modernize-use-ranges) + std::find(iod.begin(), iod.end(), bc) == iod.end()); } } @@ -1184,14 +1262,18 @@ struct EscrowToken_test : public beast::unit_test::suite { xrpl::Dir const aod(*env.current(), keylet::ownerDir(alice.id())); BEAST_EXPECT(std::distance(aod.begin(), aod.end()) == 2); - BEAST_EXPECT(std::find(aod.begin(), aod.end(), ag) != aod.end()); + BEAST_EXPECT( + // NOLINTNEXTLINE(modernize-use-ranges) + std::find(aod.begin(), aod.end(), ag) != aod.end()); xrpl::Dir const cod(*env.current(), keylet::ownerDir(carol.id())); BEAST_EXPECT(std::distance(cod.begin(), cod.end()) == 1); xrpl::Dir const iod(*env.current(), keylet::ownerDir(gw.id())); BEAST_EXPECT(std::distance(iod.begin(), iod.end()) == 3); - BEAST_EXPECT(std::find(iod.begin(), iod.end(), ag) != iod.end()); + BEAST_EXPECT( + // NOLINTNEXTLINE(modernize-use-ranges) + std::find(iod.begin(), iod.end(), ag) != iod.end()); } env.close(5s); @@ -1201,14 +1283,18 @@ struct EscrowToken_test : public beast::unit_test::suite xrpl::Dir const aod(*env.current(), keylet::ownerDir(alice.id())); BEAST_EXPECT(std::distance(aod.begin(), aod.end()) == 1); - BEAST_EXPECT(std::find(aod.begin(), aod.end(), ag) == aod.end()); + BEAST_EXPECT( + // NOLINTNEXTLINE(modernize-use-ranges) + std::find(aod.begin(), aod.end(), ag) == aod.end()); xrpl::Dir const cod(*env.current(), keylet::ownerDir(carol.id())); BEAST_EXPECT(std::distance(cod.begin(), cod.end()) == 1); xrpl::Dir const iod(*env.current(), keylet::ownerDir(gw.id())); BEAST_EXPECT(std::distance(iod.begin(), iod.end()) == 2); - BEAST_EXPECT(std::find(iod.begin(), iod.end(), ag) == iod.end()); + BEAST_EXPECT( + // NOLINTNEXTLINE(modernize-use-ranges) + std::find(iod.begin(), iod.end(), ag) == iod.end()); } } } @@ -1231,21 +1317,53 @@ struct EscrowToken_test : public beast::unit_test::suite std::array const tests = {{ // src > dst && src > issuer && dst no trustline - {Account("alice2"), Account("bob0"), Account{"gw0"}, false, true}, + {.src = Account("alice2"), + .dst = Account("bob0"), + .gw = Account{"gw0"}, + .hasTrustline = false, + .negative = true}, // src < dst && src < issuer && dst no trustline - {Account("carol0"), Account("dan1"), Account{"gw1"}, false, false}, + {.src = Account("carol0"), + .dst = Account("dan1"), + .gw = Account{"gw1"}, + .hasTrustline = false, + .negative = false}, // dst > src && dst > issuer && dst no trustline - {Account("dan1"), Account("alice2"), Account{"gw0"}, false, true}, + {.src = Account("dan1"), + .dst = Account("alice2"), + .gw = Account{"gw0"}, + .hasTrustline = false, + .negative = true}, // dst < src && dst < issuer && dst no trustline - {Account("bob0"), Account("carol0"), Account{"gw1"}, false, false}, + {.src = Account("bob0"), + .dst = Account("carol0"), + .gw = Account{"gw1"}, + .hasTrustline = false, + .negative = false}, // src > dst && src > issuer && dst has trustline - {Account("alice2"), Account("bob0"), Account{"gw0"}, true, true}, + {.src = Account("alice2"), + .dst = Account("bob0"), + .gw = Account{"gw0"}, + .hasTrustline = true, + .negative = true}, // src < dst && src < issuer && dst has trustline - {Account("carol0"), Account("dan1"), Account{"gw1"}, true, false}, + {.src = Account("carol0"), + .dst = Account("dan1"), + .gw = Account{"gw1"}, + .hasTrustline = true, + .negative = false}, // dst > src && dst > issuer && dst has trustline - {Account("dan1"), Account("alice2"), Account{"gw0"}, true, true}, + {.src = Account("dan1"), + .dst = Account("alice2"), + .gw = Account{"gw0"}, + .hasTrustline = true, + .negative = true}, // dst < src && dst < issuer && dst has trustline - {Account("bob0"), Account("carol0"), Account{"gw1"}, true, false}, + {.src = Account("bob0"), + .dst = Account("carol0"), + .gw = Account{"gw1"}, + .hasTrustline = true, + .negative = false}, }}; for (auto const& t : tests) @@ -1337,13 +1455,13 @@ struct EscrowToken_test : public beast::unit_test::suite std::array const gwDstTests = {{ // src > dst && src > issuer && dst has trustline - {Account("alice2"), Account{"gw0"}, true}, + {.src = Account("alice2"), .dst = Account{"gw0"}, .hasTrustline = true}, // src < dst && src < issuer && dst has trustline - {Account("carol0"), Account{"gw1"}, true}, + {.src = Account("carol0"), .dst = Account{"gw1"}, .hasTrustline = true}, // dst > src && dst > issuer && dst has trustline - {Account("dan1"), Account{"gw0"}, true}, + {.src = Account("dan1"), .dst = Account{"gw0"}, .hasTrustline = true}, // dst < src && dst < issuer && dst has trustline - {Account("bob0"), Account{"gw1"}, true}, + {.src = Account("bob0"), .dst = Account{"gw1"}, .hasTrustline = true}, }}; // issuer is destination @@ -3102,13 +3220,17 @@ struct EscrowToken_test : public beast::unit_test::suite { xrpl::Dir const aod(*env.current(), keylet::ownerDir(alice.id())); BEAST_EXPECT(std::distance(aod.begin(), aod.end()) == 2); - BEAST_EXPECT(std::find(aod.begin(), aod.end(), aa) != aod.end()); + BEAST_EXPECT( + // NOLINTNEXTLINE(modernize-use-ranges) + std::find(aod.begin(), aod.end(), aa) != aod.end()); } { xrpl::Dir const iod(*env.current(), keylet::ownerDir(gw.id())); BEAST_EXPECT(std::distance(iod.begin(), iod.end()) == 1); - BEAST_EXPECT(std::find(iod.begin(), iod.end(), aa) == iod.end()); + BEAST_EXPECT( + // NOLINTNEXTLINE(modernize-use-ranges) + std::find(iod.begin(), iod.end(), aa) == iod.end()); } env(escrow::create(bob, bob, MPT(1'000)), @@ -3123,7 +3245,9 @@ struct EscrowToken_test : public beast::unit_test::suite { xrpl::Dir const bod(*env.current(), keylet::ownerDir(bob.id())); BEAST_EXPECT(std::distance(bod.begin(), bod.end()) == 2); - BEAST_EXPECT(std::find(bod.begin(), bod.end(), bb) != bod.end()); + BEAST_EXPECT( + // NOLINTNEXTLINE(modernize-use-ranges) + std::find(bod.begin(), bod.end(), bb) != bod.end()); } env.close(5s); @@ -3135,11 +3259,15 @@ struct EscrowToken_test : public beast::unit_test::suite xrpl::Dir const aod(*env.current(), keylet::ownerDir(alice.id())); BEAST_EXPECT(std::distance(aod.begin(), aod.end()) == 1); - BEAST_EXPECT(std::find(aod.begin(), aod.end(), aa) == aod.end()); + BEAST_EXPECT( + // NOLINTNEXTLINE(modernize-use-ranges) + std::find(aod.begin(), aod.end(), aa) == aod.end()); xrpl::Dir const bod(*env.current(), keylet::ownerDir(bob.id())); BEAST_EXPECT(std::distance(bod.begin(), bod.end()) == 2); - BEAST_EXPECT(std::find(bod.begin(), bod.end(), bb) != bod.end()); + BEAST_EXPECT( + // NOLINTNEXTLINE(modernize-use-ranges) + std::find(bod.begin(), bod.end(), bb) != bod.end()); } env.close(5s); @@ -3151,7 +3279,9 @@ struct EscrowToken_test : public beast::unit_test::suite xrpl::Dir const bod(*env.current(), keylet::ownerDir(bob.id())); BEAST_EXPECT(std::distance(bod.begin(), bod.end()) == 1); - BEAST_EXPECT(std::find(bod.begin(), bod.end(), bb) == bod.end()); + BEAST_EXPECT( + // NOLINTNEXTLINE(modernize-use-ranges) + std::find(bod.begin(), bod.end(), bb) == bod.end()); } } @@ -3193,16 +3323,24 @@ struct EscrowToken_test : public beast::unit_test::suite { xrpl::Dir const aod(*env.current(), keylet::ownerDir(alice.id())); BEAST_EXPECT(std::distance(aod.begin(), aod.end()) == 2); - BEAST_EXPECT(std::find(aod.begin(), aod.end(), ab) != aod.end()); + BEAST_EXPECT( + // NOLINTNEXTLINE(modernize-use-ranges) + std::find(aod.begin(), aod.end(), ab) != aod.end()); xrpl::Dir const bod(*env.current(), keylet::ownerDir(bob.id())); BEAST_EXPECT(std::distance(bod.begin(), bod.end()) == 3); - BEAST_EXPECT(std::find(bod.begin(), bod.end(), ab) != bod.end()); - BEAST_EXPECT(std::find(bod.begin(), bod.end(), bc) != bod.end()); + BEAST_EXPECT( + // NOLINTNEXTLINE(modernize-use-ranges) + std::find(bod.begin(), bod.end(), ab) != bod.end()); + BEAST_EXPECT( + // NOLINTNEXTLINE(modernize-use-ranges) + std::find(bod.begin(), bod.end(), bc) != bod.end()); xrpl::Dir const cod(*env.current(), keylet::ownerDir(carol.id())); BEAST_EXPECT(std::distance(cod.begin(), cod.end()) == 2); - BEAST_EXPECT(std::find(cod.begin(), cod.end(), bc) != cod.end()); + BEAST_EXPECT( + // NOLINTNEXTLINE(modernize-use-ranges) + std::find(cod.begin(), cod.end(), bc) != cod.end()); } env.close(5s); @@ -3213,12 +3351,18 @@ struct EscrowToken_test : public beast::unit_test::suite xrpl::Dir const aod(*env.current(), keylet::ownerDir(alice.id())); BEAST_EXPECT(std::distance(aod.begin(), aod.end()) == 1); - BEAST_EXPECT(std::find(aod.begin(), aod.end(), ab) == aod.end()); + BEAST_EXPECT( + // NOLINTNEXTLINE(modernize-use-ranges) + std::find(aod.begin(), aod.end(), ab) == aod.end()); xrpl::Dir const bod(*env.current(), keylet::ownerDir(bob.id())); BEAST_EXPECT(std::distance(bod.begin(), bod.end()) == 2); - BEAST_EXPECT(std::find(bod.begin(), bod.end(), ab) == bod.end()); - BEAST_EXPECT(std::find(bod.begin(), bod.end(), bc) != bod.end()); + BEAST_EXPECT( + // NOLINTNEXTLINE(modernize-use-ranges) + std::find(bod.begin(), bod.end(), ab) == bod.end()); + BEAST_EXPECT( + // NOLINTNEXTLINE(modernize-use-ranges) + std::find(bod.begin(), bod.end(), bc) != bod.end()); xrpl::Dir const cod(*env.current(), keylet::ownerDir(carol.id())); BEAST_EXPECT(std::distance(cod.begin(), cod.end()) == 2); @@ -3232,12 +3376,18 @@ struct EscrowToken_test : public beast::unit_test::suite xrpl::Dir const aod(*env.current(), keylet::ownerDir(alice.id())); BEAST_EXPECT(std::distance(aod.begin(), aod.end()) == 1); - BEAST_EXPECT(std::find(aod.begin(), aod.end(), ab) == aod.end()); + BEAST_EXPECT( + // NOLINTNEXTLINE(modernize-use-ranges) + std::find(aod.begin(), aod.end(), ab) == aod.end()); xrpl::Dir const bod(*env.current(), keylet::ownerDir(bob.id())); BEAST_EXPECT(std::distance(bod.begin(), bod.end()) == 1); - BEAST_EXPECT(std::find(bod.begin(), bod.end(), ab) == bod.end()); - BEAST_EXPECT(std::find(bod.begin(), bod.end(), bc) == bod.end()); + BEAST_EXPECT( + // NOLINTNEXTLINE(modernize-use-ranges) + std::find(bod.begin(), bod.end(), ab) == bod.end()); + BEAST_EXPECT( + // NOLINTNEXTLINE(modernize-use-ranges) + std::find(bod.begin(), bod.end(), bc) == bod.end()); xrpl::Dir const cod(*env.current(), keylet::ownerDir(carol.id())); BEAST_EXPECT(std::distance(cod.begin(), cod.end()) == 1); @@ -3786,5 +3936,4 @@ public: BEAST_DEFINE_TESTSUITE(EscrowToken, app, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/app/Escrow_test.cpp b/src/test/app/Escrow_test.cpp index 05640cde01..77d51f2758 100644 --- a/src/test/app/Escrow_test.cpp +++ b/src/test/app/Escrow_test.cpp @@ -1,17 +1,38 @@ -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include #include #include #include +#include +#include #include #include #include #include +#include +#include +#include #include +#include -namespace xrpl { -namespace test { +namespace xrpl::test { struct Escrow_test : public beast::unit_test::suite { @@ -1102,7 +1123,9 @@ struct Escrow_test : public beast::unit_test::suite { xrpl::Dir const aod(*env.current(), keylet::ownerDir(alice.id())); BEAST_EXPECT(std::distance(aod.begin(), aod.end()) == 1); - BEAST_EXPECT(std::find(aod.begin(), aod.end(), aa) != aod.end()); + BEAST_EXPECT( + // NOLINTNEXTLINE(modernize-use-ranges) + std::find(aod.begin(), aod.end(), aa) != aod.end()); } env(escrow::create(bruce, bruce, XRP(1000)), @@ -1117,7 +1140,9 @@ struct Escrow_test : public beast::unit_test::suite { xrpl::Dir const bod(*env.current(), keylet::ownerDir(bruce.id())); BEAST_EXPECT(std::distance(bod.begin(), bod.end()) == 1); - BEAST_EXPECT(std::find(bod.begin(), bod.end(), bb) != bod.end()); + BEAST_EXPECT( + // NOLINTNEXTLINE(modernize-use-ranges) + std::find(bod.begin(), bod.end(), bb) != bod.end()); } env.close(5s); @@ -1129,11 +1154,15 @@ struct Escrow_test : public beast::unit_test::suite xrpl::Dir const aod(*env.current(), keylet::ownerDir(alice.id())); BEAST_EXPECT(std::distance(aod.begin(), aod.end()) == 0); - BEAST_EXPECT(std::find(aod.begin(), aod.end(), aa) == aod.end()); + BEAST_EXPECT( + // NOLINTNEXTLINE(modernize-use-ranges) + std::find(aod.begin(), aod.end(), aa) == aod.end()); xrpl::Dir const bod(*env.current(), keylet::ownerDir(bruce.id())); BEAST_EXPECT(std::distance(bod.begin(), bod.end()) == 1); - BEAST_EXPECT(std::find(bod.begin(), bod.end(), bb) != bod.end()); + BEAST_EXPECT( + // NOLINTNEXTLINE(modernize-use-ranges) + std::find(bod.begin(), bod.end(), bb) != bod.end()); } env.close(5s); @@ -1145,7 +1174,9 @@ struct Escrow_test : public beast::unit_test::suite xrpl::Dir const bod(*env.current(), keylet::ownerDir(bruce.id())); BEAST_EXPECT(std::distance(bod.begin(), bod.end()) == 0); - BEAST_EXPECT(std::find(bod.begin(), bod.end(), bb) == bod.end()); + BEAST_EXPECT( + // NOLINTNEXTLINE(modernize-use-ranges) + std::find(bod.begin(), bod.end(), bb) == bod.end()); } } { @@ -1176,16 +1207,24 @@ struct Escrow_test : public beast::unit_test::suite { xrpl::Dir const aod(*env.current(), keylet::ownerDir(alice.id())); BEAST_EXPECT(std::distance(aod.begin(), aod.end()) == 1); - BEAST_EXPECT(std::find(aod.begin(), aod.end(), ab) != aod.end()); + BEAST_EXPECT( + // NOLINTNEXTLINE(modernize-use-ranges) + std::find(aod.begin(), aod.end(), ab) != aod.end()); xrpl::Dir const bod(*env.current(), keylet::ownerDir(bruce.id())); BEAST_EXPECT(std::distance(bod.begin(), bod.end()) == 2); - BEAST_EXPECT(std::find(bod.begin(), bod.end(), ab) != bod.end()); - BEAST_EXPECT(std::find(bod.begin(), bod.end(), bc) != bod.end()); + BEAST_EXPECT( + // NOLINTNEXTLINE(modernize-use-ranges) + std::find(bod.begin(), bod.end(), ab) != bod.end()); + BEAST_EXPECT( + // NOLINTNEXTLINE(modernize-use-ranges) + std::find(bod.begin(), bod.end(), bc) != bod.end()); xrpl::Dir const cod(*env.current(), keylet::ownerDir(carol.id())); BEAST_EXPECT(std::distance(cod.begin(), cod.end()) == 1); - BEAST_EXPECT(std::find(cod.begin(), cod.end(), bc) != cod.end()); + BEAST_EXPECT( + // NOLINTNEXTLINE(modernize-use-ranges) + std::find(cod.begin(), cod.end(), bc) != cod.end()); } env.close(5s); @@ -1196,12 +1235,18 @@ struct Escrow_test : public beast::unit_test::suite xrpl::Dir const aod(*env.current(), keylet::ownerDir(alice.id())); BEAST_EXPECT(std::distance(aod.begin(), aod.end()) == 0); - BEAST_EXPECT(std::find(aod.begin(), aod.end(), ab) == aod.end()); + BEAST_EXPECT( + // NOLINTNEXTLINE(modernize-use-ranges) + std::find(aod.begin(), aod.end(), ab) == aod.end()); xrpl::Dir const bod(*env.current(), keylet::ownerDir(bruce.id())); BEAST_EXPECT(std::distance(bod.begin(), bod.end()) == 1); - BEAST_EXPECT(std::find(bod.begin(), bod.end(), ab) == bod.end()); - BEAST_EXPECT(std::find(bod.begin(), bod.end(), bc) != bod.end()); + BEAST_EXPECT( + // NOLINTNEXTLINE(modernize-use-ranges) + std::find(bod.begin(), bod.end(), ab) == bod.end()); + BEAST_EXPECT( + // NOLINTNEXTLINE(modernize-use-ranges) + std::find(bod.begin(), bod.end(), bc) != bod.end()); xrpl::Dir const cod(*env.current(), keylet::ownerDir(carol.id())); BEAST_EXPECT(std::distance(cod.begin(), cod.end()) == 1); @@ -1215,12 +1260,18 @@ struct Escrow_test : public beast::unit_test::suite xrpl::Dir const aod(*env.current(), keylet::ownerDir(alice.id())); BEAST_EXPECT(std::distance(aod.begin(), aod.end()) == 0); - BEAST_EXPECT(std::find(aod.begin(), aod.end(), ab) == aod.end()); + BEAST_EXPECT( + // NOLINTNEXTLINE(modernize-use-ranges) + std::find(aod.begin(), aod.end(), ab) == aod.end()); xrpl::Dir const bod(*env.current(), keylet::ownerDir(bruce.id())); BEAST_EXPECT(std::distance(bod.begin(), bod.end()) == 0); - BEAST_EXPECT(std::find(bod.begin(), bod.end(), ab) == bod.end()); - BEAST_EXPECT(std::find(bod.begin(), bod.end(), bc) == bod.end()); + BEAST_EXPECT( + // NOLINTNEXTLINE(modernize-use-ranges) + std::find(bod.begin(), bod.end(), ab) == bod.end()); + BEAST_EXPECT( + // NOLINTNEXTLINE(modernize-use-ranges) + std::find(bod.begin(), bod.end(), bc) == bod.end()); xrpl::Dir const cod(*env.current(), keylet::ownerDir(carol.id())); BEAST_EXPECT(std::distance(cod.begin(), cod.end()) == 0); @@ -1591,5 +1642,4 @@ public: BEAST_DEFINE_TESTSUITE(Escrow, app, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/app/FeeVote_test.cpp b/src/test/app/FeeVote_test.cpp index 62f1058de5..e24b54c82a 100644 --- a/src/test/app/FeeVote_test.cpp +++ b/src/test/app/FeeVote_test.cpp @@ -1,18 +1,40 @@ -#include + +#include #include +#include #include +#include +#include +#include +#include #include #include #include +#include #include +#include +#include #include +#include #include +#include +#include +#include +#include +#include +#include #include -namespace xrpl { -namespace test { +#include +#include +#include +#include +#include +#include + +namespace xrpl::test { struct FeeSettingsFields { @@ -176,7 +198,7 @@ getTxs(std::shared_ptr const& txSet) { auto const data = i->slice(); auto serialIter = SerialIter(data); - txs.push_back(STTx(serialIter)); + txs.emplace_back(serialIter); } return txs; }; @@ -725,5 +747,4 @@ class FeeVote_test : public beast::unit_test::suite BEAST_DEFINE_TESTSUITE(FeeVote, app, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/app/FixNFTokenPageLinks_test.cpp b/src/test/app/FixNFTokenPageLinks_test.cpp index 25366534cd..56484bb396 100644 --- a/src/test/app/FixNFTokenPageLinks_test.cpp +++ b/src/test/app/FixNFTokenPageLinks_test.cpp @@ -1,9 +1,32 @@ -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include #include +#include +#include +#include +#include #include -#include -#include +#include + +#include +#include +#include namespace xrpl { @@ -71,7 +94,7 @@ class FixNFTokenPageLinks_test : public beast::unit_test::suite // Sort the NFTs so they are listed in storage order, not // creation order. - std::sort(nfts.begin(), nfts.end()); + std::ranges::sort(nfts); // Verify that the owner does indeed have exactly three pages // of NFTs with 32 entries in each page. diff --git a/src/test/app/FlowMPT_test.cpp b/src/test/app/FlowMPT_test.cpp new file mode 100644 index 0000000000..eabd239550 --- /dev/null +++ b/src/test/app/FlowMPT_test.cpp @@ -0,0 +1,2149 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace xrpl::test { + +struct FlowMPT_test : public beast::unit_test::suite +{ + using Accounts = std::vector; + + void + testDirectStep(FeatureBitset features) + { + testcase("Direct Step"); + + using namespace jtx; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const carol = Account("carol"); + auto const gw = Account("gw"); + { + // Pay USD, trivial path + Env env(*this, features); + + env.fund(XRP(10000), alice, bob, gw); + MPT const USD = MPTTester({.env = env, .issuer = gw, .holders = {alice, bob}}); + env(pay(gw, alice, USD(100))); + env(pay(alice, bob, USD(10)), paths(USD)); + env.require(balance(bob, USD(10))); + } + { + // Partial payments + Env env(*this, features); + + env.fund(XRP(10000), alice, bob, gw); + MPT const USD = MPTTester({.env = env, .issuer = gw, .holders = {alice, bob}}); + env(pay(gw, alice, USD(100))); + env(pay(alice, bob, USD(110)), paths(USD), ter(tecPATH_PARTIAL)); + env.require(balance(bob, USD(0))); + env(pay(alice, bob, USD(110)), paths(USD), txflags(tfPartialPayment)); + env.require(balance(bob, USD(100))); + } + + { + // Limit quality + auto test = [&](auto&& issue1, auto&& issue2) { + Env env(*this, features); + + env.fund(XRP(10'000), gw, alice, bob, carol); + env.close(); + + auto const USD = + issue1({.env = env, .token = "USD", .issuer = gw, .holders = {alice, carol}}); + auto const EUR = + issue2({.env = env, .token = "EUR", .issuer = gw, .holders = {bob}}); + + env(pay(gw, alice, USD(100))); + env(pay(gw, bob, EUR(100))); + + env(offer(alice, EUR(4), USD(4))); + env.close(); + + env(pay(bob, carol, USD(5)), + sendmax(EUR(4)), + txflags(tfLimitQuality | tfPartialPayment), + ter(tecPATH_DRY)); + env.require(balance(carol, USD(0))); + + env(pay(bob, carol, USD(5)), sendmax(EUR(4)), txflags(tfPartialPayment)); + env.require(balance(carol, USD(4))); + }; + testHelper2TokensMix(test); + } + } + + void + testBookStep(FeatureBitset features) + { + testcase("Book Step"); + + using namespace jtx; + + auto const gw = Account("gateway"); + Account const alice("alice"); + Account const bob("bob"); + Account const carol("carol"); + + { + // simple [MPT|IOU]/[IOU|MPT] offer + auto test = [&](auto&& issue1, auto&& issue2) { + Env env(*this, features); + + env.fund(XRP(10'000), alice, bob, carol, gw); + env.close(); + + auto const USD = issue1( + {.env = env, .token = "USD", .issuer = gw, .holders = {alice, bob, carol}}); + auto const BTC = issue2( + {.env = env, .token = "BTC", .issuer = gw, .holders = {alice, bob, carol}}); + + env(pay(gw, alice, BTC(50))); + env(pay(gw, bob, USD(50))); + + env(offer(bob, BTC(50), USD(50))); + + env(pay(alice, carol, USD(50)), path(~USD), sendmax(BTC(50))); + + env.require(balance(alice, BTC(0))); + env.require(balance(bob, BTC(50))); + env.require(balance(bob, USD(0))); + env.require(balance(carol, USD(50))); + BEAST_EXPECT(!isOffer(env, bob, BTC(50), USD(50))); + }; + testHelper2TokensMix(test); + } + { + // simple [MPT|IOU]/XRP XRP/[IOU|MPT] offer + auto test = [&](auto&& issue1, auto&& issue2) { + Env env(*this, features); + + env.fund(XRP(10'000), alice, bob, carol, gw); + env.close(); + + auto const USD = issue1( + {.env = env, .token = "USD", .issuer = gw, .holders = {alice, bob, carol}}); + auto const BTC = issue2( + {.env = env, .token = "BTC", .issuer = gw, .holders = {alice, bob, carol}}); + + env(pay(gw, alice, BTC(50))); + env(pay(gw, bob, USD(50))); + + env(offer(bob, BTC(50), XRP(50))); + env(offer(bob, XRP(50), USD(50))); + + env(pay(alice, carol, USD(50)), path(~XRP, ~USD), sendmax(BTC(50))); + + env.require(balance(alice, BTC(0))); + env.require(balance(bob, BTC(50))); + env.require(balance(bob, USD(0))); + env.require(balance(carol, USD(50))); + BEAST_EXPECT(!isOffer(env, bob, XRP(50), USD(50))); + BEAST_EXPECT(!isOffer(env, bob, BTC(50), XRP(50))); + }; + testHelper2TokensMix(test); + } + { + // simple XRP -> USD through offer and sendmax + Env env(*this, features); + + env.fund(XRP(10'000), alice, bob, carol, gw); + env.close(); + + MPT const USD = MPTTester({.env = env, .issuer = gw, .holders = {alice, bob, carol}}); + MPT const BTC = MPTTester({.env = env, .issuer = gw, .holders = {alice, bob, carol}}); + + env(pay(gw, bob, USD(50))); + + env(offer(bob, XRP(50), USD(50))); + + env(pay(alice, carol, USD(50)), path(~USD), sendmax(XRP(50))); + + // fee: MPTokenAuthorize * 2(EUR, USD) + pay + env.require(balance(alice, XRP(10'000 - 50) - txfee(env, 3))); + // fee: MPTokenAuthorize * 2(EUR, USD) + offer + env.require(balance(bob, XRP(10'000 + 50) - txfee(env, 3))); + env.require(balance(bob, USD(0))); + env.require(balance(carol, USD(50))); + BEAST_EXPECT(!isOffer(env, bob, XRP(50), USD(50))); + } + { + // simple USD -> XRP through offer and sendmax + Env env(*this, features); + + env.fund(XRP(10'000), alice, bob, carol, gw); + env.close(); + + MPT const USD = MPTTester({.env = env, .issuer = gw, .holders = {alice, bob, carol}}); + MPT const BTC = MPTTester({.env = env, .issuer = gw, .holders = {alice, bob, carol}}); + + env(pay(gw, alice, USD(50))); + + env(offer(bob, USD(50), XRP(50))); + + env(pay(alice, carol, XRP(50)), path(~XRP), sendmax(USD(50))); + + env.require(balance(alice, USD(0))); + env.require(balance(bob, XRP(10'000 - 50) - txfee(env, 3))); + env.require(balance(bob, USD(50))); + env.require(balance(carol, XRP(10'000 + 50) - txfee(env, 2))); + BEAST_EXPECT(!isOffer(env, bob, USD(50), XRP(50))); + } + { + // test unfunded offers are removed when payment succeeds + auto test = [&](auto&& issue1, auto&& issue2, auto&& issue3) { + Env env(*this, features); + + env.fund(XRP(10'000), alice, bob, carol, gw); + env.close(); + + auto const USD = issue1( + {.env = env, .token = "USD", .issuer = gw, .holders = {alice, bob, carol}}); + auto const BTC = issue2( + {.env = env, .token = "BTC", .issuer = gw, .holders = {alice, bob, carol}}); + auto const EUR = issue3( + {.env = env, .token = "EUR", .issuer = gw, .holders = {alice, bob, carol}}); + + env(pay(gw, alice, BTC(60))); + env(pay(gw, bob, USD(50))); + env(pay(gw, bob, EUR(50))); + + env(offer(bob, BTC(50), USD(50))); + env(offer(bob, BTC(40), EUR(50))); + env(offer(bob, EUR(50), USD(50))); + + // unfund offer + env(pay(bob, gw, EUR(50))); + env.require(balance(bob, EUR(0))); + BEAST_EXPECT(isOffer(env, bob, BTC(50), USD(50))); + BEAST_EXPECT(isOffer(env, bob, BTC(40), EUR(50))); + BEAST_EXPECT(isOffer(env, bob, EUR(50), USD(50))); + + env(pay(alice, carol, USD(50)), path(~USD), path(~EUR, ~USD), sendmax(BTC(60))); + + env.require(balance(alice, BTC(10))); + env.require(balance(bob, BTC(50))); + env.require(balance(bob, USD(0))); + env.require(balance(bob, EUR(0))); + env.require(balance(carol, USD(50))); + // used in the payment + BEAST_EXPECT(!isOffer(env, bob, BTC(50), USD(50))); + // found unfunded + BEAST_EXPECT(!isOffer(env, bob, BTC(40), EUR(50))); + // unfunded, but should not yet be found unfunded + BEAST_EXPECT(isOffer(env, bob, EUR(50), USD(50))); + }; + testHelper3TokensMix(test); + } + { + // test unfunded offers are returned when the payment fails. + // bob makes two offers: a funded 5000 USD for 50 BTC and an + // unfunded 5000 EUR for 60 BTC. alice pays carol 6100 USD with 61 + // BTC. alice only has 60 BTC, so the payment will fail. The payment + // uses two paths: one through bob's funded offer and one through + // his unfunded offer. When the payment fails `flow` should return + // the unfunded offer. This test is intentionally similar to the one + // that removes unfunded offers when the payment succeeds. + auto test = [&](auto&& issue1, auto&& issue2, auto&& issue3) { + Env env(*this, features); + + env.fund(XRP(10'000), alice, bob, carol, gw); + env.close(); + + auto const USD = issue1( + {.env = env, + .token = "USD", + .issuer = gw, + .holders = {alice, bob, carol}, + .limit = 100'000}); + auto const BTC = issue2( + {.env = env, + .token = "BTC", + .issuer = gw, + .holders = {alice, bob, carol}, + .limit = 100'000}); + auto const EUR = issue3( + {.env = env, + .token = "EUR", + .issuer = gw, + .holders = {alice, bob, carol}, + .limit = 100'000}); + + env(pay(gw, alice, BTC(60))); + env(pay(gw, bob, USD(6'000))); + env(pay(gw, bob, EUR(5'000))); + env(pay(gw, carol, EUR(100))); + + env(offer(bob, BTC(50), USD(5'000))); + env(offer(bob, BTC(60), EUR(5'000))); + env(offer(carol, BTC(1'000), EUR(100))); + env(offer(bob, EUR(5'000), USD(5'000))); + + // unfund offer + env(pay(bob, gw, EUR(5'000))); + BEAST_EXPECT(isOffer(env, bob, BTC(50), USD(5'000))); + BEAST_EXPECT(isOffer(env, bob, BTC(60), EUR(5'000))); + BEAST_EXPECT(isOffer(env, carol, BTC(1'000), EUR(100))); + + auto flowJournal = env.app().getLogs().journal("Flow"); + auto const flowResult = [&] { + STAmount const deliver(USD(5'100)); + STAmount smax(BTC(61)); + PaymentSandbox sb(env.current().get(), tapNONE); + STPathSet paths; + auto IPE = [](Asset const& asset) { + return STPathElement( + STPathElement::typeAsset | STPathElement::typeIssuer, + xrpAccount(), + asset, + asset.getIssuer()); + }; + { + // BTC -> USD + STPath const p1({IPE(USD)}); + paths.push_back(p1); + // BTC -> EUR -> USD + STPath const p2({IPE(EUR), IPE(USD)}); + paths.push_back(p2); + } + + return flow( + sb, + deliver, + alice, + carol, + paths, + false, + false, + true, + OfferCrossing::no, + std::nullopt, + smax, + std::nullopt, + flowJournal); + }(); + + BEAST_EXPECT(flowResult.removableOffers.size() == 1); + env.app().getOpenLedger().modify([&](OpenView& view, beast::Journal j) { + if (flowResult.removableOffers.empty()) + return false; + Sandbox sb(&view, tapNONE); + for (auto const& o : flowResult.removableOffers) + { + if (auto ok = sb.peek(keylet::offer(o))) + offerDelete(sb, ok, flowJournal); + } + sb.apply(view); + return true; + }); + + // used in payment, but since payment failed should + // be untouched + BEAST_EXPECT(isOffer(env, bob, BTC(50), USD(5'000))); + BEAST_EXPECT(isOffer(env, carol, BTC(1'000), EUR(100))); + // found unfunded + BEAST_EXPECT(!isOffer(env, bob, BTC(60), EUR(5'000))); + }; + testHelper3TokensMix(test); + } + { + // Do not produce more in the forward pass than the + // reverse pass. This test uses a path whose reverse + // pass will compute a 0.5 USD input required for a 1 + // EUR output. It sets a sendmax of 0.4 USD, so the + // payment engine will need to do a forward pass. + // Without limits, the 0.4 USD would produce 1000 EUR in + // the forward pass. This test checks that the payment + // produces 1 EUR, as expected. + auto test = [&](auto&& issue1, auto&& issue2) { + Env env(*this, features); + env.fund(XRP(10'000), alice, bob, carol, gw); + + auto const USD = issue1( + {.env = env, .token = "USD", .issuer = gw, .holders = {alice, bob, carol}}); + auto const EUR = issue1( + {.env = env, .token = "EUR", .issuer = gw, .holders = {alice, bob, carol}}); + + env(pay(gw, alice, USD(1'000))); + env(pay(gw, bob, EUR(1'000))); + + Keylet const bobUsdOffer = keylet::offer(bob, env.seq(bob)); + env(offer(bob, USD(10), drops(2)), txflags(tfPassive)); + env(offer(bob, drops(1), EUR(1'000)), txflags(tfPassive)); + + bool const reducedOffersV2 = features[fixReducedOffersV2]; + + // With reducedOffersV2, it is not allowed to accept + // less than USD(0.5) of bob's USD offer. If we + // provide 1 drop for less than USD(0.5), then the + // remaining fractional offer would block the order + // book. + TER const expectedTER = reducedOffersV2 ? TER(tecPATH_DRY) : TER(tesSUCCESS); + env(pay(alice, carol, EUR(1)), + path(~XRP, ~EUR), + sendmax(USD(4)), + txflags(tfNoRippleDirect | tfPartialPayment), + ter(expectedTER)); + + if (!reducedOffersV2) + { + env.require(balance(carol, EUR(1))); + env.require(balance(bob, USD(4))); + env.require(balance(bob, EUR(999))); + + // Show that bob's USD offer is now a blocker. + std::shared_ptr const usdOffer = env.le(bobUsdOffer); + if (BEAST_EXPECT(usdOffer)) + { + std::uint64_t const bookRate = [&usdOffer]() { + // Extract the least significant 64 + // bits from the book page. That's + // where the quality is stored. + std::string bookDirStr = to_string(usdOffer->at(sfBookDirectory)); + bookDirStr.erase(0, 48); + return std::stoull(bookDirStr, nullptr, 16); + }(); + std::uint64_t const actualRate = + getRate(usdOffer->at(sfTakerGets), usdOffer->at(sfTakerPays)); + + // We expect the actual rate of the offer to + // be worse (larger) than the rate of the + // book page holding the offer. This is a + // defect which is corrected by + // fixReducedOffersV2. + BEAST_EXPECT(actualRate > bookRate); + } + } + }; + testHelper2TokensMix(test); + } + } + + void + testTransferRate(FeatureBitset features) + { + testcase("Transfer Rate"); + + using namespace jtx; + + auto const gw = Account("gateway"); + Account const alice("alice"); + Account const bob("bob"); + Account const carol("carol"); + + { + // Simple payment through a gateway with a + // transfer rate + Env env(*this, features); + + env.fund(XRP(10000), alice, bob, carol, gw); + + MPT const USD = MPTTester( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol}, + .transferFee = 25'000, + .maxAmt = 1'000}); + + env(pay(gw, alice, USD(50))); + env.require(balance(alice, USD(50))); + env(pay(alice, bob, USD(40)), sendmax(USD(50))); + env.require(balance(bob, USD(40)), balance(alice, USD(0))); + } + { + // transfer rate is not charged when issuer is src or + // dst + Env env(*this, features); + + env.fund(XRP(10'000), alice, bob, carol, gw); + + MPT const USD = MPTTester( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol}, + .transferFee = 25'000, + .maxAmt = 1'000}); + + env(pay(gw, alice, USD(50))); + env.require(balance(alice, USD(50))); + env(pay(alice, gw, USD(40)), sendmax(USD(40))); + env.require(balance(alice, USD(10))); + } + { + // transfer fee on an offer + Env env(*this, features); + + env.fund(XRP(10'000), alice, bob, carol, gw); + + MPT const USD = MPTTester( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol}, + .transferFee = 25'000, + .maxAmt = 10'000}); + + // scale by 1 + env(pay(gw, bob, USD(650))); + + env(offer(bob, XRP(50), USD(500))); + + env(pay(alice, carol, USD(500)), + path(~USD), + sendmax(XRP(50)), + txflags(tfPartialPayment)); + + // bob pays 25% on 500USD -> 100USD; 400USD goes to carol + env.require( + balance(alice, XRP(10'000 - 50) - txfee(env, 2)), + balance(bob, USD(150)), + balance(carol, USD(400))); + } + { + // Transfer fee two consecutive offers + auto test = [&](auto&& issue1, auto&& issue2) { + Env env(*this, features); + + env.fund(XRP(10'000), alice, bob, carol, gw); + env.close(); + + auto const USD = issue1( + {.env = env, + .token = "USD", + .issuer = gw, + .holders = {alice, bob, carol}, + .limit = 1'000, + .transferFee = 25'000}); + auto const EUR = issue2( + {.env = env, + .token = "EUR", + .issuer = gw, + .holders = {alice, bob, carol}, + .limit = 1'000, + .transferFee = 25'000}); + + env(pay(gw, bob, USD(50))); + env(pay(gw, bob, EUR(50))); + + env(offer(bob, XRP(50), USD(50))); + env(offer(bob, USD(50), EUR(50))); + + env(pay(alice, carol, EUR(40)), + path(~USD, ~EUR), + sendmax(XRP(40)), + txflags(tfPartialPayment)); + // +1 for fset in helperIssueIOU + using tEUR = std::decay_t; + auto const fee = txfee(env, 3); + // bob pays 25% on 40USD (40 since sendmax is 40XRP) + // 8USD goes to gw and 32USD goes back to bob -> + // bob's USD balance is 42USD. USD/EUR offer is 32USD/32EUR. + // bob pays 25% on 32EUR -> 7EUR if MPT, 6.4EUR if IOU, + // therefore carl gets 25EUR if MPT, 25.6EUR if IOU. + auto const carolEUR = [&]() { + if constexpr (std::is_same_v) + { + return EUR(25.6); + } + else + { + return EUR(25); + } + }(); + env.require( + balance(alice, XRP(10'000 - 40) - fee), + balance(bob, USD(42)), + balance(bob, EUR(18)), + balance(carol, carolEUR)); + }; + testHelper2TokensMix(test); + } + { + // Offer where the owner is also the issuer, sender pays + // fee + Env env(*this, features); + + env.fund(XRP(10'000), alice, bob, gw); + + MPT const USD = MPTTester( + {.env = env, + .issuer = gw, + .holders = {alice, bob}, + .transferFee = 25'000, + .maxAmt = 1'000}); + + env(offer(gw, XRP(100), USD(100))); + env(pay(alice, bob, USD(100)), sendmax(XRP(100)), txflags(tfPartialPayment)); + env.require(balance(alice, XRP(10'000 - 100) - txfee(env, 2)), balance(bob, USD(80))); + } + { + // Offer where the owner is also the issuer, sender pays + // fee + Env env(*this, features); + + env.fund(XRP(10'000), alice, bob, gw); + + MPT const USD = MPTTester( + {.env = env, + .issuer = gw, + .holders = {alice, bob}, + .transferFee = 25'000, + .maxAmt = 1'000}); + + env(offer(gw, XRP(125), USD(125))); + env(pay(alice, bob, USD(100)), sendmax(XRP(200))); + env.require(balance(alice, XRP(10'000 - 125) - txfee(env, 2)), balance(bob, USD(100))); + } + } + + void + testFalseDry(FeatureBitset features) + { + testcase("falseDryChanges"); + + using namespace jtx; + + auto const gw = Account("gateway"); + Account const alice("alice"); + Account const bob("bob"); + Account const carol("carol"); + + auto test = [&](auto&& issue1, auto&& issue2) { + Env env(*this, features); + + env.fund(XRP(10'000), alice, carol, gw); + env.fund(reserve(env, 5), bob); + env.close(); + + auto const USD = + issue1({.env = env, .token = "USD", .issuer = gw, .holders = {alice, carol, bob}}); + auto const EUR = + issue2({.env = env, .token = "EUR", .issuer = gw, .holders = {alice, carol, bob}}); + + env(pay(gw, alice, EUR(50))); + env(pay(gw, bob, USD(50))); + + // Bob has _just_ slightly less than 50 xrp available + // If his owner count changes, he will have more liquidity. + // This is one error case to test (when Flow is used). + // Computing the incoming xrp to the XRP/USD offer will + // require two recursive calls to the EUR/XRP offer. The + // second call will return tecPATH_DRY, but the entire path + // should not be marked as dry. This is the second error + // case to test (when flowV1 is used). + env(offer(bob, EUR(50), XRP(50))); + env(offer(bob, XRP(50), USD(50))); + + env(pay(alice, carol, USD(1'000'000)), + path(~XRP, ~USD), + sendmax(EUR(500)), + txflags(tfNoRippleDirect | tfPartialPayment)); + + auto const carolUSD = env.balance(carol, USD).value(); + BEAST_EXPECT(carolUSD > USD(0) && carolUSD < USD(50)); + }; + testHelper2TokensMix(test); + } + + void + testLimitQuality() + { + // Single path with two offers and limit quality. The + // quality limit is such that the first offer should be + // taken but the second should not. The total amount + // delivered should be the sum of the two offers and sendMax + // should be more than the first offer. + testcase("limitQuality"); + using namespace jtx; + + auto const gw = Account("gateway"); + Account const alice("alice"); + Account const bob("bob"); + Account const carol("carol"); + + { + Env env(*this); + + env.fund(XRP(10'000), alice, bob, carol, gw); + + MPT const USD = MPTTester({.env = env, .issuer = gw, .holders = {alice, bob, carol}}); + + env(pay(gw, bob, USD(100))); + env(offer(bob, XRP(50), USD(50))); + env(offer(bob, XRP(100), USD(50))); + + env(pay(alice, carol, USD(100)), + path(~USD), + sendmax(XRP(100)), + txflags(tfNoRippleDirect | tfPartialPayment | tfLimitQuality)); + + env.require(balance(carol, USD(50))); + } + } + + // Helper function that returns the reserve on an account based on + // the passed in number of owners. + static XRPAmount + reserve(jtx::Env& env, std::uint32_t count) + { + return env.current()->fees().accountReserve(count); + } + + // Helper function that returns the Offers on an account. + static std::vector> + offersOnAccount(jtx::Env& env, jtx::Account account) + { + std::vector> result; + forEachItem(*env.current(), account, [&result](std::shared_ptr const& sle) { + if (sle->getType() == ltOFFER) + result.push_back(sle); + }); + return result; + } + + void + testSelfPayment1(FeatureBitset features) + { + testcase("Self-payment 1"); + + // In this test case the new flow code mis-computes the + // amount of money to move. Fortunately the new code's + // re-execute check catches the problem and throws out the + // transaction. + // + // The old payment code handles the payment correctly. + using namespace jtx; + + auto test = [&](auto&& issue1, auto&& issue2) { + auto const gw1 = Account("gw1"); + auto const gw2 = Account("gw2"); + auto const alice = Account("alice"); + + Env env(*this, features); + + env.fund(XRP(1'000'000), gw1, gw2); + env.close(); + + // The fee that's charged for transactions. + auto const f = env.current()->fees().base; + + env.fund(reserve(env, 3) + f * 4, alice); + env.close(); + + auto const USD = issue1( + {.env = env, .token = "USD", .issuer = gw1, .holders = {alice}, .limit = 20'000}); + auto const EUR = issue2( + {.env = env, .token = "EUR", .issuer = gw2, .holders = {alice}, .limit = 20'000}); + + env(pay(gw1, alice, USD(10))); + env(pay(gw2, alice, EUR(10'000))); + env.close(); + + env(offer(alice, USD(5'000), EUR(6'000))); + env.close(); + + env.require(owners(alice, 3)); + env.require(balance(alice, USD(10))); + env.require(balance(alice, EUR(10'000))); + + auto aliceOffers = offersOnAccount(env, alice); + BEAST_EXPECT(aliceOffers.size() == 1); + for (auto const& offerPtr : aliceOffers) + { + auto const offer = *offerPtr; + BEAST_EXPECT(offer[sfLedgerEntryType] == ltOFFER); + BEAST_EXPECT(offer[sfTakerGets] == EUR(6'000)); + BEAST_EXPECT(offer[sfTakerPays] == USD(5'000)); + } + + env(pay(alice, alice, EUR(6'000)), sendmax(USD(5'000)), txflags(tfPartialPayment)); + env.close(); + + env.require(owners(alice, 3)); + env.require(balance(alice, USD(10))); + env.require(balance(alice, EUR(10'000))); + aliceOffers = offersOnAccount(env, alice); + BEAST_EXPECT(aliceOffers.size() == 1); + for (auto const& offerPtr : aliceOffers) + { + auto const offer = *offerPtr; + BEAST_EXPECT(offer[sfLedgerEntryType] == ltOFFER); + if constexpr (std::is_same_v, IOU>) + { + BEAST_EXPECT(offer[sfTakerGets] == EUR(5'988)); + } + else + { + BEAST_EXPECT(offer[sfTakerGets] == EUR(5'989)); + } + BEAST_EXPECT(offer[sfTakerPays] == USD(4'990)); + } + }; + testHelper2TokensMix(test); + } + + template + struct TokenData + { + TGets gets; + TPays pays; + jtx::PrettyAmount remTakerGets; + jtx::PrettyAmount remTakerPays; + }; + + void + testSelfPayment2(FeatureBitset features) + { + testcase("Self-payment 2"); + + using namespace jtx; + + // This test shows a difference between IOU and MPT + // self-payment result depending on IOU trustline limit. + + auto const gw1 = Account("gw1"); + auto const gw2 = Account("gw2"); + auto const alice = Account("alice"); + + auto initMPT = [&](Env& env) { + MPT const USD = + MPTTester({.env = env, .issuer = gw1, .holders = {alice}, .maxAmt = 506}); + MPT const EUR = + MPTTester({.env = env, .issuer = gw2, .holders = {alice}, .maxAmt = 606}); + // Payment's engine last step overflows + // OutstandingAmount since it doesn't know if the + // BookStep redeems or not. The BookStep then has 600EUR + // available. Consequently, the entire offer is crossed. + // Note remaining takerGets is 541 rather than 540 due to integral + // rounding. XRP has a similar result. + return TokenData{ + .gets = EUR, .pays = USD, .remTakerGets = EUR(541), .remTakerPays = USD(450)}; + }; + + auto initXRP = [&](Env& env) { + MPT const USD = + MPTTester({.env = env, .issuer = gw1, .holders = {alice}, .maxAmt = 1'000}); + // Payment's engine last step overflows + // OutstandingAmount since it doesn't know if the + // BookStep redeems or not. The BookStep then has 600EUR + // available. Consequently, the entire offer is crossed. + // Note remaining takerGets is 540.000001 rather than 540 due to + // integral rounding. + return TokenData{ + .gets = XRP, + .pays = USD, + .remTakerGets = XRP(540.000001), + .remTakerPays = USD(450)}; + }; + + auto initIOU = [&](Env& env) { + auto const USD = gw1["USD"]; + auto const EUR = gw2["EUR"]; + env(trust(alice, USD(506))); + env(trust(alice, EUR(606))); + env.close(); + // Payment's engine last step is limited by alice's + // trustline - 606. Therefore, only 6EUR is delivered + // and the offer is partially crossed. + return TokenData{ + .gets = EUR, .pays = USD, .remTakerGets = EUR(594), .remTakerPays = USD(495)}; + }; + + auto initIOU1 = [&](Env& env) { + auto const USD = gw1["USD"]; + auto const EUR = gw2["EUR"]; + env(trust(alice, USD(1'000))); + env(trust(alice, EUR(1'000))); + env.close(); + // Payment's engine last step is not limited by alice's + // trustline. Therefore, the entire offer is crossed. + // This the same result as with MPT. + return TokenData{ + .gets = EUR, .pays = USD, .remTakerGets = EUR(540), .remTakerPays = USD(450)}; + }; + + auto test = [&](auto&& initToken) { + Env env(*this, features); + + env.fund(XRP(2'000), gw1, gw2, alice); + env.close(); + + auto const f = env.current()->fees().base; + + auto const tok = initToken(env); + + auto const& TOK1 = tok.pays; + auto const& TOK2 = tok.gets; + bool const isTakerGetsXRP = isXRP(Asset{TOK2}); + std::uint32_t const ownerCnt = isTakerGetsXRP ? 2 : 3; + + env(pay(gw1, alice, TOK1(500))); + if (!isTakerGetsXRP) + env(pay(gw2, alice, TOK2(600))); + env.close(); + + env(offer(alice, TOK1(500), TOK2(600))); + env.close(); + + env.require(owners(alice, ownerCnt)); + env.require(balance(alice, TOK1(500))); + if (isTakerGetsXRP) + { + env.require(balance(alice, TOK2(2'000) - 2 * f)); + } + else + { + env.require(balance(alice, TOK2(600))); + } + + auto aliceOffers = offersOnAccount(env, alice); + BEAST_EXPECT(aliceOffers.size() == 1); + for (auto const& offerPtr : aliceOffers) + { + auto const offer = *offerPtr; + BEAST_EXPECT(offer[sfLedgerEntryType] == ltOFFER); + BEAST_EXPECT(offer[sfTakerGets] == TOK2(600)); + BEAST_EXPECT(offer[sfTakerPays] == TOK1(500)); + } + + env(pay(alice, alice, TOK2(60)), sendmax(TOK1(50)), txflags(tfPartialPayment)); + env.close(); + + env.require(owners(alice, ownerCnt)); + env.require(balance(alice, TOK1(500))); + if (isTakerGetsXRP) + { + env.require(balance(alice, TOK2(2'000) - 3 * f)); + } + else + { + env.require(balance(alice, TOK2(600))); + } + aliceOffers = offersOnAccount(env, alice); + BEAST_EXPECT(aliceOffers.size() == 1); + for (auto const& offerPtr : aliceOffers) + { + auto const offer = *offerPtr; + BEAST_EXPECT(offer[sfLedgerEntryType] == ltOFFER); + BEAST_EXPECT(offer[sfTakerGets] == tok.remTakerGets); + BEAST_EXPECT(offer[sfTakerPays] == tok.remTakerPays); + } + }; + + test(initXRP); + test(initMPT); + test(initIOU); + test(initIOU1); + } + + void + testSelfFundedXRPEndpoint(bool consumeOffer, FeatureBitset features) + { + // Test that the deferred credit table is not bypassed for + // XRPEndpointSteps. If the account in the first step is + // sending XRP and that account also owns an offer that + // receives XRP, it should not be possible for that step to + // use the XRP received in the offer as part of the payment. + testcase("Self funded XRPEndpoint"); + + using namespace jtx; + + Env env(*this, features); + + auto const alice = Account("alice"); + auto const gw = Account("gw"); + + env.fund(XRP(10'000), alice, gw); + + MPT const USD = MPTTester({.env = env, .issuer = gw, .holders = {alice}, .maxAmt = 20}); + + env(pay(gw, alice, USD(10))); + env(offer(alice, XRP(50'000), USD(10))); + + // Consuming the offer changes the owner count, which could + // also cause liquidity to decrease in the forward pass + auto const toSend = consumeOffer ? USD(10) : USD(9); + env(pay(alice, alice, toSend), + path(~USD), + sendmax(XRP(20'000)), + txflags(tfPartialPayment | tfNoRippleDirect)); + } + + void + testUnfundedOffer(FeatureBitset features) + { + testcase("Unfunded Offer"); + + using namespace jtx; + { + // Test reverse + Env env(*this, features); + + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + + env.fund(XRP(100'000), alice, bob, gw); + + MPT const USD = + MPTTester({.env = env, .issuer = gw, .holders = {alice, bob}, .maxAmt = 20E+17}); + + // scale by 17 + STAmount const tinyAmt1{USD, 9'000'000'000'000'000ll, 0, false, STAmount::unchecked{}}; + STAmount const tinyAmt3{USD, 9'000'000'000'000'003ll, 0, false, STAmount::unchecked{}}; + + env(offer(gw, drops(9'000'000'000), tinyAmt3)); + + env(pay(alice, bob, tinyAmt1), + path(~USD), + sendmax(drops(9'000'000'000)), + txflags(tfNoRippleDirect)); + + BEAST_EXPECT(!isOffer(env, gw, XRP(0), USD(0))); + } + { + // Test forward + Env env(*this, features); + + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + + env.fund(XRP(100'000), alice, bob, gw); + + MPT const USD = + MPTTester({.env = env, .issuer = gw, .holders = {alice, bob}, .maxAmt = 20E+17}); + + // scale by 17 + STAmount const tinyAmt1{USD, 9'000'000'000'000'000ll, 0, false, STAmount::unchecked{}}; + STAmount const tinyAmt3{USD, 9'000'000'000'000'003ll, 0, false, STAmount::unchecked{}}; + + env(pay(gw, alice, tinyAmt1)); + + env(offer(gw, tinyAmt3, drops(9'000'000'000))); + env(pay(alice, bob, drops(9'000'000'000)), + path(~XRP), + sendmax(USD(static_cast(1E+17))), + txflags(tfNoRippleDirect)); + + BEAST_EXPECT(!isOffer(env, gw, USD(0), XRP(0))); + } + } + + void + testReExecuteDirectStep(FeatureBitset features) + { + testcase("ReexecuteDirectStep"); + + using namespace jtx; + Env env(*this, features); + + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + + env.fund(XRP(10'000), alice, bob, gw); + + // scale by 16 + MPT const USD = + MPTTester({.env = env, .issuer = gw, .holders = {alice, bob}, .maxAmt = 100E+16}); + + env( + pay(gw, + alice, + // 12.55.... + STAmount{USD, std::uint64_t(1255555555555555ull), 2, false})); + + env(offer( + gw, + // 5.0... + STAmount{USD, std::uint64_t(5000000000000000ull), 1, false}, + XRP(1000))); + + env(offer( + gw, + // .555... + STAmount{USD, std::uint64_t(5555555555555555ull), 0, false}, + XRP(10))); + + env(offer( + gw, + // 4.44.... + STAmount{USD, std::uint64_t(4444444444444444ull), 1, false}, + XRP(.1))); + + env(offer( + alice, + // 17 + STAmount{USD, std::uint64_t(1700000000000000ull), 0, false}, + XRP(.001))); + + env(pay(alice, bob, XRP(10'000)), + path(~XRP), + sendmax(USD(static_cast(100E+16))), + txflags(tfPartialPayment | tfNoRippleDirect)); + } + + void + testSelfPayLowQualityOffer(FeatureBitset features) + { + // The new payment code used to assert if an offer was made + // for more XRP than the offering account held. This unit + // test reproduces that failing case. + testcase("Self crossing low quality offer"); + + using namespace jtx; + + Env env(*this, features); + + auto const ann = Account("ann"); + auto const gw = Account("gateway"); + + auto const fee = env.current()->fees().base; + env.fund(reserve(env, 2) + drops(9999640) + fee, ann); + env.fund(reserve(env, 2) + fee * 4, gw); + + // scale by 5 + MPT const CTB = MPTTester( + {.env = env, + .issuer = gw, + .holders = {ann}, + .transferFee = 2'000, // 2% + .maxAmt = 1'000'000}); + + env(pay(gw, ann, CTB(285'600))); + env.close(); + + env(offer(ann, drops(365'611'702'030), CTB(571'300))); + env.close(); + + // This payment caused assert. + env(pay(ann, ann, CTB(68'700)), sendmax(drops(20'000'000'000)), txflags(tfPartialPayment)); + } + + void + testEmptyStrand(FeatureBitset features) + { + testcase("Empty Strand"); + using namespace jtx; + + auto const alice = Account("alice"); + + Env env(*this, features); + + env.fund(XRP(10000), alice); + + MPT const USD; + + env(pay(alice, alice, USD(100)), path(~USD), ter(temBAD_PATH)); + } + + void + testXRPPathLoop() + { + testcase("Circular XRP"); + + using namespace jtx; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + + { + // Payment path starting with XRP + auto test = [&](auto&& issue1, auto&& issue2) { + Env env(*this); + env.fund(XRP(10'000), alice, bob, gw); + + auto const USD = + issue1({.env = env, .token = "USD", .issuer = gw, .holders = {alice, bob}}); + auto const EUR = + issue2({.env = env, .token = "EUR", .issuer = gw, .holders = {alice, bob}}); + env(pay(gw, alice, USD(100))); + env(pay(gw, alice, EUR(100))); + env.close(); + + env(offer(alice, XRP(100), USD(100)), txflags(tfPassive)); + env(offer(alice, USD(100), XRP(100)), txflags(tfPassive)); + env(offer(alice, XRP(100), EUR(100)), txflags(tfPassive)); + env.close(); + + TER const expectedTer = TER{temBAD_PATH_LOOP}; + env(pay(alice, bob, EUR(1)), + path(~USD, ~XRP, ~EUR), + sendmax(XRP(1)), + txflags(tfNoRippleDirect), + ter(expectedTer)); + }; + testHelper2TokensMix(test); + } + { + // Payment path ending with XRP + auto test = [&](auto&& issue1, auto&& issue2) { + Env env(*this); + env.fund(XRP(10'000), alice, bob, gw); + auto const USD = + issue1({.env = env, .token = "USD", .issuer = gw, .holders = {alice, bob}}); + auto const EUR = + issue2({.env = env, .token = "EUR", .issuer = gw, .holders = {alice, bob}}); + env(pay(gw, alice, USD(100))); + env(pay(gw, alice, EUR(100))); + env.close(); + + env(offer(alice, XRP(100), USD(100)), txflags(tfPassive)); + env(offer(alice, EUR(100), XRP(100)), txflags(tfPassive)); + env.close(); + // EUR -> //XRP -> //USD ->XRP + env(pay(alice, bob, XRP(1)), + path(~XRP, ~USD, ~XRP), + sendmax(EUR(1)), + txflags(tfNoRippleDirect), + ter(temBAD_PATH_LOOP)); + }; + testHelper2TokensMix(test); + } + { + // Payment where loop is formed in the middle of the + // path, not on an endpoint + auto test = [&](auto&& issue1, auto&& issue2, auto&& issue3) { + Env env(*this); + env.fund(XRP(10'000), alice, bob, gw); + env.close(); + auto const USD = + issue1({.env = env, .token = "USD", .issuer = gw, .holders = {alice, bob}}); + auto const EUR = + issue2({.env = env, .token = "EUR", .issuer = gw, .holders = {alice, bob}}); + auto const JPY = + issue3({.env = env, .token = "JPY", .issuer = gw, .holders = {alice, bob}}); + env(pay(gw, alice, USD(100))); + env(pay(gw, alice, EUR(100))); + env(pay(gw, alice, JPY(100))); + env.close(); + + env(offer(alice, USD(100), XRP(100)), txflags(tfPassive)); + env(offer(alice, XRP(100), EUR(100)), txflags(tfPassive)); + env(offer(alice, EUR(100), XRP(100)), txflags(tfPassive)); + env(offer(alice, XRP(100), JPY(100)), txflags(tfPassive)); + env.close(); + + env(pay(alice, bob, JPY(1)), + path(~XRP, ~EUR, ~XRP, ~JPY), + sendmax(USD(1)), + txflags(tfNoRippleDirect), + ter(temBAD_PATH_LOOP)); + }; + testHelper3TokensMix(test); + } + } + + void + testMaxAndSelfPaymentEdgeCases(FeatureBitset features) + { + testcase("Max Flow/Self Payment Edge Cases"); + using namespace jtx; + Account const gw("gw"); + Account const alice("alice"); + Account const carol("carol"); + Account const bob("bob"); + + // Direct payment between holders. + { + Env env(*this); + + env.fund(XRP(1'000), gw, alice, carol); + + MPT const USD = + MPTTester({.env = env, .issuer = gw, .holders = {alice, carol}, .maxAmt = 100}); + + env(pay(gw, alice, USD(100))); + + env(pay(alice, carol, USD(100))); + + BEAST_EXPECT(env.balance(gw, USD) == USD(-100)); + BEAST_EXPECT(env.balance(carol, USD) == USD(100)); + BEAST_EXPECT(env.balance(alice, USD) == USD(0)); + } + + // Direct payment between holders. Partial payment limited + // by holder funds. + { + Env env(*this); + + env.fund(XRP(1'000), gw, alice, carol); + + MPT const USD = + MPTTester({.env = env, .issuer = gw, .holders = {alice, carol}, .maxAmt = 100}); + + env(pay(gw, alice, USD(80))); + + env(pay(alice, carol, USD(100)), txflags(tfPartialPayment)); + + BEAST_EXPECT(env.balance(gw, USD) == USD(-80)); + BEAST_EXPECT(env.balance(alice, USD) == USD(0)); + BEAST_EXPECT(env.balance(carol, USD) == USD(80)); + } + + // Direct payment between holders. Partial payment limited + // by holder funds. OutstandingAmount is already at max + // before the payment. + { + Env env(*this); + + env.fund(XRP(1'000), gw, alice, carol, bob); + + MPT const USD = MPTTester( + {.env = env, .issuer = gw, .holders = {alice, carol, bob}, .maxAmt = 100}); + + env(pay(gw, bob, USD(20))); + env(pay(gw, alice, USD(80))); + + env(pay(alice, carol, USD(100)), txflags(tfPartialPayment)); + + BEAST_EXPECT(env.balance(gw, USD) == USD(-100)); + BEAST_EXPECT(env.balance(alice, USD) == USD(0)); + BEAST_EXPECT(env.balance(carol, USD) == USD(80)); + } + + // Cross-currency payment holder to holder. Holder owns an + // offer. OutstandingAmount is already at max before the + // payment. + { + Env env(*this); + + env.fund(XRP(1'000), gw, alice, carol, bob); + + MPT const USD = + MPTTester({.env = env, .issuer = gw, .holders = {alice, carol}, .maxAmt = 100}); + + env(pay(gw, alice, USD(100))); + + env(offer(alice, XRP(100), USD(100))); + + env(pay(bob, carol, USD(100)), sendmax(XRP(100)), path(~USD)); + + BEAST_EXPECT(env.balance(gw, USD) == USD(-100)); + BEAST_EXPECT(env.balance(alice, USD) == USD(0)); + BEAST_EXPECT(env.balance(carol, USD) == USD(100)); + } + + // Cross-currency payment holder to holder. Issuer owns an + // offer. OutstandingAmount is already at max before the + // payment. Since an issuer owns the offer, it issues more + // tokens to another holder, and the payment fails. + { + Env env(*this); + + env.fund(XRP(1'000), gw, alice, carol); + + MPT const USD = + MPTTester({.env = env, .issuer = gw, .holders = {carol}, .maxAmt = 100}); + + env(pay(gw, carol, USD(100))); + + env(offer(gw, XRP(100), USD(100))); + + env(pay(alice, carol, USD(100)), + sendmax(XRP(100)), + path(~USD), + txflags(tfPartialPayment), + ter(tecPATH_DRY)); + + BEAST_EXPECT(env.balance(gw, USD) == USD(-100)); + BEAST_EXPECT(env.balance(carol, USD) == USD(100)); + } + + // Cross-currency payment holder to holder. Issuer owns an + // offer. OutstandingAmount is at 80USD before the payment. + // Consequently, the issuer can issue 20USD more. + { + Env env(*this); + + env.fund(XRP(1'000), gw, alice, carol); + + MPT const USD = + MPTTester({.env = env, .issuer = gw, .holders = {carol}, .maxAmt = 100}); + + env(pay(gw, carol, USD(80))); + + env(offer(gw, XRP(100), USD(100))); + + env(pay(alice, carol, USD(100)), + sendmax(XRP(100)), + path(~USD), + txflags(tfPartialPayment)); + + BEAST_EXPECT(env.balance(gw, USD) == USD(-100)); + BEAST_EXPECT(env.balance(carol, USD) == USD(100)); + } + + // Cross-currency payment holder to holder. Holder owns an + // offer. The offer buys more MPT's. The payment fails since + // OutstandingAmount is already at max. + { + Env env(*this); + + env.fund(XRP(1'000), gw, alice); + + MPT const USD = + MPTTester({.env = env, .issuer = gw, .holders = {alice}, .maxAmt = 100}); + + env(pay(gw, alice, USD(100))); + + env(offer(alice, USD(100), XRP(100))); + + env(pay(gw, alice, XRP(100)), sendmax(USD(100)), path(~XRP), ter(tecPATH_PARTIAL)); + + BEAST_EXPECT(env.balance(gw, USD) == USD(-100)); + BEAST_EXPECT(env.balance(alice, USD) == USD(100)); + } + + // Cross-currency payment issuer to holder. Holder owns an + // offer. The offer buys EUR, OutstandingAmount goes to max, + // no overflow. The offer redeems USD to the issuer. While + // OutstandingAmount is already at max, the payment succeeds + // since USD is redeemed. + { + auto test = [&](auto&& issue1, auto&& issue2) { + Env env(*this); + + env.fund(XRP(1'000), gw, alice, carol); + env.close(); + + auto const USD = issue1( + {.env = env, + .token = "USD", + .issuer = gw, + .holders = {alice, carol}, + .limit = 100}); + using tUSD = std::decay_t; + auto const EUR = issue2( + {.env = env, + .token = "EUR", + .issuer = gw, + .holders = {alice, carol}, + .limit = 100}); + + env(pay(gw, alice, USD(100))); + + env(offer(alice, EUR(100), USD(100))); + + env(pay(gw, carol, USD(100)), sendmax(EUR(100)), path(~USD)); + + if constexpr (std::is_same_v) + BEAST_EXPECT(env.balance(gw, USD) == USD(-100)); + BEAST_EXPECT(env.balance(alice, USD) == USD(0)); + BEAST_EXPECT(env.balance(alice, EUR) == EUR(100)); + BEAST_EXPECT(env.balance(carol, USD) == USD(100)); + }; + testHelper2TokensMix(test); + } + + // Cross-currency payment holder to holder. Offer is owned + // by destination account. OutstandingAmount is not at max. + { + Env env(*this); + + env.fund(XRP(1'000), gw, alice, carol); + + MPT const USD = + MPTTester({.env = env, .issuer = gw, .holders = {carol}, .maxAmt = 120}); + + env(pay(gw, carol, USD(100))); + + env(offer(carol, XRP(100), USD(100))); + + env(pay(alice, carol, USD(100)), + path(~USD), + sendmax(XRP(100)), + txflags(tfPartialPayment)); + + BEAST_EXPECT(env.balance(carol, USD) == USD(100)); + } + + // Cross-currency payment holder to holder. Offer is owned + // by destination account. OutstandingAmount is already at + // max. + { + Env env(*this); + + env.fund(XRP(1'000), gw, alice, carol); + + MPT const USD = + MPTTester({.env = env, .issuer = gw, .holders = {carol}, .maxAmt = 100}); + + env(pay(gw, carol, USD(100))); + + env(offer(carol, XRP(100), USD(100))); + + env(pay(alice, carol, USD(100)), + path(~USD), + sendmax(XRP(100)), + txflags(tfPartialPayment)); + + BEAST_EXPECT(env.balance(carol, USD) == USD(100)); + } + + // Cross-currency payment holder to holder. Multiple offers + // with different owners - some holders, some issuer. + { + auto test = [&](auto&& issue1, auto&& issue2) { + Env env(*this); + + env.fund(XRP(1'000), gw, alice, carol, bob); + env.close(); + + auto const USD = issue1( + {.env = env, + .token = "USD", + .issuer = gw, + .holders = {alice, carol, bob}, + .limit = 1'000}); + using tUSD = std::decay_t; + auto const EUR = issue2( + {.env = env, + .token = "EUR", + .issuer = gw, + .holders = {alice, carol, bob}, + .limit = 1'000}); + using tEUR = std::decay_t; + + env(pay(gw, alice, USD(600))); + env(pay(gw, carol, EUR(700))); + + env(offer(alice, EUR(100), USD(105))); + env(offer(gw, EUR(100), USD(104))); + env(offer(gw, EUR(100), USD(103))); + env(offer(gw, EUR(100), USD(102))); + env(offer(gw, EUR(100), USD(101))); + env(offer(gw, EUR(100), USD(100))); + + env(pay(carol, bob, USD(2'000)), + sendmax(EUR(2'000)), + path(~USD), + txflags(tfPartialPayment)); + + if constexpr (std::is_same_v) + { + BEAST_EXPECT(env.balance(gw, USD) == USD(-1'000)); + BEAST_EXPECT(env.balance(alice, USD) == USD(495)); + BEAST_EXPECT(env.balance(bob, USD) == USD(505)); + } + else + { + BEAST_EXPECT(env.balance(gw, USD) == USD(0)); + BEAST_EXPECT(env.balance(alice, USD) == USD(495)); + // all offers are consumed since the limit is different + // for the holders + BEAST_EXPECT(env.balance(bob, USD) == USD(615)); + } + if constexpr (std::is_same_v) + { + if constexpr (std::is_same_v) + { + BEAST_EXPECT(env.balance(carol, EUR) == EUR(210)); + } + else + { + // carol sells 600USD since all offers are consumed + BEAST_EXPECT(env.balance(carol, EUR) == EUR(100)); + } + } + else + { + BEAST_EXPECT( + env.balance(carol, EUR) == STAmount(EUR, UINT64_C(209'9009900990099), -13)); + } + // 100/101 is partially crossed (90/91) and 100/100 is + // unfunded when MPT. All offers are consumed if IOU. + env.require(offers(gw, 0)); + // alice's offer is consumed. + env.require(offers(alice, 0)); + }; + testHelper2TokensMix(test); + } + + // Cross-currency payment holder to holder. Multiple offers + // with different owners - some holders, some issuer. Source + // and destination account is the same. + { + Env env(*this); + + env.fund(XRP(1'000), gw, alice, carol); + + MPT const USD = + MPTTester({.env = env, .issuer = gw, .holders = {alice, carol}, .maxAmt = 2'000}); + + env(pay(gw, carol, USD(1'000))); + env(pay(gw, alice, USD(600))); + + env(offer(gw, XRP(5), USD(11))); + env(offer(gw, XRP(6), USD(13))); + env(offer(carol, XRP(7), USD(15))); + env(offer(carol, XRP(17), USD(35))); + env(offer(carol, XRP(23), USD(47))); + env(offer(alice, XRP(10), USD(19))); + env(offer(alice, XRP(15), USD(28))); + env(offer(alice, XRP(25), USD(46))); + + env(pay(carol, carol, USD(200)), sendmax(XRP(100)), txflags(tfPartialPayment)); + + BEAST_EXPECT(env.balance(gw, USD) == USD(-1'624)); + BEAST_EXPECT(env.balance(carol, USD) == USD(1'102)); + env.require(offers(carol, 0)); + env.require(offers(gw, 0)); + // 100 XRP's = 5+6+7+17+23+10+15+17(25-8) + BEAST_EXPECT(isOffer(env, alice, XRP(8), USD(15))); + } + + // Cross-currency payment holder to holder. Multiple offers + // with different owners - some holders, some issuer. + { + Env env(*this); + env.fund(XRP(1'000), gw, alice, carol, bob); + + MPT const USD = + MPTTester({.env = env, .issuer = gw, .holders = {alice, carol, bob}, .maxAmt = 30}); + + env(pay(gw, alice, USD(12))); // 12, 15, 20 + env(pay(gw, bob, USD(5))); // 5, 5, 10 + + env(offer(alice, XRP(10), USD(12))); + env(offer(gw, XRP(10), USD(11))); + env(offer(bob, XRP(10), USD(10))); + + env(pay(carol, bob, USD(30)), sendmax(XRP(30)), txflags(tfPartialPayment), path(~USD)); + BEAST_EXPECT(env.balance(gw, USD) == USD(-28)); + BEAST_EXPECT(env.balance(alice, USD) == USD(0)); + // 12+11+5 + BEAST_EXPECT(env.balance(bob, USD) == USD(28)); + } + + // Cross-currency payment two steps. Second book step + // issues, first book step redeems. + { + Account const dan{"dan"}; + Account const john{"john"}; + Account const ed{"ed"}; + Account const sam{"sam"}; + Account const bill{"bill"}; + + struct TestData + { + int maxAmt; + int sendMax; + int dstTrustLimit; + int dstExpectEUR; + int outstandingUSD; + int expEdBuyUSD; + int expDanBuyUSD; + int expBobSellUSD; + int expGwXRP; // whole XRP excluding the fees + std::uint8_t expOffersGw; + bool lastGwBuyUSD; + std::uint8_t + expOffersBob() const + { + return expBobSellUSD == 0 ? 1 : 0; + } + std::uint8_t + expOffersEd() const + { + // partially crossed if < 100 + return expEdBuyUSD < 100 ? 1 : 0; + } + std::uint8_t + expOffersDan() const + { + return expDanBuyUSD == 0 ? 1 : 0; + } + }; + + auto test = [&](TestData const& d) { + Env env(*this); + env.fund(XRP(1'000), gw, alice, carol, bob, dan, john, ed, sam, bill); + env.close(); + + MPT const USD = MPTTester( + {.env = env, .issuer = gw, .holders = {alice, carol, bob}, .maxAmt = d.maxAmt}); + auto const EUR = gw["EUR"]; + + env(pay(gw, alice, USD(100))); + env(pay(gw, carol, USD(100))); + env(pay(gw, bob, USD(100))); + + BEAST_EXPECT(env.balance(gw, USD) == USD(-300)); + + env(trust(john, EUR(100))); + env(trust(dan, EUR(100))); + env(trust(ed, EUR(100))); + env(trust(bill, EUR(d.dstTrustLimit))); + + env(pay(gw, john, EUR(100))); + env(pay(gw, dan, EUR(100))); + env(pay(gw, ed, EUR(100))); + env.close(); + + // Sell USD + env(offer(alice, XRP(100), USD(100))); + env.close(); // close after each create to ensure + // the order + env(offer(carol, XRP(100), USD(100))); + env.close(); + if (!d.lastGwBuyUSD) + { + env(offer(gw, XRP(100), USD(100))); + env.close(); + } + env(offer(bob, XRP(100), USD(100))); + env.close(); + if (d.lastGwBuyUSD) + { + env(offer(gw, XRP(100), USD(100))); + env.close(); + } + BEAST_EXPECT(expectOffers(env, alice, 1)); + BEAST_EXPECT(expectOffers(env, carol, 1)); + BEAST_EXPECT(expectOffers(env, gw, 1)); + BEAST_EXPECT(expectOffers(env, bob, 1)); + + // Buy USD + env(offer(john, USD(100), EUR(100))); + env.close(); + env(offer(gw, USD(100), EUR(100))); + env.close(); + env(offer(dan, USD(100), EUR(100))); + env.close(); + env(offer(ed, USD(100), EUR(100))); + env.close(); + BEAST_EXPECT(expectOffers(env, john, 1)); + BEAST_EXPECT(expectOffers(env, gw, 2)); + BEAST_EXPECT(expectOffers(env, dan, 1)); + BEAST_EXPECT(expectOffers(env, ed, 1)); + + env(pay(sam, bill, EUR(400)), + sendmax(XRP(d.sendMax)), + path(~USD, ~EUR), + txflags(tfPartialPayment | tfNoRippleDirect)); + env.close(); + + auto const baseFee = env.current()->fees().base.drops(); + BEAST_EXPECT(env.balance(bill, EUR) == EUR(d.dstExpectEUR)); + BEAST_EXPECT(env.balance(john, USD) == USD(100)); + BEAST_EXPECT(env.balance(dan, USD) == USD(d.expDanBuyUSD)); + BEAST_EXPECT(env.balance(ed, USD) == USD(d.expEdBuyUSD)); + BEAST_EXPECT(env.balance(gw, USD) == USD(-d.outstandingUSD)); + BEAST_EXPECT(env.balance(alice, USD) == USD(0)); + BEAST_EXPECT(env.balance(carol, USD) == USD(0)); + BEAST_EXPECT(env.balance(bob, USD) == USD(100 - d.expBobSellUSD)); + BEAST_EXPECT( + env.balance(gw) == XRPAmount{d.expGwXRP * DROPS_PER_XRP - baseFee * 9}); + BEAST_EXPECT(expectOffers(env, john, 0)); + BEAST_EXPECT(expectOffers(env, gw, d.expOffersGw)); + BEAST_EXPECT(expectOffers(env, dan, d.expOffersDan())); + BEAST_EXPECT(expectOffers(env, ed, d.expOffersEd())); + BEAST_EXPECT(expectOffers(env, alice, 0)); + BEAST_EXPECT(expectOffers(env, carol, 0)); + BEAST_EXPECT(expectOffers(env, bob, d.expOffersBob())); + }; + + // clang-format off + std::vector const tests = { + // Sell USD: alice, carol, bob, gw are consumed. + // Buy USD: john, gw, dan, ed are consumed. + // gw's sell USD is consumed because there is sufficient available balance (100USD). + // but OutstandingAmount is 300USD because gw's sell offer is balanced out by + // gw's buy offer. + //*maxAmt sendMax limitEUR expectEUR outstandingUSD edBuy danBuy bobSell gwXRP offersGw lastGw + { 400, 400, 400, 400, 300, 100, 100, 100, 1100, 0, false}, + // Sell USD: alice, carol, bob, gw are consumed. + // Buy USD: john, gw, dan, ed (partially) are consumed. + // gw's sell USD is partially consumed because there is available balance (50USD). + // OutstandingAmount is 250USD because gw's sell offer is partially balanced by + // gw's buy offer. ed's offer is on the books because it's partially crossed. + // gw's offer is removed from the order book because it's partially consumed and + // the remaining offer is unfunded. + //*maxAmt sendMax limitEUR expectEUR outstandingUSD edBuy danBuy bobSell gwXRP offersGw lastGw + { 350, 400, 400, 350, 250, 50, 100, 100, 1050, 0, false}, + // Sell USD: alice, carol, bob are consumed; gw's is unfunded + // since OutstandingAmount is initially at MaximumAmount. + // Buy USD: john, gw, dan are consumed; ed's remains on the order + // book since 300USD is the sell limit. + //*maxAmt sendMax limitEUR expectEUR outstandingUSD edBuy danBuy bobSell gwXRP offersGw lastGw + { 300, 400, 400, 300, 200, 0, 100, 100, 1000, 0, false}, + // Same as above. bill's trustline limit sets the output to 300USD. + //*maxAmt sendMax limitEUR expectEUR outstandingUSD edBuy danBuy bobSell gwXRP offersGw lastGw + { 300, 400, 300, 300, 200, 0, 100, 100, 1000, 0, false}, + // Sell USD: alice, carol, bob are consumed; gw's removed from + // the order book since it's unfunded. + // Buy USD: john, gw, dan are consumed; ed's remains on the order + // book since 300USD is the limit. + //*maxAmt sendMax limitEUR expectEUR outstandingUSD edBuy danBuy bobSell gwXRP offersGw lastGw + { 300, 400, 300, 300, 200, 0, 100, 100, 1000, 0, true}, + // Sell USD: alice, carol are consumed; gw's removed from + // the order book in rev pass since it's unfunded; bob's + // remains on the order book. + // Buy USD: john, gw; ed's, dan's remains on the order + // book since 300USD is the limit. + //*maxAmt sendMax limitEUR expectEUR outstandingUSD edBuy danBuy bobSell gwXRP offersGw lastGw + { 300, 200, 300, 200, 200, 0, 0, 0, 1000, 0, false}, + // Same as three tests above since limited by buy 300USD (gw offer is unfunded) + //*maxAmt sendMax limitEUR expectEUR outstandingUSD edBuy danBuy bobSell gwXRP offersGw lastGw + { 300, 380, 400, 300, 200, 0, 100, 100, 1000, 0, false}, + }; + // clang-format on + for (auto const& t : tests) + test(t); + } + + // Cross-currency payment. BookStep issues, the first step + // redeems. + { + Account const ed{"ed"}; + + struct TestData + { + int maxAmt; + int sendMax; + int gwOffer; // quality == 1 + int dstExpectXRP; + int outstandingUSD; + int expBobBuyUSD; + int expGwXRP; // whole XRP excluding the fees + std::uint8_t expOffersGw; + bool lastGwBuyUSD; + std::uint8_t + expOffersBob() const + { + // partially crossed if < 100 + return expBobBuyUSD < 100 ? 1 : 0; + } + }; + + auto test = [&](TestData const& d) { + Env env(*this); + env.fund(XRP(1'000), gw, alice, carol, bob, ed); + env.close(); + + MPT const USD = + MPTTester({.env = env, .issuer = gw, .holders = {alice}, .maxAmt = d.maxAmt}); + + env(pay(gw, alice, USD(300))); + env.close(); + + env(offer(carol, USD(100), XRP(100))); + env.close(); + if (!d.lastGwBuyUSD) + { + env(offer(gw, USD(d.gwOffer), XRP(d.gwOffer))); + env.close(); + } + env(offer(bob, USD(100), XRP(100))); + env.close(); + if (d.lastGwBuyUSD) + { + env(offer(gw, USD(d.gwOffer), XRP(d.gwOffer))); + env.close(); + } + + BEAST_EXPECT(expectOffers(env, carol, 1)); + BEAST_EXPECT(expectOffers(env, bob, 1)); + BEAST_EXPECT(expectOffers(env, gw, 1)); + BEAST_EXPECT(env.balance(gw, USD) == USD(-300)); + + env(pay(alice, ed, XRP(300)), + sendmax(USD(d.sendMax)), + path(~XRP), + txflags(tfPartialPayment | tfNoRippleDirect)); + env.close(); + + auto const baseFee = env.current()->fees().base.drops(); + BEAST_EXPECT(env.balance(alice, USD) == USD(300 - d.sendMax)); + BEAST_EXPECT(env.balance(carol, USD) == USD(100)); + BEAST_EXPECT(env.balance(bob, USD) == USD(d.expBobBuyUSD)); + BEAST_EXPECT(env.balance(ed) == XRP(d.dstExpectXRP)); + BEAST_EXPECT(env.balance(gw, USD) == USD(-d.outstandingUSD)); + BEAST_EXPECT( + env.balance(gw) == XRPAmount{d.expGwXRP * DROPS_PER_XRP - baseFee * 3}); + BEAST_EXPECT(expectOffers(env, carol, 0)); + BEAST_EXPECT(expectOffers(env, bob, d.expOffersBob())); + BEAST_EXPECT(expectOffers(env, gw, d.expOffersGw)); + }; + + // clang-format off + std::vector const tests = { + // Buy USD: carol, gw, bob are consumed. + // Gw gets 300USD from alice; carol and bob buy 200USD, + // therefore OutstandingAmount is 200. + //*maxAmt sendMax gwOffer dstXRP outstandingUSD bobBuy gwXRP offersGw lastGw + { 300, 300, 100, 1300, 200, 100, 900, 0, false}, + // Same as above. Gw offer location in the order book doesn't matter + //*maxAmt sendMax gwOffer dstXRP outstandingUSD bobBuy gwXRP offersGw lastGw + { 300, 300, 100, 1300, 200, 100, 900, 0, true}, + // Buy USD: carol, gw are consumed. bob's offer remains on the order book. + // Gw gets 300USD from alice; carol buys 100USD, + // therefore OutstandingAmount is 100. + //*maxAmt sendMax gwOffer dstXRP outstandingUSD bobBuy gwXRP offersGw lastGw + { 300, 300, 200, 1300, 100, 0, 800, 0, false}, + // Buy USD: carol, bob are consumed; gw's is partially consumed (100/100) since it's last. + // Gw gets 300USD from alice; carol and bob buy 200USD, + // therefore OutstandingAmount is 200. + //*maxAmt sendMax gwOffer dstXRP outstandingUSD bobBuy gwXRP offersGw lastGw + { 300, 300, 200, 1300, 200, 100, 900, 1, true}, + // Buy USD: carol, bob are consumed; gw's is partially consumed (50/50) since it's last + // and sendMax limits the output. + // Gw gets 250USD from alice; carol and bob buy 200USD, alice has 50USD left, + // therefore OutstandingAmount is 200. + //*maxAmt sendMax gwOffer dstXRP outstandingUSD bobBuy gwXRP offersGw lastGw + { 300, 250, 200, 1250, 250, 100, 950, 1, true}, + }; + // clang-format on + for (auto const& t : tests) + test(t); + } + + // Cross-currency payment. BookStep redeems, the last step + // issues. + { + Account const ed{"ed"}; + + struct TestData + { + int maxAmt; + int sendMax; + int initDst; + int gwOffer; // quality == 1 + int dstExpectUSD; + int outstandingUSD; + int expAliceXRP; // whole XRP excluding the fees + int expBobSellUSD; + int expGwXRP; + std::uint8_t expOffersGw; + bool lastGwBuyUSD; + std::uint8_t + expOffersBob() const + { + return expBobSellUSD > 0 && expBobSellUSD < 100 ? 1 : 0; + } + }; + + auto test = [&](TestData const& d) { + Env env(*this); + env.fund(XRP(1'000), gw, alice, carol, bob, ed); + env.close(); + + MPT const USD = MPTTester( + {.env = env, .issuer = gw, .holders = {carol, bob, ed}, .maxAmt = d.maxAmt}); + + if (d.initDst != 0) + env(pay(gw, ed, USD(d.initDst))); + env(pay(gw, carol, USD(100))); + env(pay(gw, bob, USD(100))); + env.close(); + + env(offer(carol, XRP(100), USD(100))); + env.close(); + if (!d.lastGwBuyUSD) + { + env(offer(gw, XRP(d.gwOffer), USD(d.gwOffer))); + env.close(); + } + env(offer(bob, XRP(100), USD(100))); + env.close(); + if (d.lastGwBuyUSD) + { + env(offer(gw, XRP(d.gwOffer), USD(d.gwOffer))); + env.close(); + } + + BEAST_EXPECT(expectOffers(env, carol, 1)); + BEAST_EXPECT(expectOffers(env, bob, 1)); + BEAST_EXPECT(expectOffers(env, gw, 1)); + BEAST_EXPECT(env.balance(gw, USD) == USD(-200 - d.initDst)); + + env(pay(alice, ed, USD(300)), + sendmax(XRP(d.sendMax)), + path(~USD), + txflags(tfPartialPayment | tfNoRippleDirect)); + env.close(); + + auto const baseFee = env.current()->fees().base.drops(); + BEAST_EXPECT( + env.balance(alice) == XRPAmount{d.expAliceXRP * DROPS_PER_XRP - baseFee}); + BEAST_EXPECT(env.balance(carol, USD) == USD(0)); + BEAST_EXPECT(env.balance(bob, USD) == USD(100 - d.expBobSellUSD)); + BEAST_EXPECT(env.balance(ed, USD) == USD(d.dstExpectUSD)); + BEAST_EXPECT(env.balance(gw, USD) == USD(-d.outstandingUSD)); + BEAST_EXPECT( + env.balance(gw) == + XRPAmount{ + d.expGwXRP * DROPS_PER_XRP - baseFee * (4 + (d.initDst != 0 ? 1 : 0))}); + BEAST_EXPECT(expectOffers(env, carol, 0)); + BEAST_EXPECT(expectOffers(env, bob, d.expOffersBob())); + BEAST_EXPECT(expectOffers(env, gw, d.expOffersGw)); + }; + + // clang-format off + std::vector const tests = { + // Sell USD: carol, gw, bob are consumed. + // ed buys 300USD from carol, gw, bob therefore OutstandingAmount is 300. + //*maxAmt sendMax initDst gwOffer dstUSD outstandingUSD aliceXRP bobSell gwXRP offersGw lastGw + { 300, 300, 0, 100, 300, 300, 700, 100, 1100, 0, false}, + // Same as above. Gw offer location in the order book doesn't matter + //*maxAmt sendMax initDst gwOffer dstUSD outstandingUSD aliceXRP bobSell gwXRP offersGw lastGw + { 300, 300, 0, 100, 300, 300, 700, 100, 1100, 0, true}, + // Sell USD: carol, bob are consumed, gw is partially consumed. + // ed buys 200 from carol and bob and 50 from gw because gw can only issue 50 + // (300(max) - 200(carol+bob) - 50(ed)). ed buys 250 from carol, gw, bob and has 50 initially, + // therefore OutstandingAmount is 300. + // gw's offer is removed from the order book because it's partially consumed and the remaining + // offer is unfunded. + //*maxAmt sendMax initDst gwOffer dstUSD outstandingUSD aliceXRP bobSell gwXRP offersGw lastGw + { 300, 300, 50, 100, 300, 300, 750, 100, 1050, 0, false}, + // Same as above. Gw offer location in the order book doesn't matter. + //*maxAmt sendMax initDst gwOffer dstUSD outstandingUSD aliceXRP bobSell gwXRP offersGw lastGw + { 300, 300, 50, 100, 300, 300, 750, 100, 1050, 0, true}, + // Same as above. Gw offer size doesn't matter. + //*maxAmt sendMax initDst gwOffer dstUSD outstandingUSD aliceXRP bobSell gwXRP offersGw lastGw + { 300, 300, 50, 200, 300, 300, 750, 100, 1050, 0, true}, + // Sell USD: carol, gw are consumed, bob is partially consumed. + // ed buys 200 from carol and gw and 50 form bob because of sendMax limit. bob keeps 50, + // therefore OutstandingAmount is 300. + //*maxAmt sendMax initDst gwOffer dstUSD outstandingUSD aliceXRP bobSell gwXRP offersGw lastGw + { 300, 250, 0, 100, 250, 300, 750, 50, 1100, 0, false}, + // Sell USD: carol, bob are consumed, gw is partially consumed because of sendMax limit. + // ed buys 200 from carol and bob and 50 from gw. Therefore, OutstandingAmount is 250. + // gw's offer remains on the order book because it's partially consumed and has more funds. + //*maxAmt sendMax initDst gwOffer dstUSD outstandingUSD aliceXRP bobSell gwXRP offersGw lastGw + { 300, 250, 0, 100, 250, 250, 750, 100, 1050, 1, true}, + // Sell USD: carol, bob are consumed, gw is partially consumed because of sendMax limit, also + // there is only 50 available to issue. ed buys 200 from carol and bob and 50 from gw, plus + // he has initially 50, therefore OutstandingAmount is 300. + //*maxAmt sendMax initDst gwOffer dstUSD outstandingUSD aliceXRP bobSell gwXRP offersGw lastGw + { 300, 250, 50, 100, 300, 300, 750, 100, 1050, 0, true}, + // Sell USD: carol, bob are consumed, gw is not consumed because there is not available funds + // to issue. ed buys 200 from carol and bob and, plus he has initially 100, + // therefore OutstandingAmount is 300. gw offer is removed because it's unfunded. + //*maxAmt sendMax initDst gwOffer dstUSD outstandingUSD aliceXRP bobSell gwXRP offersGw lastGw + { 300, 250, 100, 100, 300, 300, 800, 100, 1000, 0, true}, + }; + // clang-format on + for (auto const& t : tests) + test(t); + } + + // Cross-currency payment with BookStep as the first step. + // BookStep limits the buy amount. + { + auto test = [&](int sendMax, std::uint16_t dstXRP, std::uint8_t expGwOffers) { + Env env(*this); + env.fund(XRP(1'000), gw, alice, carol); + + MPT const USD = MPTTester({.env = env, .issuer = gw, .maxAmt = 300}); + + env(offer(carol, USD(400), XRP(400))); + env(offer(gw, USD(100), XRP(100))); + BEAST_EXPECT(expectOffers(env, carol, 1)); + BEAST_EXPECT(expectOffers(env, gw, 1)); + + env(pay(gw, alice, XRP(500)), + sendmax(USD(sendMax)), + path(~XRP), + txflags(tfPartialPayment | tfNoRippleDirect)); + + BEAST_EXPECT(env.balance(alice) == XRP(dstXRP)); + BEAST_EXPECT(env.balance(gw, USD) == USD(-300)); + BEAST_EXPECT(env.balance(carol, USD) == USD(300)); + BEAST_EXPECT(expectOffers(env, carol, 0)); + BEAST_EXPECT(expectOffers(env, gw, expGwOffers)); + }; + // carol's offer is partially consumed - 300USD/300XRP + // because available amount to issue is 300USD. gw's + // offer is fully consumed because it doesn't change + // OutstandingAmount. Both offers are removed from the + // order book - carol's offer is unfunded and gw's offer + // is fully consumed. + test(500, 1'400, 0); + // carol's offer is partially consumed - 300USD/300XRP + // because available amount to issue is 300USD. gw's + // offer is partially consumed because of sendMax limit. + // carol's offer is removed from the order book because + // it's unfunded. gw's offer remains on the order book + // because it's partially consumed and gw has more + // funds. + test(350, 1'350, 1); + } + } + + void + testWithFeats(FeatureBitset features) + { + using namespace jtx; + + testMaxAndSelfPaymentEdgeCases(features); + testFalseDry(features); + testDirectStep(features); + testBookStep(features); + testTransferRate(features); + testSelfPayment1(features); + testSelfPayment2(features); + testSelfFundedXRPEndpoint(false, features); + testSelfFundedXRPEndpoint(true, features); + testUnfundedOffer(features); + testReExecuteDirectStep(features); + testSelfPayLowQualityOffer(features); + } + + void + run() override + { + using namespace jtx; + auto const sa = testable_amendments(); + testLimitQuality(); + testXRPPathLoop(); + testWithFeats(sa); + testEmptyStrand(sa); + } +}; + +BEAST_DEFINE_TESTSUITE_PRIO(FlowMPT, app, xrpl, 2); + +} // namespace xrpl::test diff --git a/src/test/app/Flow_test.cpp b/src/test/app/Flow_test.cpp index 0bc5bd1727..8f096a970f 100644 --- a/src/test/app/Flow_test.cpp +++ b/src/test/app/Flow_test.cpp @@ -1,18 +1,55 @@ -#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include -#include - +#include #include +#include +#include +#include +#include #include #include +#include #include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include -namespace xrpl { -namespace test { +#include +#include +#include +#include +#include +#include + +namespace xrpl::test { bool getNoRippleFlag( @@ -450,10 +487,10 @@ struct Flow_test : public beast::unit_test::suite }; { // BTC -> USD - STPath const p1({IPE(USD.issue())}); + STPath const p1({IPE(USD)}); paths.push_back(p1); // BTC -> EUR -> USD - STPath const p2({IPE(EUR.issue()), IPE(USD.issue())}); + STPath const p2({IPE(EUR), IPE(USD)}); paths.push_back(p2); } @@ -876,10 +913,8 @@ struct Flow_test : public beast::unit_test::suite env.close(); env(trust(bob, USD(20))); - STAmount const tinyAmt1{ - USD.issue(), 9000000000000000ll, -17, false, STAmount::unchecked{}}; - STAmount const tinyAmt3{ - USD.issue(), 9000000000000003ll, -17, false, STAmount::unchecked{}}; + STAmount const tinyAmt1{USD, 9000000000000000ll, -17, false, STAmount::unchecked{}}; + STAmount const tinyAmt3{USD, 9000000000000003ll, -17, false, STAmount::unchecked{}}; env(offer(gw, drops(9000000000), tinyAmt3)); env(pay(alice, bob, tinyAmt1), @@ -902,10 +937,8 @@ struct Flow_test : public beast::unit_test::suite env.close(); env(trust(alice, USD(20))); - STAmount const tinyAmt1{ - USD.issue(), 9000000000000000ll, -17, false, STAmount::unchecked{}}; - STAmount const tinyAmt3{ - USD.issue(), 9000000000000003ll, -17, false, STAmount::unchecked{}}; + STAmount const tinyAmt1{USD, 9000000000000000ll, -17, false, STAmount::unchecked{}}; + STAmount const tinyAmt3{USD, 9000000000000003ll, -17, false, STAmount::unchecked{}}; env(pay(gw, alice, tinyAmt1)); @@ -944,30 +977,30 @@ struct Flow_test : public beast::unit_test::suite pay(gw, alice, // 12.55.... - STAmount{USD.issue(), std::uint64_t(1255555555555555ull), -14, false})); + STAmount{USD, std::uint64_t(1255555555555555ull), -14, false})); env(offer( gw, // 5.0... - STAmount{USD.issue(), std::uint64_t(5000000000000000ull), -15, false}, + STAmount{USD, std::uint64_t(5000000000000000ull), -15, false}, XRP(1000))); env(offer( gw, // .555... - STAmount{USD.issue(), std::uint64_t(5555555555555555ull), -16, false}, + STAmount{USD, std::uint64_t(5555555555555555ull), -16, false}, XRP(10))); env(offer( gw, // 4.44.... - STAmount{USD.issue(), std::uint64_t(4444444444444444ull), -15, false}, + STAmount{USD, std::uint64_t(4444444444444444ull), -15, false}, XRP(.1))); env(offer( alice, // 17 - STAmount{USD.issue(), std::uint64_t(1700000000000000ull), -14, false}, + STAmount{USD, std::uint64_t(1700000000000000ull), -14, false}, XRP(.001))); env(pay(alice, bob, XRP(10000)), @@ -1290,5 +1323,4 @@ struct Flow_manual_test : public Flow_test BEAST_DEFINE_TESTSUITE_PRIO(Flow, app, xrpl, 2); BEAST_DEFINE_TESTSUITE_MANUAL_PRIO(Flow_manual, app, xrpl, 4); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/app/Freeze_test.cpp b/src/test/app/Freeze_test.cpp index ed5ee47578..29127ec8e1 100644 --- a/src/test/app/Freeze_test.cpp +++ b/src/test/app/Freeze_test.cpp @@ -1,12 +1,38 @@ -#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include +#include +#include #include +#include #include +#include #include +#include +#include +#include +#include + namespace xrpl { class Freeze_test : public beast::unit_test::suite diff --git a/src/test/app/GRPCServerTLS_test.cpp b/src/test/app/GRPCServerTLS_test.cpp new file mode 100644 index 0000000000..a421f981f7 --- /dev/null +++ b/src/test/app/GRPCServerTLS_test.cpp @@ -0,0 +1,849 @@ +#include +#include + +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +constexpr std::string_view kCA_CERT_CONTENT = + "-----BEGIN CERTIFICATE-----\n" + "MIIFhjCCA26gAwIBAgIJAL9P70zX30oiMA0GCSqGSIb3DQEBCwUAMFcxCzAJBgNV\n" + "BAYTAlVTMQ0wCwYDVQQIDARUZXN0MQ0wCwYDVQQHDARUZXN0MRgwFgYDVQQKDA9S\n" + "aXBwbGVkIFRlc3QgQ0ExEDAOBgNVBAMMB1Rlc3QgQ0EwIBcNMjYwNDA5MTMyNTA2\n" + "WhgPMjEyNjAzMTYxMzI1MDZaMFcxCzAJBgNVBAYTAlVTMQ0wCwYDVQQIDARUZXN0\n" + "MQ0wCwYDVQQHDARUZXN0MRgwFgYDVQQKDA9SaXBwbGVkIFRlc3QgQ0ExEDAOBgNV\n" + "BAMMB1Rlc3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCzOJ5s\n" + "dy1O0GN/kmbeWf5DmFbQBSS9FRKxh6/o9V9BqRBQfECrVK9T5Y4FYrGGtmUW3YEV\n" + "uMDZ5q6rvBT2zrrzPXnWA5Pb4I4mKqC/yk5L7Mm8A9xQsNoRzgTyl/NuHiXKn+yQ\n" + "FuidA6U36qwIAcDR7gLqrJ1ud/ng9f9Q4k6+IItY/XGhcz4nKlQq9jpzmfdSlBkU\n" + "hXsIsdNtC+UGlQMCMX2jwysIFfjjCOMlH7KFQ3dKodhsW+Ym6AsPwyRGCgNXO/zd\n" + "Fqt1MIMs1F7r40DtfVO3R7w4/2SblcceZlsDrYQUbJnH+sEPWO0SGGo6Y7Ohs09+\n" + "aJSOAGGQVgTSLuAcFtR4BXD0GLn39+10PDvHGOsMJKL1de1f96s8kPlifQ5AGWuc\n" + "xy6XsupGSa0F8LozwQmKD7hVkyladUTWFPknz5tsPEVApTO0U8Vuknuhyovo6+mx\n" + "qBoSD32NwHveFz3jWqfj0CGX9BwL9AOpMabDhROVQfyM5GrLeLOOdgOnsBXJYYdW\n" + "MeJwz6BH30q9yvEd9Ti26jSk3fM8WPuEkZzNNp8STEMyDrfhaKOe5fGPWLnqMQAf\n" + "yMCDLwB1WqIN1Q6gOELb3rxyYDVH/5x6/JXosdUe1qx/tzvRoSWxxssRRd2Em+e+\n" + "MUFLXz+9D6kZ9XCuP/mLyRGW6LEiwwQkGKMnzwIDAQABo1MwUTAPBgNVHRMBAf8E\n" + "BTADAQH/MB0GA1UdDgQWBBQPK5hXxLdTj3QqfVzGpfTga6IF3zAfBgNVHSMEGDAW\n" + "gBQPK5hXxLdTj3QqfVzGpfTga6IF3zANBgkqhkiG9w0BAQsFAAOCAgEAa06whkqv\n" + "KmdT1HVhkV7AkWEAeHMWPLLaaFbcwble7a1Vizh6GjCyNpLtoN+mtwqwiOdsIlRE\n" + "42pWILc6CuuX0ae0nHSrcQS5mq8ZKSMr1xTo9RSfBq7CDfdyquxzG83HhpdApViZ\n" + "87Bjy3WoRuomM+YiONfUVdCbC5ZmXW/z+xrXJ+JqIXrtv66sZxpQIR0+ShnWT0DE\n" + "w9jB5fxjydPFwEudYi4z9XjEZaZJ1f8VNWDuUvi3yTJtTlNaWnKveudtDZBw/fA+\n" + "MBFd9ccYVhGQPxOs6S0Ev6q5IjcnzGeEBNZOjgjQk9aFrAs2Iiy018AbYQj5XD64\n" + "hHyiNgyPjl/VgXJE1Xl3lXGpiiJlXctgnCd3UGMfKznhBIpDT13i2CmHFyR3uk7o\n" + "UOZUXCnbnmgthejmFxB35Wf5TmGaYubtRMfCPHGNbQD+7Kg2+8eel3J3JSuG6RQ8\n" + "hwNyHHQnaPVUSANItJ4cMe5DutM0vUCMkJbajL+fjC5SdsTcGfR2VmAFqulNDXjH\n" + "sGWBiWVNsgddax63m6kL9UOeE+8pu8yStKZ4mVn2EjE9eJk4vyZt4BaI6sDUMlke\n" + "S9OjcI5iYlxXNgbRQBtwK70+c3D3JoRPREkTRPPwC4NiAFed7UwXSMh5nWbpt/dq\n" + "fAbAYqu0rfMFHUYjzIVnu8WRCC56qYHO5tU=\n" + "-----END CERTIFICATE-----\n"; + +constexpr std::string_view kSERVER_CERT_CONTENT = + "-----BEGIN CERTIFICATE-----\n" + "MIIFizCCA3OgAwIBAgIJAIErcpMflkrRMA0GCSqGSIb3DQEBCwUAMFcxCzAJBgNV\n" + "BAYTAlVTMQ0wCwYDVQQIDARUZXN0MQ0wCwYDVQQHDARUZXN0MRgwFgYDVQQKDA9S\n" + "aXBwbGVkIFRlc3QgQ0ExEDAOBgNVBAMMB1Rlc3QgQ0EwIBcNMjYwNDA5MTMyNTA3\n" + "WhgPMjEyNjAzMTYxMzI1MDdaMFExCzAJBgNVBAYTAlVTMQ0wCwYDVQQIDARUZXN0\n" + "MQ0wCwYDVQQHDARUZXN0MRAwDgYDVQQKDAdSaXBwbGVkMRIwEAYDVQQDDAlsb2Nh\n" + "bGhvc3QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCv+Lj9LJfPuSOE\n" + "yZqTn2gmG5tJt02ywnuIQet7N5tduxnNs50yXQ00Jeb40dth0HwI5I+AsEVNPIG3\n" + "7tJ9RtCwwTyltaZ4bXuL9ujEVr6TAKY6rlU9bL+Zmr62Lm8B0SLouxfPtyzKhBv6\n" + "7bGUrdIX7o9DbTQ3/2mQTc7KjPCJTEutmpVyD3dABN1qDM9Qzac0NtK1nixvYGd4\n" + "SpbK95BRXby3X9um0dVXoUMbc2gDV9uUZw1xLSjuKDJtQ/rleqe0mmS6JSoagwbb\n" + "DmPX/GbIf21IWUsg3m7AyIwYf9FtIJArB/j3iVBsY9lTB0mXSLxiyN6KM822+QjH\n" + "/VeHKDUWdQB6N3smmi1OLQasukRpSUTmTsoucn30dUqS6qdTtkHzvqGEN+CXBWgG\n" + "i1AS2CacOjYSVHRppC10r/3kEChYY/9rqYBz7GedFRJ6VzQzrwFYZleLJvX6GWfe\n" + "4gNvgwLfo/q2Af1HkCY2aHO+19eAghVsy1MRUDnm/GbZAhHSrX10iEfRjs+GhfxY\n" + "v0xMrvGBCm/2CiJ8RAvdRPpNkM/3u9fjOmqdKvE9NTqDOX1HUBoqa/UguIzi6o/k\n" + "BlBtohfaeL6ZeYXl6MefIIs2pipR7S1VQ1RY9OSdnN5nIJidyn1l85P9vLn49QVw\n" + "2OAT+TcEZnxyaiHCKU6nWtusuMt3wQIDAQABo14wXDAaBgNVHREEEzARgglsb2Nh\n" + "bGhvc3SHBH8AAAEwHQYDVR0OBBYEFO9bPc31jmMlMVNhOd+eXgZPD/+pMB8GA1Ud\n" + "IwQYMBaAFA8rmFfEt1OPdCp9XMal9OBrogXfMA0GCSqGSIb3DQEBCwUAA4ICAQCm\n" + "+hnvRdr9N9a260yOD53b/Gs0c4viAOU3WmxAa89upLHnpPEi7/GlKlw+ed6SwYoX\n" + "CSopDw8AG2Ub/oHM3uIrONjfdHBwUl/SUS8wNhiELuQjKm0qGjkh/n/FHY903flc\n" + "0VP2ciLnqhSS2NY+KH0O8uny3yR4FVH7Byqtk648Z7LfIhe02TjTIjhXDrGwn5dS\n" + "tuTKEAGaxxPJuINCR1BZlwfk+10ipJK59rSpCW//P1YJVr16sdnyh3YJXoAJ5qxP\n" + "P8QWHiRIl2ZGs7KB5SU9fX1dVEU5gwrl/KF3oP+iS01wfNZGvnR+eHMPJsl/IwoC\n" + "SOZAMjgkTZh06cprfEXne8bcidiHvETbF9szMAofA91PbXi0lcwMqpkHG2AElOXI\n" + "by4ejjs9RZJF2Ef38qZPb8RuT+gLORFH5SuPQUwXKlszjpzpxkQ6IKYjFJY+j8CS\n" + "XlXhdkzK5h18cf7J2i5SQdIzE1btQqdcaMb9DzX+drCqqD8JZd1Vczua7Q5tbZ/g\n" + "Bq19Zzo1KQL0xXPdomWv+sP6eUMiW+3J5oFN2hJpilKuFSCAhDmgcmLooFy5t6rR\n" + "kW0n1P3iTWvgQHNzB/3msanvC4/hHyrHHOVGQtAjhxuoRioBJ+hg4RKDptSUcHJX\n" + "YSyd81wvumIpP+I7BDkQLgTb+NzMmoBIjRg3aVvXSg==\n" + "-----END CERTIFICATE-----\n"; + +constexpr std::string_view kSERVER_KEY_CONTENT = + "-----BEGIN PRIVATE KEY-----\n" + "MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQCv+Lj9LJfPuSOE\n" + "yZqTn2gmG5tJt02ywnuIQet7N5tduxnNs50yXQ00Jeb40dth0HwI5I+AsEVNPIG3\n" + "7tJ9RtCwwTyltaZ4bXuL9ujEVr6TAKY6rlU9bL+Zmr62Lm8B0SLouxfPtyzKhBv6\n" + "7bGUrdIX7o9DbTQ3/2mQTc7KjPCJTEutmpVyD3dABN1qDM9Qzac0NtK1nixvYGd4\n" + "SpbK95BRXby3X9um0dVXoUMbc2gDV9uUZw1xLSjuKDJtQ/rleqe0mmS6JSoagwbb\n" + "DmPX/GbIf21IWUsg3m7AyIwYf9FtIJArB/j3iVBsY9lTB0mXSLxiyN6KM822+QjH\n" + "/VeHKDUWdQB6N3smmi1OLQasukRpSUTmTsoucn30dUqS6qdTtkHzvqGEN+CXBWgG\n" + "i1AS2CacOjYSVHRppC10r/3kEChYY/9rqYBz7GedFRJ6VzQzrwFYZleLJvX6GWfe\n" + "4gNvgwLfo/q2Af1HkCY2aHO+19eAghVsy1MRUDnm/GbZAhHSrX10iEfRjs+GhfxY\n" + "v0xMrvGBCm/2CiJ8RAvdRPpNkM/3u9fjOmqdKvE9NTqDOX1HUBoqa/UguIzi6o/k\n" + "BlBtohfaeL6ZeYXl6MefIIs2pipR7S1VQ1RY9OSdnN5nIJidyn1l85P9vLn49QVw\n" + "2OAT+TcEZnxyaiHCKU6nWtusuMt3wQIDAQABAoICAQCZilzm0uT3Y2RBdaMBUaKP\n" + "NaFONbl+00D0SAhOr9tJcnp2SFVN33Eo4jVhP8K62y2OmNc5gxRE6xmIQsK4enSW\n" + "9VSUhiXliCm3m03IGqQYIgXox7oqaVvYi/QBhAxpunBKPwzsubhET/cWABXlU7Ew\n" + "HoA0ZfGdNqeGOM3JYCZ0tfSGWo4xQptbaaND6D9wErDk1z0NKSE+YRCHHhXqrQ3o\n" + "YPDL08EVEpui5VtndU/5Msyt9Sj+alf/TWWKfzlIx7fS1rAy10Cgd1khA7JMf7ez\n" + "E7Rn3zm1ST+7yICs08IJBNOmKEOswMxCdvDmCELG1LlDPF8omUDSeQKXdU7M6GFA\n" + "b5PQ11Ik6xZVw1NUESf4d9g0VhEJRXSdGwA3KepAkwRejkB5jI56C8z9dB0LWdWH\n" + "2r3dX2ZpbJv0XVNxAELRgKwyfqWxYrF3caGLrxxWAiyPFvD9FgZJB1ftBU3D+HZZ\n" + "bltdfHJBgZe3pwoCr3X2JPhcA6ecITsset14dvsXHSi9IAXTHbeXxjrHCRcXs6xV\n" + "v4ZSL5r43dv6qk7XiFONCmV8diIwJOxcaSvoBgeeCykX4RKGSk/6Atlo4C9hXb47\n" + "BAuXu3Y+SkS98EljsdeNKCr013Tvt0p4H2QfeoDTKuzC+j3hu9fCkEP3oak2nWFl\n" + "bOkrYMJCc6yxu20G58vzrQKCAQEA1y93gNuNa7Z+VrZCSEcJX2BZl1mTyhLEa9mN\n" + "QOmKlW10VrfCsJxLu+dTGWccy0c6Q8wk6uGjgYJHsdyFPIdSroPR2ysJKSP/5Vzu\n" + "xNymgbeLPnWoivC9TctovWY/15fdboYNUO54jOpFheCC1wq9ZP6CyJmw5O96Y7tJ\n" + "1l5Dq7Fe4iQbIQHPt54wVVHsm7G1ZNywgSbt0HXHeP43YN3mRawJ51++MaEksCXv\n" + "rW+vOxPdiW8djE0tqcK0tqFMhI6p+WcUu8128aRHd0iHlKsVsFU4OLLZr10zwy9i\n" + "COHoF4Fh53pGp05jv+5eMtuEiem87ZUmpJn7whHZt8sKSE71AwKCAQEA0Vkwr4KA\n" + "kRRCUPvor5mdNil05N1mLrYgr/4UAHg3tbeTGxOjSX65KnJWi5dsDmZUdGTL4StD\n" + "8H6uLzzjX88gQkpKvtRYPYKBFtTRsI+ItOvIIo8czK/Kv8dwC2WXZbZBjsCAhrCm\n" + "0fKL2jx7rgdjaqvQeqSRtcHiyiYJG/jC7Iqwm4CyPr+nkVUWKZUWXopw0QXZXHWp\n" + "Glz9TXreEI7Xb/R+RXYU21exBqg0SfHq9pA//aNTQWxWGlNVwqO/KUao9HZupKHb\n" + "mA73oxFJTKhVNNNdC5cC91pxDeDTUzpIEjCGeLI3Aa35CD0WFqEbELJphr5HGkGo\n" + "VkYod6P79+Ta6wKCAQAadFpzvAop2Ni1XljNu/X6BMVe5wNVT3NYcvl7pnqEHl20\n" + "H4lO3xgsdKbxs4yFrS8LkLhlK/JHBLY9toemxlgy3j/ZevP4W9Wk5ATyrNHHlsIG\n" + "nr5mvmv3eW9aAY0Nuzzczpwqe/bUFCUR7WUIfOiF1whLEyH9MzfPtQHB2frly7uH\n" + "f7raFvfrcgYtJxI4neNYEA2fAyMvgptQU6iJPx6FKD5bdJjUTyRMh41svBNF5w5Q\n" + "TBnM2twnR6mh3jii/0sEP1j8MalS0ch7cK5CZ7oV4JQ13D8I4SNw9o1N3EAFS8G2\n" + "jIDNJsT6npp0FCq6LcMtTi3fBJM/66PhhZOxCgvzAoIBAH1LnE/vE3PBZE+D9afj\n" + "kKwx87xmphme98FdmCsPyIgB7xFtl3UNW1WESTgS0KFtrW5cRYnmkysFJssu7gcR\n" + "uIT0YfgErythSFGZ3kaGIZPm6kmEzf/T1s0hWHX5v7soceQ2YrY6VB2jxQBA4uUt\n" + "ltrpKkW86ViXSl0ilqEfKcrY1wq64/OaUXgyLKmGiXTb9tmjXoxv/12/+fq9ZtsS\n" + "Iu7mrgx0t9bvjQwm7+Sx3abkfugXMGUfqgjnh5SO3IKfv89QcrgmB3/itWPrnKs8\n" + "tIKBXlbpcuUIRFHCFbjiUPBSCqmCQFnI/htoNCgnFEPSBEaY64VTdqTsKJwykUO0\n" + "vTECggEAEAB8vyHHk7fpU+IOwD8TP7MCMHwoJzoHQp35So7TlhmO7oDranNhg3nl\n" + "jhTOeISLG2dmPkT49vhsO30tal4CgSXVZo1bPbOK83UvgeLH5Rhji44Dmah+ohKy\n" + "wCuVLuF6YSSp5rD7VIrahhegBFXEYdW5+ZBFbDpE5EXp0WeHc7IRPwWvm+ixr1m8\n" + "VqLeeh1xkMG5WdTTwGjgKWIFXZQ3bOIdVK7uya8wFDAtftkswXiBxAlb9L6Id+Dp\n" + "bKfMAHNouU1TQn5duFgPnCbSU1Js74HkkC0NEEIjQX8k2UCPrhV0VfLfViPuPFax\n" + "S/RYUSUkZ4VvqFUfo7wT8x18urb87w==\n" + "-----END PRIVATE KEY-----\n"; + +constexpr std::string_view kCLIENT_CERT_CONTENT = + "-----BEGIN CERTIFICATE-----\n" + "MIIFeDCCA2CgAwIBAgIJAIErcpMflkrSMA0GCSqGSIb3DQEBCwUAMFcxCzAJBgNV\n" + "BAYTAlVTMQ0wCwYDVQQIDARUZXN0MQ0wCwYDVQQHDARUZXN0MRgwFgYDVQQKDA9S\n" + "aXBwbGVkIFRlc3QgQ0ExEDAOBgNVBAMMB1Rlc3QgQ0EwIBcNMjYwNDA5MTMyNTA3\n" + "WhgPMjEyNjAzMTYxMzI1MDdaMFoxCzAJBgNVBAYTAlVTMQ0wCwYDVQQIDARUZXN0\n" + "MQ0wCwYDVQQHDARUZXN0MRcwFQYDVQQKDA5SaXBwbGVkIENsaWVudDEUMBIGA1UE\n" + "AwwLdGVzdC1jbGllbnQwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDP\n" + "QHttw3TLjOqYS3VkLF3KMRaP2ZtO6A1mXfTbqbKvD41Fazf/cM/v9lPMAlRd2SEY\n" + "3MeE8KVddKJwsbF0kNgDkKB5D5V42WrTw5biFNMOeHAZMR/oWChIvZbbGbDxIdIO\n" + "2+W3X0kjpa2eKcK9qBk8xoyIeilyXtleGWuWvHxiZP9iGHxaTWB+wIKUIK6vrEOb\n" + "iO3P/9XPpHzsBt0HdTDh4V7fwnr2UndVeQyBwUwLn6pd73sTKBfA26YppRwDjPIj\n" + "6NYtF3I28lRCUo+47TAVZM97gjN9oEwyHIVtOc7fnZPwtN26M5v5083SGXU1k/PN\n" + "3xGAlDUiCF3RSMbBRylGgUVtsAD57i8tI2SqCr+ZG233VFiOdwTRKKVNTyMC9TCJ\n" + "dCFFEDFDHTTSimKRQKy0Cm2qoL6JBkziAIiu/0Zzv9YjAAnRoJ2cweMXQ/9z1oWe\n" + "EUZBLRsggYQ8FbM13FOkOs8IlzacSuhwrYKOq8LsMX4cH2mnn783FtXXqrL/xfL7\n" + "11KhzGpZNrz187ilJ+ZsmP9D6vCBP/tR7V52dgtB6I291o8zxdH8GheIGenEFaZa\n" + "oAwyN2FuJgXZqx9319I9gYerZ/BbUzA2MuOxFd0ywtdcTPqKiyAQ9rxQVCVQyYWj\n" + "kfBEYRzWxjfj3XhNprxdm3cauz01NAoTDiz52dZhGQIDAQABo0IwQDAdBgNVHQ4E\n" + "FgQUXVKwiGRrXC1sjK2D86jsjMVV0XgwHwYDVR0jBBgwFoAUDyuYV8S3U490Kn1c\n" + "xqX04GuiBd8wDQYJKoZIhvcNAQELBQADggIBACpHTm9GZMZ7OPhqVo4VltVOW9a9\n" + "LLDsVYmvpAF9+yjZGims6+p3f7eY+o+TRdUE4HEBCmH0UiFVODXCZSoqXo6y9xq7\n" + "TS1dmXll1Sajbfi7YXsM8CAUb+cSsHtmT57JtbGicDiVXAqIOlT65yXkuujdcEa0\n" + "OAw45vJDkWk/6nneFJKdTs7aT3fvIGTlMAxgMJngVsA8BRsX8TWoo05Lum8ClNgi\n" + "s6mtl+nUvjOaM0omFL/K9kqLy7OJAbmE5xuhkC9q6Kn0pHBL4u0YSWaWTpyrvAX7\n" + "BuOE0G1JezcCAcqJvXbKFvhnOSHTvzdlMgXhteGW8Uwgf8cGKtVLSwh6YTjI1XaL\n" + "DkNZfJabAyH7BsGGbAd9Jts4h+4auPqHgcpEz16280oCgZdcfLSP0UKrfwYuXOar\n" + "8KWlVRFl2NBpEJwRf2KjZFQUqYoX1MmfX0gyy+kk0ZP12L7oGNqAxkaWySfb4PSv\n" + "Hsnb8iD6sIJQjZvZ/2wLV8xwFTbFjvGbmSx+XLnMUVV8cVAMUpZz5X2R9pBvpVi4\n" + "KfUccTvIVA0p1wFSdWYQ0+QNxHxZGX1rin6KVUdV1z8K6J3FgGlRqzfz4bruGpXs\n" + "6vX5vqF9KTFpwLTOxDU+kAoIfHowHeu/LQX1l+rk1ww2UZQ1zvgKb6fxWMtviq3F\n" + "cTe8jkzRqYdUfAoV\n" + "-----END CERTIFICATE-----\n"; + +constexpr std::string_view kCLIENT_KEY_CONTENT = + "-----BEGIN PRIVATE KEY-----\n" + "MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQDPQHttw3TLjOqY\n" + "S3VkLF3KMRaP2ZtO6A1mXfTbqbKvD41Fazf/cM/v9lPMAlRd2SEY3MeE8KVddKJw\n" + "sbF0kNgDkKB5D5V42WrTw5biFNMOeHAZMR/oWChIvZbbGbDxIdIO2+W3X0kjpa2e\n" + "KcK9qBk8xoyIeilyXtleGWuWvHxiZP9iGHxaTWB+wIKUIK6vrEObiO3P/9XPpHzs\n" + "Bt0HdTDh4V7fwnr2UndVeQyBwUwLn6pd73sTKBfA26YppRwDjPIj6NYtF3I28lRC\n" + "Uo+47TAVZM97gjN9oEwyHIVtOc7fnZPwtN26M5v5083SGXU1k/PN3xGAlDUiCF3R\n" + "SMbBRylGgUVtsAD57i8tI2SqCr+ZG233VFiOdwTRKKVNTyMC9TCJdCFFEDFDHTTS\n" + "imKRQKy0Cm2qoL6JBkziAIiu/0Zzv9YjAAnRoJ2cweMXQ/9z1oWeEUZBLRsggYQ8\n" + "FbM13FOkOs8IlzacSuhwrYKOq8LsMX4cH2mnn783FtXXqrL/xfL711KhzGpZNrz1\n" + "87ilJ+ZsmP9D6vCBP/tR7V52dgtB6I291o8zxdH8GheIGenEFaZaoAwyN2FuJgXZ\n" + "qx9319I9gYerZ/BbUzA2MuOxFd0ywtdcTPqKiyAQ9rxQVCVQyYWjkfBEYRzWxjfj\n" + "3XhNprxdm3cauz01NAoTDiz52dZhGQIDAQABAoICADTppZmUeVEunQZc3Y/BtABX\n" + "IAeB6yDuJd2ox0b9wFzpf4vln9pblvsQzLwdLCT5tnV+iIHsXovJp19WPpQgFsZy\n" + "OkYuMF82Qwvlt7Po1Smwng4QeLD9MOvBW658lKw7kkGw6qkybp3nQrhKuSlqrWbS\n" + "2jZN2h8VEDHyE4HchXUpi/ojfjwf3S7/P1dKMM8xD+G5x91+17u3px0rc2rgBKbm\n" + "vy4pnPMegtETopnOG/grv3dUGPv/FHFsorOnL8vIRFnerC+++K4GmHSGV6NDCy+r\n" + "GT3TNAoyzsFMftQwGh0FQiwGQUW0v3G9HaMyVLZlG63H8dP+AsK5mBpCllvqKyMb\n" + "TQcS8mTBYAvBgiKqZBy6cwbnLaN5hYftDTg4zS62LVZzNlaMeTFGGYINDrth2S6X\n" + "+qH2GcXAUqd8aYIz/BLimCGhZMFQ0hAFCcq72Lh8UJsvvf9ng8Di/6oiZFJeN4nf\n" + "/LHUjlOyBqj8prTh0UCBjM0hdzzs96K+e3eBGjFHdVrdK5QytKZh1KTBSu0t64b2\n" + "0MSW5+2vFbdaQT5jed2lyh9YMmtGJV07T+5LKjWQGcJcc53DLA6uQ8lQuckQReE4\n" + "VzTWoG0eKEvk8ahltbl+0Gk7+fQlsMD5VizbET7EDOoiFPT3SpA/5dybXglNSuH4\n" + "9T65s7Xj2/zD8khLb3CxAoIBAQDwV3OQ66kqIC554Emz7F9ZNInMx4Vjuatd4wxe\n" + "WMT1Vlyg0ZeNSgdfggPmfntDW56NZ/h7q9F9feGfF3OogfZXVv2NzsynAOS4DR1c\n" + "0JR8/y7NG8vxHmDkNVJ3YkHfNYqK3x+sMCoXF0jDdaILXaP0nzAdcnLrRLyU9F3r\n" + "RVJpyaMWt9mtnRzlf1PTlc9WQ99MYuMfqxFj/zBFddnNFiI0FaG3/3Xdg6EH9x42\n" + "/2GXT/TlSUQo4e6Dh9mGhupUYzJt+AqjCnFA2n+D68QIdVq8ykOqGvnpwmfF94qt\n" + "8xfrKhI4zskj4N0X/xwByfEBOkU8nI8zP8PdVqKCbCRG1Z93AoIBAQDcwSRpPD5J\n" + "dmfXY2MGHvGQiJme/3YGPhcA15fQVRzWuZtn1PHULlI2V62NintzTUhjmv6SkGyX\n" + "6ze4RSCxrRFJumJwev5HtohQ7DH/nDtg+Y9Ewn32ehSEotycz1HUskOtgtLOQjwY\n" + "m66gTx6OzG4T6G2YRHcK8hFk9eLR0t2fIqPtu6APfRuo5OowiuYVzRKOplzh2J05\n" + "Q1TCJ4QL6geJQ/MzxVx33yopXWfRxZekG7ri4OJTIv8zj1Ocrytgz4hxAc8xJEf5\n" + "Z50k4JaWGBy+O/mKZ9sOGsolNv/FMUauE2EjSeNWNgvCFFvh4hDUciIakPzEeslp\n" + "hZdZCG9IV8fvAoIBAQDoDbfSbAc4Wjwlhq4C362sJrMKGnarNADGtMsjaRg6PTlQ\n" + "OS3XyGtYBuOXL/X5skNjCsj7N4kcXmdywST1xQ3BhIdp3QryEEXFgzwfenB0Q7q/\n" + "ZSBDXW51yRonlKI/TqXGseoVyadKBjxGJJTh3nbIYM8HD5Lvn71pIIxx9cu9wmcK\n" + "L1cobvMQjyCzwQigpQW77hqXYAd5glHsLv6tKrq5iU1Mp4X46/eWBj6RIYDrpNKy\n" + "c0wxIPu22XrojelAs0pkrUIv64wv7weBqyjqdcy3TZ+JZWR5FDA4D2tByt4EO+m+\n" + "GcJRNvKiEbnL7FwbMFTbUdpdxCpr0hM0VA+uqOG/AoIBAB24JuXABYawWSSHLdKq\n" + "Ic1ahowASmxmuYQUgky62KoTzNc6tN/i6JCGV0gh56LLOb6nJDSpGuWM9jBpphAl\n" + "g5lQbWZFOKyA53M1iTmnV9sjXeVc5cZkAxUkM90skBC5eyEF5sl740lQ1D6iyDNj\n" + "VEJ73R1NwlUH582WyNWEtO9yo20jAFZ1el7PirPET1uKA0CPJxwEpI4MAYIt/bn4\n" + "5NDXBAvpOxysP6nX+F0mY9blINDgg7e7k23mktQaRRXAetbz7mfoQYRTLbXEQqGs\n" + "V1pJCrxWZQhOFP7Tm7V5f9F5rG8qyF9X4VdclE4huDBRuUOoV09AVJNPN+P1nb24\n" + "i6MCggEBAIHUb8G0QKM4LPfdUmv575YmbnYY+Y3O982+jjRg4uAkYHnEkNfL6FKE\n" + "6ot7vcwDTN2Ccw6UKZU8GvyAQOGotmj6Nkgny2wFnEfoTzJaENjhPlnCHD9LDCps\n" + "w/tuoCHOUyyEb/Ygc+4xTsc0W3y2dbaYcg1qvLeIFuVZBNvY1XNlVf40/sVoiyet\n" + "Abh2yPwqOgOu8FpK4gcM8iSwL/xhEJJgT2wE+1MyHOd8KKklFHR7dF2WX1dF0Sif\n" + "cerPwqKXCvWh7og0RIJXe24fymMxtIsURBer9a3bPzUPVQoOXki4/u/kdEGH66GH\n" + "+6f4hsbp29hg+BUZ+UPdk7QyCKpZD1A=\n" + "-----END PRIVATE KEY-----\n"; + +/** + * RAII helper for managing temporary TLS certificates in tests. + * + * Creates a temporary directory and writes test certificates to it. + * Automatically cleans up the directory when destroyed. + */ +class TemporaryTLSCertificates +{ +public: + static constexpr std::string_view kCA_CERT_FILENAME = "ca.pem"; + static constexpr std::string_view kSERVER_CERT_FILENAME = "server_cert.pem"; + static constexpr std::string_view kSERVER_KEY_FILENAME = "server_key.pem"; + static constexpr std::string_view kCLIENT_CERT_FILENAME = "client_cert.pem"; + static constexpr std::string_view kCLIENT_KEY_FILENAME = "client_key.pem"; + static constexpr std::string_view kCERTS_DIR_PREFIX = "grpc_tls_test_"; + + TemporaryTLSCertificates() + { + auto tmpDir = std::filesystem::temp_directory_path(); + auto uniqueDirName = + boost::filesystem::unique_path(std::string(kCERTS_DIR_PREFIX) + "%%%%%%%%"); + tempDir_ = tmpDir / uniqueDirName.string(); + std::filesystem::create_directories(tempDir_); + + writeFile(tempDir_ / kCA_CERT_FILENAME, kCA_CERT_CONTENT); + writeFile(tempDir_ / kSERVER_CERT_FILENAME, kSERVER_CERT_CONTENT); + writeFile(tempDir_ / kSERVER_KEY_FILENAME, kSERVER_KEY_CONTENT); + writeFile(tempDir_ / kCLIENT_CERT_FILENAME, kCLIENT_CERT_CONTENT); + writeFile(tempDir_ / kCLIENT_KEY_FILENAME, kCLIENT_KEY_CONTENT); + } + + virtual ~TemporaryTLSCertificates() + { + std::error_code ec; + std::filesystem::remove_all(tempDir_, ec); + } + + TemporaryTLSCertificates(TemporaryTLSCertificates const&) = delete; + TemporaryTLSCertificates& + operator=(TemporaryTLSCertificates const&) = delete; + TemporaryTLSCertificates(TemporaryTLSCertificates&&) = delete; + TemporaryTLSCertificates& + operator=(TemporaryTLSCertificates&&) = delete; + + [[nodiscard]] std::filesystem::path + getCACertPath() const + { + return tempDir_ / kCA_CERT_FILENAME; + } + + [[nodiscard]] std::filesystem::path + getServerCertPath() const + { + return tempDir_ / kSERVER_CERT_FILENAME; + } + + [[nodiscard]] std::filesystem::path + getServerKeyPath() const + { + return tempDir_ / kSERVER_KEY_FILENAME; + } + + [[nodiscard]] std::filesystem::path + getClientCertPath() const + { + return tempDir_ / kCLIENT_CERT_FILENAME; + } + + [[nodiscard]] std::filesystem::path + getClientKeyPath() const + { + return tempDir_ / kCLIENT_KEY_FILENAME; + } + + [[nodiscard]] std::filesystem::path + getTempDir() const + { + return tempDir_; + } + +private: + static void + writeFile(std::filesystem::path const& path, std::string_view content) + { + std::ofstream file(path); + if (!file) + throw std::runtime_error("Failed to create file: " + path.string()); + file << content; + if (!file) + throw std::runtime_error("Failed to write file: " + path.string()); + } + + std::filesystem::path tempDir_; +}; + +} // namespace + +namespace xrpl::test { +/** + * Helper function to make a simple gRPC call to test connectivity. + * Returns true if the call succeeded, false otherwise. + */ +bool +makeTestGRPCCall(std::unique_ptr const& stub) +{ + grpc::ClientContext context; + org::xrpl::rpc::v1::GetLedgerRequest const request; + org::xrpl::rpc::v1::GetLedgerResponse response; + + // Set a short deadline to avoid hanging on failed connections + context.set_deadline(std::chrono::system_clock::now() + std::chrono::seconds(2)); + + grpc::Status const status = stub->GetLedger(&context, request, &response); + return status.ok(); +} + +class GRPCServerTLS_test : public beast::unit_test::suite, public TemporaryTLSCertificates +{ +public: + void + testWithoutTLS() + { + testcase("GRPCServer without TLS"); + + using namespace jtx; + + // Create config without TLS settings + auto cfg = envconfig(addGrpcConfig); + Env env(*this, std::move(cfg)); + + // Verify the server actually started by checking the port + auto const grpcPort = env.app().config()[SECTION_PORT_GRPC].get("port"); + BEAST_EXPECT(grpcPort.has_value()); + BEAST_EXPECT(*grpcPort > 0); + + // Test 1: Plaintext client should connect successfully + std::string const serverAddress = "localhost:" + std::to_string(*grpcPort); + auto plaintextStub = org::xrpl::rpc::v1::XRPLedgerAPIService::NewStub( + grpc::CreateChannel(serverAddress, grpc::InsecureChannelCredentials())); + BEAST_EXPECT(makeTestGRPCCall(plaintextStub)); + } + + void + testWithValidTLS() + { + testcase("GRPCServer with valid TLS configuration (no mutual TLS)"); + + using namespace jtx; + + // Test with just server cert and key (no client verification) + auto cfg = envconfig( + addGrpcConfigWithTLS, getServerCertPath().string(), getServerKeyPath().string()); + Env env(*this, std::move(cfg)); + + // Verify the server actually started by checking the port + auto const grpcPort = env.app().config()[SECTION_PORT_GRPC].get("port"); + BEAST_EXPECT(grpcPort.has_value()); + BEAST_EXPECT(*grpcPort > 0); + + std::string const serverAddress = "localhost:" + std::to_string(*grpcPort); + + // Test 1: Plaintext client should FAIL against TLS server + auto plaintextStub = org::xrpl::rpc::v1::XRPLedgerAPIService::NewStub( + grpc::CreateChannel(serverAddress, grpc::InsecureChannelCredentials())); + BEAST_EXPECT(!makeTestGRPCCall(plaintextStub)); + + // Test 2: TLS client with server CA should succeed + grpc::SslCredentialsOptions sslOpts; + sslOpts.pem_root_certs = std::string(kCA_CERT_CONTENT); + auto tlsStub = org::xrpl::rpc::v1::XRPLedgerAPIService::NewStub( + grpc::CreateChannel(serverAddress, grpc::SslCredentials(sslOpts))); + BEAST_EXPECT(makeTestGRPCCall(tlsStub)); + } + + void + testWithMutualTLS() + { + testcase("GRPCServer with mutual TLS (client verification enabled)"); + + using namespace jtx; + + // Test with server cert, key, and CA for client verification + auto cfg = envconfig( + addGrpcConfigWithTLSAndClientCA, + getServerCertPath().string(), + getServerKeyPath().string(), + getCACertPath().string()); + Env env(*this, std::move(cfg)); + + // Verify the server actually started by checking the port + auto const grpcPort = env.app().config()[SECTION_PORT_GRPC].get("port"); + BEAST_EXPECT(grpcPort.has_value()); + BEAST_EXPECT(*grpcPort > 0); + + auto const serverAddress = "localhost:" + std::to_string(*grpcPort); + + // Test 1: TLS client WITHOUT client certificate should FAIL (mTLS requires client cert) + grpc::SslCredentialsOptions sslOptsNoClient; + sslOptsNoClient.pem_root_certs = std::string(kCA_CERT_CONTENT); + auto tlsStubNoClient = org::xrpl::rpc::v1::XRPLedgerAPIService::NewStub( + grpc::CreateChannel(serverAddress, grpc::SslCredentials(sslOptsNoClient))); + BEAST_EXPECT(!makeTestGRPCCall(tlsStubNoClient)); + + // Test 2: TLS client WITH client certificate should succeed + grpc::SslCredentialsOptions sslOptsWithClient; + sslOptsWithClient.pem_root_certs = std::string(kCA_CERT_CONTENT); + sslOptsWithClient.pem_cert_chain = std::string(kCLIENT_CERT_CONTENT); + sslOptsWithClient.pem_private_key = std::string(kCLIENT_KEY_CONTENT); + auto tlsStubWithClient = org::xrpl::rpc::v1::XRPLedgerAPIService::NewStub( + grpc::CreateChannel(serverAddress, grpc::SslCredentials(sslOptsWithClient))); + BEAST_EXPECT(makeTestGRPCCall(tlsStubWithClient)); + } + + void + testWithMissingKey() + { + testcase("GRPCServer with cert but no key"); + + using namespace jtx; + + // Create config with only cert (missing key) + auto cfg = envconfig(); + (*cfg)[SECTION_PORT_GRPC].set("ip", "127.0.0.1"); + (*cfg)[SECTION_PORT_GRPC].set("port", "0"); + (*cfg)[SECTION_PORT_GRPC].set("ssl_cert", getServerCertPath().string()); + // Intentionally omit ssl_key + + try + { + Env const env(*this, std::move(cfg)); + fail("Should have thrown exception for incomplete TLS config"); + } + catch (std::runtime_error const& e) + { + BEAST_EXPECT( + std::string(e.what()).find("Incomplete TLS configuration") != std::string::npos); + } + } + + void + testWithMissingCert() + { + testcase("GRPCServer with key but no cert"); + + using namespace jtx; + + // Create config with only key (missing cert) + auto cfg = envconfig(); + (*cfg)[SECTION_PORT_GRPC].set("ip", "127.0.0.1"); + (*cfg)[SECTION_PORT_GRPC].set("port", "0"); + (*cfg)[SECTION_PORT_GRPC].set("ssl_key", getServerKeyPath().string()); + // Intentionally omit ssl_cert + + try + { + Env const env(*this, std::move(cfg)); + fail("Should have thrown exception for incomplete TLS config"); + } + catch (std::runtime_error const& e) + { + BEAST_EXPECT( + std::string(e.what()).find("Incomplete TLS configuration") != std::string::npos); + } + } + + void + testWithClientCAButNoTLS() + { + testcase("GRPCServer with ssl_client_ca but without both ssl_cert and ssl_key"); + + using namespace jtx; + + // Test 1: ssl_client_ca specified without any TLS config + { + auto cfg = envconfig(); + (*cfg)[SECTION_PORT_GRPC].set("ip", "127.0.0.1"); + (*cfg)[SECTION_PORT_GRPC].set("port", "0"); + (*cfg)[SECTION_PORT_GRPC].set("ssl_client_ca", getCACertPath().string()); + // Intentionally omit both ssl_cert and ssl_key + + try + { + Env const env(*this, std::move(cfg)); + fail("Should have thrown exception for ssl_client_ca without TLS config"); + } + catch (std::runtime_error const& e) + { + BEAST_EXPECT( + std::string(e.what()).find( + "ssl_client_ca requires both ssl_cert and ssl_key") != std::string::npos); + } + } + + // Test 2: ssl_client_ca with only ssl_cert (missing ssl_key) + { + auto cfg = envconfig(); + (*cfg)[SECTION_PORT_GRPC].set("ip", "127.0.0.1"); + (*cfg)[SECTION_PORT_GRPC].set("port", "0"); + (*cfg)[SECTION_PORT_GRPC].set("ssl_cert", getServerCertPath().string()); + (*cfg)[SECTION_PORT_GRPC].set("ssl_client_ca", getCACertPath().string()); + // Intentionally omit ssl_key + + try + { + Env const env(*this, std::move(cfg)); + fail("Should have thrown exception for ssl_client_ca with only ssl_cert"); + } + catch (std::runtime_error const& e) + { + // This should fail with "Incomplete TLS configuration" first + // because ssl_cert is specified without ssl_key + BEAST_EXPECT( + std::string(e.what()).find("Incomplete TLS configuration") != + std::string::npos); + } + } + + // Test 3: ssl_client_ca with only ssl_key (missing ssl_cert) + { + auto cfg = envconfig(); + (*cfg)[SECTION_PORT_GRPC].set("ip", "127.0.0.1"); + (*cfg)[SECTION_PORT_GRPC].set("port", "0"); + (*cfg)[SECTION_PORT_GRPC].set("ssl_key", getServerKeyPath().string()); + (*cfg)[SECTION_PORT_GRPC].set("ssl_client_ca", getCACertPath().string()); + // Intentionally omit ssl_cert + + try + { + Env const env(*this, std::move(cfg)); + fail("Should have thrown exception for ssl_client_ca with only ssl_key"); + } + catch (std::runtime_error const& e) + { + // This should fail with "Incomplete TLS configuration" first + // because ssl_key is specified without ssl_cert + BEAST_EXPECT( + std::string(e.what()).find("Incomplete TLS configuration") != + std::string::npos); + } + } + } + + void + testWithCertChainButNoTLS() + { + testcase("GRPCServer with ssl_cert_chain but without both ssl_cert and ssl_key"); + + using namespace jtx; + + // Test 1: ssl_cert_chain specified without any TLS config + { + auto cfg = envconfig(); + (*cfg)[SECTION_PORT_GRPC].set("ip", "127.0.0.1"); + (*cfg)[SECTION_PORT_GRPC].set("port", "0"); + (*cfg)[SECTION_PORT_GRPC].set("ssl_cert_chain", getCACertPath().string()); + // Intentionally omit both ssl_cert and ssl_key + + try + { + Env const env(*this, std::move(cfg)); + fail("Should have thrown exception for ssl_cert_chain without TLS config"); + } + catch (std::runtime_error const& e) + { + BEAST_EXPECT( + std::string(e.what()).find( + "ssl_cert_chain requires both ssl_cert and ssl_key") != std::string::npos); + } + } + + // Test 2: ssl_cert_chain with only ssl_cert (missing ssl_key) + { + auto cfg = envconfig(); + (*cfg)[SECTION_PORT_GRPC].set("ip", "127.0.0.1"); + (*cfg)[SECTION_PORT_GRPC].set("port", "0"); + (*cfg)[SECTION_PORT_GRPC].set("ssl_cert", getServerCertPath().string()); + (*cfg)[SECTION_PORT_GRPC].set("ssl_cert_chain", getCACertPath().string()); + // Intentionally omit ssl_key + + try + { + Env const env(*this, std::move(cfg)); + fail("Should have thrown exception for ssl_cert_chain with only ssl_cert"); + } + catch (std::runtime_error const& e) + { + // This should fail with "Incomplete TLS configuration" first + // because ssl_cert is specified without ssl_key + BEAST_EXPECT( + std::string(e.what()).find("Incomplete TLS configuration") != + std::string::npos); + } + } + } + + void + testWithCertChain() + { + testcase("GRPCServer with ssl_cert_chain for intermediate CA certificates"); + + using namespace jtx; + + // Test with server cert, key, and cert chain (intermediate CA) + // In this test, we use the CA cert as a stand-in for an intermediate CA cert + auto cfg = envconfig( + addGrpcConfigWithTLSAndCertChain, + getServerCertPath().string(), + getServerKeyPath().string(), + getCACertPath().string()); + Env env(*this, std::move(cfg)); + + // Verify the server actually started by checking the port + auto const grpcPort = env.app().config()[SECTION_PORT_GRPC].get("port"); + BEAST_EXPECT(grpcPort.has_value()); + BEAST_EXPECT(*grpcPort > 0); + + auto const serverAddress = "localhost:" + std::to_string(*grpcPort); + + // Test: TLS client should be able to connect (no client cert required) + grpc::SslCredentialsOptions sslOpts; + sslOpts.pem_root_certs = std::string(kCA_CERT_CONTENT); + auto tlsStub = org::xrpl::rpc::v1::XRPLedgerAPIService::NewStub( + grpc::CreateChannel(serverAddress, grpc::SslCredentials(sslOpts))); + BEAST_EXPECT(makeTestGRPCCall(tlsStub)); + + // Insecure client should fail + auto insecureStub = org::xrpl::rpc::v1::XRPLedgerAPIService::NewStub( + grpc::CreateChannel(serverAddress, grpc::InsecureChannelCredentials())); + BEAST_EXPECT(!makeTestGRPCCall(insecureStub)); + } + + void + testWithInvalidCertFile() + { + testcase("GRPCServer with invalid/non-existent certificate file"); + + using namespace jtx; + + auto cfg = envconfig(); + (*cfg)[SECTION_PORT_GRPC].set("ip", "127.0.0.1"); + (*cfg)[SECTION_PORT_GRPC].set("port", "0"); + (*cfg)[SECTION_PORT_GRPC].set("ssl_cert", "/nonexistent/path/to/cert.pem"); + (*cfg)[SECTION_PORT_GRPC].set("ssl_key", getServerKeyPath().string()); + + Env env(*this, std::move(cfg)); + + // Server should fail to start - verify port is 0 + auto const grpcPort = env.app().config()[SECTION_PORT_GRPC].get("port"); + BEAST_EXPECT(grpcPort.has_value()); + BEAST_EXPECT(*grpcPort == 0); // Server should not have started + } + + void + testWithInvalidKeyFile() + { + testcase("GRPCServer with invalid/non-existent key file"); + + using namespace jtx; + + auto cfg = envconfig(); + (*cfg)[SECTION_PORT_GRPC].set("ip", "127.0.0.1"); + (*cfg)[SECTION_PORT_GRPC].set("port", "0"); + (*cfg)[SECTION_PORT_GRPC].set("ssl_cert", getServerCertPath().string()); + (*cfg)[SECTION_PORT_GRPC].set("ssl_key", "/nonexistent/path/to/key.pem"); + + Env env(*this, std::move(cfg)); + + // Server should fail to start - verify port is 0 + auto const grpcPort = env.app().config()[SECTION_PORT_GRPC].get("port"); + BEAST_EXPECT(grpcPort.has_value()); + BEAST_EXPECT(*grpcPort == 0); // Server should not have started + } + + void + testWithInvalidCertChainFile() + { + testcase("GRPCServer with invalid/non-existent cert chain file"); + + using namespace jtx; + + auto cfg = envconfig(); + (*cfg)[SECTION_PORT_GRPC].set("ip", "127.0.0.1"); + (*cfg)[SECTION_PORT_GRPC].set("port", "0"); + (*cfg)[SECTION_PORT_GRPC].set("ssl_cert", getServerCertPath().string()); + (*cfg)[SECTION_PORT_GRPC].set("ssl_key", getServerKeyPath().string()); + (*cfg)[SECTION_PORT_GRPC].set("ssl_cert_chain", "/nonexistent/path/to/chain.pem"); + + Env env(*this, std::move(cfg)); + + // Server should fail to start - verify port is 0 + auto const grpcPort = env.app().config()[SECTION_PORT_GRPC].get("port"); + BEAST_EXPECT(grpcPort.has_value()); + BEAST_EXPECT(*grpcPort == 0); // Server should not have started + } + + void + testWithInvalidClientCAFile() + { + testcase("GRPCServer with invalid/non-existent client CA file"); + + using namespace jtx; + + auto cfg = envconfig(); + (*cfg)[SECTION_PORT_GRPC].set("ip", "127.0.0.1"); + (*cfg)[SECTION_PORT_GRPC].set("port", "0"); + (*cfg)[SECTION_PORT_GRPC].set("ssl_cert", getServerCertPath().string()); + (*cfg)[SECTION_PORT_GRPC].set("ssl_key", getServerKeyPath().string()); + (*cfg)[SECTION_PORT_GRPC].set("ssl_client_ca", "/nonexistent/path/to/ca.pem"); + + Env env(*this, std::move(cfg)); + + // Server should fail to start - verify port is 0 + auto const grpcPort = env.app().config()[SECTION_PORT_GRPC].get("port"); + BEAST_EXPECT(grpcPort.has_value()); + BEAST_EXPECT(*grpcPort == 0); // Server should not have started + } + + void + testWithEmptyClientCAFile() + { + testcase("GRPCServer with empty client CA file"); + + using namespace jtx; + + // Create an empty file for client CA + auto emptyCAPath = getTempDir() / "empty_ca.pem"; + std::ofstream emptyFile(emptyCAPath); + emptyFile.close(); + + auto cfg = envconfig(); + (*cfg)[SECTION_PORT_GRPC].set("ip", "127.0.0.1"); + (*cfg)[SECTION_PORT_GRPC].set("port", "0"); + (*cfg)[SECTION_PORT_GRPC].set("ssl_cert", getServerCertPath().string()); + (*cfg)[SECTION_PORT_GRPC].set("ssl_key", getServerKeyPath().string()); + (*cfg)[SECTION_PORT_GRPC].set("ssl_client_ca", emptyCAPath.string()); + + Env env(*this, std::move(cfg)); + + // Server should fail to start due to empty CA file + auto const grpcPort = env.app().config()[SECTION_PORT_GRPC].get("port"); + BEAST_EXPECT(grpcPort.has_value()); + BEAST_EXPECT(*grpcPort == 0); // Server should not have started + } + + void + testWithBothCertChainAndClientCA() + { + testcase("GRPCServer with both cert chain and client CA (full mTLS with intermediates)"); + + using namespace jtx; + + // Test with all TLS features enabled: cert, key, cert_chain, and client_ca + auto cfg = envconfig(); + (*cfg)[SECTION_PORT_GRPC].set("ip", getEnvLocalhostAddr()); + (*cfg)[SECTION_PORT_GRPC].set("port", "0"); + (*cfg)[SECTION_PORT_GRPC].set("ssl_cert", getServerCertPath().string()); + (*cfg)[SECTION_PORT_GRPC].set("ssl_key", getServerKeyPath().string()); + (*cfg)[SECTION_PORT_GRPC].set( + "ssl_cert_chain", getCACertPath().string()); // Using CA as intermediate + (*cfg)[SECTION_PORT_GRPC].set("ssl_client_ca", getCACertPath().string()); + + Env env(*this, std::move(cfg)); + + // Verify the server started successfully + auto const grpcPort = env.app().config()[SECTION_PORT_GRPC].get("port"); + BEAST_EXPECT(grpcPort.has_value()); + BEAST_EXPECT(*grpcPort > 0); + + auto const serverAddress = "localhost:" + std::to_string(*grpcPort); + + // Test 1: TLS client WITHOUT client certificate should FAIL (mTLS requires client cert) + grpc::SslCredentialsOptions sslOptsNoClient; + sslOptsNoClient.pem_root_certs = std::string(kCA_CERT_CONTENT); + auto tlsStubNoClient = org::xrpl::rpc::v1::XRPLedgerAPIService::NewStub( + grpc::CreateChannel(serverAddress, grpc::SslCredentials(sslOptsNoClient))); + BEAST_EXPECT(!makeTestGRPCCall(tlsStubNoClient)); + + // Test 2: TLS client WITH client certificate should succeed + grpc::SslCredentialsOptions sslOptsWithClient; + sslOptsWithClient.pem_root_certs = std::string(kCA_CERT_CONTENT); + sslOptsWithClient.pem_cert_chain = std::string(kCLIENT_CERT_CONTENT); + sslOptsWithClient.pem_private_key = std::string(kCLIENT_KEY_CONTENT); + auto tlsStubWithClient = org::xrpl::rpc::v1::XRPLedgerAPIService::NewStub( + grpc::CreateChannel(serverAddress, grpc::SslCredentials(sslOptsWithClient))); + BEAST_EXPECT(makeTestGRPCCall(tlsStubWithClient)); + } + + void + run() override + { + testWithoutTLS(); + testWithValidTLS(); + testWithMutualTLS(); + testWithMissingKey(); + testWithMissingCert(); + testWithClientCAButNoTLS(); + testWithCertChainButNoTLS(); + testWithCertChain(); + testWithInvalidCertFile(); + testWithInvalidKeyFile(); + testWithInvalidCertChainFile(); + testWithInvalidClientCAFile(); + testWithEmptyClientCAFile(); + testWithBothCertChainAndClientCA(); + } +}; + +BEAST_DEFINE_TESTSUITE(GRPCServerTLS, app, xrpl); + +} // namespace xrpl::test diff --git a/src/test/app/HashRouter_test.cpp b/src/test/app/HashRouter_test.cpp index e53515e421..3ad317b157 100644 --- a/src/test/app/HashRouter_test.cpp +++ b/src/test/app/HashRouter_test.cpp @@ -2,11 +2,17 @@ #include #include -#include +#include #include -namespace xrpl { -namespace test { +#include +#include +#include +#include +#include +#include + +namespace xrpl::test { class HashRouter_test : public beast::unit_test::suite { @@ -405,5 +411,4 @@ public: BEAST_DEFINE_TESTSUITE(HashRouter, app, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/app/Invariants_test.cpp b/src/test/app/Invariants_test.cpp index ef0624b481..5153e9d71e 100644 --- a/src/test/app/Invariants_test.cpp +++ b/src/test/app/Invariants_test.cpp @@ -1,29 +1,79 @@ -#include #include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include +#include +#include +#include #include #include #include #include +#include #include #include +#include +#include +#include #include +#include #include +#include +#include +#include #include -#include +#include +#include +#include #include +#include #include +#include #include +#include #include -#include +#include +#include +#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace xrpl { -namespace test { + +// Test-only factory — not part of the public API. +// The returned Transactor holds a raw reference to ctx; the caller must ensure +// the ApplyContext outlives the Transactor. Implemented in applySteps.cpp +std::unique_ptr +makeTransactor(ApplyContext& ctx); + +} // namespace xrpl + +namespace xrpl::test { class Invariants_test : public beast::unit_test::suite { @@ -132,6 +182,10 @@ class Invariants_test : public beast::unit_test::suite BEAST_EXPECT(precheck(A1, A2, ac)); + auto transactor = makeTransactor(ac); + if (!BEAST_EXPECT(transactor)) + return; + // invoke check twice to cover tec and tef cases if (!BEAST_EXPECT(ters.size() == 2)) return; @@ -139,8 +193,10 @@ class Invariants_test : public beast::unit_test::suite TER terActual = tesSUCCESS; for (TER const& terExpect : ters) { - terActual = ac.checkInvariants(terActual, fee); - BEAST_EXPECTS(terExpect == terActual, std::to_string(TERtoInt(terActual))); + terActual = transactor->checkInvariants(terActual, fee); + BEAST_EXPECTS( + terExpect == terActual, + "expected: " + transToken(terExpect) + " got: " + transToken(terActual)); auto const messages = sink.messages().str(); if (!isTesSuccess(terActual)) @@ -587,7 +643,7 @@ class Invariants_test : public beast::unit_test::suite using namespace test::jtx; testcase << "transfers when frozen"; - Account G1{"G1"}; + Account const G1{"G1"}; // Helper function to establish the trustlines auto const createTrustlines = [&](Account const& A1, Account const& A2, Env& env) { // Preclose callback to establish trust lines with gateway @@ -1626,23 +1682,24 @@ class Invariants_test : public beast::unit_test::suite }; auto const mods = std::to_array({ { - "pseudo-account has 0 pseudo-account fields set", - [this](SLE::pointer& sle) { - BEAST_EXPECT(sle->at(~sfVaultID)); - sle->at(~sfVaultID) = std::nullopt; - }, + .expectedFailure = "pseudo-account has 0 pseudo-account fields set", + .func = + [this](SLE::pointer& sle) { + BEAST_EXPECT(sle->at(~sfVaultID)); + sle->at(~sfVaultID) = std::nullopt; + }, }, { - "pseudo-account sequence changed", - [](SLE::pointer& sle) { sle->at(sfSequence) = 12345; }, + .expectedFailure = "pseudo-account sequence changed", + .func = [](SLE::pointer& sle) { sle->at(sfSequence) = 12345; }, }, { - "pseudo-account flags are not set", - [](SLE::pointer& sle) { sle->at(sfFlags) = lsfNoFreeze; }, + .expectedFailure = "pseudo-account flags are not set", + .func = [](SLE::pointer& sle) { sle->at(sfFlags) = lsfNoFreeze; }, }, { - "pseudo-account has a regular key", - [](SLE::pointer& sle) { sle->at(sfRegularKey) = Account("regular").id(); }, + .expectedFailure = "pseudo-account has a regular key", + .func = [](SLE::pointer& sle) { sle->at(sfRegularKey) = Account("regular").id(); }, }, }); @@ -1737,8 +1794,10 @@ class Invariants_test : public beast::unit_test::suite using namespace test::jtx; bool const fixPDEnabled = features[fixPermissionedDomainInvariant]; + bool const fixS313Enabled = features[fixSecurity3_1_3]; - testcase << "PermissionedDEX" + std::string(fixPDEnabled ? " fix" : ""); + testcase << "PermissionedDEX" + std::string(fixPDEnabled ? " fixPD" : "") + + std::string(fixS313Enabled ? " fixS313" : ""); doInvariantCheck( Env(*this, features), @@ -1826,6 +1885,45 @@ class Invariants_test : public beast::unit_test::suite {tecINVARIANT_FAILED, tecINVARIANT_FAILED}); } + // empty sfAdditionalBooks (size 0) + { + Env env1(*this, features); + + Account const A1{"A1"}; + Account const A2{"A2"}; + env1.fund(XRP(1000), A1, A2); + env1.close(); + + [[maybe_unused]] auto [seq1, pd1] = createPermissionedDomainEnv(env1, A1, A2); + env1.close(); + + doInvariantCheck( + std::move(env1), + A1, + A2, + fixS313Enabled ? std::vector{{"hybrid offer is malformed"}} + : std::vector{}, + [&pd1](Account const& A1, Account const& A2, ApplyContext& ac) { + Keylet const offerKey = keylet::offer(A2.id(), 10); + auto sleOffer = std::make_shared(offerKey); + sleOffer->setAccountID(sfAccount, A2); + sleOffer->setFieldAmount(sfTakerPays, A1["USD"](10)); + sleOffer->setFieldAmount(sfTakerGets, XRP(1)); + sleOffer->setFlag(lsfHybrid); + sleOffer->setFieldH256(sfDomainID, pd1); + + STArray const bookArr; // empty array, size 0 + sleOffer->setFieldArray(sfAdditionalBooks, bookArr); + ac.view().insert(sleOffer); + return true; + }, + XRPAmount{}, + STTx{ttOFFER_CREATE, [&](STObject&) {}}, + fixS313Enabled + ? std::initializer_list{tecINVARIANT_FAILED, tecINVARIANT_FAILED} + : std::initializer_list{tesSUCCESS, tesSUCCESS}); + } + // hybrid offer missing sfAdditionalBooks { Env env1(*this, features); @@ -2046,36 +2144,36 @@ class Invariants_test : public beast::unit_test::suite { // Initialize with a placeholder value because there's no default // ctor + auto const setupAsset = + [&](Account const& alice, Account const& issuer, Env& env) -> PrettyAsset { + switch (assetType) + { + case Asset::IOU: { + PrettyAsset const iouAsset = issuer["IOU"]; + env(trust(alice, iouAsset(1000))); + env(pay(issuer, alice, iouAsset(1000))); + env.close(); + return iouAsset; + } + case Asset::MPT: { + MPTTester mptt{env, issuer, mptInitNoFund}; + mptt.create({.flags = tfMPTCanClawback | tfMPTCanTransfer | tfMPTCanLock}); + PrettyAsset const mptAsset = mptt.issuanceID(); + mptt.authorize({.account = alice}); + env(pay(issuer, alice, mptAsset(1000))); + env.close(); + return mptAsset; + } + case Asset::XRP: + default: + return PrettyAsset{xrpIssue(), 1'000'000}; + } + }; + Keylet loanBrokerKeylet = keylet::amendments(); Preclose const createLoanBroker = [&, this](Account const& alice, Account const& issuer, Env& env) { - PrettyAsset const asset = [&]() { - switch (assetType) - { - case Asset::IOU: { - PrettyAsset const iouAsset = issuer["IOU"]; - env(trust(alice, iouAsset(1000))); - env(pay(issuer, alice, iouAsset(1000))); - env.close(); - return iouAsset; - } - - case Asset::MPT: { - MPTTester mptt{env, issuer, mptInitNoFund}; - mptt.create( - {.flags = tfMPTCanClawback | tfMPTCanTransfer | tfMPTCanLock}); - PrettyAsset const mptAsset = mptt.issuanceID(); - mptt.authorize({.account = alice}); - env(pay(issuer, alice, mptAsset(1000))); - env.close(); - return mptAsset; - } - - case Asset::XRP: - default: - return PrettyAsset{xrpIssue(), 1'000'000}; - } - }(); + auto const asset = setupAsset(alice, issuer, env); loanBrokerKeylet = this->createLoanBroker(alice, env, asset); return BEAST_EXPECT(env.le(loanBrokerKeylet)); }; @@ -2249,6 +2347,56 @@ class Invariants_test : public beast::unit_test::suite STTx{ttLOAN_BROKER_SET, [](STObject& tx) {}}, {tecINVARIANT_FAILED, tefINVARIANT_FAILED}, createLoanBroker); + + // Test: cover available less than pseudo-account asset balance + { + Keylet brokerKeylet = keylet::amendments(); + Preclose const createBrokerWithCover = + [&, this](Account const& alice, Account const& issuer, Env& env) { + auto const asset = setupAsset(alice, issuer, env); + brokerKeylet = this->createLoanBroker(alice, env, asset); + if (!BEAST_EXPECT(env.le(brokerKeylet))) + return false; + env(loanBroker::coverDeposit(alice, brokerKeylet.key, asset(10))); + env.close(); + return BEAST_EXPECT(env.le(brokerKeylet)); + }; + + doInvariantCheck( + {{"Loan Broker cover available is less than pseudo-account asset balance"}}, + [&](Account const&, Account const&, ApplyContext& ac) { + auto sle = ac.view().peek(brokerKeylet); + if (!BEAST_EXPECT(sle)) + return false; + // Pseudo-account holds 10 units, set cover to 5 + sle->at(sfCoverAvailable) = Number(5); + ac.view().update(sle); + return true; + }, + XRPAmount{}, + STTx{ttLOAN_BROKER_SET, [](STObject& tx) {}}, + {tecINVARIANT_FAILED, tefINVARIANT_FAILED}, + createBrokerWithCover); + } + + // Test: cover available greater than pseudo-account asset balance + // (requires fixSecurity3_1_3) + doInvariantCheck( + {{"Loan Broker cover available is greater than pseudo-account asset balance"}}, + [&](Account const&, Account const&, ApplyContext& ac) { + auto sle = ac.view().peek(loanBrokerKeylet); + if (!BEAST_EXPECT(sle)) + return false; + // Pseudo-account has no cover deposited; set cover + // higher than any incidental balance + sle->at(sfCoverAvailable) = Number(1'000'000); + ac.view().update(sle); + return true; + }, + XRPAmount{}, + STTx{ttLOAN_BROKER_SET, [](STObject& tx) {}}, + {tecINVARIANT_FAILED, tefINVARIANT_FAILED}, + createLoanBroker); } } @@ -2382,15 +2530,15 @@ class Invariants_test : public beast::unit_test::suite .sharesTotal = adjustment, .vaultAssets = adjustment, .accountAssets = // - AccountAmount{id, -adjustment}, + AccountAmount{.account = id, .amount = -adjustment}, .accountShares = // - AccountAmount{id, adjustment}}; + AccountAmount{.account = id, .amount = adjustment}}; fn(sample); return sample; }; - Account A3{"A3"}; - Account A4{"A4"}; + Account const A3{"A3"}; + Account const A4{"A4"}; auto const precloseXrp = [&](Account const& A1, Account const& A2, Env& env) -> bool { env.fund(XRP(1000), A3, A4); Vault const vault{env}; @@ -3805,6 +3953,276 @@ class Invariants_test : public beast::unit_test::suite precloseMpt); } + void + testMPT() + { + using namespace test::jtx; + testcase << "MPT"; + + // MPT OutstandingAmount > MaximumAmount + doInvariantCheck( + {{"OutstandingAmount overflow"}}, + [](Account const& A1, Account const&, ApplyContext& ac) { + // mptissuance outstanding is negative + auto const sle = ac.view().peek(keylet::account(A1.id())); + if (!sle) + return false; + + MPTIssue const mpt{MPTIssue{makeMptID(sle->getFieldU32(sfSequence), A1)}}; + auto sleNew = std::make_shared(keylet::mptIssuance(mpt.getMptID())); + sleNew->setFieldU64(sfOutstandingAmount, 110); + sleNew->setFieldU64(sfMaximumAmount, 100); + ac.view().insert(sleNew); + return true; + }); + + // MPTToken amount doesn't add up to OutstandingAmount + doInvariantCheck( + {{"invalid OutstandingAmount balance"}}, + [](Account const& A1, Account const& A2, ApplyContext& ac) { + // mptissuance outstanding is negative + auto const sle = ac.view().peek(keylet::account(A1.id())); + if (!sle) + return false; + + MPTIssue const mpt{MPTIssue{makeMptID(sle->getFieldU32(sfSequence), A1)}}; + auto sleNew = std::make_shared(keylet::mptIssuance(mpt.getMptID())); + sleNew->setFieldU64(sfOutstandingAmount, 100); + sleNew->setFieldU64(sfMaximumAmount, 100); + ac.view().insert(sleNew); + + sleNew = std::make_shared(keylet::mptoken(mpt.getMptID(), A2)); + sleNew->setFieldU64(sfMPTAmount, 90); + ac.view().insert(sleNew); + + return true; + }); + + // Overflow/Invalid balance on payment + auto testPayment = [&](std::string const& log, auto&& update) { + MPTID id; + doInvariantCheck( + {{log}}, + [&](Account const& A1, Account const& A2, ApplyContext& ac) { + return update(id, ac, A1); + }, + XRPAmount{}, + STTx{ttPAYMENT, [](STObject& tx) {}}, + {tecINVARIANT_FAILED, tecINVARIANT_FAILED}, + [&](Account const& A1, Account const& A2, Env& env) { + Account const gw("gw"); + env.fund(XRP(1'000), gw); + MPTTester const mpt( + {.env = env, .issuer = gw, .holders = {A1}, .pay = 100, .maxAmt = 100}); + id = mpt.issuanceID(); + return true; + }); + }; + testPayment( + "invalid OutstandingAmount balance", + [&](MPTID const& id, ApplyContext& ac, Account const& A1) { + auto sle = ac.view().peek(keylet::mptoken(id, A1)); + if (!sle) + return false; + sle->setFieldU64(sfMPTAmount, 101); + ac.view().update(sle); + return true; + }); + testPayment( + "OutstandingAmount overflow", [&](MPTID const& id, ApplyContext& ac, Account const&) { + auto sle = ac.view().peek(keylet::mptIssuance(id)); + if (!sle) + return false; + sle->setFieldU64(sfOutstandingAmount, 101); + ac.view().update(sle); + return true; + }); + + // More MPTokens created than expected + std::array, 4> const tests = { + std::make_pair(ttAMM_WITHDRAW, 2), + std::make_pair(ttAMM_CLAWBACK, 2), + std::make_pair(ttAMM_CREATE, 3), + std::make_pair(ttCHECK_CASH, 2)}; + for (auto const& [tx, nTokens] : tests) + { + doInvariantCheck( + {{std::string("MPToken created for the MPT issuer")}}, + [&](Account const& A1, Account const& A2, ApplyContext& ac) { + auto const sle = ac.view().peek(keylet::account(A1.id())); + if (!sle) + return false; + + auto seq = sle->getFieldU32(sfSequence); + for (int i = 0; i < nTokens; ++i) + { + MPTIssue const mpt{MPTIssue{makeMptID(seq + i, A1)}}; + auto sleNew = std::make_shared(keylet::mptIssuance(mpt.getMptID())); + ac.view().insert(sleNew); + + sleNew = std::make_shared(keylet::mptoken(mpt.getMptID(), A2)); + ac.view().insert(sleNew); + } + + return true; + }, + XRPAmount{}, + STTx{tx, [](STObject& tx) {}}, + {tecINVARIANT_FAILED, tefINVARIANT_FAILED}); + } + + // More MPTokens deleted than expected + for (auto const& tx : {ttAMM_WITHDRAW, ttAMM_CLAWBACK}) + { + MPTID id; + Account const A3("A3"); + doInvariantCheck( + {{"MPT authorize succeeded but created/deleted bad number of mptokens"}}, + [&](Account const& A1, Account const& A2, ApplyContext& ac) { + for (auto const& a : {A1, A2, A3}) + { + auto sle = ac.view().peek(keylet::mptoken(id, a)); + if (!sle) + return false; + ac.view().erase(sle); + } + return true; + }, + XRPAmount{}, + STTx{tx, [](STObject& tx) {}}, + {tecINVARIANT_FAILED, tefINVARIANT_FAILED}, + [&](Account const& A1, Account const& A2, Env& env) { + Account const gw("gw"); + env.fund(XRP(1'000), gw, A3); + MPTTester const mpt({.env = env, .issuer = gw, .holders = {A1, A2, A3}}); + id = mpt.issuanceID(); + return true; + }); + } + } + + void + testVaultComputeCoarsestScale() + { + using namespace jtx; + + Account const issuer{"issuer"}; + PrettyAsset const vaultAsset = issuer["IOU"]; + + struct TestCase + { + std::string name; + std::int32_t expectedMinScale; + std::vector values; + }; + + NumberMantissaScaleGuard const g{MantissaRange::large}; + + auto makeDelta = [&vaultAsset](Number const& n) -> ValidVault::DeltaInfo { + return {.delta = n, .scale = scale(n, vaultAsset.raw())}; + }; + + auto const testCases = std::vector{ + { + .name = "No values", + .expectedMinScale = 0, + .values = {}, + }, + { + .name = "Mixed integer and Number values", + .expectedMinScale = -15, + .values = {makeDelta(1), makeDelta(-1), makeDelta(Number{10, -1})}, + }, + { + .name = "Mixed scales", + .expectedMinScale = -17, + .values = + {makeDelta(Number{1, -2}), makeDelta(Number{5, -3}), makeDelta(Number{3, -2})}, + }, + { + .name = "Equal scales", + .expectedMinScale = -16, + .values = + {makeDelta(Number{1, -1}), makeDelta(Number{5, -1}), makeDelta(Number{1, -1})}, + }, + { + .name = "Mixed mantissa sizes", + .expectedMinScale = -12, + .values = + {makeDelta(Number{1}), + makeDelta(Number{1234, -3}), + makeDelta(Number{12345, -6}), + makeDelta(Number{123, 1})}, + }, + }; + + for (auto const& tc : testCases) + { + testcase("vault computeCoarsestScale: " + tc.name); + + auto const actualScale = ValidVault::computeCoarsestScale(tc.values); + + BEAST_EXPECTS( + actualScale == tc.expectedMinScale, + "expected: " + std::to_string(tc.expectedMinScale) + + ", actual: " + std::to_string(actualScale)); + for (auto const& num : tc.values) + { + // None of these scales are far enough apart that rounding the + // values would lose information, so check that the rounded + // value matches the original. + auto const actualRounded = roundToAsset(vaultAsset, num.delta, actualScale); + BEAST_EXPECTS( + actualRounded == num.delta, + "number " + to_string(num.delta) + " rounded to scale " + + std::to_string(actualScale) + " is " + to_string(actualRounded)); + } + } + + auto const testCases2 = std::vector{ + { + .name = "False equivalence", + .expectedMinScale = -15, + .values = + { + makeDelta(Number{1234567890123456789, -18}), + makeDelta(Number{12345, -4}), + makeDelta(Number{1}), + }, + }, + }; + + // Unlike the first set of test cases, the values in these test could + // look equivalent if using the wrong scale. + for (auto const& tc : testCases2) + { + testcase("vault computeCoarsestScale: " + tc.name); + + auto const actualScale = ValidVault::computeCoarsestScale(tc.values); + + BEAST_EXPECTS( + actualScale == tc.expectedMinScale, + "expected: " + std::to_string(tc.expectedMinScale) + + ", actual: " + std::to_string(actualScale)); + std::optional first; + Number firstRounded; + for (auto const& num : tc.values) + { + if (!first) + { + first = num.delta; + firstRounded = roundToAsset(vaultAsset, num.delta, actualScale); + continue; + } + auto const numRounded = roundToAsset(vaultAsset, num.delta, actualScale); + BEAST_EXPECTS( + numRounded != firstRounded, + "at a scale of " + std::to_string(actualScale) + " " + to_string(num.delta) + + " == " + to_string(*first)); + } + } + } + public: void run() override @@ -3826,14 +4244,19 @@ public: testPermissionedDomainInvariants(defaultAmendments() - fixPermissionedDomainInvariant); testPermissionedDEX(defaultAmendments() | fixPermissionedDomainInvariant); testPermissionedDEX(defaultAmendments() - fixPermissionedDomainInvariant); + testPermissionedDEX( + (defaultAmendments() | fixPermissionedDomainInvariant) - fixSecurity3_1_3); + testPermissionedDEX( + defaultAmendments() - fixPermissionedDomainInvariant - fixSecurity3_1_3); testNoModifiedUnmodifiableFields(); testValidPseudoAccounts(); testValidLoanBroker(); testVault(); + testMPT(); + testVaultComputeCoarsestScale(); } }; BEAST_DEFINE_TESTSUITE(Invariants, app, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/app/LPTokenTransfer_test.cpp b/src/test/app/LPTokenTransfer_test.cpp index 265ff7d2ef..2b440c67d5 100644 --- a/src/test/app/LPTokenTransfer_test.cpp +++ b/src/test/app/LPTokenTransfer_test.cpp @@ -1,9 +1,26 @@ -#include #include #include +#include +#include +#include +#include +#include +#include // IWYU pragma: keep +#include +#include +#include +#include +#include +#include -namespace xrpl { -namespace test { +#include +#include +#include +#include +#include +#include + +namespace xrpl::test { class LPTokenTransfer_test : public jtx::AMMTest { @@ -224,7 +241,7 @@ class LPTokenTransfer_test : public jtx::AMMTest AMM ammAlice1(env, alice, XRP(10'000), USD(10'000)); ammAlice1.deposit(carol, 10'000'000); - fund(env, gw, {alice, carol}, {EUR(10'000)}, Fund::IOUOnly); + fund(env, gw, {alice, carol}, {EUR(10'000)}, Fund::TokenOnly); AMM ammAlice2(env, alice, XRP(10'000), EUR(10'000)); ammAlice2.deposit(carol, 10'000'000); auto const token1 = ammAlice1.lptIssue(); @@ -432,5 +449,4 @@ public: }; BEAST_DEFINE_TESTSUITE(LPTokenTransfer, app, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/app/LedgerHistory_test.cpp b/src/test/app/LedgerHistory_test.cpp index 453c424251..e78bdc588f 100644 --- a/src/test/app/LedgerHistory_test.cpp +++ b/src/test/app/LedgerHistory_test.cpp @@ -1,19 +1,30 @@ -#include +#include #include +#include +#include +#include +#include +#include #include #include +#include +#include #include -#include +#include +#include +#include #include +#include +#include #include -#include -#include +#include +#include +#include -namespace xrpl { -namespace test { +namespace xrpl::test { class LedgerHistory_test : public beast::unit_test::suite { @@ -170,5 +181,4 @@ public: BEAST_DEFINE_TESTSUITE(LedgerHistory, app, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/app/LedgerLoad_test.cpp b/src/test/app/LedgerLoad_test.cpp index 1df41671fe..a97b492921 100644 --- a/src/test/app/LedgerLoad_test.cpp +++ b/src/test/app/LedgerLoad_test.cpp @@ -1,17 +1,33 @@ -#include +#include #include +#include +#include +#include +#include -#include +#include + +#include #include #include #include +#include +#include +#include #include #include -#include -#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include namespace xrpl { @@ -57,7 +73,7 @@ class LedgerLoad_test : public beast::unit_test::suite for (auto i = 0; i < 20; ++i) { - Account acct{"A" + std::to_string(i)}; + Account const acct{"A" + std::to_string(i)}; env.fund(XRP(10000), acct); env.close(); if (i > 0 && BEAST_EXPECT(prev.has_value())) @@ -68,7 +84,7 @@ class LedgerLoad_test : public beast::unit_test::suite } env(offer(acct, XRP(100), acct["USD"](1))); env.close(); - prev.emplace(std::move(acct)); + prev.emplace(acct); } retval.ledger = env.rpc("ledger", "current", "full")[jss::result]; diff --git a/src/test/app/LedgerMaster_test.cpp b/src/test/app/LedgerMaster_test.cpp index 7a8904dbd7..625eb22ac5 100644 --- a/src/test/app/LedgerMaster_test.cpp +++ b/src/test/app/LedgerMaster_test.cpp @@ -1,10 +1,24 @@ -#include +#include #include +#include +#include +#include #include +#include -namespace xrpl { -namespace test { +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace xrpl::test { class LedgerMaster_test : public beast::unit_test::suite { @@ -115,5 +129,4 @@ public: BEAST_DEFINE_TESTSUITE(LedgerMaster, app, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/app/LedgerReplay_test.cpp b/src/test/app/LedgerReplay_test.cpp index b30dce4756..aec0bec592 100644 --- a/src/test/app/LedgerReplay_test.cpp +++ b/src/test/app/LedgerReplay_test.cpp @@ -1,7 +1,15 @@ -#include +#include +#include +#include #include +#include +#include +#include +#include +#include #include +#include #include #include #include @@ -9,16 +17,53 @@ #include #include #include +#include +#include +#include #include -#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include -namespace xrpl { -namespace test { +namespace xrpl::test { struct LedgerReplay_test : public beast::unit_test::suite { @@ -68,9 +113,9 @@ public: : ledgerSource(ledgerSource), ledgerSink(ledgerSink), bhvr(bhvr) { } - virtual ~MagicInboundLedgers() = default; + ~MagicInboundLedgers() override = default; - virtual std::shared_ptr + std::shared_ptr acquire(uint256 const& hash, std::uint32_t seq, InboundLedger::Reason) override { if (bhvr == InboundLedgersBehavior::DropAll) @@ -84,18 +129,18 @@ public: return {}; } - virtual void + void acquireAsync(uint256 const& hash, std::uint32_t seq, InboundLedger::Reason reason) override { } - virtual std::shared_ptr + std::shared_ptr find(LedgerHash const& hash) override { return {}; } - virtual bool + bool gotLedgerData( LedgerHash const& ledgerHash, std::shared_ptr, @@ -104,59 +149,59 @@ public: return false; } - virtual void + void gotStaleData(std::shared_ptr packet) override { } - virtual void + void logFailure(uint256 const& h, std::uint32_t seq) override { } - virtual bool + bool isFailure(uint256 const& h) override { return false; } - virtual void + void clearFailures() override { } - virtual Json::Value + Json::Value getInfo() override { return {}; } - virtual std::size_t + std::size_t fetchRate() override { return 0; } - virtual void + void onLedgerFetched() override { } - virtual void + void gotFetchPack() override { } - virtual void + void sweep() override { } - virtual void + void stop() override { } - virtual size_t + size_t cacheSize() override { return 0; @@ -653,7 +698,7 @@ public: findTask(uint256 const& hash, int totalReplay) { std::unique_lock const lock(replayer.mtx_); - auto i = std::find_if(replayer.tasks_.begin(), replayer.tasks_.end(), [&](auto const& t) { + auto i = std::ranges::find_if(replayer.tasks_, [&](auto const& t) { return t->parameter_.finishHash_ == hash && t->parameter_.totalLedgers_ == totalReplay; }); if (i == replayer.tasks_.end()) @@ -858,7 +903,7 @@ struct LedgerReplayer_test : public beast::unit_test::suite testProofPath() { testcase("ProofPath"); - LedgerServer server(*this, {1}); + LedgerServer server(*this, {.initLedgers = 1}); auto const l = server.ledgerMaster.getClosedLedger(); { @@ -916,7 +961,7 @@ struct LedgerReplayer_test : public beast::unit_test::suite testReplayDelta() { testcase("ReplayDelta"); - LedgerServer server(*this, {1}); + LedgerServer server(*this, {.initLedgers = 1}); auto const l = server.ledgerMaster.getClosedLedger(); { @@ -1023,20 +1068,20 @@ struct LedgerReplayer_test : public beast::unit_test::suite { Config c; - std::string const toLoad(R"rippleConfig( + std::string const toLoad(R"xrpldConfig( [ledger_replay] 1 -)rippleConfig"); +)xrpldConfig"); c.loadFromString(toLoad); BEAST_EXPECT(c.LEDGER_REPLAY == true); } { Config c; - std::string const toLoad = (R"rippleConfig( + std::string const toLoad = (R"xrpldConfig( [ledger_replay] 0 -)rippleConfig"); +)xrpldConfig"); c.loadFromString(toLoad); BEAST_EXPECT(c.LEDGER_REPLAY == false); } @@ -1079,7 +1124,7 @@ struct LedgerReplayer_test : public beast::unit_test::suite auto ilBhvr = InboundLedgersBehavior::DropAll; auto peerFeature = PeerFeature::None; - NetworkOfTwo net(*this, {totalReplay + 1}, psBhvr, ilBhvr, peerFeature); + NetworkOfTwo net(*this, {.initLedgers = totalReplay + 1}, psBhvr, ilBhvr, peerFeature); auto l = net.server.ledgerMaster.getClosedLedger(); uint256 const finalHash = l->header().hash; @@ -1114,7 +1159,7 @@ struct LedgerReplayer_test : public beast::unit_test::suite testcase("all the ledgers from InboundLedgers"); NetworkOfTwo net( *this, - {totalReplay + 1}, + {.initLedgers = totalReplay + 1}, PeerSetBehavior::DropAll, InboundLedgersBehavior::Good, PeerFeature::None); @@ -1170,7 +1215,7 @@ struct LedgerReplayer_test : public beast::unit_test::suite NetworkOfTwo net( *this, - {totalReplay + 1}, + {.initLedgers = totalReplay + 1}, peerSetBehavior, InboundLedgersBehavior::DropAll, PeerFeature::LedgerReplayEnabled); @@ -1203,7 +1248,7 @@ struct LedgerReplayer_test : public beast::unit_test::suite int const totalReplay = 3; NetworkOfTwo net( *this, - {totalReplay + 1}, + {.initLedgers = totalReplay + 1}, PeerSetBehavior::DropAll, InboundLedgersBehavior::Good, PeerFeature::LedgerReplayEnabled); @@ -1228,7 +1273,7 @@ struct LedgerReplayer_test : public beast::unit_test::suite int const totalReplay = 3; NetworkOfTwo net( *this, - {totalReplay + 1 + 1}, + {.initLedgers = totalReplay + 1 + 1}, PeerSetBehavior::DropAll, InboundLedgersBehavior::DropAll, PeerFeature::LedgerReplayEnabled); @@ -1261,7 +1306,7 @@ struct LedgerReplayer_test : public beast::unit_test::suite int const totalReplay = 3; NetworkOfTwo net( *this, - {totalReplay + 1}, + {.initLedgers = totalReplay + 1}, PeerSetBehavior::DropLedgerDeltaReply, InboundLedgersBehavior::DropAll, PeerFeature::LedgerReplayEnabled); @@ -1294,7 +1339,7 @@ struct LedgerReplayer_test : public beast::unit_test::suite int const totalReplay = 5; NetworkOfTwo net( *this, - {(totalReplay * 3) + 1}, + {.initLedgers = (totalReplay * 3) + 1}, PeerSetBehavior::Good, InboundLedgersBehavior::Good, PeerFeature::LedgerReplayEnabled); @@ -1392,7 +1437,7 @@ struct LedgerReplayerTimeout_test : public beast::unit_test::suite int const totalReplay = 3; NetworkOfTwo net( *this, - {totalReplay + 1}, + {.initLedgers = totalReplay + 1}, PeerSetBehavior::DropAll, InboundLedgersBehavior::Good, PeerFeature::LedgerReplayEnabled); @@ -1418,7 +1463,7 @@ struct LedgerReplayerTimeout_test : public beast::unit_test::suite int const totalReplay = 3; NetworkOfTwo net( *this, - {totalReplay + 1}, + {.initLedgers = totalReplay + 1}, PeerSetBehavior::DropAll, InboundLedgersBehavior::Good, PeerFeature::LedgerReplayEnabled); @@ -1457,7 +1502,7 @@ struct LedgerReplayerLong_test : public beast::unit_test::suite int const rounds = 4; NetworkOfTwo net( *this, - {(totalReplay * rounds) + 1}, + {.initLedgers = (totalReplay * rounds) + 1}, PeerSetBehavior::Good, InboundLedgersBehavior::Good, PeerFeature::LedgerReplayEnabled); @@ -1505,5 +1550,4 @@ BEAST_DEFINE_TESTSUITE_PRIO(LedgerReplayer, app, xrpl, 1); BEAST_DEFINE_TESTSUITE(LedgerReplayerTimeout, app, xrpl); BEAST_DEFINE_TESTSUITE_MANUAL(LedgerReplayerLong, app, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/app/LendingHelpers_test.cpp b/src/test/app/LendingHelpers_test.cpp index 56db553583..1e43c45104 100644 --- a/src/test/app/LendingHelpers_test.cpp +++ b/src/test/app/LendingHelpers_test.cpp @@ -1,22 +1,19 @@ #include // DO NOT REMOVE -#include #include +#include #include -#include -#include -#include -#include +#include +#include +#include #include -#include -#include +#include #include #include -namespace xrpl { -namespace test { +namespace xrpl::test { class LendingHelpers_test : public beast::unit_test::suite { @@ -265,7 +262,7 @@ class LendingHelpers_test : public beast::unit_test::suite auto const expectedOverpaymentManagementFee = Number{10}; // 10% of 100 auto const expectedPrincipalPortion = Number{400}; // 1,000 - 100 - 500 - auto const components = detail::computeOverpaymentComponents( + auto const components = xrpl::detail::computeOverpaymentComponents( IOU, loanScale, overpayment, @@ -1211,5 +1208,4 @@ public: BEAST_DEFINE_TESTSUITE(LendingHelpers, app, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/app/LoadFeeTrack_test.cpp b/src/test/app/LoadFeeTrack_test.cpp index 68ebcc70a1..f6d05bf0e6 100644 --- a/src/test/app/LoadFeeTrack_test.cpp +++ b/src/test/app/LoadFeeTrack_test.cpp @@ -1,7 +1,7 @@ #include -#include -#include +#include +#include #include namespace xrpl { diff --git a/src/test/app/LoanBroker_test.cpp b/src/test/app/LoanBroker_test.cpp index f7222aa61a..55818b131c 100644 --- a/src/test/app/LoanBroker_test.cpp +++ b/src/test/app/LoanBroker_test.cpp @@ -1,10 +1,63 @@ -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include -namespace xrpl { -namespace test { +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl::test { class LoanBroker_test : public beast::unit_test::suite { @@ -495,9 +548,9 @@ class LoanBroker_test : public beast::unit_test::suite Account const issuer{"issuer"}; // For simplicity, alice will be the sole actor for the vault & brokers. - Account alice{"alice"}; + Account const alice{"alice"}; // Evan will attempt to be naughty - Account evan{"evan"}; + Account const evan{"evan"}; // Bystander doesn't have anything to do with the SAV or Broker, or any // of the relevant tokens Account const bystander{"bystander"}; @@ -1290,7 +1343,7 @@ class LoanBroker_test : public beast::unit_test::suite auto const broker = env.le(brokerKeylet); if (!BEAST_EXPECT(broker)) return; - Account brokerPseudo("pseudo", broker->at(sfAccount)); + Account const brokerPseudo("pseudo", broker->at(sfAccount)); // Can't unauthorize LoanBroker pseudo-account asset.authorize( @@ -1527,9 +1580,9 @@ class LoanBroker_test : public beast::unit_test::suite testRIPD4274IOU() { using namespace jtx; - Account issuer("broker"); - Account broker("issuer"); - Account dest("destination"); + Account const issuer("broker"); + Account const broker("issuer"); + Account const dest("destination"); auto const token = issuer["IOU"]; enum TrustState { @@ -1654,9 +1707,9 @@ class LoanBroker_test : public beast::unit_test::suite testRIPD4274MPT() { using namespace jtx; - Account issuer("broker"); - Account broker("issuer"); - Account dest("destination"); + Account const issuer("broker"); + Account const broker("issuer"); + Account const dest("destination"); enum MPTState { RequireAuth, @@ -1798,5 +1851,4 @@ public: BEAST_DEFINE_TESTSUITE(LoanBroker, tx, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/app/Loan_test.cpp b/src/test/app/Loan_test.cpp index a63d31f030..8d919d7dd3 100644 --- a/src/test/app/Loan_test.cpp +++ b/src/test/app/Loan_test.cpp @@ -1,19 +1,86 @@ #include // -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include #include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include -namespace xrpl { -namespace test { +namespace xrpl::test { class Loan_test : public beast::unit_test::suite { @@ -114,8 +181,11 @@ protected: jtx::PrettyAsset const& asset_, Keylet const& brokerKeylet_, Keylet const& vaultKeylet_, - BrokerParameters const& p) - : asset(asset_), brokerID(brokerKeylet_.key), vaultID(vaultKeylet_.key), params(p) + BrokerParameters p) + : asset(asset_) + , brokerID(brokerKeylet_.key) + , vaultID(vaultKeylet_.key) + , params(std::move(p)) { } @@ -886,7 +956,7 @@ protected: auto const borrowerInitialBalance = env.balance(borrower, broker.asset).number(); auto const initialState = state; - detail::PaymentComponents totalPaid{ + xrpl::detail::PaymentComponents totalPaid{ .trackedValueDelta = 0, .trackedPrincipalDelta = 0, .trackedManagementFeeDelta = 0}; Number totalInterestPaid = 0; Number totalFeesPaid = 0; @@ -921,7 +991,7 @@ protected: { validateBorrowerBalance(); // Compute the expected principal amount - auto const paymentComponents = detail::computePaymentComponents( + auto const paymentComponents = xrpl::detail::computePaymentComponents( broker.asset.raw(), state.loanScale, state.totalValue, @@ -934,7 +1004,7 @@ protected: BEAST_EXPECT( paymentComponents.trackedValueDelta <= roundedPeriodicPayment || - (paymentComponents.specialCase == detail::PaymentSpecialCase::final && + (paymentComponents.specialCase == xrpl::detail::PaymentSpecialCase::final && paymentComponents.trackedValueDelta >= roundedPeriodicPayment)); BEAST_EXPECT( paymentComponents.trackedValueDelta == @@ -946,11 +1016,11 @@ protected: periodicRate, state.paymentRemaining - 1, broker.params.managementFeeRate); - detail::LoanStateDeltas const deltas = currentTrueState - nextTrueState; + xrpl::detail::LoanStateDeltas const deltas = currentTrueState - nextTrueState; BEAST_EXPECT( deltas.total() == deltas.principal + deltas.interest + deltas.managementFee); BEAST_EXPECT( - paymentComponents.specialCase == detail::PaymentSpecialCase::final || + paymentComponents.specialCase == xrpl::detail::PaymentSpecialCase::final || deltas.total() == state.periodicPayment || (state.loanScale - (deltas.total() - state.periodicPayment).exponent()) > 14); @@ -963,9 +1033,9 @@ protected: << paymentComponents.trackedPrincipalDelta << ", " << paymentComponents.trackedInterestPart() << ", " << paymentComponents.trackedManagementFeeDelta << ", " << [&]() -> char const* { - if (paymentComponents.specialCase == detail::PaymentSpecialCase::final) + if (paymentComponents.specialCase == ::xrpl::detail::PaymentSpecialCase::final) return "final"; - if (paymentComponents.specialCase == detail::PaymentSpecialCase::extra) + if (paymentComponents.specialCase == ::xrpl::detail::PaymentSpecialCase::extra) return "extra"; return "none"; }() << std::endl; @@ -984,7 +1054,7 @@ protected: // IOUs, the difference should be dust. Number const diff = totalDue - totalDueAmount; BEAST_EXPECT( - paymentComponents.specialCase == detail::PaymentSpecialCase::final || + paymentComponents.specialCase == xrpl::detail::PaymentSpecialCase::final || diff == beast::zero || (diff > beast::zero && ((broker.asset.integral() && (static_cast(diff) < 3)) || @@ -994,7 +1064,7 @@ protected: paymentComponents.trackedPrincipalDelta >= beast::zero && paymentComponents.trackedPrincipalDelta <= state.principalOutstanding); BEAST_EXPECT( - paymentComponents.specialCase != detail::PaymentSpecialCase::final || + paymentComponents.specialCase != xrpl::detail::PaymentSpecialCase::final || paymentComponents.trackedPrincipalDelta == state.principalOutstanding); } @@ -1054,7 +1124,7 @@ protected: --state.paymentRemaining; state.previousPaymentDate = state.nextPaymentDate; - if (paymentComponents.specialCase == detail::PaymentSpecialCase::final) + if (paymentComponents.specialCase == xrpl::detail::PaymentSpecialCase::final) { state.paymentRemaining = 0; state.nextPaymentDate = 0; @@ -2071,7 +2141,19 @@ protected: STAmount{broker.asset, state.periodicPayment * Number{15, -1}}, tfLoanOverpayment), fee(XRPAmount{baseFee * (Number{15, -1} / loanPaymentsPerFeeIncrement + 1)}), - ter(temINVALID_FLAG)); + ter(tecNO_PERMISSION)); + + { + env.disableFeature(fixSecurity3_1_3); + env(pay(borrower, + loanKeylet.key, + STAmount{broker.asset, state.periodicPayment * Number{15, -1}}, + tfLoanOverpayment), + fee(XRPAmount{ + baseFee * (Number{15, -1} / loanPaymentsPerFeeIncrement + 1)}), + ter(temINVALID_FLAG)); + env.enableFeature(fixSecurity3_1_3); + } } // Try to send a payment marked as multiple mutually exclusive // payment types. Do not include `txFlags`, so we don't duplicate @@ -2484,7 +2566,7 @@ protected: Number::upward)); auto const initialState = state; - detail::PaymentComponents totalPaid{ + xrpl::detail::PaymentComponents totalPaid{ .trackedValueDelta = 0, .trackedPrincipalDelta = 0, .trackedManagementFeeDelta = 0}; @@ -2500,7 +2582,7 @@ protected: while (state.paymentRemaining > 0) { // Compute the expected principal amount - auto const paymentComponents = detail::computePaymentComponents( + auto const paymentComponents = xrpl::detail::computePaymentComponents( broker.asset.raw(), state.loanScale, state.totalValue, @@ -2512,7 +2594,7 @@ protected: broker.params.managementFeeRate); BEAST_EXPECTS( - paymentComponents.specialCase == detail::PaymentSpecialCase::final || + paymentComponents.specialCase == xrpl::detail::PaymentSpecialCase::final || paymentComponents.trackedValueDelta <= roundedPeriodicPayment, "Delta: " + to_string(paymentComponents.trackedValueDelta) + ", periodic payment: " + to_string(roundedPeriodicPayment)); @@ -2522,7 +2604,7 @@ protected: periodicRate, state.paymentRemaining - 1, broker.params.managementFeeRate); - detail::LoanStateDeltas const deltas = currentTrueState - nextTrueState; + xrpl::detail::LoanStateDeltas const deltas = currentTrueState - nextTrueState; testcase << currencyLabel << " Payment components: " << state.paymentRemaining << ", " << deltas.interest << ", " << deltas.principal << ", " @@ -2531,9 +2613,11 @@ protected: << paymentComponents.trackedInterestPart() << ", " << paymentComponents.trackedManagementFeeDelta << ", " << [&]() -> char const* { - if (paymentComponents.specialCase == detail::PaymentSpecialCase::final) + if (paymentComponents.specialCase == + ::xrpl::detail::PaymentSpecialCase::final) return "final"; - if (paymentComponents.specialCase == detail::PaymentSpecialCase::extra) + if (paymentComponents.specialCase == + ::xrpl::detail::PaymentSpecialCase::extra) return "extra"; return "none"; }(); @@ -2549,7 +2633,7 @@ protected: // IOUs, the difference should be after the 8th digit. Number const diff = totalDue - totalDueAmount; BEAST_EXPECT( - paymentComponents.specialCase == detail::PaymentSpecialCase::final || + paymentComponents.specialCase == xrpl::detail::PaymentSpecialCase::final || diff == beast::zero || (diff > beast::zero && ((broker.asset.integral() && (static_cast(diff) < 3)) || @@ -2561,7 +2645,7 @@ protected: paymentComponents.trackedInterestPart() + paymentComponents.trackedManagementFeeDelta); BEAST_EXPECT( - paymentComponents.specialCase == detail::PaymentSpecialCase::final || + paymentComponents.specialCase == xrpl::detail::PaymentSpecialCase::final || paymentComponents.trackedValueDelta <= roundedPeriodicPayment); BEAST_EXPECT( @@ -2576,10 +2660,10 @@ protected: paymentComponents.trackedPrincipalDelta >= beast::zero && paymentComponents.trackedPrincipalDelta <= state.principalOutstanding); BEAST_EXPECT( - paymentComponents.specialCase != detail::PaymentSpecialCase::final || + paymentComponents.specialCase != xrpl::detail::PaymentSpecialCase::final || paymentComponents.trackedPrincipalDelta == state.principalOutstanding); BEAST_EXPECT( - paymentComponents.specialCase == detail::PaymentSpecialCase::final || + paymentComponents.specialCase == xrpl::detail::PaymentSpecialCase::final || (state.periodicPayment.exponent() - (deltas.principal + deltas.interest + deltas.managementFee - state.periodicPayment) @@ -2617,7 +2701,7 @@ protected: --state.paymentRemaining; state.previousPaymentDate = state.nextPaymentDate; - if (paymentComponents.specialCase == detail::PaymentSpecialCase::final) + if (paymentComponents.specialCase == xrpl::detail::PaymentSpecialCase::final) { state.paymentRemaining = 0; state.nextPaymentDate = 0; @@ -2665,7 +2749,7 @@ protected: env(manage(lender, loanKeylet.key, tfLoanDefault), ter(tecNO_PERMISSION)); }); -#if LOANTODO +#if LOAN_TODO // TODO /* @@ -3589,10 +3673,9 @@ protected: // From FIND-001 testcase << "Batch Bypass Counterparty"; - bool const lendingBatchEnabled = !std::any_of( - Batch::disabledTxTypes.begin(), Batch::disabledTxTypes.end(), [](auto const& disabled) { - return disabled == ttLOAN_BROKER_SET; - }); + bool const lendingBatchEnabled = !std::ranges::any_of( + Batch::disabledTxTypes, + [](auto const& disabled) { return disabled == ttLOAN_BROKER_SET; }); using namespace jtx; using namespace std::chrono_literals; @@ -5232,13 +5315,13 @@ protected: } void - testCoverDepositWithdrawNonTransferableMPT() + testCoverDepositWithdrawNonTransferableMPT(FeatureBitset feature) { testcase("CoverDeposit and CoverWithdraw reject MPT without CanTransfer"); using namespace jtx; using namespace loanBroker; - Env env(*this, all); + Env env(*this, feature); Account const issuer{"issuer"}; Account const alice{"alice"}; @@ -5280,7 +5363,8 @@ protected: env.close(); // Standard Payment path should forbid third-party transfers. - env(pay(alice, pseudoAccount, asset(1)), ter(tecNO_AUTH)); + auto const err = feature[featureMPTokensV2] ? tecNO_PERMISSION : tecNO_AUTH; + env(pay(alice, pseudoAccount, asset(1)), ter(err)); env.close(); // Cover cannot be transferred to broker account @@ -5329,7 +5413,7 @@ protected: } } -#if LOANTODO +#if LOAN_TODO void testLoanPayLateFullPaymentBypassesPenalties() { @@ -5644,7 +5728,7 @@ protected: // Compute a regular periodic due and pay it early (before next due). auto state = getCurrentState(env, broker, loanKeylet); Number const periodicRate = loanPeriodicRate(state.interestRate, state.paymentInterval); - auto const components = detail::computePaymentComponents( + auto const components = xrpl::detail::computePaymentComponents( asset.raw(), state.loanScale, state.totalValue, @@ -5677,7 +5761,7 @@ protected: // Accrued + prepayment-penalty interest based on current periodic // schedule auto const fullPaymentInterest = computeFullPaymentInterest( - detail::loanPrincipalFromPeriodicPayment( + xrpl::detail::loanPrincipalFromPeriodicPayment( after.periodicPayment, periodicRate2, after.paymentRemaining), periodicRate2, env.current()->parentCloseTime(), @@ -5710,7 +5794,7 @@ protected: // window by clamping prevPaymentDate to 'now' for the full-pay path. auto const prevClamped = std::min(after.previousPaymentDate, nowSecs); auto const fullPaymentInterestClamped = computeFullPaymentInterest( - detail::loanPrincipalFromPeriodicPayment( + xrpl::detail::loanPrincipalFromPeriodicPayment( after.periodicPayment, periodicRate2, after.paymentRemaining), periodicRate2, env.current()->parentCloseTime(), @@ -6984,17 +7068,150 @@ protected: BEAST_EXPECT(afterSecondCoverAvailable == 0); } + // Tests that vault withdrawals work correctly when the vault has unrealized + // loss from an impaired loan, ensuring the invariant check properly + // accounts for the loss. + void + testWithdrawReflectsUnrealizedLoss() + { + using namespace jtx; + using namespace loan; + using namespace std::chrono_literals; + + testcase("Vault withdraw reflects sfLossUnrealized"); + + // Test constants + static constexpr std::int64_t INITIAL_FUNDING = 1'000'000; + static constexpr std::int64_t LENDER_INITIAL_IOU = 5'000'000; + static constexpr std::int64_t DEPOSITOR_INITIAL_IOU = 1'000'000; + static constexpr std::int64_t BORROWER_INITIAL_IOU = 100'000; + static constexpr std::int64_t DEPOSIT_AMOUNT = 5'000; + static constexpr std::int64_t PRINCIPAL_AMOUNT = 99; + static constexpr std::uint64_t EXPECTED_SHARES_PER_DEPOSITOR = 5'000'000'000; + static constexpr std::uint32_t PAYMENT_INTERVAL = 600; + static constexpr std::uint32_t PAYMENT_TOTAL = 2; + + Env env(*this, all); + + // Setup accounts + Account const issuer{"issuer"}; + Account const lender{"lender"}; + Account const depositorA{"lpA"}; + Account const depositorB{"lpB"}; + Account const borrower{"borrowerA"}; + + env.fund(XRP(INITIAL_FUNDING), issuer, lender, depositorA, depositorB, borrower); + env.close(); + + // Setup trust lines + PrettyAsset const iouAsset = issuer[iouCurrency]; + env(trust(lender, iouAsset(10'000'000))); + env(trust(depositorA, iouAsset(10'000'000))); + env(trust(depositorB, iouAsset(10'000'000))); + env(trust(borrower, iouAsset(10'000'000))); + env.close(); + + // Fund accounts with IOUs + env(pay(issuer, lender, iouAsset(LENDER_INITIAL_IOU))); + env(pay(issuer, depositorA, iouAsset(DEPOSITOR_INITIAL_IOU))); + env(pay(issuer, depositorB, iouAsset(DEPOSITOR_INITIAL_IOU))); + env(pay(issuer, borrower, iouAsset(BORROWER_INITIAL_IOU))); + env.close(); + + // Create vault and broker, then add deposits from two depositors + auto const broker = createVaultAndBroker(env, iouAsset, lender); + Vault v{env}; + + env(v.deposit({ + .depositor = depositorA, + .id = broker.vaultKeylet().key, + .amount = iouAsset(DEPOSIT_AMOUNT), + }), + ter(tesSUCCESS)); + env(v.deposit({ + .depositor = depositorB, + .id = broker.vaultKeylet().key, + .amount = iouAsset(DEPOSIT_AMOUNT), + }), + ter(tesSUCCESS)); + env.close(); + + // Create a loan + auto const sleBroker = env.le(keylet::loanbroker(broker.brokerID)); + if (!BEAST_EXPECT(sleBroker)) + return; + + auto const loanKeylet = keylet::loan(broker.brokerID, sleBroker->at(sfLoanSequence)); + + env(set(borrower, broker.brokerID, PRINCIPAL_AMOUNT), + sig(sfCounterpartySignature, lender), + paymentTotal(PAYMENT_TOTAL), + paymentInterval(PAYMENT_INTERVAL), + fee(env.current()->fees().base * 2), + ter(tesSUCCESS)); + env.close(); + + // Impair the loan to create unrealized loss + env(manage(lender, loanKeylet.key, tfLoanImpair), ter(tesSUCCESS)); + env.close(); + + // Verify unrealized loss is recorded in the vault + auto const vaultAfterImpair = env.le(broker.vaultKeylet()); + if (!BEAST_EXPECT(vaultAfterImpair)) + return; + + BEAST_EXPECT( + vaultAfterImpair->at(sfLossUnrealized) == broker.asset(PRINCIPAL_AMOUNT).value()); + + // Helper to get share balance for a depositor + auto const shareAsset = vaultAfterImpair->at(sfShareMPTID); + auto const getShareBalance = [&](Account const& depositor) -> std::uint64_t { + auto const token = env.le(keylet::mptoken(shareAsset, depositor.id())); + return token ? token->getFieldU64(sfMPTAmount) : 0; + }; + + // Verify both depositors have equal shares + auto const sharesLpA = getShareBalance(depositorA); + auto const sharesLpB = getShareBalance(depositorB); + BEAST_EXPECT(sharesLpA == EXPECTED_SHARES_PER_DEPOSITOR); + BEAST_EXPECT(sharesLpB == EXPECTED_SHARES_PER_DEPOSITOR); + BEAST_EXPECT(sharesLpA == sharesLpB); + + // Helper to attempt withdrawal + auto const attemptWithdrawShares = [&](Account const& depositor, + std::uint64_t shareAmount, + TER expected) { + STAmount const shareAmt{MPTIssue{shareAsset}, Number(shareAmount)}; + env(v.withdraw( + {.depositor = depositor, .id = broker.vaultKeylet().key, .amount = shareAmt}), + ter(expected)); + env.close(); + }; + + // Regression test: Both depositors should successfully withdraw despite + // unrealized loss. Previously failed with invariant violation: + // "withdrawal must change vault and destination balance by equal + // amount". This was caused by sharesToAssetsWithdraw rounding down, + // creating a mismatch where vaultDeltaAssets * -1 != destinationDelta + // when unrealized loss exists. + attemptWithdrawShares(depositorA, sharesLpA, tesSUCCESS); + attemptWithdrawShares(depositorB, sharesLpB, tesSUCCESS); + } + public: void run() override { -#if LOANTODO +#if LOAN_TODO testLoanPayLateFullPaymentBypassesPenalties(); testLoanCoverMinimumRoundingExploit(); #endif + testWithdrawReflectsUnrealizedLoss(); testInvalidLoanSet(); - testCoverDepositWithdrawNonTransferableMPT(); + auto const all = jtx::testable_amendments(); + testCoverDepositWithdrawNonTransferableMPT(all); + testCoverDepositWithdrawNonTransferableMPT(all - featureMPTokensV2); testPoC_UnsignedUnderflowOnFullPayAfterEarlyPeriodic(); testDisabled(); @@ -7168,5 +7385,4 @@ BEAST_DEFINE_TESTSUITE(Loan, tx, xrpl); BEAST_DEFINE_TESTSUITE_MANUAL(LoanBatch, tx, xrpl); BEAST_DEFINE_TESTSUITE_MANUAL(LoanArbitrary, tx, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/app/MPToken_test.cpp b/src/test/app/MPToken_test.cpp index 81fcec4b7a..a1c57ccbc2 100644 --- a/src/test/app/MPToken_test.cpp +++ b/src/test/app/MPToken_test.cpp @@ -1,19 +1,72 @@ -#include +#include +#include +#include +#include +#include +#include +#include // IWYU pragma: keep +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include #include +#include #include #include +#include +#include #include +#include +#include +#include #include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include +#include +#include +#include #include -namespace xrpl { -namespace test { +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl::test { class MPToken_test : public beast::unit_test::suite { @@ -27,7 +80,7 @@ class MPToken_test : public beast::unit_test::suite // test preflight of MPTokenIssuanceCreate { // If the MPT amendment is not enabled, you should not be able to - // create MPTokenIssuances + // create MPTokenIssuance Env env{*this, features - featureMPTokensV1}; MPTTester mptAlice(env, alice); @@ -817,6 +870,7 @@ class MPToken_test : public beast::unit_test::suite Account const alice("alice"); // issuer Account const bob("bob"); // holder Account const carol("carol"); // holder + auto const MPTokensV2 = features[featureMPTokensV2]; // preflight validation @@ -863,8 +917,10 @@ class MPToken_test : public beast::unit_test::suite mptAlice.authorize({.account = bob}); - for (auto flags : {tfNoRippleDirect, tfLimitQuality}) - env(pay(alice, bob, MPT(10)), txflags(flags), ter(temINVALID_FLAG)); + auto err = !features[featureMPTokensV2] ? ter(temINVALID_FLAG) : ter(temRIPPLE_EMPTY); + env(pay(alice, bob, MPT(10)), txflags(tfNoRippleDirect), err); + err = !features[featureMPTokensV2] ? ter(temINVALID_FLAG) : ter(tesSUCCESS); + env(pay(alice, bob, MPT(10)), txflags(tfLimitQuality), err); } // Invalid combination of send, sendMax, deliverMin, paths @@ -875,31 +931,37 @@ class MPToken_test : public beast::unit_test::suite MPTTester mptAlice(env, alice, {.holders = {carol}}); - mptAlice.create({.ownerCount = 1, .holderCount = 0}); + mptAlice.create( + {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer | tfMPTCanTrade}); mptAlice.authorize({.account = carol}); // sendMax and DeliverMin are valid XRP amount, // but is invalid combination with MPT amount auto const MPT = mptAlice["MPT"]; - env(pay(alice, carol, MPT(100)), sendmax(XRP(100)), ter(temMALFORMED)); + auto err = !MPTokensV2 ? ter(temMALFORMED) : ter(tecPATH_PARTIAL); + env(pay(alice, carol, MPT(100)), sendmax(XRP(100)), err); env(pay(alice, carol, MPT(100)), deliver_min(XRP(100)), ter(temBAD_AMOUNT)); // sendMax MPT is invalid with IOU or XRP auto const USD = alice["USD"]; - env(pay(alice, carol, USD(100)), sendmax(MPT(100)), ter(temMALFORMED)); - env(pay(alice, carol, XRP(100)), sendmax(MPT(100)), ter(temMALFORMED)); + err = !MPTokensV2 ? ter(temMALFORMED) : ter(tecPATH_DRY); + env(pay(alice, carol, USD(100)), sendmax(MPT(100)), err); + err = !MPTokensV2 ? ter(temMALFORMED) : ter(tecPATH_PARTIAL); + env(pay(alice, carol, XRP(100)), sendmax(MPT(100)), err); env(pay(alice, carol, USD(100)), deliver_min(MPT(100)), ter(temBAD_AMOUNT)); env(pay(alice, carol, XRP(100)), deliver_min(MPT(100)), ter(temBAD_AMOUNT)); // sendmax and amount are different MPT issue test::jtx::MPT const MPT1("MPT", makeMptID(env.seq(alice) + 10, alice)); - env(pay(alice, carol, MPT1(100)), sendmax(MPT(100)), ter(temMALFORMED)); - // paths is invalid - env(pay(alice, carol, MPT(100)), path(~USD), ter(temMALFORMED)); + err = !MPTokensV2 ? ter(temMALFORMED) : ter(tecOBJECT_NOT_FOUND); + env(pay(alice, carol, MPT1(100)), sendmax(MPT(100)), err); + // "paths" is invalid in V1 + err = !MPTokensV2 ? ter(temMALFORMED) : ter(tesSUCCESS); + env(pay(alice, carol, MPT(100)), path(~USD), err); } // build_path is invalid if MPT { - Env env{*this, features}; + Env env{*this, features - featureMPTokensV2}; Account const alice("alice"); Account const carol("carol"); @@ -1042,7 +1104,7 @@ class MPToken_test : public beast::unit_test::suite env(credentials::accept(bob, credIssuer1, credType)); env.close(); - MPTTester mptAlice(env, alice); + MPTTester mptAlice(env, alice, MPTInit{}); env.close(); mptAlice.create({ @@ -1084,7 +1146,7 @@ class MPToken_test : public beast::unit_test::suite env(credentials::accept(bob, credIssuer1, credType)); env.close(); - MPTTester mptAlice(env, alice); + MPTTester mptAlice(env, alice, MPTInit{}); env.close(); mptAlice.create({ @@ -1161,7 +1223,7 @@ class MPToken_test : public beast::unit_test::suite env(credentials::accept(carol, credIssuer2, credType)); env.close(); - MPTTester mptAlice(env, alice); + MPTTester mptAlice(env, alice, MPTInit{}); env.close(); mptAlice.create({ @@ -1288,11 +1350,14 @@ class MPToken_test : public beast::unit_test::suite mptAlice.pay(alice, bob, 100); mptAlice.pay(alice, carol, 100); + auto const err = + env.current()->rules().enabled(featureMPTokensV2) ? tecPATH_DRY : tecLOCKED; + // Global lock mptAlice.set({.account = alice, .flags = tfMPTLock}); // Can't send between holders - mptAlice.pay(bob, carol, 1, tecLOCKED); - mptAlice.pay(carol, bob, 2, tecLOCKED); + mptAlice.pay(bob, carol, 1, err); + mptAlice.pay(carol, bob, 2, err); // Issuer can send mptAlice.pay(alice, bob, 3); // Holder can send back to issuer @@ -1303,8 +1368,8 @@ class MPToken_test : public beast::unit_test::suite // Individual lock mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock}); // Can't send between holders - mptAlice.pay(bob, carol, 5, tecLOCKED); - mptAlice.pay(carol, bob, 6, tecLOCKED); + mptAlice.pay(bob, carol, 5, err); + mptAlice.pay(carol, bob, 6, err); // Issuer can send mptAlice.pay(alice, bob, 7); // Holder can send back to issuer @@ -1357,10 +1422,13 @@ class MPToken_test : public beast::unit_test::suite // Payment succeeds if partial payment even if // SendMax is less than deliver amount env(pay(bob, carol, MPT(100)), sendmax(MPT(90)), txflags(tfPartialPayment)); - // 82 to carol, 8 to issuer (90 / 1.1 ~ 81.81 (rounded to nearest) = - // 82) + // 82 to carol, 8 to issuer (90 / 1.1 ~ 81.81 (rounded to nearest in + // v1) = 82) BEAST_EXPECT(mptAlice.checkMPTokenAmount(bob, 690)); - BEAST_EXPECT(mptAlice.checkMPTokenAmount(carol, 282)); + // In V2 the payments are executed via the payment engine and + // the rounding results in a higher quality trade + BEAST_EXPECT( + mptAlice.checkMPTokenAmount(carol, !features[featureMPTokensV2] ? 282 : 281)); } // Insufficient SendMax with no transfer fee @@ -1432,7 +1500,8 @@ class MPToken_test : public beast::unit_test::suite mptAlice.pay(alice, bob, 100); // issuer tries to exceed max amount - mptAlice.pay(alice, bob, 1, tecPATH_PARTIAL); + auto const err = MPTokensV2 ? tecPATH_DRY : tecPATH_PARTIAL; + mptAlice.pay(alice, bob, 1, err); } // Issuer fails trying to send more than the default maximum @@ -1450,7 +1519,8 @@ class MPToken_test : public beast::unit_test::suite mptAlice.pay(alice, bob, maxMPTokenAmount); // issuer tries to exceed max amount - mptAlice.pay(alice, bob, 1, tecPATH_PARTIAL); + auto const err = MPTokensV2 ? tecPATH_DRY : tecPATH_PARTIAL; + mptAlice.pay(alice, bob, 1, err); } // Pay more than max amount fails in the json parser before @@ -1490,6 +1560,7 @@ class MPToken_test : public beast::unit_test::suite // payment between the holders env(pay(bob, carol, MPT(10'000)), sendmax(MPT(10'000)), txflags(tfPartialPayment)); + // Verify the metadata auto const meta = env.meta()->getJson(JsonOptions::none)[sfAffectedNodes.fieldName]; // Issuer got 10 in the transfer fees @@ -1509,7 +1580,8 @@ class MPToken_test : public beast::unit_test::suite // payment between the holders fails without // partial payment - env(pay(bob, carol, MPT(10'000)), sendmax(MPT(10'000)), ter(tecPATH_PARTIAL)); + auto const err = MPTokensV2 ? tecPATH_DRY : tecPATH_PARTIAL; + env(pay(bob, carol, MPT(10'000)), sendmax(MPT(10'000)), ter(err)); } // Pay maximum allowed amount @@ -1565,7 +1637,9 @@ class MPToken_test : public beast::unit_test::suite env.fund(XRP(1'000), alice, bob); STAmount const mpt{MPTID{0}, 100}; - env(pay(alice, bob, mpt), ter(tecOBJECT_NOT_FOUND)); + auto const err = + !features[featureMPTokensV2] ? ter(tecOBJECT_NOT_FOUND) : ter(temBAD_CURRENCY); + env(pay(alice, bob, mpt), err); } // Issuer fails trying to send to an account, which doesn't own MPT for @@ -1834,7 +1908,6 @@ class MPToken_test : public beast::unit_test::suite jrr = env.rpc("json", "sign", to_string(jv1)); BEAST_EXPECT(jrr[jss::result][jss::error] == "invalidParams"); }; - auto toSFieldRef = [](SField const& field) { return std::ref(field); }; auto setMPTFields = [&](SField const& field, Json::Value& jv, bool withAmount = true) { jv[jss::Asset] = to_json(xrpIssue()); jv[jss::Asset2] = to_json(USD.issue()); @@ -1857,22 +1930,6 @@ class MPToken_test : public beast::unit_test::suite // Transactions with amount fields, which can't be MPT. // Transactions with issue fields, which can't be MPT. - // AMMCreate - auto ammCreate = [&](SField const& field) { - Json::Value jv; - jv[jss::TransactionType] = jss::AMMCreate; - jv[jss::Account] = alice.human(); - jv[jss::Amount] = (field.fieldName == sfAmount.fieldName) - ? mpt.getJson(JsonOptions::none) - : "100000000"; - jv[jss::Amount2] = (field.fieldName == sfAmount2.fieldName) - ? mpt.getJson(JsonOptions::none) - : "100000000"; - jv[jss::TradingFee] = 0; - test(jv, field.fieldName); - }; - ammCreate(sfAmount); - ammCreate(sfAmount2); // AMMDeposit auto ammDeposit = [&](SField const& field) { Json::Value jv; @@ -1882,13 +1939,7 @@ class MPToken_test : public beast::unit_test::suite setMPTFields(field, jv); test(jv, field.fieldName); }; - for (SField const& field : - {toSFieldRef(sfAmount), - toSFieldRef(sfAmount2), - toSFieldRef(sfEPrice), - toSFieldRef(sfLPTokenOut), - toSFieldRef(sfAsset), - toSFieldRef(sfAsset2)}) + for (SField const& field : {std::ref(sfEPrice), std::ref(sfLPTokenOut)}) ammDeposit(field); // AMMWithdraw auto ammWithdraw = [&](SField const& field) { @@ -1899,13 +1950,7 @@ class MPToken_test : public beast::unit_test::suite setMPTFields(field, jv); test(jv, field.fieldName); }; - ammWithdraw(sfAmount); - for (SField const& field : - {toSFieldRef(sfAmount2), - toSFieldRef(sfEPrice), - toSFieldRef(sfLPTokenIn), - toSFieldRef(sfAsset), - toSFieldRef(sfAsset2)}) + for (SField const& field : {std::ref(sfEPrice), std::ref(sfLPTokenIn)}) ammWithdraw(field); // AMMBid auto ammBid = [&](SField const& field) { @@ -1915,72 +1960,8 @@ class MPToken_test : public beast::unit_test::suite setMPTFields(field, jv); test(jv, field.fieldName); }; - for (SField const& field : - {toSFieldRef(sfBidMin), - toSFieldRef(sfBidMax), - toSFieldRef(sfAsset), - toSFieldRef(sfAsset2)}) - ammBid(field); - // AMMClawback - auto ammClawback = [&](SField const& field) { - Json::Value jv; - jv[jss::TransactionType] = jss::AMMClawback; - jv[jss::Account] = alice.human(); - jv[jss::Holder] = carol.human(); - setMPTFields(field, jv); - test(jv, field.fieldName); - }; - for (SField const& field : - {toSFieldRef(sfAmount), toSFieldRef(sfAsset), toSFieldRef(sfAsset2)}) - ammClawback(field); - // AMMDelete - auto ammDelete = [&](SField const& field) { - Json::Value jv; - jv[jss::TransactionType] = jss::AMMDelete; - jv[jss::Account] = alice.human(); - setMPTFields(field, jv, false); - test(jv, field.fieldName); - }; - ammDelete(sfAsset); - ammDelete(sfAsset2); - // AMMVote - auto ammVote = [&](SField const& field) { - Json::Value jv; - jv[jss::TransactionType] = jss::AMMVote; - jv[jss::Account] = alice.human(); - jv[jss::TradingFee] = 100; - setMPTFields(field, jv, false); - test(jv, field.fieldName); - }; - ammVote(sfAsset); - ammVote(sfAsset2); - // CheckCash - auto checkCash = [&](SField const& field) { - Json::Value jv; - jv[jss::TransactionType] = jss::CheckCash; - jv[jss::Account] = alice.human(); - jv[sfCheckID.fieldName] = to_string(uint256{1}); - jv[field.fieldName] = mpt.getJson(JsonOptions::none); - test(jv, field.fieldName); - }; - checkCash(sfAmount); - checkCash(sfDeliverMin); - // CheckCreate - { - Json::Value jv; - jv[jss::TransactionType] = jss::CheckCreate; - jv[jss::Account] = alice.human(); - jv[jss::Destination] = carol.human(); - jv[jss::SendMax] = mpt.getJson(JsonOptions::none); - test(jv, jss::SendMax.c_str()); - } - // OfferCreate - { - Json::Value jv = offer(alice, USD(100), mpt); - test(jv, jss::TakerPays.c_str()); - jv = offer(alice, mpt, USD(100)); - test(jv, jss::TakerGets.c_str()); - } + ammBid(sfBidMin); + ammBid(sfBidMax); // PaymentChannelCreate { Json::Value jv; @@ -3060,45 +3041,83 @@ class MPToken_test : public beast::unit_test::suite testcase("Mutate MPTRequireAuth"); using namespace test::jtx; - Env env{*this, features}; - Account const alice("alice"); - Account const bob("bob"); + // test mutating RequireAuth flag on the issuance and its effect on payment authorization + { + Env env{*this, features}; + Account const alice("alice"); + Account const bob("bob"); - MPTTester mptAlice(env, alice, {.holders = {bob}}); - mptAlice.create( - {.ownerCount = 1, - .flags = tfMPTRequireAuth, - .mutableFlags = tmfMPTCanMutateRequireAuth}); + MPTTester mptAlice(env, alice, {.holders = {bob}}); + mptAlice.create( + {.ownerCount = 1, + .flags = tfMPTRequireAuth, + .mutableFlags = tmfMPTCanMutateRequireAuth}); - mptAlice.authorize({.account = bob}); - mptAlice.authorize({.account = alice, .holder = bob}); + mptAlice.authorize({.account = bob}); + mptAlice.authorize({.account = alice, .holder = bob}); - // Pay to bob - mptAlice.pay(alice, bob, 1000); + // Pay to bob + mptAlice.pay(alice, bob, 1000); - // Unauthorize bob - mptAlice.authorize({.account = alice, .holder = bob, .flags = tfMPTUnauthorize}); + // Unauthorize bob + mptAlice.authorize({.account = alice, .holder = bob, .flags = tfMPTUnauthorize}); - // Can not pay to bob - mptAlice.pay(bob, alice, 100, tecNO_AUTH); + // Can not pay to bob + mptAlice.pay(bob, alice, 100, tecNO_AUTH); - // Clear RequireAuth - mptAlice.set({.account = alice, .mutableFlags = tmfMPTClearRequireAuth}); + // Clear RequireAuth + mptAlice.set({.account = alice, .mutableFlags = tmfMPTClearRequireAuth}); - // Can pay to bob - mptAlice.pay(alice, bob, 1000); + // Can pay to bob + mptAlice.pay(alice, bob, 1000); - // Set RequireAuth again - mptAlice.set({.account = alice, .mutableFlags = tmfMPTSetRequireAuth}); + // Set RequireAuth again + mptAlice.set({.account = alice, .mutableFlags = tmfMPTSetRequireAuth}); - // Can not pay to bob since he is not authorized - mptAlice.pay(bob, alice, 100, tecNO_AUTH); + // Can not pay to bob since he is not authorized + mptAlice.pay(bob, alice, 100, tecNO_AUTH); - // Authorize bob again - mptAlice.authorize({.account = alice, .holder = bob}); + // Authorize bob again + mptAlice.authorize({.account = alice, .holder = bob}); - // Can pay to bob again - mptAlice.pay(alice, bob, 100); + // Can pay to bob again + mptAlice.pay(alice, bob, 100); + } + + // Cannot clear RequireAuth when a DomainID is set on the issuance + { + Account const alice{"alice"}; + Account const bob{"bob"}; + Account const credIssuer{"credIssuer"}; + pdomain::Credentials const credentials{ + {.issuer = credIssuer, .credType = "credential"}}; + + Env env{*this, features}; + env.fund(XRP(1000), credIssuer); + env.close(); + + env(pdomain::setTx(credIssuer, credentials)); + env.close(); + auto const domainId = pdomain::getNewDomain(env.meta()); + + MPTTester mptAlice(env, alice, {.holders = {bob}}); + mptAlice.create({ + .ownerCount = 1, + .flags = tfMPTRequireAuth, + .mutableFlags = tmfMPTCanMutateRequireAuth, + .domainID = domainId, + }); + + // Clearing RequireAuth while a DomainID is present must be rejected, + mptAlice.set({ + .account = alice, + .mutableFlags = tmfMPTClearRequireAuth, + .err = tecNO_PERMISSION, + }); + + // Setting RequireAuth (already set) is still allowed, though it has no effect. + mptAlice.set({.account = alice, .mutableFlags = tmfMPTSetRequireAuth}); + } } void @@ -3198,7 +3217,19 @@ class MPToken_test : public beast::unit_test::suite BEAST_EXPECT(mptAlice.isTransferFeePresent()); // Bob can pay carol - mptAlice.pay(bob, carol, 50); + MPT const MPTC = mptAlice; + if (!features[featureMPTokensV2]) + { + mptAlice.pay(bob, carol, 50); + BEAST_EXPECT(env.balance(carol, MPTC) == MPTC(50)); + } + else + { + // The difference is due to the rounding in MPT/DEX. + // 1 MPTC is the transfer fee paid by bob to the issuer. + env(pay(bob, carol, mptAlice(50)), txflags(tfPartialPayment)); + BEAST_EXPECT(env.balance(carol, MPTC) == MPTC(49)); + } // Alice clears MPTCanTransfer mptAlice.set({.account = alice, .mutableFlags = tmfMPTClearCanTransfer}); @@ -3276,7 +3307,7 @@ class MPToken_test : public beast::unit_test::suite void testMultiSendMaximumAmount(FeatureBitset features) { - // Verify that rippleSendMultiMPT correctly enforces MaximumAmount + // Verify that directSendNoLimitMultiMPT correctly enforces MaximumAmount // when the issuer sends to multiple receivers. Pre-fixSecurity3_1_3, // a stale view.read() snapshot caused per-iteration checks to miss // aggregate overflows. Post-fix, a running total is used instead. @@ -3390,6 +3421,3286 @@ class MPToken_test : public beast::unit_test::suite } } + void + testOfferCrossing(FeatureBitset features) + { + testcase("Offer Crossing"); + using namespace test::jtx; + Account const gw = Account("gw"); + Account const alice = Account("alice"); + Account const carol = Account("carol"); + auto const USD = gw["USD"]; + + // Blocking flags + for (auto flags : + {tfMPTCanTrade | tfMPTCanLock | tfMPTCanClawback, // global lock - holder, issuer fail + tfMPTCanTrade | tfMPTRequireAuth, // not authorized - holder fails + tfMPTCanTrade, // holder, issuer succeed + tfMPTCanTrade | tfMPTCanLock, // local lock - holder fails + tfMPTCanTransfer}) // can't trade - holder, issuer fail + { + Env env{*this, features}; + env.fund(XRP(1'000), gw, alice); + env.close(); + + // Use CanClawback flag to distinguish global from local lock + bool const lockMPToken = (flags & (tfMPTCanLock | tfMPTCanClawback)) == tfMPTCanLock; + bool const lockMPTIssue = + (flags & (tfMPTCanLock | tfMPTCanClawback)) == (tfMPTCanLock | tfMPTCanClawback); + bool const requireAuth = (flags & tfMPTRequireAuth) != 0u; + + auto mpt = MPTTester( + {.env = env, + .issuer = gw, + .holders = {alice}, + .pay = 1'000, + .flags = flags, + .authHolder = true}); + MPT const BTC = mpt; + + if (requireAuth) + mpt.authorize({.account = gw, .holder = alice, .flags = tfMPTUnauthorize}); + if (lockMPToken) + { + mpt.set({.holder = alice, .flags = tfMPTLock}); + } + else if (lockMPTIssue) + { + mpt.set({.flags = tfMPTLock}); + } + + auto testOffer = + [&](Account const& account, auto const& buy, auto const& sell, bool buyUSD) { + auto error = [&](auto const err) -> TER { + if (account == gw) + return tesSUCCESS; + return err; + }; + auto const [errBuy, errSell] = [&]() -> std::pair { + // Global lock + if (lockMPTIssue) + return std::make_pair(tecFROZEN, tecFROZEN); + // Local lock + if (lockMPToken) + return std::make_pair(tesSUCCESS, error(tecUNFUNDED_OFFER)); + // MPToken doesn't exist + if (requireAuth) + return std::make_pair(error(tecNO_AUTH), error(tecUNFUNDED_OFFER)); + if (flags & tfMPTCanTransfer) + return std::make_pair(tecNO_PERMISSION, tecNO_PERMISSION); + return std::make_pair(tesSUCCESS, tesSUCCESS); + }(); + + auto const err = buyUSD ? errBuy : errSell; + + auto seq(env.seq(account)); + env(offer(account, buy(10), sell(10)), ter(err)); + env(offer_cancel(account, seq)); + env.close(); + }; + + auto testOffers = [&](Account const& account) { + testOffer(account, XRP, BTC, false); + testOffer(account, BTC, XRP, true); + }; + testOffers(alice); + testOffers(gw); + } + + // MPTokenV2 is disabled + { + Env env{*this, features - featureMPTokensV2}; + + MPTTester mpt(env, gw, {.holders = {alice}}); + + mpt.create({.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer}); + + mpt.authorize({.account = alice}); + mpt.pay(gw, alice, 200); + + env(offer(alice, XRP(100), mpt.mpt(101)), ter(temDISABLED)); + env.close(); + } + + // MPTokenIssuance object doesn't exist + { + Env env(*this); + env.fund(XRP(1'000), gw, alice); + env.close(); + MPT const BTC = MPTTester({.env = env, .issuer = gw, .holders = {alice}, .pay = 100}); + MPT const ETH = MPT(gw, 1); + + env(offer(alice, ETH(10), BTC(10)), ter(tecOBJECT_NOT_FOUND)); + env(offer(alice, BTC(10), ETH(10)), ter(tecUNFUNDED_OFFER)); + } + + // MPToken object doesn't exist and the account is not the issuer of MPT + { + Env env(*this); + env.fund(XRP(1'000), gw, alice); + MPTTester const BTC({.env = env, .issuer = gw, .holders = {alice}, .pay = 100}); + MPTTester const ETH({.env = env, .issuer = gw}); + + env(offer(alice, ETH(10), BTC(10))); + env(offer(alice, BTC(10), ETH(10)), ter(tecUNFUNDED_OFFER)); + } + + // MPTLock flag is set and the account is not the issuer of MPT + { + Account const bob = Account("bob"); + Account const dan = Account("dan"); + Env env(*this); + env.fund(XRP(1'000), gw, alice, carol, bob, dan); + MPTTester BTC( + {.env = env, + .issuer = gw, + .holders = {alice, carol, bob, dan}, + .pay = 100, + .flags = tfMPTCanLock | MPTDEXFlags}); + MPTTester ETH( + {.env = env, + .issuer = gw, + .holders = {alice, carol, bob, dan}, + .pay = 100, + .flags = tfMPTCanLock | MPTDEXFlags}); + + env(offer(bob, ETH(10), BTC(10)), txflags(tfPassive)); + env(offer(dan, BTC(10), ETH(10)), txflags(tfPassive)); + + auto test = [&](auto const& flag, bool gwOwner = false) { + BTC.set({.holder = carol, .flags = flag}); + BTC.set({.holder = alice, .flags = flag}); + + if (gwOwner) + { + // Succeeds if the account is the issuer + env(offer(gw, ETH(1), BTC(1))); + env(offer(gw, BTC(1), ETH(1))); + } + else + { + auto const err = flag == tfMPTLock ? ter(tecUNFUNDED_OFFER) : ter(tesSUCCESS); + env(offer(alice, ETH(1), BTC(1)), err); + // Offer created by not crossed + env(offer(carol, BTC(1), ETH(1))); + BEAST_EXPECT(expectOffers(env, carol, 1, {{BTC(1), ETH(1)}})); + } + }; + + test(tfMPTLock); + test(tfMPTLock, true); + test(tfMPTUnlock); + } + + // MPTRequireAuth flag is set and the account is not authorized + { + Env env(*this); + env.fund(XRP(1'000), gw, alice); + MPTTester BTC( + {.env = env, + .issuer = gw, + .holders = {alice}, + .pay = 100, + .flags = tfMPTRequireAuth | MPTDEXFlags, + .authHolder = true}); + MPTTester const ETH( + {.env = env, + .issuer = gw, + .holders = {alice}, + .pay = 100, + .flags = tfMPTRequireAuth | MPTDEXFlags, + .authHolder = true}); + + BTC.authorize({.account = gw, .holder = alice, .flags = tfMPTUnauthorize}); + + env(offer(alice, ETH(10), BTC(10)), ter(tecUNFUNDED_OFFER)); + + // issuer can create + + env(offer(gw, ETH(10), BTC(10))); + env.close(); + } + + // MPTCanTransfer is not set and the account is not the issuer of MPT + { + Env env(*this); + env.fund(XRP(1'000), gw, alice, carol); + MPTTester BTC( + {.env = env, + .issuer = gw, + .holders = {alice, carol}, + .pay = 100, + .flags = tfMPTCanTrade, + .mutableFlags = tmfMPTCanMutateCanTransfer}); + MPTTester ETH( + {.env = env, + .issuer = gw, + .holders = {alice, carol}, + .pay = 100, + .flags = tfMPTCanTrade | tfMPTCanTransfer, + .mutableFlags = tmfMPTCanMutateCanTransfer}); + + // Can create + env(offer(alice, ETH(10), BTC(10)), txflags(tfPassive)); + BTC.set({.mutableFlags = tmfMPTSetCanTransfer}); + ETH.set({.mutableFlags = tmfMPTClearCanTransfer}); + env(offer(alice, ETH(10), BTC(10)), txflags(tfPassive)); + BEAST_EXPECT(getAccountOffers(env, alice)[jss::offers].size() == 2); + + // issuer can create + env(offer(gw, ETH(10), BTC(10)), txflags(tfPassive)); + env.close(); + + // can cross issuer's offer, other offers are removed + env(offer(carol, BTC(10), ETH(10))); + BEAST_EXPECT(expectOffers(env, alice, 0)); + BEAST_EXPECT(expectOffers(env, gw, 0)); + BEAST_EXPECT(expectOffers(env, carol, 0)); + // can't cross holder's offer, holder's offer is removed + env(offer(alice, ETH(10), BTC(10)), txflags(tfPassive)); + env(offer(carol, BTC(10), ETH(10))); + BEAST_EXPECT(expectOffers(env, alice, 0)); + BEAST_EXPECT(expectOffers(env, carol, 1)); + } + + // MPTCanTrade is disabled + { + Env env(*this); + env.fund(XRP(1'000), gw, alice, carol); + MPTTester const BTC( + {.env = env, + .issuer = gw, + .holders = {alice, carol}, + .pay = 100, + .flags = tfMPTCanTransfer, + .mutableFlags = tmfMPTCanMutateCanTrade}); + MPTTester const ETH( + {.env = env, + .issuer = gw, + .holders = {alice, carol}, + .pay = 100, + .flags = tfMPTCanTrade, + .mutableFlags = tmfMPTCanMutateCanTrade}); + + // Can't create + env(offer(gw, ETH(10), BTC(10)), ter(tecNO_PERMISSION)); + env.close(); + } + + // XRP/MPT + { + Env env{*this, features}; + + MPTTester mpt(env, gw, {.holders = {alice, carol}}); + + mpt.create( + {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer | tfMPTCanTrade}); + auto const MPT = mpt["MPT"]; + + mpt.authorize({.account = alice}); + mpt.pay(gw, alice, 200); + + mpt.authorize({.account = carol}); + mpt.pay(gw, carol, 200); + + env(offer(alice, XRP(100), MPT(101))); + env.close(); + BEAST_EXPECT(expectOffers(env, alice, 1, {{Amounts{XRP(100), MPT(101)}}})); + + env(offer(carol, MPT(101), XRP(100))); + env.close(); + BEAST_EXPECT(expectOffers(env, alice, 0)); + BEAST_EXPECT(expectOffers(env, carol, 0)); + BEAST_EXPECT(mpt.checkMPTokenOutstandingAmount(400)); + BEAST_EXPECT(mpt.checkMPTokenAmount(alice, 99)); + BEAST_EXPECT(mpt.checkMPTokenAmount(carol, 301)); + } + + // IOU/MPT + { + Env env{*this, features}; + + MPTTester mpt(env, gw, {.holders = {alice, carol}}); + + mpt.create( + {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer | tfMPTCanTrade}); + auto const MPT = mpt["MPT"]; + + env(trust(alice, USD(2'000))); + env(pay(gw, alice, USD(1'000))); + env.close(); + + env(trust(carol, USD(2'000))); + env(pay(gw, carol, USD(1'000))); + env.close(); + + mpt.authorize({.account = alice}); + mpt.pay(gw, alice, 200); + + mpt.authorize({.account = carol}); + mpt.pay(gw, carol, 200); + + env(offer(alice, USD(100), MPT(101))); + env.close(); + BEAST_EXPECT(expectOffers(env, alice, 1, {{Amounts{USD(100), MPT(101)}}})); + + env(offer(carol, MPT(101), USD(100))); + env.close(); + + BEAST_EXPECT(env.balance(alice, USD) == USD(1'100)); + BEAST_EXPECT(env.balance(carol, USD) == USD(900)); + BEAST_EXPECT(expectOffers(env, alice, 0)); + BEAST_EXPECT(expectOffers(env, carol, 0)); + BEAST_EXPECT(mpt.checkMPTokenOutstandingAmount(400)); + BEAST_EXPECT(mpt.checkMPTokenAmount(alice, 99)); + BEAST_EXPECT(mpt.checkMPTokenAmount(carol, 301)); + } + + // MPT/MPT + { + Env env{*this, features}; + + MPTTester mpt1(env, gw, {.holders = {alice, carol}}); + mpt1.create( + {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer | tfMPTCanTrade}); + auto const MPT1 = mpt1["MPT1"]; + + MPTTester mpt2(env, gw, {.holders = {alice, carol}, .fund = false}); + mpt2.create( + {.ownerCount = 2, .holderCount = 0, .flags = tfMPTCanTransfer | tfMPTCanTrade}); + auto const MPT2 = mpt2["MPT2"]; + + mpt1.authorize({.account = alice}); + mpt1.authorize({.account = carol}); + mpt1.pay(gw, alice, 200); + mpt1.pay(gw, carol, 200); + + mpt2.authorize({.account = alice}); + mpt2.authorize({.account = carol}); + mpt2.pay(gw, alice, 200); + mpt2.pay(gw, carol, 200); + + env(offer(alice, MPT2(100), MPT1(101))); + env.close(); + BEAST_EXPECT(expectOffers(env, alice, 1, {{Amounts{MPT2(100), MPT1(101)}}})); + + env(offer(carol, MPT1(101), MPT2(100))); + env.close(); + + BEAST_EXPECT(expectOffers(env, alice, 0)); + BEAST_EXPECT(expectOffers(env, carol, 0)); + BEAST_EXPECT(mpt1.checkMPTokenOutstandingAmount(400)); + BEAST_EXPECT(mpt1.checkMPTokenAmount(alice, 99)); + BEAST_EXPECT(mpt1.checkMPTokenAmount(carol, 301)); + BEAST_EXPECT(mpt2.checkMPTokenOutstandingAmount(400)); + BEAST_EXPECT(mpt2.checkMPTokenAmount(alice, 300)); + BEAST_EXPECT(mpt2.checkMPTokenAmount(carol, 100)); + } + } + + void + testCrossAssetPayment(FeatureBitset features) + { + testcase("Cross Asset Payment"); + using namespace test::jtx; + Account const gw = Account("gw"); + Account const alice = Account("alice"); + Account const carol = Account("carol"); + Account const bob = Account("bob"); + auto const USD = gw["USD"]; + + // Loop + { + Env env{*this, features}; + MPTTester mpt(env, gw, {.holders = {carol, bob}}); + + mpt.create( + {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer | tfMPTCanTrade}); + auto const MPT = mpt["MPT"]; + + mpt.authorize({.account = carol}); + mpt.pay(gw, carol, 200); + + mpt.authorize({.account = bob}); + + // holder to holder + env(pay(carol, bob, MPT(1)), + test::jtx::path(~MPT, ~USD, ~MPT), + sendmax(XRP(1)), + txflags(tfPartialPayment), + ter(temBAD_PATH_LOOP)); + env.close(); + + // issuer to holder + env(pay(gw, bob, MPT(1)), + test::jtx::path(~MPT, ~USD, ~MPT), + sendmax(XRP(1)), + txflags(tfPartialPayment), + ter(temBAD_PATH_LOOP)); + env.close(); + + // holder to issuer + env(pay(bob, gw, MPT(1)), + test::jtx::path(~MPT, ~USD, ~MPT), + sendmax(XRP(1)), + txflags(tfPartialPayment), + ter(temBAD_PATH_LOOP)); + env.close(); + } + + // Rippling + { + Env env{*this, features}; + MPTTester mpt(env, gw, {.holders = {carol, bob}}); + + mpt.create( + {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer | tfMPTCanTrade}); + auto const MPT = mpt["MPT"]; + + mpt.authorize({.account = carol}); + mpt.pay(gw, carol, 200); + + mpt.authorize({.account = bob}); + + // holder to holder + env(pay(carol, bob, MPT(1)), + test::jtx::path(~MPT, gw), + sendmax(XRP(1)), + txflags(tfPartialPayment), + ter(temBAD_PATH)); + env.close(); + + // issuer to holder + env(pay(gw, bob, MPT(1)), + test::jtx::path(~MPT, carol), + sendmax(XRP(1)), + txflags(tfPartialPayment), + ter(temBAD_PATH)); + env.close(); + + // holder to issuer + env(pay(bob, gw, MPT(1)), + test::jtx::path(~MPT, carol), + sendmax(XRP(1)), + txflags(tfPartialPayment), + ter(temBAD_PATH)); + env.close(); + } + + // MPTokenV2 is disabled + { + Env env{*this, features - featureMPTokensV2}; + + MPTTester mpt(env, gw, {.holders = {alice}}); + + mpt.create( + {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer | tfMPTCanTrade}); + auto const MPT = mpt["MPT"]; + + mpt.authorize({.account = alice}); + + env(pay(gw, alice, MPT(101)), + test::jtx::path(~MPT), + sendmax(XRP(100)), + txflags(tfPartialPayment), + ter(temMALFORMED)); + } + + { + auto const ed = Account{"ed"}; + Env env{*this, features}; + env.fund(XRP(1'000), gw, alice, carol, bob, ed); + MPTTester BTC( + {.env = env, + .issuer = gw, + .holders = {alice, carol, bob}, + .pay = 1'000, + .flags = tfMPTCanLock | MPTDEXFlags, + .mutableFlags = tmfMPTCanMutateRequireAuth | tmfMPTCanMutateCanTrade | + tmfMPTCanMutateCanTransfer}); + MPTTester ETH( + {.env = env, + .issuer = gw, + .holders = {alice, carol, bob}, + .pay = 1'000, + .flags = tfMPTCanLock | MPTDEXFlags, + .mutableFlags = tmfMPTCanMutateCanTransfer}); + MPTTester const USD( + {.env = env, + .issuer = gw, + .holders = {alice, carol, bob}, + .pay = 1'000, + .flags = MPTDEXFlags | tfMPTCanLock, + .mutableFlags = tmfMPTCanMutateCanTransfer}); + MPTTester const CAD( + {.env = env, + .issuer = gw, + .holders = {alice, carol, bob}, + .pay = 1'000, + .flags = MPTDEXFlags | tfMPTCanLock, + .mutableFlags = tmfMPTCanMutateCanTransfer}); + + env(offer(bob, ETH(1'000), BTC(1'000)), txflags(tfPassive)); + env.close(); + env(offer(bob, BTC(1'000), ETH(1'000)), txflags(tfPassive)); + env.close(); + + // MPTokenIssuance doesn't exist + + env(pay(alice, carol, MPT(gw, 1'000)(10)), sendmax(ETH(10)), ter(tecOBJECT_NOT_FOUND)); + env.close(); + env(pay(alice, carol, ETH(10)), sendmax(MPT(gw)(10)), ter(tecOBJECT_NOT_FOUND)); + env.close(); + + // MPToken object doesn't exist + + // holder and issuer fail + env(pay(ed, carol, BTC(10)), sendmax(ETH(10)), ter(tecNO_AUTH)); + env(pay(carol, ed, BTC(10)), sendmax(ETH(10)), ter(tecNO_AUTH)); + env(pay(ed, gw, BTC(10)), sendmax(ETH(10)), ter(tecNO_AUTH)); + env(pay(gw, ed, BTC(10)), sendmax(ETH(10)), ter(tecNO_AUTH)); + env.close(); + + // MPTRequireAuth is set + + BTC.authorize({.account = ed}); + ETH.authorize({.account = ed}); + env(pay(gw, ed, ETH(100))); + env(pay(gw, ed, BTC(100))); + env.close(); + BTC.set({.mutableFlags = tmfMPTSetRequireAuth}); + // authorize bob to enable the offers trading + BTC.authorize({.account = gw, .holder = bob}); + env.close(); + env(pay(ed, carol, BTC(10)), path(~BTC), sendmax(ETH(10)), ter(tecNO_AUTH)); + env(pay(carol, ed, BTC(10)), path(~BTC), sendmax(ETH(10)), ter(tecNO_AUTH)); + // BTC is transferred from bob to ed, ed is not authorized + env(pay(gw, ed, BTC(10)), path(~BTC), sendmax(ETH(10)), ter(tecNO_AUTH)); + // BTC is transferred from bob to issuer + env(pay(ed, gw, BTC(10)), path(~BTC), sendmax(ETH(10))); + // BTC is transferred from issuer to bob + env(pay(gw, ed, ETH(10)), path(~ETH), sendmax(BTC(10))); + // BTC is transferred from ed to bob, ed is not authorized + env(pay(ed, gw, ETH(10)), path(~ETH), sendmax(BTC(10)), ter(tecNO_AUTH)); + env.close(); + BTC.set({.mutableFlags = tmfMPTClearRequireAuth}); + + // MPTCanTransfer is not set + + // Fail regardless if source/destination is the issuer or + // not since the offer is owned by a holder. + BTC.set({.mutableFlags = tmfMPTClearCanTransfer}); + env(pay(ed, carol, BTC(10)), path(~BTC), sendmax(ETH(10)), ter(tecPATH_PARTIAL)); + env(pay(carol, ed, BTC(10)), path(~BTC), sendmax(ETH(10)), ter(tecPATH_PARTIAL)); + env(pay(ed, carol, ETH(10)), path(~ETH), sendmax(BTC(10)), ter(tecPATH_PARTIAL)); + env(pay(carol, ed, ETH(10)), path(~ETH), sendmax(BTC(10)), ter(tecPATH_PARTIAL)); + // Fail because BTC, which has CanTransfer disabled, is sent to + // bob + env(pay(ed, gw, ETH(10)), path(~ETH), sendmax(BTC(10)), ter(tecPATH_PARTIAL)); + env(pay(ed, gw, BTC(10)), path(~BTC), sendmax(ETH(10)), ter(tesSUCCESS)); + env(pay(gw, ed, ETH(10)), path(~ETH), sendmax(BTC(10)), ter(tesSUCCESS)); + // Fail because BTC, which has CanTransfer disabled, is sent to + // ed + env(pay(gw, ed, BTC(10)), path(~BTC), sendmax(ETH(10)), ter(tecPATH_PARTIAL)); + env.close(); + env(offer(gw, ETH(100), BTC(100)), txflags(tfPassive)); + env.close(); + env(offer(gw, BTC(100), ETH(100)), txflags(tfPassive)); + env.close(); + BEAST_EXPECT(expectOffers(env, bob, 2)); + env(pay(ed, carol, BTC(10)), path(~BTC), sendmax(ETH(10)), ter(tesSUCCESS)); + env(pay(ed, carol, ETH(10)), path(~ETH), sendmax(BTC(10)), ter(tesSUCCESS)); + env(pay(gw, carol, BTC(10)), path(~BTC), sendmax(ETH(10)), ter(tesSUCCESS)); + env.close(); + env(pay(ed, gw, BTC(10)), path(~BTC), sendmax(ETH(10))); + env.close(); + } + // Multiple steps: CAD/USD, USD/BTC, BTC/ETH + { + auto const ed = Account{"ed"}; + Env env{*this, features}; + env.fund(XRP(1'000), gw, alice, carol, bob, ed); + env.close(); + MPTTester BTC( + {.env = env, + .issuer = gw, + .holders = {alice, carol, bob}, + .pay = 1'000, + .flags = tfMPTCanLock | MPTDEXFlags, + .mutableFlags = tmfMPTCanMutateCanTransfer}); + MPTTester ETH( + {.env = env, + .issuer = gw, + .holders = {alice, carol, bob}, + .pay = 1'000, + .flags = tfMPTCanLock | MPTDEXFlags, + .mutableFlags = tmfMPTCanMutateCanTransfer}); + MPTTester USD( + {.env = env, + .issuer = gw, + .holders = {alice, carol, bob}, + .pay = 1'000, + .flags = MPTDEXFlags | tfMPTCanLock, + .mutableFlags = tmfMPTCanMutateCanTransfer}); + MPTTester CAD( + {.env = env, + .issuer = gw, + .holders = {alice, carol, bob}, + .pay = 1'000, + .flags = MPTDEXFlags | tfMPTCanLock, + .mutableFlags = tmfMPTCanMutateCanTransfer}); + // takerGets can transfer if: + // - CanTransfer is set + // - The offer's owner is the issuer + // - BookStep is the last step, which means strand's destination is + // the issuer + // takerPays can transfer if + // - BookStep is the first step, which means strand's source is + // the issuer + // - The offer's owner is the issuer + // - Previous step is BookStep, which transfers per above + // - CanTransfer is set + env(offer(bob, CAD(100), USD(100)), txflags(tfPassive)); + env(offer(bob, USD(100), BTC(100)), txflags(tfPassive)); + env(offer(bob, BTC(100), ETH(100)), txflags(tfPassive)); + env.close(); + BEAST_EXPECT(expectOffers(env, bob, 3)); + BTC.set({.mutableFlags = tmfMPTSetCanTransfer}); + USD.set({.mutableFlags = tmfMPTClearCanTransfer}); + // TakerGets + // fail - CAD/USD is owned by bob + env(pay(alice, carol, ETH(1)), + path(~USD, ~BTC, ~ETH), + sendmax(CAD(1)), + ter(tecPATH_PARTIAL)); + auto seq(env.seq(gw)); + env(offer(gw, USD(1), BTC(1)), txflags(tfPassive)); + env.close(); + // fail - CAD/USD is owned by bob + env(pay(alice, carol, ETH(1)), + path(~USD, ~BTC, ~ETH), + sendmax(CAD(1)), + ter(tecPATH_PARTIAL)); + env.close(); + env(offer_cancel(gw, seq)); + env(offer(gw, CAD(1), USD(1)), txflags(tfPassive)); + env.close(); + BEAST_EXPECT(expectOffers(env, bob, 3)); + // succeed - CAD/USD is owned by issuer + env(pay(alice, carol, ETH(1)), path(~USD, ~BTC, ~ETH), sendmax(CAD(1))); + env.close(); + // bob's CAD/USD is deleted + BEAST_EXPECT(expectOffers(env, bob, 2)); + env(offer(bob, CAD(100), USD(100)), txflags(tfPassive)); + BEAST_EXPECT(expectOffers(env, gw, 0)); + USD.set({.mutableFlags = tmfMPTSetCanTransfer}); + ETH.set({.mutableFlags = tmfMPTClearCanTransfer}); + // fail - BTC/ETH is owned by bob, destination is carol + env(pay(alice, carol, ETH(1)), + path(~USD, ~BTC, ~ETH), + sendmax(CAD(1)), + ter(tecPATH_PARTIAL)); + env.close(); + BEAST_EXPECT(expectOffers(env, bob, 3)); + // succeed - destination is an issuer + env(pay(alice, gw, ETH(1)), path(~USD, ~BTC, ~ETH), sendmax(CAD(1))); + env.close(); + BEAST_EXPECT(expectOffers(env, bob, 3)); + // TakerPays + ETH.set({.mutableFlags = tmfMPTSetCanTransfer}); + CAD.set({.mutableFlags = tmfMPTClearCanTransfer}); + // fail - CAD/USD is owned by bob, source is alice + env(pay(alice, carol, ETH(1)), + path(~USD, ~BTC, ~ETH), + sendmax(CAD(1)), + ter(tecPATH_PARTIAL)); + // succeed - source is the issuer + env(pay(gw, carol, ETH(1)), path(~USD, ~BTC, ~ETH), sendmax(CAD(1))); + env.close(); + env(offer(gw, CAD(1), USD(1)), txflags(tfPassive)); + env.close(); + // succeed - CAD/USD is owned by issuer + env(pay(alice, carol, ETH(1)), path(~USD, ~BTC, ~ETH), sendmax(CAD(1))); + env.close(); + BEAST_EXPECT(expectOffers(env, gw, 0)); + BEAST_EXPECT(expectOffers(env, bob, 2)); + CAD.set({.mutableFlags = tmfMPTSetCanTransfer}); + BTC.set({.mutableFlags = tmfMPTClearCanTransfer}); + env(offer(bob, CAD(1), USD(1)), txflags(tfPassive)); + env(offer(gw, USD(1), BTC(1)), txflags(tfPassive)); + env.close(); + // succeed - USD/BTC is owned by issuer + env(pay(alice, carol, ETH(1)), path(~USD, ~BTC, ~ETH), sendmax(CAD(1))); + env.close(); + BEAST_EXPECT(expectOffers(env, gw, 0)); + } + + // MPTCanTrade is not set + { + Env env{*this, features}; + env.fund(XRP(1'000), gw, alice, carol, bob); + env.close(); + MPTTester BTC( + {.env = env, + .issuer = gw, + .holders = {alice, carol, bob}, + .pay = 1'000, + .flags = tfMPTCanTransfer, + .mutableFlags = tmfMPTCanMutateCanTrade}); + MPTTester const ETH( + {.env = env, + .issuer = gw, + .holders = {alice, carol, bob}, + .pay = 1'000, + .flags = tfMPTCanTransfer | tfMPTCanTrade, + .mutableFlags = tmfMPTCanMutateCanTrade}); + MPTTester const USD( + {.env = env, + .issuer = gw, + .holders = {alice, carol, bob}, + .pay = 1'000, + .flags = tfMPTCanTransfer | tfMPTCanTrade, + .mutableFlags = tmfMPTCanMutateCanTrade}); + + env(pay(alice, carol, ETH(1)), path(~ETH), sendmax(BTC(1)), ter(tecNO_PERMISSION)); + env(pay(alice, carol, BTC(1)), path(~BTC), sendmax(ETH(1)), ter(tecNO_PERMISSION)); + env.close(); + + BTC.set({.mutableFlags = tmfMPTSetCanTrade}); + env(offer(bob, XRP(1), BTC(1))); + env(offer(bob, BTC(1), ETH(1))); + env(offer(bob, ETH(1), USD(1))); + env.close(); + BTC.set({.mutableFlags = tmfMPTClearCanTrade}); + env(pay(gw, carol, USD(1)), + path(~BTC, ~ETH, ~USD), + sendmax(XRP(1)), + ter(tecPATH_PARTIAL)); + env.close(); + BEAST_EXPECT(expectOffers(env, bob, 3)); + } + + // Holders are locked + { + enum LockType { Global, Individual, None }; + struct TestArg + { + Account src; + Account dst; + Account offerOwner; + LockType srcFlag = None; + LockType dstFlag = None; + LockType offerFlagBuy = None; + LockType offerFlagSell = None; + LockType globalFlagBuy = None; + LockType globalFlagSell = None; + TER err = tesSUCCESS; + std::optional errIOU = std::nullopt; + }; + auto getErr = [&](Token const&, TestArg const& arg) { + if constexpr (std::is_same_v) + { + return arg.errIOU.value_or(arg.err); + } + else if constexpr (std::is_same_v) + { + return arg.err; + } + }; + auto getMPT = [&](Env& env) { + MPTTester const BTC( + {.env = env, + .issuer = gw, + .holders = {alice, carol, bob}, + .pay = 100, + .flags = tfMPTCanLock | MPTDEXFlags}); + MPTTester const ETH( + {.env = env, + .issuer = gw, + .holders = {alice, carol, bob}, + .pay = 100, + .flags = tfMPTCanLock | MPTDEXFlags}); + return std::make_pair(BTC, ETH); + }; + auto getIOU = [&](Env& env) { + for (auto const& iou : {gw["BTC"], gw["ETH"]}) + { + for (auto const& a : {alice, carol, bob}) + { + env(fset(a, asfDefaultRipple)); + env.close(); + env(trust(a, iou(200))); + env(pay(gw, a, iou(100))); + env.close(); + } + } + return std::make_pair(gw["BTC"], gw["ETH"]); + }; + auto lock = [&]( + Env& env, Account const& account, Token& token, LockType lock) { + if (lock == None) + return; + if constexpr (std::is_same_v) + { + if (lock == Global) + { + env(fset(gw, asfGlobalFreeze)); + } + else + { + IOU const iou{account, token.currency}; + env(trust(gw, iou(0), tfSetFreeze)); + } + } + else if constexpr (std::is_same_v) + { + if (lock == Global) + { + token.set({.flags = tfMPTLock}); + } + else if (token.issuer() != account) + { + token.set({.holder = account, .flags = tfMPTLock}); + } + } + }; + auto test = [&](auto&& getTokens, TestArg const& arg) { + Env env(*this); + env.fund(XRP(1'000), gw, alice, carol, bob); + + auto [BTC, ETH] = getTokens(env); + + env(offer(arg.offerOwner, ETH(10), BTC(10)), txflags(tfPassive)); + env.close(); + + if (arg.globalFlagBuy != LockType::None) + { + lock(env, gw, ETH, LockType::Global); + } + else + { + lock(env, arg.offerOwner, ETH, arg.offerFlagBuy); + lock(env, arg.src, ETH, arg.srcFlag); + } + if (arg.globalFlagSell != LockType::None) + { + lock(env, gw, BTC, LockType::Global); + } + else + { + lock(env, arg.offerOwner, BTC, arg.offerFlagSell); + lock(env, arg.dst, BTC, arg.dstFlag); + } + + auto const err = getErr(ETH, arg); + env(pay(arg.src, arg.dst, BTC(1)), + path(~BTC), + txflags(tfNoRippleDirect), + sendmax(ETH(1)), + ter(err)); + env.close(); + }; + // clang-format off + std::vector const tests = { + // src, dst, offer's owner are a holder + {.src = alice, .dst = carol, .offerOwner = bob, .srcFlag = Individual, .err = tecPATH_DRY}, + // dst can receive IOU even if the account is frozen + {.src = alice, .dst = carol, .offerOwner = bob, .dstFlag = Individual, .err = tecPATH_DRY, .errIOU = tesSUCCESS}, + {.src = alice, .dst = carol, .offerOwner = bob, .globalFlagBuy = Global, .err = tecPATH_DRY}, + {.src = alice, .dst = carol, .offerOwner = bob, .globalFlagSell = Global, .err = tecPATH_DRY}, + // offer's owner can receive IOU even if the account is frozen + {.src = alice, .dst = carol, .offerOwner = bob, .offerFlagBuy = Individual, .err = + tecPATH_PARTIAL, .errIOU = tesSUCCESS}, + {.src = alice, .dst = carol, .offerOwner = bob, .offerFlagSell = Individual, .err = tecPATH_PARTIAL}, + // src, dst are a holder, offer's owner is an issuer + {.src = alice, .dst = carol, .offerOwner = gw, .srcFlag = Individual, .err = tecPATH_DRY}, + // dst can receive IOU even if the account is frozen + {.src = alice, .dst = carol, .offerOwner = gw, .dstFlag = Individual, .err = tecPATH_DRY, .errIOU = tesSUCCESS}, + {.src = alice, .dst = carol, .offerOwner = gw, .globalFlagBuy = Global, .err = tecPATH_DRY}, + {.src = alice, .dst = carol, .offerOwner = gw, .globalFlagSell = Global, .err = tecPATH_DRY}, + // src is issuer, dst and offer's owner are a holder + // dst can receive IOU even if the account is frozen + {.src = gw, .dst = carol, .offerOwner = bob, .dstFlag = Individual, .err = tecPATH_DRY, .errIOU = tesSUCCESS}, + // offer's owner can receive IOU from an issuer even if takerBuys is frozen, MPT offer is unfunded in this case + {.src = gw, .dst = carol, .offerOwner = bob, .offerFlagBuy = Individual, .err = tecPATH_PARTIAL, .errIOU = tesSUCCESS}, + {.src = gw, .dst = carol, .offerOwner = bob, .offerFlagSell = Individual, .err = tecPATH_PARTIAL}, + // dst is issuer, src and offer's owner are a holder + {.src = alice, .dst = gw, .offerOwner = bob, .srcFlag = Individual, .err = tecPATH_DRY}, + // offer's owner can receive IOU even if the account is frozen + {.src = alice, .dst = gw, .offerOwner = bob, .offerFlagBuy = Individual, .err = tecPATH_PARTIAL, + .errIOU = tesSUCCESS}, + {.src = alice, .dst = gw, .offerOwner = bob, .offerFlagSell = Individual, .err = tecPATH_PARTIAL}, + }; + // clang-format on + + for (auto const& t : tests) + { + test(getMPT, t); + test(getIOU, t); + } + } + { + Env env(*this); + auto const USD = gw["USD"]; + env.fund(XRP(1'000), gw, alice, carol, bob); + MPTTester BTC( + {.env = env, + .issuer = gw, + .holders = {alice, carol, bob}, + .pay = 100, + .flags = tfMPTCanLock | MPTDEXFlags}); + MPTTester ETH( + {.env = env, + .issuer = gw, + .holders = {alice, carol, bob}, + .pay = 100, + .flags = tfMPTCanLock | MPTDEXFlags}); + + env(trust(alice, USD(100))); + env(pay(gw, alice, USD(100))); + env(trust(carol, USD(100))); + + env(offer(alice, XRP(10), ETH(10))); + env(offer(bob, ETH(10), BTC(10))); + env(offer(alice, BTC(10), USD(10))); + env.close(); + + BTC.set({.holder = bob, .flags = tfMPTLock}); + + // Bob's offer is unfunded + env(pay(alice, carol, USD(1)), + path(~(MPT)ETH, ~(MPT)BTC, ~USD), + txflags(tfNoRippleDirect | tfPartialPayment), + sendmax(XRP(1)), + ter(tecPATH_DRY)); + env.close(); + + BTC.set({.holder = bob, .flags = tfMPTUnlock}); + ETH.set({.holder = bob, .flags = tfMPTLock}); + + env(pay(alice, carol, USD(1)), + path(~(MPT)ETH, ~(MPT)BTC, ~USD), + txflags(tfNoRippleDirect | tfPartialPayment), + sendmax(XRP(1)), + ter(tecPATH_DRY)); + } + + // A domain payment should only consume a USD/MPT offer with a domain. + // It must not consume a regular USD/MPT offer. + { + Env env(*this, features); + Account const domainOwner("DomainOwner"); + env.fund(XRP(1'000), gw, alice, carol, bob); + auto const domainID = + setupDomain(env, {alice, bob, carol, gw}, domainOwner, "permdex-cred"); + + MPTTester BTC({.env = env, .issuer = gw, .holders = {alice, carol, bob}, .pay = 100}); + MPTTester ETH({.env = env, .issuer = gw, .holders = {alice, carol, bob}, .pay = 100}); + + auto test = [&](bool withDomain) { + if (withDomain) + { + env(offer(bob, ETH(1), BTC(1)), domain(domainID)); + } + else + { + env(offer(bob, ETH(1), BTC(1))); + } + + auto const err = withDomain ? ter(tesSUCCESS) : ter(tecPATH_DRY); + env(pay(alice, carol, BTC(1)), + path(~(MPT)BTC), + txflags(tfPartialPayment), + sendmax(ETH(1)), + domain(domainID), + err); + }; + test(true); + test(false); + } + + // A hybrid USD/MPT domain offer should still be consumable by + // a regular payment. + { + Env env(*this, features); + Account const domainOwner("DomainOwner"); + env.fund(XRP(1'000), gw, alice, carol, bob); + auto const domainID = + setupDomain(env, {alice, bob, carol, gw}, domainOwner, "permdex-cred"); + + MPTTester BTC({.env = env, .issuer = gw, .holders = {alice, carol, bob}, .pay = 100}); + MPTTester ETH({.env = env, .issuer = gw, .holders = {alice, carol, bob}, .pay = 100}); + + auto test = [&](bool isHybrid) { + auto const flags = isHybrid ? tfHybrid : 0; + env(offer(bob, ETH(1), BTC(1)), txflags(flags), domain(domainID)); + + auto const err = isHybrid ? ter(tesSUCCESS) : ter(tecPATH_DRY); + env(pay(alice, carol, BTC(1)), + path(~(MPT)BTC), + txflags(tfPartialPayment), + sendmax(ETH(1)), + err); + }; + test(true); + test(false); + } + + // MPT/XRP + { + Env env{*this, features}; + MPTTester mpt(env, gw, {.holders = {alice, carol, bob}}); + + mpt.create( + {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer | tfMPTCanTrade}); + auto const MPT = mpt["MPT"]; + + mpt.authorize({.account = alice}); + mpt.pay(gw, alice, 200); + + mpt.authorize({.account = carol}); + mpt.pay(gw, carol, 200); + + mpt.authorize({.account = bob}); + + env(offer(alice, XRP(100), MPT(101))); + env.close(); + BEAST_EXPECT(expectOffers(env, alice, 1, {{Amounts{XRP(100), MPT(101)}}})); + + env(pay(carol, bob, MPT(101)), + test::jtx::path(~MPT), + sendmax(XRP(100)), + txflags(tfPartialPayment)); + env.close(); + + BEAST_EXPECT(expectOffers(env, alice, 0)); + BEAST_EXPECT(mpt.checkMPTokenOutstandingAmount(400)); + BEAST_EXPECT(mpt.checkMPTokenAmount(alice, 99)); + BEAST_EXPECT(mpt.checkMPTokenAmount(bob, 101)); + } + + // MPT/IOU + { + Env env{*this, features}; + + MPTTester mpt(env, gw, {.holders = {alice, carol, bob}}); + + mpt.create( + {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer | tfMPTCanTrade}); + auto const MPT = mpt["MPT"]; + + env(trust(alice, USD(2'000))); + env(pay(gw, alice, USD(1'000))); + env(trust(bob, USD(2'000))); + env(pay(gw, bob, USD(1'000))); + env(trust(carol, USD(2'000))); + env(pay(gw, carol, USD(1'000))); + env.close(); + + mpt.authorize({.account = alice}); + mpt.pay(gw, alice, 200); + + mpt.authorize({.account = carol}); + mpt.pay(gw, carol, 200); + + mpt.authorize({.account = bob}); + + env(offer(alice, USD(100), MPT(101))); + env.close(); + BEAST_EXPECT(expectOffers(env, alice, 1, {{Amounts{USD(100), MPT(101)}}})); + + env(pay(carol, bob, MPT(101)), + test::jtx::path(~MPT), + sendmax(USD(100)), + txflags(tfPartialPayment)); + env.close(); + + BEAST_EXPECT(expectOffers(env, alice, 0)); + BEAST_EXPECT(env.balance(carol, USD) == USD(900)); + BEAST_EXPECT(mpt.checkMPTokenOutstandingAmount(400)); + BEAST_EXPECT(mpt.checkMPTokenAmount(alice, 99)); + BEAST_EXPECT(mpt.checkMPTokenAmount(bob, 101)); + } + + // IOU/MPT + { + Env env{*this, features}; + + MPTTester mpt(env, gw, {.holders = {alice, carol, bob}}); + + mpt.create( + {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer | tfMPTCanTrade}); + auto const MPT = mpt["MPT"]; + + env(trust(alice, USD(2'000)), txflags(tfClearNoRipple)); + env(pay(gw, alice, USD(1'000))); + env(trust(bob, USD(2'000)), txflags(tfClearNoRipple)); + env.close(); + + mpt.authorize({.account = alice}); + env(pay(gw, alice, MPT(200))); + + mpt.authorize({.account = carol}); + env(pay(gw, carol, MPT(200))); + + env(offer(alice, MPT(101), USD(100))); + env.close(); + BEAST_EXPECT(expectOffers(env, alice, 1, {{Amounts{MPT(101), USD(100)}}})); + + env(pay(carol, bob, USD(100)), + test::jtx::path(~USD), + sendmax(MPT(101)), + txflags(tfPartialPayment | tfNoRippleDirect)); + env.close(); + + BEAST_EXPECT(expectOffers(env, alice, 0)); + BEAST_EXPECT(env.balance(alice, USD) == USD(900)); + BEAST_EXPECT(mpt.checkMPTokenAmount(alice, 301)); + BEAST_EXPECT(mpt.checkMPTokenOutstandingAmount(400)); + BEAST_EXPECT(mpt.checkMPTokenAmount(carol, 99)); + BEAST_EXPECT(env.balance(bob, USD) == USD(100)); + } + + // MPT/MPT + { + Env env{*this, features}; + + MPTTester mpt1(env, gw, {.holders = {alice, carol, bob}}); + mpt1.create( + {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer | tfMPTCanTrade}); + auto const MPT1 = mpt1["MPT1"]; + + MPTTester mpt2(env, gw, {.holders = {alice, carol, bob}, .fund = false}); + mpt2.create( + {.ownerCount = 2, .holderCount = 0, .flags = tfMPTCanTransfer | tfMPTCanTrade}); + auto const MPT2 = mpt2["MPT2"]; + + mpt1.authorize({.account = alice}); + mpt1.pay(gw, alice, 200); + mpt2.authorize({.account = alice}); + + mpt2.authorize({.account = carol}); + mpt2.pay(gw, carol, 200); + + mpt1.authorize({.account = bob}); + mpt2.authorize({.account = bob}); + mpt2.pay(gw, bob, 200); + + env(offer(alice, MPT2(100), MPT1(100))); + env.close(); + BEAST_EXPECT(expectOffers(env, alice, 1, {{Amounts{MPT2(100), MPT1(100)}}})); + + // holder to holder + env(pay(carol, bob, MPT1(10)), + test::jtx::path(~MPT1), + sendmax(MPT2(10)), + txflags(tfPartialPayment)); + env.close(); + + BEAST_EXPECT(expectOffers(env, alice, 1)); + BEAST_EXPECT(mpt1.checkMPTokenAmount(alice, 190)); + BEAST_EXPECT(mpt2.checkMPTokenAmount(alice, 10)); + BEAST_EXPECT(mpt1.checkMPTokenOutstandingAmount(200)); + BEAST_EXPECT(mpt2.checkMPTokenOutstandingAmount(400)); + BEAST_EXPECT(mpt2.checkMPTokenAmount(carol, 190)); + BEAST_EXPECT(mpt1.checkMPTokenAmount(bob, 10)); + + // issuer to holder + env(pay(gw, bob, MPT1(20)), + test::jtx::path(~MPT1), + sendmax(MPT2(20)), + txflags(tfPartialPayment)); + env.close(); + + BEAST_EXPECT(expectOffers(env, alice, 1)); + BEAST_EXPECT(mpt1.checkMPTokenAmount(alice, 170)); + BEAST_EXPECT(mpt2.checkMPTokenAmount(alice, 30)); + BEAST_EXPECT(mpt1.checkMPTokenOutstandingAmount(200)); + BEAST_EXPECT(mpt2.checkMPTokenOutstandingAmount(420)); + BEAST_EXPECT(mpt2.checkMPTokenAmount(carol, 190)); + BEAST_EXPECT(mpt1.checkMPTokenAmount(bob, 30)); + + // holder to issuer + env(pay(bob, gw, MPT1(70)), + test::jtx::path(~MPT1), + sendmax(MPT2(70)), + txflags(tfPartialPayment)); + env.close(); + + BEAST_EXPECT(expectOffers(env, alice, 0)); + BEAST_EXPECT(mpt1.checkMPTokenAmount(alice, 100)); + BEAST_EXPECT(mpt2.checkMPTokenAmount(alice, 100)); + BEAST_EXPECT(mpt1.checkMPTokenOutstandingAmount(130)); + BEAST_EXPECT(mpt2.checkMPTokenOutstandingAmount(420)); + BEAST_EXPECT(mpt2.checkMPTokenAmount(carol, 190)); + BEAST_EXPECT(mpt1.checkMPTokenAmount(bob, 30)); + BEAST_EXPECT(mpt2.checkMPTokenAmount(bob, 130)); + } + + // MPT/MPT, issuer owns the offer + { + Env env{*this, features}; + + MPTTester mpt1(env, gw, {.holders = {carol, bob}}); + mpt1.create( + {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer | tfMPTCanTrade}); + auto const MPT1 = mpt1["MPT1"]; + + MPTTester mpt2(env, gw, {.holders = {carol, bob}, .fund = false}); + mpt2.create( + {.ownerCount = 2, .holderCount = 0, .flags = tfMPTCanTransfer | tfMPTCanTrade}); + auto const MPT2 = mpt2["MPT2"]; + + mpt2.authorize({.account = carol}); + mpt2.pay(gw, carol, 200); + + mpt1.authorize({.account = bob}); + mpt2.authorize({.account = bob}); + mpt2.pay(gw, bob, 200); + + env(offer(gw, MPT2(100), MPT1(100))); + env.close(); + BEAST_EXPECT(expectOffers(env, gw, 1, {{Amounts{MPT2(100), MPT1(100)}}})); + + // holder to holder + env(pay(carol, bob, MPT1(10)), + test::jtx::path(~MPT1), + sendmax(MPT2(10)), + txflags(tfPartialPayment)); + env.close(); + + BEAST_EXPECT(expectOffers(env, gw, 1)); + BEAST_EXPECT(mpt1.checkMPTokenOutstandingAmount(10)); + BEAST_EXPECT(mpt2.checkMPTokenOutstandingAmount(390)); + BEAST_EXPECT(mpt2.checkMPTokenAmount(carol, 190)); + BEAST_EXPECT(mpt1.checkMPTokenAmount(bob, 10)); + + // issuer to holder + env(pay(gw, bob, MPT1(20)), + test::jtx::path(~MPT1), + sendmax(MPT2(20)), + txflags(tfPartialPayment)); + env.close(); + + BEAST_EXPECT(expectOffers(env, gw, 1)); + BEAST_EXPECT(mpt1.checkMPTokenOutstandingAmount(30)); + BEAST_EXPECT(mpt2.checkMPTokenOutstandingAmount(390)); + BEAST_EXPECT(mpt2.checkMPTokenAmount(carol, 190)); + BEAST_EXPECT(mpt1.checkMPTokenAmount(bob, 30)); + + // holder to issuer + env(pay(bob, gw, MPT1(70)), + test::jtx::path(~MPT1), + sendmax(MPT2(70)), + txflags(tfPartialPayment)); + env.close(); + + BEAST_EXPECT(expectOffers(env, gw, 0)); + BEAST_EXPECT(mpt1.checkMPTokenOutstandingAmount(30)); + BEAST_EXPECT(mpt2.checkMPTokenOutstandingAmount(320)); + BEAST_EXPECT(mpt2.checkMPTokenAmount(carol, 190)); + BEAST_EXPECT(mpt1.checkMPTokenAmount(bob, 30)); + BEAST_EXPECT(mpt2.checkMPTokenAmount(bob, 130)); + } + + // MPT/MPT, different issuer + { + Env env{*this, features}; + Account const gw1{"gw1"}; + + MPTTester mpt1(env, gw, {.holders = {alice, carol, bob}}); + mpt1.create( + {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer | tfMPTCanTrade}); + auto const MPT1 = mpt1["MPT1"]; + + env.fund(XRP(1'000), gw1); + MPTTester mpt2(env, gw1, {.holders = {alice, carol, bob}, .fund = false}); + mpt2.create( + {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer | tfMPTCanTrade}); + auto const MPT2 = mpt2["MPT2"]; + + mpt1.authorize({.account = alice}); + mpt1.pay(gw, alice, 200); + mpt2.authorize({.account = alice}); + + mpt2.authorize({.account = carol}); + mpt2.pay(gw1, carol, 200); + + mpt1.authorize({.account = bob}); + mpt1.pay(gw, bob, 200); + mpt2.authorize({.account = bob}); + mpt2.pay(gw1, bob, 200); + + mpt1.authorize({.account = gw1}); + mpt1.pay(gw, gw1, 200); + + mpt2.authorize({.account = gw}); + mpt2.pay(gw1, gw, 200); + + env(offer(alice, MPT2(100), MPT1(100))); + env.close(); + BEAST_EXPECT(expectOffers(env, alice, 1, {{Amounts{MPT2(100), MPT1(100)}}})); + + env(pay(carol, bob, MPT1(10)), + test::jtx::path(~MPT1), + sendmax(MPT2(10)), + txflags(tfPartialPayment)); + env.close(); + BEAST_EXPECT(expectOffers(env, alice, 1)); + BEAST_EXPECT(mpt1.checkMPTokenOutstandingAmount(600)); + BEAST_EXPECT(mpt2.checkMPTokenOutstandingAmount(600)); + BEAST_EXPECT(mpt1.checkMPTokenAmount(gw1, 200)); + BEAST_EXPECT(mpt2.checkMPTokenAmount(gw, 200)); + BEAST_EXPECT(mpt2.checkMPTokenAmount(carol, 190)); + BEAST_EXPECT(mpt1.checkMPTokenAmount(bob, 210)); + BEAST_EXPECT(mpt2.checkMPTokenAmount(bob, 200)); + BEAST_EXPECT(mpt1.checkMPTokenAmount(alice, 190)); + BEAST_EXPECT(mpt2.checkMPTokenAmount(alice, 10)); + + env(pay(bob, gw, MPT1(10)), + test::jtx::path(~MPT1), + sendmax(MPT2(10)), + txflags(tfPartialPayment)); + env.close(); + BEAST_EXPECT(expectOffers(env, alice, 1)); + BEAST_EXPECT(mpt1.checkMPTokenOutstandingAmount(590)); + BEAST_EXPECT(mpt2.checkMPTokenOutstandingAmount(600)); + BEAST_EXPECT(mpt1.checkMPTokenAmount(gw1, 200)); + BEAST_EXPECT(mpt2.checkMPTokenAmount(gw, 200)); + BEAST_EXPECT(mpt1.checkMPTokenAmount(bob, 210)); + BEAST_EXPECT(mpt2.checkMPTokenAmount(bob, 190)); + BEAST_EXPECT(mpt1.checkMPTokenAmount(alice, 180)); + BEAST_EXPECT(mpt2.checkMPTokenAmount(alice, 20)); + + env(pay(gw, bob, MPT1(10)), + test::jtx::path(~MPT1), + sendmax(MPT2(10)), + txflags(tfPartialPayment)); + env.close(); + BEAST_EXPECT(expectOffers(env, alice, 1)); + BEAST_EXPECT(mpt1.checkMPTokenOutstandingAmount(590)); + BEAST_EXPECT(mpt2.checkMPTokenOutstandingAmount(600)); + BEAST_EXPECT(mpt1.checkMPTokenAmount(gw1, 200)); + BEAST_EXPECT(mpt2.checkMPTokenAmount(gw, 190)); + BEAST_EXPECT(mpt1.checkMPTokenAmount(bob, 220)); + BEAST_EXPECT(mpt2.checkMPTokenAmount(bob, 190)); + BEAST_EXPECT(mpt1.checkMPTokenAmount(alice, 170)); + BEAST_EXPECT(mpt2.checkMPTokenAmount(alice, 30)); + + env(pay(bob, gw1, MPT1(10)), + test::jtx::path(~MPT1), + sendmax(MPT2(10)), + txflags(tfPartialPayment)); + env.close(); + BEAST_EXPECT(expectOffers(env, alice, 1)); + BEAST_EXPECT(mpt1.checkMPTokenOutstandingAmount(590)); + BEAST_EXPECT(mpt2.checkMPTokenOutstandingAmount(600)); + BEAST_EXPECT(mpt1.checkMPTokenAmount(gw1, 210)); + BEAST_EXPECT(mpt2.checkMPTokenAmount(gw, 190)); + BEAST_EXPECT(mpt1.checkMPTokenAmount(bob, 220)); + BEAST_EXPECT(mpt2.checkMPTokenAmount(bob, 180)); + BEAST_EXPECT(mpt1.checkMPTokenAmount(alice, 160)); + BEAST_EXPECT(mpt2.checkMPTokenAmount(alice, 40)); + + env(pay(gw1, bob, MPT1(10)), + test::jtx::path(~MPT1), + sendmax(MPT2(10)), + txflags(tfPartialPayment)); + env.close(); + BEAST_EXPECT(expectOffers(env, alice, 1)); + BEAST_EXPECT(mpt1.checkMPTokenOutstandingAmount(590)); + BEAST_EXPECT(mpt2.checkMPTokenOutstandingAmount(610)); + BEAST_EXPECT(mpt1.checkMPTokenAmount(gw1, 210)); + BEAST_EXPECT(mpt2.checkMPTokenAmount(gw, 190)); + BEAST_EXPECT(mpt1.checkMPTokenAmount(bob, 230)); + BEAST_EXPECT(mpt2.checkMPTokenAmount(bob, 180)); + BEAST_EXPECT(mpt1.checkMPTokenAmount(alice, 150)); + BEAST_EXPECT(mpt2.checkMPTokenAmount(alice, 50)); + + env(pay(gw, gw1, MPT1(10)), + test::jtx::path(~MPT1), + sendmax(MPT2(10)), + txflags(tfPartialPayment)); + env.close(); + BEAST_EXPECT(expectOffers(env, alice, 1)); + BEAST_EXPECT(mpt1.checkMPTokenOutstandingAmount(590)); + BEAST_EXPECT(mpt2.checkMPTokenOutstandingAmount(610)); + BEAST_EXPECT(mpt1.checkMPTokenAmount(gw1, 220)); + BEAST_EXPECT(mpt2.checkMPTokenAmount(gw, 180)); + BEAST_EXPECT(mpt1.checkMPTokenAmount(alice, 140)); + BEAST_EXPECT(mpt2.checkMPTokenAmount(alice, 60)); + + env(pay(gw1, gw, MPT1(40)), + test::jtx::path(~MPT1), + sendmax(MPT2(40)), + txflags(tfPartialPayment)); + env.close(); + BEAST_EXPECT(expectOffers(env, alice, 0)); + BEAST_EXPECT(mpt1.checkMPTokenOutstandingAmount(550)); + BEAST_EXPECT(mpt2.checkMPTokenOutstandingAmount(650)); + BEAST_EXPECT(mpt1.checkMPTokenAmount(gw1, 220)); + BEAST_EXPECT(mpt2.checkMPTokenAmount(gw, 180)); + BEAST_EXPECT(mpt1.checkMPTokenAmount(alice, 100)); + BEAST_EXPECT(mpt2.checkMPTokenAmount(alice, 100)); + } + + // MPT/IOU IOU/MPT1 + { + Env env = pathTestEnv(*this); + Account const gw1{"gw1"}; + Account const gw2{"gw2"}; + Account const dan{"dan"}; + env.fund(XRP(1'000), gw2); + auto const USD = gw2["USD"]; + + MPTTester mpt(env, gw, {.holders = {alice, carol}}); + mpt.create( + {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer | tfMPTCanTrade}); + auto const MPT = mpt["MPT"]; + mpt.authorize({.account = alice}); + mpt.authorize({.account = carol}); + mpt.pay(gw, carol, 200); + + MPTTester mpt1(env, gw1, {.holders = {bob, dan}}); + mpt1.create( + {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer | tfMPTCanTrade}); + auto const MPT1 = mpt1["MPT1"]; + mpt1.authorize({.account = bob}); + mpt1.pay(gw1, bob, 200); + mpt1.authorize({.account = dan}); + + env(trust(alice, USD(400))); + env(pay(gw2, alice, USD(200))); + env(trust(bob, USD(400))); + + env(offer(alice, MPT(100), USD(100))); + env(offer(bob, USD(100), MPT1(100))); + env.close(); + + env(pay(carol, dan, MPT1(100)), + sendmax(MPT(100)), + path(~USD, ~MPT1), + txflags(tfPartialPayment | tfNoRippleDirect)); + env.close(); + BEAST_EXPECT(expectOffers(env, alice, 0)); + BEAST_EXPECT(expectOffers(env, bob, 0)); + BEAST_EXPECT(mpt.checkMPTokenAmount(carol, 100)); + BEAST_EXPECT(mpt1.checkMPTokenAmount(dan, 100)); + } + + // XRP/MPT AMM + { + Env env{*this, features}; + + fund(env, gw, {alice, carol, bob}, XRP(11'000), {USD(20'000)}); + + MPTTester mpt(env, gw, {.fund = false}); + + mpt.create( + {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer | tfMPTCanTrade}); + auto const MPT = mpt["MPT"]; + + mpt.authorize({.account = alice}); + mpt.authorize({.account = bob}); + mpt.pay(gw, alice, 10'100); + + AMM const amm(env, alice, XRP(10'000), MPT(10'100)); + + env(pay(carol, bob, MPT(100)), + test::jtx::path(~MPT), + sendmax(XRP(100)), + txflags(tfPartialPayment | tfNoRippleDirect)); + env.close(); + + BEAST_EXPECT(amm.expectBalances(XRP(10'100), MPT(10'000), amm.tokens())); + BEAST_EXPECT(mpt.checkMPTokenAmount(bob, 100)); + } + + // IOU/MPT AMM + { + Env env{*this, features}; + + fund(env, gw, {alice, carol, bob}, XRP(11'000), {USD(20'000)}); + + MPTTester mpt(env, gw, {.fund = false}); + + mpt.create( + {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer | tfMPTCanTrade}); + auto const MPT = mpt["MPT"]; + + mpt.authorize({.account = alice}); + mpt.authorize({.account = bob}); + mpt.pay(gw, alice, 10'100); + + AMM const amm(env, alice, USD(10'000), MPT(10'100)); + + env(pay(carol, bob, MPT(100)), + test::jtx::path(~MPT), + sendmax(USD(100)), + txflags(tfPartialPayment | tfNoRippleDirect)); + env.close(); + + BEAST_EXPECT(amm.expectBalances(USD(10'100), MPT(10'000), amm.tokens())); + BEAST_EXPECT(mpt.checkMPTokenAmount(bob, 100)); + } + + // MPT/MPT AMM cross-asset payment + { + Env env{*this, features}; + env.fund(XRP(20'000), gw, alice, carol, bob); + env.close(); + + MPTTester mpt1(env, gw, {.fund = false}); + mpt1.create({.flags = tfMPTCanTransfer | tfMPTCanTrade}); + auto const MPT1 = mpt1["MPT1"]; + mpt1.authorize({.account = alice}); + mpt1.authorize({.account = bob}); + mpt1.pay(gw, alice, 10'100); + + MPTTester mpt2(env, gw, {.fund = false}); + mpt2.create({.flags = tfMPTCanTransfer | tfMPTCanTrade}); + auto const MPT2 = mpt2["MPT1"]; + mpt2.authorize({.account = alice}); + mpt2.authorize({.account = bob}); + mpt2.authorize({.account = carol}); + mpt2.pay(gw, alice, 10'100); + mpt2.pay(gw, carol, 100); + + AMM const amm(env, alice, MPT2(10'000), MPT1(10'100)); + + env(pay(carol, bob, MPT1(100)), + test::jtx::path(~MPT1), + sendmax(MPT2(100)), + txflags(tfPartialPayment | tfNoRippleDirect)); + env.close(); + + BEAST_EXPECT(amm.expectBalances(MPT2(10'100), MPT1(10'000), amm.tokens())); + BEAST_EXPECT(mpt1.checkMPTokenAmount(bob, 100)); + } + + // Multi-steps with AMM + // EUR/MPT1 MPT1/MPT2 MPT2/USD USD/CRN AMM:CRN/MPT MPT/YAN + { + Env env{*this, features}; + auto const USD = gw["USD"]; + auto const EUR = gw["EUR"]; + auto const CRN = gw["CRN"]; + auto const YAN = gw["YAN"]; + + fund( + env, + gw, + {alice, carol, bob}, + XRP(1'000), + {USD(1'000), EUR(1'000), CRN(2'000), YAN(1'000)}); + + auto createMPT = [&]() -> std::pair { + MPTTester mpt(env, gw, {.fund = false}); + mpt.create({.flags = tfMPTCanTransfer | tfMPTCanTrade}); + mpt.authorize({.account = alice}); + mpt.pay(gw, alice, 2'000); + return {mpt, mpt["MPT"]}; + }; + + auto const [mpt1, MPT1] = createMPT(); + auto const [mpt2, MPT2] = createMPT(); + auto const [mpt3, MPT3] = createMPT(); + + env(offer(alice, EUR(100), MPT1(101))); + env(offer(alice, MPT1(101), MPT2(102))); + env(offer(alice, MPT2(102), USD(103))); + env(offer(alice, USD(103), CRN(104))); + env.close(); + AMM const amm(env, alice, CRN(1'000), MPT3(1'104)); + env(offer(alice, MPT3(104), YAN(100))); + + env(pay(carol, bob, YAN(100)), + test::jtx::path(~MPT1, ~MPT2, ~USD, ~CRN, ~MPT3, ~YAN), + sendmax(EUR(100)), + txflags(tfPartialPayment | tfNoRippleDirect)); + env.close(); + + BEAST_EXPECT(env.balance(carol, EUR) == EUR(900)); + BEAST_EXPECT(env.balance(bob, YAN) == YAN(1'100)); + BEAST_EXPECT(amm.expectBalances(CRN(1'104), MPT3(1'000), amm.tokens())); + BEAST_EXPECT(expectOffers(env, alice, 0)); + } + + // Multi-steps with AMM and MPT endpoints + // MPT1/EUR EUR/MPT2 MPT2/USD USD/CRN AMM:CRN/MPT3 MPT3/MPT4 + { + Env env{*this, features}; + auto const USD = gw["USD"]; + auto const EUR = gw["EUR"]; + auto const CRN = gw["CRN"]; + + fund(env, gw, {alice, carol, bob}, XRP(1'000), {USD(1'000), EUR(1'000), CRN(2'000)}); + + auto createMPT = [&]() -> std::pair { + MPTTester mpt(env, gw, {.fund = false}); + mpt.create({.flags = tfMPTCanTransfer | tfMPTCanTrade}); + mpt.authorize({.account = alice}); + mpt.pay(gw, alice, 2'000); + return {mpt, mpt["MPT"]}; + }; + + auto const [mpt1, MPT1] = createMPT(); + auto const [mpt2, MPT2] = createMPT(); + auto const [mpt3, MPT3] = createMPT(); + auto [mpt4, MPT4] = createMPT(); + mpt4.authorize({.account = bob}); + + env(offer(alice, EUR(100), MPT1(101))); + env(offer(alice, MPT1(101), MPT2(102))); + env(offer(alice, MPT2(102), USD(103))); + env(offer(alice, USD(103), CRN(104))); + env.close(); + AMM const amm(env, alice, CRN(1'000), MPT3(1'104)); + env(offer(alice, MPT3(104), MPT4(100))); + + env(pay(carol, bob, MPT4(100)), + test::jtx::path(~MPT1, ~MPT2, ~USD, ~CRN, ~MPT3, ~MPT4), + sendmax(EUR(100)), + txflags(tfPartialPayment | tfNoRippleDirect)); + env.close(); + + BEAST_EXPECT(env.balance(carol, EUR) == EUR(900)); + BEAST_EXPECT(mpt4.checkMPTokenAmount(bob, 100)); + BEAST_EXPECT(amm.expectBalances(CRN(1'104), MPT3(1'000), amm.tokens())); + BEAST_EXPECT(expectOffers(env, alice, 0)); + } + + // Check that limiting step reduces maximumAmount returned by + // MPTEndpointStep::maxPaymentFlow() + { + Env env(*this, features); + + env.fund(XRP(1'000), gw, alice, carol, bob); + + MPTTester usd(env, gw, {.holders = {alice, carol, bob}, .fund = false}); + usd.create( + {.maxAmt = 1'000, + .authorize = MPTCreate::AllHolders, + .pay = {{{alice}, 1'000}}, + .flags = tfMPTCanTransfer | tfMPTCanTrade}); + auto const USD = usd["USD"]; + + MPTTester eur(env, gw, {.holders = {alice, carol, bob}, .fund = false}); + eur.create( + {.maxAmt = 1'000, + .authorize = {{alice, carol}}, + .pay = {{{carol}, 100}}, + .flags = tfMPTCanTransfer | tfMPTCanTrade}); + auto const EUR = eur["EUR"]; + + env(offer(alice, EUR(10), USD(10))); + + env(pay(carol, bob, USD(10)), + sendmax(EUR(10)), + path(~USD), + txflags(tfNoRippleDirect | tfPartialPayment)); + } + { + Env env(*this, features); + env.fund(XRP(1'000), gw, alice, carol, bob); + + auto MUSD = MPTTester( + {.env = env, .issuer = gw, .holders = {alice, carol, bob}, .maxAmt = 1'000}); + MPT const USD = MUSD; + env(pay(gw, alice, USD(800))); + env(offer(gw, XRP(300), USD(300))); + env(pay(carol, bob, USD(300)), + sendmax(XRP(300)), + path(~USD), + txflags(tfPartialPayment)); + BEAST_EXPECT(MUSD.checkMPTokenAmount(bob, 200)); + BEAST_EXPECT(MUSD.checkMPTokenOutstandingAmount(1'000)); + // initial + offer - fees + BEAST_EXPECT(env.balance(gw) == (XRP(1'000) + XRP(200) - txfee(env, 3))); + } + { + Env env(*this, features); + auto const EUR = gw["EUR"]; + env.fund(XRP(1'000), gw, alice, carol, bob); + env.close(); + + env(trust(alice, EUR(1'000))); + env(pay(gw, alice, EUR(300))); + env(trust(bob, EUR(1'000))); + + auto MUSD = MPTTester( + {.env = env, .issuer = gw, .holders = {alice, carol, bob}, .maxAmt = 1'000}); + MPT const USD = MUSD; + + env(pay(gw, alice, USD(800))); + env(offer(gw, XRP(300), USD(300))); + env(offer(alice, USD(300), EUR(300))); + env(pay(carol, bob, EUR(300)), + sendmax(XRP(300)), + path(~USD, ~EUR), + txflags(tfPartialPayment)); + BEAST_EXPECT(MUSD.checkMPTokenAmount(alice, 1'000)); + BEAST_EXPECT(MUSD.checkMPTokenOutstandingAmount(1'000)); + // initial + offer - fees + BEAST_EXPECT(env.balance(gw) == (XRP(1'000) + XRP(200) - txfee(env, 4))); + BEAST_EXPECT(env.balance(bob, EUR) == EUR(200)); + } + } + + void + testPath(FeatureBitset features) + { + testcase("Path"); + using namespace test::jtx; + Account const gw{"gw"}; + Account const gw1{"gw1"}; + Account const alice{"alice"}; + Account const carol{"carol"}; + Account const bob{"bob"}; + Account const dan{"dan"}; + auto const USD = gw["USD"]; + auto const EUR = gw1["EUR"]; + + // MPT can be a mpt end point step or a book-step + + // Direct MPT payment + { + Env env = pathTestEnv(*this); + + MPTTester mpt(env, gw, {.holders = {dan, carol}}); + mpt.create( + {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer | tfMPTCanTrade}); + auto const MPT = mpt["MPT"]; + mpt.authorize({.account = dan}); + mpt.authorize({.account = carol}); + mpt.pay(gw, carol, 200); + + auto const [pathSet, srcAmt, dstAmt] = find_paths(env, carol, dan, MPT(-1)); + BEAST_EXPECT(srcAmt == MPT(200)); + BEAST_EXPECT(dstAmt == MPT(200)); + // Direct payment, no path + BEAST_EXPECT(pathSet.empty()); + } + + // Cross-asset payment via XRP/MPT offer (one step) + { + Env env = pathTestEnv(*this); + + env.fund(XRP(1'000), carol); + + MPTTester mpt(env, gw, {.holders = {alice, dan}}); + + mpt.create( + {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer | tfMPTCanTrade}); + auto const MPT = mpt["MPT"]; + + mpt.authorize({.account = alice}); + mpt.authorize({.account = dan}); + mpt.pay(gw, alice, 200); + + env(offer(alice, XRP(100), MPT(100))); + env.close(); + + auto const [pathSet, srcAmt, dstAmt] = find_paths(env, carol, dan, MPT(-1)); + BEAST_EXPECT(srcAmt == XRP(100)); + BEAST_EXPECT(dstAmt == MPT(100)); + if (BEAST_EXPECT(same(pathSet, stpath(IPE(mpt.issuanceID()))))) + { + // validate a payment works with the path + env(pay(carol, dan, MPT(10)), + path(~MPT), + sendmax(XRP(10)), + txflags(tfNoRippleDirect | tfPartialPayment)); + } + } + + // Cross-asset payment via IOU/MPT offer (one step) + { + Env env = pathTestEnv(*this); + + env.fund(XRP(1'000), carol); + env.fund(XRP(1'000), gw); + + MPTTester mpt(env, gw1, {.holders = {alice, dan}}); + + mpt.create( + {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer | tfMPTCanTrade}); + auto const MPT = mpt["MPT"]; + + mpt.authorize({.account = alice}); + mpt.authorize({.account = dan}); + mpt.pay(gw1, alice, 200); + + env(trust(alice, USD(400))); + env(trust(carol, USD(400))); + env(pay(gw, carol, USD(200))); + + env(offer(alice, USD(100), MPT(100))); + env.close(); + + // No sendMax + STPathSet pathSet; + STAmount srcAmt; + STAmount dstAmt; + std::tie(pathSet, srcAmt, dstAmt) = find_paths(env, carol, dan, MPT(-1)); + BEAST_EXPECT(srcAmt == USD(100)); + BEAST_EXPECT(dstAmt == MPT(100)); + if (BEAST_EXPECT( + pathSet.size() == 1 && same(pathSet, stpath(gw, IPE(mpt.issuanceID()))))) + { + // Validate the payment works with the path + env(pay(carol, dan, MPT(10)), + path(pathSet[0]), + sendmax(USD(10)), + txflags(tfNoRippleDirect | tfPartialPayment)); + } + + // Include sendMax + std::tie(pathSet, srcAmt, dstAmt) = find_paths(env, carol, dan, MPT(-1), USD(-1)); + BEAST_EXPECT(srcAmt == USD(90)); + BEAST_EXPECT(dstAmt == MPT(90)); + if (BEAST_EXPECT(pathSet.size() == 1 && same(pathSet, stpath(IPE(mpt.issuanceID()))))) + { + // validate a payment works with the path + env(pay(carol, dan, MPT(10)), + path(pathSet[0]), + sendmax(USD(10)), + txflags(tfNoRippleDirect | tfPartialPayment)); + } + + // Include source token + std::tie(pathSet, srcAmt, dstAmt) = + find_paths(env, carol, dan, MPT(-1), std::nullopt, USD.currency); + BEAST_EXPECT(srcAmt == USD(80)); + BEAST_EXPECT(dstAmt == MPT(80)); + if (BEAST_EXPECT( + pathSet.size() == 1 && same(pathSet, stpath(gw, IPE(mpt.issuanceID()))))) + { + // validate a payment works with the path + env(pay(carol, dan, MPT(10)), + path(pathSet[0]), + sendmax(USD(10)), + txflags(tfNoRippleDirect | tfPartialPayment)); + } + } + + // Cross-asset payment via MPT/IOU offer (one step) + { + Env env = pathTestEnv(*this); + + env.fund(XRP(1'000), dan); + env.fund(XRP(1'000), gw); + + MPTTester mpt(env, gw1, {.holders = {carol, alice}}); + + mpt.create( + {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer | tfMPTCanTrade}); + auto const MPT = mpt["MPT"]; + + mpt.authorize({.account = carol}); + mpt.authorize({.account = alice}); + mpt.pay(gw1, carol, 200); + + env(trust(dan, USD(400))); + env(trust(alice, USD(400))); + env(pay(gw, alice, USD(200))); + + env(offer(alice, MPT(100), USD(100))); + env.close(); + + // No sendMax + STPathSet pathSet; + STAmount srcAmt; + STAmount dstAmt; + std::tie(pathSet, srcAmt, dstAmt) = find_paths(env, carol, dan, USD(-1)); + BEAST_EXPECT(srcAmt == MPT(100)); + BEAST_EXPECT(dstAmt == USD(100)); + if (BEAST_EXPECT(pathSet.size() == 1 && same(pathSet, stpath(IPE(USD))))) + { + // Validate the payment works with the path + env(pay(carol, dan, USD(10)), + path(pathSet[0]), + sendmax(MPT(10)), + txflags(tfNoRippleDirect | tfPartialPayment)); + } + + // Include sendMax + std::tie(pathSet, srcAmt, dstAmt) = find_paths(env, carol, dan, USD(-1), MPT(-1)); + BEAST_EXPECT(srcAmt == MPT(90)); + BEAST_EXPECT(dstAmt == USD(90)); + if (BEAST_EXPECT(pathSet.size() == 1 && same(pathSet, stpath(IPE(USD))))) + { + // validate a payment works with the path + env(pay(carol, dan, USD(10)), + path(pathSet[0]), + sendmax(MPT(10)), + txflags(tfNoRippleDirect | tfPartialPayment)); + } + + // Include source token + std::tie(pathSet, srcAmt, dstAmt) = + find_paths(env, carol, dan, USD(-1), std::nullopt, MPT.mpt()); + BEAST_EXPECT(srcAmt == MPT(80)); + BEAST_EXPECT(dstAmt == USD(80)); + if (BEAST_EXPECT(pathSet.size() == 1 && same(pathSet, stpath(IPE(USD))))) + { + // validate a payment works with the path + env(pay(carol, dan, USD(10)), + path(pathSet[0]), + sendmax(MPT(10)), + txflags(tfNoRippleDirect | tfPartialPayment)); + } + } + + // Cross-asset payment via MPT1/MPT offer (one step) + { + Env env = pathTestEnv(*this); + + MPTTester mpt(env, gw, {.holders = {alice, dan}}); + MPTTester mpt1(env, gw1, {.holders = {carol}}); + + mpt.create( + {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer | tfMPTCanTrade}); + auto const MPT = mpt["MPT"]; + mpt1.create( + {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer | tfMPTCanTrade}); + auto const MPT1 = mpt1["MPT1"]; + + mpt.authorize({.account = alice}); + mpt.authorize({.account = dan}); + mpt.pay(gw, alice, 200); + + mpt1.authorize({.account = carol}); + mpt1.authorize({.account = alice}); + mpt1.pay(gw1, carol, 200); + + env(offer(alice, MPT1(100), MPT(100))); + env.close(); + + // No sendMax + STPathSet pathSet; + STAmount srcAmt; + STAmount dstAmt; + std::tie(pathSet, srcAmt, dstAmt) = find_paths(env, carol, dan, MPT(-1)); + BEAST_EXPECT(srcAmt == MPT1(100)); + BEAST_EXPECT(dstAmt == MPT(100)); + if (BEAST_EXPECT(pathSet.size() == 1 && same(pathSet, stpath(IPE(mpt.issuanceID()))))) + { + // validate a payment works with the path + env(pay(carol, dan, MPT(10)), + path(pathSet[0]), + sendmax(MPT1(10)), + txflags(tfNoRippleDirect | tfPartialPayment)); + } + + // Include sendMax + std::tie(pathSet, srcAmt, dstAmt) = find_paths(env, carol, dan, MPT(-1), MPT1(-1)); + BEAST_EXPECT(srcAmt == MPT1(90)); + BEAST_EXPECT(dstAmt == MPT(90)); + if (BEAST_EXPECT(pathSet.size() == 1 && same(pathSet, stpath(IPE(mpt.issuanceID()))))) + { + // validate a payment works with the path + env(pay(carol, dan, MPT(10)), + path(pathSet[0]), + sendmax(MPT1(10)), + txflags(tfNoRippleDirect | tfPartialPayment)); + } + + // Include source token + std::tie(pathSet, srcAmt, dstAmt) = + find_paths(env, carol, dan, MPT(-1), std::nullopt, MPT1.mpt()); + BEAST_EXPECT(srcAmt == MPT1(80)); + BEAST_EXPECT(dstAmt == MPT(80)); + if (BEAST_EXPECT(pathSet.size() == 1 && same(pathSet, stpath(IPE(mpt.issuanceID()))))) + { + // validate a payment works with the path + env(pay(carol, dan, MPT(10)), + path(pathSet[0]), + sendmax(MPT1(10)), + txflags(tfNoRippleDirect | tfPartialPayment)); + } + } + + // Cross-asset payment via offers (two steps) + { + Env env = pathTestEnv(*this); + + env.fund(XRP(1'000), carol); + env.fund(XRP(1'000), dan); + + MPTTester mpt(env, gw, {.holders = {alice, bob}}); + + mpt.create( + {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer | tfMPTCanTrade}); + auto const MPT = mpt["MPT"]; + + mpt.authorize({.account = alice}); + mpt.authorize({.account = bob}); + mpt.pay(gw, alice, 200); + mpt.pay(gw, bob, 200); + + env(trust(bob, USD(200))); + env(pay(gw, bob, USD(100))); + env(trust(dan, USD(200))); + env(trust(alice, USD(200))); + + env(offer(alice, XRP(100), MPT(100))); + env(offer(bob, MPT(100), USD(100))); + env.close(); + + // No sendMax + STPathSet pathSet; + STAmount srcAmt; + STAmount dstAmt; + std::tie(pathSet, srcAmt, dstAmt) = find_paths(env, carol, dan, USD(-1)); + BEAST_EXPECT(srcAmt == XRP(100)); + BEAST_EXPECT(dstAmt == USD(100)); + if (BEAST_EXPECT( + pathSet.size() == 1 && same(pathSet, stpath(IPE(mpt.issuanceID()), IPE(USD))))) + { + // validate a payment works with the path + env(pay(carol, dan, USD(10)), + path(pathSet[0]), + sendmax(XRP(10)), + txflags(tfNoRippleDirect | tfPartialPayment)); + } + + // Include sendMax + std::tie(pathSet, srcAmt, dstAmt) = find_paths(env, carol, dan, USD(-1), XRP(100)); + BEAST_EXPECT(srcAmt == XRP(90)); + BEAST_EXPECT(dstAmt == USD(90)); + if (BEAST_EXPECT( + pathSet.size() == 1 && same(pathSet, stpath(IPE(mpt.issuanceID()), IPE(USD))))) + { + // validate a payment works with the path + env(pay(carol, dan, USD(10)), + path(pathSet[0]), + sendmax(XRP(10)), + txflags(tfNoRippleDirect | tfPartialPayment)); + } + } + + // Cross-asset payment via offers (two steps) + // Start/End with mpt/mp1 and book steps in the middle + { + Env env = pathTestEnv(*this); + Account const gw2{"gw2"}; + env.fund(XRP(1'000), gw2); + auto const USD2 = gw2["USD"]; + + MPTTester mpt(env, gw, {.holders = {alice, carol}}); + mpt.create( + {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer | tfMPTCanTrade}); + auto const MPT = mpt["MPT"]; + mpt.authorize({.account = alice}); + mpt.authorize({.account = carol}); + mpt.pay(gw, carol, 200); + + MPTTester mpt1(env, gw1, {.holders = {bob, dan}}); + mpt1.create( + {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer | tfMPTCanTrade}); + auto const MPT1 = mpt1["MPT1"]; + mpt1.authorize({.account = bob}); + mpt1.pay(gw1, bob, 200); + mpt1.authorize({.account = dan}); + + env(trust(alice, USD2(400))); + env(pay(gw2, alice, USD2(200))); + env(trust(bob, USD2(400))); + + env(offer(alice, MPT(100), USD2(100))); + env(offer(bob, USD2(100), MPT1(100))); + env.close(); + + // No sendMax + STPathSet pathSet; + STAmount srcAmt; + STAmount dstAmt; + std::tie(pathSet, srcAmt, dstAmt) = find_paths(env, carol, dan, MPT1(-1)); + BEAST_EXPECT(srcAmt == MPT(100)); + BEAST_EXPECT(dstAmt == MPT1(100)); + if (BEAST_EXPECT( + pathSet.size() == 1 && + same(pathSet, stpath(IPE(USD2), IPE(mpt1.issuanceID()))))) + { + // validate a payment works with the path + env(pay(carol, dan, MPT1(10)), + path(pathSet[0]), + sendmax(MPT(10)), + txflags(tfNoRippleDirect | tfPartialPayment)); + } + + // Include sendMax + std::tie(pathSet, srcAmt, dstAmt) = find_paths(env, carol, dan, MPT1(-1), MPT(-1)); + BEAST_EXPECT(srcAmt == MPT(90)); + BEAST_EXPECT(dstAmt == MPT1(90)); + if (BEAST_EXPECT( + pathSet.size() == 1 && + same(pathSet, stpath(IPE(USD2), IPE(mpt1.issuanceID()))))) + { + // validate a payment works with the path + env(pay(carol, dan, MPT1(10)), + path(pathSet[0]), + sendmax(MPT(10)), + txflags(tfNoRippleDirect | tfPartialPayment)); + } + + // Include source token + std::tie(pathSet, srcAmt, dstAmt) = + find_paths(env, carol, dan, MPT1(-1), std::nullopt, MPT.mpt()); + BEAST_EXPECT(srcAmt == MPT(80)); + BEAST_EXPECT(dstAmt == MPT1(80)); + if (BEAST_EXPECT( + pathSet.size() == 1 && + same(pathSet, stpath(IPE(USD2), IPE(mpt1.issuanceID()))))) + { + // validate a payment works with the path + env(pay(carol, dan, MPT1(10)), + path(pathSet[0]), + sendmax(MPT(10)), + txflags(tfNoRippleDirect | tfPartialPayment)); + } + } + + // Cross-asset payment via offers (two steps) + // Start/End with mpt/mp2 and book steps in the middle + // offers are MPT/MPT + { + Env env = pathTestEnv(*this); + Account const gw2{"gw2"}; + env.fund(XRP(1'000), gw, gw1, gw2, alice, bob, carol, dan); + + MPTTester mpt(env, gw, {.holders = {alice, carol}, .fund = false}); + mpt.create( + {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer | tfMPTCanTrade}); + auto const MPT = mpt["MPT"]; + mpt.authorize({.account = alice}); + mpt.authorize({.account = carol}); + mpt.pay(gw, carol, 200); + + MPTTester mpt1(env, gw1, {.holders = {bob, alice}, .fund = false}); + mpt1.create({.ownerCount = 1, .flags = tfMPTCanTransfer | tfMPTCanTrade}); + auto const MPT1 = mpt1["MPT1"]; + mpt1.authorize({.account = alice}); + mpt1.pay(gw1, alice, 200); + mpt1.authorize({.account = bob}); + + MPTTester mpt2(env, gw2, {.holders = {bob, dan}, .fund = false}); + mpt2.create({.ownerCount = 1, .flags = tfMPTCanTransfer | tfMPTCanTrade}); + auto const MPT2 = mpt2["MPT2"]; + mpt2.authorize({.account = bob}); + mpt2.pay(gw2, bob, 200); + mpt2.authorize({.account = dan}); + + env(offer(alice, MPT(100), MPT1(100))); + env(offer(bob, MPT1(100), MPT2(100))); + env.close(); + + // No sendMax + STPathSet pathSet; + STAmount srcAmt; + STAmount dstAmt; + std::tie(pathSet, srcAmt, dstAmt) = find_paths(env, carol, dan, MPT2(-1)); + BEAST_EXPECT(srcAmt == MPT(100)); + BEAST_EXPECT(dstAmt == MPT2(100)); + if (BEAST_EXPECT( + pathSet.size() == 1 && + same(pathSet, stpath(IPE(mpt1.issuanceID()), IPE(mpt2.issuanceID()))))) + { + // validate a payment works with the path + env(pay(carol, dan, MPT2(10)), + path(pathSet[0]), + sendmax(MPT(10)), + txflags(tfNoRippleDirect | tfPartialPayment)); + } + + // Include sendMax + std::tie(pathSet, srcAmt, dstAmt) = find_paths(env, carol, dan, MPT2(-1), MPT(-1)); + BEAST_EXPECT(srcAmt == MPT(90)); + BEAST_EXPECT(dstAmt == MPT2(90)); + if (BEAST_EXPECT( + pathSet.size() == 1 && + same(pathSet, stpath(IPE(mpt1.issuanceID()), IPE(mpt2.issuanceID()))))) + { + // validate a payment works with the path + env(pay(carol, dan, MPT2(10)), + path(pathSet[0]), + sendmax(MPT(10)), + txflags(tfNoRippleDirect | tfPartialPayment)); + } + + // Include source token + std::tie(pathSet, srcAmt, dstAmt) = + find_paths(env, carol, dan, MPT2(-1), std::nullopt, MPT.mpt()); + BEAST_EXPECT(srcAmt == MPT(80)); + BEAST_EXPECT(dstAmt == MPT2(80)); + if (BEAST_EXPECT( + pathSet.size() == 1 && + same(pathSet, stpath(IPE(mpt1.issuanceID()), IPE(mpt2.issuanceID()))))) + { + // validate a payment works with the path + env(pay(carol, dan, MPT2(10)), + path(pathSet[0]), + sendmax(MPT(10)), + txflags(tfNoRippleDirect | tfPartialPayment)); + } + } + + // verify no MPT rippling + { + Env env = pathTestEnv(*this); + Account const gw{"gw"}; + Account const gw1{"gw1"}; + Account const carol{"carol"}; + Account const bob{"bob"}; + Account const dan{"dan"}; + Account const john{"john"}; + Account const sean{"sean"}; + + env.fund(XRP(1'000'000), gw); + env.fund(XRP(1'000'000), gw1); + env.fund(XRP(1'000'000), carol); + env.fund(XRP(1'000'000), dan); + env.fund(XRP(1'000'000), bob); + env.fund(XRP(1'000'000), john); + env.fund(XRP(1'000'000), sean); + env.close(); + + MPTTester usd(env, gw, {.holders = {carol, dan}, .fund = false}); + usd.create( + {.authorize = MPTCreate::AllHolders, + .pay = {{MPTCreate::AllHolders, 100}}, + .flags = tfMPTCanTransfer | tfMPTCanTrade}); + auto const USD = usd["USD"]; + env(offer(carol, XRP(100), USD(100))); + + MPTTester gbp(env, gw, {.holders = {bob, sean}, .fund = false}); + gbp.create( + {.authorize = MPTCreate::AllHolders, + .pay = {{{bob}, 100}}, + .flags = tfMPTCanTransfer | tfMPTCanTrade}); + auto const GBP = gbp["GBP"]; + + MPTTester usd1(env, gw1, {.holders = {bob, dan}, .fund = false}); + usd1.create( + {.authorize = MPTCreate::AllHolders, + .pay = {{{dan}, 100}}, + .flags = tfMPTCanTransfer | tfMPTCanTrade}); + auto const USD1 = usd1["USD1"]; + env(offer(bob, USD1(100), GBP(100))); + + // dan has USD/gw and USD1/gw. Had USD been IOU, it would have + // been able to ripple through dan's account. + auto const [pathSet, srcAmt, dstAmt] = find_paths(env, john, sean, GBP(-1), XRP(-1)); + BEAST_EXPECT(pathSet.empty()); + + env(pay(john, sean, GBP(10)), + sendmax(XRP(20)), + path(~USD, dan, gw1, ~GBP), + txflags(tfNoRippleDirect | tfPartialPayment), + ter(temBAD_PATH)); + } + } + + void + testCheck(FeatureBitset features) + { + testcase("Check Create/Cash"); + + using namespace test::jtx; + Account const gw{"gw"}; + Account const alice{"alice"}; + Account const carol{"carol"}; + + // MPTokensV2 is disabled + { + Env env{*this, features - featureMPTokensV2}; + + MPTTester mpt(env, gw, {.holders = {alice}}); + mpt.create( + {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer | tfMPTCanTrade}); + auto const MPT = mpt["MPT"]; + mpt.authorize({.account = alice}); + + uint256 const checkId{keylet::check(gw, env.seq(gw)).key}; + + env(check::create(gw, alice, MPT(100)), ter(temDISABLED)); + env.close(); + + env(check::cash(alice, checkId, MPT(100)), ter(temDISABLED)); + env.close(); + } + + // Insufficient funds + { + Env env{*this, features}; + Account const carol{"carol"}; + + MPTTester mpt(env, gw, {.holders = {alice, carol}}); + mpt.create( + {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer | tfMPTCanTrade}); + auto const MPT = mpt["MPT"]; + mpt.authorize({.account = alice}); + mpt.pay(gw, alice, 50); + + uint256 const checkId{keylet::check(alice, env.seq(alice)).key}; + + // can create + env(check::create(alice, carol, MPT(100))); + env.close(); + + // can't cash since alice only has 50 of MPT + env(check::cash(carol, checkId, MPT(100)), ter(tecPATH_PARTIAL)); + env.close(); + + // can cash if DeliverMin is set + // carol is not authorized, MPToken is authorized by CheckCash + env(check::cash(carol, checkId, check::DeliverMin(MPT(50)))); + env.close(); + BEAST_EXPECT(mpt.checkMPTokenAmount(carol, 50)); + BEAST_EXPECT(mpt.checkMPTokenOutstandingAmount(50)); + } + + // Exceed max amount + { + Env env{*this, features}; + + MPTTester mpt(env, gw, {.holders = {alice}}); + mpt.create( + {.maxAmt = 100, + .ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanTransfer | tfMPTCanTrade}); + auto const MPT = mpt["MPT"]; + + uint256 const checkId{keylet::check(gw, env.seq(gw)).key}; + + // can create + env(check::create(gw, alice, MPT(200))); + env.close(); + + // can't cash since the outstanding amount exceeds max amount + env(check::cash(alice, checkId, MPT(200)), ter(tecPATH_PARTIAL)); + env.close(); + + // can cash if DeliverMin is set + env(check::cash(alice, checkId, check::DeliverMin(MPT(100)))); + env.close(); + BEAST_EXPECT(mpt.checkMPTokenAmount(alice, 100)); + BEAST_EXPECT(mpt.checkMPTokenOutstandingAmount(100)); + } + + // MPTokenIssuance object doesn't exist + { + Env env{*this, features}; + env.fund(XRP(1'000), gw, alice, carol); + env(check::create(alice, carol, MPT(gw)(50)), ter(tecOBJECT_NOT_FOUND)); + env.close(); + auto BTC = MPTTester({.env = env, .issuer = gw}); + uint256 const chkId{getCheckIndex(gw, env.seq(gw))}; + env(check::cash(carol, chkId, MPT(gw)(1)), ter(tecNO_ENTRY)); + env.close(); + } + + // MPToken doesn't exist - can create check since MPToken will be + // automatically created on cash check + { + Env env{*this, features}; + env.fund(XRP(1'000), gw, alice, carol); + auto BTC = MPTTester({.env = env, .issuer = gw}); + uint256 const chkId{getCheckIndex(alice, env.seq(alice))}; + env(check::create(alice, carol, BTC(50))); + env.close(); + + // But cashing fails if alice doesn't have MPToken + env(check::cash(carol, chkId, BTC(1)), ter(tecPATH_PARTIAL)); + env.close(); + } + + // MPTLock is set + { + Env env{*this, features}; + env.fund(XRP(1'000), gw, alice, carol); + env.close(); + auto mpt = MPTTester( + {.env = env, + .issuer = gw, + .holders = {alice, carol}, + .pay = 100, + .flags = MPTDEXFlags | tfMPTCanLock}); + + mpt.set({.flags = tfMPTLock}); + + // Create Check fails, holder or issuer as destination + env(check::create(alice, carol, mpt(10)), ter(tecLOCKED)); + env.close(); + env(check::create(gw, carol, mpt(10)), ter(tecLOCKED)); + env.close(); + + mpt.set({.flags = tfMPTUnlock}); + + // Create Check succeeds, holder or issuer as destination + uint256 const chkIdAlice{getCheckIndex(alice, env.seq(alice))}; + env(check::create(alice, carol, mpt(10))); + env.close(); + uint256 const chkIdGw{getCheckIndex(gw, env.seq(gw))}; + env(check::create(gw, carol, mpt(10))); + env.close(); + + mpt.set({.flags = tfMPTLock}); + + // Cash Check fails, holder and issuer env(check::cash(carol, + // chkIdAlice, mpt(1)), ter(tecPATH_PARTIAL)); // tec is different + // if the source is the issuer (this is consistent with IOU) + env(check::cash(carol, chkIdGw, mpt(2)), ter(tecLOCKED)); + env.close(); + + mpt.set({.flags = tfMPTUnlock}); + + // Cash Check succeeds, holder and issuer. + env(check::cash(carol, chkIdAlice, mpt(1))); + env(check::cash(carol, chkIdGw, mpt(2))); + + // Individual lock + mpt.set({.holder = alice, .flags = tfMPTLock}); + env(check::create(alice, carol, mpt(10)), ter(tecLOCKED)); + env.close(); + env(check::create(carol, alice, mpt(10)), ter(tecLOCKED)); + env.close(); + + mpt.set({.holder = alice, .flags = tfMPTUnlock}); + uint256 const chkId1{getCheckIndex(alice, env.seq(alice))}; + env(check::create(alice, carol, mpt(10))); + env.close(); + uint256 const chkId2{getCheckIndex(gw, env.seq(gw))}; + env(check::create(gw, alice, mpt(10))); + env.close(); + uint256 const chkId3{getCheckIndex(alice, env.seq(alice))}; + env(check::create(alice, gw, mpt(10))); + env.close(); + uint256 const chkId4{getCheckIndex(gw, env.seq(gw))}; + env(check::create(gw, alice, mpt(10))); + env.close(); + mpt.set({.holder = alice, .flags = tfMPTLock}); + env(check::cash(carol, chkId1, mpt(1)), ter(tecPATH_PARTIAL)); + env(check::cash(alice, chkId2, mpt(1)), ter(tecLOCKED)); + env(check::cash(gw, chkId3, mpt(1)), ter(tecPATH_PARTIAL)); + env(check::cash(alice, chkId4, mpt(1)), ter(tecLOCKED)); + } + + // MPTRequireAuth flag is set and the account is not authorized. + // Can create check, which is consistent with the trustlines. + // It should fail on cash check. + { + Env env{*this, features}; + env.fund(XRP(1'000), gw, alice, carol); + auto BTC = MPTTester( + {.env = env, + .issuer = gw, + .holders = {alice, carol}, + .flags = tfMPTRequireAuth | MPTDEXFlags}); + uint256 const chkId{getCheckIndex(alice, env.seq(alice))}; + env(check::create(alice, carol, BTC(50))); + env.close(); + + // Authorize alice + BTC.authorize({.account = gw, .holder = alice}); + env(pay(gw, alice, BTC(100))); + + // carol is still not authorized + env(check::cash(carol, chkId, BTC(10)), ter(tecNO_AUTH)); + env.close(); + + // authorize carol, can cash now + BTC.authorize({.account = gw, .holder = carol}); + env(check::cash(carol, chkId, BTC(10))); + env.close(); + } + + // MPTCanTransfer disabled + { + Env env{*this, features}; + env.fund(XRP(1'000), gw, alice, carol); + env.close(); + + MPTTester mpt( + {.env = env, + .issuer = gw, + .holders = {alice, carol}, + .flags = tfMPTCanTrade, + .mutableFlags = tmfMPTCanMutateCanTransfer}); + + // src is issuer + uint256 checkId{keylet::check(gw, env.seq(gw)).key}; + + // can create + env(check::create(gw, alice, mpt(100))); + env.close(); + + // can cash since source is issuer + env(check::cash(alice, checkId, mpt(100))); + env.close(); + + BEAST_EXPECT(env.balance(alice, mpt) == mpt(100)); + BEAST_EXPECT(env.balance(gw, mpt) == mpt(-100)); + + // dst is issuer + checkId = keylet::check(alice, env.seq(alice)).key; + + // can create + env(check::create(alice, gw, mpt(100))); + env.close(); + + // can cash since source is issuer + env(check::cash(gw, checkId, mpt(100))); + env.close(); + + BEAST_EXPECT(env.balance(alice, mpt) == mpt(0)); + BEAST_EXPECT(env.balance(gw, mpt) == mpt(0)); + + // neither src nor dst is issuer, can still create + checkId = keylet::check(alice, env.seq(alice)).key; + env(check::create(alice, carol, mpt(100))); + env.close(); + + // can't cash + env(check::cash(carol, checkId, mpt(10)), ter(tecPATH_PARTIAL)); + env.close(); + + // can cash now + mpt.set({.account = gw, .mutableFlags = tmfMPTSetCanTransfer}); + env(pay(gw, alice, mpt(10))); + env.close(); + env(check::cash(carol, checkId, mpt(10))); + env.close(); + } + + // MPTCanTrade disabled + { + Env env{*this, features}; + env.fund(XRP(1'000), gw, alice, carol); + env.close(); + + MPTTester mpt( + {.env = env, + .issuer = gw, + .holders = {alice, carol}, + .flags = tfMPTCanTransfer, + .mutableFlags = tmfMPTCanMutateCanTrade}); + + uint256 checkId{keylet::check(gw, env.seq(gw)).key}; + + // can't create + env(check::create(gw, alice, mpt(100)), ter(tecNO_PERMISSION)); + env.close(); + mpt.set({.account = gw, .mutableFlags = tmfMPTSetCanTrade}); + + // can't cash + checkId = keylet::check(gw, env.seq(gw)).key; + env(check::create(gw, carol, mpt(100))); + env.close(); + mpt.set({.account = gw, .mutableFlags = tmfMPTClearCanTrade}); + env(check::cash(carol, checkId, mpt(10)), ter(tecNO_PERMISSION)); + env.close(); + } + + // MPTokenIssuance object doesn't exist + { + Env env{*this, features}; + env.fund(XRP(1'000), gw, alice, carol); + auto USD = MPTTester({.env = env, .issuer = gw, .holders = {alice}}); + uint256 const chkId{getCheckIndex(alice, env.seq(alice))}; + env(check::create(alice, carol, USD(1))); + env.close(); + + // temMALFORMED because MPT is not USD. It doesn't matter if it + // exists or not + env(check::cash(carol, chkId, MPT(alice)(1)), ter(temMALFORMED)); + env.close(); + } + + // MPToken object doesn't exist and the account is not the issuer of MPT + { + Env env{*this, features}; + env.fund(XRP(1'000), gw, alice, carol); + + auto BTC = MPTTester({.env = env, .issuer = gw, .holders = {alice}, .pay = 1'000}); + + uint256 const chkId{getCheckIndex(alice, env.seq(alice))}; + + env(check::create(alice, carol, BTC(1))); + env.close(); + + // MPToken is automatically created + env(check::cash(carol, chkId, BTC(1))); + env.close(); + } + + // MPTRequireAuth flag is set and the account is not authorized. + { + Env env{*this, features}; + env.fund(XRP(1'000), gw, alice, carol); + + auto BTC = MPTTester( + {.env = env, + .issuer = gw, + .holders = {alice}, + .flags = tfMPTRequireAuth | MPTDEXFlags, + .authHolder = true}); + uint256 const chkId{getCheckIndex(alice, env.seq(alice))}; + env(check::create(alice, carol, BTC(1))); + env.close(); + + env(check::cash(carol, chkId, BTC(1)), ter(tecPATH_PARTIAL)); + env.close(); + } + + // MPTCanTransfer is not set and the account is not the issuer of MPT + { + Env env{*this, features}; + env.fund(XRP(1'000), gw, alice, carol); + + auto EUR = MPTTester( + {.env = env, .issuer = gw, .holders = {alice, carol}, .flags = tfMPTCanTrade}); + uint256 const chkId{getCheckIndex(alice, env.seq(alice))}; + // alice can create + env(check::create(alice, carol, EUR(1))); + env.close(); + + // carol can't cash + env(check::cash(carol, chkId, EUR(1)), ter(tecPATH_PARTIAL)); + env.close(); + + // if issuer creates a check then carol can cash since + // it's a transfer from the issuer + uint256 const chkId1{getCheckIndex(gw, env.seq(gw))}; + // alice can't create since CanTransfer is not set + env(check::create(gw, carol, EUR(1))); + env.close(); + + env(check::cash(carol, chkId1, EUR(1))); + env.close(); + } + + // Can create check if src/dst don't own MPT + { + Env env{*this, features}; + + MPTTester mpt(env, gw); + mpt.create( + {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer | tfMPTCanTrade}); + auto const MPT = mpt["MPT"]; + + env.fund(XRP(1'000), alice, carol); + + // src is issuer + uint256 const checkId{keylet::check(alice, env.seq(alice)).key}; + + // can create + env(check::create(alice, carol, MPT(100))); + env.close(); + + // authorize/fund alice + mpt.authorize({.account = alice}); + mpt.pay(gw, alice, 100); + + // carol can cash the check. MPToken is created automatically + env(check::cash(carol, checkId, MPT(100))); + env.close(); + + BEAST_EXPECT(mpt.checkMPTokenAmount(carol, 100)); + BEAST_EXPECT(mpt.checkMPTokenOutstandingAmount(100)); + } + + // Normal create/cash + { + Env env{*this, features}; + + MPTTester mpt(env, gw, {.holders = {alice}}); + mpt.create( + {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer | tfMPTCanTrade}); + auto const MPT = mpt["MPT"]; + mpt.authorize({.account = alice}); + + uint256 const checkId{keylet::check(gw, env.seq(gw)).key}; + + env(check::create(gw, alice, MPT(100))); + env.close(); + + env(check::cash(alice, checkId, MPT(100))); + env.close(); + + BEAST_EXPECT(mpt.checkMPTokenAmount(alice, 100)); + BEAST_EXPECT(mpt.checkMPTokenOutstandingAmount(100)); + } + } + + void + testAMMClawback(FeatureBitset features) + { + using namespace jtx; + testcase("AMMClawback"); + Account const gw{"gw"}; + Account const alice{"alice"}; + auto const USD = gw["USD"]; + + // MPTokenIssuance object doesn't exist + { + Env env(*this, features); + env.fund(XRP(1'000), gw, alice); + MPTTester const BTC({.env = env, .issuer = gw}); + AMM const amm(env, gw, BTC(100), USD(100)); + env(amm::ammClawback(gw, alice, USD, MPT(alice), std::nullopt), ter(terNO_AMM)); + env(amm::ammClawback(gw, alice, USD, BTC, MPT(alice)(100)), ter(temBAD_AMOUNT)); + } + + // MPTLock flag is set and the account is not the issuer of MPT - + // can still clawback since the issuer clawbacks + { + Env env(*this, features); + env.fund(XRP(100'000), gw, alice); + env.close(); + + env(fset(gw, asfAllowTrustLineClawback)); + env.close(); + + auto BTC = MPTTester( + {.env = env, + .issuer = gw, + .holders = {alice}, + .pay = 40'000, + .flags = tfMPTCanLock | tfMPTCanClawback | MPTDEXFlags}); + + env.trust(USD(10'000), alice); + env(pay(gw, alice, USD(10'000))); + env.close(); + + AMM amm(env, gw, BTC(100), USD(100)); + env.close(); + amm.deposit(alice, 1'000); + env.close(); + + BTC.set({.flags = tfMPTLock}); + + env(amm::ammClawback(gw, alice, BTC, USD, std::nullopt)); + } + + // MPTRequireAuth flag is set and the account is not authorized - + // can still clawback since the issuer clawbacks + { + Env env(*this, features); + env.fund(XRP(100'000), gw, alice); + env.close(); + + env(fset(gw, asfAllowTrustLineClawback)); + env.close(); + + auto BTC = MPTTester( + {.env = env, + .issuer = gw, + .holders = {alice}, + .pay = 40'000, + .flags = tfMPTRequireAuth | tfMPTCanClawback | MPTDEXFlags, + .authHolder = true}); + + env.trust(USD(10'000), alice); + env(pay(gw, alice, USD(10'000))); + env.close(); + + AMM amm(env, gw, BTC(100), USD(100)); + env.close(); + amm.deposit(alice, 1'000); + env.close(); + + BTC.authorize({.account = gw, .holder = alice, .flags = tfMPTUnauthorize}); + + env(amm::ammClawback(gw, alice, BTC, USD, std::nullopt)); + } + + // MPTCanTransfer is not set and the account is not the issuer of MPT - + // can't clawback since a holder can't deposit + { + Env env(*this, features); + env.fund(XRP(100'000), gw, alice); + env.close(); + + env(fset(gw, asfAllowTrustLineClawback)); + env.close(); + + auto BTC = MPTTester( + {.env = env, + .issuer = gw, + .holders = {alice}, + .pay = 40'000, + .flags = tfMPTCanClawback | tfMPTCanTrade, + .authHolder = true}); + + env.trust(USD(10'000), alice); + env(pay(gw, alice, USD(10'000))); + env.close(); + + AMM amm(env, gw, BTC(100), USD(100)); + env.close(); + // alice can't deposit since MPTCanTransfer is not set + amm.deposit( + DepositArg{.account = alice, .tokens = 1'000, .err = ter(tecNO_PERMISSION)}); + env.close(); + + // can't clawback since alice is not an LP + env(amm::ammClawback(gw, alice, BTC, USD, std::nullopt), ter(tecAMM_BALANCE)); + } + + { + Env env(*this, features); + fund(env, gw, {alice}, XRP(1'000), {USD(1'000)}); + MPTTester mpt(env, gw, {.fund = false}); + mpt.create({.flags = tfMPTCanTransfer | tfMPTCanTrade}); + auto const MPT = mpt["MPT"]; + AMM amm(env, gw, MPT(100), XRP(100)); + amm.deposit(DepositArg{.account = alice, .asset1In = XRP(10)}); + amm::ammClawback(gw, alice, MPTIssue(mpt.issuanceID()), xrpIssue(), MPT(10)); + } + + { + Env env(*this, features); + fund(env, gw, {alice}, XRP(1'000), {USD(1'000)}); + MPTTester mpt(env, gw, {.fund = false}); + mpt.create({.flags = tfMPTCanTransfer | tfMPTCanTrade}); + mpt.authorize({.account = alice}); + mpt.pay(gw, alice, 1'000); + auto const MPT = mpt["MPT"]; + AMM amm(env, gw, MPT(100), XRP(100)); + amm.deposit(DepositArg{.account = alice, .tokens = 10'000}); + amm::ammClawback(gw, alice, MPTIssue(mpt.issuanceID()), xrpIssue(), MPT(10)); + } + + // clawback one asset from MPT/MPT AMM. MPToken for another asset + // is created for the Liquidity Provider + { + Env env(*this, features); + env.fund(XRP(1'000), gw, alice); + auto USD = MPTTester( + {.env = env, + .issuer = gw, + .holders = {alice}, + .pay = 10'000, + .flags = tfMPTCanClawback | MPTDEXFlags}); + auto EUR = + MPTTester({.env = env, .issuer = gw, .flags = tfMPTCanClawback | MPTDEXFlags}); + AMM amm(env, gw, USD(1'000), EUR(1'000)); + amm.deposit({.account = alice, .asset1In = USD(1'000)}); + // MPToken doesn't exist + BEAST_EXPECT(env.le(keylet::mptoken(EUR.issuanceID(), alice)) == nullptr); + env(amm::ammClawback(gw, alice, USD, EUR, USD(100))); + // MPToken is created + BEAST_EXPECT(env.le(keylet::mptoken(EUR.issuanceID(), alice))); + } + } + + void + testBasicAMM(FeatureBitset features) + { + testcase("Basic AMM"); + using namespace jtx; + Account const gw{"gw"}; + Account const alice{"alice"}; + Account const carol{"carol"}; + Account const bob{"bob"}; + auto const USD = gw["USD"]; + + // Create/deposit/withdraw + { + Env env{*this}; + + fund(env, gw, {alice, carol, bob}, XRP(1'000), {USD(1'000)}); + + MPTTester mpt(env, gw, {.fund = false}); + mpt.create({.flags = tfMPTCanTransfer | tfMPTCanTrade}); + auto const MPT = mpt["MPT"]; + mpt.authorize({.account = alice}); + mpt.authorize({.account = carol}); + mpt.pay(gw, alice, 1'000); + mpt.pay(gw, carol, 1'000); + + MPTTester mpt1(env, gw, {.fund = false}); + mpt1.create({.flags = tfMPTCanTransfer | tfMPTCanTrade}); + auto const MPT1 = mpt1["MPT1"]; + mpt1.authorize({.account = alice}); + mpt1.authorize({.account = carol}); + mpt1.pay(gw, alice, 1'000); + mpt1.pay(gw, carol, 1'000); + + std::vector> pools = { + {XRP(100), MPT(100), IOUAmount{100'000}}, + {USD(100), MPT(100), IOUAmount{100}}, + {MPT(100), MPT1(100), IOUAmount{100}}}; + for (auto& pool : pools) + { + AMM amm(env, gw, std::get<0>(pool), std::get<1>(pool)); + amm.deposit(alice, std::get<2>(pool)); + amm.deposit(carol, std::get<2>(pool)); + // bob doesn't own MPT + amm.deposit( + DepositArg{ + .account = bob, .tokens = std::get<2>(pool), .err = ter(tecNO_AUTH)}); + amm.withdrawAll(alice); + amm.withdrawAll(carol); + amm.withdrawAll(gw); + BEAST_EXPECT(!amm.ammExists()); + } + } + + // Payment, one step + { + Env env(*this); + + env.fund(XRP(1'000), gw, alice, carol); + env.close(); + + MPT const USD = MPTTester({.env = env, .issuer = gw, .holders = {alice, carol}}); + MPT const EUR = MPTTester({.env = env, .issuer = gw, .holders = {alice, carol}}); + + env(pay(gw, alice, EUR(100))); + + AMM const amm(env, gw, USD(1'100), EUR(1'000)); + + env(pay(alice, carol, USD(100)), sendmax(EUR(100))); + + BEAST_EXPECT(amm.expectBalances(USD(1'000), EUR(1'100), amm.tokens())); + BEAST_EXPECT(env.balance(carol, USD) == USD(100)); + BEAST_EXPECT(env.balance(alice, EUR) == EUR(0)); + } + + // Payment, two steps + { + Env env(*this); + + env.fund(XRP(1'000), gw, alice, carol); + env.close(); + + MPT const USD = MPTTester({.env = env, .issuer = gw, .holders = {alice, carol}}); + MPT const EUR = MPTTester({.env = env, .issuer = gw, .holders = {alice, carol}}); + MPT const BTC = MPTTester({.env = env, .issuer = gw, .holders = {alice, carol}}); + env(pay(gw, alice, EUR(100))); + + AMM const ammEUR_USD(env, gw, EUR(1'000), USD(1'100)); + AMM const ammUSD_BTC(env, gw, USD(1'000), BTC(1'100)); + + env(pay(alice, carol, BTC(100)), + sendmax(EUR(100)), + path(~USD, ~BTC), + txflags(tfNoRippleDirect)); + + BEAST_EXPECT(ammEUR_USD.expectBalances(USD(1'000), EUR(1'100), ammEUR_USD.tokens())); + BEAST_EXPECT(ammUSD_BTC.expectBalances(USD(1'100), BTC(1'000), ammUSD_BTC.tokens())); + BEAST_EXPECT(env.balance(carol, BTC) == BTC(100)); + BEAST_EXPECT(env.balance(alice, EUR) == EUR(0)); + } + + // Offer crossing + { + Env env(*this); + + env.fund(XRP(1'000), gw, alice); + env.close(); + + MPT const USD = MPTTester({.env = env, .issuer = gw, .holders = {alice}}); + MPT const EUR = MPTTester({.env = env, .issuer = gw, .holders = {alice}}); + + env(pay(gw, alice, EUR(1'000))); + + AMM const amm(env, gw, EUR(1'000'000), USD(1'001'000)); + + env(offer(alice, USD(1'000), EUR(1'000))); + + BEAST_EXPECT(amm.expectBalances(USD(1'000'000), EUR(1'001'000), amm.tokens())); + BEAST_EXPECT(env.balance(alice, USD) == USD(1'000)); + BEAST_EXPECT(env.balance(alice, EUR) == EUR(0)); + } + + { + Env env(*this); + env.fund(XRP(1'000'000), gw, alice, carol); + + auto USD = MPTTester( + {.env = env, + .issuer = gw, + .flags = tfMPTCanLock | MPTDEXFlags, + .mutableFlags = tmfMPTCanMutateRequireAuth | tmfMPTCanMutateCanTransfer | + tmfMPTCanMutateCanClawback | tmfMPTCanMutateCanTrade}); + auto EUR = MPTTester({.env = env, .issuer = gw, .holders = {alice}, .pay = 1'000'000}); + + auto const increment = env.current()->fees().increment; + auto const txfee = fee(drops(increment)); + auto const badMPT = MPT(gw, 1'000); + + auto createDeleteAMM = [&](Account const& lp) { + AMM amm( + env, + lp, + USD(1'000), + EUR(1'000), + CreateArg{.fee = static_cast(increment.value())}); + amm.withdrawAll(lp); + BEAST_EXPECT(!amm.ammExists()); + }; + + // + // AMMCreate + // + + auto createJv = AMM::createJv(alice, badMPT(1'000), EUR(1'000), 0); + + auto createFail = [&](Account const& account, auto const& err) { + createJv[sfAccount] = account.human(); + env(createJv, txfee, ter(err)); + env.close(); + }; + + // MPTokenIssuance doesn't exist + + createFail(alice, tecOBJECT_NOT_FOUND); + + // MPToken doesn't exist + + createJv[sfAmount] = STAmount{USD(1'000)}.getJson(); + createFail(alice, tecNO_AUTH); + + // alice authorizes MPToken, can create + USD.authorize({.account = alice}); + env(pay(gw, alice, USD(1'000'000)), txfee); + env.close(); + createDeleteAMM(alice); + + // MPTLock is set + + // alice and issuer can't create + USD.set({.flags = tfMPTLock}); + createFail(alice, tecFROZEN); + createFail(gw, tecFROZEN); + + // MPTRequireAuth is set + + // alice is not authorized + USD.set({.flags = tfMPTUnlock}); + USD.set({.mutableFlags = tmfMPTSetRequireAuth}); + createFail(alice, tecNO_AUTH); + // issuer can create + createDeleteAMM(gw); + + // alice is authorized, can create + USD.authorize({.account = gw, .holder = alice}); + createDeleteAMM(alice); + + // MPTCanTransfer is not set + + USD.set({.mutableFlags = tmfMPTClearRequireAuth}); + USD.set({.mutableFlags = tmfMPTClearCanTransfer}); + // alice can't create + createFail(alice, tecNO_PERMISSION); + // issuer can create + createDeleteAMM(gw); + USD.set({.mutableFlags = tmfMPTSetCanTransfer}); + // alice can create + createDeleteAMM(alice); + + // MPTCanTrade is not set + + USD.set({.mutableFlags = tmfMPTSetCanTransfer}); + USD.set({.mutableFlags = tmfMPTClearCanTrade}); + // alice and issuer can't create + createFail(alice, tecNO_PERMISSION); + createFail(gw, tecNO_PERMISSION); + USD.set({.mutableFlags = tmfMPTSetCanTrade}); + + // + // AMMDeposit + // + + AMM amm(env, gw, USD(1'000), EUR(1'000)); + + // MPTokenIssuance doesn't exist + + amm.deposit( + {.account = alice, + .asset1In = badMPT(1), + .asset2In = EUR(1), + .assets = std::make_pair(badMPT, EUR), + .err = ter(terNO_AMM)}); + + // MPToken doesn't exist + + amm.deposit( + {.account = carol, .asset1In = USD(1), .asset2In = EUR(1), .err = ter(tecNO_AUTH)}); + + // MPTLock is set + + USD.set({.flags = tfMPTLock}); + // alice and issuer can't deposit + for (auto const& account : {carol, gw}) + { + amm.deposit( + {.account = account, + .asset1In = USD(1), + .asset2In = EUR(1), + .err = ter(tecFROZEN)}); + amm.deposit( + {.account = account, + .asset1In = EUR(1), + .assets = std::make_pair(EUR, USD), + .err = ter(tecFROZEN)}); + } + USD.set({.flags = tfMPTUnlock}); + + // MPTRequireAuth is set + + // carol authorizes MPToken but is not authorized by the issuer + USD.authorize({.account = carol}); + env(pay(gw, carol, USD(1'000'000))); + // carol authorizes EUR + EUR.authorize({.account = carol}); + env(pay(gw, carol, EUR(1'000'000))); + USD.set({.mutableFlags = tmfMPTSetRequireAuth}); + // have to authorize amm account + USD.authorize({.account = gw, .holder = Account{"amm", amm.ammAccount()}}); + env.close(); + amm.deposit( + {.account = carol, .asset1In = USD(1), .asset2In = EUR(1), .err = ter(tecNO_AUTH)}); + amm.deposit( + {.account = carol, + .asset1In = EUR(1), + .assets = std::make_pair(EUR, USD), + .err = ter(tecNO_AUTH)}); + // issuer can deposit + amm.deposit({.account = gw, .tokens = 1'000}); + // carol is authorized, can deposit + USD.authorize({.account = gw, .holder = carol}); + amm.deposit({.account = carol, .tokens = 1'000}); + + // MPTCanTransfer is not set + + USD.set({.mutableFlags = tmfMPTClearRequireAuth}); + USD.set({.mutableFlags = tmfMPTClearCanTransfer}); + // carol can't deposit + amm.deposit( + {.account = carol, + .asset1In = USD(1), + .asset2In = EUR(1), + .err = ter(tecNO_PERMISSION)}); + amm.deposit( + {.account = carol, + .asset1In = EUR(1), + .assets = std::make_pair(EUR, USD), + .err = ter(tecNO_PERMISSION)}); + // issuer can deposit + amm.deposit({.account = gw, .tokens = 1'000}); + // carol can deposit + USD.set({.mutableFlags = tmfMPTSetCanTransfer}); + amm.deposit({.account = carol, .tokens = 1'000}); + + // MPTCanTrade is not set + + USD.set({.mutableFlags = tmfMPTSetCanTransfer}); + USD.set({.mutableFlags = tmfMPTClearCanTrade}); + amm.deposit({.account = gw, .tokens = 1'000, .err = ter(tecNO_PERMISSION)}); + amm.deposit({.account = carol, .tokens = 1'000, .err = ter(tecNO_PERMISSION)}); + USD.set({.mutableFlags = tmfMPTSetCanTrade}); + + // + // AMMWithdraw + // + + // MPTokenIssuance doesn't exist + + amm.withdraw( + WithdrawArg{ + .account = carol, + .asset1Out = badMPT(1), + .asset2Out = EUR(1), + .assets = std::make_pair(badMPT, EUR), + .err = ter(terNO_AMM)}); + + // MPToken doesn't exist - doesn't apply since MPToken is created + // on withdraw in this case + + // MPTLock is set + + USD.set({.flags = tfMPTLock}); + // carol and issuer can't withdraw + for (auto const& account : {carol, gw}) + { + amm.withdraw( + {.account = account, + .asset1Out = USD(1), + .asset2Out = EUR(1), + .err = ter(tecFROZEN)}); + amm.withdraw({.account = account, .tokens = 1'000, .err = ter(tecFROZEN)}); + // can single withdraw another asset + amm.withdraw( + {.account = account, .asset1Out = EUR(1), .assets = std::make_pair(EUR, USD)}); + } + USD.set({.flags = tfMPTUnlock}); + + // MPTRequireAuth is set + + USD.set({.mutableFlags = tmfMPTSetRequireAuth}); + USD.authorize({.account = gw, .holder = carol, .flags = tfMPTUnauthorize}); + // carol can't withdraw + amm.withdraw( + {.account = carol, + .asset1Out = USD(1), + .asset2Out = EUR(1), + .err = ter(tecNO_AUTH)}); + // can withdraw another asset + amm.withdraw( + {.account = carol, .asset1Out = EUR(1), .assets = std::make_pair(EUR, USD)}); + // issuer can withdraw + amm.withdraw({.account = gw, .asset1Out = USD(1), .asset2Out = EUR(1)}); + // carol is authorized, can withdraw + USD.authorize({.account = gw, .holder = carol}); + amm.withdraw({.account = carol, .asset1Out = USD(1), .asset2Out = EUR(1)}); + + // MPTCanTransfer is set + + USD.set({.mutableFlags = tmfMPTClearRequireAuth}); + USD.set({.mutableFlags = tmfMPTClearCanTransfer}); + // carol can't withdraw + amm.withdraw( + {.account = carol, + .asset1Out = USD(1), + .asset2Out = EUR(1), + .err = ter(tecNO_PERMISSION)}); + // can withdraw another asset + amm.withdraw( + {.account = carol, .asset1Out = EUR(1), .assets = std::make_pair(EUR, USD)}); + // issuer can withdraw + amm.withdraw({.account = gw, .asset1Out = USD(1), .asset2Out = EUR(1)}); + // carol can withdraw + USD.set({.mutableFlags = tmfMPTSetCanTransfer}); + amm.withdraw({.account = carol, .asset1Out = USD(1), .asset2Out = EUR(1)}); + + USD.set({.mutableFlags = tmfMPTSetCanTransfer}); + USD.set({.mutableFlags = tmfMPTClearCanTrade}); + amm.withdraw({.account = gw, .tokens = 1'000, .err = ter(tecNO_PERMISSION)}); + amm.withdraw({.account = carol, .tokens = 1'000, .err = ter(tecNO_PERMISSION)}); + USD.set({.mutableFlags = tmfMPTSetCanTrade}); + + // MPToken created on withdraw + + // redeem all carol's USD and unauthorize USD + amm.withdrawAll(carol); + env(pay(carol, gw, env.balance(carol, USD))); + USD.authorize({.account = carol, .flags = tfMPTUnauthorize}); + BEAST_EXPECT(env.le(keylet::mptoken(USD.issuanceID(), carol)) == nullptr); + // single-deposit EUR + amm.deposit( + {.account = carol, .asset1In = EUR(1'000), .assets = std::make_pair(EUR, USD)}); + // withdraw in USD to create MPToken + amm.withdraw({.account = carol, .asset1Out = USD(100)}); + BEAST_EXPECT(env.le(keylet::mptoken(USD.issuanceID(), carol))); + } + } + public: void run() override @@ -3407,15 +6718,27 @@ public: // MPTokenIssuanceDestroy testDestroyValidation(all - featureSingleAssetVault); - testDestroyValidation(all); + testDestroyValidation(all - featureSingleAssetVault - featureMPTokensV2); + testDestroyValidation((all | featureSingleAssetVault) - featureMPTokensV2); + testDestroyValidation(all - featureMPTokensV2); + testDestroyValidation(all | featureSingleAssetVault); testDestroyEnabled(all - featureSingleAssetVault); - testDestroyEnabled(all); + testDestroyEnabled(all - featureSingleAssetVault - featureMPTokensV2); + testDestroyEnabled((all | featureSingleAssetVault) - featureMPTokensV2); + testDestroyEnabled(all - featureMPTokensV2); + testDestroyEnabled(all | featureSingleAssetVault); // MPTokenAuthorize testAuthorizeValidation(all - featureSingleAssetVault); - testAuthorizeValidation(all); + testAuthorizeValidation(all - featureSingleAssetVault - featureMPTokensV2); + testAuthorizeValidation((all | featureSingleAssetVault) - featureMPTokensV2); + testAuthorizeValidation(all - featureMPTokensV2); + testAuthorizeValidation(all | featureSingleAssetVault); testAuthorizeEnabled(all - featureSingleAssetVault); - testAuthorizeEnabled(all); + testAuthorizeEnabled(all - featureSingleAssetVault - featureMPTokensV2); + testAuthorizeEnabled((all | featureSingleAssetVault) - featureMPTokensV2); + testAuthorizeEnabled(all - featureMPTokensV2); + testAuthorizeEnabled(all | featureSingleAssetVault); // MPTokenIssuanceSet testSetValidation(all - featureSingleAssetVault - featureDynamicMPT); @@ -3429,12 +6752,20 @@ public: // MPT clawback testClawbackValidation(all); + testClawbackValidation(all - featureMPTokensV2); testClawback(all); + testClawback(all - featureMPTokensV2); // Test Direct Payment testPayment(all); + testPayment(all | featureSingleAssetVault); + testPayment((all | featureSingleAssetVault) - featureMPTokensV2); + testPayment(all - featureMPTokensV2); + testDepositPreauth(all); testDepositPreauth(all - featureCredentials); + testDepositPreauth(all - featureMPTokensV2); + testDepositPreauth(all - featureCredentials - featureMPTokensV2); // Test MPT Amount is invalid in Tx, which don't support MPT testMPTInvalidInTx(all); @@ -3456,11 +6787,29 @@ public: testMutateRequireAuth(all); testMutateCanEscrow(all); testMutateCanTransfer(all); + testMutateCanTransfer(all - featureMPTokensV2); testMutateCanClawback(all); + + // Test offer crossing + testOfferCrossing(all); + + // Test cross asset payment + testCrossAssetPayment(all); + + // Test path finding + testPath(all); + + // Test checks + testCheck(all); + + // Add AMMClawback + testAMMClawback(all); + + // Test AMM + testBasicAMM(all); } }; BEAST_DEFINE_TESTSUITE_PRIO(MPToken, app, xrpl, 2); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/app/Manifest_test.cpp b/src/test/app/Manifest_test.cpp index c2f789e80c..1025b73fe8 100644 --- a/src/test/app/Manifest_test.cpp +++ b/src/test/app/Manifest_test.cpp @@ -1,23 +1,43 @@ -#include +#include #include #include +#include #include #include -#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include #include -#include +#include #include #include -#include -#include -#include +#include +#include -namespace xrpl { -namespace test { +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl::test { class Manifest_test : public beast::unit_test::suite { @@ -76,7 +96,7 @@ public: { } } - ~Manifest_test() + ~Manifest_test() override { try { @@ -217,7 +237,7 @@ public: return result; }; auto sort = [](std::vector mv) -> std::vector { - std::sort(mv.begin(), mv.end(), [](Manifest const* lhs, Manifest const* rhs) { + std::ranges::sort(mv, [](Manifest const* lhs, Manifest const* rhs) { return lhs->serialized < rhs->serialized; }); return mv; @@ -930,5 +950,4 @@ public: BEAST_DEFINE_TESTSUITE(Manifest, app, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/app/MultiSign_test.cpp b/src/test/app/MultiSign_test.cpp index 7a5a49ca9b..f41042644f 100644 --- a/src/test/app/MultiSign_test.cpp +++ b/src/test/app/MultiSign_test.cpp @@ -1,12 +1,51 @@ -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include #include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include #include -namespace xrpl { -namespace test { +#include +#include +#include +#include + +namespace xrpl::test { class MultiSign_test : public beast::unit_test::suite { @@ -323,7 +362,7 @@ public: env.require(owners(alice, 1)); msig phantoms{bogie, demon}; - std::reverse(phantoms.signers.begin(), phantoms.signers.end()); + std::ranges::reverse(phantoms.signers); std::uint32_t const aliceSeq = env.seq(alice); env(noop(alice), phantoms, @@ -1162,7 +1201,7 @@ public: STTx local = *(tx.stx); // Unsort the Signers array. auto& signers = local.peekFieldArray(sfSigners); - std::reverse(signers.begin(), signers.end()); + std::ranges::reverse(signers); // Signature should fail. auto const info = submitSTTx(local); BEAST_EXPECT( @@ -1524,5 +1563,4 @@ public: BEAST_DEFINE_TESTSUITE(MultiSign, app, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/app/NFTokenAuth_test.cpp b/src/test/app/NFTokenAuth_test.cpp index 0e3fb24305..2542f2eb36 100644 --- a/src/test/app/NFTokenAuth_test.cpp +++ b/src/test/app/NFTokenAuth_test.cpp @@ -1,6 +1,31 @@ -#include -#include +#include +#include +#include +#include // IWYU pragma: keep +#include +#include // IWYU pragma: keep +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include namespace xrpl { @@ -86,8 +111,8 @@ public: using namespace test::jtx; Env env(*this, features); - Account G1{"G1"}; - Account A1{"A1"}; + Account const G1{"G1"}; + Account const A1{"A1"}; Account const A2{"A2"}; auto const USD{G1["USD"]}; @@ -132,8 +157,8 @@ public: using namespace test::jtx; Env env(*this, features); - Account G1{"G1"}; - Account A1{"A1"}; + Account const G1{"G1"}; + Account const A1{"A1"}; Account const A2{"A2"}; auto const USD{G1["USD"]}; @@ -265,8 +290,8 @@ public: using namespace test::jtx; Env env(*this, features); - Account G1{"G1"}; - Account A1{"A1"}; + Account const G1{"G1"}; + Account const A1{"A1"}; Account const A2{"A2"}; auto const USD{G1["USD"]}; @@ -373,8 +398,8 @@ public: using namespace test::jtx; Env env(*this, features); - Account G1{"G1"}; - Account A1{"A1"}; + Account const G1{"G1"}; + Account const A1{"A1"}; Account const A2{"A2"}; Account const broker{"broker"}; auto const USD{G1["USD"]}; diff --git a/src/test/app/NFTokenBurn_test.cpp b/src/test/app/NFTokenBurn_test.cpp index cd0df42c03..08d7b4305e 100644 --- a/src/test/app/NFTokenBurn_test.cpp +++ b/src/test/app/NFTokenBurn_test.cpp @@ -1,10 +1,44 @@ -#include +#include +#include +#include +#include +#include +#include +#include // IWYU pragma: keep +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include #include -#include +#include +#include +#include +#include +#include +#include +#include #include +#include namespace xrpl { @@ -366,7 +400,7 @@ class NFTokenBurn_test : public beast::unit_test::suite // Sort the NFTs so they are listed in storage order, not // creation order. - std::sort(nfts.begin(), nfts.end()); + std::ranges::sort(nfts); // Verify that the ledger does indeed contain exactly three pages // of NFTs with 32 entries in each page. @@ -620,7 +654,7 @@ class NFTokenBurn_test : public beast::unit_test::suite return; // Burn all the tokens in the first page. - std::reverse(nfts.begin(), nfts.end()); + std::ranges::reverse(nfts); for (int i = 0; i < 32; ++i) { env(token::burn(alice, {nfts.back()})); @@ -648,7 +682,7 @@ class NFTokenBurn_test : public beast::unit_test::suite BEAST_EXPECT(!lastNFTokenPage->isFieldPresent(sfNextPageMin)); // Burn all the tokens in the last page. - std::reverse(nfts.begin(), nfts.end()); + std::ranges::reverse(nfts); for (int i = 0; i < 32; ++i) { env(token::burn(alice, {nfts.back()})); @@ -1023,7 +1057,7 @@ class NFTokenBurn_test : public beast::unit_test::suite // Sort the NFTs so they are listed in storage order, not // creation order. - std::sort(nfts.begin(), nfts.end()); + std::ranges::sort(nfts); // Verify that the ledger does indeed contain exactly three pages // of NFTs with 32 entries in each page. diff --git a/src/test/app/NFTokenDir_test.cpp b/src/test/app/NFTokenDir_test.cpp index 78765cb6c0..5764e87f76 100644 --- a/src/test/app/NFTokenDir_test.cpp +++ b/src/test/app/NFTokenDir_test.cpp @@ -1,11 +1,38 @@ -#include +#include +#include +#include +#include +#include // IWYU pragma: keep +#include +#include +#include + +#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include #include +#include #include -#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include namespace xrpl { @@ -121,7 +148,7 @@ class NFTokenDir_test : public beast::unit_test::suite } // Buyer accepts all of the offers in reverse order. - std::reverse(offers.begin(), offers.end()); + std::ranges::reverse(offers); for (uint256 const& offer : offers) { env(token::acceptSellOffer(buyer, offer)); @@ -230,7 +257,7 @@ class NFTokenDir_test : public beast::unit_test::suite { uint256 ownedID; BEAST_EXPECT(ownedID.parseHex(ownedNFT[sfNFTokenID.jsonName].asString())); - auto const foundIter = std::find(nftIDs.begin(), nftIDs.end(), ownedID); + auto const foundIter = std::ranges::find(nftIDs, ownedID); // Assuming we find the NFT, erase it so we know it's been // found and can't be found again. @@ -438,7 +465,7 @@ class NFTokenDir_test : public beast::unit_test::suite { uint256 ownedID; BEAST_EXPECT(ownedID.parseHex(ownedNFT[sfNFTokenID.jsonName].asString())); - auto const foundIter = std::find(nftIDs.begin(), nftIDs.end(), ownedID); + auto const foundIter = std::ranges::find(nftIDs, ownedID); // Assuming we find the NFT, erase it so we know it's been // found and can't be found again. @@ -676,7 +703,7 @@ class NFTokenDir_test : public beast::unit_test::suite { uint256 ownedID; BEAST_EXPECT(ownedID.parseHex(ownedNFT[sfNFTokenID.jsonName].asString())); - auto const foundIter = std::find(nftIDs.begin(), nftIDs.end(), ownedID); + auto const foundIter = std::ranges::find(nftIDs, ownedID); // Assuming we find the NFT, erase it so we know it's been found // and can't be found again. diff --git a/src/test/app/NFToken_test.cpp b/src/test/app/NFToken_test.cpp index 0d391147a8..40934cc095 100644 --- a/src/test/app/NFToken_test.cpp +++ b/src/test/app/NFToken_test.cpp @@ -1,11 +1,56 @@ -#include +#include +#include +#include +#include +#include +#include // IWYU pragma: keep +#include +#include +#include +#include +#include +#include // IWYU pragma: keep +#include +#include +#include +#include +#include +#include +#include + +#include #include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include #include -#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include namespace xrpl { @@ -2253,15 +2298,14 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite env(pay(becky, gw, env.balance(becky, gwXAU))); env.close(); - STAmount const startXAUBalance( - gwXAU.issue(), STAmount::cMinValue, STAmount::cMinOffset + 5); + STAmount const startXAUBalance(gwXAU, STAmount::cMinValue, STAmount::cMinOffset + 5); env(pay(gw, alice, startXAUBalance)); env(pay(gw, minter, startXAUBalance)); env(pay(gw, becky, startXAUBalance)); env.close(); // Here is the smallest expressible gwXAU amount. - STAmount const tinyXAU(gwXAU.issue(), STAmount::cMinValue, STAmount::cMinOffset); + STAmount const tinyXAU(gwXAU, STAmount::cMinValue, STAmount::cMinOffset); // minter buys the nft for tinyXAU. Since the transfer involves // alice there should be no transfer fee. @@ -2294,7 +2338,7 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite // carol sells to becky. This is the smallest gwXAU amount // to pay for a transfer that enables a transfer fee of 1. - STAmount const cheapNFT(gwXAU.issue(), STAmount::cMinValue, STAmount::cMinOffset + 5); + STAmount const cheapNFT(gwXAU, STAmount::cMinValue, STAmount::cMinOffset + 5); STAmount beckyBalance = env.balance(becky, gwXAU); uint256 const beckyBuyOfferIndex = keylet::nftoffer(becky, env.seq(becky)).key; @@ -2475,9 +2519,9 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite sortedNFTs.reserve(nfts.size()); for (std::size_t i = 0; i < nfts.size(); ++i) sortedNFTs.push_back(nfts[i]); - std::sort( - sortedNFTs.begin(), - sortedNFTs.end(), + std::ranges::sort( + sortedNFTs, + [](Json::Value const& lhs, Json::Value const& rhs) { return lhs[jss::nft_serial] < rhs[jss::nft_serial]; }); @@ -5579,7 +5623,7 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite // The new NFT minted will not have the same ID // as any of the NFTs authorized minter minted - BEAST_EXPECT(std::find(nftIDs.begin(), nftIDs.end(), remintNFTokenID) == nftIDs.end()); + BEAST_EXPECT(std::ranges::find(nftIDs, remintNFTokenID) == nftIDs.end()); } // When an account mints and burns a batch of NFTokens using tickets, @@ -5678,7 +5722,7 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite // The new NFT minted will not have the same ID // as any of the NFTs authorized minter minted using tickets - BEAST_EXPECT(std::find(nftIDs.begin(), nftIDs.end(), remintNFTokenID) == nftIDs.end()); + BEAST_EXPECT(std::ranges::find(nftIDs, remintNFTokenID) == nftIDs.end()); } // When an authorized minter mints and burns a batch of NFTokens using // tickets, issuer's account needs to wait a longer time before it can @@ -5781,7 +5825,7 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite // The new NFT minted will not have the same ID // as one of NFTs authorized minter minted using tickets - BEAST_EXPECT(std::find(nftIDs.begin(), nftIDs.end(), remintNFTokenID) == nftIDs.end()); + BEAST_EXPECT(std::ranges::find(nftIDs, remintNFTokenID) == nftIDs.end()); } void @@ -6099,8 +6143,8 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite }); // Sort both array to prepare for comparison - std::sort(metaIDs.begin(), metaIDs.end()); - std::sort(actualNftIDs.begin(), actualNftIDs.end()); + std::ranges::sort(metaIDs); + std::ranges::sort(actualNftIDs); // Make sure the expect number of NFTs is correct BEAST_EXPECT(metaIDs.size() == actualNftIDs.size()); diff --git a/src/test/app/NetworkID_test.cpp b/src/test/app/NetworkID_test.cpp index 17245f7ee5..5ac97b8df3 100644 --- a/src/test/app/NetworkID_test.cpp +++ b/src/test/app/NetworkID_test.cpp @@ -1,13 +1,27 @@ // Copyright (c) 2020 Dev Null Productions -#include #include +#include +#include +#include +#include +#include +#include + +#include +#include #include +#include +#include +#include +#include #include -namespace xrpl { -namespace test { +#include +#include + +namespace xrpl::test { class NetworkID_test : public beast::unit_test::suite { @@ -141,5 +155,4 @@ public: BEAST_DEFINE_TESTSUITE(NetworkID, app, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/app/NetworkOPs_test.cpp b/src/test/app/NetworkOPs_test.cpp index a176279444..e6673dc2f5 100644 --- a/src/test/app/NetworkOPs_test.cpp +++ b/src/test/app/NetworkOPs_test.cpp @@ -1,11 +1,21 @@ -#include #include #include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include -namespace xrpl { -namespace test { +#include + +namespace xrpl::test { class NetworkOPs_test : public beast::unit_test::suite { @@ -51,5 +61,4 @@ public: BEAST_DEFINE_TESTSUITE(NetworkOPs, app, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/app/OfferMPT_test.cpp b/src/test/app/OfferMPT_test.cpp new file mode 100644 index 0000000000..b07fe043d2 --- /dev/null +++ b/src/test/app/OfferMPT_test.cpp @@ -0,0 +1,4772 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl::test { + +class OfferMPT_test : public beast::unit_test::suite +{ + static XRPAmount + reserve(jtx::Env& env, std::uint32_t count) + { + return env.current()->fees().accountReserve(count); + } + + static std::uint32_t + lastClose(jtx::Env& env) + { + return env.current()->header().parentCloseTime.time_since_epoch().count(); + } + +public: + void + testRmFundedOffer(FeatureBitset features) + { + testcase("Incorrect Removal of Funded Offers"); + + // We need at least two paths. One at good quality and one at bad + // quality. The bad quality path needs two offer books in a row. + // Each offer book should have two offers at the same quality, the + // offers should be completely consumed, and the payment should + // require both offers to be satisfied. The first offer must + // be "taker gets" XRP. Old, broken would remove the first + // "taker gets" xrp offer, even though the offer is still funded and + // not used for the payment. + + using namespace jtx; + auto const gw = Account{"gateway"}; + Account const alice{"alice"}; + Account const bob{"bob"}; + Account const carol{"carol"}; + + auto test = [&](auto&& issue1, auto&& issue2) { + Env env{*this, features}; + + env.fund(XRP(10'000), alice, bob, carol, gw); + env.close(); + + auto const USD = + issue1({.env = env, .token = "USD", .issuer = gw, .holders = {alice, bob, carol}}); + auto const BTC = + issue2({.env = env, .token = "BTC", .issuer = gw, .holders = {alice, bob, carol}}); + + env(pay(gw, alice, BTC(1'000))); + + env(pay(gw, carol, USD(1'000))); + env(pay(gw, carol, BTC(1'000))); + + // Must be two offers at the same quality + // "taker gets" must be XRP + // (Different amounts, so I can distinguish the offers) + env(offer(carol, BTC(49), XRP(49))); + env(offer(carol, BTC(51), XRP(51))); + + // Offers for the poor quality path + // Must be two offers at the same quality + env(offer(carol, XRP(50), USD(50))); + env(offer(carol, XRP(50), USD(50))); + + // Offers for the good quality path + env(offer(carol, BTC(1), USD(100))); + + PathSet const paths(Path(XRP, USD), Path(USD)); + + env(pay(alice, bob, USD(100)), + json(paths.json()), + sendmax(BTC(1'000)), + txflags(tfPartialPayment)); + + env.require(balance(bob, USD(100))); + BEAST_EXPECT( + !isOffer(env, carol, BTC(1), USD(100)) && isOffer(env, carol, BTC(49), XRP(49))); + }; + testHelper2TokensMix(test); + } + + void + testCanceledOffer(FeatureBitset features) + { + testcase("Removing Canceled Offers"); + + using namespace jtx; + Env env{*this, features}; + + auto const gw = Account{"gateway"}; + auto const alice = Account{"alice"}; + + env.fund(XRP(10'000), alice, gw); + env.close(); + + MPT const USD = MPTTester({.env = env, .issuer = gw, .holders = {alice}}); + + env(pay(gw, alice, USD(50))); + env.close(); + + auto const offer1Seq = env.seq(alice); + + env(offer(alice, XRP(500), USD(100)), require(offers(alice, 1))); + env.close(); + + BEAST_EXPECT(isOffer(env, alice, XRP(500), USD(100))); + + // cancel the offer above and replace it with a new offer + auto const offer2Seq = env.seq(alice); + + env(offer(alice, XRP(300), USD(100)), + json(jss::OfferSequence, offer1Seq), + require(offers(alice, 1))); + env.close(); + + BEAST_EXPECT( + isOffer(env, alice, XRP(300), USD(100)) && !isOffer(env, alice, XRP(500), USD(100))); + + // Test canceling non-existent offer. + // auto const offer3Seq = env.seq (alice); + + env(offer(alice, XRP(400), USD(200)), + json(jss::OfferSequence, offer1Seq), + require(offers(alice, 2))); + env.close(); + + BEAST_EXPECT( + isOffer(env, alice, XRP(300), USD(100)) && isOffer(env, alice, XRP(400), USD(200))); + + // Test cancellation now with OfferCancel tx + auto const offer4Seq = env.seq(alice); + env(offer(alice, XRP(222), USD(111)), require(offers(alice, 3))); + env.close(); + + BEAST_EXPECT(isOffer(env, alice, XRP(222), USD(111))); + env(offer_cancel(alice, offer4Seq)); + env.close(); + BEAST_EXPECT(env.seq(alice) == offer4Seq + 2); + + BEAST_EXPECT(!isOffer(env, alice, XRP(222), USD(111))); + + // Create an offer that both fails with a tecEXPIRED code and removes + // an offer. Show that the attempt to remove the offer fails. + env.require(offers(alice, 2)); + + env(offer(alice, XRP(5), USD(2)), + json(sfExpiration.fieldName, lastClose(env)), + json(jss::OfferSequence, offer2Seq), + ter(TER{tecEXPIRED})); + env.close(); + + env.require(offers(alice, 2)); + BEAST_EXPECT(isOffer(env, alice, XRP(300), USD(100))); // offer2 + BEAST_EXPECT(!isOffer(env, alice, XRP(5), USD(2))); // expired + } + + void + testTinyPayment(FeatureBitset features) + { + testcase("Tiny payments"); + + // Regression test for tiny payments + using namespace jtx; + using namespace std::chrono_literals; + auto const alice = Account{"alice"}; + auto const bob = Account{"bob"}; + auto const carol = Account{"carol"}; + auto const gw = Account{"gw"}; + + auto test = [&](auto&& issue1, auto&& issue2) { + Env env{*this, features}; + + env.fund(XRP(10'000), alice, bob, carol, gw); + env.close(); + + auto const USD = issue1( + {.env = env, + .token = "USD", + .issuer = gw, + .holders = {alice, bob, carol}, + .limit = 400'000'000}); + auto const EUR = issue2( + {.env = env, + .token = "EUR", + .issuer = gw, + .holders = {alice, bob, carol}, + .limit = 400'000'000}); + + env(pay(gw, alice, USD(100'000'000))); + env(pay(gw, carol, EUR(100'000'000))); + + // Create more offers than the loop max count in DeliverNodeReverse + // Note: the DeliverNodeReverse code has been removed; however since + // this is a regression test the original test is being left as-is + // for now. + for (int i = 0; i < 101; ++i) + env(offer(carol, USD(1'000'000), EUR(2'000'000))); + + // Original Offer test sends EUR(10**-81). MPT is integral, + // therefore and integral value is sent respecting the exchange + // rate. I.e. if EUR(1) is sent then it'll result in USD(0). + env(pay(alice, bob, EUR(2)), path(~EUR), sendmax(USD(100))); + }; + testHelper2TokensMix(test); + } + + void + testXRPTinyPayment(FeatureBitset features) + { + testcase("XRP Tiny payments"); + + // Regression test for tiny xrp payments + // In some cases, when the payment code calculates + // the amount of xrp needed as input to an xrp->iou offer + // it would incorrectly round the amount to zero (even when + // round-up was set to true). + // The bug would cause funded offers to be incorrectly removed + // because the code thought they were unfunded. + // The conditions to trigger the bug are: + // 1) When we calculate the amount of input xrp needed for an offer + // from xrp->iou, the amount is less than 1 drop (after rounding + // up the float representation). + // 2) There is another offer in the same book with a quality + // sufficiently bad that when calculating the input amount + // needed the amount is not set to zero. + + using namespace jtx; + using namespace std::chrono_literals; + auto const alice = Account{"alice"}; + auto const bob = Account{"bob"}; + auto const carol = Account{"carol"}; + auto const dan = Account{"dan"}; + auto const erin = Account{"erin"}; + auto const gw = Account{"gw"}; + + Env env{*this, features}; + + env.fund(XRP(10'000), alice, bob, carol, dan, erin, gw); + env.close(); + + MPT const USD = MPTTester( + {.env = env, + .issuer = gw, + .holders = {alice, bob, carol, dan, erin}, + .pay = std::nullopt}); + env(pay(gw, carol, USD(99'999))); + env(pay(gw, dan, USD(100'000))); + env(pay(gw, erin, USD(100'000))); + env.close(); + + // Carol doesn't quite have enough funds for this offer + // The amount left after this offer is taken will cause + // STAmount to incorrectly round to zero when the next offer + // (at a good quality) is considered. (when the now removed + // stAmountCalcSwitchover2 patch was inactive) + env(offer(carol, drops(1), USD(99'999))); + // Offer at a quality poor enough so when the input xrp is + // calculated in the reverse pass, the amount is not zero. + env(offer(dan, XRP(100), USD(1))); + + env.close(); + // This is the funded offer that will be incorrectly removed. + // It is considered after the offer from carol, which leaves a + // tiny amount left to pay. When calculating the amount of xrp + // needed for this offer, it will incorrectly compute zero in both + // the forward and reverse passes (when the now removed + // stAmountCalcSwitchover2 was inactive.) + env(offer(erin, drops(2), USD(100'000))); + + env(pay(alice, bob, USD(100'000)), + path(~USD), + sendmax(XRP(102)), + txflags(tfNoRippleDirect | tfPartialPayment)); + + env.require(offers(carol, 0), offers(dan, 1)); + + // offer was correctly consumed. There is still some + // liquidity left on that offer. + env.require(balance(erin, USD(99'999)), offers(erin, 1)); + } + + void + testRmSmallIncreasedQOffersXRP(FeatureBitset features) + { + testcase("Rm small increased q offers XRP"); + + // Carol places an offer, but cannot fully fund the offer. When her + // funding is taken into account, the offer's quality drops below its + // initial quality and has an input amount of 1 drop. This is removed as + // an offer that may block offer books. + + using namespace jtx; + using namespace std::chrono_literals; + auto const alice = Account{"alice"}; + auto const bob = Account{"bob"}; + auto const carol = Account{"carol"}; + auto const gw = Account{"gw"}; + + // Test offer crossing + for (auto crossBothOffers : {false, true}) + { + Env env{*this, features}; + + env.fund(XRP(10'000), alice, bob, carol, gw); + + MPT const USD = MPTTester({.env = env, .issuer = gw, .holders = {alice, bob, carol}}); + // underfund carol's offer + auto initialCarolUSD = USD(499); + env(pay(gw, carol, initialCarolUSD)); + env(pay(gw, bob, USD(100'000))); + env.close(); + // This offer is underfunded + env(offer(carol, drops(1), USD(1'000))); + env.close(); + // offer at a lower quality + env(offer(bob, drops(2), USD(1'000), tfPassive)); + env.close(); + env.require(offers(bob, 1), offers(carol, 1)); + + // alice places an offer that crosses carol's; depending on + // "crossBothOffers" it may cross bob's as well + auto aliceTakerGets = crossBothOffers ? drops(2) : drops(1); + env(offer(alice, USD(1'000), aliceTakerGets)); + env.close(); + + env.require( + offers(carol, 0), + balance( + carol, + initialCarolUSD)); // offer is removed but not taken + if (crossBothOffers) + { + env.require( + offers(alice, 0), balance(alice, USD(1'000))); // alice's offer is crossed + } + else + { + env.require( + offers(alice, 1), balance(alice, USD(0))); // alice's offer is not crossed + } + } + + // Test payments + for (auto partialPayment : {false, true}) + { + Env env{*this, features}; + + env.fund(XRP(10'000), alice, bob, carol, gw); + env.close(); + + MPT const USD = MPTTester({.env = env, .issuer = gw, .holders = {alice, bob, carol}}); + auto const initialCarolUSD = USD(999); + env(pay(gw, carol, initialCarolUSD)); + env.close(); + env(pay(gw, bob, USD(100'000))); + env.close(); + env(offer(carol, drops(1), USD(1'000))); + env.close(); + env(offer(bob, drops(2), USD(2'000), tfPassive)); + env.close(); + env.require(offers(bob, 1), offers(carol, 1)); + + std::uint32_t const flags = + partialPayment ? (tfNoRippleDirect | tfPartialPayment) : tfNoRippleDirect; + + TER const expectedTer = partialPayment ? TER{tesSUCCESS} : TER{tecPATH_PARTIAL}; + + env(pay(alice, bob, USD(5'000)), + path(~USD), + sendmax(XRP(1)), + txflags(flags), + ter(expectedTer)); + env.close(); + + if (expectedTer == tesSUCCESS) + { + env.require(offers(carol, 0)); + env.require(balance(carol, + initialCarolUSD)); // offer is removed but not taken + } + else + { + // TODO: Offers are not removed when payments fail + // If that is addressed, the test should show that carol's + // offer is removed but not taken, as in the other branch of + // this if statement + } + } + } + + void + testRmSmallIncreasedQOffersMPT(FeatureBitset features) + { + testcase("Rm small increased q offers MPT"); + + // Carol places an offer, but cannot fully fund the offer. When her + // funding is taken into account, the offer's quality drops below its + // initial quality and has an input amount of 1 drop. This is removed as + // an offer that may block offer books. + + using namespace jtx; + using namespace std::chrono_literals; + auto const alice = Account{"alice"}; + auto const bob = Account{"bob"}; + auto const carol = Account{"carol"}; + auto const gw = Account{"gw"}; + + auto test = [&](auto&& issue1, auto&& issue2) { + auto tinyAmount = [&](T const& token) -> PrettyAmount { + if constexpr (std::is_same_v) + { + STAmount const amt( + token, + /*mantissa*/ 1, + /*exponent*/ -81); + return PrettyAmount(amt, token.account.name()); + } + else + { + STAmount const amt( + token, + /*mantissa*/ 1, + /*exponent*/ 0); + return PrettyAmount(amt, "MPT"); + } + }; + + // Test offer crossing + for (auto crossBothOffers : {false, true}) + { + Env env{*this, features}; + + env.fund(XRP(10'000), alice, bob, carol, gw); + env.close(); + + auto const USD = issue1( + {.env = env, + .token = "USD", + .issuer = gw, + .holders = {alice, bob, carol}, + .limit = 100'000'000}); + auto const EUR = issue2( + {.env = env, + .token = "EUR", + .issuer = gw, + .holders = {alice, bob, carol}, + .limit = 100'000'000}); + // underfund carol's offer + auto initialCarolUSD = tinyAmount(USD); + env(pay(gw, carol, initialCarolUSD)); + env(pay(gw, bob, USD(100'000))); + env(pay(gw, alice, EUR(100'000))); + env.close(); + // This offer is underfunded + env(offer(carol, EUR(10), USD(10'000))); + env.close(); + // offer at a lower quality + env(offer(bob, EUR(10), USD(5'000), tfPassive)); + env.close(); + env.require(offers(bob, 1), offers(carol, 1)); + + // alice places an offer that crosses carol's; depending on + // "crossBothOffers" it may cross bob's as well + // Whatever + auto aliceTakerGets = crossBothOffers ? EUR(2) : EUR(1); + env(offer(alice, USD(1'000), aliceTakerGets)); + env.close(); + + // carol's offer can be partially crossed when EUR is IOU: + // 10e-3EUR/1USD + using tEUR = std::decay_t; + bool constexpr isEURIOU = std::is_same_v; + // partially crossed if IOU, removed but not taken if MPT + auto const balanceCarolUSD = isEURIOU ? USD(0) : initialCarolUSD; + + env.require(offers(carol, 0), balance(carol, balanceCarolUSD)); + if (crossBothOffers) + { + env.require( + offers(alice, 0), balance(alice, USD(1'000))); // alice's offer is crossed + } + else + { + // partially crossed if IOU, not crossed if MPT + auto const balanceAliceUSD = isEURIOU ? USD(1) : USD(0); + env.require(offers(alice, 1), balance(alice, balanceAliceUSD)); + } + } + + // Test payments + for (auto partialPayment : {false, true}) + { + Env env{*this, features}; + + env.fund(XRP(10'000), alice, bob, carol, gw); + env.close(); + + auto const USD = issue1( + {.env = env, + .token = "USD", + .issuer = gw, + .holders = {alice, bob, carol}, + .limit = 100'000'000}); + auto const EUR = issue2( + {.env = env, + .token = "EUR", + .issuer = gw, + .holders = {alice, bob, carol}, + .limit = 100'000'000}); + // underfund carol's offer + auto const initialCarolUSD = tinyAmount(USD); + env(pay(gw, carol, initialCarolUSD)); + env(pay(gw, bob, USD(100'000))); + env(pay(gw, alice, EUR(100'000))); + env.close(); + // This offer is underfunded + env(offer(carol, EUR(10), USD(2'000))); + env.close(); + env(offer(bob, EUR(20), USD(4'000), tfPassive)); + env.close(); + env.require(offers(bob, 1), offers(carol, 1)); + + std::uint32_t const flags = + partialPayment ? (tfNoRippleDirect | tfPartialPayment) : tfNoRippleDirect; + + TER const expectedTer = partialPayment ? TER{tesSUCCESS} : TER{tecPATH_PARTIAL}; + + env(pay(alice, bob, USD(5'000)), + path(~USD), + sendmax(EUR(100)), + txflags(flags), + ter(expectedTer)); + env.close(); + + if (expectedTer == tesSUCCESS) + { + // carol's offer can be partially crossed when EUR is IOU: + // 10e-3EUR/1USD + using tEUR = std::decay_t; + bool constexpr isEURIOU = std::is_same_v; + // partially crossed if IOU, removed but not taken if MPT + auto const balanceCarolUSD = isEURIOU ? USD(0) : initialCarolUSD; + env.require(offers(carol, 0)); + env.require(balance(carol, balanceCarolUSD)); + } + else + { + // TODO: Offers are not removed when payments fail + // If that is addressed, the test should show that carol's + // offer is removed but not taken, as in the other branch of + // this if statement + } + } + }; + testHelper2TokensMix(test); + } + + void + testInsufficientReserve(FeatureBitset features) + { + testcase("Insufficient Reserve"); + + // If an account places an offer and its balance + // *before* the transaction began isn't high enough + // to meet the reserve *after* the transaction runs, + // then no offer should go on the books but if the + // offer partially or fully crossed the tx succeeds. + + using namespace jtx; + + auto const gw = Account{"gateway"}; + auto const alice = Account{"alice"}; + auto const bob = Account{"bob"}; + auto const carol = Account{"carol"}; + + auto const xrpOffer = XRP(1'000); + + // No crossing: + { + Env env{*this, features}; + + env.fund(XRP(1'000'000), gw); + + auto const f = env.current()->fees().base; + auto const r = reserve(env, 0); + + env.fund(r + f, alice); + + MPT const USD = MPTTester({.env = env, .issuer = gw, .holders = {alice}}); + + auto const usdOffer = USD(1'000); + + env(pay(gw, alice, usdOffer), ter(tesSUCCESS)); + env(offer(alice, xrpOffer, usdOffer), ter(tecINSUF_RESERVE_OFFER)); + + env.require(balance(alice, r - f), owners(alice, 1)); + } + + // Partial cross: + { + Env env{*this, features}; + + env.fund(XRP(1'000'000), gw); + + auto const f = env.current()->fees().base; + auto const r = reserve(env, 0); + + env.fund(r + f, alice); + env.fund(r + 2 * f + xrpOffer, bob); + + MPT const USD = MPTTester({.env = env, .issuer = gw, .holders = {alice, bob}}); + + auto const usdOffer = USD(1'000); + auto const usdOffer2 = USD(500); + auto const xrpOffer2 = XRP(500); + + env(offer(bob, usdOffer2, xrpOffer2), ter(tesSUCCESS)); + + env(pay(gw, alice, usdOffer), ter(tesSUCCESS)); + env(offer(alice, xrpOffer, usdOffer), ter(tesSUCCESS)); + + env.require( + balance(alice, r - f + xrpOffer2), + balance(alice, usdOffer2), + owners(alice, 1), + balance(bob, r + xrpOffer2), + balance(bob, usdOffer2), + owners(bob, 1)); + } + + // Account has enough reserve as is, but not enough + // if an offer were added. Attempt to sell MPTs to + // buy XRP. If it fully crosses, we succeed. + { + Env env{*this, features}; + + env.fund(XRP(1'000'000), gw); + + auto const f = env.current()->fees().base; + auto const r = reserve(env, 0); + + env.fund(r + f, alice); + + MPT const USD = MPTTester({.env = env, .issuer = gw, .holders = {alice}}); + + auto const usdOffer = USD(1'000); + auto const usdOffer2 = USD(500); + auto const xrpOffer2 = XRP(500); + + env.fund(r + f + xrpOffer, bob, carol); + env(offer(bob, usdOffer2, xrpOffer2), ter(tesSUCCESS)); + env(offer(carol, usdOffer, xrpOffer), ter(tesSUCCESS)); + + env(pay(gw, alice, usdOffer), ter(tesSUCCESS)); + env(offer(alice, xrpOffer, usdOffer), ter(tesSUCCESS)); + + env.require( + balance(alice, r - f + xrpOffer), + balance(alice, USD(0)), + owners(alice, 1), + balance(bob, r + xrpOffer2), + balance(bob, usdOffer2), + owners(bob, 1), + balance(carol, r + xrpOffer2), + balance(carol, usdOffer2), + owners(carol, 2)); + } + } + + // Helper function that returns the Offers on an account. + static std::vector> + offersOnAccount(jtx::Env& env, jtx::Account account) + { + std::vector> result; + forEachItem(*env.current(), account, [&result](std::shared_ptr const& sle) { + if (sle->getType() == ltOFFER) + result.push_back(sle); + }); + return result; + } + + void + testFillModes(FeatureBitset features) + { + testcase("Fill Modes"); + + using namespace jtx; + + auto const startBalance = XRP(1'000'000); + auto const gw = Account{"gateway"}; + auto const alice = Account{"alice"}; + auto const bob = Account{"bob"}; + + // Fill or Kill - unless we fully cross, just charge a fee and don't + // place the offer on the books. But also clean up expired offers + // that are discovered along the way. + // + { + Env env{*this, features}; + + auto const f = env.current()->fees().base; + + env.fund(startBalance, gw, alice, bob); + + MPTTester MUSD({.env = env, .issuer = gw}); + MPT const USD = MUSD["USD"]; + + // bob creates an offer that expires before the next ledger close. + env(offer(bob, USD(500), XRP(500)), + json(sfExpiration.fieldName, lastClose(env) + 1), + ter(tesSUCCESS)); + + // The offer expires (it's not removed yet). + env.close(); + env.require(owners(bob, 1), offers(bob, 1)); + + // bob creates the offer that will be crossed. + env(offer(bob, USD(500), XRP(500)), ter(tesSUCCESS)); + env.close(); + env.require(owners(bob, 2), offers(bob, 2)); + + MUSD.authorize({.account = alice}); + env(pay(gw, alice, USD(1'000)), ter(tesSUCCESS)); + + // Order that can't be filled but will remove bob's expired offer: + env(offer(alice, XRP(1'000), USD(1'000)), txflags(tfFillOrKill), ter(tecKILLED)); + + env.require( + balance(alice, startBalance - (f * 2)), + balance(alice, USD(1'000)), + owners(alice, 1), + offers(alice, 0), + balance(bob, startBalance - (f * 2)), + balance(bob, USD(none)), + owners(bob, 1), + offers(bob, 1)); + + // Order that can be filled + env(offer(alice, XRP(500), USD(500)), txflags(tfFillOrKill), ter(tesSUCCESS)); + + env.require( + balance(alice, startBalance - (f * 3) + XRP(500)), + balance(alice, USD(500)), + owners(alice, 1), + offers(alice, 0), + balance(bob, startBalance - (f * 2) - XRP(500)), + balance(bob, USD(500)), + owners(bob, 1), + offers(bob, 0)); + } + + // Immediate or Cancel - cross as much as possible + // and add nothing on the books: + { + Env env{*this, features}; + + auto const f = env.current()->fees().base; + + env.fund(startBalance, gw, alice, bob); + + MPT const USD = MPTTester({.env = env, .issuer = gw, .holders = {alice}}); + + env(pay(gw, alice, USD(1'000)), ter(tesSUCCESS)); + + // No cross: + { + env(offer(alice, XRP(1'000), USD(1000)), + txflags(tfImmediateOrCancel), + ter(tecKILLED)); + } + + env.require( + balance(alice, startBalance - f - f), + balance(alice, USD(1000)), + owners(alice, 1), + offers(alice, 0)); + + // Partially cross: + env(offer(bob, USD(50), XRP(50)), ter(tesSUCCESS)); + env(offer(alice, XRP(1000), USD(1000)), txflags(tfImmediateOrCancel), ter(tesSUCCESS)); + + env.require( + balance(alice, startBalance - f - f - f + XRP(50)), + balance(alice, USD(950)), + owners(alice, 1), + offers(alice, 0), + balance(bob, startBalance - f - XRP(50)), + balance(bob, USD(50)), + owners(bob, 1), + offers(bob, 0)); + + // Fully cross: + env(offer(bob, USD(50), XRP(50)), ter(tesSUCCESS)); + env(offer(alice, XRP(50), USD(50)), txflags(tfImmediateOrCancel), ter(tesSUCCESS)); + + env.require( + balance(alice, startBalance - f - f - f - f + XRP(100)), + balance(alice, USD(900)), + owners(alice, 1), + offers(alice, 0), + balance(bob, startBalance - f - f - XRP(100)), + balance(bob, USD(100)), + owners(bob, 1), + offers(bob, 0)); + } + + // tfPassive -- place the offer without crossing it. + { + Env env(*this, features); + + env.fund(startBalance, gw, alice, bob); + env.close(); + + MPT const USD = MPTTester({.env = env, .issuer = gw, .holders = {bob}}); + + env(pay(gw, bob, USD(1'000))); + env.close(); + + env(offer(alice, USD(1'000), XRP(2'000))); + env.close(); + + auto const aliceOffers = offersOnAccount(env, alice); + BEAST_EXPECT(aliceOffers.size() == 1); + for (auto const& offerPtr : aliceOffers) + { + auto const& offer = *offerPtr; + BEAST_EXPECT(offer[sfTakerGets] == XRP(2'000)); + BEAST_EXPECT(offer[sfTakerPays] == USD(1'000)); + } + + // bob creates a passive offer that could cross alice's. + // bob's offer should stay in the ledger. + env(offer(bob, XRP(2'000), USD(1'000), tfPassive)); + env.close(); + env.require(offers(alice, 1)); + + auto const bobOffers = offersOnAccount(env, bob); + BEAST_EXPECT(bobOffers.size() == 1); + for (auto const& offerPtr : bobOffers) + { + auto const& offer = *offerPtr; + BEAST_EXPECT(offer[sfTakerGets] == USD(1'000)); + BEAST_EXPECT(offer[sfTakerPays] == XRP(2'000)); + } + + // It should be possible for gw to cross both of those offers. + env(offer(gw, XRP(2'000), USD(1'000))); + env.close(); + env.require(offers(alice, 0)); + env.require(offers(gw, 0)); + env.require(offers(bob, 1)); + + env(offer(gw, USD(1'000), XRP(2'000))); + env.close(); + env.require(offers(bob, 0)); + env.require(offers(gw, 0)); + } + + // tfPassive -- cross only offers of better quality. + { + Env env(*this, features); + + env.fund(startBalance, gw, "alice", "bob"); + env.close(); + + MPT const USD = MPTTester({.env = env, .issuer = gw, .holders = {bob}}); + + env(pay(gw, "bob", USD(10'000))); + env(offer("alice", USD(5'000), XRP(1'001))); + env.close(); + + env(offer("alice", USD(5'000), XRP(1'000))); + env.close(); + + auto const aliceOffers = offersOnAccount(env, "alice"); + BEAST_EXPECT(aliceOffers.size() == 2); + + // bob creates a passive offer. That offer should cross one + // of alice's (the one with better quality) and leave alice's + // other offer untouched. + env(offer("bob", XRP(2'000), USD(10'000), tfPassive)); + env.close(); + env.require(offers("alice", 1)); + + auto const bobOffers = offersOnAccount(env, "bob"); + BEAST_EXPECT(bobOffers.size() == 1); + for (auto const& offerPtr : bobOffers) + { + auto const& offer = *offerPtr; + BEAST_EXPECT(offer[sfTakerGets] == USD(4'995)); + BEAST_EXPECT(offer[sfTakerPays] == XRP(999)); + } + } + } + + void + testMalformed(FeatureBitset features) + { + testcase("Malformed Detection"); + + using namespace jtx; + + auto const startBalance = XRP(1'000'000); + auto const gw = Account{"gateway"}; + auto const alice = Account{"alice"}; + + Env env{*this, features}; + + env.fund(startBalance, gw, alice); + + MPT const USD = MPTTester({.env = env, .issuer = gw, .holders = {alice}}); + + // Sell and buy the same asset + { + // Alice tries an MPT to MPT order: + env(pay(gw, alice, USD(1'000)), ter(tesSUCCESS)); + env(offer(alice, USD(1'000), USD(1'000)), ter(temREDUNDANT)); + env.require(owners(alice, 1), offers(alice, 0)); + } + + // Offers with negative amounts + { + env(offer(alice, -USD(1'000), XRP(1'000)), ter(temBAD_OFFER)); + env.require(owners(alice, 1), offers(alice, 0)); + } + + // Bad MPT + { + auto const BAD = MPT(badMPT()); + + env(offer(alice, XRP(1'000), BAD(1'000)), ter(temBAD_CURRENCY)); + env.require(owners(alice, 1), offers(alice, 0)); + } + } + + void + testExpiration(FeatureBitset features) + { + testcase("Offer Expiration"); + + using namespace jtx; + + auto const gw = Account{"gateway"}; + auto const alice = Account{"alice"}; + auto const bob = Account{"bob"}; + + auto const startBalance = XRP(1'000'000); + auto const xrpOffer = XRP(1'000); + + Env env{*this, features}; + + env.fund(startBalance, gw, alice, bob); + env.close(); + + auto const f = env.current()->fees().base; + + MPT const USD = MPTTester({.env = env, .issuer = gw, .holders = {alice}}); + auto const usdOffer = USD(1'000); + + env(pay(gw, alice, usdOffer), ter(tesSUCCESS)); + env.close(); + env.require( + balance(alice, startBalance - f), + balance(alice, usdOffer), + offers(alice, 0), + owners(alice, 1)); + + // Place an offer that should have already expired. + env(offer(alice, xrpOffer, usdOffer), + json(sfExpiration.fieldName, lastClose(env)), + ter(TER{tecEXPIRED})); + + env.require( + balance(alice, startBalance - f - f), + balance(alice, usdOffer), + offers(alice, 0), + owners(alice, 1)); + env.close(); + + // Add an offer that expires before the next ledger close + env(offer(alice, xrpOffer, usdOffer), + json(sfExpiration.fieldName, lastClose(env) + 1), + ter(tesSUCCESS)); + env.require( + balance(alice, startBalance - f - f - f), + balance(alice, usdOffer), + offers(alice, 1), + owners(alice, 2)); + + // The offer expires (it's not removed yet) + env.close(); + env.require( + balance(alice, startBalance - f - f - f), + balance(alice, usdOffer), + offers(alice, 1), + owners(alice, 2)); + + // Add offer - the expired offer is removed + env(offer(bob, usdOffer, xrpOffer), ter(tesSUCCESS)); + + env.require( + balance(alice, startBalance - f - f - f), + balance(alice, usdOffer), + offers(alice, 0), + owners(alice, 1), + balance(bob, startBalance - f), + balance(bob, USD(none)), + offers(bob, 1), + owners(bob, 1)); + } + + void + testUnfundedCross(FeatureBitset features) + { + testcase("Unfunded Crossing"); + + using namespace jtx; + + auto const gw = Account{"gateway"}; + + auto const xrpOffer = XRP(1'000); + + Env env{*this, features}; + + env.fund(XRP(1'000'000), gw); + + // The fee that's charged for transactions + auto const f = env.current()->fees().base; + + // Account is at the reserve, and will dip below once + // fees are subtracted. + env.fund(reserve(env, 0), "alice"); + MPT const USD = MPTTester({.env = env, .issuer = gw}); + auto const usdOffer = USD(1'000); + env(offer("alice", usdOffer, xrpOffer), ter(tecUNFUNDED_OFFER)); + env.require(balance("alice", reserve(env, 0) - f), owners("alice", 0)); + + // Account has just enough for the reserve and the + // fee. + env.fund(reserve(env, 0) + f, "bob"); + env(offer("bob", usdOffer, xrpOffer), ter(tecUNFUNDED_OFFER)); + env.require(balance("bob", reserve(env, 0)), owners("bob", 0)); + + // Account has enough for the reserve, the fee and + // the offer, and a bit more, but not enough for the + // reserve after the offer is placed. + env.fund(reserve(env, 0) + f + XRP(1), "carol"); + env(offer("carol", usdOffer, xrpOffer), ter(tecINSUF_RESERVE_OFFER)); + env.require(balance("carol", reserve(env, 0) + XRP(1)), owners("carol", 0)); + + // Account has enough for the reserve plus one + // offer, and the fee. + env.fund(reserve(env, 1) + f, "dan"); + env(offer("dan", usdOffer, xrpOffer), ter(tesSUCCESS)); + env.require(balance("dan", reserve(env, 1)), owners("dan", 1)); + + // Account has enough for the reserve plus one + // offer, the fee and the entire offer amount. + env.fund(reserve(env, 1) + f + xrpOffer, "eve"); + env(offer("eve", usdOffer, xrpOffer), ter(tesSUCCESS)); + env.require(balance("eve", reserve(env, 1) + xrpOffer), owners("eve", 1)); + } + + void + testSelfCross(bool use_partner, FeatureBitset features) + { + testcase(std::string("Self-crossing") + (use_partner ? ", with partner account" : "")); + + using namespace jtx; + auto const gw = Account{"gateway"}; + auto const partner = Account{"partner"}; + + auto test = [&](auto&& issue1, auto&& issue2) { + Env env{*this, features}; + env.close(); + + env.fund(XRP(10'000), gw); + auto const USD = issue1({.env = env, .token = "USD", .issuer = gw}); + auto const BTC = issue2({.env = env, .token = "BTC", .issuer = gw}); + using tUSD = std::decay_t; + using tBTC = std::decay_t; + if (use_partner) + { + env.fund(XRP(10'000), partner); + if constexpr (std::is_same_v) + { + env(trust(partner, USD(100))); + } + else + { + MPTTester MUSD(env, gw, USD); + MUSD.authorize({.account = partner}); + } + if constexpr (std::is_same_v) + { + env(trust(partner, BTC(500))); + } + else + { + MPTTester MBTC(env, gw, BTC); + MBTC.authorize({.account = partner}); + } + env(pay(gw, partner, USD(100))); + env(pay(gw, partner, BTC(500))); + } + auto const& account_to_test = use_partner ? partner : gw; + + env.close(); + env.require(offers(account_to_test, 0)); + + // PART 1: + // we will make two offers that can be used to bridge BTC to USD + // through XRP + env(offer(account_to_test, BTC(250), XRP(1'000))); + env.require(offers(account_to_test, 1)); + + // validate that the book now shows a BTC for XRP offer + BEAST_EXPECT(isOffer(env, account_to_test, BTC(250), XRP(1'000))); + + auto const secondLegSeq = env.seq(account_to_test); + env(offer(account_to_test, XRP(1'000), USD(50))); + env.require(offers(account_to_test, 2)); + + // validate that the book also shows a XRP for USD offer + BEAST_EXPECT(isOffer(env, account_to_test, XRP(1'000), USD(50))); + + // now make an offer that will cross and auto-bridge, meaning + // the outstanding offers will be taken leaving us with none + env(offer(account_to_test, USD(50), BTC(250))); + + auto jrr = getBookOffers(env, USD, BTC); + BEAST_EXPECT(jrr[jss::offers].isArray()); + BEAST_EXPECT(jrr[jss::offers].size() == 0); + + jrr = getBookOffers(env, BTC, XRP); + BEAST_EXPECT(jrr[jss::offers].isArray()); + BEAST_EXPECT(jrr[jss::offers].size() == 0); + + // At this point, all offers are expected to be consumed. + { + auto acctOffers = offersOnAccount(env, account_to_test); + + // No stale offers + BEAST_EXPECT(acctOffers.empty()); + for (auto const& offerPtr : acctOffers) + { + auto const& offer = *offerPtr; + BEAST_EXPECT(offer[sfLedgerEntryType] == ltOFFER); + BEAST_EXPECT(offer[sfTakerGets] == USD(0)); + BEAST_EXPECT(offer[sfTakerPays] == XRP(0)); + } + } + + // cancel that lingering second offer so that it doesn't interfere + // with the next set of offers we test. This will not be needed once + // the bridging bug is fixed + env(offer_cancel(account_to_test, secondLegSeq)); + env.require(offers(account_to_test, 0)); + + // PART 2: + // simple direct crossing BTC to USD and then USD to BTC which + // causes the first offer to be replaced + env(offer(account_to_test, BTC(250), USD(50))); + env.require(offers(account_to_test, 1)); + + // validate that the book shows one BTC for USD offer and no USD for + // BTC offers + BEAST_EXPECT(isOffer(env, account_to_test, BTC(250), USD(50))); + + jrr = getBookOffers(env, USD, BTC); + BEAST_EXPECT(jrr[jss::offers].isArray()); + BEAST_EXPECT(jrr[jss::offers].size() == 0); + + // this second offer would self-cross directly, so it causes the + // first offer by the same owner/taker to be removed + env(offer(account_to_test, USD(50), BTC(250))); + env.require(offers(account_to_test, 1)); + + // validate that we now have just the second offer...the first + // was removed + jrr = getBookOffers(env, BTC, USD); + BEAST_EXPECT(jrr[jss::offers].isArray()); + BEAST_EXPECT(jrr[jss::offers].size() == 0); + + BEAST_EXPECT(isOffer(env, account_to_test, USD(50), BTC(250))); + }; + testHelper2TokensMix(test); + } + + void + testNegativeBalance(FeatureBitset features) + { + // This test creates an offer test for negative balance + // with transfer fees and miniscule funds. + testcase("Negative Balance"); + + using namespace jtx; + FeatureBitset const localFeatures = features | fixReducedOffersV2; + + Env env{*this, localFeatures}; + + auto const gw = Account{"gateway"}; + auto const alice = Account{"alice"}; + auto const bob = Account{"bob"}; + + // these *interesting* amounts were taken + // from the original JS test that was ported here + auto const gw_initial_balance = drops(1'149'999'730); + auto const alice_initial_balance = drops(499'946'999'680); + auto const bob_initial_balance = drops(10'199'999'920); + + env.fund(gw_initial_balance, gw); + env.fund(alice_initial_balance, alice); + env.fund(bob_initial_balance, bob); + + MPTTester const MUSD( + {.env = env, .issuer = gw, .holders = {alice, bob}, .transferFee = 5'000}); + MPT const USD = MUSD; + auto const small_amount = STAmount{USD, 1}; + + env(pay(gw, alice, USD(50))); + env(pay(gw, bob, small_amount)); + + env(offer(alice, USD(50), XRP(150'000))); + + // unfund the offer + env(pay(alice, gw, USD(50))); + + // verify balances + auto jrr = ledgerEntryMPT(env, alice, USD); + // this represents 0 since MPTAmount is a default field + BEAST_EXPECT(!jrr[jss::node].isMember(sfMPTAmount.fieldName)); + + jrr = ledgerEntryMPT(env, bob, USD); + BEAST_EXPECT(jrr[jss::node][sfMPTAmount.fieldName] == "1"); + + // create crossing offer + std::uint32_t const bobOfferSeq = env.seq(bob); + env(offer(bob, XRP(2000), USD(1))); + + // With the rounding introduced by fixReducedOffersV2, bob's + // offer does not cross alice's offer and goes straight into + // the ledger. + jrr = ledgerEntryMPT(env, bob, USD); + BEAST_EXPECT(jrr[jss::node][sfMPTAmount.fieldName] == "1"); + + Json::Value const bobOffer = ledgerEntryOffer(env, bob, bobOfferSeq)[jss::node]; + BEAST_EXPECT(bobOffer[sfTakerGets.jsonName][jss::value] == "1"); + BEAST_EXPECT(bobOffer[sfTakerPays.jsonName] == "2000000000"); + } + + void + testOfferCrossWithXRP(bool reverse_order, FeatureBitset features) + { + testcase( + std::string("Offer Crossing with XRP, ") + (reverse_order ? "Reverse" : "Normal") + + " order"); + + using namespace jtx; + + Env env{*this, features}; + + auto const gw = Account{"gateway"}; + auto const alice = Account{"alice"}; + auto const bob = Account{"bob"}; + + env.fund(XRP(10'000), gw, alice, bob); + + MPT const USD = MPTTester({.env = env, .issuer = gw, .holders = {alice, bob}}); + + env(pay(gw, alice, USD(500))); + + if (reverse_order) + env(offer(bob, USD(1), XRP(4'000))); + + env(offer(alice, XRP(150'000), USD(50))); + + if (!reverse_order) + env(offer(bob, USD(1), XRP(4000))); + + // Existing offer pays better than this wants. + // Fully consume existing offer. + // Pay 1 USD, get 4000 XRP. + + auto jrr = ledgerEntryMPT(env, bob, USD); + BEAST_EXPECT(jrr[jss::node][sfMPTAmount.fieldName] == "1"); + jrr = ledgerEntryRoot(env, bob); + BEAST_EXPECT( + jrr[jss::node][sfBalance.fieldName] == + to_string( + (XRP(10000) - XRP(reverse_order ? 4000 : 3000) - env.current()->fees().base * 2) + .xrp())); + + jrr = ledgerEntryMPT(env, alice, USD); + BEAST_EXPECT(jrr[jss::node][sfMPTAmount.fieldName] == "499"); + jrr = ledgerEntryRoot(env, alice); + BEAST_EXPECT( + jrr[jss::node][sfBalance.fieldName] == + to_string( + (XRP(10000) + XRP(reverse_order ? 4000 : 3000) - env.current()->fees().base * 2) + .xrp())); + } + + void + testOfferCrossWithLimitOverride(FeatureBitset features) + { + testcase("Offer Crossing with Limit Override"); + + using namespace jtx; + + Env env{*this, features}; + + auto const gw = Account{"gateway"}; + auto const alice = Account{"alice"}; + auto const bob = Account{"bob"}; + + env.fund(XRP(100000), gw, alice, bob); + + MPT const USD = MPTTester({.env = env, .issuer = gw, .holders = {alice}}); + + env(pay(gw, alice, USD(500))); + + env(offer(alice, XRP(150'000), USD(50))); + env(offer(bob, USD(1), XRP(3'000))); + + auto jrr = ledgerEntryMPT(env, bob, USD); + BEAST_EXPECT(jrr[jss::node][sfMPTAmount.fieldName] == "1"); + jrr = ledgerEntryRoot(env, bob); + BEAST_EXPECT( + jrr[jss::node][sfBalance.fieldName] == + to_string((XRP(100'000) - XRP(3'000) - env.current()->fees().base * 1).xrp())); + + jrr = ledgerEntryMPT(env, alice, USD); + BEAST_EXPECT(jrr[jss::node][sfMPTAmount.fieldName] == "499"); + jrr = ledgerEntryRoot(env, alice); + BEAST_EXPECT( + jrr[jss::node][sfBalance.fieldName] == + to_string((XRP(100'000) + XRP(3'000) - env.current()->fees().base * 2).xrp())); + } + + void + testOfferAcceptThenCancel(FeatureBitset features) + { + testcase("Offer Accept then Cancel."); + + using namespace jtx; + + Env env{*this, features}; + + MPT const USD = MPTTester({.env = env, .issuer = env.master}); + + auto const nextOfferSeq = env.seq(env.master); + env(offer(env.master, XRP(500), USD(100))); + env.close(); + + env(offer_cancel(env.master, nextOfferSeq)); + BEAST_EXPECT(env.seq(env.master) == nextOfferSeq + 2); + + // ledger_accept, call twice and verify no odd behavior + env.close(); + env.close(); + BEAST_EXPECT(env.seq(env.master) == nextOfferSeq + 2); + } + + void + testCurrencyConversionEntire(FeatureBitset features) + { + testcase("Currency Conversion: Entire Offer"); + + using namespace jtx; + + Env env{*this, features}; + + auto const gw = Account{"gateway"}; + auto const alice = Account{"alice"}; + auto const bob = Account{"bob"}; + + env.fund(XRP(10'000), gw, alice, bob); + env.require(owners(bob, 0)); + + MPT const USD = MPTTester({.env = env, .issuer = gw, .holders = {alice, bob}}); + + env.require(owners(alice, 1), owners(bob, 1)); + + env(pay(gw, alice, USD(100))); + auto const bobOfferSeq = env.seq(bob); + env(offer(bob, USD(100), XRP(500))); + + env.require(owners(alice, 1), owners(bob, 2)); + auto jro = ledgerEntryOffer(env, bob, bobOfferSeq); + BEAST_EXPECT(jro[jss::node][jss::TakerGets] == XRP(500).value().getText()); + BEAST_EXPECT(jro[jss::node][jss::TakerPays] == USD(100).value().getJson(JsonOptions::none)); + + env(pay(alice, alice, XRP(500)), sendmax(USD(100))); + + auto jrr = ledgerEntryMPT(env, alice, USD); + BEAST_EXPECT(!jrr[jss::node].isMember(sfMPTAmount.fieldName)); + jrr = ledgerEntryRoot(env, alice); + BEAST_EXPECT( + jrr[jss::node][sfBalance.fieldName] == + to_string((XRP(10'000) + XRP(500) - env.current()->fees().base * 2).xrp())); + + jrr = ledgerEntryMPT(env, bob, USD); + BEAST_EXPECT(jrr[jss::node][sfMPTAmount.fieldName] == "100"); + + jro = ledgerEntryOffer(env, bob, bobOfferSeq); + BEAST_EXPECT(jro[jss::error] == "entryNotFound"); + + env.require(owners(alice, 1), owners(bob, 1)); + } + + void + testCurrencyConversionIntoDebt(FeatureBitset features) + { + testcase("Currency Conversion: Offerer Into Debt"); + + using namespace jtx; + auto const alice = Account{"alice"}; + auto const bob = Account{"bob"}; + auto const carol = Account{"carol"}; + + auto test = [&](auto&& issue1, auto&& issue2, auto&& issue3) { + Env env{*this, features}; + + env.fund(XRP(10'000), alice, bob, carol); + + auto const USD = + issue1({.env = env, .token = "USD", .issuer = alice, .holders = {bob}}); + auto const EURC = + issue2({.env = env, .token = "EUC", .issuer = carol, .holders = {alice}}); + auto const EURB = + issue3({.env = env, .token = "EUB", .issuer = bob, .holders = {carol}}); + + auto const bobOfferSeq = env.seq(bob); + env(offer(bob, USD(50), EURC(200)), ter(tecUNFUNDED_OFFER)); + + env(offer(alice, EURC(200), USD(50))); + + auto jro = ledgerEntryOffer(env, bob, bobOfferSeq); + BEAST_EXPECT(jro[jss::error] == "entryNotFound"); + }; + testHelper3TokensMix(test); + } + + void + testCurrencyConversionInParts(FeatureBitset features) + { + testcase("Currency Conversion: In Parts"); + + using namespace jtx; + + Env env{*this, features}; + + auto const gw = Account{"gateway"}; + auto const alice = Account{"alice"}; + auto const bob = Account{"bob"}; + + env.fund(XRP(10'000), gw, alice, bob); + + MPT const USD = MPTTester({.env = env, .issuer = gw, .holders = {alice, bob}}); + + env(pay(gw, alice, USD(200))); + + auto const bobOfferSeq = env.seq(bob); + env(offer(bob, USD(100), XRP(500))); + + env(pay(alice, alice, XRP(200)), sendmax(USD(100))); + + // The previous payment reduced the remaining offer amount by 200 XRP + auto jro = ledgerEntryOffer(env, bob, bobOfferSeq); + BEAST_EXPECT(jro[jss::node][jss::TakerGets] == XRP(300).value().getText()); + BEAST_EXPECT(jro[jss::node][jss::TakerPays] == USD(60).value().getJson(JsonOptions::none)); + + // the balance between alice and gw is 160 USD..200 less the 40 taken + // by the offer + auto jrr = ledgerEntryMPT(env, alice, USD); + BEAST_EXPECT(jrr[jss::node][sfMPTAmount.fieldName] == "160"); + // alice now has 200 more XRP from the payment + jrr = ledgerEntryRoot(env, alice); + BEAST_EXPECT( + jrr[jss::node][sfBalance.fieldName] == + to_string((XRP(10'000) + XRP(200) - env.current()->fees().base * 2).xrp())); + + // bob got 40 USD from partial consumption of the offer + jrr = ledgerEntryMPT(env, bob, USD); + BEAST_EXPECT(jrr[jss::node][sfMPTAmount.fieldName] == "40"); + + // Alice converts USD to XRP which should fail + // due to PartialPayment. + env(pay(alice, alice, XRP(600)), sendmax(USD(100)), ter(tecPATH_PARTIAL)); + + // Alice converts USD to XRP, should succeed because + // we permit partial payment + env(pay(alice, alice, XRP(600)), sendmax(USD(100)), txflags(tfPartialPayment)); + + // Verify the offer was consumed + jro = ledgerEntryOffer(env, bob, bobOfferSeq); + BEAST_EXPECT(jro[jss::error] == "entryNotFound"); + + // verify balances look right after the partial payment + // only 300 XRP should have been payed since that's all + // that remained in the offer from bob. The alice balance is now + // 100 USD because another 60 USD were transferred to bob in the second + // payment + jrr = ledgerEntryMPT(env, alice, USD); + BEAST_EXPECT(jrr[jss::node][sfMPTAmount.fieldName] == "100"); + jrr = ledgerEntryRoot(env, alice); + BEAST_EXPECT( + jrr[jss::node][sfBalance.fieldName] == + to_string((XRP(10'000) + XRP(200) + XRP(300) - env.current()->fees().base * 4).xrp())); + + // bob now has 100 USD - 40 from the first payment and 60 from the + // second (partial) payment + jrr = ledgerEntryMPT(env, bob, USD); + BEAST_EXPECT(jrr[jss::node][sfMPTAmount.fieldName] == "100"); + } + + void + testCrossCurrencyStartXRP(FeatureBitset features) + { + testcase("Cross Currency Payment: Start with XRP"); + + using namespace jtx; + + Env env{*this, features}; + + auto const gw = Account{"gateway"}; + auto const alice = Account{"alice"}; + auto const bob = Account{"bob"}; + auto const carol = Account{"carol"}; + + env.fund(XRP(10'000), gw, alice, bob, carol); + + MPT const USD = MPTTester({.env = env, .issuer = gw, .holders = {carol, bob}}); + + env(pay(gw, carol, USD(500))); + + auto const carolOfferSeq = env.seq(carol); + env(offer(carol, XRP(500), USD(50))); + + env(pay(alice, bob, USD(25)), sendmax(XRP(333))); + + auto jrr = ledgerEntryMPT(env, bob, USD); + BEAST_EXPECT(jrr[jss::node][sfMPTAmount.fieldName] == "25"); + + jrr = ledgerEntryMPT(env, carol, USD); + BEAST_EXPECT(jrr[jss::node][sfMPTAmount.fieldName] == "475"); + + auto jro = ledgerEntryOffer(env, carol, carolOfferSeq); + BEAST_EXPECT(jro[jss::node][jss::TakerGets] == USD(25).value().getJson(JsonOptions::none)); + BEAST_EXPECT(jro[jss::node][jss::TakerPays] == XRP(250).value().getText()); + } + + void + testCrossCurrencyEndXRP(FeatureBitset features) + { + testcase("Cross Currency Payment: End with XRP"); + + using namespace jtx; + + Env env{*this, features}; + + auto const gw = Account{"gateway"}; + auto const alice = Account{"alice"}; + auto const bob = Account{"bob"}; + auto const carol = Account{"carol"}; + + env.fund(XRP(10'000), gw, alice, bob, carol); + + MPT const USD = MPTTester({.env = env, .issuer = gw, .holders = {alice, carol}}); + + env(pay(gw, alice, USD(500))); + + auto const carolOfferSeq = env.seq(carol); + env(offer(carol, USD(50), XRP(500))); + + env(pay(alice, bob, XRP(250)), sendmax(USD(333))); + + auto jrr = ledgerEntryMPT(env, alice, USD); + BEAST_EXPECT(jrr[jss::node][sfMPTAmount.fieldName] == "475"); + + jrr = ledgerEntryMPT(env, carol, USD); + BEAST_EXPECT(jrr[jss::node][sfMPTAmount.fieldName] == "25"); + + jrr = ledgerEntryRoot(env, bob); + BEAST_EXPECT( + jrr[jss::node][sfBalance.fieldName] == + std::to_string(XRP(10'000).value().mantissa() + XRP(250).value().mantissa())); + + auto jro = ledgerEntryOffer(env, carol, carolOfferSeq); + BEAST_EXPECT(jro[jss::node][jss::TakerGets] == XRP(250).value().getText()); + BEAST_EXPECT(jro[jss::node][jss::TakerPays] == USD(25).value().getJson(JsonOptions::none)); + } + + void + testCrossCurrencyBridged(FeatureBitset features) + { + testcase("Cross Currency Payment: Bridged"); + + using namespace jtx; + auto const gw1 = Account{"gateway_1"}; + auto const gw2 = Account{"gateway_2"}; + auto const alice = Account{"alice"}; + auto const bob = Account{"bob"}; + auto const carol = Account{"carol"}; + auto const dan = Account{"dan"}; + + auto test = [&](auto&& issue1, auto&& issue2) { + Env env{*this, features}; + + env.fund(XRP(10'000), gw1, gw2, alice, bob, carol, dan); + + auto const USD = + issue1({.env = env, .token = "USD", .issuer = gw1, .holders = {alice, carol}}); + auto const EUR = + issue1({.env = env, .token = "EUR", .issuer = gw2, .holders = {bob, dan}}); + + env(pay(gw1, alice, USD(500))); + env(pay(gw2, dan, EUR(400))); + + auto const carolOfferSeq = env.seq(carol); + env(offer(carol, USD(50), XRP(500))); + + auto const danOfferSeq = env.seq(dan); + env(offer(dan, XRP(500), EUR(50))); + + Json::Value jtp{Json::arrayValue}; + jtp[0u][0u][jss::currency] = "XRP"; + env(pay(alice, bob, EUR(30)), json(jss::Paths, jtp), sendmax(USD(333))); + + BEAST_EXPECT(env.balance(alice, USD) == USD(470)); + BEAST_EXPECT(env.balance(bob, EUR) == EUR(30)); + BEAST_EXPECT(env.balance(carol, USD) == USD(30)); + BEAST_EXPECT(env.balance(dan, EUR) == EUR(370)); + + auto jro = ledgerEntryOffer(env, carol, carolOfferSeq); + BEAST_EXPECT(jro[jss::node][jss::TakerGets] == XRP(200).value().getText()); + BEAST_EXPECT( + jro[jss::node][jss::TakerPays] == USD(20).value().getJson(JsonOptions::none)); + + jro = ledgerEntryOffer(env, dan, danOfferSeq); + BEAST_EXPECT( + jro[jss::node][jss::TakerGets] == EUR(20).value().getJson(JsonOptions::none)); + BEAST_EXPECT(jro[jss::node][jss::TakerPays] == XRP(200).value().getText()); + }; + testHelper2TokensMix(test); + } + + void + testBridgedSecondLegDry(FeatureBitset features) + { + // At least with Taker bridging, a sensitivity was identified if the + // second leg goes dry before the first one. This test exercises that + // case. + testcase("Auto Bridged Second Leg Dry"); + + using namespace jtx; + Account const alice{"alice"}; + Account const bob{"bob"}; + Account const carol{"carol"}; + Account const gw{"gateway"}; + + auto test = [&](auto&& issue1, auto&& issue2) { + Env env(*this, features); + + env.fund(XRP(100'000'000), alice, bob, carol, gw); + env.close(); + + auto const USD = + issue1({.env = env, .token = "USD", .issuer = gw, .holders = {alice, carol}}); + auto const EUR = issue1({.env = env, .token = "EUR", .issuer = gw, .holders = {bob}}); + + env(pay(gw, alice, USD(10))); + env(pay(gw, carol, USD(3))); + + env(offer(alice, EUR(2), XRP(1))); + env(offer(alice, EUR(2), XRP(1))); + + env(offer(alice, XRP(1), USD(4))); + env(offer(carol, XRP(1), USD(3))); + env.close(); + + // Bob offers to buy 10 USD for 10 EUR. + // 1. He spends 2 EUR taking Alice's auto-bridged offers and + // gets 4 USD for that. + // 2. He spends another 2 EUR taking Alice's last EUR->XRP offer + // and + // Carol's XRP-USD offer. He gets 3 USD for that. + // The key for this test is that Alice's XRP->USD leg goes dry + // before Alice's EUR->XRP. The XRP->USD leg is the second leg + // which showed some sensitivity. + env(pay(gw, bob, EUR(10))); + env.close(); + env(offer(bob, USD(10), EUR(10))); + env.close(); + + env.require(balance(bob, USD(7))); + env.require(balance(bob, EUR(6))); + env.require(offers(bob, 1)); + env.require(owners(bob, 3)); + + env.require(balance(alice, USD(6))); + env.require(balance(alice, EUR(4))); + env.require(offers(alice, 0)); + env.require(owners(alice, 2)); + + env.require(balance(carol, USD(0))); + env.require(balance(carol, EUR(none))); + + env.require(offers(carol, 0)); + env.require(owners(carol, 1)); + }; + testHelper2TokensMix(test); + } + + void + testOfferFeesConsumeFunds(FeatureBitset features) + { + testcase("Offer Fees Consume Funds"); + + using namespace jtx; + auto const gw1 = Account{"gateway_1"}; + auto const gw2 = Account{"gateway_2"}; + auto const gw3 = Account{"gateway_3"}; + auto const alice = Account{"alice"}; + auto const bob = Account{"bob"}; + + auto test = [&](auto&& issue1, auto&& issue2, auto&& issue3) { + Env env{*this, features}; + + // Provide micro amounts to compensate for fees to make results + // round nice. reserve: Alice has 3 entries in the ledger, via trust + // lines fees: + // 1 for each trust limit == 3 (alice < mtgox/amazon/bitstamp) + + // 1 for payment == 4 + auto const base = env.current()->fees().base; + auto const starting_xrp = XRP(100) + env.current()->fees().accountReserve(3) + base * 4; + + env.fund(starting_xrp, gw1, gw2, gw3, alice, bob); + env.close(); + + auto const USD1 = + issue1({.env = env, .token = "US1", .issuer = gw1, .holders = {alice, bob}}); + auto const USD2 = + issue2({.env = env, .token = "US2", .issuer = gw2, .holders = {alice, bob}}); + auto const USD3 = + issue3({.env = env, .token = "US3", .issuer = gw3, .holders = {alice}}); + + env(pay(gw1, bob, USD1(500))); + + env(offer(bob, XRP(200), USD1(200))); + // Alice has 350 fees - a reserve of 50 = 250 reserve = 100 + // available. Ask for more than available to prove reserve works. + env(offer(alice, USD1(200), XRP(200))); + + BEAST_EXPECT(env.balance(alice, USD1) == USD1(100)); + BEAST_EXPECT(env.balance(alice) == STAmount(env.current()->fees().accountReserve(3))); + + BEAST_EXPECT(env.balance(bob, USD1) == USD1(400)); + }; + testHelper3TokensMix(test); + } + + void + testOfferCreateThenCross(FeatureBitset features) + { + testcase("Offer Create, then Cross"); + + using namespace jtx; + + Env env{*this, features}; + env.enableFeature(fixUniversalNumber); + + auto const gw = Account{"gateway"}; + auto const alice = Account{"alice"}; + auto const bob = Account{"bob"}; + + env.fund(XRP(10'000), gw, alice, bob); + + MPT const CUR = + MPTTester({.env = env, .issuer = gw, .holders = {alice, bob}, .transferFee = 5'000}); + + env(pay(gw, bob, CUR(100))); + + env(offer(alice, CUR(50'000), XRP(150'000))); + env(offer(bob, XRP(100), CUR(100))); + + auto jrr = ledgerEntryMPT(env, alice, CUR); + BEAST_EXPECT(jrr[jss::node][sfMPTAmount.fieldName] == "34"); + + jrr = ledgerEntryMPT(env, bob, CUR); + BEAST_EXPECT(jrr[jss::node][sfMPTAmount.fieldName] == "64"); + } + + void + testSellFlagBasic(FeatureBitset features) + { + testcase("Offer tfSell: Basic Sell"); + + using namespace jtx; + + Env env{*this, features}; + + auto const gw = Account{"gateway"}; + auto const alice = Account{"alice"}; + auto const bob = Account{"bob"}; + + auto const starting_xrp = + XRP(100) + env.current()->fees().accountReserve(1) + env.current()->fees().base * 2; + + env.fund(starting_xrp, gw, alice, bob); + + MPT const USD = MPTTester({.env = env, .issuer = gw, .holders = {alice, bob}}); + + env(pay(gw, bob, USD(500))); + + env(offer(bob, XRP(200), USD(200)), json(jss::Flags, tfSell)); + // Alice has 350 + fees - a reserve of 50 = 250 reserve = 100 available. + // Alice has 350 + fees - a reserve of 50 = 250 reserve = 100 available. + // Ask for more than available to prove reserve works. + env(offer(alice, USD(200), XRP(200)), json(jss::Flags, tfSell)); + + auto jrr = ledgerEntryMPT(env, alice, USD); + BEAST_EXPECT(jrr[jss::node][sfMPTAmount.fieldName] == "100"); + jrr = ledgerEntryRoot(env, alice); + BEAST_EXPECT( + jrr[jss::node][sfBalance.fieldName] == + STAmount(env.current()->fees().accountReserve(1)).getText()); + + jrr = ledgerEntryMPT(env, bob, USD); + BEAST_EXPECT(jrr[jss::node][sfMPTAmount.fieldName] == "400"); + } + + void + testSellFlagExceedLimit(FeatureBitset features) + { + testcase("Offer tfSell: 2x Sell Exceed Limit"); + + using namespace jtx; + + Env env{*this, features}; + + auto const gw = Account{"gateway"}; + auto const alice = Account{"alice"}; + auto const bob = Account{"bob"}; + + auto const starting_xrp = + XRP(100) + env.current()->fees().accountReserve(1) + env.current()->fees().base * 2; + + env.fund(starting_xrp, gw, alice, bob); + + MPT const USD = MPTTester({.env = env, .issuer = gw, .holders = {alice, bob}}); + + env(pay(gw, bob, USD(500))); + + env(offer(bob, XRP(100), USD(200))); + // Alice has 350 fees - a reserve of 50 = 250 reserve = 100 available. + // Ask for more than available to prove reserve works. + // Taker pays 100 USD for 100 XRP. + // Selling XRP. + // Will sell all 100 XRP and get more USD than asked for. + env(offer(alice, USD(100), XRP(100)), json(jss::Flags, tfSell)); + + auto jrr = ledgerEntryMPT(env, alice, USD); + BEAST_EXPECT(jrr[jss::node][sfMPTAmount.fieldName] == "200"); + jrr = ledgerEntryRoot(env, alice); + BEAST_EXPECT( + jrr[jss::node][sfBalance.fieldName] == + STAmount(env.current()->fees().accountReserve(1)).getText()); + + jrr = ledgerEntryMPT(env, bob, USD); + BEAST_EXPECT(jrr[jss::node][sfMPTAmount.fieldName] == "300"); + } + + void + testGatewayCrossCurrency(FeatureBitset features) + { + testcase("Client Issue #535: Gateway Cross Currency"); + + using namespace jtx; + auto const gw = Account{"gateway"}; + auto const alice = Account{"alice"}; + auto const bob = Account{"bob"}; + + auto test = [&](auto&& issue1, auto&& issue2) { + Env env{*this, features}; + + auto const base = env.current()->fees().base; + auto const starting_xrp = + XRP(100.1) + env.current()->fees().accountReserve(1) + base * 2; + + env.fund(starting_xrp, gw, alice, bob); + env.close(); + + auto const XTS = + issue1({.env = env, .token = "XTS", .issuer = gw, .holders = {alice, bob}}); + auto const XXX = + issue2({.env = env, .token = "XXX", .issuer = gw, .holders = {alice, bob}}); + env.close(); + + env(pay(gw, alice, XTS(1'000))); + env(pay(gw, alice, XXX(100))); + env(pay(gw, bob, XTS(1'000))); + env(pay(gw, bob, XXX(100))); + + env(offer(alice, XTS(1'000), XXX(100))); + + // WS client is used here because the RPC client could not + // be convinced to pass the build_path argument + auto wsc = makeWSClient(env.app().config()); + Json::Value payment; + payment[jss::secret] = toBase58(generateSeed("bob")); + payment[jss::id] = env.seq(bob); + payment[jss::build_path] = true; + payment[jss::tx_json] = pay(bob, bob, XXX(1)); + payment[jss::tx_json][jss::Sequence] = + env.current()->read(keylet::account(bob.id()))->getFieldU32(sfSequence); + payment[jss::tx_json][jss::Fee] = to_string(env.current()->fees().base); + payment[jss::tx_json][jss::SendMax] = XTS(15).value().getJson(JsonOptions::none); + auto jrr = wsc->invoke("submit", payment); + BEAST_EXPECT(jrr[jss::status] == "success"); + BEAST_EXPECT(jrr[jss::result][jss::engine_result] == "tesSUCCESS"); + if (wsc->version() == 2) + { + BEAST_EXPECT(jrr.isMember(jss::jsonrpc) && jrr[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(jrr.isMember(jss::ripplerpc) && jrr[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(jrr.isMember(jss::id) && jrr[jss::id] == 5); + } + + BEAST_EXPECT(env.balance(alice, XTS) == XTS(1010)); + BEAST_EXPECT(env.balance(alice, XXX) == XXX(99)); + + BEAST_EXPECT(env.balance(bob, XTS) == XTS(990)); + BEAST_EXPECT(env.balance(bob, XXX) == XXX(101)); + }; + testHelper2TokensMix(test); + } + + void + testPartialCross(FeatureBitset features) + { + // Test a number of different corner cases regarding adding a + // possibly crossable offer to an account. The test is table + // driven so it should be easy to add or remove tests. + testcase("Partial Crossing"); + + using namespace jtx; + + auto const gw = Account("gateway"); + + Env env{*this, features}; + + env.fund(XRP(10'000'000), gw); + env.close(); + + auto MUSD = MPTTester({.env = env, .issuer = gw}); + MPT const USD = MUSD; + + // The fee that's charged for transactions + auto const f = env.current()->fees().base; + + // To keep things simple all offers are 1 : 1 for XRP : USD. + enum preAuthType { noPreAuth, acctPreAuth }; + struct TestData + { + std::string account; // Account operated on + STAmount fundXrp; // Account funded with + int bookAmount; // USD -> XRP offer on the books + preAuthType preAuth; // If true, pre-auth MPToken + int offerAmount; // Account offers this much XRP -> USD + TER tec; // Returned tec code + STAmount spentXrp; // Amount removed from fundXrp + PrettyAmount balanceUsd; // Balance on account end + int offers; // Offers on account + int owners; // Owners on account + int scale = 1; // Scale MPT + }; + + // clang-format off + TestData const tests[]{ + // acct fundXrp bookAmt preTrust offerAmt tec spentXrp balanceUSD offers owners scale + {.account="ann", .fundXrp=reserve(env, 0) + 0 * f, .bookAmount=1, .preAuth=noPreAuth, .offerAmount=1000, .tec=tecUNFUNDED_OFFER, .spentXrp=f, .balanceUsd=USD( 0), .offers=0, .owners=0}, // Account is at the reserve, and will dip below once fees are subtracted. + {.account="bev", .fundXrp=reserve(env, 0) + 1 * f, .bookAmount=1, .preAuth=noPreAuth, .offerAmount=1000, .tec=tecUNFUNDED_OFFER, .spentXrp=f, .balanceUsd=USD( 0), .offers=0, .owners=0}, // Account has just enough for the reserve and the fee. + {.account="cam", .fundXrp=reserve(env, 0) + 2 * f, .bookAmount=0, .preAuth=noPreAuth, .offerAmount=1000, .tec=tecINSUF_RESERVE_OFFER, .spentXrp=f, .balanceUsd=USD( 0), .offers=0, .owners=0}, // Account has enough for the reserve, the fee and the offer, and a bit more, but not enough for the reserve after the offer is placed. + {.account="deb", .fundXrp=reserve(env, 0) + 2 * f, .bookAmount=1, .preAuth=noPreAuth, .offerAmount=1000, .tec=tesSUCCESS, .spentXrp=2 * f, .balanceUsd=USD( 1), .offers=0, .owners=1, .scale=100000}, // Account has enough to buy a little USD then the offer runs dry. + {.account="eve", .fundXrp=reserve(env, 1) + 0 * f, .bookAmount=0, .preAuth=noPreAuth, .offerAmount=1000, .tec=tesSUCCESS, .spentXrp=f, .balanceUsd=USD( 0), .offers=1, .owners=1}, // No offer to cross + {.account="flo", .fundXrp=reserve(env, 1) + 0 * f, .bookAmount=1, .preAuth=noPreAuth, .offerAmount=1000, .tec=tesSUCCESS, .spentXrp=XRP( 1) + f, .balanceUsd=USD( 1), .offers=0, .owners=1}, + {.account="gay", .fundXrp=reserve(env, 1) + 1 * f, .bookAmount=1000, .preAuth=noPreAuth, .offerAmount=1000, .tec=tesSUCCESS, .spentXrp=XRP( 50) + f, .balanceUsd=USD( 50), .offers=0, .owners=1}, + {.account="hye", .fundXrp=XRP(1000) + 1 * f, .bookAmount=1000, .preAuth=noPreAuth, .offerAmount=1000, .tec=tesSUCCESS, .spentXrp=XRP( 800) + f, .balanceUsd=USD( 800), .offers=0, .owners=1}, + {.account="ivy", .fundXrp=XRP( 1) + reserve(env, 1) + 1 * f, .bookAmount=1, .preAuth=noPreAuth, .offerAmount=1000, .tec=tesSUCCESS, .spentXrp=XRP( 1) + f, .balanceUsd=USD( 1), .offers=0, .owners=1}, + {.account="joy", .fundXrp=XRP( 1) + reserve(env, 2) + 1 * f, .bookAmount=1, .preAuth=noPreAuth, .offerAmount=1000, .tec=tesSUCCESS, .spentXrp=XRP( 1) + f, .balanceUsd=USD( 1), .offers=1, .owners=2}, + {.account="kim", .fundXrp=XRP( 900) + reserve(env, 2) + 1 * f, .bookAmount=999, .preAuth=noPreAuth, .offerAmount=1000, .tec=tesSUCCESS, .spentXrp=XRP( 999) + f, .balanceUsd=USD( 999), .offers=0, .owners=1}, + {.account="liz", .fundXrp=XRP( 998) + reserve(env, 0) + 1 * f, .bookAmount=999, .preAuth=noPreAuth, .offerAmount=1000, .tec=tesSUCCESS, .spentXrp=XRP( 998) + f, .balanceUsd=USD( 998), .offers=0, .owners=1}, + {.account="meg", .fundXrp=XRP( 998) + reserve(env, 1) + 1 * f, .bookAmount=999, .preAuth=noPreAuth, .offerAmount=1000, .tec=tesSUCCESS, .spentXrp=XRP( 999) + f, .balanceUsd=USD( 999), .offers=0, .owners=1}, + {.account="nia", .fundXrp=XRP( 998) + reserve(env, 2) + 1 * f, .bookAmount=999, .preAuth=noPreAuth, .offerAmount=1000, .tec=tesSUCCESS, .spentXrp=XRP( 999) + f, .balanceUsd=USD( 999), .offers=1, .owners=2}, + {.account="ova", .fundXrp=XRP( 999) + reserve(env, 0) + 1 * f, .bookAmount=1000, .preAuth=noPreAuth, .offerAmount=1000, .tec=tesSUCCESS, .spentXrp=XRP( 999) + f, .balanceUsd=USD( 999), .offers=0, .owners=1}, + {.account="pam", .fundXrp=XRP( 999) + reserve(env, 1) + 1 * f, .bookAmount=1000, .preAuth=noPreAuth, .offerAmount=1000, .tec=tesSUCCESS, .spentXrp=XRP(1000) + f, .balanceUsd=USD( 1000), .offers=0, .owners=1}, + {.account="rae", .fundXrp=XRP( 999) + reserve(env, 2) + 1 * f, .bookAmount=1000, .preAuth=noPreAuth, .offerAmount=1000, .tec=tesSUCCESS, .spentXrp=XRP(1000) + f, .balanceUsd=USD( 1000), .offers=0, .owners=1}, + {.account="sue", .fundXrp=XRP(1000) + reserve(env, 2) + 1 * f, .bookAmount=0, .preAuth=noPreAuth, .offerAmount=1000, .tec=tesSUCCESS, .spentXrp=f, .balanceUsd=USD( 0), .offers=1, .owners=1}, + //---------------- Pre-created MPT --------------------- + // Unlike from IOU, an issuer can't pre-create MPToken for an account (see similar tests in Offer_test.cpp) + {.account="ned", .fundXrp=reserve(env, 1) + 0 * f, .bookAmount=1, .preAuth=acctPreAuth, .offerAmount=1000, .tec=tecUNFUNDED_OFFER, .spentXrp=2 * f, .balanceUsd=USD( 0), .offers=0, .owners=1}, + {.account="ole", .fundXrp=reserve(env, 1) + 1 * f, .bookAmount=1, .preAuth=acctPreAuth, .offerAmount=1000, .tec=tecUNFUNDED_OFFER, .spentXrp=2 * f, .balanceUsd=USD( 0), .offers=0, .owners=1}, + {.account="pat", .fundXrp=reserve(env, 1) + 2 * f, .bookAmount=0, .preAuth=acctPreAuth, .offerAmount=1000, .tec=tecUNFUNDED_OFFER, .spentXrp=2 * f, .balanceUsd=USD( 0), .offers=0, .owners=1}, + {.account="quy", .fundXrp=reserve(env, 1) + 2 * f, .bookAmount=1, .preAuth=acctPreAuth, .offerAmount=1000, .tec=tecUNFUNDED_OFFER, .spentXrp=2 * f, .balanceUsd=USD( 0), .offers=0, .owners=1}, + {.account="ron", .fundXrp=reserve(env, 1) + 3 * f, .bookAmount=0, .preAuth=acctPreAuth, .offerAmount=1000, .tec=tecINSUF_RESERVE_OFFER, .spentXrp=2 * f, .balanceUsd=USD( 0), .offers=0, .owners=1}, + {.account="syd", .fundXrp=reserve(env, 1) + 3 * f, .bookAmount=1, .preAuth=acctPreAuth, .offerAmount=1000, .tec=tesSUCCESS, .spentXrp=3 * f, .balanceUsd=USD( 1), .offers=0, .owners=1, .scale=100000}, + {.account="ted", .fundXrp=XRP( 20) + reserve(env, 1) + 2 * f, .bookAmount=1000, .preAuth=acctPreAuth, .offerAmount=1000, .tec=tesSUCCESS, .spentXrp=XRP(20) + 2 * f, .balanceUsd=USD( 20), .offers=0, .owners=1}, + {.account="uli", .fundXrp=reserve(env, 2) + 0 * f, .bookAmount=0, .preAuth=acctPreAuth, .offerAmount=1000, .tec=tecINSUF_RESERVE_OFFER, .spentXrp=2 * f, .balanceUsd=USD( 0), .offers=0, .owners=1}, + {.account="vic", .fundXrp=reserve(env, 2) + 0 * f, .bookAmount=1, .preAuth=acctPreAuth, .offerAmount=1000, .tec=tesSUCCESS, .spentXrp=XRP( 1) + 2 * f, .balanceUsd=USD( 1), .offers=0, .owners=1}, + {.account="wes", .fundXrp=reserve(env, 2) + 1 * f, .bookAmount=0, .preAuth=acctPreAuth, .offerAmount=1000, .tec=tesSUCCESS, .spentXrp=2 * f, .balanceUsd=USD( 0), .offers=1, .owners=2}, + {.account="xan", .fundXrp=reserve(env, 2) + 1 * f, .bookAmount=1, .preAuth=acctPreAuth, .offerAmount=1000, .tec=tesSUCCESS, .spentXrp=XRP( 1) + 2 * f, .balanceUsd=USD( 1), .offers=1, .owners=2}, + }; + // clang-format on + + for (auto const& t : tests) + { + auto const acct = Account(t.account); + env.fund(t.fundXrp, acct); + env.close(); + + // Make sure gateway has no current offers. + env.require(offers(gw, 0)); + + // The gateway optionally creates an offer that would be crossed. + auto const book = t.bookAmount; + if (book != 0) + env(offer(gw, XRP(book), USD(book * t.scale))); + env.close(); + std::uint32_t const gwOfferSeq = env.seq(gw) - 1; + + // Optionally pre-authorize MPT for acct. + // Note this is not really part of the test, so we expect there + // to be enough XRP reserve for acct to create the trust line. + if (t.preAuth == acctPreAuth) + MUSD.authorize({.account = acct}); + env.close(); + + { + // Acct creates an offer. This is the heart of the test. + auto const acctOffer = t.offerAmount; + env(offer(acct, USD(acctOffer * t.scale), XRP(acctOffer)), ter(t.tec)); + env.close(); + } + std::uint32_t const acctOfferSeq = env.seq(acct) - 1; + + auto const expBalanceUsd = [&]() { + if (t.scale == 1) + return t.balanceUsd; + // crossed offer has XRP available balance of 1 fee + // mpt to XRP ratio is 10 + return USD(f.value() / 10); + }(); + BEAST_EXPECT(env.balance(acct, USD) == expBalanceUsd); + BEAST_EXPECT(env.balance(acct, xrpIssue()) == t.fundXrp - t.spentXrp); + env.require(offers(acct, t.offers)); + env.require(owners(acct, t.owners)); + + auto acctOffers = offersOnAccount(env, acct); + BEAST_EXPECT(acctOffers.size() == t.offers); + if (!acctOffers.empty() && t.offers != 0) + { + auto const& acctOffer = *(acctOffers.front()); + + auto const leftover = t.offerAmount - t.bookAmount; + BEAST_EXPECT(acctOffer[sfTakerGets] == XRP(leftover)); + BEAST_EXPECT(acctOffer[sfTakerPays] == USD(leftover)); + } + + if (t.preAuth == noPreAuth) + { + if (t.balanceUsd.value().signum() != 0) + { + // Verify the correct contents of MPT + BEAST_EXPECT(env.balance(acct, USD) == expBalanceUsd); + } + else + { + // Verify that no MPT was created. + auto const sle = env.le(keylet::mptoken(USD.issuanceID, acct)); + BEAST_EXPECT(!sle); + } + } + + // Give the next loop a clean slate by canceling any left-overs + // in the offers. + env(offer_cancel(acct, acctOfferSeq)); + env(offer_cancel(gw, gwOfferSeq)); + env.close(); + } + } + + void + testXRPDirectCross(FeatureBitset features) + { + testcase("XRP Direct Crossing"); + + using namespace jtx; + + auto const gw = Account("gateway"); + auto const alice = Account("alice"); + auto const bob = Account("bob"); + + Env env{*this, features}; + + env.fund(XRP(1'000'000), gw, bob); + env.close(); + + // The fee that's charged for transactions. + auto const fee = env.current()->fees().base; + + // alice's account has enough for the reserve, one trust line plus two + // offers, and two fees. + env.fund(reserve(env, 2) + fee * 2, alice); + env.close(); + + MPT const USD = MPTTester({.env = env, .issuer = gw, .holders = {alice}}); + + auto const usdOffer = USD(1'000); + auto const xrpOffer = XRP(1'000); + + env(pay(gw, alice, usdOffer)); + env.close(); + env.require(balance(alice, usdOffer), offers(alice, 0), offers(bob, 0)); + + // The scenario: + // o alice has USD but wants XRP. + // o bob has XRP but wants USD. + auto const aliceXRP = env.balance(alice); + auto const bobsXRP = env.balance(bob); + + env(offer(alice, xrpOffer, usdOffer)); + env.close(); + env(offer(bob, usdOffer, xrpOffer)); + + env.close(); + env.require( + balance(alice, USD(0)), + balance(bob, usdOffer), + balance(alice, aliceXRP + xrpOffer - fee), + balance(bob, bobsXRP - xrpOffer - fee), + offers(alice, 0), + offers(bob, 0)); + + BEAST_EXPECT(env.balance(bob, USD) == usdOffer); + + // Make two more offers that leave one of the offers non-dry. + env(offer(alice, USD(999), XRP(999))); + env(offer(bob, xrpOffer, usdOffer)); + + env.close(); + env.require(balance(alice, USD(999))); + env.require(balance(bob, USD(1))); + env.require(offers(alice, 0)); + BEAST_EXPECT(env.balance(bob, USD) == USD(1)); + { + auto const bobsOffers = offersOnAccount(env, bob); + BEAST_EXPECT(bobsOffers.size() == 1); + auto const& bobsOffer = *(bobsOffers.front()); + + BEAST_EXPECT(bobsOffer[sfLedgerEntryType] == ltOFFER); + BEAST_EXPECT(bobsOffer[sfTakerGets] == USD(1)); + BEAST_EXPECT(bobsOffer[sfTakerPays] == XRP(1)); + } + } + + void + testDirectCross(FeatureBitset features) + { + testcase("Direct Crossing"); + + using namespace jtx; + auto const gw = Account("gateway"); + auto const alice = Account("alice"); + auto const bob = Account("bob"); + + auto test = [&](auto&& issue1, auto&& issue2) { + Env env{*this, features}; + + env.fund(XRP(1000000), gw); + env.close(); + + // The fee that's charged for transactions. + auto const fee = env.current()->fees().base; + + // Each account has enough for the reserve, two MPT's, one + // offer, and two fees. + env.fund(reserve(env, 3) + fee * 3, alice); + env.fund(reserve(env, 3) + fee * 2, bob); + env.close(); + + auto const USD = issue1({.env = env, .token = "USD", .issuer = gw, .holders = {alice}}); + auto const EUR = issue2({.env = env, .token = "EUR", .issuer = gw, .holders = {bob}}); + + auto const usdOffer = USD(1'000); + auto const eurOffer = EUR(1'000); + + env(pay(gw, alice, usdOffer)); + env(pay(gw, bob, eurOffer)); + env.close(); + + env.require(balance(alice, usdOffer), balance(bob, eurOffer)); + + // The scenario: + // o alice has USD but wants EUR. + // o bob has EUR but wants USD. + env(offer(alice, eurOffer, usdOffer)); + env(offer(bob, usdOffer, eurOffer)); + + env.close(); + env.require( + balance(alice, eurOffer), balance(bob, usdOffer), offers(alice, 0), offers(bob, 0)); + + // Alice's offer crossing created a default EUR trustline and + // Bob's offer crossing created a default USD trustline: + BEAST_EXPECT(env.balance(alice, EUR) == eurOffer); + BEAST_EXPECT(env.balance(bob, USD) == usdOffer); + + // Make two more offers that leave one of the offers non-dry. + // Guarantee the order of application by putting a close() + // between them. + env(offer(bob, eurOffer, usdOffer)); + env.close(); + + env(offer(alice, USD(999), eurOffer)); + env.close(); + + env.require(offers(alice, 0)); + env.require(offers(bob, 1)); + + env.require(balance(alice, USD(999))); + env.require(balance(alice, EUR(1))); + env.require(balance(bob, USD(1))); + env.require(balance(bob, EUR(999))); + + { + auto bobsOffers = offersOnAccount(env, bob); + if (BEAST_EXPECT(bobsOffers.size() == 1)) + { + auto const& bobsOffer = *(bobsOffers.front()); + + BEAST_EXPECT(bobsOffer[sfTakerGets] == USD(1)); + BEAST_EXPECT(bobsOffer[sfTakerPays] == EUR(1)); + } + } + + // alice makes one more offer that cleans out bob's offer. + env(offer(alice, USD(1), EUR(1))); + env.close(); + + env.require(balance(alice, USD(1'000))); + env.require(balance(alice, EUR(none))); + env.require(balance(bob, USD(none))); + env.require(balance(bob, EUR(1'000))); + env.require(offers(alice, 0)); + env.require(offers(bob, 0)); + + // The two MPT that were generated by the offers still here + // Unlike IOU, MPToken is not automatically deleted + if constexpr (std::is_same_v, MPT>) + { + BEAST_EXPECT(env.le(keylet::mptoken(EUR.issuanceID, alice))); + auto MEUR = MPTTester(env, gw, EUR, {bob}); + // Delete created MPToken to free up reserve + MEUR.authorize({.account = alice, .flags = tfMPTUnauthorize}); + } + if constexpr (std::is_same_v, MPT>) + { + BEAST_EXPECT(env.le(keylet::mptoken(USD.issuanceID, bob))); + auto MUSD = MPTTester(env, gw, USD, {alice}); + // Delete created MPToken to free up reserve + MUSD.authorize({.account = bob, .flags = tfMPTUnauthorize}); + } + + // Make two more offers that leave one of the offers non-dry. We + // need to properly sequence the transactions: + env(offer(alice, EUR(999), usdOffer)); + env.close(); + + env(offer(bob, usdOffer, eurOffer)); + env.close(); + + env.require(offers(alice, 0)); + env.require(offers(bob, 0)); + + env.require(balance(alice, USD(0))); + env.require(balance(alice, EUR(999))); + env.require(balance(bob, USD(1'000))); + env.require(balance(bob, EUR(1))); + }; + testHelper2TokensMix(test); + } + + void + testBridgedCross(FeatureBitset features) + { + testcase("Bridged Crossing"); + + using namespace jtx; + auto const gw = Account("gateway"); + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const carol = Account("carol"); + + auto test = [&](auto&& issue1, auto&& issue2) { + Env env{*this, features}; + + env.fund(XRP(1'000'000), gw, alice, bob, carol); + env.close(); + + auto const USD = issue1({.env = env, .token = "USD", .issuer = gw, .holders = {alice}}); + auto const EUR = issue2({.env = env, .token = "EUR", .issuer = gw, .holders = {carol}}); + + auto const usdOffer = USD(1'000); + auto const eurOffer = EUR(1'000); + + env(pay(gw, alice, usdOffer)); + env(pay(gw, carol, eurOffer)); + env.close(); + + // The scenario: + // o alice has USD but wants XRP. + // o bob has XRP but wants EUR. + // o carol has EUR but wants USD. + // Note that carol's offer must come last. If carol's offer is + // placed before bob's or alice's, then autobridging will not occur. + env(offer(alice, XRP(1'000), usdOffer)); + env(offer(bob, eurOffer, XRP(1'000))); + auto const bobXrpBalance = env.balance(bob); + env.close(); + + // carol makes an offer that partially consumes alice and bob's + // offers. + env(offer(carol, USD(400), EUR(400))); + env.close(); + + env.require( + balance(alice, USD(600)), + balance(bob, EUR(400)), + balance(carol, USD(400)), + balance(bob, bobXrpBalance - XRP(400)), + offers(carol, 0)); + BEAST_EXPECT(env.balance(bob, EUR) == EUR(400)); + BEAST_EXPECT(env.balance(carol, USD) == USD(400)); + { + auto const aliceOffers = offersOnAccount(env, alice); + BEAST_EXPECT(aliceOffers.size() == 1); + auto const& aliceOffer = *(aliceOffers.front()); + + BEAST_EXPECT(aliceOffer[sfLedgerEntryType] == ltOFFER); + BEAST_EXPECT(aliceOffer[sfTakerGets] == USD(600)); + BEAST_EXPECT(aliceOffer[sfTakerPays] == XRP(600)); + } + { + auto const bobsOffers = offersOnAccount(env, bob); + BEAST_EXPECT(bobsOffers.size() == 1); + auto const& bobsOffer = *(bobsOffers.front()); + + BEAST_EXPECT(bobsOffer[sfLedgerEntryType] == ltOFFER); + BEAST_EXPECT(bobsOffer[sfTakerGets] == XRP(600)); + BEAST_EXPECT(bobsOffer[sfTakerPays] == EUR(600)); + } + + // carol makes an offer that exactly consumes alice and bob's + // offers. + env(offer(carol, USD(600), EUR(600))); + env.close(); + + env.require( + balance(alice, USD(0)), + balance(bob, eurOffer), + balance(carol, usdOffer), + balance(bob, bobXrpBalance - XRP(1'000)), + offers(bob, 0), + offers(carol, 0)); + BEAST_EXPECT(env.balance(bob, EUR) == EUR(1'000)); + BEAST_EXPECT(env.balance(carol, USD) == USD(1'000)); + + // In pre-flow code alice's offer is left empty in the ledger. + auto const aliceOffers = offersOnAccount(env, alice); + if (!aliceOffers.empty()) + { + BEAST_EXPECT(aliceOffers.size() == 1); + auto const& aliceOffer = *(aliceOffers.front()); + + BEAST_EXPECT(aliceOffer[sfLedgerEntryType] == ltOFFER); + BEAST_EXPECT(aliceOffer[sfTakerGets] == USD(0)); + BEAST_EXPECT(aliceOffer[sfTakerPays] == XRP(0)); + } + }; + testHelper2TokensMix(test); + } + + void + testSellOffer(FeatureBitset features) + { + // Test a number of different corner cases regarding offer crossing + // when the tfSell flag is set. The test is table driven so it + // should be easy to add or remove tests. + testcase("Sell Offer"); + + using namespace jtx; + + auto const gw = Account("gateway"); + + Env env{*this, features}; + + env.fund(XRP(10'000'000), gw); + + auto MUSD = MPTTester({.env = env, .issuer = gw}); + MPT const USD = MUSD; + + // The fee that's charged for transactions + auto const f = env.current()->fees().base; + + // To keep things simple all offers are 1 : 1 for XRP : USD. + struct TestData + { + std::string account; // Account operated on + STAmount fundXrp; // XRP acct funded with + STAmount fundUSD; // USD acct funded with + STAmount gwGets; // gw's offer + STAmount gwPays; // + STAmount acctGets; // acct's offer + STAmount acctPays; // + TER tec; // Returned tec code + STAmount spentXrp; // Amount removed from fundXrp + STAmount finalUsd; // Final USD balance on acct + int offers; // Offers on acct + int owners; // Owners on acct + STAmount takerGets; // Remainder of acct's offer + STAmount takerPays; // + + // Constructor with takerGets/takerPays + TestData( + std::string&& account_, // Account operated on + STAmount fundXrp_, // XRP acct funded with + STAmount fundUSD_, // USD acct funded with + STAmount gwGets_, // gw's offer + STAmount gwPays_, // + STAmount acctGets_, // acct's offer + STAmount acctPays_, // + TER tec_, // Returned tec code + STAmount spentXrp_, // Amount removed from fundXrp + STAmount finalUsd_, // Final USD balance on acct + int offers_, // Offers on acct + int owners_, // Owners on acct + STAmount takerGets_, // Remainder of acct's offer + STAmount takerPays_) // + : account(std::move(account_)) + , fundXrp(std::move(fundXrp_)) + , fundUSD(std::move(fundUSD_)) + , gwGets(std::move(gwGets_)) + , gwPays(std::move(gwPays_)) + , acctGets(std::move(acctGets_)) + , acctPays(std::move(acctPays_)) + , tec(tec_) + , spentXrp(std::move(spentXrp_)) + , finalUsd(std::move(finalUsd_)) + , offers(offers_) + , owners(owners_) + , takerGets(std::move(takerGets_)) + , takerPays(std::move(takerPays_)) + { + } + + // Constructor without takerGets/takerPays + TestData( + std::string&& account_, // Account operated on + STAmount const& fundXrp_, // XRP acct funded with + STAmount const& fundUSD_, // USD acct funded with + STAmount const& gwGets_, // gw's offer + STAmount const& gwPays_, // + STAmount const& acctGets_, // acct's offer + STAmount const& acctPays_, // + TER tec_, // Returned tec code + STAmount const& spentXrp_, // Amount removed from fundXrp + STAmount const& finalUsd_, // Final USD balance on acct + int offers_, // Offers on acct + int owners_) // Owners on acct + : TestData( + std::move(account_), + fundXrp_, + fundUSD_, + gwGets_, + gwPays_, + acctGets_, + acctPays_, + tec_, + spentXrp_, + finalUsd_, + offers_, + owners_, + STAmount{0}, + STAmount{0}) + { + } + }; + + // clang-format off + TestData const tests[]{ + // acct pays XRP + // acct fundXrp fundUSD gwGets gwPays acctGets acctPays tec spentXrp finalUSD offers owners takerGets takerPays + {"ann", XRP(10) + reserve(env, 0) + 1 * f, USD( 0), XRP(10), USD( 5), USD(10), XRP(10), tecINSUF_RESERVE_OFFER, XRP( 0) + (1 * f), USD( 0), 0, 0}, + {"bev", XRP(10) + reserve(env, 1) + 1 * f, USD( 0), XRP(10), USD( 5), USD(10), XRP(10), tesSUCCESS, XRP( 0) + (1 * f), USD( 0), 1, 1, XRP(10), USD(10)}, + {"cam", XRP(10) + reserve(env, 0) + 1 * f, USD( 0), XRP(10), USD(10), USD(10), XRP(10), tesSUCCESS, XRP( 10) + (1 * f), USD(10), 0, 1}, + {"deb", XRP(10) + reserve(env, 0) + 1 * f, USD( 0), XRP(10), USD(20), USD(10), XRP(10), tesSUCCESS, XRP( 10) + (1 * f), USD(20), 0, 1}, + {"eve", XRP(10) + reserve(env, 0) + 1 * f, USD( 0), XRP(10), USD(20), USD( 5), XRP( 5), tesSUCCESS, XRP( 5) + (1 * f), USD(10), 0, 1}, + {"flo", XRP(10) + reserve(env, 0) + 1 * f, USD( 0), XRP(10), USD(20), USD(20), XRP(20), tesSUCCESS, XRP( 10) + (1 * f), USD(20), 0, 1}, + {"gay", XRP(20) + reserve(env, 1) + 1 * f, USD( 0), XRP(10), USD(20), USD(20), XRP(20), tesSUCCESS, XRP( 10) + (1 * f), USD(20), 0, 1}, + {"hye", XRP(20) + reserve(env, 2) + 1 * f, USD( 0), XRP(10), USD(20), USD(20), XRP(20), tesSUCCESS, XRP( 10) + (1 * f), USD(20), 1, 2, XRP(10), USD(10)}, + // acct pays USD + {"meg", reserve(env, 1) + 2 * f, USD(10), USD(10), XRP( 5), XRP(10), USD(10), tecINSUF_RESERVE_OFFER, XRP( 0) + (2 * f), USD(10), 0, 1}, + {"nia", reserve(env, 2) + 2 * f, USD(10), USD(10), XRP( 5), XRP(10), USD(10), tesSUCCESS, XRP( 0) + (2 * f), USD(10), 1, 2, USD(10), XRP(10)}, + {"ova", reserve(env, 1) + 2 * f, USD(10), USD(10), XRP(10), XRP(10), USD(10), tesSUCCESS, XRP(-10) + (2 * f), USD( 0), 0, 1}, + {"pam", reserve(env, 1) + 2 * f, USD(10), USD(10), XRP(20), XRP(10), USD(10), tesSUCCESS, XRP(-20) + (2 * f), USD( 0), 0, 1}, + {"qui", reserve(env, 1) + 2 * f, USD(10), USD(20), XRP(40), XRP(10), USD(10), tesSUCCESS, XRP(-20) + (2 * f), USD( 0), 0, 1}, + {"rae", reserve(env, 2) + 2 * f, USD(10), USD( 5), XRP( 5), XRP(10), USD(10), tesSUCCESS, XRP( -5) + (2 * f), USD( 5), 1, 2, USD( 5), XRP( 5)}, + {"sue", reserve(env, 2) + 2 * f, USD(10), USD( 5), XRP(10), XRP(10), USD(10), tesSUCCESS, XRP(-10) + (2 * f), USD( 5), 1, 2, USD( 5), XRP( 5)}, + }; + // clang-format on + + auto const zeroUsd = USD(0); + for (auto const& t : tests) + { + // Make sure gateway has no current offers. + env.require(offers(gw, 0)); + + auto const acct = Account(t.account); + + env.fund(t.fundXrp, acct); + env.close(); + + // Optionally give acct some USD. This is not part of the test, + // so we assume that acct has sufficient USD to cover the reserve + // on the trust line. + if (t.fundUSD != zeroUsd) + { + MUSD.authorize({.account = acct}); + env.close(); + env(pay(gw, acct, t.fundUSD)); + env.close(); + } + + env(offer(gw, t.gwGets, t.gwPays)); + env.close(); + std::uint32_t const gwOfferSeq = env.seq(gw) - 1; + + // Acct creates a tfSell offer. This is the heart of the test. + env(offer(acct, t.acctGets, t.acctPays, tfSell), ter(t.tec)); + env.close(); + std::uint32_t const acctOfferSeq = env.seq(acct) - 1; + + // Check results + BEAST_EXPECT(env.balance(acct, USD) == t.finalUsd); + BEAST_EXPECT(env.balance(acct, xrpIssue()) == t.fundXrp - t.spentXrp); + env.require(offers(acct, t.offers)); + env.require(owners(acct, t.owners)); + + if (t.offers != 0) + { + auto const acctOffers = offersOnAccount(env, acct); + if (!acctOffers.empty()) + { + BEAST_EXPECT(acctOffers.size() == 1); + auto const& acctOffer = *(acctOffers.front()); + + BEAST_EXPECT(acctOffer[sfLedgerEntryType] == ltOFFER); + BEAST_EXPECT(acctOffer[sfTakerGets] == t.takerGets); + BEAST_EXPECT(acctOffer[sfTakerPays] == t.takerPays); + } + } + + // Give the next loop a clean slate by canceling any left-overs + // in the offers. + env(offer_cancel(acct, acctOfferSeq)); + env(offer_cancel(gw, gwOfferSeq)); + env.close(); + } + } + + void + testSellWithFillOrKill(FeatureBitset features) + { + // Test a number of different corner cases regarding offer crossing + // when both the tfSell flag and tfFillOrKill flags are set. + testcase("Combine tfSell with tfFillOrKill"); + + using namespace jtx; + + auto const gw = Account("gateway"); + auto const alice = Account("alice"); + auto const bob = Account("bob"); + + Env env{*this, features}; + + env.fund(XRP(10'000'000), gw, alice, bob); + + MPT const USD = MPTTester({.env = env, .issuer = gw, .holders = {bob}}); + + // bob offers XRP for USD. + env(pay(gw, bob, USD(100))); + env.close(); + env(offer(bob, XRP(2'000), USD(20))); + env.close(); + { + // alice submits a tfSell | tfFillOrKill offer that does not cross. + env(offer(alice, USD(21), XRP(2'100), tfSell | tfFillOrKill), ter(tecKILLED)); + env.close(); + env.require(balance(alice, USD(none))); + env.require(offers(alice, 0)); + env.require(balance(bob, USD(100))); + } + { + // alice submits a tfSell | tfFillOrKill offer that crosses. + // Even though tfSell is present it doesn't matter this time. + env(offer(alice, USD(20), XRP(2'000), tfSell | tfFillOrKill)); + env.close(); + env.require(balance(alice, USD(20))); + env.require(offers(alice, 0)); + env.require(balance(bob, USD(80))); + } + { + // alice submits a tfSell | tfFillOrKill offer that crosses and + // returns more than was asked for (because of the tfSell flag). + env(offer(bob, XRP(2'000), USD(20))); + env.close(); + env(offer(alice, USD(10), XRP(1'500), tfSell | tfFillOrKill)); + env.close(); + env.require(balance(alice, USD(35))); + env.require(offers(alice, 0)); + env.require(balance(bob, USD(65))); + } + { + // alice submits a tfSell | tfFillOrKill offer that doesn't cross. + // This would have succeeded with a regular tfSell, but the + // fillOrKill prevents the transaction from crossing since not + // all of the offer is consumed. + + // We're using bob's left-over offer for XRP(500), USD(5) + env(offer(alice, USD(1), XRP(501), tfSell | tfFillOrKill), ter(tecKILLED)); + env.close(); + env.require(balance(alice, USD(35))); + env.require(offers(alice, 0)); + env.require(balance(bob, USD(65))); + } + { + // Alice submits a tfSell | tfFillOrKill offer that finishes + // off the remainder of bob's offer. + + // We're using bob's left-over offer for XRP(500), USD(5) + env(offer(alice, USD(1), XRP(500), tfSell | tfFillOrKill)); + env.close(); + env.require(balance(alice, USD(40))); + env.require(offers(alice, 0)); + env.require(balance(bob, USD(60))); + } + } + + void + testTransferRateOffer(FeatureBitset features) + { + testcase("Transfer Rate Offer"); + + using namespace jtx; + auto const gw1 = Account("gateway1"); + + auto test = [&](auto&& issue1, auto&& issue2) { + Env env{*this, features}; + + // The fee that's charged for transactions. + auto const fee = env.current()->fees().base; + + env.fund(XRP(100'000), gw1); + env.close(); + + auto const USD = + issue1({.env = env, .token = "USD", .issuer = gw1, .transferFee = 25'000}); + using tUSD = std::decay_t; + { + auto const ann = Account("ann"); + auto const bob = Account("bob"); + env.fund(XRP(100) + reserve(env, 2) + (fee * 2), ann, bob); + env.close(); + + if constexpr (std::is_same_v) + { + auto MUSD = MPTTester(env, gw1, USD); + MUSD.authorize({.account = ann}); + MUSD.authorize({.account = bob}); + } + else + { + env(trust(ann, USD(20'000))); + env(trust(bob, USD(20'000))); + env.close(); + } + + env(pay(gw1, bob, USD(12'500))); + env.close(); + + // bob offers to sell USD(100) for XRP. alice takes bob's + // offer. Notice that although bob only offered USD(100), + // USD(125) was removed from his account due to the gateway fee. + // + // A comparable payment would look like this: + // env (pay (bob, alice, USD(100)), sendmax(USD(125))) + env(offer(bob, XRP(1), USD(10'000))); + env.close(); + + env(offer(ann, USD(10'000), XRP(1))); + env.close(); + + env.require(balance(ann, USD(10'000))); + env.require(balance(ann, XRP(99) + reserve(env, 2))); + env.require(offers(ann, 0)); + + env.require(balance(bob, USD(0))); + env.require(balance(bob, XRP(101) + reserve(env, 2))); + env.require(offers(bob, 0)); + } + { + // Reverse the order, so the offer in the books is to sell XRP + // in return for USD. Gateway rate should still apply + // identically. + auto const che = Account("che"); + auto const deb = Account("deb"); + env.fund(XRP(100) + reserve(env, 2) + (fee * 2), che, deb); + env.close(); + + if constexpr (std::is_same_v) + { + auto MUSD = MPTTester(env, gw1, USD); + MUSD.authorize({.account = che}); + MUSD.authorize({.account = deb}); + } + else + { + env(trust(che, USD(20'000))); + env(trust(deb, USD(20'000))); + env.close(); + } + + env(pay(gw1, deb, USD(12'500))); + env.close(); + + env(offer(che, USD(10'000), XRP(1))); + env.close(); + + env(offer(deb, XRP(1), USD(10'000))); + env.close(); + + env.require(balance(che, USD(10'000))); + env.require(balance(che, XRP(99) + reserve(env, 2))); + env.require(offers(che, 0)); + + env.require(balance(deb, USD(0))); + env.require(balance(deb, XRP(101) + reserve(env, 2))); + env.require(offers(deb, 0)); + } + { + auto const eve = Account("eve"); + auto const fyn = Account("fyn"); + + env.fund(XRP(20'000) + (fee * 2), eve, fyn); + env.close(); + + if constexpr (std::is_same_v) + { + auto MUSD = MPTTester(env, gw1, USD); + MUSD.authorize({.account = eve}); + MUSD.authorize({.account = fyn}); + } + else + { + env(trust(eve, USD(20'000))); + env(trust(fyn, USD(20'000))); + env.close(); + } + + env(pay(gw1, eve, USD(10'000))); + env(pay(gw1, fyn, USD(10'000))); + env.close(); + + // This test verifies that the amount removed from an offer + // accounts for the transfer fee that is removed from the + // account but not from the remaining offer. + env(offer(eve, USD(1'000), XRP(4'000))); + env.close(); + std::uint32_t const eveOfferSeq = env.seq(eve) - 1; + + env(offer(fyn, XRP(2'000), USD(500))); + env.close(); + + env.require(balance(eve, USD(10'500))); + env.require(balance(eve, XRP(18'000))); + auto const evesOffers = offersOnAccount(env, eve); + BEAST_EXPECT(evesOffers.size() == 1); + if (!evesOffers.empty()) + { + auto const& evesOffer = *(evesOffers.front()); + BEAST_EXPECT(evesOffer[sfLedgerEntryType] == ltOFFER); + BEAST_EXPECT(evesOffer[sfTakerGets] == XRP(2'000)); + BEAST_EXPECT(evesOffer[sfTakerPays] == USD(500)); + } + env(offer_cancel(eve, eveOfferSeq)); // For later tests + + env.require(balance(fyn, USD(9'375))); + env.require(balance(fyn, XRP(22'000))); + env.require(offers(fyn, 0)); + } + // Start messing with two non-native currencies. + auto const gw2 = Account("gateway2"); + + env.fund(XRP(100'000), gw2); + env.close(); + + auto const EUR = + issue2({.env = env, .token = "EUR", .issuer = gw2, .transferFee = 50'000}); + using tEUR = std::decay_t; + { + // Remove XRP from the equation. Give the two currencies two + // different transfer rates so we can see both transfer rates + // apply in the same transaction. + auto const gay = Account("gay"); + auto const hal = Account("hal"); + env.fund(reserve(env, 3) + (fee * 3), gay, hal); + env.close(); + + if constexpr (std::is_same_v) + { + auto MUSD = MPTTester(env, gw1, USD); + MUSD.authorize({.account = gay}); + MUSD.authorize({.account = hal}); + } + else + { + env(trust(gay, USD(20'000))); + env(trust(hal, USD(20'000))); + env.close(); + } + if constexpr (std::is_same_v) + { + auto MEUR = MPTTester(env, gw2, EUR); + MEUR.authorize({.account = gay}); + MEUR.authorize({.account = hal}); + } + else + { + env(trust(gay, EUR(20'000))); + env(trust(hal, EUR(20'000))); + env.close(); + } + + env(pay(gw1, gay, USD(12'500))); + env(pay(gw2, hal, EUR(150))); + env.close(); + + env(offer(gay, EUR(100), USD(10'000))); + env.close(); + + env(offer(hal, USD(10'000), EUR(100))); + env.close(); + + env.require(balance(gay, USD(0))); + env.require(balance(gay, EUR(100))); + env.require(balance(gay, reserve(env, 3))); + env.require(offers(gay, 0)); + + env.require(balance(hal, USD(10'000))); + env.require(balance(hal, EUR(0))); + env.require(balance(hal, reserve(env, 3))); + env.require(offers(hal, 0)); + } + + { + // Make sure things work right when we're auto-bridging as well. + auto const ova = Account("ova"); + auto const pat = Account("pat"); + auto const qae = Account("qae"); + env.fund(XRP(2) + reserve(env, 3) + (fee * 3), ova, pat, qae); + env.close(); + + // o ova has USD but wants XRP. + // o pat has XRP but wants EUR. + // o qae has EUR but wants USD. + if constexpr (std::is_same_v) + { + auto MUSD = MPTTester(env, gw1, USD); + MUSD.authorize({.account = ova}); + MUSD.authorize({.account = pat}); + MUSD.authorize({.account = qae}); + } + else + { + env(trust(ova, USD(20'000))); + env(trust(pat, USD(20'000))); + env(trust(qae, USD(20'000))); + env.close(); + } + if constexpr (std::is_same_v) + { + auto MEUR = MPTTester(env, gw2, EUR); + MEUR.authorize({.account = ova}); + MEUR.authorize({.account = pat}); + MEUR.authorize({.account = qae}); + } + else + { + env(trust(ova, EUR(20'000))); + env(trust(pat, EUR(20'000))); + env(trust(qae, EUR(20'000))); + env.close(); + } + + env(pay(gw1, ova, USD(12'500))); + env(pay(gw2, qae, EUR(150))); + env.close(); + + env(offer(ova, XRP(2), USD(10'000))); + env(offer(pat, EUR(100), XRP(2))); + env.close(); + + env(offer(qae, USD(10'000), EUR(100))); + env.close(); + + env.require(balance(ova, USD(0))); + env.require(balance(ova, EUR(0))); + env.require(balance(ova, XRP(4) + reserve(env, 3))); + + // In pre-flow code ova's offer is left empty in the ledger. + auto const ovasOffers = offersOnAccount(env, ova); + if (!ovasOffers.empty()) + { + BEAST_EXPECT(ovasOffers.size() == 1); + auto const& ovasOffer = *(ovasOffers.front()); + + BEAST_EXPECT(ovasOffer[sfLedgerEntryType] == ltOFFER); + BEAST_EXPECT(ovasOffer[sfTakerGets] == USD(0)); + BEAST_EXPECT(ovasOffer[sfTakerPays] == XRP(0)); + } + + env.require(balance(pat, USD(0))); + env.require(balance(pat, EUR(100))); + env.require(balance(pat, XRP(0) + reserve(env, 3))); + env.require(offers(pat, 0)); + + env.require(balance(qae, USD(10'000))); + env.require(balance(qae, EUR(0))); + env.require(balance(qae, XRP(2) + reserve(env, 3))); + env.require(offers(qae, 0)); + } + }; + testHelper2TokensMix(test); + } + + void + testSelfCrossOffer1(FeatureBitset features) + { + // The following test verifies some correct but slightly surprising + // behavior in offer crossing. The scenario: + // + // o An entity has created one or more offers. + // o The entity creates another offer that can be directly crossed + // (not autobridged) by the previously created offer(s). + // o Rather than self crossing the offers, delete the old offer(s). + // + // See a more complete explanation in the comments for + // BookOfferCrossingStep::limitSelfCrossQuality(). + // + // Note that, in this particular example, one offer causes several + // crossable offers (worth considerably more than the new offer) + // to be removed from the book. + using namespace jtx; + + auto const gw = Account("gateway"); + + Env env{*this, features}; + + // The fee that's charged for transactions. + auto const fee = env.current()->fees().base; + auto const startBalance = XRP(1'000'000); + + env.fund(startBalance + (fee * 5), gw); + env.close(); + + MPT const USD = MPTTester({.env = env, .issuer = gw}); + + env(offer(gw, USD(60), XRP(600))); + env.close(); + env(offer(gw, USD(60), XRP(600))); + env.close(); + env(offer(gw, USD(60), XRP(600))); + env.close(); + + // three offers + MPTokenIssuance + env.require(owners(gw, 4)); + env.require(balance(gw, startBalance + fee)); + + auto gwOffers = offersOnAccount(env, gw); + BEAST_EXPECT(gwOffers.size() == 3); + for (auto const& offerPtr : gwOffers) + { + auto const& offer = *offerPtr; + BEAST_EXPECT(offer[sfLedgerEntryType] == ltOFFER); + BEAST_EXPECT(offer[sfTakerGets] == XRP(600)); + BEAST_EXPECT(offer[sfTakerPays] == USD(60)); + } + + // Since this offer crosses the first offers, the previous offers + // will be deleted and this offer will be put on the order book. + env(offer(gw, XRP(1'000), USD(100))); + env.close(); + env.require(owners(gw, 2)); + env.require(offers(gw, 1)); + env.require(balance(gw, startBalance)); + + gwOffers = offersOnAccount(env, gw); + BEAST_EXPECT(gwOffers.size() == 1); + for (auto const& offerPtr : gwOffers) + { + auto const& offer = *offerPtr; + BEAST_EXPECT(offer[sfLedgerEntryType] == ltOFFER); + BEAST_EXPECT(offer[sfTakerGets] == USD(100)); + BEAST_EXPECT(offer[sfTakerPays] == XRP(1'000)); + } + } + + void + testSelfCrossOffer2(FeatureBitset features) + { + using namespace jtx; + + auto const gw1 = Account("gateway1"); + auto const gw2 = Account("gateway2"); + auto const alice = Account("alice"); + + auto test = [&](auto&& issue1, auto&& issue2) { + Env env{*this, features}; + + env.fund(XRP(1'000'000), gw1, gw2); + env.close(); + + auto const USD = issue1({.env = env, .token = "USD", .issuer = gw1}); + using tUSD = std::decay_t; + auto const EUR = issue2({.env = env, .token = "EUR", .issuer = gw2}); + using tEUR = std::decay_t; + + // The fee that's charged for transactions. + auto const f = env.current()->fees().base; + + // Test cases + struct TestData + { + std::string acct; // Account operated on + STAmount fundXRP; // XRP acct funded with + STAmount fundUSD; // USD acct funded with + STAmount fundEUR; // EUR acct funded with + TER firstOfferTec; // tec code on first offer + TER secondOfferTec; // tec code on second offer + }; + + // clang-format off + TestData const tests[]{ + // acct fundXRP fundUSD fundEUR firstOfferTec secondOfferTec + {"ann", reserve(env, 3) + f * 4, USD(1000), EUR(1000), tesSUCCESS, tesSUCCESS}, + {"bev", reserve(env, 3) + f * 4, USD( 1), EUR(1000), tesSUCCESS, tesSUCCESS}, + {"cam", reserve(env, 3) + f * 4, USD(1000), EUR( 1), tesSUCCESS, tesSUCCESS}, + {"deb", reserve(env, 3) + f * 4, USD( 0), EUR( 1), tesSUCCESS, tecUNFUNDED_OFFER}, + {"eve", reserve(env, 3) + f * 4, USD( 1), EUR( 0), tecUNFUNDED_OFFER, tesSUCCESS}, + {"flo", reserve(env, 3) + 0, USD(1000), EUR(1000), tecINSUF_RESERVE_OFFER, tecINSUF_RESERVE_OFFER}, + }; + //clang-format on + + for (auto const& t : tests) + { + auto const acct = Account{t.acct}; + env.fund(t.fundXRP, acct); + env.close(); + + if constexpr (std::is_same_v) + { + auto MUSD = MPTTester(env, gw1, USD); + MUSD.authorize({.account = acct}); + } + else + { + env(trust(acct, USD(1'000))); + env.close(); + } + if constexpr (std::is_same_v) + { + auto MEUR = MPTTester(env, gw2, EUR); + MEUR.authorize({.account = acct}); + } + else + { + env(trust(acct, EUR(1'000))); + env.close(); + } + + if (t.fundUSD > USD(0)) + env(pay(gw1, acct, t.fundUSD)); + if (t.fundEUR > EUR(0)) + env(pay(gw2, acct, t.fundEUR)); + env.close(); + + env(offer(acct, USD(500), EUR(600)), ter(t.firstOfferTec)); + env.close(); + std::uint32_t const firstOfferSeq = env.seq(acct) - 1; + + int offerCount = t.firstOfferTec == tesSUCCESS ? 1 : 0; + env.require(owners(acct, 2 + offerCount)); + env.require(balance(acct, t.fundUSD)); + env.require(balance(acct, t.fundEUR)); + + auto acctOffers = offersOnAccount(env, acct); + BEAST_EXPECT(acctOffers.size() == offerCount); + for (auto const& offerPtr : acctOffers) + { + auto const& offer = *offerPtr; + BEAST_EXPECT(offer[sfLedgerEntryType] == ltOFFER); + BEAST_EXPECT(offer[sfTakerGets] == EUR(600)); + BEAST_EXPECT(offer[sfTakerPays] == USD(500)); + } + + env(offer(acct, EUR(600), USD(500)), ter(t.secondOfferTec)); + env.close(); + std::uint32_t const secondOfferSeq = env.seq(acct) - 1; + + offerCount = t.secondOfferTec == tesSUCCESS ? 1 : offerCount; + env.require(owners(acct, 2 + offerCount)); + env.require(balance(acct, t.fundUSD)); + env.require(balance(acct, t.fundEUR)); + + acctOffers = offersOnAccount(env, acct); + BEAST_EXPECT(acctOffers.size() == offerCount); + for (auto const& offerPtr : acctOffers) + { + auto const& offer = *offerPtr; + BEAST_EXPECT(offer[sfLedgerEntryType] == ltOFFER); + if (offer[sfSequence] == firstOfferSeq) + { + BEAST_EXPECT(offer[sfTakerGets] == EUR(600)); + BEAST_EXPECT(offer[sfTakerPays] == USD(500)); + } + else + { + BEAST_EXPECT(offer[sfTakerGets] == USD(500)); + BEAST_EXPECT(offer[sfTakerPays] == EUR(600)); + } + } + + // Remove any offers from acct for the next pass. + env(offer_cancel(acct, firstOfferSeq)); + env.close(); + env(offer_cancel(acct, secondOfferSeq)); + env.close(); + } + }; + testHelper2TokensMix(test); + } + + void + testSelfCrossOffer(FeatureBitset features) + { + testcase("Self Cross Offer"); + testSelfCrossOffer1(features); + testSelfCrossOffer2(features); + } + + void + testSelfIssueOffer(FeatureBitset features) + { + // Folks who issue their own currency have, in effect, as many + // funds as they are trusted for. This test used to fail because + // self-issuing was not properly checked. Verify that it works + // correctly now. + using namespace jtx; + + Env env{*this, features}; + + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const f = env.current()->fees().base; + + env.fund(XRP(50'000) + f, alice, bob); + env.close(); + + MPT const USD = MPTTester({.env = env, .issuer = bob}); + + env(offer(alice, USD(5'000), XRP(50'000))); + env.close(); + + // This offer should take alice's offer up to Alice's reserve. + env(offer(bob, XRP(50'000), USD(5'000))); + env.close(); + + // alice's offer should have been removed, since she's down to her + // XRP reserve. + env.require(balance(alice, XRP(250))); + env.require(owners(alice, 1)); + env.require(mptokens(alice, 1)); + + // However bob's offer should be in the ledger, since it was not + // fully crossed. + auto const bobOffers = offersOnAccount(env, bob); + BEAST_EXPECT(bobOffers.size() == 1); + for (auto const& offerPtr : bobOffers) + { + auto const& offer = *offerPtr; + BEAST_EXPECT(offer[sfLedgerEntryType] == ltOFFER); + BEAST_EXPECT(offer[sfTakerGets] == USD(25)); + BEAST_EXPECT(offer[sfTakerPays] == XRP(250)); + } + } + + void + testDirectToDirectPath(FeatureBitset features) + { + // The offer crossing code expects that a DirectStep is always + // preceded by a BookStep. In one instance the default path + // was not matching that assumption. Here we recreate that case + // so we can prove the bug stays fixed. + testcase("Direct to Direct path"); + + using namespace jtx; + auto const ann = Account("ann"); + auto const bob = Account("bob"); + auto const cam = Account("cam"); + + auto test = [&](auto&& issue1, auto&& issue2) { + Env env{*this, features}; + + auto const fee = env.current()->fees().base; + env.fund(reserve(env, 4) + (fee * 5), ann, bob, cam); + env.close(); + + auto const A_BUX = issue1( + {.env = env, .token = "AUX", .issuer = ann, .holders = {cam}}); + auto const B_BUX = issue2( + {.env = env, + .token = "BUX", + .issuer = bob, + .holders = {ann, cam}}); + + env(pay(ann, cam, A_BUX(35))); + env(pay(bob, cam, B_BUX(35))); + + env(offer(bob, A_BUX(30), B_BUX(30))); + env.close(); + + // cam puts an offer on the books that her upcoming offer could + // cross. But this offer should be deleted, not crossed, by her + // upcoming offer. + env(offer(cam, A_BUX(29), B_BUX(30), tfPassive)); + env.close(); + env.require(balance(cam, A_BUX(35))); + env.require(balance(cam, B_BUX(35))); + env.require(offers(cam, 1)); + + // This offer caused the assert. + env(offer(cam, B_BUX(30), A_BUX(30))); + env.close(); + + env.require(balance(bob, A_BUX(30))); + env.require(balance(cam, A_BUX(5))); + env.require(balance(cam, B_BUX(65))); + env.require(offers(cam, 0)); + }; + testHelper2TokensMix(test); + } + + void + testSelfCrossLowQualityOffer(FeatureBitset features) + { + // The Flow offer crossing code used to assert if an offer was made + // for more XRP than the offering account held. This unit test + // reproduces that failing case. + testcase("Self crossing low quality offer"); + + using namespace jtx; + + Env env{*this, features}; + + auto const ann = Account("ann"); + auto const gw = Account("gateway"); + + auto const fee = env.current()->fees().base; + env.fund(reserve(env, 2) + drops(9999640) + (fee), ann); + env.fund(reserve(env, 2) + (fee * 4), gw); + env.close(); + + MPT const BTC = MPTTester( + {.env = env, .issuer = gw, .holders = {ann}, .transferFee = 2'000}); + + env(pay(gw, ann, BTC(2'856))); + env.close(); + + env(offer(ann, drops(365'611'702'030), BTC(5'713))); + env.close(); + + // This offer caused the assert. + env(offer(ann, BTC(687), drops(20'000'000'000)), + ter(tecINSUF_RESERVE_OFFER)); + } + + void + testOfferInScaling(FeatureBitset features) + { + // The Flow offer crossing code had a case where it was not rounding + // the offer crossing correctly after a partial crossing. The + // failing case was found on the network. Here we add the case to + // the unit tests. + testcase("Offer In Scaling"); + + using namespace jtx; + + Env env{*this, features}; + + auto const gw = Account("gateway"); + auto const alice = Account("alice"); + auto const bob = Account("bob"); + + auto const fee = env.current()->fees().base; + env.fund(reserve(env, 2) + drops(400'000'000'000) + (fee), alice, bob); + env.fund(reserve(env, 2) + (fee * 4), gw); + env.close(); + + MPT const CNY = MPTTester({.env = env, .issuer = gw, .holders = {bob}}); + + env(pay(gw, bob, CNY(3'000'000))); + env.close(); + + env(offer(bob, drops(5'400'000'000), CNY(2'160'540))); + env.close(); + + // This offer did not round result of partial crossing correctly. + env(offer(alice, CNY(135'620'001), drops(339'000'000'000))); + env.close(); + + auto const aliceOffers = offersOnAccount(env, alice); + BEAST_EXPECT(aliceOffers.size() == 1); + for (auto const& offerPtr : aliceOffers) + { + auto const& offer = *offerPtr; + BEAST_EXPECT(offer[sfLedgerEntryType] == ltOFFER); + BEAST_EXPECT(offer[sfTakerGets] == drops(333'599'446'582)); + BEAST_EXPECT(offer[sfTakerPays] == CNY(13'3459'461)); + } + } + + void + testOfferInScalingWithXferRate(FeatureBitset features) + { + // After adding the previous case, there were still failing rounding + // cases in Flow offer crossing. This one was because the gateway + // transfer rate was not being correctly handled. + testcase("Offer In Scaling With Xfer Rate"); + + using namespace jtx; + auto const gw = Account("gateway"); + auto const alice = Account("alice"); + auto const bob = Account("bob"); + + auto test = [&](auto&& issue1, auto&& issue2) { + Env env{*this, features}; + + auto const fee = env.current()->fees().base; + env.fund( + reserve(env, 2) + drops(400'000'000'000) + fee, + alice, + bob); + env.fund(reserve(env, 2) + (fee * 4), gw); + env.close(); + + auto const JPY = issue1( + {.env = env, + .token = "JPY", + .issuer = gw, + .holders = {alice}, + .limit = maxMPTokenAmount, + .transferFee = 2'000}); + auto const BTC = issue2( + {.env = env, + .token = "BTC", + .issuer = gw, + .holders = {bob}, + .limit = maxMPTokenAmount, + .transferFee = 2'000}); + + env(pay(gw, alice, JPY(3'699'034'802'280'317))); + env(pay(gw, bob, BTC(115'672'255'914'031'100))); + env.close(); + + env(offer( + bob, JPY(1'241'913'390'770'747), BTC(1'969'825'690'469'254))); + env.close(); + + // This offer did not round result of partial crossing correctly. + env(offer( + alice, BTC(5'507'568'706'427'876), JPY(3'472'696'773'391'072))); + env.close(); + + auto const aliceOffers = offersOnAccount(env, alice); + BEAST_EXPECT(aliceOffers.size() == 1); + for (auto const& offerPtr : aliceOffers) + { + auto const& offer = *offerPtr; + BEAST_EXPECT(offer[sfLedgerEntryType] == ltOFFER); + // This test is similar to corresponding Offer_test, except + // that JPY is scaled by 10**12 and BTC is scaled by 10**17. + // There is a difference in the expected results. + // Offer_test expects values + // takerGets:2230.682446713524, takerPays: 0.035378 + // MPT test has the same order of magnitude for the scaled + // values and the first 5 digits match. Is the difference due to + // int arithmetics? + BEAST_EXPECT(offer[sfTakerGets] == JPY(2'230'659'191'281'247)); + BEAST_EXPECT(offer[sfTakerPays] == BTC(3'537'743'015'958'622)); + } + }; + testHelper2TokensMix(test); + } + + void + testSelfPayXferFeeOffer(FeatureBitset features) + { + testcase("Self Pay Xfer Fee"); + // The old offer crossing code does not charge a transfer fee + // if alice pays alice. That's different from how payments work. + // Payments always charge a transfer fee even if the money is staying + // in the same hands. + // + // What's an example where alice pays alice? There are three actors: + // gw, alice, and bob. + // + // 1. gw issues BTC and USD. gw charges a 0.2% transfer fee. + // + // 2. alice makes an offer to buy XRP and sell USD. + // 3. bob makes an offer to buy BTC and sell XRP. + // + // 4. alice now makes an offer to sell BTC and buy USD. + // + // This last offer crosses using auto-bridging. + // o alice's last offer sells BTC to... + // o bob' offer which takes alice's BTC and sells XRP to... + // o alice's first offer which takes bob's XRP and sells USD to... + // o alice's last offer. + // + // So alice sells USD to herself. + // + // There are six cases that we need to test: + // o alice crosses her own offer on the first leg (BTC). + // o alice crosses her own offer on the second leg (USD). + // o alice crosses her own offers on both legs. + // All three cases need to be tested: + // o In reverse (alice has enough BTC to cover her offer) and + // o Forward (alice owns less BTC than is in her final offer. + // + // It turns out that two of the forward cases fail for a different + // reason. They are therefore commented out here, But they are + // revisited in the testSelfPayUnlimitedFunds() unit test. + + using namespace jtx; + auto const gw = Account("gw"); + + auto test = [&](auto&& issue1, auto&& issue2) { + Env env{*this, features}; + auto const baseFee = env.current()->fees().base.drops(); + + auto const startXrpBalance = XRP(4'000'000); + + env.fund(startXrpBalance, gw); + env.close(); + + auto const BTC = issue1( + {.env = env, + .token = "BTC", + .issuer = gw, + .transferFee = 25'000}); + using tBTC = std::decay_t; + env.close(); + auto const USD = issue2( + {.env = env, + .token = "USD", + .issuer = gw, + .transferFee = 25'000}); + using tUSD = std::decay_t; + env.close(); + + // Test cases + struct Actor + { + Account acct; + int offers{}; // offers on account after crossing + PrettyAmount xrp; // final expected after crossing + PrettyAmount btc; // final expected after crossing + PrettyAmount usd; // final expected after crossing + }; + struct TestData + { + // The first three integers give the *index* in actors + // to assign each of the three roles. By using indices it is + // easy for alice to own the offer in the first leg, the second + // leg, or both. + std::size_t self{}; + std::size_t leg0{}; + std::size_t leg1{}; + PrettyAmount btcStart; + std::vector actors; + }; + + // clang-format off + TestData const tests[]{ + // btcStart --------------------- actor[0] --------------------- -------------------- actor[1] ------------------- + {0, 0, 1, BTC(200), {{"ann", 0, drops(3900000'000000 - (4 * baseFee)), BTC(200), USD(3000)}, {"abe", 0, drops(4100000'000000 - (3 * baseFee)), BTC( 0), USD(750)}}}, // no BTC xfer fee + {0, 1, 0, BTC(200), {{"bev", 0, drops(4100000'000000 - (4 * baseFee)), BTC( 75), USD(2000)}, {"bob", 0, drops(3900000'000000 - (3 * baseFee)), BTC(100), USD( 0)}}}, // no USD xfer fee + {0, 0, 0, BTC(200), {{"cam", 0, drops(4000000'000000 - (5 * baseFee)), BTC(200), USD(2000)} }}, // no xfer fee + {0, 1, 0, BTC( 50), {{"deb", 1, drops(4040000'000000 - (4 * baseFee)), BTC( 0), USD(2000)}, {"dan", 1, drops(3960000'000000 - (3 * baseFee)), BTC( 40), USD( 0)}}}, // no USD xfer fee + }; + // clang-format on + + for (auto const& t : tests) + { + Account const& self = t.actors[t.self].acct; + Account const& leg0 = t.actors[t.leg0].acct; + Account const& leg1 = t.actors[t.leg1].acct; + + for (auto const& actor : t.actors) + { + env.fund(XRP(4'000'000), actor.acct); + env.close(); + + if constexpr (std::is_same_v) + { + auto MBTC = MPTTester(env, gw, BTC); + MBTC.authorize({.account = actor.acct}); + } + else + { + env(trust(actor.acct, BTC(400))); + env.close(); + } + if constexpr (std::is_same_v) + { + auto MUSD = MPTTester(env, gw, USD); + MUSD.authorize({.account = actor.acct}); + } + else + { + env(trust(actor.acct, USD(8000))); + env.close(); + } + env.close(); + } + + env(pay(gw, self, t.btcStart)); + env(pay(gw, self, USD(2'000))); + if (self.id() != leg1.id()) + env(pay(gw, leg1, USD(2'000))); + env.close(); + + // Get the initial offers in place. Remember their sequences + // so we can delete them later. + env(offer(leg0, BTC(100), XRP(100'000), tfPassive)); + env.close(); + std::uint32_t const leg0OfferSeq = env.seq(leg0) - 1; + + env(offer(leg1, XRP(100'000), USD(1'000), tfPassive)); + env.close(); + std::uint32_t const leg1OfferSeq = env.seq(leg1) - 1; + + // This is the offer that matters. + env(offer(self, USD(1'000), BTC(100))); + env.close(); + std::uint32_t const selfOfferSeq = env.seq(self) - 1; + + // Verify results. + for (auto const& actor : t.actors) + { + // Sometimes Taker crossing gets lazy about deleting offers. + // Treat an empty offer as though it is deleted. + auto actorOffers = offersOnAccount(env, actor.acct); + auto const offerCount = std::distance( + actorOffers.begin(), + std::remove_if( + actorOffers.begin(), + actorOffers.end(), + [](std::shared_ptr& offer) { + return (*offer)[sfTakerGets].signum() == 0; + })); + BEAST_EXPECT(offerCount == actor.offers); + + env.require(balance(actor.acct, actor.xrp)); + env.require(balance(actor.acct, actor.btc)); + env.require(balance(actor.acct, actor.usd)); + } + // Remove any offers that might be left hanging around. They + // could bollix up later loops. + env(offer_cancel(leg0, leg0OfferSeq)); + env.close(); + env(offer_cancel(leg1, leg1OfferSeq)); + env.close(); + env(offer_cancel(self, selfOfferSeq)); + env.close(); + } + }; + testHelper2TokensMix(test); + } + + void + testSelfPayUnlimitedFunds(FeatureBitset features) + { + testcase("Self Pay Unlimited Funds"); + // The Taker offer crossing code recognized when Alice was paying + // Alice the same denomination. In this case, as long as Alice + // has a little bit of that denomination, it treats Alice as though + // she has unlimited funds in that denomination. + // + // Huh? What kind of sense does that make? + // + // One way to think about it is to break a single payment into a + // series of very small payments executed sequentially but very + // quickly. Alice needs to pay herself 1 USD, but she only has + // 0.01 USD. Alice says, "Hey Alice, let me pay you a penny." + // Alice does this, taking the penny out of her pocket and then + // putting it back in her pocket. Then she says, "Hey Alice, + // I found another penny. I can pay you another penny." Repeat + // these steps 100 times and Alice has paid herself 1 USD even though + // she only owns 0.01 USD. + // + // That's all very nice, but the payment code does not support this + // optimization. In part that's because the payment code can + // operate on a whole batch of offers. As a matter of fact, it can + // deal in two consecutive batches of offers. It would take a great + // deal of sorting out to figure out which offers in the two batches + // had the same owner and give them special processing. And, + // honestly, it's a weird little corner case. + // + // So, since Flow offer crossing uses the payments engine, Flow + // offer crossing no longer supports this optimization. + // + // The following test shows the difference in the behaviors between + // Taker offer crossing and Flow offer crossing. + + using namespace jtx; + auto const gw = Account("gw"); + + auto test = [&](auto&& issue1, auto&& issue2) { + Env env{*this, features}; + auto const baseFee = env.current()->fees().base.drops(); + + auto const startXrpBalance = XRP(4'000'000); + + env.fund(startXrpBalance, gw); + env.close(); + + auto const BTC = issue1( + {.env = env, .token = "BTC", .issuer = gw, .limit = 40, .transferFee = 25'000}); + using tBTC = std::decay_t; + auto const USD = issue2( + {.env = env, .token = "USD", .issuer = gw, .limit = 8'000, .transferFee = 25'000}); + using tUSD = std::decay_t; + env.close(); + + // Test cases + struct Actor + { + Account acct; + int offers{}; // offers on account after crossing + PrettyAmount xrp; // final expected after crossing + PrettyAmount btc; // final expected after crossing + PrettyAmount usd; // final expected after crossing + }; + struct TestData + { + // The first three integers give the *index* in actors + // to assign each of the three roles. By using indices it is + // easy for alice to own the offer in the first leg, the second + // leg, or both. + std::size_t self{}; + std::size_t leg0{}; + std::size_t leg1{}; + PrettyAmount btcStart; + std::vector actors; + }; + + // clang-format off + TestData const flowTests[]{ + // btcStart ------------------- actor[0] -------------------- ------------------- actor[1] -------------------- + {0, 0, 1, BTC(5), {{"gay", 1, drops(3950000'000000 - (4 * baseFee)), BTC(5), USD (2500)}, {"gar", 1, drops(4050000'000000 - (3 * baseFee)), BTC(0), USD(1375)}}}, // no BTC xfer fee + {0, 0, 0, BTC(5), {{"hye", 2, drops(4000000'000000 - (5 * baseFee)), BTC(5), USD (2000)} }} // no xfer fee + }; + // clang-format on + + for (auto const& t : flowTests) + { + Account const& self = t.actors[t.self].acct; + Account const& leg0 = t.actors[t.leg0].acct; + Account const& leg1 = t.actors[t.leg1].acct; + + for (auto const& actor : t.actors) + { + env.fund(XRP(4'000'000), actor.acct); + env.close(); + + if constexpr (std::is_same_v) + { + auto MBTC = MPTTester(env, gw, BTC); + MBTC.authorize({.account = actor.acct}); + } + else + { + env(trust(actor.acct, BTC(40))); + env.close(); + } + if constexpr (std::is_same_v) + { + auto MUSD = MPTTester(env, gw, USD); + MUSD.authorize({.account = actor.acct}); + } + else + { + env(trust(actor.acct, USD(8'000))); + env.close(); + } + } + + env(pay(gw, self, t.btcStart)); + env(pay(gw, self, USD(2'000))); + if (self.id() != leg1.id()) + env(pay(gw, leg1, USD(2'000))); + env.close(); + + // Get the initial offers in place. Remember their sequences + // so we can delete them later. + env(offer(leg0, BTC(10), XRP(100'000), tfPassive)); + env.close(); + std::uint32_t const leg0OfferSeq = env.seq(leg0) - 1; + + env(offer(leg1, XRP(100'000), USD(1'000), tfPassive)); + env.close(); + std::uint32_t const leg1OfferSeq = env.seq(leg1) - 1; + + // This is the offer that matters. + env(offer(self, USD(1'000), BTC(10))); + env.close(); + std::uint32_t const selfOfferSeq = env.seq(self) - 1; + + // Verify results. + for (auto const& actor : t.actors) + { + // Sometimes Taker offer crossing gets lazy about deleting + // offers. Treat an empty offer as though it is deleted. + auto actorOffers = offersOnAccount(env, actor.acct); + auto const offerCount = std::distance( + actorOffers.begin(), + std::remove_if( + actorOffers.begin(), + actorOffers.end(), + [](std::shared_ptr& offer) { + return (*offer)[sfTakerGets].signum() == 0; + })); + BEAST_EXPECT(offerCount == actor.offers); + + env.require(balance(actor.acct, actor.xrp)); + env.require(balance(actor.acct, actor.btc)); + env.require(balance(actor.acct, actor.usd)); + } + // Remove any offers that might be left hanging around. They + // could bollix up later loops. + env(offer_cancel(leg0, leg0OfferSeq)); + env.close(); + env(offer_cancel(leg1, leg1OfferSeq)); + env.close(); + env(offer_cancel(self, selfOfferSeq)); + env.close(); + } + }; + testHelper2TokensMix(test); + } + + void + testRequireAuth(FeatureBitset features) + { + testcase("lsfRequireAuth"); + + using namespace jtx; + + Env env{*this, features}; + + auto const gw = Account("gw"); + auto const alice = Account("alice"); + auto const bob = Account("bob"); + + env.fund(XRP(400'000), gw, alice, bob); + env.close(); + + // GW requires authorization for holders of its IOUs + auto gwMUSD = + MPTTester({.env = env, .issuer = gw, .flags = MPTDEXFlags | tfMPTRequireAuth}); + MPT const gwUSD = gwMUSD; + + // Have gw authorize bob and alice + gwMUSD.authorize({.account = alice}); + gwMUSD.authorize({.account = gw, .holder = alice}); + gwMUSD.authorize({.account = bob}); + gwMUSD.authorize({.account = gw, .holder = bob}); + // Alice is able to place the offer since the GW has authorized her + env(offer(alice, gwUSD(40), XRP(4'000))); + env.close(); + + env.require(offers(alice, 1)); + env.require(balance(alice, gwUSD(0))); + + env(pay(gw, bob, gwUSD(50))); + env.close(); + + env.require(balance(bob, gwUSD(50))); + + // Bob's offer should cross Alice's + env(offer(bob, XRP(4'000), gwUSD(40))); + env.close(); + + env.require(offers(alice, 0)); + env.require(balance(alice, gwUSD(40))); + + env.require(offers(bob, 0)); + env.require(balance(bob, gwUSD(10))); + } + + void + testMissingAuth(FeatureBitset features) + { + testcase("Missing Auth"); + // 1. gw creates MPTokenIssuance, which requires authorization. + // alice creates an offer to acquire USD/gw, an asset for which + // she does not own MPToken. This offer fails since alice + // doesn't own MPToken and authorization is required. + // + // 2. Next, alice creates MPT, but it's not authorized. + // alice attempts to create an offer and again fails. + // + // 3. Finally, gw authorizes alice to own USD/gw. + // At this point alice successfully + // creates and crosses an offer for USD/gw. + + using namespace jtx; + + Env env{*this, features}; + + auto const gw = Account("gw"); + auto const alice = Account("alice"); + auto const bob = Account("bob"); + + env.fund(XRP(400'000), gw, alice, bob); + env.close(); + + auto gwMUSD = + MPTTester({.env = env, .issuer = gw, .flags = MPTDEXFlags | tfMPTRequireAuth}); + MPT const gwUSD = gwMUSD; + + // alice can't create an offer because alice doesn't own + // MPToken and MPTokenIssuance requires authorization + env(offer(alice, gwUSD(40), XRP(4'000)), ter(tecNO_AUTH)); + env.close(); + + env.require(offers(alice, 0)); + env.require(balance(alice, gwUSD(none))); + + gwMUSD.authorize({.account = bob}); + gwMUSD.authorize({.account = gw, .holder = bob}); + + env(pay(gw, bob, gwUSD(50))); + env.close(); + env.require(balance(bob, gwUSD(50))); + + // bob can create an offer since bob owns MPToken + // and it is authorized. + env(offer(bob, XRP(4'000), gwUSD(40))); + env.close(); + std::uint32_t const bobOfferSeq = env.seq(bob) - 1; + + env.require(offers(alice, 0)); + + // alice creates MPToken, which is still not authorized. alice + // should still not be able to create an offer for USD/gw. + gwMUSD.authorize({.account = alice}); + + env(offer(alice, gwUSD(40), XRP(4'000)), ter(tecNO_AUTH)); + env.close(); + + env.require(offers(alice, 0)); + env.require(balance(alice, gwUSD(0))); + + env.require(offers(bob, 1)); + env.require(balance(bob, gwUSD(50))); + + // Delete bob's offer so alice can create an offer without crossing. + env(offer_cancel(bob, bobOfferSeq)); + env.close(); + env.require(offers(bob, 0)); + + // Finally, gw authorizes alice. Now alice's + // offer should succeed. + gwMUSD.authorize({.account = gw, .holder = alice}); + + env(offer(alice, gwUSD(40), XRP(4'000))); + env.close(); + + env.require(offers(alice, 1)); + + // Now bob creates his offer again. alice's offer should cross. + env(offer(bob, XRP(4'000), gwUSD(40))); + env.close(); + + env.require(offers(alice, 0)); + env.require(balance(alice, gwUSD(40))); + + env.require(offers(bob, 0)); + env.require(balance(bob, gwUSD(10))); + } + + void + testSelfAuth(FeatureBitset features) + { + testcase("Self Auth"); + + using namespace jtx; + + Env env{*this, features}; + + auto const gw = Account("gw"); + auto const alice = Account("alice"); + + env.fund(XRP(400'000), gw, alice); + env.close(); + + auto gwMUSD = + MPTTester({.env = env, .issuer = gw, .flags = MPTDEXFlags | tfMPTRequireAuth}); + MPT const gwUSD = gwMUSD; + + // Test that gw can create an offer to buy gw's currency. + env(offer(gw, gwUSD(40), XRP(4'000))); + env.close(); + std::uint32_t const gwOfferSeq = env.seq(gw) - 1; + env.require(offers(gw, 1)); + + // Cancel gw's offer + env(offer_cancel(gw, gwOfferSeq)); + env.close(); + env.require(offers(gw, 0)); + + // Before DepositPreauth an account with lsfRequireAuth set could not + // create an offer to buy their own currency. After DepositPreauth + // they can. + env(offer(gw, gwUSD(40), XRP(4'000))); + env.close(); + + env.require(offers(gw, 1)); + + // The rest of the test verifies DepositPreauth behavior. + + // Create/authorize alice's MPToken + gwMUSD.authorize({.account = alice}); + gwMUSD.authorize({.account = gw, .holder = alice}); + + env(pay(gw, alice, gwUSD(50))); + env.close(); + + env.require(balance(alice, gwUSD(50))); + + // alice's offer should cross gw's + env(offer(alice, XRP(4'000), gwUSD(40))); + env.close(); + + env.require(offers(alice, 0)); + env.require(balance(alice, gwUSD(10))); + + env.require(offers(gw, 0)); + } + + void + testDeletedOfferIssuer(FeatureBitset features) + { + // Show that an offer who's issuer has been deleted cannot be crossed. + using namespace jtx; + + testcase("Deleted offer issuer"); + + auto MPTokenExists = + [](jtx::Env const& env, AccountID const& account, MPTID const& issuanceID) -> bool { + return bool(env.le(keylet::mptoken(issuanceID, account))); + }; + + Account const alice("alice"); + Account const becky("becky"); + Account const carol("carol"); + Account const gw("gateway"); + + Env env{*this, features}; + + env.fund(XRP(10'000), alice, becky, carol, noripple(gw)); + + auto MUSD = MPTTester({.env = env, .issuer = gw}); + MPT const USD = MUSD; + + MUSD.authorize({.account = becky}); + BEAST_EXPECT(MPTokenExists(env, becky, USD.issuanceID)); + env(pay(gw, becky, USD(5))); + env.close(); + + auto MBUX = MPTTester({.env = env, .issuer = alice}); + MPT const BUX = MBUX; + + // Make offers that produce USD and can be crossed two ways: + // direct XRP -> USD + // direct BUX -> USD + env(offer(becky, XRP(2), USD(2)), txflags(tfPassive)); + std::uint32_t const beckyBuxUsdSeq{env.seq(becky)}; + env(offer(becky, BUX(3), USD(3)), txflags(tfPassive)); + env.close(); + + // becky keeps the offers, but removes MPT. + env(pay(becky, gw, USD(5))); + MUSD.authorize({.account = becky, .flags = tfMPTUnauthorize}); + + BEAST_EXPECT(!MPTokenExists(env, becky, USD.issuanceID)); + BEAST_EXPECT(isOffer(env, becky, XRP(2), USD(2))); + BEAST_EXPECT(isOffer(env, becky, BUX(3), USD(3))); + + // Have to delete MPTokenIssuance in order to delete + // the issuer account. + MUSD.destroy({}); + + // Delete gw's account. + { + // The ledger sequence needs to far enough ahead of the account + // sequence before the account can be deleted. + int const delta = [&env, &gw, openLedgerSeq = env.current()->seq()]() -> int { + std::uint32_t const gwSeq{env.seq(gw)}; + if (gwSeq + 255 > openLedgerSeq) + return gwSeq - openLedgerSeq + 255; + return 0; + }(); + + for (int i = 0; i < delta; ++i) + env.close(); + + // Account deletion has a high fee. Account for that. + env(acctdelete(gw, alice), fee(drops(env.current()->fees().increment))); + env.close(); + + // Verify that gw's account root is gone from the ledger. + BEAST_EXPECT(!env.closed()->exists(keylet::account(gw.id()))); + } + + // alice crosses becky's first offer. The offer create fails because + // the USD issuer is not in the ledger. + env(offer(alice, USD(2), XRP(2)), ter(tecNO_ISSUER)); + env.close(); + env.require(offers(alice, 0)); + BEAST_EXPECT(isOffer(env, becky, XRP(2), USD(2))); + BEAST_EXPECT(isOffer(env, becky, BUX(3), USD(3))); + + // alice crosses becky's second offer. Again, the offer create fails + // because the USD issuer is not in the ledger. + env(offer(alice, USD(3), BUX(3)), ter(tecNO_ISSUER)); + env.require(offers(alice, 0)); + BEAST_EXPECT(isOffer(env, becky, XRP(2), USD(2))); + BEAST_EXPECT(isOffer(env, becky, BUX(3), USD(3))); + + // Cancel becky's BUX -> USD offer so we can try auto-bridging. + env(offer_cancel(becky, beckyBuxUsdSeq)); + env.close(); + BEAST_EXPECT(!isOffer(env, becky, BUX(3), USD(3))); + + // alice creates an offer that can be auto-bridged with becky's + // remaining offer. + MBUX.authorize({.account = carol}); + env(pay(alice, carol, BUX(2))); + + env(offer(alice, BUX(2), XRP(2))); + env.close(); + + // carol attempts the auto-bridge. Again, the offer create fails + // because the USD issuer is not in the ledger. + env(offer(carol, USD(2), BUX(2)), ter(tecNO_ISSUER)); + env.close(); + BEAST_EXPECT(isOffer(env, alice, BUX(2), XRP(2))); + BEAST_EXPECT(isOffer(env, becky, XRP(2), USD(2))); + } + + // Helper function that returns offers on an account sorted by sequence. + static std::vector> + sortedOffersOnAccount(jtx::Env& env, jtx::Account const& acct) + { + std::vector> offers{offersOnAccount(env, acct)}; + std::ranges::sort( + offers, + [](std::shared_ptr const& rhs, std::shared_ptr const& lhs) { + return (*rhs)[sfSequence] < (*lhs)[sfSequence]; + }); + return offers; + } + + void + testTicketOffer(FeatureBitset features) + { + testcase("Ticket Offers"); + + using namespace jtx; + + // Two goals for this test. + // + // o Verify that offers can be created using tickets. + // + // o Show that offers in the _same_ order book remain in + // chronological order regardless of sequence/ticket numbers. + Env env{*this, features}; + auto const gw = Account{"gateway"}; + auto const alice = Account{"alice"}; + auto const bob = Account{"bob"}; + + env.fund(XRP(10'000), gw, alice, bob); + env.close(); + + MPT const USD = MPTTester({.env = env, .issuer = gw, .holders = {alice, bob}}); + + env(pay(gw, alice, USD(200))); + env.close(); + + // Create four offers from the same account with identical quality + // so they go in the same order book. Each offer goes in a different + // ledger so the chronology is clear. + std::uint32_t const offerId_0{env.seq(alice)}; + env(offer(alice, XRP(50), USD(50))); + env.close(); + + // Create two tickets. + std::uint32_t const ticketSeq{env.seq(alice) + 1}; + env(ticket::create(alice, 2)); + env.close(); + + // Create another sequence-based offer. + std::uint32_t const offerId_1{env.seq(alice)}; + BEAST_EXPECT(offerId_1 == offerId_0 + 4); + env(offer(alice, XRP(50), USD(50))); + env.close(); + + // Create two ticket based offers in reverse order. + std::uint32_t const offerId_2{ticketSeq + 1}; + env(offer(alice, XRP(50), USD(50)), ticket::use(offerId_2)); + env.close(); + + // Create the last offer. + std::uint32_t const offerId_3{ticketSeq}; + env(offer(alice, XRP(50), USD(50)), ticket::use(offerId_3)); + env.close(); + + // Verify that all of alice's offers are present. + { + auto offers = sortedOffersOnAccount(env, alice); + BEAST_EXPECT(offers.size() == 4); + BEAST_EXPECT(offers[0]->getFieldU32(sfSequence) == offerId_0); + BEAST_EXPECT(offers[1]->getFieldU32(sfSequence) == offerId_3); + BEAST_EXPECT(offers[2]->getFieldU32(sfSequence) == offerId_2); + BEAST_EXPECT(offers[3]->getFieldU32(sfSequence) == offerId_1); + env.require(balance(alice, USD(200))); + env.require(owners(alice, 5)); + } + + // Cross alice's first offer. + env(offer(bob, USD(50), XRP(50))); + env.close(); + + // Verify that the first offer alice created was consumed. + { + auto offers = sortedOffersOnAccount(env, alice); + BEAST_EXPECT(offers.size() == 3); + BEAST_EXPECT(offers[0]->getFieldU32(sfSequence) == offerId_3); + BEAST_EXPECT(offers[1]->getFieldU32(sfSequence) == offerId_2); + BEAST_EXPECT(offers[2]->getFieldU32(sfSequence) == offerId_1); + } + + // Cross alice's second offer. + env(offer(bob, USD(50), XRP(50))); + env.close(); + + // Verify that the second offer alice created was consumed. + { + auto offers = sortedOffersOnAccount(env, alice); + BEAST_EXPECT(offers.size() == 2); + BEAST_EXPECT(offers[0]->getFieldU32(sfSequence) == offerId_3); + BEAST_EXPECT(offers[1]->getFieldU32(sfSequence) == offerId_2); + } + + // Cross alice's third offer. + env(offer(bob, USD(50), XRP(50))); + env.close(); + + // Verify that the third offer alice created was consumed. + { + auto offers = sortedOffersOnAccount(env, alice); + BEAST_EXPECT(offers.size() == 1); + BEAST_EXPECT(offers[0]->getFieldU32(sfSequence) == offerId_3); + } + + // Cross alice's last offer. + env(offer(bob, USD(50), XRP(50))); + env.close(); + + // Verify that the third offer alice created was consumed. + { + auto offers = sortedOffersOnAccount(env, alice); + BEAST_EXPECT(offers.empty()); + } + env.require(balance(alice, USD(0))); + env.require(owners(alice, 1)); + env.require(balance(bob, USD(200))); + env.require(owners(bob, 1)); + } + + void + testTicketCancelOffer(FeatureBitset features) + { + testcase("Ticket Cancel Offers"); + + using namespace jtx; + + // Verify that offers created with or without tickets can be canceled + // by transactions with or without tickets. + Env env{*this, features}; + auto const gw = Account{"gateway"}; + auto const alice = Account{"alice"}; + + env.fund(XRP(10'000), gw, alice); + env.close(); + + MPT const USD = MPTTester({.env = env, .issuer = gw, .holders = {alice}}); + + env.require(owners(alice, 1), tickets(alice, 0)); + + env(pay(gw, alice, USD(200))); + env.close(); + + // Create the first of four offers using a sequence. + std::uint32_t const offerSeqId_0{env.seq(alice)}; + env(offer(alice, XRP(50), USD(50))); + env.close(); + env.require(owners(alice, 2), tickets(alice, 0)); + + // Create four tickets. + std::uint32_t const ticketSeq{env.seq(alice) + 1}; + env(ticket::create(alice, 4)); + env.close(); + env.require(owners(alice, 6), tickets(alice, 4)); + + // Create the second (also sequence-based) offer. + std::uint32_t const offerSeqId_1{env.seq(alice)}; + BEAST_EXPECT(offerSeqId_1 == offerSeqId_0 + 6); + env(offer(alice, XRP(50), USD(50))); + env.close(); + + // Create the third (ticket-based) offer. + std::uint32_t const offerTixId_0{ticketSeq + 1}; + env(offer(alice, XRP(50), USD(50)), ticket::use(offerTixId_0)); + env.close(); + + // Create the last offer. + std::uint32_t const offerTixId_1{ticketSeq}; + env(offer(alice, XRP(50), USD(50)), ticket::use(offerTixId_1)); + env.close(); + + // Verify that all of alice's offers are present. + { + auto offers = sortedOffersOnAccount(env, alice); + BEAST_EXPECT(offers.size() == 4); + BEAST_EXPECT(offers[0]->getFieldU32(sfSequence) == offerSeqId_0); + BEAST_EXPECT(offers[1]->getFieldU32(sfSequence) == offerTixId_1); + BEAST_EXPECT(offers[2]->getFieldU32(sfSequence) == offerTixId_0); + BEAST_EXPECT(offers[3]->getFieldU32(sfSequence) == offerSeqId_1); + env.require(balance(alice, USD(200))); + env.require(owners(alice, 7)); + } + + // Use a ticket to cancel an offer created with a sequence. + env(offer_cancel(alice, offerSeqId_0), ticket::use(ticketSeq + 2)); + env.close(); + + // Verify that offerSeqId_0 was canceled. + { + auto offers = sortedOffersOnAccount(env, alice); + BEAST_EXPECT(offers.size() == 3); + BEAST_EXPECT(offers[0]->getFieldU32(sfSequence) == offerTixId_1); + BEAST_EXPECT(offers[1]->getFieldU32(sfSequence) == offerTixId_0); + BEAST_EXPECT(offers[2]->getFieldU32(sfSequence) == offerSeqId_1); + } + + // Use a ticket to cancel an offer created with a ticket. + env(offer_cancel(alice, offerTixId_0), ticket::use(ticketSeq + 3)); + env.close(); + + // Verify that offerTixId_0 was canceled. + { + auto offers = sortedOffersOnAccount(env, alice); + BEAST_EXPECT(offers.size() == 2); + BEAST_EXPECT(offers[0]->getFieldU32(sfSequence) == offerTixId_1); + BEAST_EXPECT(offers[1]->getFieldU32(sfSequence) == offerSeqId_1); + } + + // All of alice's tickets should now be used up. + env.require(owners(alice, 3), tickets(alice, 0)); + + // Use a sequence to cancel an offer created with a ticket. + env(offer_cancel(alice, offerTixId_1)); + env.close(); + + // Verify that offerTixId_1 was canceled. + { + auto offers = sortedOffersOnAccount(env, alice); + BEAST_EXPECT(offers.size() == 1); + BEAST_EXPECT(offers[0]->getFieldU32(sfSequence) == offerSeqId_1); + } + + // Use a sequence to cancel an offer created with a sequence. + env(offer_cancel(alice, offerSeqId_1)); + env.close(); + + // Verify that offerSeqId_1 was canceled. + // All of alice's tickets should now be used up. + env.require(owners(alice, 1), tickets(alice, 0), offers(alice, 0)); + } + + void + testFillOrKill(FeatureBitset features) + { + testcase("fixFillOrKill"); + using namespace jtx; + Account const issuer("issuer"); + Account const maker("maker"); + Account const taker("taker"); + + auto test = [&](auto&& issue1, auto&& issue2) { + Env env(*this, features); + + env.fund(XRP(1'000), issuer); + env.fund(XRP(1'000), maker, taker); + env.close(); + + auto const USD = + issue1({.env = env, .token = "USD", .issuer = issuer, .holders = {maker, taker}}); + auto const EUR = + issue2({.env = env, .token = "EUR", .issuer = issuer, .holders = {maker, taker}}); + + env(pay(issuer, maker, USD(1'000))); + env(pay(issuer, taker, USD(1'000))); + env(pay(issuer, maker, EUR(1'000))); + env.close(); + + auto makerUSDBalance = env.balance(maker, USD).value(); + auto takerUSDBalance = env.balance(taker, USD).value(); + auto makerEURBalance = env.balance(maker, EUR).value(); + auto takerEURBalance = env.balance(taker, EUR).value(); + auto makerXRPBalance = env.balance(maker, XRP).value(); + auto takerXRPBalance = env.balance(taker, XRP).value(); + + // tfFillOrKill, TakerPays must be filled + { + TER const err = features[fixFillOrKill] ? TER(tesSUCCESS) : tecKILLED; + + env(offer(maker, XRP(100), USD(100))); + env.close(); + + env(offer(taker, USD(100), XRP(101)), txflags(tfFillOrKill), ter(err)); + env.close(); + + makerXRPBalance -= txfee(env, 1); + takerXRPBalance -= txfee(env, 1); + if (err == tesSUCCESS) + { + makerUSDBalance -= USD(100); + takerUSDBalance += USD(100); + makerXRPBalance += XRP(100).value(); + takerXRPBalance -= XRP(100).value(); + } + BEAST_EXPECT(expectOffers(env, taker, 0)); + + env(offer(maker, USD(100), XRP(100))); + env.close(); + + env(offer(taker, XRP(100), USD(101)), txflags(tfFillOrKill), ter(err)); + env.close(); + + makerXRPBalance -= txfee(env, 1); + takerXRPBalance -= txfee(env, 1); + if (err == tesSUCCESS) + { + makerUSDBalance += USD(100); + takerUSDBalance -= USD(100); + makerXRPBalance -= XRP(100).value(); + takerXRPBalance += XRP(100).value(); + } + BEAST_EXPECT(expectOffers(env, taker, 0)); + + env(offer(maker, USD(100), EUR(100))); + env.close(); + + env(offer(taker, EUR(100), USD(101)), txflags(tfFillOrKill), ter(err)); + env.close(); + + makerXRPBalance -= txfee(env, 1); + takerXRPBalance -= txfee(env, 1); + if (err == tesSUCCESS) + { + makerUSDBalance += USD(100); + takerUSDBalance -= USD(100); + makerEURBalance -= EUR(100); + takerEURBalance += EUR(100); + } + BEAST_EXPECT(expectOffers(env, taker, 0)); + } + + // tfFillOrKill + tfSell, TakerGets must be filled + { + env(offer(maker, XRP(101), USD(101))); + env.close(); + + env(offer(taker, USD(100), XRP(101)), txflags(tfFillOrKill | tfSell)); + env.close(); + + makerUSDBalance -= USD(101); + takerUSDBalance += USD(101); + makerXRPBalance += XRP(101).value() - txfee(env, 1); + takerXRPBalance -= XRP(101).value() + txfee(env, 1); + BEAST_EXPECT(expectOffers(env, taker, 0)); + + env(offer(maker, USD(101), XRP(101))); + env.close(); + + env(offer(taker, XRP(100), USD(101)), txflags(tfFillOrKill | tfSell)); + env.close(); + + makerUSDBalance += USD(101); + takerUSDBalance -= USD(101); + makerXRPBalance -= XRP(101).value() + txfee(env, 1); + takerXRPBalance += XRP(101).value() - txfee(env, 1); + BEAST_EXPECT(expectOffers(env, taker, 0)); + + env(offer(maker, USD(101), EUR(101))); + env.close(); + + env(offer(taker, EUR(100), USD(101)), txflags(tfFillOrKill | tfSell)); + env.close(); + + makerUSDBalance += USD(101); + takerUSDBalance -= USD(101); + makerEURBalance -= EUR(101); + takerEURBalance += EUR(101); + makerXRPBalance -= txfee(env, 1); + takerXRPBalance -= txfee(env, 1); + BEAST_EXPECT(expectOffers(env, taker, 0)); + } + + // Fail regardless of fixFillOrKill amendment + for (auto const flags : {tfFillOrKill, tfFillOrKill + tfSell}) + { + env(offer(maker, XRP(100), USD(100))); + env.close(); + + env(offer(taker, USD(100), XRP(99)), txflags(flags), ter(tecKILLED)); + env.close(); + + makerXRPBalance -= txfee(env, 1); + takerXRPBalance -= txfee(env, 1); + BEAST_EXPECT(expectOffers(env, taker, 0)); + + env(offer(maker, USD(100), XRP(100))); + env.close(); + + env(offer(taker, XRP(100), USD(99)), txflags(flags), ter(tecKILLED)); + env.close(); + + makerXRPBalance -= txfee(env, 1); + takerXRPBalance -= txfee(env, 1); + BEAST_EXPECT(expectOffers(env, taker, 0)); + + env(offer(maker, USD(100), EUR(100))); + env.close(); + + env(offer(taker, EUR(100), USD(99)), txflags(flags), ter(tecKILLED)); + env.close(); + + makerXRPBalance -= txfee(env, 1); + takerXRPBalance -= txfee(env, 1); + BEAST_EXPECT(expectOffers(env, taker, 0)); + } + + BEAST_EXPECT( + env.balance(maker, USD) == makerUSDBalance && + env.balance(taker, USD) == takerUSDBalance && + env.balance(maker, EUR) == makerEURBalance && + env.balance(taker, EUR) == takerEURBalance && + env.balance(maker, XRP) == makerXRPBalance && + env.balance(taker, XRP) == takerXRPBalance); + }; + testHelper2TokensMix(test); + } + + void + testTickSize(FeatureBitset features) + { + testcase("Tick Size"); + + using namespace jtx; + + auto const gw = Account{"gateway"}; + auto const alice = Account{"alice"}; + + auto getIOU = [&](Env& env) -> PrettyAsset { + static int i = 0; + std::string const name = "IO" + std::to_string(i++); + auto const iou = gw[name]; + env(trust(alice, iou(1'000))); + env(pay(gw, alice, iou(100))); + env.close(); + return iou; + }; + auto getMPT = [&](Env& env) -> PrettyAsset { + MPT const mpt = + MPTTester({.env = env, .issuer = gw, .holders = {alice}, .pay = 1'000'000'000}); + return mpt; + }; + auto getXRP = [&](Env& env) -> PrettyAsset { return XRP; }; + + using ToAsset = std::function; + struct TestInfo + { + ToAsset toAsset1; + ToAsset toAsset2; + int val1; + int val2; + }; + // XRP/MPT, MPT/XRP, MPT/MPT offers are not adjusted for TickSize + // IOU/IOU, XRP/IOU, IOU/XRP offers have TickSize logic unchanged + // IOU/MPT, MPT/IOU have TickSize logic applied to adjust IOU only + std::vector const tests = { + {getIOU, getIOU, 10, 30}, + {getIOU, getXRP, 10, 30'000'000}, + {getXRP, getIOU, 10'000'000, 30}, + {getMPT, getXRP, 10'000'000, 30'000'000}, + {getXRP, getMPT, 10'000'000, 30'000'000}, + {getIOU, getMPT, 10, 30'000'000}, + {getMPT, getIOU, 10'000'000, 30}, + {getMPT, getMPT, 10'000'000, 30'000'000}}; + for (TestInfo const& t : tests) + { + Env env{*this, features}; + env.fund(XRP(10'000), gw, alice); + env.close(); + + auto const XTS = t.toAsset1(env); + auto const XXX = t.toAsset2(env); + + auto tokenType = [](PrettyAsset const& asset) -> std::string { + return asset.raw().visit( + [&](Issue const& issue) { return issue.native() ? "XRPIssue" : "Issue"; }, + [&](MPTIssue const&) { return "MPTIssue"; }); + }; + + testcase << "offer: " << tokenType(XTS) << "/" << tokenType(XXX); + + { + // Gateway sets its tick size to 5 + auto txn = noop(gw); + txn[sfTickSize.fieldName] = 5; + env(txn); + BEAST_EXPECT((*env.le(gw))[sfTickSize] == 5); + } + + env(offer(alice, XTS(t.val1), XXX(t.val2))); + env(offer(alice, XTS(t.val2), XXX(t.val1))); + env(offer(alice, XTS(t.val1), XXX(t.val2)), json(jss::Flags, tfSell)); + env(offer(alice, XTS(t.val2), XXX(t.val1)), json(jss::Flags, tfSell)); + + std::map> offers; + forEachItem(*env.current(), alice, [&](std::shared_ptr const& sle) { + if (sle->getType() == ltOFFER) + { + offers.emplace( + (*sle)[sfSequence], + std::make_pair((*sle)[sfTakerPays], (*sle)[sfTakerGets])); + } + }); + + // first offer + auto it = offers.begin(); + BEAST_EXPECT(it != offers.end()); + if (XXX.native() && !XTS.holds()) + { + BEAST_EXPECT( + it->second.first == XTS(t.val1) && it->second.second == XRPAmount(29'999'400)); + } + else if (!XXX.integral()) + { + BEAST_EXPECT( + it->second.first == XTS(t.val1) && it->second.second < XXX(t.val2) && + it->second.second > STAmount(XXX, 29'9994, -4)); + } + else + { + BEAST_EXPECT(it->second.first == XTS(t.val1) && it->second.second == XXX(t.val2)); + } + + // second offer + ++it; + BEAST_EXPECT(it != offers.end()); + BEAST_EXPECT(it->second.first == XTS(t.val2) && it->second.second == XXX(t.val1)); + + // third offer + ++it; + BEAST_EXPECT(it != offers.end()); + if (XTS.native() && !XXX.holds()) + { + BEAST_EXPECT( + it->second.first == XRPAmount(10'000'200) && it->second.second == XXX(t.val2)); + } + else if (!XTS.integral()) + { + BEAST_EXPECT( + it->second.first == STAmount(XTS, 10'0002, -4) && + it->second.second == XXX(t.val2)); + } + else + { + BEAST_EXPECT(it->second.first == XTS(t.val1) && it->second.second == XXX(t.val2)); + } + + // fourth offer + // exact TakerPays is XTS(1/.033333) + ++it; + BEAST_EXPECT(it != offers.end()); + BEAST_EXPECT(it->second.first == XTS(t.val2) && it->second.second == XXX(t.val1)); + + BEAST_EXPECT(++it == offers.end()); + } + } + + void + testAll(FeatureBitset features) + { + testCanceledOffer(features); + testRmFundedOffer(features); + testTinyPayment(features); + testXRPTinyPayment(features); + testInsufficientReserve(features); + testFillModes(features); + testMalformed(features); + testExpiration(features); + testUnfundedCross(features); + testSelfCross(false, features); + testSelfCross(true, features); + testNegativeBalance(features); + testOfferCrossWithXRP(true, features); + testOfferCrossWithXRP(false, features); + testOfferCrossWithLimitOverride(features); + testOfferAcceptThenCancel(features); + testCurrencyConversionEntire(features); + testCurrencyConversionIntoDebt(features); + testCurrencyConversionInParts(features); + testCrossCurrencyStartXRP(features); + testCrossCurrencyEndXRP(features); + testCrossCurrencyBridged(features); + testBridgedSecondLegDry(features); + testOfferFeesConsumeFunds(features); + testOfferCreateThenCross(features); + testSellFlagBasic(features); + testSellFlagExceedLimit(features); + testGatewayCrossCurrency(features); + testPartialCross(features); + testXRPDirectCross(features); + testDirectCross(features); + testBridgedCross(features); + testSellOffer(features); + testSellWithFillOrKill(features); + testTransferRateOffer(features); + testSelfCrossOffer(features); + testSelfIssueOffer(features); + testDirectToDirectPath(features); + testSelfCrossLowQualityOffer(features); + testOfferInScaling(features); + testOfferInScalingWithXferRate(features); + testSelfPayXferFeeOffer(features); + testSelfPayUnlimitedFunds(features); + testRequireAuth(features); + testMissingAuth(features); + testSelfAuth(features); + testDeletedOfferIssuer(features); + testTicketOffer(features); + testTicketCancelOffer(features); + testRmSmallIncreasedQOffersXRP(features); + testRmSmallIncreasedQOffersMPT(features); + testFillOrKill(features); + testTickSize(features); + } + + void + run() override + { + using namespace jtx; + static FeatureBitset const all{testable_amendments()}; + testAll(all); + } +}; + +BEAST_DEFINE_TESTSUITE_PRIO(OfferMPT, tx, xrpl, 2); + +} // namespace xrpl::test diff --git a/src/test/app/OfferStream_test.cpp b/src/test/app/OfferStream_test.cpp index becb190513..9cd3724b7b 100644 --- a/src/test/app/OfferStream_test.cpp +++ b/src/test/app/OfferStream_test.cpp @@ -1,5 +1,4 @@ -#include -#include +#include namespace xrpl { diff --git a/src/test/app/Offer_test.cpp b/src/test/app/Offer_test.cpp index 66e84360ef..0c00c91f63 100644 --- a/src/test/app/Offer_test.cpp +++ b/src/test/app/Offer_test.cpp @@ -1,13 +1,60 @@ -#include +#include +#include #include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include #include -namespace xrpl { -namespace test { +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl::test { class OfferBaseUtil_test : public beast::unit_test::suite { @@ -420,7 +467,7 @@ public: auto tinyAmount = [&](IOU const& iou) -> PrettyAmount { STAmount const amt( - iou.issue(), + iou, /*mantissa*/ 1, /*exponent*/ -81); return PrettyAmount(amt, iou.account.name()); @@ -709,7 +756,7 @@ public: // Helper function that returns the Offers on an account. static std::vector> - offersOnAccount(jtx::Env& env, jtx::Account account) + offersOnAccount(jtx::Env& env, jtx::Account const& account) { std::vector> result; forEachItem(*env.current(), account, [&result](std::shared_ptr const& sle) { @@ -1203,7 +1250,6 @@ public: BEAST_EXPECT(jrr[jss::offers].isArray()); BEAST_EXPECT(jrr[jss::offers].size() == 0); - // NOTE : // At this point, all offers are expected to be consumed. { auto acctOffers = offersOnAccount(env, account_to_test); @@ -1279,7 +1325,7 @@ public: auto const gw_initial_balance = drops(1149999730); auto const alice_initial_balance = drops(499946999680); auto const bob_initial_balance = drops(10199999920); - auto const small_amount = STAmount{bob["USD"].issue(), UINT64_C(2710505431213761), -33}; + auto const small_amount = STAmount{bob["USD"], UINT64_C(2710505431213761), -33}; env.fund(gw_initial_balance, gw); env.fund(alice_initial_balance, alice); @@ -2136,18 +2182,18 @@ public: jtx::Account const& account, jtx::PrettyAmount const& expectBalance) { - auto const sleTrust = env.le(keylet::line(account.id(), expectBalance.value().issue())); + Issue const& issue = expectBalance.value().get(); + auto const sleTrust = env.le(keylet::line(account.id(), issue)); BEAST_EXPECT(sleTrust); if (sleTrust) { - Issue const issue = expectBalance.value().issue(); bool const accountLow = account.id() < issue.account; STAmount low{issue}; STAmount high{issue}; - low.setIssuer(accountLow ? account.id() : issue.account); - high.setIssuer(accountLow ? issue.account : account.id()); + low.get().account = (accountLow ? account.id() : issue.account); + high.get().account = (accountLow ? issue.account : account.id()); BEAST_EXPECT(sleTrust->getFieldAmount(sfLowLimit) == low); BEAST_EXPECT(sleTrust->getFieldAmount(sfHighLimit) == high); @@ -2200,45 +2246,45 @@ public: // clang-format off TestData const tests[]{ // acct fundXrp bookAmt preTrust offerAmount tec spentXrp balanceUSD offers owners - {"ann", reserve(env, 0) + 0 * f, 1, noPreTrust, 1000, tecUNFUNDED_OFFER, f, USD( 0), 0, 0}, // Account is at the reserve, and will dip below once fees are subtracted. - {"bev", reserve(env, 0) + 1 * f, 1, noPreTrust, 1000, tecUNFUNDED_OFFER, f, USD( 0), 0, 0}, // Account has just enough for the reserve and the fee. - {"cam", reserve(env, 0) + 2 * f, 0, noPreTrust, 1000, tecINSUF_RESERVE_OFFER, f, USD( 0), 0, 0}, // Account has enough for the reserve, the fee and the offer, and a bit more, but not enough for the reserve after the offer is placed. - {"deb", drops(10) + reserve(env, 0) + 1 * f, 1, noPreTrust, 1000, tesSUCCESS, drops(10) + f, USD(0.00001), 0, 1}, // Account has enough to buy a little USD then the offer runs dry. - {"eve", reserve(env, 1) + 0 * f, 0, noPreTrust, 1000, tesSUCCESS, f, USD( 0), 1, 1}, // No offer to cross - {"flo", reserve(env, 1) + 0 * f, 1, noPreTrust, 1000, tesSUCCESS, XRP( 1) + f, USD( 1), 0, 1}, - {"gay", reserve(env, 1) + 1 * f, 1000, noPreTrust, 1000, tesSUCCESS, XRP( 50) + f, USD( 50), 0, 1}, - {"hye", XRP(1000) + 1 * f, 1000, noPreTrust, 1000, tesSUCCESS, XRP( 800) + f, USD( 800), 0, 1}, - {"ivy", XRP( 1) + reserve(env, 1) + 1 * f, 1, noPreTrust, 1000, tesSUCCESS, XRP( 1) + f, USD( 1), 0, 1}, - {"joy", XRP( 1) + reserve(env, 2) + 1 * f, 1, noPreTrust, 1000, tesSUCCESS, XRP( 1) + f, USD( 1), 1, 2}, - {"kim", XRP( 900) + reserve(env, 2) + 1 * f, 999, noPreTrust, 1000, tesSUCCESS, XRP( 999) + f, USD( 999), 0, 1}, - {"liz", XRP( 998) + reserve(env, 0) + 1 * f, 999, noPreTrust, 1000, tesSUCCESS, XRP( 998) + f, USD( 998), 0, 1}, - {"meg", XRP( 998) + reserve(env, 1) + 1 * f, 999, noPreTrust, 1000, tesSUCCESS, XRP( 999) + f, USD( 999), 0, 1}, - {"nia", XRP( 998) + reserve(env, 2) + 1 * f, 999, noPreTrust, 1000, tesSUCCESS, XRP( 999) + f, USD( 999), 1, 2}, - {"ova", XRP( 999) + reserve(env, 0) + 1 * f, 1000, noPreTrust, 1000, tesSUCCESS, XRP( 999) + f, USD( 999), 0, 1}, - {"pam", XRP( 999) + reserve(env, 1) + 1 * f, 1000, noPreTrust, 1000, tesSUCCESS, XRP(1000) + f, USD( 1000), 0, 1}, - {"rae", XRP( 999) + reserve(env, 2) + 1 * f, 1000, noPreTrust, 1000, tesSUCCESS, XRP(1000) + f, USD( 1000), 0, 1}, - {"sue", XRP(1000) + reserve(env, 2) + 1 * f, 0, noPreTrust, 1000, tesSUCCESS, f, USD( 0), 1, 1}, + {.account="ann", .fundXrp=reserve(env, 0) + 0 * f, .bookAmount=1, .preTrust=noPreTrust, .offerAmount=1000, .tec=tecUNFUNDED_OFFER, .spentXrp=f, .balanceUsd=USD( 0), .offers=0, .owners=0}, // Account is at the reserve, and will dip below once fees are subtracted. + {.account="bev", .fundXrp=reserve(env, 0) + 1 * f, .bookAmount=1, .preTrust=noPreTrust, .offerAmount=1000, .tec=tecUNFUNDED_OFFER, .spentXrp=f, .balanceUsd=USD( 0), .offers=0, .owners=0}, // Account has just enough for the reserve and the fee. + {.account="cam", .fundXrp=reserve(env, 0) + 2 * f, .bookAmount=0, .preTrust=noPreTrust, .offerAmount=1000, .tec=tecINSUF_RESERVE_OFFER, .spentXrp=f, .balanceUsd=USD( 0), .offers=0, .owners=0}, // Account has enough for the reserve, the fee and the offer, and a bit more, but not enough for the reserve after the offer is placed. + {.account="deb", .fundXrp=drops(10) + reserve(env, 0) + 1 * f, .bookAmount=1, .preTrust=noPreTrust, .offerAmount=1000, .tec=tesSUCCESS, .spentXrp=drops(10) + f, .balanceUsd=USD(0.00001), .offers=0, .owners=1}, // Account has enough to buy a little USD then the offer runs dry. + {.account="eve", .fundXrp=reserve(env, 1) + 0 * f, .bookAmount=0, .preTrust=noPreTrust, .offerAmount=1000, .tec=tesSUCCESS, .spentXrp=f, .balanceUsd=USD( 0), .offers=1, .owners=1}, // No offer to cross + {.account="flo", .fundXrp=reserve(env, 1) + 0 * f, .bookAmount=1, .preTrust=noPreTrust, .offerAmount=1000, .tec=tesSUCCESS, .spentXrp=XRP( 1) + f, .balanceUsd=USD( 1), .offers=0, .owners=1}, + {.account="gay", .fundXrp=reserve(env, 1) + 1 * f, .bookAmount=1000, .preTrust=noPreTrust, .offerAmount=1000, .tec=tesSUCCESS, .spentXrp=XRP( 50) + f, .balanceUsd=USD( 50), .offers=0, .owners=1}, + {.account="hye", .fundXrp=XRP(1000) + 1 * f, .bookAmount=1000, .preTrust=noPreTrust, .offerAmount=1000, .tec=tesSUCCESS, .spentXrp=XRP( 800) + f, .balanceUsd=USD( 800), .offers=0, .owners=1}, + {.account="ivy", .fundXrp=XRP( 1) + reserve(env, 1) + 1 * f, .bookAmount=1, .preTrust=noPreTrust, .offerAmount=1000, .tec=tesSUCCESS, .spentXrp=XRP( 1) + f, .balanceUsd=USD( 1), .offers=0, .owners=1}, + {.account="joy", .fundXrp=XRP( 1) + reserve(env, 2) + 1 * f, .bookAmount=1, .preTrust=noPreTrust, .offerAmount=1000, .tec=tesSUCCESS, .spentXrp=XRP( 1) + f, .balanceUsd=USD( 1), .offers=1, .owners=2}, + {.account="kim", .fundXrp=XRP( 900) + reserve(env, 2) + 1 * f, .bookAmount=999, .preTrust=noPreTrust, .offerAmount=1000, .tec=tesSUCCESS, .spentXrp=XRP( 999) + f, .balanceUsd=USD( 999), .offers=0, .owners=1}, + {.account="liz", .fundXrp=XRP( 998) + reserve(env, 0) + 1 * f, .bookAmount=999, .preTrust=noPreTrust, .offerAmount=1000, .tec=tesSUCCESS, .spentXrp=XRP( 998) + f, .balanceUsd=USD( 998), .offers=0, .owners=1}, + {.account="meg", .fundXrp=XRP( 998) + reserve(env, 1) + 1 * f, .bookAmount=999, .preTrust=noPreTrust, .offerAmount=1000, .tec=tesSUCCESS, .spentXrp=XRP( 999) + f, .balanceUsd=USD( 999), .offers=0, .owners=1}, + {.account="nia", .fundXrp=XRP( 998) + reserve(env, 2) + 1 * f, .bookAmount=999, .preTrust=noPreTrust, .offerAmount=1000, .tec=tesSUCCESS, .spentXrp=XRP( 999) + f, .balanceUsd=USD( 999), .offers=1, .owners=2}, + {.account="ova", .fundXrp=XRP( 999) + reserve(env, 0) + 1 * f, .bookAmount=1000, .preTrust=noPreTrust, .offerAmount=1000, .tec=tesSUCCESS, .spentXrp=XRP( 999) + f, .balanceUsd=USD( 999), .offers=0, .owners=1}, + {.account="pam", .fundXrp=XRP( 999) + reserve(env, 1) + 1 * f, .bookAmount=1000, .preTrust=noPreTrust, .offerAmount=1000, .tec=tesSUCCESS, .spentXrp=XRP(1000) + f, .balanceUsd=USD( 1000), .offers=0, .owners=1}, + {.account="rae", .fundXrp=XRP( 999) + reserve(env, 2) + 1 * f, .bookAmount=1000, .preTrust=noPreTrust, .offerAmount=1000, .tec=tesSUCCESS, .spentXrp=XRP(1000) + f, .balanceUsd=USD( 1000), .offers=0, .owners=1}, + {.account="sue", .fundXrp=XRP(1000) + reserve(env, 2) + 1 * f, .bookAmount=0, .preTrust=noPreTrust, .offerAmount=1000, .tec=tesSUCCESS, .spentXrp=f, .balanceUsd=USD( 0), .offers=1, .owners=1}, //---------------- Pre-established trust lines --------------------- - {"abe", reserve(env, 0) + 0 * f, 1, gwPreTrust, 1000, tecUNFUNDED_OFFER, f, USD( 0), 0, 0}, - {"bud", reserve(env, 0) + 1 * f, 1, gwPreTrust, 1000, tecUNFUNDED_OFFER, f, USD( 0), 0, 0}, - {"che", reserve(env, 0) + 2 * f, 0, gwPreTrust, 1000, tecINSUF_RESERVE_OFFER, f, USD( 0), 0, 0}, - {"dan", drops(10) + reserve(env, 0) + 1 * f, 1, gwPreTrust, 1000, tesSUCCESS, drops(10) + f, USD(0.00001), 0, 0}, - {"eli", XRP( 20) + reserve(env, 0) + 1 * f, 1000, gwPreTrust, 1000, tesSUCCESS, XRP(20) + 1 * f, USD( 20), 0, 0}, - {"fyn", reserve(env, 1) + 0 * f, 0, gwPreTrust, 1000, tesSUCCESS, f, USD( 0), 1, 1}, - {"gar", reserve(env, 1) + 0 * f, 1, gwPreTrust, 1000, tesSUCCESS, XRP( 1) + f, USD( 1), 1, 1}, - {"hal", reserve(env, 1) + 1 * f, 1, gwPreTrust, 1000, tesSUCCESS, XRP( 1) + f, USD( 1), 1, 1}, + {.account="abe", .fundXrp=reserve(env, 0) + 0 * f, .bookAmount=1, .preTrust=gwPreTrust, .offerAmount=1000, .tec=tecUNFUNDED_OFFER, .spentXrp=f, .balanceUsd=USD( 0), .offers=0, .owners=0}, + {.account="bud", .fundXrp=reserve(env, 0) + 1 * f, .bookAmount=1, .preTrust=gwPreTrust, .offerAmount=1000, .tec=tecUNFUNDED_OFFER, .spentXrp=f, .balanceUsd=USD( 0), .offers=0, .owners=0}, + {.account="che", .fundXrp=reserve(env, 0) + 2 * f, .bookAmount=0, .preTrust=gwPreTrust, .offerAmount=1000, .tec=tecINSUF_RESERVE_OFFER, .spentXrp=f, .balanceUsd=USD( 0), .offers=0, .owners=0}, + {.account="dan", .fundXrp=drops(10) + reserve(env, 0) + 1 * f, .bookAmount=1, .preTrust=gwPreTrust, .offerAmount=1000, .tec=tesSUCCESS, .spentXrp=drops(10) + f, .balanceUsd=USD(0.00001), .offers=0, .owners=0}, + {.account="eli", .fundXrp=XRP( 20) + reserve(env, 0) + 1 * f, .bookAmount=1000, .preTrust=gwPreTrust, .offerAmount=1000, .tec=tesSUCCESS, .spentXrp=XRP(20) + 1 * f, .balanceUsd=USD( 20), .offers=0, .owners=0}, + {.account="fyn", .fundXrp=reserve(env, 1) + 0 * f, .bookAmount=0, .preTrust=gwPreTrust, .offerAmount=1000, .tec=tesSUCCESS, .spentXrp=f, .balanceUsd=USD( 0), .offers=1, .owners=1}, + {.account="gar", .fundXrp=reserve(env, 1) + 0 * f, .bookAmount=1, .preTrust=gwPreTrust, .offerAmount=1000, .tec=tesSUCCESS, .spentXrp=XRP( 1) + f, .balanceUsd=USD( 1), .offers=1, .owners=1}, + {.account="hal", .fundXrp=reserve(env, 1) + 1 * f, .bookAmount=1, .preTrust=gwPreTrust, .offerAmount=1000, .tec=tesSUCCESS, .spentXrp=XRP( 1) + f, .balanceUsd=USD( 1), .offers=1, .owners=1}, - {"ned", reserve(env, 1) + 0 * f, 1, acctPreTrust, 1000, tecUNFUNDED_OFFER, 2 * f, USD( 0), 0, 1}, - {"ole", reserve(env, 1) + 1 * f, 1, acctPreTrust, 1000, tecUNFUNDED_OFFER, 2 * f, USD( 0), 0, 1}, - {"pat", reserve(env, 1) + 2 * f, 0, acctPreTrust, 1000, tecUNFUNDED_OFFER, 2 * f, USD( 0), 0, 1}, - {"quy", reserve(env, 1) + 2 * f, 1, acctPreTrust, 1000, tecUNFUNDED_OFFER, 2 * f, USD( 0), 0, 1}, - {"ron", reserve(env, 1) + 3 * f, 0, acctPreTrust, 1000, tecINSUF_RESERVE_OFFER, 2 * f, USD( 0), 0, 1}, - {"syd", drops(10) + reserve(env, 1) + 2 * f, 1, acctPreTrust, 1000, tesSUCCESS, drops(10) + 2 * f, USD(0.00001), 0, 1}, - {"ted", XRP( 20) + reserve(env, 1) + 2 * f, 1000, acctPreTrust, 1000, tesSUCCESS, XRP(20) + 2 * f, USD( 20), 0, 1}, - {"uli", reserve(env, 2) + 0 * f, 0, acctPreTrust, 1000, tecINSUF_RESERVE_OFFER, 2 * f, USD( 0), 0, 1}, - {"vic", reserve(env, 2) + 0 * f, 1, acctPreTrust, 1000, tesSUCCESS, XRP( 1) + 2 * f, USD( 1), 0, 1}, - {"wes", reserve(env, 2) + 1 * f, 0, acctPreTrust, 1000, tesSUCCESS, 2 * f, USD( 0), 1, 2}, - {"xan", reserve(env, 2) + 1 * f, 1, acctPreTrust, 1000, tesSUCCESS, XRP( 1) + 2 * f, USD( 1), 1, 2}, + {.account="ned", .fundXrp=reserve(env, 1) + 0 * f, .bookAmount=1, .preTrust=acctPreTrust, .offerAmount=1000, .tec=tecUNFUNDED_OFFER, .spentXrp=2 * f, .balanceUsd=USD( 0), .offers=0, .owners=1}, + {.account="ole", .fundXrp=reserve(env, 1) + 1 * f, .bookAmount=1, .preTrust=acctPreTrust, .offerAmount=1000, .tec=tecUNFUNDED_OFFER, .spentXrp=2 * f, .balanceUsd=USD( 0), .offers=0, .owners=1}, + {.account="pat", .fundXrp=reserve(env, 1) + 2 * f, .bookAmount=0, .preTrust=acctPreTrust, .offerAmount=1000, .tec=tecUNFUNDED_OFFER, .spentXrp=2 * f, .balanceUsd=USD( 0), .offers=0, .owners=1}, + {.account="quy", .fundXrp=reserve(env, 1) + 2 * f, .bookAmount=1, .preTrust=acctPreTrust, .offerAmount=1000, .tec=tecUNFUNDED_OFFER, .spentXrp=2 * f, .balanceUsd=USD( 0), .offers=0, .owners=1}, + {.account="ron", .fundXrp=reserve(env, 1) + 3 * f, .bookAmount=0, .preTrust=acctPreTrust, .offerAmount=1000, .tec=tecINSUF_RESERVE_OFFER, .spentXrp=2 * f, .balanceUsd=USD( 0), .offers=0, .owners=1}, + {.account="syd", .fundXrp=drops(10) + reserve(env, 1) + 2 * f, .bookAmount=1, .preTrust=acctPreTrust, .offerAmount=1000, .tec=tesSUCCESS, .spentXrp=drops(10) + 2 * f, .balanceUsd=USD(0.00001), .offers=0, .owners=1}, + {.account="ted", .fundXrp=XRP( 20) + reserve(env, 1) + 2 * f, .bookAmount=1000, .preTrust=acctPreTrust, .offerAmount=1000, .tec=tesSUCCESS, .spentXrp=XRP(20) + 2 * f, .balanceUsd=USD( 20), .offers=0, .owners=1}, + {.account="uli", .fundXrp=reserve(env, 2) + 0 * f, .bookAmount=0, .preTrust=acctPreTrust, .offerAmount=1000, .tec=tecINSUF_RESERVE_OFFER, .spentXrp=2 * f, .balanceUsd=USD( 0), .offers=0, .owners=1}, + {.account="vic", .fundXrp=reserve(env, 2) + 0 * f, .bookAmount=1, .preTrust=acctPreTrust, .offerAmount=1000, .tec=tesSUCCESS, .spentXrp=XRP( 1) + 2 * f, .balanceUsd=USD( 1), .offers=0, .owners=1}, + {.account="wes", .fundXrp=reserve(env, 2) + 1 * f, .bookAmount=0, .preTrust=acctPreTrust, .offerAmount=1000, .tec=tesSUCCESS, .spentXrp=2 * f, .balanceUsd=USD( 0), .offers=1, .owners=2}, + {.account="xan", .fundXrp=reserve(env, 2) + 1 * f, .bookAmount=1, .preTrust=acctPreTrust, .offerAmount=1000, .tec=tesSUCCESS, .spentXrp=XRP( 1) + 2 * f, .balanceUsd=USD( 1), .offers=1, .owners=2}, }; // clang-format on @@ -2278,7 +2324,7 @@ public: } std::uint32_t const acctOfferSeq = env.seq(acct) - 1; - BEAST_EXPECT(env.balance(acct, USD.issue()) == t.balanceUsd); + BEAST_EXPECT(env.balance(acct, USD) == t.balanceUsd); BEAST_EXPECT(env.balance(acct, xrpIssue()) == t.fundXrp - t.spentXrp); env.require(offers(acct, t.offers)); env.require(owners(acct, t.owners)); @@ -2304,7 +2350,7 @@ public: else { // Verify that no trustline was created. - auto const sleTrust = env.le(keylet::line(acct, USD.issue())); + auto const sleTrust = env.le(keylet::line(acct, USD)); BEAST_EXPECT(!sleTrust); } } @@ -2489,8 +2535,8 @@ public: env.require(offers(bob, 0)); // The two trustlines that were generated by offers should be gone. - BEAST_EXPECT(!env.le(keylet::line(alice.id(), EUR.issue()))); - BEAST_EXPECT(!env.le(keylet::line(bob.id(), USD.issue()))); + BEAST_EXPECT(!env.le(keylet::line(alice.id(), EUR))); + BEAST_EXPECT(!env.le(keylet::line(bob.id(), USD))); // Make two more offers that leave one of the offers non-dry. We // need to properly sequence the transactions: @@ -2649,34 +2695,34 @@ public: // Constructor with takerGets/takerPays TestData( - std::string&& account_, // Account operated on - STAmount const& fundXrp_, // XRP acct funded with - STAmount const& fundUSD_, // USD acct funded with - STAmount const& gwGets_, // gw's offer - STAmount const& gwPays_, // - STAmount const& acctGets_, // acct's offer - STAmount const& acctPays_, // - TER tec_, // Returned tec code - STAmount const& spentXrp_, // Amount removed from fundXrp - STAmount const& finalUsd_, // Final USD balance on acct - int offers_, // Offers on acct - int owners_, // Owners on acct - STAmount const& takerGets_, // Remainder of acct's offer - STAmount const& takerPays_) // + std::string&& account_, // Account operated on + STAmount fundXrp_, // XRP acct funded with + STAmount fundUSD_, // USD acct funded with + STAmount gwGets_, // gw's offer + STAmount gwPays_, // + STAmount acctGets_, // acct's offer + STAmount acctPays_, // + TER tec_, // Returned tec code + STAmount spentXrp_, // Amount removed from fundXrp + STAmount finalUsd_, // Final USD balance on acct + int offers_, // Offers on acct + int owners_, // Owners on acct + STAmount takerGets_, // Remainder of acct's offer + STAmount takerPays_) // : account(std::move(account_)) - , fundXrp(fundXrp_) - , fundUSD(fundUSD_) - , gwGets(gwGets_) - , gwPays(gwPays_) - , acctGets(acctGets_) - , acctPays(acctPays_) + , fundXrp(std::move(fundXrp_)) + , fundUSD(std::move(fundUSD_)) + , gwGets(std::move(gwGets_)) + , gwPays(std::move(gwPays_)) + , acctGets(std::move(acctGets_)) + , acctPays(std::move(acctPays_)) , tec(tec_) - , spentXrp(spentXrp_) - , finalUsd(finalUsd_) + , spentXrp(std::move(spentXrp_)) + , finalUsd(std::move(finalUsd_)) , offers(offers_) , owners(owners_) - , takerGets(takerGets_) - , takerPays(takerPays_) + , takerGets(std::move(takerGets_)) + , takerPays(std::move(takerPays_)) { } @@ -2768,7 +2814,7 @@ public: std::uint32_t const acctOfferSeq = env.seq(acct) - 1; // Check results - BEAST_EXPECT(env.balance(acct, USD.issue()) == t.finalUsd); + BEAST_EXPECT(env.balance(acct, USD) == t.finalUsd); BEAST_EXPECT(env.balance(acct, xrpIssue()) == t.fundXrp - t.spentXrp); env.require(offers(acct, t.offers)); env.require(owners(acct, t.owners)); @@ -3289,12 +3335,12 @@ public: // clang-format off TestData const tests[]{ // acct fundXRP fundUSD fundEUR firstOfferTec secondOfferTec - {"ann", reserve(env, 3) + f * 4, USD(1000), EUR(1000), tesSUCCESS, tesSUCCESS}, - {"bev", reserve(env, 3) + f * 4, USD( 1), EUR(1000), tesSUCCESS, tesSUCCESS}, - {"cam", reserve(env, 3) + f * 4, USD(1000), EUR( 1), tesSUCCESS, tesSUCCESS}, - {"deb", reserve(env, 3) + f * 4, USD( 0), EUR( 1), tesSUCCESS, tecUNFUNDED_OFFER}, - {"eve", reserve(env, 3) + f * 4, USD( 1), EUR( 0), tecUNFUNDED_OFFER, tesSUCCESS}, - {"flo", reserve(env, 3) + 0, USD(1000), EUR(1000), tecINSUF_RESERVE_OFFER, tecINSUF_RESERVE_OFFER}, + {.acct="ann", .fundXRP=reserve(env, 3) + f * 4, .fundUSD=USD(1000), .fundEUR=EUR(1000), .firstOfferTec=tesSUCCESS, .secondOfferTec=tesSUCCESS}, + {.acct="bev", .fundXRP=reserve(env, 3) + f * 4, .fundUSD=USD( 1), .fundEUR=EUR(1000), .firstOfferTec=tesSUCCESS, .secondOfferTec=tesSUCCESS}, + {.acct="cam", .fundXRP=reserve(env, 3) + f * 4, .fundUSD=USD(1000), .fundEUR=EUR( 1), .firstOfferTec=tesSUCCESS, .secondOfferTec=tesSUCCESS}, + {.acct="deb", .fundXRP=reserve(env, 3) + f * 4, .fundUSD=USD( 0), .fundEUR=EUR( 1), .firstOfferTec=tesSUCCESS, .secondOfferTec=tecUNFUNDED_OFFER}, + {.acct="eve", .fundXRP=reserve(env, 3) + f * 4, .fundUSD=USD( 1), .fundEUR=EUR( 0), .firstOfferTec=tecUNFUNDED_OFFER, .secondOfferTec=tesSUCCESS}, + {.acct="flo", .fundXRP=reserve(env, 3) + 0, .fundUSD=USD(1000), .fundEUR=EUR(1000), .firstOfferTec=tecINSUF_RESERVE_OFFER, .secondOfferTec=tecINSUF_RESERVE_OFFER}, }; //clang-format on @@ -3675,7 +3721,7 @@ public: BEAST_EXPECT(offer[sfLedgerEntryType] == ltOFFER); BEAST_EXPECT( offer[sfTakerGets] == - STAmount(JPY.issue(), std::uint64_t(2230682446713524ul), -12)); + STAmount(JPY, std::uint64_t(2230682446713524ul), -12)); BEAST_EXPECT(offer[sfTakerPays] == BTC(0.035378)); } } @@ -3712,24 +3758,24 @@ public: env( pay(gw1, alice, - STAmount{USD.issue(), std::uint64_t(2185410179555600), -14})); + STAmount{USD, std::uint64_t(2185410179555600), -14})); env( pay(gw2, bob, - STAmount{JPY.issue(), std::uint64_t(6351823459548956), -12})); + STAmount{JPY, std::uint64_t(6351823459548956), -12})); env.close(); env(offer( bob, - STAmount{USD.issue(), std::uint64_t(4371257532306000), -17}, - STAmount{JPY.issue(), std::uint64_t(4573216636606000), -15})); + STAmount{USD, std::uint64_t(4371257532306000), -17}, + STAmount{JPY, std::uint64_t(4573216636606000), -15})); env.close(); // This offer did not partially cross correctly. env(offer( alice, - STAmount{JPY.issue(), std::uint64_t(2291181510070762), -12}, - STAmount{USD.issue(), std::uint64_t(2190218999914694), -14})); + STAmount{JPY, std::uint64_t(2291181510070762), -12}, + STAmount{USD, std::uint64_t(2190218999914694), -14})); env.close(); auto const aliceOffers = offersOnAccount(env, alice); @@ -3740,10 +3786,10 @@ public: BEAST_EXPECT(offer[sfLedgerEntryType] == ltOFFER); BEAST_EXPECT( offer[sfTakerGets] == - STAmount(USD.issue(), std::uint64_t(2185847305256635), -14)); + STAmount(USD, std::uint64_t(2185847305256635), -14)); BEAST_EXPECT( offer[sfTakerPays] == - STAmount(JPY.issue(), std::uint64_t(2286608293434156), -12)); + STAmount(JPY, std::uint64_t(2286608293434156), -12)); } } @@ -3772,21 +3818,21 @@ public: // Place alice's tiny offer in the book first. Let's see what happens // when a reasonable offer crosses it. STAmount const aliceCnyOffer{ - CNY.issue(), std::uint64_t(4926000000000000), -23}; + CNY, std::uint64_t(4926000000000000), -23}; env(offer(alice, aliceCnyOffer, drops(1), tfPassive)); env.close(); // bob places an ordinary offer STAmount const bobCnyStartBalance{ - CNY.issue(), std::uint64_t(3767479960090235), -15}; + CNY, std::uint64_t(3767479960090235), -15}; env(pay(gw, bob, bobCnyStartBalance)); env.close(); env(offer( bob, drops(203), - STAmount{CNY.issue(), std::uint64_t(1000000000000000), -20})); + STAmount{CNY, std::uint64_t(1000000000000000), -20})); env.close(); env.require(balance(alice, aliceCnyOffer)); @@ -3875,10 +3921,10 @@ public: // clang-format off TestData const tests[]{ // btcStart --------------------- actor[0] --------------------- -------------------- actor[1] ------------------- - {0, 0, 1, BTC(20), {{"ann", 0, drops(3900000'000000 - (4 * baseFee)), BTC(20.0), USD(3000)}, {"abe", 0, drops(4100000'000000 - (3 * baseFee)), BTC( 0), USD(750)}}}, // no BTC xfer fee - {0, 1, 0, BTC(20), {{"bev", 0, drops(4100000'000000 - (4 * baseFee)), BTC( 7.5), USD(2000)}, {"bob", 0, drops(3900000'000000 - (3 * baseFee)), BTC(10), USD( 0)}}}, // no USD xfer fee - {0, 0, 0, BTC(20), {{"cam", 0, drops(4000000'000000 - (5 * baseFee)), BTC(20.0), USD(2000)} }}, // no xfer fee - {0, 1, 0, BTC( 5), {{"deb", 1, drops(4040000'000000 - (4 * baseFee)), BTC( 0.0), USD(2000)}, {"dan", 1, drops(3960000'000000 - (3 * baseFee)), BTC( 4), USD( 0)}}}, // no USD xfer fee + {.self=0, .leg0=0, .leg1=1, .btcStart=BTC(20), .actors={{"ann", 0, drops(3900000'000000 - (4 * baseFee)), BTC(20.0), USD(3000)}, {"abe", 0, drops(4100000'000000 - (3 * baseFee)), BTC( 0), USD(750)}}}, // no BTC xfer fee + {.self=0, .leg0=1, .leg1=0, .btcStart=BTC(20), .actors={{"bev", 0, drops(4100000'000000 - (4 * baseFee)), BTC( 7.5), USD(2000)}, {"bob", 0, drops(3900000'000000 - (3 * baseFee)), BTC(10), USD( 0)}}}, // no USD xfer fee + {.self=0, .leg0=0, .leg1=0, .btcStart=BTC(20), .actors={{"cam", 0, drops(4000000'000000 - (5 * baseFee)), BTC(20.0), USD(2000)} }}, // no xfer fee + {.self=0, .leg0=1, .leg1=0, .btcStart=BTC( 5), .actors={{"deb", 1, drops(4040000'000000 - (4 * baseFee)), BTC( 0.0), USD(2000)}, {"dan", 1, drops(3960000'000000 - (3 * baseFee)), BTC( 4), USD( 0)}}}, // no USD xfer fee }; // clang-format on @@ -3927,12 +3973,9 @@ public: auto actorOffers = offersOnAccount(env, actor.acct); auto const offerCount = std::distance( actorOffers.begin(), - std::remove_if( - actorOffers.begin(), - actorOffers.end(), - [](std::shared_ptr& offer) { - return (*offer)[sfTakerGets].signum() == 0; - })); + std::ranges::remove_if(actorOffers, [](std::shared_ptr& offer) { + return (*offer)[sfTakerGets].signum() == 0; + }).begin()); BEAST_EXPECT(offerCount == actor.offers); env.require(balance(actor.acct, actor.xrp)); @@ -4026,8 +4069,8 @@ public: // clang-format off TestData const tests[]{ // btcStart ------------------- actor[0] -------------------- ------------------- actor[1] -------------------- - {0, 0, 1, BTC(5), {{"gay", 1, drops(3950000'000000 - (4 * baseFee)), BTC(5), USD(2500)}, {"gar", 1, drops(4050000'000000 - (3 * baseFee)), BTC(0), USD(1375)}}}, // no BTC xfer fee - {0, 0, 0, BTC(5), {{"hye", 2, drops(4000000'000000 - (5 * baseFee)), BTC(5), USD(2000)} }} // no xfer fee + {.self=0, .leg0=0, .leg1=1, .btcStart=BTC(5), .actors={{"gay", 1, drops(3950000'000000 - (4 * baseFee)), BTC(5), USD(2500)}, {"gar", 1, drops(4050000'000000 - (3 * baseFee)), BTC(0), USD(1375)}}}, // no BTC xfer fee + {.self=0, .leg0=0, .leg1=0, .btcStart=BTC(5), .actors={{"hye", 2, drops(4000000'000000 - (5 * baseFee)), BTC(5), USD(2000)} }} // no xfer fee }; // clang-format on @@ -4076,12 +4119,9 @@ public: auto actorOffers = offersOnAccount(env, actor.acct); auto const offerCount = std::distance( actorOffers.begin(), - std::remove_if( - actorOffers.begin(), - actorOffers.end(), - [](std::shared_ptr& offer) { - return (*offer)[sfTakerGets].signum() == 0; - })); + std::ranges::remove_if(actorOffers, [](std::shared_ptr& offer) { + return (*offer)[sfTakerGets].signum() == 0; + }).begin()); BEAST_EXPECT(offerCount == actor.offers); env.require(balance(actor.acct, actor.xrp)); @@ -4279,8 +4319,7 @@ public: Env env{*this, features}; - // This test mimics the payment flow used in the Ripple Connect - // smoke test. The players: + // This test mimics a payment flow. The players: // A USD gateway with hot and cold wallets // A EUR gateway with hot and cold walllets // A MM gateway that will provide offers from USD->EUR and EUR->USD @@ -4634,9 +4673,8 @@ public: sortedOffersOnAccount(jtx::Env& env, jtx::Account const& acct) { std::vector> offers{offersOnAccount(env, acct)}; - std::sort( - offers.begin(), - offers.end(), + std::ranges::sort( + offers, [](std::shared_ptr const& rhs, std::shared_ptr const& lhs) { return (*rhs)[sfSequence] < (*lhs)[sfSequence]; }); @@ -5178,5 +5216,4 @@ BEAST_DEFINE_TESTSUITE_PRIO(OfferWOSmallQOffers, app, xrpl, 2); BEAST_DEFINE_TESTSUITE_PRIO(OfferAllFeatures, app, xrpl, 2); BEAST_DEFINE_TESTSUITE_MANUAL_PRIO(Offer_manual, app, xrpl, 20); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/app/Oracle_test.cpp b/src/test/app/Oracle_test.cpp index 83b658ac41..43d4a13e2b 100644 --- a/src/test/app/Oracle_test.cpp +++ b/src/test/app/Oracle_test.cpp @@ -1,11 +1,37 @@ +#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include -namespace xrpl { -namespace test { -namespace jtx { -namespace oracle { +#include +#include +#include + +namespace xrpl::test::jtx::oracle { struct Oracle_test : public beast::unit_test::suite { @@ -792,10 +818,4 @@ public: BEAST_DEFINE_TESTSUITE(Oracle, app, xrpl); -} // namespace oracle - -} // namespace jtx - -} // namespace test - -} // namespace xrpl +} // namespace xrpl::test::jtx::oracle diff --git a/src/test/app/OversizeMeta_test.cpp b/src/test/app/OversizeMeta_test.cpp index d6305bedb6..17f4410cf6 100644 --- a/src/test/app/OversizeMeta_test.cpp +++ b/src/test/app/OversizeMeta_test.cpp @@ -1,9 +1,18 @@ -#include -#include +#include +#include +#include +#include // IWYU pragma: keep +#include +#include -namespace xrpl { -namespace test { +#include +#include + +#include +#include + +namespace xrpl::test { // Make sure "plump" order books don't have problems class PlumpBook_test : public beast::unit_test::suite @@ -169,5 +178,4 @@ public: BEAST_DEFINE_TESTSUITE_MANUAL_PRIO(FindOversizeCross, app, xrpl, 50); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/app/PathMPT_test.cpp b/src/test/app/PathMPT_test.cpp new file mode 100644 index 0000000000..482d120342 --- /dev/null +++ b/src/test/app/PathMPT_test.cpp @@ -0,0 +1,463 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace xrpl::test { +namespace detail { + +static Json::Value +rpf(jtx::Account const& src, + jtx::Account const& dst, + xrpl::test::jtx::MPT const& USD, + std::vector const& num_src) +{ + Json::Value jv = Json::objectValue; + jv[jss::command] = "ripple_path_find"; + jv[jss::source_account] = toBase58(src); + + if (!num_src.empty()) + { + auto& sc = (jv[jss::source_currencies] = Json::arrayValue); + Json::Value j = Json::objectValue; + for (auto const& id : num_src) + { + j[jss::mpt_issuance_id] = to_string(id); + sc.append(j); + } + } + + auto const d = toBase58(dst); + jv[jss::destination_account] = d; + + Json::Value& j = (jv[jss::destination_amount] = Json::objectValue); + j[jss::mpt_issuance_id] = to_string(USD.mpt()); + j[jss::value] = "1"; + + return jv; +} + +} // namespace detail + +//------------------------------------------------------------------------------ + +class PathMPT_test : public beast::unit_test::suite +{ + jtx::Env + pathTestEnv() + { + // These tests were originally written with search parameters that are + // different from the current defaults. This function creates an env + // with the search parameters that the tests were written for. + using namespace jtx; + return Env(*this, envconfig([](std::unique_ptr cfg) { + cfg->PATH_SEARCH_OLD = 7; + cfg->PATH_SEARCH = 7; + cfg->PATH_SEARCH_MAX = 10; + return cfg; + })); + } + +public: + void + source_currencies_limit() + { + testcase("source currency limits"); + using namespace std::chrono_literals; + using namespace jtx; + Env env = pathTestEnv(); + auto const gw = Account("gateway"); + auto const alice = Account("alice"); + auto const bob = Account("bob"); + + env.fund(XRP(10'000), "alice", "bob", gw); + + MPT const USD = + MPTTester({.env = env, .issuer = gw, .holders = {alice, bob}, .maxAmt = 100}); + + auto& app = env.app(); + Resource::Charge loadType = Resource::feeReferenceRPC; + Resource::Consumer c; + + RPC::JsonContext context{ + {.j = env.journal, + .app = app, + .loadType = loadType, + .netOps = app.getOPs(), + .ledgerMaster = app.getLedgerMaster(), + .consumer = c, + .role = Role::USER, + .coro = {}, + .infoSub = {}, + .apiVersion = RPC::apiVersionIfUnspecified}, + {}, + {}}; + Json::Value result; + gate g; + // Test RPC::Tuning::max_src_cur source currencies. + std::vector num_src; + num_src.reserve(RPC::Tuning::max_src_cur); + for (std::uint8_t i = 0; i < RPC::Tuning::max_src_cur; ++i) + num_src.push_back(makeMptID(i, bob)); + app.getJobQueue().postCoro(jtCLIENT, "RPC-Client", [&](auto const& coro) { + context.params = xrpl::test::detail::rpf(alice, bob, USD, num_src); + context.coro = coro; + RPC::doCommand(context, result); + g.signal(); + }); + BEAST_EXPECT(g.wait_for(5s)); + BEAST_EXPECT(!result.isMember(jss::error)); + + // Test more than RPC::Tuning::max_src_cur source currencies. + num_src.push_back(makeMptID(RPC::Tuning::max_src_cur, bob)); + app.getJobQueue().postCoro(jtCLIENT, "RPC-Client", [&](auto const& coro) { + context.params = xrpl::test::detail::rpf(alice, bob, USD, num_src); + context.coro = coro; + RPC::doCommand(context, result); + g.signal(); + }); + BEAST_EXPECT(g.wait_for(5s)); + BEAST_EXPECT(result.isMember(jss::error)); + + // Test RPC::Tuning::max_auto_src_cur source currencies. + num_src.clear(); + for (auto i = 0; i < (RPC::Tuning::max_auto_src_cur - 1); ++i) + { + auto CURM = MPTTester({.env = env, .issuer = alice, .holders = {bob}}); + num_src.push_back(CURM.issuanceID()); + } + app.getJobQueue().postCoro(jtCLIENT, "RPC-Client", [&](auto const& coro) { + context.params = xrpl::test::detail::rpf(alice, bob, USD, {}); + context.coro = coro; + RPC::doCommand(context, result); + g.signal(); + }); + BEAST_EXPECT(g.wait_for(5s)); + BEAST_EXPECT(!result.isMember(jss::error)); + + // Test more than RPC::Tuning::max_auto_src_cur source currencies. + auto CURM = MPTTester({.env = env, .issuer = alice, .holders = {bob}}); + app.getJobQueue().postCoro(jtCLIENT, "RPC-Client", [&](auto const& coro) { + context.params = xrpl::test::detail::rpf(alice, bob, USD, {}); + context.coro = coro; + RPC::doCommand(context, result); + g.signal(); + }); + BEAST_EXPECT(g.wait_for(5s)); + BEAST_EXPECT(result.isMember(jss::error)); + } + + void + no_direct_path_no_intermediary_no_alternatives() + { + testcase("no direct path no intermediary no alternatives"); + using namespace jtx; + + Env env = pathTestEnv(); + + env.fund(XRP(10'000), "alice", "bob"); + + auto USDM = MPTTester({.env = env, .issuer = "bob"}); + + auto const result = find_paths(env, "alice", "bob", USDM(5)); + BEAST_EXPECT(std::get<0>(result).empty()); + } + + void + direct_path_no_intermediary() + { + testcase("direct path no intermediary"); + using namespace jtx; + Env env = pathTestEnv(); + env.fund(XRP(10'000), "alice", "bob"); + + MPT const USD = MPTTester({.env = env, .issuer = "alice", .holders = {"bob"}}); + + STPathSet st; + STAmount sa; + std::tie(st, sa, std::ignore) = find_paths(env, "alice", "bob", USD(5)); + BEAST_EXPECT(st.empty()); + BEAST_EXPECT(equal(sa, USD(5))); + } + + void + payment_auto_path_find() + { + testcase("payment auto path find"); + using namespace jtx; + Env env = pathTestEnv(); + auto const gw = Account("gateway"); + env.fund(XRP(10'000), "alice", "bob", gw); + MPT const USD = MPTTester({.env = env, .issuer = gw, .holders = {"alice", "bob"}}); + env(pay(gw, "alice", USD(70))); + env(pay("alice", "bob", USD(24))); + env.require(balance("alice", USD(46))); + env.require(balance("bob", USD(24))); + } + + void + path_find(bool const domainEnabled) + { + testcase(std::string("path find") + (domainEnabled ? " w/ " : " w/o ") + "domain"); + using namespace jtx; + Env env = pathTestEnv(); + auto const gw = Account("gateway"); + env.fund(XRP(10'000), "alice", "bob", gw); + MPT const USD = MPTTester({.env = env, .issuer = gw, .holders = {"alice", "bob"}}); + env(pay(gw, "alice", USD(70))); + env(pay(gw, "bob", USD(50))); + + std::optional domainID; + if (domainEnabled) + domainID = setupDomain(env, {"alice", "bob", gw}); + + STPathSet st; + STAmount sa; + STAmount da; + std::tie(st, sa, da) = find_paths( + env, "alice", "bob", USD(5), std::nullopt, std::nullopt, std::nullopt, domainID); + // Note, a direct IOU payment will have "gateway" as alternative path + // since IOU supports rippling + BEAST_EXPECT(st.empty()); + BEAST_EXPECT(equal(sa, USD(5))); + BEAST_EXPECT(equal(da, USD(5))); + } + + void + path_find_consume_all(bool const domainEnabled) + { + testcase( + std::string("path find consume all") + (domainEnabled ? " w/ " : " w/o ") + "domain"); + using namespace jtx; + + { + Env env = pathTestEnv(); + auto const gw = Account("gateway"); + env.fund(XRP(10'000), "alice", "bob", "carol", gw); + MPT const USD = MPTTester({.env = env, .issuer = gw, .holders = {"bob", "carol"}}); + MPT const AUD(makeMptID(0, gw)); + env(pay(gw, "carol", USD(100))); + std::optional domainID; + if (domainEnabled) + { + domainID = setupDomain(env, {"alice", "bob", "carol", "gateway"}); + env(offer("carol", XRP(100), USD(100)), domain(*domainID)); + } + else + { + env(offer("carol", XRP(100), USD(100))); + } + env.close(); + + STPathSet st; + STAmount sa; + STAmount da; + std::tie(st, sa, da) = find_paths( + env, + "alice", + "bob", + AUD(-1), + std::optional(XRP(100'000'000)), + std::nullopt, + std::nullopt, + domainID); + BEAST_EXPECT(st.empty()); + std::tie(st, sa, da) = find_paths( + env, + "alice", + "bob", + USD(-1), + std::optional(XRP(100'000'000)), + std::nullopt, + std::nullopt, + domainID); + if (BEAST_EXPECT(st.size() == 1 && st[0].size() == 1)) + { + auto const& pathElem = st[0][0]; + BEAST_EXPECT( + pathElem.isOffer() && pathElem.getIssuerID() == gw.id() && + pathElem.getMPTID() == USD.issuanceID); + } + BEAST_EXPECT(sa == XRP(100)); + BEAST_EXPECT(equal(da, USD(100))); + + // if domain is used, finding path in the open offerbook will return + // empty result + if (domainEnabled) + { + std::tie(st, sa, da) = find_paths( + env, + "alice", + "bob", + Account("bob")["USD"](-1), + std::optional(XRP(1000000)), + std::nullopt, + std::nullopt); // not specifying a domain + BEAST_EXPECT(st.empty()); + } + } + } + + void + alternative_paths_consume_best_transfer(bool const domainEnabled) + { + testcase( + std::string("alternative path consume best transfer") + + (domainEnabled ? " w/ " : " w/o ") + "domain"); + using namespace jtx; + Env env = pathTestEnv(); + auto const gw = Account("gateway"); + auto const gw2 = Account("gateway2"); + env.fund(XRP(10'000), "alice", "bob", gw, gw2); + MPT const USD = MPTTester({.env = env, .issuer = gw, .holders = {"alice", "bob"}}); + MPT const gw2_USD = MPTTester( + {.env = env, .issuer = gw2, .holders = {"alice", "bob"}, .transferFee = 1'000}); + std::optional domainID; + if (domainEnabled) + { + domainID = setupDomain(env, {"alice", "bob", "gateway", "gateway2"}); + env(pay(gw, "alice", USD(70)), domain(*domainID)); + env(pay(gw2, "alice", gw2_USD(70)), domain(*domainID)); + env(pay("alice", "bob", USD(70)), domain(*domainID)); + } + else + { + env(pay(gw, "alice", USD(70))); + env(pay(gw2, "alice", gw2_USD(70))); + env(pay("alice", "bob", USD(70))); + } + env.require(balance("alice", USD(0))); + env.require(balance("alice", gw2_USD(70))); + env.require(balance("bob", USD(70))); + env.require(balance("bob", gw2_USD(0))); + } + + void + receive_max(bool const domainEnabled) + { + testcase(std::string("Receive max") + (domainEnabled ? " w/ " : " w/o ") + "domain"); + using namespace jtx; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const charlie = Account("charlie"); + auto const gw = Account("gw"); + { + // XRP -> MPT receive max + Env env = pathTestEnv(); + env.fund(XRP(10'000), alice, bob, charlie, gw); + env.close(); + MPT const USD = MPTTester({.env = env, .issuer = gw, .holders = {alice, bob, charlie}}); + env(pay(gw, charlie, USD(10))); + env.close(); + std::optional domainID; + if (domainEnabled) + { + domainID = setupDomain(env, {alice, bob, charlie, gw}); + env(offer(charlie, XRP(10), USD(10)), domain(*domainID)); + } + else + { + env(offer(charlie, XRP(10), USD(10))); + } + env.close(); + auto [st, sa, da] = find_paths( + env, alice, bob, USD(-1), XRP(100).value(), std::nullopt, std::nullopt, domainID); + BEAST_EXPECT(sa == XRP(10)); + BEAST_EXPECT(equal(da, USD(10))); + if (BEAST_EXPECT(st.size() == 1 && st[0].size() == 1)) + { + auto const& pathElem = st[0][0]; + BEAST_EXPECT( + pathElem.isOffer() && pathElem.getIssuerID() == gw.id() && + pathElem.getMPTID() == USD.mpt()); + } + } + { + // MPT -> XRP receive max + Env env = pathTestEnv(); + env.fund(XRP(10'000), alice, bob, charlie, gw); + env.close(); + MPT const USD = MPTTester({.env = env, .issuer = gw, .holders = {alice, bob, charlie}}); + env(pay(gw, alice, USD(10))); + env.close(); + std::optional domainID; + if (domainEnabled) + { + domainID = setupDomain(env, {alice, bob, charlie, gw}); + env(offer(charlie, USD(10), XRP(10)), domain(*domainID)); + } + else + { + env(offer(charlie, USD(10), XRP(10))); + } + env.close(); + auto [st, sa, da] = find_paths( + env, alice, bob, drops(-1), USD(100).value(), std::nullopt, std::nullopt, domainID); + BEAST_EXPECT(sa == USD(10)); + BEAST_EXPECT(equal(da, XRP(10))); + if (BEAST_EXPECT(st.size() == 1 && st[0].size() == 1)) + { + auto const& pathElem = st[0][0]; + BEAST_EXPECT( + pathElem.isOffer() && pathElem.getIssuerID() == xrpAccount() && + pathElem.getCurrency() == xrpCurrency()); + } + } + } + + void + run() override + { + source_currencies_limit(); + no_direct_path_no_intermediary_no_alternatives(); + direct_path_no_intermediary(); + payment_auto_path_find(); + for (auto const domainEnabled : {false, true}) + { + path_find(domainEnabled); + path_find_consume_all(domainEnabled); + alternative_paths_consume_best_transfer(domainEnabled); + receive_max(domainEnabled); + } + } +}; + +BEAST_DEFINE_TESTSUITE(PathMPT, app, xrpl); + +} // namespace xrpl::test diff --git a/src/test/app/Path_test.cpp b/src/test/app/Path_test.cpp index 841847d183..505d052faa 100644 --- a/src/test/app/Path_test.cpp +++ b/src/test/app/Path_test.cpp @@ -1,30 +1,62 @@ -#include #include #include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include // IWYU pragma: keep +#include +#include #include +#include +#include +#include +#include +#include +#include #include +#include #include -#include +#include +#include +#include #include #include +#include +#include #include +#include +#include +#include +#include #include +#include +#include #include +#include #include +#include +#include #include #include #include +#include +#include #include #include #include -#include +#include +#include -namespace xrpl { -namespace test { +namespace xrpl::test { //------------------------------------------------------------------------------ @@ -123,16 +155,16 @@ public: Resource::Consumer c; RPC::JsonContext context{ - {env.journal, - app, - loadType, - app.getOPs(), - app.getLedgerMaster(), - c, - Role::USER, - {}, - {}, - RPC::apiVersionIfUnspecified}, + {.j = env.journal, + .app = app, + .loadType = loadType, + .netOps = app.getOPs(), + .ledgerMaster = app.getLedgerMaster(), + .consumer = c, + .role = Role::USER, + .coro = {}, + .infoSub = {}, + .apiVersion = RPC::apiVersionIfUnspecified}, {}, {}}; @@ -234,16 +266,16 @@ public: Resource::Consumer c; RPC::JsonContext context{ - {env.journal, - app, - loadType, - app.getOPs(), - app.getLedgerMaster(), - c, - Role::USER, - {}, - {}, - RPC::apiVersionIfUnspecified}, + {.j = env.journal, + .app = app, + .loadType = loadType, + .netOps = app.getOPs(), + .ledgerMaster = app.getLedgerMaster(), + .consumer = c, + .role = Role::USER, + .coro = {}, + .infoSub = {}, + .apiVersion = RPC::apiVersionIfUnspecified}, {}, {}}; Json::Value result; @@ -848,7 +880,7 @@ public: })", jv); - auto const jv_l = env.le(keylet::line(Account("bob").id(), Account("alice")["USD"].issue())) + auto const jv_l = env.le(keylet::line(Account("bob").id(), Account("alice")["USD"])) ->getJson(JsonOptions::none); for (auto it = jv.begin(); it != jv.end(); ++it) BEAST_EXPECT(*it == jv_l[it.memberName()]); @@ -890,15 +922,14 @@ public: })", jv); - auto const jv_l = env.le(keylet::line(Account("bob").id(), Account("alice")["USD"].issue())) + auto const jv_l = env.le(keylet::line(Account("bob").id(), Account("alice")["USD"])) ->getJson(JsonOptions::none); for (auto it = jv.begin(); it != jv.end(); ++it) BEAST_EXPECT(*it == jv_l[it.memberName()]); env.trust(Account("bob")["USD"](0), "alice"); env.trust(Account("alice")["USD"](0), "bob"); - BEAST_EXPECT( - env.le(keylet::line(Account("bob").id(), Account("alice")["USD"].issue())) == nullptr); + BEAST_EXPECT(env.le(keylet::line(Account("bob").id(), Account("alice")["USD"])) == nullptr); } void @@ -941,14 +972,13 @@ public: })", jv); - auto const jv_l = env.le(keylet::line(Account("alice").id(), Account("bob")["USD"].issue())) + auto const jv_l = env.le(keylet::line(Account("alice").id(), Account("bob")["USD"])) ->getJson(JsonOptions::none); for (auto it = jv.begin(); it != jv.end(); ++it) BEAST_EXPECT(*it == jv_l[it.memberName()]); env(pay("alice", "bob", Account("alice")["USD"](50))); - BEAST_EXPECT( - env.le(keylet::line(Account("alice").id(), Account("bob")["USD"].issue())) == nullptr); + BEAST_EXPECT(env.le(keylet::line(Account("alice").id(), Account("bob")["USD"])) == nullptr); } void @@ -1882,5 +1912,4 @@ public: BEAST_DEFINE_TESTSUITE(Path, app, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/app/PayChan_test.cpp b/src/test/app/PayChan_test.cpp index 768031c3af..1f4376a656 100644 --- a/src/test/app/PayChan_test.cpp +++ b/src/test/app/PayChan_test.cpp @@ -1,15 +1,57 @@ -#include +#include +#include +#include +#include +#include +#include // IWYU pragma: keep +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include #include +#include +#include +#include +#include +#include #include +#include +#include #include #include +#include +#include #include +#include +#include +#include +#include +#include +#include #include #include -namespace xrpl { -namespace test { +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl::test { using namespace jtx::paychan; struct PayChan_test : public beast::unit_test::suite @@ -1606,6 +1648,7 @@ struct PayChan_test : public beast::unit_test::suite Account const& acc, std::shared_ptr const& chan) -> bool { xrpl::Dir const ownerDir(view, keylet::ownerDir(acc.id())); + // NOLINTNEXTLINE(modernize-use-ranges) return std::find(ownerDir.begin(), ownerDir.end(), chan) != ownerDir.end(); }; @@ -1936,5 +1979,4 @@ public: }; BEAST_DEFINE_TESTSUITE(PayChan, app, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/app/PayStrandMPT_test.cpp b/src/test/app/PayStrandMPT_test.cpp new file mode 100644 index 0000000000..44f25d54dc --- /dev/null +++ b/src/test/app/PayStrandMPT_test.cpp @@ -0,0 +1,652 @@ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace xrpl::test { + +struct PayStrandMPT_test : public beast::unit_test::suite +{ + static jtx::DirectStepInfo + makeEndpointStep(jtx::Account const& src, jtx::Account const& dst, jtx::IOU const& iou) + { + return jtx::DirectStepInfo{.src = src, .dst = dst, .currency = iou.currency}; + } + static jtx::MPTEndpointStepInfo + makeEndpointStep(jtx::Account const& src, jtx::Account const& dst, jtx::MPT const& mpt) + { + return jtx::MPTEndpointStepInfo{.src = src, .dst = dst, .mptid = mpt.mpt()}; + } + + void + testToStrand(FeatureBitset features) + { + testcase("To Strand"); + + using namespace jtx; + + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const carol = Account("carol"); + auto const gw = Account("gw"); + + using M = MPTEndpointStepInfo; + using B = xrpl::Book; + using XRPS = XRPEndpointStepInfo; + + AMMContext ammContext(alice, false); + + auto test = [&, this]( + jtx::Env& env, + Asset const& deliver, + std::optional const& sendMaxIssue, + STPath const& path, + TER expTer, + auto&&... expSteps) { + auto [ter, strand] = toStrand( + *env.current(), + alice, + bob, + deliver, + std::nullopt, + sendMaxIssue, + path, + true, + OfferCrossing::no, + ammContext, + std::nullopt, + env.app().getLogs().journal("Flow")); + BEAST_EXPECT(ter == expTer); + if (sizeof...(expSteps) != 0) + BEAST_EXPECT(jtx::equal(strand, std::forward(expSteps)...)); + }; + + { + auto testMultiToken = [&](auto&& issue1, auto&& issue2) { + Env env(*this, features); + env.fund(XRP(10'000), alice, bob, gw); + MPT const USD = + MPTTester({.env = env, .issuer = gw, .holders = {alice, bob}, .maxAmt = 1'000}); + auto const bobUSD = issue1( + {.env = env, + .token = "USD", + .issuer = bob, + .holders = {alice}, + .limit = 1'000}); + MPT const EUR = + MPTTester({.env = env, .issuer = gw, .holders = {alice, bob}, .maxAmt = 1'000}); + auto const bobEUR = issue2( + {.env = env, + .token = "EUR", + .issuer = bob, + .holders = {alice}, + .limit = 1'000}); + env(pay(gw, alice, EUR(100))); + + { + // Original test is + // STPath({ipe(bob["USD"]), cpe(EUR.currency)}); + // which ripples through same currency, different issuer. + // This results in 5 steps: + // 1 DirectStep alice -> gw EUR/gw + // 2 Book EUR/gw USD/bob + // 3 Book USD/bob EUR/bob + // 4 Book EUR/bob XRP + // 5 XRPEndpoint + // This is somewhat equivalent path with MPT + STPath const path = STPath({ipe(bobUSD), ipe(bobEUR), cpe(xrpCurrency())}); + auto [ter, _] = toStrand( + *env.current(), + alice, + alice, + /*deliver*/ xrpIssue(), + /*limitQuality*/ std::nullopt, + /*sendMaxIssue*/ EUR, + path, + true, + OfferCrossing::no, + ammContext, + std::nullopt, + env.app().getLogs().journal("Flow")); + (void)_; + BEAST_EXPECT(ter == tesSUCCESS); + } + { + STPath const path = STPath({ipe(USD), cpe(xrpCurrency())}); + auto [ter, _] = toStrand( + *env.current(), + alice, + alice, + /*deliver*/ xrpIssue(), + /*limitQuality*/ std::nullopt, + /*sendMaxIssue*/ EUR, + path, + true, + OfferCrossing::no, + ammContext, + std::nullopt, + env.app().getLogs().journal("Flow")); + (void)_; + BEAST_EXPECT(ter == tesSUCCESS); + } + }; + testHelper2TokensMix(testMultiToken); + } + { + auto testMultiToken = [&](auto&& issue1, auto&& issue2) { + Env env(*this, features); + env.fund(XRP(10'000), alice, bob, carol, gw); + auto USD = issue1({.env = env, .token = "USD", .issuer = gw, .limit = 1'000}); + using tUSD = std::decay_t; + auto EUR = issue2({.env = env, .token = "EUR", .issuer = gw, .limit = 1'000}); + using tEUR = std::decay_t; + + auto const err = [&]() { + if constexpr (std::is_same_v) + { + return tecNO_AUTH; + } + else + { + return terNO_LINE; + } + }(); + test(env, USD, std::nullopt, STPath(), err); + + if constexpr (std::is_same_v) + { + MPTTester(env, gw, USD).authorizeHolders({alice, bob, carol}); + } + else + { + env.trust(USD(1'000), alice, bob, carol); + } + + test(env, USD, std::nullopt, STPath(), tecPATH_DRY); + + env(pay(gw, alice, USD(100))); + env(pay(gw, carol, USD(100))); + + // Insert implied account + test( + env, + USD, + std::nullopt, + STPath(), + tesSUCCESS, + makeEndpointStep(alice, gw, USD), + makeEndpointStep(gw, bob, USD)); + if constexpr (std::is_same_v) + { + MPTTester(env, gw, EUR).authorizeHolders({alice, bob}); + } + else + { + env.trust(EUR(1'000), alice, bob); + } + + // Insert implied offer + test( + env, + EUR, + USD, + STPath(), + tesSUCCESS, + makeEndpointStep(alice, gw, USD), + B{USD, EUR, std::nullopt}, + makeEndpointStep(gw, bob, EUR)); + + // Path with explicit offer + test( + env, + EUR, + USD, + STPath({ipe(EUR)}), + tesSUCCESS, + makeEndpointStep(alice, gw, USD), + B{USD, EUR, std::nullopt}, + makeEndpointStep(gw, bob, EUR)); + + // Path with XRP src currency + test( + env, + USD, + xrpIssue(), + STPath({ipe(USD)}), + tesSUCCESS, + XRPS{alice}, + B{XRP, USD, std::nullopt}, + makeEndpointStep(gw, bob, USD)); + + // Path with XRP dst currency. + test( + env, + xrpIssue(), + USD, + STPath({STPathElement{ + STPathElement::typeCurrency, xrpAccount(), xrpCurrency(), xrpAccount()}}), + tesSUCCESS, + makeEndpointStep(alice, gw, USD), + B{USD, XRP, std::nullopt}, + XRPS{bob}); + + // Path with XRP cross currency bridged payment + test( + env, + EUR, + USD, + STPath({cpe(xrpCurrency())}), + tesSUCCESS, + makeEndpointStep(alice, gw, USD), + B{USD, XRP, std::nullopt}, + B{XRP, EUR, std::nullopt}, + makeEndpointStep(gw, bob, EUR)); + + // Create an offer with the same in/out issue + test(env, EUR, USD, STPath({ipe(USD), ipe(EUR)}), temBAD_PATH); + + // The same offer can't appear more than once on a path + test(env, EUR, USD, STPath({ipe(EUR), ipe(USD), ipe(EUR)}), temBAD_PATH_LOOP); + }; + testHelper2TokensMix(testMultiToken); + } + + { + // cannot have more than one offer with the same output issue + + using namespace jtx; + + auto testMultiToken = [&](auto&& issue1, auto&& issue2) { + Env env(*this, features); + + env.fund(XRP(10'000), alice, bob, carol, gw); + + auto const USD = issue1( + {.env = env, + .token = "USD", + .issuer = gw, + .holders = {alice, bob, carol}, + .limit = 10'000}); + auto const EUR = issue2( + {.env = env, + .token = "EUR", + .issuer = gw, + .holders = {alice, bob, carol}, + .limit = 10'000}); + + env(pay(gw, bob, USD(100))); + env(pay(gw, bob, EUR(100))); + + env(offer(bob, XRP(100), USD(100))); + env(offer(bob, USD(100), EUR(100)), txflags(tfPassive)); + env(offer(bob, EUR(100), USD(100)), txflags(tfPassive)); + + // payment path: XRP -> XRP/USD -> USD/EUR -> EUR/USD + env(pay(alice, carol, USD(100)), + path(~USD, ~EUR, ~USD), + sendmax(XRP(200)), + txflags(tfNoRippleDirect), + ter(temBAD_PATH_LOOP)); + }; + testHelper2TokensMix(testMultiToken); + } + + { + // check global freeze + Env env(*this, features); + env.fund(XRP(10000), alice, bob, gw); + auto USDM = MPTTester( + {.env = env, + .issuer = gw, + .holders = {alice, bob}, + .flags = MPTDEXFlags | tfMPTCanLock, + .maxAmt = 1'000}); + MPT const USD = USDM; + env(pay(gw, alice, USD(100))); + + // Account can't issue payments + USDM.set({.holder = alice, .flags = tfMPTLock}); + test(env, USD, std::nullopt, STPath(), terLOCKED); + USDM.set({.holder = alice, .flags = tfMPTUnlock}); + test(env, USD, std::nullopt, STPath(), tesSUCCESS); + + // Account can not issue funds + USDM.set({.flags = tfMPTLock}); + test(env, USD, std::nullopt, STPath(), terLOCKED); + USDM.set({.flags = tfMPTUnlock}); + test(env, USD, std::nullopt, STPath(), tesSUCCESS); + + // Account can not receive funds + USDM.set({.holder = bob, .flags = tfMPTLock}); + test(env, USD, std::nullopt, STPath(), terLOCKED); + USDM.set({.holder = bob, .flags = tfMPTUnlock}); + test(env, USD, std::nullopt, STPath(), tesSUCCESS); + } + + { + // check no auth + // An account may require authorization to receive MPTs from an + // issuer + Env env(*this, features); + env.fund(XRP(10'000), alice, bob, gw); + auto USDM = MPTTester( + {.env = env, + .issuer = gw, + .flags = MPTDEXFlags | tfMPTRequireAuth, + .maxAmt = 1'000}); + MPT const USD = USDM; + + // Authorize alice but not bob + USDM.authorize({.account = alice}); + USDM.authorize({.holder = alice}); + env(pay(gw, alice, USD(100))); + env.require(balance(alice, USD(100))); + test(env, USD, std::nullopt, STPath(), tecNO_AUTH); + + // Check pure issue redeem still works + auto [ter, strand] = toStrand( + *env.current(), + alice, + gw, + USD, + std::nullopt, + std::nullopt, + STPath(), + true, + OfferCrossing::no, + ammContext, + std::nullopt, + env.app().getLogs().journal("Flow")); + BEAST_EXPECT(ter == tesSUCCESS); + BEAST_EXPECT(equal(strand, M{alice, gw, USD})); + } + + { + // last step xrp from offer + Env env(*this, features); + env.fund(XRP(10'000), alice, bob, gw); + MPT const USD = + MPTTester({.env = env, .issuer = gw, .holders = {alice, bob}, .maxAmt = 1'000}); + env(pay(gw, alice, USD(100))); + + // alice -> USD/XRP -> bob + STPath path; + path.emplace_back(std::nullopt, xrpCurrency(), std::nullopt); + + auto [ter, strand] = toStrand( + *env.current(), + alice, + bob, + XRP, + std::nullopt, + USD, + path, + false, + OfferCrossing::no, + ammContext, + std::nullopt, + env.app().getLogs().journal("Flow")); + BEAST_EXPECT(ter == tesSUCCESS); + BEAST_EXPECT( + equal(strand, M{alice, gw, USD}, B{USD, xrpIssue(), std::nullopt}, XRPS{bob})); + } + } + + void + testRIPD1373(FeatureBitset features) + { + using namespace jtx; + testcase("RIPD1373"); + + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const carol = Account("carol"); + auto const gw = Account("gw"); + + { + Env env(*this, features); + + env.fund(XRP(10000), alice, bob, carol, gw); + MPT const USD = MPTTester( + {.env = env, .issuer = gw, .holders = {alice, bob, carol}, .maxAmt = 10'000}); + + env(pay(gw, bob, USD(100))); + + env(offer(bob, XRP(100), USD(100)), txflags(tfPassive)); + env(offer(bob, USD(100), XRP(100)), txflags(tfPassive)); + + // payment path: XRP -> XRP/USD -> USD/XRP + env(pay(alice, carol, XRP(100)), + path(~USD, ~XRP), + txflags(tfNoRippleDirect), + ter(temBAD_SEND_XRP_PATHS)); + } + + { + Env env(*this, features); + + env.fund(XRP(10000), alice, bob, carol, gw); + MPT const USD = MPTTester( + {.env = env, .issuer = gw, .holders = {alice, bob, carol}, .maxAmt = 10'000}); + + env(pay(gw, bob, USD(100))); + + env(offer(bob, XRP(100), USD(100)), txflags(tfPassive)); + env(offer(bob, USD(100), XRP(100)), txflags(tfPassive)); + + // payment path: XRP -> XRP/USD -> USD/XRP + env(pay(alice, carol, XRP(100)), + path(~USD, ~XRP), + sendmax(XRP(200)), + txflags(tfNoRippleDirect), + ter(temBAD_SEND_XRP_MAX)); + } + } + + void + testLoop(FeatureBitset features) + { + testcase("test loop"); + using namespace jtx; + + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const carol = Account("carol"); + auto const gw = Account("gw"); + auto const EUR = gw["EUR"]; + auto const CNY = gw["CNY"]; + + { + Env env(*this, features); + + env.fund(XRP(10'000), alice, bob, carol, gw); + MPT const USD = MPTTester( + {.env = env, .issuer = gw, .holders = {alice, bob, carol}, .maxAmt = 10'000}); + + env(pay(gw, bob, USD(100))); + env(pay(gw, alice, USD(100))); + + env(offer(bob, XRP(100), USD(100)), txflags(tfPassive)); + env(offer(bob, USD(100), XRP(100)), txflags(tfPassive)); + + // payment path: USD -> USD/XRP -> XRP/USD + env(pay(alice, carol, USD(100)), + sendmax(USD(100)), + path(~XRP, ~USD), + txflags(tfNoRippleDirect), + ter(temBAD_PATH_LOOP)); + } + { + auto testMultiToken = [&](auto&& issue1, auto&& issue2, auto&& issue3) { + Env env(*this, features); + + env.fund(XRP(10'000), alice, bob, carol, gw); + auto const USD = issue1( + {.env = env, + .token = "USD", + .issuer = gw, + .holders = {alice, bob, carol}, + .limit = 10'000}); + auto const EUR = issue2( + {.env = env, + .token = "EUR", + .issuer = gw, + .holders = {alice, bob, carol}, + .limit = 10'000}); + auto const CNY = issue3( + {.env = env, + .token = "CNY", + .issuer = gw, + .holders = {alice, bob, carol}, + .limit = 10'000}); + + env(pay(gw, bob, USD(100))); + env(pay(gw, bob, EUR(100))); + env(pay(gw, bob, CNY(100))); + + env(offer(bob, XRP(100), USD(100)), txflags(tfPassive)); + env(offer(bob, USD(100), EUR(100)), txflags(tfPassive)); + env(offer(bob, EUR(100), CNY(100)), txflags(tfPassive)); + + // payment path: XRP->XRP/USD->USD/EUR->USD/CNY + env(pay(alice, carol, CNY(100)), + sendmax(XRP(100)), + path(~USD, ~EUR, ~USD, ~CNY), + txflags(tfNoRippleDirect), + ter(temBAD_PATH_LOOP)); + }; + testHelper3TokensMix(testMultiToken); + } + } + + void + testNoAccount(FeatureBitset features) + { + testcase("test no account"); + using namespace jtx; + + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const gw = Account("gw"); + + Env env(*this, features); + env.fund(XRP(10'000), alice, bob, gw); + MPT const USD = MPTTester({.env = env, .issuer = gw, .holders = {alice, bob}}); + + STAmount const sendMax{USD, 100, 1}; + STAmount const noAccountAmount{MPTIssue{0, noAccount()}, 100, 1}; + STAmount const deliver; + AccountID const srcAcc = alice.id(); + AccountID const dstAcc = bob.id(); + STPathSet const pathSet; + xrpl::path::RippleCalc::Input inputs; + inputs.defaultPathsAllowed = true; + try + { + PaymentSandbox sb{env.current().get(), tapNONE}; + { + auto const r = ::xrpl::path::RippleCalc::rippleCalculate( + sb, + sendMax, + deliver, + dstAcc, + noAccount(), + pathSet, + std::nullopt, + env.app(), + &inputs); + BEAST_EXPECT(r.result() == temBAD_PATH); + } + { + auto const r = ::xrpl::path::RippleCalc::rippleCalculate( + sb, + sendMax, + deliver, + noAccount(), + srcAcc, + pathSet, + std::nullopt, + env.app(), + &inputs); + BEAST_EXPECT(r.result() == temBAD_PATH); + } + { + auto const r = ::xrpl::path::RippleCalc::rippleCalculate( + sb, + noAccountAmount, + deliver, + dstAcc, + srcAcc, + pathSet, + std::nullopt, + env.app(), + &inputs); + BEAST_EXPECT(r.result() == temBAD_PATH); + } + { + auto const r = ::xrpl::path::RippleCalc::rippleCalculate( + sb, + sendMax, + noAccountAmount, + dstAcc, + srcAcc, + pathSet, + std::nullopt, + env.app(), + &inputs); + BEAST_EXPECT(r.result() == temBAD_PATH); + } + } + catch (...) + { + this->fail(); + } + } + + void + run() override + { + using namespace jtx; + auto const sa = testable_amendments(); + testToStrand(sa); + + testRIPD1373(sa); + + testLoop(sa); + + testNoAccount(sa); + } +}; + +BEAST_DEFINE_TESTSUITE(PayStrandMPT, app, xrpl); + +} // namespace xrpl::test diff --git a/src/test/app/PayStrand_test.cpp b/src/test/app/PayStrand_test.cpp index 222bc2ed07..24efb4b155 100644 --- a/src/test/app/PayStrand_test.cpp +++ b/src/test/app/PayStrand_test.cpp @@ -1,33 +1,53 @@ -#include +#include +#include #include - -#include +#include +#include +#include +#include +#include +#include +#include // IWYU pragma: keep +#include +#include +#include +#include +#include #include #include +#include +#include #include +#include +#include #include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include -namespace xrpl { -namespace test { - -struct DirectStepInfo -{ - AccountID src; - AccountID dst; - Currency currency; -}; - -struct XRPEndpointStepInfo -{ - AccountID acc; -}; +namespace xrpl::test { enum class TrustFlag { freeze, auth, noripple }; @@ -69,82 +89,6 @@ getTrustFlag( return false; // silence warning } -bool -equal(std::unique_ptr const& s1, DirectStepInfo const& dsi) -{ - if (!s1) - return false; - return test::directStepEqual(*s1, dsi.src, dsi.dst, dsi.currency); -} - -bool -equal(std::unique_ptr const& s1, XRPEndpointStepInfo const& xrpStepInfo) -{ - if (!s1) - return false; - return test::xrpEndpointStepEqual(*s1, xrpStepInfo.acc); -} - -bool -equal(std::unique_ptr const& s1, xrpl::Book const& bsi) -{ - if (!s1) - return false; - return bookStepEqual(*s1, bsi); -} - -template -bool -strandEqualHelper(Iter i) -{ - // base case. all args processed and found equal. - return true; -} - -template -bool -strandEqualHelper(Iter i, StepInfo&& si, Args&&... args) -{ - if (!equal(*i, std::forward(si))) - return false; - return strandEqualHelper(++i, std::forward(args)...); -} - -template -bool -equal(Strand const& strand, Args&&... args) -{ - if (strand.size() != sizeof...(Args)) - return false; - if (strand.empty()) - return true; - return strandEqualHelper(strand.begin(), std::forward(args)...); -} - -STPathElement -ape(AccountID const& a) -{ - return STPathElement(STPathElement::typeAccount, a, xrpCurrency(), xrpAccount()); -}; - -// Issue path element -STPathElement -ipe(Issue const& iss) -{ - return STPathElement( - STPathElement::typeCurrency | STPathElement::typeIssuer, - xrpAccount(), - iss.currency, - iss.account); -}; - -// Issuer path element -STPathElement -iape(AccountID const& account) -{ - return STPathElement(STPathElement::typeIssuer, xrpAccount(), xrpCurrency(), account); -}; - class ElementComboIter { enum class SB /*state bit*/ @@ -644,7 +588,7 @@ struct PayStrand_test : public beast::unit_test::suite alice, /*deliver*/ xrpIssue(), /*limitQuality*/ std::nullopt, - /*sendMaxIssue*/ EUR.issue(), + /*sendMaxIssue*/ EUR, path, true, OfferCrossing::no, @@ -662,7 +606,7 @@ struct PayStrand_test : public beast::unit_test::suite alice, /*deliver*/ xrpIssue(), /*limitQuality*/ std::nullopt, - /*sendMaxIssue*/ EUR.issue(), + /*sendMaxIssue*/ EUR, path, true, OfferCrossing::no, @@ -695,7 +639,7 @@ struct PayStrand_test : public beast::unit_test::suite test( env, EUR, - USD.issue(), + USD, STPath(), tesSUCCESS, D{alice, gw, usdC}, @@ -706,7 +650,7 @@ struct PayStrand_test : public beast::unit_test::suite test( env, EUR, - USD.issue(), + USD, STPath({ipe(EUR)}), tesSUCCESS, D{alice, gw, usdC}, @@ -718,7 +662,7 @@ struct PayStrand_test : public beast::unit_test::suite test( env, carol["USD"], - USD.issue(), + USD, STPath({iape(carol)}), tesSUCCESS, D{alice, gw, usdC}, @@ -740,7 +684,7 @@ struct PayStrand_test : public beast::unit_test::suite test( env, xrpIssue(), - USD.issue(), + USD, STPath({STPathElement{ STPathElement::typeCurrency, xrpAccount(), xrpCurrency(), xrpAccount()}}), tesSUCCESS, @@ -752,7 +696,7 @@ struct PayStrand_test : public beast::unit_test::suite test( env, EUR, - USD.issue(), + USD, STPath({cpe(xrpCurrency())}), tesSUCCESS, D{alice, gw, usdC}, @@ -774,7 +718,7 @@ struct PayStrand_test : public beast::unit_test::suite xrpAccount(), XRP, std::nullopt, - USD.issue(), + USD, STPath(), true, OfferCrossing::no, @@ -820,7 +764,7 @@ struct PayStrand_test : public beast::unit_test::suite } // Create an offer with the same in/out issue - test(env, EUR, USD.issue(), STPath({ipe(USD), ipe(EUR)}), temBAD_PATH); + test(env, EUR, USD, STPath({ipe(USD), ipe(EUR)}), temBAD_PATH); // Path element with type zero test( @@ -836,7 +780,7 @@ struct PayStrand_test : public beast::unit_test::suite test(env, USD, std::nullopt, STPath({ape(gw), ape(carol)}), temBAD_PATH_LOOP); // The same offer can't appear more than once on a path - test(env, EUR, USD.issue(), STPath({ipe(EUR), ipe(USD), ipe(EUR)}), temBAD_PATH_LOOP); + test(env, EUR, USD, STPath({ipe(EUR), ipe(USD), ipe(EUR)}), temBAD_PATH_LOOP); } { @@ -958,7 +902,7 @@ struct PayStrand_test : public beast::unit_test::suite bob, XRP, std::nullopt, - USD.issue(), + USD, path, false, OfferCrossing::no, @@ -966,8 +910,8 @@ struct PayStrand_test : public beast::unit_test::suite std::nullopt, env.app().getJournal("Flow")); BEAST_EXPECT(isTesSuccess(ter)); - BEAST_EXPECT(equal( - strand, D{alice, gw, usdC}, B{USD.issue(), xrpIssue(), std::nullopt}, XRPS{bob})); + BEAST_EXPECT( + equal(strand, D{alice, gw, usdC}, B{USD, xrpIssue(), std::nullopt}, XRPS{bob})); } } @@ -1125,7 +1069,7 @@ struct PayStrand_test : public beast::unit_test::suite Env env(*this, features); env.fund(XRP(10000), alice, bob, gw); - STAmount const sendMax{USD.issue(), 100, 1}; + STAmount const sendMax{USD, 100, 1}; STAmount const noAccountAmount{Issue{USD.currency, noAccount()}, 100, 1}; STAmount const deliver; AccountID const srcAcc = alice.id(); @@ -1215,5 +1159,4 @@ struct PayStrand_test : public beast::unit_test::suite BEAST_DEFINE_TESTSUITE(PayStrand, app, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/app/PermissionedDEX_test.cpp b/src/test/app/PermissionedDEX_test.cpp index 58a041ff56..600f753c96 100644 --- a/src/test/app/PermissionedDEX_test.cpp +++ b/src/test/app/PermissionedDEX_test.cpp @@ -1,34 +1,51 @@ -#include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include // IWYU pragma: keep +#include +#include +#include +#include +#include +#include +#include +#include -#include -#include +#include #include -#include +#include +#include +#include #include -#include #include #include #include #include +#include #include +#include +#include #include #include -#include -#include -#include +#include +#include #include -#include #include +#include #include #include #include #include -namespace xrpl { -namespace test { +namespace xrpl::test { using namespace jtx; @@ -641,7 +658,7 @@ class PermissionedDEX_test : public beast::unit_test::suite PermissionedDEX(env); // Fund devin and create USD trustline - Account badDomainOwner("badDomainOwner"); + Account const badDomainOwner("badDomainOwner"); Account const devin("devin"); env.fund(XRP(1000), badDomainOwner, devin); env.close(); @@ -1163,7 +1180,7 @@ class PermissionedDEX_test : public beast::unit_test::suite PermissionedDEX(env); // Fund accounts - Account badDomainOwner("badDomainOwner"); + Account const badDomainOwner("badDomainOwner"); Account const devin("devin"); env.fund(XRP(1000), badDomainOwner, devin); env.close(); @@ -1372,6 +1389,73 @@ class PermissionedDEX_test : public beast::unit_test::suite BEAST_EXPECT(!offerExists(env, bob, carolOfferSeq)); } + void + testHybridMalformedOffer(FeatureBitset features) + { + bool const fixS313Enabled = features[fixSecurity3_1_3]; + + testcase << "Hybrid offer with empty AdditionalBooks" + << (fixS313Enabled ? " (fixSecurity3_1_3 enabled)" + : " (fixSecurity3_1_3 disabled)"); + + // offerInDomain has two code paths gated by fixSecurity3_1_3: + // + // pre-fix: only rejects a hybrid offer when sfAdditionalBooks is + // entirely absent — an empty array (size 0) passes through. + // post-fix: also rejects a hybrid offer whose sfAdditionalBooks array + // has size != 1 (i.e. 0 or >1 entries). + // + // We create a valid hybrid offer, then directly manipulate its SLE to + // produce the size==0 case that cannot occur via normal transactions, + // and verify that the two code paths produce the expected outcomes. + // + // Note: the PermissionedDEX invariant checker (ValidPermissionedDEX) + // does not flag this malformation for ttPAYMENT — only for + // ttOFFER_CREATE — so the without-fix payment completes as tesSUCCESS. + + Env env(*this, features); + auto const& [gw, domainOwner, alice, bob, carol, USD, domainID, credType] = + PermissionedDEX(env); + + // Create a valid hybrid offer (sfAdditionalBooks has exactly 1 entry) + auto const bobOfferSeq{env.seq(bob)}; + env(offer(bob, XRP(10), USD(10)), txflags(tfHybrid), domain(domainID)); + env.close(); + BEAST_EXPECT(offerExists(env, bob, bobOfferSeq)); + + // Directly manipulate the offer SLE in the open ledger so that + // sfAdditionalBooks is present but empty (size 0). This is the + // malformed state that fixSecurity3_1_3 is designed to catch. + auto const offerKey = keylet::offer(bob.id(), bobOfferSeq); + env.app().getOpenLedger().modify([&offerKey](OpenView& view, beast::Journal) { + auto const sle = view.read(offerKey); + if (!sle) + return false; + auto replacement = std::make_shared(*sle, sle->key()); + replacement->setFieldArray(sfAdditionalBooks, STArray{}); + view.rawReplace(replacement); + return true; + }); + + if (fixS313Enabled) + { + // post-fixSecurity3_1_3: offerInDomain rejects the malformed + // offer (size == 0), so no valid domain offer is found. + env(pay(alice, carol, USD(10)), + path(~USD), + sendmax(XRP(10)), + domain(domainID), + ter(tecPATH_PARTIAL)); + } + else + { + // pre-fixSecurity3_1_3: offerInDomain only checks for a missing + // sfAdditionalBooks field; size == 0 passes through, so the + // malformed offer is crossed and the payment succeeds. + env(pay(alice, carol, USD(10)), path(~USD), sendmax(XRP(10)), domain(domainID)); + } + } + public: void run() override @@ -1393,10 +1477,11 @@ public: testHybridBookStep(all); testHybridInvalidOffer(all); testHybridOfferDirectories(all); + testHybridMalformedOffer(all); + testHybridMalformedOffer(all - fixSecurity3_1_3); } }; BEAST_DEFINE_TESTSUITE(PermissionedDEX, app, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/app/PermissionedDomains_test.cpp b/src/test/app/PermissionedDomains_test.cpp index c4bde9831a..f94fc53f71 100644 --- a/src/test/app/PermissionedDomains_test.cpp +++ b/src/test/app/PermissionedDomains_test.cpp @@ -1,18 +1,35 @@ -#include +#include +#include +#include +#include +#include // IWYU pragma: keep +#include +#include +#include +#include +#include + +#include +#include +#include #include +#include +#include +#include #include -#include +#include #include #include #include #include +#include +#include #include #include -namespace xrpl { -namespace test { +namespace xrpl::test { using namespace jtx; @@ -531,5 +548,4 @@ public: BEAST_DEFINE_TESTSUITE(PermissionedDomains, app, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/app/PseudoTx_test.cpp b/src/test/app/PseudoTx_test.cpp index a9180d0e03..22c0cff075 100644 --- a/src/test/app/PseudoTx_test.cpp +++ b/src/test/app/PseudoTx_test.cpp @@ -1,13 +1,24 @@ -#include +#include + +#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include #include +#include #include #include -namespace xrpl { -namespace test { +namespace xrpl::test { struct PseudoTx_test : public beast::unit_test::suite { @@ -16,7 +27,7 @@ struct PseudoTx_test : public beast::unit_test::suite { std::vector res; - res.emplace_back(STTx(ttFEE, [&](auto& obj) { + res.emplace_back(ttFEE, [&](auto& obj) { obj[sfAccount] = AccountID(); obj[sfLedgerSequence] = seq; if (rules.enabled(featureXRPFees)) @@ -32,13 +43,13 @@ struct PseudoTx_test : public beast::unit_test::suite obj[sfReserveIncrement] = 0; obj[sfReferenceFeeUnits] = 0; } - })); + }); - res.emplace_back(STTx(ttAMENDMENT, [&](auto& obj) { + res.emplace_back(ttAMENDMENT, [&](auto& obj) { obj.setAccountID(sfAccount, AccountID()); obj.setFieldH256(sfAmendment, uint256(2)); obj.setFieldU32(sfLedgerSequence, seq); - })); + }); return res; } @@ -48,12 +59,12 @@ struct PseudoTx_test : public beast::unit_test::suite { std::vector res; - res.emplace_back(STTx(ttACCOUNT_SET, [&](auto& obj) { obj[sfAccount] = AccountID(1); })); + res.emplace_back(ttACCOUNT_SET, [&](auto& obj) { obj[sfAccount] = AccountID(1); }); - res.emplace_back(STTx(ttPAYMENT, [&](auto& obj) { + res.emplace_back(ttPAYMENT, [&](auto& obj) { obj.setAccountID(sfAccount, AccountID(2)); obj.setAccountID(sfDestination, AccountID(3)); - })); + }); return res; } @@ -104,5 +115,4 @@ struct PseudoTx_test : public beast::unit_test::suite BEAST_DEFINE_TESTSUITE(PseudoTx, app, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/app/RCLValidations_test.cpp b/src/test/app/RCLValidations_test.cpp index 32267cbf45..164ded6138 100644 --- a/src/test/app/RCLValidations_test.cpp +++ b/src/test/app/RCLValidations_test.cpp @@ -1,13 +1,25 @@ -#include + +#include #include +#include +#include #include -#include +#include +#include #include +#include +#include +#include +#include +#include +#include -namespace xrpl { -namespace test { +#include +#include + +namespace xrpl::test { class RCLValidations_test : public beast::unit_test::suite { @@ -312,5 +324,4 @@ public: BEAST_DEFINE_TESTSUITE(RCLValidations, app, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/app/ReducedOffer_test.cpp b/src/test/app/ReducedOffer_test.cpp index b19999fecd..a6d7160071 100644 --- a/src/test/app/ReducedOffer_test.cpp +++ b/src/test/app/ReducedOffer_test.cpp @@ -1,13 +1,30 @@ -#include +#include +#include +#include +#include // IWYU pragma: keep +#include +#include +#include +#include +#include +#include + +#include +#include +#include #include #include +#include +#include +#include #include +#include #include +#include -namespace xrpl { -namespace test { +namespace xrpl::test { class ReducedOffer_test : public beast::unit_test::suite { @@ -150,7 +167,7 @@ public: }; // bob's offer (the new offer) is the same every time: - Amounts const bobOffer{STAmount(XRP(1)), STAmount(USD.issue(), 1, 0)}; + Amounts const bobOffer{STAmount(XRP(1)), STAmount(USD, 1, 0)}; // alice's offer has a slightly smaller TakerPays with each // iteration. This should mean that the size of the offer bob @@ -161,10 +178,10 @@ public: mantissaReduce += 20'000'000ull) { STAmount const aliceUSD{ - bobOffer.out.issue(), + bobOffer.out.asset(), bobOffer.out.mantissa() - mantissaReduce, bobOffer.out.exponent()}; - STAmount const aliceXRP{bobOffer.in.issue(), bobOffer.in.mantissa() - 1}; + STAmount const aliceXRP{bobOffer.in.asset(), bobOffer.in.mantissa() - 1}; Amounts const aliceOffer{aliceUSD, aliceXRP}; blockedCount += exerciseOfferPair(aliceOffer, bobOffer); } @@ -282,7 +299,7 @@ public: }; // alice's offer (the old offer) is the same every time: - Amounts const aliceOffer{STAmount(XRP(1)), STAmount(USD.issue(), 1, 0)}; + Amounts const aliceOffer{STAmount(XRP(1)), STAmount(USD, 1, 0)}; // bob's offer has a slightly smaller TakerPays with each iteration. // This should mean that the size of the offer alice leaves in the @@ -293,10 +310,10 @@ public: mantissaReduce += 20'000'000ull) { STAmount const bobUSD{ - aliceOffer.out.issue(), + aliceOffer.out.asset(), aliceOffer.out.mantissa() - mantissaReduce, aliceOffer.out.exponent()}; - STAmount const bobXRP{aliceOffer.in.issue(), aliceOffer.in.mantissa() - 1}; + STAmount const bobXRP{aliceOffer.in.asset(), aliceOffer.in.mantissa() - 1}; Amounts const bobOffer{bobUSD, bobXRP}; blockedCount += exerciseOfferPair(aliceOffer, bobOffer); @@ -407,7 +424,7 @@ public: auto const USD = gw["USD"]; auto const EUR = gw["EUR"]; - STAmount const tinyUSD(USD.issue(), /*mantissa*/ 1, /*exponent*/ -81); + STAmount const tinyUSD(USD, /*mantissa*/ 1, /*exponent*/ -81); { Env env{*this, testable_amendments()}; @@ -417,10 +434,10 @@ public: env.trust(USD(1000), alice, bob); env.trust(EUR(1000), alice, bob); - STAmount const eurOffer(EUR.issue(), /*mantissa*/ 2957, /*exponent*/ -76); - STAmount const usdOffer(USD.issue(), /*mantissa*/ 7109, /*exponent*/ -76); + STAmount const eurOffer(EUR, /*mantissa*/ 2957, /*exponent*/ -76); + STAmount const usdOffer(USD, /*mantissa*/ 7109, /*exponent*/ -76); - STAmount const endLoop(USD.issue(), /*mantissa*/ 50, /*exponent*/ -81); + STAmount const endLoop(USD, /*mantissa*/ 50, /*exponent*/ -81); int blockedOrderBookCount = 0; for (STAmount initialBobUSD = tinyUSD; initialBobUSD <= endLoop; @@ -595,7 +612,7 @@ public: if (badRate == 0) { STAmount const tweakedTakerGets( - aliceReducedOffer.in.issue(), + aliceReducedOffer.in.asset(), aliceReducedOffer.in.mantissa() + 1, aliceReducedOffer.in.exponent(), aliceReducedOffer.in.negative()); @@ -629,7 +646,7 @@ public: unsigned int blockedCount = 0; { STAmount increaseGets = USD(0); - STAmount const step(increaseGets.issue(), 1, -8); + STAmount const step(increaseGets.asset(), 1, -8); for (unsigned int i = 0; i < loopCount; ++i) { blockedCount += @@ -667,5 +684,4 @@ public: BEAST_DEFINE_TESTSUITE_PRIO(ReducedOffer, app, xrpl, 2); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/app/Regression_test.cpp b/src/test/app/Regression_test.cpp index 59ab0e427d..c38013d1f3 100644 --- a/src/test/app/Regression_test.cpp +++ b/src/test/app/Regression_test.cpp @@ -1,18 +1,57 @@ -#include +#include +#include +#include +#include +#include // IWYU pragma: keep #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include +#include #include +#include +#include #include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include #include #include -namespace xrpl { -namespace test { +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl::test { struct Regression_test : public beast::unit_test::suite { @@ -309,5 +348,4 @@ struct Regression_test : public beast::unit_test::suite BEAST_DEFINE_TESTSUITE(Regression, app, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/app/SHAMapStore_test.cpp b/src/test/app/SHAMapStore_test.cpp index 73cbf1c617..75e4c0c721 100644 --- a/src/test/app/SHAMapStore_test.cpp +++ b/src/test/app/SHAMapStore_test.cpp @@ -1,17 +1,38 @@ -#include +#include +#include #include #include #include #include #include +#include #include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include #include -namespace xrpl { -namespace test { +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl::test { class SHAMapStore_test : public beast::unit_test::suite { @@ -157,10 +178,10 @@ public: auto ledgerTmp = env.rpc("ledger", "0"); BEAST_EXPECT(bad(ledgerTmp)); - ledgers.emplace(std::make_pair(1, env.rpc("ledger", "1"))); + ledgers.emplace(1, env.rpc("ledger", "1")); BEAST_EXPECT(goodLedger(env, ledgers[1], "1")); - ledgers.emplace(std::make_pair(2, env.rpc("ledger", "2"))); + ledgers.emplace(2, env.rpc("ledger", "2")); BEAST_EXPECT(goodLedger(env, ledgers[2], "2")); ledgerTmp = env.rpc("ledger", "current"); @@ -187,7 +208,7 @@ public: for (auto i = 3; i < deleteInterval + lastRotated; ++i) { - ledgers.emplace(std::make_pair(i, env.rpc("ledger", std::to_string(i)))); + ledgers.emplace(i, env.rpc("ledger", std::to_string(i))); BEAST_EXPECT( goodLedger(env, ledgers[i], std::to_string(i), true) && !getHash(ledgers[i]).empty()); @@ -224,7 +245,7 @@ public: ledgerTmp = env.rpc("ledger", "current"); BEAST_EXPECT(goodLedger(env, ledgerTmp, std::to_string(i + 3))); - ledgers.emplace(std::make_pair(i, env.rpc("ledger", std::to_string(i)))); + ledgers.emplace(i, env.rpc("ledger", std::to_string(i))); BEAST_EXPECT( store.getLastRotated() == lastRotated || i == lastRotated + deleteInterval - 2); BEAST_EXPECT( @@ -576,5 +597,4 @@ public: // VFALCO This test fails because of thread asynchronous issues BEAST_DEFINE_TESTSUITE(SHAMapStore, app, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/app/SetAuth_test.cpp b/src/test/app/SetAuth_test.cpp index a38cf7175a..5982fc282d 100644 --- a/src/test/app/SetAuth_test.cpp +++ b/src/test/app/SetAuth_test.cpp @@ -1,10 +1,24 @@ -#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include #include +#include +#include +#include +#include #include -namespace xrpl { -namespace test { +#include + +namespace xrpl::test { struct SetAuth_test : public beast::unit_test::suite { @@ -59,5 +73,4 @@ struct SetAuth_test : public beast::unit_test::suite BEAST_DEFINE_TESTSUITE(SetAuth, app, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/app/SetRegularKey_test.cpp b/src/test/app/SetRegularKey_test.cpp index 3cce01a112..af79bb75bc 100644 --- a/src/test/app/SetRegularKey_test.cpp +++ b/src/test/app/SetRegularKey_test.cpp @@ -1,6 +1,25 @@ -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include namespace xrpl { diff --git a/src/test/app/TheoreticalQuality_test.cpp b/src/test/app/TheoreticalQuality_test.cpp index d7e0882c3b..2316b3249f 100644 --- a/src/test/app/TheoreticalQuality_test.cpp +++ b/src/test/app/TheoreticalQuality_test.cpp @@ -1,18 +1,50 @@ -#include -#include -#include -#include +#include +#include +#include +#include // IWYU pragma: keep +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include #include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include -#include #include #include #include -namespace xrpl { -namespace test { +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl::test { struct RippleCalcTestParams { @@ -223,9 +255,9 @@ class TheoreticalQuality_test : public beast::unit_test::suite PaymentSandbox const sb(closed.get(), tapNONE); AMMContext ammContext(rcp.srcAccount, false); - auto const sendMaxIssue = [&rcp]() -> std::optional { + auto const sendMaxIssue = [&rcp]() -> std::optional { if (rcp.sendMax) - return rcp.sendMax->issue(); + return rcp.sendMax->asset(); return std::nullopt; }(); @@ -235,7 +267,7 @@ class TheoreticalQuality_test : public beast::unit_test::suite sb, rcp.srcAccount, rcp.dstAccount, - rcp.dstAmt.issue(), + rcp.dstAmt.asset(), /*limitQuality*/ std::nullopt, sendMaxIssue, rcp.paths, @@ -511,5 +543,4 @@ public: BEAST_DEFINE_TESTSUITE_PRIO(TheoreticalQuality, app, xrpl, 3); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/app/Ticket_test.cpp b/src/test/app/Ticket_test.cpp index 7f96caa05f..0dc9f0155f 100644 --- a/src/test/app/Ticket_test.cpp +++ b/src/test/app/Ticket_test.cpp @@ -1,10 +1,48 @@ -#include + +#include +#include +#include +#include // IWYU pragma: keep +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include + namespace xrpl { class Ticket_test : public beast::unit_test::suite @@ -190,8 +228,8 @@ class Ticket_test : public beast::unit_test::suite // Verify that all the expected Tickets were created. BEAST_EXPECT(ticketSeqs.size() == count); - std::sort(ticketSeqs.begin(), ticketSeqs.end()); - BEAST_EXPECT(std::adjacent_find(ticketSeqs.begin(), ticketSeqs.end()) == ticketSeqs.end()); + std::ranges::sort(ticketSeqs); + BEAST_EXPECT(std::ranges::adjacent_find(ticketSeqs) == ticketSeqs.end()); BEAST_EXPECT(*ticketSeqs.rbegin() == acctRootFinalSeq - 1); } diff --git a/src/test/app/Transaction_ordering_test.cpp b/src/test/app/Transaction_ordering_test.cpp index c50fbf4e56..af5001b06e 100644 --- a/src/test/app/Transaction_ordering_test.cpp +++ b/src/test/app/Transaction_ordering_test.cpp @@ -1,9 +1,24 @@ -#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include #include +#include -namespace xrpl { -namespace test { +#include +#include + +namespace xrpl::test { struct Transaction_ordering_test : public beast::unit_test::suite { @@ -134,5 +149,4 @@ struct Transaction_ordering_test : public beast::unit_test::suite BEAST_DEFINE_TESTSUITE(Transaction_ordering, app, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/app/TrustAndBalance_test.cpp b/src/test/app/TrustAndBalance_test.cpp index a71ddb140e..e6cc7e71ca 100644 --- a/src/test/app/TrustAndBalance_test.cpp +++ b/src/test/app/TrustAndBalance_test.cpp @@ -1,11 +1,26 @@ -#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include -#include +#include +#include +#include #include #include +#include #include +#include + namespace xrpl { class TrustAndBalance_test : public beast::unit_test::suite @@ -358,8 +373,7 @@ class TrustAndBalance_test : public beast::unit_test::suite { env.require(balance( alice, - STAmount( - carol["USD"].issue(), 6500000000000000ull, -14, true, STAmount::unchecked{}))); + STAmount(carol["USD"], 6500000000000000ull, -14, true, STAmount::unchecked{}))); env.require(balance(carol, gw["USD"](35))); } else diff --git a/src/test/app/TrustSet_test.cpp b/src/test/app/TrustSet_test.cpp index d359ba1f26..6d24b695c7 100644 --- a/src/test/app/TrustSet_test.cpp +++ b/src/test/app/TrustSet_test.cpp @@ -1,11 +1,30 @@ -#include +#include +#include +#include +#include // IWYU pragma: keep +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include #include +#include #include -namespace xrpl { +#include +#include -namespace test { +namespace xrpl::test { class TrustSet_test : public beast::unit_test::suite { @@ -600,5 +619,4 @@ public: } }; BEAST_DEFINE_TESTSUITE(TrustSet, app, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/app/TxQ_test.cpp b/src/test/app/TxQ_test.cpp index 6f13f9d419..248c8121f4 100644 --- a/src/test/app/TxQ_test.cpp +++ b/src/test/app/TxQ_test.cpp @@ -1,22 +1,61 @@ -#include +#include +#include +#include #include -#include #include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include #include #include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include #include -#include #include #include +#include -namespace xrpl { +#include +#include +#include +#include +#include +#include +#include +#include +#include -namespace test { +namespace xrpl::test { class TxQPosNegFlows_test : public beast::unit_test::suite { @@ -4654,5 +4693,4 @@ class TxQMetaInfo_test : public TxQPosNegFlows_test BEAST_DEFINE_TESTSUITE_PRIO(TxQPosNegFlows, app, xrpl, 1); BEAST_DEFINE_TESTSUITE_PRIO(TxQMetaInfo, app, xrpl, 1); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/app/ValidatorKeys_test.cpp b/src/test/app/ValidatorKeys_test.cpp index 3be6fcd028..bbc77671bc 100644 --- a/src/test/app/ValidatorKeys_test.cpp +++ b/src/test/app/ValidatorKeys_test.cpp @@ -1,4 +1,5 @@ #include +#include #include #include @@ -6,13 +7,20 @@ #include #include -#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include +#include -namespace xrpl { -namespace test { +namespace xrpl::test { class ValidatorKeys_test : public beast::unit_test::suite { @@ -167,5 +175,4 @@ public: BEAST_DEFINE_TESTSUITE(ValidatorKeys, app, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/app/ValidatorList_test.cpp b/src/test/app/ValidatorList_test.cpp index 74b208e5e2..63c2ca37d8 100644 --- a/src/test/app/ValidatorList_test.cpp +++ b/src/test/app/ValidatorList_test.cpp @@ -1,23 +1,55 @@ -#include + +#include +#include #include +#include #include #include +#include #include +#include #include +#include +#include #include +#include #include +#include +#include #include +#include #include +#include #include #include -#include +#include +#include +#include #include +#include -namespace xrpl { -namespace test { +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl::test { class ValidatorList_test : public beast::unit_test::suite { @@ -92,9 +124,9 @@ private: auto const masterPublic = derivePublicKey(KeyType::ed25519, secret); auto const signingKeys = randomKeyPair(KeyType::secp256k1); return { - masterPublic, - signingKeys.first, - base64_encode(makeManifestString( + .masterPublic = masterPublic, + .signingPublic = signingKeys.first, + .manifest = base64_encode(makeManifestString( masterPublic, secret, signingKeys.first, signingKeys.second, 1))}; } @@ -1866,11 +1898,11 @@ private: auto const sig2 = signList(blob2, pubSigningKeys); return PreparedList{ - publisherPublic, - manifest, - {{blob1, sig1, {}}, {blob2, sig2, {}}}, - version, - {expiration1, expiration2}}; + .publisherPublic = publisherPublic, + .manifest = manifest, + .blobs = {{blob1, sig1, {}}, {blob2, sig2, {}}}, + .version = version, + .expirations = {expiration1, expiration2}}; }; // Configure two publishers and prepare 2 lists @@ -3917,5 +3949,4 @@ public: BEAST_DEFINE_TESTSUITE(ValidatorList, app, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/app/ValidatorSite_test.cpp b/src/test/app/ValidatorSite_test.cpp index 004c59609c..f652689b2b 100644 --- a/src/test/app/ValidatorSite_test.cpp +++ b/src/test/app/ValidatorSite_test.cpp @@ -1,21 +1,34 @@ -#include +#include #include +#include #include +#include #include +#include #include -#include +#include +#include +#include +#include #include #include -#include -#include +#include +#include #include #include #include +#include +#include +#include +#include +#include +#include +#include namespace xrpl { namespace detail { @@ -65,7 +78,7 @@ private: "http://207.261.33.37:8080/validators", "https://ripple.com/validators", "https://ripple.com:443/validators", - "file:///etc/opt/ripple/validators.txt", + "file:///etc/opt/xrpld/validators.txt", "file:///C:/Lib/validators.txt" #if !_MSC_VER , @@ -164,7 +177,7 @@ private: for (auto const& cfg : paths) { - servers.push_back(cfg); + servers.emplace_back(cfg); auto& item = servers.back(); item.isRetry = cfg.path == "/bad-resource"; item.list.reserve(listSize); diff --git a/src/test/app/Vault_test.cpp b/src/test/app/Vault_test.cpp index 823cf7aafd..0e6b680ff3 100644 --- a/src/test/app/Vault_test.cpp +++ b/src/test/app/Vault_test.cpp @@ -1,14 +1,37 @@ -#include +#include #include +#include #include +#include #include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include #include #include +#include +#include #include #include +#include +#include #include #include #include @@ -17,16 +40,27 @@ #include #include #include +#include #include #include #include #include +#include #include #include +#include +#include #include #include +#include +#include +#include +#include #include +#include +#include +#include namespace xrpl { @@ -43,11 +77,11 @@ class Vault_test : public beast::unit_test::suite testSequences() { using namespace test::jtx; - Account issuer{"issuer"}; - Account owner{"owner"}; - Account depositor{"depositor"}; - Account charlie{"charlie"}; // authorized 3rd party - Account dave{"dave"}; + Account const issuer{"issuer"}; + Account const owner{"owner"}; + Account const depositor{"depositor"}; + Account const charlie{"charlie"}; // authorized 3rd party + Account const dave{"dave"}; auto const testSequence = [&, this]( std::string const& prefix, @@ -1329,10 +1363,10 @@ class Vault_test : public beast::unit_test::suite return defXRP; return a + XRP(1000); } - auto defIOU = STAmount{a.issue(), 30000}; + auto defIOU = STAmount{a.asset(), 30000}; if (a <= defIOU) return defIOU; - return a + STAmount{a.issue(), 1000}; + return a + STAmount{a.asset(), 1000}; }; auto const toFund1 = toFund(asset1); auto const toFund2 = toFund(asset2); @@ -1549,9 +1583,9 @@ class Vault_test : public beast::unit_test::suite MPTTester& mptt)> test, CaseArgs args = {}) { Env env{*this, testable_amendments() | featureSingleAssetVault}; - Account issuer{"issuer"}; - Account owner{"owner"}; - Account depositor{"depositor"}; + Account const issuer{"issuer"}; + Account const owner{"owner"}; + Account const depositor{"depositor"}; env.fund(XRP(args.initialXRP), issuer, owner, depositor); env.close(); Vault vault{env}; @@ -2173,8 +2207,8 @@ class Vault_test : public beast::unit_test::suite testcase("MPT shares to a vault"); Env env{*this, testable_amendments() | featureSingleAssetVault}; - Account owner{"owner"}; - Account issuer{"issuer"}; + Account const owner{"owner"}; + Account const issuer{"issuer"}; env.fund(XRP(1000000), owner, issuer); env.close(); Vault const vault{env}; @@ -2241,6 +2275,43 @@ class Vault_test : public beast::unit_test::suite // Delete vault with zero balance env(vault.del({.owner = owner, .id = keylet.key})); }); + + { + testcase("MPT OutstandingAmount > MaximumAmount"); + + Env env{*this, testable_amendments() | featureSingleAssetVault}; + Account const alice{"alice"}; + Account const issuer{"issuer"}; + env.fund(XRP(1'000), alice, issuer); + env.close(); + Vault const vault{env}; + + MPTTester const BTC({.env = env, .issuer = issuer, .holders = {alice}, .maxAmt = 100}); + + auto [tx, k] = vault.create({.owner = issuer, .asset = BTC}); + env(tx); + env.close(); + + tx = vault.deposit({.depositor = issuer, .id = k.key, .amount = BTC(110)}); + // accountHolds is the first check and the issuer has only BTC(100) + // available + env(tx, ter{tecINSUFFICIENT_FUNDS}); + env.close(); + + // OutstandingAmount == MaximumAmount + env(pay(issuer, alice, BTC(100))); + env.close(); + + tx = vault.deposit({.depositor = issuer, .id = k.key, .amount = BTC(100)}); + // the issuer has BTC(0) available + env(tx, ter{tecINSUFFICIENT_FUNDS}); + env.close(); + + tx = vault.deposit({.depositor = alice, .id = k.key, .amount = BTC(100)}); + // alice transfers BTC(100), OutstandingAmount is 100 + env(tx); + env.close(); + } } void @@ -3370,10 +3441,10 @@ class Vault_test : public beast::unit_test::suite [&](OpenView& view, beast::Journal j) -> bool { Sandbox sb(&view, tapNONE); auto vault = sb.peek(keylet::vault(keylet.key)); - if (!BEAST_EXPECT(vault != nullptr)) + if (!BEAST_EXPECT(vault)) return false; auto shares = sb.peek(keylet::mptIssuance(vault->at(sfShareMPTID))); - if (!BEAST_EXPECT(shares != nullptr)) + if (!BEAST_EXPECT(shares)) return false; if (fn(*vault, *shares)) { @@ -4102,6 +4173,66 @@ class Vault_test : public beast::unit_test::suite BEAST_EXPECT(env.balance(d.vaultAccount, d.shares).number() == 0); } }); + + // Non-1:1 ratio (scale=1, 10:1 shares:assets) with an outstanding loan. + // Deposit 100 IOU → 1000 shares. Borrow 40 → assetsAvailable=60. + // Clawback 80 IOU → clamped to 60, then share math uses truncation. + testCase(1, [&, this](Env& env, Data d) { + using namespace loanBroker; + using namespace loan; + + testcase("Scale clawback clamped with outstanding loan"); + + auto tx = d.vault.deposit( + {.depositor = d.depositor, + .id = d.keylet.key, + .amount = STAmount(d.asset, Number(100, 0))}); + env(tx); + env.close(); + BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(1000)); + + // Create a loan broker backed by this vault + auto const brokerKeylet = keylet::loanbroker(d.owner.id(), env.seq(d.owner)); + env(set(d.owner, d.keylet.key)); + env.close(); + + // Borrow 40: assetsAvailable=60, assetsTotal=100 + env(set(d.depositor, brokerKeylet.key, STAmount(d.asset, Number(40, 0))), + loan::interestRate(TenthBips32(0)), + gracePeriod(60), + paymentInterval(120), + paymentTotal(10), + sig(sfCounterpartySignature, d.owner), + fee(env.current()->fees().base * 2), + ter(tesSUCCESS)); + env.close(); + + { + auto const sle = env.le(d.keylet); + BEAST_EXPECT(sle->at(sfAssetsAvailable) == STAmount(d.asset, Number(60, 0))); + BEAST_EXPECT(sle->at(sfAssetsTotal) == STAmount(d.asset, Number(100, 0))); + } + + // Request 80 IOU clawback — clamped to assetsAvailable (60) + // With scale=1 (10:1), 60 assets = 600 shares destroyed + tx = d.vault.clawback( + {.issuer = d.issuer, + .id = d.keylet.key, + .holder = d.depositor, + .amount = STAmount(d.asset, Number(80, 0))}); + env(tx, ter(tesSUCCESS)); + env.close(); + + { + auto const sle = env.le(d.keylet); + BEAST_EXPECT(sle != nullptr); + BEAST_EXPECT(sle->at(sfAssetsAvailable) == STAmount(d.asset, Number(0, 0))); + BEAST_EXPECT(sle->at(sfAssetsTotal) == STAmount(d.asset, Number(40, 0))); + + // 600 of 1000 shares destroyed, 400 remain + BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(400)); + } + }); } void @@ -4648,8 +4779,7 @@ class Vault_test : public beast::unit_test::suite "VaultClawback (share) - " + prefix + " owner incomplete share clawback fails"); auto [vault, vaultKeylet] = setupVault(asset, owner, depositor); auto const& vaultSle = env.le(vaultKeylet); - BEAST_EXPECT(vaultSle != nullptr); - if (!vaultSle) + if (!BEAST_EXPECT(vaultSle)) return; Asset const share = vaultSle->at(sfShareMPTID); env(vault.clawback({ @@ -4684,8 +4814,7 @@ class Vault_test : public beast::unit_test::suite " owner explicit complete share clawback succeeds"); auto [vault, vaultKeylet] = setupVault(asset, owner, depositor); auto const& vaultSle = env.le(vaultKeylet); - BEAST_EXPECT(vaultSle != nullptr); - if (!vaultSle) + if (!BEAST_EXPECT(vaultSle)) return; Asset const share = vaultSle->at(sfShareMPTID); env(vault.clawback({ @@ -4701,8 +4830,7 @@ class Vault_test : public beast::unit_test::suite testcase("VaultClawback (share) - " + prefix + " owner can clawback own shares"); auto [vault, vaultKeylet] = setupVault(asset, owner, owner); auto const& vaultSle = env.le(vaultKeylet); - BEAST_EXPECT(vaultSle != nullptr); - if (!vaultSle) + if (!BEAST_EXPECT(vaultSle)) return; Asset const share = vaultSle->at(sfShareMPTID); env(vault.clawback({ @@ -4719,7 +4847,7 @@ class Vault_test : public beast::unit_test::suite testcase("VaultClawback (share) - " + prefix + " empty vault share clawback fails"); auto [vault, vaultKeylet] = setupVault(asset, owner, owner); auto const& vaultSle = env.le(vaultKeylet); - if (BEAST_EXPECT(vaultSle != nullptr)) + if (!BEAST_EXPECT(vaultSle)) return; Asset const share = vaultSle->at(sfShareMPTID); env(vault.clawback({ @@ -4735,14 +4863,15 @@ class Vault_test : public beast::unit_test::suite .issuer = owner, .id = vaultKeylet.key, .holder = owner, + .amount = share(vaultShareBalance(vaultKeylet)).value(), }), ter(tecNO_PERMISSION)); env.close(); } }; - Account owner{"alice"}; - Account depositor{"bob"}; + Account const owner{"alice"}; + Account const depositor{"bob"}; Account const issuer{"issuer"}; env.fund(XRP(10000), issuer, owner, depositor); @@ -4786,6 +4915,7 @@ class Vault_test : public beast::unit_test::suite using namespace loanBroker; using namespace loan; Env env(*this); + env.enableFeature(fixSecurity3_1_3); auto const setupVault = [&](PrettyAsset const& asset, Account const& owner, @@ -4799,6 +4929,7 @@ class Vault_test : public beast::unit_test::suite auto const& vaultSle = env.le(vaultKeylet); BEAST_EXPECT(vaultSle != nullptr); + env.memoize(Account("vault", vaultSle->at(sfAccount))); env(vault.deposit( {.depositor = depositor, .id = vaultKeylet.key, .amount = asset(100)}), ter(tesSUCCESS)); @@ -4899,8 +5030,7 @@ class Vault_test : public beast::unit_test::suite testcase("VaultClawback (asset) - " + prefix + " issuer share clawback fails"); auto [vault, vaultKeylet] = setupVault(asset, owner, depositor, issuer); auto const& vaultSle = env.le(vaultKeylet); - BEAST_EXPECT(vaultSle != nullptr); - if (!vaultSle) + if (!BEAST_EXPECT(vaultSle)) return; Asset const share = vaultSle->at(sfShareMPTID); @@ -4955,10 +5085,292 @@ class Vault_test : public beast::unit_test::suite }), ter(tesSUCCESS)); } + + { + testcase( + "VaultClawback (asset) - " + prefix + + " zero-amount clawback clamped with outstanding loan"); + auto [vault, vaultKeylet] = setupVault(asset, owner, depositor, issuer); + + auto const vaultSle = env.le(vaultKeylet); + if (!BEAST_EXPECT(vaultSle)) + return; + + PrettyAsset const shares = MPTIssue(vaultSle->at(sfShareMPTID)); + + // Create a loan broker backed by this vault + auto const brokerKeylet = keylet::loanbroker(owner.id(), env.seq(owner)); + env(set(owner, vaultKeylet.key)); + env.close(); + + // Depositor borrows 40 units, reducing assetsAvailable to 60 + // while assetsTotal stays at 100 + env(set(depositor, brokerKeylet.key, asset(40).value()), + loan::interestRate(TenthBips32(0)), + gracePeriod(60), + paymentInterval(120), + paymentTotal(10), + sig(sfCounterpartySignature, owner), + fee(env.current()->fees().base * 2), + ter(tesSUCCESS)); + env.close(); + + { + auto const sle = env.le(vaultKeylet); + BEAST_EXPECT(sle->at(sfAssetsAvailable) == asset(60).value()); + BEAST_EXPECT(sle->at(sfAssetsTotal) == asset(100).value()); + } + + // Zero-amount clawback (= "clawback all") should succeed, + // clamped to assetsAvailable (60) rather than the full + // share value (100). + env(vault.clawback({ + .issuer = issuer, + .id = vaultKeylet.key, + .holder = depositor, + }), + ter(tesSUCCESS)); + env.close(); + + // Only 60 assets clawed back; loan's 40 still outstanding + { + auto const sle = env.le(vaultKeylet); + BEAST_EXPECT(sle != nullptr); + BEAST_EXPECT(sle->at(sfAssetsAvailable) == asset(0).value()); + BEAST_EXPECT(sle->at(sfAssetsTotal) == asset(40).value()); + + // 60 of 100 shares destroyed (1:1 ratio), 40 remain + auto const sharesAfter = env.balance(depositor, shares); + BEAST_EXPECT(sharesAfter == shares(Number{4, sle->at(sfScale) + 1})); + } + } + + { + testcase( + "VaultClawback (asset) - " + prefix + + " non-zero clawback clamped with outstanding loan"); + auto [vault, vaultKeylet] = setupVault(asset, owner, depositor, issuer); + + auto const vaultSle = env.le(vaultKeylet); + if (!BEAST_EXPECT(vaultSle)) + return; + PrettyAsset const shares = MPTIssue(vaultSle->at(sfShareMPTID)); + + // Create a loan broker backed by this vault + auto const brokerKeylet = keylet::loanbroker(owner.id(), env.seq(owner)); + env(set(owner, vaultKeylet.key)); + env.close(); + + // Depositor borrows 40 units + env(set(depositor, brokerKeylet.key, asset(40).value()), + loan::interestRate(TenthBips32(0)), + gracePeriod(60), + paymentInterval(120), + paymentTotal(10), + sig(sfCounterpartySignature, owner), + fee(env.current()->fees().base * 2), + ter(tesSUCCESS)); + env.close(); + + { + auto const sle = env.le(vaultKeylet); + BEAST_EXPECT(sle->at(sfAssetsAvailable) == asset(60).value()); + BEAST_EXPECT(sle->at(sfAssetsTotal) == asset(100).value()); + } + + // Request 100 but only 60 available — clamped to 60 + env(vault.clawback({ + .issuer = issuer, + .id = vaultKeylet.key, + .holder = depositor, + .amount = asset(100).value(), + }), + ter(tesSUCCESS)); + env.close(); + + { + auto const sle = env.le(vaultKeylet); + BEAST_EXPECT(sle != nullptr); + BEAST_EXPECT(sle->at(sfAssetsAvailable) == asset(0).value()); + BEAST_EXPECT(sle->at(sfAssetsTotal) == asset(40).value()); + + // 60 of 100 shares destroyed (1:1 ratio), 40 remain + auto const sharesAfter = env.balance(depositor, shares); + BEAST_EXPECT(sharesAfter == shares(Number{4, sle->at(sfScale) + 1})); + } + } + + { + testcase( + "VaultClawback (asset) - " + prefix + + " partial clawback below available with outstanding loan"); + auto [vault, vaultKeylet] = setupVault(asset, owner, depositor, issuer); + + auto const vaultSle = env.le(vaultKeylet); + if (!BEAST_EXPECT(vaultSle)) + return; + PrettyAsset const shares = MPTIssue(vaultSle->at(sfShareMPTID)); + + // Create a loan broker backed by this vault + auto const brokerKeylet = keylet::loanbroker(owner.id(), env.seq(owner)); + env(set(owner, vaultKeylet.key)); + env.close(); + + // Depositor borrows 40 units: assetsAvailable=60, assetsTotal=100 + env(set(depositor, brokerKeylet.key, asset(40).value()), + loan::interestRate(TenthBips32(0)), + gracePeriod(60), + paymentInterval(120), + paymentTotal(10), + sig(sfCounterpartySignature, owner), + fee(env.current()->fees().base * 2), + ter(tesSUCCESS)); + env.close(); + + { + auto const sle = env.le(vaultKeylet); + BEAST_EXPECT(sle->at(sfAssetsAvailable) == asset(60).value()); + BEAST_EXPECT(sle->at(sfAssetsTotal) == asset(100).value()); + } + + // Clawback 30 — well under available (60), no clamping needed + env(vault.clawback({ + .issuer = issuer, + .id = vaultKeylet.key, + .holder = depositor, + .amount = asset(30).value(), + }), + ter(tesSUCCESS)); + env.close(); + + { + auto const sle = env.le(vaultKeylet); + BEAST_EXPECT(sle != nullptr); + BEAST_EXPECT(sle->at(sfAssetsAvailable) == asset(30).value()); + BEAST_EXPECT(sle->at(sfAssetsTotal) == asset(70).value()); + + // 30 of 100 shares destroyed (1:1 ratio), 70 remain + auto const sharesAfter = env.balance(depositor, shares); + BEAST_EXPECT(sharesAfter == shares(Number{7, sle->at(sfScale) + 1})); + } + } + + { + testcase( + "VaultClawback (asset) - " + prefix + + " clawback exactly equal to available with outstanding loan"); + auto [vault, vaultKeylet] = setupVault(asset, owner, depositor, issuer); + + auto const vaultSle = env.le(vaultKeylet); + if (!BEAST_EXPECT(vaultSle)) + return; + PrettyAsset const shares = MPTIssue(vaultSle->at(sfShareMPTID)); + + auto const brokerKeylet = keylet::loanbroker(owner.id(), env.seq(owner)); + env(set(owner, vaultKeylet.key)); + env.close(); + + // Depositor borrows 40 units: assetsAvailable=60, assetsTotal=100 + env(set(depositor, brokerKeylet.key, asset(40).value()), + loan::interestRate(TenthBips32(0)), + gracePeriod(60), + paymentInterval(120), + paymentTotal(10), + sig(sfCounterpartySignature, owner), + fee(env.current()->fees().base * 2), + ter(tesSUCCESS)); + env.close(); + + // Clawback exactly 60 — at the boundary, no clamping needed + env(vault.clawback({ + .issuer = issuer, + .id = vaultKeylet.key, + .holder = depositor, + .amount = asset(60).value(), + }), + ter(tesSUCCESS)); + env.close(); + + { + auto const sle = env.le(vaultKeylet); + BEAST_EXPECT(sle != nullptr); + BEAST_EXPECT(sle->at(sfAssetsAvailable) == asset(0).value()); + BEAST_EXPECT(sle->at(sfAssetsTotal) == asset(40).value()); + + // 60 of 100 shares destroyed (1:1 ratio), 40 remain + auto const sharesAfter = env.balance(depositor, shares); + BEAST_EXPECT(sharesAfter == shares(Number{4, sle->at(sfScale) + 1})); + } + } + + { + testcase( + "VaultClawback (asset) - " + prefix + + " clawback with zero available (fully borrowed)"); + auto [vault, vaultKeylet] = setupVault(asset, owner, depositor, issuer); + + auto const vaultSle = env.le(vaultKeylet); + if (!BEAST_EXPECT(vaultSle)) + return; + PrettyAsset const shares = MPTIssue(vaultSle->at(sfShareMPTID)); + + auto const brokerKeylet = keylet::loanbroker(owner.id(), env.seq(owner)); + env(set(owner, vaultKeylet.key)); + env.close(); + + // Depositor borrows all 100 units: assetsAvailable=0, assetsTotal=100 + env(set(depositor, brokerKeylet.key, asset(100).value()), + loan::interestRate(TenthBips32(0)), + gracePeriod(60), + paymentInterval(120), + paymentTotal(10), + sig(sfCounterpartySignature, owner), + fee(env.current()->fees().base * 2), + ter(tesSUCCESS)); + env.close(); + + { + auto const sle = env.le(vaultKeylet); + BEAST_EXPECT(sle->at(sfAssetsAvailable) == asset(0).value()); + BEAST_EXPECT(sle->at(sfAssetsTotal) == asset(100).value()); + } + + auto const sharesBefore = env.balance(depositor, shares); + + // Zero-amount clawback — nothing available, clamped to 0, + // resulting in zero shares destroyed → tecPRECISION_LOSS + env(vault.clawback({ + .issuer = issuer, + .id = vaultKeylet.key, + .holder = depositor, + }), + ter(tecPRECISION_LOSS)); + env.close(); + + // Explicit amount clawback — also nothing available + env(vault.clawback({ + .issuer = issuer, + .id = vaultKeylet.key, + .holder = depositor, + .amount = asset(50).value(), + }), + ter(tecPRECISION_LOSS)); + env.close(); + + { + // Nothing changed — vault and shares unchanged + auto const sle = env.le(vaultKeylet); + BEAST_EXPECT(sle != nullptr); + BEAST_EXPECT(sle->at(sfAssetsAvailable) == asset(0).value()); + BEAST_EXPECT(sle->at(sfAssetsTotal) == asset(100).value()); + auto const sharesAfter = env.balance(depositor, shares); + BEAST_EXPECT(sharesAfter == sharesBefore); + } + } }; Account owner{"alice"}; - Account depositor{"bob"}; + Account const depositor{"bob"}; Account const issuer{"issuer"}; env.fund(XRP(10000), issuer, owner, depositor); @@ -4972,10 +5384,10 @@ class Vault_test : public beast::unit_test::suite PrettyAsset const IOU = issuer["IOU"]; env(fset(issuer, asfAllowTrustLineClawback)); env.close(); - env.trust(IOU(1000), owner); - env.trust(IOU(1000), depositor); - env(pay(issuer, owner, IOU(1000))); - env(pay(issuer, depositor, IOU(1000))); + env.trust(IOU(2000), owner); + env.trust(IOU(2000), depositor); + env(pay(issuer, owner, IOU(2000))); + env(pay(issuer, depositor, IOU(2000))); env.close(); testCase(IOU, "IOU", owner, depositor, issuer); @@ -4985,9 +5397,77 @@ class Vault_test : public beast::unit_test::suite PrettyAsset const MPT = mptt.issuanceID(); mptt.authorize({.account = owner}); mptt.authorize({.account = depositor}); - env(pay(issuer, depositor, MPT(1000))); + env(pay(issuer, depositor, MPT(2000))); env.close(); testCase(MPT, "MPT", owner, depositor, issuer); + + // Test pre-fixSecurity3_1_3 legacy path: zero-amount clawback + // returns early without clamping to assetsAvailable. + { + testcase( + "VaultClawback (asset) - IOU pre-fixSecurity3_1_3" + " zero-amount clawback unclamped with outstanding loan"); + + env.disableFeature(fixSecurity3_1_3); + + auto [vault, vaultKeylet] = setupVault(IOU, owner, depositor, issuer); + + auto const vaultSle = env.le(vaultKeylet); + BEAST_EXPECT(vaultSle != nullptr); + if (!vaultSle) + return; + + PrettyAsset const shares = MPTIssue(vaultSle->at(sfShareMPTID)); + + // Create a loan broker backed by this vault + auto const brokerKeylet = keylet::loanbroker(owner.id(), env.seq(owner)); + env(set(owner, vaultKeylet.key)); + env.close(); + + // Depositor borrows 40 units, reducing assetsAvailable to 60 + // while assetsTotal stays at 100 + env(set(depositor, brokerKeylet.key, IOU(40).value()), + loan::interestRate(TenthBips32(0)), + gracePeriod(60), + paymentInterval(120), + paymentTotal(10), + sig(sfCounterpartySignature, owner), + fee(env.current()->fees().base * 2), + ter(tesSUCCESS)); + env.close(); + + { + auto const sle = env.le(vaultKeylet); + BEAST_EXPECT(sle->at(sfAssetsAvailable) == IOU(60).value()); + BEAST_EXPECT(sle->at(sfAssetsTotal) == IOU(100).value()); + } + + auto const sharesBefore = env.balance(depositor, shares); + + // Legacy: zero-amount clawback tries to recover the full + // share value (100) without clamping to assetsAvailable (60). + // This causes the vault balance to go negative, triggering + // the sanity check in doApply → tefINTERNAL. + env(vault.clawback({ + .issuer = issuer, + .id = vaultKeylet.key, + .holder = depositor, + }), + ter(tefINTERNAL)); + env.close(); + + { + // Transaction rolled back — vault and shares unchanged + auto const sle = env.le(vaultKeylet); + BEAST_EXPECT(sle != nullptr); + BEAST_EXPECT(sle->at(sfAssetsAvailable) == IOU(60).value()); + BEAST_EXPECT(sle->at(sfAssetsTotal) == IOU(100).value()); + auto const sharesAfter = env.balance(depositor, shares); + BEAST_EXPECT(sharesAfter == sharesBefore); + } + + env.enableFeature(fixSecurity3_1_3); + } } void @@ -5231,6 +5711,240 @@ class Vault_test : public beast::unit_test::suite } } + void + testVaultEscrowedMPT() + { + using namespace test::jtx; + using namespace std::literals; + + // Verify vault deposit/withdraw/clawback respect sfLockedAmount. + // When MPT tokens are escrowed, sfMPTAmount is reduced and + // sfLockedAmount is increased. Vault operations go through + // accountSend/accountHolds which read sfMPTAmount, so escrowed + // tokens are naturally excluded. + + { + testcase("Vault deposit fails when MPT asset is escrowed"); + + Env env{*this, testable_amendments()}; + auto const baseFee = env.current()->fees().base; + Account const owner{"owner"}; + Account const depositor{"depositor"}; + Account const issuer{"issuer"}; + Account const bob{"bob"}; + + env.fund(XRP(10000), issuer, owner, depositor, bob); + env.close(); + + MPTTester mptt{env, issuer, mptInitNoFund}; + mptt.create( + {.flags = tfMPTCanClawback | tfMPTCanTransfer | tfMPTCanLock | tfMPTCanEscrow}); + mptt.authorize({.account = owner}); + mptt.authorize({.account = depositor}); + mptt.authorize({.account = bob}); + PrettyAsset const asset = mptt.issuanceID(); + env(pay(issuer, depositor, asset(100))); + env.close(); + + // Escrow 60 of 100 MPT tokens: sfMPTAmount drops to 40 + auto const escrowSeq = env.seq(depositor); + env(escrow::create(depositor, bob, asset(60)), + escrow::condition(escrow::cb1), + escrow::finish_time(env.now() + 1s), + fee(baseFee * 150), + ter(tesSUCCESS)); + env.close(); + + Vault const vault{env}; + auto [tx, vaultKeylet] = vault.create({.owner = owner, .asset = asset}); + env(tx, ter(tesSUCCESS)); + env.close(); + + // Deposit 100 should fail — only 40 spendable + env(vault.deposit( + {.depositor = depositor, .id = vaultKeylet.key, .amount = asset(100)}), + ter(tecINSUFFICIENT_FUNDS)); + env.close(); + + // Deposit 40 (the unlocked balance) should succeed + env(vault.deposit({.depositor = depositor, .id = vaultKeylet.key, .amount = asset(40)}), + ter(tesSUCCESS)); + env.close(); + + { + auto const sle = env.le(vaultKeylet); + BEAST_EXPECT(sle->at(sfAssetsTotal) == asset(40).value()); + } + + // Clean up escrow + env(escrow::finish(bob, depositor, escrowSeq), + escrow::condition(escrow::cb1), + escrow::fulfillment(escrow::fb1), + fee(baseFee * 150), + ter(tesSUCCESS)); + env.close(); + } + + { + testcase("Vault withdraw respects escrowed shares"); + + Env env{*this, testable_amendments()}; + auto const baseFee = env.current()->fees().base; + Account const owner{"owner"}; + Account const depositor{"depositor"}; + Account const issuer{"issuer"}; + Account const bob{"bob"}; + + env.fund(XRP(10000), issuer, owner, depositor, bob); + env.close(); + + MPTTester mptt{env, issuer, mptInitNoFund}; + mptt.create( + {.flags = tfMPTCanClawback | tfMPTCanTransfer | tfMPTCanLock | tfMPTCanEscrow}); + mptt.authorize({.account = owner}); + mptt.authorize({.account = depositor}); + PrettyAsset const asset = mptt.issuanceID(); + env(pay(issuer, depositor, asset(100))); + env.close(); + + Vault const vault{env}; + auto [tx, vaultKeylet] = vault.create({.owner = owner, .asset = asset}); + env(tx, ter(tesSUCCESS)); + env.close(); + + // Deposit 100 → get shares + env(vault.deposit( + {.depositor = depositor, .id = vaultKeylet.key, .amount = asset(100)}), + ter(tesSUCCESS)); + env.close(); + + auto const vaultSle = env.le(vaultKeylet); + if (!BEAST_EXPECT(vaultSle)) + return; + env.memoize(Account("vault", vaultSle->at(sfAccount))); + PrettyAsset const shares = MPTIssue(vaultSle->at(sfShareMPTID)); + + // Authorize bob for share MPT so he can receive escrowed shares + auto const shareMPTID = vaultSle->at(sfShareMPTID); + { + Json::Value jv; + jv[jss::Account] = bob.human(); + jv[sfMPTokenIssuanceID] = to_string(shareMPTID); + jv[jss::TransactionType] = jss::MPTokenAuthorize; + env(jv, ter(tesSUCCESS)); + env.close(); + } + + // Escrow 60% of shares + auto const escrowAmount = shares(Number{6, vaultSle->at(sfScale) + 1}); + env(escrow::create(depositor, bob, escrowAmount), + escrow::condition(escrow::cb1), + escrow::finish_time(env.now() + 1s), + fee(baseFee * 150), + ter(tesSUCCESS)); + env.close(); + + // Withdraw all 100 should fail — only 40% of shares are unlocked + env(vault.withdraw( + {.depositor = depositor, .id = vaultKeylet.key, .amount = asset(100)}), + ter(tecINSUFFICIENT_FUNDS)); + env.close(); + + // Withdraw 40 (matching unlocked shares) should succeed + env(vault.withdraw( + {.depositor = depositor, .id = vaultKeylet.key, .amount = asset(40)}), + ter(tesSUCCESS)); + env.close(); + + { + auto const sle = env.le(vaultKeylet); + BEAST_EXPECT(sle->at(sfAssetsTotal) == asset(60).value()); + } + } + + { + testcase("Vault clawback only recovers unlocked shares"); + + Env env{*this, testable_amendments() | fixSecurity3_1_3}; + auto const baseFee = env.current()->fees().base; + Account const owner{"owner"}; + Account const depositor{"depositor"}; + Account const issuer{"issuer"}; + Account const bob{"bob"}; + + env.fund(XRP(10000), issuer, owner, depositor, bob); + env.close(); + + MPTTester mptt{env, issuer, mptInitNoFund}; + mptt.create( + {.flags = tfMPTCanClawback | tfMPTCanTransfer | tfMPTCanLock | tfMPTCanEscrow}); + mptt.authorize({.account = owner}); + mptt.authorize({.account = depositor}); + PrettyAsset const asset = mptt.issuanceID(); + env(pay(issuer, depositor, asset(100))); + env.close(); + + Vault const vault{env}; + auto [tx, vaultKeylet] = vault.create({.owner = owner, .asset = asset}); + env(tx, ter(tesSUCCESS)); + env.close(); + + // Deposit 100 → get shares + env(vault.deposit( + {.depositor = depositor, .id = vaultKeylet.key, .amount = asset(100)}), + ter(tesSUCCESS)); + env.close(); + + auto const vaultSle = env.le(vaultKeylet); + if (!BEAST_EXPECT(vaultSle)) + return; + env.memoize(Account("vault", vaultSle->at(sfAccount))); + PrettyAsset const shares = MPTIssue(vaultSle->at(sfShareMPTID)); + + // Authorize bob for share MPT so he can receive escrowed shares + auto const shareMPTID = vaultSle->at(sfShareMPTID); + { + Json::Value jv; + jv[jss::Account] = bob.human(); + jv[sfMPTokenIssuanceID] = to_string(shareMPTID); + jv[jss::TransactionType] = jss::MPTokenAuthorize; + env(jv, ter(tesSUCCESS)); + env.close(); + } + + // Escrow 60% of shares + auto const escrowAmount = shares(Number{6, vaultSle->at(sfScale) + 1}); + env(escrow::create(depositor, bob, escrowAmount), + escrow::condition(escrow::cb1), + escrow::finish_time(env.now() + 1s), + fee(baseFee * 150), + ter(tesSUCCESS)); + env.close(); + + // Zero-amount clawback ("all") — should only recover assets + // corresponding to unlocked shares (40%) + env(vault.clawback({ + .issuer = issuer, + .id = vaultKeylet.key, + .holder = depositor, + }), + ter(tesSUCCESS)); + env.close(); + + { + auto const sle = env.le(vaultKeylet); + BEAST_EXPECT(sle != nullptr); + // Only 40 of 100 assets recovered (matching 40% unlocked shares) + BEAST_EXPECT(sle->at(sfAssetsTotal) == asset(60).value()); + BEAST_EXPECT(sle->at(sfAssetsAvailable) == asset(60).value()); + + // Depositor's unlocked shares are now 0 + auto const sharesAfter = env.balance(depositor, shares); + BEAST_EXPECT(sharesAfter == shares(0)); + } + } + } + // Reproduction: canWithdraw IOU limit check bypassed when // withdrawal amount is specified in shares (MPT) rather than in assets. void @@ -5327,6 +6041,104 @@ class Vault_test : public beast::unit_test::suite } } + void + testRemoveEmptyHoldingLockedAmount() + { + testcase("removeEmptyHolding deletes MPToken with sfLockedAmount"); + using namespace test::jtx; + using namespace std::literals; + + auto const amendments = testable_amendments(); + auto runTest = [&](FeatureBitset f) { + Env env{*this, f}; + auto const baseFee = env.current()->fees().base; + + Account const issuer{"issuer"}; + Account const owner{"owner"}; + Account const depositor{"depositor"}; + Account const bob{"bob"}; + + env.fund(XRP(100000), issuer, owner, depositor, bob); + env.close(); + + Vault const vault{env}; + + // Create an MPT asset for the vault + MPTTester mptt{env, issuer, mptInitNoFund}; + mptt.create({.flags = tfMPTCanTransfer | tfMPTCanLock}); + PrettyAsset const asset = mptt.issuanceID(); + mptt.authorize({.account = owner}); + mptt.authorize({.account = depositor}); + env(pay(issuer, depositor, asset(1000))); + env.close(); + + // Create vault + auto [tx, keylet] = vault.create({.owner = owner, .asset = asset}); + env(tx); + env.close(); + + auto const vaultSle = env.le(keylet); + BEAST_EXPECT(vaultSle != nullptr); + auto const shareMptID = vaultSle->at(sfShareMPTID); + MPTIssue const shareIssue{shareMptID}; + + // Depositor deposits 1000 asset units into vault, receiving shares + env(vault.deposit({.depositor = depositor, .id = keylet.key, .amount = asset(1000)})); + env.close(); + + // Check depositor has shares + { + auto const sleMpt = env.le(keylet::mptoken(shareMptID, depositor)); + BEAST_EXPECT(sleMpt != nullptr); + BEAST_EXPECT(sleMpt->at(sfMPTAmount) == 1000); + } + + // Escrow 500 of those shares + env(escrow::create(depositor, bob, STAmount{shareIssue, 500}), + escrow::condition(escrow::cb1), + escrow::finish_time(env.now() + 1s), + fee(baseFee * 150), + ter(tesSUCCESS)); + env.close(); + + // Verify: sfMPTAmount=500, sfLockedAmount=500 + { + auto const sleMpt = env.le(keylet::mptoken(shareMptID, depositor)); + BEAST_EXPECT(sleMpt != nullptr); + BEAST_EXPECT(sleMpt->at(sfLockedAmount) == 500); + BEAST_EXPECT(sleMpt->at(sfMPTAmount) == 500); + } + + // Withdraw remaining spendable shares — triggers removeEmptyHolding + env(vault.withdraw({.depositor = depositor, .id = keylet.key, .amount = asset(500)}), + ter(tesSUCCESS)); + env.close(); + + auto const sleMptAfter = env.le(keylet::mptoken(shareMptID, depositor)); + if (!f[fixSecurity3_1_3]) + { + // Without the fix, removeEmptyHolding deletes the MPToken + // even though sfLockedAmount > 0, leaving the escrow's locked + // amount untracked. + BEAST_EXPECT(sleMptAfter == nullptr); + } + else + { + // With the fix, MPToken must still exist with sfLockedAmount > 0 + // and sfMPTAmount == 0 (all spendable shares withdrawn). + BEAST_EXPECT(sleMptAfter != nullptr); + if (sleMptAfter) + { + BEAST_EXPECT(sleMptAfter->at(sfLockedAmount) == 500); + BEAST_EXPECT(sleMptAfter->at(sfMPTAmount) == 0); + } + } + }; + + runTest(amendments - fixSecurity3_1_3); + runTest(amendments); + } + public: void run() override @@ -5346,8 +6158,10 @@ public: testRPC(); testVaultClawbackBurnShares(); testVaultClawbackAssets(); + testVaultEscrowedMPT(); testAssetsMaximum(); testBug6_LimitBypassWithShares(); + testRemoveEmptyHoldingLockedAmount(); } }; diff --git a/src/test/app/XChain_test.cpp b/src/test/app/XChain_test.cpp index abaeccb4ff..852bbbbb8b 100644 --- a/src/test/app/XChain_test.cpp +++ b/src/test/app/XChain_test.cpp @@ -1,25 +1,54 @@ -#include +#include #include +#include +#include #include +#include +#include +#include #include +#include +#include +#include +#include +#include #include +#include + +#include +#include #include +#include +#include +#include #include #include #include +#include #include +#include #include -#include #include -#include +#include +#include +#include +#include +#include +#include +#include +#include #include -#include +#include +#include +#include #include #include #include #include +#include +#include #include #include @@ -291,7 +320,7 @@ struct BalanceTransfer bool has_happened(STAmount const& amt, STAmount const& reward, bool check_payer = true) { - auto reward_cost = multiply(reward, STAmount(reward_accounts.size()), reward.issue()); + auto reward_cost = multiply(reward, STAmount(reward_accounts.size()), reward.asset()); return check_most_balances(amt, reward) && (!check_payer || payer_.diff() == -(reward_cost + txFees_)); } @@ -1503,7 +1532,7 @@ struct XChain_test : public beast::unit_test::suite, public jtx::XChainBridgeObj BEAST_EXPECT(!scEnv.claimID(jvb, 1)); // claim id deleted - BEAST_EXPECT(transfer.has_happened(amt, divide(reward, STAmount(3), reward.issue()))); + BEAST_EXPECT(transfer.has_happened(amt, divide(reward, STAmount(3), reward.asset()))); } // 4,4 => should succeed @@ -1528,7 +1557,7 @@ struct XChain_test : public beast::unit_test::suite, public jtx::XChainBridgeObj return result; }(); STAmount const split_reward_ = - divide(reward, STAmount(signers_.size()), reward.issue()); + divide(reward, STAmount(signers_.size()), reward.asset()); mcEnv.tx(create_bridge(mcDoor, jvb)).close(); @@ -1563,7 +1592,7 @@ struct XChain_test : public beast::unit_test::suite, public jtx::XChainBridgeObj BEAST_EXPECT(!scEnv.claimID(jvb, claimID)); // claim id deleted - BEAST_EXPECT(transfer.has_happened(amt, divide(reward, STAmount(2), reward.issue()))); + BEAST_EXPECT(transfer.has_happened(amt, divide(reward, STAmount(2), reward.asset()))); } // 1,2 => should fail @@ -2302,15 +2331,15 @@ struct XChain_test : public beast::unit_test::suite, public jtx::XChainBridgeObj Account const ua{"ua"}; // unfunded account we want to create BridgeDef xrp_b{ - doorA, - xrpIssue(), - Account::master, - xrpIssue(), - XRP(1), // reward - XRP(20), // minAccountCreate - 4, // quorum - signers, - Json::nullValue}; + .doorA = doorA, + .issueA = xrpIssue(), + .doorB = Account::master, + .issueB = xrpIssue(), + .reward = XRP(1), // reward + .minAccountCreate = XRP(20), // minAccountCreate + .quorum = 4, // quorum + .signers = signers, + .jvb = Json::nullValue}; xrp_b.initBridge(mcEnv, scEnv); @@ -3887,7 +3916,7 @@ private: void receive(jtx::Account const& acct, STAmount amt, std::uint64_t divisor = 1) { - if (amt.issue() != xrpIssue()) + if (amt.asset() != xrpIssue()) return; auto it = accounts.find(acct); if (it == accounts.end()) @@ -3898,18 +3927,18 @@ private: else { it->second.expectedDiff += - (divisor == 1 ? amt : divide(amt, STAmount(amt.issue(), divisor), amt.issue())); + (divisor == 1 ? amt : divide(amt, STAmount(amt.asset(), divisor), amt.asset())); } } void spend(jtx::Account const& acct, STAmount amt, std::uint64_t times = 1) { - if (amt.issue() != xrpIssue()) + if (amt.asset() != xrpIssue()) return; receive( acct, - times == 1 ? -amt : -multiply(amt, STAmount(amt.issue(), times), amt.issue())); + times == 1 ? -amt : -multiply(amt, STAmount(amt.asset(), times), amt.asset())); } void @@ -4132,7 +4161,7 @@ private: assert(cr.claim_id - 1 == counters.claim_count); auto r = cr.reward; - auto reward = divide(r, STAmount(num_attestors), r.issue()); + auto reward = divide(r, STAmount(num_attestors), r.asset()); for (auto i : signers) st.receive(bridge_.signers[i].account, reward); @@ -4213,7 +4242,7 @@ private: ChainStateTrack& st = srcState(); jtx::Account const& srcdoor = srcDoor(); - if (xfer.amt.issue() != xrpIssue()) + if (xfer.amt.asset() != xrpIssue()) { st.env.tx(pay(srcdoor, xfer.from, xfer.amt)); st.spendFee(srcdoor); @@ -4233,7 +4262,7 @@ private: distribute_reward(ChainStateTrack& st) { auto r = bridge_.reward; - auto reward = divide(r, STAmount(bridge_.quorum), r.issue()); + auto reward = divide(r, STAmount(bridge_.quorum), r.asset()); for (size_t i = 0; i < num_signers; ++i) { @@ -4500,30 +4529,30 @@ public: // create XRP -> XRP bridge // ------------------------ BridgeDef xrp_b{ - doorXRPLocking, - xrpIssue(), - Account::master, - xrpIssue(), - XRP(1), - XRP(20), - quorum, - signers, - Json::nullValue}; + .doorA = doorXRPLocking, + .issueA = xrpIssue(), + .doorB = Account::master, + .issueB = xrpIssue(), + .reward = XRP(1), + .minAccountCreate = XRP(20), + .quorum = quorum, + .signers = signers, + .jvb = Json::nullValue}; initBridge(xrp_b); // create USD -> USD bridge // ------------------------ BridgeDef usd_b{ - doorUSDLocking, - usdLocking, - doorUSDIssuing, - usdIssuing, - XRP(1), - XRP(20), - quorum, - signers, - Json::nullValue}; + .doorA = doorUSDLocking, + .issueA = usdLocking, + .doorB = doorUSDIssuing, + .issueB = usdIssuing, + .reward = XRP(1), + .minAccountCreate = XRP(20), + .quorum = quorum, + .signers = signers, + .jvb = Json::nullValue}; initBridge(usd_b); @@ -4532,79 +4561,262 @@ public: // give time enough for ua[0] to be funded now so it can reserve // the claimID // ----------------------------------------------------------------- - ac(0, st, xrp_b, {a[0], ua[0], XRP(777), xrp_b.reward, true}); - xfer(8, st, xrp_b, {a[0], ua[0], a[2], XRP(3), true}); + ac(0, + st, + xrp_b, + {.from = a[0], .to = ua[0], .amt = XRP(777), .reward = xrp_b.reward, .a2b = true}); + xfer( + 8, + st, + xrp_b, + {.from = a[0], .to = ua[0], .finaldest = a[2], .amt = XRP(3), .a2b = true}); runSimulation(st); // try the same thing in the other direction // ----------------------------------------- - ac(0, st, xrp_b, {a[0], ua[0], XRP(777), xrp_b.reward, false}); - xfer(8, st, xrp_b, {a[0], ua[0], a[2], XRP(3), false}); + ac(0, + st, + xrp_b, + {.from = a[0], .to = ua[0], .amt = XRP(777), .reward = xrp_b.reward, .a2b = false}); + xfer( + 8, + st, + xrp_b, + {.from = a[0], .to = ua[0], .finaldest = a[2], .amt = XRP(3), .a2b = false}); runSimulation(st); // run multiple XRP transfers // -------------------------- - xfer(0, st, xrp_b, {a[0], a[0], a[1], XRP(6), true, WithClaim::no}); - xfer(1, st, xrp_b, {a[0], a[0], a[1], XRP(8), false, WithClaim::no}); - xfer(1, st, xrp_b, {a[1], a[1], a[1], XRP(1), true}); - xfer(2, st, xrp_b, {a[0], a[0], a[1], XRP(3), false}); - xfer(2, st, xrp_b, {a[1], a[1], a[1], XRP(5), false}); - xfer(2, st, xrp_b, {a[0], a[0], a[1], XRP(7), false, WithClaim::no}); - xfer(2, st, xrp_b, {a[1], a[1], a[1], XRP(9), true}); + xfer( + 0, + st, + xrp_b, + {.from = a[0], + .to = a[0], + .finaldest = a[1], + .amt = XRP(6), + .a2b = true, + .with_claim = WithClaim::no}); + xfer( + 1, + st, + xrp_b, + {.from = a[0], + .to = a[0], + .finaldest = a[1], + .amt = XRP(8), + .a2b = false, + .with_claim = WithClaim::no}); + xfer( + 1, + st, + xrp_b, + {.from = a[1], .to = a[1], .finaldest = a[1], .amt = XRP(1), .a2b = true}); + xfer( + 2, + st, + xrp_b, + {.from = a[0], .to = a[0], .finaldest = a[1], .amt = XRP(3), .a2b = false}); + xfer( + 2, + st, + xrp_b, + {.from = a[1], .to = a[1], .finaldest = a[1], .amt = XRP(5), .a2b = false}); + xfer( + 2, + st, + xrp_b, + {.from = a[0], + .to = a[0], + .finaldest = a[1], + .amt = XRP(7), + .a2b = false, + .with_claim = WithClaim::no}); + xfer( + 2, + st, + xrp_b, + {.from = a[1], .to = a[1], .finaldest = a[1], .amt = XRP(9), .a2b = true}); runSimulation(st); // run one USD transfer // -------------------- - xfer(0, st, usd_b, {a[0], a[1], a[2], usdLocking(3), true}); + xfer( + 0, + st, + usd_b, + {.from = a[0], .to = a[1], .finaldest = a[2], .amt = usdLocking(3), .a2b = true}); runSimulation(st); // run multiple USD transfers // -------------------------- - xfer(0, st, usd_b, {a[0], a[0], a[1], usdLocking(6), true}); - xfer(1, st, usd_b, {a[0], a[0], a[1], usdIssuing(8), false}); - xfer(1, st, usd_b, {a[1], a[1], a[1], usdLocking(1), true}); - xfer(2, st, usd_b, {a[0], a[0], a[1], usdIssuing(3), false}); - xfer(2, st, usd_b, {a[1], a[1], a[1], usdIssuing(5), false}); - xfer(2, st, usd_b, {a[0], a[0], a[1], usdIssuing(7), false}); - xfer(2, st, usd_b, {a[1], a[1], a[1], usdLocking(9), true}); + xfer( + 0, + st, + usd_b, + {.from = a[0], .to = a[0], .finaldest = a[1], .amt = usdLocking(6), .a2b = true}); + xfer( + 1, + st, + usd_b, + {.from = a[0], .to = a[0], .finaldest = a[1], .amt = usdIssuing(8), .a2b = false}); + xfer( + 1, + st, + usd_b, + {.from = a[1], .to = a[1], .finaldest = a[1], .amt = usdLocking(1), .a2b = true}); + xfer( + 2, + st, + usd_b, + {.from = a[0], .to = a[0], .finaldest = a[1], .amt = usdIssuing(3), .a2b = false}); + xfer( + 2, + st, + usd_b, + {.from = a[1], .to = a[1], .finaldest = a[1], .amt = usdIssuing(5), .a2b = false}); + xfer( + 2, + st, + usd_b, + {.from = a[0], .to = a[0], .finaldest = a[1], .amt = usdIssuing(7), .a2b = false}); + xfer( + 2, + st, + usd_b, + {.from = a[1], .to = a[1], .finaldest = a[1], .amt = usdLocking(9), .a2b = true}); runSimulation(st); // run mixed transfers // ------------------- - xfer(0, st, xrp_b, {a[0], a[0], a[0], XRP(1), true}); - xfer(0, st, usd_b, {a[1], a[3], a[3], usdIssuing(3), false}); - xfer(0, st, usd_b, {a[3], a[2], a[1], usdIssuing(5), false}); + xfer( + 0, + st, + xrp_b, + {.from = a[0], .to = a[0], .finaldest = a[0], .amt = XRP(1), .a2b = true}); + xfer( + 0, + st, + usd_b, + {.from = a[1], .to = a[3], .finaldest = a[3], .amt = usdIssuing(3), .a2b = false}); + xfer( + 0, + st, + usd_b, + {.from = a[3], .to = a[2], .finaldest = a[1], .amt = usdIssuing(5), .a2b = false}); - xfer(1, st, xrp_b, {a[0], a[0], a[0], XRP(4), false}); - xfer(1, st, xrp_b, {a[1], a[1], a[0], XRP(8), true}); - xfer(1, st, usd_b, {a[4], a[1], a[1], usdLocking(7), true}); + xfer( + 1, + st, + xrp_b, + {.from = a[0], .to = a[0], .finaldest = a[0], .amt = XRP(4), .a2b = false}); + xfer( + 1, + st, + xrp_b, + {.from = a[1], .to = a[1], .finaldest = a[0], .amt = XRP(8), .a2b = true}); + xfer( + 1, + st, + usd_b, + {.from = a[4], .to = a[1], .finaldest = a[1], .amt = usdLocking(7), .a2b = true}); - xfer(3, st, xrp_b, {a[1], a[1], a[0], XRP(7), true}); - xfer(3, st, xrp_b, {a[0], a[4], a[3], XRP(2), false}); - xfer(3, st, xrp_b, {a[1], a[1], a[0], XRP(9), true}); - xfer(3, st, usd_b, {a[3], a[1], a[1], usdIssuing(11), false}); + xfer( + 3, + st, + xrp_b, + {.from = a[1], .to = a[1], .finaldest = a[0], .amt = XRP(7), .a2b = true}); + xfer( + 3, + st, + xrp_b, + {.from = a[0], .to = a[4], .finaldest = a[3], .amt = XRP(2), .a2b = false}); + xfer( + 3, + st, + xrp_b, + {.from = a[1], .to = a[1], .finaldest = a[0], .amt = XRP(9), .a2b = true}); + xfer( + 3, + st, + usd_b, + {.from = a[3], .to = a[1], .finaldest = a[1], .amt = usdIssuing(11), .a2b = false}); runSimulation(st); // run multiple account create to stress attestation batching // ---------------------------------------------------------- - ac(0, st, xrp_b, {a[0], ua[1], XRP(301), xrp_b.reward, true}); - ac(0, st, xrp_b, {a[1], ua[2], XRP(302), xrp_b.reward, true}); - ac(1, st, xrp_b, {a[0], ua[3], XRP(303), xrp_b.reward, true}); - ac(2, st, xrp_b, {a[1], ua[4], XRP(304), xrp_b.reward, true}); - ac(3, st, xrp_b, {a[0], ua[5], XRP(305), xrp_b.reward, true}); - ac(4, st, xrp_b, {a[1], ua[6], XRP(306), xrp_b.reward, true}); - ac(6, st, xrp_b, {a[0], ua[7], XRP(307), xrp_b.reward, true}); - ac(7, st, xrp_b, {a[2], ua[8], XRP(308), xrp_b.reward, true}); - ac(9, st, xrp_b, {a[0], ua[9], XRP(309), xrp_b.reward, true}); - ac(9, st, xrp_b, {a[0], ua[9], XRP(309), xrp_b.reward, true}); - ac(10, st, xrp_b, {a[0], ua[10], XRP(310), xrp_b.reward, true}); - ac(12, st, xrp_b, {a[0], ua[11], XRP(311), xrp_b.reward, true}); - ac(12, st, xrp_b, {a[3], ua[12], XRP(312), xrp_b.reward, true}); - ac(12, st, xrp_b, {a[4], ua[13], XRP(313), xrp_b.reward, true}); - ac(12, st, xrp_b, {a[3], ua[14], XRP(314), xrp_b.reward, true}); - ac(12, st, xrp_b, {a[6], ua[15], XRP(315), xrp_b.reward, true}); - ac(13, st, xrp_b, {a[7], ua[16], XRP(316), xrp_b.reward, true}); - ac(15, st, xrp_b, {a[3], ua[17], XRP(317), xrp_b.reward, true}); + ac(0, + st, + xrp_b, + {.from = a[0], .to = ua[1], .amt = XRP(301), .reward = xrp_b.reward, .a2b = true}); + ac(0, + st, + xrp_b, + {.from = a[1], .to = ua[2], .amt = XRP(302), .reward = xrp_b.reward, .a2b = true}); + ac(1, + st, + xrp_b, + {.from = a[0], .to = ua[3], .amt = XRP(303), .reward = xrp_b.reward, .a2b = true}); + ac(2, + st, + xrp_b, + {.from = a[1], .to = ua[4], .amt = XRP(304), .reward = xrp_b.reward, .a2b = true}); + ac(3, + st, + xrp_b, + {.from = a[0], .to = ua[5], .amt = XRP(305), .reward = xrp_b.reward, .a2b = true}); + ac(4, + st, + xrp_b, + {.from = a[1], .to = ua[6], .amt = XRP(306), .reward = xrp_b.reward, .a2b = true}); + ac(6, + st, + xrp_b, + {.from = a[0], .to = ua[7], .amt = XRP(307), .reward = xrp_b.reward, .a2b = true}); + ac(7, + st, + xrp_b, + {.from = a[2], .to = ua[8], .amt = XRP(308), .reward = xrp_b.reward, .a2b = true}); + ac(9, + st, + xrp_b, + {.from = a[0], .to = ua[9], .amt = XRP(309), .reward = xrp_b.reward, .a2b = true}); + ac(9, + st, + xrp_b, + {.from = a[0], .to = ua[9], .amt = XRP(309), .reward = xrp_b.reward, .a2b = true}); + ac(10, + st, + xrp_b, + {.from = a[0], .to = ua[10], .amt = XRP(310), .reward = xrp_b.reward, .a2b = true}); + ac(12, + st, + xrp_b, + {.from = a[0], .to = ua[11], .amt = XRP(311), .reward = xrp_b.reward, .a2b = true}); + ac(12, + st, + xrp_b, + {.from = a[3], .to = ua[12], .amt = XRP(312), .reward = xrp_b.reward, .a2b = true}); + ac(12, + st, + xrp_b, + {.from = a[4], .to = ua[13], .amt = XRP(313), .reward = xrp_b.reward, .a2b = true}); + ac(12, + st, + xrp_b, + {.from = a[3], .to = ua[14], .amt = XRP(314), .reward = xrp_b.reward, .a2b = true}); + ac(12, + st, + xrp_b, + {.from = a[6], .to = ua[15], .amt = XRP(315), .reward = xrp_b.reward, .a2b = true}); + ac(13, + st, + xrp_b, + {.from = a[7], .to = ua[16], .amt = XRP(316), .reward = xrp_b.reward, .a2b = true}); + ac(15, + st, + xrp_b, + {.from = a[3], .to = ua[17], .amt = XRP(317), .reward = xrp_b.reward, .a2b = true}); runSimulation(st, true); // balances verification working now. } diff --git a/src/test/app/tx/apply_test.cpp b/src/test/app/tx/apply_test.cpp index 6d3efea5f3..ffacc0a563 100644 --- a/src/test/app/tx/apply_test.cpp +++ b/src/test/app/tx/apply_test.cpp @@ -2,10 +2,16 @@ #include +#include #include -#include +#include +#include +#include #include +#include +#include + namespace xrpl { class Apply_test : public beast::unit_test::suite diff --git a/src/test/basics/Buffer_test.cpp b/src/test/basics/Buffer_test.cpp index 95a8853975..1fd0ed9e09 100644 --- a/src/test/basics/Buffer_test.cpp +++ b/src/test/basics/Buffer_test.cpp @@ -1,11 +1,14 @@ #include -#include +#include +#include +#include #include +#include #include +#include -namespace xrpl { -namespace test { +namespace xrpl::test { struct Buffer_test : beast::unit_test::suite { @@ -100,8 +103,8 @@ struct Buffer_test : beast::unit_test::suite { testcase("Move Construction / Assignment"); - static_assert(std::is_nothrow_move_constructible::value, ""); - static_assert(std::is_nothrow_move_assignable::value, ""); + static_assert(std::is_nothrow_move_constructible_v, ""); + static_assert(std::is_nothrow_move_assignable_v, ""); { // Move-construct from empty buf Buffer x; @@ -262,5 +265,4 @@ struct Buffer_test : beast::unit_test::suite BEAST_DEFINE_TESTSUITE(Buffer, basics, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/basics/DetectCrash_test.cpp b/src/test/basics/DetectCrash_test.cpp index 0dd2787dff..b47975873e 100644 --- a/src/test/basics/DetectCrash_test.cpp +++ b/src/test/basics/DetectCrash_test.cpp @@ -2,8 +2,7 @@ #include -namespace xrpl { -namespace test { +namespace xrpl::test { struct DetectCrash_test : public beast::unit_test::suite { @@ -24,5 +23,4 @@ struct DetectCrash_test : public beast::unit_test::suite BEAST_DEFINE_TESTSUITE_MANUAL(DetectCrash, basics, beast); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/basics/Expected_test.cpp b/src/test/basics/Expected_test.cpp index fa35946624..b76955f53a 100644 --- a/src/test/basics/Expected_test.cpp +++ b/src/test/basics/Expected_test.cpp @@ -1,15 +1,20 @@ #include -#include +#include #include +#include +#include + +#include +#include +#include +#include + #if BOOST_VERSION >= 107500 -#include // Not part of boost before version 1.75 -#endif // BOOST_VERSION -#include +#endif // BOOST_VERSION #include -namespace xrpl { -namespace test { +namespace xrpl::test { struct Expected_test : beast::unit_test::suite { @@ -213,5 +218,4 @@ struct Expected_test : beast::unit_test::suite BEAST_DEFINE_TESTSUITE(Expected, basics, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/basics/FileUtilities_test.cpp b/src/test/basics/FileUtilities_test.cpp index b63f8baf0a..b427e800ec 100644 --- a/src/test/basics/FileUtilities_test.cpp +++ b/src/test/basics/FileUtilities_test.cpp @@ -2,7 +2,10 @@ #include #include -#include +#include + +#include +#include namespace xrpl { diff --git a/src/test/basics/IOUAmount_test.cpp b/src/test/basics/IOUAmount_test.cpp index d0e272c28b..95362c7545 100644 --- a/src/test/basics/IOUAmount_test.cpp +++ b/src/test/basics/IOUAmount_test.cpp @@ -1,6 +1,12 @@ -#include +#include +#include +#include #include +#include +#include +#include + namespace xrpl { class IOUAmount_test : public beast::unit_test::suite diff --git a/src/test/basics/IntrusiveShared_test.cpp b/src/test/basics/IntrusiveShared_test.cpp index 52cb8f5c1c..23b0c43499 100644 --- a/src/test/basics/IntrusiveShared_test.cpp +++ b/src/test/basics/IntrusiveShared_test.cpp @@ -1,24 +1,29 @@ -#include -#include +#include // IWYU pragma: keep +#include // IWYU pragma: keep #include -#include -#include +#include +#include #include #include -#include -#include +#include +#include // IWYU pragma: keep #include +#include +#include +#include #include +#include #include #include #include #include +#include #include +#include -namespace xrpl { -namespace tests { +namespace xrpl::tests { /** Experimentally, we discovered that using std::barrier performs extremely @@ -122,7 +127,7 @@ public: assert(state.size() > id_); state[id_].store(TrackedState::alive, std::memory_order_relaxed); } - ~TIBase() + ~TIBase() override { using enum TrackedState; @@ -234,7 +239,7 @@ public: BEAST_EXPECT(b->use_count() == 1); for (int i = 0; i < 10; ++i) { - weak.push_back(b); + weak.emplace_back(b); BEAST_EXPECT(b->use_count() == 1); } BEAST_EXPECT(TIBase::getState(id) == alive); @@ -525,11 +530,11 @@ public: { if (isStrongDist(eng)) { - result.push_back(SharedIntrusive(toClone)); + result.emplace_back(SharedIntrusive(toClone)); } else { - result.push_back(WeakIntrusive(toClone)); + result.emplace_back(WeakIntrusive(toClone)); } } return result; @@ -576,7 +581,7 @@ public: toClone.resize(numThreads); auto strong = make_SharedIntrusive(); strong->tracingCallback_ = tracingCallback; - std::fill(toClone.begin(), toClone.end(), strong); + std::ranges::fill(toClone, strong); } // ------ Sync Point ------ @@ -656,7 +661,7 @@ public: auto numToCreate = toCreateDist(eng); result.reserve(numToCreate); for (int i = 0; i < numToCreate; ++i) - result.push_back(SharedIntrusive(toClone)); + result.emplace_back(SharedIntrusive(toClone)); return result; }; constexpr int loopIters = 2 * 1024; @@ -703,7 +708,7 @@ public: toClone.resize(numThreads); auto strong = make_SharedIntrusive(); strong->tracingCallback_ = tracingCallback; - std::fill(toClone.begin(), toClone.end(), strong); + std::ranges::fill(toClone, strong); } // ------ Sync Point ------ @@ -824,7 +829,7 @@ public: toLock.resize(numThreads); auto strong = make_SharedIntrusive(); strong->tracingCallback_ = tracingCallback; - std::fill(toLock.begin(), toLock.end(), strong); + std::ranges::fill(toLock, strong); } // ------ Sync Point ------ @@ -871,5 +876,4 @@ public: }; // namespace tests BEAST_DEFINE_TESTSUITE(IntrusiveShared, basics, xrpl); -} // namespace tests -} // namespace xrpl +} // namespace xrpl::tests diff --git a/src/test/basics/KeyCache_test.cpp b/src/test/basics/KeyCache_test.cpp index 55b275ae09..2370f87f5d 100644 --- a/src/test/basics/KeyCache_test.cpp +++ b/src/test/basics/KeyCache_test.cpp @@ -1,10 +1,13 @@ #include #include -#include +#include // IWYU pragma: keep #include +#include #include +#include + namespace xrpl { class KeyCache_test : public beast::unit_test::suite diff --git a/src/test/basics/Number_test.cpp b/src/test/basics/Number_test.cpp index 856b379533..cc7ccaa8c2 100644 --- a/src/test/basics/Number_test.cpp +++ b/src/test/basics/Number_test.cpp @@ -1,10 +1,18 @@ #include -#include +#include #include +#include #include #include +#include +#include +#include +#include +#include #include +#include +#include #include namespace xrpl { diff --git a/src/test/basics/PerfLog_test.cpp b/src/test/basics/PerfLog_test.cpp index 470a52d220..cd00b180e7 100644 --- a/src/test/basics/PerfLog_test.cpp +++ b/src/test/basics/PerfLog_test.cpp @@ -1,20 +1,39 @@ #include #include +#include #include #include -#include +#include #include +#include +#include #include #include +#include +#include #include -#include +#include +#include +#include +#include + +#include #include -#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include +#include +#include //------------------------------------------------------------------------------ @@ -86,7 +105,7 @@ class PerfLog_test : public beast::unit_test::suite perfLog(WithFile withFile) { perf::PerfLog::Setup const setup{ - withFile == WithFile::no ? "" : logFile(), logInterval()}; + .perfLog = withFile == WithFile::no ? "" : logFile(), .logInterval = logInterval()}; return perf::make_PerfLog(setup, app_, j_, [this]() { signalStop(); return; @@ -159,7 +178,7 @@ class PerfLog_test : public beast::unit_test::suite // Note that the longest durations should be at the front of the // vector since they were started first. - std::sort(currents.begin(), currents.end(), [](Cur const& lhs, Cur const& rhs) { + std::ranges::sort(currents, [](Cur const& lhs, Cur const& rhs) { if (lhs.dur != rhs.dur) return (rhs.dur < lhs.dur); return (lhs.name < rhs.name); diff --git a/src/test/basics/StringUtilities_test.cpp b/src/test/basics/StringUtilities_test.cpp index fb7cdb3d69..e566e43c9f 100644 --- a/src/test/basics/StringUtilities_test.cpp +++ b/src/test/basics/StringUtilities_test.cpp @@ -1,7 +1,9 @@ #include #include #include -#include +#include + +#include namespace xrpl { diff --git a/src/test/basics/TaggedCache_test.cpp b/src/test/basics/TaggedCache_test.cpp index 78dc25380b..9621719803 100644 --- a/src/test/basics/TaggedCache_test.cpp +++ b/src/test/basics/TaggedCache_test.cpp @@ -1,10 +1,13 @@ #include #include -#include +#include // IWYU pragma: keep #include +#include #include +#include + namespace xrpl { /* diff --git a/src/test/basics/Units_test.cpp b/src/test/basics/Units_test.cpp index 6bb7f400cc..8769a0d386 100644 --- a/src/test/basics/Units_test.cpp +++ b/src/test/basics/Units_test.cpp @@ -1,9 +1,15 @@ -#include +#include +#include +#include #include #include +#include -namespace xrpl { -namespace test { +#include +#include +#include + +namespace xrpl::test { class units_test : public beast::unit_test::suite { @@ -335,5 +341,4 @@ public: BEAST_DEFINE_TESTSUITE(units, basics, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/basics/XRPAmount_test.cpp b/src/test/basics/XRPAmount_test.cpp index ad81050558..b0e8213b73 100644 --- a/src/test/basics/XRPAmount_test.cpp +++ b/src/test/basics/XRPAmount_test.cpp @@ -1,6 +1,10 @@ -#include +#include +#include #include +#include +#include + namespace xrpl { class XRPAmount_test : public beast::unit_test::suite diff --git a/src/test/basics/base58_test.cpp b/src/test/basics/base58_test.cpp index 52d06b324d..948195b424 100644 --- a/src/test/basics/base58_test.cpp +++ b/src/test/basics/base58_test.cpp @@ -1,20 +1,31 @@ +#include +#include + +#include // IWYU pragma: keep + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #ifndef _MSC_VER -#include #include #include -#include -#include - #include #include #include #include #include -namespace xrpl { -namespace test { +namespace xrpl::test { namespace { [[nodiscard]] inline auto @@ -89,7 +100,7 @@ printAsChar(std::span a, std::span b) auto asString = [](std::span s) { std::string r; r.resize(s.size()); - std::copy(s.begin(), s.end(), r.begin()); + std::ranges::copy(s, r.begin()); return r; }; auto sa = asString(a); @@ -275,7 +286,7 @@ class base58_test : public beast::unit_test::suite b256Data.data(), b256Data.size(), tmpBuf.data(), tmpBuf.size()); BEAST_EXPECT(s.size()); b58Result[i] = outBuf.subspan(0, s.size()); - std::copy(s.begin(), s.end(), b58Result[i].begin()); + std::ranges::copy(s, b58Result[i].begin()); } } if (BEAST_EXPECT(b58Result[0].size() == b58Result[1].size())) @@ -304,7 +315,7 @@ class base58_test : public beast::unit_test::suite std::string const s = xrpl::b58_ref::detail::decodeBase58(st); BEAST_EXPECT(s.size()); b256Result[i] = outBuf.subspan(0, s.size()); - std::copy(s.begin(), s.end(), b256Result[i].begin()); + std::ranges::copy(s, b256Result[i].begin()); } } @@ -341,7 +352,7 @@ class base58_test : public beast::unit_test::suite xrpl::b58_ref::encodeBase58Token(tokType, b256Data.data(), b256Data.size()); BEAST_EXPECT(s.size()); b58Result[i] = outBuf.subspan(0, s.size()); - std::copy(s.begin(), s.end(), b58Result[i].begin()); + std::ranges::copy(s, b58Result[i].begin()); } } if (BEAST_EXPECT(b58Result[0].size() == b58Result[1].size())) @@ -370,7 +381,7 @@ class base58_test : public beast::unit_test::suite std::string const s = xrpl::b58_ref::decodeBase58Token(st, tokType); BEAST_EXPECT(s.size()); b256Result[i] = outBuf.subspan(0, s.size()); - std::copy(s.begin(), s.end(), b256Result[i].begin()); + std::ranges::copy(s, b256Result[i].begin()); } } @@ -423,6 +434,6 @@ class base58_test : public beast::unit_test::suite BEAST_DEFINE_TESTSUITE(base58, basics, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test + #endif // _MSC_VER diff --git a/src/test/basics/base_uint_test.cpp b/src/test/basics/base_uint_test.cpp index 139c635e5f..8148f3bdce 100644 --- a/src/test/basics/base_uint_test.cpp +++ b/src/test/basics/base_uint_test.cpp @@ -1,15 +1,25 @@ #include #include #include -#include +#include +#include -#include +#include +#include +#include #include +#include +#include +#include +#include +#include #include +#include +#include +#include -namespace xrpl { -namespace test { +namespace xrpl::test { // a non-hashing Hasher that just copies the bytes. // Used to test hash_append in base_uint @@ -40,8 +50,8 @@ struct nonhash struct base_uint_test : beast::unit_test::suite { using test96 = base_uint<96>; - static_assert(std::is_copy_constructible::value); - static_assert(std::is_copy_assignable::value); + static_assert(std::is_copy_constructible_v); + static_assert(std::is_copy_assignable_v); void testComparisons() @@ -112,8 +122,8 @@ struct base_uint_test : beast::unit_test::suite { testcase("base_uint: general purpose tests"); - static_assert(!std::is_constructible>::value); - static_assert(!std::is_assignable>::value); + static_assert(!std::is_constructible_v>); + static_assert(!std::is_assignable_v>); testComparisons(); @@ -345,5 +355,4 @@ struct base_uint_test : beast::unit_test::suite BEAST_DEFINE_TESTSUITE(base_uint, basics, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/basics/hardened_hash_test.cpp b/src/test/basics/hardened_hash_test.cpp index 3910e5e414..361a961312 100644 --- a/src/test/basics/hardened_hash_test.cpp +++ b/src/test/basics/hardened_hash_test.cpp @@ -1,14 +1,18 @@ #include -#include +#include #include +#include #include #include +#include +#include +#include +#include #include #include -namespace xrpl { -namespace detail { +namespace xrpl::detail { template class test_user_type_member @@ -50,8 +54,7 @@ public: } }; -} // namespace detail -} // namespace xrpl +} // namespace xrpl::detail //------------------------------------------------------------------------------ @@ -78,7 +81,7 @@ class unsigned_integer { private: static_assert( - std::is_integral::value && std::is_unsigned::value, + std::is_integral_v && std::is_unsigned_v, "UInt must be an unsigned integral type"); static_assert(Bits % (8 * sizeof(UInt)) == 0, "Bits must be a multiple of 8*sizeof(UInt)"); diff --git a/src/test/basics/join_test.cpp b/src/test/basics/join_test.cpp index 36d34eee04..c03a027971 100644 --- a/src/test/basics/join_test.cpp +++ b/src/test/basics/join_test.cpp @@ -1,10 +1,17 @@ #include +#include #include -#include +#include -namespace xrpl { -namespace test { +#include +#include +#include +#include +#include +#include + +namespace xrpl::test { struct join_test : beast::unit_test::suite { @@ -73,5 +80,4 @@ struct join_test : beast::unit_test::suite BEAST_DEFINE_TESTSUITE(join, basics, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/beast/IPEndpointCommon.h b/src/test/beast/IPEndpointCommon.h index 73cbe7d95b..2f839af5e2 100644 --- a/src/test/beast/IPEndpointCommon.h +++ b/src/test/beast/IPEndpointCommon.h @@ -1,8 +1,7 @@ #include #include -namespace beast { -namespace IP { +namespace beast::IP { inline Endpoint randomEP(bool v4 = true) @@ -39,5 +38,4 @@ randomEP(bool v4 = true) rand_int(1, UINT16_MAX)}; } -} // namespace IP -} // namespace beast +} // namespace beast::IP diff --git a/src/test/beast/IPEndpoint_test.cpp b/src/test/beast/IPEndpoint_test.cpp index ce01743896..759a3fe3ad 100644 --- a/src/test/beast/IPEndpoint_test.cpp +++ b/src/test/beast/IPEndpoint_test.cpp @@ -4,15 +4,25 @@ #include #include +#include +#include +#include #include -#include +#include -#include +#include #include +#include #include +#include -namespace beast { -namespace IP { +#include +#include +#include +#include +#include + +namespace beast::IP { //------------------------------------------------------------------------------ @@ -456,5 +466,4 @@ public: BEAST_DEFINE_TESTSUITE(IPEndpoint, beast, beast); -} // namespace IP -} // namespace beast +} // namespace beast::IP diff --git a/src/test/beast/LexicalCast_test.cpp b/src/test/beast/LexicalCast_test.cpp index 855fc42889..933d2e58d7 100644 --- a/src/test/beast/LexicalCast_test.cpp +++ b/src/test/beast/LexicalCast_test.cpp @@ -1,7 +1,12 @@ #include -#include +#include #include +#include +#include +#include +#include + namespace beast { class LexicalCast_test : public unit_test::suite diff --git a/src/test/beast/SemanticVersion_test.cpp b/src/test/beast/SemanticVersion_test.cpp index cae10497af..7e25e845fc 100644 --- a/src/test/beast/SemanticVersion_test.cpp +++ b/src/test/beast/SemanticVersion_test.cpp @@ -1,5 +1,7 @@ #include -#include +#include + +#include namespace beast { diff --git a/src/test/beast/aged_associative_container_test.cpp b/src/test/beast/aged_associative_container_test.cpp index c47b41478b..a979cdbb38 100644 --- a/src/test/beast/aged_associative_container_test.cpp +++ b/src/test/beast/aged_associative_container_test.cpp @@ -7,9 +7,18 @@ #include #include #include -#include +#include +#include +#include -#include +#include +#include +#include +#include +#include +#include +#include +#include #include #ifndef BEAST_AGED_UNORDERED_NO_ALLOC_DEFAULTCTOR @@ -36,6 +45,8 @@ public: template struct CompT { + CompT() = delete; + explicit CompT(int) { } @@ -51,7 +62,6 @@ public: } private: - CompT() = delete; std::less m_less; }; @@ -59,6 +69,8 @@ public: class HashT { public: + HashT() = delete; + explicit HashT(int) { } @@ -70,7 +82,6 @@ public: } private: - HashT() = delete; std::hash m_hash; }; @@ -78,6 +89,8 @@ public: struct EqualT { public: + EqualT() = delete; + explicit EqualT(int) { } @@ -89,7 +102,6 @@ public: } private: - EqualT() = delete; std::equal_to m_eq; }; @@ -148,7 +160,6 @@ public: { } #else - private: AllocT() = delete; #endif }; @@ -392,22 +403,22 @@ public: //-------------------------------------------------------------------------- template - typename std::enable_if::type + std::enable_if_t checkMapContents(Container& c, Values const& v); template - typename std::enable_if::type + std::enable_if_t checkMapContents(Container, Values const&) { } // unordered template - typename std::enable_if::type::is_unordered::value>::type + std::enable_if_t::type::is_unordered::value> checkUnorderedContentsRefRef(C&& c, Values const& v); template - typename std::enable_if::type::is_unordered::value>::type + std::enable_if_t::type::is_unordered::value> checkUnorderedContentsRefRef(C&&, Values const&) { } @@ -428,32 +439,32 @@ public: // ordered template - typename std::enable_if::type + std::enable_if_t testConstructEmpty(); // unordered template - typename std::enable_if::type + std::enable_if_t testConstructEmpty(); // ordered template - typename std::enable_if::type + std::enable_if_t testConstructRange(); // unordered template - typename std::enable_if::type + std::enable_if_t testConstructRange(); // ordered template - typename std::enable_if::type + std::enable_if_t testConstructInitList(); // unordered template - typename std::enable_if::type + std::enable_if_t testConstructInitList(); //-------------------------------------------------------------------------- @@ -470,11 +481,11 @@ public: // Unordered containers don't have reverse iterators template - typename std::enable_if::type + std::enable_if_t testReverseIterator(); template - typename std::enable_if::type + std::enable_if_t testReverseIterator() { } @@ -519,11 +530,11 @@ public: // map, unordered_map template - typename std::enable_if::type + std::enable_if_t testArrayCreate(); template - typename std::enable_if::type + std::enable_if_t testArrayCreate() { } @@ -563,11 +574,11 @@ public: // ordered template - typename std::enable_if::type + std::enable_if_t testCompare(); template - typename std::enable_if::type + std::enable_if_t testCompare() { } @@ -576,12 +587,12 @@ public: // ordered template - typename std::enable_if::type + std::enable_if_t testObservers(); // unordered template - typename std::enable_if::type + std::enable_if_t testObservers(); //-------------------------------------------------------------------------- @@ -604,7 +615,7 @@ public: // Check contents via at() and operator[] // map, unordered_map template -typename std::enable_if::type +std::enable_if_t aged_associative_container_test_base::checkMapContents(Container& c, Values const& v) { if (v.empty()) @@ -630,10 +641,10 @@ aged_associative_container_test_base::checkMapContents(Container& c, Values cons // unordered template -typename std::enable_if::type::is_unordered::value>::type +std::enable_if_t::type::is_unordered::value> aged_associative_container_test_base::checkUnorderedContentsRefRef(C&& c, Values const& v) { - using Cont = typename std::remove_reference::type; + using Cont = std::remove_reference_t; using Traits = TestTraits; using size_type = typename Cont::size_type; @@ -659,7 +670,7 @@ template void aged_associative_container_test_base::checkContentsRefRef(C&& c, Values const& v) { - using Cont = typename std::remove_reference::type; + using Cont = std::remove_reference_t; using size_type = typename Cont::size_type; BEAST_EXPECT(c.size() == v.size()); @@ -704,7 +715,7 @@ aged_associative_container_test_base::checkContents(Cont& c) // ordered template -typename std::enable_if::type +std::enable_if_t aged_associative_container_test_base::testConstructEmpty() { using Traits = TestTraits; @@ -740,7 +751,7 @@ aged_associative_container_test_base::testConstructEmpty() // unordered template -typename std::enable_if::type +std::enable_if_t aged_associative_container_test_base::testConstructEmpty() { using Traits = TestTraits; @@ -798,7 +809,7 @@ aged_associative_container_test_base::testConstructEmpty() // ordered template -typename std::enable_if::type +std::enable_if_t aged_associative_container_test_base::testConstructRange() { using Traits = TestTraits; @@ -845,7 +856,7 @@ aged_associative_container_test_base::testConstructRange() // unordered template -typename std::enable_if::type +std::enable_if_t aged_associative_container_test_base::testConstructRange() { using Traits = TestTraits; @@ -911,7 +922,7 @@ aged_associative_container_test_base::testConstructRange() // ordered template -typename std::enable_if::type +std::enable_if_t aged_associative_container_test_base::testConstructInitList() { using Traits = TestTraits; @@ -927,7 +938,7 @@ aged_associative_container_test_base::testConstructInitList() // unordered template -typename std::enable_if::type +std::enable_if_t aged_associative_container_test_base::testConstructInitList() { using Traits = TestTraits; @@ -1073,7 +1084,7 @@ aged_associative_container_test_base::testIterator() } template -typename std::enable_if::type +std::enable_if_t aged_associative_container_test_base::testReverseIterator() { using Traits = TestTraits; @@ -1349,7 +1360,7 @@ aged_associative_container_test_base::testChronological() // map, unordered_map template -typename std::enable_if::type +std::enable_if_t aged_associative_container_test_base::testArrayCreate() { using Traits = TestTraits; @@ -1632,7 +1643,7 @@ aged_associative_container_test_base::testRangeErase() // ordered template -typename std::enable_if::type +std::enable_if_t aged_associative_container_test_base::testCompare() { using Traits = TestTraits; @@ -1663,7 +1674,7 @@ aged_associative_container_test_base::testCompare() // ordered template -typename std::enable_if::type +std::enable_if_t aged_associative_container_test_base::testObservers() { using Traits = TestTraits; @@ -1681,7 +1692,7 @@ aged_associative_container_test_base::testObservers() // unordered template -typename std::enable_if::type +std::enable_if_t aged_associative_container_test_base::testObservers() { using Traits = TestTraits; @@ -1733,45 +1744,43 @@ public: using T = int; static_assert( - std::is_same, detail::aged_ordered_container>::value, + std::is_same_v, detail::aged_ordered_container>, "bad alias: aged_set"); static_assert( - std::is_same, detail::aged_ordered_container>:: - value, + std::is_same_v, detail::aged_ordered_container>, "bad alias: aged_multiset"); static_assert( - std::is_same, detail::aged_ordered_container>::value, + std::is_same_v, detail::aged_ordered_container>, "bad alias: aged_map"); static_assert( - std::is_same, detail::aged_ordered_container>:: - value, + std::is_same_v, detail::aged_ordered_container>, "bad alias: aged_multimap"); static_assert( - std::is_same< + std::is_same_v< aged_unordered_set, - detail::aged_unordered_container>::value, + detail::aged_unordered_container>, "bad alias: aged_unordered_set"); static_assert( - std::is_same< + std::is_same_v< aged_unordered_multiset, - detail::aged_unordered_container>::value, + detail::aged_unordered_container>, "bad alias: aged_unordered_multiset"); static_assert( - std::is_same< + std::is_same_v< aged_unordered_map, - detail::aged_unordered_container>::value, + detail::aged_unordered_container>, "bad alias: aged_unordered_map"); static_assert( - std::is_same< + std::is_same_v< aged_unordered_multimap, - detail::aged_unordered_container>::value, + detail::aged_unordered_container>, "bad alias: aged_unordered_multimap"); void diff --git a/src/test/beast/beast_CurrentThreadName_test.cpp b/src/test/beast/beast_CurrentThreadName_test.cpp index 2a365c6e9c..d3d3850d75 100644 --- a/src/test/beast/beast_CurrentThreadName_test.cpp +++ b/src/test/beast/beast_CurrentThreadName_test.cpp @@ -1,12 +1,17 @@ #include -#include +#include #include +#if BOOST_OS_LINUX +#include +#endif + +#include +#include #include -namespace xrpl { -namespace test { +namespace xrpl::test { class CurrentThreadName_test : public beast::unit_test::suite { @@ -100,5 +105,4 @@ public: BEAST_DEFINE_TESTSUITE(CurrentThreadName, beast, beast); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/beast/beast_Journal_test.cpp b/src/test/beast/beast_Journal_test.cpp index 35ab3640bd..7a96a1e2aa 100644 --- a/src/test/beast/beast_Journal_test.cpp +++ b/src/test/beast/beast_Journal_test.cpp @@ -1,6 +1,8 @@ -#include +#include #include +#include + namespace beast { class Journal_test : public unit_test::suite diff --git a/src/test/beast/beast_PropertyStream_test.cpp b/src/test/beast/beast_PropertyStream_test.cpp index 9e76749218..6709671a70 100644 --- a/src/test/beast/beast_PropertyStream_test.cpp +++ b/src/test/beast/beast_PropertyStream_test.cpp @@ -1,6 +1,8 @@ -#include +#include #include +#include + namespace beast { class PropertyStream_test : public unit_test::suite diff --git a/src/test/beast/beast_Zero_test.cpp b/src/test/beast/beast_Zero_test.cpp index c3dfbc8c4b..a509723773 100644 --- a/src/test/beast/beast_Zero_test.cpp +++ b/src/test/beast/beast_Zero_test.cpp @@ -1,4 +1,4 @@ -#include +#include #include namespace beast { diff --git a/src/test/beast/beast_abstract_clock_test.cpp b/src/test/beast/beast_abstract_clock_test.cpp index 43a210e128..c1a0c18839 100644 --- a/src/test/beast/beast_abstract_clock_test.cpp +++ b/src/test/beast/beast_abstract_clock_test.cpp @@ -2,9 +2,10 @@ #include #include -#include +#include -#include +#include +#include #include #include diff --git a/src/test/beast/beast_basic_seconds_clock_test.cpp b/src/test/beast/beast_basic_seconds_clock_test.cpp index ccdd76da20..b0d8ab5e92 100644 --- a/src/test/beast/beast_basic_seconds_clock_test.cpp +++ b/src/test/beast/beast_basic_seconds_clock_test.cpp @@ -1,5 +1,5 @@ #include -#include +#include namespace beast { diff --git a/src/test/beast/beast_io_latency_probe_test.cpp b/src/test/beast/beast_io_latency_probe_test.cpp index dfa18e6770..dfd20eced0 100644 --- a/src/test/beast/beast_io_latency_probe_test.cpp +++ b/src/test/beast/beast_io_latency_probe_test.cpp @@ -1,16 +1,22 @@ #include #include -#include +#include #include -#include -#include +#include // IWYU pragma: keep #include +#include +#include -#include -#include -#include -#include +#include +#include // IWYU pragma: keep +#include +#include +#include // IWYU pragma: keep +#include // IWYU pragma: keep +#include +#include +#include // IWYU pragma: keep #include using namespace std::chrono_literals; diff --git a/src/test/beast/define_print.cpp b/src/test/beast/define_print.cpp index 07b1422a54..e3da3e7fe1 100644 --- a/src/test/beast/define_print.cpp +++ b/src/test/beast/define_print.cpp @@ -5,13 +5,15 @@ #include #include #include +#include +#include +#include #include // Include this .cpp in your project to gain access to the printing suite -namespace beast { -namespace unit_test { +namespace beast::unit_test { /** A suite that prints the list of globally defined suites. */ class print_test : public suite @@ -42,5 +44,4 @@ public: BEAST_DEFINE_TESTSUITE_MANUAL(print, beast, beast); -} // namespace unit_test -} // namespace beast +} // namespace beast::unit_test diff --git a/src/test/beast/xxhasher_test.cpp b/src/test/beast/xxhasher_test.cpp index 868d522384..385ed0558a 100644 --- a/src/test/beast/xxhasher_test.cpp +++ b/src/test/beast/xxhasher_test.cpp @@ -1,5 +1,7 @@ #include -#include +#include + +#include namespace beast { diff --git a/src/test/conditions/PreimageSha256_test.cpp b/src/test/conditions/PreimageSha256_test.cpp index 374b6eb925..3e6ee24d57 100644 --- a/src/test/conditions/PreimageSha256_test.cpp +++ b/src/test/conditions/PreimageSha256_test.cpp @@ -1,19 +1,16 @@ #include #include #include -#include -#include +#include #include #include -#include -#include #include +#include #include #include -namespace xrpl { -namespace cryptoconditions { +namespace xrpl::cryptoconditions { class PreimageSha256_test : public beast::unit_test::suite { @@ -168,6 +165,4 @@ class PreimageSha256_test : public beast::unit_test::suite BEAST_DEFINE_TESTSUITE(PreimageSha256, conditions, xrpl); -} // namespace cryptoconditions - -} // namespace xrpl +} // namespace xrpl::cryptoconditions diff --git a/src/test/consensus/ByzantineFailureSim_test.cpp b/src/test/consensus/ByzantineFailureSim_test.cpp index f86ae556bf..19de5ac83c 100644 --- a/src/test/consensus/ByzantineFailureSim_test.cpp +++ b/src/test/consensus/ByzantineFailureSim_test.cpp @@ -1,11 +1,20 @@ #include +#include +#include +#include +#include +#include +#include -#include +#include -#include +#include -namespace xrpl { -namespace test { +#include +#include +#include + +namespace xrpl::test { class ByzantineFailureSim_test : public beast::unit_test::suite { @@ -77,5 +86,4 @@ class ByzantineFailureSim_test : public beast::unit_test::suite BEAST_DEFINE_TESTSUITE_MANUAL(ByzantineFailureSim, consensus, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/consensus/Consensus_test.cpp b/src/test/consensus/Consensus_test.cpp index 8b562454e3..c8be3f09f5 100644 --- a/src/test/consensus/Consensus_test.cpp +++ b/src/test/consensus/Consensus_test.cpp @@ -1,13 +1,35 @@ #include +#include +#include +#include +#include +#include +#include +#include +#include #include #include +#include +#include +#include -#include -#include +#include +#include +#include +#include +#include +#include -namespace xrpl { -namespace test { +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl::test { class Consensus_test : public beast::unit_test::suite { @@ -1028,7 +1050,7 @@ public: // Simulate clients submitting 1 tx every 5 seconds to a random // validator - Rate const rate{1, 5s}; + Rate const rate{.count = 1, .duration = 5s}; auto peerSelector = makeSelector( network.begin(), network.end(), std::vector(network.size(), 1.), sim.rng); auto txSubmitter = makeSubmitter( @@ -1421,5 +1443,4 @@ public: }; BEAST_DEFINE_TESTSUITE(Consensus, consensus, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/consensus/DistributedValidatorsSim_test.cpp b/src/test/consensus/DistributedValidatorsSim_test.cpp index f510e00628..70a7e59988 100644 --- a/src/test/consensus/DistributedValidatorsSim_test.cpp +++ b/src/test/consensus/DistributedValidatorsSim_test.cpp @@ -1,18 +1,26 @@ #include +#include +#include +#include +#include +#include +#include -#include - -#include -#include +#include #include +#include +#include #include +#include +#include +#include +#include #include #include -#include +#include -namespace xrpl { -namespace test { +namespace xrpl::test { /** In progress simulations for diversifying and distributing validators */ @@ -61,7 +69,7 @@ class DistributedValidators_test : public beast::unit_test::suite // Run for 10 minutes, submitting 100 tx/second std::chrono::nanoseconds const simDuration = 10min; std::chrono::nanoseconds const quiet = 10s; - Rate const rate{100, 1000ms}; + Rate const rate{.count = 100, .duration = 1000ms}; // Initialize timers HeartbeatTimer heart(sim.scheduler); @@ -157,7 +165,7 @@ class DistributedValidators_test : public beast::unit_test::suite // Run for 10 minutes, submitting 100 tx/second std::chrono::nanoseconds const simDuration = 10min; std::chrono::nanoseconds const quiet = 10s; - Rate const rate{100, 1000ms}; + Rate const rate{.count = 100, .duration = 1000ms}; // Initialize timers HeartbeatTimer heart(sim.scheduler); @@ -242,5 +250,4 @@ class DistributedValidators_test : public beast::unit_test::suite BEAST_DEFINE_TESTSUITE_MANUAL_PRIO(DistributedValidators, consensus, xrpl, 2); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/consensus/LedgerTiming_test.cpp b/src/test/consensus/LedgerTiming_test.cpp index 8313ffd0d4..749a2488b3 100644 --- a/src/test/consensus/LedgerTiming_test.cpp +++ b/src/test/consensus/LedgerTiming_test.cpp @@ -1,8 +1,12 @@ -#include +#include +#include #include -namespace xrpl { -namespace test { +#include +#include +#include + +namespace xrpl::test { class LedgerTiming_test : public beast::unit_test::suite { @@ -111,5 +115,4 @@ class LedgerTiming_test : public beast::unit_test::suite }; BEAST_DEFINE_TESTSUITE(LedgerTiming, consensus, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/consensus/LedgerTrie_test.cpp b/src/test/consensus/LedgerTrie_test.cpp index 0836b9c342..1ae6e99d80 100644 --- a/src/test/consensus/LedgerTrie_test.cpp +++ b/src/test/consensus/LedgerTrie_test.cpp @@ -2,12 +2,13 @@ #include -#include +#include +#include +#include #include -namespace xrpl { -namespace test { +namespace xrpl::test { class LedgerTrie_test : public beast::unit_test::suite { @@ -707,5 +708,4 @@ class LedgerTrie_test : public beast::unit_test::suite }; BEAST_DEFINE_TESTSUITE(LedgerTrie, consensus, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/consensus/NegativeUNL_test.cpp b/src/test/consensus/NegativeUNL_test.cpp index 0f97704755..220ca5ef12 100644 --- a/src/test/consensus/NegativeUNL_test.cpp +++ b/src/test/consensus/NegativeUNL_test.cpp @@ -1,15 +1,42 @@ -#include + +#include #include #include #include -#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include -namespace xrpl { -namespace test { +#include +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl::test { /* * This file implements the following negative UNL related tests: @@ -758,7 +785,13 @@ class NegativeUNLVoteInternal_test : public beast::unit_test::suite */ { // 1. no skip list - NetworkHistory history = {*this, {10, 0, false, false, 1}}; + NetworkHistory history = { + *this, + {.numNodes = 10, + .negUNLSize = 0, + .hasToDisable = false, + .hasToReEnable = false, + .numLedgers = 1}}; BEAST_EXPECT(history.goodHistory); if (history.goodHistory) { @@ -770,7 +803,13 @@ class NegativeUNLVoteInternal_test : public beast::unit_test::suite { // 2. short skip list - NetworkHistory history = {*this, {10, 0, false, false, 256 / 2}}; + NetworkHistory history = { + *this, + {.numNodes = 10, + .negUNLSize = 0, + .hasToDisable = false, + .hasToReEnable = false, + .numLedgers = 256 / 2}}; BEAST_EXPECT(history.goodHistory); if (history.goodHistory) { @@ -782,7 +821,13 @@ class NegativeUNLVoteInternal_test : public beast::unit_test::suite { // 3. local node not enough history - NetworkHistory history = {*this, {10, 0, false, false, 256 + 2}}; + NetworkHistory history = { + *this, + {.numNodes = 10, + .negUNLSize = 0, + .hasToDisable = false, + .hasToReEnable = false, + .numLedgers = 256 + 2}}; BEAST_EXPECT(history.goodHistory); if (history.goodHistory) { @@ -801,7 +846,13 @@ class NegativeUNLVoteInternal_test : public beast::unit_test::suite { // 4. a node double validated some seq // 5. local node had enough validations but on a wrong chain - NetworkHistory history = {*this, {10, 0, false, false, 256 + 2}}; + NetworkHistory history = { + *this, + {.numNodes = 10, + .negUNLSize = 0, + .hasToDisable = false, + .hasToReEnable = false, + .numLedgers = 256 + 2}}; // We need two chains for these tests bool const wrongChainSuccess = history.goodHistory; BEAST_EXPECT(wrongChainSuccess); @@ -862,7 +913,13 @@ class NegativeUNLVoteInternal_test : public beast::unit_test::suite { // 6. a good case - NetworkHistory history = {*this, {10, 0, false, false, 256 + 1}}; + NetworkHistory history = { + *this, + {.numNodes = 10, + .negUNLSize = 0, + .hasToDisable = false, + .hasToReEnable = false, + .numLedgers = 256 + 1}}; BEAST_EXPECT(history.goodHistory); if (history.goodHistory) { @@ -934,7 +991,13 @@ class NegativeUNLVoteInternal_test : public beast::unit_test::suite * 8. 2 new validators have bad scores, not in negUnl * 9. expired the new validators have bad scores, not in negUnl */ - NetworkHistory history = {*this, {35, 0, false, false, 0}}; + NetworkHistory history = { + *this, + {.numNodes = 35, + .negUNLSize = 0, + .hasToDisable = false, + .hasToReEnable = false, + .numLedgers = 0}}; hash_set negUnl_012; for (std::uint32_t i = 0; i < 3; ++i) @@ -1309,7 +1372,13 @@ class NegativeUNLVoteScoreTable_test : public beast::unit_test::suite { for (std::uint32_t sp = 0; sp < 4; ++sp) { - NetworkHistory history = {*this, {unlSize, 0, false, false, 256 + 2}}; + NetworkHistory history = { + *this, + {.numNodes = unlSize, + .negUNLSize = 0, + .hasToDisable = false, + .hasToReEnable = false, + .numLedgers = 256 + 2}}; BEAST_EXPECT(history.goodHistory); if (history.goodHistory) { @@ -1424,7 +1493,13 @@ class NegativeUNLVoteGoodScore_test : public beast::unit_test::suite { //== all good score, negativeUNL empty //-- txSet.size = 0 - NetworkHistory history = {*this, {51, 0, false, false, {}}}; + NetworkHistory history = { + *this, + {.numNodes = 51, + .negUNLSize = 0, + .hasToDisable = false, + .hasToReEnable = false, + .numLedgers = {}}}; BEAST_EXPECT(history.goodHistory); if (history.goodHistory) { @@ -1439,7 +1514,13 @@ class NegativeUNLVoteGoodScore_test : public beast::unit_test::suite { // all good score, negativeUNL not empty (use hasToDisable) //-- txSet.size = 1 - NetworkHistory history = {*this, {37, 0, true, false, {}}}; + NetworkHistory history = { + *this, + {.numNodes = 37, + .negUNLSize = 0, + .hasToDisable = true, + .hasToReEnable = false, + .numLedgers = {}}}; BEAST_EXPECT(history.goodHistory); if (history.goodHistory) { @@ -1469,7 +1550,13 @@ class NegativeUNLVoteOffline_test : public beast::unit_test::suite { //== 2 nodes offline, negativeUNL empty (use hasToReEnable) //-- txSet.size = 1 - NetworkHistory history = {*this, {29, 1, false, true, {}}}; + NetworkHistory history = { + *this, + {.numNodes = 29, + .negUNLSize = 1, + .hasToDisable = false, + .hasToReEnable = true, + .numLedgers = {}}}; BEAST_EXPECT(history.goodHistory); if (history.goodHistory) { @@ -1485,7 +1572,13 @@ class NegativeUNLVoteOffline_test : public beast::unit_test::suite { // 2 nodes offline, in negativeUNL //-- txSet.size = 0 - NetworkHistory history = {*this, {30, 1, true, false, {}}}; + NetworkHistory history = { + *this, + {.numNodes = 30, + .negUNLSize = 1, + .hasToDisable = true, + .hasToReEnable = false, + .numLedgers = {}}}; BEAST_EXPECT(history.goodHistory); if (history.goodHistory) { @@ -1519,7 +1612,13 @@ class NegativeUNLVoteMaxListed_test : public beast::unit_test::suite { // 2 nodes offline, not in negativeUNL, but maxListed //-- txSet.size = 0 - NetworkHistory history = {*this, {32, 8, true, true, {}}}; + NetworkHistory history = { + *this, + {.numNodes = 32, + .negUNLSize = 8, + .hasToDisable = true, + .hasToReEnable = true, + .numLedgers = {}}}; BEAST_EXPECT(history.goodHistory); if (history.goodHistory) { @@ -1550,7 +1649,13 @@ class NegativeUNLVoteRetiredValidator_test : public beast::unit_test::suite { //== 2 nodes offline including me, not in negativeUNL //-- txSet.size = 0 - NetworkHistory history = {*this, {35, 0, false, false, {}}}; + NetworkHistory history = { + *this, + {.numNodes = 35, + .negUNLSize = 0, + .hasToDisable = false, + .hasToReEnable = false, + .numLedgers = {}}}; BEAST_EXPECT(history.goodHistory); if (history.goodHistory) { @@ -1565,7 +1670,13 @@ class NegativeUNLVoteRetiredValidator_test : public beast::unit_test::suite { // 2 nodes offline, not in negativeUNL, but I'm not a validator //-- txSet.size = 0 - NetworkHistory history = {*this, {40, 0, false, false, {}}}; + NetworkHistory history = { + *this, + {.numNodes = 40, + .negUNLSize = 0, + .hasToDisable = false, + .hasToReEnable = false, + .numLedgers = {}}}; BEAST_EXPECT(history.goodHistory); if (history.goodHistory) { @@ -1580,7 +1691,13 @@ class NegativeUNLVoteRetiredValidator_test : public beast::unit_test::suite { //== 2 in negativeUNL, but not in unl, no other remove candidates //-- txSet.size = 1 - NetworkHistory history = {*this, {25, 2, false, false, {}}}; + NetworkHistory history = { + *this, + {.numNodes = 25, + .negUNLSize = 2, + .hasToDisable = false, + .hasToReEnable = false, + .numLedgers = {}}}; BEAST_EXPECT(history.goodHistory); if (history.goodHistory) { @@ -1614,7 +1731,13 @@ class NegativeUNLVoteNewValidator_test : public beast::unit_test::suite { //== 2 new validators have bad scores //-- txSet.size = 0 - NetworkHistory history = {*this, {15, 0, false, false, {}}}; + NetworkHistory history = { + *this, + {.numNodes = 15, + .negUNLSize = 0, + .hasToDisable = false, + .hasToReEnable = false, + .numLedgers = {}}}; BEAST_EXPECT(history.goodHistory); if (history.goodHistory) { @@ -1640,7 +1763,12 @@ class NegativeUNLVoteNewValidator_test : public beast::unit_test::suite //== 2 expired new validators have bad scores //-- txSet.size = 1 NetworkHistory history = { - *this, {21, 0, false, false, NegativeUNLVote::newValidatorDisableSkip * 2}}; + *this, + {.numNodes = 21, + .negUNLSize = 0, + .hasToDisable = false, + .hasToReEnable = false, + .numLedgers = NegativeUNLVote::newValidatorDisableSkip * 2}}; BEAST_EXPECT(history.goodHistory); if (history.goodHistory) { @@ -1858,5 +1986,4 @@ createTx(bool disabling, LedgerIndex seq, PublicKey const& txKey) return STTx(ttUNL_MODIFY, fill); } -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/consensus/RCLCensorshipDetector_test.cpp b/src/test/consensus/RCLCensorshipDetector_test.cpp index 4093ffe900..1ebf85c848 100644 --- a/src/test/consensus/RCLCensorshipDetector_test.cpp +++ b/src/test/consensus/RCLCensorshipDetector_test.cpp @@ -1,12 +1,12 @@ #include -#include +#include #include +#include #include -namespace xrpl { -namespace test { +namespace xrpl::test { class RCLCensorshipDetector_test : public beast::unit_test::suite { @@ -80,5 +80,4 @@ public: }; BEAST_DEFINE_TESTSUITE(RCLCensorshipDetector, consensus, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/consensus/ScaleFreeSim_test.cpp b/src/test/consensus/ScaleFreeSim_test.cpp index 2c8014d8e8..23952ab850 100644 --- a/src/test/consensus/ScaleFreeSim_test.cpp +++ b/src/test/consensus/ScaleFreeSim_test.cpp @@ -1,12 +1,21 @@ #include +#include +#include +#include #include +#include +#include -#include +#include -#include +#include -namespace xrpl { -namespace test { +#include +#include +#include +#include + +namespace xrpl::test { class ScaleFreeSim_test : public beast::unit_test::suite { @@ -58,7 +67,7 @@ class ScaleFreeSim_test : public beast::unit_test::suite // Run for 10 minutes, submitting 100 tx/second std::chrono::nanoseconds const simDuration = 10min; std::chrono::nanoseconds const quiet = 10s; - Rate const rate{100, 1000ms}; + Rate const rate{.count = 100, .duration = 1000ms}; // txs, start/stop/step, target auto peerSelector = makeSelector(network.begin(), network.end(), ranks, sim.rng); @@ -98,5 +107,4 @@ class ScaleFreeSim_test : public beast::unit_test::suite BEAST_DEFINE_TESTSUITE_MANUAL_PRIO(ScaleFreeSim, consensus, xrpl, 80); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/consensus/Validations_test.cpp b/src/test/consensus/Validations_test.cpp index fef6e79036..2cb8ae2f6c 100644 --- a/src/test/consensus/Validations_test.cpp +++ b/src/test/consensus/Validations_test.cpp @@ -1,17 +1,25 @@ #include +#include #include #include +#include +#include #include +#include #include -#include +#include +#include +#include +#include +#include +#include +#include #include -namespace xrpl { -namespace test { -namespace csf { +namespace xrpl::test::csf { class Validations_test : public beast::unit_test::suite { using clock_type = beast::abstract_clock const; @@ -1047,6 +1055,4 @@ class Validations_test : public beast::unit_test::suite }; BEAST_DEFINE_TESTSUITE(Validations, consensus, xrpl); -} // namespace csf -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::csf diff --git a/src/test/core/ClosureCounter_test.cpp b/src/test/core/ClosureCounter_test.cpp index 088b7b9ff9..d8e95a1dda 100644 --- a/src/test/core/ClosureCounter_test.cpp +++ b/src/test/core/ClosureCounter_test.cpp @@ -1,14 +1,16 @@ #include +#include -#include +#include +#include #include #include -#include +#include #include +#include -namespace xrpl { -namespace test { +namespace xrpl::test { //------------------------------------------------------------------------------ @@ -317,5 +319,4 @@ public: BEAST_DEFINE_TESTSUITE(ClosureCounter, core, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/core/Config_test.cpp b/src/test/core/Config_test.cpp index a7f44836d5..a3137b8e69 100644 --- a/src/test/core/Config_test.cpp +++ b/src/test/core/Config_test.cpp @@ -4,15 +4,31 @@ #include #include +#include #include #include #include -#include -#include +#include +#include // IWYU pragma: keep +#include +#include +#include +#include +#include +#include +#include #include +#include +#include #include +#include +#include +#include +#include +#include +#include namespace xrpl { namespace detail { @@ -81,7 +97,7 @@ time.apple.com time.nist.gov pool.ntp.org -# Where to find some other servers speaking the Ripple protocol. +# Where to find some other servers speaking the XRPL protocol. # [ips] r.ripple.com 51235 @@ -107,7 +123,7 @@ backend=sqlite } /** - Write a xrpld config file and remove when done. + Write an xrpld config file and remove when done. */ class FileCfgGuard : public xrpl::detail::FileDirGuard { @@ -249,9 +265,7 @@ public: return absolute(file()).string(); } - ~ValidatorsTxtGuard() - { - } + ~ValidatorsTxtGuard() = default; }; } // namespace detail @@ -507,7 +521,7 @@ port_wss_admin { c.loadFromString(boost::str(configTemplate % validationSeed % token)); } - catch (std::runtime_error& e) + catch (std::runtime_error const& e) { error = e.what(); } @@ -528,7 +542,7 @@ port_wss_admin main )xrpldConfig"); } - catch (std::runtime_error& e) + catch (std::runtime_error const& e) { error = e.what(); } @@ -541,7 +555,7 @@ main c.loadFromString(R"xrpldConfig( )xrpldConfig"); } - catch (std::runtime_error& e) + catch (std::runtime_error const& e) { error = e.what(); } @@ -556,7 +570,7 @@ main 255 )xrpldConfig"); } - catch (std::runtime_error& e) + catch (std::runtime_error const& e) { error = e.what(); } @@ -571,7 +585,7 @@ main 10000 )xrpldConfig"); } - catch (std::runtime_error& e) + catch (std::runtime_error const& e) { error = e.what(); } @@ -598,7 +612,7 @@ main Config c; c.loadFromString(boost::str(cc % missingPath)); } - catch (std::runtime_error& e) + catch (std::runtime_error const& e) { error = e.what(); } @@ -617,7 +631,7 @@ main Config c; c.loadFromString(boost::str(cc % invalidFile.string())); } - catch (std::runtime_error& e) + catch (std::runtime_error const& e) { error = e.what(); } @@ -725,7 +739,7 @@ trust-these-validators.gov c.loadFromString(toLoad); fail(); } - catch (std::runtime_error& e) + catch (std::runtime_error const& e) { error = e.what(); } @@ -754,7 +768,7 @@ value = 2 c.loadFromString(toLoad); fail(); } - catch (std::runtime_error& e) + catch (std::runtime_error const& e) { error = e.what(); } @@ -802,7 +816,7 @@ trust-these-validators.gov c.loadFromString(toLoad); fail(); } - catch (std::runtime_error& e) + catch (std::runtime_error const& e) { error = e.what(); } @@ -948,7 +962,7 @@ trust-these-validators.gov c.loadFromString(boost::str(cc % vtg.validatorsFile())); fail(); } - catch (std::runtime_error& e) + catch (std::runtime_error const& e) { error = e.what(); } @@ -974,7 +988,7 @@ trust-these-validators.gov Config c2; c2.loadFromString(boost::str(cc % vtg.validatorsFile())); } - catch (std::runtime_error& e) + catch (std::runtime_error const& e) { error = e.what(); } @@ -1262,20 +1276,41 @@ r.ripple.com:51235 }; std::array const tests = { - {{"password = aaaa\\#bbbb", "password", "aaaa#bbbb", false}, - {"password = aaaa#bbbb", "password", "aaaa", true}, - {"password = aaaa #bbbb", "password", "aaaa", true}, + {{.line = "password = aaaa\\#bbbb", + .field = "password", + .expect = "aaaa#bbbb", + .had_comment = false}, + {.line = "password = aaaa#bbbb", + .field = "password", + .expect = "aaaa", + .had_comment = true}, + {.line = "password = aaaa #bbbb", + .field = "password", + .expect = "aaaa", + .had_comment = true}, // since the value is all comment, this doesn't parse as k=v : - {"password = #aaaa #bbbb", "", "password =", true}, - {"password = aaaa\\# #bbbb", "password", "aaaa#", true}, - {"password = aaaa\\##bbbb", "password", "aaaa#", true}, - {"aaaa#bbbb", "", "aaaa", true}, - {"aaaa\\#bbbb", "", "aaaa#bbbb", false}, - {"aaaa\\##bbbb", "", "aaaa#", true}, - {"aaaa #bbbb", "", "aaaa", true}, - {"1 #comment", "", "1", true}, - {"#whole thing is comment", "", "", false}, - {" #whole comment with space", "", "", false}}}; + {.line = "password = #aaaa #bbbb", + .field = "", + .expect = "password =", + .had_comment = true}, + {.line = "password = aaaa\\# #bbbb", + .field = "password", + .expect = "aaaa#", + .had_comment = true}, + {.line = "password = aaaa\\##bbbb", + .field = "password", + .expect = "aaaa#", + .had_comment = true}, + {.line = "aaaa#bbbb", .field = "", .expect = "aaaa", .had_comment = true}, + {.line = "aaaa\\#bbbb", .field = "", .expect = "aaaa#bbbb", .had_comment = false}, + {.line = "aaaa\\##bbbb", .field = "", .expect = "aaaa#", .had_comment = true}, + {.line = "aaaa #bbbb", .field = "", .expect = "aaaa", .had_comment = true}, + {.line = "1 #comment", .field = "", .expect = "1", .had_comment = true}, + {.line = "#whole thing is comment", .field = "", .expect = "", .had_comment = false}, + {.line = " #whole comment with space", + .field = "", + .expect = "", + .had_comment = false}}}; for (auto const& t : tests) { @@ -1451,7 +1486,7 @@ r.ripple.com:51235 fail(); } } - catch (std::runtime_error&) + catch (std::runtime_error const&) { if (!shouldPass) { @@ -1477,7 +1512,7 @@ r.ripple.com:51235 c.loadFromString("[overlay]\nmax_unknown_time=" + value); return c.MAX_UNKNOWN_TIME; } - catch (std::runtime_error&) + catch (std::runtime_error const&) { return {}; } @@ -1511,7 +1546,7 @@ r.ripple.com:51235 c.loadFromString("[overlay]\nmax_diverged_time=" + value); return c.MAX_DIVERGED_TIME; } - catch (std::runtime_error&) + catch (std::runtime_error const&) { return {}; } diff --git a/src/test/core/Coroutine_test.cpp b/src/test/core/Coroutine_test.cpp index 4cfb86f931..38bafe08fd 100644 --- a/src/test/core/Coroutine_test.cpp +++ b/src/test/core/Coroutine_test.cpp @@ -1,12 +1,21 @@ -#include +#include +#include + +#include + +#include +#include +#include #include +#include #include +#include +#include #include -namespace xrpl { -namespace test { +namespace xrpl::test { class Coroutine_test : public beast::unit_test::suite { @@ -165,5 +174,4 @@ public: BEAST_DEFINE_TESTSUITE(Coroutine, core, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/core/JobQueue_test.cpp b/src/test/core/JobQueue_test.cpp index 13142c299f..8163c7b057 100644 --- a/src/test/core/JobQueue_test.cpp +++ b/src/test/core/JobQueue_test.cpp @@ -1,10 +1,13 @@ #include -#include +#include +#include #include -namespace xrpl { -namespace test { +#include +#include + +namespace xrpl::test { //------------------------------------------------------------------------------ @@ -138,5 +141,4 @@ public: BEAST_DEFINE_TESTSUITE(JobQueue, core, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/core/SociDB_test.cpp b/src/test/core/SociDB_test.cpp index c58c34756a..29aeca7ce5 100644 --- a/src/test/core/SociDB_test.cpp +++ b/src/test/core/SociDB_test.cpp @@ -2,12 +2,28 @@ #include #include +#include #include -#include -#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include #include +#include +#include namespace xrpl { class SociDB_test final : public TestSuite @@ -64,7 +80,7 @@ public: { } } - ~SociDB_test() + ~SociDB_test() override { try { @@ -113,10 +129,8 @@ public: for (int i = 0; i < stringResult.size(); ++i) { auto si = std::distance( - stringData.begin(), - std::find(stringData.begin(), stringData.end(), stringResult[i])); - auto ii = std::distance( - intData.begin(), std::find(intData.begin(), intData.end(), intResult[i])); + stringData.begin(), std::ranges::find(stringData, stringResult[i])); + auto ii = std::distance(intData.begin(), std::ranges::find(intData, intResult[i])); BEAST_EXPECT(si == ii && si < stringResult.size()); } }; diff --git a/src/test/core/Workers_test.cpp b/src/test/core/Workers_test.cpp index 6631cff1c4..84ff85f4ec 100644 --- a/src/test/core/Workers_test.cpp +++ b/src/test/core/Workers_test.cpp @@ -1,4 +1,5 @@ -#include +#include +#include #include #include #include diff --git a/src/test/csf/BasicNetwork.h b/src/test/csf/BasicNetwork.h index 85c77ac47d..63d85c3070 100644 --- a/src/test/csf/BasicNetwork.h +++ b/src/test/csf/BasicNetwork.h @@ -3,9 +3,7 @@ #include #include -namespace xrpl { -namespace test { -namespace csf { +namespace xrpl::test::csf { /** Peer to peer network simulator. The network is formed from a set of Peer objects representing @@ -74,7 +72,7 @@ class BasicNetwork { bool inbound = false; duration delay{}; - time_point established{}; + time_point established; link_type() = default; link_type(bool inbound_, duration delay_, time_point established_) : inbound(inbound_), delay(delay_), established(established_) @@ -224,6 +222,4 @@ BasicNetwork::send(Peer const& from, Peer const& to, Function&& f) }); } -} // namespace csf -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::csf diff --git a/src/test/csf/BasicNetwork_test.cpp b/src/test/csf/BasicNetwork_test.cpp index eee16c2ce1..d81638c214 100644 --- a/src/test/csf/BasicNetwork_test.cpp +++ b/src/test/csf/BasicNetwork_test.cpp @@ -1,13 +1,12 @@ #include #include -#include +#include #include #include -namespace xrpl { -namespace test { +namespace xrpl::test { class BasicNetwork_test : public beast::unit_test::suite { @@ -132,5 +131,4 @@ public: BEAST_DEFINE_TESTSUITE(BasicNetwork, csf, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/csf/CollectorRef.h b/src/test/csf/CollectorRef.h index 735a42ab16..23baa020b8 100644 --- a/src/test/csf/CollectorRef.h +++ b/src/test/csf/CollectorRef.h @@ -3,9 +3,7 @@ #include #include -namespace xrpl { -namespace test { -namespace csf { +namespace xrpl::test::csf { /** Holds a type-erased reference to an arbitrary collector. @@ -139,31 +137,31 @@ class CollectorRef Any& operator=(Any&&) = default; - virtual void + void on(PeerID node, tp when, Share const& e) override { t_.on(node, when, e); } - virtual void + void on(PeerID node, tp when, Share const& e) override { t_.on(node, when, e); } - virtual void + void on(PeerID node, tp when, Share const& e) override { t_.on(node, when, e); } - virtual void + void on(PeerID node, tp when, Share const& e) override { t_.on(node, when, e); } - virtual void + void on(PeerID node, tp when, Share const& e) override { t_.on(node, when, e); @@ -175,25 +173,25 @@ class CollectorRef t_.on(node, when, e); } - virtual void + void on(PeerID node, tp when, Receive const& e) override { t_.on(node, when, e); } - virtual void + void on(PeerID node, tp when, Receive const& e) override { t_.on(node, when, e); } - virtual void + void on(PeerID node, tp when, Receive const& e) override { t_.on(node, when, e); } - virtual void + void on(PeerID node, tp when, Receive const& e) override { t_.on(node, when, e); @@ -205,61 +203,61 @@ class CollectorRef t_.on(node, when, e); } - virtual void + void on(PeerID node, tp when, Relay const& e) override { t_.on(node, when, e); } - virtual void + void on(PeerID node, tp when, Relay const& e) override { t_.on(node, when, e); } - virtual void + void on(PeerID node, tp when, Relay const& e) override { t_.on(node, when, e); } - virtual void + void on(PeerID node, tp when, Relay const& e) override { t_.on(node, when, e); } - virtual void + void on(PeerID node, tp when, SubmitTx const& e) override { t_.on(node, when, e); } - virtual void + void on(PeerID node, tp when, StartRound const& e) override { t_.on(node, when, e); } - virtual void + void on(PeerID node, tp when, CloseLedger const& e) override { t_.on(node, when, e); } - virtual void + void on(PeerID node, tp when, AcceptLedger const& e) override { t_.on(node, when, e); } - virtual void + void on(PeerID node, tp when, WrongPrevLedger const& e) override { t_.on(node, when, e); } - virtual void + void on(PeerID node, tp when, FullyValidateLedger const& e) override { t_.on(node, when, e); @@ -324,6 +322,4 @@ public: } }; -} // namespace csf -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::csf diff --git a/src/test/csf/Digraph.h b/src/test/csf/Digraph.h index 66ef73390a..a5678cc539 100644 --- a/src/test/csf/Digraph.h +++ b/src/test/csf/Digraph.h @@ -18,8 +18,7 @@ struct NoEdgeData } // namespace detail -namespace test { -namespace csf { +namespace test::csf { /** Directed graph @@ -225,6 +224,6 @@ public: } }; -} // namespace csf -} // namespace test +} // namespace test::csf + } // namespace xrpl diff --git a/src/test/csf/Digraph_test.cpp b/src/test/csf/Digraph_test.cpp index 1c34bbcfec..8f5a0fea1b 100644 --- a/src/test/csf/Digraph_test.cpp +++ b/src/test/csf/Digraph_test.cpp @@ -1,12 +1,13 @@ #include -#include +#include +#include #include +#include #include -namespace xrpl { -namespace test { +namespace xrpl::test { class Digraph_test : public beast::unit_test::suite { @@ -77,5 +78,4 @@ public: BEAST_DEFINE_TESTSUITE(Digraph, csf, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/csf/Histogram.h b/src/test/csf/Histogram.h index cbc2d42d6c..6eef1b08c1 100644 --- a/src/test/csf/Histogram.h +++ b/src/test/csf/Histogram.h @@ -6,9 +6,7 @@ #include #include -namespace xrpl { -namespace test { -namespace csf { +namespace xrpl::test::csf { /** Basic histogram. @@ -108,6 +106,4 @@ public: } }; -} // namespace csf -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::csf diff --git a/src/test/csf/Histogram_test.cpp b/src/test/csf/Histogram_test.cpp index b0a8d8490d..6de04ad1ab 100644 --- a/src/test/csf/Histogram_test.cpp +++ b/src/test/csf/Histogram_test.cpp @@ -1,9 +1,8 @@ #include -#include +#include -namespace xrpl { -namespace test { +namespace xrpl::test { class Histogram_test : public beast::unit_test::suite { @@ -64,5 +63,4 @@ public: BEAST_DEFINE_TESTSUITE(Histogram, csf, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/csf/Peer.h b/src/test/csf/Peer.h index fb1238990e..5cf2757ab1 100644 --- a/src/test/csf/Peer.h +++ b/src/test/csf/Peer.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -19,9 +20,7 @@ #include -namespace xrpl { -namespace test { -namespace csf { +namespace xrpl::test::csf { namespace bc = boost::container; @@ -62,8 +61,8 @@ struct Peer return proposal_.getJson(); } - std::string - render() const + static std::string + render() { return ""; } @@ -295,9 +294,13 @@ struct Peer using namespace std::chrono_literals; if (when == 0ns) + { what(); + } else + { scheduler.in(when, std::forward(what)); + } } // Issue a new event to the collectors @@ -340,8 +343,10 @@ struct Peer trusts(PeerID const& oId) { for (auto const p : trustGraph.trustedPeers(this)) + { if (p->id == oId) return true; + } return false; } @@ -486,7 +491,7 @@ struct Peer Result onClose(Ledger const& prevLedger, NetClock::time_point closeTime, ConsensusMode mode) { - issue(CloseLedger{prevLedger, openTxs}); + issue(CloseLedger{.prevLedger = prevLedger, .txs = openTxs}); return Result( TxSet{openTxs}, @@ -526,15 +531,14 @@ struct Peer prevLedger, acceptedTxs.txs(), closeResolution, result.position.closeTime()); ledgers[newLedger.id()] = newLedger; - issue(AcceptLedger{newLedger, lastClosedLedger}); + issue(AcceptLedger{.ledger = newLedger, .prior = lastClosedLedger}); prevProposers = result.proposers; prevRoundTime = result.roundTime.read(); lastClosedLedger = newLedger; - auto const it = std::remove_if(openTxs.begin(), openTxs.end(), [&](Tx const& tx) { - return acceptedTxs.exists(tx.id()); - }); - openTxs.erase(it, openTxs.end()); + auto const removed = std::ranges::remove_if( + openTxs, [&](Tx const& tx) { return acceptedTxs.exists(tx.id()); }); + openTxs.erase(removed.begin(), removed.end()); // Only send validation if the new ledger is compatible with our // fully validated ledger @@ -588,7 +592,7 @@ struct Peer if (netLgr != ledgerID) { JLOG(j.trace()) << Json::Compact(validations.getJsonTrie()); - issue(WrongPrevLedger{ledgerID, netLgr}); + issue(WrongPrevLedger{.wrong = ledgerID, .right = netLgr}); } return netLgr; @@ -661,7 +665,7 @@ struct Peer quorum = static_cast(std::ceil(numTrustedPeers * 0.8)); if (count >= quorum && ledger.isAncestor(fullyValidatedLedger)) { - issue(FullyValidateLedger{ledger, fullyValidatedLedger}); + issue(FullyValidateLedger{.ledger = ledger, .prior = fullyValidatedLedger}); fullyValidatedLedger = ledger; } } @@ -750,7 +754,7 @@ struct Peer // TODO: This always suppresses relay of peer positions already seen // Should it allow forwarding if for a recent ledger ? auto& dest = peerPositions[p.prevLedger()]; - if (std::find(dest.begin(), dest.end(), p) != dest.end()) + if (std::ranges::find(dest, p) != dest.end()) return false; dest.push_back(p); @@ -774,7 +778,7 @@ struct Peer { // Ignore and suppress relay of transactions already in last ledger TxSetType const& lastClosedTxs = lastClosedLedger.txs(); - if (lastClosedTxs.find(tx) != lastClosedTxs.end()) + if (lastClosedTxs.contains(tx)) return false; // only relay if it was new to our open ledger @@ -830,8 +834,8 @@ struct Peer { } - bool - validating() const + static bool + validating() { // does not matter return false; @@ -871,7 +875,7 @@ struct Peer if (bestLCL == Ledger::ID{0}) bestLCL = lastClosedLedger.id(); - issue(StartRound{bestLCL, lastClosedLedger}); + issue(StartRound{.bestLedger = bestLCL, .prevLedger = lastClosedLedger}); // Not yet modeling dynamic UNL. hash_set const nowUntrusted; @@ -941,6 +945,4 @@ struct Peer } }; -} // namespace csf -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::csf diff --git a/src/test/csf/PeerGroup.h b/src/test/csf/PeerGroup.h index 5df6de84a6..6503ac62df 100644 --- a/src/test/csf/PeerGroup.h +++ b/src/test/csf/PeerGroup.h @@ -6,9 +6,7 @@ #include #include -namespace xrpl { -namespace test { -namespace csf { +namespace xrpl::test::csf { /** A group of simulation Peers @@ -35,11 +33,11 @@ public: } PeerGroup(std::vector&& peers) : peers_{std::move(peers)} { - std::sort(peers_.begin(), peers_.end()); + std::ranges::sort(peers_); } PeerGroup(std::vector const& peers) : peers_{peers} { - std::sort(peers_.begin(), peers_.end()); + std::ranges::sort(peers_); } PeerGroup(std::set const& peers) : peers_{peers.begin(), peers.end()} @@ -79,15 +77,14 @@ public: bool contains(Peer const* p) { - return std::find(peers_.begin(), peers_.end(), p) != peers_.end(); + return std::ranges::find(peers_, p) != peers_.end(); } bool contains(PeerID id) { - return std::find_if(peers_.begin(), peers_.end(), [id](Peer const* p) { - return p->id == id; - }) != peers_.end(); + return std::ranges::find_if(peers_, [id](Peer const* p) { return p->id == id; }) != + peers_.end(); } std::size_t @@ -215,12 +212,7 @@ public: operator+(PeerGroup const& a, PeerGroup const& b) { PeerGroup res; - std::set_union( - a.peers_.begin(), - a.peers_.end(), - b.peers_.begin(), - b.peers_.end(), - std::back_inserter(res.peers_)); + std::ranges::set_union(a.peers_, b.peers_, std::back_inserter(res.peers_)); return res; } @@ -230,12 +222,7 @@ public: { PeerGroup res; - std::set_difference( - a.peers_.begin(), - a.peers_.end(), - b.peers_.begin(), - b.peers_.end(), - std::back_inserter(res.peers_)); + std::ranges::set_difference(a.peers_, b.peers_, std::back_inserter(res.peers_)); return res; } @@ -346,6 +333,4 @@ randomRankedConnect( } } -} // namespace csf -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::csf diff --git a/src/test/csf/Proposal.h b/src/test/csf/Proposal.h index 641fd2010a..06486daed3 100644 --- a/src/test/csf/Proposal.h +++ b/src/test/csf/Proposal.h @@ -6,14 +6,10 @@ #include -namespace xrpl { -namespace test { -namespace csf { +namespace xrpl::test::csf { /** Proposal is a position taken in the consensus process and is represented directly from the generic types. */ using Proposal = ConsensusProposal; -} // namespace csf -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::csf diff --git a/src/test/csf/README.md b/src/test/csf/README.md index 30d5abb042..081c9807d7 100644 --- a/src/test/csf/README.md +++ b/src/test/csf/README.md @@ -2,7 +2,7 @@ The Consensus Simulation Framework is a set of software components for describing, running and analyzing simulations of the consensus algorithm in a -controlled manner. It is also used to unit test the generic Ripple consensus +controlled manner. It is also used to unit test the generic XRPL consensus algorithm implementation. The framework is in its early stages, so the design and supported features are subject to change. diff --git a/src/test/csf/Scheduler.h b/src/test/csf/Scheduler.h index 61ec8f62ff..82753124d9 100644 --- a/src/test/csf/Scheduler.h +++ b/src/test/csf/Scheduler.h @@ -9,9 +9,7 @@ #include #include -namespace xrpl { -namespace test { -namespace csf { +namespace xrpl::test::csf { /** Simulated discrete-event scheduler. @@ -265,7 +263,7 @@ inline Scheduler::queue_type::~queue_type() auto e = &*iter; ++iter; e->~event(); - alloc_->deallocate(e, sizeof(e)); + alloc_->deallocate(e, sizeof(e)); // NOLINT(bugprone-sizeof-expression) } } @@ -381,8 +379,10 @@ Scheduler::step() if (!step_one()) return false; for (;;) + { if (!step_one()) break; + } return true; } @@ -427,6 +427,4 @@ Scheduler::step_for(std::chrono::duration const& amount) return step_until(now() + amount); } -} // namespace csf -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::csf diff --git a/src/test/csf/Scheduler_test.cpp b/src/test/csf/Scheduler_test.cpp index a0b57dd87f..6b14a771d3 100644 --- a/src/test/csf/Scheduler_test.cpp +++ b/src/test/csf/Scheduler_test.cpp @@ -1,11 +1,10 @@ #include -#include +#include #include -namespace xrpl { -namespace test { +namespace xrpl::test { class Scheduler_test : public beast::unit_test::suite { @@ -66,5 +65,4 @@ public: BEAST_DEFINE_TESTSUITE(Scheduler, csf, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/csf/Sim.h b/src/test/csf/Sim.h index e5f64cae1f..ca85d79f47 100644 --- a/src/test/csf/Sim.h +++ b/src/test/csf/Sim.h @@ -13,9 +13,7 @@ #include #include -namespace xrpl { -namespace test { -namespace csf { +namespace xrpl::test::csf { /** Sink that prepends simulation time to messages */ class BasicSink : public beast::Journal::Sink @@ -151,6 +149,4 @@ public: branches() const; }; -} // namespace csf -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::csf diff --git a/src/test/csf/SimTime.h b/src/test/csf/SimTime.h index 2131de9b41..662f3fe19c 100644 --- a/src/test/csf/SimTime.h +++ b/src/test/csf/SimTime.h @@ -4,9 +4,7 @@ #include -namespace xrpl { -namespace test { -namespace csf { +namespace xrpl::test::csf { using RealClock = std::chrono::system_clock; using RealDuration = RealClock::duration; @@ -16,6 +14,4 @@ using SimClock = beast::manual_clock; using SimDuration = typename SimClock::duration; using SimTime = typename SimClock::time_point; -} // namespace csf -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::csf diff --git a/src/test/csf/TrustGraph.h b/src/test/csf/TrustGraph.h index 3f1fcae0c1..096104bc15 100644 --- a/src/test/csf/TrustGraph.h +++ b/src/test/csf/TrustGraph.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -9,9 +10,7 @@ #include #include -namespace xrpl { -namespace test { -namespace csf { +namespace xrpl::test::csf { /** Trust graph @@ -146,6 +145,4 @@ public: } }; -} // namespace csf -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::csf diff --git a/src/test/csf/Tx.h b/src/test/csf/Tx.h index 81393f7bda..8e8f6ea7e4 100644 --- a/src/test/csf/Tx.h +++ b/src/test/csf/Tx.h @@ -12,10 +12,9 @@ #include #include #include +#include -namespace xrpl { -namespace test { -namespace csf { +namespace xrpl::test::csf { //! A single transaction class Tx @@ -96,7 +95,7 @@ public: }; TxSet() = default; - TxSet(TxSetType const& s) : txs_{s}, id_{calcID(txs_)} + TxSet(TxSetType s) : txs_{std::move(s)}, id_{calcID(txs_)} { } @@ -183,9 +182,13 @@ operator<<(std::ostream& o, boost::container::flat_set const& ts) for (auto const& t : ts) { if (do_comma) + { o << ", "; + } else + { do_comma = true; + } o << t; } o << " }"; @@ -208,6 +211,4 @@ hash_append(Hasher& h, Tx const& tx) hash_append(h, tx.id()); } -} // namespace csf -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::csf diff --git a/src/test/csf/Validation.h b/src/test/csf/Validation.h index 5ebbcf3ae5..2adf64196b 100644 --- a/src/test/csf/Validation.h +++ b/src/test/csf/Validation.h @@ -8,9 +8,7 @@ #include #include -namespace xrpl { -namespace test { -namespace csf { +namespace xrpl::test::csf { struct PeerIDTag; //< Uniquely identifies a peer @@ -58,7 +56,7 @@ public: , seq_{seq} , signTime_{sign} , seenTime_{seen} - , key_{key} + , key_{std::move(key)} , nodeID_{nodeID} , full_{full} , loadFee_{loadFee} @@ -129,7 +127,7 @@ public: Validation const& unwrap() const { - // For the rippled implementation in which RCLValidation wraps + // For the xrpld implementation in which RCLValidation wraps // STValidation, the csf::Validation has no more specific type it // wraps, so csf::Validation unwraps to itself return *this; @@ -172,6 +170,4 @@ public: } }; -} // namespace csf -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::csf diff --git a/src/test/csf/collectors.h b/src/test/csf/collectors.h index cfe40330ff..8da86cc287 100644 --- a/src/test/csf/collectors.h +++ b/src/test/csf/collectors.h @@ -11,9 +11,7 @@ #include #include -namespace xrpl { -namespace test { -namespace csf { +namespace xrpl::test::csf { // A collector is any class that implements // @@ -134,7 +132,9 @@ struct SimDurationCollector init = true; } else + { stop = when; + } } }; @@ -631,7 +631,7 @@ struct JumpCollector { // Not a direct child -> parent switch if (e.ledger.parentID() != e.prior.id()) - closeJumps.emplace_back(Jump{who, when, e.prior, e.ledger}); + closeJumps.emplace_back(Jump{.id = who, .when = when, .from = e.prior, .to = e.ledger}); } void @@ -639,10 +639,11 @@ struct JumpCollector { // Not a direct child -> parent switch if (e.ledger.parentID() != e.prior.id()) - fullyValidatedJumps.emplace_back(Jump{who, when, e.prior, e.ledger}); + { + fullyValidatedJumps.emplace_back( + Jump{.id = who, .when = when, .from = e.prior, .to = e.ledger}); + } } }; -} // namespace csf -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::csf diff --git a/src/test/csf/events.h b/src/test/csf/events.h index 2aae22d125..ea163d3a80 100644 --- a/src/test/csf/events.h +++ b/src/test/csf/events.h @@ -7,9 +7,7 @@ #include -namespace xrpl { -namespace test { -namespace csf { +namespace xrpl::test::csf { // Events are emitted by peers at a variety of points during the simulation. // Each event is emitted by a particular peer at a particular time. Collectors @@ -126,6 +124,4 @@ struct FullyValidateLedger Ledger prior; }; -} // namespace csf -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::csf diff --git a/src/test/csf/impl/Sim.cpp b/src/test/csf/impl/Sim.cpp index a775dd30ff..8cf6869522 100644 --- a/src/test/csf/impl/Sim.cpp +++ b/src/test/csf/impl/Sim.cpp @@ -1,8 +1,14 @@ #include -namespace xrpl { -namespace test { -namespace csf { +#include +#include + +#include +#include +#include +#include + +namespace xrpl::test::csf { void Sim::run(int ledgers) @@ -38,7 +44,7 @@ Sim::synchronized(PeerGroup const& g) if (g.size() < 1) return true; Peer const* ref = g[0]; - return std::all_of(g.begin(), g.end(), [&ref](Peer const* p) { + return std::ranges::all_of(g, [&ref](Peer const* p) { return p->lastClosedLedger.id() == ref->lastClosedLedger.id() && p->fullyValidatedLedger.id() == ref->fullyValidatedLedger.id(); }); @@ -61,6 +67,4 @@ Sim::branches(PeerGroup const& g) const return oracle.branches(ledgers); } -} // namespace csf -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::csf diff --git a/src/test/csf/impl/ledgers.cpp b/src/test/csf/impl/ledgers.cpp index 9b0a4e3973..a4203b2a15 100644 --- a/src/test/csf/impl/ledgers.cpp +++ b/src/test/csf/impl/ledgers.cpp @@ -1,10 +1,19 @@ #include -#include +#include -namespace xrpl { -namespace test { -namespace csf { +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace xrpl::test::csf { Ledger::Instance const Ledger::genesis; @@ -155,6 +164,4 @@ LedgerOracle::branches(std::set const& ledgers) // The size of tips is the number of branches return tips.size(); } -} // namespace csf -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::csf diff --git a/src/test/csf/ledgers.h b/src/test/csf/ledgers.h index 67a7427af6..3869fb7bd4 100644 --- a/src/test/csf/ledgers.h +++ b/src/test/csf/ledgers.h @@ -14,9 +14,7 @@ #include #include -namespace xrpl { -namespace test { -namespace csf { +namespace xrpl::test::csf { /** A ledger is a set of observed transactions and a sequence number identifying the ledger. @@ -59,9 +57,7 @@ private: // ID by the oracle struct Instance { - Instance() - { - } + Instance() = default; // Sequence number Seq seq{0}; @@ -332,6 +328,4 @@ struct LedgerHistoryHelper } }; -} // namespace csf -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::csf diff --git a/src/test/csf/random.h b/src/test/csf/random.h index f3ecca1dbc..a5c54ec9bf 100644 --- a/src/test/csf/random.h +++ b/src/test/csf/random.h @@ -3,9 +3,7 @@ #include #include -namespace xrpl { -namespace test { -namespace csf { +namespace xrpl::test::csf { /** Return a randomly shuffled copy of vector based on weights w. @@ -76,7 +74,7 @@ public: { using tag = typename std::iterator_traits::iterator_category; static_assert( - std::is_same::value, + std::is_same_v, "Selector only supports random access iterators."); // TODO: Allow for forward iterators } @@ -111,7 +109,7 @@ public: } template - inline double + double operator()(Generator&) { return t_; @@ -140,7 +138,7 @@ public: } template - inline double + double operator()(Generator& g) { // use inverse transform of CDF to sample @@ -149,6 +147,4 @@ public: } }; -} // namespace csf -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::csf diff --git a/src/test/csf/submitters.h b/src/test/csf/submitters.h index ae81b3e89c..be45b4ea2a 100644 --- a/src/test/csf/submitters.h +++ b/src/test/csf/submitters.h @@ -7,9 +7,7 @@ #include -namespace xrpl { -namespace test { -namespace csf { +namespace xrpl::test::csf { // Submitters are classes for simulating submission of transactions to the // network @@ -62,7 +60,7 @@ class Submitter } template - static std::enable_if_t::value, SimDuration> + static std::enable_if_t, SimDuration> asDuration(T t) { return SimDuration{static_cast(t)}; @@ -105,6 +103,4 @@ makeSubmitter( return Submitter(dist, start, end, sel, s, g); } -} // namespace csf -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::csf diff --git a/src/test/csf/timers.h b/src/test/csf/timers.h index beb4e142d9..2f2ec4dc93 100644 --- a/src/test/csf/timers.h +++ b/src/test/csf/timers.h @@ -6,9 +6,7 @@ #include #include -namespace xrpl { -namespace test { -namespace csf { +namespace xrpl::test::csf { // Timers are classes that schedule repeated events and are mostly independent // of simulation-specific details. @@ -60,6 +58,4 @@ public: } }; -} // namespace csf -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::csf diff --git a/src/test/jtx/AMM.h b/src/test/jtx/AMM.h index 210138f290..faad982bcc 100644 --- a/src/test/jtx/AMM.h +++ b/src/test/jtx/AMM.h @@ -12,30 +12,34 @@ #include #include -namespace xrpl { -namespace test { -namespace jtx { +#include + +namespace xrpl::test::jtx { class LPToken { - IOUAmount const tokens_; + Number const tokens_; + Asset asset_; public: - LPToken(std::uint64_t tokens) : tokens_(tokens) + LPToken(std::uint64_t tokens) : tokens_(tokens), asset_(xrpIssue()) { } - LPToken(IOUAmount tokens) : tokens_(tokens) + LPToken(IOUAmount tokens) : tokens_(tokens), asset_(xrpIssue()) { } - IOUAmount const& + LPToken(STAmount tokens) : tokens_(tokens), asset_(tokens.asset()) + { + } + STAmount tokens() const { - return tokens_; + return STAmount{asset_, tokens_}; } STAmount tokens(Issue const& ammIssue) const { - return STAmount{tokens_, ammIssue}; + return STAmount{ammIssue, tokens_}; } }; @@ -59,7 +63,7 @@ struct DepositArg std::optional asset2In = std::nullopt; std::optional maxEP = std::nullopt; std::optional flags = std::nullopt; - std::optional> assets = std::nullopt; + std::optional> assets = std::nullopt; std::optional seq = std::nullopt; std::optional tfee = std::nullopt; std::optional err = std::nullopt; @@ -71,9 +75,9 @@ struct WithdrawArg std::optional tokens = std::nullopt; std::optional asset1Out = std::nullopt; std::optional asset2Out = std::nullopt; - std::optional maxEP = std::nullopt; + std::optional maxEP = std::nullopt; std::optional flags = std::nullopt; - std::optional> assets = std::nullopt; + std::optional> assets = std::nullopt; std::optional seq = std::nullopt; std::optional err = std::nullopt; }; @@ -84,7 +88,7 @@ struct VoteArg std::uint32_t tfee = 0; std::optional flags = std::nullopt; std::optional seq = std::nullopt; - std::optional> assets = std::nullopt; + std::optional> assets = std::nullopt; std::optional err = std::nullopt; }; @@ -93,9 +97,19 @@ struct BidArg std::optional account = std::nullopt; std::optional> bidMin = std::nullopt; std::optional> bidMax = std::nullopt; - std::vector authAccounts = {}; + std::vector authAccounts = {}; // NOLINT(readability-redundant-member-init) std::optional flags = std::nullopt; - std::optional> assets = std::nullopt; + std::optional> assets = std::nullopt; +}; + +struct ClawbackArg +{ + Account issuer; + Account holder; + std::optional> assets = std::nullopt; + std::optional amount = std::nullopt; + std::optional flags = std::nullopt; + std::optional err = std::nullopt; }; /** Convenience class to test AMM functionality. @@ -123,9 +137,9 @@ class AMM public: AMM(Env& env, - Account const& account, - STAmount const& asset1, - STAmount const& asset2, + Account account, + STAmount asset1, + STAmount asset2, bool log = false, std::uint16_t tfee = 0, std::uint32_t fee = 0, @@ -147,14 +161,21 @@ public: STAmount const& asset2, CreateArg const& arg); + static Json::Value + createJv( + AccountID const& account, + STAmount const& asset1, + STAmount const& asset2, + std::uint16_t const& tfee); + /** Send amm_info RPC command */ Json::Value ammRpcInfo( std::optional const& account = std::nullopt, std::optional const& ledgerIndex = std::nullopt, - std::optional issue1 = std::nullopt, - std::optional issue2 = std::nullopt, + std::optional asset1 = std::nullopt, + std::optional asset2 = std::nullopt, std::optional const& ammAccount = std::nullopt, bool ignoreParams = false, unsigned apiVersion = RPC::apiInvalidVersion) const; @@ -172,14 +193,14 @@ public: */ std::tuple balances( - Issue const& issue1, - Issue const& issue2, + Asset const& asset1, + Asset const& asset2, std::optional const& account = std::nullopt) const; std::tuple balances(std::optional const& account = std::nullopt) const { - return balances(asset1_.get(), asset2_.get(), account); + return balances(asset1_.asset(), asset2_.asset(), account); } [[nodiscard]] bool @@ -214,6 +235,9 @@ public: [[nodiscard]] bool ammExists() const; + static Json::Value + depositJv(DepositArg const& arg); + IOUAmount deposit( std::optional const& account, @@ -239,7 +263,7 @@ public: std::optional const& asset2In, std::optional const& maxEP, std::optional const& flags, - std::optional> const& assets, + std::optional> const& assets, std::optional const& seq, std::optional const& tfee = std::nullopt, std::optional const& ter = std::nullopt); @@ -247,6 +271,9 @@ public: IOUAmount deposit(DepositArg const& arg); + static Json::Value + withdrawJv(WithdrawArg const& arg); + IOUAmount withdraw( std::optional const& account, @@ -274,7 +301,7 @@ public: std::optional const& account, STAmount const& asset1Out, std::optional const& asset2Out = std::nullopt, - std::optional const& maxEP = std::nullopt, + std::optional const& maxEP = std::nullopt, std::optional const& ter = std::nullopt); IOUAmount @@ -283,22 +310,25 @@ public: std::optional const& tokens, std::optional const& asset1Out, std::optional const& asset2Out, - std::optional const& maxEP, + std::optional const& maxEP, std::optional const& flags, - std::optional> const& assets, + std::optional> const& assets, std::optional const& seq, std::optional const& ter = std::nullopt); IOUAmount withdraw(WithdrawArg const& arg); + static Json::Value + voteJv(VoteArg const& arg); + void vote( std::optional const& account, std::uint32_t feeVal, std::optional const& flags = std::nullopt, std::optional const& seq = std::nullopt, - std::optional> const& assets = std::nullopt, + std::optional> const& assets = std::nullopt, std::optional const& ter = std::nullopt); void @@ -307,6 +337,9 @@ public: Json::Value bid(BidArg const& arg); + void + clawback(ClawbackArg const& arg); + AccountID const& ammAccount() const { @@ -337,19 +370,22 @@ public: } std::string - operator[](AccountID const& lp) + operator[](AccountID const& lp) const { return ammRpcInfo(lp).toStyledString(); } Json::Value - operator()(AccountID const& lp) + operator()(AccountID const& lp) const { return ammRpcInfo(lp); } + static Json::Value + deleteJv(AccountID const& account, Asset const& asset1, Asset const& assets); + void - ammDelete(AccountID const& deleter, std::optional const& ter = std::nullopt); + ammDelete(AccountID const& account, std::optional const& ter = std::nullopt); void setClose(bool close) @@ -364,7 +400,83 @@ public: } void - setTokens(Json::Value& jv, std::optional> const& assets = std::nullopt); + setTokens(Json::Value& jv, std::optional> const& assets = std::nullopt); + + Asset const& + operator[](std::uint8_t i) + { + if (i > 1) + Throw("AMM: operator[], invalid index"); + return i == 0 ? asset1_.asset() : asset2_.asset(); + } + + struct Pool + { + AMM const& amm; + std::vector names; + Pool(AMM const& a, std::vector const& n = {}) : amm(a), names(n) + { + } + friend std::ostream& + operator<<(std::ostream& s, Pool const& p) + { + auto const& jr = p.amm.ammRpcInfo(); + auto out = [&](Json::Value const& jv) { + if (jv.isMember(jss::value)) + { + std::cout << jv[jss::value].asString(); + } + else + { + std::cout << jv.asString(); + } + std::cout << " "; + }; + if (p.names.empty()) + { + out(jr[jss::amm][jss::amount]); + out(jr[jss::amm][jss::amount2]); + out(jr[jss::amm][jss::lp_token]); + } + else + { + for (auto const& n : p.names) + out(jr[jss::amm][n]); + } + std::cout << std::endl; + return s; + } + }; + struct Offers + { + Json::Value const& jv; + Offers(Json::Value const& j) : jv(j) + { + } + friend std::ostream& + operator<<(std::ostream& s, Offers const& offers) + { + auto out = [&](Json::Value const& jv) { + if (jv.isMember(jss::value)) + { + s << jv[jss::value].asString(); + } + else + { + s << jv; + } + }; + for (auto const& o : offers.jv[jss::offers]) + { + s << "taker_pays: "; + out(o[jss::taker_pays]); + s << " taker_gets: "; + out(o[jss::taker_gets]); + s << std::endl; + } + return s; + } + }; private: AccountID @@ -374,22 +486,6 @@ private: std::optional const& seq = std::nullopt, std::optional const& ter = std::nullopt); - IOUAmount - deposit( - std::optional const& account, - Json::Value& jv, - std::optional> const& assets = std::nullopt, - std::optional const& seq = std::nullopt, - std::optional const& ter = std::nullopt); - - IOUAmount - withdraw( - std::optional const& account, - Json::Value& jv, - std::optional const& seq, - std::optional> const& assets = std::nullopt, - std::optional const& ter = std::nullopt); - void log(bool log) { @@ -417,20 +513,14 @@ private: }; namespace amm { -Json::Value -trust(AccountID const& account, STAmount const& amount, std::uint32_t flags = 0); -Json::Value -pay(Account const& account, AccountID const& to, STAmount const& amount); Json::Value ammClawback( Account const& issuer, Account const& holder, - Issue const& asset, - Issue const& asset2, + Asset const& asset, + Asset const& asset2, std::optional const& amount); } // namespace amm -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/AMMTest.h b/src/test/jtx/AMMTest.h index a311d9c638..8f46ef6f59 100644 --- a/src/test/jtx/AMMTest.h +++ b/src/test/jtx/AMMTest.h @@ -7,13 +7,11 @@ #include #include -namespace xrpl { -namespace test { -namespace jtx { +namespace xrpl::test::jtx { class AMM; -enum class Fund { All, Acct, Gw, IOUOnly }; +enum class Fund { All, Acct, Gw, TokenOnly }; struct TestAMMArg { @@ -28,7 +26,13 @@ struct TestAMMArg bool noLog = false; }; -void +// A hint to testAMM() or fund() to create/fund MPT. +// A distinct MPT is created if both AMM assets +// are MPT. The actual MPT asset can be accessed +// via AMM::operator[](0|1). +inline static auto AMMMPT = MPT("AMM"); + +[[maybe_unused]] std::vector fund( jtx::Env& env, jtx::Account const& gw, @@ -36,7 +40,7 @@ fund( std::vector const& amts, Fund how); -void +[[maybe_unused]] std::vector fund( jtx::Env& env, jtx::Account const& gw, @@ -45,13 +49,22 @@ fund( std::vector const& amts = {}, Fund how = Fund::All); -void +[[maybe_unused]] std::vector fund( jtx::Env& env, std::vector const& accounts, STAmount const& xrp, std::vector const& amts = {}, - Fund how = Fund::All); + Fund how = Fund::All, + std::optional const& mptIssuer = std::nullopt); + +struct TestAMMArgs +{ + std::optional> const& pool = std::nullopt; + std::uint16_t tfee = 0; + std::optional const& ter = std::nullopt; + std::vector const& features = {testable_amendments()}; +}; class AMMTestBase : public beast::unit_test::suite { @@ -135,26 +148,6 @@ protected: jtx::Env pathTestEnv(); - - Json::Value - find_paths_request( - jtx::Env& env, - jtx::Account const& src, - jtx::Account const& dst, - STAmount const& saDstAmount, - std::optional const& saSendMax = std::nullopt, - std::optional const& saSrcCurrency = std::nullopt); - - std::tuple - find_paths( - jtx::Env& env, - jtx::Account const& src, - jtx::Account const& dst, - STAmount const& saDstAmount, - std::optional const& saSendMax = std::nullopt, - std::optional const& saSrcCurrency = std::nullopt); }; -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/AbstractClient.h b/src/test/jtx/AbstractClient.h index 9b1ef178ca..2002b3a7d8 100644 --- a/src/test/jtx/AbstractClient.h +++ b/src/test/jtx/AbstractClient.h @@ -2,13 +2,12 @@ #include -namespace xrpl { -namespace test { +namespace xrpl::test { -/* Abstract Ripple Client interface. +/* Abstract XRPL client interface. This abstracts the transport layer, allowing - commands to be submitted to a rippled server. + commands to be submitted to an xrpld server. */ class AbstractClient { @@ -38,5 +37,4 @@ public: version() const = 0; }; -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/jtx/Account.h b/src/test/jtx/Account.h index 6f796e08b3..1e39f5a546 100644 --- a/src/test/jtx/Account.h +++ b/src/test/jtx/Account.h @@ -8,9 +8,7 @@ #include #include -namespace xrpl { -namespace test { -namespace jtx { +namespace xrpl::test::jtx { class IOU; @@ -143,6 +141,4 @@ operator<=>(Account const& lhs, Account const& rhs) noexcept return lhs.id() <=> rhs.id(); } -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/CaptureLogs.h b/src/test/jtx/CaptureLogs.h index 8c8da6817b..0e9598decd 100644 --- a/src/test/jtx/CaptureLogs.h +++ b/src/test/jtx/CaptureLogs.h @@ -2,8 +2,7 @@ #include -namespace xrpl { -namespace test { +namespace xrpl::test { /** * @brief Log manager for CaptureSinks. This class holds the stream @@ -66,5 +65,4 @@ public: } }; -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/jtx/CheckMessageLogs.h b/src/test/jtx/CheckMessageLogs.h index 286cfa1844..4c6bd35036 100644 --- a/src/test/jtx/CheckMessageLogs.h +++ b/src/test/jtx/CheckMessageLogs.h @@ -2,8 +2,7 @@ #include -namespace xrpl { -namespace test { +namespace xrpl::test { /** Log manager that searches for a specific message substring */ @@ -55,5 +54,4 @@ public: } }; -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/jtx/Env.h b/src/test/jtx/Env.h index b494ade31c..75c0195f40 100644 --- a/src/test/jtx/Env.h +++ b/src/test/jtx/Env.h @@ -40,9 +40,7 @@ #include #include -namespace xrpl { -namespace test { -namespace jtx { +namespace xrpl::test::jtx { /** Wrapper that captures std::source_location when implicitly constructed. This solves the problem of combining std::source_location with variadic @@ -85,9 +83,13 @@ testable_amendments() { (void)vote; if (auto const f = getRegisteredFeature(s)) + { feats.push_back(*f); + } else + { Throw("Unknown feature: " + s + " in allAmendments."); + } } return FeatureBitset(feats); }(); @@ -128,12 +130,12 @@ public: /// Used by parseResult() and postConditions() struct ParsedResult { - std::optional ter{}; + std::optional ter; // RPC errors tend to return either a "code" and a "message" (sometimes // with an "error" that corresponds to the "code"), or with an "error" // and an "exception". However, this structure allows all possible // combinations. - std::optional rpcCode{}; + std::optional rpcCode; std::string rpcMessage; std::string rpcError; std::string rpcException; @@ -256,6 +258,7 @@ public: virtual ~Env() = default; Application& + // NOLINTNEXTLINE(readability-make-member-function-const) app() { return *bundle_.app; @@ -268,6 +271,7 @@ public: } ManualTimeKeeper& + // NOLINTNEXTLINE(readability-make-member-function-const) timeKeeper() { return *bundle_.timeKeeper; @@ -279,6 +283,7 @@ public: close or by callers. */ NetClock::time_point + // NOLINTNEXTLINE(readability-make-member-function-const) now() { return timeKeeper().now(); @@ -286,6 +291,7 @@ public: /** Returns the connected client. */ AbstractClient& + // NOLINTNEXTLINE(readability-make-member-function-const) client() { return *bundle_.client; @@ -510,15 +516,8 @@ public: */ // VFALCO NOTE This should return a unit-less amount PrettyAmount - // NOLINTNEXTLINE(readability-convert-member-functions-to-static) balance(Account const& account, Asset const& asset) const; - PrettyAmount - balance(Account const& account, Issue const& issue) const; - - PrettyAmount - balance(Account const& account, MPTIssue const& mptIssue) const; - /** Returns the IOU limit on an account. Returns 0 if the trust line does not exist. */ @@ -780,7 +779,7 @@ public: trust(STAmount const& amount, Account const& to0, Account const& to1, Accounts const&... toN) { trust(amount, to0); - trust(amount, to1, toN...); + trust(amount, to1, toN...); // NOLINT(readability-suspicious-call-argument) } /** @} */ @@ -884,6 +883,4 @@ Env::rpc(std::string const& cmd, Args&&... args) return rpc(std::unordered_map(), cmd, std::forward(args)...); } -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/Env_ss.h b/src/test/jtx/Env_ss.h index d6f5fd1a29..e9f6ae3bb1 100644 --- a/src/test/jtx/Env_ss.h +++ b/src/test/jtx/Env_ss.h @@ -2,9 +2,7 @@ #include -namespace xrpl { -namespace test { -namespace jtx { +namespace xrpl::test::jtx { /** A transaction testing environment wrapper. Transactions submitted in sign-and-submit mode @@ -66,6 +64,4 @@ public: } }; -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/Env_test.cpp b/src/test/jtx/Env_test.cpp index 4e9f0d99e2..6cef617ea7 100644 --- a/src/test/jtx/Env_test.cpp +++ b/src/test/jtx/Env_test.cpp @@ -1,21 +1,69 @@ -#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include #include -#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include #include +#include #include #include #include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include #include -namespace xrpl { -namespace test { +namespace xrpl::test { class Env_test : public beast::unit_test::suite { @@ -61,10 +109,10 @@ public: PrettyAmount(0u); // NOLINT(bugprone-unused-raii) PrettyAmount(1u); // NOLINT(bugprone-unused-raii) PrettyAmount(-1); // NOLINT(bugprone-unused-raii) - static_assert(!std::is_trivially_constructible::value, ""); - static_assert(!std::is_trivially_constructible::value, ""); - static_assert(!std::is_trivially_constructible::value, ""); - static_assert(!std::is_trivially_constructible::value, ""); + static_assert(!std::is_trivially_constructible_v, ""); + static_assert(!std::is_trivially_constructible_v, ""); + static_assert(!std::is_trivially_constructible_v, ""); + static_assert(!std::is_trivially_constructible_v, ""); try { @@ -854,5 +902,4 @@ public: BEAST_DEFINE_TESTSUITE(Env, jtx, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/jtx/JSONRPCClient.h b/src/test/jtx/JSONRPCClient.h index 41129a36ec..a2ff815f48 100644 --- a/src/test/jtx/JSONRPCClient.h +++ b/src/test/jtx/JSONRPCClient.h @@ -6,12 +6,10 @@ #include -namespace xrpl { -namespace test { +namespace xrpl::test { /** Returns a client using JSON-RPC over HTTP/S. */ std::unique_ptr makeJSONRPCClient(Config const& cfg, unsigned rpc_version = 2); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/jtx/JTx.h b/src/test/jtx/JTx.h index bf43d0aa75..a4db523ff5 100644 --- a/src/test/jtx/JTx.h +++ b/src/test/jtx/JTx.h @@ -12,9 +12,7 @@ #include #include -namespace xrpl { -namespace test { -namespace jtx { +namespace xrpl::test::jtx { class Env; @@ -152,6 +150,4 @@ private: prop_list props_; }; -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/ManualTimeKeeper.h b/src/test/jtx/ManualTimeKeeper.h index d5fdd467e1..1fd94858d6 100644 --- a/src/test/jtx/ManualTimeKeeper.h +++ b/src/test/jtx/ManualTimeKeeper.h @@ -4,13 +4,12 @@ #include -namespace xrpl { -namespace test { +namespace xrpl::test { class ManualTimeKeeper : public TimeKeeper { private: - std::atomic now_{}; + std::atomic now_; public: ManualTimeKeeper() = default; @@ -28,5 +27,4 @@ public: } }; -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/jtx/Oracle.h b/src/test/jtx/Oracle.h index 7924d278e5..42376b3599 100644 --- a/src/test/jtx/Oracle.h +++ b/src/test/jtx/Oracle.h @@ -4,10 +4,7 @@ #include -namespace xrpl { -namespace test { -namespace jtx { -namespace oracle { +namespace xrpl::test::jtx::oracle { using AnyValue = std::variant; using OraclesData = std::vector, std::optional>>; @@ -58,7 +55,7 @@ struct UpdateArg { std::optional owner = std::nullopt; std::optional documentID = std::nullopt; - DataSeries series = {}; + DataSeries series = {}; // NOLINT(readability-redundant-member-init) std::optional assetClass = std::nullopt; std::optional provider = std::nullopt; std::optional uri = "URI"; @@ -81,7 +78,7 @@ struct RemoveArg std::optional const& err = std::nullopt; }; -// Simulate testStartTime as 10'000s from Ripple epoch time to make +// Simulate testStartTime as 10'000s from XRPL epoch time to make // LastUpdateTime validation to work and to make unit-test consistent. // The value doesn't matter much, it has to be greater // than maxLastUpdateTimeDelta in order to pass LastUpdateTime @@ -177,7 +174,4 @@ public: } }; -} // namespace oracle -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx::oracle diff --git a/src/test/jtx/PathSet.h b/src/test/jtx/PathSet.h index c522ed635e..4db3b7d62e 100644 --- a/src/test/jtx/PathSet.h +++ b/src/test/jtx/PathSet.h @@ -6,8 +6,7 @@ #include #include -namespace xrpl { -namespace test { +namespace xrpl::test { /** Count offer */ @@ -15,13 +14,13 @@ inline std::size_t countOffers( jtx::Env& env, jtx::Account const& account, - Issue const& takerPays, - Issue const& takerGets) + Asset const& takerPays, + Asset const& takerGets) { size_t count = 0; forEachItem(*env.current(), account, [&](std::shared_ptr const& sle) { - if (sle->getType() == ltOFFER && sle->getFieldAmount(sfTakerPays).issue() == takerPays && - sle->getFieldAmount(sfTakerGets).issue() == takerGets) + if (sle->getType() == ltOFFER && sle->getFieldAmount(sfTakerPays).asset() == takerPays && + sle->getFieldAmount(sfTakerGets).asset() == takerGets) ++count; }); return count; @@ -58,7 +57,7 @@ isOffer( /** An offer exists */ inline bool -isOffer(jtx::Env& env, jtx::Account const& account, Issue const& takerPays, Issue const& takerGets) +isOffer(jtx::Env& env, jtx::Account const& account, Asset const& takerPays, Asset const& takerGets) { return countOffers(env, account, takerPays, takerGets) > 0; } @@ -84,6 +83,8 @@ public: Path& push_back(Issue const& iss); Path& + push_back(MPTIssue const& iss); + Path& push_back(jtx::Account const& acc); Path& push_back(STPathElement const& pe); @@ -114,10 +115,21 @@ Path::push_back(Issue const& iss) return *this; } +inline Path& +Path::push_back(MPTIssue const& iss) +{ + path.emplace_back( + STPathElement::typeMPT | STPathElement::typeIssuer, + beast::zero, + iss.getMptID(), + iss.getIssuer()); + return *this; +} + inline Path& Path::push_back(jtx::Account const& account) { - path.emplace_back(account.id(), beast::zero, beast::zero); + path.emplace_back(account.id(), Currency{beast::zero}, beast::zero); return *this; } @@ -173,5 +185,4 @@ private: } }; -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/jtx/SignerUtils.h b/src/test/jtx/SignerUtils.h index 994868e4a2..dd68701303 100644 --- a/src/test/jtx/SignerUtils.h +++ b/src/test/jtx/SignerUtils.h @@ -2,11 +2,11 @@ #include +#include +#include #include -namespace xrpl { -namespace test { -namespace jtx { +namespace xrpl::test::jtx { struct Reg { @@ -17,7 +17,7 @@ struct Reg { } - Reg(Account const& acct_, Account const& regularSig) : acct(acct_), sig(regularSig) + Reg(Account acct_, Account regularSig) : acct(std::move(acct_)), sig(std::move(regularSig)) { } @@ -40,11 +40,7 @@ struct Reg inline void sortSigners(std::vector& signers) { - std::sort(signers.begin(), signers.end(), [](Reg const& lhs, Reg const& rhs) { - return lhs.acct < rhs.acct; - }); + std::ranges::sort(signers, [](Reg const& lhs, Reg const& rhs) { return lhs.acct < rhs.acct; }); } -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/TestHelpers.h b/src/test/jtx/TestHelpers.h index 4da086b05b..70692d1675 100644 --- a/src/test/jtx/TestHelpers.h +++ b/src/test/jtx/TestHelpers.h @@ -5,21 +5,20 @@ #include #include -#include #include #include #include #include -#include #include #include +#include +#include #include +#include #include -namespace xrpl { -namespace test { -namespace jtx { +namespace xrpl::test::jtx { /** Generic helper class for helper classes that set a field on a JTx. @@ -41,7 +40,7 @@ protected: SV value_; public: - explicit JTxField(SF const& sfield, SV const& value) : sfield_(sfield), value_(value) + explicit JTxField(SF const& sfield, SV value) : sfield_(sfield), value_(std::move(value)) { } @@ -69,7 +68,7 @@ protected: SV value_; public: - explicit JTxField(SF const& sfield, SV const& value) : sfield_(sfield), value_(value) + explicit JTxField(SF const& sfield, SV value) : sfield_(sfield), value_(std::move(value)) { } @@ -368,7 +367,7 @@ void stpath_append_one(STPath& st, Account const& account); template -std::enable_if_t::value> +std::enable_if_t> stpath_append_one(STPath& st, T const& t) { stpath_append_one(st, Account{t}); @@ -402,6 +401,9 @@ equal(STAmount const& sa1, STAmount const& sa2); STPathElement IPE(Issue const& iss); +STPathElement +IPE(MPTIssue const& iss); + template STPath stpath(Args const&... args) @@ -422,12 +424,85 @@ same(STPathSet const& st1, Args const&... args) for (auto const& p : st2) { - if (std::find(st1.begin(), st1.end(), p) == st1.end()) + if (std::ranges::find(st1, p) == st1.end()) return false; } return true; } +Json::Value +rpf(jtx::Account const& src, + jtx::Account const& dst, + STAmount const& dstAmount, + std::optional const& sendMax = std::nullopt, + std::optional const& srcAsset = std::nullopt, + std::optional const& srcIssuer = std::nullopt); + +jtx::Env +pathTestEnv(beast::unit_test::suite& suite); + +class gate +{ +private: + std::condition_variable cv_; + std::mutex mutex_; + bool signaled_ = false; + +public: + // Thread safe, blocks until signaled or period expires. + // Returns `true` if signaled. + template + bool + wait_for(std::chrono::duration const& rel_time) + { + std::unique_lock lk(mutex_); + auto b = cv_.wait_for(lk, rel_time, [this] { return signaled_; }); + signaled_ = false; + return b; + } + + void + signal() + { + std::lock_guard const lk(mutex_); + signaled_ = true; + cv_.notify_all(); + } +}; + +Json::Value +find_paths_request( + jtx::Env& env, + jtx::Account const& src, + jtx::Account const& dst, + STAmount const& saDstAmount, + std::optional const& saSendMax = std::nullopt, + std::optional const& srcAsset = std::nullopt, + std::optional const& srcIssuer = std::nullopt, + std::optional const& domain = std::nullopt); + +std::tuple +find_paths( + jtx::Env& env, + jtx::Account const& src, + jtx::Account const& dst, + STAmount const& saDstAmount, + std::optional const& saSendMax = std::nullopt, + std::optional const& srcAsset = std::nullopt, + std::optional const& srcIssuer = std::nullopt, + std::optional const& domain = std::nullopt); + +std::tuple +find_paths_by_element( + jtx::Env& env, + jtx::Account const& src, + jtx::Account const& dst, + STAmount const& saDstAmount, + std::optional const& saSendMax = std::nullopt, + std::optional const& srcElement = std::nullopt, + std::optional const& srcIssuer = std::nullopt, + std::optional const& domain = std::nullopt); + /******************************************************************************/ XRPAmount @@ -453,6 +528,9 @@ expectHolding(Env& env, AccountID const& account, STAmount const& value, Amts co bool expectHolding(Env& env, AccountID const& account, None const& value); +bool +expectMPT(Env& env, AccountID const& account, STAmount const& value); + bool expectOffers( Env& env, @@ -470,6 +548,15 @@ ledgerEntryState( Account const& acct_b, std::string const& currency); +Json::Value +ledgerEntryOffer(jtx::Env& env, jtx::Account const& acct, std::uint32_t offer_seq); + +Json::Value +ledgerEntryMPT(jtx::Env& env, jtx::Account const& acct, MPTID const& mptID); + +Json::Value +getBookOffers(jtx::Env& env, Asset const& taker_pays, Asset const& taker_gets); + Json::Value accountBalance(Env& env, Account const& acct); @@ -545,13 +632,85 @@ n_offers(Env& env, std::size_t n, Account const& account, STAmount const& in, ST /* Pay Strand */ /***************************************************************/ -// Currency path element +struct DirectStepInfo +{ + AccountID src; + AccountID dst; + Currency currency; +}; + +struct MPTEndpointStepInfo +{ + AccountID src; + AccountID dst; + MPTID mptid; +}; + +struct XRPEndpointStepInfo +{ + AccountID acc; +}; + +// Currency/MPTID path element STPathElement -cpe(Currency const& c); +cpe(PathAsset const& pa); + +// Currency/MPTID and issuer path element +STPathElement +ipe(Asset const& asset); + +// Issuer path element +STPathElement +iape(AccountID const& account); + +// Account path element +STPathElement +ape(AccountID const& a); // All path element STPathElement -allPathElements(AccountID const& a, Issue const& iss); +allPathElements(AccountID const& a, Asset const& asset); + +bool +equal(std::unique_ptr const& s1, DirectStepInfo const& dsi); + +bool +equal(std::unique_ptr const& s1, MPTEndpointStepInfo const& dsi); + +bool +equal(std::unique_ptr const& s1, XRPEndpointStepInfo const& xrpStepInfo); + +bool +equal(std::unique_ptr const& s1, xrpl::Book const& bsi); + +template +bool +strandEqualHelper(Iter i) +{ + // base case. all args processed and found equal. + return true; +} + +template +bool +strandEqualHelper(Iter i, StepInfo&& si, Args&&... args) +{ + if (!jtx::equal(*i, std::forward(si))) + return false; + return strandEqualHelper(++i, std::forward(args)...); +} + +template +bool +equal(Strand const& strand, Args&&... args) +{ + if (strand.size() != sizeof...(Args)) + return false; + if (strand.empty()) + return true; + return strandEqualHelper(strand.begin(), std::forward(args)...); +} + /***************************************************************/ /* Check */ @@ -583,6 +742,12 @@ create(jtx::Account const& account, jtx::Account const& dest, STAmount const& se static constexpr FeeLevel64 baseFeeLevel{TxQ::baseLevel}; static constexpr FeeLevel64 minEscalationFeeLevel = baseFeeLevel * 500; +inline uint256 +getCheckIndex(AccountID const& account, std::uint32_t uSequence) +{ + return keylet::check(account, uSequence).key; +} + template void checkMetrics( @@ -770,6 +935,104 @@ pay(AccountID const& account, } // namespace loan -} // namespace jtx -} // namespace test -} // namespace xrpl +/** Set Expiration on a JTx. */ +class expiration +{ +private: + std::uint32_t const expiry_; + +public: + explicit expiration(NetClock::time_point const& expiry) + : expiry_{expiry.time_since_epoch().count()} + { + } + + void + operator()(Env&, JTx& jt) const + { + jt[sfExpiration.jsonName] = expiry_; + } +}; + +/** Set SourceTag on a JTx. */ +class source_tag +{ +private: + std::uint32_t const tag_; + +public: + explicit source_tag(std::uint32_t tag) : tag_{tag} + { + } + + void + operator()(Env&, JTx& jt) const + { + jt[sfSourceTag.jsonName] = tag_; + } +}; + +/** Set DestinationTag on a JTx. */ +class dest_tag +{ +private: + std::uint32_t const tag_; + +public: + explicit dest_tag(std::uint32_t tag) : tag_{tag} + { + } + + void + operator()(Env&, JTx& jt) const + { + jt[sfDestinationTag.jsonName] = tag_; + } +}; + +struct IssuerArgs +{ + jtx::Env& env; + // 3-letter currency if Issue, ignored if MPT + std::string token; + jtx::Account issuer; + std::vector holders = {}; // NOLINT(readability-redundant-member-init) + // trust-limit if Issue, maxAmount if MPT + std::optional limit = std::nullopt; + // 0-50'000 (0-50%) + std::uint16_t transferFee = 0; +}; + +namespace detail { + +IOU +issueHelperIOU(IssuerArgs const& args); + +MPT +issueHelperMPT(IssuerArgs const& args); + +} // namespace detail + +template +void +testHelper2TokensMix(TTester&& tester) +{ + tester(detail::issueHelperMPT, detail::issueHelperMPT); + tester(detail::issueHelperIOU, detail::issueHelperMPT); + tester(detail::issueHelperMPT, detail::issueHelperIOU); +} + +template +void +testHelper3TokensMix(TTester&& tester) +{ + tester(detail::issueHelperMPT, detail::issueHelperMPT, detail::issueHelperMPT); + tester(detail::issueHelperMPT, detail::issueHelperMPT, detail::issueHelperIOU); + tester(detail::issueHelperMPT, detail::issueHelperIOU, detail::issueHelperMPT); + tester(detail::issueHelperMPT, detail::issueHelperIOU, detail::issueHelperIOU); + tester(detail::issueHelperIOU, detail::issueHelperMPT, detail::issueHelperMPT); + tester(detail::issueHelperIOU, detail::issueHelperMPT, detail::issueHelperIOU); + tester(detail::issueHelperIOU, detail::issueHelperIOU, detail::issueHelperMPT); +} + +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/TrustedPublisherServer.h b/src/test/jtx/TrustedPublisherServer.h index d36babf380..097dae97bb 100644 --- a/src/test/jtx/TrustedPublisherServer.h +++ b/src/test/jtx/TrustedPublisherServer.h @@ -21,9 +21,9 @@ #include #include +#include -namespace xrpl { -namespace test { +namespace xrpl::test { class TrustedPublisherServer : public std::enable_shared_from_this { @@ -54,7 +54,7 @@ class TrustedPublisherServer : public std::enable_shared_from_this( path.substr(strlen(refreshPrefix))); + } res.body() = getList2_(refresh); } } @@ -543,16 +554,22 @@ private: res.result(http::status::ok); res.insert("Content-Type", "application/json"); if (path == "/validators/bad") + { res.body() = "{ 'bad': \"1']"; + } else if (path == "/validators/missing") + { res.body() = "{\"version\": 1}"; + } else { int refresh = 5; constexpr char const* refreshPrefix = "/validators/refresh/"; if (boost::starts_with(path, refreshPrefix)) + { refresh = boost::lexical_cast( path.substr(strlen(refreshPrefix))); + } res.body() = getList_(refresh); } } @@ -570,8 +587,10 @@ private: { std::stringstream body; for (auto i = 0; i < 1024; ++i) + { body << static_cast(rand_int(32, 126)), res.body() = body.str(); + } } } else if (boost::starts_with(path, "/sleep/")) @@ -582,13 +601,21 @@ private: else if (boost::starts_with(path, "/redirect")) { if (boost::ends_with(path, "/301")) + { res.result(http::status::moved_permanently); + } else if (boost::ends_with(path, "/302")) + { res.result(http::status::found); + } else if (boost::ends_with(path, "/307")) + { res.result(http::status::temporary_redirect); + } else if (boost::ends_with(path, "/308")) + { res.result(http::status::permanent_redirect); + } std::stringstream location; if (boost::starts_with(path, "/redirect_to/")) @@ -630,9 +657,13 @@ private: } if (ssl) + { write(*ssl_stream, res, ec); + } else + { write(sock, res, ec); + } if (ec || req.need_eof()) break; @@ -662,5 +693,4 @@ make_TrustedPublisherServer( return r; } -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/jtx/WSClient.h b/src/test/jtx/WSClient.h index d1801cb4a9..e95920d085 100644 --- a/src/test/jtx/WSClient.h +++ b/src/test/jtx/WSClient.h @@ -8,8 +8,7 @@ #include #include -namespace xrpl { -namespace test { +namespace xrpl::test { class WSClient : public AbstractClient { @@ -33,5 +32,4 @@ makeWSClient( unsigned rpc_version = 2, std::unordered_map const& headers = {}); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/jtx/WSClient_test.cpp b/src/test/jtx/WSClient_test.cpp index 206b550b87..157b7665a8 100644 --- a/src/test/jtx/WSClient_test.cpp +++ b/src/test/jtx/WSClient_test.cpp @@ -1,10 +1,13 @@ -#include +#include #include +#include -#include +#include +#include -namespace xrpl { -namespace test { +#include + +namespace xrpl::test { class WSClient_test : public beast::unit_test::suite { @@ -29,5 +32,4 @@ public: BEAST_DEFINE_TESTSUITE(WSClient, jtx, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/jtx/account_txn_id.h b/src/test/jtx/account_txn_id.h index 71ac606418..424ce13b14 100644 --- a/src/test/jtx/account_txn_id.h +++ b/src/test/jtx/account_txn_id.h @@ -2,9 +2,7 @@ #include -namespace xrpl { -namespace test { -namespace jtx { +namespace xrpl::test::jtx { struct account_txn_id { @@ -19,6 +17,4 @@ public: void operator()(Env&, JTx& jt) const; }; -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/acctdelete.h b/src/test/jtx/acctdelete.h index c081eb92a5..774baca7a1 100644 --- a/src/test/jtx/acctdelete.h +++ b/src/test/jtx/acctdelete.h @@ -5,9 +5,7 @@ #include -namespace xrpl { -namespace test { -namespace jtx { +namespace xrpl::test::jtx { /** Delete account. If successful transfer remaining XRP to dest. */ Json::Value @@ -19,7 +17,4 @@ acctdelete(Account const& account, Account const& dest); void incLgrSeqForAccDel(jtx::Env& env, jtx::Account const& acc, std::uint32_t margin = 0); -} // namespace jtx - -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/amount.h b/src/test/jtx/amount.h index 1912f01330..122af2faa1 100644 --- a/src/test/jtx/amount.h +++ b/src/test/jtx/amount.h @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -12,6 +13,7 @@ #include #include #include +#include namespace xrpl { namespace detail { @@ -23,8 +25,7 @@ struct epsilon_multiple } // namespace detail -namespace test { -namespace jtx { +namespace test::jtx { /* @@ -56,7 +57,7 @@ struct None // could change that value (however unlikely). constexpr XRPAmount dropsPerXRP{1'000'000}; -/** Represents an XRP or IOU quantity +/** Represents an XRP, IOU, or MPT quantity This customizes the string conversion and supports XRP conversions from integer and floating point. */ @@ -73,7 +74,8 @@ public: PrettyAmount& operator=(PrettyAmount const&) = default; - PrettyAmount(STAmount const& amount, std::string const& name) : amount_(amount), name_(name) + PrettyAmount(STAmount amount, std::string name) + : amount_(std::move(amount)), name_(std::move(name)) { } @@ -119,7 +121,7 @@ public: return amount_; } - inline int + int signum() const { return amount_.signum(); @@ -231,11 +233,9 @@ public: // Specifies an order book struct BookSpec { - AccountID account; - xrpl::Currency currency; + xrpl::Asset asset; - BookSpec(AccountID const& account_, xrpl::Currency const& currency_) - : account(account_), currency(currency_) + BookSpec(xrpl::Asset const& asset_) : asset(asset_) { } }; @@ -253,9 +253,13 @@ struct XRP_t { return xrpIssue(); } + operator Asset() const + { + return xrpIssue(); + } - bool - integral() const + static bool + integral() { return true; } @@ -317,7 +321,7 @@ struct XRP_t friend BookSpec operator~(XRP_t const&) { - return BookSpec(xrpAccount(), xrpCurrency()); + return BookSpec(Issue{xrpCurrency(), xrpAccount()}); } }; @@ -357,9 +361,7 @@ drops(XRPAmount i) // The smallest possible IOU STAmount struct epsilon_t { - epsilon_t() - { - } + epsilon_t() = default; detail::epsilon_multiple operator()(std::size_t n) const @@ -383,8 +385,8 @@ public: Account account; xrpl::Currency currency; - IOU(Account const& account_, xrpl::Currency const& currency_) - : account(account_), currency(currency_) + IOU(Account account_, xrpl::Currency const& currency_) + : account(std::move(account_)), currency(currency_) { } @@ -413,6 +415,10 @@ public: { return issue(); } + operator Asset() const + { + return asset(); + } operator PrettyAsset() const { return asset(); @@ -420,7 +426,7 @@ public: template < class T, - class = std::enable_if_t= sizeof(int) && std::is_arithmetic::value>> + class = std::enable_if_t= sizeof(int) && std::is_arithmetic_v>> PrettyAmount operator()(T v) const { @@ -447,7 +453,7 @@ public: friend BookSpec operator~(IOU const& iou) { - return BookSpec(iou.account.id(), iou.currency); + return BookSpec(Issue{iou.currency, iou.account.id()}); } }; @@ -469,7 +475,16 @@ public: std::string name; xrpl::MPTID issuanceID; - MPT(std::string const& n, xrpl::MPTID const& issuanceID_) : name(n), issuanceID(issuanceID_) + MPT(std::string n, xrpl::MPTID const& issuanceID_) : name(std::move(n)), issuanceID(issuanceID_) + { + } + MPT(std::string n = "") : name(std::move(n)), issuanceID(noMPT()) + { + } + MPT(Asset const& asset) : issuanceID(asset.get()) + { + } + MPT(AccountID const& account, std::int32_t seq = 0) : issuanceID(makeMptID(seq, account)) { } @@ -491,8 +506,8 @@ public: { return mptIssue(); } - bool - integral() const + static bool + integral() { return true; } @@ -511,6 +526,14 @@ public: { return asset(); } + operator xrpl::Asset() const + { + return mpt(); + } + operator xrpl::MPTID() const + { + return mpt(); + } template requires(sizeof(T) >= sizeof(int) && std::is_arithmetic_v) @@ -529,15 +552,13 @@ public: None operator()(none_t) const { - return {mptIssue()}; + return {noMPT()}; } friend BookSpec operator~(MPT const& mpt) { - assert(false); - Throw("MPT is not supported"); - return BookSpec{beast::zero, noCurrency()}; + return BookSpec{Asset{mpt}}; } }; @@ -563,11 +584,11 @@ struct AnyAmount AnyAmount& operator=(AnyAmount const&) = default; - AnyAmount(STAmount const& amount) : is_any(false), value(amount) + AnyAmount(STAmount amount) : is_any(false), value(std::move(amount)) { } - AnyAmount(STAmount const& amount, any_t const*) : is_any(true), value(amount) + AnyAmount(STAmount amount, any_t const*) : is_any(true), value(std::move(amount)) { } @@ -577,7 +598,7 @@ struct AnyAmount { if (!is_any) return; - value.setIssuer(id); + value.get().account = id; } }; @@ -592,6 +613,6 @@ any_t::operator()(STAmount const& sta) const */ extern any_t const any; -} // namespace jtx -} // namespace test +} // namespace test::jtx + } // namespace xrpl diff --git a/src/test/jtx/attester.h b/src/test/jtx/attester.h index 6904135f21..1c38684890 100644 --- a/src/test/jtx/attester.h +++ b/src/test/jtx/attester.h @@ -13,8 +13,7 @@ class SecretKey; class STXChainBridge; class STAmount; -namespace test { -namespace jtx { +namespace test::jtx { Buffer sign_claim_attestation( @@ -40,6 +39,6 @@ sign_create_account_attestation( bool wasLockingChainSend, std::uint64_t createCount, AccountID const& dst); -} // namespace jtx -} // namespace test +} // namespace test::jtx + } // namespace xrpl diff --git a/src/test/jtx/balance.h b/src/test/jtx/balance.h index 2181429908..a75583b1a3 100644 --- a/src/test/jtx/balance.h +++ b/src/test/jtx/balance.h @@ -3,9 +3,9 @@ #include #include -namespace xrpl { -namespace test { -namespace jtx { +#include + +namespace xrpl::test::jtx { /** A balance matches. @@ -23,17 +23,17 @@ private: STAmount const value_; public: - balance(Account const& account, none_t) : none_(true), account_(account), value_(XRP) + balance(Account account, none_t) : none_(true), account_(std::move(account)), value_(XRP) { } - balance(Account const& account, None const& value) - : none_(true), account_(account), value_(value.asset) + balance(Account account, None const& value) + : none_(true), account_(std::move(account)), value_(value.asset) { } - balance(Account const& account, STAmount const& value) - : none_(false), account_(account), value_(value) + balance(Account account, STAmount value) + : none_(false), account_(std::move(account)), value_(std::move(value)) { } @@ -41,6 +41,4 @@ public: operator()(Env&) const; }; -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/basic_prop.h b/src/test/jtx/basic_prop.h index 080eecd34b..d2b4805651 100644 --- a/src/test/jtx/basic_prop.h +++ b/src/test/jtx/basic_prop.h @@ -2,9 +2,7 @@ #include -namespace xrpl { -namespace test { -namespace jtx { +namespace xrpl::test::jtx { struct basic_prop { @@ -38,6 +36,4 @@ struct prop_type : basic_prop } }; -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/batch.h b/src/test/jtx/batch.h index f81bed3c6e..90a7bef8cc 100644 --- a/src/test/jtx/batch.h +++ b/src/test/jtx/batch.h @@ -2,24 +2,20 @@ #include #include +#include #include #include #include #include -#include "test/jtx/SignerUtils.h" - #include #include #include - -namespace xrpl { -namespace test { -namespace jtx { +#include /** Batch operations */ -namespace batch { +namespace xrpl::test::jtx::batch { /** Calculate Batch Fee. */ XRPAmount @@ -39,10 +35,10 @@ private: public: inner( - Json::Value const& txn, + Json::Value txn, std::uint32_t const& sequence, std::optional const& ticket = std::nullopt) - : txn_(txn), seq_(sequence), ticket_(ticket) + : txn_(std::move(txn)), seq_(sequence), ticket_(ticket) { txn_[jss::SigningPubKey] = ""; txn_[jss::Sequence] = seq_; @@ -109,16 +105,16 @@ public: Account master; std::vector signers; - msig(Account const& masterAccount, std::vector signers_) - : master(masterAccount), signers(std::move(signers_)) + msig(Account masterAccount, std::vector signers_) + : master(std::move(masterAccount)), signers(std::move(signers_)) { sortSigners(signers); } template requires std::convertible_to - explicit msig(Account const& masterAccount, AccountType&& a0, Accounts&&... aN) - : master(masterAccount) + explicit msig(Account masterAccount, AccountType&& a0, Accounts&&... aN) + : master(std::move(masterAccount)) , signers{std::forward(a0), std::forward(aN)...} { sortSigners(signers); @@ -128,9 +124,4 @@ public: operator()(Env&, JTx& jt) const; }; -} // namespace batch - -} // namespace jtx - -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx::batch diff --git a/src/test/jtx/check.h b/src/test/jtx/check.h index 9a1c6b2d2c..fc1d8054c6 100644 --- a/src/test/jtx/check.h +++ b/src/test/jtx/check.h @@ -4,9 +4,9 @@ #include #include -namespace xrpl { -namespace test { -namespace jtx { +#include + +namespace xrpl::test::jtx { /** Check operations. */ namespace check { @@ -19,7 +19,7 @@ cash(jtx::Account const& dest, uint256 const& checkId, STAmount const& amount); struct DeliverMin { STAmount value; - explicit DeliverMin(STAmount const& deliverMin) : value(deliverMin) + explicit DeliverMin(STAmount deliverMin) : value(std::move(deliverMin)) { } }; @@ -37,7 +37,4 @@ cancel(jtx::Account const& dest, uint256 const& checkId); /** Match the number of checks on the account. */ using checks = owner_count; -} // namespace jtx - -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/credentials.h b/src/test/jtx/credentials.h index 56d127fca9..fd6d31f56d 100644 --- a/src/test/jtx/credentials.h +++ b/src/test/jtx/credentials.h @@ -4,11 +4,7 @@ #include #include -namespace xrpl { -namespace test { -namespace jtx { - -namespace credentials { +namespace xrpl::test::jtx::credentials { inline Keylet keylet( @@ -80,7 +76,4 @@ ledgerEntry( Json::Value ledgerEntry(jtx::Env& env, std::string const& credIdx); -} // namespace credentials -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx::credentials diff --git a/src/test/jtx/delegate.h b/src/test/jtx/delegate.h index 7aecb54922..197750a511 100644 --- a/src/test/jtx/delegate.h +++ b/src/test/jtx/delegate.h @@ -3,11 +3,9 @@ #include #include -namespace xrpl { -namespace test { -namespace jtx { +#include -namespace delegate { +namespace xrpl::test::jtx::delegate { Json::Value set(jtx::Account const& account, @@ -23,7 +21,7 @@ private: jtx::Account delegate_; public: - explicit as(jtx::Account const& account) : delegate_(account) + explicit as(jtx::Account account) : delegate_(std::move(account)) { } @@ -34,7 +32,4 @@ public: } }; -} // namespace delegate -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx::delegate diff --git a/src/test/jtx/delivermin.h b/src/test/jtx/delivermin.h index 3edb38ffef..6dc0bea2ad 100644 --- a/src/test/jtx/delivermin.h +++ b/src/test/jtx/delivermin.h @@ -4,9 +4,9 @@ #include -namespace xrpl { -namespace test { -namespace jtx { +#include + +namespace xrpl::test::jtx { /** Sets the DeliverMin on a JTx. */ class deliver_min @@ -15,7 +15,7 @@ private: STAmount amount_; public: - deliver_min(STAmount const& amount) : amount_(amount) + deliver_min(STAmount amount) : amount_(std::move(amount)) { } @@ -23,6 +23,4 @@ public: operator()(Env&, JTx& jtx) const; }; -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/deposit.h b/src/test/jtx/deposit.h index d7762e9ae3..d74db770c7 100644 --- a/src/test/jtx/deposit.h +++ b/src/test/jtx/deposit.h @@ -3,12 +3,8 @@ #include #include -namespace xrpl { -namespace test { -namespace jtx { - /** Deposit preauthorize operations */ -namespace deposit { +namespace xrpl::test::jtx::deposit { /** Preauthorize for deposit. Invoke as deposit::auth. */ Json::Value @@ -52,9 +48,4 @@ authCredentials(jtx::Account const& account, std::vector c Json::Value unauthCredentials(jtx::Account const& account, std::vector const& auth); -} // namespace deposit - -} // namespace jtx - -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx::deposit diff --git a/src/test/jtx/did.h b/src/test/jtx/did.h index e6b06f8f7c..faf4b2046c 100644 --- a/src/test/jtx/did.h +++ b/src/test/jtx/did.h @@ -4,12 +4,8 @@ #include #include -namespace xrpl { -namespace test { -namespace jtx { - /** DID operations. */ -namespace did { +namespace xrpl::test::jtx::did { Json::Value set(jtx::Account const& account); @@ -74,9 +70,4 @@ public: Json::Value del(jtx::Account const& account); -} // namespace did - -} // namespace jtx - -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx::did diff --git a/src/test/jtx/directory.h b/src/test/jtx/directory.h index e9fff35434..0940c23623 100644 --- a/src/test/jtx/directory.h +++ b/src/test/jtx/directory.h @@ -9,10 +9,8 @@ #include #include -namespace xrpl::test::jtx { - /** Directory operations. */ -namespace directory { +namespace xrpl::test::jtx::directory { enum Error { DirectoryRootNotFound, @@ -53,6 +51,4 @@ maximumPageIndex(Env const& env) -> std::uint64_t return dirNodeMaxPages - 1; } -} // namespace directory - -} // namespace xrpl::test::jtx +} // namespace xrpl::test::jtx::directory diff --git a/src/test/jtx/domain.h b/src/test/jtx/domain.h index cb67ce3622..993c89ee19 100644 --- a/src/test/jtx/domain.h +++ b/src/test/jtx/domain.h @@ -2,9 +2,7 @@ #include -namespace xrpl { -namespace test { -namespace jtx { +namespace xrpl::test::jtx { /** Set the domain on a JTx. */ class domain @@ -21,6 +19,4 @@ public: operator()(Env&, JTx& jt) const; }; -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/envconfig.h b/src/test/jtx/envconfig.h index e4a1975e74..dcfafc426e 100644 --- a/src/test/jtx/envconfig.h +++ b/src/test/jtx/envconfig.h @@ -2,8 +2,7 @@ #include -namespace xrpl { -namespace test { +namespace xrpl::test { // frequently used macros defined here for convenience. #define PORT_WS "port_ws" @@ -107,11 +106,56 @@ std::unique_ptr addGrpcConfig(std::unique_ptr); std::unique_ptr addGrpcConfigWithSecureGateway(std::unique_ptr, std::string const& secureGateway); +/// @brief add a grpc address, port and TLS certificate/key paths to config +/// +/// This is intended for use with envconfig, for tests that require a grpc +/// server with TLS enabled. +/// +/// @param cfg config instance to be modified +/// @param certPath path to SSL certificate file +/// @param keyPath path to SSL private key file +std::unique_ptr +addGrpcConfigWithTLS( + std::unique_ptr, + std::string const& certPath, + std::string const& keyPath); + +/// @brief add a grpc address, port and TLS certificate/key/client CA paths to config +/// +/// This is intended for use with envconfig, for tests that require a grpc +/// server with mutual TLS (client certificate verification) enabled. +/// +/// @param cfg config instance to be modified +/// @param certPath path to SSL certificate file +/// @param keyPath path to SSL private key file +/// @param clientCAPath path to SSL client CA certificate file for mTLS +std::unique_ptr +addGrpcConfigWithTLSAndClientCA( + std::unique_ptr, + std::string const& certPath, + std::string const& keyPath, + std::string const& clientCAPath); + +/// @brief add a grpc address, port and TLS with server cert chain to config +/// +/// This is intended for use with envconfig, for tests that require a grpc +/// server with TLS enabled and intermediate CA certificates. +/// +/// @param cfg config instance to be modified +/// @param certPath path to SSL certificate file +/// @param keyPath path to SSL private key file +/// @param certChainPath path to SSL intermediate CA certificate(s) file +std::unique_ptr +addGrpcConfigWithTLSAndCertChain( + std::unique_ptr, + std::string const& certPath, + std::string const& keyPath, + std::string const& certChainPath); + std::unique_ptr makeConfig( std::map extraTxQ = {}, std::map extraVoting = {}); } // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/jtx/escrow.h b/src/test/jtx/escrow.h index 8fe9d9f5f9..8bb505a592 100644 --- a/src/test/jtx/escrow.h +++ b/src/test/jtx/escrow.h @@ -8,12 +8,8 @@ #include -namespace xrpl { -namespace test { -namespace jtx { - /** Escrow operations. */ -namespace escrow { +namespace xrpl::test::jtx::escrow { Json::Value create(AccountID const& account, AccountID const& to, STAmount const& amount); @@ -79,9 +75,4 @@ auto const condition = JTxFieldWrapper(sfCondition); auto const fulfillment = JTxFieldWrapper(sfFulfillment); -} // namespace escrow - -} // namespace jtx - -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx::escrow diff --git a/src/test/jtx/fee.h b/src/test/jtx/fee.h index 281066f0aa..f586e2a082 100644 --- a/src/test/jtx/fee.h +++ b/src/test/jtx/fee.h @@ -8,9 +8,7 @@ #include -namespace xrpl { -namespace test { -namespace jtx { +namespace xrpl::test::jtx { /** Set the fee on a JTx. */ class fee @@ -47,6 +45,4 @@ public: operator()(Env&, JTx& jt) const; }; -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/flags.h b/src/test/jtx/flags.h index a203f1461e..4bf095e685 100644 --- a/src/test/jtx/flags.h +++ b/src/test/jtx/flags.h @@ -6,6 +6,8 @@ #include #include +#include + namespace xrpl { namespace detail { @@ -87,8 +89,7 @@ protected: } // namespace detail -namespace test { -namespace jtx { +namespace test::jtx { // JSON generators @@ -104,14 +105,14 @@ fclear(Account const& account, std::uint32_t off) } /** Match set account flags */ -class flags : private detail::flags_helper +class flags : private xrpl::detail::flags_helper { private: Account account_; public: template - flags(Account const& account, Args... args) : flags_helper(args...), account_(account) + flags(Account account, Args... args) : flags_helper(args...), account_(std::move(account)) { } @@ -120,14 +121,14 @@ public: }; /** Match clear account flags */ -class nflags : private detail::flags_helper +class nflags : private xrpl::detail::flags_helper { private: Account account_; public: template - nflags(Account const& account, Args... args) : flags_helper(args...), account_(account) + nflags(Account account, Args... args) : flags_helper(args...), account_(std::move(account)) { } @@ -135,6 +136,6 @@ public: operator()(Env& env) const; }; -} // namespace jtx -} // namespace test +} // namespace test::jtx + } // namespace xrpl diff --git a/src/test/jtx/impl/AMM.cpp b/src/test/jtx/impl/AMM.cpp index fe8fb8c443..6f68033fc4 100644 --- a/src/test/jtx/impl/AMM.cpp +++ b/src/test/jtx/impl/AMM.cpp @@ -1,17 +1,44 @@ #include -#include +#include +#include +#include +#include +#include + +#include +#include #include +#include +#include +#include +#include #include +#include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include -#include -#include -namespace xrpl { -namespace test { -namespace jtx { +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl::test::jtx { static Number number(STAmount const& a) @@ -34,9 +61,9 @@ AMM::initialTokens() AMM::AMM( Env& env, - Account const& account, - STAmount const& asset1, - STAmount const& asset2, + Account account, + STAmount asset1, + STAmount asset2, bool log, std::uint16_t tfee, std::uint32_t fee, @@ -46,17 +73,17 @@ AMM::AMM( std::optional const& ter, bool close) : env_(env) - , creatorAccount_(account) - , asset1_(asset1) - , asset2_(asset2) - , ammID_(keylet::amm(asset1_.issue(), asset2_.issue()).key) + , creatorAccount_(std::move(account)) + , asset1_(std::move(asset1)) + , asset2_(std::move(asset2)) + , ammID_(keylet::amm(asset1_.asset(), asset2_.asset()).key) , log_(log) , doClose_(close) , lastPurchasePrice_(0) - , msig_(ms) + , msig_(std::move(ms)) , fee_(fee) , ammAccount_(create(tfee, flags, seq, ter)) - , lptIssue_(xrpl::ammLPTIssue(asset1_.issue().currency, asset2_.issue().currency, ammAccount_)) + , lptIssue_(xrpl::ammLPTIssue(asset1_.asset(), asset2_.asset(), ammAccount_)) , initialLPTokens_(initialTokens()) { } @@ -105,6 +132,23 @@ AMM::AMM( { } +Json::Value +AMM::createJv( + AccountID const& account, + STAmount const& asset1, + STAmount const& asset2, + std::uint16_t const& tfee) +{ + Json::Value jv; + jv[jss::Account] = to_string(account); + jv[jss::Amount] = asset1.getJson(JsonOptions::none); + jv[jss::Amount2] = asset2.getJson(JsonOptions::none); + jv[jss::TradingFee] = tfee; + jv[jss::TransactionType] = jss::AMMCreate; + + return jv; +} + [[nodiscard]] AccountID AMM::create( std::uint32_t tfee, @@ -112,12 +156,7 @@ AMM::create( std::optional const& seq, std::optional const& ter) { - Json::Value jv; - jv[jss::Account] = creatorAccount_.human(); - jv[jss::Amount] = asset1_.getJson(JsonOptions::none); - jv[jss::Amount2] = asset2_.getJson(JsonOptions::none); - jv[jss::TradingFee] = tfee; - jv[jss::TransactionType] = jss::AMMCreate; + Json::Value jv = createJv(creatorAccount_, asset1_, asset2_, tfee); if (flags) jv[jss::Flags] = *flags; if (fee_ != 0) @@ -132,7 +171,7 @@ AMM::create( if (!ter || env_.ter() == tesSUCCESS) { - if (auto const amm = env_.current()->read(keylet::amm(asset1_.issue(), asset2_.issue()))) + if (auto const amm = env_.current()->read(keylet::amm(asset1_.asset(), asset2_.asset()))) { return amm->getAccountID(sfAccount); } @@ -144,8 +183,8 @@ Json::Value AMM::ammRpcInfo( std::optional const& account, std::optional const& ledgerIndex, - std::optional issue1, - std::optional issue2, + std::optional asset1, + std::optional asset2, std::optional const& ammAccount, bool ignoreParams, unsigned apiVersion) const @@ -157,17 +196,17 @@ AMM::ammRpcInfo( jv[jss::ledger_index] = *ledgerIndex; if (!ignoreParams) { - if (issue1 || issue2) + if (asset1 || asset2) { - if (issue1) - jv[jss::asset] = STIssue(sfAsset, *issue1).getJson(JsonOptions::none); - if (issue2) - jv[jss::asset2] = STIssue(sfAsset2, *issue2).getJson(JsonOptions::none); + if (asset1) + jv[jss::asset] = STIssue(sfAsset, *asset1).getJson(JsonOptions::none); + if (asset2) + jv[jss::asset2] = STIssue(sfAsset2, *asset2).getJson(JsonOptions::none); } else if (!ammAccount) { - jv[jss::asset] = STIssue(sfAsset, asset1_.issue()).getJson(JsonOptions::none); - jv[jss::asset2] = STIssue(sfAsset2, asset2_.issue()).getJson(JsonOptions::none); + jv[jss::asset] = STIssue(sfAsset, asset1_.asset()).getJson(JsonOptions::none); + jv[jss::asset2] = STIssue(sfAsset2, asset2_.asset()).getJson(JsonOptions::none); } if (ammAccount) jv[jss::amm_account] = to_string(*ammAccount); @@ -182,18 +221,19 @@ AMM::ammRpcInfo( } std::tuple -AMM::balances(Issue const& issue1, Issue const& issue2, std::optional const& account) +AMM::balances(Asset const& asset1, Asset const& asset2, std::optional const& account) const { - if (auto const amm = env_.current()->read(keylet::amm(asset1_.issue(), asset2_.issue()))) + if (auto const amm = env_.current()->read(keylet::amm(asset1_.asset(), asset2_.asset()))) { auto const ammAccountID = amm->getAccountID(sfAccount); auto const [asset1Balance, asset2Balance] = ammPoolHolds( *env_.current(), ammAccountID, - issue1, - issue2, + asset1, + asset2, FreezeHandling::fhIGNORE_FREEZE, + AuthHandling::ahIGNORE_AUTH, env_.journal); auto const lptAMMBalance = account ? ammLPHolds(*env_.current(), *amm, *account, env_.journal) @@ -211,7 +251,7 @@ AMM::expectBalances( std::optional const& account) const { auto const [asset1Balance, asset2Balance, lptAMMBalance] = - balances(asset1.issue(), asset2.issue(), account); + balances(asset1.asset(), asset2.asset(), account); return asset1 == asset1Balance && asset2 == asset2Balance && lptAMMBalance == STAmount{lpt, lptIssue_}; } @@ -229,7 +269,7 @@ AMM::getLPTokensBalance(std::optional const& account) const env_.journal) .iou(); } - if (auto const amm = env_.current()->read(keylet::amm(asset1_.issue(), asset2_.issue()))) + if (auto const amm = env_.current()->read(keylet::amm(asset1_.asset(), asset2_.asset()))) return amm->getFieldAmount(sfLPTokenBalance).iou(); return IOUAmount{0}; } @@ -237,7 +277,7 @@ AMM::getLPTokensBalance(std::optional const& account) const bool AMM::expectLPTokens(AccountID const& account, IOUAmount const& expTokens) const { - if (auto const amm = env_.current()->read(keylet::amm(asset1_.issue(), asset2_.issue()))) + if (auto const amm = env_.current()->read(keylet::amm(asset1_.asset(), asset2_.asset()))) { auto const lptAMMBalance = ammLPHolds(*env_.current(), *amm, account, env_.journal); return lptAMMBalance == STAmount{expTokens, lptIssue_}; @@ -270,10 +310,8 @@ AMM::expectAuctionSlot(std::vector const& authAccounts) const [&](std::uint32_t, std::optional, IOUAmount const&, STArray const& accounts) { for (auto const& account : accounts) { - if (std::find( - authAccounts.cbegin(), - authAccounts.cend(), - account.getAccountID(sfAccount)) == authAccounts.end()) + if (std::ranges::find(authAccounts, account.getAccountID(sfAccount)) == + authAccounts.end()) return false; } return true; @@ -283,7 +321,7 @@ AMM::expectAuctionSlot(std::vector const& authAccounts) const bool AMM::expectTradingFee(std::uint16_t fee) const { - auto const amm = env_.current()->read(keylet::amm(asset1_.issue(), asset2_.issue())); + auto const amm = env_.current()->read(keylet::amm(asset1_.asset(), asset2_.asset())); return amm && (*amm)[sfTradingFee] == fee; } @@ -291,7 +329,7 @@ bool AMM::ammExists() const { return env_.current()->read(keylet::account(ammAccount_)) != nullptr && - env_.current()->read(keylet::amm(asset1_.issue(), asset2_.issue())) != nullptr; + env_.current()->read(keylet::amm(asset1_.asset(), asset2_.asset())) != nullptr; } bool @@ -329,14 +367,14 @@ AMM::expectAmmInfo( if (!amountFromJsonNoThrow(lptBalance, jv[jss::lp_token])) return false; // ammRpcInfo returns unordered assets - if (asset1Info.issue() != asset1.issue()) + if (asset1Info.asset() != asset1.asset()) std::swap(asset1Info, asset2Info); return asset1 == asset1Info && asset2 == asset2Info && lptBalance == STAmount{balance, lptIssue_}; } void -AMM::setTokens(Json::Value& jv, std::optional> const& assets) +AMM::setTokens(Json::Value& jv, std::optional> const& assets) { if (assets) { @@ -345,28 +383,65 @@ AMM::setTokens(Json::Value& jv, std::optional> const& as } else { - jv[jss::Asset] = STIssue(sfAsset, asset1_.issue()).getJson(JsonOptions::none); - jv[jss::Asset2] = STIssue(sfAsset, asset2_.issue()).getJson(JsonOptions::none); + jv[jss::Asset] = STIssue(sfAsset, asset1_.asset()).getJson(JsonOptions::none); + jv[jss::Asset2] = STIssue(sfAsset, asset2_.asset()).getJson(JsonOptions::none); } } -IOUAmount -AMM::deposit( - std::optional const& account, - Json::Value& jv, - std::optional> const& assets, - std::optional const& seq, - std::optional const& ter) +Json::Value +AMM::depositJv(DepositArg const& arg) { - auto const& acct = account ? *account : creatorAccount_; - auto const lpTokens = getLPTokensBalance(acct); - jv[jss::Account] = acct.human(); - setTokens(jv, assets); + Json::Value jv; + if (!arg.account || !arg.assets) + Throw("AMM::depositJv: account or assets not set"); + + jv[jss::Account] = arg.account->human(); + jv[jss::Asset] = STIssue(sfAsset, arg.assets->first).getJson(JsonOptions::none); + jv[jss::Asset2] = STIssue(sfAsset, arg.assets->second).getJson(JsonOptions::none); + if (arg.tokens) + arg.tokens->tokens().setJson(jv[jss::LPTokenOut]); + if (arg.asset1In) + arg.asset1In->setJson(jv[jss::Amount]); + if (arg.asset2In) + arg.asset2In->setJson(jv[jss::Amount2]); + if (arg.maxEP) + arg.maxEP->setJson(jv[jss::EPrice]); + if (arg.tfee) + jv[jss::TradingFee] = *arg.tfee; + std::uint32_t flags = 0; + if (arg.flags) + flags = *arg.flags; + // If including asset1In and asset2In or tokens as + // deposit min amounts then must set the flags + // explicitly instead of relying on this logic. + if ((flags & tfDepositSubTx) == 0u) + { + if (arg.tokens && !arg.asset1In) + { + flags |= tfLPToken; + } + else if (arg.tokens && arg.asset1In) + { + flags |= tfOneAssetLPToken; + } + else if (arg.asset1In && arg.asset2In) + { + flags |= tfTwoAsset; + } + else if (arg.maxEP && arg.asset1In) + { + flags |= tfLimitLPToken; + } + else if (arg.asset1In) + { + flags |= tfSingleAsset; + } + } + jv[jss::Flags] = flags; + jv[jss::TransactionType] = jss::AMMDeposit; - if (fee_ != 0) - jv[jss::Fee] = std::to_string(fee_); - submit(jv, seq, ter); - return getLPTokensBalance(acct) - lpTokens; + + return jv; } IOUAmount @@ -399,7 +474,8 @@ AMM::deposit( std::optional const& flags, std::optional const& ter) { - assert(!(asset2In && maxEP)); + if (asset2In && maxEP) + Throw("Invalid options: asset2In and maxEP"); return deposit( account, std::nullopt, @@ -421,53 +497,26 @@ AMM::deposit( std::optional const& asset2In, std::optional const& maxEP, std::optional const& flags, - std::optional> const& assets, + std::optional> const& assets, std::optional const& seq, std::optional const& tfee, std::optional const& ter) { - Json::Value jv; - if (tokens) - tokens->tokens(lptIssue_).setJson(jv[jss::LPTokenOut]); - if (asset1In) - asset1In->setJson(jv[jss::Amount]); - if (asset2In) - asset2In->setJson(jv[jss::Amount2]); - if (maxEP) - maxEP->setJson(jv[jss::EPrice]); - if (tfee) - jv[jss::TradingFee] = *tfee; - std::uint32_t jvFlags = 0; - if (flags) - jvFlags = *flags; - // If including asset1In and asset2In or tokens as - // deposit min amounts then must set the flags - // explicitly instead of relying on this logic. - if ((jvFlags & tfDepositSubTx) == 0u) - { - if (tokens && !asset1In) - { - jvFlags |= tfLPToken; - } - else if (tokens && asset1In) - { - jvFlags |= tfOneAssetLPToken; - } - else if (asset1In && asset2In) - { - jvFlags |= tfTwoAsset; - } - else if (maxEP && asset1In) - { - jvFlags |= tfLimitLPToken; - } - else if (asset1In) - { - jvFlags |= tfSingleAsset; - } - } - jv[jss::Flags] = jvFlags; - return deposit(account, jv, assets, seq, ter); + auto const acct = account ? account : creatorAccount_; + auto const lpTokens = getLPTokensBalance(acct); + Json::Value jv = depositJv( + {.account = acct, + .tokens = tokens ? tokens->tokens(lptIssue_) : tokens, + .asset1In = asset1In, + .asset2In = asset2In, + .maxEP = maxEP, + .flags = flags, + .assets = assets ? assets : std::make_pair(asset1_.asset(), asset2_.asset()), + .tfee = tfee}); + if (fee_ != 0) + jv[jss::Fee] = std::to_string(fee_); + submit(jv, seq, ter); + return getLPTokensBalance(acct) - lpTokens; } IOUAmount @@ -486,23 +535,54 @@ AMM::deposit(DepositArg const& arg) arg.err); } -IOUAmount -AMM::withdraw( - std::optional const& account, - Json::Value& jv, - std::optional const& seq, - std::optional> const& assets, - std::optional const& ter) +Json::Value +AMM::withdrawJv(WithdrawArg const& arg) { - auto const& acct = account ? *account : creatorAccount_; - auto const lpTokens = getLPTokensBalance(acct); - jv[jss::Account] = acct.human(); - setTokens(jv, assets); + Json::Value jv; + if (!arg.account || !arg.assets) + Throw("AMM::withdrawJv: account or assets not set"); + jv[jss::Account] = arg.account->human(); + jv[jss::Asset] = STIssue(sfAsset, arg.assets->first).getJson(JsonOptions::none); + jv[jss::Asset2] = STIssue(sfAsset, arg.assets->second).getJson(JsonOptions::none); + if (arg.tokens) + arg.tokens->tokens().setJson(jv[jss::LPTokenIn]); + if (arg.asset1Out) + arg.asset1Out->setJson(jv[jss::Amount]); + if (arg.asset2Out) + arg.asset2Out->setJson(jv[jss::Amount2]); + if (arg.maxEP) + arg.maxEP->tokens().setJson(jv[jss::EPrice]); + std::uint32_t flags = 0; + if (arg.flags) + flags = *arg.flags; + if ((flags & tfWithdrawSubTx) == 0u) + { + if (arg.tokens && !arg.asset1Out) + { + flags |= tfLPToken; + } + else if (arg.asset1Out && arg.asset2Out) + { + flags |= tfTwoAsset; + } + else if (arg.tokens && arg.asset1Out) + { + flags |= tfOneAssetLPToken; + } + else if (arg.asset1Out && arg.maxEP) + { + flags |= tfLimitLPToken; + } + else if (arg.asset1Out) + { + flags |= tfSingleAsset; + } + } + jv[jss::Flags] = flags; + jv[jss::TransactionType] = jss::AMMWithdraw; - if (fee_ != 0) - jv[jss::Fee] = std::to_string(fee_); - submit(jv, seq, ter); - return lpTokens - getLPTokensBalance(acct); + + return jv; } IOUAmount @@ -530,10 +610,11 @@ AMM::withdraw( std::optional const& account, STAmount const& asset1Out, std::optional const& asset2Out, - std::optional const& maxEP, + std::optional const& maxEP, std::optional const& ter) { - assert(!(asset2Out && maxEP)); + if (asset2Out && maxEP) + Throw("Invalid options: asset2Out and maxEP"); return withdraw( account, std::nullopt, @@ -552,52 +633,27 @@ AMM::withdraw( std::optional const& tokens, std::optional const& asset1Out, std::optional const& asset2Out, - std::optional const& maxEP, + std::optional const& maxEP, std::optional const& flags, - std::optional> const& assets, + std::optional> const& assets, std::optional const& seq, std::optional const& ter) { - Json::Value jv; - if (tokens) - tokens->tokens(lptIssue_).setJson(jv[jss::LPTokenIn]); - if (asset1Out) - asset1Out->setJson(jv[jss::Amount]); - if (asset2Out) - asset2Out->setJson(jv[jss::Amount2]); - if (maxEP) - { - STAmount const saMaxEP{*maxEP, lptIssue_}; - saMaxEP.setJson(jv[jss::EPrice]); - } - std::uint32_t jvFlags = 0; - if (flags) - jvFlags = *flags; - if ((jvFlags & tfWithdrawSubTx) == 0u) - { - if (tokens && !asset1Out) - { - jvFlags |= tfLPToken; - } - else if (asset1Out && asset2Out) - { - jvFlags |= tfTwoAsset; - } - else if (tokens && asset1Out) - { - jvFlags |= tfOneAssetLPToken; - } - else if (asset1Out && maxEP) - { - jvFlags |= tfLimitLPToken; - } - else if (asset1Out) - { - jvFlags |= tfSingleAsset; - } - } - jv[jss::Flags] = jvFlags; - return withdraw(account, jv, seq, assets, ter); + auto const acct = account ? account : creatorAccount_; + auto const lpTokens = getLPTokensBalance(acct); + Json::Value jv = withdrawJv({ + .account = acct, + .tokens = tokens ? tokens->tokens(lptIssue_) : tokens, + .asset1Out = asset1Out, + .asset2Out = asset2Out, + .maxEP = maxEP ? maxEP->tokens(lptIssue_) : maxEP, + .flags = flags, + .assets = assets ? assets : std::make_pair(asset1_.asset(), asset2_.asset()), + }); + if (fee_ != 0) + jv[jss::Fee] = std::to_string(fee_); + submit(jv, seq, ter); + return lpTokens - getLPTokensBalance(acct); } IOUAmount @@ -615,22 +671,39 @@ AMM::withdraw(WithdrawArg const& arg) arg.err); } +Json::Value +AMM::voteJv(VoteArg const& arg) +{ + Json::Value jv; + if (!arg.account || !arg.assets) + Throw("AMM::withdrawJv: account or assets not set"); + jv[jss::Account] = arg.account->human(); + jv[jss::Asset] = STIssue(sfAsset, arg.assets->first).getJson(JsonOptions::none); + jv[jss::Asset2] = STIssue(sfAsset, arg.assets->second).getJson(JsonOptions::none); + jv[jss::TradingFee] = arg.tfee; + if (arg.flags) + jv[jss::Flags] = *arg.flags; + + jv[jss::TransactionType] = jss::AMMVote; + + return jv; +} + void AMM::vote( std::optional const& account, std::uint32_t feeVal, std::optional const& flags, std::optional const& seq, - std::optional> const& assets, + std::optional> const& assets, std::optional const& ter) { - Json::Value jv; - jv[jss::Account] = account ? account->human() : creatorAccount_.human(); - setTokens(jv, assets); - jv[jss::TradingFee] = feeVal; - jv[jss::TransactionType] = jss::AMMVote; - if (flags) - jv[jss::Flags] = *flags; + Json::Value jv = voteJv({ + .account = account ? account : creatorAccount_, + .tfee = feeVal, + .flags = flags, + .assets = assets ? assets : std::make_pair(asset1_.asset(), asset2_.asset()), + }); if (fee_ != 0) jv[jss::Fee] = std::to_string(fee_); submit(jv, seq, ter); @@ -645,11 +718,11 @@ AMM::vote(VoteArg const& arg) Json::Value AMM::bid(BidArg const& arg) { - if (auto const amm = env_.current()->read(keylet::amm(asset1_.issue(), asset2_.issue()))) + if (auto const amm = env_.current()->read(keylet::amm(asset1_.asset(), asset2_.asset()))) { - assert( - !env_.current()->rules().enabled(fixInnerObjTemplate) || - amm->isFieldPresent(sfAuctionSlot)); + if (env_.current()->rules().enabled(fixInnerObjTemplate) && + !amm->isFieldPresent(sfAuctionSlot)) + Throw("AMM::Bid"); if (amm->isFieldPresent(sfAuctionSlot)) { auto const& auctionSlot = @@ -708,6 +781,22 @@ AMM::bid(BidArg const& arg) return jv; } +void +AMM::clawback(ClawbackArg const& arg) +{ + auto const& [asset, asset2] = [&]() { + if (arg.assets) + return *arg.assets; + return std::make_pair(asset1_.asset(), asset2_.asset()); + }(); + auto jv = amm::ammClawback(arg.issuer, arg.holder, asset, asset2, arg.amount); + if (arg.flags) + jv[jss::Flags] = *arg.flags; + if (fee_ != 0) + jv[jss::Fee] = std::to_string(fee_); + submit(jv, std::nullopt, arg.err); +} + void AMM::submit( Json::Value const& jv, @@ -758,11 +847,11 @@ AMM::submit( bool AMM::expectAuctionSlot(auto&& cb) const { - if (auto const amm = env_.current()->read(keylet::amm(asset1_.issue(), asset2_.issue()))) + if (auto const amm = env_.current()->read(keylet::amm(asset1_.asset(), asset2_.asset()))) { - assert( - !env_.current()->rules().enabled(fixInnerObjTemplate) || - amm->isFieldPresent(sfAuctionSlot)); + if (env_.current()->rules().enabled(fixInnerObjTemplate) && + !amm->isFieldPresent(sfAuctionSlot)) + Throw("AMM::expectAuctionSlot"); if (amm->isFieldPresent(sfAuctionSlot)) { auto const& auctionSlot = @@ -785,48 +874,36 @@ AMM::expectAuctionSlot(auto&& cb) const return false; } -void -AMM::ammDelete(AccountID const& deleter, std::optional const& ter) +Json::Value +AMM::deleteJv(AccountID const& account, Asset const& asset1, Asset const& asset2) { Json::Value jv; - jv[jss::Account] = to_string(deleter); - setTokens(jv); + jv[jss::Account] = to_string(account); + jv[jss::Asset] = STIssue(sfAsset, asset1).getJson(JsonOptions::none); + jv[jss::Asset2] = STIssue(sfAsset, asset2).getJson(JsonOptions::none); + jv[jss::TransactionType] = jss::AMMDelete; + + return jv; +} + +void +AMM::ammDelete(AccountID const& account, std::optional const& ter) +{ + Json::Value jv = deleteJv(account, asset1_.asset(), asset2_.asset()); if (fee_ != 0) jv[jss::Fee] = std::to_string(fee_); submit(jv, std::nullopt, ter); } namespace amm { -Json::Value -trust(AccountID const& account, STAmount const& amount, std::uint32_t flags) -{ - if (isXRP(amount)) - Throw("trust() requires IOU"); - Json::Value jv; - jv[jss::Account] = to_string(account); - jv[jss::LimitAmount] = amount.getJson(JsonOptions::none); - jv[jss::TransactionType] = jss::TrustSet; - jv[jss::Flags] = flags; - return jv; -} -Json::Value -pay(Account const& account, AccountID const& to, STAmount const& amount) -{ - Json::Value jv; - jv[jss::Account] = account.human(); - jv[jss::Amount] = amount.getJson(JsonOptions::none); - jv[jss::Destination] = to_string(to); - jv[jss::TransactionType] = jss::Payment; - return jv; -} Json::Value ammClawback( Account const& issuer, Account const& holder, - Issue const& asset, - Issue const& asset2, + Asset const& asset, + Asset const& asset2, std::optional const& amount) { Json::Value jv; @@ -841,6 +918,4 @@ ammClawback( return jv; } } // namespace amm -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/impl/AMMTest.cpp b/src/test/jtx/impl/AMMTest.cpp index 9d9a537210..24d831be7d 100644 --- a/src/test/jtx/impl/AMMTest.cpp +++ b/src/test/jtx/impl/AMMTest.cpp @@ -1,20 +1,36 @@ -#include #include + +#include +#include #include #include +#include +#include +#include #include +#include -#include +#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include -namespace xrpl { -namespace test { -namespace jtx { +#include +#include +#include +#include +#include +#include -void +namespace xrpl::test::jtx { + +[[maybe_unused]] std::vector fund( jtx::Env& env, jtx::Account const& gw, @@ -22,16 +38,17 @@ fund( std::vector const& amts, Fund how) { - fund(env, gw, accounts, XRP(30000), amts, how); + return fund(env, gw, accounts, XRP(30000), amts, how); } -void +[[maybe_unused]] std::vector fund( jtx::Env& env, std::vector const& accounts, STAmount const& xrp, std::vector const& amts, - Fund how) + Fund how, + std::optional const& mptIssuer) { for (auto const& account : accounts) { @@ -41,18 +58,37 @@ fund( } } env.close(); + + std::vector amtsOut; for (auto const& account : accounts) { + int i = 0; for (auto const& amt : amts) { - env.trust(amt + amt, account); - env(pay(amt.issue().account, account, amt)); + auto amt_ = [&]() { + if (amtsOut.size() == amts.size()) + { + return amtsOut[i++]; + } + if (amt.holds() && mptIssuer) + { + MPTTester const mpt({.env = env, .issuer = *mptIssuer, .holders = accounts}); + return STAmount{mpt.issuanceID(), amt.mpt().value()}; + } + return amt; + }(); + if (amt.holds()) + env.trust(amt_ + amt_, account); + if (amtsOut.size() != amts.size()) + amtsOut.push_back(amt_); + env(pay(amt_.getIssuer(), account, amt_)); } } env.close(); + return amtsOut; } -void +[[maybe_unused]] std::vector fund( jtx::Env& env, jtx::Account const& gw, @@ -64,7 +100,7 @@ fund( if (how == Fund::All || how == Fund::Gw) env.fund(xrp, gw); env.close(); - fund(env, accounts, xrp, amts, how); + return fund(env, accounts, xrp, amts, how, gw); } AMMTestBase::AMMTestBase() @@ -119,31 +155,41 @@ AMMTestBase::testAMM(std::function const& cb, TestAM return defXRP; return a + XRP(1000); } - auto defIOU = STAmount{a.issue(), 30000}; - if (a <= defIOU) - return defIOU; - return a + STAmount{a.issue(), 1000}; + auto defAmt = STAmount{a.asset(), 30000}; + if (a <= defAmt) + return defAmt; + return a + STAmount{a.asset(), 1000}; }; auto const toFund1 = toFund(asset1); auto const toFund2 = toFund(asset2); BEAST_EXPECT(asset1 <= toFund1 && asset2 <= toFund2); + // asset1/asset2 could be dummy MPT. In this case real MPT + // is created by fund(), which returns the funded amounts. + // The amounts then can be used to figure out the created + // MPT if any. + std::vector funded; if (!asset1.native() && !asset2.native()) { - fund(env, gw, {alice, carol}, {toFund1, toFund2}, Fund::All); + funded = fund(env, gw, {alice, carol}, {toFund1, toFund2}, Fund::All); } else if (asset1.native()) { - fund(env, gw, {alice, carol}, toFund1, {toFund2}, Fund::All); + funded = fund(env, gw, {alice, carol}, toFund1, {toFund2}, Fund::All); + funded.insert(funded.begin(), toFund1); } else if (asset2.native()) { - fund(env, gw, {alice, carol}, toFund2, {toFund1}, Fund::All); + funded = fund(env, gw, {alice, carol}, toFund2, {toFund1}, Fund::All); + funded.push_back(toFund2); } + auto const pool1 = STAmount{funded[0].asset(), static_cast(asset1)}; + auto const pool2 = STAmount{funded[1].asset(), static_cast(asset2)}; + AMM ammAlice( - env, alice, asset1, asset2, CreateArg{.log = false, .tfee = arg.tfee, .err = arg.ter}); - if (BEAST_EXPECT(ammAlice.expectBalances(asset1, asset2, ammAlice.tokens()))) + env, alice, pool1, pool2, CreateArg{.log = false, .tfee = arg.tfee, .err = arg.ter}); + if (BEAST_EXPECT(ammAlice.expectBalances(pool1, pool2, ammAlice.tokens()))) cb(ammAlice, env); } } @@ -173,111 +219,4 @@ AMMTest::pathTestEnv() return cfg; })); } - -Json::Value -AMMTest::find_paths_request( - jtx::Env& env, - jtx::Account const& src, - jtx::Account const& dst, - STAmount const& saDstAmount, - std::optional const& saSendMax, - std::optional const& saSrcCurrency) -{ - using namespace jtx; - - auto& app = env.app(); - Resource::Charge loadType = Resource::feeReferenceRPC; - Resource::Consumer c; - - RPC::JsonContext context{ - {env.journal, - app, - loadType, - app.getOPs(), - app.getLedgerMaster(), - c, - Role::USER, - {}, - {}, - RPC::apiVersionIfUnspecified}, - {}, - {}}; - - Json::Value params = Json::objectValue; - params[jss::command] = "ripple_path_find"; - params[jss::source_account] = toBase58(src); - params[jss::destination_account] = toBase58(dst); - params[jss::destination_amount] = saDstAmount.getJson(JsonOptions::none); - if (saSendMax) - params[jss::send_max] = saSendMax->getJson(JsonOptions::none); - if (saSrcCurrency) - { - auto& sc = params[jss::source_currencies] = Json::arrayValue; - Json::Value j = Json::objectValue; - j[jss::currency] = to_string(saSrcCurrency.value()); - sc.append(j); - } - - Json::Value result; - gate g; - app.getJobQueue().postCoro(jtCLIENT, "RPC-Client", [&](auto const& coro) { - context.params = std::move(params); - context.coro = coro; - RPC::doCommand(context, result); - g.signal(); - }); - - using namespace std::chrono_literals; - BEAST_EXPECT(g.wait_for(5s)); - BEAST_EXPECT(!result.isMember(jss::error)); - return result; -} - -std::tuple -AMMTest::find_paths( - jtx::Env& env, - jtx::Account const& src, - jtx::Account const& dst, - STAmount const& saDstAmount, - std::optional const& saSendMax, - std::optional const& saSrcCurrency) -{ - Json::Value result = find_paths_request(env, src, dst, saDstAmount, saSendMax, saSrcCurrency); - BEAST_EXPECT(!result.isMember(jss::error)); - - STAmount da; - if (result.isMember(jss::destination_amount)) - da = amountFromJson(sfGeneric, result[jss::destination_amount]); - - STAmount sa; - STPathSet paths; - if (result.isMember(jss::alternatives)) - { - auto const& alts = result[jss::alternatives]; - if (alts.size() > 0) - { - auto const& path = alts[0u]; - - if (path.isMember(jss::source_amount)) - sa = amountFromJson(sfGeneric, path[jss::source_amount]); - - if (path.isMember(jss::destination_amount)) - da = amountFromJson(sfGeneric, path[jss::destination_amount]); - - if (path.isMember(jss::paths_computed)) - { - Json::Value p; - p["Paths"] = path[jss::paths_computed]; - STParsedJSONObject po("generic", p); - // NOLINTNEXTLINE(bugprone-unchecked-optional-access) - paths = po.object->getFieldPathSet(sfPaths); - } - } - } - - return std::make_tuple(std::move(paths), std::move(sa), std::move(da)); -} - -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/impl/Account.cpp b/src/test/jtx/impl/Account.cpp index a7b71ea6eb..4e09940487 100644 --- a/src/test/jtx/impl/Account.cpp +++ b/src/test/jtx/impl/Account.cpp @@ -1,11 +1,25 @@ #include + #include +#include +#include +#include +#include +#include +#include +#include #include -namespace xrpl { -namespace test { -namespace jtx { +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl::test::jtx { std::unordered_map, Account, beast::uhash<>> Account::cache_; @@ -79,6 +93,4 @@ Account::operator[](std::string const& s) const return IOU(*this, currency); } -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/impl/Env.cpp b/src/test/jtx/impl/Env.cpp index a6344b5ab1..f03e26b567 100644 --- a/src/test/jtx/impl/Env.cpp +++ b/src/test/jtx/impl/Env.cpp @@ -1,25 +1,48 @@ #include + +#include #include +#include +#include +#include #include #include #include #include #include #include +#include #include #include +#include #include +#include +#include #include -#include +#include +#include +#include #include +#include #include +#include +#include +#include #include +#include #include #include +#include +#include #include #include +#include +#include +#include +#include +#include #include #include #include @@ -27,12 +50,22 @@ #include #include +#include +#include +#include +#include #include +#include +#include #include +#include +#include +#include +#include +#include +#include -namespace xrpl { -namespace test { -namespace jtx { +namespace xrpl::test::jtx { //------------------------------------------------------------------------------ @@ -174,54 +207,47 @@ Env::balance(Account const& account) const } PrettyAmount -Env::balance(Account const& account, Issue const& issue) const -{ - if (isXRP(issue.currency)) - return balance(account); - auto const sle = le(keylet::line(account.id(), issue)); - if (!sle) - return {STAmount(issue, 0), account.name()}; - auto amount = sle->getFieldAmount(sfBalance); - amount.setIssuer(issue.account); - if (account.id() > issue.account) - amount.negate(); - return {amount, lookup(issue.account).name()}; -} - -PrettyAmount -Env::balance(Account const& account, MPTIssue const& mptIssue) const -{ - MPTID const id = mptIssue.getMptID(); - if (!id) - return {STAmount(mptIssue, 0), account.name()}; - - AccountID const issuer = mptIssue.getIssuer(); - if (account.id() == issuer) - { - // Issuer balance - auto const sle = le(keylet::mptIssuance(id)); - if (!sle) - return {STAmount(mptIssue, 0), account.name()}; - - // Make it negative - STAmount const amount{mptIssue, sle->getFieldU64(sfOutstandingAmount), 0, true}; - return {amount, lookup(issuer).name()}; - } - - // Holder balance - auto const sle = le(keylet::mptoken(id, account)); - if (!sle) - return {STAmount(mptIssue, 0), account.name()}; - - STAmount const amount{mptIssue, sle->getFieldU64(sfMPTAmount)}; - return {amount, lookup(issuer).name()}; -} - -PrettyAmount -// NOLINTNEXTLINE(readability-convert-member-functions-to-static) Env::balance(Account const& account, Asset const& asset) const { - return std::visit([&](auto const& issue) { return balance(account, issue); }, asset.value()); + return asset.visit( + [&](Issue const& issue) -> PrettyAmount { + if (isXRP(issue.currency)) + return balance(account); + auto const sle = le(keylet::line(account.id(), issue)); + if (!sle) + return {STAmount(issue, 0), account.name()}; + auto amount = sle->getFieldAmount(sfBalance); + amount.get().account = issue.account; + if (account.id() > issue.account) + amount.negate(); + return {amount, lookup(issue.account).name()}; + }, + [&](MPTIssue const& mptIssue) -> PrettyAmount { + MPTID const& id = mptIssue.getMptID(); + if (!id) + return {STAmount(mptIssue, 0), account.name()}; + + AccountID const& issuer = mptIssue.getIssuer(); + if (account.id() == issuer) + { + // Issuer balance + auto const sle = le(keylet::mptIssuance(id)); + if (!sle) + return {STAmount(mptIssue, 0), account.name()}; + + // Make it negative + STAmount const amount{mptIssue, sle->getFieldU64(sfOutstandingAmount), 0, true}; + return {amount, lookup(issuer).name()}; + } + + // Holder balance + auto const sle = le(keylet::mptoken(id, account)); + if (!sle) + return {STAmount(mptIssue, 0), account.name()}; + + STAmount const amount{mptIssue, sle->getFieldU64(sfMPTAmount)}; + return {amount, lookup(issuer).name()}; + }); } PrettyAmount @@ -300,6 +326,8 @@ Env::fund(bool setDefaultRipple, STAmount const& amount, Account const& account) void Env::trust(STAmount const& amount, Account const& account) { + if (!amount.holds()) + Throw("Env::trust: amount doesn't hold Issue"); auto const start = balance(account); apply( jtx::trust(account, amount), @@ -666,6 +694,4 @@ Env::disableFeature(uint256 const feature) app().config().features.erase(feature); } -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/impl/JSONRPCClient.cpp b/src/test/jtx/impl/JSONRPCClient.cpp index 6b60744aad..c44371c13e 100644 --- a/src/test/jtx/impl/JSONRPCClient.cpp +++ b/src/test/jtx/impl/JSONRPCClient.cpp @@ -1,21 +1,37 @@ #include +#include + +#include + +#include +#include #include +#include #include #include #include -#include +#include +#include +#include +#include +#include +#include #include #include #include #include +#include #include +#include +#include +#include +#include #include -namespace xrpl { -namespace test { +namespace xrpl::test { class JSONRPCClient : public AbstractClient { @@ -31,7 +47,7 @@ class JSONRPCClient : public AbstractClient continue; ParsedPort pp; parse_Port(pp, cfg[name], log); - if (pp.protocol.count("http") == 0) + if (not pp.protocol.contains("http")) continue; using namespace boost::asio::ip; if (pp.ip && pp.ip->is_unspecified()) @@ -74,12 +90,6 @@ public: stream_.connect(ep_); } - ~JSONRPCClient() override - { - // stream_.shutdown(boost::asio::ip::tcp::socket::shutdown_both); - // stream_.close(); - } - /* Return value is an Object type with up to three keys: status @@ -148,5 +158,4 @@ makeJSONRPCClient(Config const& cfg, unsigned rpc_version) return std::make_unique(cfg, rpc_version); } -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/jtx/impl/Oracle.cpp b/src/test/jtx/impl/Oracle.cpp index d48432e8e4..7e49cdcd3d 100644 --- a/src/test/jtx/impl/Oracle.cpp +++ b/src/test/jtx/impl/Oracle.cpp @@ -1,16 +1,34 @@ #include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include -#include +#include // IWYU pragma: keep +#include -#include +#include +#include +#include +#include +#include +#include +#include -namespace xrpl { -namespace test { -namespace jtx { -namespace oracle { +namespace xrpl::test::jtx::oracle { Oracle::Oracle(Env& env, CreateArg const& arg, bool submit) : env_(env) { @@ -18,7 +36,7 @@ Oracle::Oracle(Env& env, CreateArg const& arg, bool submit) : env_(env) // {close-maxLastUpdateTimeDelta, close+maxLastUpdateTimeDelta}. // To make the validation work and to make the clock consistent // for tests running at different time, simulate Unix time starting - // on testStartTime since Ripple epoch. + // on testStartTime since XRPL epoch. auto const now = env_.timeKeeper().now(); if (now.time_since_epoch().count() == 0 || arg.close) env_.close(now + testStartTime - epoch_offset); @@ -116,7 +134,7 @@ Oracle::expectPrice(DataSeries const& series) const return false; for (auto const& data : series) { - if (std::find_if(leSeries.begin(), leSeries.end(), [&](STObject const& o) -> bool { + if (std::ranges::find_if(leSeries, [&](STObject const& o) -> bool { auto const& baseAsset = o.getFieldCurrency(sfBaseAsset); auto const& quoteAsset = o.getFieldCurrency(sfQuoteAsset); auto const& price = o.getFieldU64(sfAssetPrice); @@ -401,7 +419,4 @@ validDocumentID(AnyValue const& v) } } -} // namespace oracle -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx::oracle diff --git a/src/test/jtx/impl/TestHelpers.cpp b/src/test/jtx/impl/TestHelpers.cpp index e10a0c46d0..2495e151f3 100644 --- a/src/test/jtx/impl/TestHelpers.cpp +++ b/src/test/jtx/impl/TestHelpers.cpp @@ -1,13 +1,67 @@ #include + +#include +#include +#include +#include // IWYU pragma: keep +#include +#include #include #include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include -namespace xrpl { -namespace test { -namespace jtx { +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl::test::jtx { // Functions used in debugging Json::Value @@ -55,7 +109,7 @@ stpath_append_one(STPath& st, STPathElement const& pe) bool equal(STAmount const& sa1, STAmount const& sa2) { - return sa1 == sa2 && sa1.issue().account == sa2.issue().account; + return sa1 == sa2 && sa1.getIssuer() == sa2.getIssuer(); } // Issue path element @@ -65,9 +119,211 @@ IPE(Issue const& iss) return STPathElement( STPathElement::typeCurrency | STPathElement::typeIssuer, xrpAccount(), - iss.currency, + PathAsset{iss.currency}, iss.account); } +STPathElement +IPE(MPTIssue const& iss) +{ + return STPathElement( + STPathElement::typeMPT | STPathElement::typeIssuer, + xrpAccount(), + PathAsset{iss.getMptID()}, + iss.getIssuer()); +} + +static void +addSourceAsset( + Json::Value& jv, + PathAsset const& srcAsset, + std::optional const& srcIssuer) +{ + std::visit( + [&](TAsset const& asset) { + if constexpr (std::is_same_v) + { + jv[jss::currency] = to_string(asset); + if (srcIssuer) + jv[jss::issuer] = to_string(*srcIssuer); + } + else + { + if (srcIssuer) + Throw("MPT source_currencies can't have issuer"); + jv[jss::mpt_issuance_id] = to_string(asset); + } + }, + srcAsset.value()); +} + +Json::Value +rpf(jtx::Account const& src, + jtx::Account const& dst, + STAmount const& dstAmount, + std::optional const& sendMax, + std::optional const& srcAsset, + std::optional const& srcIssuer) +{ + Json::Value jv = Json::objectValue; + jv[jss::command] = "ripple_path_find"; + jv[jss::source_account] = toBase58(src); + jv[jss::destination_account] = toBase58(dst); + jv[jss::destination_amount] = dstAmount.getJson(JsonOptions::none); + if (sendMax) + jv[jss::send_max] = sendMax->getJson(JsonOptions::none); + if (srcAsset) + { + auto& sc = jv[jss::source_currencies] = Json::arrayValue; + Json::Value j = Json::objectValue; + addSourceAsset(j, *srcAsset, srcIssuer); + sc.append(j); + } + + return jv; +} + +jtx::Env +pathTestEnv(beast::unit_test::suite& suite) +{ + // These tests were originally written with search parameters that are + // different from the current defaults. This function creates an env + // with the search parameters that the tests were written for. + using namespace jtx; + return Env(suite, envconfig([](std::unique_ptr cfg) { + cfg->PATH_SEARCH_OLD = 7; + cfg->PATH_SEARCH = 7; + cfg->PATH_SEARCH_MAX = 10; + return cfg; + })); +} + +Json::Value +find_paths_request( + jtx::Env& env, + jtx::Account const& src, + jtx::Account const& dst, + STAmount const& saDstAmount, + std::optional const& saSendMax, + std::optional const& srcAsset, + std::optional const& srcIssuer, + std::optional const& domain) +{ + using namespace jtx; + + auto& app = env.app(); + Resource::Charge loadType = Resource::feeReferenceRPC; + Resource::Consumer c; + + RPC::JsonContext context{ + {.j = env.journal, + .app = app, + .loadType = loadType, + .netOps = app.getOPs(), + .ledgerMaster = app.getLedgerMaster(), + .consumer = c, + .role = Role::USER, + .coro = {}, + .infoSub = {}, + .apiVersion = RPC::apiVersionIfUnspecified}, + {}, + {}}; + + Json::Value params = Json::objectValue; + params[jss::command] = "ripple_path_find"; + params[jss::source_account] = toBase58(src); + params[jss::destination_account] = toBase58(dst); + params[jss::destination_amount] = saDstAmount.getJson(JsonOptions::none); + if (saSendMax) + params[jss::send_max] = saSendMax->getJson(JsonOptions::none); + + if (srcAsset) + { + auto& sc = params[jss::source_currencies] = Json::arrayValue; + Json::Value j = Json::objectValue; + addSourceAsset(j, *srcAsset, srcIssuer); + sc.append(j); + } + + if (domain) + params[jss::domain] = to_string(*domain); + + Json::Value result; + gate g; + app.getJobQueue().postCoro(jtCLIENT, "RPC-Client", [&](auto const& coro) { + context.params = std::move(params); + context.coro = coro; + RPC::doCommand(context, result); + g.signal(); + }); + + using namespace std::chrono_literals; + using namespace beast::unit_test; + g.wait_for(5s); + return result; +} + +std::tuple +find_paths( + jtx::Env& env, + jtx::Account const& src, + jtx::Account const& dst, + STAmount const& saDstAmount, + std::optional const& saSendMax, + std::optional const& srcAsset, + std::optional const& srcIssuer, + std::optional const& domain) +{ + Json::Value result = + find_paths_request(env, src, dst, saDstAmount, saSendMax, srcAsset, srcIssuer, domain); + if (result.isMember(jss::error)) + return std::make_tuple(STPathSet{}, STAmount{}, STAmount{}); + + STAmount da; + if (result.isMember(jss::destination_amount)) + da = amountFromJson(sfGeneric, result[jss::destination_amount]); + + STAmount sa; + STPathSet paths; + if (result.isMember(jss::alternatives)) + { + auto const& alts = result[jss::alternatives]; + if (alts.size() > 0) + { + auto const& path = alts[0u]; + + if (path.isMember(jss::source_amount)) + sa = amountFromJson(sfGeneric, path[jss::source_amount]); + + if (path.isMember(jss::destination_amount)) + da = amountFromJson(sfGeneric, path[jss::destination_amount]); + + if (path.isMember(jss::paths_computed)) + { + Json::Value p; + p["Paths"] = path[jss::paths_computed]; + STParsedJSONObject po("generic", p); + paths = po.object->getFieldPathSet(sfPaths); + } + } + } + + return std::make_tuple(std::move(paths), std::move(sa), std::move(da)); +} + +std::tuple +find_paths_by_element( + jtx::Env& env, + jtx::Account const& src, + jtx::Account const& dst, + STAmount const& saDstAmount, + std::optional const& saSendMax, + std::optional const& srcElement, + std::optional const& srcIssuer, + std::optional const& domain) +{ + return find_paths( + env, src, dst, saDstAmount, saSendMax, srcElement->getPathAsset(), srcIssuer, domain); +} /******************************************************************************/ @@ -87,9 +343,9 @@ xrpMinusFee(Env const& env, std::int64_t xrpAmount) [[nodiscard]] bool expectHolding(Env& env, AccountID const& account, STAmount const& value, bool defaultLimits) { - if (auto const sle = env.le(keylet::line(account, value.issue()))) + if (auto const sle = env.le(keylet::line(account, value.get()))) { - Issue const issue = value.issue(); + Issue const issue = value.get(); bool const accountLow = account < issue.account; bool expectDefaultTrustLine = true; @@ -98,15 +354,15 @@ expectHolding(Env& env, AccountID const& account, STAmount const& value, bool de STAmount low{issue}; STAmount high{issue}; - low.setIssuer(accountLow ? account : issue.account); - high.setIssuer(accountLow ? issue.account : account); + low.get().account = accountLow ? account : issue.account; + high.get().account = accountLow ? issue.account : account; expectDefaultTrustLine = sle->getFieldAmount(sfLowLimit) == low && sle->getFieldAmount(sfHighLimit) == high; } auto amount = sle->getFieldAmount(sfBalance); - amount.setIssuer(value.issue().account); + amount.get().account = value.getIssuer(); if (!accountLow) amount.negate(); return amount == value && expectDefaultTrustLine; @@ -134,6 +390,14 @@ expectHolding(Env& env, AccountID const& account, None const& value) value.asset.value()); } +[[nodiscard]] bool +expectMPT(Env& env, AccountID const& account, STAmount const& value) +{ + auto const mptIssuanceID = keylet::mptIssuance(value.asset().get()); + auto const mptToken = env.le(keylet::mptoken(mptIssuanceID.key, account)); + return mptToken && (*mptToken)[sfMPTAmount] == value.mpt().value(); +} + [[nodiscard]] bool expectOffers( Env& env, @@ -149,7 +413,7 @@ expectOffers( if (sle->getType() == ltOFFER) { ++cnt; - if (std::find_if(toMatch.begin(), toMatch.end(), [&](auto const& a) { + if (std::ranges::find_if(toMatch, [&](auto const& a) { return a.in == sle->getFieldAmount(sfTakerPays) && a.out == sle->getFieldAmount(sfTakerGets); }) != toMatch.end()) @@ -157,7 +421,7 @@ expectOffers( } return true; }); - return size == cnt && matched == toMatch.size(); + return size == cnt && ((toMatch.empty() && size != 0) || (matched == toMatch.size())); } Json::Value @@ -185,6 +449,34 @@ ledgerEntryState( return env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result]; } +Json::Value +ledgerEntryOffer(jtx::Env& env, jtx::Account const& acct, std::uint32_t offer_seq) +{ + Json::Value jvParams; + jvParams[jss::offer][jss::account] = acct.human(); + jvParams[jss::offer][jss::seq] = offer_seq; + return env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result]; +} + +Json::Value +ledgerEntryMPT(jtx::Env& env, jtx::Account const& acct, MPTID const& mptID) +{ + Json::Value jvParams; + jvParams[jss::mptoken][jss::account] = acct.human(); + jvParams[jss::mptoken][jss::mpt_issuance_id] = to_string(mptID); + return env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result]; +} + +Json::Value +getBookOffers(jtx::Env& env, Asset const& taker_pays, Asset const& taker_gets) +{ + Json::Value jvbp; + jvbp[jss::ledger_index] = "current"; + taker_pays.setJson(jvbp[jss::taker_pays]); + taker_gets.setJson(jvbp[jss::taker_gets]); + return env.rpc("json", "book_offers", to_string(jvbp))[jss::result]; +} + Json::Value accountBalance(Env& env, Account const& acct) { @@ -312,22 +604,133 @@ n_offers(Env& env, std::size_t n, Account const& account, STAmount const& in, ST // Currency path element STPathElement -cpe(Currency const& c) +cpe(PathAsset const& pa) { - return STPathElement(STPathElement::typeCurrency, xrpAccount(), c, xrpAccount()); + return pa.visit( + [](Currency const& currency) { + return STPathElement(STPathElement::typeCurrency, xrpAccount(), currency, xrpAccount()); + }, + [](MPTID const& mpt) { + return STPathElement(STPathElement::typeMPT, xrpAccount(), mpt, xrpAccount()); + }); }; // All path element STPathElement -allPathElements(AccountID const& a, Issue const& iss) +allPathElements(AccountID const& a, Asset const& asset) { - return STPathElement( - STPathElement::typeAccount | STPathElement::typeCurrency | STPathElement::typeIssuer, - a, - iss.currency, - iss.account); + return STPathElement(a, asset, asset.getIssuer()); }; +STPathElement +ipe(Asset const& asset) +{ + return asset.visit( + [](Issue const& issue) { + return STPathElement( + STPathElement::typeCurrency | STPathElement::typeIssuer, + xrpAccount(), + issue.currency, + issue.account); + }, + [](MPTIssue const& issue) { + return STPathElement( + STPathElement::typeMPT | STPathElement::typeIssuer, + xrpAccount(), + issue.getMptID(), + issue.getIssuer()); + }); +}; + +// Issuer path element +STPathElement +iape(AccountID const& account) +{ + return STPathElement(STPathElement::typeIssuer, xrpAccount(), xrpCurrency(), account); +}; + +// Account path element +STPathElement +ape(AccountID const& a) +{ + return STPathElement(STPathElement::typeAccount, a, xrpCurrency(), xrpAccount()); +}; + +bool +equal(std::unique_ptr const& s1, DirectStepInfo const& dsi) +{ + if (!s1) + return false; + return test::directStepEqual(*s1, dsi.src, dsi.dst, dsi.currency); +} + +bool +equal(std::unique_ptr const& s1, MPTEndpointStepInfo const& dsi) +{ + if (!s1) + return false; + return test::mptEndpointStepEqual(*s1, dsi.src, dsi.dst, dsi.mptid); +} + +bool +equal(std::unique_ptr const& s1, XRPEndpointStepInfo const& xrpStepInfo) +{ + if (!s1) + return false; + return test::xrpEndpointStepEqual(*s1, xrpStepInfo.acc); +} + +bool +equal(std::unique_ptr const& s1, xrpl::Book const& bsi) +{ + if (!s1) + return false; + return bookStepEqual(*s1, bsi); +} + +namespace detail { + +IOU +issueHelperIOU(IssuerArgs const& args) +{ + auto const iou = args.issuer[args.token]; + if (args.transferFee != 0) + { + auto const tfee = 1. + (static_cast(args.transferFee) / 100'000); + args.env(rate(args.issuer, tfee)); + } + for (auto const& account : args.holders) + { + args.env(trust(account, iou(args.limit.value_or(1'000)))); + } + return iou; +} + +MPT +issueHelperMPT(IssuerArgs const& args) +{ + using namespace jtx; + if (args.limit) + { + MPT const mpt = MPTTester( + {.env = args.env, + .issuer = args.issuer, + .holders = args.holders, + .transferFee = args.transferFee, + .maxAmt = args.limit}); + return mpt; + } + + MPT const mpt = MPTTester( + {.env = args.env, + .issuer = args.issuer, + .holders = args.holders, + .transferFee = args.transferFee}); + return mpt; +} + +} // namespace detail + /* LoanBroker */ /******************************************************************************/ @@ -453,6 +856,4 @@ pay(AccountID const& account, uint256 const& loanID, STAmount const& amount, std } } // namespace loan -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/impl/WSClient.cpp b/src/test/jtx/impl/WSClient.cpp index 0c9b72c4d0..617e2b8881 100644 --- a/src/test/jtx/impl/WSClient.cpp +++ b/src/test/jtx/impl/WSClient.cpp @@ -1,23 +1,48 @@ -#include #include +#include + +#include +#include #include +#include #include #include #include +#include +#include #include #include +#include +#include +#include +#include #include #include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include #include +#include #include +#include -namespace xrpl { -namespace test { +namespace xrpl::test { class WSClientImpl : public WSClient { @@ -184,7 +209,14 @@ public: jp[jss::command] = cmd; } auto const s = to_string(jp); - ws_.write_some(true, buffer(s)); + + // Use the error_code overload to avoid an unhandled exception + // when the server closes the WebSocket connection (e.g. after + // booting a client that exceeded resource thresholds). + error_code ec; + ws_.write_some(true, buffer(s), ec); + if (ec) + return {}; } auto jv = @@ -302,5 +334,4 @@ makeWSClient( return std::make_unique(cfg, v2, rpc_version, headers); } -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/jtx/impl/account_txn_id.cpp b/src/test/jtx/impl/account_txn_id.cpp index ceda6e7b50..a556ea8e91 100644 --- a/src/test/jtx/impl/account_txn_id.cpp +++ b/src/test/jtx/impl/account_txn_id.cpp @@ -1,8 +1,11 @@ #include -namespace xrpl { -namespace test { -namespace jtx { +#include +#include + +#include + +namespace xrpl::test::jtx { void account_txn_id::operator()(Env&, JTx& jt) const @@ -11,6 +14,4 @@ account_txn_id::operator()(Env&, JTx& jt) const jt["AccountTxnID"] = strHex(hash_); } -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/impl/acctdelete.cpp b/src/test/jtx/impl/acctdelete.cpp index 0b87d501dd..433ed5ec92 100644 --- a/src/test/jtx/impl/acctdelete.cpp +++ b/src/test/jtx/impl/acctdelete.cpp @@ -1,11 +1,16 @@ -#include #include +#include +#include + +#include +#include +#include #include -namespace xrpl { -namespace test { -namespace jtx { +#include + +namespace xrpl::test::jtx { // Delete account. If successful transfer remaining XRP to dest. Json::Value @@ -38,6 +43,4 @@ incLgrSeqForAccDel(jtx::Env& env, jtx::Account const& acc, std::uint32_t margin) env.test.BEAST_EXPECT(openLedgerSeq(env) == env.seq(acc) + 255 - margin); } -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/impl/amount.cpp b/src/test/jtx/impl/amount.cpp index 25fcb5c8f0..1a02e07bdf 100644 --- a/src/test/jtx/impl/amount.cpp +++ b/src/test/jtx/impl/amount.cpp @@ -1,32 +1,22 @@ -#include #include +#include + +#include #include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include -namespace xrpl { -namespace test { -namespace jtx { - -#if 0 -std::ostream& -operator<<(std::ostream&& os, - AnyAmount const& amount) -{ - if (amount.is_any) - { - os << amount.value.getText() << "/" << - to_string(amount.value.issue().currency) << - "*"; - return os; - } - os << amount.value.getText() << "/" << - to_string(amount.value.issue().currency) << - "(" << amount.name() << ")"; - return os; -} -#endif +namespace xrpl::test::jtx { PrettyAmount:: operator AnyAmount() const @@ -54,39 +44,45 @@ to_places(T const d, std::uint8_t places) std::ostream& operator<<(std::ostream& os, PrettyAmount const& amount) { - if (amount.value().native()) - { - // measure in hundredths - auto const c = dropsPerXRP.drops() / 100; - auto const n = amount.value().mantissa(); - if (n < c) - { - if (amount.value().negative()) + amount.value().asset().visit( + [&](Issue const& issue) { + if (issue.native()) { - os << "-" << n << " drops"; + // measure in hundredths + auto const c = dropsPerXRP.drops() / 100; + auto const n = amount.value().mantissa(); + if (n < c) + { + if (amount.value().negative()) + { + os << "-" << n << " drops"; + } + else + { + os << n << " drops"; + } + } + else + { + auto const d = double(n) / dropsPerXRP.drops(); + if (amount.value().negative()) + { + os << "-"; + } + + os << to_places(d, 6) << " XRP"; + } } else { - os << n << " drops"; + os << amount.value().getText() << "/" << to_string(issue.currency) << "(" + << amount.name() << ")"; } - return os; - } - auto const d = double(n) / dropsPerXRP.drops(); - if (amount.value().negative()) - os << "-"; - - os << to_places(d, 6) << " XRP"; - } - else if (amount.value().holds()) - { - os << amount.value().getText() << "/" << to_string(amount.value().issue().currency) << "(" - << amount.name() << ")"; - } - else - { - auto const& mptIssue = amount.value().asset().get(); - os << amount.value().getText() << "/" << to_string(mptIssue) << "(" << amount.name() << ")"; - } + }, + [&](MPTIssue const& issue) { + os << amount.value().getText() << "/" << to_string(issue) << "(" << amount.name() + << ")"; + }); return os; } @@ -101,7 +97,7 @@ IOU::operator()(epsilon_t) const } PrettyAmount -IOU::operator()(detail::epsilon_multiple m) const +IOU::operator()(xrpl::detail::epsilon_multiple m) const { return {STAmount(issue(), safe_cast(m.n), -81), account.name()}; } @@ -109,12 +105,17 @@ IOU::operator()(detail::epsilon_multiple m) const std::ostream& operator<<(std::ostream& os, IOU const& iou) { - os << to_string(iou.issue().currency) << "(" << iou.account.name() << ")"; + os << to_string(iou.currency) << "(" << iou.account.name() << ")"; + return os; +} + +std::ostream& +operator<<(std::ostream& os, MPT const& mpt) +{ + os << to_string(mpt.issuanceID); return os; } any_t const any{}; -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/impl/attester.cpp b/src/test/jtx/impl/attester.cpp index d4693e69fb..74c0ae24ed 100644 --- a/src/test/jtx/impl/attester.cpp +++ b/src/test/jtx/impl/attester.cpp @@ -1,13 +1,16 @@ #include +#include #include +#include #include #include #include -namespace xrpl { -namespace test { -namespace jtx { +#include +#include + +namespace xrpl::test::jtx { Buffer sign_claim_attestation( @@ -51,6 +54,4 @@ sign_create_account_attestation( return sign(pk, sk, makeSlice(toSign)); } -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/impl/balance.cpp b/src/test/jtx/impl/balance.cpp index 1c3cc0b3c3..1c7b4c0c25 100644 --- a/src/test/jtx/impl/balance.cpp +++ b/src/test/jtx/impl/balance.cpp @@ -1,8 +1,17 @@ #include -namespace xrpl { -namespace test { -namespace jtx { +#include + +#include +#include +#include +#include +#include +#include + +#include + +namespace xrpl::test::jtx { #define TEST_EXPECT(cond) env.test.expect(cond, __FILE__, __LINE__) #define TEST_EXPECTS(cond, reason) \ @@ -35,8 +44,8 @@ doBalance(Env& env, AccountID const& account, bool none, STAmount const& value, else if (TEST_EXPECT(sle)) { auto amount = sle->getFieldAmount(sfBalance); - amount.setIssuer(issue.account); - if (account > issue.account) + amount.get().account = value.getIssuer(); + if (account > value.getIssuer()) amount.negate(); TEST_EXPECTS(amount == value, amount.getText()); } @@ -71,6 +80,4 @@ balance::operator()(Env& env) const value_.asset().value()); } -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/impl/batch.cpp b/src/test/jtx/impl/batch.cpp index 65aca2a935..d45f195b66 100644 --- a/src/test/jtx/impl/batch.cpp +++ b/src/test/jtx/impl/batch.cpp @@ -1,19 +1,34 @@ #include + +#include +#include +#include #include +#include +#include +#include +#include +#include #include -#include +#include +#include +#include +#include +#include +#include +#include #include +#include #include +#include +#include #include -#include +#include +#include -namespace xrpl { -namespace test { -namespace jtx { - -namespace batch { +namespace xrpl::test::jtx::batch { XRPAmount calcBatchFee(test::jtx::Env const& env, uint32_t const& numSigners, uint32_t const& txns) @@ -119,8 +134,4 @@ msig::operator()(Env& env, JTx& jt) const } } -} // namespace batch - -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx::batch diff --git a/src/test/jtx/impl/check.cpp b/src/test/jtx/impl/check.cpp index 22f348aa3f..d03700e2c3 100644 --- a/src/test/jtx/impl/check.cpp +++ b/src/test/jtx/impl/check.cpp @@ -1,13 +1,14 @@ #include -#include +#include + +#include +#include +#include +#include #include -namespace xrpl { -namespace test { -namespace jtx { - -namespace check { +namespace xrpl::test::jtx::check { // Cash a check requiring that a specific amount be delivered. Json::Value @@ -44,8 +45,4 @@ cancel(jtx::Account const& dest, uint256 const& checkId) return jv; } -} // namespace check - -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx::check diff --git a/src/test/jtx/impl/creds.cpp b/src/test/jtx/impl/creds.cpp index e1018cc0e5..4d38d6d88b 100644 --- a/src/test/jtx/impl/creds.cpp +++ b/src/test/jtx/impl/creds.cpp @@ -1,13 +1,16 @@ +#include +#include #include -#include +#include +#include +#include +#include #include -namespace xrpl { -namespace test { -namespace jtx { +#include -namespace credentials { +namespace xrpl::test::jtx::credentials { Json::Value create(jtx::Account const& subject, jtx::Account const& issuer, std::string_view credType) @@ -73,9 +76,4 @@ ledgerEntry(jtx::Env& env, std::string const& credIdx) return env.rpc("json", "ledger_entry", to_string(jvParams)); } -} // namespace credentials - -} // namespace jtx - -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx::credentials diff --git a/src/test/jtx/impl/delegate.cpp b/src/test/jtx/impl/delegate.cpp index 7cca9aa738..e4e5e4ca96 100644 --- a/src/test/jtx/impl/delegate.cpp +++ b/src/test/jtx/impl/delegate.cpp @@ -1,12 +1,17 @@ #include +#include +#include + +#include +#include +#include #include -namespace xrpl { -namespace test { -namespace jtx { +#include +#include -namespace delegate { +namespace xrpl::test::jtx::delegate { Json::Value set(jtx::Account const& account, @@ -42,7 +47,4 @@ entry(jtx::Env& env, jtx::Account const& account, jtx::Account const& authorize) return env.rpc("json", "ledger_entry", to_string(jvParams)); } -} // namespace delegate -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx::delegate diff --git a/src/test/jtx/impl/delivermin.cpp b/src/test/jtx/impl/delivermin.cpp index a5c1414525..140f4d7c66 100644 --- a/src/test/jtx/impl/delivermin.cpp +++ b/src/test/jtx/impl/delivermin.cpp @@ -1,10 +1,11 @@ #include +#include +#include + #include -namespace xrpl { -namespace test { -namespace jtx { +namespace xrpl::test::jtx { void deliver_min::operator()(Env& env, JTx& jt) const @@ -12,6 +13,4 @@ deliver_min::operator()(Env& env, JTx& jt) const jt.jv[jss::DeliverMin] = amount_.getJson(JsonOptions::none); } -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/impl/deposit.cpp b/src/test/jtx/impl/deposit.cpp index 4a2ecf0139..5e432b6aa8 100644 --- a/src/test/jtx/impl/deposit.cpp +++ b/src/test/jtx/impl/deposit.cpp @@ -1,12 +1,15 @@ #include +#include + +#include +#include #include -namespace xrpl { -namespace test { -namespace jtx { +#include +#include -namespace deposit { +namespace xrpl::test::jtx::deposit { // Add DepositPreauth. Json::Value @@ -66,8 +69,4 @@ unauthCredentials(jtx::Account const& account, std::vector return jv; } -} // namespace deposit - -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx::deposit diff --git a/src/test/jtx/impl/dids.cpp b/src/test/jtx/impl/dids.cpp index bb782bcd43..9b3a08bbfa 100644 --- a/src/test/jtx/impl/dids.cpp +++ b/src/test/jtx/impl/dids.cpp @@ -1,14 +1,14 @@ +#include #include -#include +#include +#include +#include +#include #include -namespace xrpl { -namespace test { -namespace jtx { - /** DID operations. */ -namespace did { +namespace xrpl::test::jtx::did { Json::Value set(jtx::Account const& account) @@ -38,9 +38,4 @@ del(jtx::Account const& account) return jv; } -} // namespace did - -} // namespace jtx - -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx::did diff --git a/src/test/jtx/impl/directory.cpp b/src/test/jtx/impl/directory.cpp index da0a338e7c..ac4d81bdfb 100644 --- a/src/test/jtx/impl/directory.cpp +++ b/src/test/jtx/impl/directory.cpp @@ -1,11 +1,25 @@ #include -#include +#include -namespace xrpl::test::jtx { +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include /** Directory operations. */ -namespace directory { +namespace xrpl::test::jtx::directory { auto bumpLastPage( @@ -122,6 +136,4 @@ adjustOwnerNode(ApplyView& view, uint256 key, std::uint64_t page) return false; } -} // namespace directory - -} // namespace xrpl::test::jtx +} // namespace xrpl::test::jtx::directory diff --git a/src/test/jtx/impl/domain.cpp b/src/test/jtx/impl/domain.cpp index 91568783d2..70d4daf002 100644 --- a/src/test/jtx/impl/domain.cpp +++ b/src/test/jtx/impl/domain.cpp @@ -1,10 +1,12 @@ #include -#include +#include +#include -namespace xrpl { -namespace test { -namespace jtx { +#include +#include + +namespace xrpl::test::jtx { void domain::operator()(Env&, JTx& jt) const @@ -12,6 +14,4 @@ domain::operator()(Env&, JTx& jt) const jt[sfDomainID.jsonName] = to_string(v_); } -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/impl/envconfig.cpp b/src/test/jtx/impl/envconfig.cpp index e31e687c3d..ae05e4f1dd 100644 --- a/src/test/jtx/impl/envconfig.cpp +++ b/src/test/jtx/impl/envconfig.cpp @@ -1,10 +1,16 @@ -#include #include +#include + +#include #include -namespace xrpl { -namespace test { +#include +#include +#include +#include + +namespace xrpl::test { std::atomic envUseIPv4{false}; @@ -125,6 +131,49 @@ addGrpcConfigWithSecureGateway(std::unique_ptr cfg, std::string const& s return cfg; } +std::unique_ptr +addGrpcConfigWithTLS( + std::unique_ptr cfg, + std::string const& certPath, + std::string const& keyPath) +{ + (*cfg)[SECTION_PORT_GRPC].set("ip", getEnvLocalhostAddr()); + (*cfg)[SECTION_PORT_GRPC].set("port", "0"); + (*cfg)[SECTION_PORT_GRPC].set("ssl_cert", certPath); + (*cfg)[SECTION_PORT_GRPC].set("ssl_key", keyPath); + return cfg; +} + +std::unique_ptr +addGrpcConfigWithTLSAndClientCA( + std::unique_ptr cfg, + std::string const& certPath, + std::string const& keyPath, + std::string const& clientCAPath) +{ + (*cfg)[SECTION_PORT_GRPC].set("ip", getEnvLocalhostAddr()); + (*cfg)[SECTION_PORT_GRPC].set("port", "0"); + (*cfg)[SECTION_PORT_GRPC].set("ssl_cert", certPath); + (*cfg)[SECTION_PORT_GRPC].set("ssl_key", keyPath); + (*cfg)[SECTION_PORT_GRPC].set("ssl_client_ca", clientCAPath); + return cfg; +} + +std::unique_ptr +addGrpcConfigWithTLSAndCertChain( + std::unique_ptr cfg, + std::string const& certPath, + std::string const& keyPath, + std::string const& certChainPath) +{ + (*cfg)[SECTION_PORT_GRPC].set("ip", getEnvLocalhostAddr()); + (*cfg)[SECTION_PORT_GRPC].set("port", "0"); + (*cfg)[SECTION_PORT_GRPC].set("ssl_cert", certPath); + (*cfg)[SECTION_PORT_GRPC].set("ssl_key", keyPath); + (*cfg)[SECTION_PORT_GRPC].set("ssl_cert_chain", certChainPath); + return cfg; +} + std::unique_ptr makeConfig( std::map extraTxQ, @@ -159,5 +208,4 @@ makeConfig( } } // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/jtx/impl/escrow.cpp b/src/test/jtx/impl/escrow.cpp index 067c304178..3d5d1e2bac 100644 --- a/src/test/jtx/impl/escrow.cpp +++ b/src/test/jtx/impl/escrow.cpp @@ -1,14 +1,21 @@ #include +#include +#include + +#include +#include +#include +#include +#include +#include #include #include -namespace xrpl { -namespace test { -namespace jtx { +#include /** Escrow operations. */ -namespace escrow { +namespace xrpl::test::jtx::escrow { Json::Value create(AccountID const& account, AccountID const& to, STAmount const& amount) @@ -55,9 +62,4 @@ rate(Env& env, Account const& account, std::uint32_t const& seq) return Rate{0}; } -} // namespace escrow - -} // namespace jtx - -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx::escrow diff --git a/src/test/jtx/impl/fee.cpp b/src/test/jtx/impl/fee.cpp index fc05afcb46..edc3b5adf9 100644 --- a/src/test/jtx/impl/fee.cpp +++ b/src/test/jtx/impl/fee.cpp @@ -1,10 +1,13 @@ #include -#include +#include +#include -namespace xrpl { -namespace test { -namespace jtx { +#include + +#include + +namespace xrpl::test::jtx { void fee::operator()(Env& env, JTx& jt) const @@ -23,6 +26,4 @@ fee::operator()(Env& env, JTx& jt) const } } -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/impl/flags.cpp b/src/test/jtx/impl/flags.cpp index aee01a107e..3caa2ece17 100644 --- a/src/test/jtx/impl/flags.cpp +++ b/src/test/jtx/impl/flags.cpp @@ -1,10 +1,15 @@ #include +#include +#include + +#include +#include #include -namespace xrpl { -namespace test { -namespace jtx { +#include + +namespace xrpl::test::jtx { Json::Value fset(Account const& account, std::uint32_t on, std::uint32_t off) @@ -55,6 +60,4 @@ nflags::operator()(Env& env) const } } -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/impl/invoice_id.cpp b/src/test/jtx/impl/invoice_id.cpp index 6d6dae0fbf..ce3eb783ac 100644 --- a/src/test/jtx/impl/invoice_id.cpp +++ b/src/test/jtx/impl/invoice_id.cpp @@ -1,8 +1,11 @@ #include -namespace xrpl { -namespace test { -namespace jtx { +#include +#include + +#include + +namespace xrpl::test::jtx { void invoice_id::operator()(Env&, JTx& jt) const @@ -11,6 +14,4 @@ invoice_id::operator()(Env&, JTx& jt) const jt["InvoiceID"] = strHex(hash_); } -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/impl/jtx_json.cpp b/src/test/jtx/impl/jtx_json.cpp index c39503d038..0360dba351 100644 --- a/src/test/jtx/impl/jtx_json.cpp +++ b/src/test/jtx/impl/jtx_json.cpp @@ -1,12 +1,17 @@ #include + +#include +#include #include #include #include +#include -namespace xrpl { -namespace test { -namespace jtx { +#include +#include + +namespace xrpl::test::jtx { json::json(std::string const& s) { @@ -30,6 +35,4 @@ json::operator()(Env&, JTx& jt) const jv[iter.key().asString()] = *iter; } -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/impl/last_ledger_sequence.cpp b/src/test/jtx/impl/last_ledger_sequence.cpp index 5f29282ad6..dc4bae6212 100644 --- a/src/test/jtx/impl/last_ledger_sequence.cpp +++ b/src/test/jtx/impl/last_ledger_sequence.cpp @@ -1,8 +1,9 @@ #include -namespace xrpl { -namespace test { -namespace jtx { +#include +#include + +namespace xrpl::test::jtx { void last_ledger_seq::operator()(Env&, JTx& jt) const @@ -10,6 +11,4 @@ last_ledger_seq::operator()(Env&, JTx& jt) const jt["LastLedgerSequence"] = num_; } -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/impl/ledgerStateFixes.cpp b/src/test/jtx/impl/ledgerStateFixes.cpp index e5a7495a44..99cd03cbb6 100644 --- a/src/test/jtx/impl/ledgerStateFixes.cpp +++ b/src/test/jtx/impl/ledgerStateFixes.cpp @@ -1,14 +1,12 @@ +#include #include -#include +#include +#include #include #include -namespace xrpl { -namespace test { -namespace jtx { - -namespace ledgerStateFix { +namespace xrpl::test::jtx::ledgerStateFix { // Fix NFTokenPage links on owner's account. acct pays fee. Json::Value @@ -22,8 +20,4 @@ nftPageLinks(jtx::Account const& acct, jtx::Account const& owner) return jv; } -} // namespace ledgerStateFix - -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx::ledgerStateFix diff --git a/src/test/jtx/impl/memo.cpp b/src/test/jtx/impl/memo.cpp index e503b9a073..f092f48d70 100644 --- a/src/test/jtx/impl/memo.cpp +++ b/src/test/jtx/impl/memo.cpp @@ -1,8 +1,11 @@ #include -namespace xrpl { -namespace test { -namespace jtx { +#include +#include + +#include + +namespace xrpl::test::jtx { void memo::operator()(Env&, JTx& jt) const @@ -46,6 +49,4 @@ memo_type::operator()(Env&, JTx& jt) const m["MemoType"] = strHex(s_); } -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/impl/mpt.cpp b/src/test/jtx/impl/mpt.cpp index 0a6af63450..e2fd95144c 100644 --- a/src/test/jtx/impl/mpt.cpp +++ b/src/test/jtx/impl/mpt.cpp @@ -1,12 +1,44 @@ -#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include #include +#include +#include +#include #include -namespace xrpl { -namespace test { -namespace jtx { +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl::test::jtx { void mptflags::operator()(Env& env) const @@ -39,8 +71,8 @@ MPTTester::makeHolders(std::vector const& holders) return accounts; } -MPTTester::MPTTester(Env& env, Account const& issuer, MPTInit const& arg) - : env_(env), issuer_(issuer), holders_(makeHolders(arg.holders)), close_(arg.close) +MPTTester::MPTTester(Env& env, Account issuer, MPTInit const& arg) + : env_(env), issuer_(std::move(issuer)), holders_(makeHolders(arg.holders)), close_(arg.close) { if (arg.fund) { @@ -66,11 +98,11 @@ MPTTester::MPTTester(Env& env, Account const& issuer, MPTInit const& arg) MPTTester::MPTTester( Env& env, - Account const& issuer, + Account issuer, MPTID const& id, std::vector const& holders, bool close) - : env_(env), issuer_(issuer), holders_(makeHolders(holders)), id_(id), close_(close) + : env_(env), issuer_(std::move(issuer)), holders_(makeHolders(holders)), id_(id), close_(close) { } @@ -84,6 +116,7 @@ makeMPTCreate(MPTInitDef const& arg) .transferFee = arg.transferFee, .pay = {{arg.holders, *arg.pay}}, .flags = arg.flags, + .mutableFlags = arg.mutableFlags, .authHolder = arg.authHolder}; } return { @@ -91,6 +124,7 @@ makeMPTCreate(MPTInitDef const& arg) .transferFee = arg.transferFee, .authorize = arg.holders, .flags = arg.flags, + .mutableFlags = arg.mutableFlags, .authHolder = arg.authHolder}; } @@ -663,6 +697,4 @@ MPTTester::operator()(std::int64_t amount) const return MPT("", issuanceID())(amount); } -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/impl/multisign.cpp b/src/test/jtx/impl/multisign.cpp index 8e3c37f68c..e21a3621be 100644 --- a/src/test/jtx/impl/multisign.cpp +++ b/src/test/jtx/impl/multisign.cpp @@ -1,15 +1,31 @@ #include + +#include +#include +#include +#include #include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include #include #include +#include +#include #include +#include +#include -namespace xrpl { -namespace test { -namespace jtx { +namespace xrpl::test::jtx { Json::Value signers(Account const& account, std::uint32_t quorum, std::vector const& v) @@ -93,6 +109,4 @@ msig::operator()(Env& env, JTx& jt) const } } -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/impl/offer.cpp b/src/test/jtx/impl/offer.cpp index 5a2264601a..abb777484a 100644 --- a/src/test/jtx/impl/offer.cpp +++ b/src/test/jtx/impl/offer.cpp @@ -1,10 +1,14 @@ #include +#include + +#include +#include #include -namespace xrpl { -namespace test { -namespace jtx { +#include + +namespace xrpl::test::jtx { Json::Value offer( @@ -33,6 +37,4 @@ offer_cancel(Account const& account, std::uint32_t offerSeq) return jv; } -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/impl/owners.cpp b/src/test/jtx/impl/owners.cpp index 855c5b04ff..fde456f3ab 100644 --- a/src/test/jtx/impl/owners.cpp +++ b/src/test/jtx/impl/owners.cpp @@ -1,6 +1,16 @@ #include +#include + +#include +#include #include +#include +#include +#include + +#include +#include namespace xrpl { namespace detail { @@ -28,8 +38,7 @@ owned_count_helper( } // namespace detail -namespace test { -namespace jtx { +namespace test::jtx { void owners::operator()(Env& env) const @@ -37,6 +46,6 @@ owners::operator()(Env& env) const env.test.expect(env.le(account_)->getFieldU32(sfOwnerCount) == value_); } -} // namespace jtx -} // namespace test +} // namespace test::jtx + } // namespace xrpl diff --git a/src/test/jtx/impl/paths.cpp b/src/test/jtx/impl/paths.cpp index af72b8bd5a..f508d0d3b6 100644 --- a/src/test/jtx/impl/paths.cpp +++ b/src/test/jtx/impl/paths.cpp @@ -1,14 +1,26 @@ #include +#include +#include +#include + +#include #include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include #include -namespace xrpl { -namespace test { -namespace jtx { +namespace xrpl::test::jtx { void paths::operator()(Env& env, JTx& jt) const @@ -30,11 +42,11 @@ paths::operator()(Env& env, JTx& jt) const } Pathfinder pf( - std::make_shared(env.current(), env.app().getJournal("RippleLineCache")), + std::make_shared(env.current(), env.app().getJournal("AssetCache")), from, to, - in_.currency, - in_.account, + in_, + in_.getIssuer(), amount, std::nullopt, domain, @@ -44,7 +56,7 @@ paths::operator()(Env& env, JTx& jt) const STPath fp; pf.computePathRanks(limit_); - auto const found = pf.getBestPaths(limit_, fp, {}, in_.account); + auto const found = pf.getBestPaths(limit_, fp, {}, in_.getIssuer()); // VFALCO TODO API to allow caller to examine the STPathSet // VFALCO isDefault should be renamed to empty() @@ -54,6 +66,11 @@ paths::operator()(Env& env, JTx& jt) const //------------------------------------------------------------------------------ +path::path(STPath const& p) +{ + jv_ = p.getJson(JsonOptions::none); +} + Json::Value& path::create() { @@ -77,16 +94,20 @@ void path::append_one(IOU const& iou) { auto& jv = create(); - jv["currency"] = to_string(iou.issue().currency); - jv["account"] = toBase58(iou.issue().account); + jv["currency"] = to_string(iou.currency); + jv["account"] = toBase58(iou.account); } void path::append_one(BookSpec const& book) { auto& jv = create(); - jv["currency"] = to_string(book.currency); - jv["issuer"] = toBase58(book.account); + book.asset.visit( + [&](Issue const& issue) { + jv["currency"] = to_string(issue.currency); + jv["issuer"] = toBase58(issue.account); + }, + [&](MPTIssue const& issue) { jv["mpt_issuance_id"] = to_string(issue.getMptID()); }); } void @@ -95,6 +116,4 @@ path::operator()(Env& env, JTx& jt) const jt.jv["Paths"].append(jv_); } -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/impl/pay.cpp b/src/test/jtx/impl/pay.cpp index 9e927c6270..34b6b13d86 100644 --- a/src/test/jtx/impl/pay.cpp +++ b/src/test/jtx/impl/pay.cpp @@ -1,11 +1,14 @@ #include +#include +#include + +#include +#include #include #include -namespace xrpl { -namespace test { -namespace jtx { +namespace xrpl::test::jtx { Json::Value pay(AccountID const& account, AccountID const& to, AnyAmount amount) @@ -25,6 +28,4 @@ pay(Account const& account, Account const& to, AnyAmount amount) return pay(account.id(), to.id(), amount); } -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/impl/permissioned_dex.cpp b/src/test/jtx/impl/permissioned_dex.cpp index 494ea897d4..012932fed5 100644 --- a/src/test/jtx/impl/permissioned_dex.cpp +++ b/src/test/jtx/impl/permissioned_dex.cpp @@ -1,13 +1,19 @@ -#include -#include -#include +#include -#include +#include +#include +#include +#include +#include +#include -namespace xrpl { -namespace test { -namespace jtx { +#include + +#include +#include + +namespace xrpl::test::jtx { uint256 setupDomain( @@ -61,6 +67,4 @@ PermissionedDEX::PermissionedDEX(Env& env) } } -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/impl/permissioned_domains.cpp b/src/test/jtx/impl/permissioned_domains.cpp index 60d653e956..ff3146e67a 100644 --- a/src/test/jtx/impl/permissioned_domains.cpp +++ b/src/test/jtx/impl/permissioned_domains.cpp @@ -1,9 +1,28 @@ -#include +#include -namespace xrpl { -namespace test { -namespace jtx { -namespace pdomain { +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl::test::jtx::pdomain { // helpers // Make json for PermissionedDomainSet transaction @@ -149,7 +168,4 @@ getNewDomain(std::shared_ptr const& meta) return ret; } -} // namespace pdomain -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx::pdomain diff --git a/src/test/jtx/impl/quality2.cpp b/src/test/jtx/impl/quality2.cpp index 9da366d4c2..32aca1b1cd 100644 --- a/src/test/jtx/impl/quality2.cpp +++ b/src/test/jtx/impl/quality2.cpp @@ -1,11 +1,14 @@ +#include +#include #include #include #include -namespace xrpl { -namespace test { -namespace jtx { +#include +#include + +namespace xrpl::test::jtx { qualityInPercent::qualityInPercent(double percent) // NOLINTNEXTLINE(cppcoreguidelines-use-default-member-init) @@ -51,6 +54,4 @@ qualityOutPercent::operator()(Env&, JTx& jt) const insertQualityIntoJtx(sfQualityOut, qOut_, jt); } -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/impl/rate.cpp b/src/test/jtx/impl/rate.cpp index b4e9b2cb60..dab02b33c4 100644 --- a/src/test/jtx/impl/rate.cpp +++ b/src/test/jtx/impl/rate.cpp @@ -1,13 +1,15 @@ #include +#include + #include +#include #include +#include #include -namespace xrpl { -namespace test { -namespace jtx { +namespace xrpl::test::jtx { Json::Value rate(Account const& account, double multiplier) @@ -21,6 +23,4 @@ rate(Account const& account, double multiplier) return jv; } -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/impl/regkey.cpp b/src/test/jtx/impl/regkey.cpp index a2e2198eee..2f4c1bccdd 100644 --- a/src/test/jtx/impl/regkey.cpp +++ b/src/test/jtx/impl/regkey.cpp @@ -1,10 +1,13 @@ #include +#include +#include + +#include +#include #include -namespace xrpl { -namespace test { -namespace jtx { +namespace xrpl::test::jtx { Json::Value regkey(Account const& account, disabled_t) @@ -25,6 +28,4 @@ regkey(Account const& account, Account const& signer) return jv; } -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/impl/sendmax.cpp b/src/test/jtx/impl/sendmax.cpp index f117458cfa..c33cab79fa 100644 --- a/src/test/jtx/impl/sendmax.cpp +++ b/src/test/jtx/impl/sendmax.cpp @@ -1,10 +1,11 @@ #include +#include +#include + #include -namespace xrpl { -namespace test { -namespace jtx { +namespace xrpl::test::jtx { void sendmax::operator()(Env& env, JTx& jt) const @@ -12,6 +13,4 @@ sendmax::operator()(Env& env, JTx& jt) const jt.jv[jss::SendMax] = amount_.getJson(JsonOptions::none); } -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/impl/seq.cpp b/src/test/jtx/impl/seq.cpp index 99c6ddbf0d..cab970974b 100644 --- a/src/test/jtx/impl/seq.cpp +++ b/src/test/jtx/impl/seq.cpp @@ -1,10 +1,11 @@ #include +#include +#include + #include -namespace xrpl { -namespace test { -namespace jtx { +namespace xrpl::test::jtx { void seq::operator()(Env&, JTx& jt) const @@ -16,6 +17,4 @@ seq::operator()(Env&, JTx& jt) const jt[jss::Sequence] = *num_; } -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/impl/sig.cpp b/src/test/jtx/impl/sig.cpp index 1bdadc0bd3..fe246f8f90 100644 --- a/src/test/jtx/impl/sig.cpp +++ b/src/test/jtx/impl/sig.cpp @@ -1,9 +1,10 @@ #include + +#include +#include #include -namespace xrpl { -namespace test { -namespace jtx { +namespace xrpl::test::jtx { void sig::operator()(Env&, JTx& jt) const @@ -33,6 +34,4 @@ sig::operator()(Env&, JTx& jt) const } } -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/impl/tag.cpp b/src/test/jtx/impl/tag.cpp index 8321322f75..9c1dab724c 100644 --- a/src/test/jtx/impl/tag.cpp +++ b/src/test/jtx/impl/tag.cpp @@ -1,8 +1,9 @@ #include -namespace xrpl { -namespace test { -namespace jtx { +#include +#include + +namespace xrpl::test::jtx { void dtag::operator()(Env&, JTx& jt) const @@ -16,6 +17,4 @@ stag::operator()(Env&, JTx& jt) const jt.jv["SourceTag"] = value_; } -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/impl/ticket.cpp b/src/test/jtx/impl/ticket.cpp index 2cb1826bbb..f0975e6151 100644 --- a/src/test/jtx/impl/ticket.cpp +++ b/src/test/jtx/impl/ticket.cpp @@ -1,12 +1,16 @@ #include +#include +#include +#include + +#include +#include #include -namespace xrpl { -namespace test { -namespace jtx { +#include -namespace ticket { +namespace xrpl::test::jtx::ticket { Json::Value create(Account const& account, std::uint32_t count) @@ -26,8 +30,4 @@ use::operator()(Env&, JTx& jt) const jt[sfTicketSequence.jsonName] = ticketSeq_; } -} // namespace ticket - -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx::ticket diff --git a/src/test/jtx/impl/token.cpp b/src/test/jtx/impl/token.cpp index 9db79361eb..c3779c3c56 100644 --- a/src/test/jtx/impl/token.cpp +++ b/src/test/jtx/impl/token.cpp @@ -1,14 +1,23 @@ -#include #include +#include +#include +#include +#include + +#include +#include #include +#include #include +#include #include -namespace xrpl { -namespace test { -namespace jtx { -namespace token { +#include +#include +#include + +namespace xrpl::test::jtx::token { Json::Value mint(jtx::Account const& account, std::uint32_t nfTokenTaxon) @@ -210,7 +219,4 @@ modify(jtx::Account const& account, uint256 const& nftokenID) return jv; } -} // namespace token -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx::token diff --git a/src/test/jtx/impl/trust.cpp b/src/test/jtx/impl/trust.cpp index 08cd4ef94c..5450e0892d 100644 --- a/src/test/jtx/impl/trust.cpp +++ b/src/test/jtx/impl/trust.cpp @@ -1,13 +1,18 @@ #include +#include + #include +#include +#include +#include #include +#include +#include #include -namespace xrpl { -namespace test { -namespace jtx { +namespace xrpl::test::jtx { Json::Value trust(Account const& account, STAmount const& amount, std::uint32_t flags) @@ -56,6 +61,4 @@ claw(Account const& account, STAmount const& amount, std::optional cons return jv; } -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/impl/txflags.cpp b/src/test/jtx/impl/txflags.cpp index 7b49f9380b..686be767fe 100644 --- a/src/test/jtx/impl/txflags.cpp +++ b/src/test/jtx/impl/txflags.cpp @@ -1,10 +1,11 @@ #include +#include +#include + #include -namespace xrpl { -namespace test { -namespace jtx { +namespace xrpl::test::jtx { void txflags::operator()(Env&, JTx& jt) const @@ -12,6 +13,4 @@ txflags::operator()(Env&, JTx& jt) const jt[jss::Flags] = v_ /*| tfFullyCanonicalSig*/; } -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/impl/utility.cpp b/src/test/jtx/impl/utility.cpp index 7332358031..4bb6c83b88 100644 --- a/src/test/jtx/impl/utility.cpp +++ b/src/test/jtx/impl/utility.cpp @@ -1,18 +1,30 @@ #include +#include + #include #include +#include +#include +#include +#include +#include #include #include #include +#include +#include #include -#include +#include +#include +#include #include -namespace xrpl { -namespace test { -namespace jtx { +#include +#include + +namespace xrpl::test::jtx { STObject parse(Json::Value const& jv) @@ -89,6 +101,4 @@ cmdToJSONRPC(std::vector const& args, beast::Journal j, unsigned in return jv; } -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/impl/vault.cpp b/src/test/jtx/impl/vault.cpp index 49c0dddaec..5f4b488092 100644 --- a/src/test/jtx/impl/vault.cpp +++ b/src/test/jtx/impl/vault.cpp @@ -1,15 +1,20 @@ -#include #include +#include + +#include #include -#include +#include +#include +#include +#include +#include #include #include +#include -namespace xrpl { -namespace test { -namespace jtx { +namespace xrpl::test::jtx { std::tuple Vault::create(CreateArgs const& args) const @@ -79,6 +84,4 @@ Vault::clawback(ClawbackArgs const& args) return jv; } -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/impl/xchain_bridge.cpp b/src/test/jtx/impl/xchain_bridge.cpp index 72d932463f..2cf1c305a7 100644 --- a/src/test/jtx/impl/xchain_bridge.cpp +++ b/src/test/jtx/impl/xchain_bridge.cpp @@ -1,20 +1,31 @@ -#include -#include #include +#include +#include +#include +#include +#include + +#include #include +#include +#include #include +#include #include +#include #include #include -#include -#include -#include #include -namespace xrpl { -namespace test { -namespace jtx { +#include +#include +#include +#include +#include +#include + +namespace xrpl::test::jtx { // use this for creating a bridge for a transaction Json::Value @@ -414,16 +425,17 @@ XChainBridgeObjects::XChainBridgeObjects() return r; }()) , reward(XRP(1)) - , split_reward_quorum(divide(reward, STAmount(UT_XCHAIN_DEFAULT_QUORUM), reward.issue())) - , split_reward_everyone(divide(reward, STAmount(UT_XCHAIN_DEFAULT_NUM_SIGNERS), reward.issue())) + , split_reward_quorum(divide(reward, STAmount(UT_XCHAIN_DEFAULT_QUORUM), reward.get())) + , split_reward_everyone( + divide(reward, STAmount(UT_XCHAIN_DEFAULT_NUM_SIGNERS), reward.get())) , tiny_reward(drops(37)) , tiny_reward_split( - (divide(tiny_reward, STAmount(UT_XCHAIN_DEFAULT_QUORUM), tiny_reward.issue()))) + (divide(tiny_reward, STAmount(UT_XCHAIN_DEFAULT_QUORUM), tiny_reward.get()))) , tiny_reward_remainder( tiny_reward - - multiply(tiny_reward_split, STAmount(UT_XCHAIN_DEFAULT_QUORUM), tiny_reward.issue())) + multiply(tiny_reward_split, STAmount(UT_XCHAIN_DEFAULT_QUORUM), tiny_reward.get())) , one_xrp(XRP(1)) - , xrp_dust(divide(one_xrp, STAmount(10000), one_xrp.issue())) + , xrp_dust(divide(one_xrp, STAmount(10000), one_xrp.get())) { } @@ -467,6 +479,4 @@ XChainBridgeObjects::createBridgeObjects(Env& mcEnv, Env& scEnv) createMcBridgeObjects(mcEnv); createScBridgeObjects(scEnv); } -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/invoice_id.h b/src/test/jtx/invoice_id.h index fd1b7ae45b..9366cc9bf5 100644 --- a/src/test/jtx/invoice_id.h +++ b/src/test/jtx/invoice_id.h @@ -2,9 +2,7 @@ #include -namespace xrpl { -namespace test { -namespace jtx { +namespace xrpl::test::jtx { struct invoice_id { @@ -19,6 +17,4 @@ public: void operator()(Env&, JTx& jt) const; }; -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/jtx_json.h b/src/test/jtx/jtx_json.h index c7f52fe283..0bd7c2ff94 100644 --- a/src/test/jtx/jtx_json.h +++ b/src/test/jtx/jtx_json.h @@ -4,9 +4,7 @@ #include -namespace xrpl { -namespace test { -namespace jtx { +namespace xrpl::test::jtx { /** Inject raw JSON. */ class json @@ -37,6 +35,4 @@ public: operator()(Env&, JTx& jt) const; }; -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/last_ledger_sequence.h b/src/test/jtx/last_ledger_sequence.h index 540d6ff384..9015c87f02 100644 --- a/src/test/jtx/last_ledger_sequence.h +++ b/src/test/jtx/last_ledger_sequence.h @@ -2,9 +2,7 @@ #include -namespace xrpl { -namespace test { -namespace jtx { +namespace xrpl::test::jtx { struct last_ledger_seq { @@ -20,6 +18,4 @@ public: operator()(Env&, JTx& jt) const; }; -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/ledgerStateFix.h b/src/test/jtx/ledgerStateFix.h index 7adc863cb4..8735882d15 100644 --- a/src/test/jtx/ledgerStateFix.h +++ b/src/test/jtx/ledgerStateFix.h @@ -3,20 +3,11 @@ #include #include -namespace xrpl { -namespace test { -namespace jtx { - /** LedgerStateFix operations. */ -namespace ledgerStateFix { +namespace xrpl::test::jtx::ledgerStateFix { /** Repair the links in an NFToken directory. */ Json::Value nftPageLinks(jtx::Account const& acct, jtx::Account const& owner); -} // namespace ledgerStateFix - -} // namespace jtx - -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx::ledgerStateFix diff --git a/src/test/jtx/memo.h b/src/test/jtx/memo.h index 2371490b30..8cb41b3f1c 100644 --- a/src/test/jtx/memo.h +++ b/src/test/jtx/memo.h @@ -2,9 +2,9 @@ #include -namespace xrpl { -namespace test { -namespace jtx { +#include + +namespace xrpl::test::jtx { /** Add a memo to a JTx. @@ -19,8 +19,8 @@ private: std::string type_; public: - memo(std::string const& data, std::string const& format, std::string const& type) - : data_(data), format_(format), type_(type) + memo(std::string data, std::string format, std::string type) + : data_(std::move(data)), format_(std::move(format)), type_(std::move(type)) { } @@ -34,7 +34,7 @@ private: std::string s_; public: - memo_data(std::string const& s) : s_(s) + memo_data(std::string s) : s_(std::move(s)) { } @@ -48,7 +48,7 @@ private: std::string s_; public: - memo_format(std::string const& s) : s_(s) + memo_format(std::string s) : s_(std::move(s)) { } @@ -62,7 +62,7 @@ private: std::string s_; public: - memo_type(std::string const& s) : s_(s) + memo_type(std::string s) : s_(std::move(s)) { } @@ -70,6 +70,4 @@ public: operator()(Env&, JTx& jt) const; }; -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/mpt.h b/src/test/jtx/mpt.h index 3990dd3087..20b8f03762 100644 --- a/src/test/jtx/mpt.h +++ b/src/test/jtx/mpt.h @@ -2,14 +2,14 @@ #include #include +#include #include #include +#include #include -namespace xrpl { -namespace test { -namespace jtx { +namespace xrpl::test::jtx { class MPTTester; @@ -95,7 +95,7 @@ struct MPTCreate struct MPTInit { - Holders holders = {}; + Holders holders = {}; // NOLINT(readability-redundant-member-init) PrettyAmount const xrp = XRP(10'000); PrettyAmount const xrpHolders = XRP(10'000); bool fund = true; @@ -109,10 +109,11 @@ struct MPTInitDef { Env& env; Account issuer; - Holders holders = {}; + Holders holders = {}; // NOLINT(readability-redundant-member-init) std::uint16_t transferFee = 0; std::optional pay = std::nullopt; std::uint32_t flags = MPTDEXFlags; + std::optional mutableFlags = std::nullopt; bool authHolder = false; bool fund = false; bool close = true; @@ -166,11 +167,11 @@ class MPTTester bool close_; public: - MPTTester(Env& env, Account const& issuer, MPTInit const& constr = {}); + MPTTester(Env& env, Account issuer, MPTInit const& constr = {}); MPTTester(MPTInitDef const& constr); MPTTester( Env& env, - Account const& issuer, + Account issuer, MPTID const& id, std::vector const& holders = {}, bool close = true); @@ -272,6 +273,12 @@ public: operator Asset() const; + friend BookSpec + operator~(MPTTester const& mpt) + { + return ~static_cast(mpt); + } + private: using SLEP = SLE::const_pointer; bool @@ -304,6 +311,4 @@ private: getFlags(std::optional const& holder) const; }; -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/multisign.h b/src/test/jtx/multisign.h index b3f38cc453..5b6a18b527 100644 --- a/src/test/jtx/multisign.h +++ b/src/test/jtx/multisign.h @@ -10,9 +10,7 @@ #include #include -namespace xrpl { -namespace test { -namespace jtx { +namespace xrpl::test::jtx { /** A signer in a SignerList */ struct signer @@ -100,6 +98,4 @@ public: /** The number of signer lists matches. */ using siglists = owner_count; -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/noop.h b/src/test/jtx/noop.h index 47d12fb9af..29c6188386 100644 --- a/src/test/jtx/noop.h +++ b/src/test/jtx/noop.h @@ -2,9 +2,7 @@ #include -namespace xrpl { -namespace test { -namespace jtx { +namespace xrpl::test::jtx { /** The null transaction. */ inline Json::Value @@ -13,6 +11,4 @@ noop(Account const& account) return fset(account, 0); } -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/offer.h b/src/test/jtx/offer.h index 0fad9ec6dc..5f81db751f 100644 --- a/src/test/jtx/offer.h +++ b/src/test/jtx/offer.h @@ -5,9 +5,7 @@ #include #include -namespace xrpl { -namespace test { -namespace jtx { +namespace xrpl::test::jtx { /** Create an offer. */ Json::Value @@ -21,6 +19,4 @@ offer( Json::Value offer_cancel(Account const& account, std::uint32_t offerSeq); -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/owners.h b/src/test/jtx/owners.h index 3f54e0c55f..572b63757c 100644 --- a/src/test/jtx/owners.h +++ b/src/test/jtx/owners.h @@ -7,6 +7,7 @@ #include #include +#include namespace xrpl { @@ -24,8 +25,7 @@ owned_count_helper( } // namespace detail -namespace test { -namespace jtx { +namespace test::jtx { // Helper for aliases template @@ -36,14 +36,14 @@ private: std::uint32_t value_; public: - owner_count(Account const& account, std::uint32_t value) : account_(account), value_(value) + owner_count(Account account, std::uint32_t value) : account_(std::move(account)), value_(value) { } void operator()(Env& env) const { - detail::owned_count_helper(env, account_.id(), Type, value_); + xrpl::detail::owned_count_helper(env, account_.id(), Type, value_); } }; @@ -55,7 +55,7 @@ private: std::uint32_t value_; public: - owners(Account const& account, std::uint32_t value) : account_(account), value_(value) + owners(Account account, std::uint32_t value) : account_(std::move(account)), value_(value) { } @@ -69,6 +69,9 @@ using lines = owner_count; /** Match the number of offers in the account's owner directory */ using offers = owner_count; -} // namespace jtx -} // namespace test +/** Match the number of MPToken in the account's owner directory */ +using mptokens = owner_count; + +} // namespace test::jtx + } // namespace xrpl diff --git a/src/test/jtx/paths.h b/src/test/jtx/paths.h index 6522364e14..f63faa6292 100644 --- a/src/test/jtx/paths.h +++ b/src/test/jtx/paths.h @@ -7,19 +7,20 @@ #include namespace xrpl { -namespace test { -namespace jtx { +class STPath; + +namespace test::jtx { /** Set Paths, SendMax on a JTx. */ class paths { private: - Issue in_; + Asset in_; int depth_; unsigned int limit_; public: - paths(Issue const& in, int depth = 7, unsigned int limit = 4) + paths(Asset const& in, int depth = 7, unsigned int limit = 4) : in_(in), depth_(depth), limit_(limit) { } @@ -45,6 +46,8 @@ public: template explicit path(T const& t, Args const&... args); + path(STPath const& p); + void operator()(Env&, JTx& jt) const; @@ -59,7 +62,7 @@ private: append_one(AccountID const& account); template - std::enable_if_t::value> + std::enable_if_t> append_one(T const& t) { append_one(Account{t}); @@ -91,6 +94,6 @@ path::append(T const& t, Args const&... args) append(args...); } -} // namespace jtx -} // namespace test +} // namespace test::jtx + } // namespace xrpl diff --git a/src/test/jtx/pay.h b/src/test/jtx/pay.h index a155d7bdc8..093920a970 100644 --- a/src/test/jtx/pay.h +++ b/src/test/jtx/pay.h @@ -5,9 +5,7 @@ #include -namespace xrpl { -namespace test { -namespace jtx { +namespace xrpl::test::jtx { /** Create a payment. */ Json::Value @@ -15,6 +13,4 @@ pay(AccountID const& account, AccountID const& to, AnyAmount amount); Json::Value pay(Account const& account, Account const& to, AnyAmount amount); -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/permissioned_dex.h b/src/test/jtx/permissioned_dex.h index 2023342ded..025097116f 100644 --- a/src/test/jtx/permissioned_dex.h +++ b/src/test/jtx/permissioned_dex.h @@ -3,9 +3,7 @@ #include #include -namespace xrpl { -namespace test { -namespace jtx { +namespace xrpl::test::jtx { uint256 setupDomain( @@ -29,6 +27,4 @@ public: PermissionedDEX(Env& env); }; -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/permissioned_domains.h b/src/test/jtx/permissioned_domains.h index bf67722c9c..a582b001b1 100644 --- a/src/test/jtx/permissioned_domains.h +++ b/src/test/jtx/permissioned_domains.h @@ -4,10 +4,7 @@ #include #include -namespace xrpl { -namespace test { -namespace jtx { -namespace pdomain { +namespace xrpl::test::jtx::pdomain { // Helpers for PermissionedDomains testing using Credential = xrpl::test::jtx::deposit::AuthorizeCredentials; @@ -47,7 +44,4 @@ sortCredentials(Credentials const& input); uint256 getNewDomain(std::shared_ptr const& meta); -} // namespace pdomain -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx::pdomain diff --git a/src/test/jtx/prop.h b/src/test/jtx/prop.h index b1f8cb5ffc..5651ac58ce 100644 --- a/src/test/jtx/prop.h +++ b/src/test/jtx/prop.h @@ -4,9 +4,7 @@ #include -namespace xrpl { -namespace test { -namespace jtx { +namespace xrpl::test::jtx { /** Set a property on a JTx. */ template @@ -26,6 +24,4 @@ struct prop } }; -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/quality.h b/src/test/jtx/quality.h index 2da83eeef5..adb198038a 100644 --- a/src/test/jtx/quality.h +++ b/src/test/jtx/quality.h @@ -2,9 +2,7 @@ #include -namespace xrpl { -namespace test { -namespace jtx { +namespace xrpl::test::jtx { /** Sets the literal QualityIn on a trust JTx. */ class qualityIn @@ -62,6 +60,4 @@ public: operator()(Env&, JTx& jtx) const; }; -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/rate.h b/src/test/jtx/rate.h index a92bd2364a..740c2ffd1a 100644 --- a/src/test/jtx/rate.h +++ b/src/test/jtx/rate.h @@ -4,14 +4,10 @@ #include -namespace xrpl { -namespace test { -namespace jtx { +namespace xrpl::test::jtx { /** Set a transfer rate. */ Json::Value rate(Account const& account, double multiplier); -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/regkey.h b/src/test/jtx/regkey.h index 2c95baacc3..3da055c66f 100644 --- a/src/test/jtx/regkey.h +++ b/src/test/jtx/regkey.h @@ -5,9 +5,7 @@ #include -namespace xrpl { -namespace test { -namespace jtx { +namespace xrpl::test::jtx { /** Disable the regular key. */ Json::Value @@ -17,6 +15,4 @@ regkey(Account const& account, disabled_t); Json::Value regkey(Account const& account, Account const& signer); -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/require.h b/src/test/jtx/require.h index 7d712e8ab7..20f79ff838 100644 --- a/src/test/jtx/require.h +++ b/src/test/jtx/require.h @@ -20,8 +20,7 @@ require_args(test::jtx::requires_t& vec, Cond const& cond, Args const&... args) } // namespace detail -namespace test { -namespace jtx { +namespace test::jtx { /** Compose many condition functors into one */ template @@ -60,6 +59,6 @@ public: } }; -} // namespace jtx -} // namespace test +} // namespace test::jtx + } // namespace xrpl diff --git a/src/test/jtx/requires.h b/src/test/jtx/requires.h index 2411d040c6..d41d0cd0b0 100644 --- a/src/test/jtx/requires.h +++ b/src/test/jtx/requires.h @@ -3,15 +3,11 @@ #include #include -namespace xrpl { -namespace test { -namespace jtx { +namespace xrpl::test::jtx { class Env; using require_t = std::function; using requires_t = std::vector; -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/rpc.h b/src/test/jtx/rpc.h index bbdecf1519..be7ab8d456 100644 --- a/src/test/jtx/rpc.h +++ b/src/test/jtx/rpc.h @@ -3,10 +3,9 @@ #include #include +#include -namespace xrpl { -namespace test { -namespace jtx { +namespace xrpl::test::jtx { /** Set the expected result code for a JTx The test will fail if the code doesn't match. @@ -22,13 +21,13 @@ private: public: /// If there's an error code, we expect an error message explicit rpc(error_code_i code, std::optional m = {}) - : code_(code), errorMessage_(m) + : code_(code), errorMessage_(std::move(m)) { } /// If there is not a code, we expect an exception message explicit rpc(std::string error, std::optional exceptionMessage = {}) - : error_(error), errorException_(exceptionMessage) + : error_(error), errorException_(std::move(exceptionMessage)) { } @@ -56,6 +55,4 @@ public: } }; -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/sendmax.h b/src/test/jtx/sendmax.h index 8a471be3cd..e559a87641 100644 --- a/src/test/jtx/sendmax.h +++ b/src/test/jtx/sendmax.h @@ -4,9 +4,9 @@ #include -namespace xrpl { -namespace test { -namespace jtx { +#include + +namespace xrpl::test::jtx { /** Sets the SendMax on a JTx. */ class sendmax @@ -15,7 +15,7 @@ private: STAmount amount_; public: - sendmax(STAmount const& amount) : amount_(amount) + sendmax(STAmount amount) : amount_(std::move(amount)) { } @@ -23,6 +23,4 @@ public: operator()(Env&, JTx& jtx) const; }; -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/seq.h b/src/test/jtx/seq.h index b861800ac9..ea5ccfa259 100644 --- a/src/test/jtx/seq.h +++ b/src/test/jtx/seq.h @@ -5,9 +5,7 @@ #include -namespace xrpl { -namespace test { -namespace jtx { +namespace xrpl::test::jtx { /** Set the sequence number on a JTx. */ struct seq @@ -33,6 +31,4 @@ public: operator()(Env&, JTx& jt) const; }; -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/sig.h b/src/test/jtx/sig.h index 0ed59de50e..e23022b4b5 100644 --- a/src/test/jtx/sig.h +++ b/src/test/jtx/sig.h @@ -4,9 +4,7 @@ #include -namespace xrpl { -namespace test { -namespace jtx { +namespace xrpl::test::jtx { /** Set the regular signature on a JTx. @note For multisign, use msig. @@ -56,6 +54,4 @@ public: operator()(Env&, JTx& jt) const; }; -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/tag.h b/src/test/jtx/tag.h index c8d5723754..c11dff2550 100644 --- a/src/test/jtx/tag.h +++ b/src/test/jtx/tag.h @@ -2,10 +2,7 @@ #include -namespace xrpl { -namespace test { - -namespace jtx { +namespace xrpl::test::jtx { /** Set the destination tag on a JTx*/ struct dtag @@ -37,7 +34,4 @@ public: operator()(Env&, JTx& jt) const; }; -} // namespace jtx - -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/tags.h b/src/test/jtx/tags.h index c8a6170048..9c7a21145d 100644 --- a/src/test/jtx/tags.h +++ b/src/test/jtx/tags.h @@ -1,45 +1,31 @@ #pragma once -namespace xrpl { -namespace test { - -namespace jtx { +namespace xrpl::test::jtx { struct none_t { - none_t() - { - } + none_t() = default; }; static none_t const none; struct autofill_t { - autofill_t() - { - } + autofill_t() = default; }; static autofill_t const autofill; struct disabled_t { - disabled_t() - { - } + disabled_t() = default; }; static disabled_t const disabled; /** Used for fee() calls that use an owner reserve increment */ struct increment_t { - increment_t() - { - } + increment_t() = default; }; static increment_t const increment; -} // namespace jtx - -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/ter.h b/src/test/jtx/ter.h index 074ba307cb..ab21bc8e2b 100644 --- a/src/test/jtx/ter.h +++ b/src/test/jtx/ter.h @@ -4,9 +4,7 @@ #include -namespace xrpl { -namespace test { -namespace jtx { +namespace xrpl::test::jtx { /** Set the expected result code for a JTx The test will fail if the code doesn't match. @@ -32,6 +30,4 @@ public: } }; -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/ticket.h b/src/test/jtx/ticket.h index dca16ac7c6..7ab3b9c1f9 100644 --- a/src/test/jtx/ticket.h +++ b/src/test/jtx/ticket.h @@ -6,9 +6,7 @@ #include -namespace xrpl { -namespace test { -namespace jtx { +namespace xrpl::test::jtx { /* This shows how the jtx system may be extended to other @@ -43,7 +41,4 @@ public: /** Match the number of tickets on the account. */ using tickets = owner_count; -} // namespace jtx - -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/token.h b/src/test/jtx/token.h index 3f4a3f75a3..28ccd7520b 100644 --- a/src/test/jtx/token.h +++ b/src/test/jtx/token.h @@ -8,11 +8,7 @@ #include -namespace xrpl { -namespace test { -namespace jtx { - -namespace token { +namespace xrpl::test::jtx::token { /** Mint an NFToken. */ Json::Value @@ -214,9 +210,4 @@ clearMinter(jtx::Account const& account); Json::Value modify(jtx::Account const& account, uint256 const& nftokenID); -} // namespace token - -} // namespace jtx - -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx::token diff --git a/src/test/jtx/trust.h b/src/test/jtx/trust.h index afea256382..f6fb5b388f 100644 --- a/src/test/jtx/trust.h +++ b/src/test/jtx/trust.h @@ -5,9 +5,7 @@ #include #include -namespace xrpl { -namespace test { -namespace jtx { +namespace xrpl::test::jtx { /** Modify a trust line. */ Json::Value @@ -23,6 +21,4 @@ claw( STAmount const& amount, std::optional const& mptHolder = std::nullopt); -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/txflags.h b/src/test/jtx/txflags.h index f51c26d035..838f7f5f30 100644 --- a/src/test/jtx/txflags.h +++ b/src/test/jtx/txflags.h @@ -2,9 +2,7 @@ #include -namespace xrpl { -namespace test { -namespace jtx { +namespace xrpl::test::jtx { /** Set the flags on a JTx. */ class txflags @@ -21,6 +19,4 @@ public: operator()(Env&, JTx& jt) const; }; -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/utility.h b/src/test/jtx/utility.h index 15c323f047..b00f5b433e 100644 --- a/src/test/jtx/utility.h +++ b/src/test/jtx/utility.h @@ -8,9 +8,7 @@ #include -namespace xrpl { -namespace test { -namespace jtx { +namespace xrpl::test::jtx { /** Thrown when parse fails. */ struct parse_error : std::logic_error @@ -49,10 +47,8 @@ fill_fee(Json::Value& jv, ReadView const& view); void fill_seq(Json::Value& jv, ReadView const& view); -/** Given a rippled unit test rpc command, return the corresponding JSON. */ +/** Given an xrpld unit test rpc command, return the corresponding JSON. */ Json::Value cmdToJSONRPC(std::vector const& args, beast::Journal j, unsigned int apiVersion); -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/vault.h b/src/test/jtx/vault.h index 748d3341a5..bbd8c129cc 100644 --- a/src/test/jtx/vault.h +++ b/src/test/jtx/vault.h @@ -11,9 +11,7 @@ #include #include -namespace xrpl { -namespace test { -namespace jtx { +namespace xrpl::test::jtx { class Env; @@ -25,7 +23,8 @@ struct Vault { Account owner; Asset asset; - std::optional flags{}; + std::optional flags = + std::nullopt; // NOLINT(readability-redundant-member-init) }; /** Return a VaultCreate transaction and the Vault's expected keylet. */ @@ -75,13 +74,11 @@ struct Vault Account issuer; uint256 id; Account holder; - std::optional amount{}; + std::optional amount = std::nullopt; // NOLINT(readability-redundant-member-init) }; static Json::Value clawback(ClawbackArgs const& args); }; -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/jtx/xchain_bridge.h b/src/test/jtx/xchain_bridge.h index 1270d03ed6..bb01bf17ba 100644 --- a/src/test/jtx/xchain_bridge.h +++ b/src/test/jtx/xchain_bridge.h @@ -8,9 +8,7 @@ #include #include -namespace xrpl { -namespace test { -namespace jtx { +namespace xrpl::test::jtx { using JValueVec = std::vector; @@ -224,13 +222,11 @@ struct XChainBridgeObjects Account const& acc, Json::Value const& bridge = Json::nullValue, STAmount const& _reward = XRP(1), - std::optional const& minAccountCreate = std::nullopt) + std::optional const& minAccountCreate = std::nullopt) const { return bridge_create( acc, bridge == Json::nullValue ? jvb : bridge, _reward, minAccountCreate); } }; -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx diff --git a/src/test/ledger/BookDirs_test.cpp b/src/test/ledger/BookDirs_test.cpp index e8733d159d..efd1688c6f 100644 --- a/src/test/ledger/BookDirs_test.cpp +++ b/src/test/ledger/BookDirs_test.cpp @@ -1,10 +1,21 @@ -#include +#include +#include +#include +#include +#include + +#include #include +#include #include +#include +#include -namespace xrpl { -namespace test { +#include +#include + +namespace xrpl::test { struct BookDirs_test : public beast::unit_test::suite { @@ -19,7 +30,7 @@ struct BookDirs_test : public beast::unit_test::suite env.close(); { - Book const book(xrpIssue(), USD.issue(), std::nullopt); + Book const book(xrpIssue(), USD, std::nullopt); { auto d = BookDirs(*env.current(), book); BEAST_EXPECT(std::begin(d) == std::end(d)); @@ -33,14 +44,14 @@ struct BookDirs_test : public beast::unit_test::suite { env(offer("alice", Account("alice")["USD"](50), XRP(10))); - auto d = BookDirs( - *env.current(), Book(Account("alice")["USD"].issue(), xrpIssue(), std::nullopt)); + auto d = + BookDirs(*env.current(), Book(Account("alice")["USD"], xrpIssue(), std::nullopt)); BEAST_EXPECT(std::distance(d.begin(), d.end()) == 1); } { env(offer("alice", gw["CNY"](50), XRP(10))); - auto d = BookDirs(*env.current(), Book(gw["CNY"].issue(), xrpIssue(), std::nullopt)); + auto d = BookDirs(*env.current(), Book(gw["CNY"], xrpIssue(), std::nullopt)); BEAST_EXPECT(std::distance(d.begin(), d.end()) == 1); } @@ -48,8 +59,7 @@ struct BookDirs_test : public beast::unit_test::suite env.trust(Account("bob")["CNY"](10), "alice"); env(pay("bob", "alice", Account("bob")["CNY"](10))); env(offer("alice", USD(50), Account("bob")["CNY"](10))); - auto d = BookDirs( - *env.current(), Book(USD.issue(), Account("bob")["CNY"].issue(), std::nullopt)); + auto d = BookDirs(*env.current(), Book(USD, Account("bob")["CNY"], std::nullopt)); BEAST_EXPECT(std::distance(d.begin(), d.end()) == 1); } @@ -61,7 +71,7 @@ struct BookDirs_test : public beast::unit_test::suite env(offer("alice", AUD(i), XRP(j))); } - auto d = BookDirs(*env.current(), Book(AUD.issue(), xrpIssue(), std::nullopt)); + auto d = BookDirs(*env.current(), Book(AUD, xrpIssue(), std::nullopt)); BEAST_EXPECT(std::distance(d.begin(), d.end()) == 240); auto i = 1, j = 3, k = 0; for (auto const& e : d) @@ -89,5 +99,4 @@ struct BookDirs_test : public beast::unit_test::suite BEAST_DEFINE_TESTSUITE(BookDirs, ledger, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/ledger/Directory_test.cpp b/src/test/ledger/Directory_test.cpp index 4115d03c19..f14ce2f0d7 100644 --- a/src/test/ledger/Directory_test.cpp +++ b/src/test/ledger/Directory_test.cpp @@ -1,19 +1,50 @@ -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // IWYU pragma: keep +#include +#include +#include +#include + +#include #include +#include +#include +#include +#include +#include #include #include #include +#include #include +#include +#include +#include #include +#include #include #include #include +#include +#include #include +#include +#include +#include +#include +#include -namespace xrpl { -namespace test { +namespace xrpl::test { struct Directory_test : public beast::unit_test::suite { @@ -102,7 +133,7 @@ struct Directory_test : public beast::unit_test::suite // Ensure that the entries in the page are sorted auto const& v = p->getFieldV256(sfIndexes); - BEAST_EXPECT(std::is_sorted(v.begin(), v.end())); + BEAST_EXPECT(std::ranges::is_sorted(v)); // Ensure that the page contains the correct orders by // calculating which sequence numbers belong here. @@ -123,7 +154,7 @@ struct Directory_test : public beast::unit_test::suite // Now check the orderbook: it should be in the order we placed // the offers. - auto book = BookDirs(*env.current(), Book({xrpIssue(), USD.issue(), std::nullopt})); + auto book = BookDirs(*env.current(), Book({xrpIssue(), USD, std::nullopt})); int count = 1; for (auto const& offer : book) @@ -283,7 +314,7 @@ struct Directory_test : public beast::unit_test::suite // should have no entries and be empty: { Sandbox const sb(env.closed().get(), tapNONE); - uint256 const bookBase = getBookBase({xrpIssue(), USD.issue(), std::nullopt}); + uint256 const bookBase = getBookBase({xrpIssue(), USD, std::nullopt}); BEAST_EXPECT(dirIsEmpty(sb, keylet::page(bookBase))); BEAST_EXPECT(!sb.succ(bookBase, getQualityNext(bookBase))); @@ -475,7 +506,7 @@ struct Directory_test : public beast::unit_test::suite testDirectoryFull() { using namespace test::jtx; - Account alice("alice"); + Account const alice("alice"); auto const testCase = [&, this](FeatureBitset features, auto setup) { using namespace test::jtx; @@ -568,5 +599,4 @@ struct Directory_test : public beast::unit_test::suite BEAST_DEFINE_TESTSUITE_PRIO(Directory, ledger, xrpl, 1); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/ledger/PaymentSandbox_test.cpp b/src/test/ledger/PaymentSandbox_test.cpp index ab01c4852e..0dc2cb6e74 100644 --- a/src/test/ledger/PaymentSandbox_test.cpp +++ b/src/test/ledger/PaymentSandbox_test.cpp @@ -1,14 +1,34 @@ +#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include +#include #include #include +#include #include #include +#include +#include +#include +#include +#include +#include -namespace xrpl { -namespace test { +#include + +namespace xrpl::test { class PaymentSandbox_test : public beast::unit_test::suite { @@ -105,7 +125,7 @@ class PaymentSandbox_test : public beast::unit_test::suite // accountSend, no deferredCredits ApplyViewImpl av(&*env.current(), tapNONE); - auto const iss = USD_gw1.issue(); + auto const iss = USD_gw1; auto const startingAmount = accountHolds(av, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j); { @@ -125,19 +145,19 @@ class PaymentSandbox_test : public beast::unit_test::suite } { - // rippleCredit, no deferredCredits + // directSendNoFee, no deferredCredits ApplyViewImpl av(&*env.current(), tapNONE); - auto const iss = USD_gw1.issue(); + auto const iss = USD_gw1; auto const startingAmount = accountHolds(av, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j); - rippleCredit(av, gw1, alice, toCredit, true, j); + directSendNoFee(av, gw1, alice, toCredit, true, j); BEAST_EXPECT( accountHolds(av, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j) == startingAmount + toCredit); - rippleCredit(av, alice, gw1, toDebit, true, j); + directSendNoFee(av, alice, gw1, toDebit, true, j); BEAST_EXPECT( accountHolds(av, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j) == startingAmount + toCredit - toDebit); @@ -148,7 +168,7 @@ class PaymentSandbox_test : public beast::unit_test::suite ApplyViewImpl av(&*env.current(), tapNONE); PaymentSandbox pv(&av); - auto const iss = USD_gw1.issue(); + auto const iss = USD_gw1; auto const startingAmount = accountHolds(pv, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j); @@ -170,15 +190,15 @@ class PaymentSandbox_test : public beast::unit_test::suite } { - // rippleCredit, w/ deferredCredits + // directSendNoFee, w/ deferredCredits ApplyViewImpl av(&*env.current(), tapNONE); PaymentSandbox pv(&av); - auto const iss = USD_gw1.issue(); + auto const iss = USD_gw1; auto const startingAmount = accountHolds(pv, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j); - rippleCredit(pv, gw1, alice, toCredit, true, j); + directSendNoFee(pv, gw1, alice, toCredit, true, j); BEAST_EXPECT( accountHolds(pv, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j) == startingAmount); @@ -189,7 +209,7 @@ class PaymentSandbox_test : public beast::unit_test::suite ApplyViewImpl av(&*env.current(), tapNONE); PaymentSandbox pv(&av); - auto const iss = USD_gw1.issue(); + auto const iss = USD_gw1; auto const startingAmount = accountHolds(pv, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j); @@ -204,7 +224,7 @@ class PaymentSandbox_test : public beast::unit_test::suite ApplyViewImpl av(&*env.current(), tapNONE); PaymentSandbox pv(&av); - auto const iss = USD_gw1.issue(); + auto const iss = USD_gw1; auto const startingAmount = accountHolds(pv, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j); @@ -219,7 +239,7 @@ class PaymentSandbox_test : public beast::unit_test::suite ApplyViewImpl av(&*env.current(), tapNONE); PaymentSandbox pv(&av); - auto const iss = USD_gw1.issue(); + auto const iss = USD_gw1; auto const startingAmount = accountHolds(pv, alice, iss.currency, iss.account, fhIGNORE_FREEZE, j); @@ -272,7 +292,7 @@ class PaymentSandbox_test : public beast::unit_test::suite Account const alice("alice"); auto const USD = gw["USD"]; - auto const issue = USD.issue(); + auto const issue = USD; STAmount const tinyAmt( issue, STAmount::cMinValue, STAmount::cMinOffset + 1, false, STAmount::unchecked{}); STAmount const hugeAmt( @@ -280,8 +300,8 @@ class PaymentSandbox_test : public beast::unit_test::suite ApplyViewImpl av(&*env.current(), tapNONE); PaymentSandbox pv(&av); - pv.creditHook(gw, alice, hugeAmt, -tinyAmt); - BEAST_EXPECT(pv.balanceHook(alice, gw, hugeAmt) == tinyAmt); + pv.creditHookIOU(gw, alice, hugeAmt, -tinyAmt); + BEAST_EXPECT(pv.balanceHookIOU(alice, gw, hugeAmt) == tinyAmt); } void @@ -328,8 +348,8 @@ class PaymentSandbox_test : public beast::unit_test::suite void testBalanceHook(FeatureBitset features) { - // Make sure the Issue::Account returned by PAymentSandbox::balanceHook - // is correct. + // Make sure the Issue::Account returned by + // PaymentSandbox::balanceHookIOU is correct. testcase("balanceHook"); using namespace jtx; @@ -343,16 +363,18 @@ class PaymentSandbox_test : public beast::unit_test::suite PaymentSandbox sb(&av); // The currency we pass for the last argument mimics the currency that - // is typically passed to creditHook, since it comes from a trust line. + // is typically passed to creditHookIOU, since it comes from a trust + // line. Issue tlIssue = noIssue(); - tlIssue.currency = USD.issue().currency; + tlIssue.currency = USD.currency; - sb.creditHook(gw.id(), alice.id(), {USD, 400}, {tlIssue, 600}); - sb.creditHook(gw.id(), alice.id(), {USD, 100}, {tlIssue, 600}); + sb.creditHookIOU(gw.id(), alice.id(), {USD, 400}, {tlIssue, 600}); + sb.creditHookIOU(gw.id(), alice.id(), {USD, 100}, {tlIssue, 600}); - // Expect that the STAmount issuer returned by balanceHook() is correct. - STAmount const balance = sb.balanceHook(gw.id(), alice.id(), {USD, 600}); - BEAST_EXPECT(balance.getIssuer() == USD.issue().account); + // Expect that the STAmount issuer returned by balanceHookIOU() is + // correct. + STAmount const balance = sb.balanceHookIOU(gw.id(), alice.id(), {USD, 600}); + BEAST_EXPECT(balance.getIssuer() == USD.account.id()); } public: @@ -375,5 +397,4 @@ public: BEAST_DEFINE_TESTSUITE(PaymentSandbox, ledger, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/ledger/PendingSaves_test.cpp b/src/test/ledger/PendingSaves_test.cpp index 5e08fc53e4..deca50c9be 100644 --- a/src/test/ledger/PendingSaves_test.cpp +++ b/src/test/ledger/PendingSaves_test.cpp @@ -1,8 +1,7 @@ -#include +#include #include -namespace xrpl { -namespace test { +namespace xrpl::test { struct PendingSaves_test : public beast::unit_test::suite { @@ -41,5 +40,4 @@ struct PendingSaves_test : public beast::unit_test::suite BEAST_DEFINE_TESTSUITE(PendingSaves, ledger, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/ledger/SkipList_test.cpp b/src/test/ledger/SkipList_test.cpp index a2695bfce8..33f6f505f1 100644 --- a/src/test/ledger/SkipList_test.cpp +++ b/src/test/ledger/SkipList_test.cpp @@ -1,11 +1,17 @@ #include -#include +#include + +#include +#include #include #include -namespace xrpl { -namespace test { +#include +#include +#include + +namespace xrpl::test { class SkipList_test : public beast::unit_test::suite { @@ -74,5 +80,4 @@ class SkipList_test : public beast::unit_test::suite BEAST_DEFINE_TESTSUITE(SkipList, ledger, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/ledger/View_test.cpp b/src/test/ledger/View_test.cpp index d2d930732e..cf2e478889 100644 --- a/src/test/ledger/View_test.cpp +++ b/src/test/ledger/View_test.cpp @@ -1,20 +1,55 @@ -#include -#include +#include +#include +#include +#include // IWYU pragma: keep +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include #include #include #include #include +#include #include +#include #include #include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include -namespace xrpl { -namespace test { +namespace xrpl::test { class View_test : public beast::unit_test::suite { @@ -1048,5 +1083,4 @@ class GetAmendments_test : public beast::unit_test::suite BEAST_DEFINE_TESTSUITE(View, ledger, xrpl); BEAST_DEFINE_TESTSUITE(GetAmendments, ledger, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/nodestore/Backend_test.cpp b/src/test/nodestore/Backend_test.cpp index 101138de88..0bba847c30 100644 --- a/src/test/nodestore/Backend_test.cpp +++ b/src/test/nodestore/Backend_test.cpp @@ -1,17 +1,22 @@ #include #include +#include #include -#include +#include #include +#include +#include #include #include +#include #include +#include +#include +#include -namespace xrpl { - -namespace NodeStore { +namespace xrpl::NodeStore { // Tests the Backend interface // @@ -73,8 +78,8 @@ public: Batch copy; fetchCopyOfBatch(*backend, ©, batch); // Canonicalize the source and destination batches - std::sort(batch.begin(), batch.end(), LessThan{}); - std::sort(copy.begin(), copy.end(), LessThan{}); + std::ranges::sort(batch, LessThan{}); + std::ranges::sort(copy, LessThan{}); BEAST_EXPECT(areBatchesEqual(batch, copy)); } } @@ -100,5 +105,4 @@ public: BEAST_DEFINE_TESTSUITE(Backend, nodestore, xrpl); -} // namespace NodeStore -} // namespace xrpl +} // namespace xrpl::NodeStore diff --git a/src/test/nodestore/Basics_test.cpp b/src/test/nodestore/Basics_test.cpp index c9755d04d7..f31111eed5 100644 --- a/src/test/nodestore/Basics_test.cpp +++ b/src/test/nodestore/Basics_test.cpp @@ -1,10 +1,14 @@ #include +#include +#include #include #include -namespace xrpl { -namespace NodeStore { +#include +#include + +namespace xrpl::NodeStore { // Tests predictable batches, and NodeObject blob encoding // @@ -66,5 +70,4 @@ public: BEAST_DEFINE_TESTSUITE(NodeStoreBasic, nodestore, xrpl); -} // namespace NodeStore -} // namespace xrpl +} // namespace xrpl::NodeStore diff --git a/src/test/nodestore/Database_test.cpp b/src/test/nodestore/Database_test.cpp index 43bda9dcae..684347c6cc 100644 --- a/src/test/nodestore/Database_test.cpp +++ b/src/test/nodestore/Database_test.cpp @@ -1,17 +1,32 @@ -#include #include +#include #include #include #include +#include + +#include +#include +#include #include +#include +#include #include #include +#include +#include #include -namespace xrpl { +#include +#include +#include +#include +#include +#include +#include -namespace NodeStore { +namespace xrpl::NodeStore { class Database_test : public TestBase { @@ -556,8 +571,8 @@ public: } // Canonicalize the source and destination batches - std::sort(batch.begin(), batch.end(), LessThan{}); - std::sort(copy.begin(), copy.end(), LessThan{}); + std::ranges::sort(batch, LessThan{}); + std::ranges::sort(copy, LessThan{}); BEAST_EXPECT(areBatchesEqual(batch, copy)); } @@ -621,8 +636,8 @@ public: fetchCopyOfBatch(*db, ©, batch); // Canonicalize the source and destination batches - std::sort(batch.begin(), batch.end(), LessThan{}); - std::sort(copy.begin(), copy.end(), LessThan{}); + std::ranges::sort(batch, LessThan{}); + std::ranges::sort(copy, LessThan{}); BEAST_EXPECT(areBatchesEqual(batch, copy)); } @@ -709,5 +724,4 @@ public: BEAST_DEFINE_TESTSUITE(Database, nodestore, xrpl); -} // namespace NodeStore -} // namespace xrpl +} // namespace xrpl::NodeStore diff --git a/src/test/nodestore/NuDBFactory_test.cpp b/src/test/nodestore/NuDBFactory_test.cpp index 3e6b68c9e5..0755708cee 100644 --- a/src/test/nodestore/NuDBFactory_test.cpp +++ b/src/test/nodestore/NuDBFactory_test.cpp @@ -3,15 +3,23 @@ #include #include +#include +#include +#include #include #include #include +#include +#include +#include #include #include +#include +#include +#include -namespace xrpl { -namespace NodeStore { +namespace xrpl::NodeStore { class NuDBFactory_test : public TestBase { @@ -438,5 +446,4 @@ public: BEAST_DEFINE_TESTSUITE(NuDBFactory, xrpl_core, xrpl); -} // namespace NodeStore -} // namespace xrpl +} // namespace xrpl::NodeStore diff --git a/src/test/nodestore/TestBase.h b/src/test/nodestore/TestBase.h index 893e06579c..e527942629 100644 --- a/src/test/nodestore/TestBase.h +++ b/src/test/nodestore/TestBase.h @@ -13,8 +13,7 @@ #include -namespace xrpl { -namespace NodeStore { +namespace xrpl::NodeStore { /** Binary function that satisfies the strict-weak-ordering requirement. @@ -75,9 +74,10 @@ public: return hotTRANSACTION_NODE; case 3: return hotUNKNOWN; + default: + // will never happen, but make static analysis tool happy. + return hotUNKNOWN; } - // will never happen, but make static analysis tool happy. - return hotUNKNOWN; }(); uint256 hash; @@ -118,7 +118,7 @@ public: } // Store a batch in a backend - void + static void storeBatch(Backend& backend, Batch const& batch) { for (int i = 0; i < batch.size(); ++i) @@ -195,5 +195,4 @@ public: } }; -} // namespace NodeStore -} // namespace xrpl +} // namespace xrpl::NodeStore diff --git a/src/test/nodestore/Timing_test.cpp b/src/test/nodestore/Timing_test.cpp index fb60e6c7a5..cc9881dbac 100644 --- a/src/test/nodestore/Timing_test.cpp +++ b/src/test/nodestore/Timing_test.cpp @@ -2,34 +2,48 @@ #include #include +#include #include +#include +#include #include -#include +#include #include +#include #include #include +#include #include #include +#include +#include +#include -#include +#include +#include #include #include #include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include -#include -#include +#include #include +#include #ifndef NODESTORE_TIMING_DO_VERIFY #define NODESTORE_TIMING_DO_VERIFY 0 #endif -namespace xrpl { -namespace NodeStore { +namespace xrpl::NodeStore { std::unique_ptr make_Backend(Section const& config, Scheduler& scheduler, beast::Journal journal) @@ -211,7 +225,7 @@ public: parallel_for(std::size_t const n, std::size_t number_of_threads, Args const&... args) { std::atomic c(0); - std::vector t; + std::vector t; t.reserve(number_of_threads); for (std::size_t id = 0; id < number_of_threads; ++id) t.emplace_back(*this, parallel_for_lambda(n, c), args...); @@ -224,7 +238,7 @@ public: parallel_for_id(std::size_t const n, std::size_t number_of_threads, Args const&... args) { std::atomic c(0); - std::vector t; + std::vector t; t.reserve(number_of_threads); for (std::size_t id = 0; id < number_of_threads; ++id) t.emplace_back(*this, parallel_for_lambda(n, c), id, args...); @@ -490,7 +504,7 @@ public: backend->close(); } - // Simulate a rippled workload: + // Simulate an xrpld workload: // Each thread randomly: // inserts a new key // fetches an old key @@ -714,5 +728,4 @@ public: BEAST_DEFINE_TESTSUITE_MANUAL_PRIO(Timing, nodestore, xrpl, 1); -} // namespace NodeStore -} // namespace xrpl +} // namespace xrpl::NodeStore diff --git a/src/test/nodestore/import_test.cpp b/src/test/nodestore/import_test.cpp index 6d336c1f51..1341b98894 100644 --- a/src/test/nodestore/import_test.cpp +++ b/src/test/nodestore/import_test.cpp @@ -1,23 +1,46 @@ #include -#include #include -#include #include -#include +#include #include #include -#include +#include // IWYU pragma: keep +#include +#include +#include -#include +#include // IWYU pragma: keep +#include +#include +#include +#include #include +#include +#include +#include +#include #include +#include +#include +#include +#include #include #include +#include +#include +#include +#include #include +#include #include +#include +#include +#include #include +#include +#include /* diff --git a/src/test/nodestore/varint_test.cpp b/src/test/nodestore/varint_test.cpp index f6145a9f52..b97b31f1fe 100644 --- a/src/test/nodestore/varint_test.cpp +++ b/src/test/nodestore/varint_test.cpp @@ -1,12 +1,12 @@ -#include +#include #include #include +#include +#include #include -namespace xrpl { -namespace NodeStore { -namespace tests { +namespace xrpl::NodeStore::tests { class varint_test : public beast::unit_test::suite { @@ -54,6 +54,4 @@ public: BEAST_DEFINE_TESTSUITE(varint, nodestore, xrpl); -} // namespace tests -} // namespace NodeStore -} // namespace xrpl +} // namespace xrpl::NodeStore::tests diff --git a/src/test/overlay/ProtocolVersion_test.cpp b/src/test/overlay/ProtocolVersion_test.cpp index fc25812cbb..a04bb3b36e 100644 --- a/src/test/overlay/ProtocolVersion_test.cpp +++ b/src/test/overlay/ProtocolVersion_test.cpp @@ -1,6 +1,9 @@ #include -#include +#include + +#include +#include namespace xrpl { diff --git a/src/test/overlay/TMGetObjectByHash_test.cpp b/src/test/overlay/TMGetObjectByHash_test.cpp index a2a934f182..c1db5262d0 100644 --- a/src/test/overlay/TMGetObjectByHash_test.cpp +++ b/src/test/overlay/TMGetObjectByHash_test.cpp @@ -1,20 +1,42 @@ -#include #include +#include +#include #include +#include #include #include +#include #include -#include +#include +#include +#include #include -#include +#include +#include #include +#include +#include +#include #include -#include +#include +#include -namespace xrpl { -namespace test { +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +namespace xrpl::test { using namespace jtx; @@ -59,7 +81,7 @@ class TMGetObjectByHash_test : public beast::unit_test::suite { } - ~PeerTest() = default; + ~PeerTest() override = default; void run() override @@ -199,5 +221,4 @@ class TMGetObjectByHash_test : public beast::unit_test::suite BEAST_DEFINE_TESTSUITE(TMGetObjectByHash, overlay, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/overlay/cluster_test.cpp b/src/test/overlay/cluster_test.cpp index 6fc7f3f59b..41db90dc57 100644 --- a/src/test/overlay/cluster_test.cpp +++ b/src/test/overlay/cluster_test.cpp @@ -4,10 +4,20 @@ #include #include +#include +#include +#include +#include #include +#include -namespace xrpl { -namespace tests { +#include +#include +#include +#include +#include + +namespace xrpl::tests { class cluster_test : public xrpl::TestSuite { @@ -81,7 +91,7 @@ public: for (auto const& n : network) { - auto found = std::find(cluster.begin(), cluster.end(), n); + auto found = std::ranges::find(cluster, n); BEAST_EXPECT(static_cast(c->member(n)) == (found != cluster.end())); } } @@ -98,7 +108,7 @@ public: for (auto const& n : network) { - auto found = std::find(cluster.begin(), cluster.end(), n); + auto found = std::ranges::find(cluster, n); BEAST_EXPECT(static_cast(c->member(n)) == (found != cluster.end())); } } @@ -243,5 +253,4 @@ public: BEAST_DEFINE_TESTSUITE(cluster, overlay, xrpl); -} // namespace tests -} // namespace xrpl +} // namespace xrpl::tests diff --git a/src/test/overlay/compression_test.cpp b/src/test/overlay/compression_test.cpp index 4ffc805726..bb4b95220a 100644 --- a/src/test/overlay/compression_test.cpp +++ b/src/test/overlay/compression_test.cpp @@ -2,37 +2,55 @@ #include #include #include +#include #include -#include +#include #include #include #include #include #include +#include +#include +#include +#include +#include #include -#include +#include +#include +#include #include -#include +#include #include -#include +#include +#include +#include +#include #include +#include +#include #include #include #include -#include #include -#include +#include +#include #include -#include +#include + +#include #include +#include +#include +#include +#include +#include -namespace xrpl { - -namespace test { +namespace xrpl::test { using namespace xrpl::test; using namespace xrpl::test::jtx; @@ -59,9 +77,7 @@ class compression_test : public beast::unit_test::suite using Algorithm = compression::Algorithm; public: - compression_test() - { - } + compression_test() = default; template void @@ -446,5 +462,4 @@ public: BEAST_DEFINE_TESTSUITE_MANUAL(compression, overlay, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/overlay/handshake_test.cpp b/src/test/overlay/handshake_test.cpp index 8e2d5d5ec1..50b987f794 100644 --- a/src/test/overlay/handshake_test.cpp +++ b/src/test/overlay/handshake_test.cpp @@ -1,10 +1,8 @@ #include -#include +#include -namespace xrpl { - -namespace test { +namespace xrpl::test { class handshake_test : public beast::unit_test::suite { @@ -42,5 +40,4 @@ public: BEAST_DEFINE_TESTSUITE(handshake, overlay, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/overlay/reduce_relay_test.cpp b/src/test/overlay/reduce_relay_test.cpp index 4a8d62fbc2..842511b860 100644 --- a/src/test/overlay/reduce_relay_test.cpp +++ b/src/test/overlay/reduce_relay_test.cpp @@ -1,28 +1,52 @@ -#include #include +#include +#include #include #include +#include #include #include #include +#include #include -#include +#include +#include +#include +#include +#include +#include +#include #include -#include -#include +#include + +#include #include +#include #include +#include +#include +#include #include +#include +#include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include -namespace xrpl { - -namespace test { +namespace xrpl::test { using namespace std::chrono; @@ -51,9 +75,7 @@ public: } PublicKey nodePublicKey_; - virtual ~PeerPartial() - { - } + ~PeerPartial() override = default; virtual void onMessage(MessageSPtr const& m, SquelchCB f) = 0; virtual void @@ -174,10 +196,10 @@ public: class ManualClock { public: - typedef uint64_t rep; - typedef std::milli period; - typedef std::chrono::duration duration; - typedef std::chrono::time_point time_point; + using rep = uint64_t; + using period = std::milli; + using duration = std::chrono::duration; + using time_point = std::chrono::time_point; inline static bool const is_steady = false; static void @@ -246,11 +268,8 @@ class Link using Latency = std::pair; public: - Link( - Validator& validator, - PeerSPtr peer, - Latency const& latency = {milliseconds(5), milliseconds(15)}) - : validator_(validator), peer_(peer), latency_(latency) + Link(Validator& validator, PeerSPtr peer, Latency latency = {milliseconds(5), milliseconds(15)}) + : validator_(validator), peer_(peer), latency_(std::move(latency)) { auto sp = peer_.lock(); assert(sp); @@ -371,9 +390,7 @@ public: for_links(LinkIterCB f, bool simulateSlow = false) { std::vector v; - std::transform(links_.begin(), links_.end(), std::back_inserter(v), [](auto& kv) { - return kv.second; - }); + std::ranges::transform(links_, std::back_inserter(v), [](auto& kv) { return kv.second; }); std::random_device d; std::mt19937 g(d()); std::shuffle(v.begin(), v.end(), g); @@ -443,7 +460,7 @@ public: id_ = sid_++; } - ~PeerSim() = default; + ~PeerSim() override = default; id_t id() const override @@ -477,7 +494,7 @@ public: } /** Remote Peer (Directly connected Peer) */ - virtual void + void onMessage(protocol::TMSquelch const& squelch) override { auto validator = squelch.validatorpubkey(); @@ -511,7 +528,7 @@ public: { } - ~OverlaySim() = default; + ~OverlaySim() override = default; void clear() @@ -752,8 +769,7 @@ public: void enableLink(std::uint16_t validatorId, Peer::id_t peer, bool enable) { - auto it = std::find_if( - validators_.begin(), validators_.end(), [&](auto& v) { return v.id() == validatorId; }); + auto it = std::ranges::find_if(validators_, [&](auto& v) { return v.id() == validatorId; }); assert(it != validators_.end()); if (enable) { @@ -1251,7 +1267,7 @@ protected: ManualClock::advance(seconds(601)); BEAST_EXPECT(propagateAndSquelch(log, true, false)); auto peers = network_.overlay().getPeers(network_.validator(0)); - auto it = std::find_if(peers.begin(), peers.end(), [&](auto it) { + auto it = std::ranges::find_if(peers, [&](auto it) { return std::get(it.second) == reduce_relay::PeerState::Squelched; }); @@ -1270,10 +1286,10 @@ protected: doTest("Test Config - squelch enabled (legacy)", log, [&](bool log) { Config c; - std::string const toLoad(R"rippleConfig( + std::string const toLoad(R"xrpldConfig( [reduce_relay] vp_enable=1 -)rippleConfig"); +)xrpldConfig"); c.loadFromString(toLoad); BEAST_EXPECT(c.VP_REDUCE_RELAY_BASE_SQUELCH_ENABLE == true); @@ -1282,19 +1298,19 @@ vp_enable=1 doTest("Test Config - squelch disabled (legacy)", log, [&](bool log) { Config c; - std::string toLoad(R"rippleConfig( + std::string toLoad(R"xrpldConfig( [reduce_relay] vp_enable=0 -)rippleConfig"); +)xrpldConfig"); c.loadFromString(toLoad); BEAST_EXPECT(c.VP_REDUCE_RELAY_BASE_SQUELCH_ENABLE == false); Config c1; - toLoad = R"rippleConfig( + toLoad = R"xrpldConfig( [reduce_relay] -)rippleConfig"; +)xrpldConfig"; c1.loadFromString(toLoad); BEAST_EXPECT(c1.VP_REDUCE_RELAY_BASE_SQUELCH_ENABLE == false); @@ -1303,10 +1319,10 @@ vp_enable=0 doTest("Test Config - squelch enabled", log, [&](bool log) { Config c; - std::string const toLoad(R"rippleConfig( + std::string const toLoad(R"xrpldConfig( [reduce_relay] vp_base_squelch_enable=1 -)rippleConfig"); +)xrpldConfig"); c.loadFromString(toLoad); BEAST_EXPECT(c.VP_REDUCE_RELAY_BASE_SQUELCH_ENABLE == true); @@ -1315,10 +1331,10 @@ vp_base_squelch_enable=1 doTest("Test Config - squelch disabled", log, [&](bool log) { Config c; - std::string const toLoad(R"rippleConfig( + std::string const toLoad(R"xrpldConfig( [reduce_relay] vp_base_squelch_enable=0 -)rippleConfig"); +)xrpldConfig"); c.loadFromString(toLoad); BEAST_EXPECT(c.VP_REDUCE_RELAY_BASE_SQUELCH_ENABLE == false); @@ -1327,11 +1343,11 @@ vp_base_squelch_enable=0 doTest("Test Config - legacy and new", log, [&](bool log) { Config c; - std::string const toLoad(R"rippleConfig( + std::string const toLoad(R"xrpldConfig( [reduce_relay] vp_base_squelch_enable=0 vp_enable=0 -)rippleConfig"); +)xrpldConfig"); std::string error; auto const expectedError = @@ -1345,7 +1361,7 @@ vp_enable=0 { c.loadFromString(toLoad); } - catch (std::runtime_error& e) + catch (std::runtime_error const& e) { error = e.what(); } @@ -1356,29 +1372,29 @@ vp_enable=0 doTest("Test Config - max selected peers", log, [&](bool log) { Config c; - std::string toLoad(R"rippleConfig( + std::string toLoad(R"xrpldConfig( [reduce_relay] -)rippleConfig"); +)xrpldConfig"); c.loadFromString(toLoad); BEAST_EXPECT(c.VP_REDUCE_RELAY_SQUELCH_MAX_SELECTED_PEERS == 5); Config c1; - toLoad = R"rippleConfig( + toLoad = R"xrpldConfig( [reduce_relay] vp_base_squelch_max_selected_peers=6 -)rippleConfig"; +)xrpldConfig"; c1.loadFromString(toLoad); BEAST_EXPECT(c1.VP_REDUCE_RELAY_SQUELCH_MAX_SELECTED_PEERS == 6); Config c2; - toLoad = R"rippleConfig( + toLoad = R"xrpldConfig( [reduce_relay] vp_base_squelch_max_selected_peers=2 -)rippleConfig"; +)xrpldConfig"; std::string error; auto const expectedError = @@ -1389,7 +1405,7 @@ vp_base_squelch_max_selected_peers=2 { c2.loadFromString(toLoad); } - catch (std::runtime_error& e) + catch (std::runtime_error const& e) { error = e.what(); } @@ -1463,9 +1479,7 @@ vp_base_squelch_max_selected_peers=2 struct Handler : public reduce_relay::SquelchHandler { - Handler() - { - } + Handler() = default; void squelch(PublicKey const&, Peer::id_t, std::uint32_t duration) const override { @@ -1657,6 +1671,4 @@ class reduce_relay_simulate_test : public reduce_relay_test BEAST_DEFINE_TESTSUITE(reduce_relay, overlay, xrpl); BEAST_DEFINE_TESTSUITE_MANUAL(reduce_relay_simulate, overlay, xrpl); -} // namespace test - -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/overlay/short_read_test.cpp b/src/test/overlay/short_read_test.cpp index 0c8b7f8a43..5d145fb6b1 100644 --- a/src/test/overlay/short_read_test.cpp +++ b/src/test/overlay/short_read_test.cpp @@ -2,21 +2,39 @@ #include #include -#include +#include +#include #include #include +#include +#include +#include +#include #include +#include #include -#include +#include +#include #include #include -#include +#include +#include +#include +#include +#include #include +#include #include +#include +#include +#include +#include +#include #include #include +#include namespace xrpl { /* @@ -627,7 +645,7 @@ public: { } - ~short_read_test() + ~short_read_test() override { work_.reset(); thread_.join(); diff --git a/src/test/overlay/traffic_count_test.cpp b/src/test/overlay/traffic_count_test.cpp index 5e6e4e685c..b5cb73c65d 100644 --- a/src/test/overlay/traffic_count_test.cpp +++ b/src/test/overlay/traffic_count_test.cpp @@ -1,12 +1,13 @@ -#include #include -#include -#include +#include -namespace xrpl { +#include -namespace test { +#include +#include + +namespace xrpl::test { class traffic_count_test : public beast::unit_test::suite { @@ -50,13 +51,13 @@ public: TrafficCount m_traffic; auto const counts = m_traffic.getCounts(); - std::for_each(counts.begin(), counts.end(), [&](auto const& pair) { + std::ranges::for_each(counts, [&](auto const& pair) { for (auto i = 0; i < tc.messageCount; ++i) m_traffic.addCount(pair.first, tc.inbound, tc.size); }); auto const counts_new = m_traffic.getCounts(); - std::for_each(counts_new.begin(), counts_new.end(), [&](auto const& pair) { + std::ranges::for_each(counts_new, [&](auto const& pair) { BEAST_EXPECT(pair.second.bytesIn.load() == tc.expectedBytesIn); BEAST_EXPECT(pair.second.bytesOut.load() == tc.expectedBytesOut); BEAST_EXPECT(pair.second.messagesIn.load() == tc.expectedMessagesIn); @@ -125,5 +126,4 @@ public: BEAST_DEFINE_TESTSUITE(traffic_count, overlay, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/overlay/tx_reduce_relay_test.cpp b/src/test/overlay/tx_reduce_relay_test.cpp index abb1632858..bbb31fd38b 100644 --- a/src/test/overlay/tx_reduce_relay_test.cpp +++ b/src/test/overlay/tx_reduce_relay_test.cpp @@ -1,16 +1,49 @@ -#include #include +#include +#include +#include +#include +#include +#include #include #include -#include +#include +#include +#include #include -#include +#include +#include +#include +#include +#include +#include +#include +#include -namespace xrpl { +#include +#include +#include +#include +#include +#include +#include -namespace test { +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl::test { class tx_reduce_relay_test : public beast::unit_test::suite { @@ -108,7 +141,7 @@ private: { sid_++; } - ~PeerTest() = default; + ~PeerTest() override = default; void run() override @@ -264,5 +297,4 @@ private: }; BEAST_DEFINE_TESTSUITE(tx_reduce_relay, overlay, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/peerfinder/Livecache_test.cpp b/src/test/peerfinder/Livecache_test.cpp index d12da84ffd..561b17f71c 100644 --- a/src/test/peerfinder/Livecache_test.cpp +++ b/src/test/peerfinder/Livecache_test.cpp @@ -1,16 +1,28 @@ #include #include +#include #include +#include #include -#include -#include +#include +#include +#include -#include +#include +#include +#include +#include -namespace xrpl { -namespace PeerFinder { +#include +#include +#include +#include +#include +#include + +namespace xrpl::PeerFinder { bool operator==(Endpoint const& a, Endpoint const& b) @@ -199,5 +211,4 @@ public: BEAST_DEFINE_TESTSUITE(Livecache, peerfinder, xrpl); -} // namespace PeerFinder -} // namespace xrpl +} // namespace xrpl::PeerFinder diff --git a/src/test/peerfinder/PeerFinder_test.cpp b/src/test/peerfinder/PeerFinder_test.cpp index a787a38e14..0e37c7baf4 100644 --- a/src/test/peerfinder/PeerFinder_test.cpp +++ b/src/test/peerfinder/PeerFinder_test.cpp @@ -2,15 +2,28 @@ #include #include +#include #include +#include #include +#include #include +#include #include #include -namespace xrpl { -namespace PeerFinder { +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl::PeerFinder { class PeerFinder_test : public beast::unit_test::suite { @@ -469,32 +482,32 @@ public: pass(); } }; - run(R"rippleConfig( + run(R"xrpldConfig( [peers_in_max] 100 -)rippleConfig"); - run(R"rippleConfig( +)xrpldConfig"); + run(R"xrpldConfig( [peers_out_max] 100 -)rippleConfig"); - run(R"rippleConfig( +)xrpldConfig"); + run(R"xrpldConfig( [peers_in_max] 100 [peers_out_max] 5 -)rippleConfig"); - run(R"rippleConfig( +)xrpldConfig"); + run(R"xrpldConfig( [peers_in_max] 1001 [peers_out_max] 10 -)rippleConfig"); - run(R"rippleConfig( +)xrpldConfig"); + run(R"xrpldConfig( [peers_in_max] 10 [peers_out_max] 1001 -)rippleConfig"); +)xrpldConfig"); } void @@ -516,5 +529,4 @@ public: BEAST_DEFINE_TESTSUITE(PeerFinder, peerfinder, xrpl); -} // namespace PeerFinder -} // namespace xrpl +} // namespace xrpl::PeerFinder diff --git a/src/test/protocol/ApiVersion_test.cpp b/src/test/protocol/ApiVersion_test.cpp index 0b7babc764..569aed5fa2 100644 --- a/src/test/protocol/ApiVersion_test.cpp +++ b/src/test/protocol/ApiVersion_test.cpp @@ -1,16 +1,7 @@ -#include #include #include -#include -#include -#include -#include -#include -#include - -namespace xrpl { -namespace test { +namespace xrpl::test { struct ApiVersion_test : beast::unit_test::suite { void @@ -47,5 +38,4 @@ struct ApiVersion_test : beast::unit_test::suite BEAST_DEFINE_TESTSUITE(ApiVersion, protocol, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/protocol/BuildInfo_test.cpp b/src/test/protocol/BuildInfo_test.cpp index d9810e93ea..d157978744 100644 --- a/src/test/protocol/BuildInfo_test.cpp +++ b/src/test/protocol/BuildInfo_test.cpp @@ -1,4 +1,4 @@ -#include +#include #include namespace xrpl { @@ -53,13 +53,13 @@ public: } void - testIsRippledVersion() + testIsXrpldVersion() { - testcase("IsRippledVersion"); + testcase("IsXrpldVersion"); auto vFF = 0xFFFF'FFFF'FFFF'FFFFLLU; - BEAST_EXPECT(!BuildInfo::isRippledVersion(vFF)); - auto vRippled = 0x183B'0000'0000'0000LLU; - BEAST_EXPECT(BuildInfo::isRippledVersion(vRippled)); + BEAST_EXPECT(!BuildInfo::isXrpldVersion(vFF)); + auto vXrpld = 0x183B'0000'0000'0000LLU; + BEAST_EXPECT(BuildInfo::isXrpldVersion(vXrpld)); } void @@ -83,7 +83,7 @@ public: run() override { testEncodeSoftwareVersion(); - testIsRippledVersion(); + testIsXrpldVersion(); testIsNewerVersion(); } }; diff --git a/src/test/protocol/Hooks_test.cpp b/src/test/protocol/Hooks_test.cpp index 53f20a2b5e..c6254d16bb 100644 --- a/src/test/protocol/Hooks_test.cpp +++ b/src/test/protocol/Hooks_test.cpp @@ -1,7 +1,15 @@ -#include -#include +#include // IWYU pragma: keep + +#include +#include +#include +#include +#include +#include + +#include #include #include @@ -11,7 +19,7 @@ class Hooks_test : public beast::unit_test::suite { /** * This unit test was requested here: - * https://github.com/ripple/rippled/pull/4089#issuecomment-1050274539 + * https://github.com/XRPLF/rippled/pull/4089#issuecomment-1050274539 * These are tests that exercise facilities that are reserved for when Hooks * is merged in the future. **/ diff --git a/src/test/protocol/InnerObjectFormats_test.cpp b/src/test/protocol/InnerObjectFormats_test.cpp index 2961e90db7..8a4d527db3 100644 --- a/src/test/protocol/InnerObjectFormats_test.cpp +++ b/src/test/protocol/InnerObjectFormats_test.cpp @@ -1,11 +1,16 @@ -#include + +#include #include -#include -#include // Json::Reader +#include +#include // Json::Reader +#include #include // RPC::containsError #include // STParsedJSONObject +#include +#include + namespace xrpl { namespace InnerObjectFormatsUnitTestDetail { @@ -19,7 +24,7 @@ struct TestJSONTxt static TestJSONTxt const testArray[] = { // Valid SignerEntry - {R"({ + {.txt = R"({ "Account" : "rDg53Haik2475DJx8bjMDSDPj4VX7htaMd", "SignerEntries" : [ @@ -41,10 +46,10 @@ static TestJSONTxt const testArray[] = { "SignerQuorum" : 7, "TransactionType" : "SignerListSet" })", - false}, + .expectFail = false}, // SignerEntry missing Account - {R"({ + {.txt = R"({ "Account" : "rDg53Haik2475DJx8bjMDSDPj4VX7htaMd", "SignerEntries" : [ @@ -65,10 +70,10 @@ static TestJSONTxt const testArray[] = { "SignerQuorum" : 7, "TransactionType" : "SignerListSet" })", - true}, + .expectFail = true}, // SignerEntry missing SignerWeight - {R"({ + {.txt = R"({ "Account" : "rDg53Haik2475DJx8bjMDSDPj4VX7htaMd", "SignerEntries" : [ @@ -89,10 +94,10 @@ static TestJSONTxt const testArray[] = { "SignerQuorum" : 7, "TransactionType" : "SignerListSet" })", - true}, + .expectFail = true}, // SignerEntry with unexpected Amount - {R"({ + {.txt = R"({ "Account" : "rDg53Haik2475DJx8bjMDSDPj4VX7htaMd", "SignerEntries" : [ @@ -115,10 +120,10 @@ static TestJSONTxt const testArray[] = { "SignerQuorum" : 7, "TransactionType" : "SignerListSet" })", - true}, + .expectFail = true}, // SignerEntry with no Account and unexpected Amount - {R"({ + {.txt = R"({ "Account" : "rDg53Haik2475DJx8bjMDSDPj4VX7htaMd", "SignerEntries" : [ @@ -140,7 +145,7 @@ static TestJSONTxt const testArray[] = { "SignerQuorum" : 7, "TransactionType" : "SignerListSet" })", - true}, + .expectFail = true}, }; diff --git a/src/test/protocol/Issue_test.cpp b/src/test/protocol/Issue_test.cpp index eddaf1c6d8..116ac7ca27 100644 --- a/src/test/protocol/Issue_test.cpp +++ b/src/test/protocol/Issue_test.cpp @@ -1,15 +1,16 @@ #include -#include +#include +#include +#include #include #include +#include -#include - +#include #include #include #include -#include -#include +#include #if BEAST_MSVC #define STL_SET_HAS_EMPLACE 1 diff --git a/src/test/protocol/Memo_test.cpp b/src/test/protocol/Memo_test.cpp index 9a0fd7d94c..fa6d7905a4 100644 --- a/src/test/protocol/Memo_test.cpp +++ b/src/test/protocol/Memo_test.cpp @@ -1,6 +1,17 @@ -#include + +#include +#include +#include +#include +#include +#include +#include #include +#include +#include + +#include namespace xrpl { @@ -13,7 +24,7 @@ public: testcase("Test memos"); using namespace test::jtx; - Account alice{"alice"}; + Account const alice{"alice"}; Env env(*this); env.fund(XRP(10000), alice); diff --git a/src/test/protocol/MultiApiJson_test.cpp b/src/test/protocol/MultiApiJson_test.cpp index 5aafa19771..f0cbf805f3 100644 --- a/src/test/protocol/MultiApiJson_test.cpp +++ b/src/test/protocol/MultiApiJson_test.cpp @@ -1,14 +1,16 @@ -#include +#include +#include +#include #include -#include +#include +#include #include #include #include #include -namespace xrpl { -namespace test { +namespace xrpl::test { namespace { @@ -917,5 +919,4 @@ struct MultiApiJson_test : beast::unit_test::suite BEAST_DEFINE_TESTSUITE(MultiApiJson, protocol, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/protocol/PublicKey_test.cpp b/src/test/protocol/PublicKey_test.cpp index 79ab589404..cc5658d1c1 100644 --- a/src/test/protocol/PublicKey_test.cpp +++ b/src/test/protocol/PublicKey_test.cpp @@ -1,7 +1,17 @@ -#include +#include +#include +#include #include #include +#include +#include +#include +#include +#include +#include +#include +#include #include namespace xrpl { diff --git a/src/test/protocol/Quality_test.cpp b/src/test/protocol/Quality_test.cpp index f421f98c94..09308c71a8 100644 --- a/src/test/protocol/Quality_test.cpp +++ b/src/test/protocol/Quality_test.cpp @@ -1,6 +1,12 @@ -#include +#include +#include +#include +#include #include +#include +#include +#include #include namespace xrpl { @@ -16,17 +22,17 @@ public: template static STAmount - amount(Integer integer, std::enable_if_t::value>* = 0) + amount(Integer integer, std::enable_if_t>* = 0) { - static_assert(std::is_integral::value, ""); + static_assert(std::is_integral_v, ""); return STAmount(integer, false); } template static STAmount - amount(Integer integer, std::enable_if_t::value>* = 0) + amount(Integer integer, std::enable_if_t>* = 0) { - static_assert(std::is_integral::value, ""); + static_assert(std::is_integral_v, ""); if (integer < 0) return STAmount(-integer, true); return STAmount(integer, false); diff --git a/src/test/protocol/STAccount_test.cpp b/src/test/protocol/STAccount_test.cpp index 3b12605a92..eb356ecb8d 100644 --- a/src/test/protocol/STAccount_test.cpp +++ b/src/test/protocol/STAccount_test.cpp @@ -1,5 +1,12 @@ -#include +#include +#include +#include +#include #include +#include + +#include +#include namespace xrpl { diff --git a/src/test/protocol/STAmount_test.cpp b/src/test/protocol/STAmount_test.cpp index 2c2e005146..78ef9c2a8a 100644 --- a/src/test/protocol/STAmount_test.cpp +++ b/src/test/protocol/STAmount_test.cpp @@ -1,10 +1,30 @@ -#include +#include #include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include + namespace xrpl { class STAmount_test : public beast::unit_test::suite @@ -35,10 +55,10 @@ public: mantissa--; if (mantissa < STAmount::cMinValue) - return {amount.issue(), mantissa, amount.exponent(), amount.negative()}; + return {amount.asset(), mantissa, amount.exponent(), amount.negative()}; return { - amount.issue(), + amount.asset(), mantissa, amount.exponent(), amount.negative(), @@ -50,10 +70,10 @@ public: mantissa++; if (mantissa > STAmount::cMaxValue) - return {amount.issue(), mantissa, amount.exponent(), amount.negative()}; + return {amount.asset(), mantissa, amount.exponent(), amount.negative()}; return { - amount.issue(), + amount.asset(), mantissa, amount.exponent(), amount.negative(), @@ -79,7 +99,7 @@ public: BEAST_EXPECT(!cmp.native()); - BEAST_EXPECT(cmp.issue().currency == res.issue().currency); + BEAST_EXPECT(cmp.get().currency == res.get().currency); if (res != cmp) { diff --git a/src/test/protocol/STInteger_test.cpp b/src/test/protocol/STInteger_test.cpp index 4a0204d349..ccb3e726da 100644 --- a/src/test/protocol/STInteger_test.cpp +++ b/src/test/protocol/STInteger_test.cpp @@ -1,6 +1,7 @@ -#include +#include #include #include +#include #include #include diff --git a/src/test/protocol/STIssue_test.cpp b/src/test/protocol/STIssue_test.cpp index 2ffdb1d9af..41b8e27d7b 100644 --- a/src/test/protocol/STIssue_test.cpp +++ b/src/test/protocol/STIssue_test.cpp @@ -1,10 +1,17 @@ -#include -#include +#include +#include // IWYU pragma: keep + +#include +#include +#include +#include +#include #include +#include +#include -namespace xrpl { -namespace test { +namespace xrpl::test { class STIssue_test : public beast::unit_test::suite { @@ -141,5 +148,4 @@ public: BEAST_DEFINE_TESTSUITE(STIssue, protocol, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/protocol/STNumber_test.cpp b/src/test/protocol/STNumber_test.cpp index 3b794b23b5..156f5b42aa 100644 --- a/src/test/protocol/STNumber_test.cpp +++ b/src/test/protocol/STNumber_test.cpp @@ -1,14 +1,20 @@ -#include +#include #include #include +#include #include #include #include #include +#include +#include +#include #include -#include #include +#include +#include +#include namespace xrpl { @@ -58,7 +64,7 @@ struct STNumber_test : public beast::unit_test::suite STNumber const factor{sfNumber, 100}; auto const iouValue = strikePrice.iou(); IOUAmount const totalValue{iouValue * factor}; - STAmount const totalAmount{totalValue, strikePrice.issue()}; + STAmount const totalAmount{totalValue, strikePrice.get()}; BEAST_EXPECT(totalAmount == Number{10'000}); } diff --git a/src/test/protocol/STObject_test.cpp b/src/test/protocol/STObject_test.cpp index 135c577fb4..d76446599c 100644 --- a/src/test/protocol/STObject_test.cpp +++ b/src/test/protocol/STObject_test.cpp @@ -1,4 +1,30 @@ -#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace xrpl { @@ -330,7 +356,7 @@ public: STObject st(sfGeneric); auto const v = ~st[~sf1Outer]; static_assert( - std::is_same, std::optional>::value, ""); + std::is_same_v, std::optional>, ""); } // UDT scalar fields @@ -405,7 +431,7 @@ public: BEAST_EXPECT(cst[sf][0] == 1); BEAST_EXPECT(cst[sf][1] == 2); static_assert( - std::is_same const&>::value, ""); + std::is_same_v const&>, ""); } // Default by reference field diff --git a/src/test/protocol/STParsedJSON_test.cpp b/src/test/protocol/STParsedJSON_test.cpp index 1c86a90348..75d3d74ddd 100644 --- a/src/test/protocol/STParsedJSON_test.cpp +++ b/src/test/protocol/STParsedJSON_test.cpp @@ -1,11 +1,27 @@ -#include -#include +#include + +#include +#include +#include #include +#include +#include +#include +#include +#include #include #include #include -#include +#include +#include + +#include +#include +#include +#include +#include +#include namespace xrpl { @@ -403,8 +419,7 @@ class STParsedJSON_test : public beast::unit_test::suite // NOLINTNEXTLINE(bugprone-unchecked-optional-access) auto const& h128 = obj.object->getFieldH128(sfEmailHash); BEAST_EXPECT(h128.size() == 16); - bool const allZero = - std::all_of(h128.begin(), h128.end(), [](auto b) { return b == 0; }); + bool const allZero = std::ranges::all_of(h128, [](auto b) { return b == 0; }); BEAST_EXPECT(allZero); } @@ -499,8 +514,7 @@ class STParsedJSON_test : public beast::unit_test::suite // NOLINTNEXTLINE(bugprone-unchecked-optional-access) auto const& h160 = obj.object->getFieldH160(sfTakerPaysCurrency); BEAST_EXPECT(h160.size() == 20); - bool const allZero = - std::all_of(h160.begin(), h160.end(), [](auto b) { return b == 0; }); + bool const allZero = std::ranges::all_of(h160, [](auto b) { return b == 0; }); BEAST_EXPECT(allZero); } @@ -588,8 +602,7 @@ class STParsedJSON_test : public beast::unit_test::suite // NOLINTNEXTLINE(bugprone-unchecked-optional-access) auto const& h192 = obj.object->getFieldH192(sfMPTokenIssuanceID); BEAST_EXPECT(h192.size() == 24); - bool const allZero = - std::all_of(h192.begin(), h192.end(), [](auto b) { return b == 0; }); + bool const allZero = std::ranges::all_of(h192, [](auto b) { return b == 0; }); BEAST_EXPECT(allZero); } @@ -690,8 +703,7 @@ class STParsedJSON_test : public beast::unit_test::suite // NOLINTNEXTLINE(bugprone-unchecked-optional-access) auto const& h256 = obj.object->getFieldH256(sfLedgerHash); BEAST_EXPECT(h256.size() == 32); - bool const allZero = - std::all_of(h256.begin(), h256.end(), [](auto b) { return b == 0; }); + bool const allZero = std::ranges::all_of(h256, [](auto b) { return b == 0; }); BEAST_EXPECT(allZero); } @@ -2130,10 +2142,10 @@ class STParsedJSON_test : public beast::unit_test::suite STParsedJSONObject const parsed("test", faultyJson); BEAST_EXPECT(!parsed.object); } - catch (std::runtime_error& e) + catch (std::runtime_error const& e) { std::string const what(e.what()); - unexpected(what.find("First level children of `Template`") != 0); + unexpected(!what.starts_with("First level children of `Template`")); } } } diff --git a/src/test/protocol/STTx_test.cpp b/src/test/protocol/STTx_test.cpp index 0804c89bd4..70074ed496 100644 --- a/src/test/protocol/STTx_test.cpp +++ b/src/test/protocol/STTx_test.cpp @@ -1,15 +1,34 @@ #include -#include -#include +#include +#include +#include +#include // IWYU pragma: keep +#include +#include +#include #include +#include +#include #include +#include +#include #include #include +#include +#include #include #include -#include +#include + +#include +#include +#include +#include #include +#include +#include +#include namespace xrpl { diff --git a/src/test/protocol/STValidation_test.cpp b/src/test/protocol/STValidation_test.cpp index e426af3e44..779e4d4c55 100644 --- a/src/test/protocol/STValidation_test.cpp +++ b/src/test/protocol/STValidation_test.cpp @@ -1,12 +1,21 @@ -#include +#include #include -#include +#include #include -#include +#include // IWYU pragma: keep +#include +#include +#include +#include +#include +#include +#include +#include #include -#include +#include +#include namespace xrpl { diff --git a/src/test/protocol/SecretKey_test.cpp b/src/test/protocol/SecretKey_test.cpp index 9072f5c0d9..7e71775988 100644 --- a/src/test/protocol/SecretKey_test.cpp +++ b/src/test/protocol/SecretKey_test.cpp @@ -1,13 +1,22 @@ #include -#include +#include +#include +#include #include #include +#include +#include #include #include #include +#include #include +#include +#include +#include +#include #include #include @@ -158,8 +167,7 @@ public: // swaps the smallest and largest elements in buffer std::iter_swap( - std::min_element(badData.begin(), badData.end()), - std::max_element(badData.begin(), badData.end())); + std::ranges::min_element(badData), std::ranges::max_element(badData)); // Wrong data: should fail BEAST_EXPECT(!verify(pk, makeSlice(badData), sig)); @@ -345,1149 +353,1149 @@ public: private: // clang-format off inline static TestKeyData const secp256k1TestVectors[] = { - {{0xDE,0xDC,0xE9,0xCE,0x67,0xB4,0x51,0xD8,0x52,0xFD,0x4E,0x84,0x6F,0xCD,0xE3,0x1C}, - {0x03,0x30,0xE7,0xFC,0x9D,0x56,0xBB,0x25,0xD6,0x89,0x3B,0xA3,0xF3,0x17,0xAE,0x5B, + {.seed={0xDE,0xDC,0xE9,0xCE,0x67,0xB4,0x51,0xD8,0x52,0xFD,0x4E,0x84,0x6F,0xCD,0xE3,0x1C}, + .pubkey={0x03,0x30,0xE7,0xFC,0x9D,0x56,0xBB,0x25,0xD6,0x89,0x3B,0xA3,0xF3,0x17,0xAE,0x5B, 0xCF,0x33,0xB3,0x29,0x1B,0xD6,0x3D,0xB3,0x26,0x54,0xA3,0x13,0x22,0x2F,0x7F,0xD0,0x20}, - {0x1A,0xCA,0xAE,0xDE,0xCE,0x40,0x5B,0x2A,0x95,0x82,0x12,0x62,0x9E,0x16,0xF2,0xEB, + .seckey={0x1A,0xCA,0xAE,0xDE,0xCE,0x40,0x5B,0x2A,0x95,0x82,0x12,0x62,0x9E,0x16,0xF2,0xEB, 0x46,0xB1,0x53,0xEE,0xE9,0x4C,0xDD,0x35,0x0F,0xDE,0xFF,0x52,0x79,0x55,0x25,0xB7}, - "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh"}, - {{0xF7,0x5C,0x48,0xFE,0xC4,0x6D,0x4D,0x64,0x92,0x8B,0x79,0x5F,0x3F,0xBA,0xBB,0xA0}, - {0x03,0xAF,0x53,0xE8,0x01,0x1E,0x85,0xB3,0x66,0x64,0xF1,0x71,0x08,0x90,0x50,0x1C, + .addr="rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh"}, + {.seed={0xF7,0x5C,0x48,0xFE,0xC4,0x6D,0x4D,0x64,0x92,0x8B,0x79,0x5F,0x3F,0xBA,0xBB,0xA0}, + .pubkey={0x03,0xAF,0x53,0xE8,0x01,0x1E,0x85,0xB3,0x66,0x64,0xF1,0x71,0x08,0x90,0x50,0x1C, 0x3E,0x86,0xFC,0x2C,0x66,0x58,0xC2,0xEE,0x83,0xCA,0x58,0x0D,0xC9,0x97,0x25,0x41,0xB1}, - {0x5B,0x8A,0xB0,0xE7,0xCD,0xAF,0x48,0x87,0x4D,0x5D,0x99,0x34,0xBF,0x3E,0x7B,0x2C, + .seckey={0x5B,0x8A,0xB0,0xE7,0xCD,0xAF,0x48,0x87,0x4D,0x5D,0x99,0x34,0xBF,0x3E,0x7B,0x2C, 0xB0,0x6B,0xC4,0xC7,0xEA,0xAA,0xF7,0x62,0x68,0x2E,0xD8,0xD0,0xA3,0x1E,0x3C,0x70}, - "r9ZERztesFu3ZBs7zsWTeCvBg14GQ9zWF7"}, - {{0x7A,0xEA,0x88,0xC1,0x48,0xA8,0xC4,0xA8,0x90,0x69,0xF9,0x8A,0x37,0x33,0x16,0x7B}, - {0x03,0x7A,0xD9,0x30,0xB3,0x1B,0x0F,0x81,0x03,0x6D,0x65,0xA6,0x2D,0x4E,0x11,0x31, + .addr="r9ZERztesFu3ZBs7zsWTeCvBg14GQ9zWF7"}, + {.seed={0x7A,0xEA,0x88,0xC1,0x48,0xA8,0xC4,0xA8,0x90,0x69,0xF9,0x8A,0x37,0x33,0x16,0x7B}, + .pubkey={0x03,0x7A,0xD9,0x30,0xB3,0x1B,0x0F,0x81,0x03,0x6D,0x65,0xA6,0x2D,0x4E,0x11,0x31, 0xEF,0x60,0x5F,0x73,0x8E,0x7D,0x7F,0x95,0x46,0x5B,0xBE,0xCB,0xCB,0xFF,0xA7,0x07,0x6E}, - {0x3D,0xDC,0x5F,0xE1,0x63,0x78,0xAE,0x85,0x03,0xE9,0x74,0x23,0x30,0x17,0x79,0x1C, + .seckey={0x3D,0xDC,0x5F,0xE1,0x63,0x78,0xAE,0x85,0x03,0xE9,0x74,0x23,0x30,0x17,0x79,0x1C, 0xC0,0x7C,0x10,0x77,0x65,0x3A,0xB7,0x23,0xC8,0xF6,0x5B,0x17,0xB9,0x98,0x0D,0xA1}, - "rfmZ1iAuSzB2gxjoJgBub6iJPCk92D7YfC"}, - {{0xE4,0x24,0x9B,0xFF,0x02,0xB7,0x66,0x91,0x99,0xA2,0x3F,0xAF,0x48,0x8C,0x17,0x9E}, - {0x02,0xEF,0x88,0x8E,0x93,0xB9,0xA8,0x50,0xFC,0x93,0xCF,0x00,0xF6,0xA9,0x3C,0x3C, + .addr="rfmZ1iAuSzB2gxjoJgBub6iJPCk92D7YfC"}, + {.seed={0xE4,0x24,0x9B,0xFF,0x02,0xB7,0x66,0x91,0x99,0xA2,0x3F,0xAF,0x48,0x8C,0x17,0x9E}, + .pubkey={0x02,0xEF,0x88,0x8E,0x93,0xB9,0xA8,0x50,0xFC,0x93,0xCF,0x00,0xF6,0xA9,0x3C,0x3C, 0xDE,0xF2,0x86,0x0D,0xBF,0x3A,0x4F,0x11,0xCD,0x0A,0xE8,0xE6,0x45,0x4D,0x20,0x95,0xE3}, - {0x84,0xBF,0xD9,0x22,0x68,0xCA,0x02,0x01,0x9C,0xE6,0xD9,0x35,0xEF,0xA9,0x94,0x4B, + .seckey={0x84,0xBF,0xD9,0x22,0x68,0xCA,0x02,0x01,0x9C,0xE6,0xD9,0x35,0xEF,0xA9,0x94,0x4B, 0xAE,0xC3,0x3E,0x23,0x41,0x34,0xDE,0xF4,0xF2,0xA8,0xE7,0x75,0x19,0xF3,0x42,0xBE}, - "rwm4pKGoRDHez72gumG7VZw5Fb9ah7o6K2"}, - {{0x04,0xC7,0x18,0x9B,0x62,0x72,0x94,0x39,0x81,0x6B,0x25,0x9C,0x4E,0x61,0x99,0xE1}, - {0x03,0x3E,0x4F,0xA3,0x7A,0x91,0xFC,0xCD,0x49,0xEF,0x91,0xB8,0x1D,0x34,0x1F,0x27, + .addr="rwm4pKGoRDHez72gumG7VZw5Fb9ah7o6K2"}, + {.seed={0x04,0xC7,0x18,0x9B,0x62,0x72,0x94,0x39,0x81,0x6B,0x25,0x9C,0x4E,0x61,0x99,0xE1}, + .pubkey={0x03,0x3E,0x4F,0xA3,0x7A,0x91,0xFC,0xCD,0x49,0xEF,0x91,0xB8,0x1D,0x34,0x1F,0x27, 0x24,0x1A,0x35,0xE9,0x13,0x05,0x55,0x33,0xE3,0x0C,0xA0,0x4D,0xA4,0x12,0x10,0x8F,0xCA}, - {0xA6,0x74,0x67,0x08,0x1E,0x99,0x33,0xC0,0x74,0x63,0x5C,0xEB,0x81,0x92,0x51,0xA5, + .seckey={0xA6,0x74,0x67,0x08,0x1E,0x99,0x33,0xC0,0x74,0x63,0x5C,0xEB,0x81,0x92,0x51,0xA5, 0x02,0x5E,0xD3,0x6E,0x88,0x87,0xDC,0xC1,0xD9,0xEB,0x8F,0x5B,0x52,0x08,0x74,0x81}, - "rHsP2baL7CdEjfJ89AYoWuocKBiPSikBnm"}, - {{0x3E,0x94,0x83,0x6F,0xA5,0x42,0x87,0xA9,0x5D,0xAD,0xB1,0xDF,0xAF,0x03,0xB4,0x28}, - {0x03,0x6F,0x9B,0xB1,0x59,0xB4,0x72,0x58,0x7E,0xCC,0xD2,0x24,0x35,0xC2,0xA8,0xB8, + .addr="rHsP2baL7CdEjfJ89AYoWuocKBiPSikBnm"}, + {.seed={0x3E,0x94,0x83,0x6F,0xA5,0x42,0x87,0xA9,0x5D,0xAD,0xB1,0xDF,0xAF,0x03,0xB4,0x28}, + .pubkey={0x03,0x6F,0x9B,0xB1,0x59,0xB4,0x72,0x58,0x7E,0xCC,0xD2,0x24,0x35,0xC2,0xA8,0xB8, 0xC4,0xF5,0xF5,0x13,0x33,0xFA,0xAE,0xC5,0xA8,0x35,0xAE,0x58,0x34,0x68,0x97,0x8B,0x4B}, - {0xD1,0xB4,0xE7,0xFE,0x29,0x19,0xF9,0x7E,0x41,0x3F,0x41,0x74,0x4D,0x29,0x1B,0x46, + .seckey={0xD1,0xB4,0xE7,0xFE,0x29,0x19,0xF9,0x7E,0x41,0x3F,0x41,0x74,0x4D,0x29,0x1B,0x46, 0x77,0xD0,0x32,0xC1,0x52,0x61,0xAC,0x94,0x2A,0x9E,0x93,0x03,0x06,0x52,0x7A,0xA1}, - "rpuqfwe4b4vP4ndmUBkgVXwyquPYAtGFku"}, - {{0x82,0xCB,0xB5,0x75,0xC7,0x39,0xC6,0xCC,0x29,0x9E,0xC0,0x1C,0x21,0x21,0xB6,0x10}, - {0x03,0x58,0x8B,0x56,0x6F,0x57,0x3A,0xC8,0x30,0xE5,0xEC,0xE4,0x03,0x08,0x59,0x52, + .addr="rpuqfwe4b4vP4ndmUBkgVXwyquPYAtGFku"}, + {.seed={0x82,0xCB,0xB5,0x75,0xC7,0x39,0xC6,0xCC,0x29,0x9E,0xC0,0x1C,0x21,0x21,0xB6,0x10}, + .pubkey={0x03,0x58,0x8B,0x56,0x6F,0x57,0x3A,0xC8,0x30,0xE5,0xEC,0xE4,0x03,0x08,0x59,0x52, 0x3E,0x44,0x7A,0x5A,0xCA,0x95,0x51,0xB7,0xA1,0x18,0xB9,0x1F,0x37,0x24,0xD2,0x40,0x83}, - {0x79,0x63,0x69,0x53,0xF7,0x64,0x9B,0x82,0x39,0x86,0x31,0xCC,0x47,0xC9,0x7B,0xD8, + .seckey={0x79,0x63,0x69,0x53,0xF7,0x64,0x9B,0x82,0x39,0x86,0x31,0xCC,0x47,0xC9,0x7B,0xD8, 0xA8,0x84,0x74,0x70,0xB7,0xF7,0xFA,0x0C,0x48,0x47,0x73,0xF1,0x74,0xB7,0xA2,0x47}, - "rp45fL94nhjferVtQWa9PkyydFQK6aZbEy"}, - {{0x8A,0x91,0x69,0x2D,0x45,0x00,0x23,0xC4,0x65,0x0C,0xC2,0x18,0x5D,0xCF,0x0F,0x68}, - {0x02,0x09,0x65,0xCE,0x5C,0x65,0x51,0x0C,0xC6,0xD7,0xAA,0x64,0x98,0xFD,0x0C,0xE1, + .addr="rp45fL94nhjferVtQWa9PkyydFQK6aZbEy"}, + {.seed={0x8A,0x91,0x69,0x2D,0x45,0x00,0x23,0xC4,0x65,0x0C,0xC2,0x18,0x5D,0xCF,0x0F,0x68}, + .pubkey={0x02,0x09,0x65,0xCE,0x5C,0x65,0x51,0x0C,0xC6,0xD7,0xAA,0x64,0x98,0xFD,0x0C,0xE1, 0xD8,0xE9,0xAD,0xCD,0x72,0x00,0x12,0xA5,0x1D,0x36,0x64,0x94,0xB1,0x97,0xF6,0xB9,0x9E}, - {0xE3,0x69,0x28,0xEE,0x12,0x60,0x08,0x8D,0x47,0x90,0xB3,0x99,0x25,0x47,0x79,0xF7, + .seckey={0xE3,0x69,0x28,0xEE,0x12,0x60,0x08,0x8D,0x47,0x90,0xB3,0x99,0x25,0x47,0x79,0xF7, 0x9B,0xDD,0x48,0xA9,0xEC,0xAD,0xC1,0x3B,0xF4,0x59,0x7B,0x69,0xAD,0xE1,0x6F,0x3E}, - "rfZCLUjvKSNmg5xMufb6fgq9VfP5biBDfU"}, - {{0x9F,0x65,0x49,0xF5,0x89,0xA4,0x72,0x3C,0xD9,0x46,0x77,0xE2,0xCF,0xCD,0x6E,0xB9}, - {0x03,0x95,0xE9,0x7A,0x55,0xCE,0x55,0x2E,0xC0,0xE3,0xC0,0x00,0xF6,0x05,0x53,0x63, + .addr="rfZCLUjvKSNmg5xMufb6fgq9VfP5biBDfU"}, + {.seed={0x9F,0x65,0x49,0xF5,0x89,0xA4,0x72,0x3C,0xD9,0x46,0x77,0xE2,0xCF,0xCD,0x6E,0xB9}, + .pubkey={0x03,0x95,0xE9,0x7A,0x55,0xCE,0x55,0x2E,0xC0,0xE3,0xC0,0x00,0xF6,0x05,0x53,0x63, 0x2A,0x3E,0xEA,0xF9,0x47,0x34,0x64,0x0B,0xAB,0xD1,0x54,0x3A,0xD0,0xA6,0x90,0x05,0xCD}, - {0xB4,0xB6,0xAB,0x76,0x33,0x75,0x98,0xE2,0xBF,0x43,0x07,0x92,0xEF,0x14,0x04,0x36, + .seckey={0xB4,0xB6,0xAB,0x76,0x33,0x75,0x98,0xE2,0xBF,0x43,0x07,0x92,0xEF,0x14,0x04,0x36, 0xE2,0x5C,0x43,0xAD,0xA0,0x6B,0xED,0x8C,0xA1,0xCC,0x80,0x7F,0xEA,0x3B,0xA5,0x26}, - "rpyTz8db86bWHi8E43GGexhRsLDwwMRta3"}, - {{0xCE,0xEC,0xF7,0xAF,0xBF,0x34,0xEF,0xA3,0x0D,0x5C,0xBA,0x33,0xFC,0x5A,0x9A,0x83}, - {0x02,0xD2,0x6D,0xF4,0xAC,0x1F,0x19,0x04,0xD2,0x05,0x85,0xDC,0x21,0x9F,0xB3,0xE4, + .addr="rpyTz8db86bWHi8E43GGexhRsLDwwMRta3"}, + {.seed={0xCE,0xEC,0xF7,0xAF,0xBF,0x34,0xEF,0xA3,0x0D,0x5C,0xBA,0x33,0xFC,0x5A,0x9A,0x83}, + .pubkey={0x02,0xD2,0x6D,0xF4,0xAC,0x1F,0x19,0x04,0xD2,0x05,0x85,0xDC,0x21,0x9F,0xB3,0xE4, 0x35,0xAC,0xAE,0x14,0x0B,0xCF,0x59,0xBE,0x5E,0x91,0xD8,0xDC,0xCD,0xB7,0x41,0xB2,0xF3}, - {0x23,0x89,0x7A,0x4F,0xDD,0xD2,0x40,0xF7,0x3D,0x2F,0xA3,0x26,0xC5,0xBC,0x41,0x06, + .seckey={0x23,0x89,0x7A,0x4F,0xDD,0xD2,0x40,0xF7,0x3D,0x2F,0xA3,0x26,0xC5,0xBC,0x41,0x06, 0xD8,0x22,0xEA,0x2A,0x01,0x1D,0x7E,0x8E,0xDF,0xE8,0xC6,0xFE,0xAA,0x76,0x41,0x41}, - "rfwGiMcsffk9TcwQu1KMYYvkPq4cdHdMik"}, - {{0xFC,0x0D,0x60,0xC9,0xBF,0x88,0x71,0x32,0x49,0x5A,0xD7,0x53,0xA5,0x19,0x97,0xED}, - {0x03,0x90,0x69,0x2D,0x7C,0x32,0x6C,0x90,0xA7,0xF3,0x2B,0xA6,0x71,0xDB,0x1E,0x28, + .addr="rfwGiMcsffk9TcwQu1KMYYvkPq4cdHdMik"}, + {.seed={0xFC,0x0D,0x60,0xC9,0xBF,0x88,0x71,0x32,0x49,0x5A,0xD7,0x53,0xA5,0x19,0x97,0xED}, + .pubkey={0x03,0x90,0x69,0x2D,0x7C,0x32,0x6C,0x90,0xA7,0xF3,0x2B,0xA6,0x71,0xDB,0x1E,0x28, 0x29,0xC9,0xBB,0xA6,0x8B,0x95,0x6E,0x88,0x19,0x13,0x59,0xEE,0x7E,0x85,0x67,0xCC,0x35}, - {0x39,0x43,0x83,0x14,0xC3,0x6E,0x24,0x7B,0x66,0x3F,0x65,0x60,0x5A,0xA5,0xE2,0x0C, + .seckey={0x39,0x43,0x83,0x14,0xC3,0x6E,0x24,0x7B,0x66,0x3F,0x65,0x60,0x5A,0xA5,0xE2,0x0C, 0x71,0xAF,0x8D,0xBD,0x9A,0x04,0xBB,0x1D,0x3B,0x67,0xFC,0x63,0x0A,0x3D,0x74,0xB6}, - "rNYp97qFzBZ5SLfXRtRMwFNxHrGR9cQyGL"}, - {{0x57,0x44,0x63,0xB8,0x56,0x88,0x76,0xC7,0x58,0x2B,0x91,0xC9,0x82,0xF6,0xE4,0x6F}, - {0x03,0x00,0xF4,0x84,0x0C,0xD6,0x00,0xD1,0xC7,0x54,0x5D,0x2D,0xBD,0x1B,0xB7,0x9A, + .addr="rNYp97qFzBZ5SLfXRtRMwFNxHrGR9cQyGL"}, + {.seed={0x57,0x44,0x63,0xB8,0x56,0x88,0x76,0xC7,0x58,0x2B,0x91,0xC9,0x82,0xF6,0xE4,0x6F}, + .pubkey={0x03,0x00,0xF4,0x84,0x0C,0xD6,0x00,0xD1,0xC7,0x54,0x5D,0x2D,0xBD,0x1B,0xB7,0x9A, 0xE3,0x55,0x58,0x67,0x98,0x2E,0x2D,0x38,0x7C,0x3B,0x9C,0xE8,0x1D,0x85,0x76,0x94,0x6E}, - {0x12,0x8F,0xA4,0xE0,0x64,0xA3,0x06,0x90,0x3E,0xC7,0x33,0x44,0xF6,0x10,0xB3,0x8B, + .seckey={0x12,0x8F,0xA4,0xE0,0x64,0xA3,0x06,0x90,0x3E,0xC7,0x33,0x44,0xF6,0x10,0xB3,0x8B, 0xA4,0xDA,0x20,0x97,0x6C,0x46,0x91,0x9B,0xA7,0x81,0x05,0xCA,0x1E,0x8E,0x58,0xE6}, - "r9U2Q5oiKTxp643G8gh6okmToJL6xVhqUn"}, - {{0x6D,0xDD,0x5C,0xC5,0x55,0x90,0x7E,0x56,0x08,0x7D,0x08,0xAA,0xD0,0xCD,0xEB,0x85}, - {0x02,0xA1,0x82,0x14,0xBF,0xF9,0xA9,0x64,0xE3,0x52,0x60,0x5F,0xA3,0x7C,0xB4,0xE4, + .addr="r9U2Q5oiKTxp643G8gh6okmToJL6xVhqUn"}, + {.seed={0x6D,0xDD,0x5C,0xC5,0x55,0x90,0x7E,0x56,0x08,0x7D,0x08,0xAA,0xD0,0xCD,0xEB,0x85}, + .pubkey={0x02,0xA1,0x82,0x14,0xBF,0xF9,0xA9,0x64,0xE3,0x52,0x60,0x5F,0xA3,0x7C,0xB4,0xE4, 0x47,0x08,0xBE,0x9E,0xDF,0x3A,0xC7,0x9E,0x59,0xBC,0x09,0x7B,0x58,0x24,0xC8,0x43,0x78}, - {0x6E,0x46,0x58,0x1D,0x14,0x4D,0xA5,0x0A,0xEB,0xBE,0xD4,0xF5,0x24,0x9C,0x6E,0x7B, + .seckey={0x6E,0x46,0x58,0x1D,0x14,0x4D,0xA5,0x0A,0xEB,0xBE,0xD4,0xF5,0x24,0x9C,0x6E,0x7B, 0x63,0x00,0xA3,0xAB,0xFE,0x5B,0x64,0x23,0xE1,0x9A,0x31,0x82,0x8E,0xAD,0x03,0xFF}, - "raYyxUC2RFpNnoYcpd8u9yRcRUofg1VU1i"}, - {{0x21,0xE4,0xC8,0x8B,0x52,0x28,0x7A,0xF8,0x98,0x5C,0x86,0x20,0xB8,0xD6,0x12,0xA6}, - {0x03,0xBD,0x83,0x65,0x5D,0x79,0xDF,0x33,0xE8,0x47,0x28,0xA1,0xBC,0xD1,0x1E,0x9A, + .addr="raYyxUC2RFpNnoYcpd8u9yRcRUofg1VU1i"}, + {.seed={0x21,0xE4,0xC8,0x8B,0x52,0x28,0x7A,0xF8,0x98,0x5C,0x86,0x20,0xB8,0xD6,0x12,0xA6}, + .pubkey={0x03,0xBD,0x83,0x65,0x5D,0x79,0xDF,0x33,0xE8,0x47,0x28,0xA1,0xBC,0xD1,0x1E,0x9A, 0xD7,0x4E,0x03,0xDF,0x2E,0x77,0x12,0xA0,0x88,0xD1,0x90,0x4A,0x36,0xF1,0x0B,0xA5,0xC1}, - {0xED,0x14,0x67,0x13,0xDD,0x51,0xF0,0xF1,0x7F,0x9A,0x46,0xA3,0x2C,0x5E,0xA9,0x8A, + .seckey={0xED,0x14,0x67,0x13,0xDD,0x51,0xF0,0xF1,0x7F,0x9A,0x46,0xA3,0x2C,0x5E,0xA9,0x8A, 0xC6,0xF5,0xA8,0x1C,0x2F,0x41,0x7F,0x81,0x99,0x9B,0xFF,0x1C,0xBA,0x2A,0xF6,0x77}, - "raTGiNwu2Rrv33y1Ssv7BqhbRiyjPQLze4"}, - {{0x78,0x36,0xB1,0x6B,0x67,0x4D,0x8A,0xF1,0x2D,0xDC,0x09,0xF7,0x4D,0x56,0xAC,0x8A}, - {0x03,0x57,0xEE,0x1D,0x13,0x73,0x48,0xD5,0xA2,0x2C,0x9D,0x1F,0x31,0x25,0x4C,0x19, + .addr="raTGiNwu2Rrv33y1Ssv7BqhbRiyjPQLze4"}, + {.seed={0x78,0x36,0xB1,0x6B,0x67,0x4D,0x8A,0xF1,0x2D,0xDC,0x09,0xF7,0x4D,0x56,0xAC,0x8A}, + .pubkey={0x03,0x57,0xEE,0x1D,0x13,0x73,0x48,0xD5,0xA2,0x2C,0x9D,0x1F,0x31,0x25,0x4C,0x19, 0x5B,0x70,0xBE,0x48,0x08,0xCC,0xF0,0xF1,0xE6,0x39,0x98,0x69,0x81,0x65,0x8E,0xFB,0xEB}, - {0x5B,0x6E,0x55,0x4A,0x2D,0x42,0xDA,0x3E,0x9D,0x23,0x57,0xC2,0x69,0xD0,0x0D,0xDF, + .seckey={0x5B,0x6E,0x55,0x4A,0x2D,0x42,0xDA,0x3E,0x9D,0x23,0x57,0xC2,0x69,0xD0,0x0D,0xDF, 0xA8,0xFC,0x67,0x51,0x4F,0xE7,0xC4,0xE5,0xEB,0x36,0x90,0xF2,0x10,0x62,0xE9,0x02}, - "rJ9UHCYDkUrBrJK6Hr24BraygxKzpVG5LJ"}, - {{0x43,0x84,0x27,0x4F,0x79,0xE4,0xCF,0xBB,0x4B,0x10,0xFA,0x6C,0x1D,0xA9,0xFD,0xBD}, - {0x02,0x59,0xCE,0x86,0xBA,0xB6,0xF5,0x4E,0x6A,0xB8,0x83,0xD0,0x9C,0x05,0x98,0x8D, + .addr="rJ9UHCYDkUrBrJK6Hr24BraygxKzpVG5LJ"}, + {.seed={0x43,0x84,0x27,0x4F,0x79,0xE4,0xCF,0xBB,0x4B,0x10,0xFA,0x6C,0x1D,0xA9,0xFD,0xBD}, + .pubkey={0x02,0x59,0xCE,0x86,0xBA,0xB6,0xF5,0x4E,0x6A,0xB8,0x83,0xD0,0x9C,0x05,0x98,0x8D, 0x9E,0x8A,0x71,0x53,0x88,0x32,0xB5,0x0B,0xD4,0xDE,0x1E,0xFA,0x8B,0x13,0x50,0xF8,0xBC}, - {0x97,0x33,0xA7,0x37,0x8B,0x17,0x85,0x80,0x5B,0x16,0x3A,0x72,0xA0,0x13,0x2E,0xFE, + .seckey={0x97,0x33,0xA7,0x37,0x8B,0x17,0x85,0x80,0x5B,0x16,0x3A,0x72,0xA0,0x13,0x2E,0xFE, 0xF6,0x52,0x57,0x35,0x39,0x5F,0x4F,0xE8,0x3C,0xC0,0x3C,0x61,0x63,0xA3,0xA3,0x86}, - "r37FSSzFWe2FtR6kYfKdPeeZ8i6NZ8uDBU"}, - {{0x90,0x6F,0xF5,0xDF,0xA6,0x06,0xE2,0x6D,0xED,0x61,0xC5,0xFE,0x41,0x6F,0xAA,0x09}, - {0x03,0x71,0x70,0x38,0x45,0x72,0x99,0x33,0xA2,0xAB,0xF8,0x4C,0x51,0x82,0x9E,0x34, + .addr="r37FSSzFWe2FtR6kYfKdPeeZ8i6NZ8uDBU"}, + {.seed={0x90,0x6F,0xF5,0xDF,0xA6,0x06,0xE2,0x6D,0xED,0x61,0xC5,0xFE,0x41,0x6F,0xAA,0x09}, + .pubkey={0x03,0x71,0x70,0x38,0x45,0x72,0x99,0x33,0xA2,0xAB,0xF8,0x4C,0x51,0x82,0x9E,0x34, 0x00,0xA7,0x41,0x0F,0x9E,0x57,0x66,0x5C,0x65,0xD1,0xD6,0x07,0xF2,0xD1,0x67,0x52,0x2F}, - {0x55,0x80,0x14,0xCD,0xC8,0x08,0xB5,0x9A,0x6A,0x32,0x2D,0x61,0xB9,0xEA,0xE1,0x7B, + .seckey={0x55,0x80,0x14,0xCD,0xC8,0x08,0xB5,0x9A,0x6A,0x32,0x2D,0x61,0xB9,0xEA,0xE1,0x7B, 0xDD,0x73,0xCD,0x52,0x4A,0xDA,0x8B,0x7C,0x55,0x2E,0xAE,0x90,0xDB,0x65,0x18,0x9B}, - "rK5rBKf8uDunrTTNY7GWmB3Jnasg2CEuiE"}, - {{0x85,0x3C,0x3B,0xE4,0xD9,0x9F,0xF2,0x03,0x3D,0xF0,0xE2,0x35,0x40,0xA4,0xF3,0x7D}, - {0x03,0x44,0xB4,0x5E,0x37,0xCD,0xD1,0x73,0x88,0xD6,0xE0,0x20,0xED,0x0A,0x52,0x8D, + .addr="rK5rBKf8uDunrTTNY7GWmB3Jnasg2CEuiE"}, + {.seed={0x85,0x3C,0x3B,0xE4,0xD9,0x9F,0xF2,0x03,0x3D,0xF0,0xE2,0x35,0x40,0xA4,0xF3,0x7D}, + .pubkey={0x03,0x44,0xB4,0x5E,0x37,0xCD,0xD1,0x73,0x88,0xD6,0xE0,0x20,0xED,0x0A,0x52,0x8D, 0x94,0x0D,0x43,0x84,0x86,0x36,0x3B,0x5D,0x1D,0x6A,0xB0,0x3C,0x5E,0xDD,0xEC,0x1D,0x1E}, - {0xD4,0x88,0x5F,0x26,0xE9,0xB9,0x8F,0x46,0xAA,0x3C,0xD2,0x26,0x6A,0x57,0x32,0x48, + .seckey={0xD4,0x88,0x5F,0x26,0xE9,0xB9,0x8F,0x46,0xAA,0x3C,0xD2,0x26,0x6A,0x57,0x32,0x48, 0x61,0x03,0xD5,0x7B,0x5F,0x52,0xB3,0x49,0xC1,0xAA,0x4F,0xF6,0xDC,0x63,0xEE,0x28}, - "rB9xKaxuHXDxe3fYQSoTGTBN5LkMsXp7vB"}, - {{0x15,0x6F,0x19,0xDA,0xC4,0x69,0x5A,0x47,0x9C,0x8F,0x2D,0x59,0x90,0xC7,0x7A,0x96}, - {0x03,0x51,0x5A,0xAA,0x83,0x0A,0xDA,0xB9,0xD8,0xA5,0x1C,0x35,0x41,0xD0,0x0A,0xF8, + .addr="rB9xKaxuHXDxe3fYQSoTGTBN5LkMsXp7vB"}, + {.seed={0x15,0x6F,0x19,0xDA,0xC4,0x69,0x5A,0x47,0x9C,0x8F,0x2D,0x59,0x90,0xC7,0x7A,0x96}, + .pubkey={0x03,0x51,0x5A,0xAA,0x83,0x0A,0xDA,0xB9,0xD8,0xA5,0x1C,0x35,0x41,0xD0,0x0A,0xF8, 0x25,0x05,0x15,0x0A,0xD4,0x5B,0x9D,0x0A,0x66,0x79,0x97,0xCE,0x01,0x88,0xE7,0x57,0xA8}, - {0xEE,0x73,0xBC,0xC7,0xEA,0x9E,0x93,0x2D,0xA1,0x7A,0x28,0xD6,0x7E,0x2A,0x85,0x99, + .seckey={0xEE,0x73,0xBC,0xC7,0xEA,0x9E,0x93,0x2D,0xA1,0x7A,0x28,0xD6,0x7E,0x2A,0x85,0x99, 0x97,0x6D,0x18,0x29,0x60,0x1F,0x7D,0x17,0xD8,0x6D,0x68,0x53,0x82,0x2E,0x4D,0xF2}, - "rM83yYQQN6aLjfobwvfYMR2r6xo9bE66ZY"}, - {{0x95,0xCF,0x0F,0x72,0xD7,0xF6,0x87,0x8A,0x76,0xE6,0xF8,0x4E,0x96,0xFD,0x34,0x2B}, - {0x03,0xA7,0x5E,0x58,0x08,0xAB,0x79,0x48,0x34,0xAF,0xBB,0x7B,0x7C,0x14,0x3A,0x48, + .addr="rM83yYQQN6aLjfobwvfYMR2r6xo9bE66ZY"}, + {.seed={0x95,0xCF,0x0F,0x72,0xD7,0xF6,0x87,0x8A,0x76,0xE6,0xF8,0x4E,0x96,0xFD,0x34,0x2B}, + .pubkey={0x03,0xA7,0x5E,0x58,0x08,0xAB,0x79,0x48,0x34,0xAF,0xBB,0x7B,0x7C,0x14,0x3A,0x48, 0x83,0x53,0xF5,0x03,0xB2,0xFC,0x79,0x20,0x05,0x3E,0xDA,0x92,0xE3,0xFB,0xFE,0x53,0xA8}, - {0x43,0x6F,0x3A,0x67,0xC1,0xE4,0x2F,0x61,0x05,0x20,0x46,0x7D,0xB1,0x94,0x2B,0xDB, + .seckey={0x43,0x6F,0x3A,0x67,0xC1,0xE4,0x2F,0x61,0x05,0x20,0x46,0x7D,0xB1,0x94,0x2B,0xDB, 0xE9,0xD2,0xD6,0xE1,0x14,0x13,0xEB,0xE1,0x9B,0x87,0x63,0xBC,0x7C,0xCF,0x27,0x57}, - "rJ3MkKKsTAxzgW87YTZ6T5kauftDAa21hn"}, - {{0x27,0x8E,0x7E,0x47,0x39,0x7D,0xCC,0xF5,0x4B,0xBF,0x52,0x79,0xD8,0x5B,0x60,0x52}, - {0x02,0xA9,0x07,0xF0,0x09,0xFD,0x99,0xB5,0x9E,0x8C,0x90,0x46,0x99,0x59,0xCF,0xA9, + .addr="rJ3MkKKsTAxzgW87YTZ6T5kauftDAa21hn"}, + {.seed={0x27,0x8E,0x7E,0x47,0x39,0x7D,0xCC,0xF5,0x4B,0xBF,0x52,0x79,0xD8,0x5B,0x60,0x52}, + .pubkey={0x02,0xA9,0x07,0xF0,0x09,0xFD,0x99,0xB5,0x9E,0x8C,0x90,0x46,0x99,0x59,0xCF,0xA9, 0xEE,0xB8,0xE6,0x9F,0x66,0x8C,0xE3,0x21,0x15,0xAE,0x48,0x8F,0x37,0xDA,0x4B,0x3D,0xB5}, - {0x71,0xD8,0x4B,0x78,0x69,0x6B,0x9E,0x93,0x97,0xDB,0x07,0xB2,0x3D,0x4C,0x4B,0x6D, + .seckey={0x71,0xD8,0x4B,0x78,0x69,0x6B,0x9E,0x93,0x97,0xDB,0x07,0xB2,0x3D,0x4C,0x4B,0x6D, 0x7C,0x0B,0x87,0x9D,0x18,0xB5,0x45,0x61,0x27,0xF4,0x78,0x42,0xD8,0xE7,0x20,0xB5}, - "rMMn8XqJvtmn2CN3E19vvjA3Y8GLWdgwi"}, - {{0x92,0xF0,0x1F,0xC7,0x8F,0x22,0x58,0xB1,0xA9,0x70,0xF4,0xA2,0xDD,0xDD,0xD3,0xA1}, - {0x03,0x0C,0x28,0x5D,0x66,0x77,0x0A,0xFD,0x42,0xAB,0xAE,0x15,0x18,0x74,0x7A,0x64, + .addr="rMMn8XqJvtmn2CN3E19vvjA3Y8GLWdgwi"}, + {.seed={0x92,0xF0,0x1F,0xC7,0x8F,0x22,0x58,0xB1,0xA9,0x70,0xF4,0xA2,0xDD,0xDD,0xD3,0xA1}, + .pubkey={0x03,0x0C,0x28,0x5D,0x66,0x77,0x0A,0xFD,0x42,0xAB,0xAE,0x15,0x18,0x74,0x7A,0x64, 0x33,0xB7,0xD5,0x87,0x7A,0x92,0x66,0x78,0x05,0x78,0x2C,0x36,0xDD,0xEA,0x9A,0xF3,0x8F}, - {0x44,0xC0,0x46,0xE7,0x1D,0x7E,0x99,0xAE,0x70,0xDE,0x9B,0x97,0x1A,0x48,0xCA,0xB4, + .seckey={0x44,0xC0,0x46,0xE7,0x1D,0x7E,0x99,0xAE,0x70,0xDE,0x9B,0x97,0x1A,0x48,0xCA,0xB4, 0x26,0x06,0xB4,0xD7,0x81,0xE8,0x84,0x40,0x9B,0x8C,0xC6,0xB3,0x14,0xE1,0xA5,0xCA}, - "rMRLSmwj5SPYidP7UG6vLVmZm849vj9Boy"}, - {{0x4D,0x0A,0x15,0x15,0x19,0x8D,0xAC,0xF3,0xAA,0x74,0x70,0x06,0x24,0x75,0xE1,0x71}, - {0x02,0x3F,0xBA,0x9F,0xF3,0x33,0x71,0x35,0x12,0xE9,0xA3,0x16,0xA8,0x7A,0xBE,0x5E, + .addr="rMRLSmwj5SPYidP7UG6vLVmZm849vj9Boy"}, + {.seed={0x4D,0x0A,0x15,0x15,0x19,0x8D,0xAC,0xF3,0xAA,0x74,0x70,0x06,0x24,0x75,0xE1,0x71}, + .pubkey={0x02,0x3F,0xBA,0x9F,0xF3,0x33,0x71,0x35,0x12,0xE9,0xA3,0x16,0xA8,0x7A,0xBE,0x5E, 0x22,0x2D,0xE0,0xE1,0x82,0xE1,0x8F,0x4B,0xE8,0x05,0xC7,0xE4,0xE1,0x95,0xA8,0x58,0x27}, - {0x62,0x76,0x1B,0xC1,0xD8,0x79,0x83,0xC7,0xA5,0x49,0x1E,0x7F,0x4A,0x0F,0x6C,0x04, + .seckey={0x62,0x76,0x1B,0xC1,0xD8,0x79,0x83,0xC7,0xA5,0x49,0x1E,0x7F,0x4A,0x0F,0x6C,0x04, 0xC1,0xB4,0x36,0x17,0x31,0x99,0xFF,0x78,0x07,0x36,0xD4,0x58,0x3F,0xA9,0x24,0xA5}, - "rEwu1cBZ37WaTF6aJDMQKTKxBBok4bkeW9"}, - {{0xF9,0x3E,0x0D,0xFE,0xEB,0xFC,0x30,0x3B,0x2F,0x13,0x7C,0xF8,0xCF,0x9F,0x25,0x3B}, - {0x03,0x1B,0x7E,0xEC,0x18,0x2A,0xEA,0x64,0xB9,0x1F,0xF6,0x13,0x31,0x28,0x6F,0xC9, + .addr="rEwu1cBZ37WaTF6aJDMQKTKxBBok4bkeW9"}, + {.seed={0xF9,0x3E,0x0D,0xFE,0xEB,0xFC,0x30,0x3B,0x2F,0x13,0x7C,0xF8,0xCF,0x9F,0x25,0x3B}, + .pubkey={0x03,0x1B,0x7E,0xEC,0x18,0x2A,0xEA,0x64,0xB9,0x1F,0xF6,0x13,0x31,0x28,0x6F,0xC9, 0xC5,0xFA,0x62,0x91,0x8F,0x06,0xFF,0x04,0x84,0xF2,0x2C,0x7D,0x9B,0x11,0xBB,0xA2,0xC4}, - {0x4D,0x79,0xBC,0x7F,0x52,0x29,0x8C,0xEB,0x31,0x37,0xE9,0x83,0x31,0x28,0x8B,0x62, + .seckey={0x4D,0x79,0xBC,0x7F,0x52,0x29,0x8C,0xEB,0x31,0x37,0xE9,0x83,0x31,0x28,0x8B,0x62, 0xDE,0xB6,0x4C,0xCD,0x41,0x28,0x4B,0x59,0xBF,0xA7,0x3F,0xFA,0x99,0xE5,0x95,0xB4}, - "raaSAqSnLbGGyfpxgk7jNfyc3BwVdv6YFS"}, - {{0xFC,0xB2,0x17,0xA7,0xD4,0xE6,0x6A,0x5C,0x27,0x9F,0xED,0xFF,0x22,0xAD,0x35,0x8A}, - {0x02,0xB9,0xCE,0x1E,0xC9,0x4F,0x42,0x20,0x6E,0xCD,0xC0,0x11,0x65,0xAE,0xC9,0x28, + .addr="raaSAqSnLbGGyfpxgk7jNfyc3BwVdv6YFS"}, + {.seed={0xFC,0xB2,0x17,0xA7,0xD4,0xE6,0x6A,0x5C,0x27,0x9F,0xED,0xFF,0x22,0xAD,0x35,0x8A}, + .pubkey={0x02,0xB9,0xCE,0x1E,0xC9,0x4F,0x42,0x20,0x6E,0xCD,0xC0,0x11,0x65,0xAE,0xC9,0x28, 0x65,0xAB,0x84,0x97,0x59,0x6F,0x03,0x37,0xCA,0xEF,0xE1,0xF1,0x86,0xA5,0x6B,0xF9,0x24}, - {0x95,0x2E,0xBD,0xC1,0x1E,0xCB,0x7C,0x67,0xE6,0x90,0xF7,0x9D,0x7B,0xB9,0x72,0x3C, + .seckey={0x95,0x2E,0xBD,0xC1,0x1E,0xCB,0x7C,0x67,0xE6,0x90,0xF7,0x9D,0x7B,0xB9,0x72,0x3C, 0xCE,0x84,0xD6,0x0C,0x37,0x23,0xDE,0x71,0x66,0x9E,0xD8,0xF5,0xCA,0x7D,0xE5,0x60}, - "raCkMBRoTrd41BuoAQq6f54E8iA97Fgwiq"}, - {{0x20,0xE6,0xD1,0xE2,0x10,0x0D,0xF4,0x2C,0x39,0x81,0x03,0x61,0xEB,0x54,0xFA,0x6B}, - {0x02,0x51,0x6D,0xF5,0x75,0xF9,0x9E,0x61,0xB4,0xBE,0x78,0xFD,0xCC,0x7D,0x2E,0x53, + .addr="raCkMBRoTrd41BuoAQq6f54E8iA97Fgwiq"}, + {.seed={0x20,0xE6,0xD1,0xE2,0x10,0x0D,0xF4,0x2C,0x39,0x81,0x03,0x61,0xEB,0x54,0xFA,0x6B}, + .pubkey={0x02,0x51,0x6D,0xF5,0x75,0xF9,0x9E,0x61,0xB4,0xBE,0x78,0xFD,0xCC,0x7D,0x2E,0x53, 0x9E,0x3A,0x59,0xD4,0x6B,0x2E,0xA9,0xEA,0x90,0x2F,0x5C,0xBE,0x3C,0xC3,0xA1,0x71,0xA9}, - {0x4F,0x50,0xD3,0x78,0x7F,0x50,0x4D,0x4E,0xCF,0xD8,0xAE,0x40,0xD8,0xA0,0xB1,0xBE, + .seckey={0x4F,0x50,0xD3,0x78,0x7F,0x50,0x4D,0x4E,0xCF,0xD8,0xAE,0x40,0xD8,0xA0,0xB1,0xBE, 0x92,0x2E,0xD1,0x0F,0x1B,0x26,0x8A,0x97,0xF3,0x36,0xB2,0x1D,0xCB,0x9F,0x87,0x92}, - "rnqPbY4BaFVNrbt2YwLQUSwxk7CPRJChf2"}, - {{0x63,0x09,0x81,0x8C,0xB7,0xE1,0xFC,0x55,0x77,0xBE,0xC7,0xDE,0x6C,0x5A,0x2B,0x73}, - {0x03,0x64,0x7B,0x2F,0xF7,0xD8,0xC0,0x56,0xFC,0x19,0x0F,0xF1,0x5B,0x6F,0x51,0x53, + .addr="rnqPbY4BaFVNrbt2YwLQUSwxk7CPRJChf2"}, + {.seed={0x63,0x09,0x81,0x8C,0xB7,0xE1,0xFC,0x55,0x77,0xBE,0xC7,0xDE,0x6C,0x5A,0x2B,0x73}, + .pubkey={0x03,0x64,0x7B,0x2F,0xF7,0xD8,0xC0,0x56,0xFC,0x19,0x0F,0xF1,0x5B,0x6F,0x51,0x53, 0x00,0x1F,0xF7,0x9C,0x44,0x98,0xD7,0x34,0x72,0x21,0x89,0xD3,0xE8,0x00,0xBC,0x01,0xDA}, - {0xC5,0x0C,0x5C,0x92,0xB4,0xFB,0x2D,0x6A,0xC0,0xC6,0xCA,0xC0,0xD5,0xB4,0x26,0xC5, + .seckey={0xC5,0x0C,0x5C,0x92,0xB4,0xFB,0x2D,0x6A,0xC0,0xC6,0xCA,0xC0,0xD5,0xB4,0x26,0xC5, 0x03,0xAE,0xE7,0x72,0xEA,0xF1,0x71,0x3C,0xDC,0xE4,0xE4,0x78,0xFF,0x28,0x22,0xAF}, - "rsBwJKLppzK5XK6WscKXW96NT4ajjYN4oV"}, - {{0xBE,0x60,0x25,0x4D,0xB3,0xED,0xDE,0xE2,0x47,0x69,0x25,0x83,0x80,0xA6,0xF7,0x94}, - {0x02,0xD8,0x4C,0x88,0xBF,0x11,0x7E,0x9E,0xFB,0x30,0xC9,0x63,0x08,0x87,0x04,0x9A, + .addr="rsBwJKLppzK5XK6WscKXW96NT4ajjYN4oV"}, + {.seed={0xBE,0x60,0x25,0x4D,0xB3,0xED,0xDE,0xE2,0x47,0x69,0x25,0x83,0x80,0xA6,0xF7,0x94}, + .pubkey={0x02,0xD8,0x4C,0x88,0xBF,0x11,0x7E,0x9E,0xFB,0x30,0xC9,0x63,0x08,0x87,0x04,0x9A, 0xF2,0xB2,0x33,0x52,0xBC,0x77,0x81,0x3B,0x76,0x5B,0x56,0x0D,0xC5,0x49,0x21,0x20,0xE3}, - {0xAC,0x21,0xC4,0x05,0xED,0x57,0x17,0xB0,0xB7,0x20,0x6C,0x0A,0x59,0x1C,0x6A,0x32, + .seckey={0xAC,0x21,0xC4,0x05,0xED,0x57,0x17,0xB0,0xB7,0x20,0x6C,0x0A,0x59,0x1C,0x6A,0x32, 0xCF,0xD1,0x61,0xC2,0x18,0x2D,0xE4,0x24,0x88,0xA0,0xE2,0xE9,0xC5,0x7E,0x72,0xDB}, - "rGh8eA3PAbpXF2i4YuWdJGknvXxjLs7UFn"}, - {{0x92,0x9A,0x69,0xD1,0xA4,0x72,0x50,0xA4,0x40,0x58,0x00,0xB6,0x93,0xC4,0x0F,0x65}, - {0x03,0xB4,0xCE,0x74,0x68,0x26,0x64,0x2B,0xD9,0x2F,0x7E,0xC0,0xB3,0x6A,0x25,0xAA, + .addr="rGh8eA3PAbpXF2i4YuWdJGknvXxjLs7UFn"}, + {.seed={0x92,0x9A,0x69,0xD1,0xA4,0x72,0x50,0xA4,0x40,0x58,0x00,0xB6,0x93,0xC4,0x0F,0x65}, + .pubkey={0x03,0xB4,0xCE,0x74,0x68,0x26,0x64,0x2B,0xD9,0x2F,0x7E,0xC0,0xB3,0x6A,0x25,0xAA, 0xA8,0x72,0xE0,0x91,0x2A,0x2F,0x09,0x49,0x6B,0x5D,0x08,0xDD,0x0D,0xC3,0x29,0x7A,0xEA}, - {0x3D,0x6A,0x38,0x3A,0xCC,0x5D,0x22,0xD9,0x44,0xD2,0x84,0xE7,0xA2,0x73,0x34,0xE4, + .seckey={0x3D,0x6A,0x38,0x3A,0xCC,0x5D,0x22,0xD9,0x44,0xD2,0x84,0xE7,0xA2,0x73,0x34,0xE4, 0xE3,0x01,0x2A,0x0B,0x0C,0x5B,0x7E,0xAA,0x64,0xD0,0x41,0xE4,0x1E,0xE3,0x1D,0x38}, - "rPWb2cxWuXV39hFBJgEup4enkiBXDMMuPs"}, - {{0x72,0xF2,0x97,0x35,0x2E,0x8F,0x35,0xB3,0x4D,0x2E,0x6B,0xC7,0x3A,0x42,0xDD,0xC9}, - {0x02,0xB5,0x28,0xB7,0x7B,0x4E,0xA1,0xFC,0xC5,0x0D,0x00,0x72,0xD9,0x0B,0x69,0x46, + .addr="rPWb2cxWuXV39hFBJgEup4enkiBXDMMuPs"}, + {.seed={0x72,0xF2,0x97,0x35,0x2E,0x8F,0x35,0xB3,0x4D,0x2E,0x6B,0xC7,0x3A,0x42,0xDD,0xC9}, + .pubkey={0x02,0xB5,0x28,0xB7,0x7B,0x4E,0xA1,0xFC,0xC5,0x0D,0x00,0x72,0xD9,0x0B,0x69,0x46, 0x58,0x60,0xD6,0xD1,0x81,0xC1,0x1B,0xC4,0x8D,0xDD,0x01,0xAA,0x35,0x53,0x85,0x6E,0xBA}, - {0xA9,0x21,0xF9,0x54,0xF4,0xF7,0x78,0xA6,0xC9,0x78,0x90,0x46,0xED,0x12,0xCC,0xBA, + .seckey={0xA9,0x21,0xF9,0x54,0xF4,0xF7,0x78,0xA6,0xC9,0x78,0x90,0x46,0xED,0x12,0xCC,0xBA, 0x06,0x9C,0x79,0x71,0x8D,0x24,0xA0,0x33,0xC4,0x7B,0x91,0xDF,0x41,0xEC,0x17,0x00}, - "rMMikVEYA9b7cyA82F4Lezpccti8coi39u"}, - {{0xC8,0x55,0x71,0x45,0x38,0xC5,0xF7,0xAA,0xC4,0x62,0x05,0xDC,0xC5,0x4E,0x49,0xCC}, - {0x02,0xBB,0xD4,0x85,0x34,0x2E,0x89,0xEA,0x33,0x31,0xE7,0xC8,0x1C,0xAE,0x1D,0xE6, + .addr="rMMikVEYA9b7cyA82F4Lezpccti8coi39u"}, + {.seed={0xC8,0x55,0x71,0x45,0x38,0xC5,0xF7,0xAA,0xC4,0x62,0x05,0xDC,0xC5,0x4E,0x49,0xCC}, + .pubkey={0x02,0xBB,0xD4,0x85,0x34,0x2E,0x89,0xEA,0x33,0x31,0xE7,0xC8,0x1C,0xAE,0x1D,0xE6, 0x64,0x7C,0xE5,0xBE,0x2A,0xA2,0xF8,0xB9,0xAD,0x6E,0x44,0x66,0xD4,0xD8,0x3E,0xDC,0x71}, - {0x52,0xB9,0x26,0x2D,0xED,0xD5,0x1D,0xB5,0xA0,0x48,0x1A,0x64,0x32,0xD2,0xAE,0x2E, + .seckey={0x52,0xB9,0x26,0x2D,0xED,0xD5,0x1D,0xB5,0xA0,0x48,0x1A,0x64,0x32,0xD2,0xAE,0x2E, 0x95,0x12,0x66,0x0F,0xF1,0x04,0x99,0xBD,0xE2,0xA9,0x4A,0x7D,0x42,0x64,0x84,0xC0}, - "r4cJGjZb7v6u9oAG5vZHbzAZeKjG3oubhT"}, - {{0xA7,0x8F,0xA2,0xA0,0x01,0xEE,0xD7,0xC9,0xE4,0x8D,0x36,0x1B,0xBA,0xDD,0xB3,0x8C}, - {0x02,0x0D,0xAA,0xC3,0x5A,0xB5,0xEB,0x1D,0x24,0xFA,0xE0,0x10,0x8D,0xBC,0xE8,0xEA, + .addr="r4cJGjZb7v6u9oAG5vZHbzAZeKjG3oubhT"}, + {.seed={0xA7,0x8F,0xA2,0xA0,0x01,0xEE,0xD7,0xC9,0xE4,0x8D,0x36,0x1B,0xBA,0xDD,0xB3,0x8C}, + .pubkey={0x02,0x0D,0xAA,0xC3,0x5A,0xB5,0xEB,0x1D,0x24,0xFA,0xE0,0x10,0x8D,0xBC,0xE8,0xEA, 0x37,0xD4,0xEE,0xA7,0x1C,0xFA,0xF2,0x0C,0x24,0xAF,0xA1,0xF9,0x92,0x1E,0xD4,0x9B,0x67}, - {0xE8,0xFA,0x0D,0xEC,0x05,0x35,0xE5,0x46,0x5D,0x75,0x24,0x93,0x84,0x48,0x9B,0x55, + .seckey={0xE8,0xFA,0x0D,0xEC,0x05,0x35,0xE5,0x46,0x5D,0x75,0x24,0x93,0x84,0x48,0x9B,0x55, 0x16,0xA5,0x1D,0x9F,0xBF,0xB8,0xC3,0x9A,0x91,0xA8,0x63,0xF8,0xED,0x2C,0xC9,0x41}, - "rLEDPnaJhjcDYzFvYufDTaJvXBokJka1Wf"}, - {{0x8D,0x3B,0xC5,0x48,0x77,0x83,0x56,0x43,0xB9,0xA7,0x6A,0xBE,0xBE,0x16,0xEA,0xE9}, - {0x03,0x54,0x44,0x02,0x4A,0x23,0x22,0x3A,0xCB,0xA9,0x71,0xD5,0x7C,0x74,0xF4,0x25, + .addr="rLEDPnaJhjcDYzFvYufDTaJvXBokJka1Wf"}, + {.seed={0x8D,0x3B,0xC5,0x48,0x77,0x83,0x56,0x43,0xB9,0xA7,0x6A,0xBE,0xBE,0x16,0xEA,0xE9}, + .pubkey={0x03,0x54,0x44,0x02,0x4A,0x23,0x22,0x3A,0xCB,0xA9,0x71,0xD5,0x7C,0x74,0xF4,0x25, 0x49,0x77,0x9D,0x33,0xDC,0x3A,0x8E,0x61,0xF8,0xE7,0x34,0x5E,0x25,0x5D,0x8B,0x81,0x40}, - {0xCB,0x6F,0x1C,0xA0,0x22,0x68,0x5F,0xF1,0x4E,0x0C,0xBF,0xB7,0x90,0x4F,0x3A,0x13, + .seckey={0xCB,0x6F,0x1C,0xA0,0x22,0x68,0x5F,0xF1,0x4E,0x0C,0xBF,0xB7,0x90,0x4F,0x3A,0x13, 0x17,0x5E,0xEF,0x08,0x47,0x44,0xD3,0xA3,0x0A,0x21,0x75,0x9B,0x8A,0xB7,0xDD,0x94}, - "rnxJTkqbxpbdoC1nPQvAfPDi8zpmsschsR"}, - {{0x27,0x1B,0xEA,0xD4,0xBF,0x66,0x55,0x06,0xCE,0x06,0x7D,0xA6,0xF8,0xAF,0xFC,0xF3}, - {0x03,0x01,0xC3,0xBE,0xA1,0xD1,0xA8,0x57,0x5E,0x29,0x85,0x56,0x93,0x81,0x12,0xD0, + .addr="rnxJTkqbxpbdoC1nPQvAfPDi8zpmsschsR"}, + {.seed={0x27,0x1B,0xEA,0xD4,0xBF,0x66,0x55,0x06,0xCE,0x06,0x7D,0xA6,0xF8,0xAF,0xFC,0xF3}, + .pubkey={0x03,0x01,0xC3,0xBE,0xA1,0xD1,0xA8,0x57,0x5E,0x29,0x85,0x56,0x93,0x81,0x12,0xD0, 0x41,0x7F,0x08,0x57,0xCC,0x7C,0x15,0xCC,0x75,0x22,0x5E,0x51,0xEC,0x3A,0x1A,0xAF,0x17}, - {0xA3,0x2A,0x64,0x81,0xC8,0xFA,0xFB,0xD0,0xF6,0x7C,0x80,0xD6,0xD4,0x30,0x98,0x4E, + .seckey={0xA3,0x2A,0x64,0x81,0xC8,0xFA,0xFB,0xD0,0xF6,0x7C,0x80,0xD6,0xD4,0x30,0x98,0x4E, 0xC1,0xDA,0x9E,0x63,0x8E,0xDE,0x69,0x3C,0xE1,0xFA,0xD0,0x63,0xCA,0xA8,0x62,0xD9}, - "rENrUzb4cctMQApEcDRLRNkwHbVencPYSn"}, - {{0x3B,0x1A,0x33,0x24,0x72,0xF4,0x1A,0xF6,0xDA,0x71,0x5F,0x02,0x0E,0x90,0x96,0xAE}, - {0x03,0x5C,0x34,0x4A,0x9B,0x4A,0xE2,0xFF,0x1B,0xF4,0x5C,0xDE,0x91,0x92,0xA2,0xF6, + .addr="rENrUzb4cctMQApEcDRLRNkwHbVencPYSn"}, + {.seed={0x3B,0x1A,0x33,0x24,0x72,0xF4,0x1A,0xF6,0xDA,0x71,0x5F,0x02,0x0E,0x90,0x96,0xAE}, + .pubkey={0x03,0x5C,0x34,0x4A,0x9B,0x4A,0xE2,0xFF,0x1B,0xF4,0x5C,0xDE,0x91,0x92,0xA2,0xF6, 0x23,0x27,0x0E,0x0B,0xC0,0x0C,0xB3,0x31,0x9B,0x18,0x33,0x11,0x1A,0x37,0x76,0xCE,0x57}, - {0x51,0xBD,0x74,0x0F,0xCD,0xEC,0x6D,0x96,0x36,0xB4,0xC8,0x53,0x11,0x00,0xD7,0xB5, + .seckey={0x51,0xBD,0x74,0x0F,0xCD,0xEC,0x6D,0x96,0x36,0xB4,0xC8,0x53,0x11,0x00,0xD7,0xB5, 0xD6,0xE9,0x9B,0xED,0x27,0x85,0x6C,0x48,0x22,0xE7,0x13,0x94,0xCD,0x85,0x7D,0xA9}, - "rraNgbPXeioSQyDw9gq1otuJURr4tkrVbR"}, - {{0xC6,0xC2,0x5B,0x54,0xB5,0xF3,0x67,0x54,0xE5,0xE1,0x3E,0x07,0xFB,0x8B,0x9A,0x12}, - {0x02,0x44,0x78,0x11,0x6A,0x52,0x0F,0x16,0xCD,0xD9,0x0E,0x73,0xFF,0xDD,0x73,0x50, + .addr="rraNgbPXeioSQyDw9gq1otuJURr4tkrVbR"}, + {.seed={0xC6,0xC2,0x5B,0x54,0xB5,0xF3,0x67,0x54,0xE5,0xE1,0x3E,0x07,0xFB,0x8B,0x9A,0x12}, + .pubkey={0x02,0x44,0x78,0x11,0x6A,0x52,0x0F,0x16,0xCD,0xD9,0x0E,0x73,0xFF,0xDD,0x73,0x50, 0xAE,0x40,0x3F,0x70,0xAF,0x1C,0xC6,0xB6,0xC9,0x07,0xE3,0x49,0x9D,0xF0,0x86,0xBB,0x01}, - {0x0C,0x13,0x9C,0x31,0x96,0xAB,0xCD,0x88,0x76,0x4F,0x75,0x28,0xCE,0xCC,0x17,0x4A, + .seckey={0x0C,0x13,0x9C,0x31,0x96,0xAB,0xCD,0x88,0x76,0x4F,0x75,0x28,0xCE,0xCC,0x17,0x4A, 0x02,0xA7,0x75,0xF5,0x9A,0xA7,0xA7,0x03,0x01,0x38,0xD4,0xFD,0x1D,0x2B,0x57,0x82}, - "rraDh9AGcTXLCZ6mX6v1vXT8kTpZ9A2E4o"}, - {{0x82,0x34,0xCA,0xC5,0x2D,0x12,0x9E,0xAC,0x87,0xA8,0xA3,0x96,0x25,0x24,0x7C,0x3C}, - {0x03,0xA0,0x24,0xFA,0xB8,0x0C,0x29,0x10,0x22,0xE8,0x3E,0xEE,0xD0,0xBB,0xA1,0x0C, + .addr="rraDh9AGcTXLCZ6mX6v1vXT8kTpZ9A2E4o"}, + {.seed={0x82,0x34,0xCA,0xC5,0x2D,0x12,0x9E,0xAC,0x87,0xA8,0xA3,0x96,0x25,0x24,0x7C,0x3C}, + .pubkey={0x03,0xA0,0x24,0xFA,0xB8,0x0C,0x29,0x10,0x22,0xE8,0x3E,0xEE,0xD0,0xBB,0xA1,0x0C, 0x58,0x2D,0x7F,0x74,0x99,0x87,0xE4,0x86,0x3D,0xCD,0xFD,0x4C,0x97,0xEB,0xEF,0xF1,0x88}, - {0x8C,0xA9,0x94,0xCD,0xF0,0x44,0xD1,0xE0,0x3C,0xC2,0xD8,0x81,0xE5,0x88,0x41,0xF8, + .seckey={0x8C,0xA9,0x94,0xCD,0xF0,0x44,0xD1,0xE0,0x3C,0xC2,0xD8,0x81,0xE5,0x88,0x41,0xF8, 0xD6,0x2F,0xDF,0x28,0x23,0x04,0x7A,0x9A,0x9D,0x1E,0x53,0x35,0x56,0x12,0xD1,0x9E}, - "rfHFE2euELEfCS1jSqSZbZct7K3rDYxJiA"}, - {{0x60,0x31,0x24,0xCB,0xD1,0xD6,0xFF,0xCB,0x2A,0xC0,0x07,0x0E,0xC2,0x34,0x84,0xE0}, - {0x03,0x08,0xE5,0x04,0xD0,0x1D,0xB2,0x94,0xB0,0xDB,0xD1,0xD6,0x9F,0xCF,0xE7,0x87, + .addr="rfHFE2euELEfCS1jSqSZbZct7K3rDYxJiA"}, + {.seed={0x60,0x31,0x24,0xCB,0xD1,0xD6,0xFF,0xCB,0x2A,0xC0,0x07,0x0E,0xC2,0x34,0x84,0xE0}, + .pubkey={0x03,0x08,0xE5,0x04,0xD0,0x1D,0xB2,0x94,0xB0,0xDB,0xD1,0xD6,0x9F,0xCF,0xE7,0x87, 0x70,0x83,0x81,0xF4,0x99,0x04,0xE9,0x51,0x12,0x6F,0xAF,0x27,0x51,0xF7,0x26,0xC0,0xCC}, - {0xD1,0x76,0x39,0xE8,0x2D,0xDB,0x35,0x67,0x24,0x19,0xFE,0x65,0x25,0x5B,0xE2,0x43, + .seckey={0xD1,0x76,0x39,0xE8,0x2D,0xDB,0x35,0x67,0x24,0x19,0xFE,0x65,0x25,0x5B,0xE2,0x43, 0x56,0x45,0xE0,0x20,0x69,0xCF,0xD9,0x32,0x21,0xB6,0xD0,0xEF,0x3A,0x3E,0xBD,0xD9}, - "rh1KG2QmCBwZiNJpVzJxw2mDR7bQz6QeeA"}, - {{0xF3,0x8C,0xD6,0x55,0x1D,0xCF,0x05,0xC1,0x09,0xFD,0x04,0xDD,0x06,0xCF,0x04,0xDB}, - {0x02,0xDA,0xBD,0xB1,0x5F,0x23,0xB9,0x23,0xD6,0x3D,0x6F,0xC3,0x96,0x68,0xB3,0xAC, + .addr="rh1KG2QmCBwZiNJpVzJxw2mDR7bQz6QeeA"}, + {.seed={0xF3,0x8C,0xD6,0x55,0x1D,0xCF,0x05,0xC1,0x09,0xFD,0x04,0xDD,0x06,0xCF,0x04,0xDB}, + .pubkey={0x02,0xDA,0xBD,0xB1,0x5F,0x23,0xB9,0x23,0xD6,0x3D,0x6F,0xC3,0x96,0x68,0xB3,0xAC, 0x77,0x58,0xA2,0x17,0x46,0xE9,0xE8,0xC3,0x91,0x91,0xCB,0xFA,0x89,0x43,0xB1,0x77,0xF4}, - {0x29,0x59,0xF0,0xAF,0x1B,0xDB,0x52,0x16,0xB8,0x26,0x42,0xEB,0x87,0x1C,0x90,0xBC, + .seckey={0x29,0x59,0xF0,0xAF,0x1B,0xDB,0x52,0x16,0xB8,0x26,0x42,0xEB,0x87,0x1C,0x90,0xBC, 0x96,0xCC,0xE9,0xE4,0x95,0xBF,0xE6,0x02,0x5D,0xDB,0x26,0x2E,0x1B,0x6D,0xC6,0x8E}, - "rfsgasx89kAvYvqhLmJt8Pn5JsHFnZadwm"}, - {{0x30,0x9F,0xC8,0x61,0x10,0x78,0xE1,0x2C,0x0B,0x3F,0x80,0x01,0x16,0x0D,0x00,0xB0}, - {0x02,0x23,0xE5,0x05,0xAD,0x30,0x89,0x32,0x66,0x12,0x0F,0xCD,0xDE,0xB8,0xFD,0xF9, + .addr="rfsgasx89kAvYvqhLmJt8Pn5JsHFnZadwm"}, + {.seed={0x30,0x9F,0xC8,0x61,0x10,0x78,0xE1,0x2C,0x0B,0x3F,0x80,0x01,0x16,0x0D,0x00,0xB0}, + .pubkey={0x02,0x23,0xE5,0x05,0xAD,0x30,0x89,0x32,0x66,0x12,0x0F,0xCD,0xDE,0xB8,0xFD,0xF9, 0xC2,0xFA,0xE0,0x6D,0xCE,0xBA,0x67,0x1B,0xEB,0xE1,0x25,0x85,0xD0,0xB6,0x23,0xE7,0xE0}, - {0xCE,0xD8,0xD9,0xEB,0x26,0xB5,0xD2,0x5A,0xD1,0xC7,0xDD,0x84,0xA3,0xD1,0x4F,0x07, + .seckey={0xCE,0xD8,0xD9,0xEB,0x26,0xB5,0xD2,0x5A,0xD1,0xC7,0xDD,0x84,0xA3,0xD1,0x4F,0x07, 0xB4,0xBC,0xE0,0x1A,0xA7,0xA1,0xBD,0xBD,0x41,0x7B,0x2C,0xF1,0x72,0xC3,0xD8,0x6E}, - "rMnU6UGDbmG4FNHPwY8KZaiY96fJWA8uqN"}, - {{0x03,0x09,0x10,0x63,0x99,0xE1,0x71,0x0E,0xE7,0xC2,0x4E,0xC2,0xD3,0x07,0x11,0x55}, - {0x03,0x4F,0x9D,0x3A,0x5B,0x58,0x4C,0x9F,0x1C,0xF6,0x8C,0x5D,0xB0,0x0E,0x3F,0xB9, + .addr="rMnU6UGDbmG4FNHPwY8KZaiY96fJWA8uqN"}, + {.seed={0x03,0x09,0x10,0x63,0x99,0xE1,0x71,0x0E,0xE7,0xC2,0x4E,0xC2,0xD3,0x07,0x11,0x55}, + .pubkey={0x03,0x4F,0x9D,0x3A,0x5B,0x58,0x4C,0x9F,0x1C,0xF6,0x8C,0x5D,0xB0,0x0E,0x3F,0xB9, 0xC9,0xDC,0x91,0xFF,0x4E,0x43,0xAA,0x6C,0x9E,0x49,0x77,0x83,0xDE,0x51,0x17,0x0F,0x96}, - {0x2E,0xF1,0x99,0x7B,0x37,0xCE,0x08,0x4B,0x04,0x8D,0x18,0x47,0x10,0x9B,0xA6,0xF8, + .seckey={0x2E,0xF1,0x99,0x7B,0x37,0xCE,0x08,0x4B,0x04,0x8D,0x18,0x47,0x10,0x9B,0xA6,0xF8, 0xA9,0xED,0x24,0x91,0x9A,0x3B,0x03,0xB2,0x32,0x09,0x0E,0x60,0x6B,0x08,0x63,0xD3}, - "rPDive23VFL8tBzwSsh5VfxYM69EhFYUmV"}, - {{0xC4,0x43,0x48,0xC9,0xFE,0x00,0xC4,0x9F,0x60,0xA0,0x6D,0xE5,0x67,0xB8,0x2C,0x1A}, - {0x02,0xF9,0x76,0xF3,0x6E,0x24,0x6D,0x65,0x79,0x76,0xE8,0x05,0xCF,0x65,0xEC,0x84, + .addr="rPDive23VFL8tBzwSsh5VfxYM69EhFYUmV"}, + {.seed={0xC4,0x43,0x48,0xC9,0xFE,0x00,0xC4,0x9F,0x60,0xA0,0x6D,0xE5,0x67,0xB8,0x2C,0x1A}, + .pubkey={0x02,0xF9,0x76,0xF3,0x6E,0x24,0x6D,0x65,0x79,0x76,0xE8,0x05,0xCF,0x65,0xEC,0x84, 0xC9,0x17,0x94,0x2C,0x04,0xBA,0x68,0x0B,0x42,0xB2,0x2C,0x6B,0x02,0x13,0xF8,0xD8,0x4E}, - {0x75,0x56,0xCF,0x71,0x82,0x3C,0xA4,0x93,0xEB,0x0B,0xD9,0xAE,0xEF,0x89,0xE4,0xE7, + .seckey={0x75,0x56,0xCF,0x71,0x82,0x3C,0xA4,0x93,0xEB,0x0B,0xD9,0xAE,0xEF,0x89,0xE4,0xE7, 0xCE,0x09,0x0F,0x50,0x67,0xD8,0x5D,0x95,0xA4,0x26,0x52,0x17,0x0B,0x1A,0xFF,0x18}, - "rNxe7tnN6gAjov9RERrbqPQmQZX5XDo5BJ"}, - {{0x44,0x7A,0x7B,0xEF,0x65,0x8D,0x23,0x7A,0x3E,0x43,0x33,0x49,0x63,0x66,0x97,0x5B}, - {0x03,0xC3,0xAD,0x1A,0xFF,0x40,0x0C,0x0B,0xCA,0x4C,0x30,0x45,0xB6,0x58,0xE3,0x7F, + .addr="rNxe7tnN6gAjov9RERrbqPQmQZX5XDo5BJ"}, + {.seed={0x44,0x7A,0x7B,0xEF,0x65,0x8D,0x23,0x7A,0x3E,0x43,0x33,0x49,0x63,0x66,0x97,0x5B}, + .pubkey={0x03,0xC3,0xAD,0x1A,0xFF,0x40,0x0C,0x0B,0xCA,0x4C,0x30,0x45,0xB6,0x58,0xE3,0x7F, 0x34,0xE7,0x6C,0x2A,0x80,0xD8,0xE0,0x99,0x29,0x83,0x39,0xED,0x3C,0x2C,0x0C,0x86,0x49}, - {0x6D,0x74,0x5A,0xDE,0xCC,0x4E,0xC9,0x80,0xFD,0x23,0x81,0xE3,0xE6,0x53,0x6F,0x97, + .seckey={0x6D,0x74,0x5A,0xDE,0xCC,0x4E,0xC9,0x80,0xFD,0x23,0x81,0xE3,0xE6,0x53,0x6F,0x97, 0x7E,0xD1,0x9B,0x28,0x41,0x61,0x6E,0xB8,0x4D,0x00,0x3D,0x97,0x6B,0x8C,0xE3,0xBD}, - "rMun2HwdoraftsxG3JoHXKYhBnCfwmWjyK"}, - {{0x34,0xE3,0x6E,0xC3,0x13,0x76,0xC4,0xB0,0x11,0x94,0x81,0x90,0x89,0xB1,0x24,0x92}, - {0x02,0x89,0x33,0x7D,0x4D,0x1D,0xD8,0x72,0x8F,0x8F,0xF4,0x1B,0xC6,0xDE,0x73,0x0F, + .addr="rMun2HwdoraftsxG3JoHXKYhBnCfwmWjyK"}, + {.seed={0x34,0xE3,0x6E,0xC3,0x13,0x76,0xC4,0xB0,0x11,0x94,0x81,0x90,0x89,0xB1,0x24,0x92}, + .pubkey={0x02,0x89,0x33,0x7D,0x4D,0x1D,0xD8,0x72,0x8F,0x8F,0xF4,0x1B,0xC6,0xDE,0x73,0x0F, 0x21,0x35,0x85,0x4D,0xA9,0x8E,0xA2,0x08,0xC3,0x2C,0xD2,0x50,0x7C,0x57,0x39,0xB4,0xBC}, - {0xCA,0x68,0x9E,0x9C,0x73,0x76,0x0F,0x91,0x37,0xC6,0x41,0x4C,0xBA,0x88,0x44,0xBD, + .seckey={0xCA,0x68,0x9E,0x9C,0x73,0x76,0x0F,0x91,0x37,0xC6,0x41,0x4C,0xBA,0x88,0x44,0xBD, 0x23,0x28,0xD8,0x5C,0x77,0x40,0x81,0x6B,0xB9,0xD8,0xF3,0x3A,0x0A,0x8F,0x0E,0xA0}, - "rEFR7fgCegzeKBSJYGgvxMVuUcw1QmYrqm"}, - {{0x60,0x08,0x22,0x98,0x97,0xAA,0xB6,0x7E,0x1B,0x24,0x63,0x50,0xA8,0x6A,0x5D,0x40}, - {0x03,0x61,0x72,0xB2,0x19,0x99,0xC8,0x7D,0xE4,0x4B,0x0B,0x93,0x42,0x4F,0x46,0xB0, + .addr="rEFR7fgCegzeKBSJYGgvxMVuUcw1QmYrqm"}, + {.seed={0x60,0x08,0x22,0x98,0x97,0xAA,0xB6,0x7E,0x1B,0x24,0x63,0x50,0xA8,0x6A,0x5D,0x40}, + .pubkey={0x03,0x61,0x72,0xB2,0x19,0x99,0xC8,0x7D,0xE4,0x4B,0x0B,0x93,0x42,0x4F,0x46,0xB0, 0x8B,0x3F,0x45,0xA0,0xEC,0xF3,0xE1,0xE4,0x14,0x87,0x99,0x6C,0x42,0xD1,0x2E,0x1C,0xC4}, - {0x5B,0x65,0x66,0x3C,0x23,0xF9,0x4A,0x85,0x26,0x02,0x7E,0x99,0xEC,0xDB,0xB9,0x0A, + .seckey={0x5B,0x65,0x66,0x3C,0x23,0xF9,0x4A,0x85,0x26,0x02,0x7E,0x99,0xEC,0xDB,0xB9,0x0A, 0x7F,0xA2,0xB5,0x6F,0x80,0x92,0x1A,0x2E,0xE9,0x74,0x80,0x0C,0xF5,0x32,0x31,0x72}, - "rMyoPQ4P86M5PYUd9y63muzy28dJ6oS9zk"}, - {{0xE3,0x47,0x3F,0xB0,0xDC,0x1D,0x26,0x01,0xCE,0xF4,0x36,0x7C,0xA6,0xCF,0x65,0xD7}, - {0x02,0x20,0x9F,0xF0,0xB0,0x08,0x49,0x66,0xF9,0xAA,0xD6,0x41,0xDD,0xC3,0x9C,0x7B, + .addr="rMyoPQ4P86M5PYUd9y63muzy28dJ6oS9zk"}, + {.seed={0xE3,0x47,0x3F,0xB0,0xDC,0x1D,0x26,0x01,0xCE,0xF4,0x36,0x7C,0xA6,0xCF,0x65,0xD7}, + .pubkey={0x02,0x20,0x9F,0xF0,0xB0,0x08,0x49,0x66,0xF9,0xAA,0xD6,0x41,0xDD,0xC3,0x9C,0x7B, 0xAE,0x6A,0xAF,0x87,0xF1,0x29,0xDC,0x19,0x7E,0x62,0xD1,0x15,0xF2,0x9D,0xE8,0xE2,0xF7}, - {0xDF,0xF6,0x37,0x4B,0xFC,0x42,0x2C,0x2C,0xF3,0x48,0xC2,0xF1,0xBF,0x4B,0x30,0xB1, + .seckey={0xDF,0xF6,0x37,0x4B,0xFC,0x42,0x2C,0x2C,0xF3,0x48,0xC2,0xF1,0xBF,0x4B,0x30,0xB1, 0x66,0x0E,0xCB,0xA9,0x0C,0xA9,0x32,0x77,0x71,0xB8,0x4A,0xB0,0xDC,0x24,0x69,0x86}, - "rKD4abu3RY94MdoZXG4SrUBxdkQcUJLKFq"}, - {{0xB1,0x25,0x1E,0xC0,0x39,0xFD,0xA9,0xD1,0x84,0x3A,0x08,0xE8,0x38,0xD6,0xE7,0xDA}, - {0x03,0xEC,0x13,0x79,0x7E,0x49,0xE2,0x01,0x11,0xB0,0x0E,0x04,0xCA,0xA9,0x05,0xE3, + .addr="rKD4abu3RY94MdoZXG4SrUBxdkQcUJLKFq"}, + {.seed={0xB1,0x25,0x1E,0xC0,0x39,0xFD,0xA9,0xD1,0x84,0x3A,0x08,0xE8,0x38,0xD6,0xE7,0xDA}, + .pubkey={0x03,0xEC,0x13,0x79,0x7E,0x49,0xE2,0x01,0x11,0xB0,0x0E,0x04,0xCA,0xA9,0x05,0xE3, 0x03,0xF3,0xEB,0x93,0xE5,0x07,0xC0,0xA9,0xAD,0xBA,0xD3,0xA0,0x14,0x8D,0x24,0x0E,0x5F}, - {0x9F,0xF4,0x31,0xC9,0x16,0xAF,0xAB,0x6A,0xCA,0x80,0xC1,0x2D,0xE6,0x76,0xEF,0x86, + .seckey={0x9F,0xF4,0x31,0xC9,0x16,0xAF,0xAB,0x6A,0xCA,0x80,0xC1,0x2D,0xE6,0x76,0xEF,0x86, 0x94,0xB4,0xE4,0x8B,0xFD,0x31,0x77,0x5C,0x2D,0xCD,0x9D,0x17,0x71,0x5B,0x95,0x3F}, - "rnvVe22GM2vweXRNuqggRXoEx3XAZs9knV"}, - {{0x84,0x22,0xC2,0xF7,0x26,0xF0,0xAA,0xBE,0x8A,0xDB,0x36,0xF3,0xE0,0xCC,0x68,0x3E}, - {0x03,0xEB,0x3C,0xE9,0xB6,0xFB,0xD1,0xDF,0x71,0x13,0x5E,0x06,0xA3,0x2F,0x42,0x9B, + .addr="rnvVe22GM2vweXRNuqggRXoEx3XAZs9knV"}, + {.seed={0x84,0x22,0xC2,0xF7,0x26,0xF0,0xAA,0xBE,0x8A,0xDB,0x36,0xF3,0xE0,0xCC,0x68,0x3E}, + .pubkey={0x03,0xEB,0x3C,0xE9,0xB6,0xFB,0xD1,0xDF,0x71,0x13,0x5E,0x06,0xA3,0x2F,0x42,0x9B, 0x1C,0x4F,0xBC,0xA5,0xB4,0x89,0xA7,0x22,0x4B,0x0F,0x91,0xAD,0x6F,0x6C,0x3B,0x98,0x02}, - {0xAF,0xA4,0x70,0x55,0xA0,0x5B,0xE6,0x7C,0x99,0x62,0x4F,0xB8,0xCE,0xE1,0x62,0x48, + .seckey={0xAF,0xA4,0x70,0x55,0xA0,0x5B,0xE6,0x7C,0x99,0x62,0x4F,0xB8,0xCE,0xE1,0x62,0x48, 0x19,0xD4,0xE9,0xEF,0x86,0x10,0x67,0x97,0x0E,0x8E,0x37,0x6B,0xB5,0x82,0xA4,0x6D}, - "r4UZeovKZA8evpNM8AEkodxUPE759ZJx6K"}, - {{0xE4,0x0B,0x92,0x07,0x46,0x0F,0x30,0xC6,0x5D,0xB3,0xB0,0xFE,0xA6,0x8E,0x9C,0x88}, - {0x03,0x14,0x17,0x68,0xD2,0xEE,0x03,0x9D,0xCE,0xB4,0xB1,0x25,0x1F,0x09,0x21,0xBE, + .addr="r4UZeovKZA8evpNM8AEkodxUPE759ZJx6K"}, + {.seed={0xE4,0x0B,0x92,0x07,0x46,0x0F,0x30,0xC6,0x5D,0xB3,0xB0,0xFE,0xA6,0x8E,0x9C,0x88}, + .pubkey={0x03,0x14,0x17,0x68,0xD2,0xEE,0x03,0x9D,0xCE,0xB4,0xB1,0x25,0x1F,0x09,0x21,0xBE, 0x82,0xA1,0xDB,0xED,0x44,0xAE,0x20,0x88,0xD4,0x93,0xDB,0xF4,0x29,0xDB,0x8B,0xCA,0xB1}, - {0x1A,0xBD,0xD6,0xB1,0x15,0x03,0x3D,0x43,0xED,0xD7,0x71,0x22,0x7D,0xA1,0xEC,0x59, + .seckey={0x1A,0xBD,0xD6,0xB1,0x15,0x03,0x3D,0x43,0xED,0xD7,0x71,0x22,0x7D,0xA1,0xEC,0x59, 0x17,0x6B,0xC5,0x56,0x85,0x22,0x68,0x2A,0x3A,0x84,0x0B,0x76,0x96,0x07,0x32,0xB3}, - "rfxzt744qLZjaupkaMz6Vyg9HfAqhAxHuN"}, - {{0xE0,0xA1,0xA6,0xA2,0xF8,0x4E,0xE7,0x83,0x37,0x67,0x93,0xF6,0x9B,0xAB,0x6A,0x1C}, - {0x02,0x14,0x45,0xE1,0xB9,0x85,0x3A,0x10,0xFB,0x3D,0x19,0x51,0x06,0x29,0x89,0x86, + .addr="rfxzt744qLZjaupkaMz6Vyg9HfAqhAxHuN"}, + {.seed={0xE0,0xA1,0xA6,0xA2,0xF8,0x4E,0xE7,0x83,0x37,0x67,0x93,0xF6,0x9B,0xAB,0x6A,0x1C}, + .pubkey={0x02,0x14,0x45,0xE1,0xB9,0x85,0x3A,0x10,0xFB,0x3D,0x19,0x51,0x06,0x29,0x89,0x86, 0xD3,0x21,0xFC,0xD6,0xEA,0xA4,0xA2,0x1F,0x0C,0x21,0x99,0xF4,0xB9,0x17,0xEF,0x9A,0x98}, - {0xC1,0xFC,0x56,0xB0,0xE6,0x3F,0x27,0xC4,0xB3,0x3D,0x83,0x8D,0x3F,0x51,0xBB,0x30, + .seckey={0xC1,0xFC,0x56,0xB0,0xE6,0x3F,0x27,0xC4,0xB3,0x3D,0x83,0x8D,0x3F,0x51,0xBB,0x30, 0xBD,0xF0,0x1B,0x79,0xD2,0x39,0x17,0xFE,0x81,0xEB,0x4F,0xB4,0x4B,0x2B,0x68,0x4E}, - "rGmqckB9JMAtx4qgW8kJzvnftRcm3He4pC"}, - {{0x08,0x89,0x4E,0x50,0x14,0xC7,0x4B,0x29,0x0A,0x92,0x8E,0x49,0x7A,0x41,0x03,0x94}, - {0x03,0xC9,0xAB,0x0B,0x23,0x91,0x7F,0x68,0x93,0x4F,0x05,0x5C,0x22,0x9B,0x84,0x9F, + .addr="rGmqckB9JMAtx4qgW8kJzvnftRcm3He4pC"}, + {.seed={0x08,0x89,0x4E,0x50,0x14,0xC7,0x4B,0x29,0x0A,0x92,0x8E,0x49,0x7A,0x41,0x03,0x94}, + .pubkey={0x03,0xC9,0xAB,0x0B,0x23,0x91,0x7F,0x68,0x93,0x4F,0x05,0x5C,0x22,0x9B,0x84,0x9F, 0xFD,0x39,0x27,0x45,0x1F,0x2A,0x91,0x29,0xD1,0x7D,0x89,0x35,0xF7,0xA5,0xC4,0x7C,0x68}, - {0xCD,0x24,0x10,0xF6,0xB3,0x4F,0xC9,0xBA,0xC5,0x12,0xEB,0x65,0xFB,0xB2,0x69,0xFB, + .seckey={0xCD,0x24,0x10,0xF6,0xB3,0x4F,0xC9,0xBA,0xC5,0x12,0xEB,0x65,0xFB,0xB2,0x69,0xFB, 0x9F,0xC5,0x10,0xA8,0x64,0xF3,0xA8,0x65,0x1E,0x7B,0xF4,0x54,0xD4,0xDD,0xE7,0x48}, - "rBUZAvsXZJfUHCc9tWhEqDJUnDGdPTbA61"}, - {{0xA9,0xEA,0x18,0x15,0x9C,0x7E,0x1B,0xF4,0xCB,0xD7,0x2D,0x3E,0x16,0xF1,0x1A,0x45}, - {0x02,0x3F,0xF7,0x7D,0x42,0x2D,0xE7,0x08,0xA1,0xB8,0xC5,0x56,0x55,0x2C,0xE8,0x74, + .addr="rBUZAvsXZJfUHCc9tWhEqDJUnDGdPTbA61"}, + {.seed={0xA9,0xEA,0x18,0x15,0x9C,0x7E,0x1B,0xF4,0xCB,0xD7,0x2D,0x3E,0x16,0xF1,0x1A,0x45}, + .pubkey={0x02,0x3F,0xF7,0x7D,0x42,0x2D,0xE7,0x08,0xA1,0xB8,0xC5,0x56,0x55,0x2C,0xE8,0x74, 0xF4,0x40,0x9A,0xA8,0x84,0xD7,0x44,0x0C,0x4D,0x03,0xC9,0x99,0xD2,0x9D,0xF9,0xEF,0x89}, - {0x73,0xEA,0xB1,0xD5,0x7E,0xC8,0x36,0x1C,0x41,0x53,0x94,0x2F,0xBC,0x30,0xB7,0x1A, + .seckey={0x73,0xEA,0xB1,0xD5,0x7E,0xC8,0x36,0x1C,0x41,0x53,0x94,0x2F,0xBC,0x30,0xB7,0x1A, 0xA2,0xA0,0x75,0x39,0xD9,0x59,0x4A,0x66,0x7E,0xF3,0xB3,0x41,0xA9,0x5C,0x11,0xD0}, - "rBxAYvkEfc9LwqvNDuij3PnVPtf2LeZvZG"}, - {{0x85,0x12,0xCF,0xA4,0xB8,0xAB,0x77,0x60,0x1A,0xAF,0xB0,0x16,0x22,0xFD,0xF7,0xE0}, - {0x02,0xA0,0x25,0x79,0x1A,0x75,0x9B,0xDE,0x29,0xB6,0x0A,0xA1,0x55,0x82,0xF6,0x3D, + .addr="rBxAYvkEfc9LwqvNDuij3PnVPtf2LeZvZG"}, + {.seed={0x85,0x12,0xCF,0xA4,0xB8,0xAB,0x77,0x60,0x1A,0xAF,0xB0,0x16,0x22,0xFD,0xF7,0xE0}, + .pubkey={0x02,0xA0,0x25,0x79,0x1A,0x75,0x9B,0xDE,0x29,0xB6,0x0A,0xA1,0x55,0x82,0xF6,0x3D, 0x7E,0x59,0x9A,0x1C,0xD3,0x30,0x28,0x44,0xF1,0x51,0x8A,0xA8,0x2D,0x9B,0x64,0x7B,0x12}, - {0xEA,0x04,0x9E,0xBC,0x5E,0xCB,0xEB,0x71,0xD9,0xC1,0x3E,0x00,0xA4,0x53,0x1F,0xFA, + .seckey={0xEA,0x04,0x9E,0xBC,0x5E,0xCB,0xEB,0x71,0xD9,0xC1,0x3E,0x00,0xA4,0x53,0x1F,0xFA, 0x88,0x32,0x9D,0xC2,0xA2,0x6F,0x9B,0x38,0xDC,0xDC,0xA7,0xDF,0xAE,0x60,0xCF,0xC8}, - "rwqKXTVGf6DqKKHKtVYTqiyTi9Utj4dtUg"}, - {{0xDB,0xB2,0x21,0xA2,0x30,0xF8,0xDE,0x09,0xCB,0x4A,0xD7,0xA3,0x4D,0xCF,0x65,0x8F}, - {0x02,0x24,0x93,0x14,0x32,0xAB,0x9B,0xAD,0x1C,0xCB,0x55,0x48,0x52,0x86,0xD8,0x53, + .addr="rwqKXTVGf6DqKKHKtVYTqiyTi9Utj4dtUg"}, + {.seed={0xDB,0xB2,0x21,0xA2,0x30,0xF8,0xDE,0x09,0xCB,0x4A,0xD7,0xA3,0x4D,0xCF,0x65,0x8F}, + .pubkey={0x02,0x24,0x93,0x14,0x32,0xAB,0x9B,0xAD,0x1C,0xCB,0x55,0x48,0x52,0x86,0xD8,0x53, 0xAD,0xFE,0x69,0x63,0x35,0x85,0x7D,0xF7,0xBE,0xBB,0xC2,0x38,0x37,0xC5,0x16,0xDB,0x85}, - {0xCC,0x3F,0x07,0xB0,0x02,0xCF,0x11,0x8C,0x6E,0x16,0x52,0xA9,0x0E,0x53,0xB3,0xE4, + .seckey={0xCC,0x3F,0x07,0xB0,0x02,0xCF,0x11,0x8C,0x6E,0x16,0x52,0xA9,0x0E,0x53,0xB3,0xE4, 0x9A,0x02,0x59,0xB0,0x85,0x55,0x66,0x12,0xE7,0x64,0x24,0x5C,0xC9,0x87,0x68,0x94}, - "rwUVvqHuUdHHbaUgRL2tboAT8BtfnLKzu7"}, - {{0x13,0x13,0xED,0xD6,0xA9,0x99,0xEC,0x78,0x41,0xB9,0xAF,0x7B,0x63,0x4C,0x72,0x40}, - {0x03,0xAB,0x57,0x27,0x14,0xC7,0x64,0xBD,0x16,0xCC,0xC3,0xEC,0xCC,0x73,0xB8,0xC5, + .addr="rwUVvqHuUdHHbaUgRL2tboAT8BtfnLKzu7"}, + {.seed={0x13,0x13,0xED,0xD6,0xA9,0x99,0xEC,0x78,0x41,0xB9,0xAF,0x7B,0x63,0x4C,0x72,0x40}, + .pubkey={0x03,0xAB,0x57,0x27,0x14,0xC7,0x64,0xBD,0x16,0xCC,0xC3,0xEC,0xCC,0x73,0xB8,0xC5, 0x25,0xDC,0xD0,0xAE,0x44,0x7D,0xE5,0xE6,0x99,0xFC,0xA1,0x77,0xEB,0x6D,0x8A,0x3D,0x68}, - {0xCE,0xEA,0x88,0x81,0x86,0x7C,0xB8,0x75,0xEA,0x2E,0xE6,0x3D,0x16,0xDC,0x31,0x45, + .seckey={0xCE,0xEA,0x88,0x81,0x86,0x7C,0xB8,0x75,0xEA,0x2E,0xE6,0x3D,0x16,0xDC,0x31,0x45, 0xA0,0x6B,0xF9,0x84,0xD5,0xBC,0x9F,0x9C,0x3D,0xF6,0x0E,0x70,0xDC,0x4E,0x45,0xEA}, - "r4JubQs7WVESYxYbF4Uvsf7sUpJ7awzC4v"}, - {{0xC6,0x4B,0x3E,0xF6,0x4B,0x44,0x46,0xBA,0x46,0x86,0x8D,0x60,0x68,0xFD,0xBA,0x79}, - {0x02,0xDC,0xC5,0x44,0xEF,0xAF,0x0B,0xAD,0x8C,0x12,0x0F,0xA7,0xC7,0x9B,0xB9,0xF3, + .addr="r4JubQs7WVESYxYbF4Uvsf7sUpJ7awzC4v"}, + {.seed={0xC6,0x4B,0x3E,0xF6,0x4B,0x44,0x46,0xBA,0x46,0x86,0x8D,0x60,0x68,0xFD,0xBA,0x79}, + .pubkey={0x02,0xDC,0xC5,0x44,0xEF,0xAF,0x0B,0xAD,0x8C,0x12,0x0F,0xA7,0xC7,0x9B,0xB9,0xF3, 0xC8,0x8A,0x2A,0x7B,0x9C,0xB0,0x87,0xDD,0x1D,0x06,0x77,0x01,0x05,0x1D,0x77,0x92,0x34}, - {0x0E,0x26,0x89,0x9C,0xED,0xB1,0x4E,0xD2,0x90,0x99,0x2D,0x38,0xAD,0xCD,0x48,0xA2, + .seckey={0x0E,0x26,0x89,0x9C,0xED,0xB1,0x4E,0xD2,0x90,0x99,0x2D,0x38,0xAD,0xCD,0x48,0xA2, 0xCA,0x76,0x13,0xEE,0x42,0xCC,0x0E,0x00,0xEC,0xCC,0x72,0xEE,0x20,0xCE,0xF2,0x82}, - "rGpXpDnhPMqgNR2yxhiAbW2H15mJrxhfVp"}, - {{0xCE,0x2A,0x1F,0xEC,0xCE,0x8D,0x4F,0xEF,0x4D,0x33,0xCE,0x57,0xF2,0xC8,0x4E,0x6E}, - {0x02,0x14,0x57,0xE0,0x7D,0xBF,0x3A,0x09,0x07,0x30,0x10,0x57,0x64,0x5C,0x9F,0xCD, + .addr="rGpXpDnhPMqgNR2yxhiAbW2H15mJrxhfVp"}, + {.seed={0xCE,0x2A,0x1F,0xEC,0xCE,0x8D,0x4F,0xEF,0x4D,0x33,0xCE,0x57,0xF2,0xC8,0x4E,0x6E}, + .pubkey={0x02,0x14,0x57,0xE0,0x7D,0xBF,0x3A,0x09,0x07,0x30,0x10,0x57,0x64,0x5C,0x9F,0xCD, 0xE0,0x57,0x2E,0xAB,0x73,0x24,0x34,0x6C,0x1D,0x93,0xC4,0xD3,0xBB,0xF1,0x52,0x66,0x1D}, - {0x9A,0xF2,0x85,0x67,0x64,0x0B,0x12,0x29,0x45,0x2D,0xC8,0x51,0xBC,0x86,0x1C,0x24, + .seckey={0x9A,0xF2,0x85,0x67,0x64,0x0B,0x12,0x29,0x45,0x2D,0xC8,0x51,0xBC,0x86,0x1C,0x24, 0x30,0x18,0x8E,0xF8,0xFB,0x11,0x5D,0x45,0xDA,0x1F,0x67,0xE6,0xBC,0x5B,0x94,0x92}, - "r3qch21RVHuqAHapjovBW5owVHRFoiN8xB"}, - {{0xF4,0x78,0x3A,0x98,0xA3,0x8E,0x79,0xF1,0xC4,0x26,0x0C,0x04,0x6F,0xB4,0x28,0xD8}, - {0x02,0xA3,0x68,0x74,0x6B,0x3D,0x7D,0x10,0xFC,0x5C,0x51,0x8E,0x44,0xBE,0x95,0xD3, + .addr="r3qch21RVHuqAHapjovBW5owVHRFoiN8xB"}, + {.seed={0xF4,0x78,0x3A,0x98,0xA3,0x8E,0x79,0xF1,0xC4,0x26,0x0C,0x04,0x6F,0xB4,0x28,0xD8}, + .pubkey={0x02,0xA3,0x68,0x74,0x6B,0x3D,0x7D,0x10,0xFC,0x5C,0x51,0x8E,0x44,0xBE,0x95,0xD3, 0xAB,0x09,0x94,0x97,0x8C,0xD5,0x3E,0x0D,0x1B,0x4B,0x8F,0x41,0x08,0x67,0x0B,0x15,0x50}, - {0x3C,0xCC,0x43,0x41,0xDC,0x14,0x8F,0xB6,0x65,0x1E,0x47,0x6F,0xA5,0x57,0x53,0xBA, + .seckey={0x3C,0xCC,0x43,0x41,0xDC,0x14,0x8F,0xB6,0x65,0x1E,0x47,0x6F,0xA5,0x57,0x53,0xBA, 0x15,0xF1,0x14,0x4E,0x45,0x5F,0x15,0x52,0x30,0xBB,0xCD,0x4E,0x49,0x6A,0x76,0x77}, - "rs1b7fDHFMxovR6czQVekuYnRrBQJs2JjG"}, - {{0x16,0x7C,0x0B,0xB3,0xC7,0x9D,0x2B,0xC4,0x45,0xCA,0x0F,0x0C,0x16,0x20,0xF2,0xC0}, - {0x02,0x4F,0xA3,0xA0,0x8A,0xA4,0x00,0xFC,0x9D,0xC4,0x43,0x6C,0xB3,0x5F,0xC5,0x9B, + .addr="rs1b7fDHFMxovR6czQVekuYnRrBQJs2JjG"}, + {.seed={0x16,0x7C,0x0B,0xB3,0xC7,0x9D,0x2B,0xC4,0x45,0xCA,0x0F,0x0C,0x16,0x20,0xF2,0xC0}, + .pubkey={0x02,0x4F,0xA3,0xA0,0x8A,0xA4,0x00,0xFC,0x9D,0xC4,0x43,0x6C,0xB3,0x5F,0xC5,0x9B, 0xEB,0x96,0x6A,0x06,0x95,0x1C,0x90,0x78,0x87,0xD0,0xC1,0x3C,0x36,0xC8,0x9E,0xA6,0xF0}, - {0x1F,0x3F,0xBE,0xDD,0xFD,0xA4,0x79,0xA5,0xA0,0x01,0x8B,0x88,0x86,0x46,0x0C,0xC4, + .seckey={0x1F,0x3F,0xBE,0xDD,0xFD,0xA4,0x79,0xA5,0xA0,0x01,0x8B,0x88,0x86,0x46,0x0C,0xC4, 0x02,0x6A,0x62,0x14,0x42,0x02,0xA8,0x9A,0x4C,0x2B,0x1C,0x73,0xBA,0x02,0xDB,0x4E}, - "rNoQNeoYjCBQHUBhpmNi43yNVc3hQnYPGK"}, - {{0x5C,0x30,0xD9,0xEF,0xDF,0x24,0x77,0xA3,0x7C,0x1B,0x14,0x06,0xE2,0xF6,0x10,0x7A}, - {0x02,0xA1,0xEC,0x81,0xC0,0xBE,0xBC,0x84,0x78,0x09,0xD8,0x49,0x20,0x39,0x3A,0xE8, + .addr="rNoQNeoYjCBQHUBhpmNi43yNVc3hQnYPGK"}, + {.seed={0x5C,0x30,0xD9,0xEF,0xDF,0x24,0x77,0xA3,0x7C,0x1B,0x14,0x06,0xE2,0xF6,0x10,0x7A}, + .pubkey={0x02,0xA1,0xEC,0x81,0xC0,0xBE,0xBC,0x84,0x78,0x09,0xD8,0x49,0x20,0x39,0x3A,0xE8, 0x6C,0x2E,0x8E,0xF1,0x9F,0xFB,0x46,0xB8,0xD3,0x98,0x67,0x9F,0xA6,0x76,0x41,0x21,0x9B}, - {0xA2,0xE3,0x24,0xD9,0x10,0xA4,0x50,0x4A,0x6E,0x03,0x2F,0x93,0x30,0xB9,0xF7,0xFE, + .seckey={0xA2,0xE3,0x24,0xD9,0x10,0xA4,0x50,0x4A,0x6E,0x03,0x2F,0x93,0x30,0xB9,0xF7,0xFE, 0xAE,0x1D,0xEB,0x05,0x95,0x15,0xEA,0x72,0x1C,0xF0,0x0E,0xFA,0x23,0x6F,0x3D,0xA5}, - "rhPf4tS9DK2BTbUrbCqn4TpTWMC9WrgFWA"}, - {{0xD2,0xA7,0xDE,0x9A,0x73,0x18,0x28,0x05,0x99,0x2D,0x56,0x44,0xBC,0xF3,0x01,0xA9}, - {0x03,0x35,0x3C,0x34,0x9E,0x96,0x28,0xEF,0xEE,0x83,0x6E,0x74,0x03,0xF9,0x41,0xA2, + .addr="rhPf4tS9DK2BTbUrbCqn4TpTWMC9WrgFWA"}, + {.seed={0xD2,0xA7,0xDE,0x9A,0x73,0x18,0x28,0x05,0x99,0x2D,0x56,0x44,0xBC,0xF3,0x01,0xA9}, + .pubkey={0x03,0x35,0x3C,0x34,0x9E,0x96,0x28,0xEF,0xEE,0x83,0x6E,0x74,0x03,0xF9,0x41,0xA2, 0xBF,0xAA,0x33,0x51,0x49,0x52,0xA5,0xAE,0x90,0xE9,0x3C,0x86,0x23,0xA6,0x5E,0xF4,0x68}, - {0x75,0x8F,0xE1,0x17,0x2F,0x22,0xB7,0xC9,0x8B,0xF8,0x45,0xB2,0x2C,0x60,0x3D,0xF3, + .seckey={0x75,0x8F,0xE1,0x17,0x2F,0x22,0xB7,0xC9,0x8B,0xF8,0x45,0xB2,0x2C,0x60,0x3D,0xF3, 0xDB,0xB1,0xE6,0x16,0x55,0x38,0x18,0x7F,0x0A,0xE6,0x7A,0xCF,0x7D,0x59,0x1B,0xC9}, - "rKysepUdRUS3mSsb1F5qFKmwQiA2j7N6Rp"}, - {{0xDB,0xC5,0xFE,0x60,0xD5,0x0B,0xD3,0x17,0x6A,0x6E,0x17,0xF3,0x50,0x3C,0xD3,0x44}, - {0x02,0x08,0xE0,0x68,0xD4,0x94,0x17,0xE6,0xA1,0xA7,0xF2,0x20,0x99,0xA9,0xFF,0xD3, + .addr="rKysepUdRUS3mSsb1F5qFKmwQiA2j7N6Rp"}, + {.seed={0xDB,0xC5,0xFE,0x60,0xD5,0x0B,0xD3,0x17,0x6A,0x6E,0x17,0xF3,0x50,0x3C,0xD3,0x44}, + .pubkey={0x02,0x08,0xE0,0x68,0xD4,0x94,0x17,0xE6,0xA1,0xA7,0xF2,0x20,0x99,0xA9,0xFF,0xD3, 0x02,0xA1,0x1F,0xD1,0xD3,0x06,0xED,0x61,0xCF,0x3A,0xA0,0xC3,0x50,0x79,0x7C,0xFE,0x21}, - {0x56,0xEF,0x3F,0xAF,0xFC,0xDD,0x12,0x60,0xEE,0xC3,0xB7,0xC6,0xE4,0x26,0x48,0xA2, + .seckey={0x56,0xEF,0x3F,0xAF,0xFC,0xDD,0x12,0x60,0xEE,0xC3,0xB7,0xC6,0xE4,0x26,0x48,0xA2, 0x58,0x7D,0xF4,0x85,0xC8,0x18,0x8B,0x7A,0xF8,0x35,0x8D,0xF5,0x14,0x73,0xBE,0xA0}, - "rECdVqueE3m1oUn2GBuwZap6M8ohGUPi3"}, - {{0x02,0xD0,0x7A,0xEE,0xF2,0x0F,0x48,0x49,0xE0,0x8E,0x59,0x0B,0xE5,0x0F,0x8A,0xC1}, - {0x03,0x2D,0x3C,0x65,0x57,0x78,0xB4,0xE1,0x6F,0x29,0xCD,0x06,0x7F,0x7A,0x3F,0xFE, + .addr="rECdVqueE3m1oUn2GBuwZap6M8ohGUPi3"}, + {.seed={0x02,0xD0,0x7A,0xEE,0xF2,0x0F,0x48,0x49,0xE0,0x8E,0x59,0x0B,0xE5,0x0F,0x8A,0xC1}, + .pubkey={0x03,0x2D,0x3C,0x65,0x57,0x78,0xB4,0xE1,0x6F,0x29,0xCD,0x06,0x7F,0x7A,0x3F,0xFE, 0xD0,0x66,0xB5,0x2A,0x69,0x28,0x42,0xB9,0xD3,0x46,0xB6,0xD7,0xC3,0xFE,0x41,0xA3,0x4E}, - {0x18,0x2C,0x95,0x96,0x1D,0x22,0x9C,0x2C,0xF1,0x3F,0xD6,0x06,0x41,0x87,0x8C,0x04, + .seckey={0x18,0x2C,0x95,0x96,0x1D,0x22,0x9C,0x2C,0xF1,0x3F,0xD6,0x06,0x41,0x87,0x8C,0x04, 0x43,0x31,0x0D,0xAA,0x0B,0x41,0x89,0xF5,0x6C,0xBA,0xBC,0xE5,0xF7,0xA1,0xD5,0xB0}, - "rwmvZKoC8nPtyggfBBXcAwgVs5vnDX6pQu"}, - {{0xEB,0xC8,0xC9,0xA3,0xC0,0x39,0xB2,0x10,0x49,0x6C,0x95,0x3C,0xC9,0x47,0xD4,0x13}, - {0x03,0xAA,0x10,0xE0,0xA3,0x1A,0x3F,0x49,0x07,0xCF,0x96,0xA8,0x3E,0x4A,0x4C,0x26, + .addr="rwmvZKoC8nPtyggfBBXcAwgVs5vnDX6pQu"}, + {.seed={0xEB,0xC8,0xC9,0xA3,0xC0,0x39,0xB2,0x10,0x49,0x6C,0x95,0x3C,0xC9,0x47,0xD4,0x13}, + .pubkey={0x03,0xAA,0x10,0xE0,0xA3,0x1A,0x3F,0x49,0x07,0xCF,0x96,0xA8,0x3E,0x4A,0x4C,0x26, 0xA2,0xA0,0x03,0x33,0x7A,0x76,0x4D,0xFB,0x9C,0xFA,0x0C,0x9E,0x61,0x68,0x4E,0xCD,0x83}, - {0xC1,0x17,0x0A,0x11,0x40,0xCD,0x86,0xB1,0xE0,0x8D,0x00,0x1E,0x36,0x96,0xDA,0x35, + .seckey={0xC1,0x17,0x0A,0x11,0x40,0xCD,0x86,0xB1,0xE0,0x8D,0x00,0x1E,0x36,0x96,0xDA,0x35, 0x37,0x92,0x09,0x73,0xC9,0xEA,0x0B,0x88,0x72,0xA7,0xEC,0x3A,0xA0,0x00,0x70,0x5F}, - "raCPJGiwzZUP9Kq2dUEtegMFVFqPYvYUnJ"}, - {{0x58,0x17,0x37,0x52,0x1F,0x1B,0x9F,0x6D,0xFC,0xBB,0xE4,0xC8,0xE6,0x71,0x0F,0x57}, - {0x03,0xB3,0x7D,0x66,0x22,0x5B,0x46,0x1A,0xC1,0xB3,0x34,0x87,0x70,0x52,0x32,0xB7, + .addr="raCPJGiwzZUP9Kq2dUEtegMFVFqPYvYUnJ"}, + {.seed={0x58,0x17,0x37,0x52,0x1F,0x1B,0x9F,0x6D,0xFC,0xBB,0xE4,0xC8,0xE6,0x71,0x0F,0x57}, + .pubkey={0x03,0xB3,0x7D,0x66,0x22,0x5B,0x46,0x1A,0xC1,0xB3,0x34,0x87,0x70,0x52,0x32,0xB7, 0x8F,0x26,0x56,0x3B,0x9B,0xE7,0x0D,0x3A,0x49,0xCA,0xF1,0xC4,0x24,0x91,0x44,0xD1,0x93}, - {0xE3,0x40,0x86,0xA5,0x1A,0xBE,0xBE,0x68,0x6B,0x6B,0x3C,0x1F,0x8D,0xE9,0x72,0x40, + .seckey={0xE3,0x40,0x86,0xA5,0x1A,0xBE,0xBE,0x68,0x6B,0x6B,0x3C,0x1F,0x8D,0xE9,0x72,0x40, 0x4C,0x44,0x98,0xC3,0x20,0x5C,0x73,0xBF,0x7E,0x41,0x89,0xF8,0x4A,0x5C,0x34,0x71}, - "rGUdJNjYKPTscEdv32PBA3sLHFGgNJvKZw"}, - {{0xCD,0x63,0x23,0xF6,0xBB,0xAF,0xB2,0xF7,0x57,0x43,0xD0,0xA4,0x2E,0x4E,0x6F,0xC6}, - {0x02,0xB4,0x24,0x17,0xAD,0x45,0xA7,0xA2,0x53,0xC0,0x6A,0x14,0x37,0x5F,0x66,0xFF, + .addr="rGUdJNjYKPTscEdv32PBA3sLHFGgNJvKZw"}, + {.seed={0xCD,0x63,0x23,0xF6,0xBB,0xAF,0xB2,0xF7,0x57,0x43,0xD0,0xA4,0x2E,0x4E,0x6F,0xC6}, + .pubkey={0x02,0xB4,0x24,0x17,0xAD,0x45,0xA7,0xA2,0x53,0xC0,0x6A,0x14,0x37,0x5F,0x66,0xFF, 0x1E,0xFA,0xC0,0x45,0x9E,0x1E,0x95,0xCE,0x4C,0x37,0x0B,0x2B,0x09,0xE0,0x9D,0x69,0xA2}, - {0x78,0x6E,0xE9,0x7A,0xB1,0x4E,0x9D,0x14,0x67,0x92,0x2E,0xB2,0x4C,0xEB,0xAD,0x95, + .seckey={0x78,0x6E,0xE9,0x7A,0xB1,0x4E,0x9D,0x14,0x67,0x92,0x2E,0xB2,0x4C,0xEB,0xAD,0x95, 0x71,0x7F,0x09,0xBA,0xD9,0xF7,0x47,0xBB,0x6F,0x45,0xDC,0x0A,0x4B,0x7A,0x12,0x9C}, - "rK98g4BtQRoFGich8iakyB9as6twXb99bi"}, - {{0x67,0x0C,0x12,0x8C,0xE5,0x2A,0x14,0x34,0x13,0x26,0xFA,0xFB,0xDA,0x88,0x9C,0x7E}, - {0x03,0x09,0x7E,0xED,0x79,0x95,0xE2,0x52,0xDC,0x01,0xF8,0x6D,0x6C,0x88,0x63,0x60, + .addr="rK98g4BtQRoFGich8iakyB9as6twXb99bi"}, + {.seed={0x67,0x0C,0x12,0x8C,0xE5,0x2A,0x14,0x34,0x13,0x26,0xFA,0xFB,0xDA,0x88,0x9C,0x7E}, + .pubkey={0x03,0x09,0x7E,0xED,0x79,0x95,0xE2,0x52,0xDC,0x01,0xF8,0x6D,0x6C,0x88,0x63,0x60, 0x8B,0xF0,0x69,0xC7,0x99,0x30,0xFF,0x7C,0xA2,0x09,0xC1,0x58,0x0B,0x49,0xF1,0xDA,0xCA}, - {0x35,0x19,0x45,0x94,0x2E,0x70,0xDB,0x5E,0xC6,0x87,0xD8,0xFF,0xE8,0xBA,0x1F,0x9D, + .seckey={0x35,0x19,0x45,0x94,0x2E,0x70,0xDB,0x5E,0xC6,0x87,0xD8,0xFF,0xE8,0xBA,0x1F,0x9D, 0xF3,0xC7,0xF9,0x84,0xC3,0x3A,0x46,0x63,0xA1,0x0D,0x21,0x49,0x30,0xF9,0x95,0xA4}, - "raFXt1LJkm732epPXyTE1xW3DrEb7oUt6m"}, - {{0xF8,0x1F,0xAB,0xD1,0x2A,0x1E,0x8B,0x8B,0xF4,0x24,0x88,0xC0,0xCB,0x95,0xF4,0x5F}, - {0x02,0x8E,0xEA,0xEC,0x17,0xD8,0xAF,0x7E,0x2F,0x06,0xE4,0xA4,0xE1,0x2C,0x3F,0x41, + .addr="raFXt1LJkm732epPXyTE1xW3DrEb7oUt6m"}, + {.seed={0xF8,0x1F,0xAB,0xD1,0x2A,0x1E,0x8B,0x8B,0xF4,0x24,0x88,0xC0,0xCB,0x95,0xF4,0x5F}, + .pubkey={0x02,0x8E,0xEA,0xEC,0x17,0xD8,0xAF,0x7E,0x2F,0x06,0xE4,0xA4,0xE1,0x2C,0x3F,0x41, 0x42,0xA0,0x29,0x69,0x45,0x2D,0x94,0xF7,0x18,0xEE,0x47,0x80,0x1F,0x86,0x40,0x25,0x12}, - {0x53,0x1F,0xDE,0xDB,0xEB,0x50,0xC0,0x49,0x37,0xFF,0x57,0xE5,0x7E,0x8A,0x59,0x6C, + .seckey={0x53,0x1F,0xDE,0xDB,0xEB,0x50,0xC0,0x49,0x37,0xFF,0x57,0xE5,0x7E,0x8A,0x59,0x6C, 0xC3,0x75,0xF1,0xA0,0x69,0x82,0x62,0xF0,0x06,0xF3,0x1E,0xA0,0xEC,0xA7,0x14,0xEB}, - "rpCWmksupngLFRiYd7Dwn2v9qLr2ipHAFA"}, - {{0x55,0x9F,0x01,0xFB,0x87,0x06,0x8F,0x68,0xB2,0x42,0x5B,0x83,0xC8,0xFE,0x98,0xE3}, - {0x03,0x8F,0x99,0x37,0x38,0xFA,0xA0,0xA4,0x1A,0x07,0xDF,0x9D,0xE9,0x8F,0x5E,0x16, + .addr="rpCWmksupngLFRiYd7Dwn2v9qLr2ipHAFA"}, + {.seed={0x55,0x9F,0x01,0xFB,0x87,0x06,0x8F,0x68,0xB2,0x42,0x5B,0x83,0xC8,0xFE,0x98,0xE3}, + .pubkey={0x03,0x8F,0x99,0x37,0x38,0xFA,0xA0,0xA4,0x1A,0x07,0xDF,0x9D,0xE9,0x8F,0x5E,0x16, 0xBC,0x75,0x98,0xC7,0x51,0x39,0x96,0x63,0x47,0xE5,0x78,0xAB,0xE6,0x1E,0x56,0x5A,0xB8}, - {0x4E,0xC4,0x2C,0x51,0x67,0x8D,0x99,0xC2,0xA3,0x81,0x5F,0x83,0x93,0x4C,0xD6,0xC6, + .seckey={0x4E,0xC4,0x2C,0x51,0x67,0x8D,0x99,0xC2,0xA3,0x81,0x5F,0x83,0x93,0x4C,0xD6,0xC6, 0x23,0x68,0x00,0xE9,0x23,0x83,0xD0,0x5D,0xC6,0x06,0xF9,0xB5,0xA2,0x68,0xB2,0x0C}, - "r9y3WKMydDKLxN3YyxHZi4fNvm2kqMJcCb"}, - {{0x49,0x27,0x9B,0x34,0x0D,0xFF,0xFC,0xF8,0xD7,0x42,0x04,0x66,0x7E,0xF3,0xEC,0x4C}, - {0x03,0x16,0x76,0xEF,0x9A,0xED,0xE0,0x96,0xDA,0x3F,0x97,0xF3,0xF4,0x86,0x66,0xBA, + .addr="r9y3WKMydDKLxN3YyxHZi4fNvm2kqMJcCb"}, + {.seed={0x49,0x27,0x9B,0x34,0x0D,0xFF,0xFC,0xF8,0xD7,0x42,0x04,0x66,0x7E,0xF3,0xEC,0x4C}, + .pubkey={0x03,0x16,0x76,0xEF,0x9A,0xED,0xE0,0x96,0xDA,0x3F,0x97,0xF3,0xF4,0x86,0x66,0xBA, 0x73,0x41,0x03,0xBD,0xBE,0x7A,0x72,0x27,0xEF,0xC3,0x55,0xA3,0x32,0x76,0x7F,0x28,0x60}, - {0xD0,0x36,0x6F,0x72,0xF5,0x3E,0x30,0x54,0x66,0xAB,0x5E,0x8E,0x90,0x00,0xDA,0x4B, + .seckey={0xD0,0x36,0x6F,0x72,0xF5,0x3E,0x30,0x54,0x66,0xAB,0x5E,0x8E,0x90,0x00,0xDA,0x4B, 0x3C,0x29,0x18,0xA8,0x33,0xD3,0x6D,0x1C,0x36,0x61,0xF3,0x89,0x86,0x3B,0x40,0x3F}, - "rL6so6e6amZa5whfyERVc5xPS5T67AMKKS"}, - {{0xFC,0x36,0x64,0x43,0xE6,0x7B,0x56,0xD0,0xB8,0x37,0xE2,0xF4,0x04,0x38,0xD3,0xCF}, - {0x03,0xA2,0xB4,0xEA,0x91,0xE4,0x79,0x11,0xDB,0xA3,0x58,0x08,0x52,0x25,0x73,0x4F, + .addr="rL6so6e6amZa5whfyERVc5xPS5T67AMKKS"}, + {.seed={0xFC,0x36,0x64,0x43,0xE6,0x7B,0x56,0xD0,0xB8,0x37,0xE2,0xF4,0x04,0x38,0xD3,0xCF}, + .pubkey={0x03,0xA2,0xB4,0xEA,0x91,0xE4,0x79,0x11,0xDB,0xA3,0x58,0x08,0x52,0x25,0x73,0x4F, 0xF4,0xD2,0xCB,0xF2,0xE9,0x3F,0x11,0xC7,0x65,0xA5,0x91,0x39,0x7E,0x43,0xA1,0x26,0xCC}, - {0x84,0x00,0xD2,0x01,0x17,0x8E,0x9D,0x59,0xFD,0xBE,0x42,0xB3,0x03,0x22,0x5F,0x5F, + .seckey={0x84,0x00,0xD2,0x01,0x17,0x8E,0x9D,0x59,0xFD,0xBE,0x42,0xB3,0x03,0x22,0x5F,0x5F, 0x6C,0x1E,0x5B,0x44,0x8B,0xEE,0x09,0xE0,0x28,0x38,0x9D,0x9A,0xE3,0xC6,0xBA,0x81}, - "rCDbqPiELAx4yWT6tzxSsLPpRjMAk6Fch"}, - {{0xF6,0x89,0x85,0xC1,0x1D,0x7D,0x8D,0x7D,0xC7,0xE4,0xA0,0x4C,0x6C,0xE4,0x14,0x57}, - {0x02,0xCB,0x96,0xE5,0xBF,0xB8,0x22,0xCB,0x83,0xF3,0xB9,0x0C,0x17,0xB0,0x2F,0x6B, + .addr="rCDbqPiELAx4yWT6tzxSsLPpRjMAk6Fch"}, + {.seed={0xF6,0x89,0x85,0xC1,0x1D,0x7D,0x8D,0x7D,0xC7,0xE4,0xA0,0x4C,0x6C,0xE4,0x14,0x57}, + .pubkey={0x02,0xCB,0x96,0xE5,0xBF,0xB8,0x22,0xCB,0x83,0xF3,0xB9,0x0C,0x17,0xB0,0x2F,0x6B, 0x20,0x9B,0xFD,0x8C,0x50,0x3C,0x18,0x52,0xB4,0xD1,0xE9,0x63,0xD8,0x49,0xAD,0x21,0x90}, - {0x6B,0xCD,0x18,0x4E,0x5B,0x3D,0x86,0x49,0x1A,0x7E,0xFD,0x20,0x1B,0x23,0xB7,0x26, + .seckey={0x6B,0xCD,0x18,0x4E,0x5B,0x3D,0x86,0x49,0x1A,0x7E,0xFD,0x20,0x1B,0x23,0xB7,0x26, 0xCB,0x11,0x97,0x25,0x03,0xF9,0x74,0x2C,0x2D,0x4F,0x98,0xD3,0x39,0xA2,0x50,0x7E}, - "rUYQbfFYyPmSqmoy4B81MR8hZ6AfFGQRdw"}, - {{0xB2,0x2F,0x75,0xE7,0x47,0x95,0xB9,0xBE,0x63,0x05,0xD0,0xBE,0x63,0x62,0xBC,0xDE}, - {0x03,0xA2,0xA9,0x69,0x7F,0x83,0x3D,0x36,0x50,0xB0,0x34,0x4A,0xD1,0x0D,0xD9,0x58, + .addr="rUYQbfFYyPmSqmoy4B81MR8hZ6AfFGQRdw"}, + {.seed={0xB2,0x2F,0x75,0xE7,0x47,0x95,0xB9,0xBE,0x63,0x05,0xD0,0xBE,0x63,0x62,0xBC,0xDE}, + .pubkey={0x03,0xA2,0xA9,0x69,0x7F,0x83,0x3D,0x36,0x50,0xB0,0x34,0x4A,0xD1,0x0D,0xD9,0x58, 0x34,0xC5,0xA8,0x49,0x06,0xA5,0x1F,0x97,0x11,0x39,0x4C,0xFE,0xA9,0x5A,0xBF,0xD3,0x1B}, - {0x97,0x91,0xE1,0xE5,0x89,0x57,0x43,0x49,0x09,0x1C,0xB7,0xF1,0x00,0xC4,0xBE,0x3B, + .seckey={0x97,0x91,0xE1,0xE5,0x89,0x57,0x43,0x49,0x09,0x1C,0xB7,0xF1,0x00,0xC4,0xBE,0x3B, 0xA4,0xD6,0x7F,0x46,0x2F,0x4E,0x3D,0xE7,0x0B,0x3C,0x6F,0xE1,0xFB,0x49,0x9C,0x63}, - "rML4d4KyRFaCS6r77TtzvEQB6WGExUqcv9"}, - {{0x4B,0xAE,0xC7,0x44,0xC6,0x88,0xC1,0x32,0x64,0x5D,0x84,0x64,0x30,0x70,0x34,0x33}, - {0x03,0x22,0x46,0x84,0xAB,0xA5,0xB0,0x09,0x17,0x01,0xC3,0xAB,0x50,0xCA,0x14,0xA2, + .addr="rML4d4KyRFaCS6r77TtzvEQB6WGExUqcv9"}, + {.seed={0x4B,0xAE,0xC7,0x44,0xC6,0x88,0xC1,0x32,0x64,0x5D,0x84,0x64,0x30,0x70,0x34,0x33}, + .pubkey={0x03,0x22,0x46,0x84,0xAB,0xA5,0xB0,0x09,0x17,0x01,0xC3,0xAB,0x50,0xCA,0x14,0xA2, 0x68,0xC6,0xFA,0xDA,0xAF,0x60,0xD1,0xC9,0xD1,0xAC,0x7D,0x35,0x6B,0xDA,0x37,0xC4,0xBF}, - {0x32,0x76,0x88,0xBF,0xAE,0x1F,0x3D,0xCF,0xF4,0x3A,0xBC,0xFA,0xB2,0xD3,0x0C,0xF8, + .seckey={0x32,0x76,0x88,0xBF,0xAE,0x1F,0x3D,0xCF,0xF4,0x3A,0xBC,0xFA,0xB2,0xD3,0x0C,0xF8, 0xD8,0x98,0x11,0x64,0x2F,0x3A,0x3F,0xDC,0x47,0x45,0x41,0xDA,0x29,0x1D,0xB4,0x16}, - "rhuSsVQ4xKy2vDo54usSAKqfwqJPGw6G9F"}, - {{0xE4,0x35,0x2C,0xEF,0x61,0x98,0x0C,0x0D,0x96,0x43,0x37,0x1B,0x15,0x36,0x4C,0x7F}, - {0x03,0xB6,0xBF,0x72,0x89,0xCA,0x30,0x20,0xD3,0xD7,0x7F,0xB5,0x16,0x43,0x41,0x3C, + .addr="rhuSsVQ4xKy2vDo54usSAKqfwqJPGw6G9F"}, + {.seed={0xE4,0x35,0x2C,0xEF,0x61,0x98,0x0C,0x0D,0x96,0x43,0x37,0x1B,0x15,0x36,0x4C,0x7F}, + .pubkey={0x03,0xB6,0xBF,0x72,0x89,0xCA,0x30,0x20,0xD3,0xD7,0x7F,0xB5,0x16,0x43,0x41,0x3C, 0xCC,0x43,0x68,0x8D,0x78,0xB4,0xEE,0x34,0x27,0x1D,0x5C,0x75,0x8F,0xB4,0x08,0x5B,0xA4}, - {0x5A,0x30,0x70,0x43,0x3F,0x4A,0x6A,0x86,0x8A,0x26,0x44,0xAC,0x26,0xCA,0x96,0xCB, + .seckey={0x5A,0x30,0x70,0x43,0x3F,0x4A,0x6A,0x86,0x8A,0x26,0x44,0xAC,0x26,0xCA,0x96,0xCB, 0xAF,0x44,0x2D,0x62,0xBB,0xE4,0x87,0x99,0x51,0x2D,0xA8,0xCF,0xD1,0x38,0xDB,0x95}, - "rJrJVxcGtPvr9FpVdpAGZfvvjGHUKApbni"}, - {{0xD0,0x0C,0xCA,0xD5,0x2B,0x2C,0x4C,0x35,0xA6,0x2F,0x02,0x3B,0xC8,0x11,0xEE,0xA5}, - {0x03,0xAB,0x25,0xBD,0x7F,0x32,0x6C,0xCB,0x08,0x44,0xFF,0x70,0x4C,0xD1,0x22,0x09, + .addr="rJrJVxcGtPvr9FpVdpAGZfvvjGHUKApbni"}, + {.seed={0xD0,0x0C,0xCA,0xD5,0x2B,0x2C,0x4C,0x35,0xA6,0x2F,0x02,0x3B,0xC8,0x11,0xEE,0xA5}, + .pubkey={0x03,0xAB,0x25,0xBD,0x7F,0x32,0x6C,0xCB,0x08,0x44,0xFF,0x70,0x4C,0xD1,0x22,0x09, 0x52,0x67,0x0B,0x7D,0x7C,0x3E,0xE4,0xD1,0x2B,0x05,0x0B,0x5F,0x3D,0xC8,0x2A,0x16,0xE7}, - {0x55,0xA2,0x8E,0x3B,0xA9,0xA1,0x33,0xA1,0x6F,0xA2,0x0E,0x6A,0x5F,0x50,0x47,0x74, + .seckey={0x55,0xA2,0x8E,0x3B,0xA9,0xA1,0x33,0xA1,0x6F,0xA2,0x0E,0x6A,0x5F,0x50,0x47,0x74, 0x85,0xFC,0x39,0xD5,0x21,0x23,0x5B,0x46,0x18,0x94,0x37,0x1C,0x90,0x22,0x07,0x70}, - "rLsjU161cWg3apNv1tPeQ3FKs3MSYUsieW"}, - {{0x08,0x86,0xA3,0x91,0x6D,0xD5,0xF6,0x36,0xCF,0xFD,0x4E,0xD6,0x90,0x64,0x1C,0xA5}, - {0x02,0x3B,0x48,0xD2,0x6E,0xAE,0xF4,0x29,0x4F,0x29,0xA7,0x05,0xB8,0x3A,0x67,0x36, + .addr="rLsjU161cWg3apNv1tPeQ3FKs3MSYUsieW"}, + {.seed={0x08,0x86,0xA3,0x91,0x6D,0xD5,0xF6,0x36,0xCF,0xFD,0x4E,0xD6,0x90,0x64,0x1C,0xA5}, + .pubkey={0x02,0x3B,0x48,0xD2,0x6E,0xAE,0xF4,0x29,0x4F,0x29,0xA7,0x05,0xB8,0x3A,0x67,0x36, 0xDA,0xCF,0xEB,0x72,0x34,0x3A,0xA5,0x13,0x9A,0xF5,0x2E,0x91,0xC8,0xED,0x27,0x8B,0xE7}, - {0xB4,0x67,0x02,0xDA,0xD6,0xBB,0x10,0x74,0x38,0x0D,0xFB,0xA7,0x5E,0x1F,0xD4,0x07, + .seckey={0xB4,0x67,0x02,0xDA,0xD6,0xBB,0x10,0x74,0x38,0x0D,0xFB,0xA7,0x5E,0x1F,0xD4,0x07, 0x37,0xCA,0x86,0x03,0x42,0x81,0xF7,0x88,0x81,0x86,0xF7,0xFF,0x04,0xE0,0xD5,0xA9}, - "rEvTZcokycXRHtvhvNphV6z3792SxGqXXL"}, - {{0x96,0x14,0x14,0xBE,0xCC,0x47,0xFF,0xD8,0x5B,0x6A,0x58,0x7A,0x07,0x32,0x3B,0x81}, - {0x03,0x58,0x9F,0xAB,0x0E,0xE6,0xA2,0x4C,0x5F,0x69,0x5E,0xD3,0xB5,0xD7,0x55,0x84, + .addr="rEvTZcokycXRHtvhvNphV6z3792SxGqXXL"}, + {.seed={0x96,0x14,0x14,0xBE,0xCC,0x47,0xFF,0xD8,0x5B,0x6A,0x58,0x7A,0x07,0x32,0x3B,0x81}, + .pubkey={0x03,0x58,0x9F,0xAB,0x0E,0xE6,0xA2,0x4C,0x5F,0x69,0x5E,0xD3,0xB5,0xD7,0x55,0x84, 0x8D,0xAE,0xB0,0x95,0x48,0xEB,0x87,0xE8,0x51,0xF4,0x5C,0xB2,0x52,0x50,0x81,0x5E,0xAE}, - {0xDC,0x68,0x56,0x7F,0x75,0x88,0x8D,0xB3,0xB9,0xD2,0x53,0x07,0x34,0x94,0xA3,0xDD, + .seckey={0xDC,0x68,0x56,0x7F,0x75,0x88,0x8D,0xB3,0xB9,0xD2,0x53,0x07,0x34,0x94,0xA3,0xDD, 0x0E,0x54,0xBD,0x25,0x21,0x08,0x9B,0xEA,0x1D,0x3F,0xA7,0x81,0x2E,0x01,0xFB,0x12}, - "rLmR9oBFB9dk7xzshrRtCaLQqB35ZnFR7u"}, - {{0xD0,0x73,0x46,0x77,0x1B,0x7C,0xDD,0xF5,0xB8,0xC6,0xAD,0x6D,0x61,0xFA,0x39,0x5A}, - {0x03,0x2C,0x6E,0x59,0xED,0x4C,0xED,0x4F,0xDF,0x99,0x3A,0x81,0x38,0xA0,0x82,0x49, + .addr="rLmR9oBFB9dk7xzshrRtCaLQqB35ZnFR7u"}, + {.seed={0xD0,0x73,0x46,0x77,0x1B,0x7C,0xDD,0xF5,0xB8,0xC6,0xAD,0x6D,0x61,0xFA,0x39,0x5A}, + .pubkey={0x03,0x2C,0x6E,0x59,0xED,0x4C,0xED,0x4F,0xDF,0x99,0x3A,0x81,0x38,0xA0,0x82,0x49, 0xA9,0x08,0xCB,0x5F,0x4D,0x09,0x77,0x8A,0xF2,0xAD,0x2E,0xDE,0x21,0x71,0xC4,0x68,0x0D}, - {0xA7,0x19,0x7C,0x70,0xAC,0x21,0xB5,0x88,0xB5,0xB5,0x30,0x5E,0x35,0x80,0x6F,0x18, + .seckey={0xA7,0x19,0x7C,0x70,0xAC,0x21,0xB5,0x88,0xB5,0xB5,0x30,0x5E,0x35,0x80,0x6F,0x18, 0xA6,0x74,0x3F,0x2F,0x55,0x5A,0xDE,0xF7,0x9A,0xAC,0xAC,0xE9,0x5D,0x5B,0x02,0xD2}, - "rfJ21METXMqkygW1qWJHZnBPhXHAmjSKwn"}, - {{0xE6,0x8F,0x9A,0xA4,0x0E,0x73,0x2B,0x57,0x13,0xEE,0x4D,0xC3,0xDD,0xB8,0xB9,0x75}, - {0x02,0x20,0x8D,0x14,0xE1,0x30,0xF2,0x50,0xC3,0xDD,0x37,0xBF,0x89,0x13,0x46,0xF2, + .addr="rfJ21METXMqkygW1qWJHZnBPhXHAmjSKwn"}, + {.seed={0xE6,0x8F,0x9A,0xA4,0x0E,0x73,0x2B,0x57,0x13,0xEE,0x4D,0xC3,0xDD,0xB8,0xB9,0x75}, + .pubkey={0x02,0x20,0x8D,0x14,0xE1,0x30,0xF2,0x50,0xC3,0xDD,0x37,0xBF,0x89,0x13,0x46,0xF2, 0x35,0xE2,0x75,0x52,0x91,0x0C,0x1F,0x16,0x0A,0x1A,0x3B,0xF7,0xD1,0x8F,0xF9,0x87,0xBF}, - {0x17,0x49,0xCE,0xFB,0xA1,0xD2,0x03,0xBC,0x53,0x4C,0x66,0x6F,0x9B,0x4E,0x88,0x5A, + .seckey={0x17,0x49,0xCE,0xFB,0xA1,0xD2,0x03,0xBC,0x53,0x4C,0x66,0x6F,0x9B,0x4E,0x88,0x5A, 0xCB,0xBB,0x25,0x96,0x59,0x48,0x72,0xC5,0xD3,0x8C,0x35,0x29,0x44,0x6F,0x02,0xB0}, - "rKtzApfAhyETwX6uhgAxPHXELzd9snNNQp"}, - {{0x00,0x5B,0x64,0x20,0x65,0xF1,0x78,0x8D,0x52,0x3B,0x14,0x8C,0x72,0xB3,0x36,0x95}, - {0x02,0xFB,0xF2,0xC9,0x46,0x4A,0x2D,0x61,0xF0,0xD6,0x3A,0x26,0x10,0xD2,0x1E,0xAC, + .addr="rKtzApfAhyETwX6uhgAxPHXELzd9snNNQp"}, + {.seed={0x00,0x5B,0x64,0x20,0x65,0xF1,0x78,0x8D,0x52,0x3B,0x14,0x8C,0x72,0xB3,0x36,0x95}, + .pubkey={0x02,0xFB,0xF2,0xC9,0x46,0x4A,0x2D,0x61,0xF0,0xD6,0x3A,0x26,0x10,0xD2,0x1E,0xAC, 0xD3,0xAB,0x13,0xF1,0x22,0xB5,0x67,0xBD,0xA5,0xF8,0x22,0x3D,0x2A,0xE0,0x92,0x17,0xCE}, - {0xDF,0xCB,0xE0,0x55,0xF4,0x02,0x89,0x24,0xF0,0x8E,0xE0,0x7E,0x5E,0x1E,0xCA,0x70, + .seckey={0xDF,0xCB,0xE0,0x55,0xF4,0x02,0x89,0x24,0xF0,0x8E,0xE0,0x7E,0x5E,0x1E,0xCA,0x70, 0x5B,0x27,0x05,0x1C,0xAF,0x2B,0xA9,0x04,0xA4,0xCC,0x4A,0x41,0x69,0xCE,0xF3,0x56}, - "rfZGGsJvDoKuQugtqe6F3gYF2acVsakwpN"}, - {{0x04,0x85,0xFB,0x73,0xE3,0x20,0x9F,0x46,0x61,0x24,0xD7,0x0B,0x73,0x01,0x3B,0x7F}, - {0x03,0xD6,0x4A,0xFF,0x62,0x93,0xC5,0xF2,0xAB,0x1E,0x49,0xD9,0x43,0x87,0x4E,0x9D, + .addr="rfZGGsJvDoKuQugtqe6F3gYF2acVsakwpN"}, + {.seed={0x04,0x85,0xFB,0x73,0xE3,0x20,0x9F,0x46,0x61,0x24,0xD7,0x0B,0x73,0x01,0x3B,0x7F}, + .pubkey={0x03,0xD6,0x4A,0xFF,0x62,0x93,0xC5,0xF2,0xAB,0x1E,0x49,0xD9,0x43,0x87,0x4E,0x9D, 0x8C,0x1C,0x8E,0x31,0x0D,0xE3,0x12,0x9B,0xDA,0x1C,0xBE,0x68,0x35,0x67,0x10,0xCB,0x7E}, - {0xB8,0x75,0xD8,0x38,0xF9,0x8E,0xEE,0x21,0x52,0x08,0x52,0xD1,0xE6,0x8D,0x1D,0x24, + .seckey={0xB8,0x75,0xD8,0x38,0xF9,0x8E,0xEE,0x21,0x52,0x08,0x52,0xD1,0xE6,0x8D,0x1D,0x24, 0x57,0x4D,0x3D,0xD1,0x17,0xA9,0xD4,0x03,0xE3,0xB5,0xD5,0x71,0x75,0x0D,0x43,0xFE}, - "rrpPqHKZZirhRZXERvhd4uaNviXFxPfAqL"}, - {{0x6E,0x3B,0xA9,0x5A,0x9B,0x01,0x25,0x68,0x72,0xC6,0x92,0x0B,0x8E,0x41,0x66,0xC1}, - {0x03,0xA5,0x99,0x0F,0xC7,0x1B,0x92,0xB3,0x07,0x0C,0x45,0xBC,0x84,0x31,0x61,0x9C, + .addr="rrpPqHKZZirhRZXERvhd4uaNviXFxPfAqL"}, + {.seed={0x6E,0x3B,0xA9,0x5A,0x9B,0x01,0x25,0x68,0x72,0xC6,0x92,0x0B,0x8E,0x41,0x66,0xC1}, + .pubkey={0x03,0xA5,0x99,0x0F,0xC7,0x1B,0x92,0xB3,0x07,0x0C,0x45,0xBC,0x84,0x31,0x61,0x9C, 0xF8,0x86,0x20,0x90,0x20,0x16,0x26,0xB9,0xCC,0x55,0x96,0x1E,0x6B,0x90,0xE0,0x5C,0x04}, - {0x47,0xC2,0xE3,0xBB,0x91,0xC2,0xBD,0x02,0x63,0x11,0xC6,0x9D,0x09,0x6D,0x1E,0xE0, + .seckey={0x47,0xC2,0xE3,0xBB,0x91,0xC2,0xBD,0x02,0x63,0x11,0xC6,0x9D,0x09,0x6D,0x1E,0xE0, 0xD3,0x15,0x78,0x14,0xF5,0x0A,0x20,0x8C,0xF1,0x58,0xB4,0x98,0xF6,0xF5,0x45,0x61}, - "rLK3rs3qjCoMEZ4NymVLn3xQ48Y3HWxSJT"}, - {{0xDC,0x53,0x22,0x9E,0x30,0x02,0xAF,0xFB,0x9B,0x78,0x97,0xD5,0x74,0x02,0x40,0x11}, - {0x02,0xD1,0xA5,0x65,0x10,0x30,0xE8,0xE8,0x49,0x1E,0x4C,0xCB,0xDA,0xDD,0xC1,0x6D, + .addr="rLK3rs3qjCoMEZ4NymVLn3xQ48Y3HWxSJT"}, + {.seed={0xDC,0x53,0x22,0x9E,0x30,0x02,0xAF,0xFB,0x9B,0x78,0x97,0xD5,0x74,0x02,0x40,0x11}, + .pubkey={0x02,0xD1,0xA5,0x65,0x10,0x30,0xE8,0xE8,0x49,0x1E,0x4C,0xCB,0xDA,0xDD,0xC1,0x6D, 0x77,0x2B,0xBA,0x2D,0x66,0x77,0x5C,0xB7,0xF7,0x5F,0xE2,0xD8,0x17,0x68,0x93,0x36,0x0A}, - {0xF7,0x7C,0x0B,0x27,0x03,0xB6,0xEE,0x18,0x47,0x32,0x32,0x60,0xC0,0x41,0x57,0x6D, + .seckey={0xF7,0x7C,0x0B,0x27,0x03,0xB6,0xEE,0x18,0x47,0x32,0x32,0x60,0xC0,0x41,0x57,0x6D, 0x67,0xB0,0x1A,0x88,0x7E,0x14,0x35,0xF3,0xCB,0xDC,0x80,0xE3,0x63,0x4B,0xE2,0xE6}, - "rQDdUbPvEMn2GyZaHcaoLA5kb3tc2Hmcjw"}, - {{0x28,0x60,0x32,0xD1,0x23,0x21,0x4B,0xBB,0xC2,0x9A,0x46,0x47,0x6F,0x44,0x23,0xD6}, - {0x03,0xC6,0xB0,0x71,0x0D,0x59,0x5B,0x3D,0xE9,0x45,0x69,0x08,0x86,0x7C,0x7B,0x4C, + .addr="rQDdUbPvEMn2GyZaHcaoLA5kb3tc2Hmcjw"}, + {.seed={0x28,0x60,0x32,0xD1,0x23,0x21,0x4B,0xBB,0xC2,0x9A,0x46,0x47,0x6F,0x44,0x23,0xD6}, + .pubkey={0x03,0xC6,0xB0,0x71,0x0D,0x59,0x5B,0x3D,0xE9,0x45,0x69,0x08,0x86,0x7C,0x7B,0x4C, 0x0B,0x1A,0x0C,0x3D,0xDE,0x6B,0x50,0x80,0xCA,0x74,0x03,0x6A,0xBC,0x9D,0x45,0x31,0x21}, - {0xD5,0xAC,0xDA,0x20,0xD0,0x09,0xEF,0x93,0xD3,0xC0,0xF7,0xB9,0x08,0x20,0x3A,0xFA, + .seckey={0xD5,0xAC,0xDA,0x20,0xD0,0x09,0xEF,0x93,0xD3,0xC0,0xF7,0xB9,0x08,0x20,0x3A,0xFA, 0x6D,0x5C,0xDC,0xD6,0x5F,0xC3,0xD6,0x02,0x66,0x5D,0x63,0x11,0x02,0x23,0x56,0xD4}, - "rsaHcnSaQqyUh3ZHbfBXoVA2kU5DcsoLev"}, - {{0x41,0x21,0xFA,0x4A,0xDC,0x02,0xF0,0xF9,0x49,0x08,0x95,0xEA,0xF0,0x4A,0xEC,0x39}, - {0x03,0x56,0x5B,0x47,0xFF,0x02,0x01,0x64,0x64,0x0B,0x6F,0xE3,0x45,0x63,0xC6,0xD6, + .addr="rsaHcnSaQqyUh3ZHbfBXoVA2kU5DcsoLev"}, + {.seed={0x41,0x21,0xFA,0x4A,0xDC,0x02,0xF0,0xF9,0x49,0x08,0x95,0xEA,0xF0,0x4A,0xEC,0x39}, + .pubkey={0x03,0x56,0x5B,0x47,0xFF,0x02,0x01,0x64,0x64,0x0B,0x6F,0xE3,0x45,0x63,0xC6,0xD6, 0xD8,0xC7,0x95,0xFB,0x28,0x2F,0xD0,0x16,0x3E,0xDE,0xB5,0x05,0x89,0xCE,0x91,0x87,0xDA}, - {0x92,0x91,0x84,0x4E,0x08,0x44,0x8F,0x8D,0x38,0xAE,0xF8,0xB3,0xC3,0xD8,0x3A,0xF3, + .seckey={0x92,0x91,0x84,0x4E,0x08,0x44,0x8F,0x8D,0x38,0xAE,0xF8,0xB3,0xC3,0xD8,0x3A,0xF3, 0x6B,0x6C,0xD5,0x64,0x59,0x7F,0x5C,0x31,0x82,0x05,0xE7,0x38,0x82,0xF7,0xB1,0x9D}, - "rfD992xwtGE81HFt63FJ9GrmjqpMmE9DjX"}, - {{0x51,0x47,0x8C,0x21,0xB8,0xBA,0x1A,0x79,0x79,0x3E,0x4E,0x1F,0x84,0xC5,0xF2,0xAF}, - {0x02,0xBD,0xD3,0xCE,0x87,0xE0,0x71,0xD1,0x43,0x12,0x78,0x09,0x27,0x13,0x93,0x3A, + .addr="rfD992xwtGE81HFt63FJ9GrmjqpMmE9DjX"}, + {.seed={0x51,0x47,0x8C,0x21,0xB8,0xBA,0x1A,0x79,0x79,0x3E,0x4E,0x1F,0x84,0xC5,0xF2,0xAF}, + .pubkey={0x02,0xBD,0xD3,0xCE,0x87,0xE0,0x71,0xD1,0x43,0x12,0x78,0x09,0x27,0x13,0x93,0x3A, 0xB0,0xE8,0xAC,0x70,0x50,0x20,0x7E,0x03,0xC7,0x25,0xCA,0xF2,0x2C,0xE3,0x56,0xF0,0x80}, - {0xBD,0x3F,0xDA,0x5C,0x4B,0xB2,0xAA,0x89,0xD2,0x31,0x70,0x07,0xDF,0x2A,0x20,0xBB, + .seckey={0xBD,0x3F,0xDA,0x5C,0x4B,0xB2,0xAA,0x89,0xD2,0x31,0x70,0x07,0xDF,0x2A,0x20,0xBB, 0xD3,0x12,0x96,0xC1,0xCC,0x6F,0xDD,0xCB,0xC4,0xFD,0x71,0xCA,0xDC,0xAE,0x3B,0xFA}, - "rfvXs2SzVri4gK7iLo89V1VagjZFxXo9Yy"}, - {{0x7B,0x12,0x69,0x74,0x33,0xD8,0x99,0x4D,0x1E,0xBF,0x98,0x69,0x0D,0x57,0xA4,0x1C}, - {0x03,0x3A,0x6B,0x78,0x60,0x0C,0x0B,0x47,0xB2,0x81,0x63,0x47,0x3C,0x9A,0x48,0x23, + .addr="rfvXs2SzVri4gK7iLo89V1VagjZFxXo9Yy"}, + {.seed={0x7B,0x12,0x69,0x74,0x33,0xD8,0x99,0x4D,0x1E,0xBF,0x98,0x69,0x0D,0x57,0xA4,0x1C}, + .pubkey={0x03,0x3A,0x6B,0x78,0x60,0x0C,0x0B,0x47,0xB2,0x81,0x63,0x47,0x3C,0x9A,0x48,0x23, 0x5C,0xC0,0x86,0xDE,0xAF,0x92,0xA5,0xC6,0x99,0xA0,0xA9,0x66,0xBB,0xA6,0x43,0x79,0x1F}, - {0x9A,0xD7,0x7A,0xEF,0x71,0x7B,0x26,0x32,0x3C,0xCC,0xE4,0xDE,0x74,0xAB,0x69,0x0C, + .seckey={0x9A,0xD7,0x7A,0xEF,0x71,0x7B,0x26,0x32,0x3C,0xCC,0xE4,0xDE,0x74,0xAB,0x69,0x0C, 0x65,0x00,0xEE,0x33,0x43,0x73,0xA4,0xA3,0x29,0x3F,0x78,0x15,0x76,0xF7,0x43,0xB2}, - "rfPrK4Wd87KNxS8sHY4YwWvhcWafGUr6MA"}, - {{0x8F,0x94,0xC3,0xE1,0xD5,0xFF,0xBB,0x25,0x8B,0xF0,0xA1,0xF5,0xD9,0x3D,0xAE,0x95}, - {0x02,0x48,0x25,0x2D,0x93,0x06,0x30,0x16,0xD4,0xF6,0xCF,0xCB,0x4B,0xA4,0xCA,0x21, + .addr="rfPrK4Wd87KNxS8sHY4YwWvhcWafGUr6MA"}, + {.seed={0x8F,0x94,0xC3,0xE1,0xD5,0xFF,0xBB,0x25,0x8B,0xF0,0xA1,0xF5,0xD9,0x3D,0xAE,0x95}, + .pubkey={0x02,0x48,0x25,0x2D,0x93,0x06,0x30,0x16,0xD4,0xF6,0xCF,0xCB,0x4B,0xA4,0xCA,0x21, 0x7C,0x91,0x92,0x62,0x19,0x41,0x97,0xDD,0x91,0x15,0x05,0x72,0x64,0x3A,0xBB,0xA6,0x67}, - {0x86,0xD3,0x84,0xF4,0x45,0xF9,0xF7,0xA0,0x73,0x8C,0xB8,0xE1,0xAB,0xE0,0xFA,0xAB, + .seckey={0x86,0xD3,0x84,0xF4,0x45,0xF9,0xF7,0xA0,0x73,0x8C,0xB8,0xE1,0xAB,0xE0,0xFA,0xAB, 0x09,0x8B,0xDE,0xD4,0x82,0x76,0x49,0xCA,0x31,0xC0,0xB8,0x75,0xD0,0x9D,0x1C,0x33}, - "rJ7VtUxctZEXqeYud3Caakv21UJksrXouq"}, - {{0xD0,0x2B,0xFC,0x58,0xC1,0xF6,0xCC,0xEC,0x84,0xB4,0x2D,0x2A,0x80,0xD2,0x51,0x78}, - {0x03,0xBB,0xF0,0xD5,0x04,0x63,0xFE,0x0E,0x28,0x9F,0x1D,0x4F,0xF3,0x0F,0x14,0x82, + .addr="rJ7VtUxctZEXqeYud3Caakv21UJksrXouq"}, + {.seed={0xD0,0x2B,0xFC,0x58,0xC1,0xF6,0xCC,0xEC,0x84,0xB4,0x2D,0x2A,0x80,0xD2,0x51,0x78}, + .pubkey={0x03,0xBB,0xF0,0xD5,0x04,0x63,0xFE,0x0E,0x28,0x9F,0x1D,0x4F,0xF3,0x0F,0x14,0x82, 0x23,0x00,0xDB,0x6B,0x90,0x9F,0x9A,0x52,0x78,0x82,0x26,0x1F,0x72,0x18,0xF5,0x49,0xDD}, - {0x7B,0xA2,0x53,0x96,0x22,0x33,0x6E,0x49,0xD7,0xCA,0xE8,0x15,0x46,0xD1,0xD5,0xDA, + .seckey={0x7B,0xA2,0x53,0x96,0x22,0x33,0x6E,0x49,0xD7,0xCA,0xE8,0x15,0x46,0xD1,0xD5,0xDA, 0xB5,0x84,0xBD,0x35,0x68,0xEB,0x6C,0x00,0x8F,0xE4,0xDA,0xC7,0x11,0x07,0x62,0xED}, - "rPp3JQPodyon8FS3Xi7vmj7eLkVuQG3DNw"}, - {{0x70,0xD0,0xFA,0x4C,0xE3,0x0E,0xBD,0x73,0x7B,0x1E,0xEE,0x48,0x25,0x2F,0xAC,0xAE}, - {0x03,0x29,0x71,0xCB,0xB1,0x40,0xBF,0xCB,0x43,0x1D,0x23,0xDC,0x29,0x30,0x71,0x3B, + .addr="rPp3JQPodyon8FS3Xi7vmj7eLkVuQG3DNw"}, + {.seed={0x70,0xD0,0xFA,0x4C,0xE3,0x0E,0xBD,0x73,0x7B,0x1E,0xEE,0x48,0x25,0x2F,0xAC,0xAE}, + .pubkey={0x03,0x29,0x71,0xCB,0xB1,0x40,0xBF,0xCB,0x43,0x1D,0x23,0xDC,0x29,0x30,0x71,0x3B, 0xEC,0xA3,0x23,0xC3,0x65,0x98,0x52,0x27,0x5D,0x2A,0x6E,0x79,0xD2,0x43,0xC1,0xE7,0xB7}, - {0x03,0xFD,0x46,0xBA,0xC2,0x9F,0x06,0x73,0x31,0x47,0x7B,0x94,0x81,0x20,0x54,0x78, + .seckey={0x03,0xFD,0x46,0xBA,0xC2,0x9F,0x06,0x73,0x31,0x47,0x7B,0x94,0x81,0x20,0x54,0x78, 0x0D,0x48,0x81,0x6A,0x89,0xBD,0x9D,0xA2,0xF0,0x85,0xDA,0x2F,0x1D,0x01,0xC7,0x22}, - "rLj3kdAoNSaLbM78rZL4sxPzLUmnD6TJx9"}, - {{0x09,0xD8,0x00,0x1B,0xF3,0xED,0x9D,0x61,0x37,0xAA,0xF9,0x2A,0x59,0xB7,0xE5,0x83}, - {0x03,0x3F,0x4D,0x07,0xBC,0xEA,0x06,0xB8,0x52,0xD0,0x42,0xDC,0x2B,0x18,0x5E,0xAD, + .addr="rLj3kdAoNSaLbM78rZL4sxPzLUmnD6TJx9"}, + {.seed={0x09,0xD8,0x00,0x1B,0xF3,0xED,0x9D,0x61,0x37,0xAA,0xF9,0x2A,0x59,0xB7,0xE5,0x83}, + .pubkey={0x03,0x3F,0x4D,0x07,0xBC,0xEA,0x06,0xB8,0x52,0xD0,0x42,0xDC,0x2B,0x18,0x5E,0xAD, 0xEA,0xF1,0x42,0x5A,0x2B,0x73,0xD4,0xF5,0x74,0x1B,0x05,0xD4,0x9C,0x1B,0xE6,0xDF,0x59}, - {0x5B,0x6A,0x89,0x0A,0x3C,0x10,0xA9,0xAE,0x72,0x89,0xC2,0x9A,0x4D,0xFA,0xE9,0xFE, + .seckey={0x5B,0x6A,0x89,0x0A,0x3C,0x10,0xA9,0xAE,0x72,0x89,0xC2,0x9A,0x4D,0xFA,0xE9,0xFE, 0x36,0x8C,0xDA,0xE5,0x57,0x9A,0x39,0xF6,0xB6,0xE3,0xDB,0x27,0x46,0xFE,0x64,0xB1}, - "rU39rkKyaPEDdSmyqTiwAzMWHXvFDjtgvB"}, - {{0x31,0x4D,0x49,0x2B,0x8D,0x6A,0x2A,0xCC,0x18,0x6C,0x4D,0xEF,0x51,0xC7,0x5A,0x88}, - {0x03,0x7A,0x92,0xC7,0x9E,0xC2,0x64,0xFA,0x7E,0x8B,0x27,0x80,0xB1,0x43,0xF2,0x78, + .addr="rU39rkKyaPEDdSmyqTiwAzMWHXvFDjtgvB"}, + {.seed={0x31,0x4D,0x49,0x2B,0x8D,0x6A,0x2A,0xCC,0x18,0x6C,0x4D,0xEF,0x51,0xC7,0x5A,0x88}, + .pubkey={0x03,0x7A,0x92,0xC7,0x9E,0xC2,0x64,0xFA,0x7E,0x8B,0x27,0x80,0xB1,0x43,0xF2,0x78, 0x98,0x42,0xF5,0x11,0xB9,0x83,0x2D,0xEA,0xB2,0x80,0x11,0x36,0x78,0x27,0xF6,0x4C,0x06}, - {0x10,0xF6,0x70,0xA7,0x7C,0x5F,0x7C,0x47,0x35,0xE0,0x83,0xA0,0x42,0x4E,0xF2,0x9F, + .seckey={0x10,0xF6,0x70,0xA7,0x7C,0x5F,0x7C,0x47,0x35,0xE0,0x83,0xA0,0x42,0x4E,0xF2,0x9F, 0x17,0x6E,0xA8,0xEA,0xD3,0x5B,0x0B,0x4C,0x09,0xB8,0x8C,0x5B,0xFF,0x1C,0x67,0xF1}, - "rstDhfAJHDmJ6bJWMo6cmRR4BqDDBtfVZY"}, - {{0x44,0x21,0x1C,0x1C,0xAB,0x78,0x83,0xA4,0x62,0x56,0xA6,0x2D,0x59,0x39,0x47,0x7B}, - {0x02,0x88,0xEC,0xDF,0x6A,0x6C,0x38,0x63,0x4D,0xF9,0xA1,0x02,0x82,0xDD,0x65,0x5A, + .addr="rstDhfAJHDmJ6bJWMo6cmRR4BqDDBtfVZY"}, + {.seed={0x44,0x21,0x1C,0x1C,0xAB,0x78,0x83,0xA4,0x62,0x56,0xA6,0x2D,0x59,0x39,0x47,0x7B}, + .pubkey={0x02,0x88,0xEC,0xDF,0x6A,0x6C,0x38,0x63,0x4D,0xF9,0xA1,0x02,0x82,0xDD,0x65,0x5A, 0x84,0x50,0x26,0x12,0x6F,0xA9,0x60,0x66,0x20,0x25,0xD9,0xBC,0xC4,0x42,0x1C,0xE0,0x76}, - {0x31,0x7D,0x2F,0x06,0xFA,0xBC,0xC2,0xE1,0x37,0x89,0x58,0x57,0x76,0x62,0xDB,0x76, + .seckey={0x31,0x7D,0x2F,0x06,0xFA,0xBC,0xC2,0xE1,0x37,0x89,0x58,0x57,0x76,0x62,0xDB,0x76, 0x4D,0x5F,0x43,0x19,0x35,0xA4,0xB6,0x95,0x64,0xBA,0x98,0x53,0xCA,0x9C,0x94,0xAF}, - "rLdLivptphqe4SLAEWaTYoD9ur5af5dnJ1"}, - {{0xDB,0xEB,0xF6,0xC6,0x1D,0x64,0x25,0xAF,0xB4,0x52,0x85,0x83,0xA4,0x21,0x2E,0x57}, - {0x02,0x20,0x87,0xC0,0xBA,0xCD,0x25,0xEC,0x51,0x4B,0xE7,0x73,0x5E,0x1C,0x28,0x39, + .addr="rLdLivptphqe4SLAEWaTYoD9ur5af5dnJ1"}, + {.seed={0xDB,0xEB,0xF6,0xC6,0x1D,0x64,0x25,0xAF,0xB4,0x52,0x85,0x83,0xA4,0x21,0x2E,0x57}, + .pubkey={0x02,0x20,0x87,0xC0,0xBA,0xCD,0x25,0xEC,0x51,0x4B,0xE7,0x73,0x5E,0x1C,0x28,0x39, 0xA1,0x82,0xFB,0xC6,0x81,0x2B,0xE8,0xC4,0x0F,0x22,0xF4,0xE5,0x27,0x97,0x7E,0x31,0xFC}, - {0xFF,0x37,0xB9,0xD1,0x0E,0x21,0x52,0x35,0x18,0x76,0x37,0xA7,0xB8,0x96,0xB9,0x69, + .seckey={0xFF,0x37,0xB9,0xD1,0x0E,0x21,0x52,0x35,0x18,0x76,0x37,0xA7,0xB8,0x96,0xB9,0x69, 0xB9,0xA3,0x5B,0xC9,0xB1,0x6A,0x18,0x14,0x9F,0xDB,0xE2,0x69,0xEF,0x59,0x28,0x70}, - "rsYryUWhbYRiQivh693pgjnseAwPHezNj1"} + .addr="rsYryUWhbYRiQivh693pgjnseAwPHezNj1"} }; inline static TestKeyData const ed25519TestVectors[] = { - {{0xAF,0x41,0xFF,0x66,0xF7,0x5E,0xBD,0x3A,0x6B,0x18,0xFB,0x7A,0x1D,0xF6,0x1C,0x97}, - {0xED,0x48,0xCB,0xBB,0xE0,0xEE,0x7B,0x86,0x86,0xA7,0xDE,0x9F,0x0A,0x01,0x59,0x73, + {.seed={0xAF,0x41,0xFF,0x66,0xF7,0x5E,0xBD,0x3A,0x6B,0x18,0xFB,0x7A,0x1D,0xF6,0x1C,0x97}, + .pubkey={0xED,0x48,0xCB,0xBB,0xE0,0xEE,0x7B,0x86,0x86,0xA7,0xDE,0x9F,0x0A,0x01,0x59,0x73, 0x4E,0x65,0xF9,0xC3,0x69,0x94,0x7F,0x2E,0x26,0x96,0x23,0x2B,0x46,0x1E,0x55,0x32,0x13}, - {0x1A,0x10,0x97,0xFC,0xD9,0xCE,0x4E,0x1D,0xA2,0x46,0x66,0xB6,0x98,0x87,0x97,0x66, + .seckey={0x1A,0x10,0x97,0xFC,0xD9,0xCE,0x4E,0x1D,0xA2,0x46,0x66,0xB6,0x98,0x87,0x97,0x66, 0xE1,0x75,0x75,0x47,0xD1,0xD4,0xE3,0x64,0xB6,0x43,0x55,0xF7,0xC8,0x4B,0xA0,0xF3}, - "rVAEQBhWT6nZ4woEifdN3TMMdUZaxeXnR"}, - {{0x14,0x0C,0x1D,0x08,0x13,0x19,0x33,0x9C,0x79,0x9D,0xC6,0xA1,0x65,0x95,0x1B,0xE1}, - {0xED,0x3B,0xC8,0x2E,0xF4,0x5F,0x89,0x09,0xCC,0x00,0xF8,0xB7,0xAA,0xF0,0x59,0x31, + .addr="rVAEQBhWT6nZ4woEifdN3TMMdUZaxeXnR"}, + {.seed={0x14,0x0C,0x1D,0x08,0x13,0x19,0x33,0x9C,0x79,0x9D,0xC6,0xA1,0x65,0x95,0x1B,0xE1}, + .pubkey={0xED,0x3B,0xC8,0x2E,0xF4,0x5F,0x89,0x09,0xCC,0x00,0xF8,0xB7,0xAA,0xF0,0x59,0x31, 0x68,0x14,0x11,0x75,0x8C,0x11,0x71,0x24,0x87,0x50,0x66,0xC2,0x83,0x98,0xFE,0x15,0x6D}, - {0xFE,0x3E,0x5A,0x82,0xB8,0x0D,0xD8,0x2E,0x91,0x5F,0x76,0x38,0x94,0x2A,0x33,0x2C, + .seckey={0xFE,0x3E,0x5A,0x82,0xB8,0x0D,0xD8,0x2E,0x91,0x5F,0x76,0x38,0x94,0x2A,0x33,0x2C, 0xE3,0x06,0x88,0x79,0x74,0x0C,0x7E,0x90,0xE2,0x20,0xA4,0xFB,0x0B,0x37,0xCE,0xC8}, - "rK57dJ9533WtoY8NNwVWGY7ffuAc8WCcPE"}, - {{0x86,0x53,0x23,0xB6,0xE4,0x5A,0xD6,0xEE,0xFA,0x27,0x9F,0xA5,0x84,0x85,0x2E,0xD4}, - {0xED,0xE8,0x34,0x22,0xEB,0xE0,0x75,0x70,0x73,0x12,0x66,0x1E,0x4B,0x03,0x3A,0x29, + .addr="rK57dJ9533WtoY8NNwVWGY7ffuAc8WCcPE"}, + {.seed={0x86,0x53,0x23,0xB6,0xE4,0x5A,0xD6,0xEE,0xFA,0x27,0x9F,0xA5,0x84,0x85,0x2E,0xD4}, + .pubkey={0xED,0xE8,0x34,0x22,0xEB,0xE0,0x75,0x70,0x73,0x12,0x66,0x1E,0x4B,0x03,0x3A,0x29, 0xBA,0x86,0x38,0x62,0x30,0x50,0x0C,0xF2,0x8C,0xF1,0x65,0xF3,0xC2,0x1E,0x90,0x6D,0x00}, - {0xA3,0xC4,0xE2,0x43,0xA4,0x64,0x4E,0x73,0x8A,0x24,0x7A,0x59,0xAA,0xBB,0x5C,0x89, + .seckey={0xA3,0xC4,0xE2,0x43,0xA4,0x64,0x4E,0x73,0x8A,0x24,0x7A,0x59,0xAA,0xBB,0x5C,0x89, 0xE4,0x09,0x0D,0x1B,0x73,0x02,0xF2,0x45,0x82,0x64,0x87,0xC8,0x38,0xDA,0x69,0x89}, - "rfZiEDieHSHsQJ1UNfv2jYDuQawdRSBFwz"}, - {{0xC3,0x90,0x0B,0x26,0x1C,0x5E,0x7D,0x50,0xBA,0xC7,0x12,0x2D,0x97,0x35,0xDB,0xF9}, - {0xED,0x8D,0x64,0x8A,0x7B,0xD5,0xAD,0x7E,0xF2,0x41,0x5A,0x5D,0x38,0xA9,0xC1,0x3A, + .addr="rfZiEDieHSHsQJ1UNfv2jYDuQawdRSBFwz"}, + {.seed={0xC3,0x90,0x0B,0x26,0x1C,0x5E,0x7D,0x50,0xBA,0xC7,0x12,0x2D,0x97,0x35,0xDB,0xF9}, + .pubkey={0xED,0x8D,0x64,0x8A,0x7B,0xD5,0xAD,0x7E,0xF2,0x41,0x5A,0x5D,0x38,0xA9,0xC1,0x3A, 0x82,0xF0,0xD2,0x51,0x4B,0x7F,0xDC,0x47,0x57,0x04,0xC0,0x89,0x42,0x40,0x0C,0x69,0x38}, - {0x79,0xE6,0x10,0x38,0xA3,0x7E,0xB1,0x37,0xA7,0x7F,0xE0,0xDF,0x17,0xC8,0x44,0x9E, + .seckey={0x79,0xE6,0x10,0x38,0xA3,0x7E,0xB1,0x37,0xA7,0x7F,0xE0,0xDF,0x17,0xC8,0x44,0x9E, 0xA1,0x7A,0x85,0x71,0xD5,0x7D,0x33,0x15,0x4B,0x09,0x2D,0x39,0x38,0xAD,0x6A,0x0D}, - "r9Ug1JMQjGH92gpgh7kZJgF2dvwXpfmVXL"}, - {{0x63,0x9B,0x6A,0xE7,0x62,0x76,0x40,0x6D,0xBB,0x95,0x0F,0x39,0xA4,0xC2,0x77,0x27}, - {0xED,0x48,0x69,0x7E,0x9B,0x17,0x43,0x8B,0x30,0xB2,0x8C,0xBE,0x9E,0x88,0xEB,0xEE, + .addr="r9Ug1JMQjGH92gpgh7kZJgF2dvwXpfmVXL"}, + {.seed={0x63,0x9B,0x6A,0xE7,0x62,0x76,0x40,0x6D,0xBB,0x95,0x0F,0x39,0xA4,0xC2,0x77,0x27}, + .pubkey={0xED,0x48,0x69,0x7E,0x9B,0x17,0x43,0x8B,0x30,0xB2,0x8C,0xBE,0x9E,0x88,0xEB,0xEE, 0x32,0xC7,0xC8,0x64,0xAE,0x99,0x5C,0x96,0x8D,0x68,0x84,0xAD,0x1D,0x64,0xCB,0xAD,0xAA}, - {0x0D,0xC3,0xB8,0x2D,0xDA,0xA5,0x86,0x49,0x03,0x41,0xAA,0x5E,0xDF,0x0B,0x45,0x7B, + .seckey={0x0D,0xC3,0xB8,0x2D,0xDA,0xA5,0x86,0x49,0x03,0x41,0xAA,0x5E,0xDF,0x0B,0x45,0x7B, 0xCE,0x45,0x3F,0x2E,0xDC,0x0F,0x1F,0xFE,0xE1,0x2E,0xD1,0x50,0x26,0xCB,0x23,0x64}, - "rGTbZBCD2UrFooY8F7Q6dnxSpp12xX13ZE"}, - {{0xA3,0x8D,0x1E,0xB3,0x79,0x31,0x63,0xEF,0x28,0x3C,0xB9,0x88,0x85,0x80,0xFE,0xF8}, - {0xED,0x05,0xA1,0x49,0x6B,0x70,0x20,0x8C,0x26,0xDA,0x92,0xFF,0x97,0x8A,0x1C,0x69, + .addr="rGTbZBCD2UrFooY8F7Q6dnxSpp12xX13ZE"}, + {.seed={0xA3,0x8D,0x1E,0xB3,0x79,0x31,0x63,0xEF,0x28,0x3C,0xB9,0x88,0x85,0x80,0xFE,0xF8}, + .pubkey={0xED,0x05,0xA1,0x49,0x6B,0x70,0x20,0x8C,0x26,0xDA,0x92,0xFF,0x97,0x8A,0x1C,0x69, 0xAC,0xEC,0xFE,0x16,0x5A,0x1B,0x41,0x82,0xBA,0xD9,0xDC,0x13,0x17,0x21,0x7D,0xE3,0x75}, - {0xA8,0x6D,0x59,0x90,0xE1,0x63,0xEA,0x86,0x27,0x6D,0x8A,0x30,0x74,0x83,0x15,0xD4, + .seckey={0xA8,0x6D,0x59,0x90,0xE1,0x63,0xEA,0x86,0x27,0x6D,0x8A,0x30,0x74,0x83,0x15,0xD4, 0x35,0x6D,0x0B,0x65,0x10,0xE4,0xC8,0xE3,0xA4,0xA7,0xD9,0xA9,0x3E,0xE7,0x29,0x4D}, - "rDxumR1RE4rVUHHy1fHwURFw1ZLHkqqhXj"}, - {{0xF9,0x57,0x52,0x2C,0x0C,0xF4,0x93,0x67,0xF9,0x0D,0xCF,0x67,0x4D,0x26,0x31,0x47}, - {0xED,0x26,0x9F,0xD9,0xAF,0xE4,0xA5,0xC4,0x52,0x54,0x3F,0x92,0x27,0xAE,0xB2,0x93, + .addr="rDxumR1RE4rVUHHy1fHwURFw1ZLHkqqhXj"}, + {.seed={0xF9,0x57,0x52,0x2C,0x0C,0xF4,0x93,0x67,0xF9,0x0D,0xCF,0x67,0x4D,0x26,0x31,0x47}, + .pubkey={0xED,0x26,0x9F,0xD9,0xAF,0xE4,0xA5,0xC4,0x52,0x54,0x3F,0x92,0x27,0xAE,0xB2,0x93, 0xE8,0xEB,0xC4,0xC0,0x74,0x30,0x12,0x75,0xC6,0xA2,0x61,0x1D,0xC8,0x52,0xEC,0x11,0x76}, - {0x57,0xD1,0x51,0x01,0xA4,0x20,0x78,0x76,0xA2,0xFE,0xF5,0xC4,0x71,0x17,0x3E,0x48, + .seckey={0x57,0xD1,0x51,0x01,0xA4,0x20,0x78,0x76,0xA2,0xFE,0xF5,0xC4,0x71,0x17,0x3E,0x48, 0xC7,0x18,0x1B,0x52,0x10,0xA2,0xFD,0x1B,0x32,0xEE,0xF8,0xF5,0x33,0x58,0xAC,0x44}, - "rL2eCZwE2ziPN6LCLQc2NqzhEkyWSe9aYw"}, - {{0x47,0x94,0x2C,0x2D,0x0F,0xE1,0x64,0x2D,0xC5,0x23,0x6D,0x08,0x3D,0x61,0xA4,0x78}, - {0xED,0x33,0x3A,0xEC,0x09,0x30,0x3C,0x01,0x0F,0x34,0x17,0xEB,0xD3,0x21,0x36,0xC3, + .addr="rL2eCZwE2ziPN6LCLQc2NqzhEkyWSe9aYw"}, + {.seed={0x47,0x94,0x2C,0x2D,0x0F,0xE1,0x64,0x2D,0xC5,0x23,0x6D,0x08,0x3D,0x61,0xA4,0x78}, + .pubkey={0xED,0x33,0x3A,0xEC,0x09,0x30,0x3C,0x01,0x0F,0x34,0x17,0xEB,0xD3,0x21,0x36,0xC3, 0xF3,0x8C,0xF5,0x2C,0x9B,0x14,0xAF,0xB0,0xAB,0x6E,0x82,0x05,0xDB,0xEE,0x61,0x06,0x53}, - {0x76,0x73,0x6B,0x24,0xB6,0xD1,0x16,0xCD,0xCC,0x6F,0x4B,0x85,0x05,0x0B,0xBA,0x67, + .seckey={0x76,0x73,0x6B,0x24,0xB6,0xD1,0x16,0xCD,0xCC,0x6F,0x4B,0x85,0x05,0x0B,0xBA,0x67, 0xDE,0x79,0xB6,0x93,0xD0,0x57,0x10,0x35,0x18,0x71,0x6D,0x03,0x3A,0xB8,0x58,0xFE}, - "rGvzhRmWqvC67oZyAqrJqgo3MNeXahkBzj"}, - {{0xBA,0xFE,0x17,0x25,0x21,0x89,0x1D,0xDB,0x2D,0xE5,0x10,0x24,0xA7,0x21,0x9F,0xB6}, - {0xED,0x1A,0x37,0x9C,0xD6,0x6B,0x0A,0x85,0xDB,0x9F,0x0E,0xAE,0xA5,0x9E,0x8D,0xEE, + .addr="rGvzhRmWqvC67oZyAqrJqgo3MNeXahkBzj"}, + {.seed={0xBA,0xFE,0x17,0x25,0x21,0x89,0x1D,0xDB,0x2D,0xE5,0x10,0x24,0xA7,0x21,0x9F,0xB6}, + .pubkey={0xED,0x1A,0x37,0x9C,0xD6,0x6B,0x0A,0x85,0xDB,0x9F,0x0E,0xAE,0xA5,0x9E,0x8D,0xEE, 0xDC,0xA2,0x53,0x45,0xCB,0xC2,0x67,0x0E,0xFC,0x3C,0x34,0xA4,0x96,0x9F,0x80,0xDA,0x84}, - {0x8C,0x95,0x46,0x34,0xD6,0xD3,0x98,0x96,0x6B,0x28,0xBF,0xA6,0xB7,0xE2,0xEB,0x78, + .seckey={0x8C,0x95,0x46,0x34,0xD6,0xD3,0x98,0x96,0x6B,0x28,0xBF,0xA6,0xB7,0xE2,0xEB,0x78, 0x14,0x4B,0x5A,0x87,0xF6,0x64,0x91,0x01,0x3B,0xCB,0x9B,0x8C,0xCE,0xEE,0x9F,0x44}, - "r95fjtovjRNhUEuv2JJnPBFyhgXyX5p8wD"}, - {{0xD8,0x0F,0x6B,0xB7,0x88,0xBA,0x01,0xEC,0x66,0xA0,0x7A,0x74,0xA4,0xA8,0xC6,0x35}, - {0xED,0x01,0xDD,0xD2,0x59,0x15,0x86,0xBC,0x53,0x44,0x0B,0xB3,0x65,0xA9,0x33,0x04, + .addr="r95fjtovjRNhUEuv2JJnPBFyhgXyX5p8wD"}, + {.seed={0xD8,0x0F,0x6B,0xB7,0x88,0xBA,0x01,0xEC,0x66,0xA0,0x7A,0x74,0xA4,0xA8,0xC6,0x35}, + .pubkey={0xED,0x01,0xDD,0xD2,0x59,0x15,0x86,0xBC,0x53,0x44,0x0B,0xB3,0x65,0xA9,0x33,0x04, 0x01,0x1E,0xD9,0x4E,0x17,0x2C,0xC3,0xB8,0x1C,0x1B,0x9A,0x40,0xFA,0x6B,0x17,0xD4,0x91}, - {0x17,0xDB,0xCC,0x50,0x27,0xAF,0xE2,0x42,0x30,0xA4,0xE7,0x98,0xA4,0xE9,0x76,0x39, + .seckey={0x17,0xDB,0xCC,0x50,0x27,0xAF,0xE2,0x42,0x30,0xA4,0xE7,0x98,0xA4,0xE9,0x76,0x39, 0x9A,0x8F,0x86,0xB3,0x5B,0x5D,0xEB,0x70,0x34,0x1B,0x8E,0x95,0x31,0x9D,0x69,0xDC}, - "rnBDWLjCaA6q9phZCqdEp7hjqAPfwHnfPA"}, - {{0x6B,0x5D,0x6A,0xC6,0x16,0xE9,0x2E,0x67,0x75,0xCC,0xAA,0x9D,0x7E,0x0B,0x06,0xEE}, - {0xED,0xE9,0xF8,0x84,0x0B,0x73,0x6B,0xC9,0x21,0xDF,0xAF,0xF5,0x69,0x8C,0x55,0xA1, + .addr="rnBDWLjCaA6q9phZCqdEp7hjqAPfwHnfPA"}, + {.seed={0x6B,0x5D,0x6A,0xC6,0x16,0xE9,0x2E,0x67,0x75,0xCC,0xAA,0x9D,0x7E,0x0B,0x06,0xEE}, + .pubkey={0xED,0xE9,0xF8,0x84,0x0B,0x73,0x6B,0xC9,0x21,0xDF,0xAF,0xF5,0x69,0x8C,0x55,0xA1, 0xB4,0xF6,0xFD,0x4F,0xA9,0xB6,0x6C,0xC2,0x2F,0x8E,0x98,0x5B,0x6F,0xD6,0x45,0x74,0x89}, - {0x67,0x4D,0xB7,0x06,0xEC,0xC0,0xB6,0xA0,0xA7,0xF9,0x5D,0xA6,0xE4,0x30,0x68,0x54, + .seckey={0x67,0x4D,0xB7,0x06,0xEC,0xC0,0xB6,0xA0,0xA7,0xF9,0x5D,0xA6,0xE4,0x30,0x68,0x54, 0x4D,0x12,0x35,0x17,0x78,0xA7,0x55,0xC1,0x55,0x23,0xD1,0xED,0x22,0x91,0xC8,0x01}, - "ra4G5d8mvtNJyZYFdo11BrWrgAz9AvP3Gz"}, - {{0x00,0xFB,0x91,0x86,0x81,0xE9,0x7E,0xF1,0x9D,0xF0,0x95,0x74,0x55,0xC2,0x06,0xA8}, - {0xED,0x07,0x88,0x61,0x92,0x19,0x34,0xBA,0xF4,0x20,0xCC,0x6F,0x7F,0xC5,0x90,0xE5, + .addr="ra4G5d8mvtNJyZYFdo11BrWrgAz9AvP3Gz"}, + {.seed={0x00,0xFB,0x91,0x86,0x81,0xE9,0x7E,0xF1,0x9D,0xF0,0x95,0x74,0x55,0xC2,0x06,0xA8}, + .pubkey={0xED,0x07,0x88,0x61,0x92,0x19,0x34,0xBA,0xF4,0x20,0xCC,0x6F,0x7F,0xC5,0x90,0xE5, 0x1A,0x8F,0xF7,0x59,0x07,0x05,0x3B,0x0C,0xD1,0xF6,0x99,0x52,0xE1,0x22,0x8F,0x02,0x08}, - {0xB1,0x8D,0xCE,0x19,0xF5,0xFD,0xA0,0x3E,0x92,0xEA,0x23,0x8A,0x35,0xB3,0x40,0xF2, + .seckey={0xB1,0x8D,0xCE,0x19,0xF5,0xFD,0xA0,0x3E,0x92,0xEA,0x23,0x8A,0x35,0xB3,0x40,0xF2, 0xA9,0x16,0xAF,0xEE,0x16,0x8B,0x9B,0xA4,0x44,0xBC,0x6E,0x59,0xC3,0x71,0x87,0x03}, - "rDaKYn9RrKrLHdeZAixuyi24uKnxSU5odT"}, - {{0x9A,0xF1,0x00,0x6A,0x00,0x74,0x91,0x8C,0x6A,0x0F,0xFB,0xD8,0x5E,0xD5,0xCA,0x1F}, - {0xED,0x19,0x23,0x72,0xA2,0xD8,0xB4,0x80,0x23,0x5A,0x74,0x69,0x39,0xDB,0x54,0xBF, + .addr="rDaKYn9RrKrLHdeZAixuyi24uKnxSU5odT"}, + {.seed={0x9A,0xF1,0x00,0x6A,0x00,0x74,0x91,0x8C,0x6A,0x0F,0xFB,0xD8,0x5E,0xD5,0xCA,0x1F}, + .pubkey={0xED,0x19,0x23,0x72,0xA2,0xD8,0xB4,0x80,0x23,0x5A,0x74,0x69,0x39,0xDB,0x54,0xBF, 0x7E,0xCF,0x3A,0x52,0xBB,0xE8,0xFC,0xB1,0x74,0x04,0xAE,0x2D,0x62,0x69,0x7B,0x33,0x4A}, - {0x21,0x79,0xBF,0x60,0xD5,0xF4,0xAB,0x6A,0x1D,0x72,0xB9,0xC4,0xAD,0xEE,0xFB,0x04, + .seckey={0x21,0x79,0xBF,0x60,0xD5,0xF4,0xAB,0x6A,0x1D,0x72,0xB9,0xC4,0xAD,0xEE,0xFB,0x04, 0x39,0x3D,0xD0,0xB3,0x17,0xAF,0xEC,0xE3,0x6D,0xB6,0xC1,0x1E,0xE1,0x73,0x98,0xDF}, - "rUyMe6fvoJ4Lm1PnPquFabP3VSKzqjBkkw"}, - {{0xCA,0x67,0x89,0xD8,0x5A,0x4F,0x15,0x1F,0xF5,0x1C,0x9B,0x1B,0x66,0xC2,0xFD,0xE0}, - {0xED,0x6A,0x17,0x41,0x82,0xEF,0x33,0xF7,0xC3,0x68,0x3C,0x0C,0x61,0xF0,0x17,0xDB, + .addr="rUyMe6fvoJ4Lm1PnPquFabP3VSKzqjBkkw"}, + {.seed={0xCA,0x67,0x89,0xD8,0x5A,0x4F,0x15,0x1F,0xF5,0x1C,0x9B,0x1B,0x66,0xC2,0xFD,0xE0}, + .pubkey={0xED,0x6A,0x17,0x41,0x82,0xEF,0x33,0xF7,0xC3,0x68,0x3C,0x0C,0x61,0xF0,0x17,0xDB, 0x12,0x68,0xE3,0x27,0x3F,0x39,0xE5,0x39,0x63,0x59,0x17,0x84,0xAF,0x08,0x71,0xAF,0x6E}, - {0x05,0xF2,0x19,0x7C,0xCB,0x44,0xD3,0x9E,0x60,0x88,0xA3,0x57,0x38,0x04,0x9E,0x23, + .seckey={0x05,0xF2,0x19,0x7C,0xCB,0x44,0xD3,0x9E,0x60,0x88,0xA3,0x57,0x38,0x04,0x9E,0x23, 0xD8,0x8F,0x42,0xDC,0x5A,0xB2,0x10,0x16,0xB1,0x07,0xF5,0x8F,0x0D,0x26,0xAB,0x09}, - "rN9pVkhR6qUeT73hkPCKyDHsuWWyarWRK"}, - {{0x31,0x95,0xC9,0x48,0x17,0x51,0xA8,0x9E,0xC5,0x6F,0xE1,0xC9,0x63,0x30,0x78,0xAF}, - {0xED,0x9C,0xEF,0xC2,0xF4,0xB3,0xC9,0x2D,0xC8,0xC2,0x01,0xE5,0xC5,0x21,0x0C,0x6B, + .addr="rN9pVkhR6qUeT73hkPCKyDHsuWWyarWRK"}, + {.seed={0x31,0x95,0xC9,0x48,0x17,0x51,0xA8,0x9E,0xC5,0x6F,0xE1,0xC9,0x63,0x30,0x78,0xAF}, + .pubkey={0xED,0x9C,0xEF,0xC2,0xF4,0xB3,0xC9,0x2D,0xC8,0xC2,0x01,0xE5,0xC5,0x21,0x0C,0x6B, 0x27,0x77,0x91,0xF8,0x6D,0x8F,0xE1,0x7B,0x8D,0x45,0xBB,0x70,0x56,0x7F,0x59,0xB5,0xFA}, - {0x8D,0x29,0xBE,0x69,0x84,0xF2,0x49,0x7E,0xE6,0xE1,0x6F,0xB1,0xD7,0x40,0x6A,0x78, + .seckey={0x8D,0x29,0xBE,0x69,0x84,0xF2,0x49,0x7E,0xE6,0xE1,0x6F,0xB1,0xD7,0x40,0x6A,0x78, 0x93,0x5C,0xDF,0x01,0xB5,0xBB,0xE2,0xB7,0xAA,0x0B,0x44,0x08,0x82,0x56,0xC6,0xD2}, - "rJcfnmHiPFzbn5gqQYdDk3aziuNztmGLcv"}, - {{0x29,0x03,0x13,0x97,0x68,0x4B,0xD2,0x9A,0x2A,0x09,0xF8,0xFF,0xC7,0x5A,0x87,0x07}, - {0xED,0xCA,0x63,0x21,0xD4,0xA9,0xDB,0x02,0x5E,0xE9,0x2A,0x2D,0xF3,0xCD,0x12,0xFD, + .addr="rJcfnmHiPFzbn5gqQYdDk3aziuNztmGLcv"}, + {.seed={0x29,0x03,0x13,0x97,0x68,0x4B,0xD2,0x9A,0x2A,0x09,0xF8,0xFF,0xC7,0x5A,0x87,0x07}, + .pubkey={0xED,0xCA,0x63,0x21,0xD4,0xA9,0xDB,0x02,0x5E,0xE9,0x2A,0x2D,0xF3,0xCD,0x12,0xFD, 0x75,0x69,0x7B,0xA4,0x39,0x4F,0xE4,0xA5,0x08,0xE0,0x8E,0xAD,0x21,0x83,0xAC,0x58,0xFD}, - {0x07,0x71,0x01,0xB4,0x34,0xA8,0x2D,0xCF,0x05,0x2D,0xD6,0x8E,0x08,0x14,0xCA,0x25, + .seckey={0x07,0x71,0x01,0xB4,0x34,0xA8,0x2D,0xCF,0x05,0x2D,0xD6,0x8E,0x08,0x14,0xCA,0x25, 0x1A,0xCB,0x12,0x62,0x25,0x2D,0x7B,0x14,0xA0,0x09,0xF0,0x6C,0x82,0x57,0xF9,0x24}, - "rU619NnNQQGiyPXLCGRcuXP7MhZR1TxFmP"}, - {{0xBA,0x63,0xEC,0x83,0x0F,0x51,0x96,0x76,0x67,0xC6,0x00,0x1C,0x9C,0x23,0x76,0xCB}, - {0xED,0xB7,0x37,0xBD,0xAD,0x90,0x48,0x85,0xDE,0xF9,0xBE,0x25,0x2D,0xA4,0x59,0x77, + .addr="rU619NnNQQGiyPXLCGRcuXP7MhZR1TxFmP"}, + {.seed={0xBA,0x63,0xEC,0x83,0x0F,0x51,0x96,0x76,0x67,0xC6,0x00,0x1C,0x9C,0x23,0x76,0xCB}, + .pubkey={0xED,0xB7,0x37,0xBD,0xAD,0x90,0x48,0x85,0xDE,0xF9,0xBE,0x25,0x2D,0xA4,0x59,0x77, 0xC7,0x5E,0xD7,0x8B,0x47,0x39,0x83,0xCC,0x66,0xA0,0x3F,0x5D,0x22,0x4F,0x17,0x60,0x9C}, - {0x1B,0x52,0xBB,0x05,0x64,0x2E,0x9B,0x06,0xB9,0x72,0xAF,0xE0,0x77,0x5B,0x1B,0xE5, + .seckey={0x1B,0x52,0xBB,0x05,0x64,0x2E,0x9B,0x06,0xB9,0x72,0xAF,0xE0,0x77,0x5B,0x1B,0xE5, 0x68,0x92,0x14,0xCB,0x32,0xC8,0x1A,0x46,0xB3,0x99,0xDC,0x52,0xDA,0x7B,0xD2,0x4D}, - "rP7ywfUBLDeAk1uV1RnWhu1pyJnG3sQL9h"}, - {{0x60,0x4C,0x3E,0x3D,0xA8,0xB3,0xF5,0xD7,0x67,0x8A,0xA7,0xAD,0xBB,0xBE,0x54,0xCB}, - {0xED,0x51,0xD2,0x7A,0x66,0x94,0xB6,0x09,0xE4,0xE4,0x76,0xFD,0x3E,0x67,0xB6,0x52, + .addr="rP7ywfUBLDeAk1uV1RnWhu1pyJnG3sQL9h"}, + {.seed={0x60,0x4C,0x3E,0x3D,0xA8,0xB3,0xF5,0xD7,0x67,0x8A,0xA7,0xAD,0xBB,0xBE,0x54,0xCB}, + .pubkey={0xED,0x51,0xD2,0x7A,0x66,0x94,0xB6,0x09,0xE4,0xE4,0x76,0xFD,0x3E,0x67,0xB6,0x52, 0x5C,0x76,0x83,0x75,0x4E,0x8C,0x82,0x36,0xA9,0xBC,0x5E,0x5F,0xBE,0xEC,0x2B,0xCB,0x7F}, - {0xE8,0x47,0xB6,0xE5,0xE8,0xC4,0x0F,0x70,0x92,0xC7,0x2E,0xE4,0xFD,0xF6,0x94,0xC4, + .seckey={0xE8,0x47,0xB6,0xE5,0xE8,0xC4,0x0F,0x70,0x92,0xC7,0x2E,0xE4,0xFD,0xF6,0x94,0xC4, 0x9E,0xB4,0x07,0x6D,0x93,0xD0,0xE7,0xCF,0x3C,0x54,0xCD,0x3C,0xC5,0x89,0x14,0xBA}, - "rDrShqPhNLn9e1yP7UTNZCty2ghfEgQMNa"}, - {{0x98,0xCC,0x63,0x7E,0x7C,0xE8,0x3C,0xD9,0xE2,0xDF,0xFA,0xC5,0xF2,0x32,0x84,0xA0}, - {0xED,0x22,0x7A,0xF8,0x21,0x57,0x3B,0xCC,0xCB,0x17,0xD9,0xD9,0x02,0x2D,0x20,0xFF, + .addr="rDrShqPhNLn9e1yP7UTNZCty2ghfEgQMNa"}, + {.seed={0x98,0xCC,0x63,0x7E,0x7C,0xE8,0x3C,0xD9,0xE2,0xDF,0xFA,0xC5,0xF2,0x32,0x84,0xA0}, + .pubkey={0xED,0x22,0x7A,0xF8,0x21,0x57,0x3B,0xCC,0xCB,0x17,0xD9,0xD9,0x02,0x2D,0x20,0xFF, 0xB4,0x35,0x1C,0x86,0xFF,0x3F,0xE6,0x34,0xB4,0xB1,0xB2,0x72,0x81,0x0A,0x76,0x98,0x32}, - {0x38,0xD3,0x62,0x0A,0xB9,0x25,0xD6,0x14,0xCF,0x7D,0x14,0x9B,0x68,0x31,0x46,0xF5, + .seckey={0x38,0xD3,0x62,0x0A,0xB9,0x25,0xD6,0x14,0xCF,0x7D,0x14,0x9B,0x68,0x31,0x46,0xF5, 0xB0,0xAE,0x35,0x6F,0xDA,0x00,0xFD,0x85,0x8F,0xC4,0x1F,0x26,0x9B,0xCC,0x54,0x84}, - "r3uekKMfuYuxuYkZjsrmYgJG8K39VE4VJY"}, - {{0x78,0x60,0x6D,0xAF,0x5A,0x6E,0xB5,0xD5,0xBF,0x54,0x65,0xD5,0xCB,0x85,0x69,0x07}, - {0xED,0x99,0x8D,0x93,0x23,0x7C,0x18,0x9A,0x18,0xA9,0x3C,0xB7,0xA0,0x0E,0xD7,0x70, + .addr="r3uekKMfuYuxuYkZjsrmYgJG8K39VE4VJY"}, + {.seed={0x78,0x60,0x6D,0xAF,0x5A,0x6E,0xB5,0xD5,0xBF,0x54,0x65,0xD5,0xCB,0x85,0x69,0x07}, + .pubkey={0xED,0x99,0x8D,0x93,0x23,0x7C,0x18,0x9A,0x18,0xA9,0x3C,0xB7,0xA0,0x0E,0xD7,0x70, 0x1C,0xE3,0x58,0x9A,0xDA,0x7D,0x72,0xDE,0xE2,0x38,0x26,0xB3,0xFC,0x0F,0x3E,0x4A,0xD7}, - {0xA9,0x43,0xF9,0x80,0xC7,0x65,0x70,0x41,0x90,0xB1,0x19,0xEC,0x1B,0x8A,0xE1,0xAA, + .seckey={0xA9,0x43,0xF9,0x80,0xC7,0x65,0x70,0x41,0x90,0xB1,0x19,0xEC,0x1B,0x8A,0xE1,0xAA, 0x00,0xB3,0x34,0xC7,0x36,0x64,0x5A,0xB4,0x94,0xB4,0x88,0x14,0x23,0x4B,0xFF,0xD9}, - "rNUXnPHuuwqftzNbwosussmnFcUePnbwjQ"}, - {{0x3F,0x2C,0xE1,0xDF,0x92,0x1B,0xD3,0xFE,0xA3,0x1B,0x4C,0x00,0xE1,0x2D,0x7F,0x3D}, - {0xED,0x31,0x8C,0x56,0x0E,0x69,0x41,0x7D,0x2E,0xCF,0x9F,0xDB,0x61,0xAC,0xB3,0xE0, + .addr="rNUXnPHuuwqftzNbwosussmnFcUePnbwjQ"}, + {.seed={0x3F,0x2C,0xE1,0xDF,0x92,0x1B,0xD3,0xFE,0xA3,0x1B,0x4C,0x00,0xE1,0x2D,0x7F,0x3D}, + .pubkey={0xED,0x31,0x8C,0x56,0x0E,0x69,0x41,0x7D,0x2E,0xCF,0x9F,0xDB,0x61,0xAC,0xB3,0xE0, 0x7B,0x99,0xC3,0x93,0x44,0x72,0xAC,0x5E,0x7B,0xE0,0xED,0x53,0xCB,0x3D,0x92,0x76,0x18}, - {0xD9,0xF7,0x8C,0x20,0x08,0xEB,0xBF,0xFF,0x76,0xA7,0xA9,0x32,0x3E,0x7E,0x7E,0x6A, + .seckey={0xD9,0xF7,0x8C,0x20,0x08,0xEB,0xBF,0xFF,0x76,0xA7,0xA9,0x32,0x3E,0x7E,0x7E,0x6A, 0x35,0x18,0x4F,0x2F,0xB5,0xE9,0xE5,0xF1,0xE9,0x3B,0x3F,0x00,0x33,0x49,0xB8,0x42}, - "rnj6vSuUXmMW8qwdXwUKWEezmBUWjf8krG"}, - {{0x98,0x3F,0x64,0x6B,0x02,0xDB,0xE9,0x4A,0x46,0x52,0x2D,0xA5,0x5C,0x73,0x04,0x66}, - {0xED,0x26,0x87,0xDB,0x80,0x3B,0xE4,0x35,0x5F,0x19,0xC1,0x57,0x74,0xDB,0x80,0x0A, + .addr="rnj6vSuUXmMW8qwdXwUKWEezmBUWjf8krG"}, + {.seed={0x98,0x3F,0x64,0x6B,0x02,0xDB,0xE9,0x4A,0x46,0x52,0x2D,0xA5,0x5C,0x73,0x04,0x66}, + .pubkey={0xED,0x26,0x87,0xDB,0x80,0x3B,0xE4,0x35,0x5F,0x19,0xC1,0x57,0x74,0xDB,0x80,0x0A, 0xD7,0xB4,0xB8,0xA9,0xA1,0x4D,0x41,0xCE,0x4B,0xFC,0xE5,0x13,0x4E,0x69,0x6A,0x2B,0xD3}, - {0x1D,0xF8,0x5C,0xFF,0xA0,0x11,0x59,0x99,0xB6,0xF4,0x33,0xE5,0xAA,0x2A,0x92,0x5D, + .seckey={0x1D,0xF8,0x5C,0xFF,0xA0,0x11,0x59,0x99,0xB6,0xF4,0x33,0xE5,0xAA,0x2A,0x92,0x5D, 0x8E,0x12,0x63,0xD3,0xD0,0x3C,0x65,0x6E,0xA2,0x7E,0x53,0xCE,0xC0,0x4B,0x87,0x0C}, - "rLwPm1MKU645YKtuEUCtUBfaKLuUafZKpK"}, - {{0x1E,0x03,0x9B,0x04,0x57,0xC1,0x9E,0xE3,0xA9,0xBF,0x48,0x1C,0x78,0x54,0x6C,0x3A}, - {0xED,0x1C,0x54,0xB7,0x64,0x7D,0xD8,0xD3,0x59,0x4D,0x9E,0x08,0xAA,0xF0,0x22,0xC2, + .addr="rLwPm1MKU645YKtuEUCtUBfaKLuUafZKpK"}, + {.seed={0x1E,0x03,0x9B,0x04,0x57,0xC1,0x9E,0xE3,0xA9,0xBF,0x48,0x1C,0x78,0x54,0x6C,0x3A}, + .pubkey={0xED,0x1C,0x54,0xB7,0x64,0x7D,0xD8,0xD3,0x59,0x4D,0x9E,0x08,0xAA,0xF0,0x22,0xC2, 0x85,0xCD,0xC6,0x54,0x76,0xD6,0x77,0x44,0x94,0x9A,0x7D,0x0F,0xDB,0x07,0xC1,0xAA,0x44}, - {0x18,0xE9,0xD0,0xA8,0xDE,0xA7,0x55,0x3E,0xA7,0xD2,0x88,0x6F,0x49,0xBB,0x0C,0xCD, + .seckey={0x18,0xE9,0xD0,0xA8,0xDE,0xA7,0x55,0x3E,0xA7,0xD2,0x88,0x6F,0x49,0xBB,0x0C,0xCD, 0xA4,0x46,0x42,0x9D,0x88,0xE8,0x3D,0xF0,0x38,0xC5,0x47,0xD8,0x95,0xF2,0x21,0x34}, - "r4JxbSBo2LNGLLkYF7D3waf3Nhcz2wJVPD"}, - {{0x3A,0x11,0x4E,0x5D,0xE8,0xBB,0x4E,0x17,0x9C,0x9E,0x31,0x92,0xD9,0x94,0x4E,0x88}, - {0xED,0x90,0x0E,0x5C,0xF1,0xED,0xCB,0xC3,0x8E,0xE2,0x40,0x1D,0x51,0xB4,0x1E,0x19, + .addr="r4JxbSBo2LNGLLkYF7D3waf3Nhcz2wJVPD"}, + {.seed={0x3A,0x11,0x4E,0x5D,0xE8,0xBB,0x4E,0x17,0x9C,0x9E,0x31,0x92,0xD9,0x94,0x4E,0x88}, + .pubkey={0xED,0x90,0x0E,0x5C,0xF1,0xED,0xCB,0xC3,0x8E,0xE2,0x40,0x1D,0x51,0xB4,0x1E,0x19, 0x8D,0xFC,0xA8,0x2C,0x6E,0x23,0xB4,0x28,0x2D,0xBA,0xF4,0x44,0xF2,0x86,0xE0,0x9A,0xC2}, - {0x64,0x21,0xB4,0x02,0x02,0x63,0x68,0xD5,0xEB,0xFB,0xA6,0x15,0x29,0x03,0x04,0x2D, + .seckey={0x64,0x21,0xB4,0x02,0x02,0x63,0x68,0xD5,0xEB,0xFB,0xA6,0x15,0x29,0x03,0x04,0x2D, 0x05,0xC6,0xB2,0x7C,0xCD,0x52,0x2C,0x21,0xBE,0x93,0xEE,0x3F,0x13,0xB0,0x0D,0x0F}, - "rPowZBd6n845AtJsjTnpMQQ4go4aE5rSrU"}, - {{0x27,0x2C,0x1B,0x39,0x6F,0x36,0x48,0xC8,0x45,0x68,0x7E,0x27,0x0C,0x31,0xD1,0x38}, - {0xED,0x98,0xBB,0xF9,0x98,0x65,0xED,0xAF,0x1A,0x03,0x81,0xE7,0x14,0x9E,0x49,0xC1, + .addr="rPowZBd6n845AtJsjTnpMQQ4go4aE5rSrU"}, + {.seed={0x27,0x2C,0x1B,0x39,0x6F,0x36,0x48,0xC8,0x45,0x68,0x7E,0x27,0x0C,0x31,0xD1,0x38}, + .pubkey={0xED,0x98,0xBB,0xF9,0x98,0x65,0xED,0xAF,0x1A,0x03,0x81,0xE7,0x14,0x9E,0x49,0xC1, 0x29,0x03,0x99,0x63,0xD4,0x60,0xB1,0x65,0xF5,0x9F,0x1D,0xDA,0xA9,0x5B,0x31,0xA9,0x25}, - {0x03,0x2C,0x27,0x61,0xB0,0x0F,0xA4,0xAB,0x89,0xB4,0x03,0x67,0x83,0x42,0x12,0x94, + .seckey={0x03,0x2C,0x27,0x61,0xB0,0x0F,0xA4,0xAB,0x89,0xB4,0x03,0x67,0x83,0x42,0x12,0x94, 0x16,0xAA,0xB1,0x3A,0x39,0x5F,0x3D,0x02,0x87,0xE4,0x4C,0xC9,0xDF,0x36,0x91,0xFD}, - "rsdP5KvUtanQDrDZ7gebdSMQN2zJveJL7n"}, - {{0x22,0xA2,0x2B,0xCA,0xB7,0xC7,0xD2,0x27,0xEC,0xD2,0x72,0x6A,0x4E,0xF2,0x8A,0x60}, - {0xED,0x75,0xBC,0x6C,0x7D,0x63,0x18,0x08,0x5F,0xEE,0xE3,0x66,0x81,0x34,0x3B,0x25, + .addr="rsdP5KvUtanQDrDZ7gebdSMQN2zJveJL7n"}, + {.seed={0x22,0xA2,0x2B,0xCA,0xB7,0xC7,0xD2,0x27,0xEC,0xD2,0x72,0x6A,0x4E,0xF2,0x8A,0x60}, + .pubkey={0xED,0x75,0xBC,0x6C,0x7D,0x63,0x18,0x08,0x5F,0xEE,0xE3,0x66,0x81,0x34,0x3B,0x25, 0xB5,0x23,0xD9,0x59,0xE7,0x1C,0xF8,0x93,0x08,0xD6,0xD6,0xF9,0xEF,0x10,0xF9,0xD7,0x23}, - {0x87,0xD1,0x07,0xAD,0x10,0x12,0x58,0x85,0xEA,0xF1,0xCD,0x2D,0x48,0x96,0xE2,0xA7, + .seckey={0x87,0xD1,0x07,0xAD,0x10,0x12,0x58,0x85,0xEA,0xF1,0xCD,0x2D,0x48,0x96,0xE2,0xA7, 0x17,0x11,0x28,0x4F,0xC1,0xCC,0x33,0x5E,0x83,0x6A,0x31,0xBD,0xB2,0xBD,0xA0,0xFE}, - "rwpWQzs9x9tZofsf1PdZA7E7LBzjgzC5DD"}, - {{0xF7,0xDA,0xC7,0x2E,0x0A,0x0B,0x75,0xB9,0x04,0x0C,0xFB,0xE9,0xAA,0x5B,0x96,0xAE}, - {0xED,0x82,0xE4,0x7D,0xF7,0xC4,0x6B,0xBF,0x23,0x43,0x65,0x51,0xB8,0xD0,0xF6,0x21, + .addr="rwpWQzs9x9tZofsf1PdZA7E7LBzjgzC5DD"}, + {.seed={0xF7,0xDA,0xC7,0x2E,0x0A,0x0B,0x75,0xB9,0x04,0x0C,0xFB,0xE9,0xAA,0x5B,0x96,0xAE}, + .pubkey={0xED,0x82,0xE4,0x7D,0xF7,0xC4,0x6B,0xBF,0x23,0x43,0x65,0x51,0xB8,0xD0,0xF6,0x21, 0x8B,0x98,0xE5,0x92,0x9E,0x57,0xDB,0x82,0x52,0x43,0xDB,0x74,0x77,0x7B,0x7A,0x6D,0x86}, - {0xA7,0x53,0xFD,0x0F,0xCC,0x59,0xA7,0xCA,0x47,0x74,0x46,0x5C,0xAC,0x2C,0x3C,0x5D, + .seckey={0xA7,0x53,0xFD,0x0F,0xCC,0x59,0xA7,0xCA,0x47,0x74,0x46,0x5C,0xAC,0x2C,0x3C,0x5D, 0x3E,0x59,0x8E,0x7C,0x68,0x99,0x81,0x91,0x09,0x15,0x3D,0x40,0x7A,0x3E,0xA5,0x11}, - "rfSP8muPdQGdevuRZmt1SDRWbzuLAk2S4C"}, - {{0x4B,0xC7,0x86,0x0F,0x05,0x33,0xEA,0x29,0xF0,0x17,0x45,0x94,0xB1,0x80,0x79,0xD9}, - {0xED,0x09,0xE6,0x5E,0xFA,0xD0,0x39,0x72,0x82,0x33,0xA6,0xE4,0x3D,0xDB,0xD5,0x97, + .addr="rfSP8muPdQGdevuRZmt1SDRWbzuLAk2S4C"}, + {.seed={0x4B,0xC7,0x86,0x0F,0x05,0x33,0xEA,0x29,0xF0,0x17,0x45,0x94,0xB1,0x80,0x79,0xD9}, + .pubkey={0xED,0x09,0xE6,0x5E,0xFA,0xD0,0x39,0x72,0x82,0x33,0xA6,0xE4,0x3D,0xDB,0xD5,0x97, 0x17,0x56,0xB8,0xB3,0xAA,0xE4,0x0C,0x37,0x28,0x3E,0x21,0x73,0x4B,0x2C,0x31,0x3C,0x4E}, - {0x07,0xC0,0x48,0xA5,0x27,0x72,0xCC,0xC0,0xCD,0x49,0xF3,0xE9,0xFC,0xEB,0x0A,0x89, + .seckey={0x07,0xC0,0x48,0xA5,0x27,0x72,0xCC,0xC0,0xCD,0x49,0xF3,0xE9,0xFC,0xEB,0x0A,0x89, 0xAC,0xCD,0x70,0xB0,0x72,0x43,0x60,0xFA,0x4E,0xBD,0x87,0x49,0x7A,0x43,0x3A,0x1F}, - "rhPpCGk6r6f1bLHaBvT1fc4sCknWLi6Kyd"}, - {{0x64,0x66,0x2B,0xCD,0x83,0x06,0x43,0xCC,0x2B,0x59,0x07,0x2F,0x3B,0x6C,0x6C,0xF7}, - {0xED,0xC8,0x1E,0x2D,0x5B,0xF8,0x3E,0xEB,0x9A,0x70,0x46,0x56,0x67,0x37,0x05,0xD0, + .addr="rhPpCGk6r6f1bLHaBvT1fc4sCknWLi6Kyd"}, + {.seed={0x64,0x66,0x2B,0xCD,0x83,0x06,0x43,0xCC,0x2B,0x59,0x07,0x2F,0x3B,0x6C,0x6C,0xF7}, + .pubkey={0xED,0xC8,0x1E,0x2D,0x5B,0xF8,0x3E,0xEB,0x9A,0x70,0x46,0x56,0x67,0x37,0x05,0xD0, 0x27,0xAC,0x07,0xDC,0x33,0x37,0xC2,0xD1,0x67,0x30,0xB6,0xEC,0x6D,0x6D,0x3B,0xD8,0x39}, - {0x8E,0xE8,0xF0,0x87,0x00,0x3E,0xA3,0x2E,0x94,0x62,0x2A,0x6F,0x3E,0x38,0xCC,0x1B, + .seckey={0x8E,0xE8,0xF0,0x87,0x00,0x3E,0xA3,0x2E,0x94,0x62,0x2A,0x6F,0x3E,0x38,0xCC,0x1B, 0xA9,0xFD,0x2E,0x19,0x7A,0x71,0x3C,0xB6,0x12,0x54,0x1A,0x1F,0x8F,0xBD,0xBC,0x57}, - "rMBy6xJfbGV4RNDy8mdBVDYTpoGArkEu95"}, - {{0xC2,0xBB,0x19,0xF7,0x2A,0x8B,0x59,0x5A,0xAF,0x57,0x34,0xB8,0xD0,0xCF,0x35,0x63}, - {0xED,0xB0,0x76,0xC8,0x12,0x11,0x54,0x3C,0x9D,0x49,0xF2,0xED,0x2E,0x7D,0xD5,0xF4, + .addr="rMBy6xJfbGV4RNDy8mdBVDYTpoGArkEu95"}, + {.seed={0xC2,0xBB,0x19,0xF7,0x2A,0x8B,0x59,0x5A,0xAF,0x57,0x34,0xB8,0xD0,0xCF,0x35,0x63}, + .pubkey={0xED,0xB0,0x76,0xC8,0x12,0x11,0x54,0x3C,0x9D,0x49,0xF2,0xED,0x2E,0x7D,0xD5,0xF4, 0x94,0x3B,0x74,0x55,0xB3,0xB1,0x93,0x73,0xF9,0x71,0x80,0x1F,0x9B,0x6D,0xC0,0xC3,0x4C}, - {0x1A,0xD7,0x16,0x90,0x7D,0x6A,0xDA,0x1A,0x27,0xD9,0x3C,0xB9,0xC6,0x29,0x90,0x24, + .seckey={0x1A,0xD7,0x16,0x90,0x7D,0x6A,0xDA,0x1A,0x27,0xD9,0x3C,0xB9,0xC6,0x29,0x90,0x24, 0x59,0x38,0x0B,0x5C,0x62,0xC0,0x91,0x19,0x70,0x45,0x57,0xA1,0x48,0xB3,0x57,0x55}, - "rDoEhMUA33qViCmhx69WxvmtfFXC55Avah"}, - {{0xCF,0xE0,0x00,0x67,0x9F,0x8E,0x7F,0xEC,0x63,0xDA,0x7C,0x9E,0x59,0xFB,0xD5,0xE9}, - {0xED,0x2A,0x83,0x43,0xF3,0x6B,0xCC,0x64,0xB3,0x9A,0x24,0xAD,0xE1,0xCB,0x94,0x39, + .addr="rDoEhMUA33qViCmhx69WxvmtfFXC55Avah"}, + {.seed={0xCF,0xE0,0x00,0x67,0x9F,0x8E,0x7F,0xEC,0x63,0xDA,0x7C,0x9E,0x59,0xFB,0xD5,0xE9}, + .pubkey={0xED,0x2A,0x83,0x43,0xF3,0x6B,0xCC,0x64,0xB3,0x9A,0x24,0xAD,0xE1,0xCB,0x94,0x39, 0x77,0xFC,0x33,0x66,0xE8,0xA2,0x50,0xD7,0x24,0x7D,0x06,0xE2,0xD8,0x07,0x75,0x38,0xC2}, - {0x25,0x52,0x3F,0x87,0xBB,0x6F,0x80,0x84,0x0C,0x40,0x3E,0xBA,0x50,0xE9,0x1D,0x46, + .seckey={0x25,0x52,0x3F,0x87,0xBB,0x6F,0x80,0x84,0x0C,0x40,0x3E,0xBA,0x50,0xE9,0x1D,0x46, 0x13,0x9F,0xA8,0x7F,0x25,0x0B,0x50,0x34,0x50,0xCC,0x35,0x83,0x41,0x94,0x01,0x5D}, - "rJ8ry1zRxQTnp1fu9qSxPNhxfWtFb2caio"}, - {{0x49,0x47,0xC6,0x34,0x42,0x84,0x19,0xC3,0x1A,0x26,0x7D,0xFF,0x87,0xDA,0x69,0xED}, - {0xED,0xD2,0x87,0xFB,0xDA,0xC8,0x4E,0x88,0x29,0x47,0x13,0x87,0x30,0x20,0x2B,0xF9, + .addr="rJ8ry1zRxQTnp1fu9qSxPNhxfWtFb2caio"}, + {.seed={0x49,0x47,0xC6,0x34,0x42,0x84,0x19,0xC3,0x1A,0x26,0x7D,0xFF,0x87,0xDA,0x69,0xED}, + .pubkey={0xED,0xD2,0x87,0xFB,0xDA,0xC8,0x4E,0x88,0x29,0x47,0x13,0x87,0x30,0x20,0x2B,0xF9, 0xA4,0xCA,0x90,0x10,0x38,0xC3,0x7D,0xD8,0x00,0xB5,0xD2,0xBA,0xBB,0x57,0xE6,0xED,0xD1}, - {0xBB,0xE6,0x3C,0x1F,0x2B,0x03,0x71,0xDC,0xEB,0x27,0x4E,0x64,0x48,0xE9,0x05,0xE0, + .seckey={0xBB,0xE6,0x3C,0x1F,0x2B,0x03,0x71,0xDC,0xEB,0x27,0x4E,0x64,0x48,0xE9,0x05,0xE0, 0x95,0xE2,0x04,0xB8,0xDA,0x32,0xD4,0x96,0x45,0x15,0xC3,0x77,0xA9,0xB4,0xC3,0x8A}, - "rBTmbytPkthAonMoFEFdr9ewQc9d5AQdVE"}, - {{0x6B,0xC3,0x37,0xB0,0x07,0x6F,0xB7,0x60,0xAF,0x78,0x58,0x9A,0xDE,0xDA,0x0D,0x71}, - {0xED,0x7E,0x0D,0xBB,0x97,0xD6,0x0D,0xB6,0x74,0x57,0xB0,0x22,0x28,0xF0,0x20,0x34, + .addr="rBTmbytPkthAonMoFEFdr9ewQc9d5AQdVE"}, + {.seed={0x6B,0xC3,0x37,0xB0,0x07,0x6F,0xB7,0x60,0xAF,0x78,0x58,0x9A,0xDE,0xDA,0x0D,0x71}, + .pubkey={0xED,0x7E,0x0D,0xBB,0x97,0xD6,0x0D,0xB6,0x74,0x57,0xB0,0x22,0x28,0xF0,0x20,0x34, 0x9F,0x1B,0xCD,0x89,0xA0,0xB5,0xFB,0xD3,0x2B,0xE8,0x8C,0x5B,0x2A,0x8D,0x50,0x5A,0x4F}, - {0xEE,0xC0,0x42,0x75,0x85,0x15,0x24,0xC5,0x30,0x62,0xE6,0x21,0xBA,0xC7,0x02,0xEC, + .seckey={0xEE,0xC0,0x42,0x75,0x85,0x15,0x24,0xC5,0x30,0x62,0xE6,0x21,0xBA,0xC7,0x02,0xEC, 0x3C,0x46,0x3F,0xC1,0x7D,0xDF,0x03,0xD4,0xAA,0xF4,0xF4,0x83,0xCE,0xB1,0x59,0xF2}, - "r3q66ccmLGZHdsStqGDawGt8ek8DKthLU"}, - {{0xD8,0xE6,0x93,0x8A,0x9F,0x79,0xDE,0x01,0x3B,0x76,0x47,0x9E,0xD5,0x1C,0xB5,0xF4}, - {0xED,0x2A,0xCC,0x07,0xAC,0x51,0x1E,0xEB,0x5F,0x0C,0x37,0xC4,0x5B,0x4D,0x7F,0x58, + .addr="r3q66ccmLGZHdsStqGDawGt8ek8DKthLU"}, + {.seed={0xD8,0xE6,0x93,0x8A,0x9F,0x79,0xDE,0x01,0x3B,0x76,0x47,0x9E,0xD5,0x1C,0xB5,0xF4}, + .pubkey={0xED,0x2A,0xCC,0x07,0xAC,0x51,0x1E,0xEB,0x5F,0x0C,0x37,0xC4,0x5B,0x4D,0x7F,0x58, 0xBE,0x16,0x9B,0xF7,0x30,0xE8,0x6D,0x5B,0x39,0x3F,0x61,0x7A,0xFD,0xDA,0xFA,0x6F,0xBA}, - {0x97,0x18,0xB2,0x55,0xAE,0xC6,0x25,0x34,0xE9,0xC7,0x7C,0x1F,0xC7,0xCF,0xA9,0x9A, + .seckey={0x97,0x18,0xB2,0x55,0xAE,0xC6,0x25,0x34,0xE9,0xC7,0x7C,0x1F,0xC7,0xCF,0xA9,0x9A, 0x24,0x6E,0x41,0xFF,0xB5,0x19,0x7F,0x14,0xB7,0x33,0x17,0x4A,0xBE,0x06,0x5E,0x9C}, - "rMS5BvvLjC1HCDtL81M394JAbg7ymqymiV"}, - {{0xB6,0x14,0xFA,0x0C,0x4A,0x18,0x83,0x96,0x5E,0x04,0x93,0x55,0x83,0xBC,0x7D,0x88}, - {0xED,0xB0,0xD8,0xE9,0x32,0x13,0xB6,0x10,0xF9,0x0B,0xF7,0xEE,0x59,0x01,0x52,0xFD, + .addr="rMS5BvvLjC1HCDtL81M394JAbg7ymqymiV"}, + {.seed={0xB6,0x14,0xFA,0x0C,0x4A,0x18,0x83,0x96,0x5E,0x04,0x93,0x55,0x83,0xBC,0x7D,0x88}, + .pubkey={0xED,0xB0,0xD8,0xE9,0x32,0x13,0xB6,0x10,0xF9,0x0B,0xF7,0xEE,0x59,0x01,0x52,0xFD, 0x21,0xCA,0x9D,0xC9,0x4B,0xCD,0x02,0x03,0x45,0x6C,0xF2,0x86,0xF2,0x65,0x52,0x2B,0xCA}, - {0x3B,0xD7,0x9F,0x6C,0xFC,0x9F,0x74,0x03,0x0E,0xB1,0xE9,0x1E,0xA9,0x78,0x0E,0x82, + .seckey={0x3B,0xD7,0x9F,0x6C,0xFC,0x9F,0x74,0x03,0x0E,0xB1,0xE9,0x1E,0xA9,0x78,0x0E,0x82, 0x77,0x64,0x4D,0x4B,0x9C,0x15,0xD7,0x59,0x05,0xC4,0x90,0x57,0xB1,0x61,0x04,0x43}, - "rGGVv46pEy7R8G7tcYsPAsjWhR6WCA7L4T"}, - {{0xA7,0xA2,0x39,0xD1,0x52,0xA2,0x89,0xE4,0xD5,0x02,0xC2,0x43,0x8E,0xFF,0x6B,0x71}, - {0xED,0xFE,0xA6,0xAF,0x9A,0xEA,0x34,0xC8,0xA2,0x78,0x3C,0xED,0x52,0x25,0x97,0x28, + .addr="rGGVv46pEy7R8G7tcYsPAsjWhR6WCA7L4T"}, + {.seed={0xA7,0xA2,0x39,0xD1,0x52,0xA2,0x89,0xE4,0xD5,0x02,0xC2,0x43,0x8E,0xFF,0x6B,0x71}, + .pubkey={0xED,0xFE,0xA6,0xAF,0x9A,0xEA,0x34,0xC8,0xA2,0x78,0x3C,0xED,0x52,0x25,0x97,0x28, 0x1E,0x2E,0x0E,0x47,0xC4,0x8B,0x5D,0x7E,0xDD,0x80,0xAE,0x6B,0xE7,0x30,0x05,0x14,0x84}, - {0xEA,0x8D,0x5B,0x9F,0xA2,0x5B,0x56,0x31,0x97,0x07,0x73,0x6C,0x3B,0xB2,0xA0,0x24, + .seckey={0xEA,0x8D,0x5B,0x9F,0xA2,0x5B,0x56,0x31,0x97,0x07,0x73,0x6C,0x3B,0xB2,0xA0,0x24, 0x66,0x84,0xBA,0x72,0x3E,0xA8,0x64,0x9D,0xDE,0xB7,0x66,0x33,0x54,0x49,0xCE,0xE7}, - "rSZmSEymX238fWeoscc1DqvP3GuXNmNDR"}, - {{0xF6,0xA6,0xE7,0x2C,0xD6,0x32,0x8B,0x7B,0xAF,0x46,0x32,0xC3,0xC5,0x08,0x6C,0xE7}, - {0xED,0x80,0xE2,0x78,0x44,0x83,0x23,0x7B,0xE7,0x14,0xF6,0xE6,0x65,0x34,0x64,0xF2, + .addr="rSZmSEymX238fWeoscc1DqvP3GuXNmNDR"}, + {.seed={0xF6,0xA6,0xE7,0x2C,0xD6,0x32,0x8B,0x7B,0xAF,0x46,0x32,0xC3,0xC5,0x08,0x6C,0xE7}, + .pubkey={0xED,0x80,0xE2,0x78,0x44,0x83,0x23,0x7B,0xE7,0x14,0xF6,0xE6,0x65,0x34,0x64,0xF2, 0x72,0x16,0x11,0xEF,0x4A,0xAC,0x79,0xCC,0xD9,0xA5,0x5E,0x94,0xBB,0x5C,0x8E,0x9A,0x0D}, - {0x9A,0x0F,0xB3,0x5E,0x98,0x6E,0x7C,0x86,0x6E,0xB1,0x99,0x5C,0xD6,0xFA,0xFC,0x97, + .seckey={0x9A,0x0F,0xB3,0x5E,0x98,0x6E,0x7C,0x86,0x6E,0xB1,0x99,0x5C,0xD6,0xFA,0xFC,0x97, 0xE7,0xE3,0x93,0x59,0xF8,0xA9,0x4E,0xBC,0x2C,0xA0,0x3D,0xD2,0x35,0x45,0xCE,0x75}, - "rDdnbVT5k4GDWtqE8qVUTTVvj1Hpsxnm31"}, - {{0x52,0x9B,0x42,0xD5,0x7F,0xC7,0x88,0x9B,0x76,0x37,0xA9,0x3A,0x78,0xF3,0x25,0xA8}, - {0xED,0x42,0xFD,0x63,0x56,0x5F,0x4F,0xBD,0xBA,0x05,0x64,0x07,0xB0,0xD1,0xB7,0xEC, + .addr="rDdnbVT5k4GDWtqE8qVUTTVvj1Hpsxnm31"}, + {.seed={0x52,0x9B,0x42,0xD5,0x7F,0xC7,0x88,0x9B,0x76,0x37,0xA9,0x3A,0x78,0xF3,0x25,0xA8}, + .pubkey={0xED,0x42,0xFD,0x63,0x56,0x5F,0x4F,0xBD,0xBA,0x05,0x64,0x07,0xB0,0xD1,0xB7,0xEC, 0x22,0x57,0x1F,0x40,0x16,0x88,0xA1,0xA1,0x4E,0x4F,0x64,0xF8,0xF7,0xC5,0xD1,0x75,0x75}, - {0xB0,0xC0,0x27,0x7E,0x16,0xF9,0xAD,0xEF,0x10,0xC8,0x69,0xBE,0x35,0x21,0xD2,0xE9, + .seckey={0xB0,0xC0,0x27,0x7E,0x16,0xF9,0xAD,0xEF,0x10,0xC8,0x69,0xBE,0x35,0x21,0xD2,0xE9, 0x3C,0x88,0x97,0x77,0xAD,0xE9,0xAE,0x0C,0x9E,0x54,0xDD,0xB4,0x06,0xDE,0xD3,0xF0}, - "rNLiq2KbE7N4qTcuiFzTAJ34hHKgaSAp22"}, - {{0x39,0xCF,0x81,0xC4,0x1F,0xD3,0xB3,0x2E,0x94,0x55,0x86,0xC7,0x6C,0xF9,0x9B,0x6E}, - {0xED,0x79,0x65,0x13,0x48,0x56,0x93,0xFD,0xE0,0x53,0x85,0x5A,0x95,0xB9,0x17,0x6A, + .addr="rNLiq2KbE7N4qTcuiFzTAJ34hHKgaSAp22"}, + {.seed={0x39,0xCF,0x81,0xC4,0x1F,0xD3,0xB3,0x2E,0x94,0x55,0x86,0xC7,0x6C,0xF9,0x9B,0x6E}, + .pubkey={0xED,0x79,0x65,0x13,0x48,0x56,0x93,0xFD,0xE0,0x53,0x85,0x5A,0x95,0xB9,0x17,0x6A, 0x7D,0x87,0x1D,0x1A,0xA5,0xB9,0x6A,0x5D,0x05,0x3D,0x71,0x71,0xD9,0x4B,0x8F,0xCA,0x78}, - {0x8C,0x38,0x71,0x66,0x07,0x67,0x21,0x7C,0x56,0xBD,0xC9,0xAE,0x16,0x8C,0x3C,0x20, + .seckey={0x8C,0x38,0x71,0x66,0x07,0x67,0x21,0x7C,0x56,0xBD,0xC9,0xAE,0x16,0x8C,0x3C,0x20, 0xED,0xE4,0x08,0xED,0x9E,0xF5,0xEA,0x74,0xDF,0xAA,0xE8,0xC6,0x3E,0xA0,0x16,0xF1}, - "rnAiWEMNatkbaQkCeTdkwB4jeR56Ybwn8H"}, - {{0xE0,0x82,0x92,0x4A,0x14,0x48,0x1B,0xE5,0x91,0xAC,0xD9,0xED,0x33,0x5C,0xDE,0xD7}, - {0xED,0x8D,0xCE,0x64,0x63,0xCB,0xF2,0x73,0x98,0xC1,0xF3,0x5A,0x01,0x51,0xA5,0x22, + .addr="rnAiWEMNatkbaQkCeTdkwB4jeR56Ybwn8H"}, + {.seed={0xE0,0x82,0x92,0x4A,0x14,0x48,0x1B,0xE5,0x91,0xAC,0xD9,0xED,0x33,0x5C,0xDE,0xD7}, + .pubkey={0xED,0x8D,0xCE,0x64,0x63,0xCB,0xF2,0x73,0x98,0xC1,0xF3,0x5A,0x01,0x51,0xA5,0x22, 0x71,0xD3,0xF4,0xF6,0xD7,0x1A,0x10,0x4B,0x05,0x1F,0x23,0x16,0xDD,0xE5,0x0C,0xAC,0x71}, - {0x5A,0xC1,0x39,0x8F,0x4E,0x80,0xF9,0x9D,0xCB,0xC2,0xEF,0xF4,0x16,0xDB,0x5C,0xE6, + .seckey={0x5A,0xC1,0x39,0x8F,0x4E,0x80,0xF9,0x9D,0xCB,0xC2,0xEF,0xF4,0x16,0xDB,0x5C,0xE6, 0xD1,0x62,0xDE,0xA4,0xBD,0x0D,0x99,0x66,0x3C,0xCE,0xFF,0xC9,0xF9,0x84,0xD6,0xB2}, - "r8wffJxiecJuUHxFvhWK6pUP5CG7zKqJR"}, - {{0xD9,0xCB,0x92,0x95,0xB6,0xE6,0xD3,0xBC,0xC9,0x07,0xF9,0x51,0x5D,0xF3,0x11,0x58}, - {0xED,0x86,0xB3,0x5D,0x6E,0xFD,0xDE,0x1E,0x35,0x47,0x48,0x75,0xFB,0xB2,0xA7,0x5B, + .addr="r8wffJxiecJuUHxFvhWK6pUP5CG7zKqJR"}, + {.seed={0xD9,0xCB,0x92,0x95,0xB6,0xE6,0xD3,0xBC,0xC9,0x07,0xF9,0x51,0x5D,0xF3,0x11,0x58}, + .pubkey={0xED,0x86,0xB3,0x5D,0x6E,0xFD,0xDE,0x1E,0x35,0x47,0x48,0x75,0xFB,0xB2,0xA7,0x5B, 0x27,0xC4,0x85,0x56,0xDC,0x34,0x19,0xC3,0xEA,0x2E,0xA6,0xA5,0x42,0x7B,0x5B,0x41,0x29}, - {0xB5,0x5A,0xD0,0xD9,0x93,0x95,0x8E,0xC5,0xF4,0x65,0x00,0x05,0x3F,0xA6,0xD5,0xB4, + .seckey={0xB5,0x5A,0xD0,0xD9,0x93,0x95,0x8E,0xC5,0xF4,0x65,0x00,0x05,0x3F,0xA6,0xD5,0xB4, 0x3F,0x21,0x46,0x0B,0x07,0x37,0xD1,0xD1,0x85,0xF7,0x90,0x62,0x76,0x0F,0x28,0x9B}, - "rGqEtykmqM48BLYHpaX1EvLQRAsxcQdrVb"}, - {{0xC0,0x48,0xD9,0x81,0x1D,0x69,0x70,0x9C,0xD5,0xCE,0x39,0x3A,0xC4,0x53,0x4F,0x10}, - {0xED,0x03,0x77,0x2E,0x58,0x61,0xB1,0xE3,0xEE,0x00,0xDA,0xF6,0x56,0xCD,0x31,0x1B, + .addr="rGqEtykmqM48BLYHpaX1EvLQRAsxcQdrVb"}, + {.seed={0xC0,0x48,0xD9,0x81,0x1D,0x69,0x70,0x9C,0xD5,0xCE,0x39,0x3A,0xC4,0x53,0x4F,0x10}, + .pubkey={0xED,0x03,0x77,0x2E,0x58,0x61,0xB1,0xE3,0xEE,0x00,0xDA,0xF6,0x56,0xCD,0x31,0x1B, 0x3F,0x69,0x49,0x93,0xA0,0x1C,0x57,0x82,0xD5,0x76,0x29,0xB0,0x00,0xCD,0x77,0x2E,0xDB}, - {0xD3,0xED,0x63,0x35,0x0D,0x04,0xF8,0x63,0x98,0xAF,0x78,0xBA,0xCA,0x05,0xB9,0x6F, + .seckey={0xD3,0xED,0x63,0x35,0x0D,0x04,0xF8,0x63,0x98,0xAF,0x78,0xBA,0xCA,0x05,0xB9,0x6F, 0xD4,0x77,0xCF,0x9A,0xA1,0xB4,0xC1,0xFA,0x97,0x8D,0xF9,0xCE,0xF2,0xE3,0xD8,0xE4}, - "rL4NnQiQnF6c5Y1RpcmRLoDXBfhqGbXfz7"}, - {{0x59,0x15,0xCB,0xBC,0xBE,0x81,0x71,0x7F,0x92,0x87,0xB4,0x9D,0xC0,0x71,0x92,0xCE}, - {0xED,0x50,0xBF,0xA6,0x4D,0x2D,0x46,0xB6,0x37,0xCB,0xF7,0x4C,0xEB,0x0B,0xAE,0xF2, + .addr="rL4NnQiQnF6c5Y1RpcmRLoDXBfhqGbXfz7"}, + {.seed={0x59,0x15,0xCB,0xBC,0xBE,0x81,0x71,0x7F,0x92,0x87,0xB4,0x9D,0xC0,0x71,0x92,0xCE}, + .pubkey={0xED,0x50,0xBF,0xA6,0x4D,0x2D,0x46,0xB6,0x37,0xCB,0xF7,0x4C,0xEB,0x0B,0xAE,0xF2, 0xF4,0xAF,0xB2,0x67,0xEF,0x7D,0x9B,0xE9,0x23,0x46,0x20,0x2B,0x67,0x6E,0x7B,0x96,0x68}, - {0xFB,0x75,0x27,0xE7,0xA6,0x5F,0x41,0x04,0xA4,0xFE,0x56,0xB3,0xFD,0x22,0x8A,0x38, + .seckey={0xFB,0x75,0x27,0xE7,0xA6,0x5F,0x41,0x04,0xA4,0xFE,0x56,0xB3,0xFD,0x22,0x8A,0x38, 0x73,0x28,0x1E,0x01,0x07,0xF7,0x50,0x7C,0x33,0x9B,0x64,0x53,0x01,0x65,0x1B,0x9D}, - "rUxeSACZn7nT3VqubgoREY3yqnRbzKLYs"}, - {{0xB9,0xE1,0x68,0xB8,0x95,0x4C,0xA3,0xB2,0x7E,0x03,0x68,0xC0,0xC8,0x45,0x13,0x47}, - {0xED,0xB4,0x64,0xF1,0x2B,0xFB,0x7B,0x04,0x93,0x5E,0xFC,0x6E,0x46,0x40,0xBB,0x46, + .addr="rUxeSACZn7nT3VqubgoREY3yqnRbzKLYs"}, + {.seed={0xB9,0xE1,0x68,0xB8,0x95,0x4C,0xA3,0xB2,0x7E,0x03,0x68,0xC0,0xC8,0x45,0x13,0x47}, + .pubkey={0xED,0xB4,0x64,0xF1,0x2B,0xFB,0x7B,0x04,0x93,0x5E,0xFC,0x6E,0x46,0x40,0xBB,0x46, 0x6D,0x40,0x94,0x72,0x61,0x60,0xA7,0xC2,0x08,0xF6,0x80,0x1C,0x1A,0xE8,0xEF,0xAF,0x1D}, - {0xE8,0xD3,0x03,0x76,0xA9,0x68,0x90,0x03,0xE5,0x87,0x4E,0x37,0x3C,0xD6,0x78,0xE1, + .seckey={0xE8,0xD3,0x03,0x76,0xA9,0x68,0x90,0x03,0xE5,0x87,0x4E,0x37,0x3C,0xD6,0x78,0xE1, 0x0E,0xCC,0x9C,0xB0,0xAD,0xD5,0xB6,0x6D,0xFA,0xDF,0x70,0xAA,0x68,0x86,0xED,0x07}, - "rMCAnfWAwGSNCq8aBh6QdZjwvfjy1icMnA"}, - {{0x35,0xD6,0x75,0x85,0x52,0xBF,0x61,0x4C,0xA7,0xEB,0xA4,0xB5,0xDD,0x03,0x01,0x75}, - {0xED,0x9C,0xC9,0x6A,0x89,0x2E,0x2D,0x7B,0x10,0xEB,0x55,0xAD,0x5B,0xE1,0xDC,0x9B, + .addr="rMCAnfWAwGSNCq8aBh6QdZjwvfjy1icMnA"}, + {.seed={0x35,0xD6,0x75,0x85,0x52,0xBF,0x61,0x4C,0xA7,0xEB,0xA4,0xB5,0xDD,0x03,0x01,0x75}, + .pubkey={0xED,0x9C,0xC9,0x6A,0x89,0x2E,0x2D,0x7B,0x10,0xEB,0x55,0xAD,0x5B,0xE1,0xDC,0x9B, 0x40,0xC4,0x5C,0xE8,0x5B,0x4C,0xEA,0x53,0x03,0x3B,0x96,0x58,0xBD,0x0F,0x50,0x6A,0x21}, - {0x43,0xA6,0xFB,0x04,0xA6,0xBB,0x0A,0x6E,0x8B,0x0D,0x16,0x7E,0x1F,0x4E,0x60,0xEF, + .seckey={0x43,0xA6,0xFB,0x04,0xA6,0xBB,0x0A,0x6E,0x8B,0x0D,0x16,0x7E,0x1F,0x4E,0x60,0xEF, 0x8D,0x54,0xFF,0x7F,0xCD,0x8B,0x4C,0x4B,0x67,0xAF,0x26,0x86,0x46,0xB1,0x67,0x0B}, - "rB9EttAzy3mwq1snef5nZdTVrvEU7BXfmv"}, - {{0xD2,0xB7,0x78,0x83,0x50,0x80,0x9B,0x83,0x32,0x01,0xBE,0xF2,0x0E,0xF4,0xA5,0x77}, - {0xED,0x9D,0x0B,0x8F,0x08,0x35,0x7A,0xF9,0x27,0x52,0xAB,0x85,0x56,0x7A,0x6C,0xA6, + .addr="rB9EttAzy3mwq1snef5nZdTVrvEU7BXfmv"}, + {.seed={0xD2,0xB7,0x78,0x83,0x50,0x80,0x9B,0x83,0x32,0x01,0xBE,0xF2,0x0E,0xF4,0xA5,0x77}, + .pubkey={0xED,0x9D,0x0B,0x8F,0x08,0x35,0x7A,0xF9,0x27,0x52,0xAB,0x85,0x56,0x7A,0x6C,0xA6, 0xE3,0xCC,0x6F,0xEF,0x6F,0x68,0xBE,0xEA,0xE9,0x23,0x3C,0x17,0x38,0xF3,0xFE,0x7A,0x21}, - {0x3C,0xF3,0xD7,0x99,0x8B,0x32,0xE1,0xE3,0xFD,0x4C,0x2D,0x64,0x81,0xA4,0x5B,0x37, + .seckey={0x3C,0xF3,0xD7,0x99,0x8B,0x32,0xE1,0xE3,0xFD,0x4C,0x2D,0x64,0x81,0xA4,0x5B,0x37, 0x32,0xBC,0x5E,0xF2,0x49,0x3B,0x66,0x8C,0xF7,0x09,0x7D,0xBF,0xEE,0x04,0x20,0x42}, - "rHRcCSww7zrZd6YWATkHSPfB8hiY95x89W"}, - {{0xE6,0xC3,0x5D,0x69,0xB0,0x5A,0xA4,0x68,0xC4,0x55,0xCF,0xBE,0x48,0x1C,0x44,0xDA}, - {0xED,0x06,0x93,0x43,0xDF,0x20,0x7F,0x83,0x13,0x36,0x92,0x84,0xD4,0x8E,0xAA,0xD4, + .addr="rHRcCSww7zrZd6YWATkHSPfB8hiY95x89W"}, + {.seed={0xE6,0xC3,0x5D,0x69,0xB0,0x5A,0xA4,0x68,0xC4,0x55,0xCF,0xBE,0x48,0x1C,0x44,0xDA}, + .pubkey={0xED,0x06,0x93,0x43,0xDF,0x20,0x7F,0x83,0x13,0x36,0x92,0x84,0xD4,0x8E,0xAA,0xD4, 0xDA,0xC9,0x2F,0x90,0x68,0xE5,0xFB,0x70,0xE9,0x02,0x4E,0x6C,0xD3,0x0E,0x95,0x56,0x59}, - {0xDE,0x0B,0x64,0x22,0xB7,0x27,0x23,0x17,0xC1,0xC2,0x87,0x14,0xFE,0x76,0x12,0x01, + .seckey={0xDE,0x0B,0x64,0x22,0xB7,0x27,0x23,0x17,0xC1,0xC2,0x87,0x14,0xFE,0x76,0x12,0x01, 0xE3,0x88,0x61,0xFC,0xCB,0x3C,0x6C,0xD1,0x28,0xB2,0x5F,0xE5,0xBA,0xAF,0xA1,0xF5}, - "rMGb1sfstgo3wrEsaJHpUq7HnQHGB18ca4"}, - {{0xBC,0xA0,0x3D,0xB1,0xC1,0xAC,0x5D,0x95,0x8A,0x0A,0x8F,0x85,0x57,0xEA,0x8F,0x3A}, - {0xED,0x81,0xFD,0x11,0x50,0xF0,0x7E,0x30,0x8C,0x82,0x09,0xAE,0xA6,0x18,0xFA,0x00, + .addr="rMGb1sfstgo3wrEsaJHpUq7HnQHGB18ca4"}, + {.seed={0xBC,0xA0,0x3D,0xB1,0xC1,0xAC,0x5D,0x95,0x8A,0x0A,0x8F,0x85,0x57,0xEA,0x8F,0x3A}, + .pubkey={0xED,0x81,0xFD,0x11,0x50,0xF0,0x7E,0x30,0x8C,0x82,0x09,0xAE,0xA6,0x18,0xFA,0x00, 0xDC,0xF7,0xA9,0xD8,0xCE,0xB4,0xA6,0x07,0x53,0x6F,0x3D,0x2B,0x16,0xEE,0xDD,0x27,0x27}, - {0xB4,0x02,0x85,0x56,0xD6,0xDF,0x94,0x96,0x02,0x17,0x3B,0x35,0xA4,0xF3,0x23,0xC9, + .seckey={0xB4,0x02,0x85,0x56,0xD6,0xDF,0x94,0x96,0x02,0x17,0x3B,0x35,0xA4,0xF3,0x23,0xC9, 0xB2,0x63,0x6B,0xDA,0x5A,0xC2,0xF0,0x62,0x0C,0x5E,0xBF,0x3B,0xE2,0x1B,0xDD,0x1C}, - "rMd5FozFR6P5KZpdiYobDsR7pLcGYzYNNC"}, - {{0x6E,0x8F,0x73,0xA9,0x4F,0x55,0xD8,0x78,0x79,0xF7,0x1F,0xDA,0x52,0xF2,0x21,0xFE}, - {0xED,0x10,0x2F,0xF5,0xD3,0xED,0xC4,0xDB,0xB8,0xF7,0x8F,0x1B,0x76,0xF0,0xFC,0x1B, + .addr="rMd5FozFR6P5KZpdiYobDsR7pLcGYzYNNC"}, + {.seed={0x6E,0x8F,0x73,0xA9,0x4F,0x55,0xD8,0x78,0x79,0xF7,0x1F,0xDA,0x52,0xF2,0x21,0xFE}, + .pubkey={0xED,0x10,0x2F,0xF5,0xD3,0xED,0xC4,0xDB,0xB8,0xF7,0x8F,0x1B,0x76,0xF0,0xFC,0x1B, 0x4E,0xB6,0x5A,0xFA,0xD5,0x88,0x70,0xA7,0xD0,0x97,0xB3,0x03,0x6F,0x76,0x49,0x15,0x47}, - {0xF2,0x13,0xEE,0xF6,0xF9,0x2E,0x96,0x1F,0xA3,0x07,0x9C,0xD8,0x43,0x5B,0x72,0xC9, + .seckey={0xF2,0x13,0xEE,0xF6,0xF9,0x2E,0x96,0x1F,0xA3,0x07,0x9C,0xD8,0x43,0x5B,0x72,0xC9, 0xA1,0xF9,0xDE,0xF5,0xA6,0x60,0x65,0x43,0x8E,0x43,0xE2,0x04,0xF9,0x51,0x32,0x9F}, - "rQUcKBg3eHzTWRUJD4PbNcmPBNwvhbvF3o"}, - {{0x1E,0x52,0x4A,0x10,0x70,0x6B,0x4C,0x92,0x71,0xAA,0x5A,0x43,0x18,0x7C,0x2B,0x63}, - {0xED,0x57,0x58,0x44,0xBA,0x0B,0xCA,0xAE,0x24,0xB1,0xFF,0xFE,0xD9,0xA8,0x7C,0xB6, + .addr="rQUcKBg3eHzTWRUJD4PbNcmPBNwvhbvF3o"}, + {.seed={0x1E,0x52,0x4A,0x10,0x70,0x6B,0x4C,0x92,0x71,0xAA,0x5A,0x43,0x18,0x7C,0x2B,0x63}, + .pubkey={0xED,0x57,0x58,0x44,0xBA,0x0B,0xCA,0xAE,0x24,0xB1,0xFF,0xFE,0xD9,0xA8,0x7C,0xB6, 0xE4,0xB3,0x8F,0x64,0x6C,0x91,0xBD,0x78,0x2A,0x8A,0xFA,0x82,0x99,0x2B,0x31,0x2A,0xBE}, - {0xE0,0x10,0x7E,0x8C,0x5F,0x0A,0xFE,0xAC,0xC4,0xEC,0x23,0xDD,0x19,0x2B,0x6F,0x19, + .seckey={0xE0,0x10,0x7E,0x8C,0x5F,0x0A,0xFE,0xAC,0xC4,0xEC,0x23,0xDD,0x19,0x2B,0x6F,0x19, 0x56,0xD9,0xF0,0x11,0xAE,0x78,0xE3,0x09,0xF8,0x5A,0x40,0xFF,0x55,0x28,0x70,0xDB}, - "rJmrBAQs7Rw6pZiKRchGS9zySz3H9S9jRE"}, - {{0x6E,0xA9,0x92,0xD3,0x42,0x3A,0x80,0x53,0x9C,0xC3,0x0C,0xFC,0x3A,0x37,0x30,0x61}, - {0xED,0xB2,0x46,0xA6,0x54,0x0A,0xE4,0xDE,0x4E,0xB1,0xFA,0x44,0x25,0xB9,0x8E,0x8D, + .addr="rJmrBAQs7Rw6pZiKRchGS9zySz3H9S9jRE"}, + {.seed={0x6E,0xA9,0x92,0xD3,0x42,0x3A,0x80,0x53,0x9C,0xC3,0x0C,0xFC,0x3A,0x37,0x30,0x61}, + .pubkey={0xED,0xB2,0x46,0xA6,0x54,0x0A,0xE4,0xDE,0x4E,0xB1,0xFA,0x44,0x25,0xB9,0x8E,0x8D, 0x14,0x0D,0xC5,0x4B,0x74,0xC5,0xEE,0x5F,0xFB,0x7C,0x92,0x1F,0xBE,0x65,0x2E,0xBC,0x44}, - {0xE4,0x3D,0xFE,0x62,0x4E,0x98,0x00,0x30,0x7E,0x79,0x70,0x6D,0x70,0x0E,0x6B,0xEC, + .seckey={0xE4,0x3D,0xFE,0x62,0x4E,0x98,0x00,0x30,0x7E,0x79,0x70,0x6D,0x70,0x0E,0x6B,0xEC, 0x32,0x93,0x6C,0xBB,0x99,0xC4,0x4C,0x49,0x94,0xF5,0x6C,0xC4,0x9E,0x5B,0x85,0x88}, - "rMasL6hDPMB25XMh3AoTXcNE1ggQpox4sX"}, - {{0xE0,0xB2,0x0C,0x61,0x5E,0x22,0x0F,0xA8,0xB7,0xC9,0xAA,0x7C,0x06,0x39,0x38,0x0E}, - {0xED,0x1A,0x10,0xE1,0xE5,0xF6,0xBC,0x51,0x92,0xFC,0x8B,0x3B,0xBD,0x76,0x2A,0xEC, + .addr="rMasL6hDPMB25XMh3AoTXcNE1ggQpox4sX"}, + {.seed={0xE0,0xB2,0x0C,0x61,0x5E,0x22,0x0F,0xA8,0xB7,0xC9,0xAA,0x7C,0x06,0x39,0x38,0x0E}, + .pubkey={0xED,0x1A,0x10,0xE1,0xE5,0xF6,0xBC,0x51,0x92,0xFC,0x8B,0x3B,0xBD,0x76,0x2A,0xEC, 0x16,0x09,0xE5,0x08,0xDE,0x1E,0xAE,0x33,0x37,0x47,0x0E,0xD3,0xC1,0x7C,0xA9,0x48,0x84}, - {0x59,0x94,0x21,0x92,0xEF,0x9C,0xA7,0xF8,0xD3,0x76,0xA9,0x82,0xFC,0x76,0x4B,0xF7, + .seckey={0x59,0x94,0x21,0x92,0xEF,0x9C,0xA7,0xF8,0xD3,0x76,0xA9,0x82,0xFC,0x76,0x4B,0xF7, 0x87,0x1F,0x15,0x7C,0x35,0xF0,0xB2,0xB5,0x79,0xD3,0xCE,0x5B,0xFE,0x5F,0x8B,0x9C}, - "rKdzTk4JtuS6iad4WvA7vCVgNAYLd3L6R1"}, - {{0x4E,0x7E,0x66,0xFF,0x98,0x41,0x44,0x82,0xC1,0x96,0xAA,0xF9,0x30,0xA9,0xC2,0x6D}, - {0xED,0x87,0x05,0x76,0xA3,0x27,0x67,0x78,0x32,0x2D,0x46,0xF4,0x10,0x1F,0x64,0x3A, + .addr="rKdzTk4JtuS6iad4WvA7vCVgNAYLd3L6R1"}, + {.seed={0x4E,0x7E,0x66,0xFF,0x98,0x41,0x44,0x82,0xC1,0x96,0xAA,0xF9,0x30,0xA9,0xC2,0x6D}, + .pubkey={0xED,0x87,0x05,0x76,0xA3,0x27,0x67,0x78,0x32,0x2D,0x46,0xF4,0x10,0x1F,0x64,0x3A, 0xDC,0x81,0xBC,0xE0,0xE2,0x5A,0xB3,0xB3,0x23,0x31,0x78,0x87,0x82,0xFA,0xA1,0x83,0xB0}, - {0x83,0x2D,0x28,0x6A,0xA6,0x7B,0xD6,0x31,0xDB,0x54,0x83,0x40,0x43,0xC4,0x4D,0x09, + .seckey={0x83,0x2D,0x28,0x6A,0xA6,0x7B,0xD6,0x31,0xDB,0x54,0x83,0x40,0x43,0xC4,0x4D,0x09, 0x6F,0xBD,0xFD,0x0B,0x6C,0x00,0x9E,0x27,0x28,0xC9,0x92,0xCF,0x27,0xD4,0x7B,0x9A}, - "rP9f847oBmousWhxkf3FsYFmvmmDXr4U8F"}, - {{0xE6,0x8D,0x1F,0xF8,0x07,0x3B,0xA3,0xFB,0x3D,0xA7,0xD0,0x21,0x09,0x0F,0x00,0xAE}, - {0xED,0x63,0x58,0xA0,0x9A,0x6B,0x4A,0xF9,0xD8,0x8D,0xCF,0x59,0x3E,0x6C,0xB7,0x7B, + .addr="rP9f847oBmousWhxkf3FsYFmvmmDXr4U8F"}, + {.seed={0xE6,0x8D,0x1F,0xF8,0x07,0x3B,0xA3,0xFB,0x3D,0xA7,0xD0,0x21,0x09,0x0F,0x00,0xAE}, + .pubkey={0xED,0x63,0x58,0xA0,0x9A,0x6B,0x4A,0xF9,0xD8,0x8D,0xCF,0x59,0x3E,0x6C,0xB7,0x7B, 0x6A,0x49,0x82,0x98,0x35,0x0D,0x38,0x60,0x49,0xE0,0xD2,0xDB,0xEF,0xE2,0x49,0xE7,0xF4}, - {0xB8,0x97,0xC6,0x04,0xCF,0x24,0xED,0x9D,0x2D,0x24,0x9D,0x8D,0x7A,0xD1,0xED,0x31, + .seckey={0xB8,0x97,0xC6,0x04,0xCF,0x24,0xED,0x9D,0x2D,0x24,0x9D,0x8D,0x7A,0xD1,0xED,0x31, 0x20,0x4D,0x77,0xD9,0x03,0x47,0x69,0x85,0x08,0xFA,0x88,0xFC,0xB1,0x25,0x85,0x92}, - "rUU3a724prcKc7ZhPFjD2jbqi2ASTWLwb5"}, - {{0x8B,0x69,0x73,0xBE,0xBE,0x79,0x14,0x81,0x0E,0x0C,0x83,0x13,0x47,0x8E,0xCF,0x3F}, - {0xED,0x84,0x49,0xF9,0xB5,0xC9,0x0E,0xEE,0x3B,0x13,0x3B,0xBF,0x60,0x17,0x11,0x14, + .addr="rUU3a724prcKc7ZhPFjD2jbqi2ASTWLwb5"}, + {.seed={0x8B,0x69,0x73,0xBE,0xBE,0x79,0x14,0x81,0x0E,0x0C,0x83,0x13,0x47,0x8E,0xCF,0x3F}, + .pubkey={0xED,0x84,0x49,0xF9,0xB5,0xC9,0x0E,0xEE,0x3B,0x13,0x3B,0xBF,0x60,0x17,0x11,0x14, 0xB1,0xD8,0xC4,0x9E,0x25,0xB8,0x04,0xD3,0xDC,0x3F,0xB5,0x97,0x9D,0xDE,0xB6,0x71,0xF8}, - {0xBD,0xEF,0xBD,0x40,0xC4,0x11,0x3F,0xDD,0x5C,0x7E,0xBE,0xA1,0x11,0xC5,0x23,0x59, + .seckey={0xBD,0xEF,0xBD,0x40,0xC4,0x11,0x3F,0xDD,0x5C,0x7E,0xBE,0xA1,0x11,0xC5,0x23,0x59, 0x36,0xA8,0x73,0x14,0x64,0xE5,0x8B,0x16,0x2E,0xF7,0xF8,0x46,0x83,0x28,0x84,0xF1}, - "rNrAyuhowX4Lvy1qdb19CZhDe7jWjAuQQh"}, - {{0x3B,0xF3,0x6E,0x7E,0xE4,0xB1,0x7E,0x2B,0xD3,0x86,0xC0,0xE6,0x81,0xED,0x07,0x07}, - {0xED,0x99,0xBD,0x79,0xAA,0xE3,0x57,0x89,0xF0,0x1D,0xDC,0x29,0x12,0x47,0x1A,0xF7, + .addr="rNrAyuhowX4Lvy1qdb19CZhDe7jWjAuQQh"}, + {.seed={0x3B,0xF3,0x6E,0x7E,0xE4,0xB1,0x7E,0x2B,0xD3,0x86,0xC0,0xE6,0x81,0xED,0x07,0x07}, + .pubkey={0xED,0x99,0xBD,0x79,0xAA,0xE3,0x57,0x89,0xF0,0x1D,0xDC,0x29,0x12,0x47,0x1A,0xF7, 0xC0,0x05,0xB1,0xD9,0xD5,0xAC,0x3A,0xA7,0x76,0x57,0x7C,0x49,0xEC,0xF3,0xCC,0xFB,0xD2}, - {0x1D,0x87,0x3A,0x2F,0xCE,0x48,0xFF,0xD4,0xEF,0x13,0xCE,0x83,0x94,0x88,0x75,0xF5, + .seckey={0x1D,0x87,0x3A,0x2F,0xCE,0x48,0xFF,0xD4,0xEF,0x13,0xCE,0x83,0x94,0x88,0x75,0xF5, 0x58,0x39,0x82,0x50,0x7D,0x46,0xBF,0x6C,0x25,0xAB,0x1F,0xDB,0x42,0xB3,0x54,0x5C}, - "rDMWBAzDeam8BvJEpjntHBczHPQeppR9nx"}, - {{0x68,0x52,0x0F,0x9F,0xB9,0x6E,0xB5,0x01,0x10,0xC3,0x6C,0x13,0x83,0x78,0xE9,0x14}, - {0xED,0x17,0xA9,0x94,0x74,0x77,0x89,0x18,0x02,0xE0,0xE9,0x48,0xAE,0xC4,0x31,0x7E, + .addr="rDMWBAzDeam8BvJEpjntHBczHPQeppR9nx"}, + {.seed={0x68,0x52,0x0F,0x9F,0xB9,0x6E,0xB5,0x01,0x10,0xC3,0x6C,0x13,0x83,0x78,0xE9,0x14}, + .pubkey={0xED,0x17,0xA9,0x94,0x74,0x77,0x89,0x18,0x02,0xE0,0xE9,0x48,0xAE,0xC4,0x31,0x7E, 0x1E,0xE0,0xEC,0x45,0xBC,0xC5,0x69,0x6E,0x94,0x07,0x00,0xEB,0x0B,0x26,0x6B,0xF5,0x0D}, - {0x02,0xE9,0xBC,0x25,0xB7,0x1F,0xA4,0x8A,0x3D,0x35,0xA2,0xDB,0x89,0xF2,0xAE,0xE0, + .seckey={0x02,0xE9,0xBC,0x25,0xB7,0x1F,0xA4,0x8A,0x3D,0x35,0xA2,0xDB,0x89,0xF2,0xAE,0xE0, 0x5C,0x15,0xA1,0xA3,0x31,0x28,0x53,0xFF,0x43,0xD1,0xC8,0x66,0x74,0x14,0x3B,0xE6}, - "rBAzfmZZqjPFK7TzjHfCMhV8aHLDgr6wBC"}, - {{0x8A,0x6D,0x4F,0xF7,0x60,0xD3,0xDB,0xD1,0xE5,0xF7,0x3E,0xA5,0x4A,0xC3,0x8B,0xA5}, - {0xED,0x66,0x4B,0x49,0x86,0xBD,0x84,0x6F,0x8E,0x6F,0x79,0x43,0x5C,0x45,0x38,0xF7, + .addr="rBAzfmZZqjPFK7TzjHfCMhV8aHLDgr6wBC"}, + {.seed={0x8A,0x6D,0x4F,0xF7,0x60,0xD3,0xDB,0xD1,0xE5,0xF7,0x3E,0xA5,0x4A,0xC3,0x8B,0xA5}, + .pubkey={0xED,0x66,0x4B,0x49,0x86,0xBD,0x84,0x6F,0x8E,0x6F,0x79,0x43,0x5C,0x45,0x38,0xF7, 0x12,0xC8,0x7F,0xCE,0xF4,0xD0,0x29,0xD8,0x23,0x03,0xCE,0x69,0x22,0x41,0xFB,0x93,0x1F}, - {0x07,0x20,0xDB,0x0A,0xBC,0x3D,0x1F,0xB5,0x83,0x80,0x97,0x13,0xE0,0x64,0xAD,0x6B, + .seckey={0x07,0x20,0xDB,0x0A,0xBC,0x3D,0x1F,0xB5,0x83,0x80,0x97,0x13,0xE0,0x64,0xAD,0x6B, 0xAD,0x9A,0x88,0xD1,0x3A,0x94,0xC2,0x63,0xE9,0x54,0x9D,0x1E,0x61,0xB6,0x20,0xF5}, - "rGvs6gtwzf6JM1emzYDftUHngJ4CjTmnjz"}, - {{0xEA,0xD6,0xF2,0x74,0x7C,0x69,0x18,0xB3,0x24,0x18,0xF6,0xAE,0x68,0x32,0xC2,0x3B}, - {0xED,0xC9,0x61,0xB8,0xFE,0xB0,0x67,0x63,0xC6,0xCD,0x26,0x44,0x12,0xBD,0xEC,0xEE, + .addr="rGvs6gtwzf6JM1emzYDftUHngJ4CjTmnjz"}, + {.seed={0xEA,0xD6,0xF2,0x74,0x7C,0x69,0x18,0xB3,0x24,0x18,0xF6,0xAE,0x68,0x32,0xC2,0x3B}, + .pubkey={0xED,0xC9,0x61,0xB8,0xFE,0xB0,0x67,0x63,0xC6,0xCD,0x26,0x44,0x12,0xBD,0xEC,0xEE, 0x8E,0xD4,0x6F,0x9E,0x75,0x13,0xF5,0xDB,0x0D,0x7D,0x1D,0x8C,0xD4,0x2C,0xAE,0xD0,0x0C}, - {0x80,0x46,0x0F,0x71,0xF0,0x51,0x2C,0xF7,0xFB,0x33,0x1A,0xF2,0x7D,0x18,0xD4,0x33, + .seckey={0x80,0x46,0x0F,0x71,0xF0,0x51,0x2C,0xF7,0xFB,0x33,0x1A,0xF2,0x7D,0x18,0xD4,0x33, 0x2E,0xC6,0x1E,0xD8,0x54,0x2E,0x6C,0x04,0x40,0xB4,0xE1,0xCC,0xC4,0x0E,0x18,0x93}, - "rsQcFiFTAHgY8JEssbG9daaz7YV8XeFMK4"}, - {{0x00,0xF7,0xAD,0xB8,0x65,0xE9,0x3A,0x29,0xD0,0x16,0xEC,0x90,0x5B,0x26,0xA4,0x36}, - {0xED,0xF9,0x3C,0x87,0xD0,0x9D,0x31,0xA7,0x19,0x18,0xC3,0x5E,0xDC,0xD3,0x1E,0x87, + .addr="rsQcFiFTAHgY8JEssbG9daaz7YV8XeFMK4"}, + {.seed={0x00,0xF7,0xAD,0xB8,0x65,0xE9,0x3A,0x29,0xD0,0x16,0xEC,0x90,0x5B,0x26,0xA4,0x36}, + .pubkey={0xED,0xF9,0x3C,0x87,0xD0,0x9D,0x31,0xA7,0x19,0x18,0xC3,0x5E,0xDC,0xD3,0x1E,0x87, 0x44,0x1A,0xA8,0x11,0xA8,0x07,0x6B,0xCB,0x0E,0xED,0xBA,0x44,0x28,0x33,0xE0,0x9E,0x65}, - {0x64,0xAB,0xE9,0xFE,0x6D,0x4F,0x82,0x70,0x7D,0xEF,0x9A,0xF4,0x81,0x88,0x16,0x95, + .seckey={0x64,0xAB,0xE9,0xFE,0x6D,0x4F,0x82,0x70,0x7D,0xEF,0x9A,0xF4,0x81,0x88,0x16,0x95, 0x49,0x42,0x8D,0x8A,0xD9,0x7E,0x62,0x81,0xF4,0x4A,0x35,0x01,0xD3,0x4B,0xFA,0xF0}, - "r58iT3yfxxeLZvLevRcWcDgNFrSUyBieP"}, - {{0x83,0x6D,0xFD,0xE4,0x9A,0xCA,0xAA,0xDA,0x08,0xA4,0xBD,0xB0,0x65,0xE6,0x84,0x10}, - {0xED,0xE1,0x2C,0x6C,0x9B,0x70,0x0B,0x1D,0xBA,0x49,0x49,0x1F,0xEF,0x72,0x4D,0x46, + .addr="r58iT3yfxxeLZvLevRcWcDgNFrSUyBieP"}, + {.seed={0x83,0x6D,0xFD,0xE4,0x9A,0xCA,0xAA,0xDA,0x08,0xA4,0xBD,0xB0,0x65,0xE6,0x84,0x10}, + .pubkey={0xED,0xE1,0x2C,0x6C,0x9B,0x70,0x0B,0x1D,0xBA,0x49,0x49,0x1F,0xEF,0x72,0x4D,0x46, 0xE8,0x72,0xB5,0x2B,0x16,0xDC,0xC6,0xC1,0xB6,0x7D,0xC4,0x5B,0xA1,0xAA,0x99,0x43,0x9E}, - {0x8F,0xD8,0x72,0x25,0x43,0x5E,0xD6,0x6B,0xD0,0x6A,0x21,0x2D,0x11,0x81,0xCD,0xE9, + .seckey={0x8F,0xD8,0x72,0x25,0x43,0x5E,0xD6,0x6B,0xD0,0x6A,0x21,0x2D,0x11,0x81,0xCD,0xE9, 0x3C,0x58,0xA7,0x91,0xDC,0x29,0x79,0x3F,0x29,0xB1,0x23,0x80,0xBE,0x6C,0xAB,0x3D}, - "rhUqESrPkP75maWRrpGqAAVda6HsR9wQbR"}, - {{0xD1,0xB7,0x57,0x48,0xD2,0x27,0x31,0x3B,0x43,0x15,0xA2,0x6D,0x30,0x8A,0xB4,0x2A}, - {0xED,0x02,0xBB,0x15,0x41,0x52,0x62,0xE0,0xA6,0xDA,0xCD,0x5E,0xA7,0xF1,0x64,0xBA, + .addr="rhUqESrPkP75maWRrpGqAAVda6HsR9wQbR"}, + {.seed={0xD1,0xB7,0x57,0x48,0xD2,0x27,0x31,0x3B,0x43,0x15,0xA2,0x6D,0x30,0x8A,0xB4,0x2A}, + .pubkey={0xED,0x02,0xBB,0x15,0x41,0x52,0x62,0xE0,0xA6,0xDA,0xCD,0x5E,0xA7,0xF1,0x64,0xBA, 0x5E,0x45,0xB9,0x6A,0xFA,0x62,0xE0,0xF1,0x82,0x6B,0x94,0x10,0x8A,0xD9,0xDB,0xD6,0xDC}, - {0x18,0xFE,0xAD,0x09,0x91,0xFB,0x01,0xDF,0xFB,0x98,0x3B,0xB3,0x52,0xB8,0xB8,0xA4, + .seckey={0x18,0xFE,0xAD,0x09,0x91,0xFB,0x01,0xDF,0xFB,0x98,0x3B,0xB3,0x52,0xB8,0xB8,0xA4, 0x0E,0x81,0x84,0xBC,0x04,0xD8,0x69,0xC0,0x0C,0x2C,0xD4,0x0F,0x28,0x49,0xFC,0x11}, - "r9iuLmgHRCbUaA9AMybjBuBjNYTN34viiK"}, - {{0xBE,0x60,0x4D,0x3F,0x85,0x07,0x3B,0x0E,0x0C,0xD7,0xD5,0xDB,0x69,0x9E,0x2A,0xAC}, - {0xED,0xD8,0xB2,0x19,0x60,0xC3,0xA0,0x27,0x24,0x3C,0x01,0x4E,0x07,0x39,0x17,0xF3, + .addr="r9iuLmgHRCbUaA9AMybjBuBjNYTN34viiK"}, + {.seed={0xBE,0x60,0x4D,0x3F,0x85,0x07,0x3B,0x0E,0x0C,0xD7,0xD5,0xDB,0x69,0x9E,0x2A,0xAC}, + .pubkey={0xED,0xD8,0xB2,0x19,0x60,0xC3,0xA0,0x27,0x24,0x3C,0x01,0x4E,0x07,0x39,0x17,0xF3, 0xC9,0x93,0x7B,0xB5,0x5E,0xF4,0x55,0xEB,0xD0,0xD0,0xB7,0x62,0x82,0xC7,0xD7,0xA9,0xAE}, - {0x07,0x3C,0x18,0x34,0x37,0xCA,0xE4,0xCE,0x3B,0xD6,0xBD,0xB0,0x5D,0xB0,0x2A,0xE1, + .seckey={0x07,0x3C,0x18,0x34,0x37,0xCA,0xE4,0xCE,0x3B,0xD6,0xBD,0xB0,0x5D,0xB0,0x2A,0xE1, 0x31,0xBD,0x95,0x9A,0x9D,0xC7,0x3E,0x4C,0xEA,0x18,0x44,0x05,0xF4,0xE9,0x31,0xB2}, - "rnrYBExFRbfamRgyrTo7uFhG2r1XzjgL8Y"}, - {{0x3A,0xAC,0x26,0x07,0xC1,0xAD,0x65,0x58,0xDE,0x12,0x6B,0x94,0xB9,0x4F,0x5F,0x68}, - {0xED,0x26,0x42,0xE5,0xF8,0x41,0x5B,0x28,0x2C,0x7B,0x32,0x19,0x73,0x31,0xFB,0x44, + .addr="rnrYBExFRbfamRgyrTo7uFhG2r1XzjgL8Y"}, + {.seed={0x3A,0xAC,0x26,0x07,0xC1,0xAD,0x65,0x58,0xDE,0x12,0x6B,0x94,0xB9,0x4F,0x5F,0x68}, + .pubkey={0xED,0x26,0x42,0xE5,0xF8,0x41,0x5B,0x28,0x2C,0x7B,0x32,0x19,0x73,0x31,0xFB,0x44, 0x89,0xBD,0x3E,0xA9,0xCC,0x82,0x5D,0xC9,0x18,0x7D,0x0A,0xF8,0x4D,0x2E,0xA2,0xA4,0x20}, - {0x1E,0x8C,0x09,0x43,0x9A,0x3E,0x23,0x7B,0x54,0x73,0x75,0x67,0xCE,0x6C,0x56,0xCD, + .seckey={0x1E,0x8C,0x09,0x43,0x9A,0x3E,0x23,0x7B,0x54,0x73,0x75,0x67,0xCE,0x6C,0x56,0xCD, 0x6E,0x1B,0x26,0xCC,0xE6,0x7D,0x69,0x9F,0x00,0xE5,0x10,0x2F,0x36,0xFC,0xC5,0xF7}, - "r3YdsPCvfJ5AVbaWyd2oK7fy7zFnSiyA8a"}, - {{0xF0,0x42,0x36,0x84,0xA4,0xC3,0xF7,0xED,0x27,0x0F,0x8E,0x99,0x8C,0x62,0x6A,0x01}, - {0xED,0xEA,0xCE,0xD9,0x7E,0xF9,0x59,0x6D,0x7D,0x3A,0x82,0x24,0x42,0x2A,0x1C,0xF5, + .addr="r3YdsPCvfJ5AVbaWyd2oK7fy7zFnSiyA8a"}, + {.seed={0xF0,0x42,0x36,0x84,0xA4,0xC3,0xF7,0xED,0x27,0x0F,0x8E,0x99,0x8C,0x62,0x6A,0x01}, + .pubkey={0xED,0xEA,0xCE,0xD9,0x7E,0xF9,0x59,0x6D,0x7D,0x3A,0x82,0x24,0x42,0x2A,0x1C,0xF5, 0xF8,0x1A,0xBF,0xD0,0x62,0x23,0x0D,0xDF,0x18,0x4F,0x5A,0xD7,0x1E,0x1E,0x2B,0x05,0xED}, - {0x39,0xBC,0xA3,0x9D,0x64,0xCB,0x30,0x5B,0x7A,0x9F,0xFC,0x0B,0xD0,0x01,0x4C,0xEB, + .seckey={0x39,0xBC,0xA3,0x9D,0x64,0xCB,0x30,0x5B,0x7A,0x9F,0xFC,0x0B,0xD0,0x01,0x4C,0xEB, 0xF8,0x26,0x5F,0x03,0x04,0x7D,0xF2,0x29,0x6E,0x21,0xF3,0x70,0xE3,0xCD,0x92,0x08}, - "rNMgmiC2hzF6Qe5NRb4Nfd2r14hx3sc2v5"}, - {{0x32,0x68,0x6A,0x27,0x24,0xDC,0xFA,0x84,0xFE,0xB8,0x70,0xC0,0xCD,0xB3,0x4C,0x91}, - {0xED,0xB2,0xF1,0xFB,0xDC,0x26,0x17,0x49,0x0D,0x9D,0x6B,0x4B,0xEC,0xFA,0xC7,0xCE, + .addr="rNMgmiC2hzF6Qe5NRb4Nfd2r14hx3sc2v5"}, + {.seed={0x32,0x68,0x6A,0x27,0x24,0xDC,0xFA,0x84,0xFE,0xB8,0x70,0xC0,0xCD,0xB3,0x4C,0x91}, + .pubkey={0xED,0xB2,0xF1,0xFB,0xDC,0x26,0x17,0x49,0x0D,0x9D,0x6B,0x4B,0xEC,0xFA,0xC7,0xCE, 0x37,0x8F,0x90,0x98,0x87,0x88,0x34,0xE3,0x85,0x12,0xD4,0x82,0xC8,0x61,0xCD,0xA7,0x25}, - {0xFF,0x49,0x97,0x28,0x69,0xD9,0x20,0x49,0x9A,0x5D,0x67,0x1D,0xB7,0x25,0x68,0x23, + .seckey={0xFF,0x49,0x97,0x28,0x69,0xD9,0x20,0x49,0x9A,0x5D,0x67,0x1D,0xB7,0x25,0x68,0x23, 0x5C,0x4A,0x53,0xAB,0xFB,0xED,0xDB,0x50,0x30,0xDC,0x4B,0xED,0xF8,0x81,0xBD,0x9C}, - "rMCRWBFcdcC36Nvams6VfztDbVe7TNYh1j"}, - {{0x88,0x8B,0x76,0x7B,0x59,0x28,0x21,0x47,0xAA,0xF3,0xB4,0x88,0x99,0x4B,0x55,0xC1}, - {0xED,0x08,0x56,0x1B,0x8F,0x67,0x6F,0xC1,0xE1,0x7F,0xF2,0xF1,0x18,0xA6,0x1A,0x94, + .addr="rMCRWBFcdcC36Nvams6VfztDbVe7TNYh1j"}, + {.seed={0x88,0x8B,0x76,0x7B,0x59,0x28,0x21,0x47,0xAA,0xF3,0xB4,0x88,0x99,0x4B,0x55,0xC1}, + .pubkey={0xED,0x08,0x56,0x1B,0x8F,0x67,0x6F,0xC1,0xE1,0x7F,0xF2,0xF1,0x18,0xA6,0x1A,0x94, 0x60,0xAB,0x6E,0xE2,0x2B,0xEB,0x15,0xBB,0xBA,0x51,0x2D,0x99,0x1B,0xDE,0x30,0xDC,0x41}, - {0x73,0xA8,0xE8,0xDC,0xCD,0x83,0xB6,0x7C,0xA7,0x3A,0x4B,0x3C,0xF1,0x11,0x0A,0x01, + .seckey={0x73,0xA8,0xE8,0xDC,0xCD,0x83,0xB6,0x7C,0xA7,0x3A,0x4B,0x3C,0xF1,0x11,0x0A,0x01, 0x13,0x69,0x6E,0x07,0x6C,0x6C,0xFF,0x23,0xE2,0x7B,0x16,0xAC,0x50,0xED,0x2A,0x9F}, - "rM6iPDQ7RkieWJdU9vUxM4ynhFNScHWszZ"}, - {{0xA5,0x94,0x33,0x40,0xE0,0x33,0xC9,0xF8,0x37,0x11,0x7A,0xB7,0xA5,0xD1,0xD2,0x90}, - {0xED,0x9E,0x74,0x80,0x3B,0xFF,0x2A,0xD4,0x02,0xE4,0x81,0xD0,0x1A,0x98,0xAA,0x51, + .addr="rM6iPDQ7RkieWJdU9vUxM4ynhFNScHWszZ"}, + {.seed={0xA5,0x94,0x33,0x40,0xE0,0x33,0xC9,0xF8,0x37,0x11,0x7A,0xB7,0xA5,0xD1,0xD2,0x90}, + .pubkey={0xED,0x9E,0x74,0x80,0x3B,0xFF,0x2A,0xD4,0x02,0xE4,0x81,0xD0,0x1A,0x98,0xAA,0x51, 0x84,0xC0,0x7F,0x5D,0xC4,0x69,0xC1,0x76,0xAE,0x81,0x2B,0xC9,0x5E,0xA0,0xE7,0x12,0x53}, - {0x46,0x8F,0x5A,0x8B,0x56,0x92,0xE8,0x12,0xC9,0x48,0x60,0xAF,0x8F,0xDF,0xDA,0x15, + .seckey={0x46,0x8F,0x5A,0x8B,0x56,0x92,0xE8,0x12,0xC9,0x48,0x60,0xAF,0x8F,0xDF,0xDA,0x15, 0xEC,0x4C,0xF4,0xA1,0x0C,0x56,0x2B,0x8A,0x6B,0xFA,0xF9,0x9D,0xF1,0x1A,0xEC,0x4B}, - "rEfpJHQrcCyps27bjHL4kvvousu1UDHWVA"}, - {{0xF2,0x5D,0x4B,0x04,0xDD,0xD0,0xF6,0x59,0x89,0xC7,0x02,0xF6,0x17,0x24,0x6D,0x0D}, - {0xED,0xE6,0x37,0x43,0x60,0xF5,0xDF,0x14,0xCE,0x95,0xC9,0x80,0xDD,0xF5,0x14,0x31, + .addr="rEfpJHQrcCyps27bjHL4kvvousu1UDHWVA"}, + {.seed={0xF2,0x5D,0x4B,0x04,0xDD,0xD0,0xF6,0x59,0x89,0xC7,0x02,0xF6,0x17,0x24,0x6D,0x0D}, + .pubkey={0xED,0xE6,0x37,0x43,0x60,0xF5,0xDF,0x14,0xCE,0x95,0xC9,0x80,0xDD,0xF5,0x14,0x31, 0xD6,0xE2,0x4F,0xFD,0x75,0x17,0x4E,0xDE,0xC6,0x70,0x63,0x7F,0xB2,0x88,0x22,0xD7,0xD6}, - {0xA4,0x46,0xD7,0xBA,0x7C,0x56,0x3C,0x72,0xE9,0xD8,0x4C,0x0D,0x5A,0x06,0xBE,0xBB, + .seckey={0xA4,0x46,0xD7,0xBA,0x7C,0x56,0x3C,0x72,0xE9,0xD8,0x4C,0x0D,0x5A,0x06,0xBE,0xBB, 0xF2,0xD2,0x5C,0x27,0x7E,0x44,0x97,0x8A,0x5B,0x57,0x0C,0xC9,0x45,0x53,0xD8,0x09}, - "rDzNPeQPwy67Q64PHi759dLdrJZGZ6Fam6"}, - {{0x1B,0x3B,0xA6,0xA3,0x34,0xAC,0xF1,0x2F,0x1F,0x56,0xA3,0xC4,0xAF,0x43,0xE4,0x69}, - {0xED,0x99,0xC0,0x65,0x5D,0xBA,0x42,0x4E,0x53,0x0F,0xF8,0x92,0x4F,0x6B,0x41,0xEC, + .addr="rDzNPeQPwy67Q64PHi759dLdrJZGZ6Fam6"}, + {.seed={0x1B,0x3B,0xA6,0xA3,0x34,0xAC,0xF1,0x2F,0x1F,0x56,0xA3,0xC4,0xAF,0x43,0xE4,0x69}, + .pubkey={0xED,0x99,0xC0,0x65,0x5D,0xBA,0x42,0x4E,0x53,0x0F,0xF8,0x92,0x4F,0x6B,0x41,0xEC, 0xDD,0x94,0xB6,0xF9,0x27,0xF8,0xCA,0xE7,0x79,0xAD,0x0A,0xA4,0x02,0x98,0x8D,0xA6,0x6B}, - {0x81,0x8D,0xD1,0x46,0xE8,0xEE,0x60,0x6E,0x18,0x7E,0xDF,0x41,0xC4,0xFE,0x64,0x9B, + .seckey={0x81,0x8D,0xD1,0x46,0xE8,0xEE,0x60,0x6E,0x18,0x7E,0xDF,0x41,0xC4,0xFE,0x64,0x9B, 0x8D,0x96,0xF8,0x1A,0xB4,0x1D,0x67,0xE3,0x03,0xB2,0x7E,0x63,0xAC,0x31,0x98,0x17}, - "r99hrhMgWrzUssGoLUttkmVFoERgf9fB56"}, - {{0x21,0x06,0xF7,0xF1,0xEB,0xF6,0xA6,0xFE,0x11,0x1E,0x58,0x3D,0x01,0x1E,0xD9,0x6D}, - {0xED,0x36,0xD8,0x7E,0x9F,0x16,0xC4,0xA5,0x36,0x8F,0xBF,0x20,0x8C,0x4C,0x19,0x19, + .addr="r99hrhMgWrzUssGoLUttkmVFoERgf9fB56"}, + {.seed={0x21,0x06,0xF7,0xF1,0xEB,0xF6,0xA6,0xFE,0x11,0x1E,0x58,0x3D,0x01,0x1E,0xD9,0x6D}, + .pubkey={0xED,0x36,0xD8,0x7E,0x9F,0x16,0xC4,0xA5,0x36,0x8F,0xBF,0x20,0x8C,0x4C,0x19,0x19, 0x55,0x33,0x28,0xEC,0x16,0xA3,0xAB,0xAE,0xD4,0x75,0x4D,0xCC,0x53,0x5C,0xB2,0x4A,0x53}, - {0x9F,0xCE,0x33,0x4E,0x5D,0x8A,0x5F,0xAE,0x98,0x99,0x8A,0x2B,0x31,0x22,0x49,0x53, + .seckey={0x9F,0xCE,0x33,0x4E,0x5D,0x8A,0x5F,0xAE,0x98,0x99,0x8A,0x2B,0x31,0x22,0x49,0x53, 0x5A,0xA3,0x00,0x89,0x53,0x4C,0xFE,0x35,0xBE,0x15,0xD1,0xD3,0xE7,0x5E,0xBB,0x90}, - "rHkZ4ddVoyrkEcANucjXDXouZ8GYVmG9R2"}, - {{0x76,0xE4,0x37,0x07,0x61,0x85,0x64,0x5E,0x78,0x0F,0xAA,0xC3,0x4F,0x22,0x52,0x12}, - {0xED,0x0E,0xFD,0x87,0xB2,0x99,0xE2,0xD2,0x68,0x11,0xFE,0xE6,0x16,0x46,0xF9,0x2D, + .addr="rHkZ4ddVoyrkEcANucjXDXouZ8GYVmG9R2"}, + {.seed={0x76,0xE4,0x37,0x07,0x61,0x85,0x64,0x5E,0x78,0x0F,0xAA,0xC3,0x4F,0x22,0x52,0x12}, + .pubkey={0xED,0x0E,0xFD,0x87,0xB2,0x99,0xE2,0xD2,0x68,0x11,0xFE,0xE6,0x16,0x46,0xF9,0x2D, 0xEF,0x64,0x2E,0x77,0x83,0x3A,0x0D,0xC7,0xFF,0x70,0xDD,0x21,0xEA,0x51,0x1A,0xCB,0xB8}, - {0x8F,0x3C,0xD2,0x22,0x84,0xAE,0xE0,0x91,0x3D,0x11,0x0B,0x29,0xE1,0xAE,0x43,0x12, + .seckey={0x8F,0x3C,0xD2,0x22,0x84,0xAE,0xE0,0x91,0x3D,0x11,0x0B,0x29,0xE1,0xAE,0x43,0x12, 0x8D,0xF6,0xD1,0x2C,0x5F,0x2F,0x1E,0xCD,0xB8,0x93,0xE5,0x69,0x4F,0xA9,0x90,0x40}, - "rB4z3FJgPhFGBKsCvAY6bpN6dbychpAzZm"}, - {{0x7D,0x9A,0x2F,0x52,0xF8,0x5F,0x65,0x6A,0xFC,0x30,0x68,0xFE,0x3A,0xB2,0xDA,0x88}, - {0xED,0x21,0xDC,0x2B,0x58,0x3E,0xE9,0x24,0x89,0x53,0xA6,0xB2,0x37,0xC5,0x97,0xB6, + .addr="rB4z3FJgPhFGBKsCvAY6bpN6dbychpAzZm"}, + {.seed={0x7D,0x9A,0x2F,0x52,0xF8,0x5F,0x65,0x6A,0xFC,0x30,0x68,0xFE,0x3A,0xB2,0xDA,0x88}, + .pubkey={0xED,0x21,0xDC,0x2B,0x58,0x3E,0xE9,0x24,0x89,0x53,0xA6,0xB2,0x37,0xC5,0x97,0xB6, 0x46,0xBC,0xAB,0xD7,0xB0,0xCE,0x73,0xCD,0xE1,0x68,0xA7,0xAC,0x20,0x9A,0x94,0xDF,0xC4}, - {0x2E,0x55,0x98,0xA9,0xA3,0xDD,0x00,0xAF,0xDB,0x2A,0xB0,0x04,0xFA,0x20,0x1E,0xEA, + .seckey={0x2E,0x55,0x98,0xA9,0xA3,0xDD,0x00,0xAF,0xDB,0x2A,0xB0,0x04,0xFA,0x20,0x1E,0xEA, 0x9D,0xB9,0x9F,0xD8,0xB4,0xCC,0xA9,0x30,0x52,0x6A,0x4D,0x97,0x43,0xD2,0x31,0xE1}, - "rjFtnLNUWENruoRZd7WTZxvh2qrNrQEir"}, - {{0xB7,0xFC,0x52,0xEC,0x34,0x1F,0x62,0x2F,0xCF,0xBF,0x90,0x38,0x6F,0x12,0x4D,0x1C}, - {0xED,0xF4,0x8C,0xBC,0xC5,0xEA,0x1A,0xA7,0x1F,0x79,0xC7,0xFB,0x44,0x97,0x3D,0x9A, + .addr="rjFtnLNUWENruoRZd7WTZxvh2qrNrQEir"}, + {.seed={0xB7,0xFC,0x52,0xEC,0x34,0x1F,0x62,0x2F,0xCF,0xBF,0x90,0x38,0x6F,0x12,0x4D,0x1C}, + .pubkey={0xED,0xF4,0x8C,0xBC,0xC5,0xEA,0x1A,0xA7,0x1F,0x79,0xC7,0xFB,0x44,0x97,0x3D,0x9A, 0x60,0xDD,0xA3,0xD4,0x83,0x68,0x31,0xE8,0xF8,0xA3,0x77,0xC8,0x98,0x2C,0x5F,0x64,0x26}, - {0x81,0xD7,0x7D,0x1F,0xE3,0xC0,0xAE,0xD6,0x2C,0x13,0x3A,0xBF,0x7E,0x63,0x20,0x9D, + .seckey={0x81,0xD7,0x7D,0x1F,0xE3,0xC0,0xAE,0xD6,0x2C,0x13,0x3A,0xBF,0x7E,0x63,0x20,0x9D, 0xF6,0x04,0x50,0xD6,0xCC,0x6F,0x6D,0xED,0xC3,0x35,0x10,0x32,0x23,0x36,0x1D,0x74}, - "r4iFodQDmVS27jsvHoa1c58d5MQjHCFCzr"}, - {{0x6B,0xD9,0x01,0xBA,0x68,0xCF,0xAA,0xB3,0x2E,0x58,0x72,0xCC,0x4A,0xD3,0xAC,0xFA}, - {0xED,0x6A,0xF4,0xE2,0x9D,0xFD,0x47,0x5B,0x62,0x2C,0x50,0x17,0x3E,0x70,0x08,0x27, + .addr="r4iFodQDmVS27jsvHoa1c58d5MQjHCFCzr"}, + {.seed={0x6B,0xD9,0x01,0xBA,0x68,0xCF,0xAA,0xB3,0x2E,0x58,0x72,0xCC,0x4A,0xD3,0xAC,0xFA}, + .pubkey={0xED,0x6A,0xF4,0xE2,0x9D,0xFD,0x47,0x5B,0x62,0x2C,0x50,0x17,0x3E,0x70,0x08,0x27, 0x0D,0x78,0x72,0x91,0x3B,0x9D,0xD2,0xA5,0x0F,0xFD,0x92,0x18,0xC5,0x21,0xAA,0xB5,0xDB}, - {0x6A,0x4D,0x06,0xBE,0xB6,0xA6,0x24,0x63,0x93,0x94,0x86,0x5D,0xF3,0xCD,0x21,0x79, + .seckey={0x6A,0x4D,0x06,0xBE,0xB6,0xA6,0x24,0x63,0x93,0x94,0x86,0x5D,0xF3,0xCD,0x21,0x79, 0x36,0xF1,0x60,0x42,0x36,0x8F,0xD4,0xE7,0xE3,0x4E,0x6F,0x48,0xC7,0x5F,0xF5,0x49}, - "raXmHhhDutvNXtZijEds9YkiuRrGSGBu32"}, - {{0x06,0x6E,0xCB,0xEA,0x65,0xA8,0x14,0x8F,0xBB,0x06,0x4B,0x57,0xB3,0x6D,0x53,0x5C}, - {0xED,0x71,0x36,0xFF,0xA4,0xF9,0x41,0xB4,0x99,0x94,0xD4,0x28,0xD6,0xAF,0xE8,0xEF, + .addr="raXmHhhDutvNXtZijEds9YkiuRrGSGBu32"}, + {.seed={0x06,0x6E,0xCB,0xEA,0x65,0xA8,0x14,0x8F,0xBB,0x06,0x4B,0x57,0xB3,0x6D,0x53,0x5C}, + .pubkey={0xED,0x71,0x36,0xFF,0xA4,0xF9,0x41,0xB4,0x99,0x94,0xD4,0x28,0xD6,0xAF,0xE8,0xEF, 0xE4,0xD4,0x18,0x8D,0xC9,0xFB,0xF4,0x9D,0x8A,0x00,0x10,0x3A,0x62,0xEA,0x5A,0xF6,0x20}, - {0xEA,0x33,0xE3,0xD1,0x8B,0x96,0x82,0x5D,0x88,0xC0,0x43,0x78,0xC3,0x54,0x32,0x09, + .seckey={0xEA,0x33,0xE3,0xD1,0x8B,0x96,0x82,0x5D,0x88,0xC0,0x43,0x78,0xC3,0x54,0x32,0x09, 0xAA,0x6F,0xBB,0xDC,0xFB,0x5A,0x8B,0xFE,0xCF,0x8C,0xF6,0x2D,0x9D,0xA9,0xA1,0xE1}, - "rPxBkKtFFZHJT71r3AFdAN4pU89PLQJ1QG"}, - {{0xDC,0x30,0xE4,0xB2,0x30,0xBA,0x1A,0x0C,0xF1,0xA4,0x92,0x7B,0x16,0x58,0xB9,0xA1}, - {0xED,0x82,0x91,0xCA,0xBF,0x4A,0x95,0x59,0x0F,0x32,0x92,0xE5,0x0C,0x3B,0x26,0x2D, + .addr="rPxBkKtFFZHJT71r3AFdAN4pU89PLQJ1QG"}, + {.seed={0xDC,0x30,0xE4,0xB2,0x30,0xBA,0x1A,0x0C,0xF1,0xA4,0x92,0x7B,0x16,0x58,0xB9,0xA1}, + .pubkey={0xED,0x82,0x91,0xCA,0xBF,0x4A,0x95,0x59,0x0F,0x32,0x92,0xE5,0x0C,0x3B,0x26,0x2D, 0x9F,0xF8,0xBE,0xC3,0x61,0x91,0x0E,0xE5,0xAD,0x67,0x0D,0x22,0x56,0xF6,0xB3,0x98,0xB7}, - {0x1D,0x7A,0x60,0x86,0xB7,0xB4,0x96,0x1B,0xFC,0x9D,0x49,0x5C,0x40,0x5D,0x05,0x4C, + .seckey={0x1D,0x7A,0x60,0x86,0xB7,0xB4,0x96,0x1B,0xFC,0x9D,0x49,0x5C,0x40,0x5D,0x05,0x4C, 0x51,0x26,0x55,0x01,0xCC,0xAC,0x6D,0x24,0x55,0xAE,0x87,0x74,0x6C,0xE2,0x45,0xDB}, - "rKFEmeByntzm5pti2qAVvpbTpK8S7sEsV4"}, - {{0xE7,0x17,0xCA,0x1B,0xFC,0x0D,0x50,0xFD,0x7D,0xD7,0x56,0x3C,0x8A,0xDE,0x1B,0xCB}, - {0xED,0x76,0x1C,0xE5,0x81,0xD1,0xB0,0x8F,0xE5,0x3C,0x79,0x18,0xC0,0xB6,0xE6,0x8B, + .addr="rKFEmeByntzm5pti2qAVvpbTpK8S7sEsV4"}, + {.seed={0xE7,0x17,0xCA,0x1B,0xFC,0x0D,0x50,0xFD,0x7D,0xD7,0x56,0x3C,0x8A,0xDE,0x1B,0xCB}, + .pubkey={0xED,0x76,0x1C,0xE5,0x81,0xD1,0xB0,0x8F,0xE5,0x3C,0x79,0x18,0xC0,0xB6,0xE6,0x8B, 0x95,0xA1,0x37,0x32,0xD4,0x2B,0x36,0xBB,0x63,0x4E,0xE6,0x32,0xF8,0xCE,0x4B,0xF1,0x1E}, - {0x9A,0xCF,0x55,0xF4,0xAD,0x1C,0xDE,0xB7,0x6A,0xE8,0x44,0xD5,0x3E,0x15,0x2A,0x20, + .seckey={0x9A,0xCF,0x55,0xF4,0xAD,0x1C,0xDE,0xB7,0x6A,0xE8,0x44,0xD5,0x3E,0x15,0x2A,0x20, 0xF6,0x38,0x59,0xBF,0xB4,0x98,0x2F,0xC7,0x69,0xF2,0x70,0x51,0x2C,0x40,0xBC,0x43}, - "rM7P6ouMaCkvHpCPANcXrhnXcQZUuwHLhT"}, - {{0x6A,0x2E,0xBE,0xE7,0xD7,0x89,0x85,0xB2,0xF6,0x09,0xEE,0x5C,0x23,0x0C,0xE0,0xCB}, - {0xED,0x6A,0xE8,0x90,0x42,0x56,0xFF,0x41,0x90,0xAB,0x1E,0xBE,0x6B,0x7D,0xC7,0x53, + .addr="rM7P6ouMaCkvHpCPANcXrhnXcQZUuwHLhT"}, + {.seed={0x6A,0x2E,0xBE,0xE7,0xD7,0x89,0x85,0xB2,0xF6,0x09,0xEE,0x5C,0x23,0x0C,0xE0,0xCB}, + .pubkey={0xED,0x6A,0xE8,0x90,0x42,0x56,0xFF,0x41,0x90,0xAB,0x1E,0xBE,0x6B,0x7D,0xC7,0x53, 0x7E,0x36,0xB3,0x6A,0x41,0xD0,0x42,0x46,0x22,0x51,0x9D,0x73,0x76,0x0B,0x74,0xE6,0x6A}, - {0x01,0xDD,0x0D,0x7C,0xB3,0xF1,0xF7,0x58,0x0B,0xD9,0x08,0x39,0xE7,0x5F,0x15,0x85, + .seckey={0x01,0xDD,0x0D,0x7C,0xB3,0xF1,0xF7,0x58,0x0B,0xD9,0x08,0x39,0xE7,0x5F,0x15,0x85, 0xE5,0xE4,0x68,0xCC,0x11,0xB3,0x96,0x6A,0x78,0x0F,0xB8,0xBE,0x8A,0xDC,0xF9,0x89}, - "rLGH2vSxgeeM5QynNK8qmcdFtTP1rfg4Tg"}, - {{0x00,0xCC,0xB8,0xC5,0x68,0x56,0x59,0xAD,0x75,0xEF,0x34,0xAF,0x9D,0xEA,0xCF,0x77}, - {0xED,0xF8,0x15,0x47,0x9F,0xFC,0x3F,0x8F,0x60,0xF8,0xE2,0x97,0xD4,0x90,0x5F,0x0D, + .addr="rLGH2vSxgeeM5QynNK8qmcdFtTP1rfg4Tg"}, + {.seed={0x00,0xCC,0xB8,0xC5,0x68,0x56,0x59,0xAD,0x75,0xEF,0x34,0xAF,0x9D,0xEA,0xCF,0x77}, + .pubkey={0xED,0xF8,0x15,0x47,0x9F,0xFC,0x3F,0x8F,0x60,0xF8,0xE2,0x97,0xD4,0x90,0x5F,0x0D, 0x7A,0x7D,0x34,0x5A,0x1E,0x49,0x1B,0x27,0x7F,0x49,0x9E,0x80,0xB0,0xDE,0x3A,0x9D,0xDD}, - {0x27,0x2D,0x1E,0xF0,0xD6,0xE0,0xE1,0x03,0x1E,0xEC,0xE9,0x71,0x4E,0x74,0x5E,0xEF, + .seckey={0x27,0x2D,0x1E,0xF0,0xD6,0xE0,0xE1,0x03,0x1E,0xEC,0xE9,0x71,0x4E,0x74,0x5E,0xEF, 0xCC,0x97,0xA4,0xF0,0x98,0xC7,0x5D,0x1D,0x2B,0xDB,0xDA,0x02,0xAC,0x92,0xE9,0x17}, - "r3sq3TsjtadXRzt7N5bUCkJCBCrR26zNZJ"}, - {{0xB4,0xBC,0x80,0x7F,0xDA,0x31,0xB0,0xD1,0x69,0x7B,0xEB,0x0F,0xE9,0x9A,0x32,0x4B}, - {0xED,0xD6,0x6A,0x27,0x23,0x52,0xAB,0x9B,0xD4,0x72,0xC0,0xD6,0x88,0xA2,0x75,0x68, + .addr="r3sq3TsjtadXRzt7N5bUCkJCBCrR26zNZJ"}, + {.seed={0xB4,0xBC,0x80,0x7F,0xDA,0x31,0xB0,0xD1,0x69,0x7B,0xEB,0x0F,0xE9,0x9A,0x32,0x4B}, + .pubkey={0xED,0xD6,0x6A,0x27,0x23,0x52,0xAB,0x9B,0xD4,0x72,0xC0,0xD6,0x88,0xA2,0x75,0x68, 0x0A,0xAC,0x21,0x8B,0xAB,0x3C,0x99,0xDA,0xAE,0x89,0xCF,0x27,0xDD,0x7D,0x04,0x80,0xA3}, - {0x10,0xA2,0xBA,0xF1,0xBA,0xDB,0x75,0x0C,0x71,0x79,0xAC,0x82,0x95,0x81,0x2E,0xFA, + .seckey={0x10,0xA2,0xBA,0xF1,0xBA,0xDB,0x75,0x0C,0x71,0x79,0xAC,0x82,0x95,0x81,0x2E,0xFA, 0x83,0x7C,0x63,0x7C,0x9C,0x45,0x67,0x4C,0x59,0x49,0x7B,0x17,0x0C,0x2A,0xF3,0x91}, - "rPbwRy2SXuYjHBpamBttr73degia9aLTJV"}, - {{0x1F,0x79,0x56,0x35,0x2E,0x46,0x34,0xFD,0x32,0xDA,0xF2,0xE1,0xE7,0xA5,0xF8,0x6D}, - {0xED,0x6A,0xB7,0x3A,0x06,0xC0,0x29,0x6D,0x38,0x4F,0xC1,0xFE,0x79,0xF1,0xCA,0xBB, + .addr="rPbwRy2SXuYjHBpamBttr73degia9aLTJV"}, + {.seed={0x1F,0x79,0x56,0x35,0x2E,0x46,0x34,0xFD,0x32,0xDA,0xF2,0xE1,0xE7,0xA5,0xF8,0x6D}, + .pubkey={0xED,0x6A,0xB7,0x3A,0x06,0xC0,0x29,0x6D,0x38,0x4F,0xC1,0xFE,0x79,0xF1,0xCA,0xBB, 0x1A,0xBD,0xAE,0xCF,0xA4,0x49,0xE5,0x52,0x68,0xB6,0x16,0xF5,0x05,0xF7,0xE0,0x1D,0xE0}, - {0x67,0x22,0xC5,0xC3,0x26,0xEF,0x52,0x22,0xE5,0x03,0xFA,0x61,0x01,0x64,0x49,0x63, + .seckey={0x67,0x22,0xC5,0xC3,0x26,0xEF,0x52,0x22,0xE5,0x03,0xFA,0x61,0x01,0x64,0x49,0x63, 0xF0,0x54,0x84,0x01,0x34,0x43,0xF5,0xF0,0xCA,0x72,0x55,0xE5,0xDB,0xA7,0xCE,0xD6}, - "rfS18n1hbRaEaJw79nbV2ZTHSmMuRUsDdN"}, - {{0xD1,0xF7,0x94,0xBE,0xFD,0x48,0x18,0x3F,0xA8,0x00,0x0C,0xCE,0x91,0x93,0x65,0xD5}, - {0xED,0xEA,0x05,0x86,0x7C,0x18,0x83,0x59,0x07,0x2C,0xA7,0x82,0x09,0xB1,0xF5,0x63, + .addr="rfS18n1hbRaEaJw79nbV2ZTHSmMuRUsDdN"}, + {.seed={0xD1,0xF7,0x94,0xBE,0xFD,0x48,0x18,0x3F,0xA8,0x00,0x0C,0xCE,0x91,0x93,0x65,0xD5}, + .pubkey={0xED,0xEA,0x05,0x86,0x7C,0x18,0x83,0x59,0x07,0x2C,0xA7,0x82,0x09,0xB1,0xF5,0x63, 0x3B,0x52,0x1D,0xF3,0x2A,0x6C,0x95,0x44,0xE1,0xD8,0xD8,0x07,0x06,0xA0,0x7B,0x24,0xCD}, - {0x5E,0x8E,0xCA,0xB0,0xC2,0xA3,0x33,0x8C,0x21,0xDF,0xEC,0xC3,0x34,0xF6,0x48,0x3A, + .seckey={0x5E,0x8E,0xCA,0xB0,0xC2,0xA3,0x33,0x8C,0x21,0xDF,0xEC,0xC3,0x34,0xF6,0x48,0x3A, 0x87,0x83,0xEB,0xD9,0x9F,0xF3,0x45,0xBC,0x3F,0xFE,0x13,0x54,0x35,0x5F,0xF8,0x92}, - "rKj6PKqZAtChGkjspvRCP9SR5JkqUvzrnY"}, - {{0x3D,0x77,0x10,0x8C,0x0D,0x8D,0xE3,0xB6,0x78,0xC2,0x44,0x42,0x39,0xB8,0xD2,0xD6}, - {0xED,0x90,0x0C,0xCE,0x77,0xC7,0x19,0xAF,0x5A,0xF2,0xEF,0xBE,0x4B,0xC1,0xA4,0xF6, + .addr="rKj6PKqZAtChGkjspvRCP9SR5JkqUvzrnY"}, + {.seed={0x3D,0x77,0x10,0x8C,0x0D,0x8D,0xE3,0xB6,0x78,0xC2,0x44,0x42,0x39,0xB8,0xD2,0xD6}, + .pubkey={0xED,0x90,0x0C,0xCE,0x77,0xC7,0x19,0xAF,0x5A,0xF2,0xEF,0xBE,0x4B,0xC1,0xA4,0xF6, 0xA7,0x66,0x11,0xBF,0x6D,0x57,0xA5,0xAE,0xAC,0x18,0x12,0x3E,0x76,0x5A,0xCC,0x5B,0x10}, - {0x6E,0x32,0x83,0xAB,0x90,0xCB,0xA6,0x69,0xF1,0xD8,0xF9,0xED,0xCF,0x0A,0xF4,0x11, + .seckey={0x6E,0x32,0x83,0xAB,0x90,0xCB,0xA6,0x69,0xF1,0xD8,0xF9,0xED,0xCF,0x0A,0xF4,0x11, 0xC3,0x67,0x15,0xF5,0xA3,0x0E,0x29,0x26,0x2E,0xC9,0xB2,0x9F,0xCB,0x08,0x2B,0xAF}, - "rn7ddvAPRroTMaF3fGh4DUw3H5h1u4CdCP"}, - {{0xB4,0x7E,0x80,0x90,0xFD,0xCC,0xD9,0x90,0xC2,0x66,0x7A,0x92,0xDF,0x29,0x36,0xAE}, - {0xED,0x83,0xDD,0xE0,0x7B,0xFE,0xC4,0x4F,0x53,0xB9,0x78,0x79,0xFD,0xC3,0xFF,0x47, + .addr="rn7ddvAPRroTMaF3fGh4DUw3H5h1u4CdCP"}, + {.seed={0xB4,0x7E,0x80,0x90,0xFD,0xCC,0xD9,0x90,0xC2,0x66,0x7A,0x92,0xDF,0x29,0x36,0xAE}, + .pubkey={0xED,0x83,0xDD,0xE0,0x7B,0xFE,0xC4,0x4F,0x53,0xB9,0x78,0x79,0xFD,0xC3,0xFF,0x47, 0xDD,0xB5,0x09,0xCF,0x3B,0xFC,0xB4,0x49,0x7E,0x8E,0x8A,0x83,0x1D,0xD8,0x80,0x36,0x83}, - {0x03,0x54,0x27,0x82,0x38,0x85,0x9B,0xBD,0x07,0xB7,0xE7,0xBE,0xF4,0xC7,0x20,0xE4, + .seckey={0x03,0x54,0x27,0x82,0x38,0x85,0x9B,0xBD,0x07,0xB7,0xE7,0xBE,0xF4,0xC7,0x20,0xE4, 0xB8,0x74,0xAD,0x77,0xC9,0xA5,0x8A,0x8A,0x62,0xD4,0xA4,0x5F,0xB0,0x62,0xE7,0x7F}, - "rfTHpX7jyQgVL4T5ErYvTVMfYEiQmrhpdu"}, - {{0xDF,0x89,0xC9,0xA7,0xB6,0xFB,0xB5,0x03,0xFF,0xD3,0x16,0xA0,0xBF,0x57,0xED,0xD1}, - {0xED,0x03,0x48,0x03,0x06,0x47,0x50,0xC9,0xB6,0xE9,0x7B,0x9F,0xD0,0x53,0x50,0xAE, + .addr="rfTHpX7jyQgVL4T5ErYvTVMfYEiQmrhpdu"}, + {.seed={0xDF,0x89,0xC9,0xA7,0xB6,0xFB,0xB5,0x03,0xFF,0xD3,0x16,0xA0,0xBF,0x57,0xED,0xD1}, + .pubkey={0xED,0x03,0x48,0x03,0x06,0x47,0x50,0xC9,0xB6,0xE9,0x7B,0x9F,0xD0,0x53,0x50,0xAE, 0x5F,0x92,0xEF,0xDA,0x63,0xBB,0xF7,0xD7,0x81,0x69,0x96,0x43,0x71,0xCF,0x97,0xD4,0xCD}, - {0xBA,0xA9,0x3D,0x91,0xEE,0xEE,0x96,0xBD,0x0C,0x53,0xEF,0x0F,0xF2,0x8C,0xD6,0xA2, + .seckey={0xBA,0xA9,0x3D,0x91,0xEE,0xEE,0x96,0xBD,0x0C,0x53,0xEF,0x0F,0xF2,0x8C,0xD6,0xA2, 0x48,0xDD,0xF8,0x9F,0xC1,0x22,0x45,0x70,0x0C,0xD5,0x3C,0xA2,0x20,0x65,0x8F,0x7B}, - "rM84gaT9yh6TXS51xsTawH2CDrEZeuffgZ"}, - {{0x88,0x2D,0xB9,0x2E,0x15,0xCC,0x70,0xB0,0xE0,0xD7,0x82,0x06,0x4E,0x7F,0x06,0xA4}, - {0xED,0x30,0xC8,0x1F,0x58,0x63,0x63,0xD4,0x61,0xFF,0x8C,0x21,0x18,0xF9,0x2F,0x19, + .addr="rM84gaT9yh6TXS51xsTawH2CDrEZeuffgZ"}, + {.seed={0x88,0x2D,0xB9,0x2E,0x15,0xCC,0x70,0xB0,0xE0,0xD7,0x82,0x06,0x4E,0x7F,0x06,0xA4}, + .pubkey={0xED,0x30,0xC8,0x1F,0x58,0x63,0x63,0xD4,0x61,0xFF,0x8C,0x21,0x18,0xF9,0x2F,0x19, 0x9A,0xDA,0x24,0x94,0xA7,0xD1,0x9C,0xC5,0x19,0xAD,0xC6,0x5D,0xA5,0x95,0xD9,0x56,0xEA}, - {0x69,0x2D,0xDE,0xE4,0xD0,0x31,0xF3,0x30,0x1D,0xDC,0x17,0x1F,0x40,0x1F,0xB5,0xDF, + .seckey={0x69,0x2D,0xDE,0xE4,0xD0,0x31,0xF3,0x30,0x1D,0xDC,0x17,0x1F,0x40,0x1F,0xB5,0xDF, 0x41,0xEA,0xD3,0x8B,0x45,0x94,0x8D,0x79,0x81,0xCD,0x6B,0x84,0xE8,0x25,0x43,0xCF}, - "rH2q2kVDnmazJUGuEJrskgPdMZehdLzeUA"}, - {{0x84,0xB7,0xB5,0x7E,0x48,0x52,0x17,0xDA,0xDD,0xF9,0x33,0xBA,0x5A,0x04,0x38,0xF4}, - {0xED,0x16,0x8A,0x23,0xDB,0x39,0x5C,0x84,0x6D,0xC1,0x5A,0x68,0x44,0xE3,0x51,0xAC, + .addr="rH2q2kVDnmazJUGuEJrskgPdMZehdLzeUA"}, + {.seed={0x84,0xB7,0xB5,0x7E,0x48,0x52,0x17,0xDA,0xDD,0xF9,0x33,0xBA,0x5A,0x04,0x38,0xF4}, + .pubkey={0xED,0x16,0x8A,0x23,0xDB,0x39,0x5C,0x84,0x6D,0xC1,0x5A,0x68,0x44,0xE3,0x51,0xAC, 0x9A,0xD8,0x18,0x02,0x34,0xC5,0xAC,0xAF,0x02,0xB6,0xEB,0x3A,0x7E,0x9D,0x51,0x7B,0x82}, - {0x5E,0xEB,0x85,0xCE,0xD3,0x5D,0x95,0x77,0x66,0x4A,0x8D,0x6B,0x2B,0xE0,0x71,0x61, + .seckey={0x5E,0xEB,0x85,0xCE,0xD3,0x5D,0x95,0x77,0x66,0x4A,0x8D,0x6B,0x2B,0xE0,0x71,0x61, 0xF3,0x46,0x2C,0x43,0x20,0x09,0xEB,0x8E,0x1A,0x45,0x4D,0x27,0x28,0x5C,0x7A,0x4C}, - "rsHuiJJnnhyGf4vtT3wVt4ZnsxdA8P7sZm"}, - {{0xBC,0x7C,0x5D,0x12,0x55,0x64,0xD2,0x5C,0xD0,0x70,0xA9,0xDE,0x04,0x61,0xAA,0x9E}, - {0xED,0x86,0xD3,0xD8,0x44,0x91,0xA7,0x8F,0x78,0xA8,0x16,0x8E,0x92,0x6C,0x0A,0x72, + .addr="rsHuiJJnnhyGf4vtT3wVt4ZnsxdA8P7sZm"}, + {.seed={0xBC,0x7C,0x5D,0x12,0x55,0x64,0xD2,0x5C,0xD0,0x70,0xA9,0xDE,0x04,0x61,0xAA,0x9E}, + .pubkey={0xED,0x86,0xD3,0xD8,0x44,0x91,0xA7,0x8F,0x78,0xA8,0x16,0x8E,0x92,0x6C,0x0A,0x72, 0x84,0x4B,0xD4,0xEB,0x15,0x9C,0xF7,0xB8,0x3F,0xF1,0x4E,0xC6,0x54,0x3D,0xB1,0xE3,0x2A}, - {0x16,0x22,0xF0,0x6D,0xB9,0x22,0xF8,0xA1,0x27,0x46,0x0E,0xF9,0x9C,0x31,0x3E,0xA9, + .seckey={0x16,0x22,0xF0,0x6D,0xB9,0x22,0xF8,0xA1,0x27,0x46,0x0E,0xF9,0x9C,0x31,0x3E,0xA9, 0x34,0xE9,0xC7,0x7D,0xC3,0x5F,0x02,0x6D,0x1C,0xEF,0x71,0x64,0x75,0x97,0x46,0x83}, - "rnesu4gx539Q7CwFkZQtG1AuGsjHNZro46"}, - {{0x4B,0x58,0x3A,0xF8,0xAF,0x5C,0xFF,0xC3,0xFD,0x39,0x74,0xBE,0x05,0x62,0x23,0x5F}, - {0xED,0x5B,0x64,0x25,0xC7,0x8D,0xEE,0xF6,0x4A,0x97,0x9C,0xF9,0xAF,0xFF,0xD3,0x8C, + .addr="rnesu4gx539Q7CwFkZQtG1AuGsjHNZro46"}, + {.seed={0x4B,0x58,0x3A,0xF8,0xAF,0x5C,0xFF,0xC3,0xFD,0x39,0x74,0xBE,0x05,0x62,0x23,0x5F}, + .pubkey={0xED,0x5B,0x64,0x25,0xC7,0x8D,0xEE,0xF6,0x4A,0x97,0x9C,0xF9,0xAF,0xFF,0xD3,0x8C, 0x57,0x15,0x6B,0x73,0xD9,0xB4,0xB0,0xDB,0x6C,0xAC,0x5F,0xA1,0x25,0x79,0xDA,0xA8,0x53}, - {0xF5,0x40,0x53,0x15,0xA2,0x34,0x9E,0xCE,0x4E,0x1E,0xE5,0x19,0x78,0x99,0xB3,0xE4, + .seckey={0xF5,0x40,0x53,0x15,0xA2,0x34,0x9E,0xCE,0x4E,0x1E,0xE5,0x19,0x78,0x99,0xB3,0xE4, 0x0D,0xB7,0x84,0x05,0xCF,0x60,0x78,0x3E,0x82,0x23,0x22,0x2A,0x0D,0xE2,0x34,0xAB}, - "rHYvTtFVVm7fed5no7JjXQnVJz3vMV2dRV"}, - {{0x13,0xB4,0x06,0x1F,0x0E,0xB1,0xE6,0x94,0xC3,0x1A,0x4A,0x73,0x2E,0x6B,0x26,0xA7}, - {0xED,0x61,0x7A,0x04,0xF7,0xC3,0xBA,0x23,0xF0,0x1B,0x93,0x57,0x25,0x1B,0x6E,0x42, + .addr="rHYvTtFVVm7fed5no7JjXQnVJz3vMV2dRV"}, + {.seed={0x13,0xB4,0x06,0x1F,0x0E,0xB1,0xE6,0x94,0xC3,0x1A,0x4A,0x73,0x2E,0x6B,0x26,0xA7}, + .pubkey={0xED,0x61,0x7A,0x04,0xF7,0xC3,0xBA,0x23,0xF0,0x1B,0x93,0x57,0x25,0x1B,0x6E,0x42, 0x63,0x18,0x72,0x1B,0x55,0x32,0xEC,0x81,0x03,0x17,0x6A,0x65,0xB9,0xCE,0x49,0xEA,0x09}, - {0xC5,0xF4,0xEB,0x19,0x28,0xD6,0x80,0xFA,0x85,0x59,0xC5,0xC1,0xB1,0x15,0x68,0x03, + .seckey={0xC5,0xF4,0xEB,0x19,0x28,0xD6,0x80,0xFA,0x85,0x59,0xC5,0xC1,0xB1,0x15,0x68,0x03, 0x9C,0x8C,0xF4,0x38,0x9E,0x5B,0x62,0x98,0x3A,0x05,0x2E,0x69,0x17,0x0B,0x5C,0x52}, - "rKggDDftQLRV2sSrYexWZXhA5nMwLzguZq"}, - {{0xE2,0xCE,0x8C,0xE1,0x86,0xDF,0x83,0x43,0x40,0x1F,0x78,0x6E,0xA0,0x17,0x9D,0x2D}, - {0xED,0x49,0xEA,0x8C,0x43,0x5F,0xB1,0xBC,0xC0,0xC9,0x59,0xD8,0xAF,0x24,0x24,0xFC, + .addr="rKggDDftQLRV2sSrYexWZXhA5nMwLzguZq"}, + {.seed={0xE2,0xCE,0x8C,0xE1,0x86,0xDF,0x83,0x43,0x40,0x1F,0x78,0x6E,0xA0,0x17,0x9D,0x2D}, + .pubkey={0xED,0x49,0xEA,0x8C,0x43,0x5F,0xB1,0xBC,0xC0,0xC9,0x59,0xD8,0xAF,0x24,0x24,0xFC, 0xDB,0xA4,0x96,0x92,0xD6,0xA2,0x58,0xB3,0xA3,0xFD,0xEB,0x86,0xBE,0x3E,0x71,0x3F,0x29}, - {0x1D,0x75,0xE3,0xFB,0x07,0x54,0xEA,0x72,0x69,0xAD,0xBA,0x37,0x20,0x7B,0xFB,0xEF, + .seckey={0x1D,0x75,0xE3,0xFB,0x07,0x54,0xEA,0x72,0x69,0xAD,0xBA,0x37,0x20,0x7B,0xFB,0xEF, 0x81,0x22,0x88,0x94,0xBB,0x8B,0xDA,0x0F,0x1E,0x37,0xC6,0xF8,0x0D,0x46,0xA5,0x5B}, - "rwgf6e2bg4a8vPadtsgZ6HJuNsorufBwzZ"}, - {{0xDE,0x5E,0xDD,0x25,0x80,0x44,0x31,0x37,0xF8,0x38,0x72,0xC4,0x39,0x69,0x9C,0x51}, - {0xED,0x39,0x3B,0x1C,0x26,0x8C,0x24,0x11,0x4B,0xD5,0xD4,0xB5,0xAB,0xA7,0xAE,0x48, + .addr="rwgf6e2bg4a8vPadtsgZ6HJuNsorufBwzZ"}, + {.seed={0xDE,0x5E,0xDD,0x25,0x80,0x44,0x31,0x37,0xF8,0x38,0x72,0xC4,0x39,0x69,0x9C,0x51}, + .pubkey={0xED,0x39,0x3B,0x1C,0x26,0x8C,0x24,0x11,0x4B,0xD5,0xD4,0xB5,0xAB,0xA7,0xAE,0x48, 0xED,0x5E,0xF0,0x0B,0x2F,0x53,0x1C,0x49,0x64,0xF8,0xEA,0x16,0x76,0xDA,0xDF,0xE0,0x51}, - {0xBC,0xFD,0x2C,0x3D,0x65,0x97,0x83,0x64,0x04,0x42,0x2E,0x5C,0x72,0x5C,0xC4,0xCF, + .seckey={0xBC,0xFD,0x2C,0x3D,0x65,0x97,0x83,0x64,0x04,0x42,0x2E,0x5C,0x72,0x5C,0xC4,0xCF, 0x28,0x92,0x81,0xE3,0xAB,0xF2,0x22,0xA4,0x2A,0xCF,0xE1,0xB5,0xDE,0x5F,0x70,0x10}, - "rD7xAXQhKidGjkVtzo7ixZr3c3PCEiiDRS"}, - {{0xC7,0x41,0xF5,0x5E,0x62,0x2D,0x4F,0x10,0xAE,0xCA,0xA5,0x0D,0x91,0xA7,0xD3,0x66}, - {0xED,0x36,0x81,0xFA,0xD1,0xF5,0xCF,0xBB,0x60,0x28,0x88,0x6D,0x51,0xB1,0xC7,0x75, + .addr="rD7xAXQhKidGjkVtzo7ixZr3c3PCEiiDRS"}, + {.seed={0xC7,0x41,0xF5,0x5E,0x62,0x2D,0x4F,0x10,0xAE,0xCA,0xA5,0x0D,0x91,0xA7,0xD3,0x66}, + .pubkey={0xED,0x36,0x81,0xFA,0xD1,0xF5,0xCF,0xBB,0x60,0x28,0x88,0x6D,0x51,0xB1,0xC7,0x75, 0xD1,0xCA,0xB7,0x10,0xED,0x86,0x75,0xA1,0x71,0x9C,0x41,0x81,0xC8,0x9D,0xB9,0x04,0xC7}, - {0xA3,0x5A,0xBA,0xF9,0xB2,0xE5,0x61,0x92,0xAC,0x22,0x67,0x8F,0x5A,0x5E,0x61,0x35, + .seckey={0xA3,0x5A,0xBA,0xF9,0xB2,0xE5,0x61,0x92,0xAC,0x22,0x67,0x8F,0x5A,0x5E,0x61,0x35, 0xAE,0x67,0x96,0xDD,0x75,0xFB,0x00,0xB1,0xF8,0x4F,0xC4,0x48,0x4A,0xEA,0xFA,0xFC}, - "rwdo3TEgC2uS173RasWvhBouuWfTSFEPhD"}, - {{0xF5,0x73,0xD8,0xDE,0xA2,0x07,0xDB,0x93,0x96,0x9A,0x92,0x2E,0xE6,0xE7,0x3C,0xBA}, - {0xED,0x61,0x55,0xF1,0xD3,0xDA,0xD3,0x19,0xCF,0xB3,0xDE,0xB1,0xCA,0xCB,0x65,0xDC, + .addr="rwdo3TEgC2uS173RasWvhBouuWfTSFEPhD"}, + {.seed={0xF5,0x73,0xD8,0xDE,0xA2,0x07,0xDB,0x93,0x96,0x9A,0x92,0x2E,0xE6,0xE7,0x3C,0xBA}, + .pubkey={0xED,0x61,0x55,0xF1,0xD3,0xDA,0xD3,0x19,0xCF,0xB3,0xDE,0xB1,0xCA,0xCB,0x65,0xDC, 0x63,0xAD,0x61,0x67,0xB6,0x1C,0xFA,0x52,0xCF,0x2B,0x07,0x7C,0x15,0xF0,0xCD,0x41,0x6A}, - {0x0C,0x87,0x0A,0x05,0x07,0x25,0x95,0x4D,0x66,0x4E,0xE0,0x01,0x0C,0x58,0x0A,0xD6, + .seckey={0x0C,0x87,0x0A,0x05,0x07,0x25,0x95,0x4D,0x66,0x4E,0xE0,0x01,0x0C,0x58,0x0A,0xD6, 0x28,0x52,0x03,0x05,0x6A,0xAF,0x6F,0x59,0x43,0x02,0xCB,0x98,0xE0,0x3E,0x56,0xE3}, - "rNXHq25tNB23Ce6fbPw2wakxnmdzE3Fq6Q"} + .addr="rNXHq25tNB23Ce6fbPw2wakxnmdzE3Fq6Q"} }; // clang-format on diff --git a/src/test/protocol/Seed_test.cpp b/src/test/protocol/Seed_test.cpp index d7ad1f4afa..ee4709a780 100644 --- a/src/test/protocol/Seed_test.cpp +++ b/src/test/protocol/Seed_test.cpp @@ -1,11 +1,18 @@ +#include +#include #include -#include +#include #include +#include +#include #include #include #include +#include #include +#include +#include namespace xrpl { @@ -100,8 +107,8 @@ public: void testKeypairGenerationAndSigning() { - std::string const message1 = "http://www.ripple.com"; - std::string const message2 = "https://www.ripple.com"; + std::string const message1 = "http://www.xrpl.org"; + std::string const message2 = "https://www.xrpl.org"; { testcase("Node keypair generation & signing (secp256k1)"); diff --git a/src/test/protocol/SeqProxy_test.cpp b/src/test/protocol/SeqProxy_test.cpp index 7e7a21ab6e..d0764d7399 100644 --- a/src/test/protocol/SeqProxy_test.cpp +++ b/src/test/protocol/SeqProxy_test.cpp @@ -1,8 +1,10 @@ -#include +#include #include +#include #include #include +#include namespace xrpl { @@ -52,7 +54,7 @@ struct SeqProxy_test : public beast::unit_test::suite ss << seqProx; std::string str{ss.str()}; - return str.find(type) == 0 && str[type.size()] == ' ' && + return str.starts_with(type) && str[type.size()] == ' ' && str.find(value) == (type.size() + 1); } diff --git a/src/test/protocol/Serializer_test.cpp b/src/test/protocol/Serializer_test.cpp index e4eaac8a58..7cf27b4348 100644 --- a/src/test/protocol/Serializer_test.cpp +++ b/src/test/protocol/Serializer_test.cpp @@ -1,6 +1,8 @@ -#include +#include #include +#include +#include #include namespace xrpl { diff --git a/src/test/protocol/TER_test.cpp b/src/test/protocol/TER_test.cpp index 2ad4b634aa..5b83a7905b 100644 --- a/src/test/protocol/TER_test.cpp +++ b/src/test/protocol/TER_test.cpp @@ -1,6 +1,8 @@ -#include +#include #include +#include +#include #include #include @@ -49,20 +51,18 @@ struct TER_test : public beast::unit_test::suite using To_t = std::decay_t(tup))>; using From_t = std::decay_t(tup))>; static_assert( - std::is_same::value == std::is_convertible::value, - "Convert err"); + std::is_same_v == std::is_convertible_v, "Convert err"); static_assert( - std::is_same::value == std::is_constructible::value, + std::is_same_v == std::is_constructible_v, "Construct err"); static_assert( - std::is_same::value == - std::is_assignable::value, + std::is_same_v == std::is_assignable_v, "Assign err"); // Assignment or conversion from integer to type should never work. - static_assert(!std::is_convertible::value, "Convert err"); - static_assert(!std::is_constructible::value, "Construct err"); - static_assert(!std::is_assignable::value, "Assign err"); + static_assert(!std::is_convertible_v, "Convert err"); + static_assert(!std::is_constructible_v, "Construct err"); + static_assert(!std::is_assignable_v, "Assign err"); } }; @@ -91,7 +91,7 @@ struct TER_test : public beast::unit_test::suite { Func const func; func(tup, s); - testIterate::value - 1, I2 - 1, Func>(tup, s); + testIterate - 1, I2 - 1, Func>(tup, s); } // Finish iteration over the tuple. @@ -116,7 +116,7 @@ struct TER_test : public beast::unit_test::suite // Examples of each kind of enum. static auto const terEnums = std::make_tuple( telLOCAL_ERROR, temMALFORMED, tefFAILURE, terRETRY, tesSUCCESS, tecCLAIM); - static int const hiIndex{std::tuple_size::value - 1}; + static int const hiIndex{std::tuple_size_v - 1}; // Verify that enums cannot be converted to other enum types. testIterate(terEnums, *this); @@ -125,9 +125,9 @@ struct TER_test : public beast::unit_test::suite auto isConvertible = [](auto from, auto to) { using From_t = std::decay_t; using To_t = std::decay_t; - static_assert(std::is_convertible::value, "Convert err"); - static_assert(std::is_constructible::value, "Construct err"); - static_assert(std::is_assignable::value, "Assign err"); + static_assert(std::is_convertible_v, "Convert err"); + static_assert(std::is_constructible_v, "Construct err"); + static_assert(std::is_assignable_v, "Assign err"); }; // Verify the right types convert to NotTEC. @@ -143,9 +143,9 @@ struct TER_test : public beast::unit_test::suite auto notConvertible = [](auto from, auto to) { using To_t = std::decay_t; using From_t = std::decay_t; - static_assert(!std::is_convertible::value, "Convert err"); - static_assert(!std::is_constructible::value, "Construct err"); - static_assert(!std::is_assignable::value, "Assign err"); + static_assert(!std::is_convertible_v, "Convert err"); + static_assert(!std::is_constructible_v, "Construct err"); + static_assert(!std::is_assignable_v, "Assign err"); }; // Verify types that shouldn't convert to NotTEC. @@ -187,17 +187,17 @@ struct TER_test : public beast::unit_test::suite auto const lhs = std::get(tup); auto const rhs = std::get(tup); - static_assert(std::is_same::value, "== err"); + static_assert(std::is_same_v, "== err"); - static_assert(std::is_same::value, "!= err"); + static_assert(std::is_same_v, "!= err"); - static_assert(std::is_same::value, "< err"); + static_assert(std::is_same_v, "< err"); - static_assert(std::is_same::value, "<= err"); + static_assert(std::is_same_v, "<= err"); - static_assert(std::is_same(lhs, rhs)), bool>::value, "> err"); + static_assert(std::is_same_v(lhs, rhs)), bool>, "> err"); - static_assert(std::is_same=(lhs, rhs)), bool>::value, ">= err"); + static_assert(std::is_same_v=(lhs, rhs)), bool>, ">= err"); // Make sure a sampling of TER types exhibit the expected behavior // for all comparison operators. @@ -225,7 +225,7 @@ struct TER_test : public beast::unit_test::suite tecCLAIM, NotTEC{telLOCAL_ERROR}, TER{tecCLAIM}); - static int const hiIndex{std::tuple_size::value - 1}; + static int const hiIndex{std::tuple_size_v - 1}; // Verify that all types in the ters tuple can be compared with all // the other types in ters. diff --git a/src/test/resource/Logic_test.cpp b/src/test/resource/Logic_test.cpp index 12c1e631e2..f6da313b22 100644 --- a/src/test/resource/Logic_test.cpp +++ b/src/test/resource/Logic_test.cpp @@ -1,18 +1,27 @@ #include +#include #include #include -#include +#include +#include +#include +#include +#include #include -#include +#include +#include #include +#include #include +#include +#include #include +#include -namespace xrpl { -namespace Resource { +namespace xrpl::Resource { class ResourceManager_test : public beast::unit_test::suite { @@ -282,5 +291,4 @@ public: BEAST_DEFINE_TESTSUITE(ResourceManager, resource, xrpl); -} // namespace Resource -} // namespace xrpl +} // namespace xrpl::Resource diff --git a/src/test/rpc/AMMInfo_test.cpp b/src/test/rpc/AMMInfo_test.cpp index 3f20e0378d..876c9b66f1 100644 --- a/src/test/rpc/AMMInfo_test.cpp +++ b/src/test/rpc/AMMInfo_test.cpp @@ -1,13 +1,30 @@ -#include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include #include +#include +#include -namespace xrpl { -namespace test { +namespace xrpl::test { class AMMInfo_test : public jtx::AMMTestBase { @@ -38,7 +55,7 @@ public: testAMM([&](AMM& ammAlice, Env&) { Account const gw("gw"); auto const USD = gw["USD"]; - auto const jv = ammAlice.ammRpcInfo({}, {}, USD.issue(), USD.issue()); + auto const jv = ammAlice.ammRpcInfo({}, {}, USD, USD); BEAST_EXPECT(jv[jss::error_message] == "Account not found."); }); @@ -51,10 +68,10 @@ public: std::vector, std::optional, TestAccount, bool>> const invalidParams = { {xrpIssue(), std::nullopt, None, false}, - {std::nullopt, USD.issue(), None, false}, + {std::nullopt, USD, None, false}, {xrpIssue(), std::nullopt, Alice, false}, - {std::nullopt, USD.issue(), Alice, false}, - {xrpIssue(), USD.issue(), Alice, false}, + {std::nullopt, USD, Alice, false}, + {xrpIssue(), USD, Alice, false}, {std::nullopt, std::nullopt, None, true}}; // Invalid parameters @@ -113,10 +130,10 @@ public: std::vector, std::optional, TestAccount, bool>> const invalidParamsBadAccount = { {xrpIssue(), std::nullopt, None, false}, - {std::nullopt, USD.issue(), None, false}, + {std::nullopt, USD, None, false}, {xrpIssue(), std::nullopt, Bogie, false}, - {std::nullopt, USD.issue(), Bogie, false}, - {xrpIssue(), USD.issue(), Bogie, false}, + {std::nullopt, USD, Bogie, false}, + {xrpIssue(), USD, Bogie, false}, {std::nullopt, std::nullopt, None, true}}; // Invalid parameters *and* invalid AMM account, default API version @@ -161,7 +178,6 @@ public: using namespace jtx; testAMM([&](AMM& ammAlice, Env&) { - BEAST_EXPECT(ammAlice.expectAmmRpcInfo(XRP(10000), USD(10000), IOUAmount{10000000, 0})); BEAST_EXPECT(ammAlice.expectAmmRpcInfo( XRP(10000), USD(10000), @@ -170,6 +186,32 @@ public: std::nullopt, ammAlice.ammAccount())); }); + + { + Env env{*this}; + env.fund(XRP(1'000), gw); + MPTTester mpt(env, gw, {.fund = false}); + mpt.create({.flags = tfMPTCanTransfer | tfMPTCanTrade}); + MPTTester mpt1(env, gw, {.fund = false}); + mpt1.create({.flags = tfMPTCanTransfer | tfMPTCanTrade}); + auto const MPT = mpt["MPT"]; + auto const MPT1 = mpt1["MPT"]; + std::vector> pools = { + {XRP(100), MPT(100), IOUAmount{100'000}}, + {USD(100), MPT(100), IOUAmount{100}}, + {MPT(100), MPT1(100), IOUAmount{100}}}; + for (auto& pool : pools) + { + AMM const amm(env, gw, std::get<0>(pool), std::get<1>(pool)); + BEAST_EXPECT(amm.expectAmmRpcInfo( + std::get<0>(pool), + std::get<1>(pool), + std::get<2>(pool), + std::nullopt, + std::nullopt, + amm.ammAccount())); + } + } } void @@ -334,5 +376,4 @@ public: BEAST_DEFINE_TESTSUITE(AMMInfo, rpc, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/rpc/AccountCurrencies_test.cpp b/src/test/rpc/AccountCurrencies_test.cpp index 009af9d454..c3b9135f07 100644 --- a/src/test/rpc/AccountCurrencies_test.cpp +++ b/src/test/rpc/AccountCurrencies_test.cpp @@ -1,8 +1,21 @@ -#include -#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include #include +#include +#include +#include +#include + namespace xrpl { class AccountCurrencies_test : public beast::unit_test::suite @@ -114,7 +127,7 @@ class AccountCurrencies_test : public beast::unit_test::suite env.fund(XRP(10000), alice, gw); char currencySuffix{'A'}; std::vector> gwCurrencies(26); // A - Z - std::generate(gwCurrencies.begin(), gwCurrencies.end(), [&]() { + std::ranges::generate(gwCurrencies, [&]() { auto gwc = gw[std::string("US") + currencySuffix++]; env(trust(alice, gwc(100))); return gwc; diff --git a/src/test/rpc/AccountInfo_test.cpp b/src/test/rpc/AccountInfo_test.cpp index 518398f9ea..a8f849b6d2 100644 --- a/src/test/rpc/AccountInfo_test.cpp +++ b/src/test/rpc/AccountInfo_test.cpp @@ -1,12 +1,26 @@ -#include -#include -#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include #include +#include +#include #include -namespace xrpl { -namespace test { +#include +#include +#include +#include +#include + +namespace xrpl::test { class AccountInfo_test : public beast::unit_test::suite { @@ -627,5 +641,4 @@ public: BEAST_DEFINE_TESTSUITE(AccountInfo, rpc, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/rpc/AccountLines_test.cpp b/src/test/rpc/AccountLines_test.cpp index f91b2aed1c..6f50c93bd8 100644 --- a/src/test/rpc/AccountLines_test.cpp +++ b/src/test/rpc/AccountLines_test.cpp @@ -1,12 +1,40 @@ -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // IWYU pragma: keep +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include #include #include -namespace xrpl { -namespace RPC { +#include +#include +#include +#include + +namespace xrpl::RPC { class AccountLines_test : public beast::unit_test::suite { @@ -1258,5 +1286,4 @@ public: BEAST_DEFINE_TESTSUITE(AccountLines, rpc, xrpl); -} // namespace RPC -} // namespace xrpl +} // namespace xrpl::RPC diff --git a/src/test/rpc/AccountObjects_test.cpp b/src/test/rpc/AccountObjects_test.cpp index b6e8f6fa76..6f3ae2e2b3 100644 --- a/src/test/rpc/AccountObjects_test.cpp +++ b/src/test/rpc/AccountObjects_test.cpp @@ -1,20 +1,40 @@ -#include #include +#include +#include +#include +#include +#include +#include +#include +#include // IWYU pragma: keep +#include +#include +#include +#include +#include #include +#include +#include +#include #include #include #include +#include +#include +#include +#include #include #include #include -#include - #include +#include +#include +#include +#include -namespace xrpl { -namespace test { +namespace xrpl::test { static char const* bob_account_objects[] = { R"json({ @@ -921,7 +941,7 @@ public: jss::RippleState.c_str(), jss::PayChannel.c_str(), jss::PermissionedDomain.c_str()}; - std::sort(v.begin(), v.end()); + std::ranges::sort(v); return v; }(); @@ -937,7 +957,7 @@ public: { gotLedgerTypes.push_back(aobjs[i]["LedgerEntryType"].asString()); } - std::sort(gotLedgerTypes.begin(), gotLedgerTypes.end()); + std::ranges::sort(gotLedgerTypes); BEAST_EXPECT(gotLedgerTypes == expectedLedgerTypes); } } @@ -962,7 +982,7 @@ public: auto const objs = resp[jss::result][jss::account_objects]; for (auto const& obj : resp[jss::result][jss::account_objects]) typesOut.push_back(obj[sfLedgerEntryType.fieldName].asString()); - std::sort(typesOut.begin(), typesOut.end()); + std::ranges::sort(typesOut); }; // Make a lambda we can use to check the number of fetched // account objects and their ledger type @@ -1346,5 +1366,4 @@ public: BEAST_DEFINE_TESTSUITE(AccountObjects, rpc, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/rpc/AccountOffers_test.cpp b/src/test/rpc/AccountOffers_test.cpp index 6b93e0570f..8c93b15c4a 100644 --- a/src/test/rpc/AccountOffers_test.cpp +++ b/src/test/rpc/AccountOffers_test.cpp @@ -1,9 +1,20 @@ -#include +#include +#include +#include +#include +#include +#include +#include // IWYU pragma: keep +#include + +#include +#include +#include +#include #include -namespace xrpl { -namespace test { +namespace xrpl::test { class AccountOffers_test : public beast::unit_test::suite { @@ -288,5 +299,4 @@ public: BEAST_DEFINE_TESTSUITE(AccountOffers, rpc, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/rpc/AccountTx_test.cpp b/src/test/rpc/AccountTx_test.cpp index 2470ec3c4c..60ad83934a 100644 --- a/src/test/rpc/AccountTx_test.cpp +++ b/src/test/rpc/AccountTx_test.cpp @@ -1,17 +1,54 @@ -#include +#include +#include +#include +#include +#include +#include // IWYU pragma: keep +#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include -#include +#include + +#include +#include +#include #include +#include +#include +#include #include +#include +#include +#include +#include #include #include #include -namespace xrpl { +#include +#include +#include +#include +#include +#include +#include +#include +#include -namespace test { +namespace xrpl::test { class AccountTx_test : public beast::unit_test::suite { @@ -863,5 +900,4 @@ public: }; BEAST_DEFINE_TESTSUITE(AccountTx, rpc, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/rpc/AmendmentBlocked_test.cpp b/src/test/rpc/AmendmentBlocked_test.cpp index e61f22fa0e..0476b7406a 100644 --- a/src/test/rpc/AmendmentBlocked_test.cpp +++ b/src/test/rpc/AmendmentBlocked_test.cpp @@ -1,11 +1,26 @@ -#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include #include #include +#include + namespace xrpl { class AmendmentBlocked_test : public beast::unit_test::suite diff --git a/src/test/rpc/BookChanges_test.cpp b/src/test/rpc/BookChanges_test.cpp index 0618f4e7d0..d6881217b5 100644 --- a/src/test/rpc/BookChanges_test.cpp +++ b/src/test/rpc/BookChanges_test.cpp @@ -1,11 +1,21 @@ -#include +#include #include +#include +#include +#include +#include +#include +#include +#include -#include "xrpl/beast/unit_test/suite.h" -#include "xrpl/protocol/jss.h" +#include +#include +#include +#include +#include +#include -namespace xrpl { -namespace test { +namespace xrpl::test { class BookChanges_test : public beast::unit_test::suite { @@ -118,5 +128,4 @@ public: BEAST_DEFINE_TESTSUITE(BookChanges, rpc, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/rpc/Book_test.cpp b/src/test/rpc/Book_test.cpp index 5e149ebc0e..90a621c693 100644 --- a/src/test/rpc/Book_test.cpp +++ b/src/test/rpc/Book_test.cpp @@ -1,17 +1,39 @@ -#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include -#include +#include +#include +#include +#include #include +#include +#include #include +#include #include +#include #include #include -namespace xrpl { -namespace test { +#include +#include +#include +#include + +namespace xrpl::test { class Book_test : public beast::unit_test::suite { @@ -940,7 +962,7 @@ public: BEAST_EXPECT(jrr[jss::offers].size() == 1); auto const jrOffer = jrr[jss::offers][0u]; BEAST_EXPECT(jrOffer[sfAccount.fieldName] == alice.human()); - BEAST_EXPECT(jrOffer[sfBookDirectory.fieldName] == getBookDir(env, XRP, USD.issue())); + BEAST_EXPECT(jrOffer[sfBookDirectory.fieldName] == getBookDir(env, XRP, USD)); BEAST_EXPECT(jrOffer[sfBookNode.fieldName] == "0"); BEAST_EXPECT(jrOffer[jss::Flags] == 0); BEAST_EXPECT(jrOffer[sfLedgerEntryType.fieldName] == jss::Offer); @@ -984,7 +1006,7 @@ public: BEAST_EXPECT(jrr[jss::offers].size() == 2); auto const jrNextOffer = jrr[jss::offers][1u]; BEAST_EXPECT(jrNextOffer[sfAccount.fieldName] == bob.human()); - BEAST_EXPECT(jrNextOffer[sfBookDirectory.fieldName] == getBookDir(env, XRP, USD.issue())); + BEAST_EXPECT(jrNextOffer[sfBookDirectory.fieldName] == getBookDir(env, XRP, USD)); BEAST_EXPECT(jrNextOffer[sfBookNode.fieldName] == "0"); BEAST_EXPECT(jrNextOffer[jss::Flags] == 0); BEAST_EXPECT(jrNextOffer[sfLedgerEntryType.fieldName] == jss::Offer); @@ -1825,5 +1847,4 @@ public: BEAST_DEFINE_TESTSUITE_PRIO(Book, rpc, xrpl, 1); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/rpc/Connect_test.cpp b/src/test/rpc/Connect_test.cpp index c029356bf6..a37c36d907 100644 --- a/src/test/rpc/Connect_test.cpp +++ b/src/test/rpc/Connect_test.cpp @@ -1,5 +1,7 @@ -#include +#include + +#include #include namespace xrpl { diff --git a/src/test/rpc/DeliveredAmount_test.cpp b/src/test/rpc/DeliveredAmount_test.cpp index 358657a6c3..133c5771b7 100644 --- a/src/test/rpc/DeliveredAmount_test.cpp +++ b/src/test/rpc/DeliveredAmount_test.cpp @@ -1,12 +1,30 @@ -#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include -#include +#include #include +#include +#include +#include +#include +#include +#include +#include #include -namespace xrpl { -namespace test { +#include +#include + +namespace xrpl::test { // Helper class to track the expected number `delivered_amount` results. class CheckDeliveredAmount @@ -396,12 +414,11 @@ public: testTxDeliveredAmountRPC(); testAccountDeliveredAmountSubscribe(); - testMPTDeliveredAmountRPC(all - fixMPTDeliveredAmount); + testMPTDeliveredAmountRPC(all - fixMPTDeliveredAmount - featureMPTokensV2); testMPTDeliveredAmountRPC(all); } }; BEAST_DEFINE_TESTSUITE(DeliveredAmount, rpc, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/rpc/DepositAuthorized_test.cpp b/src/test/rpc/DepositAuthorized_test.cpp index 7921d063c9..900adb5c7e 100644 --- a/src/test/rpc/DepositAuthorized_test.cpp +++ b/src/test/rpc/DepositAuthorized_test.cpp @@ -1,9 +1,24 @@ -#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include #include -namespace xrpl { -namespace test { +#include +#include +#include +#include +#include + +namespace xrpl::test { class DepositAuthorized_test : public beast::unit_test::suite { @@ -530,5 +545,4 @@ public: BEAST_DEFINE_TESTSUITE(DepositAuthorized, rpc, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/rpc/Feature_test.cpp b/src/test/rpc/Feature_test.cpp index 657eda2c1c..cdeff57131 100644 --- a/src/test/rpc/Feature_test.cpp +++ b/src/test/rpc/Feature_test.cpp @@ -1,10 +1,25 @@ -#include +#include +#include + +#include + +#include +#include +#include +#include #include +#include #include #include #include +#include +#include +#include +#include +#include + namespace xrpl { class Feature_test : public beast::unit_test::suite @@ -505,11 +520,10 @@ class Feature_test : public beast::unit_test::suite using namespace test::jtx; Env env{*this}; - auto const& supportedAmendments = detail::supportedAmendments(); - auto obsoleteFeature = std::find_if( - std::begin(supportedAmendments), std::end(supportedAmendments), [](auto const& pair) { - return pair.second == VoteBehavior::Obsolete; - }); + auto const& supportedAmendments = xrpl::detail::supportedAmendments(); + auto obsoleteFeature = std::ranges::find_if(supportedAmendments, [](auto const& pair) { + return pair.second == VoteBehavior::Obsolete; + }); if (obsoleteFeature == std::end(supportedAmendments)) { diff --git a/src/test/rpc/GatewayBalances_test.cpp b/src/test/rpc/GatewayBalances_test.cpp index b6efea17fc..be51f60f02 100644 --- a/src/test/rpc/GatewayBalances_test.cpp +++ b/src/test/rpc/GatewayBalances_test.cpp @@ -1,12 +1,21 @@ -#include +#include +#include #include +#include +#include +#include +#include +#include -#include +#include +#include +#include #include +#include +#include #include -namespace xrpl { -namespace test { +namespace xrpl::test { class GatewayBalances_test : public beast::unit_test::suite { @@ -181,7 +190,7 @@ public: auto USD = alice["USD"]; // The largest valid STAmount of USD: - STAmount const maxUSD(USD.issue(), STAmount::cMaxValue, STAmount::cMaxOffset); + STAmount const maxUSD(USD, STAmount::cMaxValue, STAmount::cMaxOffset); // Create a hotwallet Account const hw{"hw"}; @@ -279,5 +288,4 @@ public: BEAST_DEFINE_TESTSUITE(GatewayBalances, rpc, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/rpc/GetAggregatePrice_test.cpp b/src/test/rpc/GetAggregatePrice_test.cpp index 1d14678f76..f4473c0aa3 100644 --- a/src/test/rpc/GetAggregatePrice_test.cpp +++ b/src/test/rpc/GetAggregatePrice_test.cpp @@ -1,14 +1,19 @@ -#include +#include +#include #include +#include -#include - +#include +#include +#include #include -namespace xrpl { -namespace test { -namespace jtx { -namespace oracle { +#include +#include +#include +#include + +namespace xrpl::test::jtx::oracle { class GetAggregatePrice_test : public beast::unit_test::suite { @@ -317,7 +322,4 @@ public: BEAST_DEFINE_TESTSUITE(GetAggregatePrice, rpc, xrpl); -} // namespace oracle -} // namespace jtx -} // namespace test -} // namespace xrpl +} // namespace xrpl::test::jtx::oracle diff --git a/src/test/rpc/GetCounts_test.cpp b/src/test/rpc/GetCounts_test.cpp index d0729b3f56..62e2aadfe6 100644 --- a/src/test/rpc/GetCounts_test.cpp +++ b/src/test/rpc/GetCounts_test.cpp @@ -1,10 +1,16 @@ -#include + +#include +#include +#include +#include #include -#include -#include +#include +#include #include +#include + namespace xrpl { class GetCounts_test : public beast::unit_test::suite diff --git a/src/test/rpc/Handler_test.cpp b/src/test/rpc/Handler_test.cpp index 7aeb059f56..bb9b72c63f 100644 --- a/src/test/rpc/Handler_test.cpp +++ b/src/test/rpc/Handler_test.cpp @@ -1,13 +1,20 @@ -#include + +#include #include -#include +#include +#include +#include +#include #include +#include +#include #include -#include #include +#include +#include // cspell: words stdev namespace xrpl::test { @@ -25,7 +32,7 @@ operator<<(std::ostream& os, std::chrono::nanoseconds ns) // NOTE This is a rather naive effort at a microbenchmark. Ideally we want // Google Benchmark, or something similar. Also, this actually does not belong // to unit tests, as it makes little sense to run it in conditions very -// dissimilar to how rippled will normally work. +// dissimilar to how xrpld will normally work. // TODO as https://github.com/XRPLF/rippled/issues/4765 class Handler_test : public beast::unit_test::suite @@ -57,7 +64,7 @@ class Handler_test : public beast::unit_test::suite samples[k] = (std::chrono::steady_clock::now() - start).count(); } - std::sort(samples.begin(), samples.end()); + std::ranges::sort(samples); for (std::size_t k = 35; k < 65; ++k) { j += 1; diff --git a/src/test/rpc/JSONRPC_test.cpp b/src/test/rpc/JSONRPC_test.cpp index b9d4ee4a98..40c2389617 100644 --- a/src/test/rpc/JSONRPC_test.cpp +++ b/src/test/rpc/JSONRPC_test.cpp @@ -1,19 +1,42 @@ -#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include #include #include +#include #include #include -#include +#include #include +#include +#include #include +#include +#include +#include #include +#include -namespace xrpl { +#include +#include +#include +#include +#include +#include +#include -namespace RPC { +namespace xrpl::RPC { struct TxnTestData { @@ -2824,5 +2847,4 @@ public: BEAST_DEFINE_TESTSUITE(JSONRPC, rpc, xrpl); -} // namespace RPC -} // namespace xrpl +} // namespace xrpl::RPC diff --git a/src/test/rpc/KeyGeneration_test.cpp b/src/test/rpc/KeyGeneration_test.cpp index 87d2ded7ad..a496664ef0 100644 --- a/src/test/rpc/KeyGeneration_test.cpp +++ b/src/test/rpc/KeyGeneration_test.cpp @@ -1,16 +1,21 @@ #include #include -#include +#include +#include #include #include +#include #include +#include #include +#include -namespace xrpl { +#include +#include -namespace RPC { +namespace xrpl::RPC { struct key_strings { @@ -33,45 +38,48 @@ static char const* master_seed_hex = "BE6A670A19B209E112146D0A7ED2AAD7"; } // namespace common static key_strings const secp256k1_strings = { - "r4Vtj2jrfmTVZGfSP3gH9hQPMqFPQFin8f", - common::master_key, - common::master_seed, - common::master_seed_hex, - "aBQxK2YFNqzmAaXNczYcjqDjfiKkLsJUizsr1UBf44RCF8FHdrmX", - "038AAE247B2344B1837FBED8F57389C8C11774510A3F7D784F2A09F0CB6843236C", - "1949ECD889EA71324BC7A30C8E81F4E93CB73EE19D59E9082111E78CC3DDABC2", - common::passphrase, - "This wallet was generated using a user-supplied " - "passphrase that has low entropy and is vulnerable " - "to brute-force attacks.", + .account_id = "r4Vtj2jrfmTVZGfSP3gH9hQPMqFPQFin8f", + .master_key = common::master_key, + .master_seed = common::master_seed, + .master_seed_hex = common::master_seed_hex, + .public_key = "aBQxK2YFNqzmAaXNczYcjqDjfiKkLsJUizsr1UBf44RCF8FHdrmX", + .public_key_hex = "038AAE247B2344B1837FBED8F57389C8C11774510A3F7D784F2A09F0CB6843236C", + .secret_key_hex = "1949ECD889EA71324BC7A30C8E81F4E93CB73EE19D59E9082111E78CC3DDABC2", + .passphrase = common::passphrase, + .passphrase_warning = + "This wallet was generated using a user-supplied " + "passphrase that has low entropy and is vulnerable " + "to brute-force attacks.", }; static key_strings const ed25519_strings = { - "r4qV6xTXerqaZav3MJfSY79ynmc1BSBev1", - common::master_key, - common::master_seed, - common::master_seed_hex, - "aKEQmgLMyZPMruJFejUuedp169LgW6DbJt1rej1DJ5hWUMH4pHJ7", - "ED54C3F5BEDA8BD588B203D23A27398FAD9D20F88A974007D6994659CD7273FE1D", - "77AAED2698D56D6676323629160F4EEF21CFD9EE3D0745CC78FA291461F98278", - common::passphrase, - "This wallet was generated using a user-supplied " - "passphrase that has low entropy and is vulnerable " - "to brute-force attacks.", + .account_id = "r4qV6xTXerqaZav3MJfSY79ynmc1BSBev1", + .master_key = common::master_key, + .master_seed = common::master_seed, + .master_seed_hex = common::master_seed_hex, + .public_key = "aKEQmgLMyZPMruJFejUuedp169LgW6DbJt1rej1DJ5hWUMH4pHJ7", + .public_key_hex = "ED54C3F5BEDA8BD588B203D23A27398FAD9D20F88A974007D6994659CD7273FE1D", + .secret_key_hex = "77AAED2698D56D6676323629160F4EEF21CFD9EE3D0745CC78FA291461F98278", + .passphrase = common::passphrase, + .passphrase_warning = + "This wallet was generated using a user-supplied " + "passphrase that has low entropy and is vulnerable " + "to brute-force attacks.", }; static key_strings const strong_brain_strings = { - "rBcvXmNb7KPkNdMkpckdWPpbvkWgcV3nir", - "TED AVON CAVE HOUR BRAG JEFF RIFT NEAL TOLD FAT SEW SAN", - "shKdhWka8hS7Es3bpctCZXBiAwfUN", - "74BA8389B44F98CF41E795CD91F9C93F", - "aBRL2sqVuzrsM6zikPB4v8UBHGn1aKkrsxhYEffhcQxB2LKyywE5", - "03BD334FB9E06C58D69603E9922686528B18A754BC2F2E1ADA095FFE67DE952C64", - "84262FB16AA25BE407174C7EDAB531220C30FA4D8A28AA9D564673FB3D34502C", - "A4yKIRGdzrw0YQ$2%TFKYG9HP*&ok^!sy7E@RwICs", - "This wallet was generated using a user-supplied " - "passphrase. It may be vulnerable to brute-force " - "attacks.", + .account_id = "rBcvXmNb7KPkNdMkpckdWPpbvkWgcV3nir", + .master_key = "TED AVON CAVE HOUR BRAG JEFF RIFT NEAL TOLD FAT SEW SAN", + .master_seed = "shKdhWka8hS7Es3bpctCZXBiAwfUN", + .master_seed_hex = "74BA8389B44F98CF41E795CD91F9C93F", + .public_key = "aBRL2sqVuzrsM6zikPB4v8UBHGn1aKkrsxhYEffhcQxB2LKyywE5", + .public_key_hex = "03BD334FB9E06C58D69603E9922686528B18A754BC2F2E1ADA095FFE67DE952C64", + .secret_key_hex = "84262FB16AA25BE407174C7EDAB531220C30FA4D8A28AA9D564673FB3D34502C", + .passphrase = "A4yKIRGdzrw0YQ$2%TFKYG9HP*&ok^!sy7E@RwICs", + .passphrase_warning = + "This wallet was generated using a user-supplied " + "passphrase. It may be vulnerable to brute-force " + "attacks.", }; class WalletPropose_test : public xrpl::TestSuite @@ -685,9 +693,9 @@ public: } void - testRippleLibEd25519() + testXrplLibEd25519() { - testcase("ripple-lib encoded Ed25519 keys"); + testcase("XrplLib encoded Ed25519 keys"); auto test = [this](char const* seed, char const* addr) { { @@ -784,7 +792,7 @@ public: testKeypairForSignature(std::string("ed25519"), ed25519_strings); testKeypairForSignature(std::string("secp256k1"), strong_brain_strings); - testRippleLibEd25519(); + testXrplLibEd25519(); testKeypairForSignatureErrors(); } @@ -792,5 +800,4 @@ public: BEAST_DEFINE_TESTSUITE(WalletPropose, rpc, xrpl); -} // namespace RPC -} // namespace xrpl +} // namespace xrpl::RPC diff --git a/src/test/rpc/LedgerClosed_test.cpp b/src/test/rpc/LedgerClosed_test.cpp index 2bc5c2b0c9..efe7c2a815 100644 --- a/src/test/rpc/LedgerClosed_test.cpp +++ b/src/test/rpc/LedgerClosed_test.cpp @@ -1,8 +1,16 @@ -#include +#include +#include +#include +#include + +#include #include +#include #include +#include + namespace xrpl { class LedgerClosed_test : public beast::unit_test::suite diff --git a/src/test/rpc/LedgerData_test.cpp b/src/test/rpc/LedgerData_test.cpp index 472f5b2aa2..612899e47d 100644 --- a/src/test/rpc/LedgerData_test.cpp +++ b/src/test/rpc/LedgerData_test.cpp @@ -1,8 +1,31 @@ -#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include + namespace xrpl { class LedgerData_test : public beast::unit_test::suite diff --git a/src/test/rpc/LedgerEntry_test.cpp b/src/test/rpc/LedgerEntry_test.cpp index bd053d71c9..d208d718f4 100644 --- a/src/test/rpc/LedgerEntry_test.cpp +++ b/src/test/rpc/LedgerEntry_test.cpp @@ -1,22 +1,68 @@ -#include +#include +#include +#include #include +#include +#include #include +#include #include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include #include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include #include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include #include +#include #include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include -namespace xrpl { - -namespace test { +namespace xrpl::test { enum class FieldType { AccountField, @@ -26,7 +72,7 @@ enum class FieldType { HashField, HashOrObjectField, FixedHashField, - IssueField, + AssetField, ObjectField, StringField, TwoAccountArrayField, @@ -37,8 +83,8 @@ enum class FieldType { std::vector> mappings{ {jss::account, FieldType::AccountField}, {jss::accounts, FieldType::TwoAccountArrayField}, - {jss::asset, FieldType::IssueField}, - {jss::asset2, FieldType::IssueField}, + {jss::asset, FieldType::AssetField}, + {jss::asset2, FieldType::AssetField}, {jss::authorize, FieldType::AccountField}, {jss::authorized, FieldType::AccountField}, {jss::credential_type, FieldType::BlobField}, @@ -82,8 +128,8 @@ getTypeName(FieldType typeID) return "hex string"; case FieldType::HashOrObjectField: return "hex string or object"; - case FieldType::IssueField: - return "Issue"; + case FieldType::AssetField: + return "Asset"; case FieldType::TwoAccountArrayField: return "length-2 array of Accounts"; case FieldType::UInt32Field: @@ -194,7 +240,7 @@ class LedgerEntry_test : public beast::unit_test::suite static auto const& badIndexValues = remove({12, 16, 18, 19}); static auto const& badUInt32Values = remove({2, 3}); static auto const& badUInt64Values = remove({2, 3}); - static auto const& badIssueValues = remove({}); + static auto const& badAssetValues = remove({}); switch (fieldType) { @@ -213,8 +259,8 @@ class LedgerEntry_test : public beast::unit_test::suite return badIndexValues; case FieldType::FixedHashField: return badFixedHashValues; - case FieldType::IssueField: - return badIssueValues; + case FieldType::AssetField: + return badAssetValues; case FieldType::UInt32Field: return badUInt32Values; case FieldType::UInt64Field: @@ -254,7 +300,7 @@ class LedgerEntry_test : public beast::unit_test::suite case FieldType::HashField: return "5233D68B4D44388F98559DE42903767803EFA7C1F8D01413FC16EE6" "B01403D6D"; - case FieldType::IssueField: + case FieldType::AssetField: return issueObject; case FieldType::HashOrObjectField: return "5233D68B4D44388F98559DE42903767803EFA7C1F8D01413FC16EE6" @@ -693,59 +739,67 @@ class LedgerEntry_test : public beast::unit_test::suite { testcase("AMM"); using namespace test::jtx; - Env env{*this}; - - // positive test Account const alice{"alice"}; - env.fund(XRP(10000), alice); - env.close(); - AMM const amm(env, alice, XRP(10), alice["USD"](1000)); - env.close(); - { - Json::Value jvParams; - jvParams[jss::amm] = to_string(amm.ammID()); - auto const result = env.rpc("json", "ledger_entry", to_string(jvParams)); - BEAST_EXPECT( - result.isObject() && result.isMember(jss::result) && - !result[jss::result].isMember(jss::error) && - result[jss::result].isMember(jss::node) && - result[jss::result][jss::node].isMember(sfLedgerEntryType.jsonName) && - result[jss::result][jss::node][sfLedgerEntryType.jsonName] == jss::AMM); - } + auto test = [&](auto&& getAsset) { + Env env{*this}; + + // positive test + env.fund(XRP(10000), alice); + env.close(); + PrettyAsset const USD = getAsset(env); + AMM const amm(env, alice, XRP(10), USD(1000)); + env.close(); - { - Json::Value jvParams; - Json::Value ammParams(Json::objectValue); { - Json::Value obj(Json::objectValue); - obj[jss::currency] = "XRP"; - ammParams[jss::asset] = obj; + Json::Value jvParams; + jvParams[jss::amm] = to_string(amm.ammID()); + auto const result = env.rpc("json", "ledger_entry", to_string(jvParams)); + BEAST_EXPECT( + result.isObject() && result.isMember(jss::result) && + !result[jss::result].isMember(jss::error) && + result[jss::result].isMember(jss::node) && + result[jss::result][jss::node].isMember(sfLedgerEntryType.jsonName) && + result[jss::result][jss::node][sfLedgerEntryType.jsonName] == jss::AMM); } - { - Json::Value obj(Json::objectValue); - obj[jss::currency] = "USD"; - obj[jss::issuer] = alice.human(); - ammParams[jss::asset2] = obj; - } - jvParams[jss::amm] = ammParams; - auto const result = env.rpc("json", "ledger_entry", to_string(jvParams)); - BEAST_EXPECT( - result.isObject() && result.isMember(jss::result) && - !result[jss::result].isMember(jss::error) && - result[jss::result].isMember(jss::node) && - result[jss::result][jss::node].isMember(sfLedgerEntryType.jsonName) && - result[jss::result][jss::node][sfLedgerEntryType.jsonName] == jss::AMM); - } - // negative tests - runLedgerEntryTest( - env, - jss::amm, { - {jss::asset, "malformedRequest"}, - {jss::asset2, "malformedRequest"}, - }); + Json::Value jvParams; + Json::Value ammParams(Json::objectValue); + { + Json::Value obj(Json::objectValue); + obj[jss::currency] = "XRP"; + ammParams[jss::asset] = obj; + } + { + Json::Value const obj(Json::objectValue); + ammParams[jss::asset2] = to_json(USD.raw()); + } + jvParams[jss::amm] = ammParams; + auto const result = env.rpc("json", "ledger_entry", to_string(jvParams)); + BEAST_EXPECT( + result.isObject() && result.isMember(jss::result) && + !result[jss::result].isMember(jss::error) && + result[jss::result].isMember(jss::node) && + result[jss::result][jss::node].isMember(sfLedgerEntryType.jsonName) && + result[jss::result][jss::node][sfLedgerEntryType.jsonName] == jss::AMM); + } + + // negative tests + runLedgerEntryTest( + env, + jss::amm, + { + {jss::asset, "malformedRequest"}, + {jss::asset2, "malformedRequest"}, + }); + }; + auto getIOU = [&](Env& env) -> PrettyAsset { return alice["USD"]; }; + auto getMPT = [&](Env& env) -> PrettyAsset { + return MPTTester({.env = env, .issuer = alice}); + }; + test(getIOU); + test(getMPT); } void @@ -2872,12 +2926,12 @@ class LedgerEntry_XChain_test : public beast::unit_test::suite, a[i].isMember(jss::Destination) && a[i][jss::Destination] == scCarol.human()); BEAST_EXPECT( a[i].isMember(sfAttestationSignerAccount.jsonName) && - std::any_of(signers.begin(), signers.end(), [&](signer const& s) { + std::ranges::any_of(signers, [&](signer const& s) { return a[i][sfAttestationSignerAccount.jsonName] == s.account.human(); })); BEAST_EXPECT( a[i].isMember(sfAttestationRewardAccount.jsonName) && - std::any_of(payee.begin(), payee.end(), [&](Account const& account) { + std::ranges::any_of(payee, [&](Account const& account) { return a[i][sfAttestationRewardAccount.jsonName] == account.human(); })); BEAST_EXPECT( @@ -2921,5 +2975,4 @@ public: BEAST_DEFINE_TESTSUITE(LedgerEntry, rpc, xrpl); BEAST_DEFINE_TESTSUITE(LedgerEntry_XChain, rpc, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/rpc/LedgerHeader_test.cpp b/src/test/rpc/LedgerHeader_test.cpp index 8b460fe6a4..bb1ce8b51f 100644 --- a/src/test/rpc/LedgerHeader_test.cpp +++ b/src/test/rpc/LedgerHeader_test.cpp @@ -1,6 +1,8 @@ #include #include +#include +#include #include namespace xrpl { diff --git a/src/test/rpc/LedgerRPC_test.cpp b/src/test/rpc/LedgerRPC_test.cpp index 8f965aa2cf..c8c40ce0cc 100644 --- a/src/test/rpc/LedgerRPC_test.cpp +++ b/src/test/rpc/LedgerRPC_test.cpp @@ -1,20 +1,31 @@ -#include -#include -#include -#include -#include -#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include -#include +#include +#include #include +#include #include +#include #include -namespace xrpl { +#include +#include +#include -namespace test { +namespace xrpl::test { class LedgerRPC_test : public beast::unit_test::suite { @@ -696,5 +707,4 @@ public: BEAST_DEFINE_TESTSUITE(LedgerRPC, rpc, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/rpc/LedgerRequest_test.cpp b/src/test/rpc/LedgerRequest_test.cpp index 4462c1f039..496cd9478a 100644 --- a/src/test/rpc/LedgerRequest_test.cpp +++ b/src/test/rpc/LedgerRequest_test.cpp @@ -1,16 +1,22 @@ -#include -#include -#include +#include +#include +#include +#include + +#include +#include +#include #include #include #include +#include +#include +#include -namespace xrpl { - -namespace RPC { +namespace xrpl::RPC { class LedgerRequest_test : public beast::unit_test::suite { @@ -352,5 +358,4 @@ public: BEAST_DEFINE_TESTSUITE(LedgerRequest, rpc, xrpl); -} // namespace RPC -} // namespace xrpl +} // namespace xrpl::RPC diff --git a/src/test/rpc/ManifestRPC_test.cpp b/src/test/rpc/ManifestRPC_test.cpp index f42fdd7164..aa27c67ca9 100644 --- a/src/test/rpc/ManifestRPC_test.cpp +++ b/src/test/rpc/ManifestRPC_test.cpp @@ -1,16 +1,18 @@ // Copyright (c) 2020 Dev Null Productions -#include +#include +#include +#include #include -#include +#include #include +#include #include -namespace xrpl { -namespace test { +namespace xrpl::test { class ManifestRPC_test : public beast::unit_test::suite { @@ -70,5 +72,4 @@ public: }; BEAST_DEFINE_TESTSUITE(ManifestRPC, rpc, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/rpc/NoRippleCheck_test.cpp b/src/test/rpc/NoRippleCheck_test.cpp index 54a2931ba5..ac283a54b1 100644 --- a/src/test/rpc/NoRippleCheck_test.cpp +++ b/src/test/rpc/NoRippleCheck_test.cpp @@ -1,17 +1,37 @@ -#include +#include +#include +#include // IWYU pragma: keep #include +#include +#include +#include +#include +#include +#include +#include #include #include -#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include #include #include #include #include +#include +#include + namespace xrpl { class NoRippleCheck_test : public beast::unit_test::suite diff --git a/src/test/rpc/NoRipple_test.cpp b/src/test/rpc/NoRipple_test.cpp index fcf412bbd4..75d26ca208 100644 --- a/src/test/rpc/NoRipple_test.cpp +++ b/src/test/rpc/NoRipple_test.cpp @@ -1,11 +1,27 @@ -#include +#include +#include +#include +#include +#include // IWYU pragma: keep +#include +#include +#include +#include +#include + +#include +#include +#include +#include #include +#include +#include #include -namespace xrpl { +#include -namespace test { +namespace xrpl::test { class NoRipple_test : public beast::unit_test::suite { @@ -265,5 +281,4 @@ public: BEAST_DEFINE_TESTSUITE(NoRipple, rpc, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/rpc/OwnerInfo_test.cpp b/src/test/rpc/OwnerInfo_test.cpp index 8a09fed467..fb0cc47995 100644 --- a/src/test/rpc/OwnerInfo_test.cpp +++ b/src/test/rpc/OwnerInfo_test.cpp @@ -1,8 +1,18 @@ -#include -#include +#include +#include +#include +#include // IWYU pragma: keep +#include +#include + +#include +#include +#include #include +#include #include +#include #include namespace xrpl { diff --git a/src/test/rpc/Peers_test.cpp b/src/test/rpc/Peers_test.cpp index 984e767516..3e36ddb5d7 100644 --- a/src/test/rpc/Peers_test.cpp +++ b/src/test/rpc/Peers_test.cpp @@ -1,12 +1,18 @@ -#include #include #include -#include +#include +#include +#include +#include +#include #include +#include +#include #include +#include namespace xrpl { diff --git a/src/test/rpc/RPCCall_test.cpp b/src/test/rpc/RPCCall_test.cpp index ae876dded4..3ece97344c 100644 --- a/src/test/rpc/RPCCall_test.cpp +++ b/src/test/rpc/RPCCall_test.cpp @@ -1,21 +1,27 @@ -#include +#include +#include #include -#include +#include -#include +#include #include +#include #include -#include +#include -#include +#include +#include +#include #include #include +#include +#include +#include #include -namespace xrpl { -namespace test { +namespace xrpl::test { struct RPCCallTestData { @@ -5927,5 +5933,4 @@ public: BEAST_DEFINE_TESTSUITE(RPCCall, rpc, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/rpc/RPCHelpers_test.cpp b/src/test/rpc/RPCHelpers_test.cpp index f180bd5c1a..e87ac50bcf 100644 --- a/src/test/rpc/RPCHelpers_test.cpp +++ b/src/test/rpc/RPCHelpers_test.cpp @@ -1,10 +1,13 @@ +#include #include -#include +#include +#include +#include +#include #include -namespace xrpl { -namespace test { +namespace xrpl::test { class RPCHelpers_test : public beast::unit_test::suite { @@ -72,5 +75,4 @@ public: BEAST_DEFINE_TESTSUITE(RPCHelpers, rpc, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/rpc/RPCOverload_test.cpp b/src/test/rpc/RPCOverload_test.cpp index e5558af82c..a49b52578a 100644 --- a/src/test/rpc/RPCOverload_test.cpp +++ b/src/test/rpc/RPCOverload_test.cpp @@ -1,14 +1,24 @@ -#include +#include +#include +#include #include #include +#include +#include +#include +#include #include -#include +#include +#include +#include #include -namespace xrpl { -namespace test { +#include +#include + +namespace xrpl::test { class RPCOverload_test : public beast::unit_test::suite { @@ -72,5 +82,4 @@ public: BEAST_DEFINE_TESTSUITE(RPCOverload, rpc, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/rpc/RobustTransaction_test.cpp b/src/test/rpc/RobustTransaction_test.cpp index 0d47a39573..96a8c5f869 100644 --- a/src/test/rpc/RobustTransaction_test.cpp +++ b/src/test/rpc/RobustTransaction_test.cpp @@ -1,12 +1,20 @@ -#include +#include #include +#include +#include // IWYU pragma: keep +#include +#include -#include +#include #include +#include +#include +#include #include -namespace xrpl { -namespace test { +#include + +namespace xrpl::test { class RobustTransaction_test : public beast::unit_test::suite { @@ -433,5 +441,4 @@ public: BEAST_DEFINE_TESTSUITE(RobustTransaction, rpc, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/rpc/Roles_test.cpp b/src/test/rpc/Roles_test.cpp index e3d90a9c56..eaf6143f5d 100644 --- a/src/test/rpc/Roles_test.cpp +++ b/src/test/rpc/Roles_test.cpp @@ -1,14 +1,16 @@ -#include +#include #include +#include -#include +#include + +#include +#include #include #include -namespace xrpl { - -namespace test { +namespace xrpl::test { class Roles_test : public beast::unit_test::suite { @@ -43,6 +45,7 @@ class Roles_test : public beast::unit_test::suite Env env{*this, envconfig(secure_gateway)}; BEAST_EXPECT(env.rpc("ping")["result"]["role"] == "proxied"); + BEAST_EXPECT(!env.rpc("ping")["result"].isMember("ip")); auto wsRes = makeWSClient(env.app().config())->invoke("ping")["result"]; BEAST_EXPECT(!wsRes.isMember("unlimited") || !wsRes["unlimited"].asBool()); @@ -344,6 +347,4 @@ public: BEAST_DEFINE_TESTSUITE(Roles, rpc, xrpl); -} // namespace test - -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/rpc/ServerDefinitions_test.cpp b/src/test/rpc/ServerDefinitions_test.cpp index a2c45cfa65..14567dfd25 100644 --- a/src/test/rpc/ServerDefinitions_test.cpp +++ b/src/test/rpc/ServerDefinitions_test.cpp @@ -1,14 +1,14 @@ -#include +#include -#include +#include + +#include #include #include #include #include -namespace xrpl { - -namespace test { +namespace xrpl::test { class ServerDefinitions_test : public beast::unit_test::suite { @@ -452,14 +452,43 @@ public: } } + void + testGetServerDefinitionsJson() + { + testcase("getServerDefinitionsJson"); + + auto const& defs = getServerDefinitionsJson(); + for (auto const& field : + {jss::ACCOUNT_SET_FLAGS, + jss::FIELDS, + jss::LEDGER_ENTRY_FLAGS, + jss::LEDGER_ENTRY_FORMATS, + jss::LEDGER_ENTRY_TYPES, + jss::TRANSACTION_FLAGS, + jss::TRANSACTION_FORMATS, + jss::TRANSACTION_RESULTS, + jss::TRANSACTION_TYPES, + jss::TYPES, + jss::hash}) + { + BEAST_EXPECT(defs.isMember(field)); + } + + // verify it returns the same hash as the RPC handler + using namespace test::jtx; + Env env(*this); + auto const rpcResult = env.rpc("server_definitions"); + BEAST_EXPECT(defs[jss::hash] == rpcResult[jss::result][jss::hash]); + } + void run() override { testServerDefinitions(); + testGetServerDefinitionsJson(); } }; BEAST_DEFINE_TESTSUITE(ServerDefinitions, rpc, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/rpc/ServerInfo_test.cpp b/src/test/rpc/ServerInfo_test.cpp index 978b995ce8..f94fe51d5d 100644 --- a/src/test/rpc/ServerInfo_test.cpp +++ b/src/test/rpc/ServerInfo_test.cpp @@ -1,16 +1,19 @@ -#include +#include +#include + +#include #include -#include +#include #include #include -#include +#include -namespace xrpl { +#include -namespace test { +namespace xrpl::test { namespace validator_data { static auto const public_key = "nHBt9fsb4849WmZiCds4r5TXyBeQjqnH5kzPtqgMAQMgi39YZRPa"; @@ -33,7 +36,7 @@ public: makeValidatorConfig() { auto p = std::make_unique(); - boost::format toLoad(R"rippleConfig( + boost::format toLoad(R"xrpldConfig( [validator_token] %1% @@ -49,7 +52,7 @@ ip = 0.0.0.0 port = 50052 protocol = wss2 admin = 127.0.0.1 -)rippleConfig"); +)xrpldConfig"); p->loadFromString(boost::str(toLoad % validator_data::token % validator_data::public_key)); @@ -157,5 +160,4 @@ admin = 127.0.0.1 BEAST_DEFINE_TESTSUITE(ServerInfo, rpc, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/rpc/Simulate_test.cpp b/src/test/rpc/Simulate_test.cpp index 0581313e7a..0ef66d67d9 100644 --- a/src/test/rpc/Simulate_test.cpp +++ b/src/test/rpc/Simulate_test.cpp @@ -1,22 +1,46 @@ -#include +#include #include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include #include -#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include #include #include +#include +#include +#include #include -#include +#include +#include +#include +#include #include -#include +#include -namespace xrpl { - -namespace test { +namespace xrpl::test { class Simulate_test : public beast::unit_test::suite { @@ -1167,6 +1191,4 @@ public: BEAST_DEFINE_TESTSUITE(Simulate, rpc, xrpl); -} // namespace test - -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/rpc/Status_test.cpp b/src/test/rpc/Status_test.cpp index a4a7b8c961..d1a5684262 100644 --- a/src/test/rpc/Status_test.cpp +++ b/src/test/rpc/Status_test.cpp @@ -1,10 +1,18 @@ #include #include -#include +#include +#include +#include +#include +#include -namespace xrpl { -namespace RPC { +#include +#include +#include +#include + +namespace xrpl::RPC { class codeString_test : public beast::unit_test::suite { @@ -196,5 +204,4 @@ public: BEAST_DEFINE_TESTSUITE(fillJson, rpc, RPC); -} // namespace RPC -} // namespace xrpl +} // namespace xrpl::RPC diff --git a/src/test/rpc/Subscribe_test.cpp b/src/test/rpc/Subscribe_test.cpp index bf52bde7aa..02c4fd8c8d 100644 --- a/src/test/rpc/Subscribe_test.cpp +++ b/src/test/rpc/Subscribe_test.cpp @@ -1,22 +1,60 @@ -#include +#include #include +#include +#include #include +#include +#include +#include // IWYU pragma: keep +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include #include -#include +#include +#include +#include +#include #include #include +#include #include +#include +#include +#include +#include +#include +#include +#include #include +#include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include -namespace xrpl { -namespace test { +namespace xrpl::test { class Subscribe_test : public beast::unit_test::suite { @@ -783,9 +821,9 @@ public: using namespace jtx; using IdxHashVec = std::vector>; - Account alice("alice"); + Account const alice("alice"); Account const bob("bob"); - Account carol("carol"); + Account const carol("carol"); Account const david("david"); /////////////////////////////////////////////////////////////////// @@ -1331,8 +1369,8 @@ public: return nftID; }); // Sort both array to prepare for comparison - std::sort(metaIDs.begin(), metaIDs.end()); - std::sort(actualNftIDs.begin(), actualNftIDs.end()); + std::ranges::sort(metaIDs); + std::ranges::sort(actualNftIDs); // Make sure the expect number of NFTs is correct BEAST_EXPECT(metaIDs.size() == actualNftIDs.size()); @@ -1493,5 +1531,4 @@ public: BEAST_DEFINE_TESTSUITE(Subscribe, rpc, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/rpc/TransactionEntry_test.cpp b/src/test/rpc/TransactionEntry_test.cpp index 6421587478..1940194937 100644 --- a/src/test/rpc/TransactionEntry_test.cpp +++ b/src/test/rpc/TransactionEntry_test.cpp @@ -1,11 +1,26 @@ -#include +#include #include +#include +#include +#include +#include +#include + +#include +#include +#include #include #include +#include +#include +#include #include #include +#include +#include +#include namespace xrpl { diff --git a/src/test/rpc/TransactionHistory_test.cpp b/src/test/rpc/TransactionHistory_test.cpp index 1d555ecd8c..bc6d2910d9 100644 --- a/src/test/rpc/TransactionHistory_test.cpp +++ b/src/test/rpc/TransactionHistory_test.cpp @@ -1,12 +1,20 @@ -#include +#include #include +#include #include +#include +#include +#include +#include +#include #include #include -#include +#include +#include +#include namespace xrpl { diff --git a/src/test/rpc/Transaction_test.cpp b/src/test/rpc/Transaction_test.cpp index 9f06607729..fdb0ed1c8f 100644 --- a/src/test/rpc/Transaction_test.cpp +++ b/src/test/rpc/Transaction_test.cpp @@ -1,19 +1,42 @@ -#include +#include #include +#include #include +#include +#include +#include +#include #include #include +#include +#include +#include #include +#include +#include +#include #include +#include +#include #include +#include +#include +#include #include #include +#include #include +#include +#include +#include +#include #include +#include #include +#include namespace xrpl { @@ -637,7 +660,7 @@ class Transaction_test : public beast::unit_test::suite // Change the first upper case letter to lower case. std::string mixedCase = ctid; { - auto const iter = std::find_if(mixedCase.begin(), mixedCase.end(), isUpper); + auto const iter = std::ranges::find_if(mixedCase, isUpper); *iter = std::tolower(*iter); } BEAST_EXPECT(ctid != mixedCase); diff --git a/src/test/rpc/ValidatorInfo_test.cpp b/src/test/rpc/ValidatorInfo_test.cpp index d4769f40fb..71760b601b 100644 --- a/src/test/rpc/ValidatorInfo_test.cpp +++ b/src/test/rpc/ValidatorInfo_test.cpp @@ -1,17 +1,19 @@ // Copyright (c) 2020 Dev Null Productions -#include +#include +#include +#include #include -#include +#include #include +#include #include #include -namespace xrpl { -namespace test { +namespace xrpl::test { class ValidatorInfo_test : public beast::unit_test::suite { @@ -89,5 +91,4 @@ public: }; BEAST_DEFINE_TESTSUITE(ValidatorInfo, rpc, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/rpc/ValidatorRPC_test.cpp b/src/test/rpc/ValidatorRPC_test.cpp index 6c6a75dd01..2d8a73e31d 100644 --- a/src/test/rpc/ValidatorRPC_test.cpp +++ b/src/test/rpc/ValidatorRPC_test.cpp @@ -1,19 +1,34 @@ -#include +#include #include +#include #include #include +#include #include -#include +#include +#include +#include +#include #include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include #include +#include +#include +#include -namespace xrpl { - -namespace test { +namespace xrpl::test { class ValidatorRPC_test : public beast::unit_test::suite { @@ -530,5 +545,4 @@ public: BEAST_DEFINE_TESTSUITE(ValidatorRPC, rpc, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/rpc/Version_test.cpp b/src/test/rpc/Version_test.cpp index c12e397459..39983ac09e 100644 --- a/src/test/rpc/Version_test.cpp +++ b/src/test/rpc/Version_test.cpp @@ -1,8 +1,16 @@ -#include +#include +#include + +#include +#include #include #include +#include +#include +#include + namespace xrpl { class Version_test : public beast::unit_test::suite @@ -45,7 +53,8 @@ class Version_test : public beast::unit_test::suite { if (re["error_what"].isString()) { - return re["error_what"].asString().find(jss::invalid_API_version.c_str()) == 0; + return re["error_what"].asString().starts_with( + jss::invalid_API_version.c_str()); } } return false; diff --git a/src/test/server/ServerStatus_test.cpp b/src/test/server/ServerStatus_test.cpp index b7c9825a55..09282c9d14 100644 --- a/src/test/server/ServerStatus_test.cpp +++ b/src/test/server/ServerStatus_test.cpp @@ -1,31 +1,50 @@ -#include +#include #include #include #include #include -#include #include #include +#include #include +#include +#include +#include +#include #include #include #include -#include +#include +#include #include -#include +#include +#include +#include +#include +#include +#include +#include +#include #include -#include +#include +#include +#include +#include +#include -#include #include +#include +#include #include #include +#include +#include +#include -namespace xrpl { -namespace test { +namespace xrpl::test { class ServerStatus_test : public beast::unit_test::suite, public beast::test::enable_yield_to { @@ -575,8 +594,7 @@ class ServerStatus_test : public beast::unit_test::suite, public beast::test::en int const testTo = (limit == 0) ? 50 : limit + 1; while (connectionCount < testTo) { - clients.emplace_back( - std::make_pair(ip::tcp::socket{ios}, boost::beast::multi_buffer{})); + clients.emplace_back(ip::tcp::socket{ios}, boost::beast::multi_buffer{}); async_connect(clients.back().first, it, yield[ec]); BEAST_EXPECT(!ec); auto req = makeHTTPRequest(ip, port, to_string(jr), {}); @@ -1152,5 +1170,4 @@ public: BEAST_DEFINE_TESTSUITE(ServerStatus, server, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/server/Server_test.cpp b/src/test/server/Server_test.cpp index 97a822fd76..462df0d707 100644 --- a/src/test/server/Server_test.cpp +++ b/src/test/server/Server_test.cpp @@ -1,29 +1,43 @@ -#include #include +#include #include #include +#include #include -#include #include -#include +#include +#include +#include +#include #include #include +#include +#include -#include +#include #include +#include +#include +#include +#include #include #include -#include +#include #include +#include +#include #include +#include #include +#include #include +#include +#include -namespace xrpl { -namespace test { +namespace xrpl::test { using socket_type = boost::beast::tcp_stream; using stream_type = boost::beast::ssl_stream; @@ -501,5 +515,4 @@ public: BEAST_DEFINE_TESTSUITE(Server, server, xrpl); -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/shamap/FetchPack_test.cpp b/src/test/shamap/FetchPack_test.cpp index 1cf7d97b33..fd44423ba8 100644 --- a/src/test/shamap/FetchPack_test.cpp +++ b/src/test/shamap/FetchPack_test.cpp @@ -1,20 +1,34 @@ #include #include +#include +#include +#include +#include #include #include #include -#include +#include +#include #include +#include #include #include +#include +#include #include +#include -#include +#include + +#include +#include +#include +#include +#include #include -namespace xrpl { -namespace tests { +namespace xrpl::tests { class FetchPack_test : public beast::unit_test::suite { @@ -151,5 +165,4 @@ public: BEAST_DEFINE_TESTSUITE(FetchPack, shamap, xrpl); -} // namespace tests -} // namespace xrpl +} // namespace xrpl::tests diff --git a/src/test/shamap/SHAMapSync_test.cpp b/src/test/shamap/SHAMapSync_test.cpp index 6374e49e71..a1db1875d3 100644 --- a/src/test/shamap/SHAMapSync_test.cpp +++ b/src/test/shamap/SHAMapSync_test.cpp @@ -1,14 +1,30 @@ #include #include +#include +#include +#include +#include #include -#include +#include #include +#include #include #include +#include +#include -namespace xrpl { -namespace tests { +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl::tests { class SHAMapSync_test : public beast::unit_test::suite { @@ -158,5 +174,4 @@ public: BEAST_DEFINE_TESTSUITE(SHAMapSync, shamap, xrpl); -} // namespace tests -} // namespace xrpl +} // namespace xrpl::tests diff --git a/src/test/shamap/SHAMap_test.cpp b/src/test/shamap/SHAMap_test.cpp index 1c6d62be97..8bbc4ae084 100644 --- a/src/test/shamap/SHAMap_test.cpp +++ b/src/test/shamap/SHAMap_test.cpp @@ -3,12 +3,27 @@ #include #include -#include +#include +#include +#include #include +#include #include +#include +#include +#include +#include +#include -namespace xrpl { -namespace tests { +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl::tests { #ifndef __INTELLISENSE__ static_assert(std::is_nothrow_destructible{}, ""); @@ -409,5 +424,4 @@ class SHAMapPathProof_test : public beast::unit_test::suite BEAST_DEFINE_TESTSUITE(SHAMap, shamap, xrpl); BEAST_DEFINE_TESTSUITE(SHAMapPathProof, shamap, xrpl); -} // namespace tests -} // namespace xrpl +} // namespace xrpl::tests diff --git a/src/test/shamap/common.h b/src/test/shamap/common.h index 8284051d44..cd942076b8 100644 --- a/src/test/shamap/common.h +++ b/src/test/shamap/common.h @@ -5,8 +5,7 @@ #include #include -namespace xrpl { -namespace tests { +namespace xrpl::tests { class TestNodeFamily : public Family { @@ -103,5 +102,4 @@ public: } }; -} // namespace tests -} // namespace xrpl +} // namespace xrpl::tests diff --git a/src/test/unit_test/FileDirGuard.h b/src/test/unit_test/FileDirGuard.h index b551b9389d..9d4b94d8c5 100644 --- a/src/test/unit_test/FileDirGuard.h +++ b/src/test/unit_test/FileDirGuard.h @@ -8,8 +8,7 @@ #include -namespace xrpl { -namespace detail { +namespace xrpl::detail { /** Create a directory and remove it when it's done @@ -30,10 +29,14 @@ protected: rmDir(path const& toRm) { if (is_directory(toRm) && is_empty(toRm)) + { remove(toRm); + } else + { test_.log << "Expected " << toRm.string() << " to be an empty existing directory." << std::endl; + } } public: @@ -51,7 +54,9 @@ public: rmSubDir_ = true; } else if (is_directory(subDir_)) + { rmSubDir_ = false; + } else { // Cannot run the test. Someone created a file where we want to @@ -129,8 +134,10 @@ public: else { if (created_) + { test_.log << "Expected " << file_.string() << " to be an existing file." << std::endl; + } } } catch (std::exception& e) @@ -153,5 +160,4 @@ public: } }; -} // namespace detail -} // namespace xrpl +} // namespace xrpl::detail diff --git a/src/test/unit_test/SuiteJournal.h b/src/test/unit_test/SuiteJournal.h index 93005401e6..c3820b8709 100644 --- a/src/test/unit_test/SuiteJournal.h +++ b/src/test/unit_test/SuiteJournal.h @@ -3,8 +3,7 @@ #include #include -namespace xrpl { -namespace test { +namespace xrpl::test { // A Journal::Sink intended for use with the beast unit test framework. class SuiteJournalSink : public beast::Journal::Sink @@ -22,7 +21,7 @@ public: } // For unit testing, always generate logging text. - inline bool + bool active(beast::severities::Severity level) const override { return true; @@ -114,7 +113,7 @@ public: writeAlways(level, text); } - inline void + void writeAlways(beast::severities::Severity level, std::string const& text) override { strm_ << text << std::endl; @@ -127,5 +126,4 @@ public: } }; -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/test/unit_test/multi_runner.cpp b/src/test/unit_test/multi_runner.cpp index e4fb1c4f45..a09110ca43 100644 --- a/src/test/unit_test/multi_runner.cpp +++ b/src/test/unit_test/multi_runner.cpp @@ -1,13 +1,29 @@ #include #include +#include +#include +#include +#include +#include #include #include +#include +#include +#include +#include +#include #include #include +#include +#include #include +#include +#include +#include +#include #include namespace xrpl { @@ -48,6 +64,7 @@ results::add(suite_results const& r) auto const elapsed = clock_type::now() - r.start; if (elapsed >= std::chrono::seconds{1}) { + // NOLINTNEXTLINE(modernize-use-ranges) auto const iter = std::lower_bound( top.begin(), top.end(), @@ -88,13 +105,9 @@ results::merge(results const& r) // combine the two top collections boost::container::static_vector top_result; top_result.resize(top.size() + r.top.size()); - std::merge( - top.begin(), - top.end(), - r.top.begin(), - r.top.end(), - top_result.begin(), - [](run_time const& t1, run_time const& t2) { return t1.second > t2.second; }); + std::ranges::merge(top, r.top, top_result.begin(), [](run_time const& t1, run_time const& t2) { + return t1.second > t2.second; + }); if (top_result.size() > max_top) top_result.resize(max_top); diff --git a/src/test/unit_test/multi_runner.h b/src/test/unit_test/multi_runner.h index 8148079292..86d4699017 100644 --- a/src/test/unit_test/multi_runner.h +++ b/src/test/unit_test/multi_runner.h @@ -131,10 +131,10 @@ class multi_runner_base print_results(S& s); }; - static constexpr char const* shared_mem_name_ = "RippledUnitTestSharedMem"; + static constexpr char const* shared_mem_name_ = "XrpldUnitTestSharedMem"; // name of the message queue a multi_runner_child will use to communicate // with multi_runner_parent - static constexpr char const* message_queue_name_ = "RippledUnitTestMessageQueue"; + static constexpr char const* message_queue_name_ = "XrpldUnitTestMessageQueue"; // `inner_` will be created in shared memory inner* inner_; @@ -252,7 +252,7 @@ public: operator=(multi_runner_child const&) = delete; multi_runner_child(std::size_t num_jobs, bool quiet, bool print_log); - ~multi_runner_child(); + ~multi_runner_child() override; std::size_t tests() const; @@ -268,25 +268,25 @@ public: run_multi(Pred pred); private: - virtual void + void on_suite_begin(beast::unit_test::suite_info const& info) override; - virtual void + void on_suite_end() override; - virtual void + void on_case_begin(std::string const& name) override; - virtual void + void on_case_end() override; - virtual void + void on_pass() override; - virtual void + void on_fail(std::string const& reason) override; - virtual void + void on_log(std::string const& s) override; }; diff --git a/src/test/unit_test/utils.h b/src/test/unit_test/utils.h index 1f6ee58436..d386818e10 100644 --- a/src/test/unit_test/utils.h +++ b/src/test/unit_test/utils.h @@ -2,8 +2,7 @@ #include -namespace xrpl { -namespace test { +namespace xrpl::test { /// Compare two SecretKey objects for equality. /// SecretKey::operator== is deleted, so a named function is used @@ -15,5 +14,4 @@ equal(SecretKey const& lhs, SecretKey const& rhs) std::memcmp(lhs.data(), rhs.data(), SecretKey::size_) == 0; } -} // namespace test -} // namespace xrpl +} // namespace xrpl::test diff --git a/src/tests/libxrpl/CMakeLists.txt b/src/tests/libxrpl/CMakeLists.txt index a82ed1472f..0b666441d1 100644 --- a/src/tests/libxrpl/CMakeLists.txt +++ b/src/tests/libxrpl/CMakeLists.txt @@ -32,20 +32,9 @@ xrpl_add_test(json) target_link_libraries(xrpl.test.json PRIVATE xrpl.imports.test) add_dependencies(xrpl.tests xrpl.test.json) -# protocol_autogen tests use explicit source list (not GLOB) because sources are generated -# Mark generated sources so CMake knows they'll be created at build time -set_source_files_properties( - ${PROTOCOL_AUTOGEN_TEST_SOURCES} - PROPERTIES GENERATED TRUE -) -add_executable(xrpl.test.protocol_autogen ${PROTOCOL_AUTOGEN_TEST_SOURCES}) +xrpl_add_test(protocol_autogen) target_link_libraries(xrpl.test.protocol_autogen PRIVATE xrpl.imports.test) add_dependencies(xrpl.tests xrpl.test.protocol_autogen) -add_test(NAME xrpl.test.protocol_autogen COMMAND xrpl.test.protocol_autogen) -# Ensure code generation runs before compiling tests -if(TARGET protocol_autogen_generate) - add_dependencies(xrpl.test.protocol_autogen protocol_autogen_generate) -endif() # Network unit tests are currently not supported on Windows if(NOT WIN32) diff --git a/src/tests/libxrpl/basics/MallocTrim.cpp b/src/tests/libxrpl/basics/MallocTrim.cpp index 93ed48b885..7c72cd6781 100644 --- a/src/tests/libxrpl/basics/MallocTrim.cpp +++ b/src/tests/libxrpl/basics/MallocTrim.cpp @@ -1,9 +1,13 @@ #include +#include + #include #include +#include + using namespace xrpl; // cSpell:ignore statm diff --git a/src/tests/libxrpl/basics/Mutex.cpp b/src/tests/libxrpl/basics/Mutex.cpp index 9f58799fe7..e91781c463 100644 --- a/src/tests/libxrpl/basics/Mutex.cpp +++ b/src/tests/libxrpl/basics/Mutex.cpp @@ -6,6 +6,8 @@ #include #include #include +#include +#include #include using namespace xrpl; diff --git a/src/tests/libxrpl/basics/RangeSet.cpp b/src/tests/libxrpl/basics/RangeSet.cpp index 41a33133f2..1d5d2391f8 100644 --- a/src/tests/libxrpl/basics/RangeSet.cpp +++ b/src/tests/libxrpl/basics/RangeSet.cpp @@ -1,5 +1,8 @@ #include +#include +#include + #include #include diff --git a/src/tests/libxrpl/basics/Slice.cpp b/src/tests/libxrpl/basics/Slice.cpp index 72f2d081c7..b7ef9f6a33 100644 --- a/src/tests/libxrpl/basics/Slice.cpp +++ b/src/tests/libxrpl/basics/Slice.cpp @@ -3,6 +3,7 @@ #include #include +#include #include using namespace xrpl; diff --git a/src/tests/libxrpl/basics/contract.cpp b/src/tests/libxrpl/basics/contract.cpp index d9b729e85d..721aa19fd3 100644 --- a/src/tests/libxrpl/basics/contract.cpp +++ b/src/tests/libxrpl/basics/contract.cpp @@ -3,7 +3,6 @@ #include #include -#include using namespace xrpl; diff --git a/src/tests/libxrpl/basics/scope.cpp b/src/tests/libxrpl/basics/scope.cpp index 067698bce4..a13bab30df 100644 --- a/src/tests/libxrpl/basics/scope.cpp +++ b/src/tests/libxrpl/basics/scope.cpp @@ -2,6 +2,8 @@ #include +#include + using namespace xrpl; TEST(scope, scope_exit) diff --git a/src/tests/libxrpl/basics/tagged_integer.cpp b/src/tests/libxrpl/basics/tagged_integer.cpp index 85a246428b..53e0ddc1d9 100644 --- a/src/tests/libxrpl/basics/tagged_integer.cpp +++ b/src/tests/libxrpl/basics/tagged_integer.cpp @@ -2,6 +2,7 @@ #include +#include #include using namespace xrpl; @@ -21,85 +22,85 @@ using TagUInt3 = tagged_integer; // Check construction of tagged_integers static_assert( - std::is_constructible::value, + std::is_constructible_v, "TagUInt1 should be constructible using a std::uint32_t"); static_assert( - !std::is_constructible::value, + !std::is_constructible_v, "TagUInt1 should not be constructible using a std::uint64_t"); static_assert( - std::is_constructible::value, + std::is_constructible_v, "TagUInt3 should be constructible using a std::uint32_t"); static_assert( - std::is_constructible::value, + std::is_constructible_v, "TagUInt3 should be constructible using a std::uint64_t"); // Check assignment of tagged_integers static_assert( - !std::is_assignable::value, + !std::is_assignable_v, "TagUInt1 should not be assignable with a std::uint32_t"); static_assert( - !std::is_assignable::value, + !std::is_assignable_v, "TagUInt1 should not be assignable with a std::uint64_t"); static_assert( - !std::is_assignable::value, + !std::is_assignable_v, "TagUInt3 should not be assignable with a std::uint32_t"); static_assert( - !std::is_assignable::value, + !std::is_assignable_v, "TagUInt3 should not be assignable with a std::uint64_t"); static_assert( - std::is_assignable::value, + std::is_assignable_v, "TagUInt1 should be assignable with a TagUInt1"); static_assert( - !std::is_assignable::value, + !std::is_assignable_v, "TagUInt1 should not be assignable with a TagUInt2"); static_assert( - std::is_assignable::value, + std::is_assignable_v, "TagUInt3 should be assignable with a TagUInt1"); static_assert( - !std::is_assignable::value, + !std::is_assignable_v, "TagUInt1 should not be assignable with a TagUInt3"); static_assert( - !std::is_assignable::value, + !std::is_assignable_v, "TagUInt3 should not be assignable with a TagUInt1"); // Check convertibility of tagged_integers static_assert( - !std::is_convertible::value, + !std::is_convertible_v, "std::uint32_t should not be convertible to a TagUInt1"); static_assert( - !std::is_convertible::value, + !std::is_convertible_v, "std::uint32_t should not be convertible to a TagUInt3"); static_assert( - !std::is_convertible::value, + !std::is_convertible_v, "std::uint64_t should not be convertible to a TagUInt3"); static_assert( - !std::is_convertible::value, + !std::is_convertible_v, "std::uint64_t should not be convertible to a TagUInt2"); static_assert( - !std::is_convertible::value, + !std::is_convertible_v, "TagUInt1 should not be convertible to TagUInt2"); static_assert( - !std::is_convertible::value, + !std::is_convertible_v, "TagUInt1 should not be convertible to TagUInt3"); static_assert( - !std::is_convertible::value, + !std::is_convertible_v, "TagUInt2 should not be convertible to a TagUInt3"); using TagInt = tagged_integer; diff --git a/src/tests/libxrpl/crypto/csprng.cpp b/src/tests/libxrpl/crypto/csprng.cpp index 41dcfd57a9..4a383cae62 100644 --- a/src/tests/libxrpl/crypto/csprng.cpp +++ b/src/tests/libxrpl/crypto/csprng.cpp @@ -2,6 +2,8 @@ #include +#include + using namespace xrpl; TEST(csprng, get_values) diff --git a/src/tests/libxrpl/helpers/TestSink.cpp b/src/tests/libxrpl/helpers/TestSink.cpp index 17cc110429..3b138edfd8 100644 --- a/src/tests/libxrpl/helpers/TestSink.cpp +++ b/src/tests/libxrpl/helpers/TestSink.cpp @@ -1,8 +1,11 @@ -#include - #include +#include + +#include + #include // for getenv +#include #if BOOST_OS_WINDOWS #include // for _isatty, _fileno diff --git a/src/tests/libxrpl/json/Output.cpp b/src/tests/libxrpl/json/Output.cpp index 96d7369d51..bc26c068ca 100644 --- a/src/tests/libxrpl/json/Output.cpp +++ b/src/tests/libxrpl/json/Output.cpp @@ -1,4 +1,5 @@ #include + #include #include diff --git a/src/tests/libxrpl/json/Value.cpp b/src/tests/libxrpl/json/Value.cpp index 194c677024..a53d81b5e1 100644 --- a/src/tests/libxrpl/json/Value.cpp +++ b/src/tests/libxrpl/json/Value.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -7,10 +8,15 @@ #include #include -#include +#include +#include +#include +#include +#include #include #include #include +#include namespace xrpl { @@ -1099,7 +1105,7 @@ TEST(json_value, access_members) EXPECT_FALSE(val.isValidIndex(0)); EXPECT_FALSE(val.isMember("key")); - val = 3.14159; + val = std::numbers::pi; EXPECT_EQ(val.type(), Json::realValue); EXPECT_EQ(val.size(), 0); EXPECT_FALSE(val.isValidIndex(0)); diff --git a/src/tests/libxrpl/json/Writer.cpp b/src/tests/libxrpl/json/Writer.cpp index 7016b4322d..a21f27199f 100644 --- a/src/tests/libxrpl/json/Writer.cpp +++ b/src/tests/libxrpl/json/Writer.cpp @@ -1,6 +1,8 @@ #include -#include +#include +#include + #include #include diff --git a/src/tests/libxrpl/net/HTTPClient.cpp b/src/tests/libxrpl/net/HTTPClient.cpp index de567a93ab..d3dfd32361 100644 --- a/src/tests/libxrpl/net/HTTPClient.cpp +++ b/src/tests/libxrpl/net/HTTPClient.cpp @@ -1,24 +1,29 @@ -#include #include -#include -#include +#include +#include +#include + +#include +#include // IWYU pragma: keep #include +#include #include +#include #include -#include -#include -#include -#include +#include // IWYU pragma: keep +#include // IWYU pragma: keep +#include // IWYU pragma: keep #include #include -#include +#include +#include #include -#include -#include -#include +#include +#include +#include using namespace xrpl; diff --git a/src/tests/libxrpl/protocol_autogen/ledger_entries/DirectoryNodeTests.cpp b/src/tests/libxrpl/protocol_autogen/ledger_entries/DirectoryNodeTests.cpp index d1b6edc704..af617c2634 100644 --- a/src/tests/libxrpl/protocol_autogen/ledger_entries/DirectoryNodeTests.cpp +++ b/src/tests/libxrpl/protocol_autogen/ledger_entries/DirectoryNodeTests.cpp @@ -23,8 +23,10 @@ TEST(DirectoryNodeTests, BuilderSettersRoundTrip) auto const ownerValue = canonical_ACCOUNT(); auto const takerPaysCurrencyValue = canonical_UINT160(); auto const takerPaysIssuerValue = canonical_UINT160(); + auto const takerPaysMPTValue = canonical_UINT192(); auto const takerGetsCurrencyValue = canonical_UINT160(); auto const takerGetsIssuerValue = canonical_UINT160(); + auto const takerGetsMPTValue = canonical_UINT192(); auto const exchangeRateValue = canonical_UINT64(); auto const indexesValue = canonical_VECTOR256(); auto const rootIndexValue = canonical_UINT256(); @@ -43,8 +45,10 @@ TEST(DirectoryNodeTests, BuilderSettersRoundTrip) builder.setOwner(ownerValue); builder.setTakerPaysCurrency(takerPaysCurrencyValue); builder.setTakerPaysIssuer(takerPaysIssuerValue); + builder.setTakerPaysMPT(takerPaysMPTValue); builder.setTakerGetsCurrency(takerGetsCurrencyValue); builder.setTakerGetsIssuer(takerGetsIssuerValue); + builder.setTakerGetsMPT(takerGetsMPTValue); builder.setExchangeRate(exchangeRateValue); builder.setIndexNext(indexNextValue); builder.setIndexPrevious(indexPreviousValue); @@ -98,6 +102,14 @@ TEST(DirectoryNodeTests, BuilderSettersRoundTrip) EXPECT_TRUE(entry.hasTakerPaysIssuer()); } + { + auto const& expected = takerPaysMPTValue; + auto const actualOpt = entry.getTakerPaysMPT(); + ASSERT_TRUE(actualOpt.has_value()); + expectEqualField(expected, *actualOpt, "sfTakerPaysMPT"); + EXPECT_TRUE(entry.hasTakerPaysMPT()); + } + { auto const& expected = takerGetsCurrencyValue; auto const actualOpt = entry.getTakerGetsCurrency(); @@ -114,6 +126,14 @@ TEST(DirectoryNodeTests, BuilderSettersRoundTrip) EXPECT_TRUE(entry.hasTakerGetsIssuer()); } + { + auto const& expected = takerGetsMPTValue; + auto const actualOpt = entry.getTakerGetsMPT(); + ASSERT_TRUE(actualOpt.has_value()); + expectEqualField(expected, *actualOpt, "sfTakerGetsMPT"); + EXPECT_TRUE(entry.hasTakerGetsMPT()); + } + { auto const& expected = exchangeRateValue; auto const actualOpt = entry.getExchangeRate(); @@ -186,8 +206,10 @@ TEST(DirectoryNodeTests, BuilderFromSleRoundTrip) auto const ownerValue = canonical_ACCOUNT(); auto const takerPaysCurrencyValue = canonical_UINT160(); auto const takerPaysIssuerValue = canonical_UINT160(); + auto const takerPaysMPTValue = canonical_UINT192(); auto const takerGetsCurrencyValue = canonical_UINT160(); auto const takerGetsIssuerValue = canonical_UINT160(); + auto const takerGetsMPTValue = canonical_UINT192(); auto const exchangeRateValue = canonical_UINT64(); auto const indexesValue = canonical_VECTOR256(); auto const rootIndexValue = canonical_UINT256(); @@ -203,8 +225,10 @@ TEST(DirectoryNodeTests, BuilderFromSleRoundTrip) sle->at(sfOwner) = ownerValue; sle->at(sfTakerPaysCurrency) = takerPaysCurrencyValue; sle->at(sfTakerPaysIssuer) = takerPaysIssuerValue; + sle->at(sfTakerPaysMPT) = takerPaysMPTValue; sle->at(sfTakerGetsCurrency) = takerGetsCurrencyValue; sle->at(sfTakerGetsIssuer) = takerGetsIssuerValue; + sle->at(sfTakerGetsMPT) = takerGetsMPTValue; sle->at(sfExchangeRate) = exchangeRateValue; sle->at(sfIndexes) = indexesValue; sle->at(sfRootIndex) = rootIndexValue; @@ -283,6 +307,19 @@ TEST(DirectoryNodeTests, BuilderFromSleRoundTrip) expectEqualField(expected, *fromBuilderOpt, "sfTakerPaysIssuer"); } + { + auto const& expected = takerPaysMPTValue; + + auto const fromSleOpt = entryFromSle.getTakerPaysMPT(); + auto const fromBuilderOpt = entryFromBuilder.getTakerPaysMPT(); + + ASSERT_TRUE(fromSleOpt.has_value()); + ASSERT_TRUE(fromBuilderOpt.has_value()); + + expectEqualField(expected, *fromSleOpt, "sfTakerPaysMPT"); + expectEqualField(expected, *fromBuilderOpt, "sfTakerPaysMPT"); + } + { auto const& expected = takerGetsCurrencyValue; @@ -309,6 +346,19 @@ TEST(DirectoryNodeTests, BuilderFromSleRoundTrip) expectEqualField(expected, *fromBuilderOpt, "sfTakerGetsIssuer"); } + { + auto const& expected = takerGetsMPTValue; + + auto const fromSleOpt = entryFromSle.getTakerGetsMPT(); + auto const fromBuilderOpt = entryFromBuilder.getTakerGetsMPT(); + + ASSERT_TRUE(fromSleOpt.has_value()); + ASSERT_TRUE(fromBuilderOpt.has_value()); + + expectEqualField(expected, *fromSleOpt, "sfTakerGetsMPT"); + expectEqualField(expected, *fromBuilderOpt, "sfTakerGetsMPT"); + } + { auto const& expected = exchangeRateValue; @@ -462,10 +512,14 @@ TEST(DirectoryNodeTests, OptionalFieldsReturnNullopt) EXPECT_FALSE(entry.getTakerPaysCurrency().has_value()); EXPECT_FALSE(entry.hasTakerPaysIssuer()); EXPECT_FALSE(entry.getTakerPaysIssuer().has_value()); + EXPECT_FALSE(entry.hasTakerPaysMPT()); + EXPECT_FALSE(entry.getTakerPaysMPT().has_value()); EXPECT_FALSE(entry.hasTakerGetsCurrency()); EXPECT_FALSE(entry.getTakerGetsCurrency().has_value()); EXPECT_FALSE(entry.hasTakerGetsIssuer()); EXPECT_FALSE(entry.getTakerGetsIssuer().has_value()); + EXPECT_FALSE(entry.hasTakerGetsMPT()); + EXPECT_FALSE(entry.getTakerGetsMPT().has_value()); EXPECT_FALSE(entry.hasExchangeRate()); EXPECT_FALSE(entry.getExchangeRate().has_value()); EXPECT_FALSE(entry.hasIndexNext()); diff --git a/src/xrpld/README.md b/src/xrpld/README.md index cf71589dd7..83dc8c6a84 100644 --- a/src/xrpld/README.md +++ b/src/xrpld/README.md @@ -1,4 +1,4 @@ -# Ripple Source Guidelines +# XRPL Source Guidelines Each folder contains a single module following the newest style: diff --git a/src/xrpld/app/consensus/RCLConsensus.cpp b/src/xrpld/app/consensus/RCLConsensus.cpp index b7b0919aad..6d99c2ee15 100644 --- a/src/xrpld/app/consensus/RCLConsensus.cpp +++ b/src/xrpld/app/consensus/RCLConsensus.cpp @@ -1,34 +1,88 @@ #include + +#include +#include +#include +#include #include #include +#include #include #include #include #include #include +#include #include #include #include #include +#include +#include #include #include +#include +#include +#include +#include +#include +#include #include -#include +#include +#include #include #include +#include +#include +#include +#include #include +#include #include #include +#include +#include +#include #include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include #include #include +#include +#include +#include + +#include + +#include #include +#include +#include +#include +#include #include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include namespace xrpl { diff --git a/src/xrpld/app/consensus/RCLCxPeerPos.cpp b/src/xrpld/app/consensus/RCLCxPeerPos.cpp index 8f99dceea8..7cc2ab8b90 100644 --- a/src/xrpld/app/consensus/RCLCxPeerPos.cpp +++ b/src/xrpld/app/consensus/RCLCxPeerPos.cpp @@ -1,7 +1,16 @@ #include +#include +#include +#include +#include +#include +#include #include #include +#include + +#include namespace xrpl { diff --git a/src/xrpld/app/consensus/RCLCxPeerPos.h b/src/xrpld/app/consensus/RCLCxPeerPos.h index e334320826..f5d3f0e8f0 100644 --- a/src/xrpld/app/consensus/RCLCxPeerPos.h +++ b/src/xrpld/app/consensus/RCLCxPeerPos.h @@ -95,7 +95,7 @@ private: { using beast::hash_append; hash_append(h, HashPrefix::proposal); - hash_append(h, std::uint32_t(proposal().proposeSeq())); + hash_append(h, proposal().proposeSeq()); hash_append(h, proposal().closeTime()); hash_append(h, proposal().prevLedger()); hash_append(h, proposal().position()); diff --git a/src/xrpld/app/consensus/RCLValidations.cpp b/src/xrpld/app/consensus/RCLValidations.cpp index 7bc16f194e..b969774954 100644 --- a/src/xrpld/app/consensus/RCLValidations.cpp +++ b/src/xrpld/app/consensus/RCLValidations.cpp @@ -1,16 +1,28 @@ #include + #include #include #include #include #include +#include #include +#include #include +#include +#include #include #include +#include +#include +#include +#include +#include +#include #include +#include namespace xrpl { diff --git a/src/xrpld/app/consensus/README.md b/src/xrpld/app/consensus/README.md index bdf5afe87c..44a4dc8ae7 100644 --- a/src/xrpld/app/consensus/README.md +++ b/src/xrpld/app/consensus/README.md @@ -2,11 +2,11 @@ This directory holds the types and classes needed to connect the generic consensus algorithm to the -rippled-specific instance of consensus. +xrpld-specific instance of consensus. - `RCLCxTx` adapts a `SHAMapItem` transaction. - `RCLCxTxSet` adapts a `SHAMap` to represent a set of transactions. - `RCLCxLedger` adapts a `Ledger`. - `RCLConsensus` is implements the requirements of the generic - `Consensus` class by connecting to the rest of the `rippled` + `Consensus` class by connecting to the rest of the `xrpld` application. diff --git a/src/xrpld/app/ledger/AcceptedLedger.cpp b/src/xrpld/app/ledger/AcceptedLedger.cpp index 1da70702bf..f866a5f3bc 100644 --- a/src/xrpld/app/ledger/AcceptedLedger.cpp +++ b/src/xrpld/app/ledger/AcceptedLedger.cpp @@ -1,6 +1,10 @@ #include +#include +#include + #include +#include namespace xrpl { @@ -19,7 +23,7 @@ AcceptedLedger::AcceptedLedger(std::shared_ptr const& ledger) : transactions_.reserve(256); insertAll(ledger->txs); - std::sort(transactions_.begin(), transactions_.end(), [](auto const& a, auto const& b) { + std::ranges::sort(transactions_, [](auto const& a, auto const& b) { return a->getTxnSeq() < b->getTxnSeq(); }); } diff --git a/src/xrpld/app/ledger/AccountStateSF.cpp b/src/xrpld/app/ledger/AccountStateSF.cpp index 79c1f0eaf6..d5fa5d83ff 100644 --- a/src/xrpld/app/ledger/AccountStateSF.cpp +++ b/src/xrpld/app/ledger/AccountStateSF.cpp @@ -1,5 +1,14 @@ #include +#include +#include +#include +#include + +#include +#include +#include + namespace xrpl { void diff --git a/src/xrpld/app/ledger/ConsensusTransSetSF.cpp b/src/xrpld/app/ledger/ConsensusTransSetSF.cpp index 5fd614a1d9..d42ff0a9e0 100644 --- a/src/xrpld/app/ledger/ConsensusTransSetSF.cpp +++ b/src/xrpld/app/ledger/ConsensusTransSetSF.cpp @@ -1,12 +1,25 @@ #include + #include #include +#include +#include +#include +#include +#include #include -#include #include -#include +#include +#include // IWYU pragma: keep #include +#include + +#include +#include +#include +#include +#include namespace xrpl { diff --git a/src/xrpld/app/ledger/InboundLedger.h b/src/xrpld/app/ledger/InboundLedger.h index b17b59b27f..4176aa0b09 100644 --- a/src/xrpld/app/ledger/InboundLedger.h +++ b/src/xrpld/app/ledger/InboundLedger.h @@ -36,7 +36,7 @@ public: clock_type&, std::unique_ptr peerSet); - ~InboundLedger(); + ~InboundLedger() override; // Called when another attempt is made to fetch this same ledger void diff --git a/src/xrpld/app/ledger/LedgerCleaner.h b/src/xrpld/app/ledger/LedgerCleaner.h index aa8d042c24..a5ad4f981b 100644 --- a/src/xrpld/app/ledger/LedgerCleaner.h +++ b/src/xrpld/app/ledger/LedgerCleaner.h @@ -17,7 +17,7 @@ protected: } public: - virtual ~LedgerCleaner() = default; + ~LedgerCleaner() override = default; virtual void start() = 0; diff --git a/src/xrpld/app/ledger/LedgerHistory.cpp b/src/xrpld/app/ledger/LedgerHistory.cpp index 969511db4c..8fd2e075c6 100644 --- a/src/xrpld/app/ledger/LedgerHistory.cpp +++ b/src/xrpld/app/ledger/LedgerHistory.cpp @@ -1,11 +1,33 @@ #include + #include #include #include +#include +#include +#include #include #include -#include +#include +#include +#include +#include +#include // IWYU pragma: keep +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include namespace xrpl { @@ -283,9 +305,8 @@ leaves(SHAMap const& sm) std::vector v; for (auto const& item : sm) v.push_back(&item); - std::sort(v.begin(), v.end(), [](SHAMapItem const* lhs, SHAMapItem const* rhs) { - return lhs->key() < rhs->key(); - }); + std::ranges::sort( + v, [](SHAMapItem const* lhs, SHAMapItem const* rhs) { return lhs->key() < rhs->key(); }); return v; } diff --git a/src/xrpld/app/ledger/LedgerMaster.h b/src/xrpld/app/ledger/LedgerMaster.h index e598787047..1bd4530d6d 100644 --- a/src/xrpld/app/ledger/LedgerMaster.h +++ b/src/xrpld/app/ledger/LedgerMaster.h @@ -38,7 +38,7 @@ public: beast::insight::Collector::ptr const& collector, beast::Journal journal); - virtual ~LedgerMaster() = default; + ~LedgerMaster() override = default; LedgerIndex getCurrentLedgerIndex(); @@ -363,7 +363,7 @@ private: LedgerIndex const max_ledger_difference_{1000000}; // Time that the previous upgrade warning was issued. - TimeKeeper::time_point upgradeWarningPrevTime_{}; + TimeKeeper::time_point upgradeWarningPrevTime_; private: struct Stats diff --git a/src/xrpld/app/ledger/LedgerReplayTask.h b/src/xrpld/app/ledger/LedgerReplayTask.h index 030121b240..65c43edb2d 100644 --- a/src/xrpld/app/ledger/LedgerReplayTask.h +++ b/src/xrpld/app/ledger/LedgerReplayTask.h @@ -31,8 +31,8 @@ public: // to be updated std::uint32_t finishSeq_ = 0; - std::vector skipList_ = {}; // including the finishHash - uint256 startHash_ = {}; + std::vector skipList_; // including the finishHash + uint256 startHash_; std::uint32_t startSeq_ = 0; bool full_ = false; @@ -80,7 +80,7 @@ public: std::shared_ptr& skipListAcquirer, TaskParameter const& parameter); - ~LedgerReplayTask(); + ~LedgerReplayTask() override; /** Start the task */ void @@ -146,7 +146,7 @@ private: TaskParameter parameter_; uint32_t maxTimeouts_; std::shared_ptr skipListAcquirer_; - std::shared_ptr parent_ = {}; + std::shared_ptr parent_; uint32_t deltaToBuild_ = 0; // should not build until have parent std::vector> deltas_; diff --git a/src/xrpld/app/ledger/LedgerToJson.h b/src/xrpld/app/ledger/LedgerToJson.h index 3686311656..53985f343b 100644 --- a/src/xrpld/app/ledger/LedgerToJson.h +++ b/src/xrpld/app/ledger/LedgerToJson.h @@ -19,7 +19,7 @@ struct LedgerFill std::vector q = {}) : ledger(l), options(o), txQueue(std::move(q)), context(ctx) { - if (context) + if (context != nullptr) closeTime = context->ledgerMaster.getCloseTimeBySeq(ledger.seq()); } diff --git a/src/xrpld/app/ledger/OrderBookDB.h b/src/xrpld/app/ledger/OrderBookDB.h new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/xrpld/app/ledger/OrderBookDBImpl.cpp b/src/xrpld/app/ledger/OrderBookDBImpl.cpp index ffd8499aba..1c64c5e6fa 100644 --- a/src/xrpld/app/ledger/OrderBookDBImpl.cpp +++ b/src/xrpld/app/ledger/OrderBookDBImpl.cpp @@ -1,10 +1,34 @@ -#include #include +#include + +#include +#include +#include +#include #include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include -#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include namespace xrpl { @@ -109,10 +133,34 @@ OrderBookDBImpl::update(std::shared_ptr const& ledger) { Book book; - book.in.currency = sle->getFieldH160(sfTakerPaysCurrency); - book.in.account = sle->getFieldH160(sfTakerPaysIssuer); - book.out.currency = sle->getFieldH160(sfTakerGetsCurrency); - book.out.account = sle->getFieldH160(sfTakerGetsIssuer); + if (sle->isFieldPresent(sfTakerPaysCurrency)) + { + Issue issue; + issue.currency = sle->getFieldH160(sfTakerPaysCurrency); + issue.account = sle->getFieldH160(sfTakerPaysIssuer); + book.in = issue; + } + else + { + XRPL_ASSERT( + sle->isFieldPresent(sfTakerPaysMPT), + "OrderBookDB::update, must be TakerPaysMPT"); + book.in = sle->getFieldH192(sfTakerPaysMPT); + } + if (sle->isFieldPresent(sfTakerGetsCurrency)) + { + Issue issue; + issue.currency = sle->getFieldH160(sfTakerGetsCurrency); + issue.account = sle->getFieldH160(sfTakerGetsIssuer); + book.out = issue; + } + else + { + XRPL_ASSERT( + sle->isFieldPresent(sfTakerGetsMPT), + "OrderBookDB::update, must be TakerGetsMPT"); + book.out = sle->getFieldH192(sfTakerGetsMPT); + } book.domain = (*sle)[~sfDomainID]; if (book.domain) @@ -137,9 +185,9 @@ OrderBookDBImpl::update(std::shared_ptr const& ledger) } else if (sle->getType() == ltAMM) { - auto const issue1 = (*sle)[sfAsset].get(); - auto const issue2 = (*sle)[sfAsset2].get(); - auto addBook = [&](Issue const& in, Issue const& out) { + auto const asset1 = (*sle)[sfAsset]; + auto const asset2 = (*sle)[sfAsset2]; + auto addBook = [&](Asset const& in, Asset const& out) { allBooks[in].insert(out); if (isXRP(out)) @@ -147,8 +195,8 @@ OrderBookDBImpl::update(std::shared_ptr const& ledger) ++cnt; }; - addBook(issue1, issue2); - addBook(issue2, issue1); + addBook(asset1, asset2); + addBook(asset2, asset1); } } } @@ -200,7 +248,7 @@ OrderBookDBImpl::addOrderBook(Book const& book) // return list of all orderbooks that want this issuerID and currencyID std::vector -OrderBookDBImpl::getBooksByTakerPays(Issue const& issue, std::optional const& domain) +OrderBookDBImpl::getBooksByTakerPays(Asset const& asset, std::optional const& domain) { std::vector ret; @@ -214,17 +262,17 @@ OrderBookDBImpl::getBooksByTakerPays(Issue const& issue, std::optional ret.reserve(books.size()); for (auto const& gets : books) - ret.emplace_back(issue, gets, domain); + ret.emplace_back(asset, gets, domain); } }; if (!domain) { - getBooks(allBooks_, issue); + getBooks(allBooks_, asset); } else { - getBooks(domainBooks_, std::make_pair(issue, *domain)); + getBooks(domainBooks_, std::make_pair(asset, *domain)); } } @@ -232,18 +280,18 @@ OrderBookDBImpl::getBooksByTakerPays(Issue const& issue, std::optional } int -OrderBookDBImpl::getBookSize(Issue const& issue, std::optional const& domain) +OrderBookDBImpl::getBookSize(Asset const& asset, std::optional const& domain) { std::lock_guard const sl(mLock); if (!domain) { - if (auto it = allBooks_.find(issue); it != allBooks_.end()) + if (auto it = allBooks_.find(asset); it != allBooks_.end()) return static_cast(it->second.size()); } else { - if (auto it = domainBooks_.find({issue, *domain}); it != domainBooks_.end()) + if (auto it = domainBooks_.find({asset, *domain}); it != domainBooks_.end()) return static_cast(it->second.size()); } @@ -251,12 +299,12 @@ OrderBookDBImpl::getBookSize(Issue const& issue, std::optional const& d } bool -OrderBookDBImpl::isBookToXRP(Issue const& issue, std::optional const& domain) +OrderBookDBImpl::isBookToXRP(Asset const& asset, std::optional const& domain) { std::lock_guard const sl(mLock); if (domain) - return xrpDomainBooks_.contains({issue, *domain}); - return xrpBooks_.contains(issue); + return xrpDomainBooks_.contains({asset, *domain}); + return xrpBooks_.contains(asset); } BookListeners::pointer @@ -320,8 +368,8 @@ OrderBookDBImpl::processTxn( data->isFieldPresent(sfTakerPays) && data->isFieldPresent(sfTakerGets)) { auto listeners = getBookListeners( - {data->getFieldAmount(sfTakerGets).issue(), - data->getFieldAmount(sfTakerPays).issue(), + {data->getFieldAmount(sfTakerGets).asset(), + data->getFieldAmount(sfTakerPays).asset(), (*data)[~sfDomainID]}); if (listeners) listeners->publish(jvObj, havePublished); diff --git a/src/xrpld/app/ledger/OrderBookDBImpl.h b/src/xrpld/app/ledger/OrderBookDBImpl.h index 4dd413438e..a72669bef2 100644 --- a/src/xrpld/app/ledger/OrderBookDBImpl.h +++ b/src/xrpld/app/ledger/OrderBookDBImpl.h @@ -41,14 +41,14 @@ public: addOrderBook(Book const& book) override; std::vector - getBooksByTakerPays(Issue const& issue, std::optional const& domain = std::nullopt) + getBooksByTakerPays(Asset const& asset, std::optional const& domain = std::nullopt) override; int - getBookSize(Issue const& issue, std::optional const& domain = std::nullopt) override; + getBookSize(Asset const& asset, std::optional const& domain = std::nullopt) override; bool - isBookToXRP(Issue const& issue, std::optional const& domain = std::nullopt) override; + isBookToXRP(Asset const& asset, std::optional const& domain = std::nullopt) override; // OrderBookDBImpl-specific methods void @@ -71,16 +71,16 @@ private: int const pathSearchMax_; bool const standalone_; - // Maps order books by "issue in" to "issue out": - hardened_hash_map> allBooks_; + // Maps order books by "asset in" to "asset out": + hardened_hash_map> allBooks_; - hardened_hash_map, hardened_hash_set> domainBooks_; + hardened_hash_map, hardened_hash_set> domainBooks_; // does an order book to XRP exist - hash_set xrpBooks_; + hash_set xrpBooks_; // does an order book to XRP exist - hash_set> xrpDomainBooks_; + hash_set> xrpDomainBooks_; std::recursive_mutex mLock; diff --git a/src/xrpld/app/ledger/README.md b/src/xrpld/app/ledger/README.md index cb935897b8..7dc38ce9fa 100644 --- a/src/xrpld/app/ledger/README.md +++ b/src/xrpld/app/ledger/README.md @@ -63,7 +63,7 @@ that the validator sends its proposals and validations to the network. ## Ledger Priorities -There are two ledgers that are the most important for a rippled server to have: +There are two ledgers that are the most important for an xrpld server to have: - The consensus ledger and - The last validated ledger. @@ -224,7 +224,7 @@ conclusion about which last closed ledger is authoritative. ## Consensus -A distributed agreement protocol. Ripple uses the consensus process to solve +A distributed agreement protocol. XRPL uses the consensus process to solve the problem of double-spending. ## Validation @@ -402,7 +402,7 @@ are occupied by the exchange rate. ## Overview -The Ripple server permits clients to subscribe to a continuous stream of +The XRPL server permits clients to subscribe to a continuous stream of fully-validated ledgers. The publication code maintains this stream. The server attempts to maintain this continuous stream unless it falls diff --git a/src/xrpld/app/ledger/TransactionStateSF.cpp b/src/xrpld/app/ledger/TransactionStateSF.cpp index 11d3c83058..e2ec3ca7b6 100644 --- a/src/xrpld/app/ledger/TransactionStateSF.cpp +++ b/src/xrpld/app/ledger/TransactionStateSF.cpp @@ -1,5 +1,15 @@ #include +#include +#include +#include +#include +#include + +#include +#include +#include + namespace xrpl { void diff --git a/src/xrpld/app/ledger/detail/BuildLedger.cpp b/src/xrpld/app/ledger/detail/BuildLedger.cpp index 3b48ab13c5..8f5184336a 100644 --- a/src/xrpld/app/ledger/detail/BuildLedger.cpp +++ b/src/xrpld/app/ledger/detail/BuildLedger.cpp @@ -1,13 +1,27 @@ #include + #include #include #include +#include +#include +#include +#include +#include #include #include -#include +#include +#include +#include +#include #include +#include +#include +#include +#include + namespace xrpl { /* Generic buildLedgerImpl that dispatches to ApplyTxs invocable with signature diff --git a/src/xrpld/app/ledger/detail/InboundLedger.cpp b/src/xrpld/app/ledger/detail/InboundLedger.cpp index 2402b5b561..a65fae2ff3 100644 --- a/src/xrpld/app/ledger/detail/InboundLedger.cpp +++ b/src/xrpld/app/ledger/detail/InboundLedger.cpp @@ -1,21 +1,53 @@ -#include #include + +#include #include #include #include +#include #include +#include #include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include #include +#include +#include +#include #include #include #include +#include #include +#include + #include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include namespace xrpl { @@ -64,7 +96,7 @@ InboundLedger::InboundLedger( app, hash, ledgerAcquireTimeout, - {jtLEDGER_DATA, "InboundLedger", 5}, + {.jobType = jtLEDGER_DATA, .jobName = "InboundLedger", .jobLimit = 5}, app.getJournal("InboundLedger")) , m_clock(clock) , mSeq(seq) @@ -522,7 +554,7 @@ InboundLedger::trigger(std::shared_ptr const& peer, TriggerReason reason) auto packet = std::make_shared(tmBH, protocol::mtGET_OBJECTS); auto const& peerIds = mPeerSet->getPeerIds(); - std::for_each(peerIds.begin(), peerIds.end(), [this, &packet](auto id) { + std::ranges::for_each(peerIds, [this, &packet](auto id) { if (auto p = app_.getOverlay().findPeerByShortID(id)) { mByHash = false; @@ -724,14 +756,13 @@ InboundLedger::filterNodes( { // Sort nodes so that the ones we haven't recently // requested come before the ones we have. - auto dup = std::stable_partition(nodes.begin(), nodes.end(), [this](auto const& item) { - return mRecentNodes.count(item.second) == 0; - }); + auto dup = std::ranges::stable_partition( + nodes, [this](auto const& item) { return mRecentNodes.count(item.second) == 0; }); // If everything is a duplicate we don't want to send // any query at all except on a timeout where we need // to query everyone: - if (dup == nodes.begin()) + if (dup.begin() == nodes.begin()) { JLOG(journal_.trace()) << "filterNodes: all duplicates"; @@ -745,7 +776,7 @@ InboundLedger::filterNodes( { JLOG(journal_.trace()) << "filterNodes: pruning duplicates"; - nodes.erase(dup, nodes.end()); + nodes.erase(dup.begin(), dup.end()); } std::size_t const limit = (reason == TriggerReason::reply) ? reqNodesReply : reqNodes; @@ -957,7 +988,7 @@ InboundLedger::getNeededHashes() if (!mHaveHeader) { - ret.push_back(std::make_pair(protocol::TMGetObjectByHash::otLEDGER, hash_)); + ret.emplace_back(protocol::TMGetObjectByHash::otLEDGER, hash_); return ret; } @@ -966,7 +997,7 @@ InboundLedger::getNeededHashes() AccountStateSF filter(mLedger->stateMap().family().db(), app_.getLedgerMaster()); for (auto const& h : neededStateHashes(4, &filter)) { - ret.push_back(std::make_pair(protocol::TMGetObjectByHash::otSTATE_NODE, h)); + ret.emplace_back(protocol::TMGetObjectByHash::otSTATE_NODE, h); } } @@ -975,7 +1006,7 @@ InboundLedger::getNeededHashes() TransactionStateSF filter(mLedger->txMap().family().db(), app_.getLedgerMaster()); for (auto const& h : neededTxHashes(4, &filter)) { - ret.push_back(std::make_pair(protocol::TMGetObjectByHash::otTRANSACTION_NODE, h)); + ret.emplace_back(protocol::TMGetObjectByHash::otTRANSACTION_NODE, h); } } diff --git a/src/xrpld/app/ledger/detail/InboundLedgers.cpp b/src/xrpld/app/ledger/detail/InboundLedgers.cpp index f147a35ca4..2207308737 100644 --- a/src/xrpld/app/ledger/detail/InboundLedgers.cpp +++ b/src/xrpld/app/ledger/detail/InboundLedgers.cpp @@ -1,18 +1,43 @@ #include + +#include #include #include +#include +#include #include +#include +#include +#include +#include #include #include +#include +#include +#include +#include #include #include +#include +#include +#include #include #include +#include +#include + +#include +#include +#include #include +#include #include #include +#include +#include +#include #include namespace xrpl { @@ -286,7 +311,7 @@ public: for (auto const& it : mLedgers) { XRPL_ASSERT(it.second, "xrpl::InboundLedgersImp::getInfo : non-null ledger"); - acqs.push_back(it); + acqs.emplace_back(it); } for (auto const& it : mRecentFailures) { diff --git a/src/xrpld/app/ledger/detail/InboundTransactions.cpp b/src/xrpld/app/ledger/detail/InboundTransactions.cpp index cc3585a3fb..56c9c633f4 100644 --- a/src/xrpld/app/ledger/detail/InboundTransactions.cpp +++ b/src/xrpld/app/ledger/detail/InboundTransactions.cpp @@ -1,16 +1,29 @@ -#include #include + #include #include +#include -#include +#include +#include +#include +#include #include #include #include +#include +#include +#include + +#include #include +#include +#include #include #include +#include +#include namespace xrpl { @@ -150,7 +163,7 @@ public: return; } - data.emplace_back(std::make_pair(*id, makeSlice(node.nodedata()))); + data.emplace_back(*id, makeSlice(node.nodedata())); } if (!ta->takeNodes(data, peer).isUseful()) diff --git a/src/xrpld/app/ledger/detail/LedgerCleaner.cpp b/src/xrpld/app/ledger/detail/LedgerCleaner.cpp index a0d168f299..850b60f4ed 100644 --- a/src/xrpld/app/ledger/detail/LedgerCleaner.cpp +++ b/src/xrpld/app/ledger/detail/LedgerCleaner.cpp @@ -1,12 +1,34 @@ -#include #include + +#include +#include #include #include #include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include +#include + +#include +#include +#include +#include +#include +#include namespace xrpl { diff --git a/src/xrpld/app/ledger/detail/LedgerDeltaAcquire.cpp b/src/xrpld/app/ledger/detail/LedgerDeltaAcquire.cpp index f829e58830..0ff9a0ed98 100644 --- a/src/xrpld/app/ledger/detail/LedgerDeltaAcquire.cpp +++ b/src/xrpld/app/ledger/detail/LedgerDeltaAcquire.cpp @@ -1,12 +1,34 @@ +#include + #include #include #include #include -#include +#include #include +#include #include +#include +#include +#include +#include +#include #include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include namespace xrpl { @@ -20,7 +42,9 @@ LedgerDeltaAcquire::LedgerDeltaAcquire( app, ledgerHash, LedgerReplayParameters::SUB_TASK_TIMEOUT, - {jtREPLAY_TASK, "LedReplDelta", LedgerReplayParameters::MAX_QUEUED_TASKS}, + {.jobType = jtREPLAY_TASK, + .jobName = "LedReplDelta", + .jobLimit = LedgerReplayParameters::MAX_QUEUED_TASKS}, app.getJournal("LedgerReplayDelta")) , inboundLedgers_(inboundLedgers) , ledgerSeq_(ledgerSeq) diff --git a/src/xrpld/app/ledger/detail/LedgerDeltaAcquire.h b/src/xrpld/app/ledger/detail/LedgerDeltaAcquire.h index 9ac58c2e7c..6839c44386 100644 --- a/src/xrpld/app/ledger/detail/LedgerDeltaAcquire.h +++ b/src/xrpld/app/ledger/detail/LedgerDeltaAcquire.h @@ -125,8 +125,8 @@ private: InboundLedgers& inboundLedgers_; std::uint32_t const ledgerSeq_; std::unique_ptr peerSet_; - std::shared_ptr replayTemp_ = {}; - std::shared_ptr fullLedger_ = {}; + std::shared_ptr replayTemp_; + std::shared_ptr fullLedger_; std::map> orderedTxns_; std::vector dataReadyCallbacks_; std::set reasons_; diff --git a/src/xrpld/app/ledger/detail/LedgerMaster.cpp b/src/xrpld/app/ledger/detail/LedgerMaster.cpp index 876a7e0578..880e8e9e38 100644 --- a/src/xrpld/app/ledger/detail/LedgerMaster.cpp +++ b/src/xrpld/app/ledger/detail/LedgerMaster.cpp @@ -1,6 +1,9 @@ -#include #include + +#include +#include #include +#include #include #include #include @@ -8,33 +11,71 @@ #include #include #include +#include #include #include #include #include +#include #include +#include +#include +#include #include +#include +#include #include #include #include +#include +#include +#include #include +#include +#include #include #include #include #include +#include +#include #include #include +#include +#include +#include +#include +#include #include #include #include #include #include +#include +#include +#include + +#include + +#include #include +#include #include +#include #include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include #include namespace xrpl { @@ -231,7 +272,7 @@ LedgerMaster::setValidLedger(std::shared_ptr const& l) if (!times.empty() && times.size() >= app_.getValidators().quorum()) { // Calculate the sample median - std::sort(times.begin(), times.end()); + std::ranges::sort(times); auto const t0 = times[(times.size() - 1) / 2]; auto const t1 = times[times.size() / 2]; signTime = t0 + (t1 - t0) / 2; @@ -942,12 +983,12 @@ LedgerMaster::checkAccept(std::shared_ptr const& ledger) { auto fees2 = app_.getValidations().fees(ledger->header().parentHash, base); fees.reserve(fees.size() + fees2.size()); - std::copy(fees2.begin(), fees2.end(), std::back_inserter(fees)); + std::ranges::copy(fees2, std::back_inserter(fees)); } std::uint32_t fee = 0; if (!fees.empty()) { - std::sort(fees.begin(), fees.end()); + std::ranges::sort(fees); if (auto stream = m_journal.debug()) { std::stringstream s; @@ -971,10 +1012,10 @@ LedgerMaster::checkAccept(std::shared_ptr const& ledger) if (ledger->seq() % 256 == 0) { - // Check if the majority of validators run a higher version rippled + // Check if the majority of validators run a higher version xrpld // software. If so print a warning. // - // Validators include their rippled software version in the validation + // Validators include their xrpld software version in the validation // messages of every (flag - 1) ledger. We wait for one ledger time // before checking the version information to accumulate more validation // messages. @@ -990,28 +1031,28 @@ LedgerMaster::checkAccept(std::shared_ptr const& ledger) auto const vals = app_.getValidations().getTrustedForLedger( ledger->header().parentHash, ledger->header().seq - 1); std::size_t higherVersionCount = 0; - std::size_t rippledCount = 0; + std::size_t xrpldCount = 0; for (auto const& v : vals) { if (v->isFieldPresent(sfServerVersion)) { auto version = v->getFieldU64(sfServerVersion); higherVersionCount += BuildInfo::isNewerVersion(version) ? 1 : 0; - rippledCount += BuildInfo::isRippledVersion(version) ? 1 : 0; + xrpldCount += BuildInfo::isXrpldVersion(version) ? 1 : 0; } } // We report only if (1) we have accumulated validation messages // from 90% validators from the UNL, (2) 60% of validators - // running the rippled implementation have higher version numbers, + // running the xrpld implementation have higher version numbers, // and (3) the calculation won't cause divide-by-zero. - if (higherVersionCount > 0 && rippledCount > 0) + if (higherVersionCount > 0 && xrpldCount > 0) { constexpr std::size_t reportingPercent = 90; constexpr std::size_t cutoffPercent = 60; auto const unlSize{app_.getValidators().getQuorumKeys().second.size()}; needPrint = unlSize > 0 && calculatePercent(vals.size(), unlSize) >= reportingPercent && - calculatePercent(higherVersionCount, rippledCount) >= cutoffPercent; + calculatePercent(higherVersionCount, xrpldCount) >= cutoffPercent; } } // To throttle the warning messages, instead of printing a warning diff --git a/src/xrpld/app/ledger/detail/LedgerPersistence.cpp b/src/xrpld/app/ledger/detail/LedgerPersistence.cpp index 91de010f1d..0edf425f13 100644 --- a/src/xrpld/app/ledger/detail/LedgerPersistence.cpp +++ b/src/xrpld/app/ledger/detail/LedgerPersistence.cpp @@ -1,14 +1,22 @@ #include +#include +#include #include #include +#include #include #include -#include #include -#include +#include #include +#include +#include +#include +#include +#include + namespace xrpl { static bool diff --git a/src/xrpld/app/ledger/detail/LedgerReplay.cpp b/src/xrpld/app/ledger/detail/LedgerReplay.cpp index a02267e4a1..925ede6d27 100644 --- a/src/xrpld/app/ledger/detail/LedgerReplay.cpp +++ b/src/xrpld/app/ledger/detail/LedgerReplay.cpp @@ -1,6 +1,12 @@ #include #include +#include + +#include +#include +#include +#include namespace xrpl { diff --git a/src/xrpld/app/ledger/detail/LedgerReplayMsgHandler.cpp b/src/xrpld/app/ledger/detail/LedgerReplayMsgHandler.cpp index 93d7ac0d2f..9facb24c9d 100644 --- a/src/xrpld/app/ledger/detail/LedgerReplayMsgHandler.cpp +++ b/src/xrpld/app/ledger/detail/LedgerReplayMsgHandler.cpp @@ -1,12 +1,35 @@ +#include + #include #include -#include #include +#include +#include +#include +#include #include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include #include +#include +#include +#include namespace xrpl { LedgerReplayMsgHandler::LedgerReplayMsgHandler(Application& app, LedgerReplayer& replayer) diff --git a/src/xrpld/app/ledger/detail/LedgerReplayTask.cpp b/src/xrpld/app/ledger/detail/LedgerReplayTask.cpp index f393c7fca8..1afe94bbc8 100644 --- a/src/xrpld/app/ledger/detail/LedgerReplayTask.cpp +++ b/src/xrpld/app/ledger/detail/LedgerReplayTask.cpp @@ -1,8 +1,23 @@ -#include #include + +#include +#include #include #include #include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include namespace xrpl { @@ -52,7 +67,7 @@ LedgerReplayTask::TaskParameter::canMergeInto(TaskParameter const& existingTask) if (existingTask.full_) { auto const& exList = existingTask.skipList_; - if (auto i = std::find(exList.begin(), exList.end(), finishHash_); i != exList.end()) + if (auto i = std::ranges::find(exList, finishHash_); i != exList.end()) { return existingTask.totalLedgers_ >= totalLedgers_ + (exList.end() - i) - 1; } @@ -72,7 +87,9 @@ LedgerReplayTask::LedgerReplayTask( app, parameter.finishHash_, LedgerReplayParameters::TASK_TIMEOUT, - {jtREPLAY_TASK, "LedReplTask", LedgerReplayParameters::MAX_QUEUED_TASKS}, + {.jobType = jtREPLAY_TASK, + .jobName = "LedReplTask", + .jobLimit = LedgerReplayParameters::MAX_QUEUED_TASKS}, app.getJournal("LedgerReplayTask")) , inboundLedgers_(inboundLedgers) , replayer_(replayer) diff --git a/src/xrpld/app/ledger/detail/LedgerReplayer.cpp b/src/xrpld/app/ledger/detail/LedgerReplayer.cpp index ae3552f258..7779132b39 100644 --- a/src/xrpld/app/ledger/detail/LedgerReplayer.cpp +++ b/src/xrpld/app/ledger/detail/LedgerReplayer.cpp @@ -1,6 +1,28 @@ #include + +#include +#include #include #include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include namespace xrpl { @@ -97,8 +119,7 @@ LedgerReplayer::createDeltas(std::shared_ptr task) JLOG(j_.trace()) << "Creating " << parameter.totalLedgers_ - 1 << " deltas"; if (parameter.totalLedgers_ > 1) { - auto skipListItem = - std::find(parameter.skipList_.begin(), parameter.skipList_.end(), parameter.startHash_); + auto skipListItem = std::ranges::find(parameter.skipList_, parameter.startHash_); auto const wasLast = skipListItem == parameter.skipList_.end(); if (not wasLast) ++skipListItem; @@ -197,9 +218,9 @@ LedgerReplayer::sweep() << skipLists_.size() << " skipLists, and " << deltas_.size() << " deltas."; tasks_.erase( - std::remove_if( - tasks_.begin(), - tasks_.end(), + std::ranges::remove_if( + tasks_, + [this](auto const& t) -> bool { if (t->finished()) { @@ -207,7 +228,8 @@ LedgerReplayer::sweep() return true; } return false; - }), + }) + .begin(), tasks_.end()); auto removeCannotLocked = [](auto& subTasks) { @@ -239,7 +261,7 @@ LedgerReplayer::stop() JLOG(j_.info()) << "Stopping..."; { std::lock_guard const lock(mtx_); - std::for_each(tasks_.begin(), tasks_.end(), [](auto& i) { i->cancel(); }); + std::ranges::for_each(tasks_, [](auto& i) { i->cancel(); }); tasks_.clear(); auto lockAndCancel = [](auto& i) { if (auto sptr = i.second.lock(); sptr) @@ -247,9 +269,9 @@ LedgerReplayer::stop() sptr->cancel(); } }; - std::for_each(skipLists_.begin(), skipLists_.end(), lockAndCancel); + std::ranges::for_each(skipLists_, lockAndCancel); skipLists_.clear(); - std::for_each(deltas_.begin(), deltas_.end(), lockAndCancel); + std::ranges::for_each(deltas_, lockAndCancel); deltas_.clear(); } diff --git a/src/xrpld/app/ledger/detail/LedgerToJson.cpp b/src/xrpld/app/ledger/detail/LedgerToJson.cpp index a48756f9b6..8ba45fb515 100644 --- a/src/xrpld/app/ledger/detail/LedgerToJson.cpp +++ b/src/xrpld/app/ledger/detail/LedgerToJson.cpp @@ -1,15 +1,35 @@ -#include #include + +#include #include #include #include #include #include +#include #include +#include +#include +#include +#include #include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include + +#include +#include +#include namespace xrpl { diff --git a/src/xrpld/app/ledger/detail/LocalTxs.cpp b/src/xrpld/app/ledger/detail/LocalTxs.cpp index 38969a092c..5326568e35 100644 --- a/src/xrpld/app/ledger/detail/LocalTxs.cpp +++ b/src/xrpld/app/ledger/detail/LocalTxs.cpp @@ -1,7 +1,19 @@ #include -#include +#include +#include +#include +#include #include +#include +#include +#include + +#include +#include +#include +#include +#include /* This code prevents scenarios like the following: diff --git a/src/xrpld/app/ledger/detail/OpenLedger.cpp b/src/xrpld/app/ledger/detail/OpenLedger.cpp index dfce2278a5..5db4e23e1e 100644 --- a/src/xrpld/app/ledger/detail/OpenLedger.cpp +++ b/src/xrpld/app/ledger/detail/OpenLedger.cpp @@ -1,17 +1,39 @@ #include + #include #include #include -#include #include +#include +#include +#include #include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include #include #include +#include + +#include +#include +#include +#include +#include +#include +#include + namespace xrpl { OpenLedger::OpenLedger( diff --git a/src/xrpld/app/ledger/detail/SkipListAcquire.cpp b/src/xrpld/app/ledger/detail/SkipListAcquire.cpp index 20d63bcb64..c77c0f1b03 100644 --- a/src/xrpld/app/ledger/detail/SkipListAcquire.cpp +++ b/src/xrpld/app/ledger/detail/SkipListAcquire.cpp @@ -1,9 +1,30 @@ +#include + #include #include -#include +#include #include +#include #include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include +#include +#include + namespace xrpl { SkipListAcquire::SkipListAcquire( @@ -15,7 +36,9 @@ SkipListAcquire::SkipListAcquire( app, ledgerHash, LedgerReplayParameters::SUB_TASK_TIMEOUT, - {jtREPLAY_TASK, "SkipListAcq", LedgerReplayParameters::MAX_QUEUED_TASKS}, + {.jobType = jtREPLAY_TASK, + .jobName = "SkipListAcq", + .jobLimit = LedgerReplayParameters::MAX_QUEUED_TASKS}, app.getJournal("LedgerReplaySkipList")) , inboundLedgers_(inboundLedgers) , peerSet_(std::move(peerSet)) diff --git a/src/xrpld/app/ledger/detail/TimeoutCounter.cpp b/src/xrpld/app/ledger/detail/TimeoutCounter.cpp index 216771e60d..9cf58bee47 100644 --- a/src/xrpld/app/ledger/detail/TimeoutCounter.cpp +++ b/src/xrpld/app/ledger/detail/TimeoutCounter.cpp @@ -1,7 +1,19 @@ #include +#include + +#include +#include +#include +#include #include +#include +#include + +#include +#include + namespace xrpl { using namespace std::chrono_literals; diff --git a/src/xrpld/app/ledger/detail/TransactionAcquire.cpp b/src/xrpld/app/ledger/detail/TransactionAcquire.cpp index d2561718a3..b38c413344 100644 --- a/src/xrpld/app/ledger/detail/TransactionAcquire.cpp +++ b/src/xrpld/app/ledger/detail/TransactionAcquire.cpp @@ -1,13 +1,28 @@ -#include -#include -#include #include -#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include #include +#include +#include +#include + +#include #include +#include +#include #include +#include +#include namespace xrpl { @@ -29,11 +44,11 @@ TransactionAcquire::TransactionAcquire( app, hash, TX_ACQUIRE_TIMEOUT, - {jtTXN_DATA, "TxAcq", {}}, + {.jobType = jtTXN_DATA, .jobName = "TxAcq", .jobLimit = {}}, app.getJournal("TransactionAcquire")) , mPeerSet(std::move(peerSet)) { - mMap = std::make_shared(SHAMapType::TRANSACTION, hash, app_.getNodeFamily()); + mMap = std::make_shared(SHAMapType::TRANSACTION, hash, app.getNodeFamily()); mMap->setUnbacked(); } diff --git a/src/xrpld/app/ledger/detail/TransactionAcquire.h b/src/xrpld/app/ledger/detail/TransactionAcquire.h index f29a01fca4..d0887f8418 100644 --- a/src/xrpld/app/ledger/detail/TransactionAcquire.h +++ b/src/xrpld/app/ledger/detail/TransactionAcquire.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -16,7 +17,7 @@ public: using pointer = std::shared_ptr; TransactionAcquire(Application& app, uint256 const& hash, std::unique_ptr peerSet); - ~TransactionAcquire() = default; + ~TransactionAcquire() override = default; SHAMapAddNode takeNodes( diff --git a/src/xrpld/app/ledger/detail/TransactionMaster.cpp b/src/xrpld/app/ledger/detail/TransactionMaster.cpp index 4be3c95993..798c29dc05 100644 --- a/src/xrpld/app/ledger/detail/TransactionMaster.cpp +++ b/src/xrpld/app/ledger/detail/TransactionMaster.cpp @@ -1,10 +1,29 @@ #include + #include #include -#include +#include +#include // IWYU pragma: keep +#include #include +#include +#include #include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include namespace xrpl { diff --git a/src/xrpld/app/main/Application.cpp b/src/xrpld/app/main/Application.cpp index 6d02ea38ae..c83b45f247 100644 --- a/src/xrpld/app/main/Application.cpp +++ b/src/xrpld/app/main/Application.cpp @@ -1,16 +1,20 @@ +#include + #include +#include #include #include #include #include #include +#include #include #include #include #include #include -#include #include +#include #include #include #include @@ -22,53 +26,109 @@ #include #include #include +#include #include #include #include #include #include +#include +#include +#include +#include #include +#include #include +#include #include +#include #include #include +#include +#include +#include +#include #include #include #include +#include +#include +#include +#include +#include #include +#include +#include #include #include +#include #include +#include #include +#include #include +#include +#include #include +#include #include +#include +#include #include #include #include #include #include +#include +#include #include +#include +#include #include +#include #include +#include #include +#include +#include +#include +#include +#include #include #include +#include +#include +#include #include -#include +#include +#include +#include +#include +#include #include +#include +#include #include -#include +#include +#include #include +#include #include +#include +#include +#include #include -#include +#include #include +#include +#include +#include #include +#include namespace xrpl { @@ -93,7 +153,7 @@ private: beast::Journal journal, std::chrono::milliseconds interval, boost::asio::io_context& ios) - : m_event(ev), m_journal(journal), m_probe(interval, ios) + : m_event(std::move(ev)), m_journal(journal), m_probe(interval, ios) { } @@ -329,7 +389,9 @@ public: , nodeFamily_(*this, *m_collectorManager) - , m_orderBookDB(make_OrderBookDB(*this, {config_->PATH_SEARCH_MAX, config_->standalone()})) + , m_orderBookDB(make_OrderBookDB( + *this, + {.pathSearchMax = config_->PATH_SEARCH_MAX, .standalone = config_->standalone()})) , m_pathRequestManager( std::make_unique( @@ -548,7 +610,7 @@ public: return *m_networkOPs; } - virtual ServerHandler& + ServerHandler& getServerHandler() override { XRPL_ASSERT( @@ -1072,7 +1134,7 @@ public: return maxDisallowedLedger_; } - virtual std::optional const& + std::optional const& getTrapTxID() const override { return trapTxID_; @@ -1399,7 +1461,7 @@ ApplicationImp::setup(boost::program_options::variables_map const& cmdline) "implications and have"; JLOG(m_journal.warn()) << "*** been deprecated. They will be removed " "in a future release of"; - JLOG(m_journal.warn()) << "*** rippled."; + JLOG(m_journal.warn()) << "*** xrpld."; JLOG(m_journal.warn()) << "*** If you do not use them to sign " "transactions please edit your"; JLOG(m_journal.warn()) << "*** configuration file and remove the [enable_signing] stanza."; @@ -1430,16 +1492,16 @@ ApplicationImp::setup(boost::program_options::variables_map const& cmdline) Resource::Charge loadType = Resource::feeReferenceRPC; Resource::Consumer c; RPC::JsonContext context{ - {getJournal("RPCHandler"), - *this, - loadType, - getOPs(), - getLedgerMaster(), - c, - Role::ADMIN, - {}, - {}, - RPC::apiMaximumSupportedVersion}, + {.j = getJournal("RPCHandler"), + .app = *this, + .loadType = loadType, + .netOps = getOPs(), + .ledgerMaster = getLedgerMaster(), + .consumer = c, + .role = Role::ADMIN, + .coro = {}, + .infoSub = {}, + .apiVersion = RPC::apiMaximumSupportedVersion}, jvCommand}; Json::Value jvResult; @@ -1949,7 +2011,7 @@ ApplicationImp::loadOldLedger( << " UTC.\n" "This replay will not handle your ledger as it was " "originally " - "handled.\nConsider running an earlier version of rippled " + "handled.\nConsider running an earlier version of xrpld " "to " "get the older rules.\n*** CONTINUING ***\n"; } diff --git a/src/xrpld/app/main/Application.h b/src/xrpld/app/main/Application.h index 45bd94adce..d0437be9a6 100644 --- a/src/xrpld/app/main/Application.h +++ b/src/xrpld/app/main/Application.h @@ -16,15 +16,6 @@ namespace xrpl { -namespace unl { -class Manager; -} // namespace unl -namespace Resource { -class Manager; -} // namespace Resource -namespace NodeStore { -class Database; -} // namespace NodeStore namespace perf { class PerfLog; } // namespace perf diff --git a/src/xrpld/app/main/BasicApp.cpp b/src/xrpld/app/main/BasicApp.cpp index 4e976d6775..71138c6517 100644 --- a/src/xrpld/app/main/BasicApp.cpp +++ b/src/xrpld/app/main/BasicApp.cpp @@ -4,6 +4,9 @@ #include +#include +#include + BasicApp::BasicApp(std::size_t numberOfThreads) { work_.emplace(boost::asio::make_work_guard(io_context_)); diff --git a/src/xrpld/app/main/CollectorManager.cpp b/src/xrpld/app/main/CollectorManager.cpp index 353a49de91..a722e1447d 100644 --- a/src/xrpld/app/main/CollectorManager.cpp +++ b/src/xrpld/app/main/CollectorManager.cpp @@ -1,6 +1,16 @@ #include +#include +#include +#include +#include +#include +#include +#include +#include + #include +#include namespace xrpl { @@ -31,7 +41,7 @@ public: m_groups = beast::insight::make_Groups(m_collector); } - ~CollectorManagerImp() = default; + ~CollectorManagerImp() override = default; beast::insight::Collector::ptr const& collector() override diff --git a/src/xrpld/app/main/GRPCServer.cpp b/src/xrpld/app/main/GRPCServer.cpp index a6c0c933be..3c64606516 100644 --- a/src/xrpld/app/main/GRPCServer.cpp +++ b/src/xrpld/app/main/GRPCServer.cpp @@ -1,9 +1,55 @@ #include -#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include #include #include +#include +#include +#include +#include +#include +#include +#include #include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace xrpl { @@ -343,6 +389,48 @@ GRPCServerImpl::GRPCServerImpl(Application& app) Throw("Error parsing secure_gateway section"); } } + + // Read TLS certificate configuration (optional) + sslCertPath_ = section.get("ssl_cert"); + sslKeyPath_ = section.get("ssl_key"); + sslCertChainPath_ = section.get("ssl_cert_chain"); + sslClientCAPath_ = section.get("ssl_client_ca"); + + // If cert or key is specified, both must be specified + if (sslCertPath_.has_value() || sslKeyPath_.has_value()) + { + if (!sslCertPath_.has_value() || !sslKeyPath_.has_value()) + { + JLOG(journal_.error()) + << "Both ssl_cert and ssl_key must be specified for gRPC TLS"; + Throw("Incomplete TLS configuration for gRPC"); + } + JLOG(journal_.info()) << "gRPC TLS enabled with certificate: " << *sslCertPath_; + } + + // Validate TLS configuration consistency: ssl_cert_chain only makes sense when TLS is + // enabled + if (sslCertChainPath_.has_value() && + (!sslCertPath_.has_value() || !sslKeyPath_.has_value())) + { + JLOG(journal_.error()) + << "ssl_cert_chain specified for gRPC without both ssl_cert and ssl_key; " + << "this is an invalid TLS configuration"; + Throw( + "Invalid gRPC TLS configuration: ssl_cert_chain requires both ssl_cert and " + "ssl_key"); + } + + // Validate TLS configuration consistency: ssl_client_ca only makes sense when TLS is + // enabled + if (sslClientCAPath_.has_value() && (!sslCertPath_.has_value() || !sslKeyPath_.has_value())) + { + JLOG(journal_.error()) + << "ssl_client_ca specified for gRPC without both ssl_cert and ssl_key; " + << "this is an invalid TLS configuration"; + Throw( + "Invalid gRPC TLS configuration: ssl_client_ca requires both ssl_cert and ssl_key"); + } } } @@ -377,10 +465,8 @@ GRPCServerImpl::handleRpcs() std::vector> requests = setupListeners(); auto erase = [&requests](Processor* ptr) { - auto it = - std::find_if(requests.begin(), requests.end(), [ptr](std::shared_ptr& sPtr) { - return sPtr.get() == ptr; - }); + auto it = std::ranges::find_if( + requests, [ptr](std::shared_ptr& sPtr) { return sPtr.get() == ptr; }); BOOST_ASSERT(it != requests.end()); it->swap(requests.back()); requests.pop_back(); @@ -514,6 +600,104 @@ GRPCServerImpl::setupListeners() return requests; } +std::shared_ptr +GRPCServerImpl::createServerCredentials() +{ + if (not sslCertPath_.has_value() or not sslKeyPath_.has_value()) + { + JLOG(journal_.info()) << "Configuring gRPC server without TLS"; + return grpc::InsecureServerCredentials(); + } + + JLOG(journal_.info()) << "Configuring gRPC server with TLS"; + + try + { + boost::system::error_code ec; + grpc::SslServerCredentialsOptions sslOpts; + grpc::SslServerCredentialsOptions::PemKeyCertPair keyCertPair; + + std::string const certContents = getFileContents(ec, *sslCertPath_); + if (ec) + { + JLOG(journal_.error()) << "Failed to read gRPC SSL certificate file: " << *sslCertPath_ + << " - " << ec.message(); // LCOV_EXCL_LINE + return nullptr; + } + + std::string const keyContents = getFileContents(ec, *sslKeyPath_); + if (ec) + { + JLOG(journal_.error()) << "Failed to read gRPC SSL key file: " << *sslKeyPath_ << " - " + << ec.message(); // LCOV_EXCL_LINE + return nullptr; + } + + keyCertPair.private_key = keyContents; + + // Read intermediate CA certificates for server certificate chain (optional) + std::string certChainContents; + if (sslCertChainPath_.has_value()) + { + certChainContents = getFileContents(ec, *sslCertChainPath_); + if (ec) + { + JLOG(journal_.error()) + << "Failed to read gRPC SSL cert chain file: " << *sslCertChainPath_ << " - " + << ec.message(); // LCOV_EXCL_LINE + return nullptr; + } + } + + // Read CA certificate for client verification (mTLS, optional) + if (sslClientCAPath_.has_value()) + { + auto const clientCAContents = getFileContents(ec, *sslClientCAPath_); + if (ec) + { + JLOG(journal_.error()) + << "Failed to read gRPC SSL client CA file: " << *sslClientCAPath_ << " - " + << ec.message(); // LCOV_EXCL_LINE + return nullptr; + } + + if (clientCAContents.empty()) + { + JLOG(journal_.error()) + << "Empty/truncated gRPC SSL client CA file: " << *sslClientCAPath_ + << " - failed to configure mutual TLS"; // LCOV_EXCL_LINE + return nullptr; + } + + sslOpts.pem_root_certs = clientCAContents; + sslOpts.client_certificate_request = + GRPC_SSL_REQUEST_AND_REQUIRE_CLIENT_CERTIFICATE_AND_VERIFY; + JLOG(journal_.info()) << "gRPC mutual TLS enabled - client certificates will be " + "required and verified"; + } + + // Combine server cert with intermediate CA certs for complete chain + keyCertPair.cert_chain = certContents; + if (!certChainContents.empty()) + { + keyCertPair.cert_chain += '\n' + certChainContents; + JLOG(journal_.info()) << "gRPC server certificate chain configured with " + "intermediate CA certificates"; // LCOV_EXCL_LINE + } + + sslOpts.pem_key_cert_pairs.push_back(keyCertPair); + + JLOG(journal_.info()) << "gRPC TLS credentials configured successfully"; // LCOV_EXCL_LINE + return grpc::SslServerCredentials(sslOpts); + } + catch (std::exception const& e) + { + JLOG(journal_.error()) << "Exception while configuring gRPC TLS: " + << e.what(); // LCOV_EXCL_LINE + return nullptr; + } +} + bool GRPCServerImpl::start() { @@ -521,24 +705,63 @@ GRPCServerImpl::start() if (serverAddress_.empty()) return false; - JLOG(journal_.info()) << "Starting gRPC server at " << serverAddress_; + // Determine TLS mode for logging + bool const tlsEnabled = sslCertPath_.has_value() && sslKeyPath_.has_value(); + bool const mtlsEnabled = tlsEnabled && sslClientCAPath_.has_value(); + + std::string tlsMode = "without TLS"; + if (mtlsEnabled) + { + tlsMode = "with mutual TLS (mTLS)"; + } + else if (tlsEnabled) + { + tlsMode = "with TLS"; + } + + JLOG(journal_.info()) << "Starting gRPC server at " << serverAddress_ << " " + << tlsMode; // LCOV_EXCL_LINE grpc::ServerBuilder builder; - - // Listen on the given address without any authentication mechanism. - // Actually binded port will be returned into "port" variable. int port = 0; - builder.AddListeningPort(serverAddress_, grpc::InsecureServerCredentials(), &port); + + // Create credentials (TLS or insecure) based on configuration + auto credentials = createServerCredentials(); + if (!credentials) + { + JLOG(journal_.error()) << "Failed to create gRPC server credentials for " << serverAddress_ + << " (TLS mode: " << tlsMode + << ") - server will not start"; // LCOV_EXCL_LINE + return false; + } + + // Add listening port with appropriate credentials + builder.AddListeningPort(serverAddress_, credentials, &port); + // Register "service_" as the instance through which we'll communicate with // clients. In this case it corresponds to an *asynchronous* service. builder.RegisterService(&service_); + // Get hold of the completion queue used for the asynchronous communication // with the gRPC runtime. cq_ = builder.AddCompletionQueue(); + // Finally assemble the server. server_ = builder.BuildAndStart(); serverPort_ = static_cast(port); + if (serverPort_ != 0u) + { + JLOG(journal_.info()) << "gRPC server started successfully on port " << serverPort_; + } + else + { + JLOG(journal_.error()) + << "Failed to start gRPC server at " << serverAddress_ << " (TLS mode: " << tlsMode + << "); Possible causes: address already in use, invalid address format, or permission " + "denied"; // LCOV_EXCL_LINE + } + return static_cast(serverPort_); } @@ -557,7 +780,7 @@ GRPCServer::start() { thread_ = std::thread([this]() { // Start the event loop and begin handling requests - beast::setCurrentThreadName("rippled: grpc"); + beast::setCurrentThreadName("xrpld: grpc"); this->impl_.handleRpcs(); }); } diff --git a/src/xrpld/app/main/GRPCServer.h b/src/xrpld/app/main/GRPCServer.h index 037c91df93..215c1e037d 100644 --- a/src/xrpld/app/main/GRPCServer.h +++ b/src/xrpld/app/main/GRPCServer.h @@ -66,6 +66,13 @@ private: std::vector secureGatewayIPs_; + // TLS certificate paths + std::optional sslCertPath_; + std::optional sslKeyPath_; + std::optional sslCertChainPath_; // Intermediate CA certs for server cert chain + std::optional + sslClientCAPath_; // CA cert for client certificate verification (mTLS) + beast::Journal journal_; // typedef for function to bind a listener @@ -124,6 +131,10 @@ public: getEndpoint() const; private: + // Create server credentials (TLS or insecure) based on configuration + std::shared_ptr + createServerCredentials(); + // Class encompassing the state and logic needed to serve a request. template class CallData : public Processor, @@ -175,7 +186,7 @@ private: std::vector const& secureGatewayIPs_; public: - virtual ~CallData() = default; + ~CallData() override = default; // Take in the "service" instance (in this case representing an // asynchronous server) and the completion queue "cq" used for @@ -196,10 +207,10 @@ private: CallData& operator=(CallData const&) = delete; - virtual void + void process() override; - virtual bool + bool isFinished() override; std::shared_ptr @@ -234,14 +245,14 @@ private: getClientEndpoint(); // If the request was proxied through - // another rippled node, returns the ip of the originating client. + // another xrpld node, returns the ip of the originating client. // Empty optional if request was not proxied or there was an error // decoding the client ip std::optional getProxiedClientIpAddress(); // If the request was proxied through - // another rippled node, returns the endpoint of the originating client. + // another xrpld node, returns the endpoint of the originating client. // Empty optional if request was not proxied or there was an error // decoding the client endpoint std::optional @@ -261,7 +272,7 @@ private: bool clientIsUnlimited(); - // True if the request was proxied through another rippled node prior + // True if the request was proxied through another xrpld node prior // to arriving here bool wasForwarded(); diff --git a/src/xrpld/app/main/LoadManager.cpp b/src/xrpld/app/main/LoadManager.cpp index 47948a4031..84c57ad360 100644 --- a/src/xrpld/app/main/LoadManager.cpp +++ b/src/xrpld/app/main/LoadManager.cpp @@ -1,11 +1,18 @@ -#include #include +#include + +#include +#include #include -#include +#include +#include +#include // IWYU pragma: keep #include #include +#include +#include #include #include #include diff --git a/src/xrpld/app/main/LoadManager.h b/src/xrpld/app/main/LoadManager.h index c36afb1804..3ae1f45e3b 100644 --- a/src/xrpld/app/main/LoadManager.h +++ b/src/xrpld/app/main/LoadManager.h @@ -3,6 +3,7 @@ #include #include +#include #include #include #include diff --git a/src/xrpld/app/main/Main.cpp b/src/xrpld/app/main/Main.cpp index f3953823dd..2ee2ac90cd 100644 --- a/src/xrpld/app/main/Main.cpp +++ b/src/xrpld/app/main/Main.cpp @@ -3,30 +3,52 @@ #include #include #include +#include #include +#include +#include #include +#include +#include +#include +#include #include +#include #include +#include #include -#include +#include +#include +#include #include -#include +#include // IWYU pragma: keep #include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include #ifdef ENABLE_TESTS #include #include #endif // ENABLE_TESTS -#include -#include #include #include -#include #include #include @@ -49,7 +71,7 @@ #endif #ifdef ENABLE_VOIDSTAR -#include "antithesis_instrumentation.h" +#include #endif namespace po = boost::program_options; @@ -188,7 +210,7 @@ public: std::vector v; boost::split(v, patterns, boost::algorithm::is_any_of(",")); selectors_.reserve(v.size()); - std::for_each(v.begin(), v.end(), [this](std::string s) { + std::ranges::for_each(v, [this](std::string s) { boost::trim(s); if (selectors_.empty() || !s.empty()) selectors_.emplace_back(beast::unit_test::selector::automatch, s); @@ -356,12 +378,12 @@ run(int argc, char** argv) "nodeid", po::value(), "Specify the node identity for this server.")( "quorum", po::value(), "Override the minimum validation quorum.")( "silent", "No output to the console after startup.")("standalone,a", "Run with no peers.")( - "verbose,v", "Verbose logging.") - - ("force_ledger_present_range", - po::value(), - "Specify the range of present ledgers for testing purposes. Min and " - "max values are comma separated.")("version", "Display the build version."); + "verbose,v", "Verbose logging.")( + "definitions", "Output server definitions as JSON and exit.")( + "force_ledger_present_range", + po::value(), + "Specify the range of present ledgers for testing purposes. Min and " + "max values are comma separated.")("version", "Display the build version."); po::options_description data("Ledger/Data Options"); data.add_options()("import", importText.c_str())( @@ -470,8 +492,8 @@ run(int argc, char** argv) } catch (std::exception const& ex) { - std::cerr << "rippled: " << ex.what() << std::endl; - std::cerr << "Try 'rippled --help' for a list of options." << std::endl; + std::cerr << "xrpld: " << ex.what() << std::endl; + std::cerr << "Try 'xrpld --help' for a list of options." << std::endl; return 1; } @@ -483,17 +505,27 @@ run(int argc, char** argv) if (vm.contains("version")) { - std::cout << "rippled version " << BuildInfo::getVersionString() << std::endl; + // LCOV_EXCL_START + std::cout << "xrpld version " << BuildInfo::getVersionString() << std::endl; std::cout << "Git commit hash: " << xrpl::git::getCommitHash() << std::endl; std::cout << "Git build branch: " << xrpl::git::getBuildBranch() << std::endl; return 0; + // LCOV_EXCL_STOP + } + + if (vm.contains("definitions")) + { + // LCOV_EXCL_START + std::cout << Json::FastWriter().write(getServerDefinitionsJson()); + return 0; + // LCOV_EXCL_STOP } #ifndef ENABLE_TESTS if (vm.count("unittest") || vm.count("unittest-child")) { - std::cerr << "rippled: Tests disabled in this build." << std::endl; - std::cerr << "Try 'rippled --help' for a list of options." << std::endl; + std::cerr << "xrpld: Tests disabled in this build." << std::endl; + std::cerr << "Try 'xrpld --help' for a list of options." << std::endl; return 1; } #else @@ -529,7 +561,7 @@ run(int argc, char** argv) if (vm.contains("unittest-jobs")) { // unittest jobs only makes sense with `unittest` - std::cerr << "rippled: '--unittest-jobs' specified without " + std::cerr << "xrpld: '--unittest-jobs' specified without " "'--unittest'.\n"; std::cerr << "To run the unit tests the '--unittest' option must " "be present.\n"; @@ -793,7 +825,7 @@ run(int argc, char** argv) } // We have an RPC command to process: - beast::setCurrentThreadName("rippled: rpc"); + beast::setCurrentThreadName("xrpld: rpc"); return RPCCall::fromCommandLine( *config, vm["parameters"].as>(), *logs); // LCOV_EXCL_STOP diff --git a/src/xrpld/app/main/NodeIdentity.cpp b/src/xrpld/app/main/NodeIdentity.cpp index bc2c943b10..f52e7e372e 100644 --- a/src/xrpld/app/main/NodeIdentity.cpp +++ b/src/xrpld/app/main/NodeIdentity.cpp @@ -1,10 +1,22 @@ -#include #include + +#include #include #include +#include +#include +#include +#include #include +#include + +#include +#include +#include +#include + namespace xrpl { std::pair diff --git a/src/xrpld/app/main/NodeStoreScheduler.cpp b/src/xrpld/app/main/NodeStoreScheduler.cpp index 1ca8e80523..2aebe40252 100644 --- a/src/xrpld/app/main/NodeStoreScheduler.cpp +++ b/src/xrpld/app/main/NodeStoreScheduler.cpp @@ -1,5 +1,10 @@ #include +#include +#include +#include +#include + namespace xrpl { NodeStoreScheduler::NodeStoreScheduler(JobQueue& jobQueue) : jobQueue_(jobQueue) diff --git a/src/xrpld/app/misc/DeliverMax.h b/src/xrpld/app/misc/DeliverMax.h index 7fec517d28..8610a6a529 100644 --- a/src/xrpld/app/misc/DeliverMax.h +++ b/src/xrpld/app/misc/DeliverMax.h @@ -6,9 +6,7 @@ namespace Json { class Value; } // namespace Json -namespace xrpl { - -namespace RPC { +namespace xrpl::RPC { /** Copy `Amount` field to `DeliverMax` field in transaction output JSON. @@ -24,5 +22,4 @@ insertDeliverMax(Json::Value& tx_json, TxType txnType, unsigned int apiVersion); /** @} */ -} // namespace RPC -} // namespace xrpl +} // namespace xrpl::RPC diff --git a/src/xrpld/app/misc/FeeEscalation.md b/src/xrpld/app/misc/FeeEscalation.md index 7843620320..5a3edbe95a 100644 --- a/src/xrpld/app/misc/FeeEscalation.md +++ b/src/xrpld/app/misc/FeeEscalation.md @@ -1,6 +1,6 @@ # Fees -Rippled's fee mechanism consists of several interrelated processes: +Xrpld's fee mechanism consists of several interrelated processes: 1. [Rapid Fee escalation](#fee-escalation) 2. [The Transaction Queue](#transaction-queue) @@ -184,7 +184,7 @@ than a more complex transaction paying more XRP. ### Load Fee -Each rippled server maintains a minimum cost threshold based on its current load. If you submit a transaction with a fee that is lower than the current load-based transaction cost of the rippled server, the server neither applies nor relays the transaction to its peers. A transaction is very unlikely to survive the consensus process unless its transaction fee value meets the requirements of a majority of servers. +Each xrpld server maintains a minimum cost threshold based on its current load. If you submit a transaction with a fee that is lower than the current load-based transaction cost of the xrpld server, the server neither applies nor relays the transaction to its peers. A transaction is very unlikely to survive the consensus process unless its transaction fee value meets the requirements of a majority of servers. ### Reference Transaction @@ -193,7 +193,7 @@ single-signed transaction (eg. Payment, Account Set, Offer Create, etc) that requires a fee. In the future, there may be other transaction types that require -more (or less) work for rippled to process. Those transactions may have +more (or less) work for xrpld to process. Those transactions may have a higher (or lower) base fee, requiring a correspondingly higher (or lower) fee to get into the same position as a reference transaction. @@ -211,7 +211,7 @@ Another factor to consider is the duration of the consensus process itself. This generally takes under 5 seconds on the main network under low volume. This is based on historical observations. However factors such as transaction volume -can increase consensus duration. This is because rippled performs +can increase consensus duration. This is because xrpld performs more work as transaction volume increases. Under sufficient load this tends to increase consensus duration. It's possible that relatively high consensus duration indicates a problem, but it is not appropriate to @@ -293,7 +293,7 @@ values by 5 for a multi-signed transaction with 4 signatures.) The `fee` result is always instantaneous, and relates to the open ledger. It includes the sequence number of the current open ledger, -but may not make sense if rippled is not synced to the network. +but may not make sense if xrpld is not synced to the network. Result format: diff --git a/src/xrpld/app/misc/FeeVoteImpl.cpp b/src/xrpld/app/misc/FeeVoteImpl.cpp index 414d8d7421..53e56286b8 100644 --- a/src/xrpld/app/misc/FeeVoteImpl.cpp +++ b/src/xrpld/app/misc/FeeVoteImpl.cpp @@ -1,10 +1,31 @@ #include #include +#include +#include #include -#include +#include +#include +#include +#include +#include +#include #include -#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include namespace xrpl { diff --git a/src/xrpld/app/misc/NegativeUNLVote.cpp b/src/xrpld/app/misc/NegativeUNLVote.cpp index 212cfaa2e9..8db726ff48 100644 --- a/src/xrpld/app/misc/NegativeUNLVote.cpp +++ b/src/xrpld/app/misc/NegativeUNLVote.cpp @@ -1,8 +1,31 @@ -#include #include +#include + +#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include + +#include +#include +#include +#include +#include +#include +#include namespace xrpl { diff --git a/src/xrpld/app/misc/NetworkOPs.cpp b/src/xrpld/app/misc/NetworkOPs.cpp index 361eada455..cedc888825 100644 --- a/src/xrpld/app/misc/NetworkOPs.cpp +++ b/src/xrpld/app/misc/NetworkOPs.cpp @@ -1,7 +1,10 @@ +#include + #include #include #include #include +#include #include #include #include @@ -11,17 +14,19 @@ #include #include #include +#include #include #include #include #include -#include #include #include -#include #include +#include +#include #include #include +#include #include #include #include @@ -30,47 +35,122 @@ #include #include +#include +#include +#include #include +#include +#include +#include #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include #include +#include #include #include +#include #include #include #include +#include +#include +#include +#include #include +#include +#include +#include +#include #include +#include #include #include #include +#include +#include +#include #include +#include #include +#include +#include +#include +#include #include #include +#include +#include #include #include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include #include +#include +#include #include +#include #include +#include #include +#include +#include #include +#include +#include #include #include +#include +#include +#include + +#include #include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include #include #include #include #include +#include #include -#include +#include +#include #include +#include +#include namespace xrpl { @@ -91,7 +171,7 @@ class NetworkOPsImp final : public NetworkOPs TER result; TransactionStatus(std::shared_ptr t, bool a, bool l, FailHard f) - : transaction(t), admin(a), local(l), failType(f) + : transaction(std::move(t)), admin(a), local(l), failType(f) { XRPL_ASSERT( local || failType == FailHard::no, @@ -178,7 +258,11 @@ class NetworkOPsImp final : public NetworkOPs getCounterData() const { std::lock_guard const lock(mutex_); - return {counters_, mode_, start_, initialSyncUs_}; + return { + .counters = counters_, + .mode = mode_, + .start = start_, + .initialSyncUs = initialSyncUs_}; } }; @@ -1254,7 +1338,7 @@ NetworkOPsImp::doTransactionAsync( if (transaction->getApplying()) return; - mTransactions.push_back(TransactionStatus(transaction, bUnlimited, false, failType)); + mTransactions.emplace_back(transaction, bUnlimited, false, failType); transaction->setApplying(); if (mDispatchState == DispatchState::none) @@ -1276,7 +1360,7 @@ NetworkOPsImp::doTransactionSync( if (!transaction->getApplying()) { - mTransactions.push_back(TransactionStatus(transaction, bUnlimited, true, failType)); + mTransactions.emplace_back(transaction, bUnlimited, true, failType); transaction->setApplying(); } @@ -1373,9 +1457,8 @@ NetworkOPsImp::processTransactionSet(CanonicalTXSet const& set) doTransactionSyncBatch(lock, [&](std::unique_lock const&) { XRPL_ASSERT(lock.owns_lock(), "xrpl::NetworkOPsImp::processTransactionSet has lock"); - return std::any_of(mTransactions.begin(), mTransactions.end(), [](auto const& t) { - return t.transaction->getApplying(); - }); + return std::ranges::any_of( + mTransactions, [](auto const& t) { return t.transaction->getApplying(); }); }); } @@ -2860,7 +2943,7 @@ NetworkOPsImp::getServerInfo(bool human, bool admin, bool counters) // This array must be sorted in increasing order. static constexpr std::array protocols{ "http", "https", "peer", "ws", "ws2", "wss", "wss2"}; - static_assert(std::is_sorted(std::begin(protocols), std::end(protocols))); + static_assert(std::ranges::is_sorted(protocols)); { Json::Value ports{Json::arrayValue}; for (auto const& port : registry_.get().getServerHandler().setup().ports) @@ -2871,6 +2954,7 @@ NetworkOPsImp::getServerInfo(bool human, bool admin, bool counters) port.admin_user.empty() && port.admin_password.empty())) continue; std::vector proto; + // NOLINTNEXTLINE(modernize-use-ranges) std::set_intersection( std::begin(port.protocol), std::end(port.protocol), @@ -3177,10 +3261,15 @@ NetworkOPsImp::transJson( auto const amount = transaction->getFieldAmount(sfTakerGets); // If the offer create is not self funded then add the owner balance - if (account != amount.issue().account) + if (account != amount.getIssuer()) { auto const ownerFunds = accountFunds( - *ledger, account, amount, fhIGNORE_FREEZE, registry_.get().getJournal("View")); + *ledger, + account, + amount, + fhIGNORE_FREEZE, + ahIGNORE_AUTH, + registry_.get().getJournal("View")); jvObj[jss::transaction][jss::owner_funds] = ownerFunds.getText(); } } @@ -3344,7 +3433,7 @@ NetworkOPsImp::pubAccountTransaction( if (auto isSptr = info.sinkWptr_.lock(); isSptr) { accountHistoryNotify.emplace_back( - SubAccountHistoryInfo{isSptr, info.index_}); + SubAccountHistoryInfo{.sink_ = isSptr, .index_ = info.index_}); ++it; } else @@ -3673,7 +3762,11 @@ NetworkOPsImp::addAccountHistoryJob(SubAccountHistoryInfoWeak subInfo) case Sqlite: { auto& db = registry_.get().getRelationalDatabase(); RelationalDatabase::AccountTxPageOptions const options{ - accountId, {minLedger, maxLedger}, marker, 0, true}; + .account = accountId, + .ledgerRange = {.min = minLedger, .max = maxLedger}, + .marker = marker, + .limit = 0, + .bAdmin = true}; return db.newestAccountTxPage(options); } // LCOV_EXCL_START @@ -3897,7 +3990,8 @@ NetworkOPsImp::subAccountHistory(InfoSub::ref isrListener, AccountID const& acco } std::lock_guard const sl(mSubLock); - SubAccountHistoryInfoWeak ahi{isrListener, std::make_shared(accountId)}; + SubAccountHistoryInfoWeak ahi{ + .sinkWptr_ = isrListener, .index_ = std::make_shared(accountId)}; auto simIterator = mSubAccountHistory.find(accountId); if (simIterator == mSubAccountHistory.end()) { @@ -4271,7 +4365,7 @@ NetworkOPsImp::getBookPage( ReadView const& view = *lpLedger; bool const bGlobalFreeze = - isGlobalFrozen(view, book.out.account) || isGlobalFrozen(view, book.in.account); + isGlobalFrozen(view, book.out.getIssuer()) || isGlobalFrozen(view, book.in.getIssuer()); bool bDone = false; bool bDirectAdvance = true; @@ -4281,7 +4375,7 @@ NetworkOPsImp::getBookPage( unsigned int uBookEntry = 0; STAmount saDirRate; - auto const rate = transferRate(view, book.out.account); + auto const rate = transferRate(view, book.out.getIssuer()); auto viewJ = registry_.get().getJournal("View"); while (!bDone && iLimit-- > 0) @@ -4331,7 +4425,7 @@ NetworkOPsImp::getBookPage( STAmount saOwnerFunds; bool firstOwnerOffer(true); - if (book.out.account == uOfferOwnerID) + if (book.out.getIssuer() == uOfferOwnerID) { // If an offer is selling issuer's own IOUs, it is fully // funded. @@ -4360,9 +4454,9 @@ NetworkOPsImp::getBookPage( saOwnerFunds = accountHolds( view, uOfferOwnerID, - book.out.currency, - book.out.account, + book.out, fhZERO_IF_FROZEN, + ahZERO_IF_UNAUTHORIZED, viewJ); if (saOwnerFunds < beast::zero) @@ -4382,9 +4476,9 @@ NetworkOPsImp::getBookPage( if (rate != parityRate // Have a transfer fee. - && uTakerID != book.out.account + && uTakerID != book.out.getIssuer() // Not taking offers of own IOUs. - && book.out.account != uOfferOwnerID) + && book.out.getIssuer() != uOfferOwnerID) // Offer owner not issuing ownfunds { // Need to charge a transfer fee to offer owner. @@ -4405,7 +4499,7 @@ NetworkOPsImp::getBookPage( saTakerGetsFunded.setJson(jvOffer[jss::taker_gets_funded]); std::min( - saTakerPays, multiply(saTakerGetsFunded, saDirRate, saTakerPays.issue())) + saTakerPays, multiply(saTakerGetsFunded, saDirRate, saTakerPays.asset())) .setJson(jvOffer[jss::taker_pays_funded]); } @@ -4549,7 +4643,7 @@ NetworkOPsImp::getBookPage( // TODO(tom): The result of this expression is not used - what's // going on here? - std::min(saTakerPays, multiply(saTakerGetsFunded, saDirRate, saTakerPays.issue())) + std::min(saTakerPays, multiply(saTakerGetsFunded, saDirRate, saTakerPays.asset())) .setJson(jvOffer[jss::taker_pays_funded]); } diff --git a/src/xrpld/app/misc/README.md b/src/xrpld/app/misc/README.md index 2f9fff0ca3..359f58578d 100644 --- a/src/xrpld/app/misc/README.md +++ b/src/xrpld/app/misc/README.md @@ -1,6 +1,6 @@ # Fee Voting -The Ripple payment protocol enforces a fee schedule expressed in units of the +The XRPL payment protocol enforces a fee schedule expressed in units of the native currency, XRP. Fees for transactions are paid directly from the account owner. There are also reserve requirements for each item that occupies storage in the ledger. The reserve fee schedule contains both a per-account reserve, @@ -20,7 +20,7 @@ subsequent ledgers a new fee schedule is enacted. ## Consensus -The Ripple consensus algorithm allows distributed participants to arrive at +The XRPL consensus algorithm allows distributed participants to arrive at the same answer for yes/no questions. The canonical case for consensus is whether or not a particular transaction is included in the ledger. Fees present a more difficult challenge, since the decision on the new fee is not @@ -54,7 +54,7 @@ be converged in the consensus process, the following algorithm is used: ## Configuration -A validating instance of rippled uses information in the configuration file +A validating instance of xrpld uses information in the configuration file to determine how it wants to vote on the fee schedule. It is the responsibility of the administrator to set these values. @@ -64,7 +64,7 @@ of the administrator to set these values. An Amendment is a new or proposed change to a ledger rule. Ledger rules affect transaction processing and consensus; peers must use the same set of rules for -consensus to succeed, otherwise different instances of rippled will get +consensus to succeed, otherwise different instances of xrpld will get different results. Amendments can be almost anything but they must be accepted by a network majority through a consensus process before they are utilized. An Amendment must receive at least an 80% approval rate from validating nodes for @@ -77,7 +77,7 @@ process of an Amendment from its conception to approval and usage. - Some members contribute their time and work to develop the Amendment. -- A pull request is created and the new code is folded into a rippled build +- A pull request is created and the new code is folded into an xrpld build and made available for use. - The consensus process begins with the validating nodes. diff --git a/src/xrpld/app/misc/SHAMapStore.h b/src/xrpld/app/misc/SHAMapStore.h index b377538f62..6788d15392 100644 --- a/src/xrpld/app/misc/SHAMapStore.h +++ b/src/xrpld/app/misc/SHAMapStore.h @@ -1,5 +1,7 @@ #pragma once +#include + #include #include diff --git a/src/xrpld/app/misc/SHAMapStoreImp.cpp b/src/xrpld/app/misc/SHAMapStoreImp.cpp index 140f260d56..518d9f6b14 100644 --- a/src/xrpld/app/misc/SHAMapStoreImp.cpp +++ b/src/xrpld/app/misc/SHAMapStoreImp.cpp @@ -1,16 +1,45 @@ -#include #include + +#include +#include #include +#include #include +#include +#include +#include +#include #include +#include +#include +#include +#include #include #include +#include #include #include #include +#include #include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace xrpl { void diff --git a/src/xrpld/app/misc/SHAMapStoreImp.h b/src/xrpld/app/misc/SHAMapStoreImp.h index df3c16b24f..08e3dd70eb 100644 --- a/src/xrpld/app/misc/SHAMapStoreImp.h +++ b/src/xrpld/app/misc/SHAMapStoreImp.h @@ -61,7 +61,7 @@ private: // minimum # of ledgers required for standalone mode. static std::uint32_t const minimumDeletionIntervalSA_ = 8; // minimum ledger to maintain online. - std::atomic minimumOnline_{}; + std::atomic minimumOnline_; NodeStore::Scheduler& scheduler_; beast::Journal const journal_; @@ -102,7 +102,7 @@ public: std::uint32_t clampFetchDepth(std::uint32_t fetch_depth) const override { - return deleteInterval_ ? std::min(fetch_depth, deleteInterval_) : fetch_depth; + return (deleteInterval_ != 0u) ? std::min(fetch_depth, deleteInterval_) : fetch_depth; } std::unique_ptr @@ -195,7 +195,7 @@ private: clearPrior(LedgerIndex lastRotated); /** - * This is a health check for online deletion that waits until rippled is + * This is a health check for online deletion that waits until xrpld is * stable before returning. It returns an indication of whether the server * is stopping. * @@ -209,7 +209,7 @@ public: void start() override { - if (deleteInterval_) + if (deleteInterval_ != 0u) thread_ = std::thread(&SHAMapStoreImp::run, this); } diff --git a/src/xrpld/app/misc/Transaction.h b/src/xrpld/app/misc/Transaction.h index fa126e6bb5..31d899d99b 100644 --- a/src/xrpld/app/misc/Transaction.h +++ b/src/xrpld/app/misc/Transaction.h @@ -21,7 +21,6 @@ namespace xrpl { // class Application; -class Database; class Rules; enum TransStatus { @@ -139,7 +138,7 @@ public: * @return Whether transaction is being applied within a batch. */ bool - getApplying() + getApplying() const { // Note that all access to mApplying are made by NetworkOPsImp, and must // be done under that class's lock. @@ -308,7 +307,7 @@ public: // Calling the wrong getter function will throw an exception. // See documentation for the getter functions for more details bool - isFound() + isFound() const { return std::holds_alternative>(locator); } diff --git a/src/xrpld/app/misc/TxQ.h b/src/xrpld/app/misc/TxQ.h index 772d51b959..621f3b8c60 100644 --- a/src/xrpld/app/misc/TxQ.h +++ b/src/xrpld/app/misc/TxQ.h @@ -382,11 +382,12 @@ private: , targetTxnCount_( setup.targetTxnInLedger < minimumTxnCount_ ? minimumTxnCount_ : setup.targetTxnInLedger) - , maximumTxnCount_( - setup.maximumTxnInLedger ? *setup.maximumTxnInLedger < targetTxnCount_ - ? targetTxnCount_ - : *setup.maximumTxnInLedger - : std::optional(std::nullopt)) + , maximumTxnCount_([&]() -> std::optional { + if (!setup.maximumTxnInLedger) + return std::nullopt; + return *setup.maximumTxnInLedger < targetTxnCount_ ? targetTxnCount_ + : *setup.maximumTxnInLedger; + }()) , txnsExpected_(minimumTxnCount_) , recentTxnCounts_(setup.ledgersInQueue) , escalationMultiplier_(setup.minimumEscalationMultiplier) @@ -398,9 +399,9 @@ private: Updates fee metrics based on the transactions in the ReadView for use in fee escalation calculations. - @param app Rippled Application object. + @param app Xrpld Application object. @param view View of the LCL that was just closed or received. - @param timeLeap Indicates that rippled is under load so fees + @param timeLeap Indicates that xrpld is under load so fees should grow faster. @param setup Customization params. */ @@ -414,7 +415,7 @@ private: // Number of transactions expected per ledger. // One more than this value will be accepted // before escalation kicks in. - std::size_t const txnsExpected; + std::size_t const txnsExpected{}; // Based on the median fee of the LCL. Used // when fee escalation kicks in. FeeLevel64 const escalationMultiplier; @@ -424,7 +425,7 @@ private: Snapshot getSnapshot() const { - return {txnsExpected_, escalationMultiplier_}; + return {.txnsExpected = txnsExpected_, .escalationMultiplier = escalationMultiplier_}; } /** Use the number of transactions in the current open ledger @@ -672,7 +673,7 @@ private: bool empty() const { - return !getTxnCount(); + return getTxnCount() == 0u; } /// Find the entry in transactions that precedes seqProx, if one does. diff --git a/src/xrpld/app/misc/ValidatorList.h b/src/xrpld/app/misc/ValidatorList.h index 9b7670a482..ff5aa8c71f 100644 --- a/src/xrpld/app/misc/ValidatorList.h +++ b/src/xrpld/app/misc/ValidatorList.h @@ -108,11 +108,11 @@ struct ValidatorBlobInfo Trusted Validators List ----------------------- - Rippled accepts ledger proposals and validations from trusted validator + Xrpld accepts ledger proposals and validations from trusted validator nodes. A ledger is considered fully-validated once the number of received trusted validations for a ledger meets or exceeds a quorum value. - This class manages the set of validation public keys the local rippled node + This class manages the set of validation public keys the local xrpld node trusts. The list of trusted keys is populated using the keys listed in the configuration file as well as lists signed by trusted publishers. The trusted publisher public keys are specified in the config. @@ -121,9 +121,9 @@ struct ValidatorBlobInfo @li @c "blob": Base64-encoded JSON string containing a @c "sequence", @c "validFrom", @c "validUntil", and @c "validators" field. @c "validFrom" - contains the Ripple timestamp (seconds since January 1st, 2000 (00:00 + contains the XRPL timestamp (seconds since January 1st, 2000 (00:00 UTC)) for when the list becomes valid. @c "validUntil" contains the - Ripple timestamp for when the list expires. @c "validators" contains + XRPL timestamp for when the list expires. @c "validators" contains an array of objects with a @c "validation_public_key" and optional @c "manifest" field. @c "validation_public_key" should be the hex-encoded master public key. @c "manifest" should be the diff --git a/src/xrpld/app/misc/ValidatorSite.h b/src/xrpld/app/misc/ValidatorSite.h index 270df6f9f0..ab82ae168d 100644 --- a/src/xrpld/app/misc/ValidatorSite.h +++ b/src/xrpld/app/misc/ValidatorSite.h @@ -28,7 +28,7 @@ namespace xrpl { @li @c "blob": Base64-encoded JSON string containing a @c "sequence", @c "validUntil", and @c "validators" field. @c "validUntil" contains the - Ripple timestamp (seconds since January 1st, 2000 (00:00 UTC)) for when + XRPL timestamp (seconds since January 1st, 2000 (00:00 UTC)) for when the list expires. @c "validators" contains an array of objects with a @c "validation_public_key" and optional @c "manifest" field. @c "validation_public_key" should be the hex-encoded master public key. diff --git a/src/xrpld/app/misc/detail/AccountTxPaging.cpp b/src/xrpld/app/misc/detail/AccountTxPaging.cpp index bb0a09426a..d29bedc19d 100644 --- a/src/xrpld/app/misc/detail/AccountTxPaging.cpp +++ b/src/xrpld/app/misc/detail/AccountTxPaging.cpp @@ -1,11 +1,22 @@ +#include + #include #include #include #include -#include +#include +#include #include +#include +#include #include +#include + +#include +#include +#include +#include namespace xrpl { diff --git a/src/xrpld/app/misc/detail/AmendmentTable.cpp b/src/xrpld/app/misc/detail/AmendmentTable.cpp index afecb08b24..0698230eb2 100644 --- a/src/xrpld/app/misc/detail/AmendmentTable.cpp +++ b/src/xrpld/app/misc/detail/AmendmentTable.cpp @@ -1,18 +1,48 @@ -#include #include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include #include +#include #include #include +#include #include -#include -#include +#include +#include #include -#include +#include +#include +#include #include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include namespace xrpl { @@ -45,7 +75,7 @@ parseSection(Section const& section) "Invalid amendment ID '" + match[1] + "' in [" + section.name() + "]"); } - names.push_back(std::make_pair(id, match[2])); + names.emplace_back(id, match[2]); } return names; @@ -191,9 +221,8 @@ public: } // Now remove any expired records from recordedVotes_. - std::for_each( - recordedVotes_.begin(), - recordedVotes_.end(), + std::ranges::for_each( + recordedVotes_, [&closeTime, newTimeout, &j](decltype(recordedVotes_)::value_type& votes) { auto const pkHuman = toBase58(TokenType::NodePublic, votes.first); if (!votes.second.timeout) @@ -754,7 +783,7 @@ AmendmentTableImpl::doValidation(std::set const& enabled) const } if (!amendments.empty()) - std::sort(amendments.begin(), amendments.end()); + std::ranges::sort(amendments); return amendments; } diff --git a/src/xrpld/app/misc/detail/DeliverMax.cpp b/src/xrpld/app/misc/detail/DeliverMax.cpp index 44500eec7e..4e3d6563f9 100644 --- a/src/xrpld/app/misc/detail/DeliverMax.cpp +++ b/src/xrpld/app/misc/detail/DeliverMax.cpp @@ -1,9 +1,9 @@ #include +#include #include -namespace xrpl { -namespace RPC { +namespace xrpl::RPC { void insertDeliverMax(Json::Value& tx_json, TxType txnType, unsigned int apiVersion) @@ -19,5 +19,4 @@ insertDeliverMax(Json::Value& tx_json, TxType txnType, unsigned int apiVersion) } } -} // namespace RPC -} // namespace xrpl +} // namespace xrpl::RPC diff --git a/src/xrpld/app/misc/detail/Transaction.cpp b/src/xrpld/app/misc/detail/Transaction.cpp index f0cabf0fa6..758f560835 100644 --- a/src/xrpld/app/misc/detail/Transaction.cpp +++ b/src/xrpld/app/misc/detail/Transaction.cpp @@ -1,14 +1,34 @@ +#include + #include #include -#include #include +#include +#include +#include #include -#include +#include +#include #include +#include +#include +#include +#include +#include +#include #include #include -#include + +#include + +#include +#include +#include +#include +#include +#include +#include namespace xrpl { diff --git a/src/xrpld/app/misc/detail/TxQ.cpp b/src/xrpld/app/misc/detail/TxQ.cpp index 3494a4b7bd..eb3b7a54be 100644 --- a/src/xrpld/app/misc/detail/TxQ.cpp +++ b/src/xrpld/app/misc/detail/TxQ.cpp @@ -1,16 +1,53 @@ -#include -#include #include +#include +#include + +#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include -#include #include +#include + +#include #include +#include +#include +#include #include +#include +#include #include +#include +#include +#include +#include +#include +#include namespace xrpl { @@ -76,7 +113,7 @@ TxQ::FeeMetrics::update( std::for_each(txBegin, txEnd, [&](auto const& tx) { feeLevels.push_back(getFeeLevelPaid(view, *tx.first)); }); - std::sort(feeLevels.begin(), feeLevels.end()); + std::ranges::sort(feeLevels); XRPL_ASSERT(size == feeLevels.size(), "xrpl::TxQ::FeeMetrics::update : fee levels size"); JLOG((timeLeap ? j_.warn() : j_.debug())) @@ -102,7 +139,7 @@ TxQ::FeeMetrics::update( { recentTxnCounts_.push_back(mulDiv(size, 100 + setup.normalConsensusIncreasePercent, 100) .value_or(xrpl::muldiv_max)); - auto const iter = std::max_element(recentTxnCounts_.begin(), recentTxnCounts_.end()); + auto const iter = std::ranges::max_element(recentTxnCounts_); BOOST_ASSERT(iter != recentTxnCounts_.end()); auto const next = [&] { // Grow quickly: If the max_element is >= the @@ -1725,10 +1762,10 @@ TxQ::getTxRequiredFeeAndSeq(OpenView const& view, std::shared_ptr co std::uint32_t const accountSeq = sle ? (*sle)[sfSequence] : 0; std::uint32_t const availableSeq = nextQueuableSeqImpl(sle, lock).value(); return { - mulDiv(fee, baseFee, baseLevel) - .value_or(XRPAmount(std::numeric_limits::max())), - accountSeq, - availableSeq}; + .fee = mulDiv(fee, baseFee, baseLevel) + .value_or(XRPAmount(std::numeric_limits::max())), + .accountSeq = accountSeq, + .availableSeq = availableSeq}; } std::vector diff --git a/src/xrpld/app/misc/detail/ValidatorKeys.cpp b/src/xrpld/app/misc/detail/ValidatorKeys.cpp index 59ddc6d702..bd9c723acb 100644 --- a/src/xrpld/app/misc/detail/ValidatorKeys.cpp +++ b/src/xrpld/app/misc/detail/ValidatorKeys.cpp @@ -1,11 +1,19 @@ #include + #include #include #include #include +#include +#include +#include +#include +#include #include +#include + namespace xrpl { ValidatorKeys::ValidatorKeys(Config const& config, beast::Journal j) { diff --git a/src/xrpld/app/misc/detail/ValidatorList.cpp b/src/xrpld/app/misc/detail/ValidatorList.cpp index bed91afc44..a799ad4834 100644 --- a/src/xrpld/app/misc/detail/ValidatorList.cpp +++ b/src/xrpld/app/misc/detail/ValidatorList.cpp @@ -1,24 +1,61 @@ #include -#include +#include +#include +#include +#include + +#include #include +#include #include #include #include +#include +#include +#include +#include +#include #include +#include #include +#include #include #include +#include #include #include -#include +#include +#include #include -#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include #include +#include +#include +#include +#include namespace xrpl { @@ -340,7 +377,7 @@ ValidatorList::cacheValidatorFile(ValidatorList::lock_guard const& lock, PublicK boost::system::error_code ec; Json::Value value = buildFileData(strHex(pubKey), publisherLists_.at(pubKey), j_); - // rippled should be the only process writing to this file, so + // xrpld should be the only process writing to this file, so // if it ever needs to be read, it is not expected to change externally, so // delay the refresh as long as possible: 24 hours. (See also // `ValidatorSite::missingSite()`) @@ -786,10 +823,14 @@ ValidatorList::buildBlobInfos( { auto const& current = lists.current; auto const& remaining = lists.remaining; - blobInfos[current.sequence] = {current.rawBlob, current.rawSignature, current.rawManifest}; + blobInfos[current.sequence] = { + .blob = current.rawBlob, + .signature = current.rawSignature, + .manifest = current.rawManifest}; for (auto const& [sequence, vl] : remaining) { - blobInfos[sequence] = {vl.rawBlob, vl.rawSignature, vl.rawManifest}; + blobInfos[sequence] = { + .blob = vl.rawBlob, .signature = vl.rawSignature, .manifest = vl.rawManifest}; } } @@ -1199,7 +1240,7 @@ ValidatorList::applyList( } else { - publisherList.push_back(PublicKey(Slice{ret->data(), ret->size()})); + publisherList.emplace_back(Slice{ret->data(), ret->size()}); } if (val.isMember(jss::manifest) && val[jss::manifest].isString()) @@ -1208,7 +1249,7 @@ ValidatorList::applyList( } // Standardize the list order by sorting - std::sort(publisherList.begin(), publisherList.end()); + std::sort(publisherList.begin(), publisherList.end()); // NOLINT(modernize-use-ranges) } // If this publisher has ever sent a more updated version than the one // in this file, keep it. This scenario is unlikely, but legal. @@ -1710,7 +1751,7 @@ ValidatorList::for_each_available( if (plCollection.status != PublisherStatus::available) continue; XRPL_ASSERT( - plCollection.maxSequence != 0, + plCollection.maxSequence.value_or(0) != 0, "xrpl::ValidatorList::for_each_available : nonzero maxSequence"); func( plCollection.rawManifest, @@ -2058,9 +2099,8 @@ ValidatorList::negativeUNLFilter(std::vector>&& va if (!negativeUNL_.empty()) { ret.erase( - std::remove_if( - ret.begin(), - ret.end(), + std::ranges::remove_if( + ret, [&](auto const& v) -> bool { if (auto const masterKey = getTrustedKey(read_lock, v->getSignerPublic()); masterKey) @@ -2069,7 +2109,8 @@ ValidatorList::negativeUNLFilter(std::vector>&& va } return false; - }), + }) + .begin(), ret.end()); } diff --git a/src/xrpld/app/misc/detail/ValidatorSite.cpp b/src/xrpld/app/misc/detail/ValidatorSite.cpp index a4623e7acc..ca96e0fa37 100644 --- a/src/xrpld/app/misc/detail/ValidatorSite.cpp +++ b/src/xrpld/app/misc/detail/ValidatorSite.cpp @@ -1,14 +1,46 @@ -#include #include + +#include +#include +#include #include #include #include +#include +#include +#include +#include +#include +#include #include +#include #include #include +#include +#include +#include +#include +#include +#include +#include + #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace xrpl { @@ -190,9 +222,8 @@ ValidatorSite::setTimer( std::lock_guard const& site_lock, std::lock_guard const& state_lock) { - auto next = std::min_element(sites_.begin(), sites_.end(), [](Site const& a, Site const& b) { - return a.nextRefresh < b.nextRefresh; - }); + auto next = std::ranges::min_element( + sites_, [](Site const& a, Site const& b) { return a.nextRefresh < b.nextRefresh; }); if (next != sites_.end()) { @@ -405,7 +436,10 @@ ValidatorSite::parseJsonResponse( app_.getOPs()); sites_[siteIdx].lastRefreshStatus.emplace( - Site::Status{clock_type::now(), applyResult.bestDisposition(), ""}); + Site::Status{ + .refreshed = clock_type::now(), + .disposition = applyResult.bestDisposition(), + .message = ""}); for (auto const& [disp, count] : applyResult.dispositions) { @@ -517,7 +551,10 @@ ValidatorSite::onSiteFetch( << endpoint; auto onError = [&](std::string const& errMsg, bool retry) { sites_[siteIdx].lastRefreshStatus.emplace( - Site::Status{clock_type::now(), ListDisposition::invalid, errMsg}); + Site::Status{ + .refreshed = clock_type::now(), + .disposition = ListDisposition::invalid, + .message = errMsg}); if (retry) sites_[siteIdx].nextRefresh = clock_type::now() + error_retry_interval; @@ -610,7 +647,10 @@ ValidatorSite::onTextFetch( { JLOG(j_.error()) << "Exception in " << __func__ << ": " << ex.what(); sites_[siteIdx].lastRefreshStatus.emplace( - Site::Status{clock_type::now(), ListDisposition::invalid, ex.what()}); + Site::Status{ + .refreshed = clock_type::now(), + .disposition = ListDisposition::invalid, + .message = ex.what()}); } sites_[siteIdx].activeResource.reset(); } diff --git a/src/xrpld/app/misc/detail/Work.h b/src/xrpld/app/misc/detail/Work.h index 5261cf3bd7..27f4e7f6eb 100644 --- a/src/xrpld/app/misc/detail/Work.h +++ b/src/xrpld/app/misc/detail/Work.h @@ -3,9 +3,7 @@ #include #include -namespace xrpl { - -namespace detail { +namespace xrpl::detail { using response_type = boost::beast::http::response; @@ -21,6 +19,4 @@ public: cancel() = 0; }; -} // namespace detail - -} // namespace xrpl +} // namespace xrpl::detail diff --git a/src/xrpld/app/misc/detail/WorkBase.h b/src/xrpld/app/misc/detail/WorkBase.h index 35f04efe00..56b227613f 100644 --- a/src/xrpld/app/misc/detail/WorkBase.h +++ b/src/xrpld/app/misc/detail/WorkBase.h @@ -12,9 +12,9 @@ #include #include -namespace xrpl { +#include -namespace detail { +namespace xrpl::detail { template class WorkBase : public Work @@ -47,16 +47,18 @@ protected: endpoint_type lastEndpoint_; bool lastStatus_; -public: +private: WorkBase( - std::string const& host, - std::string const& path, - std::string const& port, + std::string host, + std::string path, + std::string port, boost::asio::io_context& ios, - endpoint_type const& lastEndpoint, + endpoint_type lastEndpoint, bool lastStatus, callback_type cb); - ~WorkBase(); + +public: + ~WorkBase() override; Impl& impl() @@ -91,28 +93,30 @@ public: private: void close(); + + friend Impl; }; //------------------------------------------------------------------------------ template WorkBase::WorkBase( - std::string const& host, - std::string const& path, - std::string const& port, + std::string host, + std::string path, + std::string port, boost::asio::io_context& ios, - endpoint_type const& lastEndpoint, + endpoint_type lastEndpoint, bool lastStatus, callback_type cb) - : host_(host) - , path_(path) - , port_(port) + : host_(std::move(host)) + , path_(std::move(path)) + , port_(std::move(port)) , cb_(std::move(cb)) , ios_(ios) , strand_(boost::asio::make_strand(ios)) , resolver_(ios) , socket_(ios) - , lastEndpoint_{lastEndpoint} + , lastEndpoint_{std::move(lastEndpoint)} , lastStatus_(lastStatus) { } @@ -130,10 +134,12 @@ void WorkBase::run() { if (!strand_.running_in_this_thread()) + { return boost::asio::post( ios_, boost::asio::bind_executor( strand_, std::bind(&WorkBase::run, impl().shared_from_this()))); + } resolver_.async_resolve( host_, @@ -268,6 +274,4 @@ WorkBase::close() } } -} // namespace detail - -} // namespace xrpl +} // namespace xrpl::detail diff --git a/src/xrpld/app/misc/detail/WorkFile.h b/src/xrpld/app/misc/detail/WorkFile.h index 5113ec5f3a..067dc4c38b 100644 --- a/src/xrpld/app/misc/detail/WorkFile.h +++ b/src/xrpld/app/misc/detail/WorkFile.h @@ -10,9 +10,9 @@ #include #include -namespace xrpl { +#include -namespace detail { +namespace xrpl::detail { // Work with files class WorkFile : public Work, public std::enable_shared_from_this @@ -26,8 +26,8 @@ public: using callback_type = std::function; public: - WorkFile(std::string const& path, boost::asio::io_context& ios, callback_type cb); - ~WorkFile(); + WorkFile(std::string path, boost::asio::io_context& ios, callback_type cb); + ~WorkFile() override; void run() override; @@ -44,8 +44,8 @@ private: //------------------------------------------------------------------------------ -inline WorkFile::WorkFile(std::string const& path, boost::asio::io_context& ios, callback_type cb) - : path_(path), cb_(std::move(cb)), ios_(ios), strand_(boost::asio::make_strand(ios)) +inline WorkFile::WorkFile(std::string path, boost::asio::io_context& ios, callback_type cb) + : path_(std::move(path)), cb_(std::move(cb)), ios_(ios), strand_(boost::asio::make_strand(ios)) { } @@ -59,9 +59,12 @@ inline void WorkFile::run() { if (!strand_.running_in_this_thread()) - return boost::asio::post( + { + boost::asio::post( ios_, boost::asio::bind_executor(strand_, std::bind(&WorkFile::run, shared_from_this()))); + return; + } error_code ec; auto const fileContents = getFileContents(ec, path_, megabytes(1)); @@ -77,6 +80,4 @@ WorkFile::cancel() // Nothing to do. Either it finished in run, or it didn't start. } -} // namespace detail - -} // namespace xrpl +} // namespace xrpl::detail diff --git a/src/xrpld/app/misc/detail/WorkPlain.h b/src/xrpld/app/misc/detail/WorkPlain.h index 361a7b4513..d3c0309e77 100644 --- a/src/xrpld/app/misc/detail/WorkPlain.h +++ b/src/xrpld/app/misc/detail/WorkPlain.h @@ -2,9 +2,7 @@ #include -namespace xrpl { - -namespace detail { +namespace xrpl::detail { // Work over TCP/IP class WorkPlain : public WorkBase, public std::enable_shared_from_this @@ -20,7 +18,7 @@ public: endpoint_type const& lastEndpoint, bool lastStatus, callback_type cb); - ~WorkPlain() = default; + ~WorkPlain() override = default; private: void @@ -51,11 +49,12 @@ inline void WorkPlain::onConnect(error_code const& ec) { if (ec) - return fail(ec); + { + fail(ec); + return; + } onStart(); } -} // namespace detail - -} // namespace xrpl +} // namespace xrpl::detail diff --git a/src/xrpld/app/misc/detail/WorkSSL.cpp b/src/xrpld/app/misc/detail/WorkSSL.cpp index 3ae0db1a96..0a8d53b1a2 100644 --- a/src/xrpld/app/misc/detail/WorkSSL.cpp +++ b/src/xrpld/app/misc/detail/WorkSSL.cpp @@ -1,7 +1,22 @@ #include -namespace xrpl { -namespace detail { +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace xrpl::detail { WorkSSL::WorkSSL( std::string const& host, @@ -55,6 +70,4 @@ WorkSSL::onHandshake(error_code const& ec) onStart(); } -} // namespace detail - -} // namespace xrpl +} // namespace xrpl::detail diff --git a/src/xrpld/app/misc/detail/WorkSSL.h b/src/xrpld/app/misc/detail/WorkSSL.h index b12c774f6e..74676bb7c1 100644 --- a/src/xrpld/app/misc/detail/WorkSSL.h +++ b/src/xrpld/app/misc/detail/WorkSSL.h @@ -11,9 +11,7 @@ #include -namespace xrpl { - -namespace detail { +namespace xrpl::detail { // Work over SSL class WorkSSL : public WorkBase, public std::enable_shared_from_this @@ -37,7 +35,7 @@ public: endpoint_type const& lastEndpoint, bool lastStatus, callback_type cb); - ~WorkSSL() = default; + ~WorkSSL() override = default; private: stream_type& @@ -53,6 +51,4 @@ private: onHandshake(error_code const& ec); }; -} // namespace detail - -} // namespace xrpl +} // namespace xrpl::detail diff --git a/src/xrpld/app/misc/detail/setup_HashRouter.cpp b/src/xrpld/app/misc/detail/setup_HashRouter.cpp index 0cc61f0730..57e2b9b282 100644 --- a/src/xrpld/app/misc/detail/setup_HashRouter.cpp +++ b/src/xrpld/app/misc/detail/setup_HashRouter.cpp @@ -1,7 +1,14 @@ #include + #include #include +#include +#include + +#include +#include +#include namespace xrpl { diff --git a/src/xrpld/app/rdb/README.md b/src/xrpld/app/rdb/README.md index a50bb395c1..53e6b0c6fa 100644 --- a/src/xrpld/app/rdb/README.md +++ b/src/xrpld/app/rdb/README.md @@ -2,7 +2,7 @@ The guiding principles of the Relational Database Interface are summarized below: -- All hard-coded SQL statements should be stored in the [files](#source-files) under the `xrpld/app/rdb` directory. With the exception of test modules, no hard-coded SQL should be added to any other file in rippled. +- All hard-coded SQL statements should be stored in the [files](#source-files) under the `xrpld/app/rdb` directory. With the exception of test modules, no hard-coded SQL should be added to any other file in xrpld. - The base class `RelationalDatabase` is inherited by derived classes that each provide an interface for operating on distinct relational database systems. ## Overview diff --git a/src/xrpld/app/rdb/backend/detail/Node.cpp b/src/xrpld/app/rdb/backend/detail/Node.cpp index b176588771..f1b5f4edc3 100644 --- a/src/xrpld/app/rdb/backend/detail/Node.cpp +++ b/src/xrpld/app/rdb/backend/detail/Node.cpp @@ -1,25 +1,71 @@ +#include + #include #include #include #include #include -#include +#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include -#include +#include +#include // IWYU pragma: keep #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include -#include +#include +#include +#include +#include -#include +#include +#include +#include +#include +#include +#include -namespace xrpl { -namespace detail { +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl::detail { /** * @brief to_string Returns the name of a table according to its TableType. @@ -91,15 +137,16 @@ makeLedgerDBs( { if (pk == 1) { - return {std::move(lgr), std::move(tx), false}; + return { + .ledgerDb = std::move(lgr), .transactionDb = std::move(tx), .valid = false}; } } } - return {std::move(lgr), std::move(tx), true}; + return {.ledgerDb = std::move(lgr), .transactionDb = std::move(tx), .valid = true}; } - return {std::move(lgr), {}, true}; + return {.ledgerDb = std::move(lgr), .transactionDb = {}, .valid = true}; } std::optional @@ -1092,7 +1139,8 @@ accountTxPage( else if (numberOfResults == 0) { newmarker = { - rangeCheckedCast(ledgerSeq.value_or(0)), txnSeq.value_or(0)}; + .ledgerSeq = rangeCheckedCast(ledgerSeq.value_or(0)), + .txnSeq = txnSeq.value_or(0)}; break; } @@ -1288,7 +1336,7 @@ dbHasSpace(soci::session& session, Config const& config, beast::Journal j) if (freeSpace < megabytes(512)) { JLOG(j.fatal()) << "Free SQLite space for transaction db is less than " - "512MB. To fix this, rippled must be executed with the " + "512MB. To fix this, xrpld must be executed with the " "vacuum parameter before restarting. " "Note that this activity can take multiple days, " "depending on database size."; @@ -1299,5 +1347,4 @@ dbHasSpace(soci::session& session, Config const& config, beast::Journal j) return true; } -} // namespace detail -} // namespace xrpl +} // namespace xrpl::detail diff --git a/src/xrpld/app/rdb/backend/detail/Node.h b/src/xrpld/app/rdb/backend/detail/Node.h index 5fbabeca47..7ea2992447 100644 --- a/src/xrpld/app/rdb/backend/detail/Node.h +++ b/src/xrpld/app/rdb/backend/detail/Node.h @@ -5,8 +5,7 @@ #include #include -namespace xrpl { -namespace detail { +namespace xrpl::detail { /* Need to change TableTypeCount if TableType is modified. */ enum class TableType { Ledgers, Transactions, AccountTransactions }; @@ -402,5 +401,4 @@ getTransaction( bool dbHasSpace(soci::session& session, Config const& config, beast::Journal j); -} // namespace detail -} // namespace xrpl +} // namespace xrpl::detail diff --git a/src/xrpld/app/rdb/backend/detail/SQLiteDatabase.cpp b/src/xrpld/app/rdb/backend/detail/SQLiteDatabase.cpp index 90d3b5a8e4..59f77142f8 100644 --- a/src/xrpld/app/rdb/backend/detail/SQLiteDatabase.cpp +++ b/src/xrpld/app/rdb/backend/detail/SQLiteDatabase.cpp @@ -1,13 +1,37 @@ -#include -#include -#include #include -#include -#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + namespace xrpl { bool @@ -170,7 +194,7 @@ SQLiteDatabase::getLedgerCountMinMax() return detail::getRowsMinMax(*db, detail::TableType::Ledgers); } - return {0, 0, 0}; + return {.numberOfRows = 0, .minLedgerSequence = 0, .maxLedgerSequence = 0}; } bool @@ -612,7 +636,10 @@ SQLiteDatabase::SQLiteDatabase(ServiceRegistry& registry, Config const& config, , j_(registry.getJournal("SQLiteDatabase")) { DatabaseCon::Setup const setup = setup_DatabaseCon(config, j_); - if (!makeLedgerDBs(config, setup, DatabaseCon::CheckpointerSetup{&jobQueue, registry_})) + if (!makeLedgerDBs( + config, + setup, + DatabaseCon::CheckpointerSetup{.jobQueue = &jobQueue, .registry = registry_})) { std::string_view constexpr error = "Failed to create ledger databases"; diff --git a/src/xrpld/app/rdb/detail/PeerFinder.cpp b/src/xrpld/app/rdb/detail/PeerFinder.cpp index a568461fb7..2f8d0355af 100644 --- a/src/xrpld/app/rdb/detail/PeerFinder.cpp +++ b/src/xrpld/app/rdb/detail/PeerFinder.cpp @@ -1,5 +1,27 @@ #include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + namespace xrpl { void diff --git a/src/xrpld/consensus/Consensus.cpp b/src/xrpld/consensus/Consensus.cpp index 9ad7e677ad..309813cf7e 100644 --- a/src/xrpld/consensus/Consensus.cpp +++ b/src/xrpld/consensus/Consensus.cpp @@ -1,6 +1,16 @@ #include +#include +#include + #include +#include + +#include +#include +#include +#include +#include namespace xrpl { diff --git a/src/xrpld/consensus/Consensus.h b/src/xrpld/consensus/Consensus.h index 142b1a01f0..4337e8da01 100644 --- a/src/xrpld/consensus/Consensus.h +++ b/src/xrpld/consensus/Consensus.h @@ -779,9 +779,13 @@ Consensus::peerProposalInternal( } if (peerPosIt != currPeerPositions_.end()) + { peerPosIt->second = newPeerPos; + } else + { currPeerPositions_.emplace(peerID, newPeerPos); + } } if (newPeerProp.isInitial()) @@ -803,7 +807,9 @@ Consensus::peerProposalInternal( // spawn a request for it and return nullopt/nullptr. It will call // gotTxSet once it arrives if (auto set = adaptor_.acquireTxSet(newPeerProp.position())) + { gotTxSet(now_, *set); + } else JLOG(j_.debug()) << "Don't have tx set for peer"; } @@ -843,9 +849,13 @@ Consensus::timerEntry( } if (phase_ == ConsensusPhase::open) + { phaseOpen(clog); + } else if (phase_ == ConsensusPhase::establish) + { phaseEstablish(clog); + } CLOG(clog) << "timerEntry finishing in phase " << to_string(phase_) << ". "; } @@ -932,7 +942,9 @@ Consensus::getJson(bool full) const ret["close_granularity"] = static_cast(closeResolution_.count()); } else + { ret["synched"] = false; + } ret["phase"] = to_string(phase_); @@ -1137,9 +1149,13 @@ Consensus::phaseOpen(std::unique_ptr const& clog) : prevCloseTime_; // use the time we saw internally if (now_ >= lastCloseTime) + { sinceClose = duration_cast(now_ - lastCloseTime); + } else + { sinceClose = -duration_cast(lastCloseTime - now_); + } CLOG(clog) << "calculating how long since last ledger's close time " "based on mode : " << to_string(mode) << ", previous closeAgree: " << closeAgree @@ -1201,7 +1217,7 @@ Consensus::shouldPause(std::unique_ptr const& clog) << "offline: " << offline << ", " << "quorum: " << quorum << ")"; - if (!ahead || !laggards || !totalValidators || !adaptor_.validator() || + if ((ahead == 0u) || (laggards == 0u) || (totalValidators == 0u) || !adaptor_.validator() || !adaptor_.haveValidated() || result_->roundTime.read() > parms.ledgerMAX_CONSENSUS) { j_.debug() << "not pausing (early)" << vars.str(); diff --git a/src/xrpld/consensus/ConsensusParms.h b/src/xrpld/consensus/ConsensusParms.h index 97fbb2c4a0..c8b79c4de9 100644 --- a/src/xrpld/consensus/ConsensusParms.h +++ b/src/xrpld/consensus/ConsensusParms.h @@ -121,14 +121,14 @@ struct ConsensusParms std::map const avalancheCutoffs{ // {state, {time, percent, nextState}}, // Initial state: 50% of nodes must vote yes - {init, {0, 50, mid}}, + {init, {.consensusTime = 0, .consensusPct = 50, .next = mid}}, // mid-consensus starts after 50% of the previous round time, and // requires 65% yes - {mid, {50, 65, late}}, + {mid, {.consensusTime = 50, .consensusPct = 65, .next = late}}, // late consensus starts after 85% time, and requires 70% yes - {late, {85, 70, stuck}}, + {late, {.consensusTime = 85, .consensusPct = 70, .next = stuck}}, // we're stuck after 2x time, requires 95% yes votes - {stuck, {200, 95, stuck}}, + {stuck, {.consensusTime = 200, .consensusPct = 95, .next = stuck}}, }; //! Percentage of nodes required to reach agreement on ledger close time diff --git a/src/xrpld/consensus/DisputedTx.h b/src/xrpld/consensus/DisputedTx.h index e8304f4242..2172fd5d47 100644 --- a/src/xrpld/consensus/DisputedTx.h +++ b/src/xrpld/consensus/DisputedTx.h @@ -8,6 +8,8 @@ #include +#include + namespace xrpl { /** A transaction discovered to be in dispute during consensus. @@ -38,8 +40,8 @@ public: @param numPeers Anticipated number of peer votes @param j Journal for debugging */ - DisputedTx(Tx_t const& tx, bool ourVote, std::size_t numPeers, beast::Journal j) - : ourVote_(ourVote), tx_(tx), j_(j) + DisputedTx(Tx_t tx, bool ourVote, std::size_t numPeers, beast::Journal j) + : ourVote_(ourVote), tx_(std::move(tx)), j_(j) { votes_.reserve(numPeers); } @@ -98,9 +100,11 @@ public: // Compute the percentage of nodes voting 'yes' (possibly including us) int const support = (yays_ + (proposing && ourVote_ ? 1 : 0)) * 100; int const total = nays_ + yays_ + (proposing ? 1 : 0); - if (!total) + if (total == 0) + { // There are no votes, so we know nothing return false; + } int const weight = support / total; // Returns true if the tx has more than minCONSENSUS_PCT (80) percent // agreement. Either voting for _or_ voting against the tx. @@ -210,7 +214,7 @@ DisputedTx::setVote(NodeID_t const& peer, bool votesYes) return true; } // changes vote to yes - else if (votesYes && !it->second) + if (votesYes && !it->second) { JLOG(j_.debug()) << "Peer " << peer << " now votes YES on " << tx_.id(); --nays_; @@ -219,7 +223,7 @@ DisputedTx::setVote(NodeID_t const& peer, bool votesYes) return true; } // changes vote to no - else if (!votesYes && it->second) + if (!votesYes && it->second) { JLOG(j_.debug()) << "Peer " << peer << " now votes NO on " << tx_.id(); ++nays_; @@ -240,9 +244,13 @@ DisputedTx::unVote(NodeID_t const& peer) if (it != votes_.end()) { if (it->second) + { --yays_; + } else + { --nays_; + } votes_.erase(it); } diff --git a/src/xrpld/consensus/LedgerTrie.h b/src/xrpld/consensus/LedgerTrie.h index aecd105c07..38ad0afbf9 100644 --- a/src/xrpld/consensus/LedgerTrie.h +++ b/src/xrpld/consensus/LedgerTrie.h @@ -10,6 +10,7 @@ #include #include #include +#include #include namespace xrpl { @@ -133,7 +134,7 @@ public: } private: - Span(Seq start, Seq end, Ledger const& l) : start_{start}, end_{end}, ledger_{l} + Span(Seq start, Seq end, Ledger l) : start_{start}, end_{end}, ledger_{std::move(l)} { // Spans cannot be empty XRPL_ASSERT(start < end, "xrpl::Span::Span : non-empty span input"); @@ -383,7 +384,7 @@ class LedgerTrie Node* findByLedgerID(Ledger const& ledger, Node* parent = nullptr) const { - if (!parent) + if (parent == nullptr) parent = root.get(); if (ledger.id() == parent->span.tip().id) return parent; @@ -513,7 +514,7 @@ public: { Node* loc = findByLedgerID(ledger); // Must be exact match with tip support - if (!loc || loc->tipSupport == 0) + if ((loc == nullptr) || loc->tipSupport == 0) return false; // found our node, remove it @@ -553,7 +554,9 @@ public: parent->erase(loc); } else + { break; + } loc = parent; } return true; @@ -582,7 +585,7 @@ public: branchSupport(Ledger const& ledger) const { Node const* loc = findByLedgerID(ledger); - if (!loc) + if (loc == nullptr) { Seq diffSeq; std::tie(loc, diffSeq) = find(ledger); @@ -692,8 +695,10 @@ public: uncommitted += uncommittedIt->second; uncommittedIt++; } - else // otherwise we jump to the end of the span + else + { // otherwise we jump to the end of the span nextSeq = curr->span.end(); + } } // We did not consume the entire span, so we have found the // preferred ledger @@ -736,9 +741,13 @@ public: // If the best child has margin exceeding the uncommitted support, // continue from that child, otherwise we are done if (best && ((margin > uncommitted) || (uncommitted == 0))) + { curr = best; - else // current is the best + } + else + { // current is the best done = true; + } } return curr->span.tip(); } @@ -785,7 +794,7 @@ public: { Node const* curr = nodes.top(); nodes.pop(); - if (!curr) + if (curr == nullptr) continue; // Node with 0 tip support must have multiple children diff --git a/src/xrpld/consensus/Validations.h b/src/xrpld/consensus/Validations.h index 4d0b64a350..00f10f89cf 100644 --- a/src/xrpld/consensus/Validations.h +++ b/src/xrpld/consensus/Validations.h @@ -369,7 +369,9 @@ private: it = acquiring_.erase(it); } else + { ++it; + } } } @@ -431,9 +433,13 @@ private: else { if (std::optional ledger = adaptor_.acquire(val.ledgerID())) + { updateTrie(lock, nodeID, *ledger); + } else + { acquiring_[valPair].insert(nodeID); + } } } @@ -654,7 +660,9 @@ public: updateTrie(lock, nodeID, val, old); } else + { return ValStatus::stale; + } } else if (val.trusted()) { @@ -917,9 +925,11 @@ public: // Use trie if ledger is the right one if (ledger.id() == ledgerID) + { return withTrie(lock, [&ledger](LedgerTrie& trie) { return trie.branchSupport(ledger) - trie.tipSupport(ledger); }); + } // Count parent ledgers as fallback return std::count_if(lastLedger_.begin(), lastLedger_.end(), [&ledgerID](auto const& it) { @@ -1028,9 +1038,13 @@ public: { std::optional loadFee = v.loadFee(); if (loadFee) + { res.push_back(*loadFee); + } else + { res.push_back(baseFee); + } } }); return res; diff --git a/src/xrpld/core/Config.h b/src/xrpld/core/Config.h index 78a35c6bc1..d4d8396ba5 100644 --- a/src/xrpld/core/Config.h +++ b/src/xrpld/core/Config.h @@ -273,10 +273,10 @@ public: // First, attempt to load the latest ledger directly from disk. bool FAST_LOAD = false; - // When starting rippled with existing database it do not know it has those + // When starting xrpld with existing database it do not know it has those // ledgers locally until the server naturally tries to backfill. This makes // is difficult to test some functionality (in particular performance - // testing sidechains). With this variable the user is able to force rippled + // testing sidechains). With this variable the user is able to force xrpld // to consider the ledger range to be present. It should be used for testing // only. std::optional> FORCED_LEDGER_RANGE_PRESENT; diff --git a/src/xrpld/core/TimeKeeper.h b/src/xrpld/core/TimeKeeper.h index 72c4468e1f..8f2bbbcd53 100644 --- a/src/xrpld/core/TimeKeeper.h +++ b/src/xrpld/core/TimeKeeper.h @@ -11,7 +11,7 @@ namespace xrpl { class TimeKeeper : public beast::abstract_clock { private: - std::atomic closeOffset_{}; + std::atomic closeOffset_; // Adjust system_clock::time_point for NetClock epoch static constexpr time_point @@ -22,7 +22,7 @@ private: } public: - virtual ~TimeKeeper() = default; + ~TimeKeeper() override = default; /** Returns the current time, using the server's clock. @@ -34,7 +34,7 @@ public: protocol, but it is possible for them to make an educated guess if this server publishes proposals or validations. - @note The network time is adjusted for the "Ripple epoch" which + @note The network time is adjusted for the "XRPL epoch" which was arbitrarily defined as 2000-01-01T00:00:00Z by Arthur Britto and David Schwartz during early development of the code. No rationale has been provided for this curious and diff --git a/src/xrpld/core/detail/Config.cpp b/src/xrpld/core/detail/Config.cpp index 204b29ad30..b7063287bb 100644 --- a/src/xrpld/core/detail/Config.cpp +++ b/src/xrpld/core/detail/Config.cpp @@ -1,33 +1,60 @@ #include + #include +#include #include #include #include +#include #include #include -#include +#include +#include #include #include #include +#include +#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include -#include +#include // IWYU pragma: keep +#include +#include +#include #include +#include +#include +#include #include #include #include +#include +#include +#include #include +#include +#include +#include #include +#include +#include +#include #if BOOST_OS_WINDOWS #include -namespace xrpl { -namespace detail { +namespace xrpl::detail { [[nodiscard]] std::uint64_t getMemorySize() @@ -38,15 +65,14 @@ getMemorySize() return 0; } -} // namespace detail -} // namespace xrpl +} // namespace xrpl::detail + #endif #if BOOST_OS_LINUX -#include +#include // IWYU pragma: keep -namespace xrpl { -namespace detail { +namespace xrpl::detail { [[nodiscard]] std::uint64_t getMemorySize() @@ -57,17 +83,14 @@ getMemorySize() return 0; } -} // namespace detail -} // namespace xrpl +} // namespace xrpl::detail #endif #if BOOST_OS_MACOS #include -#include -namespace xrpl { -namespace detail { +namespace xrpl::detail { [[nodiscard]] std::uint64_t getMemorySize() @@ -82,8 +105,8 @@ getMemorySize() return 0; } -} // namespace detail -} // namespace xrpl +} // namespace xrpl::detail + #endif namespace xrpl { @@ -262,10 +285,9 @@ Config::setupControl(bool bQuiet, bool bSilent, bool bStandalone) // First, check against 'minimum' RAM requirements per node size: auto const& threshold = sizedItems[std::underlying_type_t(SizedItem::ramSizeGB)]; - auto ns = std::find_if( - threshold.second.begin(), threshold.second.end(), [this](std::size_t limit) { - return (limit == 0) || (ramSize_ < limit); - }); + auto ns = std::ranges::find_if(threshold.second, [this](std::size_t limit) { + return (limit == 0) || (ramSize_ < limit); + }); XRPL_ASSERT(ns != threshold.second.end(), "xrpl::Config::setupControl : valid node size"); diff --git a/src/xrpld/core/detail/NetworkIDServiceImpl.cpp b/src/xrpld/core/detail/NetworkIDServiceImpl.cpp index 839eb0c464..38e72a4db6 100644 --- a/src/xrpld/core/detail/NetworkIDServiceImpl.cpp +++ b/src/xrpld/core/detail/NetworkIDServiceImpl.cpp @@ -1,6 +1,7 @@ -#include #include +#include + namespace xrpl { NetworkIDServiceImpl::NetworkIDServiceImpl(std::uint32_t networkID) : networkID_(networkID) diff --git a/src/xrpld/overlay/ClusterNode.h b/src/xrpld/overlay/ClusterNode.h index c6ab208383..3e319ef8be 100644 --- a/src/xrpld/overlay/ClusterNode.h +++ b/src/xrpld/overlay/ClusterNode.h @@ -5,6 +5,7 @@ #include #include +#include namespace xrpl { @@ -15,10 +16,10 @@ public: ClusterNode( PublicKey const& identity, - std::string const& name, + std::string name, std::uint32_t fee = 0, NetClock::time_point rtime = NetClock::time_point{}) - : identity_(identity), name_(name), mLoadFee(fee), mReportTime(rtime) + : identity_(identity), name_(std::move(name)), mLoadFee(fee), mReportTime(rtime) { } @@ -50,7 +51,7 @@ private: PublicKey const identity_; std::string name_; std::uint32_t mLoadFee = 0; - NetClock::time_point mReportTime = {}; + NetClock::time_point mReportTime; }; } // namespace xrpl diff --git a/src/xrpld/overlay/Compression.h b/src/xrpld/overlay/Compression.h index e9f530035a..cb60b4bb48 100644 --- a/src/xrpld/overlay/Compression.h +++ b/src/xrpld/overlay/Compression.h @@ -3,9 +3,7 @@ #include #include -namespace xrpl { - -namespace compression { +namespace xrpl::compression { std::size_t constexpr headerBytes = 6; std::size_t constexpr headerBytesCompressed = 10; @@ -36,20 +34,20 @@ decompress( try { if (algorithm == Algorithm::LZ4) + { return xrpl::compression_algorithms::lz4Decompress( in, inSize, decompressed, decompressedSize); - else - { - // LCOV_EXCL_START - JLOG(debugLog().warn()) - << "decompress: invalid compression algorithm " << static_cast(algorithm); - UNREACHABLE( - "xrpl::compression::decompress : invalid compression " - "algorithm"); - // LCOV_EXCL_STOP } + + // LCOV_EXCL_START + JLOG(debugLog().warn()) << "decompress: invalid compression algorithm " + << static_cast(algorithm); + UNREACHABLE( + "xrpl::compression::decompress : invalid compression " + "algorithm"); + // LCOV_EXCL_STOP } - catch (...) + catch (...) // NOLINT(bugprone-empty-catch) { } return 0; @@ -75,24 +73,22 @@ compress( try { if (algorithm == Algorithm::LZ4) + { return xrpl::compression_algorithms::lz4Compress( in, inSize, std::forward(bf)); - else - { - // LCOV_EXCL_START - JLOG(debugLog().warn()) - << "compress: invalid compression algorithm" << static_cast(algorithm); - UNREACHABLE( - "xrpl::compression::compress : invalid compression " - "algorithm"); - // LCOV_EXCL_STOP } + + // LCOV_EXCL_START + JLOG(debugLog().warn()) << "compress: invalid compression algorithm" + << static_cast(algorithm); + UNREACHABLE( + "xrpl::compression::compress : invalid compression " + "algorithm"); + // LCOV_EXCL_STOP } - catch (...) + catch (...) // NOLINT(bugprone-empty-catch) { } return 0; } -} // namespace compression - -} // namespace xrpl +} // namespace xrpl::compression diff --git a/src/xrpld/overlay/Overlay.h b/src/xrpld/overlay/Overlay.h index 7d2508a584..2c2371a1d1 100644 --- a/src/xrpld/overlay/Overlay.h +++ b/src/xrpld/overlay/Overlay.h @@ -14,13 +14,9 @@ #include #include -namespace boost { -namespace asio { -namespace ssl { +namespace boost::asio::ssl { class context; -} // namespace ssl -} // namespace asio -} // namespace boost +} // namespace boost::asio::ssl namespace xrpl { @@ -55,7 +51,7 @@ public: using PeerSequence = std::vector>; - virtual ~Overlay() = default; + ~Overlay() override = default; virtual void start() diff --git a/src/xrpld/overlay/README.md b/src/xrpld/overlay/README.md index 51eb96a001..bda0027ea7 100644 --- a/src/xrpld/overlay/README.md +++ b/src/xrpld/overlay/README.md @@ -3,11 +3,11 @@ ## Introduction The _XRP Ledger network_ consists of a collection of _peers_ running -**`rippled`** or other compatible software. Each peer maintains multiple +**`xrpld`** or other compatible software. Each peer maintains multiple outgoing connections and optional incoming connections to other peers. These connections are made over both the public Internet and private local area networks. This network defines a connected directed graph of nodes -where vertices are instances of `rippled` and edges are persistent TCP/IP +where vertices are instances of `xrpld` and edges are persistent TCP/IP connections. Peers send and receive messages to other connected peers. This peer to peer network, layered on top of the public and private Internet, forms an [_overlay network_][overlay_network]. The contents of the messages @@ -56,7 +56,7 @@ failed (e.g. by sending HTTP 400 "Bad Request" or HTTP 503 "Service Unavailable" ``` GET / HTTP/1.1 -User-Agent: rippled-1.4.0-b1+DEBUG +User-Agent: xrpld-1.4.0-b1+DEBUG Upgrade: RTXP/1.2, XRPL/2.0 Connection: Upgrade Connect-As: Peer @@ -77,7 +77,7 @@ HTTP/1.1 101 Switching Protocols Connection: Upgrade Upgrade: RTXP/1.2 Connect-As: Peer -Server: rippled-1.3.1 +Server: xrpld-1.3.1 Crawl: public Public-Key: n9K1ZXXXzzA3dtgKBuQUnZXkhygMRgZbSo3diFNPVHLMsUG5osJM Session-Signature: MEQCIHMlLGTcGyPvHji7WY2nRM2B0iSBnw9xeDUGW7bPq7IjAiAmy+ofEu+8nOq2eChRTr3wjoKi3EYRqLgzP+q+ORFcig== @@ -90,7 +90,7 @@ Previous-Ledger: EPvIpAD2iavGFyyZYi8REexAXyKGXsi1jMF7OIBY6/Y= ``` HTTP/1.1 503 Service Unavailable -Server: rippled-0.27.0 +Server: xrpld-0.27.0 Remote-Address: 63.104.209.13 Content-Length: 253 Content-Type: application/json @@ -354,9 +354,9 @@ transferred between A and B and will not be able to intelligently tamper with th message stream between Alice and Bob, although she may be still be able to inject delays or terminate the link. -# Ripple Clustering +# XRPL clustering -A cluster consists of more than one Ripple server under common +A cluster consists of more than one XRPL server under common administration that share load information, distribute cryptography operations, and provide greater response consistency. diff --git a/src/xrpld/overlay/ReduceRelayCommon.h b/src/xrpld/overlay/ReduceRelayCommon.h index 9ddde335e6..84e9cf9994 100644 --- a/src/xrpld/overlay/ReduceRelayCommon.h +++ b/src/xrpld/overlay/ReduceRelayCommon.h @@ -2,13 +2,11 @@ #include -namespace xrpl { - // Blog post explaining the rationale behind reduction of flooding gossip // protocol: // https://xrpl.org/blog/2021/message-routing-optimizations-pt-1-proposal-validation-relaying.html -namespace reduce_relay { +namespace xrpl::reduce_relay { // Peer's squelch is limited in time to // rand{MIN_UNSQUELCH_EXPIRE, max_squelch}, @@ -38,6 +36,4 @@ static constexpr auto WAIT_ON_BOOTUP = std::chrono::minutes{10}; // size limit of 64MB. static constexpr std::size_t MAX_TX_QUEUE_SIZE = 10000; -} // namespace reduce_relay - -} // namespace xrpl +} // namespace xrpl::reduce_relay diff --git a/src/xrpld/overlay/Slot.h b/src/xrpld/overlay/Slot.h index 22e908ee99..d7f3e9b4d3 100644 --- a/src/xrpld/overlay/Slot.h +++ b/src/xrpld/overlay/Slot.h @@ -19,9 +19,7 @@ #include #include -namespace xrpl { - -namespace reduce_relay { +namespace xrpl::reduce_relay { template class Slots; @@ -52,9 +50,7 @@ epoch(TP const& t) class SquelchHandler { public: - virtual ~SquelchHandler() - { - } + virtual ~SquelchHandler() = default; /** Squelch handler * @param validator Public key of the source validator * @param id Peer's id to squelch @@ -324,7 +320,7 @@ Slot::update( // idled peers. std::unordered_set selected; auto const consideredPoolSize = considered_.size(); - while (selected.size() != maxSelectedPeers_ && considered_.size() != 0) + while (selected.size() != maxSelectedPeers_ && !considered_.empty()) { auto i = considered_.size() == 1 ? 0 : rand_int(considered_.size() - 1); auto it = std::next(considered_.begin(), i); @@ -366,7 +362,9 @@ Slot::update( v.count = 0; if (selected.find(k) != selected.end()) + { v.state = PeerState::Selected; + } else if (v.state != PeerState::Squelched) { if (journal_.trace()) @@ -411,7 +409,7 @@ Slot::deletePeer(PublicKey const& validator, id_t id, bool erase) JLOG(journal_.trace()) << "deletePeer: " << Slice(validator) << " " << id << " selected " << (it->second.state == PeerState::Selected) << " considered " - << (considered_.find(id) != considered_.end()) << " erase " << erase; + << (considered_.contains(id)) << " erase " << erase; auto now = clock_type::now(); if (it->second.state == PeerState::Selected) { @@ -428,7 +426,7 @@ Slot::deletePeer(PublicKey const& validator, id_t id, bool erase) reachedThreshold_ = 0; state_ = SlotState::Counting; } - else if (considered_.find(id) != considered_.end()) + else if (considered_.contains(id)) { if (it->second.count > MAX_MESSAGE_THRESHOLD) --reachedThreshold_; @@ -490,8 +488,10 @@ Slot::getSelected() const { std::set r; for (auto const& [id, info] : peers_) + { if (info.state == PeerState::Selected) r.insert(id); + } return r; } @@ -504,6 +504,7 @@ Slot::getPeers() const unordered_map>(); for (auto const& [id, info] : peers_) + { r.emplace( std::make_pair( id, @@ -513,6 +514,7 @@ Slot::getPeers() const info.count, epoch(info.expire).count(), epoch(info.lastMessage).count())))); + } return r; } @@ -560,8 +562,10 @@ public: reduceRelayReady() { if (!reduceRelayReady_) + { reduceRelayReady_ = reduce_relay::epoch(clock_type::now()) > reduce_relay::WAIT_ON_BOOTUP; + } return reduceRelayReady_; } @@ -756,7 +760,9 @@ Slots::updateSlotAndSquelch( it->second.update(validator, id, type, callback); } else + { it->second.update(validator, id, type, callback); + } } template @@ -782,10 +788,10 @@ Slots::deleteIdlePeers() it = slots_.erase(it); } else + { ++it; + } } } -} // namespace reduce_relay - -} // namespace xrpl +} // namespace xrpl::reduce_relay diff --git a/src/xrpld/overlay/Squelch.h b/src/xrpld/overlay/Squelch.h index 1d92cfed3a..3f1d1b3192 100644 --- a/src/xrpld/overlay/Squelch.h +++ b/src/xrpld/overlay/Squelch.h @@ -2,16 +2,13 @@ #include +#include #include #include -#include #include -#include -namespace xrpl { - -namespace reduce_relay { +namespace xrpl::reduce_relay { /** Maintains squelching of relaying messages from validators */ template @@ -89,7 +86,7 @@ Squelch::expireSquelch(PublicKey const& validator) auto const& it = squelched_.find(validator); if (it == squelched_.end()) return true; - else if (it->second > now) + if (it->second > now) return false; // squelch expired @@ -98,6 +95,4 @@ Squelch::expireSquelch(PublicKey const& validator) return true; } -} // namespace reduce_relay - -} // namespace xrpl +} // namespace xrpl::reduce_relay diff --git a/src/xrpld/overlay/detail/Cluster.cpp b/src/xrpld/overlay/detail/Cluster.cpp index 72b7ef5147..c74a9aa5bd 100644 --- a/src/xrpld/overlay/detail/Cluster.cpp +++ b/src/xrpld/overlay/detail/Cluster.cpp @@ -1,13 +1,24 @@ -#include -#include #include + #include +#include #include #include +#include +#include +#include #include -#include +#include +#include + +#include +#include +#include +#include +#include +#include namespace xrpl { diff --git a/src/xrpld/overlay/detail/ConnectAttempt.cpp b/src/xrpld/overlay/detail/ConnectAttempt.cpp index 40466f19b9..a90400dd2d 100644 --- a/src/xrpld/overlay/detail/ConnectAttempt.cpp +++ b/src/xrpld/overlay/detail/ConnectAttempt.cpp @@ -1,18 +1,55 @@ -#include #include + +#include +#include +#include +#include #include #include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include #include +#include +#include namespace xrpl { ConnectAttempt::ConnectAttempt( Application& app, boost::asio::io_context& io_context, - endpoint_type const& remote_endpoint, + endpoint_type remote_endpoint, Resource::Consumer usage, shared_context const& context, std::uint32_t id, @@ -24,7 +61,7 @@ ConnectAttempt::ConnectAttempt( , id_(id) , sink_(journal, OverlayImpl::makePrefix(id)) , journal_(sink_) - , remote_endpoint_(remote_endpoint) + , remote_endpoint_(std::move(remote_endpoint)) , usage_(usage) , strand_(boost::asio::make_strand(io_context)) , timer_(io_context) diff --git a/src/xrpld/overlay/detail/ConnectAttempt.h b/src/xrpld/overlay/detail/ConnectAttempt.h index 520ebe277e..0626efd9c7 100644 --- a/src/xrpld/overlay/detail/ConnectAttempt.h +++ b/src/xrpld/overlay/detail/ConnectAttempt.h @@ -137,7 +137,7 @@ public: ConnectAttempt( Application& app, boost::asio::io_context& io_context, - endpoint_type const& remote_endpoint, + endpoint_type remote_endpoint, Resource::Consumer usage, shared_context const& context, Peer::id_t id, @@ -145,7 +145,7 @@ public: beast::Journal journal, OverlayImpl& overlay); - virtual ~ConnectAttempt(); + ~ConnectAttempt() override; /** * @brief Stop the connection attempt diff --git a/src/xrpld/overlay/detail/Handshake.cpp b/src/xrpld/overlay/detail/Handshake.cpp index f70ec864da..22e971dd6e 100644 --- a/src/xrpld/overlay/detail/Handshake.cpp +++ b/src/xrpld/overlay/detail/Handshake.cpp @@ -1,15 +1,46 @@ -#include -#include #include +#include +#include +#include + +#include +#include +#include #include +#include +#include #include +#include #include +#include +#include +#include +#include +#include +#include #include +#include -#include +#include +#include +#include +#include +#include +#include -#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include // VFALCO Shouldn't we have to include the OpenSSL // headers or something for SSL_get_finished? @@ -99,7 +130,7 @@ makeFeaturesResponseHeader( @note This construct is non-standard. There are potential "standard" alternatives that should be considered. For a discussion, on this topic, see https://github.com/openssl/openssl/issues/5509 and - https://github.com/ripple/rippled/issues/2413. + https://github.com/XRPLF/rippled/issues/2413. */ static std::optional> hashLastMessage(SSL const* ssl, size_t (*get)(const SSL*, void*, size_t)) diff --git a/src/xrpld/overlay/detail/Message.cpp b/src/xrpld/overlay/detail/Message.cpp index 1f0c6f608d..f8d3fbc8ff 100644 --- a/src/xrpld/overlay/detail/Message.cpp +++ b/src/xrpld/overlay/detail/Message.cpp @@ -1,7 +1,20 @@ #include + +#include #include +#include +#include + +#include + +#include + +#include #include +#include +#include +#include namespace xrpl { diff --git a/src/xrpld/overlay/detail/OverlayImpl.cpp b/src/xrpld/overlay/detail/OverlayImpl.cpp index 2720c10140..30fa2587e7 100644 --- a/src/xrpld/overlay/detail/OverlayImpl.cpp +++ b/src/xrpld/overlay/detail/OverlayImpl.cpp @@ -1,30 +1,93 @@ +#include + #include #include #include #include +#include #include +#include #include #include -#include +#include +#include #include -#include +#include +#include #include +#include +#include +#include +#include #include +#include +#include +#include #include #include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include #include -#include +#include +#include +#include +#include +#include +#include #include #include #include +#include #include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace xrpl { @@ -102,7 +165,7 @@ OverlayImpl::Timer::on_timer(error_code ec) OverlayImpl::OverlayImpl( Application& app, - Setup const& setup, + Setup setup, ServerHandler& serverHandler, Resource::Manager& resourceManager, Resolver& resolver, @@ -113,7 +176,7 @@ OverlayImpl::OverlayImpl( , io_context_(io_context) , work_(std::in_place, boost::asio::make_work_guard(io_context_)) , strand_(boost::asio::make_strand(io_context_)) - , setup_(setup) + , setup_(std::move(setup)) , journal_(app_.getJournal("Overlay")) , serverHandler_(serverHandler) , m_resourceManager(resourceManager) @@ -192,7 +255,7 @@ OverlayImpl::onHandoff( { auto const types = beast::rfc2616::split_commas(request["Connect-As"]); - if (std::find_if(types.begin(), types.end(), [](std::string const& s) { + if (std::ranges::find_if(types, [](std::string const& s) { return boost::iequals(s, "peer"); }) == types.end()) { @@ -461,16 +524,16 @@ OverlayImpl::start() if (bootstrapIps.empty()) { // Pool of servers operated by Ripple Labs Inc. - https://ripple.com - bootstrapIps.push_back("r.ripple.com 51235"); + bootstrapIps.emplace_back("r.ripple.com 51235"); // Pool of servers operated by ISRDC - https://isrdc.in - bootstrapIps.push_back("sahyadri.isrdc.in 51235"); + bootstrapIps.emplace_back("sahyadri.isrdc.in 51235"); // Pool of servers operated by @Xrpkuwait - https://xrpkuwait.com - bootstrapIps.push_back("hubs.xrpkuwait.com 51235"); + bootstrapIps.emplace_back("hubs.xrpkuwait.com 51235"); // Pool of servers operated by XRPL Commons - https://xrpl-commons.org - bootstrapIps.push_back("hub.xrpl-commons.org 51235"); + bootstrapIps.emplace_back("hub.xrpl-commons.org 51235"); } m_resolver.resolve( @@ -663,7 +726,7 @@ OverlayImpl::reportOutboundTraffic(TrafficCount::category cat, int size) } /** The number of active peers on the network Active peers are only those peers that have completed the handshake - and are running the Ripple protocol. + and are running the XRPL protocol. */ std::size_t OverlayImpl::size() const diff --git a/src/xrpld/overlay/detail/OverlayImpl.h b/src/xrpld/overlay/detail/OverlayImpl.h index 167d574188..0c4990a6f4 100644 --- a/src/xrpld/overlay/detail/OverlayImpl.h +++ b/src/xrpld/overlay/detail/OverlayImpl.h @@ -119,7 +119,7 @@ private: public: OverlayImpl( Application& app, - Setup const& setup, + Setup setup, ServerHandler& serverHandler, Resource::Manager& resourceManager, Resolver& resolver, diff --git a/src/xrpld/overlay/detail/PeerImp.cpp b/src/xrpld/overlay/detail/PeerImp.cpp index 13fe0c571c..c0f21e3a5d 100644 --- a/src/xrpld/overlay/detail/PeerImp.cpp +++ b/src/xrpld/overlay/detail/PeerImp.cpp @@ -1,3 +1,6 @@ +#include + +#include #include #include #include @@ -5,31 +8,96 @@ #include #include #include +#include #include -#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include #include #include +#include +#include #include #include +#include +#include +#include +#include #include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include +#include +#include +#include +#include +#include +#include +#include +#include #include #include +#include #include #include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include + +#include + +#include #include #include +#include +#include +#include +#include +#include #include #include #include +#include +#include #include +#include +#include +#include +#include +#include using namespace std::chrono_literals; @@ -77,7 +145,7 @@ PeerImp::PeerImp( , remote_address_(slot->remote_endpoint()) , overlay_(overlay) , inbound_(true) - , protocol_(protocol) + , protocol_(std::move(protocol)) , tracking_(Tracking::unknown) , trackingTime_(clock_type::now()) , publicKey_(publicKey) @@ -85,7 +153,7 @@ PeerImp::PeerImp( , creationTime_(clock_type::now()) , squelch_(app_.getJournal("Squelch")) , usage_(consumer) - , fee_{Resource::feeTrivialPeer, ""} + , fee_{.fee = Resource::feeTrivialPeer, .context = ""} , slot_(slot) , request_(std::move(request)) , headers_(request_) @@ -297,9 +365,8 @@ PeerImp::sendTxQueue() if (!txQueue_.empty()) { protocol::TMHaveTransactions ht; - std::for_each(txQueue_.begin(), txQueue_.end(), [&](auto const& hash) { - ht.add_hashes(hash.data(), hash.size()); - }); + std::ranges::for_each( + txQueue_, [&](auto const& hash) { ht.add_hashes(hash.data(), hash.size()); }); JLOG(p_journal_.trace()) << "sendTxQueue " << txQueue_.size(); txQueue_.clear(); send(std::make_shared(ht, protocol::mtHAVE_TRANSACTIONS)); @@ -514,7 +581,7 @@ PeerImp::hasLedger(uint256 const& hash, std::uint32_t seq) const if ((seq != 0) && (seq >= minLedger_) && (seq <= maxLedger_) && (tracking_.load() == Tracking::converged)) return true; - if (std::find(recentLedgers_.begin(), recentLedgers_.end(), hash) != recentLedgers_.end()) + if (std::ranges::find(recentLedgers_, hash) != recentLedgers_.end()) return true; } return false; @@ -533,7 +600,7 @@ bool PeerImp::hasTxSet(uint256 const& hash) const { std::lock_guard const sl(recentLock_); - return std::find(recentTxSets_.begin(), recentTxSets_.end(), hash) != recentTxSets_.end(); + return std::ranges::find(recentTxSets_, hash) != recentTxSets_.end(); } void @@ -1119,7 +1186,7 @@ PeerImp::onMessageBegin( { auto const name = protocolMessageName(type); load_event_ = app_.getJobQueue().makeLoadEvent(jtPEER, name); - fee_ = {Resource::feeTrivialPeer, name}; + fee_ = {.fee = Resource::feeTrivialPeer, .context = name}; auto const category = TrafficCount::categorize(*m, static_cast(type), true); @@ -2116,7 +2183,7 @@ PeerImp::onMessage(std::shared_ptr const& m) { std::lock_guard const sl(recentLock_); - if (std::find(recentTxSets_.begin(), recentTxSets_.end(), hash) != recentTxSets_.end()) + if (std::ranges::find(recentTxSets_, hash) != recentTxSets_.end()) { fee_.update(Resource::feeUselessData, "duplicate (tsHAVE)"); return; @@ -2781,7 +2848,7 @@ PeerImp::addLedger(uint256 const& hash, std::lock_guard const& locke // locked by the caller. (void)lockedRecentLock; - if (std::find(recentLedgers_.begin(), recentLedgers_.end(), hash) != recentLedgers_.end()) + if (std::ranges::find(recentLedgers_, hash) != recentLedgers_.end()) return; recentLedgers_.push_back(hash); diff --git a/src/xrpld/overlay/detail/PeerImp.h b/src/xrpld/overlay/detail/PeerImp.h index 61b8e1e758..f2cca1407a 100644 --- a/src/xrpld/overlay/detail/PeerImp.h +++ b/src/xrpld/overlay/detail/PeerImp.h @@ -23,6 +23,7 @@ #include #include #include +#include namespace xrpl { @@ -190,7 +191,7 @@ private: struct ChargeWithContext { Resource::Charge fee = Resource::feeTrivialPeer; - std::string context = {}; + std::string context{}; // NOLINT(readability-redundant-member-init) void update(Resource::Charge f, std::string const& add) @@ -313,7 +314,7 @@ public: id_t id, OverlayImpl& overlay); - virtual ~PeerImp(); + ~PeerImp() override; beast::Journal const& pJournal() const @@ -361,9 +362,8 @@ public: /** Send a set of PeerFinder endpoints as a protocol message. */ template < class FwdIt, - class = typename std::enable_if_t::value_type, - PeerFinder::Endpoint>::value>> + class = typename std::enable_if_t< + std::is_same_v::value_type, PeerFinder::Endpoint>>> void sendEndpoints(FwdIt first, FwdIt last); @@ -408,7 +408,7 @@ public: return publicKey_; } - /** Return the version of rippled that the peer is running, if reported. */ + /** Return the version of xrpld that the peer is running, if reported. */ std::string getVersion() const; @@ -826,7 +826,7 @@ PeerImp::PeerImp( , remote_address_(slot->remote_endpoint()) , overlay_(overlay) , inbound_(false) - , protocol_(protocol) + , protocol_(std::move(protocol)) , tracking_(Tracking::unknown) , trackingTime_(clock_type::now()) , publicKey_(publicKey) @@ -834,7 +834,7 @@ PeerImp::PeerImp( , creationTime_(clock_type::now()) , squelch_(app_.getJournal("Squelch")) , usage_(usage) - , fee_{Resource::feeTrivialPeer} + , fee_{.fee = Resource::feeTrivialPeer} , slot_(std::move(slot)) , response_(std::move(response)) , headers_(response_) diff --git a/src/xrpld/overlay/detail/PeerReservationTable.cpp b/src/xrpld/overlay/detail/PeerReservationTable.cpp index 8f90848954..f0e5f55a00 100644 --- a/src/xrpld/overlay/detail/PeerReservationTable.cpp +++ b/src/xrpld/overlay/detail/PeerReservationTable.cpp @@ -1,13 +1,15 @@ #include + #include #include #include -#include +#include #include #include #include #include +#include #include #include @@ -32,13 +34,13 @@ PeerReservationTable::list() const -> std::vector { std::lock_guard const lock(mutex_); list.reserve(table_.size()); - std::copy(table_.begin(), table_.end(), std::back_inserter(list)); + std::ranges::copy(table_, std::back_inserter(list)); } - std::sort(list.begin(), list.end()); + std::sort(list.begin(), list.end()); // NOLINT(modernize-use-ranges) return list; } -// See `ripple/app/main/DBInit.cpp` for the `CREATE TABLE` statement. +// See `include/xrpl/rdb/DBInit.h` for the `CREATE TABLE` statement. // It is unfortunate that we do not get to define a function for it. // We choose a `bool` return type to fit in with the error handling scheme @@ -98,7 +100,7 @@ PeerReservationTable::erase(PublicKey const& nodeId) std::lock_guard const lock(mutex_); - auto const it = table_.find({nodeId}); + auto const it = table_.find({.nodeId = nodeId}); if (it != table_.end()) { previous = *it; diff --git a/src/xrpld/overlay/detail/PeerSet.cpp b/src/xrpld/overlay/detail/PeerSet.cpp index 391fb6d3ca..8d3d79d358 100644 --- a/src/xrpld/overlay/detail/PeerSet.cpp +++ b/src/xrpld/overlay/detail/PeerSet.cpp @@ -1,8 +1,24 @@ -#include -#include #include -#include +#include +#include +#include +#include + +#include +#include + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include namespace xrpl { @@ -59,9 +75,8 @@ PeerSetImpl::addPeers( pairs.emplace_back(score, std::move(peer)); }); - std::sort(pairs.begin(), pairs.end(), [](ScoredPeer const& lhs, ScoredPeer const& rhs) { - return lhs.first > rhs.first; - }); + std::ranges::sort( + pairs, [](ScoredPeer const& lhs, ScoredPeer const& rhs) { return lhs.first > rhs.first; }); std::size_t accepted = 0; for (auto const& pair : pairs) @@ -108,7 +123,7 @@ public: { } - virtual std::unique_ptr + std::unique_ptr build() override { return std::make_unique(app_); diff --git a/src/xrpld/overlay/detail/ProtocolMessage.h b/src/xrpld/overlay/detail/ProtocolMessage.h index 41d42674ba..6b5b746ffd 100644 --- a/src/xrpld/overlay/detail/ProtocolMessage.h +++ b/src/xrpld/overlay/detail/ProtocolMessage.h @@ -233,7 +233,7 @@ parseMessageHeader(boost::system::error_code& ec, BufferSequence const& bufs, st template < class T, class Buffers, - class = std::enable_if_t::value>> + class = std::enable_if_t>> std::shared_ptr parseMessageContent(MessageHeader const& header, Buffers const& buffers) { @@ -258,7 +258,9 @@ parseMessageContent(MessageHeader const& header, Buffers const& buffers) return {}; } else if (!m->ParseFromZeroCopyStream(&stream)) + { return {}; + } return m; } @@ -267,7 +269,7 @@ template < class T, class Buffers, class Handler, - class = std::enable_if_t::value>> + class = std::enable_if_t>> bool invoke(MessageHeader const& header, Buffers const& buffers, Handler& handler) { diff --git a/src/xrpld/overlay/detail/ProtocolVersion.cpp b/src/xrpld/overlay/detail/ProtocolVersion.cpp index 1a55030cd4..c7416973ba 100644 --- a/src/xrpld/overlay/detail/ProtocolVersion.cpp +++ b/src/xrpld/overlay/detail/ProtocolVersion.cpp @@ -3,11 +3,19 @@ #include #include +#include #include -#include +#include +#include +#include #include +#include #include +#include +#include +#include +#include namespace xrpl { @@ -96,8 +104,9 @@ parseProtocolVersions(boost::beast::string_view const& value) } // We guarantee that the returned list is sorted and contains no duplicates: - std::sort(result.begin(), result.end()); - result.erase(std::unique(result.begin(), result.end()), result.end()); + std::ranges::sort(result); + auto const uniq = std::ranges::unique(result); + result.erase(uniq.begin(), uniq.end()); return result; } @@ -115,12 +124,8 @@ negotiateProtocolVersion(std::vector const& versions) std::function const pickVersion = [&result](ProtocolVersion const& v) { result = v; }; - std::set_intersection( - std::begin(versions), - std::end(versions), - std::begin(supportedProtocolList), - std::end(supportedProtocolList), - boost::make_function_output_iterator(pickVersion)); + std::ranges::set_intersection( + versions, supportedProtocolList, boost::make_function_output_iterator(pickVersion)); return result; } @@ -154,8 +159,7 @@ supportedProtocolVersions() bool isProtocolSupported(ProtocolVersion const& v) { - return std::end(supportedProtocolList) != - std::find(std::begin(supportedProtocolList), std::end(supportedProtocolList), v); + return std::end(supportedProtocolList) != std::ranges::find(supportedProtocolList, v); } } // namespace xrpl diff --git a/src/xrpld/overlay/detail/ProtocolVersion.h b/src/xrpld/overlay/detail/ProtocolVersion.h index 5b3a1b3302..c4fa7ced0b 100644 --- a/src/xrpld/overlay/detail/ProtocolVersion.h +++ b/src/xrpld/overlay/detail/ProtocolVersion.h @@ -17,7 +17,7 @@ namespace xrpl { * */ using ProtocolVersion = std::pair; -inline constexpr ProtocolVersion +constexpr ProtocolVersion make_protocol(std::uint16_t major, std::uint16_t minor) { return {major, minor}; diff --git a/src/xrpld/overlay/detail/TrafficCount.cpp b/src/xrpld/overlay/detail/TrafficCount.cpp index 2ce32b4468..3841be28b0 100644 --- a/src/xrpld/overlay/detail/TrafficCount.cpp +++ b/src/xrpld/overlay/detail/TrafficCount.cpp @@ -1,5 +1,11 @@ #include +#include + +#include + +#include + namespace xrpl { std::unordered_map const type_lookup = { diff --git a/src/xrpld/overlay/detail/TrafficCount.h b/src/xrpld/overlay/detail/TrafficCount.h index 930e92129d..bdc0729a51 100644 --- a/src/xrpld/overlay/detail/TrafficCount.h +++ b/src/xrpld/overlay/detail/TrafficCount.h @@ -56,7 +56,7 @@ public: operator bool() const { - return messagesIn || messagesOut; + return (messagesIn != 0u) || (messagesOut != 0u); } }; diff --git a/src/xrpld/overlay/detail/Tuning.h b/src/xrpld/overlay/detail/Tuning.h index bd62cd2a03..0471587fec 100644 --- a/src/xrpld/overlay/detail/Tuning.h +++ b/src/xrpld/overlay/detail/Tuning.h @@ -2,9 +2,7 @@ #include -namespace xrpl { - -namespace Tuning { +namespace xrpl::Tuning { enum { /** How many ledgers off a server can be and we will @@ -44,6 +42,4 @@ enum { /** Size of buffer used to read from the socket. */ std::size_t constexpr readBufferBytes = 16384; -} // namespace Tuning - -} // namespace xrpl +} // namespace xrpl::Tuning diff --git a/src/xrpld/overlay/detail/TxMetrics.cpp b/src/xrpld/overlay/detail/TxMetrics.cpp index ee0e42e5d6..0a44c719f4 100644 --- a/src/xrpld/overlay/detail/TxMetrics.cpp +++ b/src/xrpld/overlay/detail/TxMetrics.cpp @@ -1,12 +1,17 @@ #include +#include #include +#include + +#include +#include +#include #include +#include -namespace xrpl { - -namespace metrics { +namespace xrpl::metrics { void TxMetrics::addMetrics(protocol::MessageType type, std::uint32_t val) @@ -124,6 +129,4 @@ TxMetrics::json() const return ret; } -} // namespace metrics - -} // namespace xrpl +} // namespace xrpl::metrics diff --git a/src/xrpld/overlay/detail/TxMetrics.h b/src/xrpld/overlay/detail/TxMetrics.h index 37194c34e3..a5aea854cc 100644 --- a/src/xrpld/overlay/detail/TxMetrics.h +++ b/src/xrpld/overlay/detail/TxMetrics.h @@ -8,9 +8,7 @@ #include #include -namespace xrpl { - -namespace metrics { +namespace xrpl::metrics { /** Run single metrics rolling average. Can be either average of a value per second or average of a value's sample per second. For instance, @@ -111,6 +109,4 @@ struct TxMetrics json() const; }; -} // namespace metrics - -} // namespace xrpl +} // namespace xrpl::metrics diff --git a/src/xrpld/overlay/detail/ZeroCopyStream.h b/src/xrpld/overlay/detail/ZeroCopyStream.h index 6ce81edc54..034f69a8da 100644 --- a/src/xrpld/overlay/detail/ZeroCopyStream.h +++ b/src/xrpld/overlay/detail/ZeroCopyStream.h @@ -124,7 +124,7 @@ private: public: explicit ZeroCopyOutputStream(Streambuf& streambuf, std::size_t blockSize); - ~ZeroCopyOutputStream(); + ~ZeroCopyOutputStream() override; bool Next(void** data, int* size) override; diff --git a/src/xrpld/overlay/predicates.h b/src/xrpld/overlay/predicates.h index 3adecf7c6a..e0b4a626ec 100644 --- a/src/xrpld/overlay/predicates.h +++ b/src/xrpld/overlay/predicates.h @@ -101,10 +101,7 @@ struct match_peer bool operator()(std::shared_ptr const& peer) const { - if (matchPeer && (peer.get() == matchPeer)) - return true; - - return false; + return (matchPeer != nullptr) && (peer.get() == matchPeer); } }; @@ -146,10 +143,7 @@ struct peer_in_set bool operator()(std::shared_ptr const& peer) const { - if (peerSet.count(peer->id()) == 0) - return false; - - return true; + return peerSet.contains(peer->id()); } }; diff --git a/src/xrpld/peerfinder/PeerfinderManager.h b/src/xrpld/peerfinder/PeerfinderManager.h index 57fde8a569..1ceaebe04d 100644 --- a/src/xrpld/peerfinder/PeerfinderManager.h +++ b/src/xrpld/peerfinder/PeerfinderManager.h @@ -2,18 +2,16 @@ #include #include +#include #include #include #include -#include "xrpld/peerfinder/detail/Tuning.h" - #include -namespace xrpl { -namespace PeerFinder { +namespace xrpl::PeerFinder { using clock_type = beast::abstract_clock; @@ -103,7 +101,7 @@ struct Endpoint { Endpoint() = default; - Endpoint(beast::IP::Endpoint const& ep, std::uint32_t hops_); + Endpoint(beast::IP::Endpoint ep, std::uint32_t hops_); std::uint32_t hops = 0; beast::IP::Endpoint address; @@ -169,7 +167,7 @@ public: There may be some listener calls made before the destructor returns. */ - virtual ~Manager() = default; + ~Manager() override = default; /** Set the configuration for the manager. The new settings will be applied asynchronously. @@ -286,5 +284,4 @@ public: once_per_second() = 0; }; -} // namespace PeerFinder -} // namespace xrpl +} // namespace xrpl::PeerFinder diff --git a/src/xrpld/peerfinder/README.md b/src/xrpld/peerfinder/README.md index 806984035b..e6eda747ce 100644 --- a/src/xrpld/peerfinder/README.md +++ b/src/xrpld/peerfinder/README.md @@ -2,8 +2,8 @@ ## Introduction -The _Ripple payment network_ consists of a collection of _peers_ running the -**rippled software**. Each peer maintains multiple outgoing connections and +The _XRPL payment network_ consists of a collection of _peers_ running the +**xrpld software**. Each peer maintains multiple outgoing connections and optional incoming connections to other peers. These connections are made over both the public Internet and private local area networks. This network defines a fully connected directed graph of nodes. Peers send and receive messages to @@ -175,7 +175,7 @@ When choosing addresses from the boot cache for the purpose of establishing outgoing connections, addresses are ranked in decreasing order of valence. The Bootcache is persistent. Entries are periodically inserted and updated in the corresponding SQLite database during program operation. When -**rippled** is launched, the existing Bootcache database data is accessed and +**xrpld** is launched, the existing Bootcache database data is accessed and loaded to accelerate the bootstrap process. Desirable entries in the Bootcache are addresses for servers which are known to @@ -306,7 +306,7 @@ the address, and its retry timer has expired. The PeerFinder makes its best effort to become fully connected to the fixed addresses specified in the configuration file before moving on to establish -outgoing connections to foreign peers. This security feature helps rippled +outgoing connections to foreign peers. This security feature helps xrpld establish itself with a trusted set of peers first before accepting untrusted data from the network. diff --git a/src/xrpld/peerfinder/Slot.h b/src/xrpld/peerfinder/Slot.h index d0fce0626d..81249f54df 100644 --- a/src/xrpld/peerfinder/Slot.h +++ b/src/xrpld/peerfinder/Slot.h @@ -5,8 +5,7 @@ #include -namespace xrpl { -namespace PeerFinder { +namespace xrpl::PeerFinder { /** Properties and state associated with a peer to peer overlay connection. */ class Slot @@ -58,5 +57,4 @@ public: public_key() const = 0; }; -} // namespace PeerFinder -} // namespace xrpl +} // namespace xrpl::PeerFinder diff --git a/src/xrpld/peerfinder/detail/Bootcache.cpp b/src/xrpld/peerfinder/detail/Bootcache.cpp index 580ebe0c53..7b26bc8d9a 100644 --- a/src/xrpld/peerfinder/detail/Bootcache.cpp +++ b/src/xrpld/peerfinder/detail/Bootcache.cpp @@ -1,13 +1,22 @@ #include + +#include +#include #include #include #include +#include +#include +#include +#include #include +#include +#include +#include -namespace xrpl { -namespace PeerFinder { +namespace xrpl::PeerFinder { Bootcache::Bootcache(Store& store, clock_type& clock, beast::Journal journal) : m_store(store), m_clock(clock), m_journal(journal), m_whenUpdate(m_clock.now()) @@ -134,7 +143,7 @@ Bootcache::on_success(beast::IP::Endpoint const& endpoint) ++entry.valence(); m_map.erase(result.first); result = m_map.insert(value_type(endpoint, entry)); - XRPL_ASSERT(result.second, "ripple:PeerFinder::Bootcache::on_success : endpoint inserted"); + XRPL_ASSERT(result.second, "xrpl::PeerFinder::Bootcache::on_success : endpoint inserted"); } Entry const& entry(result.first->right); JLOG(m_journal.info()) << beast::leftw(18) << "Bootcache connect " << endpoint << " with " @@ -158,7 +167,7 @@ Bootcache::on_failure(beast::IP::Endpoint const& endpoint) --entry.valence(); m_map.erase(result.first); result = m_map.insert(value_type(endpoint, entry)); - XRPL_ASSERT(result.second, "ripple:PeerFinder::Bootcache::on_failure : endpoint inserted"); + XRPL_ASSERT(result.second, "xrpl::PeerFinder::Bootcache::on_failure : endpoint inserted"); } Entry const& entry(result.first->right); auto const n(std::abs(entry.valence())); @@ -252,5 +261,4 @@ Bootcache::flagForUpdate() checkUpdate(); } -} // namespace PeerFinder -} // namespace xrpl +} // namespace xrpl::PeerFinder diff --git a/src/xrpld/peerfinder/detail/Bootcache.h b/src/xrpld/peerfinder/detail/Bootcache.h index 9ab0a878e8..5405dd3432 100644 --- a/src/xrpld/peerfinder/detail/Bootcache.h +++ b/src/xrpld/peerfinder/detail/Bootcache.h @@ -12,8 +12,7 @@ #include #include -namespace xrpl { -namespace PeerFinder { +namespace xrpl::PeerFinder { /** Stores IP addresses useful for gaining initial connections. @@ -55,9 +54,7 @@ private: friend bool operator<(Entry const& lhs, Entry const& rhs) { - if (lhs.valence() > rhs.valence()) - return true; - return false; + return lhs.valence() > rhs.valence(); } private: @@ -171,5 +168,4 @@ private: flagForUpdate(); }; -} // namespace PeerFinder -} // namespace xrpl +} // namespace xrpl::PeerFinder diff --git a/src/xrpld/peerfinder/detail/Checker.h b/src/xrpld/peerfinder/detail/Checker.h index 21cc0f160e..3bd23f85db 100644 --- a/src/xrpld/peerfinder/detail/Checker.h +++ b/src/xrpld/peerfinder/detail/Checker.h @@ -10,8 +10,7 @@ #include #include -namespace xrpl { -namespace PeerFinder { +namespace xrpl::PeerFinder { /** Tests remote listening sockets to make sure they are connectable. */ template @@ -44,7 +43,10 @@ private: async_op(Checker& owner, boost::asio::io_context& io_context, Handler&& handler); - virtual ~async_op(); + ~async_op() override + { + checker_.remove(*this); + } void stop() override; @@ -113,13 +115,6 @@ Checker::async_op::async_op( { } -template -template -Checker::async_op::~async_op() -{ - checker_.remove(*this); -} - template template void @@ -198,5 +193,4 @@ Checker::remove(basic_async_op& op) cond_.notify_all(); } -} // namespace PeerFinder -} // namespace xrpl +} // namespace xrpl::PeerFinder diff --git a/src/xrpld/peerfinder/detail/Counts.h b/src/xrpld/peerfinder/detail/Counts.h index 6c6a335dda..b3f8241776 100644 --- a/src/xrpld/peerfinder/detail/Counts.h +++ b/src/xrpld/peerfinder/detail/Counts.h @@ -6,8 +6,7 @@ #include -namespace xrpl { -namespace PeerFinder { +namespace xrpl::PeerFinder { /** Direction of a slot count adjustment. */ enum class CountAdjustment : int { Decrement = -1, Increment = 1 }; @@ -182,15 +181,12 @@ public: // // Fixed peers do not count towards the active outgoing total. - if (m_out_max > 0) - return false; - - return true; + return m_out_max <= 0; } /** Output statistics. */ void - onWrite(beast::PropertyStream::Map& map) + onWrite(beast::PropertyStream::Map& map) const { map["accept"] = acceptCount(); map["connect"] = connectCount(); @@ -324,5 +320,4 @@ private: int m_closingCount{0}; }; -} // namespace PeerFinder -} // namespace xrpl +} // namespace xrpl::PeerFinder diff --git a/src/xrpld/peerfinder/detail/Endpoint.cpp b/src/xrpld/peerfinder/detail/Endpoint.cpp index 46f4f28b88..5d58e6c9bb 100644 --- a/src/xrpld/peerfinder/detail/Endpoint.cpp +++ b/src/xrpld/peerfinder/detail/Endpoint.cpp @@ -1,13 +1,17 @@ #include #include -namespace xrpl { -namespace PeerFinder { +#include -Endpoint::Endpoint(beast::IP::Endpoint const& ep, std::uint32_t hops_) - : hops(std::min(hops_, Tuning::maxHops + 1)), address(ep) +#include +#include +#include + +namespace xrpl::PeerFinder { + +Endpoint::Endpoint(beast::IP::Endpoint ep, std::uint32_t hops_) + : hops(std::min(hops_, Tuning::maxHops + 1)), address(std::move(ep)) { } -} // namespace PeerFinder -} // namespace xrpl +} // namespace xrpl::PeerFinder diff --git a/src/xrpld/peerfinder/detail/Fixed.h b/src/xrpld/peerfinder/detail/Fixed.h index 8b67347e6a..b898c6ce3f 100644 --- a/src/xrpld/peerfinder/detail/Fixed.h +++ b/src/xrpld/peerfinder/detail/Fixed.h @@ -2,8 +2,7 @@ #include -namespace xrpl { -namespace PeerFinder { +namespace xrpl::PeerFinder { /** Metadata for a Fixed slot. */ class Fixed @@ -43,5 +42,4 @@ private: std::size_t m_failures{0}; }; -} // namespace PeerFinder -} // namespace xrpl +} // namespace xrpl::PeerFinder diff --git a/src/xrpld/peerfinder/detail/Handouts.h b/src/xrpld/peerfinder/detail/Handouts.h index 77e2e31ed9..0d5eae7ef7 100644 --- a/src/xrpld/peerfinder/detail/Handouts.h +++ b/src/xrpld/peerfinder/detail/Handouts.h @@ -6,8 +6,9 @@ #include #include -namespace xrpl { -namespace PeerFinder { +#include + +namespace xrpl::PeerFinder { namespace detail { @@ -78,7 +79,7 @@ class RedirectHandouts { public: template - explicit RedirectHandouts(SlotImp::ptr const& slot); + explicit RedirectHandouts(SlotImp::ptr slot); template bool @@ -114,7 +115,7 @@ private: }; template -RedirectHandouts::RedirectHandouts(SlotImp::ptr const& slot) : slot_(slot) +RedirectHandouts::RedirectHandouts(SlotImp::ptr slot) : slot_(std::move(slot)) { list_.reserve(Tuning::redirectEndpointCount); } @@ -162,7 +163,7 @@ class SlotHandouts { public: template - explicit SlotHandouts(SlotImp::ptr const& slot); + explicit SlotHandouts(SlotImp::ptr slot); template bool @@ -198,7 +199,7 @@ private: }; template -SlotHandouts::SlotHandouts(SlotImp::ptr const& slot) : slot_(slot) +SlotHandouts::SlotHandouts(SlotImp::ptr slot) : slot_(std::move(slot)) { list_.reserve(Tuning::numberOfEndpoints); } @@ -329,5 +330,4 @@ ConnectHandouts::try_insert(beast::IP::Endpoint const& endpoint) return true; } -} // namespace PeerFinder -} // namespace xrpl +} // namespace xrpl::PeerFinder diff --git a/src/xrpld/peerfinder/detail/Livecache.h b/src/xrpld/peerfinder/detail/Livecache.h index ac435e1e24..5c5ed577af 100644 --- a/src/xrpld/peerfinder/detail/Livecache.h +++ b/src/xrpld/peerfinder/detail/Livecache.h @@ -13,9 +13,9 @@ #include #include +#include -namespace xrpl { -namespace PeerFinder { +namespace xrpl::PeerFinder { template class Livecache; @@ -30,7 +30,7 @@ public: protected: struct Element : boost::intrusive::list_base_hook<> { - Element(Endpoint const& endpoint_) : endpoint(endpoint_) + Element(Endpoint endpoint_) : endpoint(std::move(endpoint_)) { } @@ -409,7 +409,7 @@ Livecache::insert(Endpoint const& ep) << " at hops " << ep.hops; return; } - else if (!result.second && (ep.hops > e.endpoint.hops)) + if (!result.second && (ep.hops > e.endpoint.hops)) { // Drop duplicates at higher hops std::size_t const excess(ep.hops - e.endpoint.hops); @@ -465,7 +465,7 @@ Livecache::hops_t::shuffle() { std::vector> v; v.reserve(list.size()); - std::copy(list.begin(), list.end(), std::back_inserter(v)); + std::ranges::copy(list, std::back_inserter(v)); std::shuffle(v.begin(), v.end(), default_prng()); list.clear(); for (auto& e : v) @@ -490,7 +490,7 @@ Livecache::hops_t::histogram() const template Livecache::hops_t::hops_t(Allocator const& alloc) { - std::fill(m_hist.begin(), m_hist.end(), 0); + std::ranges::fill(m_hist, 0); } template @@ -532,5 +532,4 @@ Livecache::hops_t::remove(Element& e) list.erase(list.iterator_to(e)); } -} // namespace PeerFinder -} // namespace xrpl +} // namespace xrpl::PeerFinder diff --git a/src/xrpld/peerfinder/detail/Logic.h b/src/xrpld/peerfinder/detail/Logic.h index ad0a2f8b96..9e2cbedc64 100644 --- a/src/xrpld/peerfinder/detail/Logic.h +++ b/src/xrpld/peerfinder/detail/Logic.h @@ -23,8 +23,7 @@ #include #include -namespace xrpl { -namespace PeerFinder { +namespace xrpl::PeerFinder { /** The Logic for maintaining the list of Slot addresses. We keep this in a separate class so it can be instantiated @@ -254,7 +253,7 @@ public: } // Check for duplicate connection - if (slots_.find(remote_endpoint) != slots_.end()) + if (slots_.contains(remote_endpoint)) { JLOG(m_journal.debug()) << beast::leftw(18) << "Logic dropping " << remote_endpoint << " as duplicate incoming"; @@ -290,7 +289,7 @@ public: std::lock_guard const _(lock_); // Check for duplicate connection - if (slots_.find(remote_endpoint) != slots_.end()) + if (slots_.contains(remote_endpoint)) { JLOG(m_journal.debug()) << beast::leftw(18) << "Logic dropping " << remote_endpoint << " as duplicate connect"; @@ -377,7 +376,7 @@ public: "xrpl::PeerFinder::Logic::activate : valid slot state"); // Check for duplicate connection by key - if (keys_.find(key) != keys_.end()) + if (keys_.contains(key)) return Result::duplicatePeer; // If the peer belongs to a cluster or is reserved, @@ -418,9 +417,11 @@ public: { auto iter(fixed_.find(slot->remote_endpoint())); if (iter == fixed_.end()) + { LogicError( "PeerFinder::Logic::activate(): remote_endpoint " "missing from fixed_"); + } iter->second.success(m_clock.now()); JLOG(journal.trace()) << "Logic fixed success"; @@ -516,7 +517,7 @@ public: << ((h.list().size() > 1) ? "endpoints" : "endpoint"); return h.list(); } - else if (counts_.attempts() > 0) + if (counts_.attempts() > 0) { JLOG(m_journal.debug()) << beast::leftw(18) << "Logic waiting on " << counts_.attempts() << " attempts"; @@ -627,7 +628,7 @@ public: beast::Journal const journal{sink}; JLOG(journal.trace()) << "Logic sending " << list.size() << ((list.size() == 1) ? " endpoint" : " endpoints"); - result.push_back(std::make_pair(slot, list)); + result.emplace_back(slot, list); } m_whenBroadcast = now + Tuning::secondsPerMessage; @@ -825,9 +826,11 @@ public: auto const iter = slots_.find(slot->remote_endpoint()); // The slot must exist in the table if (iter == slots_.end()) + { LogicError( "PeerFinder::Logic::remove(): remote_endpoint " "missing from slots_"); + } // Remove from slot by IP table slots_.erase(iter); @@ -838,9 +841,11 @@ public: auto const iter = keys_.find(*slot->public_key()); // Key must exist if (iter == keys_.end()) + { LogicError( "PeerFinder::Logic::remove(): public_key missing " "from keys_"); + } keys_.erase(iter); } @@ -849,9 +854,11 @@ public: auto const iter(connectedAddresses_.find(slot->remote_endpoint().address())); // Address must exist if (iter == connectedAddresses_.end()) + { LogicError( "PeerFinder::Logic::remove(): remote_endpoint " "address missing from connectedAddresses_"); + } connectedAddresses_.erase(iter); } @@ -875,9 +882,11 @@ public: { auto iter(fixed_.find(slot->remote_endpoint())); if (iter == fixed_.end()) + { LogicError( "PeerFinder::Logic::on_closed(): remote_endpoint " "missing from fixed_"); + } iter->second.failure(m_clock.now()); JLOG(journal.debug()) << "Logic fixed failed"; @@ -939,8 +948,10 @@ public: fixed(beast::IP::Endpoint const& endpoint) const { for (auto const& entry : fixed_) + { if (entry.first == endpoint) return true; + } return false; } @@ -951,8 +962,10 @@ public: fixed(beast::IP::Address const& address) const { for (auto const& entry : fixed_) + { if (entry.first.address() == address) return true; + } return false; } @@ -1199,5 +1212,4 @@ Logic::onRedirects( } } -} // namespace PeerFinder -} // namespace xrpl +} // namespace xrpl::PeerFinder diff --git a/src/xrpld/peerfinder/detail/PeerfinderConfig.cpp b/src/xrpld/peerfinder/detail/PeerfinderConfig.cpp index 6a158fbbab..1c57f5187e 100644 --- a/src/xrpld/peerfinder/detail/PeerfinderConfig.cpp +++ b/src/xrpld/peerfinder/detail/PeerfinderConfig.cpp @@ -1,10 +1,14 @@ +#include #include #include -#include +#include -namespace xrpl { -namespace PeerFinder { +#include +#include +#include + +namespace xrpl::PeerFinder { Config::Config() : outPeers(calcOutPeers()) @@ -123,5 +127,4 @@ Config::makeConfig( return config; } -} // namespace PeerFinder -} // namespace xrpl +} // namespace xrpl::PeerFinder diff --git a/src/xrpld/peerfinder/detail/PeerfinderManager.cpp b/src/xrpld/peerfinder/detail/PeerfinderManager.cpp index e9c42b7eb5..2858586120 100644 --- a/src/xrpld/peerfinder/detail/PeerfinderManager.cpp +++ b/src/xrpld/peerfinder/detail/PeerfinderManager.cpp @@ -1,17 +1,34 @@ #include + +#include #include #include +#include #include #include +#include +#include +#include +#include +#include +#include +#include +#include + #include #include +#include +#include #include +#include #include +#include +#include +#include -namespace xrpl { -namespace PeerFinder { +namespace xrpl::PeerFinder { class ManagerImp : public Manager { @@ -249,5 +266,4 @@ make_Manager( return std::make_unique(io_context, clock, journal, config, collector); } -} // namespace PeerFinder -} // namespace xrpl +} // namespace xrpl::PeerFinder diff --git a/src/xrpld/peerfinder/detail/SlotImp.cpp b/src/xrpld/peerfinder/detail/SlotImp.cpp index 07156c6a87..f763a154aa 100644 --- a/src/xrpld/peerfinder/detail/SlotImp.cpp +++ b/src/xrpld/peerfinder/detail/SlotImp.cpp @@ -1,13 +1,21 @@ -#include #include + +#include +#include #include -namespace xrpl { -namespace PeerFinder { +#include +#include +#include + +#include +#include + +namespace xrpl::PeerFinder { SlotImp::SlotImp( beast::IP::Endpoint const& local_endpoint, - beast::IP::Endpoint const& remote_endpoint, + beast::IP::Endpoint remote_endpoint, bool fixed, clock_type& clock) : recent(clock) @@ -15,7 +23,7 @@ SlotImp::SlotImp( , m_fixed(fixed) , m_reserved(false) , m_state(accept) - , m_remote_endpoint(remote_endpoint) + , m_remote_endpoint(std::move(remote_endpoint)) , m_local_endpoint(local_endpoint) , m_listening_port(unknownPort) , checked(false) @@ -24,13 +32,13 @@ SlotImp::SlotImp( { } -SlotImp::SlotImp(beast::IP::Endpoint const& remote_endpoint, bool fixed, clock_type& clock) +SlotImp::SlotImp(beast::IP::Endpoint remote_endpoint, bool fixed, clock_type& clock) : recent(clock) , m_inbound(false) , m_fixed(fixed) , m_reserved(false) , m_state(connect) - , m_remote_endpoint(remote_endpoint) + , m_remote_endpoint(std::move(remote_endpoint)) , m_listening_port(unknownPort) , checked(true) , canAccept(true) @@ -125,5 +133,4 @@ SlotImp::recent_t::expire() beast::expire(cache, Tuning::liveCacheSecondsToLive); } -} // namespace PeerFinder -} // namespace xrpl +} // namespace xrpl::PeerFinder diff --git a/src/xrpld/peerfinder/detail/SlotImp.h b/src/xrpld/peerfinder/detail/SlotImp.h index f18410d214..a94473be87 100644 --- a/src/xrpld/peerfinder/detail/SlotImp.h +++ b/src/xrpld/peerfinder/detail/SlotImp.h @@ -8,8 +8,7 @@ #include #include -namespace xrpl { -namespace PeerFinder { +namespace xrpl::PeerFinder { class SlotImp : public Slot { @@ -19,12 +18,12 @@ public: // inbound SlotImp( beast::IP::Endpoint const& local_endpoint, - beast::IP::Endpoint const& remote_endpoint, + beast::IP::Endpoint remote_endpoint, bool fixed, clock_type& clock); // outbound - SlotImp(beast::IP::Endpoint const& remote_endpoint, bool fixed, clock_type& clock); + SlotImp(beast::IP::Endpoint remote_endpoint, bool fixed, clock_type& clock); bool inbound() const override @@ -190,5 +189,4 @@ public: clock_type::time_point whenAcceptEndpoints; }; -} // namespace PeerFinder -} // namespace xrpl +} // namespace xrpl::PeerFinder diff --git a/src/xrpld/peerfinder/detail/Source.h b/src/xrpld/peerfinder/detail/Source.h index 8fc97b639f..c86176911e 100644 --- a/src/xrpld/peerfinder/detail/Source.h +++ b/src/xrpld/peerfinder/detail/Source.h @@ -4,8 +4,7 @@ #include -namespace xrpl { -namespace PeerFinder { +namespace xrpl::PeerFinder { /** A static or dynamic source of peer addresses. These are used as fallbacks when we are bootstrapping and don't have @@ -30,9 +29,7 @@ public: IPAddresses addresses; }; - virtual ~Source() - { - } + virtual ~Source() = default; virtual std::string const& name() = 0; virtual void @@ -43,5 +40,4 @@ public: fetch(Results& results, beast::Journal journal) = 0; }; -} // namespace PeerFinder -} // namespace xrpl +} // namespace xrpl::PeerFinder diff --git a/src/xrpld/peerfinder/detail/SourceStrings.cpp b/src/xrpld/peerfinder/detail/SourceStrings.cpp index eb2e62fc09..4f6d58450c 100644 --- a/src/xrpld/peerfinder/detail/SourceStrings.cpp +++ b/src/xrpld/peerfinder/detail/SourceStrings.cpp @@ -1,17 +1,25 @@ #include -namespace xrpl { -namespace PeerFinder { +#include + +#include +#include + +#include +#include +#include + +namespace xrpl::PeerFinder { class SourceStringsImp : public SourceStrings { public: - SourceStringsImp(std::string const& name, Strings const& strings) - : m_name(name), m_strings(strings) + SourceStringsImp(std::string name, Strings strings) + : m_name(std::move(name)), m_strings(std::move(strings)) { } - ~SourceStringsImp() = default; + ~SourceStringsImp() override = default; std::string const& name() override @@ -47,5 +55,4 @@ SourceStrings::New(std::string const& name, Strings const& strings) return std::make_shared(name, strings); } -} // namespace PeerFinder -} // namespace xrpl +} // namespace xrpl::PeerFinder diff --git a/src/xrpld/peerfinder/detail/SourceStrings.h b/src/xrpld/peerfinder/detail/SourceStrings.h index 1cf6d81040..11b2b927ee 100644 --- a/src/xrpld/peerfinder/detail/SourceStrings.h +++ b/src/xrpld/peerfinder/detail/SourceStrings.h @@ -4,8 +4,7 @@ #include -namespace xrpl { -namespace PeerFinder { +namespace xrpl::PeerFinder { /** Provides addresses from a static set of strings. */ class SourceStrings : public Source @@ -19,5 +18,4 @@ public: New(std::string const& name, Strings const& strings); }; -} // namespace PeerFinder -} // namespace xrpl +} // namespace xrpl::PeerFinder diff --git a/src/xrpld/peerfinder/detail/Store.h b/src/xrpld/peerfinder/detail/Store.h index 390f80800a..347fc09b15 100644 --- a/src/xrpld/peerfinder/detail/Store.h +++ b/src/xrpld/peerfinder/detail/Store.h @@ -1,15 +1,12 @@ #pragma once -namespace xrpl { -namespace PeerFinder { +namespace xrpl::PeerFinder { /** Abstract persistence for PeerFinder data. */ class Store { public: - virtual ~Store() - { - } + virtual ~Store() = default; // load the bootstrap cache using load_callback = std::function; @@ -28,5 +25,4 @@ public: save(std::vector const& v) = 0; }; -} // namespace PeerFinder -} // namespace xrpl +} // namespace xrpl::PeerFinder diff --git a/src/xrpld/peerfinder/detail/StoreSqdb.h b/src/xrpld/peerfinder/detail/StoreSqdb.h index 47b8a6825c..626a82c932 100644 --- a/src/xrpld/peerfinder/detail/StoreSqdb.h +++ b/src/xrpld/peerfinder/detail/StoreSqdb.h @@ -5,8 +5,7 @@ #include -namespace xrpl { -namespace PeerFinder { +namespace xrpl::PeerFinder { /** Database persistence for PeerFinder using SQLite */ class StoreSqdb : public Store @@ -26,9 +25,7 @@ public: { } - ~StoreSqdb() - { - } + ~StoreSqdb() override = default; void open(BasicConfig const& config) @@ -85,5 +82,4 @@ private: } }; -} // namespace PeerFinder -} // namespace xrpl +} // namespace xrpl::PeerFinder diff --git a/src/xrpld/peerfinder/detail/Tuning.h b/src/xrpld/peerfinder/detail/Tuning.h index d572c7b5e3..23a96a7374 100644 --- a/src/xrpld/peerfinder/detail/Tuning.h +++ b/src/xrpld/peerfinder/detail/Tuning.h @@ -2,12 +2,9 @@ #include -namespace xrpl { -namespace PeerFinder { - /** Heuristically tuned constants. */ /** @{ */ -namespace Tuning { +namespace xrpl::PeerFinder::Tuning { enum { //--------------------------------------------------------- @@ -110,8 +107,5 @@ std::chrono::seconds constexpr liveCacheSecondsToLive(30); // Note that we ignore the port for purposes of comparison. std::chrono::seconds constexpr recentAttemptDuration(60); -} // namespace Tuning +} // namespace xrpl::PeerFinder::Tuning /** @} */ - -} // namespace PeerFinder -} // namespace xrpl diff --git a/src/xrpld/peerfinder/make_Manager.h b/src/xrpld/peerfinder/make_Manager.h index 846907e08c..8350255ae2 100644 --- a/src/xrpld/peerfinder/make_Manager.h +++ b/src/xrpld/peerfinder/make_Manager.h @@ -6,8 +6,7 @@ #include -namespace xrpl { -namespace PeerFinder { +namespace xrpl::PeerFinder { /** Create a new Manager. */ std::unique_ptr @@ -18,5 +17,4 @@ make_Manager( BasicConfig const& config, beast::insight::Collector::ptr const& collector); -} // namespace PeerFinder -} // namespace xrpl +} // namespace xrpl::PeerFinder diff --git a/src/xrpld/perflog/detail/PerfLogImp.cpp b/src/xrpld/perflog/detail/PerfLogImp.cpp index 266c99a147..57e7be5156 100644 --- a/src/xrpld/perflog/detail/PerfLogImp.cpp +++ b/src/xrpld/perflog/detail/PerfLogImp.cpp @@ -1,25 +1,36 @@ #include #include +#include +#include #include #include +#include +#include #include +#include +#include #include +#include -#include +#include +#include + +#include #include #include -#include +#include +#include +#include #include -#include -#include -#include +#include +#include #include #include #include +#include -namespace xrpl { -namespace perf { +namespace xrpl::perf { PerfLogImp::Counters::Counters(std::set const& labels, JobTypes const& jobTypes) { @@ -284,11 +295,11 @@ PerfLogImp::report() } PerfLogImp::PerfLogImp( - Setup const& setup, + Setup setup, Application& app, beast::Journal journal, std::function&& signalStop) - : setup_(setup), app_(app), j_(journal), signalStop_(std::move(signalStop)) + : setup_(std::move(setup)), app_(app), j_(journal), signalStop_(std::move(signalStop)) { openLog(); } @@ -494,5 +505,4 @@ make_PerfLog( return std::make_unique(setup, app, journal, std::move(signalStop)); } -} // namespace perf -} // namespace xrpl +} // namespace xrpl::perf diff --git a/src/xrpld/perflog/detail/PerfLogImp.h b/src/xrpld/perflog/detail/PerfLogImp.h index 61329fb198..2898158ea5 100644 --- a/src/xrpld/perflog/detail/PerfLogImp.h +++ b/src/xrpld/perflog/detail/PerfLogImp.h @@ -16,8 +16,7 @@ #include #include -namespace xrpl { -namespace perf { +namespace xrpl::perf { /** A box coupling data with a mutex for locking access to it. */ template @@ -123,7 +122,7 @@ class PerfLogImp : public PerfLog public: PerfLogImp( - Setup const& setup, + Setup setup, Application& app, beast::Journal journal, std::function&& signalStop); @@ -177,5 +176,4 @@ public: stop() override; }; -} // namespace perf -} // namespace xrpl +} // namespace xrpl::perf diff --git a/src/xrpld/rpc/BookChanges.h b/src/xrpld/rpc/BookChanges.h index a318c80221..89b99cabe3 100644 --- a/src/xrpld/rpc/BookChanges.h +++ b/src/xrpld/rpc/BookChanges.h @@ -99,10 +99,10 @@ computeBookChanges(std::shared_ptr const& lpAccepted) STAmount const deltaPays = finalFields.getFieldAmount(sfTakerPays) - previousFields.getFieldAmount(sfTakerPays); - std::string const g{to_string(deltaGets.issue())}; - std::string const p{to_string(deltaPays.issue())}; + std::string const g{to_string(deltaGets.asset())}; + std::string const p{to_string(deltaPays.asset())}; - bool const noswap = isXRP(deltaGets) ? true : (isXRP(deltaPays) ? false : (g < p)); + bool const noswap = isXRP(deltaGets) || (!isXRP(deltaPays) && (g < p)); STAmount first = noswap ? deltaGets : deltaPays; STAmount second = noswap ? deltaPays : deltaGets; @@ -121,15 +121,20 @@ computeBookChanges(std::shared_ptr const& lpAccepted) std::stringstream ss; if (noswap) + { ss << g << "|" << p; + } else + { ss << p << "|" << g; + } std::optional const domain = finalFields[~sfDomainID]; std::string const key{ss.str()}; - if (tally.find(key) == tally.end()) + if (!tally.contains(key)) + { tally[key] = { first, // side A vol second, // side B vol @@ -138,6 +143,7 @@ computeBookChanges(std::shared_ptr const& lpAccepted) rate, // open rate, // close domain}; + } else { // increment volume @@ -170,6 +176,16 @@ computeBookChanges(std::shared_ptr const& lpAccepted) jvObj[jss::changes] = Json::arrayValue; + auto volToStr = [](STAmount const& vol) { + return vol.asset().visit( + [&](Issue const& issue) { + if (isXRP(issue)) + return to_string(vol.xrp()); + return to_string(vol.iou()); + }, + [&](MPTIssue const&) { return to_string(vol.mpt()); }); + }; + for (auto const& entry : tally) { Json::Value& inner = jvObj[jss::changes].append(Json::objectValue); @@ -177,11 +193,20 @@ computeBookChanges(std::shared_ptr const& lpAccepted) STAmount const volA = std::get<0>(entry.second); STAmount const volB = std::get<1>(entry.second); - inner[jss::currency_a] = (isXRP(volA) ? "XRP_drops" : to_string(volA.issue())); - inner[jss::currency_b] = (isXRP(volB) ? "XRP_drops" : to_string(volB.issue())); + volA.asset().visit( + [&](Issue const&) { + inner[jss::currency_a] = (isXRP(volA) ? "XRP_drops" : to_string(volA.asset())); + }, + [&](MPTIssue const&) { inner[jss::mpt_issuance_id_a] = to_string(volA.asset()); }); - inner[jss::volume_a] = (isXRP(volA) ? to_string(volA.xrp()) : to_string(volA.iou())); - inner[jss::volume_b] = (isXRP(volB) ? to_string(volB.xrp()) : to_string(volB.iou())); + volB.asset().visit( + [&](Issue const&) { + inner[jss::currency_b] = (isXRP(volB) ? "XRP_drops" : to_string(volB.asset())); + }, + [&](MPTIssue const&) { inner[jss::mpt_issuance_id_b] = to_string(volB.asset()); }); + + inner[jss::volume_a] = volToStr(volA); + inner[jss::volume_b] = volToStr(volB); inner[jss::high] = to_string(std::get<2>(entry.second).iou()); inner[jss::low] = to_string(std::get<3>(entry.second).iou()); diff --git a/src/xrpld/rpc/CTID.h b/src/xrpld/rpc/CTID.h index 42efb4c157..9b36cf167d 100644 --- a/src/xrpld/rpc/CTID.h +++ b/src/xrpld/rpc/CTID.h @@ -6,9 +6,7 @@ #include #include -namespace xrpl { - -namespace RPC { +namespace xrpl::RPC { // CTID stands for Concise Transaction ID. // @@ -108,5 +106,4 @@ decodeCTID(T const ctid) noexcept return std::make_tuple(ledgerSeq, txnIndex, networkID); } -} // namespace RPC -} // namespace xrpl +} // namespace xrpl::RPC diff --git a/src/xrpld/rpc/Context.h b/src/xrpld/rpc/Context.h index e77d9adeb3..58768d028a 100644 --- a/src/xrpld/rpc/Context.h +++ b/src/xrpld/rpc/Context.h @@ -24,8 +24,8 @@ struct Context LedgerMaster& ledgerMaster; Resource::Consumer& consumer; Role role; - std::shared_ptr coro{}; - InfoSub::pointer infoSub{}; + std::shared_ptr coro; + InfoSub::pointer infoSub; unsigned int apiVersion; }; diff --git a/src/xrpld/rpc/MPTokenIssuanceID.h b/src/xrpld/rpc/MPTokenIssuanceID.h index 34aa0474d6..b48f785f87 100644 --- a/src/xrpld/rpc/MPTokenIssuanceID.h +++ b/src/xrpld/rpc/MPTokenIssuanceID.h @@ -8,9 +8,7 @@ #include #include -namespace xrpl { - -namespace RPC { +namespace xrpl::RPC { /** Add a `mpt_issuance_id` field to the `meta` input/output parameter. @@ -25,7 +23,7 @@ canHaveMPTokenIssuanceID( std::shared_ptr const& serializedTx, TxMeta const& transactionMeta); -std::optional +std::optional getIDFromCreatedIssuance(TxMeta const& transactionMeta); void @@ -35,5 +33,4 @@ insertMPTokenIssuanceID( TxMeta const& transactionMeta); /** @} */ -} // namespace RPC -} // namespace xrpl +} // namespace xrpl::RPC diff --git a/src/xrpld/rpc/README.md b/src/xrpld/rpc/README.md index 749f3d0ed1..cf023770ea 100644 --- a/src/xrpld/rpc/README.md +++ b/src/xrpld/rpc/README.md @@ -6,7 +6,7 @@ By default, an RPC handler runs as an uninterrupted task on the JobQueue. This is fine for commands that are fast to compute but might not be acceptable for tasks that require multiple parts or are large, like a full ledger. -For this purpose, the rippled RPC handler allows _suspension with continuation_ +For this purpose, the xrpld RPC handler allows _suspension with continuation_ - a request to suspend execution of the RPC response and to continue it after some function or job has been executed. A default continuation is supplied diff --git a/src/xrpld/rpc/RPCCall.h b/src/xrpld/rpc/RPCCall.h index 6ab9f1d1fa..9994195bb0 100644 --- a/src/xrpld/rpc/RPCCall.h +++ b/src/xrpld/rpc/RPCCall.h @@ -20,7 +20,7 @@ namespace xrpl { // // Improvements to be more strict and to provide better diagnostics are welcome. -/** Processes Ripple RPC calls. */ +/** Processes XRPL RPC calls. */ namespace RPCCall { int @@ -52,7 +52,7 @@ rpcCmdToJson( beast::Journal j); /** Internal invocation of RPC client. - * Used by both rippled command line as well as rippled unit tests + * Used by both xrpld command line as well as xrpld unit tests */ std::pair rpcClient( diff --git a/src/xrpld/rpc/RPCHandler.h b/src/xrpld/rpc/RPCHandler.h index 519740b75d..c8133f3fe6 100644 --- a/src/xrpld/rpc/RPCHandler.h +++ b/src/xrpld/rpc/RPCHandler.h @@ -3,8 +3,7 @@ #include #include -namespace xrpl { -namespace RPC { +namespace xrpl::RPC { struct JsonContext; @@ -15,5 +14,4 @@ doCommand(RPC::JsonContext&, Json::Value&); Role roleRequired(unsigned int version, bool betaEnabled, std::string const& method); -} // namespace RPC -} // namespace xrpl +} // namespace xrpl::RPC diff --git a/src/xrpld/rpc/Status.h b/src/xrpld/rpc/Status.h index 418ad9ccae..f0c6d932e7 100644 --- a/src/xrpld/rpc/Status.h +++ b/src/xrpld/rpc/Status.h @@ -4,8 +4,7 @@ #include #include -namespace xrpl { -namespace RPC { +namespace xrpl::RPC { /** Status represents the results of an operation that might fail. @@ -28,7 +27,7 @@ public: Status() = default; // The enable_if allows only integers (not enums). Prevents enum narrowing. - template ::value>> + template >> Status(T code, Strings d = {}) : code_(code), messages_(std::move(d)) { } @@ -93,9 +92,13 @@ public: if (auto ec = toErrorCode()) { if (messages_.empty()) + { inject_error(ec, object); + } else + { inject_error(ec, message(), object); + } } } @@ -130,5 +133,4 @@ private: Strings messages_; }; -} // namespace RPC -} // namespace xrpl +} // namespace xrpl::RPC diff --git a/src/xrpld/rpc/detail/AccountAssets.cpp b/src/xrpld/rpc/detail/AccountAssets.cpp new file mode 100644 index 0000000000..b990939bd6 --- /dev/null +++ b/src/xrpld/rpc/detail/AccountAssets.cpp @@ -0,0 +1,97 @@ +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include + +namespace xrpl { + +hash_set +accountSourceAssets( + AccountID const& account, + std::shared_ptr const& lrCache, + bool includeXRP) +{ + hash_set assets; + + // YYY Only bother if they are above reserve + if (includeXRP) + assets.insert(xrpCurrency()); + + if (auto const lines = lrCache->getRippleLines(account, LineDirection::outgoing)) + { + for (auto const& rspEntry : *lines) + { + auto& saBalance = rspEntry.getBalance(); + + // Filter out non + if (saBalance > beast::zero + // Have IOUs to send. + || (rspEntry.getLimitPeer() + // Peer extends credit. + && ((-saBalance) < rspEntry.getLimitPeer()))) // Credit left. + { + assets.insert(saBalance.get().currency); + } + } + } + + assets.erase(badCurrency()); + + if (auto const mpts = lrCache->getMPTs(account)) + { + for (auto const& rspEntry : *mpts) + { + if (!rspEntry.isZeroBalance() && !rspEntry.isMaxedOut()) + assets.insert(rspEntry.getMptID()); + } + } + + return assets; +} + +hash_set +accountDestAssets( + AccountID const& account, + std::shared_ptr const& lrCache, + bool includeXRP) +{ + hash_set assets; + + if (includeXRP) + assets.insert(xrpCurrency()); + // Even if account doesn't exist + + if (auto const lines = lrCache->getRippleLines(account, LineDirection::outgoing)) + { + for (auto const& rspEntry : *lines) + { + auto& saBalance = rspEntry.getBalance(); + + if (saBalance < rspEntry.getLimit()) // Can take more + assets.insert(saBalance.get().currency); + } + } + + assets.erase(badCurrency()); + + if (auto const mpts = lrCache->getMPTs(account)) + { + for (auto const& rspEntry : *mpts) + { + if (rspEntry.isZeroBalance() && !rspEntry.isMaxedOut()) + assets.insert(rspEntry.getMptID()); + } + } + + return assets; +} + +} // namespace xrpl diff --git a/src/xrpld/rpc/detail/AccountAssets.h b/src/xrpld/rpc/detail/AccountAssets.h new file mode 100644 index 0000000000..55fce62fa6 --- /dev/null +++ b/src/xrpld/rpc/detail/AccountAssets.h @@ -0,0 +1,21 @@ +#pragma once + +#include + +#include + +namespace xrpl { + +hash_set +accountDestAssets( + AccountID const& account, + std::shared_ptr const& cache, + bool includeXRP); + +hash_set +accountSourceAssets( + AccountID const& account, + std::shared_ptr const& lrLedger, + bool includeXRP); + +} // namespace xrpl diff --git a/src/xrpld/rpc/detail/AccountCurrencies.cpp b/src/xrpld/rpc/detail/AccountCurrencies.cpp deleted file mode 100644 index c839b1475c..0000000000 --- a/src/xrpld/rpc/detail/AccountCurrencies.cpp +++ /dev/null @@ -1,66 +0,0 @@ -#include - -namespace xrpl { - -hash_set -accountSourceCurrencies( - AccountID const& account, - std::shared_ptr const& lrCache, - bool includeXRP) -{ - hash_set currencies; - - // YYY Only bother if they are above reserve - if (includeXRP) - currencies.insert(xrpCurrency()); - - if (auto const lines = lrCache->getRippleLines(account, LineDirection::outgoing)) - { - for (auto const& rspEntry : *lines) - { - auto& saBalance = rspEntry.getBalance(); - - // Filter out non - if (saBalance > beast::zero - // Have IOUs to send. - || (rspEntry.getLimitPeer() - // Peer extends credit. - && ((-saBalance) < rspEntry.getLimitPeer()))) // Credit left. - { - currencies.insert(saBalance.getCurrency()); - } - } - } - - currencies.erase(badCurrency()); - return currencies; -} - -hash_set -accountDestCurrencies( - AccountID const& account, - std::shared_ptr const& lrCache, - bool includeXRP) -{ - hash_set currencies; - - if (includeXRP) - currencies.insert(xrpCurrency()); - // Even if account doesn't exist - - if (auto const lines = lrCache->getRippleLines(account, LineDirection::outgoing)) - { - for (auto const& rspEntry : *lines) - { - auto& saBalance = rspEntry.getBalance(); - - if (saBalance < rspEntry.getLimit()) // Can take more - currencies.insert(saBalance.getCurrency()); - } - } - - currencies.erase(badCurrency()); - return currencies; -} - -} // namespace xrpl diff --git a/src/xrpld/rpc/detail/AccountCurrencies.h b/src/xrpld/rpc/detail/AccountCurrencies.h deleted file mode 100644 index 76c531cb9b..0000000000 --- a/src/xrpld/rpc/detail/AccountCurrencies.h +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once - -#include - -#include - -namespace xrpl { - -hash_set -accountDestCurrencies( - AccountID const& account, - std::shared_ptr const& cache, - bool includeXRP); - -hash_set -accountSourceCurrencies( - AccountID const& account, - std::shared_ptr const& lrLedger, - bool includeXRP); - -} // namespace xrpl diff --git a/src/xrpld/rpc/detail/AssetCache.cpp b/src/xrpld/rpc/detail/AssetCache.cpp new file mode 100644 index 0000000000..bbe8fefc98 --- /dev/null +++ b/src/xrpld/rpc/detail/AssetCache.cpp @@ -0,0 +1,163 @@ +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace xrpl { + +AssetCache::AssetCache(std::shared_ptr const& ledger, beast::Journal j) + : ledger_(ledger), journal_(j) +{ + JLOG(journal_.debug()) << "created for ledger " << ledger_->header().seq; +} + +AssetCache::~AssetCache() +{ + JLOG(journal_.debug()) << "destroyed for ledger " << ledger_->header().seq << " with " + << lines_.size() << " accounts and " << totalLineCount_ + << " distinct trust lines."; +} + +std::shared_ptr> +AssetCache::getRippleLines(AccountID const& accountID, LineDirection direction) +{ + auto const hash = hasher_(accountID); + AccountKey key(accountID, direction, hash); + AccountKey otherkey( + accountID, + direction == LineDirection::outgoing ? LineDirection::incoming : LineDirection::outgoing, + hash); + + std::lock_guard const sl(mLock); + + auto [it, inserted] = [&]() { + if (auto otheriter = lines_.find(otherkey); otheriter != lines_.end()) + { + // The whole point of using the direction flag is to reduce the + // number of trust line objects held in memory. Ensure that there is + // only a single set of trustlines in the cache per account. + auto const size = otheriter->second ? otheriter->second->size() : 0; + JLOG(journal_.info()) + << "Request for " + << (direction == LineDirection::outgoing ? "outgoing" : "incoming") + << " trust lines for account " << accountID << " found " << size + << (direction == LineDirection::outgoing ? " incoming" : " outgoing") + << " trust lines. " + << (direction == LineDirection::outgoing ? "Deleting the subset of incoming" + : "Returning the superset of outgoing") + << " trust lines. "; + if (direction == LineDirection::outgoing) + { + // This request is for the outgoing set, but there is already a + // subset of incoming lines in the cache. Erase that subset + // to be replaced by the full set. The full set will be built + // below, and will be returned, if needed, on subsequent calls + // for either value of outgoing. + XRPL_ASSERT( + size <= totalLineCount_, "xrpl::AssetCache::getRippleLines : maximum lines"); + totalLineCount_ -= size; + lines_.erase(otheriter); + } + else + { + // This request is for the incoming set, but there is + // already a superset of the outgoing trust lines in the cache. + // The path finding engine will disregard the non-rippling trust + // lines, so to prevent them from being stored twice, return the + // outgoing set. + key = otherkey; + return std::pair{otheriter, false}; + } + } + return lines_.emplace(key, nullptr); + }(); + + if (inserted) + { + XRPL_ASSERT(it->second == nullptr, "xrpl::Asset::getRippleLines : null lines"); + auto lines = PathFindTrustLine::getItems(accountID, *ledger_, direction); + if (!lines.empty()) + { + it->second = std::make_shared>(std::move(lines)); + totalLineCount_ += it->second->size(); + } + } + + XRPL_ASSERT( + !it->second || !it->second->empty(), + "xrpl::AssetCache::getRippleLines : null or nonempty lines"); + auto const size = it->second ? it->second->size() : 0; + JLOG(journal_.trace()) << "getRippleLines for ledger " << ledger_->header().seq << " found " + << size + << (key.direction_ == LineDirection::outgoing ? " outgoing" + : " incoming") + << " lines for " << (inserted ? "new " : "existing ") << accountID + << " out of a total of " << lines_.size() << " accounts and " + << totalLineCount_ << " trust lines"; + + return it->second; +} + +std::shared_ptr> const& +AssetCache::getMPTs(xrpl::AccountID const& account) +{ + std::lock_guard const sl(mLock); + + if (auto it = mpts_.find(account); it != mpts_.end()) + return it->second; + + std::vector mpts; + // Get issued/authorized tokens + forEachItem(*ledger_, account, [&](std::shared_ptr const& sle) { + if (sle->getType() == ltMPTOKEN_ISSUANCE) + { + auto const mptID = makeMptID(sle->getFieldU32(sfSequence), account); + bool const maxedOut = sle->at(sfOutstandingAmount) == maxMPTAmount(*sle); + mpts.emplace_back(mptID, false, maxedOut); + } + else if (sle->getType() == ltMPTOKEN) + { + auto const mptID = sle->getFieldH192(sfMPTokenIssuanceID); + bool const zeroBalance = sle->at(sfMPTAmount) == 0; + bool const maxedOut = [&] { + if (auto const sleIssuance = ledger_->read(keylet::mptIssuance(mptID))) + { + return sleIssuance->at(sfOutstandingAmount) == maxMPTAmount(*sleIssuance); + } + return true; + }(); + + mpts.emplace_back(mptID, zeroBalance, maxedOut); + } + }); + + if (mpts.empty()) + { + mpts_.emplace(account, nullptr); + } + else + { + mpts_.emplace(account, std::make_shared>(std::move(mpts))); + } + + return mpts_[account]; +} + +} // namespace xrpl diff --git a/src/xrpld/rpc/detail/AssetCache.h b/src/xrpld/rpc/detail/AssetCache.h new file mode 100644 index 0000000000..0709df2c5f --- /dev/null +++ b/src/xrpld/rpc/detail/AssetCache.h @@ -0,0 +1,106 @@ +#pragma once + +#include +#include + +#include +#include +#include + +#include +#include +#include + +namespace xrpl { + +// Used by Pathfinder +class AssetCache final : public CountedObject +{ +public: + explicit AssetCache(std::shared_ptr const& l, beast::Journal j); + ~AssetCache(); + + std::shared_ptr const& + getLedger() const + { + return ledger_; + } + + /** Find the trust lines associated with an account. + + @param accountID The account + @param direction Whether the account is an "outgoing" link on the path. + "Outgoing" is defined as the source account, or an account found via a + trustline that has rippling enabled on the @accountID's side. If an + account is "outgoing", all trust lines will be returned. If an account is + not "outgoing", then any trust lines that don't have rippling enabled are + not usable, so only return trust lines that have rippling enabled on + @accountID's side. + @return Returns a vector of the usable trust lines. + */ + std::shared_ptr> + getRippleLines(AccountID const& accountID, LineDirection direction); + + std::shared_ptr> const& + getMPTs(AccountID const& account); + +private: + std::mutex mLock; + + xrpl::hardened_hash<> hasher_; + std::shared_ptr ledger_; + + beast::Journal journal_; + + struct AccountKey final : public CountedObject + { + AccountID account_; + LineDirection direction_; + std::size_t hash_value_; + + AccountKey(AccountID const& account, LineDirection direction, std::size_t hash) + : account_(account), direction_(direction), hash_value_(hash) + { + } + + AccountKey(AccountKey const& other) = default; + + AccountKey& + operator=(AccountKey const& other) = default; + + bool + operator==(AccountKey const& lhs) const + { + return hash_value_ == lhs.hash_value_ && account_ == lhs.account_ && + direction_ == lhs.direction_; + } + + std::size_t + get_hash() const + { + return hash_value_; + } + + struct Hash + { + Hash() = default; + + std::size_t + operator()(AccountKey const& key) const noexcept + { + return key.get_hash(); + } + }; + }; + + // Use a shared_ptr so entries can be removed from the map safely. + // Even though a shared_ptr to a vector will take more memory just a vector, + // most accounts are not going to have any entries (estimated over 90%), so + // vectors will not need to be created for them. This should lead to far + // less memory usage overall. + hash_map>, AccountKey::Hash> lines_; + std::size_t totalLineCount_ = 0; + hash_map>> mpts_; +}; + +} // namespace xrpl diff --git a/src/xrpld/rpc/detail/DeliveredAmount.cpp b/src/xrpld/rpc/detail/DeliveredAmount.cpp index e2f5bd8cd9..6b9ef15db5 100644 --- a/src/xrpld/rpc/detail/DeliveredAmount.cpp +++ b/src/xrpld/rpc/detail/DeliveredAmount.cpp @@ -1,14 +1,22 @@ -#include -#include -#include -#include #include -#include -#include +#include +#include +#include -namespace xrpl { -namespace RPC { +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace xrpl::RPC { /* GetLedgerIndex and GetCloseTime are lambdas that allow the close time and @@ -170,5 +178,4 @@ insertDeliveredAmount( } } -} // namespace RPC -} // namespace xrpl +} // namespace xrpl::RPC diff --git a/src/xrpld/rpc/detail/Handler.cpp b/src/xrpld/rpc/detail/Handler.cpp index 0f95e69d3f..b8489e0b34 100644 --- a/src/xrpld/rpc/detail/Handler.cpp +++ b/src/xrpld/rpc/detail/Handler.cpp @@ -1,14 +1,24 @@ #include + +#include +#include #include -#include +#include +#include #include +#include +#include #include +#include +#include #include +#include +#include +#include -namespace xrpl { -namespace RPC { +namespace xrpl::RPC { namespace { /** Adjust an old-style handler to be call-by-reference. */ @@ -68,79 +78,293 @@ handlerFrom() Handler const handlerArray[]{ // Some handlers not specified here are added to the table via addHandler() // Request-response methods - {"account_info", byRef(&doAccountInfo), Role::USER, NO_CONDITION}, - {"account_currencies", byRef(&doAccountCurrencies), Role::USER, NO_CONDITION}, - {"account_lines", byRef(&doAccountLines), Role::USER, NO_CONDITION}, - {"account_channels", byRef(&doAccountChannels), Role::USER, NO_CONDITION}, - {"account_nfts", byRef(&doAccountNFTs), Role::USER, NO_CONDITION}, - {"account_objects", byRef(&doAccountObjects), Role::USER, NO_CONDITION}, - {"account_offers", byRef(&doAccountOffers), Role::USER, NO_CONDITION}, - {"account_tx", byRef(&doAccountTxJson), Role::USER, NO_CONDITION}, - {"amm_info", byRef(&doAMMInfo), Role::USER, NO_CONDITION}, - {"blacklist", byRef(&doBlackList), Role::ADMIN, NO_CONDITION}, - {"book_changes", byRef(&doBookChanges), Role::USER, NO_CONDITION}, - {"book_offers", byRef(&doBookOffers), Role::USER, NO_CONDITION}, - {"can_delete", byRef(&doCanDelete), Role::ADMIN, NO_CONDITION}, - {"channel_authorize", byRef(&doChannelAuthorize), Role::USER, NO_CONDITION}, - {"channel_verify", byRef(&doChannelVerify), Role::USER, NO_CONDITION}, - {"connect", byRef(&doConnect), Role::ADMIN, NO_CONDITION}, - {"consensus_info", byRef(&doConsensusInfo), Role::ADMIN, NO_CONDITION}, - {"deposit_authorized", byRef(&doDepositAuthorized), Role::USER, NO_CONDITION}, - {"feature", byRef(&doFeature), Role::USER, NO_CONDITION}, - {"fee", byRef(&doFee), Role::USER, NEEDS_CURRENT_LEDGER}, - {"fetch_info", byRef(&doFetchInfo), Role::ADMIN, NO_CONDITION}, - {"gateway_balances", byRef(&doGatewayBalances), Role::USER, NO_CONDITION}, - {"get_counts", byRef(&doGetCounts), Role::ADMIN, NO_CONDITION}, - {"get_aggregate_price", byRef(&doGetAggregatePrice), Role::USER, NO_CONDITION}, - {"ledger_accept", byRef(&doLedgerAccept), Role::ADMIN, NEEDS_CURRENT_LEDGER}, - {"ledger_cleaner", byRef(&doLedgerCleaner), Role::ADMIN, NEEDS_NETWORK_CONNECTION}, - {"ledger_closed", byRef(&doLedgerClosed), Role::USER, NEEDS_CLOSED_LEDGER}, - {"ledger_current", byRef(&doLedgerCurrent), Role::USER, NEEDS_CURRENT_LEDGER}, - {"ledger_data", byRef(&doLedgerData), Role::USER, NO_CONDITION}, - {"ledger_entry", byRef(&doLedgerEntry), Role::USER, NO_CONDITION}, - {"ledger_header", byRef(&doLedgerHeader), Role::USER, NO_CONDITION, 1, 1}, - {"ledger_request", byRef(&doLedgerRequest), Role::ADMIN, NO_CONDITION}, - {"log_level", byRef(&doLogLevel), Role::ADMIN, NO_CONDITION}, - {"logrotate", byRef(&doLogRotate), Role::ADMIN, NO_CONDITION}, - {"manifest", byRef(&doManifest), Role::USER, NO_CONDITION}, - {"nft_buy_offers", byRef(&doNFTBuyOffers), Role::USER, NO_CONDITION}, - {"nft_sell_offers", byRef(&doNFTSellOffers), Role::USER, NO_CONDITION}, - {"noripple_check", byRef(&doNoRippleCheck), Role::USER, NO_CONDITION}, - {"owner_info", byRef(&doOwnerInfo), Role::USER, NEEDS_CURRENT_LEDGER}, - {"peers", byRef(&doPeers), Role::ADMIN, NO_CONDITION}, - {"path_find", byRef(&doPathFind), Role::USER, NEEDS_CURRENT_LEDGER}, - {"ping", byRef(&doPing), Role::USER, NO_CONDITION}, - {"print", byRef(&doPrint), Role::ADMIN, NO_CONDITION}, + {.name_ = "account_info", + .valueMethod_ = byRef(&doAccountInfo), + .role_ = Role::USER, + .condition_ = NO_CONDITION}, + {.name_ = "account_currencies", + .valueMethod_ = byRef(&doAccountCurrencies), + .role_ = Role::USER, + .condition_ = NO_CONDITION}, + {.name_ = "account_lines", + .valueMethod_ = byRef(&doAccountLines), + .role_ = Role::USER, + .condition_ = NO_CONDITION}, + {.name_ = "account_channels", + .valueMethod_ = byRef(&doAccountChannels), + .role_ = Role::USER, + .condition_ = NO_CONDITION}, + {.name_ = "account_nfts", + .valueMethod_ = byRef(&doAccountNFTs), + .role_ = Role::USER, + .condition_ = NO_CONDITION}, + {.name_ = "account_objects", + .valueMethod_ = byRef(&doAccountObjects), + .role_ = Role::USER, + .condition_ = NO_CONDITION}, + {.name_ = "account_offers", + .valueMethod_ = byRef(&doAccountOffers), + .role_ = Role::USER, + .condition_ = NO_CONDITION}, + {.name_ = "account_tx", + .valueMethod_ = byRef(&doAccountTx), + .role_ = Role::USER, + .condition_ = NO_CONDITION}, + {.name_ = "amm_info", + .valueMethod_ = byRef(&doAMMInfo), + .role_ = Role::USER, + .condition_ = NO_CONDITION}, + {.name_ = "blacklist", + .valueMethod_ = byRef(&doBlackList), + .role_ = Role::ADMIN, + .condition_ = NO_CONDITION}, + {.name_ = "book_changes", + .valueMethod_ = byRef(&doBookChanges), + .role_ = Role::USER, + .condition_ = NO_CONDITION}, + {.name_ = "book_offers", + .valueMethod_ = byRef(&doBookOffers), + .role_ = Role::USER, + .condition_ = NO_CONDITION}, + {.name_ = "can_delete", + .valueMethod_ = byRef(&doCanDelete), + .role_ = Role::ADMIN, + .condition_ = NO_CONDITION}, + {.name_ = "channel_authorize", + .valueMethod_ = byRef(&doChannelAuthorize), + .role_ = Role::USER, + .condition_ = NO_CONDITION}, + {.name_ = "channel_verify", + .valueMethod_ = byRef(&doChannelVerify), + .role_ = Role::USER, + .condition_ = NO_CONDITION}, + {.name_ = "connect", + .valueMethod_ = byRef(&doConnect), + .role_ = Role::ADMIN, + .condition_ = NO_CONDITION}, + {.name_ = "consensus_info", + .valueMethod_ = byRef(&doConsensusInfo), + .role_ = Role::ADMIN, + .condition_ = NO_CONDITION}, + {.name_ = "deposit_authorized", + .valueMethod_ = byRef(&doDepositAuthorized), + .role_ = Role::USER, + .condition_ = NO_CONDITION}, + {.name_ = "feature", + .valueMethod_ = byRef(&doFeature), + .role_ = Role::USER, + .condition_ = NO_CONDITION}, + {.name_ = "fee", + .valueMethod_ = byRef(&doFee), + .role_ = Role::USER, + .condition_ = NEEDS_CURRENT_LEDGER}, + {.name_ = "fetch_info", + .valueMethod_ = byRef(&doFetchInfo), + .role_ = Role::ADMIN, + .condition_ = NO_CONDITION}, + {.name_ = "gateway_balances", + .valueMethod_ = byRef(&doGatewayBalances), + .role_ = Role::USER, + .condition_ = NO_CONDITION}, + {.name_ = "get_counts", + .valueMethod_ = byRef(&doGetCounts), + .role_ = Role::ADMIN, + .condition_ = NO_CONDITION}, + {.name_ = "get_aggregate_price", + .valueMethod_ = byRef(&doGetAggregatePrice), + .role_ = Role::USER, + .condition_ = NO_CONDITION}, + {.name_ = "ledger_accept", + .valueMethod_ = byRef(&doLedgerAccept), + .role_ = Role::ADMIN, + .condition_ = NEEDS_CURRENT_LEDGER}, + {.name_ = "ledger_cleaner", + .valueMethod_ = byRef(&doLedgerCleaner), + .role_ = Role::ADMIN, + .condition_ = NEEDS_NETWORK_CONNECTION}, + {.name_ = "ledger_closed", + .valueMethod_ = byRef(&doLedgerClosed), + .role_ = Role::USER, + .condition_ = NEEDS_CLOSED_LEDGER}, + {.name_ = "ledger_current", + .valueMethod_ = byRef(&doLedgerCurrent), + .role_ = Role::USER, + .condition_ = NEEDS_CURRENT_LEDGER}, + {.name_ = "ledger_data", + .valueMethod_ = byRef(&doLedgerData), + .role_ = Role::USER, + .condition_ = NO_CONDITION}, + {.name_ = "ledger_entry", + .valueMethod_ = byRef(&doLedgerEntry), + .role_ = Role::USER, + .condition_ = NO_CONDITION}, + {.name_ = "ledger_header", + .valueMethod_ = byRef(&doLedgerHeader), + .role_ = Role::USER, + .condition_ = NO_CONDITION, + .minApiVer_ = 1, + .maxApiVer_ = 1}, + {.name_ = "ledger_request", + .valueMethod_ = byRef(&doLedgerRequest), + .role_ = Role::ADMIN, + .condition_ = NO_CONDITION}, + {.name_ = "log_level", + .valueMethod_ = byRef(&doLogLevel), + .role_ = Role::ADMIN, + .condition_ = NO_CONDITION}, + {.name_ = "logrotate", + .valueMethod_ = byRef(&doLogRotate), + .role_ = Role::ADMIN, + .condition_ = NO_CONDITION}, + {.name_ = "manifest", + .valueMethod_ = byRef(&doManifest), + .role_ = Role::USER, + .condition_ = NO_CONDITION}, + {.name_ = "nft_buy_offers", + .valueMethod_ = byRef(&doNFTBuyOffers), + .role_ = Role::USER, + .condition_ = NO_CONDITION}, + {.name_ = "nft_sell_offers", + .valueMethod_ = byRef(&doNFTSellOffers), + .role_ = Role::USER, + .condition_ = NO_CONDITION}, + {.name_ = "noripple_check", + .valueMethod_ = byRef(&doNoRippleCheck), + .role_ = Role::USER, + .condition_ = NO_CONDITION}, + {.name_ = "owner_info", + .valueMethod_ = byRef(&doOwnerInfo), + .role_ = Role::USER, + .condition_ = NEEDS_CURRENT_LEDGER}, + {.name_ = "peers", + .valueMethod_ = byRef(&doPeers), + .role_ = Role::ADMIN, + .condition_ = NO_CONDITION}, + {.name_ = "path_find", + .valueMethod_ = byRef(&doPathFind), + .role_ = Role::USER, + .condition_ = NEEDS_CURRENT_LEDGER}, + {.name_ = "ping", + .valueMethod_ = byRef(&doPing), + .role_ = Role::USER, + .condition_ = NO_CONDITION}, + {.name_ = "print", + .valueMethod_ = byRef(&doPrint), + .role_ = Role::ADMIN, + .condition_ = NO_CONDITION}, // { "profile", byRef (&doProfile), Role::USER, // NEEDS_CURRENT_LEDGER }, - {"random", byRef(&doRandom), Role::USER, NO_CONDITION}, - {"peer_reservations_add", byRef(&doPeerReservationsAdd), Role::ADMIN, NO_CONDITION}, - {"peer_reservations_del", byRef(&doPeerReservationsDel), Role::ADMIN, NO_CONDITION}, - {"peer_reservations_list", byRef(&doPeerReservationsList), Role::ADMIN, NO_CONDITION}, - {"ripple_path_find", byRef(&doRipplePathFind), Role::USER, NO_CONDITION}, - {"server_definitions", byRef(&doServerDefinitions), Role::USER, NO_CONDITION}, - {"server_info", byRef(&doServerInfo), Role::USER, NO_CONDITION}, - {"server_state", byRef(&doServerState), Role::USER, NO_CONDITION}, - {"sign", byRef(&doSign), Role::USER, NO_CONDITION}, - {"sign_for", byRef(&doSignFor), Role::USER, NO_CONDITION}, - {"simulate", byRef(&doSimulate), Role::USER, NEEDS_CURRENT_LEDGER}, - {"stop", byRef(&doStop), Role::ADMIN, NO_CONDITION}, - {"submit", byRef(&doSubmit), Role::USER, NEEDS_CURRENT_LEDGER}, - {"submit_multisigned", byRef(&doSubmitMultiSigned), Role::USER, NEEDS_CURRENT_LEDGER}, - {"transaction_entry", byRef(&doTransactionEntry), Role::USER, NO_CONDITION}, - {"tx", byRef(&doTxJson), Role::USER, NEEDS_NETWORK_CONNECTION}, - {"tx_history", byRef(&doTxHistory), Role::USER, NO_CONDITION, 1, 1}, - {"tx_reduce_relay", byRef(&doTxReduceRelay), Role::USER, NO_CONDITION}, - {"unl_list", byRef(&doUnlList), Role::ADMIN, NO_CONDITION}, - {"validation_create", byRef(&doValidationCreate), Role::ADMIN, NO_CONDITION}, - {"validators", byRef(&doValidators), Role::ADMIN, NO_CONDITION}, - {"validator_list_sites", byRef(&doValidatorListSites), Role::ADMIN, NO_CONDITION}, - {"validator_info", byRef(&doValidatorInfo), Role::ADMIN, NO_CONDITION}, - {"vault_info", byRef(&doVaultInfo), Role::USER, NO_CONDITION}, - {"wallet_propose", byRef(&doWalletPropose), Role::ADMIN, NO_CONDITION}, + {.name_ = "random", + .valueMethod_ = byRef(&doRandom), + .role_ = Role::USER, + .condition_ = NO_CONDITION}, + {.name_ = "peer_reservations_add", + .valueMethod_ = byRef(&doPeerReservationsAdd), + .role_ = Role::ADMIN, + .condition_ = NO_CONDITION}, + {.name_ = "peer_reservations_del", + .valueMethod_ = byRef(&doPeerReservationsDel), + .role_ = Role::ADMIN, + .condition_ = NO_CONDITION}, + {.name_ = "peer_reservations_list", + .valueMethod_ = byRef(&doPeerReservationsList), + .role_ = Role::ADMIN, + .condition_ = NO_CONDITION}, + {.name_ = "ripple_path_find", + .valueMethod_ = byRef(&doRipplePathFind), + .role_ = Role::USER, + .condition_ = NO_CONDITION}, + {.name_ = "server_definitions", + .valueMethod_ = byRef(&doServerDefinitions), + .role_ = Role::USER, + .condition_ = NO_CONDITION}, + {.name_ = "server_info", + .valueMethod_ = byRef(&doServerInfo), + .role_ = Role::USER, + .condition_ = NO_CONDITION}, + {.name_ = "server_state", + .valueMethod_ = byRef(&doServerState), + .role_ = Role::USER, + .condition_ = NO_CONDITION}, + {.name_ = "sign", + .valueMethod_ = byRef(&doSign), + .role_ = Role::USER, + .condition_ = NO_CONDITION}, + {.name_ = "sign_for", + .valueMethod_ = byRef(&doSignFor), + .role_ = Role::USER, + .condition_ = NO_CONDITION}, + {.name_ = "simulate", + .valueMethod_ = byRef(&doSimulate), + .role_ = Role::USER, + .condition_ = NEEDS_CURRENT_LEDGER}, + {.name_ = "stop", + .valueMethod_ = byRef(&doStop), + .role_ = Role::ADMIN, + .condition_ = NO_CONDITION}, + {.name_ = "submit", + .valueMethod_ = byRef(&doSubmit), + .role_ = Role::USER, + .condition_ = NEEDS_CURRENT_LEDGER}, + {.name_ = "submit_multisigned", + .valueMethod_ = byRef(&doSubmitMultiSigned), + .role_ = Role::USER, + .condition_ = NEEDS_CURRENT_LEDGER}, + {.name_ = "transaction_entry", + .valueMethod_ = byRef(&doTransactionEntry), + .role_ = Role::USER, + .condition_ = NO_CONDITION}, + {.name_ = "tx", + .valueMethod_ = byRef(&doTxJson), + .role_ = Role::USER, + .condition_ = NEEDS_NETWORK_CONNECTION}, + {.name_ = "tx_history", + .valueMethod_ = byRef(&doTxHistory), + .role_ = Role::USER, + .condition_ = NO_CONDITION, + .minApiVer_ = 1, + .maxApiVer_ = 1}, + {.name_ = "tx_reduce_relay", + .valueMethod_ = byRef(&doTxReduceRelay), + .role_ = Role::USER, + .condition_ = NO_CONDITION}, + {.name_ = "unl_list", + .valueMethod_ = byRef(&doUnlList), + .role_ = Role::ADMIN, + .condition_ = NO_CONDITION}, + {.name_ = "validation_create", + .valueMethod_ = byRef(&doValidationCreate), + .role_ = Role::ADMIN, + .condition_ = NO_CONDITION}, + {.name_ = "validators", + .valueMethod_ = byRef(&doValidators), + .role_ = Role::ADMIN, + .condition_ = NO_CONDITION}, + {.name_ = "validator_list_sites", + .valueMethod_ = byRef(&doValidatorListSites), + .role_ = Role::ADMIN, + .condition_ = NO_CONDITION}, + {.name_ = "validator_info", + .valueMethod_ = byRef(&doValidatorInfo), + .role_ = Role::ADMIN, + .condition_ = NO_CONDITION}, + {.name_ = "vault_info", + .valueMethod_ = byRef(&doVaultInfo), + .role_ = Role::USER, + .condition_ = NO_CONDITION}, + {.name_ = "wallet_propose", + .valueMethod_ = byRef(&doWalletPropose), + .role_ = Role::ADMIN, + .condition_ = NO_CONDITION}, // Event methods - {"subscribe", byRef(&doSubscribe), Role::USER, NO_CONDITION}, - {"unsubscribe", byRef(&doUnsubscribe), Role::USER, NO_CONDITION}, + {.name_ = "subscribe", + .valueMethod_ = byRef(&doSubscribe), + .role_ = Role::USER, + .condition_ = NO_CONDITION}, + {.name_ = "unsubscribe", + .valueMethod_ = byRef(&doUnsubscribe), + .role_ = Role::USER, + .condition_ = NO_CONDITION}, }; class HandlerTable @@ -262,5 +486,4 @@ getHandlerNames() return HandlerTable::instance().getHandlerNames(); } -} // namespace RPC -} // namespace xrpl +} // namespace xrpl::RPC diff --git a/src/xrpld/rpc/detail/Handler.h b/src/xrpld/rpc/detail/Handler.h index 3628962a69..d249409ffe 100644 --- a/src/xrpld/rpc/detail/Handler.h +++ b/src/xrpld/rpc/detail/Handler.h @@ -12,8 +12,7 @@ namespace Json { class Object; } // namespace Json -namespace xrpl { -namespace RPC { +namespace xrpl::RPC { // Under what condition can we call this RPC? enum Condition { @@ -111,5 +110,4 @@ conditionMet(Condition condition_required, T& context) return rpcSUCCESS; } -} // namespace RPC -} // namespace xrpl +} // namespace xrpl::RPC diff --git a/src/xrpld/rpc/detail/LegacyPathFind.cpp b/src/xrpld/rpc/detail/LegacyPathFind.cpp index 5b5bcc540b..11debe00c1 100644 --- a/src/xrpld/rpc/detail/LegacyPathFind.cpp +++ b/src/xrpld/rpc/detail/LegacyPathFind.cpp @@ -1,13 +1,15 @@ -#include #include + +#include #include #include #include #include -namespace xrpl { -namespace RPC { +#include + +namespace xrpl::RPC { LegacyPathFind::LegacyPathFind(bool isAdmin, Application& app) { @@ -45,5 +47,4 @@ LegacyPathFind::~LegacyPathFind() std::atomic LegacyPathFind::inProgress(0); -} // namespace RPC -} // namespace xrpl +} // namespace xrpl::RPC diff --git a/src/xrpld/rpc/detail/MPT.h b/src/xrpld/rpc/detail/MPT.h new file mode 100644 index 0000000000..61dcb8fa7d --- /dev/null +++ b/src/xrpld/rpc/detail/MPT.h @@ -0,0 +1,45 @@ +#pragma once + +#include + +namespace xrpl { + +class PathFindMPT final +{ +private: + MPTID const mptID_; + // If true then holder's balance is 0, always false for issuer + bool const zeroBalance_; + // OutstandingAmount is equal to MaximumAmount + bool const maxedOut_; + +public: + PathFindMPT(MPTID const& mptID) : mptID_(mptID), zeroBalance_(false), maxedOut_(false) + { + } + PathFindMPT(MPTID const& mptID, bool zeroBalance, bool maxedOut) + : mptID_(mptID), zeroBalance_(zeroBalance), maxedOut_(maxedOut) + { + } + operator MPTID const&() const + { + return mptID_; + } + MPTID const& + getMptID() const + { + return mptID_; + } + bool + isZeroBalance() const + { + return zeroBalance_; + } + bool + isMaxedOut() const + { + return maxedOut_; + } +}; + +} // namespace xrpl diff --git a/src/xrpld/rpc/detail/MPTokenIssuanceID.cpp b/src/xrpld/rpc/detail/MPTokenIssuanceID.cpp index ef8bc448ee..8af745122a 100644 --- a/src/xrpld/rpc/detail/MPTokenIssuanceID.cpp +++ b/src/xrpld/rpc/detail/MPTokenIssuanceID.cpp @@ -1,8 +1,22 @@ #include -namespace xrpl { +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include -namespace RPC { +#include +#include + +namespace xrpl::RPC { bool canHaveMPTokenIssuanceID( @@ -23,7 +37,7 @@ canHaveMPTokenIssuanceID( return true; } -std::optional +std::optional getIDFromCreatedIssuance(TxMeta const& transactionMeta) { for (STObject const& node : transactionMeta.getNodes()) @@ -48,10 +62,9 @@ insertMPTokenIssuanceID( if (!canHaveMPTokenIssuanceID(transaction, transactionMeta)) return; - std::optional result = getIDFromCreatedIssuance(transactionMeta); + std::optional result = getIDFromCreatedIssuance(transactionMeta); if (result) response[jss::mpt_issuance_id] = to_string(result.value()); } -} // namespace RPC -} // namespace xrpl +} // namespace xrpl::RPC diff --git a/src/xrpld/rpc/detail/PathRequest.cpp b/src/xrpld/rpc/detail/PathRequest.cpp index fe85c519eb..b4ac252f10 100644 --- a/src/xrpld/rpc/detail/PathRequest.cpp +++ b/src/xrpld/rpc/detail/PathRequest.cpp @@ -1,21 +1,50 @@ +#include + #include #include -#include -#include +#include #include +#include #include #include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include #include +#include +#include +#include +#include #include +#include +#include +#include #include -#include #include +#include +#include +#include +#include +#include #include -#include +#include +#include +#include namespace xrpl { @@ -146,7 +175,7 @@ PathRequest::updateComplete() } bool -PathRequest::isValid(std::shared_ptr const& crCache) +PathRequest::isValid(std::shared_ptr const& crCache) { if (!raSrcAccount || !raDstAccount) return false; @@ -192,10 +221,11 @@ PathRequest::isValid(std::shared_ptr const& crCache) { bool const disallowXRP((sleDest->getFlags() & lsfDisallowXRP) != 0u); - auto usDestCurrID = accountDestCurrencies(*raDstAccount, crCache, !disallowXRP); + auto const destAssets = accountDestAssets(*raDstAccount, crCache, !disallowXRP); + + for (auto const& asset : destAssets) + jvDestCur.append(to_string(asset)); - for (auto const& currency : usDestCurrID) - jvDestCur.append(to_string(currency)); jvStatus[jss::destination_tag] = (sleDest->getFlags() & lsfRequireDestTag); } @@ -214,7 +244,7 @@ PathRequest::isValid(std::shared_ptr const& crCache) in all cases. */ std::pair -PathRequest::doCreate(std::shared_ptr const& cache, Json::Value const& value) +PathRequest::doCreate(std::shared_ptr const& cache, Json::Value const& value) { bool valid = false; @@ -282,11 +312,9 @@ PathRequest::parseJson(Json::Value const& jvParams) return PFR_PJ_INVALID; } - convert_all_ = saDstAmount == STAmount(saDstAmount.issue(), 1u, 0, true); + convert_all_ = saDstAmount == STAmount(saDstAmount.asset(), 1u, 0, true); - if ((saDstAmount.getCurrency().isZero() && saDstAmount.getIssuer().isNonZero()) || - (saDstAmount.getCurrency() == badCurrency()) || - (!convert_all_ && saDstAmount <= beast::zero)) + if (!validAsset(saDstAmount.asset()) || (!convert_all_ && saDstAmount <= beast::zero)) { jvStatus = rpcError(rpcDST_AMT_MALFORMED); return PFR_PJ_INVALID; @@ -303,9 +331,8 @@ PathRequest::parseJson(Json::Value const& jvParams) saSendMax.emplace(); if (!amountFromJsonNoThrow(*saSendMax, jvParams[jss::send_max]) || - (saSendMax->getCurrency().isZero() && saSendMax->getIssuer().isNonZero()) || - (saSendMax->getCurrency() == badCurrency()) || - (*saSendMax <= beast::zero && *saSendMax != STAmount(saSendMax->issue(), 1u, 0, true))) + !validAsset(saSendMax->asset()) || + (*saSendMax <= beast::zero && *saSendMax != STAmount(saSendMax->asset(), 1u, 0, true))) { jvStatus = rpcError(rpcSENDMAX_MALFORMED); return PFR_PJ_INVALID; @@ -322,45 +349,71 @@ PathRequest::parseJson(Json::Value const& jvParams) return PFR_PJ_INVALID; } - sciSourceCurrencies.clear(); + sciSourceAssets.clear(); for (auto const& c : jvSrcCurrencies) { - // Mandatory currency - Currency srcCurrencyID; - if (!c.isObject() || !c.isMember(jss::currency) || !c[jss::currency].isString() || - !to_currency(srcCurrencyID, c[jss::currency].asString())) + // Mandatory currency or MPT + if (!validJSONAsset(c) || !c.isObject()) { jvStatus = rpcError(rpcSRC_CUR_MALFORMED); return PFR_PJ_INVALID; } + PathAsset srcPathAsset; + if (c.isMember(jss::currency)) + { + Currency currency; + if (!c[jss::currency].isString() || + !to_currency(currency, c[jss::currency].asString())) + { + jvStatus = rpcError(rpcSRC_CUR_MALFORMED); + return PFR_PJ_INVALID; + } + srcPathAsset = currency; + } + else + { + uint192 u; + if (!c[jss::mpt_issuance_id].isString() || + !u.parseHex(c[jss::mpt_issuance_id].asString())) + { + jvStatus = rpcError(rpcSRC_CUR_MALFORMED); + return PFR_PJ_INVALID; + } + srcPathAsset = u; + } + // Optional issuer AccountID srcIssuerID; if (c.isMember(jss::issuer) && - (!c[jss::issuer].isString() || !to_issuer(srcIssuerID, c[jss::issuer].asString()))) + (c.isMember(jss::mpt_issuance_id) || !c[jss::issuer].isString() || + !to_issuer(srcIssuerID, c[jss::issuer].asString()))) { jvStatus = rpcError(rpcSRC_ISR_MALFORMED); return PFR_PJ_INVALID; } - if (srcCurrencyID.isZero()) + if (srcPathAsset.holds()) { - if (srcIssuerID.isNonZero()) + if (srcPathAsset.get().isZero()) { - jvStatus = rpcError(rpcSRC_CUR_MALFORMED); - return PFR_PJ_INVALID; + if (srcIssuerID.isNonZero()) + { + jvStatus = rpcError(rpcSRC_CUR_MALFORMED); + return PFR_PJ_INVALID; + } + } + else if (srcIssuerID.isZero()) + { + srcIssuerID = *raSrcAccount; } - } - else if (srcIssuerID.isZero()) - { - srcIssuerID = *raSrcAccount; } if (saSendMax) { - // If the currencies don't match, ignore the source currency. - if (srcCurrencyID == saSendMax->getCurrency()) + // If the assets don't match, ignore the source asset. + if (srcPathAsset == saSendMax->asset()) { // If neither is the source and they are not equal, then the // source issuer is illegal. @@ -373,23 +426,30 @@ PathRequest::parseJson(Json::Value const& jvParams) // If both are the source, use the source. // Otherwise, use the one that's not the source. - if (srcIssuerID != *raSrcAccount) - { - sciSourceCurrencies.insert({srcCurrencyID, srcIssuerID}); - } - else if (saSendMax->getIssuer() != *raSrcAccount) - { - sciSourceCurrencies.insert({srcCurrencyID, saSendMax->getIssuer()}); - } - else - { - sciSourceCurrencies.insert({srcCurrencyID, *raSrcAccount}); - } + srcPathAsset.visit( + [&](Currency const& currency) { + if (srcIssuerID != *raSrcAccount) + { + sciSourceAssets.insert(Issue{currency, srcIssuerID}); + } + else if (saSendMax->getIssuer() != *raSrcAccount) + { + sciSourceAssets.insert(Issue{currency, saSendMax->getIssuer()}); + } + { + sciSourceAssets.insert(Issue{currency, *raSrcAccount}); + } + }, + [&](MPTID const& mpt) { sciSourceAssets.insert(mpt); }); } } else { - sciSourceCurrencies.insert({srcCurrencyID, srcIssuerID}); + srcPathAsset.visit( + [&](Currency const& currency) { + sciSourceAssets.insert(Issue{currency, srcIssuerID}); + }, + [&](MPTID const& mpt) { sciSourceAssets.insert(MPTIssue{mpt}); }); } } } @@ -437,21 +497,21 @@ PathRequest::doAborting() const std::unique_ptr const& PathRequest::getPathFinder( - std::shared_ptr const& cache, - hash_map>& currency_map, - Currency const& currency, + std::shared_ptr const& cache, + hash_map>& pathasset_map, + PathAsset const& asset, STAmount const& dst_amount, int const level, std::function const& continueCallback) { - auto i = currency_map.find(currency); - if (i != currency_map.end()) + auto i = pathasset_map.find(asset); + if (i != pathasset_map.end()) return i->second; auto pathfinder = std::make_unique( cache, *raSrcAccount, *raDstAccount, - currency, + asset, std::nullopt, dst_amount, saSendMax, @@ -465,47 +525,63 @@ PathRequest::getPathFinder( { pathfinder.reset(); // It's a bad request - clear it. } - return currency_map[currency] = std::move(pathfinder); + return pathasset_map[asset] = std::move(pathfinder); } bool PathRequest::findPaths( - std::shared_ptr const& cache, + std::shared_ptr const& cache, int const level, Json::Value& jvArray, std::function const& continueCallback) { - auto sourceCurrencies = sciSourceCurrencies; - if (sourceCurrencies.empty() && saSendMax) + auto sourceAssets = sciSourceAssets; + if (sourceAssets.empty() && saSendMax) { - sourceCurrencies.insert(saSendMax->issue()); + sourceAssets.insert(saSendMax->asset()); } - if (sourceCurrencies.empty()) + if (sourceAssets.empty()) { - auto currencies = accountSourceCurrencies(*raSrcAccount, cache, true); + auto assets = accountSourceAssets(*raSrcAccount, cache, true); bool const sameAccount = *raSrcAccount == *raDstAccount; - for (auto const& c : currencies) + for (auto const& asset : assets) { - if (!sameAccount || c != saDstAmount.getCurrency()) + if (!std::visit( + [&](TAsset const& a) { + if (!sameAccount || a != saDstAmount.asset()) + { + if (sourceAssets.size() >= RPC::Tuning::max_auto_src_cur) + return false; + if constexpr (std::is_same_v) + { + sourceAssets.insert( + Issue{a, a.isZero() ? xrpAccount() : *raSrcAccount}); + } + else + { + sourceAssets.insert(MPTIssue{a}); + } + } + return true; + }, + asset.value())) { - if (sourceCurrencies.size() >= RPC::Tuning::max_auto_src_cur) - return false; - sourceCurrencies.insert({c, c.isZero() ? xrpAccount() : *raSrcAccount}); + return false; } } } auto const dst_amount = convertAmount(saDstAmount, convert_all_); - hash_map> currency_map; - for (auto const& issue : sourceCurrencies) + hash_map> pathasset_map; + for (auto const& asset : sourceAssets) { if (continueCallback && !continueCallback()) break; JLOG(m_journal.debug()) << iIdentifier - << " Trying to find paths: " << STAmount(issue, 1).getFullText(); + << " Trying to find paths: " << STAmount(asset, 1).getFullText(); auto& pathfinder = - getPathFinder(cache, currency_map, issue.currency, dst_amount, level, continueCallback); + getPathFinder(cache, pathasset_map, asset, dst_amount, level, continueCallback); if (!pathfinder) { JLOG(m_journal.debug()) << iIdentifier << " No paths found"; @@ -514,21 +590,28 @@ PathRequest::findPaths( STPath fullLiquidityPath; auto ps = pathfinder->getBestPaths( - max_paths_, fullLiquidityPath, mContext[issue], issue.account, continueCallback); - mContext[issue] = ps; + max_paths_, fullLiquidityPath, mContext[asset], asset.getIssuer(), continueCallback); + mContext[asset] = ps; auto const& sourceAccount = [&] { - if (!isXRP(issue.account)) - return issue.account; + if (!isXRP(asset.getIssuer())) + return asset.getIssuer(); - if (isXRP(issue.currency)) + if (isXRP(asset)) return xrpAccount(); return *raSrcAccount; }(); - STAmount const saMaxAmount = - saSendMax.value_or(STAmount(Issue{issue.currency, sourceAccount}, 1u, 0, true)); + STAmount const saMaxAmount = [&]() { + if (saSendMax) + return *saSendMax; + return asset.visit( + [&](Issue const& issue) { + return STAmount(Issue{issue.currency, sourceAccount}, 1u, 0, true); + }, + [](MPTIssue const& issue) { return STAmount(issue, 1u, 0, true); }); + }(); JLOG(m_journal.debug()) << iIdentifier << " Paths found, calling rippleCalc"; @@ -581,7 +664,8 @@ PathRequest::findPaths( if (rc.result() == tesSUCCESS) { Json::Value jvEntry(Json::objectValue); - rc.actualAmountIn.setIssuer(sourceAccount); + if (rc.actualAmountIn.holds()) + rc.actualAmountIn.get().account = sourceAccount; jvEntry[jss::source_amount] = rc.actualAmountIn.getJson(JsonOptions::none); jvEntry[jss::paths_computed] = ps.getJson(JsonOptions::none); @@ -607,14 +691,14 @@ PathRequest::findPaths( The minimum cost is 50 and the maximum is 400. The cost increases after four source currencies, 50 - (4 * 4) = 34. */ - int const size = sourceCurrencies.size(); + int const size = sourceAssets.size(); consumer_.charge({std::clamp((size * size) + 34, 50, 400), "path update"}); return true; } Json::Value PathRequest::doUpdate( - std::shared_ptr const& cache, + std::shared_ptr const& cache, bool fast, std::function const& continueCallback) { @@ -633,10 +717,10 @@ PathRequest::doUpdate( if (hasCompletion()) { // Old ripple_path_find API gives destination_currencies - auto& destCurrencies = (newStatus[jss::destination_currencies] = Json::arrayValue); - auto usCurrencies = accountDestCurrencies(*raDstAccount, cache, true); - for (auto const& c : usCurrencies) - destCurrencies.append(to_string(c)); + auto& destAssets = (newStatus[jss::destination_currencies] = Json::arrayValue); + auto const assets = accountDestAssets(*raDstAccount, cache, true); + for (auto const& asset : assets) + destAssets.append(to_string(asset)); } newStatus[jss::source_account] = toBase58(*raSrcAccount); diff --git a/src/xrpld/rpc/detail/PathRequest.h b/src/xrpld/rpc/detail/PathRequest.h index db173e307b..046c643a84 100644 --- a/src/xrpld/rpc/detail/PathRequest.h +++ b/src/xrpld/rpc/detail/PathRequest.h @@ -1,11 +1,12 @@ #pragma once +#include #include -#include #include #include #include +#include #include #include @@ -19,11 +20,11 @@ namespace xrpl { // A pathfinding request submitted by a client // The request issuer must maintain a strong pointer -class RippleLineCache; +class AssetCache; class PathRequestManager; // Return values from parseJson <0 = invalid, >0 = valid -#define PFR_PJ_INVALID -1 +#define PFR_PJ_INVALID (-1) #define PFR_PJ_NOCHANGE 0 class PathRequest final : public InfoSubRequest, @@ -56,7 +57,7 @@ public: PathRequestManager&, beast::Journal journal); - ~PathRequest(); + ~PathRequest() override; bool isNew(); @@ -68,7 +69,7 @@ public: updateComplete(); std::pair - doCreate(std::shared_ptr const&, Json::Value const&); + doCreate(std::shared_ptr const&, Json::Value const&); Json::Value doClose() override; @@ -80,7 +81,7 @@ public: // update jvStatus Json::Value doUpdate( - std::shared_ptr const&, + std::shared_ptr const&, bool fast, std::function const& continueCallback = {}); InfoSub::pointer @@ -90,13 +91,13 @@ public: private: bool - isValid(std::shared_ptr const& crCache); + isValid(std::shared_ptr const& crCache); std::unique_ptr const& getPathFinder( - std::shared_ptr const&, - hash_map>&, - Currency const&, + std::shared_ptr const&, + hash_map>&, + PathAsset const&, STAmount const&, int const, std::function const&); @@ -106,7 +107,7 @@ private: */ bool findPaths( - std::shared_ptr const&, + std::shared_ptr const&, int const, Json::Value&, std::function const&); @@ -134,8 +135,8 @@ private: STAmount saDstAmount; std::optional saSendMax; - std::set sciSourceCurrencies; - std::map mContext; + std::set sciSourceAssets; + std::map mContext; std::optional domain; diff --git a/src/xrpld/rpc/detail/PathRequestManager.cpp b/src/xrpld/rpc/detail/PathRequestManager.cpp index 4455e304e5..7508884be1 100644 --- a/src/xrpld/rpc/detail/PathRequestManager.cpp +++ b/src/xrpld/rpc/detail/PathRequestManager.cpp @@ -1,29 +1,44 @@ -#include -#include #include +#include +#include +#include +#include + +#include +#include #include +#include +#include #include #include #include +#include +#include #include +#include +#include +#include +#include +#include +#include namespace xrpl { -/** Get the current RippleLineCache, updating it if necessary. +/** Get the current AssetCache, updating it if necessary. Get the correct ledger to use. */ -std::shared_ptr -PathRequestManager::getLineCache(std::shared_ptr const& ledger, bool authoritative) +std::shared_ptr +PathRequestManager::getAssetCache(std::shared_ptr const& ledger, bool authoritative) { std::lock_guard const sl(mLock); - auto lineCache = lineCache_.lock(); + auto assetCache = assetCache_.lock(); - std::uint32_t const lineSeq = lineCache ? lineCache->getLedger()->seq() : 0; + std::uint32_t const lineSeq = assetCache ? assetCache->getLedger()->seq() : 0; std::uint32_t const lgrSeq = ledger->seq(); - JLOG(mJournal.debug()) << "getLineCache has cache for " << lineSeq << ", considering " + JLOG(mJournal.debug()) << "getAssetCache has cache for " << lineSeq << ", considering " << lgrSeq; if ((lineSeq == 0) || // no ledger @@ -31,14 +46,14 @@ PathRequestManager::getLineCache(std::shared_ptr const& ledger, (authoritative && ((lgrSeq + 8) < lineSeq)) || // we jumped way back for some reason (lgrSeq > (lineSeq + 8))) // we jumped way forward for some reason { - JLOG(mJournal.debug()) << "getLineCache creating new cache for " << lgrSeq; + JLOG(mJournal.debug()) << "getAssetCache creating new cache for " << lgrSeq; // Assign to the local before the member, because the member is a // weak_ptr, and will immediately discard it if there are no other // references. - lineCache_ = lineCache = - std::make_shared(ledger, app_.getJournal("RippleLineCache")); + assetCache_ = assetCache = + std::make_shared(ledger, app_.getJournal("AssetCache")); } - return lineCache; + return assetCache; } void @@ -47,13 +62,13 @@ PathRequestManager::updateAll(std::shared_ptr const& inLedger) auto event = app_.getJobQueue().makeLoadEvent(jtPATH_FIND, "PathRequest::updateAll"); std::vector requests; - std::shared_ptr cache; + std::shared_ptr cache; // Get the ledger and cache we should be using { std::lock_guard const sl(mLock); requests = requests_; - cache = getLineCache(inLedger, true); + cache = getAssetCache(inLedger, true); } bool newRequests = app_.getLedgerMaster().isNewPathRequest(); @@ -134,17 +149,16 @@ PathRequestManager::updateAll(std::shared_ptr const& inLedger) // Remove any dangling weak pointers or weak // pointers that refer to this path request. - auto ret = std::remove_if( - requests_.begin(), requests_.end(), [&removed, &request](auto const& wl) { - auto r = wl.lock(); + auto ret = std::ranges::remove_if(requests_, [&removed, &request](auto const& wl) { + auto r = wl.lock(); - if (r && r != request) - return false; - ++removed; - return true; - }); + if (r && r != request) + return false; + ++removed; + return true; + }); - requests_.erase(ret, requests_.end()); + requests_.erase(ret.begin(), ret.end()); } mustBreak = !newRequests && app_.getLedgerMaster().isNewPathRequest(); @@ -172,7 +186,7 @@ PathRequestManager::updateAll(std::shared_ptr const& inLedger) // Hold on to the line cache until after the lock is released, so it can // be destroyed outside of the lock - std::shared_ptr lastCache; + std::shared_ptr lastCache; { // Get the latest requests, cache, and ledger for next pass std::lock_guard const sl(mLock); @@ -181,7 +195,7 @@ PathRequestManager::updateAll(std::shared_ptr const& inLedger) break; requests = requests_; lastCache = cache; - cache = getLineCache(cache->getLedger(), false); + cache = getAssetCache(cache->getLedger(), false); } } while (!app_.getJobQueue().isStopping()); @@ -203,7 +217,7 @@ PathRequestManager::insertPathRequest(PathRequest::pointer const& req) // Insert after any older unserviced requests but before // any serviced requests - auto ret = std::find_if(requests_.begin(), requests_.end(), [](auto const& wl) { + auto ret = std::ranges::find_if(requests_, [](auto const& wl) { auto r = wl.lock(); // We come before handled requests @@ -222,7 +236,7 @@ PathRequestManager::makePathRequest( { auto req = std::make_shared(app_, subscriber, ++mLastIdentifier, *this, mJournal); - auto [valid, jvRes] = req->doCreate(getLineCache(inLedger, false), requestJson); + auto [valid, jvRes] = req->doCreate(getAssetCache(inLedger, false), requestJson); if (valid) { @@ -247,7 +261,7 @@ PathRequestManager::makeLegacyPathRequest( req = std::make_shared( app_, completion, consumer, ++mLastIdentifier, *this, mJournal); - auto [valid, jvRes] = req->doCreate(getLineCache(inLedger, false), request); + auto [valid, jvRes] = req->doCreate(getAssetCache(inLedger, false), request); if (!valid) { @@ -273,7 +287,7 @@ PathRequestManager::doLegacyPathRequest( std::shared_ptr const& inLedger, Json::Value const& request) { - auto cache = std::make_shared(inLedger, app_.getJournal("RippleLineCache")); + auto cache = std::make_shared(inLedger, app_.getJournal("AssetCache")); auto req = std::make_shared(app_, [] {}, consumer, ++mLastIdentifier, *this, mJournal); diff --git a/src/xrpld/rpc/detail/PathRequestManager.h b/src/xrpld/rpc/detail/PathRequestManager.h index 0f884de5f3..f76a52bc1c 100644 --- a/src/xrpld/rpc/detail/PathRequestManager.h +++ b/src/xrpld/rpc/detail/PathRequestManager.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -34,8 +35,8 @@ public: bool requestsPending() const; - std::shared_ptr - getLineCache(std::shared_ptr const& ledger, bool authoritative); + std::shared_ptr + getAssetCache(std::shared_ptr const& ledger, bool authoritative); // Create a new-style path request that pushes // updates to a subscriber @@ -89,8 +90,8 @@ private: // Track all requests std::vector requests_; - // Use a RippleLineCache - std::weak_ptr lineCache_; + // Use a AssetCache + std::weak_ptr assetCache_; std::atomic mLastIdentifier; diff --git a/src/xrpld/rpc/detail/Pathfinder.cpp b/src/xrpld/rpc/detail/Pathfinder.cpp index 4749caaccb..a31d4522a7 100644 --- a/src/xrpld/rpc/detail/Pathfinder.cpp +++ b/src/xrpld/rpc/detail/Pathfinder.cpp @@ -1,16 +1,46 @@ -#include #include + +#include +#include #include #include +#include +#include +#include #include +#include +#include +#include #include -#include +#include // IWYU pragma: keep +#include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* @@ -66,12 +96,16 @@ compareAccountCandidate( AccountCandidate const& first, AccountCandidate const& second) { - if (first.priority < second.priority) - return false; + // Primary sort key: priority descending + if (first.priority != second.priority) + return first.priority > second.priority; - if (first.account > second.account) - return true; + // Secondary sort key: account descending + if (first.account != second.account) + return first.account > second.account; + // Tertiary sort key (tie-breaker): (priority ^ seq) ascending + // Note: The primary and secondary keys are equal here. return (first.priority ^ seq) < (second.priority ^ seq); } @@ -134,15 +168,38 @@ pathTypeToString(Pathfinder::PathType const& type) STAmount smallestUsefulAmount(STAmount const& amount, int maxPaths) { - return divide(amount, STAmount(maxPaths + 2), amount.issue()); + return divide(amount, STAmount(maxPaths + 2), amount.asset()); } + +STAmount +amountFromPathAsset( + PathAsset const& pathAsset, + std::optional const& srcIssuer, + AccountID const& srcAccount) +{ + return pathAsset.visit( + [&](Currency const& currency) { + auto const& account = srcIssuer.value_or(isXRP(currency) ? xrpAccount() : srcAccount); + return STAmount(Issue{currency, account}, 1u, 0, true); + }, + [](MPTID const& mpt) { return STAmount(mpt, 1u, 0, true); }); +} + +Asset +assetFromPathAsset(PathAsset const& pathAsset, AccountID const& account) +{ + return pathAsset.visit( + [&](Currency const& currency) { return Asset{Issue{currency, account}}; }, + [](MPTID const& mpt) { return Asset{mpt}; }); +} + } // namespace Pathfinder::Pathfinder( - std::shared_ptr const& cache, + std::shared_ptr const& cache, AccountID const& uSrcAccount, AccountID const& uDstAccount, - Currency const& uSrcCurrency, + PathAsset const& uSrcPathAsset, std::optional const& uSrcIssuer, STAmount const& saDstAmount, std::optional const& srcAmount, @@ -152,24 +209,18 @@ Pathfinder::Pathfinder( , mDstAccount(uDstAccount) , mEffectiveDst(isXRP(saDstAmount.getIssuer()) ? uDstAccount : saDstAmount.getIssuer()) , mDstAmount(saDstAmount) - , mSrcCurrency(uSrcCurrency) + , mSrcPathAsset(uSrcPathAsset) , mSrcIssuer(uSrcIssuer) - , mSrcAmount(srcAmount.value_or(STAmount( - Issue{ - uSrcCurrency, - uSrcIssuer.value_or(isXRP(uSrcCurrency) ? xrpAccount() : uSrcAccount)}, - 1u, - 0, - true))) + , mSrcAmount(amountFromPathAsset(uSrcPathAsset, uSrcIssuer, uSrcAccount)) , convert_all_(convertAllCheck(mDstAmount)) , mDomain(domain) , mLedger(cache->getLedger()) - , mRLCache(cache) + , mAssetCache(cache) , app_(app) , j_(app.getJournal("Pathfinder")) { XRPL_ASSERT( - !uSrcIssuer || isXRP(uSrcCurrency) == isXRP(uSrcIssuer.value()), + !uSrcIssuer || uSrcPathAsset.isXRP() == isXRP(uSrcIssuer.value()), "xrpl::Pathfinder::Pathfinder : valid inputs"); } @@ -189,7 +240,7 @@ Pathfinder::findPaths(int searchLevel, std::function const& continue } if (mSrcAccount == mDstAccount && mDstAccount == mEffectiveDst && - mSrcCurrency == mDstAmount.getCurrency()) + mSrcPathAsset == mDstAmount.asset()) { // No need to send to same account with same currency. JLOG(j_.debug()) << "Tried to send to same issuer"; @@ -197,24 +248,24 @@ Pathfinder::findPaths(int searchLevel, std::function const& continue return false; } - if (mSrcAccount == mEffectiveDst && mSrcCurrency == mDstAmount.getCurrency()) + if (mSrcAccount == mEffectiveDst && mSrcPathAsset == mDstAmount.asset()) { // Default path might work, but any path would loop return true; } m_loadEvent = app_.getJobQueue().makeLoadEvent(jtPATH_FIND, "FindPath"); - auto currencyIsXRP = isXRP(mSrcCurrency); + auto currencyIsXRP = isXRP(mSrcPathAsset); bool const useIssuerAccount = mSrcIssuer && !currencyIsXRP && !isXRP(*mSrcIssuer); auto& account = useIssuerAccount ? *mSrcIssuer : mSrcAccount; auto issuer = currencyIsXRP ? AccountID() : account; - mSource = STPathElement(account, mSrcCurrency, issuer); + mSource = STPathElement(account, mSrcPathAsset, issuer); auto issuerString = mSrcIssuer ? to_string(*mSrcIssuer) : std::string("none"); JLOG(j_.trace()) << "findPaths>" << " mSrcAccount=" << mSrcAccount << " mDstAccount=" << mDstAccount << " mDstAmount=" << mDstAmount.getFullText() - << " mSrcCurrency=" << mSrcCurrency << " mSrcIssuer=" << issuerString; + << " mSrcPathAsset=" << mSrcPathAsset << " mSrcIssuer=" << issuerString; if (!mLedger) { @@ -222,8 +273,8 @@ Pathfinder::findPaths(int searchLevel, std::function const& continue return false; } - bool const bSrcXrp = isXRP(mSrcCurrency); - bool const bDstXrp = isXRP(mDstAmount.getCurrency()); + bool const bSrcXrp = isXRP(mSrcPathAsset); + bool const bDstXrp = isXRP(mDstAmount.asset()); if (!mLedger->exists(keylet::account(mSrcAccount))) { @@ -278,7 +329,7 @@ Pathfinder::findPaths(int searchLevel, std::function const& continue JLOG(j_.debug()) << "non-XRP to XRP payment"; paymentType = pt_nonXRP_to_XRP; } - else if (mSrcCurrency == mDstAmount.getCurrency()) + else if (mSrcPathAsset == mDstAmount.asset()) { // non-XRP -> non-XRP - Same currency JLOG(j_.debug()) << "non-XRP to non-XRP - same currency"; @@ -513,10 +564,8 @@ Pathfinder::rankPaths( // width of path // length of path // A better PathRank is lower, best are sorted to the beginning. - std::sort( - rankedPaths.begin(), - rankedPaths.end(), - [&](Pathfinder::PathRank const& a, Pathfinder::PathRank const& b) { + std::ranges::sort( + rankedPaths, [&](Pathfinder::PathRank const& a, Pathfinder::PathRank const& b) { // 1) Higher quality (lower cost) is better if (!convert_all_ && a.quality != b.quality) return a.quality < b.quality; @@ -550,7 +599,7 @@ Pathfinder::getBestPaths( XRPL_ASSERT( fullLiquidityPath.empty(), "xrpl::Pathfinder::getBestPaths : first empty path result"); - bool const issuerIsSender = isXRP(mSrcCurrency) || (srcIssuer == mSrcAccount); + bool const issuerIsSender = isXRP(mSrcPathAsset) || (srcIssuer == mSrcAccount); std::vector extraPathRanks; rankPaths(maxPaths, extraPaths, extraPathRanks, continueCallback); @@ -672,27 +721,27 @@ Pathfinder::getBestPaths( } bool -Pathfinder::issueMatchesOrigin(Issue const& issue) +Pathfinder::issueMatchesOrigin(Asset const& asset) { - bool const matchingCurrency = (issue.currency == mSrcCurrency); - bool const matchingAccount = isXRP(issue.currency) || - (mSrcIssuer && issue.account == mSrcIssuer) || issue.account == mSrcAccount; + bool const matchingAsset = (asset == mSrcPathAsset); + bool const matchingAccount = isXRP(asset) || (mSrcIssuer && asset.getIssuer() == mSrcIssuer) || + asset.getIssuer() == mSrcAccount; - return matchingCurrency && matchingAccount; + return matchingAsset && matchingAccount; } int Pathfinder::getPathsOut( - Currency const& currency, + PathAsset const& pathAsset, AccountID const& account, LineDirection direction, - bool isDstCurrency, + bool isDstAsset, AccountID const& dstAccount, std::function const& continueCallback) { - Issue const issue(currency, account); + Asset const asset = assetFromPathAsset(pathAsset, account); - auto [it, inserted] = mPathsOutCountMap.emplace(issue, 0); + auto [it, inserted] = mPathsOutCountMap.emplace(asset, 0); // If it was already present, return the stored number of paths if (!inserted) @@ -703,48 +752,87 @@ Pathfinder::getPathsOut( if (!sleAccount) return 0; - int const aFlags = sleAccount->getFieldU32(sfFlags); - bool const bAuthRequired = (aFlags & lsfRequireAuth) != 0; - bool const bFrozen = ((aFlags & lsfGlobalFreeze) != 0); + auto const aFlags = sleAccount->getFieldU32(sfFlags); + bool const bAuthRequired = [&]() { + if (pathAsset.holds()) + return (aFlags & lsfRequireAuth) != 0; + return !isTesSuccess(requireAuth(*mLedger, asset.get(), account)); + }(); + bool const bFrozen = [&]() { + if (pathAsset.holds()) + return (aFlags & lsfGlobalFreeze) != 0; + return isGlobalFrozen(*mLedger, asset.get()); + }(); int count = 0; if (!bFrozen) { - count = app_.getOrderBookDB().getBookSize(issue, mDomain); + count = app_.getOrderBookDB().getBookSize(asset, mDomain); - if (auto const lines = mRLCache->getRippleLines(account, direction)) - { - for (auto const& rspEntry : *lines) - { - if (currency != rspEntry.getLimit().getCurrency()) + asset.visit( + [&](Issue const&) { + if (auto const lines = mAssetCache->getRippleLines(account, direction)) { + for (auto const& rspEntry : *lines) + { + if (pathAsset.get() != rspEntry.getLimit().get().currency) + { + } + else if ( + rspEntry.getBalance() <= beast::zero && + (!rspEntry.getLimitPeer() || + -rspEntry.getBalance() >= rspEntry.getLimitPeer() || + (bAuthRequired && !rspEntry.getAuth()))) + { + } + else if (isDstAsset && dstAccount == rspEntry.getAccountIDPeer()) + { + count += 10000; // count a path to the destination extra + } + else if (rspEntry.getNoRipplePeer()) + { + // This probably isn't a useful path out + } + else if (rspEntry.getFreezePeer()) + { + // Not a useful path out + } + else + { + ++count; + } + } } - else if ( - rspEntry.getBalance() <= beast::zero && - (!rspEntry.getLimitPeer() || - -rspEntry.getBalance() >= rspEntry.getLimitPeer() || - (bAuthRequired && !rspEntry.getAuth()))) + }, + [&](MPTIssue const&) { + if (auto const mpts = mAssetCache->getMPTs(account)) { + for (auto const& mpt : *mpts) + { + if (pathAsset.get() != mpt.getMptID()) + { + } + else if (mpt.isZeroBalance() || mpt.isMaxedOut()) + { + } + else if (bAuthRequired) + { + } + else if (isDstAsset && dstAccount == getMPTIssuer(mpt)) + { + count += 10000; + } + else if (isIndividualFrozen(*mLedger, account, MPTIssue{mpt.getMptID()})) + { + } + else + { + ++count; + } + } } - else if (isDstCurrency && dstAccount == rspEntry.getAccountIDPeer()) - { - count += 10000; // count a path to the destination extra - } - else if (rspEntry.getNoRipplePeer()) - { - // This probably isn't a useful path out - } - else if (rspEntry.getFreezePeer()) - { - // Not a useful path out - } - else - { - ++count; - } - } - } + }); } it->second = count; return count; @@ -872,7 +960,7 @@ Pathfinder::isNoRippleOut(STPath const& currentPath) auto const& fromAccount = (currentPath.size() == 1) ? mSrcAccount : (currentPath.end() - 2)->getAccountID(); auto const& toAccount = endElement.getAccountID(); - return isNoRipple(fromAccount, toAccount, endElement.getCurrency()); + return endElement.hasCurrency() && isNoRipple(fromAccount, toAccount, endElement.getCurrency()); } void @@ -896,10 +984,10 @@ Pathfinder::addLink( std::function const& continueCallback) { auto const& pathEnd = currentPath.empty() ? mSource : currentPath.back(); - auto const& uEndCurrency = pathEnd.getCurrency(); + auto const& uEndPathAsset = pathEnd.getPathAsset(); auto const& uEndIssuer = pathEnd.getIssuerID(); auto const& uEndAccount = pathEnd.getAccountID(); - bool const bOnXRP = uEndCurrency.isZero(); + bool const bOnXRP = isXRP(uEndPathAsset); // Does pathfinding really need to get this to // a gateway (the issuer of the destination amount) @@ -930,25 +1018,38 @@ Pathfinder::addLink( if (sleEnd) { bool const bRequireAuth((sleEnd->getFieldU32(sfFlags) & lsfRequireAuth) != 0u); - bool const bIsEndCurrency(uEndCurrency == mDstAmount.getCurrency()); + bool const bIsEndAsset(uEndPathAsset == mDstAmount.asset()); bool const bIsNoRippleOut(isNoRippleOut(currentPath)); bool const bDestOnly((addFlags & afAC_LAST) != 0u); - if (auto const lines = mRLCache->getRippleLines( - uEndAccount, - bIsNoRippleOut ? LineDirection::incoming : LineDirection::outgoing)) - { - auto& rippleLines = *lines; + AccountCandidates candidates; - AccountCandidates candidates; - candidates.reserve(rippleLines.size()); + auto forAssets = [&](AssetType const& assets) { + candidates.reserve(assets.size()); - for (auto const& rs : rippleLines) + static bool constexpr isLine = + std::is_same_v>; + static bool constexpr isMPT = + std::is_same_v>; + + for (auto const& asset : assets) { if (continueCallback && !continueCallback()) return; - auto const& acct = rs.getAccountIDPeer(); - LineDirection const direction = rs.getDirectionPeer(); + auto const& acct = [&]() constexpr { + if constexpr (isLine) + return asset.getAccountIDPeer(); + // Unlike trustline, MPT is not bidirectional + if constexpr (isMPT) + return getMPTIssuer(asset); + }(); + auto const direction = [&]() constexpr -> LineDirection { + if constexpr (isLine) + return asset.getDirectionPeer(); + // incoming for MPT since MPT doesn't support + // rippling (see LineDirection comments) + return LineDirection::incoming; + }(); if (hasEffectiveDestination && (acct == mDstAccount)) { @@ -963,25 +1064,46 @@ Pathfinder::addLink( continue; } - if ((uEndCurrency == rs.getLimit().getCurrency()) && - !currentPath.hasSeen(acct, uEndCurrency, acct)) + auto const correctAsset = [&]() { + if constexpr (isLine) + { + return uEndPathAsset.get() == + asset.getLimit().template get().currency; + } + if constexpr (isMPT) + { + return uEndPathAsset.get() == asset.getMptID(); + } + }(); + auto checkAsset = [&]() { + if constexpr (isLine) + { + return ( + (asset.getBalance() <= beast::zero && + (!asset.getLimitPeer() || + -asset.getBalance() >= asset.getLimitPeer() || + (bRequireAuth && !asset.getAuth()))) || + (bIsNoRippleOut && asset.getNoRipple())); + } + if constexpr (isMPT) + { + return asset.isZeroBalance() || asset.isMaxedOut() || + requireAuth(*mLedger, MPTIssue{asset}, acct); + } + }; + + if (correctAsset && !currentPath.hasSeen(acct, uEndPathAsset, acct)) { // path is for correct currency and has not been // seen - if (rs.getBalance() <= beast::zero && - (!rs.getLimitPeer() || -rs.getBalance() >= rs.getLimitPeer() || - (bRequireAuth && !rs.getAuth()))) - { - // path has no credit - } - else if (bIsNoRippleOut && rs.getNoRipple()) + if (checkAsset()) { // Can't leave on this path } else if (bToDestination) { // destination is always worth trying - if (uEndCurrency == mDstAmount.getCurrency()) + if (uEndPathAsset == mDstAmount.asset()) { // this is a complete path if (!currentPath.empty()) @@ -1005,10 +1127,10 @@ Pathfinder::addLink( { // save this candidate int const out = getPathsOut( - uEndCurrency, + uEndPathAsset, acct, direction, - bIsEndCurrency, + bIsEndAsset, mEffectiveDst, continueCallback); if (out != 0) @@ -1016,40 +1138,56 @@ Pathfinder::addLink( } } } + }; - if (!candidates.empty()) + uEndPathAsset.visit( + [&](Currency const&) { + if (auto const lines = mAssetCache->getRippleLines( + uEndAccount, + bIsNoRippleOut ? LineDirection::incoming : LineDirection::outgoing)) + { + forAssets(*lines); + } + }, + [&](MPTID const&) { + if (auto const mpts = mAssetCache->getMPTs(uEndAccount)) + { + forAssets(*mpts); + } + }); + + if (!candidates.empty()) + { + std::ranges::sort( + candidates, + + std::bind( + compareAccountCandidate, + mLedger->seq(), + std::placeholders::_1, + std::placeholders::_2)); + + int count = candidates.size(); + // allow more paths from source + if ((count > 10) && (uEndAccount != mSrcAccount)) { - std::sort( - candidates.begin(), - candidates.end(), - std::bind( - compareAccountCandidate, - mLedger->seq(), - std::placeholders::_1, - std::placeholders::_2)); + count = 10; + } + else if (count > 50) + { + count = 50; + } - int count = candidates.size(); - // allow more paths from source - if ((count > 10) && (uEndAccount != mSrcAccount)) - { - count = 10; - } - else if (count > 50) - { - count = 50; - } - - auto it = candidates.begin(); - while (count-- != 0) - { - if (continueCallback && !continueCallback()) - return; - // Add accounts to incompletePaths - STPathElement const pathElement( - STPathElement::typeAccount, it->account, uEndCurrency, it->account); - incompletePaths.assembleAdd(currentPath, pathElement); - ++it; - } + auto it = candidates.begin(); + while (count-- != 0) + { + if (continueCallback && !continueCallback()) + return; + // Add accounts to incompletePaths + STPathElement const pathElement( + STPathElement::typeAccount, it->account, uEndPathAsset, it->account); + incompletePaths.assembleAdd(currentPath, pathElement); + ++it; } } } @@ -1065,7 +1203,9 @@ Pathfinder::addLink( if ((addFlags & afOB_XRP) != 0u) { // to XRP only - if (!bOnXRP && app_.getOrderBookDB().isBookToXRP({uEndCurrency, uEndIssuer}, mDomain)) + if (!bOnXRP && + app_.getOrderBookDB().isBookToXRP( + assetFromPathAsset(uEndPathAsset, uEndIssuer), mDomain)) { STPathElement const pathElement( STPathElement::typeCurrency, xrpAccount(), xrpCurrency(), xrpAccount()); @@ -1075,28 +1215,28 @@ Pathfinder::addLink( else { bool const bDestOnly = (addFlags & afOB_LAST) != 0; - auto books = - app_.getOrderBookDB().getBooksByTakerPays({uEndCurrency, uEndIssuer}, mDomain); + auto books = app_.getOrderBookDB().getBooksByTakerPays( + assetFromPathAsset(uEndPathAsset, uEndIssuer), mDomain); JLOG(j_.trace()) << books.size() << " books found from this currency/issuer"; for (auto const& book : books) { if (continueCallback && !continueCallback()) return; - if (!currentPath.hasSeen(xrpAccount(), book.out.currency, book.out.account) && + if (!currentPath.hasSeen(xrpAccount(), book.out, book.out.getIssuer()) && !issueMatchesOrigin(book.out) && - (!bDestOnly || (book.out.currency == mDstAmount.getCurrency()))) + (!bDestOnly || equalTokens(book.out, mDstAmount.asset()))) { STPath newPath(currentPath); - if (book.out.currency.isZero()) + if (isXRP(book.out)) { // to XRP // add the order book itself newPath.emplace_back( STPathElement::typeCurrency, xrpAccount(), xrpCurrency(), xrpAccount()); - if (mDstAmount.getCurrency().isZero()) + if (isXRP(mDstAmount.asset())) { // destination is XRP, add account and path is // complete @@ -1110,8 +1250,10 @@ Pathfinder::addLink( } } else if (!currentPath.hasSeen( - book.out.account, book.out.currency, book.out.account)) + book.out.getIssuer(), book.out, book.out.getIssuer())) { + auto const assetType = book.out.holds() ? STPathElement::typeCurrency + : STPathElement::typeMPT; // Don't want the book if we've already seen the issuer // book -> account -> book if ((newPath.size() >= 2) && (newPath.back().isAccount()) && @@ -1119,29 +1261,29 @@ Pathfinder::addLink( { // replace the redundant account with the order book newPath[newPath.size() - 1] = STPathElement( - STPathElement::typeCurrency | STPathElement::typeIssuer, + assetType | STPathElement::typeIssuer, xrpAccount(), - book.out.currency, - book.out.account); + book.out, + book.out.getIssuer()); } else { // add the order book newPath.emplace_back( - STPathElement::typeCurrency | STPathElement::typeIssuer, + assetType | STPathElement::typeIssuer, xrpAccount(), - book.out.currency, - book.out.account); + book.out, + book.out.getIssuer()); } - if (hasEffectiveDestination && book.out.account == mDstAccount && - book.out.currency == mDstAmount.getCurrency()) + if (hasEffectiveDestination && book.out.getIssuer() == mDstAccount && + equalTokens(book.out, mDstAmount.asset())) { // We skipped a required issuer } else if ( - book.out.account == mEffectiveDst && - book.out.currency == mDstAmount.getCurrency()) + book.out.getIssuer() == mEffectiveDst && + equalTokens(book.out, mDstAmount.asset())) { // with the destination account, this path is // complete JLOG(j_.trace()) << "complete path found ba: " @@ -1155,9 +1297,9 @@ Pathfinder::addLink( newPath, STPathElement( STPathElement::typeAccount, - book.out.account, - book.out.currency, - book.out.account)); + book.out.getIssuer(), + book.out, + book.out.getIssuer())); } } } diff --git a/src/xrpld/rpc/detail/Pathfinder.h b/src/xrpld/rpc/detail/Pathfinder.h index 662d59ac9a..964ec8c1d1 100644 --- a/src/xrpld/rpc/detail/Pathfinder.h +++ b/src/xrpld/rpc/detail/Pathfinder.h @@ -1,10 +1,12 @@ #pragma once -#include +#include +#include #include #include #include +#include #include #include @@ -21,10 +23,10 @@ class Pathfinder : public CountedObject public: /** Construct a pathfinder without an issuer.*/ Pathfinder( - std::shared_ptr const& cache, + std::shared_ptr const& cache, AccountID const& srcAccount, AccountID const& dstAccount, - Currency const& uSrcCurrency, + PathAsset const& uSrcPathAsset, std::optional const& uSrcIssuer, STAmount const& dstAmount, std::optional const& srcAmount, @@ -114,14 +116,14 @@ private: addPathsForType(PathType const& type, std::function const& continueCallback); bool - issueMatchesOrigin(Issue const&); + issueMatchesOrigin(Asset const&); int getPathsOut( - Currency const& currency, + PathAsset const& pathAsset, AccountID const& account, LineDirection direction, - bool isDestCurrency, + bool isDestPathAsset, AccountID const& dest, std::function const& continueCallback); @@ -170,7 +172,7 @@ private: AccountID mDstAccount; AccountID mEffectiveDst; // The account the paths need to end at STAmount mDstAmount; - Currency mSrcCurrency; + PathAsset mSrcPathAsset; std::optional mSrcIssuer; STAmount mSrcAmount; /** The amount remaining from mSrcAccount after the default liquidity has @@ -181,14 +183,14 @@ private: std::shared_ptr mLedger; std::unique_ptr m_loadEvent; - std::shared_ptr mRLCache; + std::shared_ptr mAssetCache; STPathElement mSource; STPathSet mCompletePaths; std::vector mPathRanks; std::map mPaths; - hash_map mPathsOutCountMap; + hash_map mPathsOutCountMap; Application& app_; beast::Journal const j_; diff --git a/src/xrpld/rpc/detail/PathfinderUtils.h b/src/xrpld/rpc/detail/PathfinderUtils.h index 560deca2d6..4f5566bdec 100644 --- a/src/xrpld/rpc/detail/PathfinderUtils.h +++ b/src/xrpld/rpc/detail/PathfinderUtils.h @@ -7,10 +7,13 @@ namespace xrpl { inline STAmount largestAmount(STAmount const& amt) { - if (amt.native()) - return INITIAL_XRP; - - return STAmount(amt.issue(), STAmount::cMaxValue, STAmount::cMaxOffset); + return amt.asset().visit( + [&](Issue const& issue) -> STAmount { + if (issue.native()) + return INITIAL_XRP; + return STAmount(amt.asset(), STAmount::cMaxValue, STAmount::cMaxOffset); + }, + [&](MPTIssue const&) { return STAmount(amt.asset(), maxMPTokenAmount, 0); }); } inline STAmount diff --git a/src/xrpld/rpc/detail/RPCCall.cpp b/src/xrpld/rpc/detail/RPCCall.cpp index f4613a3e72..749b66791e 100644 --- a/src/xrpld/rpc/detail/RPCCall.cpp +++ b/src/xrpld/rpc/detail/RPCCall.cpp @@ -1,32 +1,56 @@ #include + +#include #include #include #include +#include #include #include +#include #include #include +#include +#include +#include #include #include +#include #include #include +#include #include #include +#include #include #include #include -#include #include +#include #include +#include #include -#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include #include -#include +#include +#include +#include +#include #include +#include +#include namespace xrpl { @@ -118,7 +142,7 @@ private: if (!strIssuer.empty()) { - // Could confirm issuer is a valid Ripple address. + // Could confirm issuer is a valid XRPL address. jvResult[jss::issuer] = strIssuer; } @@ -1249,74 +1273,212 @@ public: // Request-response methods // - Returns an error, or the request. // - To modify the method, provide a new method in the request. - {"account_currencies", &RPCParser::parseAccountCurrencies, 1, 3}, - {"account_info", &RPCParser::parseAccountItems, 1, 3}, - {"account_lines", &RPCParser::parseAccountLines, 1, 5}, - {"account_channels", &RPCParser::parseAccountChannels, 1, 3}, - {"account_nfts", &RPCParser::parseAccountItems, 1, 5}, - {"account_objects", &RPCParser::parseAccountItems, 1, 5}, - {"account_offers", &RPCParser::parseAccountItems, 1, 4}, - {"account_tx", &RPCParser::parseAccountTransactions, 1, 8}, - {"amm_info", &RPCParser::parseAsIs, 1, 2}, - {"vault_info", &RPCParser::parseVault, 1, 2}, - {"book_changes", &RPCParser::parseLedgerId, 1, 1}, - {"book_offers", &RPCParser::parseBookOffers, 2, 7}, - {"can_delete", &RPCParser::parseCanDelete, 0, 1}, - {"channel_authorize", &RPCParser::parseChannelAuthorize, 3, 4}, - {"channel_verify", &RPCParser::parseChannelVerify, 4, 4}, - {"connect", &RPCParser::parseConnect, 1, 2}, - {"consensus_info", &RPCParser::parseAsIs, 0, 0}, - {"deposit_authorized", &RPCParser::parseDepositAuthorized, 2, 11}, - {"feature", &RPCParser::parseFeature, 0, 2}, - {"fetch_info", &RPCParser::parseFetchInfo, 0, 1}, - {"gateway_balances", &RPCParser::parseGatewayBalances, 1, -1}, - {"get_counts", &RPCParser::parseGetCounts, 0, 1}, - {"json", &RPCParser::parseJson, 2, 2}, - {"json2", &RPCParser::parseJson2, 1, 1}, - {"ledger", &RPCParser::parseLedger, 0, 2}, - {"ledger_accept", &RPCParser::parseAsIs, 0, 0}, - {"ledger_closed", &RPCParser::parseAsIs, 0, 0}, - {"ledger_current", &RPCParser::parseAsIs, 0, 0}, - {"ledger_entry", &RPCParser::parseLedgerEntry, 1, 2}, - {"ledger_header", &RPCParser::parseLedgerId, 1, 1}, - {"ledger_request", &RPCParser::parseLedgerId, 1, 1}, - {"log_level", &RPCParser::parseLogLevel, 0, 2}, - {"logrotate", &RPCParser::parseAsIs, 0, 0}, - {"manifest", &RPCParser::parseManifest, 1, 1}, - {"owner_info", &RPCParser::parseAccountItems, 1, 3}, - {"peers", &RPCParser::parseAsIs, 0, 0}, - {"ping", &RPCParser::parseAsIs, 0, 0}, - {"print", &RPCParser::parseAsIs, 0, 1}, + {.name = "account_currencies", + .parse = &RPCParser::parseAccountCurrencies, + .minParams = 1, + .maxParams = 3}, + {.name = "account_info", + .parse = &RPCParser::parseAccountItems, + .minParams = 1, + .maxParams = 3}, + {.name = "account_lines", + .parse = &RPCParser::parseAccountLines, + .minParams = 1, + .maxParams = 5}, + {.name = "account_channels", + .parse = &RPCParser::parseAccountChannels, + .minParams = 1, + .maxParams = 3}, + {.name = "account_nfts", + .parse = &RPCParser::parseAccountItems, + .minParams = 1, + .maxParams = 5}, + {.name = "account_objects", + .parse = &RPCParser::parseAccountItems, + .minParams = 1, + .maxParams = 5}, + {.name = "account_offers", + .parse = &RPCParser::parseAccountItems, + .minParams = 1, + .maxParams = 4}, + {.name = "account_tx", + .parse = &RPCParser::parseAccountTransactions, + .minParams = 1, + .maxParams = 8}, + {.name = "amm_info", .parse = &RPCParser::parseAsIs, .minParams = 1, .maxParams = 2}, + {.name = "vault_info", .parse = &RPCParser::parseVault, .minParams = 1, .maxParams = 2}, + {.name = "book_changes", + .parse = &RPCParser::parseLedgerId, + .minParams = 1, + .maxParams = 1}, + {.name = "book_offers", + .parse = &RPCParser::parseBookOffers, + .minParams = 2, + .maxParams = 7}, + {.name = "can_delete", + .parse = &RPCParser::parseCanDelete, + .minParams = 0, + .maxParams = 1}, + {.name = "channel_authorize", + .parse = &RPCParser::parseChannelAuthorize, + .minParams = 3, + .maxParams = 4}, + {.name = "channel_verify", + .parse = &RPCParser::parseChannelVerify, + .minParams = 4, + .maxParams = 4}, + {.name = "connect", .parse = &RPCParser::parseConnect, .minParams = 1, .maxParams = 2}, + {.name = "consensus_info", + .parse = &RPCParser::parseAsIs, + .minParams = 0, + .maxParams = 0}, + {.name = "deposit_authorized", + .parse = &RPCParser::parseDepositAuthorized, + .minParams = 2, + .maxParams = 11}, + {.name = "feature", .parse = &RPCParser::parseFeature, .minParams = 0, .maxParams = 2}, + {.name = "fetch_info", + .parse = &RPCParser::parseFetchInfo, + .minParams = 0, + .maxParams = 1}, + {.name = "gateway_balances", + .parse = &RPCParser::parseGatewayBalances, + .minParams = 1, + .maxParams = -1}, + {.name = "get_counts", + .parse = &RPCParser::parseGetCounts, + .minParams = 0, + .maxParams = 1}, + {.name = "json", .parse = &RPCParser::parseJson, .minParams = 2, .maxParams = 2}, + {.name = "json2", .parse = &RPCParser::parseJson2, .minParams = 1, .maxParams = 1}, + {.name = "ledger", .parse = &RPCParser::parseLedger, .minParams = 0, .maxParams = 2}, + {.name = "ledger_accept", + .parse = &RPCParser::parseAsIs, + .minParams = 0, + .maxParams = 0}, + {.name = "ledger_closed", + .parse = &RPCParser::parseAsIs, + .minParams = 0, + .maxParams = 0}, + {.name = "ledger_current", + .parse = &RPCParser::parseAsIs, + .minParams = 0, + .maxParams = 0}, + {.name = "ledger_entry", + .parse = &RPCParser::parseLedgerEntry, + .minParams = 1, + .maxParams = 2}, + {.name = "ledger_header", + .parse = &RPCParser::parseLedgerId, + .minParams = 1, + .maxParams = 1}, + {.name = "ledger_request", + .parse = &RPCParser::parseLedgerId, + .minParams = 1, + .maxParams = 1}, + {.name = "log_level", + .parse = &RPCParser::parseLogLevel, + .minParams = 0, + .maxParams = 2}, + {.name = "logrotate", .parse = &RPCParser::parseAsIs, .minParams = 0, .maxParams = 0}, + {.name = "manifest", + .parse = &RPCParser::parseManifest, + .minParams = 1, + .maxParams = 1}, + {.name = "owner_info", + .parse = &RPCParser::parseAccountItems, + .minParams = 1, + .maxParams = 3}, + {.name = "peers", .parse = &RPCParser::parseAsIs, .minParams = 0, .maxParams = 0}, + {.name = "ping", .parse = &RPCParser::parseAsIs, .minParams = 0, .maxParams = 0}, + {.name = "print", .parse = &RPCParser::parseAsIs, .minParams = 0, .maxParams = 1}, // { "profile", &RPCParser::parseProfile, 1, 9 // }, - {"random", &RPCParser::parseAsIs, 0, 0}, - {"peer_reservations_add", &RPCParser::parsePeerReservationsAdd, 1, 2}, - {"peer_reservations_del", &RPCParser::parsePeerReservationsDel, 1, 1}, - {"peer_reservations_list", &RPCParser::parseAsIs, 0, 0}, - {"ripple_path_find", &RPCParser::parseRipplePathFind, 1, 2}, - {"server_definitions", &RPCParser::parseServerDefinitions, 0, 1}, - {"server_info", &RPCParser::parseServerInfo, 0, 1}, - {"server_state", &RPCParser::parseServerInfo, 0, 1}, - {"sign", &RPCParser::parseSignSubmit, 2, 4}, - {"sign_for", &RPCParser::parseSignFor, 3, 4}, - {"stop", &RPCParser::parseAsIs, 0, 0}, - {"simulate", &RPCParser::parseSimulate, 1, 2}, - {"submit", &RPCParser::parseSignSubmit, 1, 4}, - {"submit_multisigned", &RPCParser::parseSubmitMultiSigned, 1, 1}, - {"transaction_entry", &RPCParser::parseTransactionEntry, 2, 2}, - {"tx", &RPCParser::parseTx, 1, 4}, - {"tx_history", &RPCParser::parseTxHistory, 1, 1}, - {"unl_list", &RPCParser::parseAsIs, 0, 0}, - {"validation_create", &RPCParser::parseValidationCreate, 0, 1}, - {"validator_info", &RPCParser::parseAsIs, 0, 0}, - {"version", &RPCParser::parseAsIs, 0, 0}, - {"wallet_propose", &RPCParser::parseWalletPropose, 0, 1}, - {"internal", &RPCParser::parseInternal, 1, -1}, + {.name = "random", .parse = &RPCParser::parseAsIs, .minParams = 0, .maxParams = 0}, + {.name = "peer_reservations_add", + .parse = &RPCParser::parsePeerReservationsAdd, + .minParams = 1, + .maxParams = 2}, + {.name = "peer_reservations_del", + .parse = &RPCParser::parsePeerReservationsDel, + .minParams = 1, + .maxParams = 1}, + {.name = "peer_reservations_list", + .parse = &RPCParser::parseAsIs, + .minParams = 0, + .maxParams = 0}, + {.name = "ripple_path_find", + .parse = &RPCParser::parseRipplePathFind, + .minParams = 1, + .maxParams = 2}, + {.name = "server_definitions", + .parse = &RPCParser::parseServerDefinitions, + .minParams = 0, + .maxParams = 1}, + {.name = "server_info", + .parse = &RPCParser::parseServerInfo, + .minParams = 0, + .maxParams = 1}, + {.name = "server_state", + .parse = &RPCParser::parseServerInfo, + .minParams = 0, + .maxParams = 1}, + {.name = "sign", .parse = &RPCParser::parseSignSubmit, .minParams = 2, .maxParams = 4}, + {.name = "sign_for", .parse = &RPCParser::parseSignFor, .minParams = 3, .maxParams = 4}, + {.name = "stop", .parse = &RPCParser::parseAsIs, .minParams = 0, .maxParams = 0}, + {.name = "simulate", + .parse = &RPCParser::parseSimulate, + .minParams = 1, + .maxParams = 2}, + {.name = "submit", + .parse = &RPCParser::parseSignSubmit, + .minParams = 1, + .maxParams = 4}, + {.name = "submit_multisigned", + .parse = &RPCParser::parseSubmitMultiSigned, + .minParams = 1, + .maxParams = 1}, + {.name = "transaction_entry", + .parse = &RPCParser::parseTransactionEntry, + .minParams = 2, + .maxParams = 2}, + {.name = "tx", .parse = &RPCParser::parseTx, .minParams = 1, .maxParams = 4}, + {.name = "tx_history", + .parse = &RPCParser::parseTxHistory, + .minParams = 1, + .maxParams = 1}, + {.name = "unl_list", .parse = &RPCParser::parseAsIs, .minParams = 0, .maxParams = 0}, + {.name = "validation_create", + .parse = &RPCParser::parseValidationCreate, + .minParams = 0, + .maxParams = 1}, + {.name = "validator_info", + .parse = &RPCParser::parseAsIs, + .minParams = 0, + .maxParams = 0}, + {.name = "version", .parse = &RPCParser::parseAsIs, .minParams = 0, .maxParams = 0}, + {.name = "wallet_propose", + .parse = &RPCParser::parseWalletPropose, + .minParams = 0, + .maxParams = 1}, + {.name = "internal", + .parse = &RPCParser::parseInternal, + .minParams = 1, + .maxParams = -1}, // Event methods - {"path_find", &RPCParser::parseEvented, -1, -1}, - {"subscribe", &RPCParser::parseEvented, -1, -1}, - {"unsubscribe", &RPCParser::parseEvented, -1, -1}, + {.name = "path_find", + .parse = &RPCParser::parseEvented, + .minParams = -1, + .maxParams = -1}, + {.name = "subscribe", + .parse = &RPCParser::parseEvented, + .minParams = -1, + .maxParams = -1}, + {.name = "unsubscribe", + .parse = &RPCParser::parseEvented, + .minParams = -1, + .maxParams = -1}, }; auto const count = jvParams.size(); @@ -1406,14 +1568,14 @@ struct RPCCallImp { Throw( "no response from server. Please " - "ensure that the rippled server is running in another " + "ensure that the xrpld server is running in another " "process."); } // Parse reply JLOG(j.debug()) << "RPC reply: " << strData << std::endl; - if (strData.find("Unable to parse request") == 0 || - strData.find(jss::invalid_API_version.c_str()) == 0) + if (strData.starts_with("Unable to parse request") || + strData.starts_with(jss::invalid_API_version.c_str())) Throw(strData); Json::Reader reader; Json::Value jvReply; @@ -1490,6 +1652,7 @@ rpcCmdToJson( } else if (jvRequest.isArray()) { + // NOLINTNEXTLINE(modernize-use-ranges) std::for_each(jvRequest.begin(), jvRequest.end(), insert_api_version); } @@ -1632,7 +1795,7 @@ rpcClient( // YYY We could have a command line flag for single line output for // scripts. YYY We would intercept output here and simplify it. } - catch (RequestNotParsable& e) + catch (RequestNotParsable const& e) { jvOutput = rpcError(rpcINVALID_PARAMS); jvOutput["error_what"] = e.what(); diff --git a/src/xrpld/rpc/detail/RPCHandler.cpp b/src/xrpld/rpc/detail/RPCHandler.cpp index 1d8e1168b4..cbd08a2677 100644 --- a/src/xrpld/rpc/detail/RPCHandler.cpp +++ b/src/xrpld/rpc/detail/RPCHandler.cpp @@ -1,29 +1,29 @@ -#include -#include -#include +#include + #include #include #include -#include #include +#include #include #include #include +#include #include #include -#include +#include // IWYU pragma: keep #include #include #include -#include -#include #include #include +#include +#include +#include -namespace xrpl { -namespace RPC { +namespace xrpl::RPC { namespace { @@ -234,5 +234,4 @@ roleRequired(unsigned int version, bool betaEnabled, std::string const& method) return handler->role_; } -} // namespace RPC -} // namespace xrpl +} // namespace xrpl::RPC diff --git a/src/xrpld/rpc/detail/RPCHelpers.cpp b/src/xrpld/rpc/detail/RPCHelpers.cpp index b4a0685bd6..1bf6d32bf9 100644 --- a/src/xrpld/rpc/detail/RPCHelpers.cpp +++ b/src/xrpld/rpc/detail/RPCHelpers.cpp @@ -1,22 +1,49 @@ -#include +#include + #include #include -#include -#include +#include +#include +#include -#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include #include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include -#include #include -namespace xrpl { -namespace RPC { +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl::RPC { std::uint64_t getStartHint(std::shared_ptr const& sle, AccountID const& accountID) @@ -114,10 +141,10 @@ readLimitField(unsigned int& limit, Tuning::LimitRange const& range, JsonContext } std::optional -parseRippleLibSeed(Json::Value const& value) +parseXrplLibSeed(Json::Value const& value) { - // ripple-lib encodes seed used to generate an Ed25519 wallet in a - // non-standard way. While rippled never encode seeds that way, we + // XrplLib encodes seed used to generate an Ed25519 wallet in a + // non-standard way. While xrpld never encode seeds that way, we // try to detect such keys to avoid user confusion. if (!value.isString()) return std::nullopt; @@ -258,14 +285,14 @@ keypairForSignature(Json::Value const& params, Json::Value& error, unsigned int } } - // ripple-lib encodes seed used to generate an Ed25519 wallet in a + // XrplLib encodes seed used to generate an Ed25519 wallet in a // non-standard way. While we never encode seeds that way, we try // to detect such keys to avoid user confusion. // using strcmp as pointers may not match (see // https://developercommunity.visualstudio.com/t/assigning-constexpr-char--to-static-cha/10021357?entry=problem) if (strcmp(secretType, jss::seed_hex.c_str()) != 0) { - seed = RPC::parseRippleLibSeed(params[secretType]); + seed = RPC::parseXrplLibSeed(params[secretType]); if (seed) { @@ -384,5 +411,63 @@ isAccountObjectsValidType(LedgerEntryType const& type) } } -} // namespace RPC -} // namespace xrpl +error_code_i +parseSubUnsubJson( + Asset& asset, + Json::Value const& params, + Json::StaticString const& name, + beast::Journal j) +{ + auto const& jv = params[name]; + auto const [issuerError, assetError] = [&]() { + if (name == jss::taker_pays) + return std::make_pair(rpcSRC_ISR_MALFORMED, rpcSRC_CUR_MALFORMED); + return std::make_pair(rpcDST_ISR_MALFORMED, rpcDST_AMT_MALFORMED); + }(); + + if (jv.isMember(jss::mpt_issuance_id) && + (jv.isMember(jss::currency) || jv.isMember(jss::issuer))) + { + JLOG(j.info()) << boost::format("Bad %s currency or MPT.") % name.c_str(); + return rpcINVALID_PARAMS; + } + + if (jv.isMember(jss::currency)) + { + Issue issue = xrpIssue(); + // Parse mandatory currency. + if (!jv.isMember(jss::currency) || + !to_currency(issue.currency, jv[jss::currency].asString())) + { + JLOG(j.info()) << boost::format("Bad %s currency.") % name.c_str(); + return assetError; + } + + // Parse optional issuer. + if (((jv.isMember(jss::issuer)) && + (!jv[jss::issuer].isString() || !to_issuer(issue.account, jv[jss::issuer].asString()))) + // Don't allow illegal issuers. + || (!issue.currency != !issue.account) || noAccount() == issue.account) + { + JLOG(j.info()) << boost::format("Bad %s issuer.") % name.c_str(); + return issuerError; + } + asset = issue; + } + else if (jv.isMember(jss::mpt_issuance_id)) + { + MPTID mptid; + if (!mptid.parseHex(jv[jss::mpt_issuance_id].asString())) + return assetError; + asset = mptid; + } + else + { + JLOG(j.info()) << boost::format("Neither %s currency or MPT is present.") % name.c_str(); + return assetError; + } + + return rpcSUCCESS; +} + +} // namespace xrpl::RPC diff --git a/src/xrpld/rpc/detail/RPCHelpers.h b/src/xrpld/rpc/detail/RPCHelpers.h index 54a976b78e..64f7d223a6 100644 --- a/src/xrpld/rpc/detail/RPCHelpers.h +++ b/src/xrpld/rpc/detail/RPCHelpers.h @@ -94,16 +94,16 @@ std::optional getSeedFromRPC(Json::Value const& params, Json::Value& error); /** - * @brief Parses a RippleLib seed from RPC parameters. + * @brief Parses a XrplLib seed from RPC parameters. * * Attempts to extract and return a Seed from the provided JSON parameters using - * RippleLib conventions. + * XrplLib conventions. * * @param params The JSON value containing RPC parameters. * @return An optional Seed if parsing is successful, or std::nullopt otherwise. */ std::optional -parseRippleLibSeed(Json::Value const& params); +parseXrplLibSeed(Json::Value const& params); /** * @brief Chooses the ledger entry type based on RPC parameters. @@ -154,6 +154,15 @@ keypairForSignature( Json::Value& error, unsigned int apiVersion = apiVersionIfUnspecified); +/** Parse subscribe/unsubscribe parameters + */ +error_code_i +parseSubUnsubJson( + Asset& asset, + Json::Value const& jv, + Json::StaticString const& name, + beast::Journal j); + } // namespace RPC } // namespace xrpl diff --git a/src/xrpld/rpc/detail/RPCLedgerHelpers.cpp b/src/xrpld/rpc/detail/RPCLedgerHelpers.cpp index 955533c776..0934289226 100644 --- a/src/xrpld/rpc/detail/RPCLedgerHelpers.cpp +++ b/src/xrpld/rpc/detail/RPCLedgerHelpers.cpp @@ -1,15 +1,32 @@ -#include -#include -#include -#include #include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include -#include +#include -namespace xrpl { -namespace RPC { +#include +#include + +namespace xrpl::RPC { namespace { @@ -475,5 +492,4 @@ getOrAcquireLedger(RPC::JsonContext const& context) RPC::make_error(rpcNOT_READY, "findCreate failed to return an inbound ledger")); } -} // namespace RPC -} // namespace xrpl +} // namespace xrpl::RPC diff --git a/src/xrpld/rpc/detail/RPCSub.cpp b/src/xrpld/rpc/detail/RPCSub.cpp index 3b5b56d937..f9cd860908 100644 --- a/src/xrpld/rpc/detail/RPCSub.cpp +++ b/src/xrpld/rpc/detail/RPCSub.cpp @@ -1,12 +1,28 @@ -#include #include +#include + #include #include #include -#include +#include +#include +#include +#include +#include // IWYU pragma: keep +#include +#include + +#include #include +#include +#include +#include +#include +#include +#include +#include namespace xrpl { @@ -19,15 +35,15 @@ public: boost::asio::io_context& io_context, JobQueue& jobQueue, std::string const& strUrl, - std::string const& strUsername, - std::string const& strPassword, + std::string strUsername, + std::string strPassword, ServiceRegistry& registry) : RPCSub(source) , m_io_context(io_context) , m_jobQueue(jobQueue) , mUrl(strUrl) - , mUsername(strUsername) - , mPassword(strPassword) + , mUsername(std::move(strUsername)) + , mPassword(std::move(strPassword)) , j_(registry.getJournal("RPCSub")) , logs_(registry.getLogs()) { @@ -63,7 +79,7 @@ public: << " ssl= " << (mSSL ? "yes" : "no") << " path='" << mPath << "'"; } - ~RPCSubImp() = default; + ~RPCSubImp() override = default; void send(Json::Value const& jvObj, bool broadcast) override @@ -73,7 +89,7 @@ public: auto jm = broadcast ? j_.debug() : j_.info(); JLOG(jm) << "RPCCall::fromNetwork push: " << jvObj; - mDeque.push_back(std::make_pair(mSeq++, jvObj)); + mDeque.emplace_back(mSeq++, jvObj); if (!mSending) { diff --git a/src/xrpld/rpc/detail/RippleLineCache.cpp b/src/xrpld/rpc/detail/RippleLineCache.cpp index 7cc77f9b8b..e69de29bb2 100644 --- a/src/xrpld/rpc/detail/RippleLineCache.cpp +++ b/src/xrpld/rpc/detail/RippleLineCache.cpp @@ -1,101 +0,0 @@ -#include -#include - -namespace xrpl { - -RippleLineCache::RippleLineCache(std::shared_ptr const& ledger, beast::Journal j) - : ledger_(ledger), journal_(j) -{ - JLOG(journal_.debug()) << "created for ledger " << ledger_->header().seq; -} - -RippleLineCache::~RippleLineCache() -{ - JLOG(journal_.debug()) << "destroyed for ledger " << ledger_->header().seq << " with " - << lines_.size() << " accounts and " << totalLineCount_ - << " distinct trust lines."; -} - -std::shared_ptr> -RippleLineCache::getRippleLines(AccountID const& accountID, LineDirection direction) -{ - auto const hash = hasher_(accountID); - AccountKey key(accountID, direction, hash); - AccountKey otherkey( - accountID, - direction == LineDirection::outgoing ? LineDirection::incoming : LineDirection::outgoing, - hash); - - std::lock_guard const sl(mLock); - - auto [it, inserted] = - [&]() { - if (auto otheriter = lines_.find(otherkey); otheriter != lines_.end()) - { - // The whole point of using the direction flag is to reduce the - // number of trust line objects held in memory. Ensure that there is - // only a single set of trustlines in the cache per account. - auto const size = otheriter->second ? otheriter->second->size() : 0; - JLOG(journal_.info()) - << "Request for " - << (direction == LineDirection::outgoing ? "outgoing" : "incoming") - << " trust lines for account " << accountID << " found " << size - << (direction == LineDirection::outgoing ? " incoming" : " outgoing") - << " trust lines. " - << (direction == LineDirection::outgoing ? "Deleting the subset of incoming" - : "Returning the superset of outgoing") - << " trust lines. "; - if (direction == LineDirection::outgoing) - { - // This request is for the outgoing set, but there is already a - // subset of incoming lines in the cache. Erase that subset - // to be replaced by the full set. The full set will be built - // below, and will be returned, if needed, on subsequent calls - // for either value of outgoing. - XRPL_ASSERT( - size <= totalLineCount_, - "xrpl::RippleLineCache::getRippleLines : maximum lines"); - totalLineCount_ -= size; - lines_.erase(otheriter); - } - else - { - // This request is for the incoming set, but there is - // already a superset of the outgoing trust lines in the cache. - // The path finding engine will disregard the non-rippling trust - // lines, so to prevent them from being stored twice, return the - // outgoing set. - key = otherkey; - return std::pair{otheriter, false}; - } - } - return lines_.emplace(key, nullptr); - }(); - - if (inserted) - { - XRPL_ASSERT(it->second == nullptr, "xrpl::RippleLineCache::getRippleLines : null lines"); - auto lines = PathFindTrustLine::getItems(accountID, *ledger_, direction); - if (!lines.empty()) - { - it->second = std::make_shared>(std::move(lines)); - totalLineCount_ += it->second->size(); - } - } - - XRPL_ASSERT( - !it->second || (!it->second->empty()), - "xrpl::RippleLineCache::getRippleLines : null or nonempty lines"); - auto const size = it->second ? it->second->size() : 0; - JLOG(journal_.trace()) << "getRippleLines for ledger " << ledger_->header().seq << " found " - << size - << (key.direction_ == LineDirection::outgoing ? " outgoing" - : " incoming") - << " lines for " << (inserted ? "new " : "existing ") << accountID - << " out of a total of " << lines_.size() << " accounts and " - << totalLineCount_ << " trust lines"; - - return it->second; -} - -} // namespace xrpl diff --git a/src/xrpld/rpc/detail/RippleLineCache.h b/src/xrpld/rpc/detail/RippleLineCache.h index 65607f2d25..e69de29bb2 100644 --- a/src/xrpld/rpc/detail/RippleLineCache.h +++ b/src/xrpld/rpc/detail/RippleLineCache.h @@ -1,101 +0,0 @@ -#pragma once - -#include - -#include -#include -#include - -#include -#include -#include - -namespace xrpl { - -// Used by Pathfinder -class RippleLineCache final : public CountedObject -{ -public: - explicit RippleLineCache(std::shared_ptr const& l, beast::Journal j); - ~RippleLineCache(); - - std::shared_ptr const& - getLedger() const - { - return ledger_; - } - - /** Find the trust lines associated with an account. - - @param accountID The account - @param direction Whether the account is an "outgoing" link on the path. - "Outgoing" is defined as the source account, or an account found via a - trustline that has rippling enabled on the @accountID's side. If an - account is "outgoing", all trust lines will be returned. If an account is - not "outgoing", then any trust lines that don't have rippling enabled are - not usable, so only return trust lines that have rippling enabled on - @accountID's side. - @return Returns a vector of the usable trust lines. - */ - std::shared_ptr> - getRippleLines(AccountID const& accountID, LineDirection direction); - -private: - std::mutex mLock; - - xrpl::hardened_hash<> hasher_; - std::shared_ptr ledger_; - - beast::Journal journal_; - - struct AccountKey final : public CountedObject - { - AccountID account_; - LineDirection direction_; - std::size_t hash_value_; - - AccountKey(AccountID const& account, LineDirection direction, std::size_t hash) - : account_(account), direction_(direction), hash_value_(hash) - { - } - - AccountKey(AccountKey const& other) = default; - - AccountKey& - operator=(AccountKey const& other) = default; - - bool - operator==(AccountKey const& lhs) const - { - return hash_value_ == lhs.hash_value_ && account_ == lhs.account_ && - direction_ == lhs.direction_; - } - - std::size_t - get_hash() const - { - return hash_value_; - } - - struct Hash - { - Hash() = default; - - std::size_t - operator()(AccountKey const& key) const noexcept - { - return key.get_hash(); - } - }; - }; - - // Use a shared_ptr so entries can be removed from the map safely. - // Even though a shared_ptr to a vector will take more memory just a vector, - // most accounts are not going to have any entries (estimated over 90%), so - // vectors will not need to be created for them. This should lead to far - // less memory usage overall. - hash_map>, AccountKey::Hash> lines_; - std::size_t totalLineCount_ = 0; -}; - -} // namespace xrpl diff --git a/src/xrpld/rpc/detail/Role.cpp b/src/xrpld/rpc/detail/Role.cpp index f832e43119..325b7eb3c6 100644 --- a/src/xrpld/rpc/detail/Role.cpp +++ b/src/xrpld/rpc/detail/Role.cpp @@ -1,9 +1,25 @@ #include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include #include -#include #include +#include +#include +#include +#include namespace xrpl { @@ -193,7 +209,7 @@ extractIpAddrFromField(std::string_view field) // We may have an IPv6 address in square brackets. Scan up to the // closing square bracket. - auto const closeBracket = std::find_if_not(ret.begin(), ret.end(), [](unsigned char c) { + auto const closeBracket = std::ranges::find_if_not(ret, [](unsigned char c) { return std::isxdigit(c) || c == ':' || c == '.' || c == ' '; }); @@ -213,8 +229,8 @@ extractIpAddrFromField(std::string_view field) // then there cannot be an appended port. In that case we're done. { // Skip any leading hex digits. - auto const colon = std::find_if_not( - ret.begin(), ret.end(), [](unsigned char c) { return std::isxdigit(c) || c == ' '; }); + auto const colon = std::ranges::find_if_not( + ret, [](unsigned char c) { return std::isxdigit(c) || c == ' '; }); // If the string starts with optional hex digits followed by a colon // it's an IVv6 address. We're done. diff --git a/src/xrpld/rpc/detail/ServerHandler.cpp b/src/xrpld/rpc/detail/ServerHandler.cpp index e5cc7a83bf..db6dad2f1c 100644 --- a/src/xrpld/rpc/detail/ServerHandler.cpp +++ b/src/xrpld/rpc/detail/ServerHandler.cpp @@ -1,38 +1,74 @@ +#include + #include #include #include #include #include -#include #include #include -#include +#include #include #include #include +#include #include #include +#include +#include #include +#include +#include #include +#include +#include #include #include +#include #include #include +#include +#include +#include +#include #include #include +#include +#include #include +#include #include +#include #include +#include #include -#include +#include +#include +#include +#include +#include #include +#include #include +#include +#include +#include +#include #include +#include +#include +#include +#include #include -#include +#include +#include +#include +#include +#include +#include namespace xrpl { @@ -74,7 +110,7 @@ authorized(Port const& port, std::map const& h) return true; auto const it = h.find("authorization"); - if ((it == h.end()) || (it->second.substr(0, 6) != "Basic ")) + if ((it == h.end()) || (!it->second.starts_with("Basic "))) return false; std::string strUserPass64 = it->second.substr(6); boost::trim(strUserPass64); @@ -241,9 +277,8 @@ build_map(boost::beast::http::fields const& h) // key cannot be a std::string_view because it needs to be used in // map and along with iterators std::string key(e.name_string()); - std::transform(key.begin(), key.end(), key.begin(), [](auto kc) { - return std::tolower(static_cast(kc)); - }); + std::ranges::transform( + key, key.begin(), [](auto kc) { return std::tolower(static_cast(kc)); }); c[key] = e.value(); } return c; @@ -439,18 +474,18 @@ ServerHandler::processSession( else { RPC::JsonContext context{ - {app_.getJournal("RPCHandler"), - app_, - loadType, - app_.getOPs(), - app_.getLedgerMaster(), - is->getConsumer(), - role, - coro, - is, - apiVersion}, + {.j = app_.getJournal("RPCHandler"), + .app = app_, + .loadType = loadType, + .netOps = app_.getOPs(), + .ledgerMaster = app_.getLedgerMaster(), + .consumer = is->getConsumer(), + .role = role, + .coro = coro, + .infoSub = is, + .apiVersion = apiVersion}, jv, - {is->user(), is->forwarded_for()}}; + {.user = is->user(), .forwardedFor = is->forwarded_for()}}; auto start = std::chrono::system_clock::now(); RPC::doCommand(context, jr[jss::result]); @@ -822,18 +857,18 @@ ServerHandler::processRequest( Resource::Charge loadType = Resource::feeReferenceRPC; RPC::JsonContext context{ - {m_journal, - app_, - loadType, - m_networkOPs, - app_.getLedgerMaster(), - usage, - role, - coro, - InfoSub::pointer(), - apiVersion}, + {.j = m_journal, + .app = app_, + .loadType = loadType, + .netOps = m_networkOPs, + .ledgerMaster = app_.getLedgerMaster(), + .consumer = usage, + .role = role, + .coro = coro, + .infoSub = InfoSub::pointer(), + .apiVersion = apiVersion}, params, - {user, forwardedFor}}; + {.user = user, .forwardedFor = forwardedFor}}; Json::Value result; auto start = std::chrono::system_clock::now(); @@ -1196,9 +1231,8 @@ setup_Client(ServerHandler::Setup& setup) static void setup_Overlay(ServerHandler::Setup& setup) { - auto const iter = std::find_if(setup.ports.cbegin(), setup.ports.cend(), [](Port const& port) { - return port.protocol.count("peer") != 0; - }); + auto const iter = std::ranges::find_if( + setup.ports, [](Port const& port) { return port.protocol.count("peer") != 0; }); if (iter == setup.ports.cend()) { setup.overlay = {}; diff --git a/src/xrpld/rpc/detail/Status.cpp b/src/xrpld/rpc/detail/Status.cpp index ce3082f0fa..c622b9a7e2 100644 --- a/src/xrpld/rpc/detail/Status.cpp +++ b/src/xrpld/rpc/detail/Status.cpp @@ -1,9 +1,15 @@ #include -#include +#include +#include +#include +#include +#include -namespace xrpl { -namespace RPC { +#include +#include + +namespace xrpl::RPC { std::string Status::codeString() const @@ -79,5 +85,4 @@ Status::toString() const return ""; } -} // namespace RPC -} // namespace xrpl +} // namespace xrpl::RPC diff --git a/src/xrpld/rpc/detail/TransactionSign.cpp b/src/xrpld/rpc/detail/TransactionSign.cpp index 23c70b9def..92b6d73050 100644 --- a/src/xrpld/rpc/detail/TransactionSign.cpp +++ b/src/xrpld/rpc/detail/TransactionSign.cpp @@ -1,31 +1,68 @@ +#include + #include #include #include #include #include +#include +#include #include #include #include -#include +#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include +#include +#include #include +#include +#include #include +#include +#include +#include +#include #include +#include +#include +#include +#include #include +#include +#include +#include +#include #include -#include +#include +#include +#include +#include +#include #include // Validity::Valid #include #include -#include +#include +#include +#include +#include #include +#include +#include +#include -namespace xrpl { -namespace RPC { +namespace xrpl::RPC { namespace detail { // Used to pass extra parameters used when returning a @@ -206,7 +243,10 @@ checkPayment( if (!dstAccountID) return RPC::invalid_field_error("tx_json.Destination"); - if (params.isMember(jss::build_path) && ((!doPath) || amount.holds())) + if (params.isMember(jss::build_path) && + (!doPath || + (!app.getOpenLedger().current()->rules().enabled(featureMPTokensV2) && + amount.holds()))) { return RPC::make_error( rpcINVALID_PARAMS, "Field 'build_path' not allowed in this context."); @@ -242,9 +282,11 @@ checkPayment( } else { - // If no SendMax, default to Amount with sender as issuer. + // If no SendMax, default to Amount with sender as issuer if Issue. sendMax = amount; - sendMax.setIssuer(srcAddressID); + sendMax.asset().visit( + [&](Issue const&) { sendMax.get().account = srcAddressID; }, + [](MPTIssue const&) {}); } if (sendMax.native() && amount.native()) @@ -260,11 +302,11 @@ checkPayment( if (auto ledger = app.getOpenLedger().current()) { Pathfinder pf( - std::make_shared(ledger, app.getJournal("RippleLineCache")), + std::make_shared(ledger, app.getJournal("AssetCache")), srcAddressID, *dstAccountID, - sendMax.issue().currency, - sendMax.issue().account, + sendMax.asset(), + sendMax.getIssuer(), amount, std::nullopt, domain, @@ -275,7 +317,7 @@ checkPayment( pf.computePathRanks(4); STPath fullLiquidityPath; STPathSet const paths; - result = pf.getBestPaths(4, fullLiquidityPath, paths, sendMax.issue().account); + result = pf.getBestPaths(4, fullLiquidityPath, paths, sendMax.getIssuer()); } } @@ -614,7 +656,7 @@ transactionPreProcessImpl( stTx = std::make_shared(std::move(parsed.object.value())); } - catch (STObject::FieldErr& err) + catch (STObject::FieldErr const& err) { return RPC::make_error(rpcINVALID_PARAMS, err.what()); } @@ -1063,15 +1105,14 @@ sortAndValidateSigners(STArray& signers, AccountID const& signingForID) return RPC::make_param_error("Signers array may not be empty."); // Signers must be sorted by Account. - std::sort(signers.begin(), signers.end(), [](STObject const& a, STObject const& b) { + std::ranges::sort(signers, [](STObject const& a, STObject const& b) { return (a[sfAccount] < b[sfAccount]); }); // Signers may not contain any duplicates. - auto const dupIter = std::adjacent_find( - signers.begin(), signers.end(), [](STObject const& a, STObject const& b) { - return (a[sfAccount] == b[sfAccount]); - }); + auto const dupIter = std::ranges::adjacent_find( + signers, + [](STObject const& a, STObject const& b) { return (a[sfAccount] == b[sfAccount]); }); if (dupIter != signers.end()) { @@ -1082,8 +1123,7 @@ sortAndValidateSigners(STArray& signers, AccountID const& signingForID) } // An account may not sign for itself. - if (signers.end() != - std::find_if(signers.begin(), signers.end(), [&signingForID](STObject const& elem) { + if (signers.end() != std::ranges::find_if(signers, [&signingForID](STObject const& elem) { return elem[sfAccount] == signingForID; })) { @@ -1286,7 +1326,7 @@ transactionSubmitMultiSigned( { stTx = std::make_shared(std::move(parsedTx_json.object.value())); } - catch (STObject::FieldErr& err) + catch (STObject::FieldErr const& err) { return RPC::make_error(rpcINVALID_PARAMS, err.what()); } @@ -1348,7 +1388,7 @@ transactionSubmitMultiSigned( return RPC::make_param_error("tx_json.Signers array may not be empty."); // The Signers array may only contain Signer objects. - if (std::find_if_not(signers.begin(), signers.end(), [](STObject const& obj) { + if (std::ranges::find_if_not(signers, [](STObject const& obj) { return ( // A Signer object always contains these fields and no // others. @@ -1385,5 +1425,4 @@ transactionSubmitMultiSigned( return transactionFormatResultImpl(txn.second, apiVersion); } -} // namespace RPC -} // namespace xrpl +} // namespace xrpl::RPC diff --git a/src/xrpld/rpc/detail/TransactionSign.h b/src/xrpld/rpc/detail/TransactionSign.h index 62a34d931e..8ffcb44a84 100644 --- a/src/xrpld/rpc/detail/TransactionSign.h +++ b/src/xrpld/rpc/detail/TransactionSign.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include diff --git a/src/xrpld/rpc/detail/TrustLine.cpp b/src/xrpld/rpc/detail/TrustLine.cpp index c2bc152448..5b7b555eca 100644 --- a/src/xrpld/rpc/detail/TrustLine.cpp +++ b/src/xrpld/rpc/detail/TrustLine.cpp @@ -1,9 +1,17 @@ #include +#include +#include #include +#include +#include +#include #include +#include #include +#include +#include namespace xrpl { diff --git a/src/xrpld/rpc/detail/TrustLine.h b/src/xrpld/rpc/detail/TrustLine.h index a0fce8847a..59fa2e73f3 100644 --- a/src/xrpld/rpc/detail/TrustLine.h +++ b/src/xrpld/rpc/detail/TrustLine.h @@ -32,6 +32,10 @@ enum class LineDirection : bool { incoming = false, outgoing = true }; */ class TrustLineBase { +public: + TrustLineBase& + operator=(TrustLineBase const&) = delete; + protected: // This class should not be instantiated directly. Use one of the derived // classes. @@ -39,8 +43,6 @@ protected: ~TrustLineBase() = default; TrustLineBase(TrustLineBase const&) = default; - TrustLineBase& - operator=(TrustLineBase const&) = delete; TrustLineBase(TrustLineBase&&) = default; public: @@ -69,25 +71,25 @@ public: bool getAuth() const { - return mFlags & (mViewLowest ? lsfLowAuth : lsfHighAuth); + return (mFlags & (mViewLowest ? lsfLowAuth : lsfHighAuth)) != 0u; } bool getAuthPeer() const { - return mFlags & (!mViewLowest ? lsfLowAuth : lsfHighAuth); + return (mFlags & (!mViewLowest ? lsfLowAuth : lsfHighAuth)) != 0u; } bool getNoRipple() const { - return mFlags & (mViewLowest ? lsfLowNoRipple : lsfHighNoRipple); + return (mFlags & (mViewLowest ? lsfLowNoRipple : lsfHighNoRipple)) != 0u; } bool getNoRipplePeer() const { - return mFlags & (!mViewLowest ? lsfLowNoRipple : lsfHighNoRipple); + return (mFlags & (!mViewLowest ? lsfLowNoRipple : lsfHighNoRipple)) != 0u; } LineDirection @@ -106,28 +108,28 @@ public: bool getFreeze() const { - return mFlags & (mViewLowest ? lsfLowFreeze : lsfHighFreeze); + return (mFlags & (mViewLowest ? lsfLowFreeze : lsfHighFreeze)) != 0u; } /** Have we set the deep freeze flag on our peer */ bool getDeepFreeze() const { - return mFlags & (mViewLowest ? lsfLowDeepFreeze : lsfHighDeepFreeze); + return (mFlags & (mViewLowest ? lsfLowDeepFreeze : lsfHighDeepFreeze)) != 0u; } /** Has the peer set the freeze flag on us */ bool getFreezePeer() const { - return mFlags & (!mViewLowest ? lsfLowFreeze : lsfHighFreeze); + return (mFlags & (!mViewLowest ? lsfLowFreeze : lsfHighFreeze)) != 0u; } /** Has the peer set the deep freeze flag on us */ bool getDeepFreezePeer() const { - return mFlags & (!mViewLowest ? lsfLowDeepFreeze : lsfHighDeepFreeze); + return (mFlags & (!mViewLowest ? lsfLowDeepFreeze : lsfHighDeepFreeze)) != 0u; } STAmount const& diff --git a/src/xrpld/rpc/detail/Tuning.h b/src/xrpld/rpc/detail/Tuning.h index e9dd2c37d7..eb3cfa0ebf 100644 --- a/src/xrpld/rpc/detail/Tuning.h +++ b/src/xrpld/rpc/detail/Tuning.h @@ -1,11 +1,8 @@ #pragma once -namespace xrpl { -namespace RPC { - /** Tuned constants. */ /** @{ */ -namespace Tuning { +namespace xrpl::RPC::Tuning { /** Represents RPC limit parameter values that have a min, default and max. */ struct LimitRange @@ -14,31 +11,31 @@ struct LimitRange }; /** Limits for the account_lines command. */ -static LimitRange constexpr accountLines = {10, 200, 400}; +static LimitRange constexpr accountLines = {.rmin = 10, .rDefault = 200, .rmax = 400}; /** Limits for the account_channels command. */ -static LimitRange constexpr accountChannels = {10, 200, 400}; +static LimitRange constexpr accountChannels = {.rmin = 10, .rDefault = 200, .rmax = 400}; /** Limits for the account_objects command. */ -static LimitRange constexpr accountObjects = {10, 200, 400}; +static LimitRange constexpr accountObjects = {.rmin = 10, .rDefault = 200, .rmax = 400}; /** Limits for the account_offers command. */ -static LimitRange constexpr accountOffers = {10, 200, 400}; +static LimitRange constexpr accountOffers = {.rmin = 10, .rDefault = 200, .rmax = 400}; /** Limits for the account_tx command. */ -static LimitRange constexpr accountTx = {10, 200, 400}; +static LimitRange constexpr accountTx = {.rmin = 10, .rDefault = 200, .rmax = 400}; /** Limits for the book_offers command. */ -static LimitRange constexpr bookOffers = {0, 60, 100}; +static LimitRange constexpr bookOffers = {.rmin = 1, .rDefault = 60, .rmax = 100}; /** Limits for the no_ripple_check command. */ -static LimitRange constexpr noRippleCheck = {10, 300, 400}; +static LimitRange constexpr noRippleCheck = {.rmin = 10, .rDefault = 300, .rmax = 400}; /** Limits for the account_nftokens command, in pages. */ -static LimitRange constexpr accountNFTokens = {20, 100, 400}; +static LimitRange constexpr accountNFTokens = {.rmin = 20, .rDefault = 100, .rmax = 400}; /** Limits for the nft_buy_offers & nft_sell_offers commands. */ -static LimitRange constexpr nftOffers = {50, 250, 500}; +static LimitRange constexpr nftOffers = {.rmin = 50, .rDefault = 250, .rmax = 500}; static int constexpr defaultAutoFillFeeMultiplier = 10; static int constexpr defaultAutoFillFeeDivisor = 1; @@ -55,7 +52,7 @@ static int constexpr binaryPageLength = 2048; static int constexpr jsonPageLength = 256; /** Maximum number of pages in a LedgerData response. */ -inline int constexpr pageLength(bool isBinary) +int constexpr pageLength(bool isBinary) { return isBinary ? binaryPageLength : jsonPageLength; } @@ -66,8 +63,5 @@ static int constexpr max_src_cur = 18; /** Maximum number of auto source currencies in a path find request. */ static int constexpr max_auto_src_cur = 88; -} // namespace Tuning +} // namespace xrpl::RPC::Tuning /** @} */ - -} // namespace RPC -} // namespace xrpl diff --git a/src/xrpld/rpc/handlers/BookOffers.cpp b/src/xrpld/rpc/handlers/BookOffers.cpp deleted file mode 100644 index ec53473821..0000000000 --- a/src/xrpld/rpc/handlers/BookOffers.cpp +++ /dev/null @@ -1,220 +0,0 @@ -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace xrpl { - -Json::Value -doBookOffers(RPC::JsonContext& context) -{ - // VFALCO TODO Here is a terrible place for this kind of business - // logic. It needs to be moved elsewhere and documented, - // and encapsulated into a function. - if (context.app.getJobQueue().getJobCountGE(jtCLIENT) > 200) - return rpcError(rpcTOO_BUSY); - - std::shared_ptr lpLedger; - auto jvResult = RPC::lookupLedger(lpLedger, context); - - if (!lpLedger) - return jvResult; - - if (!context.params.isMember(jss::taker_pays)) - return RPC::missing_field_error(jss::taker_pays); - - if (!context.params.isMember(jss::taker_gets)) - return RPC::missing_field_error(jss::taker_gets); - - Json::Value const& taker_pays = context.params[jss::taker_pays]; - Json::Value const& taker_gets = context.params[jss::taker_gets]; - - if (!taker_pays.isObjectOrNull()) - return RPC::object_field_error(jss::taker_pays); - - if (!taker_gets.isObjectOrNull()) - return RPC::object_field_error(jss::taker_gets); - - if (!taker_pays.isMember(jss::currency)) - return RPC::missing_field_error("taker_pays.currency"); - - if (!taker_pays[jss::currency].isString()) - return RPC::expected_field_error("taker_pays.currency", "string"); - - if (!taker_gets.isMember(jss::currency)) - return RPC::missing_field_error("taker_gets.currency"); - - if (!taker_gets[jss::currency].isString()) - return RPC::expected_field_error("taker_gets.currency", "string"); - - Currency pay_currency; - - if (!to_currency(pay_currency, taker_pays[jss::currency].asString())) - { - JLOG(context.j.info()) << "Bad taker_pays currency."; - return RPC::make_error( - rpcSRC_CUR_MALFORMED, "Invalid field 'taker_pays.currency', bad currency."); - } - - Currency get_currency; - - if (!to_currency(get_currency, taker_gets[jss::currency].asString())) - { - JLOG(context.j.info()) << "Bad taker_gets currency."; - return RPC::make_error( - rpcDST_AMT_MALFORMED, "Invalid field 'taker_gets.currency', bad currency."); - } - - AccountID pay_issuer; - - if (taker_pays.isMember(jss::issuer)) - { - if (!taker_pays[jss::issuer].isString()) - return RPC::expected_field_error("taker_pays.issuer", "string"); - - if (!to_issuer(pay_issuer, taker_pays[jss::issuer].asString())) - { - return RPC::make_error( - rpcSRC_ISR_MALFORMED, "Invalid field 'taker_pays.issuer', bad issuer."); - } - - if (pay_issuer == noAccount()) - { - return RPC::make_error( - rpcSRC_ISR_MALFORMED, "Invalid field 'taker_pays.issuer', bad issuer account one."); - } - } - else - { - pay_issuer = xrpAccount(); - } - - if (isXRP(pay_currency) && !isXRP(pay_issuer)) - { - return RPC::make_error( - rpcSRC_ISR_MALFORMED, - "Unneeded field 'taker_pays.issuer' for " - "XRP currency specification."); - } - - if (!isXRP(pay_currency) && isXRP(pay_issuer)) - { - return RPC::make_error( - rpcSRC_ISR_MALFORMED, "Invalid field 'taker_pays.issuer', expected non-XRP issuer."); - } - - AccountID get_issuer; - - if (taker_gets.isMember(jss::issuer)) - { - if (!taker_gets[jss::issuer].isString()) - return RPC::expected_field_error("taker_gets.issuer", "string"); - - if (!to_issuer(get_issuer, taker_gets[jss::issuer].asString())) - { - return RPC::make_error( - rpcDST_ISR_MALFORMED, "Invalid field 'taker_gets.issuer', bad issuer."); - } - - if (get_issuer == noAccount()) - { - return RPC::make_error( - rpcDST_ISR_MALFORMED, "Invalid field 'taker_gets.issuer', bad issuer account one."); - } - } - else - { - get_issuer = xrpAccount(); - } - - if (isXRP(get_currency) && !isXRP(get_issuer)) - { - return RPC::make_error( - rpcDST_ISR_MALFORMED, - "Unneeded field 'taker_gets.issuer' for " - "XRP currency specification."); - } - - if (!isXRP(get_currency) && isXRP(get_issuer)) - { - return RPC::make_error( - rpcDST_ISR_MALFORMED, "Invalid field 'taker_gets.issuer', expected non-XRP issuer."); - } - - std::optional takerID; - if (context.params.isMember(jss::taker)) - { - if (!context.params[jss::taker].isString()) - return RPC::expected_field_error(jss::taker, "string"); - - takerID = parseBase58(context.params[jss::taker].asString()); - if (!takerID) - return RPC::invalid_field_error(jss::taker); - } - - std::optional domain; - if (context.params.isMember(jss::domain)) - { - uint256 num; - if (!context.params[jss::domain].isString() || - !num.parseHex(context.params[jss::domain].asString())) - { - return RPC::make_error(rpcDOMAIN_MALFORMED, "Unable to parse domain."); - } - - domain = num; - } - - if (pay_currency == get_currency && pay_issuer == get_issuer) - { - JLOG(context.j.info()) << "taker_gets same as taker_pays."; - return RPC::make_error(rpcBAD_MARKET); - } - - unsigned int limit = 0; - if (auto err = readLimitField(limit, RPC::Tuning::bookOffers, context)) - return *err; - - bool const bProof(context.params.isMember(jss::proof)); - - Json::Value const jvMarker( - context.params.isMember(jss::marker) ? context.params[jss::marker] - : Json::Value(Json::nullValue)); - - context.netOps.getBookPage( - lpLedger, - {{pay_currency, pay_issuer}, {get_currency, get_issuer}, domain}, - takerID ? *takerID : beast::zero, - bProof, - limit, - jvMarker, - jvResult); - - context.loadType = Resource::feeMediumBurdenRPC; - - return jvResult; -} - -Json::Value -doBookChanges(RPC::JsonContext& context) -{ - std::shared_ptr ledger; - - Json::Value result = RPC::lookupLedger(ledger, context); - if (ledger == nullptr) - return result; - - return RPC::computeBookChanges(ledger); -} - -} // namespace xrpl diff --git a/src/xrpld/rpc/handlers/ChannelVerify.cpp b/src/xrpld/rpc/handlers/ChannelVerify.cpp new file mode 100644 index 0000000000..c1c7ab750b --- /dev/null +++ b/src/xrpld/rpc/handlers/ChannelVerify.cpp @@ -0,0 +1,80 @@ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace xrpl { + +// { +// public_key: +// channel_id: 256-bit channel id +// drops: 64-bit uint (as string) +// signature: signature to verify +// } +Json::Value +doChannelVerify(RPC::JsonContext& context) +{ + auto const& params(context.params); + for (auto const& p : {jss::public_key, jss::channel_id, jss::amount, jss::signature}) + { + if (!params.isMember(p)) + return RPC::missing_field_error(p); + } + + std::optional pk; + { + std::string const strPk = params[jss::public_key].asString(); + pk = parseBase58(TokenType::AccountPublic, strPk); + + if (!pk) + { + auto pkHex = strUnHex(strPk); + if (!pkHex) + return rpcError(rpcPUBLIC_MALFORMED); + auto const pkType = publicKeyType(makeSlice(*pkHex)); + if (!pkType) + return rpcError(rpcPUBLIC_MALFORMED); + pk.emplace(makeSlice(*pkHex)); + } + } + + uint256 channelId; + if (!channelId.parseHex(params[jss::channel_id].asString())) + return rpcError(rpcCHANNEL_MALFORMED); + + std::optional const optDrops = + params[jss::amount].isString() ? to_uint64(params[jss::amount].asString()) : std::nullopt; + + if (!optDrops) + return rpcError(rpcCHANNEL_AMT_MALFORMED); + + std::uint64_t const drops = *optDrops; + + auto sig = strUnHex(params[jss::signature].asString()); + if (!sig || sig->empty()) + return rpcError(rpcINVALID_PARAMS); + + Serializer msg; + serializePayChanAuthorization(msg, channelId, XRPAmount(drops)); + + Json::Value result; + result[jss::signature_verified] = verify(*pk, msg.slice(), makeSlice(*sig)); + return result; +} + +} // namespace xrpl diff --git a/src/xrpld/rpc/handlers/Handlers.h b/src/xrpld/rpc/handlers/Handlers.h index a5a96baa31..23328cf52a 100644 --- a/src/xrpld/rpc/handlers/Handlers.h +++ b/src/xrpld/rpc/handlers/Handlers.h @@ -1,6 +1,6 @@ #pragma once -#include +#include namespace xrpl { @@ -19,7 +19,7 @@ doAccountObjects(RPC::JsonContext&); Json::Value doAccountOffers(RPC::JsonContext&); Json::Value -doAccountTxJson(RPC::JsonContext&); +doAccountTx(RPC::JsonContext&); Json::Value doAMMInfo(RPC::JsonContext&); Json::Value diff --git a/src/xrpld/rpc/handlers/VaultInfo.cpp b/src/xrpld/rpc/handlers/VaultInfo.cpp index 4a704e0b0b..711478658a 100644 --- a/src/xrpld/rpc/handlers/VaultInfo.cpp +++ b/src/xrpld/rpc/handlers/VaultInfo.cpp @@ -1,12 +1,17 @@ #include #include +#include #include #include +#include #include #include +#include #include -#include + +#include +#include namespace xrpl { diff --git a/src/xrpld/rpc/handlers/AccountChannels.cpp b/src/xrpld/rpc/handlers/account/AccountChannels.cpp similarity index 89% rename from src/xrpld/rpc/handlers/AccountChannels.cpp rename to src/xrpld/rpc/handlers/account/AccountChannels.cpp index 7bf8a03f21..1fa492a3c0 100644 --- a/src/xrpld/rpc/handlers/AccountChannels.cpp +++ b/src/xrpld/rpc/handlers/account/AccountChannels.cpp @@ -3,15 +3,36 @@ #include #include +#include +#include +#include +#include +#include +#include #include -#include #include +#include #include +#include +#include #include #include +#include #include +#include #include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + namespace xrpl { void @@ -93,7 +114,7 @@ doAccountChannels(RPC::JsonContext& context) AccountID const& accountID; std::optional const& raDstAccount; }; - VisitData visitData = {{}, accountID, raDstAccount}; + VisitData visitData = {.items = {}, .accountID = accountID, .raDstAccount = raDstAccount}; visitData.items.reserve(limit); uint256 startAfter = beast::zero; std::uint64_t startHint = 0; diff --git a/src/xrpld/rpc/handlers/AccountCurrenciesHandler.cpp b/src/xrpld/rpc/handlers/account/AccountCurrencies.cpp similarity index 85% rename from src/xrpld/rpc/handlers/AccountCurrenciesHandler.cpp rename to src/xrpld/rpc/handlers/account/AccountCurrencies.cpp index e509a72862..dfcfffbf27 100644 --- a/src/xrpld/rpc/handlers/AccountCurrenciesHandler.cpp +++ b/src/xrpld/rpc/handlers/account/AccountCurrencies.cpp @@ -2,11 +2,20 @@ #include #include +#include #include +#include #include +#include #include +#include +#include #include +#include +#include +#include + namespace xrpl { Json::Value @@ -55,9 +64,9 @@ doAccountCurrencies(RPC::JsonContext& context) STAmount const& saBalance = rspEntry.getBalance(); if (saBalance < rspEntry.getLimit()) - receive.insert(saBalance.getCurrency()); + receive.insert(saBalance.get().currency); if ((-saBalance) < rspEntry.getLimitPeer()) - send.insert(saBalance.getCurrency()); + send.insert(saBalance.get().currency); } send.erase(badCurrency()); diff --git a/src/xrpld/rpc/handlers/AccountInfo.cpp b/src/xrpld/rpc/handlers/account/AccountInfo.cpp similarity index 95% rename from src/xrpld/rpc/handlers/AccountInfo.cpp rename to src/xrpld/rpc/handlers/account/AccountInfo.cpp index becaea8a51..019ff83def 100644 --- a/src/xrpld/rpc/handlers/AccountInfo.cpp +++ b/src/xrpld/rpc/handlers/account/AccountInfo.cpp @@ -1,18 +1,36 @@ #include #include #include -#include #include +#include +#include +#include +#include +#include #include #include #include +#include #include +#include #include -#include +#include +#include +#include +#include #include #include +#include + +#include +#include +#include +#include +#include +#include +#include namespace xrpl { diff --git a/src/xrpld/rpc/handlers/AccountLines.cpp b/src/xrpld/rpc/handlers/account/AccountLines.cpp similarity index 90% rename from src/xrpld/rpc/handlers/AccountLines.cpp rename to src/xrpld/rpc/handlers/account/AccountLines.cpp index 24ebfaa446..5a879f0cbe 100644 --- a/src/xrpld/rpc/handlers/AccountLines.cpp +++ b/src/xrpld/rpc/handlers/account/AccountLines.cpp @@ -4,13 +4,34 @@ #include #include +#include +#include +#include +#include +#include #include #include +#include #include +#include +#include #include +#include +#include +#include #include #include +#include +#include + +#include +#include +#include +#include +#include +#include + namespace xrpl { void @@ -28,7 +49,7 @@ addLine(Json::Value& jsonLines, RPCTrustLine const& line) // Amount reported is negative if other account holds current // account's IOUs. jPeer[jss::balance] = saBalance.getText(); - jPeer[jss::currency] = to_string(saBalance.issue().currency); + jPeer[jss::currency] = to_string(saBalance.get().currency); jPeer[jss::limit] = saLimit.getText(); jPeer[jss::limit_peer] = saLimitPeer.getText(); jPeer[jss::quality_in] = line.getQualityIn().value; @@ -117,7 +138,12 @@ doAccountLines(RPC::JsonContext& context) bool ignoreDefault; uint32_t foundCount; }; - VisitData visitData = {{}, accountID, raPeerAccount, ignoreDefault, 0}; + VisitData visitData = { + .items = {}, + .accountID = accountID, + .raPeerAccount = raPeerAccount, + .ignoreDefault = ignoreDefault, + .foundCount = 0}; uint256 startAfter = beast::zero; std::uint64_t startHint = 0; diff --git a/src/xrpld/rpc/handlers/account/AccountNFTs.cpp b/src/xrpld/rpc/handlers/account/AccountNFTs.cpp new file mode 100644 index 0000000000..605dd3b07c --- /dev/null +++ b/src/xrpld/rpc/handlers/account/AccountNFTs.cpp @@ -0,0 +1,168 @@ +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace xrpl { + +/** General RPC command that can retrieve objects in the account root. + { + account: + ledger_hash: // optional + ledger_index: // optional + type: // optional, defaults to all account objects types + limit: // optional + marker: // optional, resume previous query + } +*/ +Json::Value +doAccountNFTs(RPC::JsonContext& context) +{ + auto const& params = context.params; + if (!params.isMember(jss::account)) + return RPC::missing_field_error(jss::account); + + if (!params[jss::account].isString()) + return RPC::invalid_field_error(jss::account); + + auto id = parseBase58(params[jss::account].asString()); + if (!id) + { + return rpcError(rpcACT_MALFORMED); + } + + std::shared_ptr ledger; + auto result = RPC::lookupLedger(ledger, context); + if (ledger == nullptr) + return result; + auto const accountID{id.value()}; + + if (!ledger->exists(keylet::account(accountID))) + return rpcError(rpcACT_NOT_FOUND); + + unsigned int limit = 0; + if (auto err = readLimitField(limit, RPC::Tuning::accountNFTokens, context)) + return *err; + + uint256 marker; + bool const markerSet = params.isMember(jss::marker); + + if (markerSet) + { + auto const& m = params[jss::marker]; + if (!m.isString()) + return RPC::expected_field_error(jss::marker, "string"); + + if (!marker.parseHex(m.asString())) + return RPC::invalid_field_error(jss::marker); + } + + auto const first = keylet::nftpage(keylet::nftpage_min(accountID), marker); + auto const last = keylet::nftpage_max(accountID); + + auto cp = ledger->read( + Keylet(ltNFTOKEN_PAGE, ledger->succ(first.key, last.key.next()).value_or(last.key))); + + std::uint32_t cnt = 0; + auto& nfts = (result[jss::account_nfts] = Json::arrayValue); + + // Continue iteration from the current page: + bool pastMarker = marker.isZero(); + bool markerFound = false; + uint256 const maskedMarker = marker & nft::pageMask; + while (cp) + { + auto arr = cp->getFieldArray(sfNFTokens); + + for (auto const& o : arr) + { + // Scrolling past the marker gets weird. We need to look at + // a couple of conditions. + // + // 1. If the low 96-bits don't match, then we compare only + // against the low 96-bits, since that's what determines + // the sort order of the pages. + // + // 2. However, within one page there can be a number of + // NFTokenIDs that all have the same low 96 bits. If we're + // in that case then we need to compare against the full + // 256 bits. + uint256 const nftokenID = o[sfNFTokenID]; + uint256 const maskedNftokenID = nftokenID & nft::pageMask; + + if (!pastMarker) + { + if (maskedNftokenID < maskedMarker) + continue; + + if (maskedNftokenID == maskedMarker && nftokenID < marker) + continue; + + if (nftokenID == marker) + { + markerFound = true; + continue; + } + } + + if (markerSet && !markerFound) + return RPC::invalid_field_error(jss::marker); + + pastMarker = true; + + { + Json::Value& obj = nfts.append(o.getJson(JsonOptions::none)); + + // Pull out the components of the nft ID. + obj[sfFlags.jsonName] = nft::getFlags(nftokenID); + obj[sfIssuer.jsonName] = to_string(nft::getIssuer(nftokenID)); + obj[sfNFTokenTaxon.jsonName] = nft::toUInt32(nft::getTaxon(nftokenID)); + obj[jss::nft_serial] = nft::getSerial(nftokenID); + if (std::uint16_t const xferFee = {nft::getTransferFee(nftokenID)}) + obj[sfTransferFee.jsonName] = xferFee; + } + + if (++cnt == limit) + { + result[jss::limit] = limit; + result[jss::marker] = to_string(o.getFieldH256(sfNFTokenID)); + return result; + } + } + + if (auto npm = (*cp)[~sfNextPageMin]) + { + cp = ledger->read(Keylet(ltNFTOKEN_PAGE, *npm)); + } + else + { + cp = nullptr; + } + } + + if (markerSet && !markerFound) + return RPC::invalid_field_error(jss::marker); + + result[jss::account] = toBase58(accountID); + context.loadType = Resource::feeMediumBurdenRPC; + return result; +} + +} // namespace xrpl diff --git a/src/xrpld/rpc/handlers/AccountObjects.cpp b/src/xrpld/rpc/handlers/account/AccountObjects.cpp similarity index 64% rename from src/xrpld/rpc/handlers/AccountObjects.cpp rename to src/xrpld/rpc/handlers/account/AccountObjects.cpp index 12132122c1..5b172cd4c3 100644 --- a/src/xrpld/rpc/handlers/AccountObjects.cpp +++ b/src/xrpld/rpc/handlers/account/AccountObjects.cpp @@ -3,164 +3,29 @@ #include #include +#include +#include +#include #include +#include #include #include #include #include +#include #include #include #include -#include +#include +#include +#include +#include #include +#include namespace xrpl { -/** General RPC command that can retrieve objects in the account root. - { - account: - ledger_hash: // optional - ledger_index: // optional - type: // optional, defaults to all account objects types - limit: // optional - marker: // optional, resume previous query - } -*/ - -Json::Value -doAccountNFTs(RPC::JsonContext& context) -{ - auto const& params = context.params; - if (!params.isMember(jss::account)) - return RPC::missing_field_error(jss::account); - - if (!params[jss::account].isString()) - return RPC::invalid_field_error(jss::account); - - auto id = parseBase58(params[jss::account].asString()); - if (!id) - { - return rpcError(rpcACT_MALFORMED); - } - - std::shared_ptr ledger; - auto result = RPC::lookupLedger(ledger, context); - if (ledger == nullptr) - return result; - auto const accountID{id.value()}; - - if (!ledger->exists(keylet::account(accountID))) - return rpcError(rpcACT_NOT_FOUND); - - unsigned int limit = 0; - if (auto err = readLimitField(limit, RPC::Tuning::accountNFTokens, context)) - return *err; - - uint256 marker; - bool const markerSet = params.isMember(jss::marker); - - if (markerSet) - { - auto const& m = params[jss::marker]; - if (!m.isString()) - return RPC::expected_field_error(jss::marker, "string"); - - if (!marker.parseHex(m.asString())) - return RPC::invalid_field_error(jss::marker); - } - - auto const first = keylet::nftpage(keylet::nftpage_min(accountID), marker); - auto const last = keylet::nftpage_max(accountID); - - auto cp = ledger->read( - Keylet(ltNFTOKEN_PAGE, ledger->succ(first.key, last.key.next()).value_or(last.key))); - - std::uint32_t cnt = 0; - auto& nfts = (result[jss::account_nfts] = Json::arrayValue); - - // Continue iteration from the current page: - bool pastMarker = marker.isZero(); - bool markerFound = false; - uint256 const maskedMarker = marker & nft::pageMask; - while (cp) - { - auto arr = cp->getFieldArray(sfNFTokens); - - for (auto const& o : arr) - { - // Scrolling past the marker gets weird. We need to look at - // a couple of conditions. - // - // 1. If the low 96-bits don't match, then we compare only - // against the low 96-bits, since that's what determines - // the sort order of the pages. - // - // 2. However, within one page there can be a number of - // NFTokenIDs that all have the same low 96 bits. If we're - // in that case then we need to compare against the full - // 256 bits. - uint256 const nftokenID = o[sfNFTokenID]; - uint256 const maskedNftokenID = nftokenID & nft::pageMask; - - if (!pastMarker) - { - if (maskedNftokenID < maskedMarker) - continue; - - if (maskedNftokenID == maskedMarker && nftokenID < marker) - continue; - - if (nftokenID == marker) - { - markerFound = true; - continue; - } - } - - if (markerSet && !markerFound) - return RPC::invalid_field_error(jss::marker); - - pastMarker = true; - - { - Json::Value& obj = nfts.append(o.getJson(JsonOptions::none)); - - // Pull out the components of the nft ID. - obj[sfFlags.jsonName] = nft::getFlags(nftokenID); - obj[sfIssuer.jsonName] = to_string(nft::getIssuer(nftokenID)); - obj[sfNFTokenTaxon.jsonName] = nft::toUInt32(nft::getTaxon(nftokenID)); - obj[jss::nft_serial] = nft::getSerial(nftokenID); - if (std::uint16_t const xferFee = {nft::getTransferFee(nftokenID)}) - obj[sfTransferFee.jsonName] = xferFee; - } - - if (++cnt == limit) - { - result[jss::limit] = limit; - result[jss::marker] = to_string(o.getFieldH256(sfNFTokenID)); - return result; - } - } - - if (auto npm = (*cp)[~sfNextPageMin]) - { - cp = ledger->read(Keylet(ltNFTOKEN_PAGE, *npm)); - } - else - { - cp = nullptr; - } - } - - if (markerSet && !markerFound) - return RPC::invalid_field_error(jss::marker); - - result[jss::account] = toBase58(accountID); - context.loadType = Resource::feeMediumBurdenRPC; - return result; -} - /** Gathers all objects for an account in a ledger. @param ledger Ledger to search account objects. @param account AccountID to find objects for. @@ -186,7 +51,7 @@ getAccountObjects( auto typeMatchesFilter = [](std::vector const& typeFilter, LedgerEntryType ledgerType) { - auto it = std::find(typeFilter.begin(), typeFilter.end(), ledgerType); + auto it = std::ranges::find(typeFilter, ledgerType); return it != typeFilter.end(); }; @@ -397,18 +262,19 @@ doAccountObjects(RPC::JsonContext& context) Json::StaticString name; LedgerEntryType type; } static constexpr deletionBlockers[] = { - {jss::check, ltCHECK}, - {jss::escrow, ltESCROW}, - {jss::nft_page, ltNFTOKEN_PAGE}, - {jss::payment_channel, ltPAYCHAN}, - {jss::state, ltRIPPLE_STATE}, - {jss::xchain_owned_claim_id, ltXCHAIN_OWNED_CLAIM_ID}, - {jss::xchain_owned_create_account_claim_id, ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID}, - {jss::bridge, ltBRIDGE}, - {jss::mpt_issuance, ltMPTOKEN_ISSUANCE}, - {jss::mptoken, ltMPTOKEN}, - {jss::permissioned_domain, ltPERMISSIONED_DOMAIN}, - {jss::vault, ltVAULT}, + {.name = jss::check, .type = ltCHECK}, + {.name = jss::escrow, .type = ltESCROW}, + {.name = jss::nft_page, .type = ltNFTOKEN_PAGE}, + {.name = jss::payment_channel, .type = ltPAYCHAN}, + {.name = jss::state, .type = ltRIPPLE_STATE}, + {.name = jss::xchain_owned_claim_id, .type = ltXCHAIN_OWNED_CLAIM_ID}, + {.name = jss::xchain_owned_create_account_claim_id, + .type = ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID}, + {.name = jss::bridge, .type = ltBRIDGE}, + {.name = jss::mpt_issuance, .type = ltMPTOKEN_ISSUANCE}, + {.name = jss::mptoken, .type = ltMPTOKEN}, + {.name = jss::permissioned_domain, .type = ltPERMISSIONED_DOMAIN}, + {.name = jss::vault, .type = ltVAULT}, }; typeFilter.emplace(); diff --git a/src/xrpld/rpc/handlers/AccountOffers.cpp b/src/xrpld/rpc/handlers/account/AccountOffers.cpp similarity index 90% rename from src/xrpld/rpc/handlers/AccountOffers.cpp rename to src/xrpld/rpc/handlers/account/AccountOffers.cpp index 38cc7c1dc5..19b88e7499 100644 --- a/src/xrpld/rpc/handlers/AccountOffers.cpp +++ b/src/xrpld/rpc/handlers/account/AccountOffers.cpp @@ -3,15 +3,33 @@ #include #include +#include +#include +#include +#include #include #include -#include #include +#include #include +#include +#include #include +#include +#include #include #include +#include +#include + +#include +#include +#include +#include +#include +#include + namespace xrpl { void diff --git a/src/xrpld/rpc/handlers/AccountTx.cpp b/src/xrpld/rpc/handlers/account/AccountTx.cpp similarity index 94% rename from src/xrpld/rpc/handlers/AccountTx.cpp rename to src/xrpld/rpc/handlers/account/AccountTx.cpp index f46a71308c..c01fc044e7 100644 --- a/src/xrpld/rpc/handlers/AccountTx.cpp +++ b/src/xrpld/rpc/handlers/account/AccountTx.cpp @@ -7,19 +7,35 @@ #include #include #include +#include #include #include #include +#include +#include +#include +#include +#include #include #include +#include #include +#include #include #include -#include +#include #include +#include #include +#include +#include +#include +#include +#include +#include + namespace xrpl { using TxnsData = RelationalDatabase::AccountTxs; @@ -57,7 +73,7 @@ parseLedgerArgs(RPC::Context& context, Json::Value const& params) ? params[jss::ledger_index_max].asUInt() : UINT32_MAX; - return LedgerRange{min, max}; + return LedgerRange{.min = min, .max = max}; } if (params.isMember(jss::ledger_hash)) { @@ -189,7 +205,7 @@ getLedgerRange(RPC::Context& context, std::optional const& ledg if (status) return status; } - return LedgerRange{uLedgerMin, uLedgerMax}; + return LedgerRange{.min = uLedgerMin, .max = uLedgerMax}; } std::pair @@ -211,7 +227,11 @@ doAccountTxHelp(RPC::Context& context, AccountTxArgs const& args) result.marker = args.marker; RelationalDatabase::AccountTxPageOptions const options = { - args.account, result.ledgerRange, result.marker, args.limit, isUnlimited(context.role)}; + .account = args.account, + .ledgerRange = result.ledgerRange, + .marker = result.marker, + .limit = args.limit, + .bAdmin = isUnlimited(context.role)}; auto& db = context.app.getRelationalDatabase(); @@ -364,7 +384,7 @@ populateJsonResponse( // resume previous query // } Json::Value -doAccountTxJson(RPC::JsonContext& context) +doAccountTx(RPC::JsonContext& context) { if (!context.app.config().useTxTables()) return rpcError(rpcNOT_ENABLED); @@ -426,7 +446,8 @@ doAccountTxJson(RPC::JsonContext& context) status.inject(response); return response; } - args.marker = {token[jss::ledger].asUInt(), token[jss::seq].asUInt()}; + args.marker = { + .ledgerSeq = token[jss::ledger].asUInt(), .txnSeq = token[jss::seq].asUInt()}; } auto res = doAccountTxHelp(context, args); diff --git a/src/xrpld/rpc/handlers/GatewayBalances.cpp b/src/xrpld/rpc/handlers/account/GatewayBalances.cpp similarity index 91% rename from src/xrpld/rpc/handlers/GatewayBalances.cpp rename to src/xrpld/rpc/handlers/account/GatewayBalances.cpp index 20679e5f42..81e464ba87 100644 --- a/src/xrpld/rpc/handlers/GatewayBalances.cpp +++ b/src/xrpld/rpc/handlers/account/GatewayBalances.cpp @@ -3,14 +3,30 @@ #include #include +#include +#include #include #include #include #include +#include +#include +#include #include +#include +#include +#include #include #include +#include +#include +#include +#include +#include +#include +#include + namespace xrpl { // Query: @@ -136,7 +152,7 @@ doGatewayBalances(RPC::JsonContext& context) if (escrow.holds()) return; - auto& bal = locked[escrow.getCurrency()]; + auto& bal = locked[escrow.get().currency]; if (bal == beast::zero) { // This is needed to set the currency code correctly @@ -154,7 +170,7 @@ doGatewayBalances(RPC::JsonContext& context) // On overflow return the largest valid STAmount. // Very large sums of STAmount are approximations // anyway. - bal = STAmount(bal.issue(), STAmount::cMaxValue, STAmount::cMaxOffset); + bal = STAmount(bal.get(), STAmount::cMaxValue, STAmount::cMaxOffset); } } } @@ -192,7 +208,7 @@ doGatewayBalances(RPC::JsonContext& context) else { // normal negative balance, obligation to customer - auto& bal = sums[rs->getBalance().getCurrency()]; + auto& bal = sums[rs->getBalance().get().currency]; if (bal == beast::zero) { // This is needed to set the currency code correctly @@ -210,7 +226,7 @@ doGatewayBalances(RPC::JsonContext& context) // On overflow return the largest valid STAmount. // Very large sums of STAmount are approximations // anyway. - bal = STAmount(bal.issue(), STAmount::cMaxValue, STAmount::cMaxOffset); + bal = STAmount(bal.asset(), STAmount::cMaxValue, STAmount::cMaxOffset); } } } @@ -239,7 +255,7 @@ doGatewayBalances(RPC::JsonContext& context) for (auto const& balance : accBalances) { Json::Value entry; - entry[jss::currency] = to_string(balance.issue().currency); + entry[jss::currency] = to_string(balance.get().currency); entry[jss::value] = balance.getText(); balanceArray.append(std::move(entry)); } diff --git a/src/xrpld/rpc/handlers/NoRippleCheck.cpp b/src/xrpld/rpc/handlers/account/NoRippleCheck.cpp similarity index 93% rename from src/xrpld/rpc/handlers/NoRippleCheck.cpp rename to src/xrpld/rpc/handlers/account/NoRippleCheck.cpp index d00a3e279b..54964be8da 100644 --- a/src/xrpld/rpc/handlers/NoRippleCheck.cpp +++ b/src/xrpld/rpc/handlers/account/NoRippleCheck.cpp @@ -2,17 +2,26 @@ #include #include #include -#include #include +#include +#include #include #include +#include #include +#include +#include #include +#include #include +#include #include #include +#include +#include + namespace xrpl { static void @@ -155,14 +164,14 @@ doNoRippleCheck(RPC::JsonContext& context) ownedItem->getFieldAmount(bLow ? sfHighLimit : sfLowLimit).getIssuer(); STAmount const peerLimit = ownedItem->getFieldAmount(bLow ? sfHighLimit : sfLowLimit); - problem += to_string(peerLimit.getCurrency()); + problem += to_string(peerLimit.get().currency); problem += " line to "; problem += to_string(peerLimit.getIssuer()); problems.append(problem); STAmount limitAmount( ownedItem->getFieldAmount(bLow ? sfLowLimit : sfHighLimit)); - limitAmount.setIssuer(peer); + limitAmount.get().account = peer; Json::Value& tx = jvTransactions.append(Json::objectValue); tx["TransactionType"] = jss::TrustSet; diff --git a/src/xrpld/rpc/handlers/OwnerInfo.cpp b/src/xrpld/rpc/handlers/account/OwnerInfo.cpp similarity index 94% rename from src/xrpld/rpc/handlers/OwnerInfo.cpp rename to src/xrpld/rpc/handlers/account/OwnerInfo.cpp index 659a149e20..d287d2fd25 100644 --- a/src/xrpld/rpc/handlers/OwnerInfo.cpp +++ b/src/xrpld/rpc/handlers/account/OwnerInfo.cpp @@ -2,11 +2,15 @@ #include #include +#include #include #include #include #include +#include +#include + namespace xrpl { // { diff --git a/src/xrpld/rpc/handlers/BlackList.cpp b/src/xrpld/rpc/handlers/admin/BlackList.cpp similarity index 92% rename from src/xrpld/rpc/handlers/BlackList.cpp rename to src/xrpld/rpc/handlers/admin/BlackList.cpp index 86abe53686..dfcb1aaa3a 100644 --- a/src/xrpld/rpc/handlers/BlackList.cpp +++ b/src/xrpld/rpc/handlers/admin/BlackList.cpp @@ -1,6 +1,7 @@ #include #include +#include #include #include diff --git a/src/xrpld/rpc/handlers/UnlList.cpp b/src/xrpld/rpc/handlers/admin/UnlList.cpp similarity index 83% rename from src/xrpld/rpc/handlers/UnlList.cpp rename to src/xrpld/rpc/handlers/admin/UnlList.cpp index 31f41b4a33..79eb2acf29 100644 --- a/src/xrpld/rpc/handlers/UnlList.cpp +++ b/src/xrpld/rpc/handlers/admin/UnlList.cpp @@ -2,8 +2,12 @@ #include #include -#include +#include +#include #include +#include + +#include namespace xrpl { diff --git a/src/xrpld/rpc/handlers/CanDelete.cpp b/src/xrpld/rpc/handlers/admin/data/CanDelete.cpp similarity index 94% rename from src/xrpld/rpc/handlers/CanDelete.cpp rename to src/xrpld/rpc/handlers/admin/data/CanDelete.cpp index 7d881e7d2e..59d4b3a75c 100644 --- a/src/xrpld/rpc/handlers/CanDelete.cpp +++ b/src/xrpld/rpc/handlers/admin/data/CanDelete.cpp @@ -3,12 +3,18 @@ #include #include +#include #include +#include #include #include #include +#include +#include +#include + namespace xrpl { // can_delete [||now|always|never] diff --git a/src/xrpld/rpc/handlers/LedgerCleanerHandler.cpp b/src/xrpld/rpc/handlers/admin/data/LedgerCleaner.cpp similarity index 99% rename from src/xrpld/rpc/handlers/LedgerCleanerHandler.cpp rename to src/xrpld/rpc/handlers/admin/data/LedgerCleaner.cpp index 408cd16023..71dcb6d62f 100644 --- a/src/xrpld/rpc/handlers/LedgerCleanerHandler.cpp +++ b/src/xrpld/rpc/handlers/admin/data/LedgerCleaner.cpp @@ -1,4 +1,5 @@ #include + #include #include #include diff --git a/src/xrpld/rpc/handlers/LedgerRequest.cpp b/src/xrpld/rpc/handlers/admin/data/LedgerRequest.cpp similarity index 94% rename from src/xrpld/rpc/handlers/LedgerRequest.cpp rename to src/xrpld/rpc/handlers/admin/data/LedgerRequest.cpp index da29addd2d..ec31529226 100644 --- a/src/xrpld/rpc/handlers/LedgerRequest.cpp +++ b/src/xrpld/rpc/handlers/admin/data/LedgerRequest.cpp @@ -2,7 +2,7 @@ #include #include -#include +#include #include #include diff --git a/src/xrpld/rpc/handlers/ValidationCreate.cpp b/src/xrpld/rpc/handlers/admin/keygen/ValidationCreate.cpp similarity index 85% rename from src/xrpld/rpc/handlers/ValidationCreate.cpp rename to src/xrpld/rpc/handlers/admin/keygen/ValidationCreate.cpp index c82ddffea5..27ae21d393 100644 --- a/src/xrpld/rpc/handlers/ValidationCreate.cpp +++ b/src/xrpld/rpc/handlers/admin/keygen/ValidationCreate.cpp @@ -1,9 +1,16 @@ #include +#include #include +#include +#include #include +#include #include #include +#include + +#include namespace xrpl { diff --git a/src/xrpld/rpc/handlers/WalletPropose.cpp b/src/xrpld/rpc/handlers/admin/keygen/WalletPropose.cpp similarity index 89% rename from src/xrpld/rpc/handlers/WalletPropose.cpp rename to src/xrpld/rpc/handlers/admin/keygen/WalletPropose.cpp index a5005ce701..bbd8f622be 100644 --- a/src/xrpld/rpc/handlers/WalletPropose.cpp +++ b/src/xrpld/rpc/handlers/admin/keygen/WalletPropose.cpp @@ -1,8 +1,11 @@ +#include + #include #include -#include #include +#include +#include #include #include #include @@ -10,9 +13,12 @@ #include #include #include +#include #include #include +#include +#include namespace xrpl { @@ -56,7 +62,7 @@ walletPropose(Json::Value const& params) { std::optional keyType; std::optional seed; - bool rippleLibSeed = false; + bool libSeed = false; if (params.isMember(jss::key_type)) { @@ -71,22 +77,22 @@ walletPropose(Json::Value const& params) return rpcError(rpcINVALID_PARAMS); } - // ripple-lib encodes seed used to generate an Ed25519 wallet in a + // XrplLib encodes seed used to generate an Ed25519 wallet in a // non-standard way. While we never encode seeds that way, we try // to detect such keys to avoid user confusion. { if (params.isMember(jss::passphrase)) { - seed = RPC::parseRippleLibSeed(params[jss::passphrase]); + seed = RPC::parseXrplLibSeed(params[jss::passphrase]); } else if (params.isMember(jss::seed)) { - seed = RPC::parseRippleLibSeed(params[jss::seed]); + seed = RPC::parseXrplLibSeed(params[jss::seed]); } if (seed) { - rippleLibSeed = true; + libSeed = true; // If the user *explicitly* requests a key type other than // Ed25519 we return an error. @@ -137,7 +143,7 @@ walletPropose(Json::Value const& params) // If a passphrase was specified, and it was hashed and used as a seed // run a quick entropy check and add an appropriate warning, because // "brain wallets" can be easily attacked. - if (!rippleLibSeed && params.isMember(jss::passphrase)) + if (!libSeed && params.isMember(jss::passphrase)) { auto const passphrase = params[jss::passphrase].asString(); diff --git a/src/xrpld/rpc/handlers/WalletPropose.h b/src/xrpld/rpc/handlers/admin/keygen/WalletPropose.h similarity index 100% rename from src/xrpld/rpc/handlers/WalletPropose.h rename to src/xrpld/rpc/handlers/admin/keygen/WalletPropose.h diff --git a/src/xrpld/rpc/handlers/LogLevel.cpp b/src/xrpld/rpc/handlers/admin/log/LogLevel.cpp similarity index 97% rename from src/xrpld/rpc/handlers/LogLevel.cpp rename to src/xrpld/rpc/handlers/admin/log/LogLevel.cpp index e1e637435c..9cfa321654 100644 --- a/src/xrpld/rpc/handlers/LogLevel.cpp +++ b/src/xrpld/rpc/handlers/admin/log/LogLevel.cpp @@ -9,6 +9,10 @@ #include +#include +#include +#include + namespace xrpl { Json::Value diff --git a/src/xrpld/rpc/handlers/LogRotate.cpp b/src/xrpld/rpc/handlers/admin/log/LogRotate.cpp similarity index 83% rename from src/xrpld/rpc/handlers/LogRotate.cpp rename to src/xrpld/rpc/handlers/admin/log/LogRotate.cpp index 3cc7f35381..c1fdd58c23 100644 --- a/src/xrpld/rpc/handlers/LogRotate.cpp +++ b/src/xrpld/rpc/handlers/admin/log/LogRotate.cpp @@ -1,8 +1,10 @@ #include +#include #include #include #include +#include namespace xrpl { diff --git a/src/xrpld/rpc/handlers/Connect.cpp b/src/xrpld/rpc/handlers/admin/peer/Connect.cpp similarity index 93% rename from src/xrpld/rpc/handlers/Connect.cpp rename to src/xrpld/rpc/handlers/admin/peer/Connect.cpp index fe3183484e..4d9667e4a9 100644 --- a/src/xrpld/rpc/handlers/Connect.cpp +++ b/src/xrpld/rpc/handlers/admin/peer/Connect.cpp @@ -4,11 +4,15 @@ #include #include +#include +#include #include #include #include #include +#include + namespace xrpl { // { diff --git a/src/xrpld/rpc/handlers/Reservations.cpp b/src/xrpld/rpc/handlers/admin/peer/PeerReservationsAdd.cpp similarity index 60% rename from src/xrpld/rpc/handlers/Reservations.cpp rename to src/xrpld/rpc/handlers/admin/peer/PeerReservationsAdd.cpp index fb874247ad..6aaa5cc78a 100644 --- a/src/xrpld/rpc/handlers/Reservations.cpp +++ b/src/xrpld/rpc/handlers/admin/peer/PeerReservationsAdd.cpp @@ -1,15 +1,16 @@ #include #include +#include #include #include #include #include #include +#include #include #include -#include namespace xrpl { @@ -55,8 +56,8 @@ doPeerReservationsAdd(RPC::JsonContext& context) return rpcError(rpcPUBLIC_MALFORMED); PublicKey const& nodeId = *optPk; - auto const previous = - context.app.getPeerReservations().insert_or_assign(PeerReservation{nodeId, desc}); + auto const previous = context.app.getPeerReservations().insert_or_assign( + PeerReservation{.nodeId = nodeId, .description = desc}); Json::Value result{Json::objectValue}; if (previous) @@ -66,46 +67,4 @@ doPeerReservationsAdd(RPC::JsonContext& context) return result; } -Json::Value -doPeerReservationsDel(RPC::JsonContext& context) -{ - auto const& params = context.params; - - // We repeat much of the parameter parsing from `doPeerReservationsAdd`. - if (!params.isMember(jss::public_key)) - return RPC::missing_field_error(jss::public_key); - if (!params[jss::public_key].isString()) - return RPC::expected_field_error(jss::public_key, "a string"); - - std::optional optPk = - parseBase58(TokenType::NodePublic, params[jss::public_key].asString()); - if (!optPk) - return rpcError(rpcPUBLIC_MALFORMED); - PublicKey const& nodeId = *optPk; - - auto const previous = context.app.getPeerReservations().erase(nodeId); - - Json::Value result{Json::objectValue}; - if (previous) - { - result[jss::previous] = previous->toJson(); - } - return result; -} - -Json::Value -doPeerReservationsList(RPC::JsonContext& context) -{ - auto const& reservations = context.app.getPeerReservations().list(); - // Enumerate the reservations in context.app.getPeerReservations() - // as a Json::Value. - Json::Value result{Json::objectValue}; - Json::Value& jaReservations = result[jss::reservations] = Json::arrayValue; - for (auto const& reservation : reservations) - { - jaReservations.append(reservation.toJson()); - } - return result; -} - } // namespace xrpl diff --git a/src/xrpld/rpc/handlers/admin/peer/PeerReservationsDel.cpp b/src/xrpld/rpc/handlers/admin/peer/PeerReservationsDel.cpp new file mode 100644 index 0000000000..d60979aab3 --- /dev/null +++ b/src/xrpld/rpc/handlers/admin/peer/PeerReservationsDel.cpp @@ -0,0 +1,42 @@ +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +namespace xrpl { + +Json::Value +doPeerReservationsDel(RPC::JsonContext& context) +{ + auto const& params = context.params; + + // We repeat much of the parameter parsing from `doPeerReservationsAdd`. + if (!params.isMember(jss::public_key)) + return RPC::missing_field_error(jss::public_key); + if (!params[jss::public_key].isString()) + return RPC::expected_field_error(jss::public_key, "a string"); + + std::optional optPk = + parseBase58(TokenType::NodePublic, params[jss::public_key].asString()); + if (!optPk) + return rpcError(rpcPUBLIC_MALFORMED); + PublicKey const& nodeId = *optPk; + + auto const previous = context.app.getPeerReservations().erase(nodeId); + + Json::Value result{Json::objectValue}; + if (previous) + { + result[jss::previous] = previous->toJson(); + } + return result; +} + +} // namespace xrpl diff --git a/src/xrpld/rpc/handlers/admin/peer/PeerReservationsList.cpp b/src/xrpld/rpc/handlers/admin/peer/PeerReservationsList.cpp new file mode 100644 index 0000000000..119af3f73c --- /dev/null +++ b/src/xrpld/rpc/handlers/admin/peer/PeerReservationsList.cpp @@ -0,0 +1,24 @@ +#include +#include + +#include +#include + +namespace xrpl { + +Json::Value +doPeerReservationsList(RPC::JsonContext& context) +{ + auto const& reservations = context.app.getPeerReservations().list(); + // Enumerate the reservations in context.app.getPeerReservations() + // as a Json::Value. + Json::Value result{Json::objectValue}; + Json::Value& jaReservations = result[jss::reservations] = Json::arrayValue; + for (auto const& reservation : reservations) + { + jaReservations.append(reservation.toJson()); + } + return result; +} + +} // namespace xrpl diff --git a/src/xrpld/rpc/handlers/Peers.cpp b/src/xrpld/rpc/handlers/admin/peer/Peers.cpp similarity index 90% rename from src/xrpld/rpc/handlers/Peers.cpp rename to src/xrpld/rpc/handlers/admin/peer/Peers.cpp index 646aae7bc8..9164089e83 100644 --- a/src/xrpld/rpc/handlers/Peers.cpp +++ b/src/xrpld/rpc/handlers/admin/peer/Peers.cpp @@ -1,13 +1,19 @@ #include #include #include +#include #include #include -#include +#include +#include +#include #include +#include #include +#include + namespace xrpl { Json::Value diff --git a/src/xrpld/rpc/handlers/LedgerAccept.cpp b/src/xrpld/rpc/handlers/admin/server_control/LedgerAccept.cpp similarity index 94% rename from src/xrpld/rpc/handlers/LedgerAccept.cpp rename to src/xrpld/rpc/handlers/admin/server_control/LedgerAccept.cpp index 91e88b707f..7119b5235e 100644 --- a/src/xrpld/rpc/handlers/LedgerAccept.cpp +++ b/src/xrpld/rpc/handlers/admin/server_control/LedgerAccept.cpp @@ -3,7 +3,7 @@ #include #include -#include +#include #include #include diff --git a/src/xrpld/rpc/handlers/Stop.cpp b/src/xrpld/rpc/handlers/admin/server_control/Stop.cpp similarity index 89% rename from src/xrpld/rpc/handlers/Stop.cpp rename to src/xrpld/rpc/handlers/admin/server_control/Stop.cpp index b47c35e21d..e3e0f29fa2 100644 --- a/src/xrpld/rpc/handlers/Stop.cpp +++ b/src/xrpld/rpc/handlers/admin/server_control/Stop.cpp @@ -2,6 +2,7 @@ #include #include +#include namespace xrpl { diff --git a/src/xrpld/rpc/handlers/PayChanClaim.cpp b/src/xrpld/rpc/handlers/admin/signing/ChannelAuthorize.cpp similarity index 59% rename from src/xrpld/rpc/handlers/PayChanClaim.cpp rename to src/xrpld/rpc/handlers/admin/signing/ChannelAuthorize.cpp index b24a241147..d93e1efadc 100644 --- a/src/xrpld/rpc/handlers/PayChanClaim.cpp +++ b/src/xrpld/rpc/handlers/admin/signing/ChannelAuthorize.cpp @@ -1,15 +1,25 @@ #include #include +#include #include #include -#include +#include +#include +#include +#include #include #include #include +#include +#include +#include #include +#include +#include #include +#include namespace xrpl { @@ -83,61 +93,4 @@ doChannelAuthorize(RPC::JsonContext& context) return result; } -// { -// public_key: -// channel_id: 256-bit channel id -// drops: 64-bit uint (as string) -// signature: signature to verify -// } -Json::Value -doChannelVerify(RPC::JsonContext& context) -{ - auto const& params(context.params); - for (auto const& p : {jss::public_key, jss::channel_id, jss::amount, jss::signature}) - { - if (!params.isMember(p)) - return RPC::missing_field_error(p); - } - - std::optional pk; - { - std::string const strPk = params[jss::public_key].asString(); - pk = parseBase58(TokenType::AccountPublic, strPk); - - if (!pk) - { - auto pkHex = strUnHex(strPk); - if (!pkHex) - return rpcError(rpcPUBLIC_MALFORMED); - auto const pkType = publicKeyType(makeSlice(*pkHex)); - if (!pkType) - return rpcError(rpcPUBLIC_MALFORMED); - pk.emplace(makeSlice(*pkHex)); - } - } - - uint256 channelId; - if (!channelId.parseHex(params[jss::channel_id].asString())) - return rpcError(rpcCHANNEL_MALFORMED); - - std::optional const optDrops = - params[jss::amount].isString() ? to_uint64(params[jss::amount].asString()) : std::nullopt; - - if (!optDrops) - return rpcError(rpcCHANNEL_AMT_MALFORMED); - - std::uint64_t const drops = *optDrops; - - auto sig = strUnHex(params[jss::signature].asString()); - if (!sig || sig->empty()) - return rpcError(rpcINVALID_PARAMS); - - Serializer msg; - serializePayChanAuthorization(msg, channelId, XRPAmount(drops)); - - Json::Value result; - result[jss::signature_verified] = verify(*pk, msg.slice(), makeSlice(*sig)); - return result; -} - } // namespace xrpl diff --git a/src/xrpld/rpc/handlers/SignHandler.cpp b/src/xrpld/rpc/handlers/admin/signing/Sign.cpp similarity index 92% rename from src/xrpld/rpc/handlers/SignHandler.cpp rename to src/xrpld/rpc/handlers/admin/signing/Sign.cpp index e7150c5e2f..d92506c672 100644 --- a/src/xrpld/rpc/handlers/SignHandler.cpp +++ b/src/xrpld/rpc/handlers/admin/signing/Sign.cpp @@ -1,8 +1,11 @@ #include #include +#include #include +#include #include +#include #include namespace xrpl { diff --git a/src/xrpld/rpc/handlers/SignFor.cpp b/src/xrpld/rpc/handlers/admin/signing/SignFor.cpp similarity index 92% rename from src/xrpld/rpc/handlers/SignFor.cpp rename to src/xrpld/rpc/handlers/admin/signing/SignFor.cpp index 54f23c9d81..572093856f 100644 --- a/src/xrpld/rpc/handlers/SignFor.cpp +++ b/src/xrpld/rpc/handlers/admin/signing/SignFor.cpp @@ -1,8 +1,11 @@ #include #include +#include #include +#include #include +#include #include namespace xrpl { diff --git a/src/xrpld/rpc/handlers/ConsensusInfo.cpp b/src/xrpld/rpc/handlers/admin/status/ConsensusInfo.cpp similarity index 90% rename from src/xrpld/rpc/handlers/ConsensusInfo.cpp rename to src/xrpld/rpc/handlers/admin/status/ConsensusInfo.cpp index f9c5a97785..5ccbda2a16 100644 --- a/src/xrpld/rpc/handlers/ConsensusInfo.cpp +++ b/src/xrpld/rpc/handlers/admin/status/ConsensusInfo.cpp @@ -1,7 +1,6 @@ #include #include -#include #include #include diff --git a/src/xrpld/rpc/handlers/FetchInfo.cpp b/src/xrpld/rpc/handlers/admin/status/FetchInfo.cpp similarity index 93% rename from src/xrpld/rpc/handlers/FetchInfo.cpp rename to src/xrpld/rpc/handlers/admin/status/FetchInfo.cpp index f25f18acf7..fd916ef53e 100644 --- a/src/xrpld/rpc/handlers/FetchInfo.cpp +++ b/src/xrpld/rpc/handlers/admin/status/FetchInfo.cpp @@ -1,7 +1,6 @@ #include #include -#include #include #include diff --git a/src/xrpld/rpc/handlers/GetCounts.cpp b/src/xrpld/rpc/handlers/admin/status/GetCounts.cpp similarity index 97% rename from src/xrpld/rpc/handlers/GetCounts.cpp rename to src/xrpld/rpc/handlers/admin/status/GetCounts.cpp index 648d29a5fd..8cc687fec2 100644 --- a/src/xrpld/rpc/handlers/GetCounts.cpp +++ b/src/xrpld/rpc/handlers/admin/status/GetCounts.cpp @@ -1,4 +1,3 @@ -#include #include #include #include @@ -6,12 +5,16 @@ #include #include +#include #include #include -#include #include #include +#include +#include +#include + namespace xrpl { static void diff --git a/src/xrpld/rpc/handlers/GetCounts.h b/src/xrpld/rpc/handlers/admin/status/GetCounts.h similarity index 100% rename from src/xrpld/rpc/handlers/GetCounts.h rename to src/xrpld/rpc/handlers/admin/status/GetCounts.h diff --git a/src/xrpld/rpc/handlers/Print.cpp b/src/xrpld/rpc/handlers/admin/status/Print.cpp similarity index 100% rename from src/xrpld/rpc/handlers/Print.cpp rename to src/xrpld/rpc/handlers/admin/status/Print.cpp diff --git a/src/xrpld/rpc/handlers/ValidatorInfo.cpp b/src/xrpld/rpc/handlers/admin/status/ValidatorInfo.cpp similarity index 95% rename from src/xrpld/rpc/handlers/ValidatorInfo.cpp rename to src/xrpld/rpc/handlers/admin/status/ValidatorInfo.cpp index aec9dba4b4..c8ec6a2c15 100644 --- a/src/xrpld/rpc/handlers/ValidatorInfo.cpp +++ b/src/xrpld/rpc/handlers/admin/status/ValidatorInfo.cpp @@ -1,13 +1,14 @@ // Copyright (c) 2019 Dev Null Productions #include -#include #include #include #include #include +#include #include +#include namespace xrpl { Json::Value diff --git a/src/xrpld/rpc/handlers/ValidatorListSites.cpp b/src/xrpld/rpc/handlers/admin/status/ValidatorListSites.cpp similarity index 87% rename from src/xrpld/rpc/handlers/ValidatorListSites.cpp rename to src/xrpld/rpc/handlers/admin/status/ValidatorListSites.cpp index 36e2064387..80823a8936 100644 --- a/src/xrpld/rpc/handlers/ValidatorListSites.cpp +++ b/src/xrpld/rpc/handlers/admin/status/ValidatorListSites.cpp @@ -2,7 +2,7 @@ #include #include -#include +#include namespace xrpl { diff --git a/src/xrpld/rpc/handlers/Validators.cpp b/src/xrpld/rpc/handlers/admin/status/Validators.cpp similarity index 87% rename from src/xrpld/rpc/handlers/Validators.cpp rename to src/xrpld/rpc/handlers/admin/status/Validators.cpp index 95e6de9e68..cf9cab6b84 100644 --- a/src/xrpld/rpc/handlers/Validators.cpp +++ b/src/xrpld/rpc/handlers/admin/status/Validators.cpp @@ -2,7 +2,7 @@ #include #include -#include +#include namespace xrpl { diff --git a/src/xrpld/rpc/handlers/LedgerHandler.cpp b/src/xrpld/rpc/handlers/ledger/Ledger.cpp similarity index 95% rename from src/xrpld/rpc/handlers/LedgerHandler.cpp rename to src/xrpld/rpc/handlers/ledger/Ledger.cpp index 0707ad1ffe..dd2280b51c 100644 --- a/src/xrpld/rpc/handlers/LedgerHandler.cpp +++ b/src/xrpld/rpc/handlers/ledger/Ledger.cpp @@ -1,14 +1,35 @@ +#include + #include #include +#include #include #include +#include #include -#include +#include +#include +#include #include +#include +#include +#include +#include +#include #include #include #include +#include + +#include +#include + +#include +#include +#include +#include +#include namespace xrpl { namespace RPC { diff --git a/src/xrpld/rpc/handlers/LedgerHandler.h b/src/xrpld/rpc/handlers/ledger/Ledger.h similarity index 94% rename from src/xrpld/rpc/handlers/LedgerHandler.h rename to src/xrpld/rpc/handlers/ledger/Ledger.h index f024241546..db70f35904 100644 --- a/src/xrpld/rpc/handlers/LedgerHandler.h +++ b/src/xrpld/rpc/handlers/ledger/Ledger.h @@ -16,8 +16,7 @@ namespace Json { class Object; } // namespace Json -namespace xrpl { -namespace RPC { +namespace xrpl::RPC { struct JsonContext; @@ -56,5 +55,4 @@ private: int options_ = 0; }; -} // namespace RPC -} // namespace xrpl +} // namespace xrpl::RPC diff --git a/src/xrpld/rpc/handlers/LedgerClosed.cpp b/src/xrpld/rpc/handlers/ledger/LedgerClosed.cpp similarity index 87% rename from src/xrpld/rpc/handlers/LedgerClosed.cpp rename to src/xrpld/rpc/handlers/ledger/LedgerClosed.cpp index e26019cca6..43e6fa686e 100644 --- a/src/xrpld/rpc/handlers/LedgerClosed.cpp +++ b/src/xrpld/rpc/handlers/ledger/LedgerClosed.cpp @@ -1,6 +1,8 @@ #include #include +#include +#include #include #include #include diff --git a/src/xrpld/rpc/handlers/LedgerCurrent.cpp b/src/xrpld/rpc/handlers/ledger/LedgerCurrent.cpp similarity index 100% rename from src/xrpld/rpc/handlers/LedgerCurrent.cpp rename to src/xrpld/rpc/handlers/ledger/LedgerCurrent.cpp diff --git a/src/xrpld/rpc/handlers/LedgerData.cpp b/src/xrpld/rpc/handlers/ledger/LedgerData.cpp similarity index 95% rename from src/xrpld/rpc/handlers/LedgerData.cpp rename to src/xrpld/rpc/handlers/ledger/LedgerData.cpp index 059c844e6e..f0a361d951 100644 --- a/src/xrpld/rpc/handlers/LedgerData.cpp +++ b/src/xrpld/rpc/handlers/ledger/LedgerData.cpp @@ -6,10 +6,21 @@ #include #include +#include +#include #include #include +#include #include +#include #include +#include + +#include +#include + +#include +#include namespace xrpl { diff --git a/src/xrpld/rpc/handlers/LedgerDiff.cpp b/src/xrpld/rpc/handlers/ledger/LedgerDiff.cpp similarity index 89% rename from src/xrpld/rpc/handlers/LedgerDiff.cpp rename to src/xrpld/rpc/handlers/ledger/LedgerDiff.cpp index 56a4d97b94..f1a9253de2 100644 --- a/src/xrpld/rpc/handlers/LedgerDiff.cpp +++ b/src/xrpld/rpc/handlers/ledger/LedgerDiff.cpp @@ -1,6 +1,18 @@ +#include #include #include +#include +#include +#include + +#include +#include + +#include +#include +#include + namespace xrpl { std::pair doLedgerDiffGrpc(RPC::GRPCContext& context) @@ -36,7 +48,7 @@ doLedgerDiffGrpc(RPC::GRPCContext& con std::dynamic_pointer_cast(desiredLedgerRv); if (!desiredLedger) { - grpc::Status const errorStatus{grpc::StatusCode::NOT_FOUND, "base ledger not validated"}; + grpc::Status const errorStatus{grpc::StatusCode::NOT_FOUND, "desired ledger not validated"}; return {response, errorStatus}; } diff --git a/src/xrpld/rpc/handlers/LedgerEntry.cpp b/src/xrpld/rpc/handlers/ledger/LedgerEntry.cpp similarity index 96% rename from src/xrpld/rpc/handlers/LedgerEntry.cpp rename to src/xrpld/rpc/handlers/ledger/LedgerEntry.cpp index d27574944d..4de75da1b0 100644 --- a/src/xrpld/rpc/handlers/LedgerEntry.cpp +++ b/src/xrpld/rpc/handlers/ledger/LedgerEntry.cpp @@ -1,21 +1,37 @@ #include #include #include -#include +#include -#include +#include +#include #include -#include #include +#include #include #include +#include #include #include +#include #include -#include +#include +#include +#include #include +#include #include +#include +#include + +#include +#include +#include +#include +#include +#include + namespace xrpl { using FunctionType = std::function( @@ -112,11 +128,11 @@ parseAMM( return Unexpected(value.error()); } - auto const asset = LedgerEntryHelpers::requiredIssue(params, jss::asset, "malformedRequest"); + auto const asset = LedgerEntryHelpers::requiredAsset(params, jss::asset, "malformedRequest"); if (!asset) return Unexpected(asset.error()); - auto const asset2 = LedgerEntryHelpers::requiredIssue(params, jss::asset2, "malformedRequest"); + auto const asset2 = LedgerEntryHelpers::requiredAsset(params, jss::asset2, "malformedRequest"); if (!asset2) return Unexpected(asset2.error()); @@ -827,10 +843,14 @@ doLedgerEntry(RPC::JsonContext& context) #undef LEDGER_ENTRY #pragma pop_macro("LEDGER_ENTRY") - {jss::index, parseIndex, ltANY}, + {.fieldName = jss::index, .parseFunction = parseIndex, .expectedType = ltANY}, // aliases - {jss::account_root, parseAccountRoot, ltACCOUNT_ROOT}, - {jss::ripple_state, parseRippleState, ltRIPPLE_STATE}, + {.fieldName = jss::account_root, + .parseFunction = parseAccountRoot, + .expectedType = ltACCOUNT_ROOT}, + {.fieldName = jss::ripple_state, + .parseFunction = parseRippleState, + .expectedType = ltRIPPLE_STATE}, }); auto const hasMoreThanOneMember = [&]() { @@ -897,7 +917,7 @@ doLedgerEntry(RPC::JsonContext& context) return RPC::make_param_error("No ledger_entry params provided."); } } - catch (Json::error& e) + catch (Json::error const& e) { if (context.apiVersion > 1u) { diff --git a/src/xrpld/rpc/handlers/LedgerEntryHelpers.h b/src/xrpld/rpc/handlers/ledger/LedgerEntryHelpers.h similarity index 95% rename from src/xrpld/rpc/handlers/LedgerEntryHelpers.h rename to src/xrpld/rpc/handlers/ledger/LedgerEntryHelpers.h index 9c5f0b2fcb..d0d643661d 100644 --- a/src/xrpld/rpc/handlers/LedgerEntryHelpers.h +++ b/src/xrpld/rpc/handlers/ledger/LedgerEntryHelpers.h @@ -12,9 +12,7 @@ #include -namespace xrpl { - -namespace LedgerEntryHelpers { +namespace xrpl::LedgerEntryHelpers { inline Unexpected missingFieldError(Json::StaticString const field, std::optional err = std::nullopt) @@ -212,12 +210,12 @@ requiredUInt192( } template <> -inline std::optional +inline std::optional parse(Json::Value const& param) { try { - return issueFromJson(param); + return assetFromJson(param); } catch (std::runtime_error const&) { @@ -225,10 +223,10 @@ parse(Json::Value const& param) } } -inline Expected -requiredIssue(Json::Value const& params, Json::StaticString const fieldName, std::string const& err) +inline Expected +requiredAsset(Json::Value const& params, Json::StaticString const fieldName, std::string const& err) { - return required(params, fieldName, err, "Issue"); + return required(params, fieldName, err, "Asset"); } inline Expected @@ -283,6 +281,4 @@ parseBridgeFields(Json::Value const& params) *lockingChainDoor, lockingChainIssue, *issuingChainDoor, issuingChainIssue); } -} // namespace LedgerEntryHelpers - -} // namespace xrpl +} // namespace xrpl::LedgerEntryHelpers diff --git a/src/xrpld/rpc/handlers/LedgerHeader.cpp b/src/xrpld/rpc/handlers/ledger/LedgerHeader.cpp similarity index 85% rename from src/xrpld/rpc/handlers/LedgerHeader.cpp rename to src/xrpld/rpc/handlers/ledger/LedgerHeader.cpp index 6b93594020..6f01889b59 100644 --- a/src/xrpld/rpc/handlers/LedgerHeader.cpp +++ b/src/xrpld/rpc/handlers/ledger/LedgerHeader.cpp @@ -1,10 +1,16 @@ +#include + #include #include #include +#include #include +#include #include +#include + namespace xrpl { // { diff --git a/src/xrpld/rpc/handlers/AMMInfo.cpp b/src/xrpld/rpc/handlers/orderbook/AMMInfo.cpp similarity index 76% rename from src/xrpld/rpc/handlers/AMMInfo.cpp rename to src/xrpld/rpc/handlers/orderbook/AMMInfo.cpp index 9204f48907..c093c5fcf5 100644 --- a/src/xrpld/rpc/handlers/AMMInfo.cpp +++ b/src/xrpld/rpc/handlers/orderbook/AMMInfo.cpp @@ -2,27 +2,48 @@ #include #include +#include +#include +#include +#include #include +#include +#include +#include #include #include +#include +#include #include -#include -#include +#include +#include +#include +#include +#include +#include +#include -#include +#include + +#include +#include +#include +#include +#include +#include namespace xrpl { -Expected -getIssue(Json::Value const& v, beast::Journal j) +Expected +getAsset(Json::Value const& v, beast::Journal j) { try { - return issueFromJson(v); + return assetFromJson(v); } catch (std::runtime_error const& ex) { - JLOG(j.debug()) << "getIssue " << ex.what(); + JLOG(j.debug()) << "getAsset " << ex.what(); } return Unexpected(rpcISSUE_MALFORMED); } @@ -52,15 +73,15 @@ doAMMInfo(RPC::JsonContext& context) struct ValuesFromContextParams { std::optional accountID; - Issue issue1; - Issue issue2; + Asset asset1; + Asset asset2; std::shared_ptr amm; }; auto getValuesFromContextParams = [&]() -> Expected { std::optional accountID; - std::optional issue1; - std::optional issue2; + std::optional asset1; + std::optional asset2; std::optional ammID; constexpr auto invalid = [](Json::Value const& params) -> bool { @@ -74,9 +95,9 @@ doAMMInfo(RPC::JsonContext& context) if (params.isMember(jss::asset)) { - if (auto const i = getIssue(params[jss::asset], context.j)) + if (auto const i = getAsset(params[jss::asset], context.j)) { - issue1 = *i; + asset1 = *i; } else { @@ -86,9 +107,9 @@ doAMMInfo(RPC::JsonContext& context) if (params.isMember(jss::asset2)) { - if (auto const i = getIssue(params[jss::asset2], context.j)) + if (auto const i = getAsset(params[jss::asset2], context.j)) { - issue2 = *i; + asset2 = *i; } else { @@ -121,25 +142,26 @@ doAMMInfo(RPC::JsonContext& context) return Unexpected(rpcINVALID_PARAMS); XRPL_ASSERT( - (issue1.has_value() == issue2.has_value()) && (issue1.has_value() != ammID.has_value()), - "xrpl::doAMMInfo : issue1 and issue2 do match"); + (asset1.has_value() == asset2.has_value()) && (asset1.has_value() != ammID.has_value()), + "xrpl::doAMMInfo : asset1 and asset2 do match"); auto const ammKeylet = [&]() { - if (issue1 && issue2) - return keylet::amm(*issue1, *issue2); + if (asset1 && asset2) + return keylet::amm(*asset1, *asset2); XRPL_ASSERT(ammID, "xrpl::doAMMInfo::ammKeylet : ammID is set"); return keylet::amm(*ammID); }(); auto const amm = ledger->read(ammKeylet); if (!amm) return Unexpected(rpcACT_NOT_FOUND); - if (!issue1 && !issue2) + if (!asset1 && !asset2) { - issue1 = (*amm)[sfAsset].get(); - issue2 = (*amm)[sfAsset2].get(); + asset1 = (*amm)[sfAsset]; + asset2 = (*amm)[sfAsset2]; } - return ValuesFromContextParams{accountID, *issue1, *issue2, amm}; + return ValuesFromContextParams{ + .accountID = accountID, .asset1 = *asset1, .asset2 = *asset2, .amm = amm}; }; auto const r = getValuesFromContextParams(); @@ -149,13 +171,19 @@ doAMMInfo(RPC::JsonContext& context) return result; } - auto const& [accountID, issue1, issue2, amm] = *r; + auto const& [accountID, asset1, asset2, amm] = *r; auto const ammAccountID = amm->getAccountID(sfAccount); // provide funds if frozen, specify asset_frozen flag auto const [asset1Balance, asset2Balance] = ammPoolHolds( - *ledger, ammAccountID, issue1, issue2, FreezeHandling::fhIGNORE_FREEZE, context.j); + *ledger, + ammAccountID, + asset1, + asset2, + FreezeHandling::fhIGNORE_FREEZE, + AuthHandling::ahIGNORE_AUTH, + context.j); auto const lptAMMBalance = accountID ? ammLPHolds(*ledger, *amm, *accountID, context.j) : (*amm)[sfLPTokenBalance]; @@ -213,13 +241,11 @@ doAMMInfo(RPC::JsonContext& context) if (!isXRP(asset1Balance)) { - ammResult[jss::asset_frozen] = - isFrozen(*ledger, ammAccountID, issue1.currency, issue1.account); + ammResult[jss::asset_frozen] = isFrozen(*ledger, ammAccountID, asset1); } if (!isXRP(asset2Balance)) { - ammResult[jss::asset2_frozen] = - isFrozen(*ledger, ammAccountID, issue2.currency, issue2.account); + ammResult[jss::asset2_frozen] = isFrozen(*ledger, ammAccountID, asset2); } result[jss::amm] = std::move(ammResult); diff --git a/src/xrpld/rpc/handlers/orderbook/BookChanges.cpp b/src/xrpld/rpc/handlers/orderbook/BookChanges.cpp new file mode 100644 index 0000000000..502895e734 --- /dev/null +++ b/src/xrpld/rpc/handlers/orderbook/BookChanges.cpp @@ -0,0 +1,24 @@ +#include + +#include +#include + +#include + +#include + +namespace xrpl { + +Json::Value +doBookChanges(RPC::JsonContext& context) +{ + std::shared_ptr ledger; + + Json::Value result = RPC::lookupLedger(ledger, context); + if (ledger == nullptr) + return result; + + return RPC::computeBookChanges(ledger); +} + +} // namespace xrpl diff --git a/src/xrpld/rpc/handlers/orderbook/BookOffers.cpp b/src/xrpld/rpc/handlers/orderbook/BookOffers.cpp new file mode 100644 index 0000000000..04fceac268 --- /dev/null +++ b/src/xrpld/rpc/handlers/orderbook/BookOffers.cpp @@ -0,0 +1,269 @@ +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace xrpl { + +std::optional +validateTakerJSON(Json::Value const& taker, Json::StaticString const& name) +{ + if (!taker.isMember(jss::currency) && !taker.isMember(jss::mpt_issuance_id)) + { + return RPC::missing_field_error((boost::format("%s.currency") % name.c_str()).str()); + } + + if (taker.isMember(jss::mpt_issuance_id) && + (taker.isMember(jss::currency) || taker.isMember(jss::issuer))) + { + return RPC::invalid_field_error(name.c_str()); + } + + if ((taker.isMember(jss::currency) && !taker[jss::currency].isString()) || + (taker.isMember(jss::mpt_issuance_id) && !taker[jss::mpt_issuance_id].isString())) + { + return RPC::expected_field_error( + (boost::format("%s.currency") % name.c_str()).str(), "string"); + } + + return std::nullopt; +} + +std::optional +parseTakerAssetJSON( + Asset& asset, + Json::Value const& taker, + Json::StaticString const& name, + beast::Journal j) +{ + auto const assetError = [&]() { + if (name == jss::taker_pays) + return rpcSRC_CUR_MALFORMED; + return rpcDST_AMT_MALFORMED; + }(); + + if (taker.isMember(jss::currency)) + { + Issue issue = xrpIssue(); + + if (!to_currency(issue.currency, taker[jss::currency].asString())) + { + JLOG(j.info()) << boost::format("Bad %s currency.") % name.c_str(); + return RPC::make_error( + assetError, + (boost::format("Invalid field '%s.currency', bad currency.") % name.c_str()).str()); + } + asset = issue; + } + else if (taker.isMember(jss::mpt_issuance_id)) + { + MPTID mptid; + if (!mptid.parseHex(taker[jss::mpt_issuance_id].asString())) + { + return RPC::make_error( + assetError, + (boost::format("Invalid field '%s.mpt_issuance_id'") % name.c_str()).str()); + } + asset = mptid; + } + + return std::nullopt; +} + +std::optional +parseTakerIssuerJSON( + Asset& asset, + Json::Value const& taker, + Json::StaticString const& name, + beast::Journal j) +{ + auto const issuerError = [&]() { + if (name == jss::taker_pays) + return rpcSRC_ISR_MALFORMED; + return rpcDST_ISR_MALFORMED; + }(); + + if (taker.isMember(jss::currency)) + { + Issue& issue = asset.get(); + + if (taker.isMember(jss::issuer)) + { + if (!taker[jss::issuer].isString()) + { + return RPC::expected_field_error( + (boost::format("%s.issuer") % name.c_str()).str(), "string"); + } + + if (!to_issuer(issue.account, taker[jss::issuer].asString())) + { + return RPC::make_error( + issuerError, + (boost::format("Invalid field '%s.issuer', bad issuer.") % name.c_str()).str()); + } + + if (issue.account == noAccount()) + { + return RPC::make_error( + issuerError, + (boost::format("Invalid field '%s.issuer', bad issuer account one.") % + name.c_str()) + .str()); + } + } + else + { + issue.account = xrpAccount(); + } + + if (isXRP(issue.currency) && !isXRP(issue.account)) + { + return RPC::make_error( + issuerError, + (boost::format( + "Unneeded field '%s.issuer' for XRP currency " + "specification.") % + name.c_str()) + .str()); + } + + if (!isXRP(issue.currency) && isXRP(issue.account)) + { + return RPC::make_error( + issuerError, + (boost::format("Invalid field '%s.issuer', expected non-XRP issuer.") % + name.c_str()) + .str()); + } + } + + return std::nullopt; +} + +Json::Value +doBookOffers(RPC::JsonContext& context) +{ + // VFALCO TODO Here is a terrible place for this kind of business + // logic. It needs to be moved elsewhere and documented, + // and encapsulated into a function. + if (context.app.getJobQueue().getJobCountGE(jtCLIENT) > 200) + return rpcError(rpcTOO_BUSY); + + std::shared_ptr lpLedger; + auto jvResult = RPC::lookupLedger(lpLedger, context); + + if (!lpLedger) + return jvResult; + + if (!context.params.isMember(jss::taker_pays)) + return RPC::missing_field_error(jss::taker_pays); + + if (!context.params.isMember(jss::taker_gets)) + return RPC::missing_field_error(jss::taker_gets); + + Json::Value const& taker_pays = context.params[jss::taker_pays]; + Json::Value const& taker_gets = context.params[jss::taker_gets]; + + if (!taker_pays.isObjectOrNull()) + return RPC::object_field_error(jss::taker_pays); + + if (!taker_gets.isObjectOrNull()) + return RPC::object_field_error(jss::taker_gets); + + if (auto const err = validateTakerJSON(taker_pays, jss::taker_pays)) + return *err; + + if (auto const err = validateTakerJSON(taker_gets, jss::taker_gets)) + return *err; + + Book book; + + if (auto const err = parseTakerAssetJSON(book.in, taker_pays, jss::taker_pays, context.j)) + return *err; + + if (auto const err = parseTakerAssetJSON(book.out, taker_gets, jss::taker_gets, context.j)) + return *err; + + if (auto const err = parseTakerIssuerJSON(book.in, taker_pays, jss::taker_pays, context.j)) + return *err; + + if (auto const err = parseTakerIssuerJSON(book.out, taker_gets, jss::taker_gets, context.j)) + return *err; + + std::optional takerID; + if (context.params.isMember(jss::taker)) + { + if (!context.params[jss::taker].isString()) + return RPC::expected_field_error(jss::taker, "string"); + + takerID = parseBase58(context.params[jss::taker].asString()); + if (!takerID) + return RPC::invalid_field_error(jss::taker); + } + + std::optional domain; + if (context.params.isMember(jss::domain)) + { + uint256 num; + if (!context.params[jss::domain].isString() || + !num.parseHex(context.params[jss::domain].asString())) + { + return RPC::make_error(rpcDOMAIN_MALFORMED, "Unable to parse domain."); + } + + domain = num; + } + + if (book.in == book.out) + { + JLOG(context.j.info()) << "taker_gets same as taker_pays."; + return RPC::make_error(rpcBAD_MARKET); + } + + unsigned int limit = 0; + if (auto err = readLimitField(limit, RPC::Tuning::bookOffers, context)) + return *err; + + bool const bProof(context.params.isMember(jss::proof)); + + Json::Value const jvMarker( + context.params.isMember(jss::marker) ? context.params[jss::marker] + : Json::Value(Json::nullValue)); + + context.netOps.getBookPage( + lpLedger, + {book.in, book.out, domain}, + takerID ? *takerID : beast::zero, + bProof, + limit, + jvMarker, + jvResult); + + context.loadType = Resource::feeMediumBurdenRPC; + + return jvResult; +} + +} // namespace xrpl diff --git a/src/xrpld/rpc/handlers/DepositAuthorized.cpp b/src/xrpld/rpc/handlers/orderbook/DepositAuthorized.cpp similarity index 94% rename from src/xrpld/rpc/handlers/DepositAuthorized.cpp rename to src/xrpld/rpc/handlers/orderbook/DepositAuthorized.cpp index fb1c0db884..95c7238d20 100644 --- a/src/xrpld/rpc/handlers/DepositAuthorized.cpp +++ b/src/xrpld/rpc/handlers/orderbook/DepositAuthorized.cpp @@ -1,13 +1,26 @@ #include #include +#include +#include +#include +#include #include #include +#include #include #include +#include +#include #include +#include #include +#include +#include +#include +#include + namespace xrpl { // { diff --git a/src/xrpld/rpc/handlers/GetAggregatePrice.cpp b/src/xrpld/rpc/handlers/orderbook/GetAggregatePrice.cpp similarity index 94% rename from src/xrpld/rpc/handlers/GetAggregatePrice.cpp rename to src/xrpld/rpc/handlers/orderbook/GetAggregatePrice.cpp index 281f2d63a7..10359c810e 100644 --- a/src/xrpld/rpc/handlers/GetAggregatePrice.cpp +++ b/src/xrpld/rpc/handlers/orderbook/GetAggregatePrice.cpp @@ -3,15 +3,38 @@ #include #include +#include +#include #include +#include #include #include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include #include #include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include + namespace xrpl { using namespace boost::bimaps; @@ -248,9 +271,8 @@ doGetAggregatePrice(RPC::JsonContext& context) iteratePriceData(context, sle, [&](STObject const& node) { auto const& series = node.getFieldArray(sfPriceDataSeries); // find the token pair entry with the price - if (auto iter = std::find_if( - series.begin(), - series.end(), + if (auto iter = std::ranges::find_if( + series, [&](STObject const& o) -> bool { return o.getFieldCurrency(sfBaseAsset).getText() == std::get(baseAsset) && diff --git a/src/xrpld/rpc/handlers/orderbook/NFTBuyOffers.cpp b/src/xrpld/rpc/handlers/orderbook/NFTBuyOffers.cpp new file mode 100644 index 0000000000..98c4a73784 --- /dev/null +++ b/src/xrpld/rpc/handlers/orderbook/NFTBuyOffers.cpp @@ -0,0 +1,26 @@ +#include +#include + +#include +#include +#include +#include +#include + +namespace xrpl { + +Json::Value +doNFTBuyOffers(RPC::JsonContext& context) +{ + if (!context.params.isMember(jss::nft_id)) + return RPC::missing_field_error(jss::nft_id); + + uint256 nftId; + + if (!nftId.parseHex(context.params[jss::nft_id].asString())) + return RPC::invalid_field_error(jss::nft_id); + + return enumerateNFTOffers(context, nftId, keylet::nft_buys(nftId)); +} + +} // namespace xrpl diff --git a/src/xrpld/rpc/handlers/NFTOffers.cpp b/src/xrpld/rpc/handlers/orderbook/NFTOffersHelpers.h similarity index 82% rename from src/xrpld/rpc/handlers/NFTOffers.cpp rename to src/xrpld/rpc/handlers/orderbook/NFTOffersHelpers.h index 5fe2e3bede..8f68aeaadd 100644 --- a/src/xrpld/rpc/handlers/NFTOffers.cpp +++ b/src/xrpld/rpc/handlers/orderbook/NFTOffersHelpers.h @@ -1,3 +1,5 @@ +#pragma once + #include #include #include @@ -14,7 +16,7 @@ namespace xrpl { -static void +inline void appendNftOfferJson( Application const& app, std::shared_ptr const& offer, @@ -42,7 +44,7 @@ appendNftOfferJson( // limit: integer // optional // marker: opaque // optional, resume previous query // } -static Json::Value +inline Json::Value enumerateNFTOffers(RPC::JsonContext& context, uint256 const& nftId, Keylet const& directory) { unsigned int limit = 0; @@ -127,32 +129,4 @@ enumerateNFTOffers(RPC::JsonContext& context, uint256 const& nftId, Keylet const return result; } -Json::Value -doNFTSellOffers(RPC::JsonContext& context) -{ - if (!context.params.isMember(jss::nft_id)) - return RPC::missing_field_error(jss::nft_id); - - uint256 nftId; - - if (!nftId.parseHex(context.params[jss::nft_id].asString())) - return RPC::invalid_field_error(jss::nft_id); - - return enumerateNFTOffers(context, nftId, keylet::nft_sells(nftId)); -} - -Json::Value -doNFTBuyOffers(RPC::JsonContext& context) -{ - if (!context.params.isMember(jss::nft_id)) - return RPC::missing_field_error(jss::nft_id); - - uint256 nftId; - - if (!nftId.parseHex(context.params[jss::nft_id].asString())) - return RPC::invalid_field_error(jss::nft_id); - - return enumerateNFTOffers(context, nftId, keylet::nft_buys(nftId)); -} - } // namespace xrpl diff --git a/src/xrpld/rpc/handlers/orderbook/NFTSellOffers.cpp b/src/xrpld/rpc/handlers/orderbook/NFTSellOffers.cpp new file mode 100644 index 0000000000..8612fdf587 --- /dev/null +++ b/src/xrpld/rpc/handlers/orderbook/NFTSellOffers.cpp @@ -0,0 +1,26 @@ +#include +#include + +#include +#include +#include +#include +#include + +namespace xrpl { + +Json::Value +doNFTSellOffers(RPC::JsonContext& context) +{ + if (!context.params.isMember(jss::nft_id)) + return RPC::missing_field_error(jss::nft_id); + + uint256 nftId; + + if (!nftId.parseHex(context.params[jss::nft_id].asString())) + return RPC::invalid_field_error(jss::nft_id); + + return enumerateNFTOffers(context, nftId, keylet::nft_sells(nftId)); +} + +} // namespace xrpl diff --git a/src/xrpld/rpc/handlers/PathFind.cpp b/src/xrpld/rpc/handlers/orderbook/PathFind.cpp similarity index 96% rename from src/xrpld/rpc/handlers/PathFind.cpp rename to src/xrpld/rpc/handlers/orderbook/PathFind.cpp index ced3625b4c..ffe00f54a8 100644 --- a/src/xrpld/rpc/handlers/PathFind.cpp +++ b/src/xrpld/rpc/handlers/orderbook/PathFind.cpp @@ -3,10 +3,12 @@ #include #include +#include #include #include #include #include +#include namespace xrpl { diff --git a/src/xrpld/rpc/handlers/RipplePathFind.cpp b/src/xrpld/rpc/handlers/orderbook/RipplePathFind.cpp similarity index 94% rename from src/xrpld/rpc/handlers/RipplePathFind.cpp rename to src/xrpld/rpc/handlers/orderbook/RipplePathFind.cpp index ac4f22a1aa..c0a2a17a49 100644 --- a/src/xrpld/rpc/handlers/RipplePathFind.cpp +++ b/src/xrpld/rpc/handlers/orderbook/RipplePathFind.cpp @@ -1,12 +1,22 @@ #include #include +#include #include +#include #include #include +#include +#include +#include +#include #include +#include #include +#include +#include + namespace xrpl { // This interface is deprecated. @@ -94,7 +104,7 @@ doRipplePathFind(RPC::JsonContext& context) // Both of these failure modes are hard to recreate in a unit test // because they are so dependent on inter-thread timing. However // the failure modes can be observed by synchronously (inside the - // rippled source code) shutting down the application. The code to + // xrpld source code) shutting down the application. The code to // do so looks like this: // // context.app.signalStop(); diff --git a/src/xrpld/rpc/handlers/Feature1.cpp b/src/xrpld/rpc/handlers/server_info/Feature.cpp similarity index 94% rename from src/xrpld/rpc/handlers/Feature1.cpp rename to src/xrpld/rpc/handlers/server_info/Feature.cpp index 24ff0d62b8..f6f5316a55 100644 --- a/src/xrpld/rpc/handlers/Feature1.cpp +++ b/src/xrpld/rpc/handlers/server_info/Feature.cpp @@ -1,8 +1,12 @@ #include #include #include +#include +#include +#include #include +#include #include #include #include diff --git a/src/xrpld/rpc/handlers/Fee1.cpp b/src/xrpld/rpc/handlers/server_info/Fee.cpp similarity index 86% rename from src/xrpld/rpc/handlers/Fee1.cpp rename to src/xrpld/rpc/handlers/server_info/Fee.cpp index 49a36261f4..d943d02f77 100644 --- a/src/xrpld/rpc/handlers/Fee1.cpp +++ b/src/xrpld/rpc/handlers/server_info/Fee.cpp @@ -1,9 +1,9 @@ -#include #include #include #include -#include +#include +#include #include namespace xrpl { diff --git a/src/xrpld/rpc/handlers/DoManifest.cpp b/src/xrpld/rpc/handlers/server_info/Manifest.cpp similarity index 96% rename from src/xrpld/rpc/handlers/DoManifest.cpp rename to src/xrpld/rpc/handlers/server_info/Manifest.cpp index ba3461033f..75533f0e78 100644 --- a/src/xrpld/rpc/handlers/DoManifest.cpp +++ b/src/xrpld/rpc/handlers/server_info/Manifest.cpp @@ -6,7 +6,9 @@ #include #include #include +#include #include +#include namespace xrpl { Json::Value diff --git a/src/xrpld/rpc/handlers/ServerDefinitions.cpp b/src/xrpld/rpc/handlers/server_info/ServerDefinitions.cpp similarity index 95% rename from src/xrpld/rpc/handlers/ServerDefinitions.cpp rename to src/xrpld/rpc/handlers/server_info/ServerDefinitions.cpp index f99f427ca8..76f123f442 100644 --- a/src/xrpld/rpc/handlers/ServerDefinitions.cpp +++ b/src/xrpld/rpc/handlers/server_info/ServerDefinitions.cpp @@ -1,8 +1,11 @@ -#include -#include +#include +#include + +#include #include #include +#include #include #include #include @@ -11,9 +14,14 @@ #include #include -#include +#include +#include +#include +#include +#include #include +#include #include #include @@ -363,8 +371,21 @@ ServerDefinitions::ServerDefinitions() : defs_{Json::objectValue} } } +ServerDefinitions const& +getDefinitions() +{ + static ServerDefinitions const defs{}; + return defs; +} + } // namespace detail +Json::Value const& +getServerDefinitionsJson() +{ + return detail::getDefinitions().get(); +} + Json::Value doServerDefinitions(RPC::JsonContext& context) { @@ -377,7 +398,7 @@ doServerDefinitions(RPC::JsonContext& context) return RPC::invalid_field_error(jss::hash); } - static detail::ServerDefinitions const defs{}; + auto const& defs = detail::getDefinitions(); if (defs.hashMatches(hash)) { Json::Value jv = Json::objectValue; diff --git a/src/xrpld/rpc/handlers/server_info/ServerDefinitions.h b/src/xrpld/rpc/handlers/server_info/ServerDefinitions.h new file mode 100644 index 0000000000..5b94a2a518 --- /dev/null +++ b/src/xrpld/rpc/handlers/server_info/ServerDefinitions.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +namespace xrpl { + +Json::Value const& +getServerDefinitionsJson(); + +} // namespace xrpl diff --git a/src/xrpld/rpc/handlers/ServerInfo.cpp b/src/xrpld/rpc/handlers/server_info/ServerInfo.cpp similarity index 100% rename from src/xrpld/rpc/handlers/ServerInfo.cpp rename to src/xrpld/rpc/handlers/server_info/ServerInfo.cpp diff --git a/src/xrpld/rpc/handlers/ServerState.cpp b/src/xrpld/rpc/handlers/server_info/ServerState.cpp similarity index 100% rename from src/xrpld/rpc/handlers/ServerState.cpp rename to src/xrpld/rpc/handlers/server_info/ServerState.cpp diff --git a/src/xrpld/rpc/handlers/Version.h b/src/xrpld/rpc/handlers/server_info/Version.h similarity index 85% rename from src/xrpld/rpc/handlers/Version.h rename to src/xrpld/rpc/handlers/server_info/Version.h index a03212e906..233ae3e4d8 100644 --- a/src/xrpld/rpc/handlers/Version.h +++ b/src/xrpld/rpc/handlers/server_info/Version.h @@ -2,8 +2,7 @@ #include -namespace xrpl { -namespace RPC { +namespace xrpl::RPC { class VersionHandler { @@ -13,14 +12,14 @@ public: { } - Status + static Status check() { return Status::OK; } void - writeResult(Json::Value& obj) + writeResult(Json::Value& obj) const { setVersion(obj, apiVersion_, betaEnabled_); } @@ -40,5 +39,4 @@ private: bool betaEnabled_; }; -} // namespace RPC -} // namespace xrpl +} // namespace xrpl::RPC diff --git a/src/xrpld/rpc/handlers/Subscribe.cpp b/src/xrpld/rpc/handlers/subscribe/Subscribe.cpp similarity index 84% rename from src/xrpld/rpc/handlers/Subscribe.cpp rename to src/xrpld/rpc/handlers/subscribe/Subscribe.cpp index af3e998a58..9be273587d 100644 --- a/src/xrpld/rpc/handlers/Subscribe.cpp +++ b/src/xrpld/rpc/handlers/subscribe/Subscribe.cpp @@ -4,14 +4,26 @@ #include #include #include +#include +#include +#include +#include #include +#include +#include #include #include #include #include +#include #include +#include +#include +#include +#include + namespace xrpl { Json::Value @@ -65,7 +77,7 @@ doSubscribe(RPC::JsonContext& context) ispSub = context.netOps.addRpcSub(strUrl, std::dynamic_pointer_cast(rspSub)); } - catch (std::runtime_error& ex) + catch (std::runtime_error const& ex) { return RPC::make_param_error(ex.what()); } @@ -217,48 +229,16 @@ doSubscribe(RPC::JsonContext& context) return rpcError(rpcINVALID_PARAMS); Book book; - Json::Value taker_pays = j[jss::taker_pays]; - Json::Value taker_gets = j[jss::taker_gets]; - // Parse mandatory currency. - if (!taker_pays.isMember(jss::currency) || - !to_currency(book.in.currency, taker_pays[jss::currency].asString())) - { - JLOG(context.j.info()) << "Bad taker_pays currency."; - return rpcError(rpcSRC_CUR_MALFORMED); - } + if (auto const err = RPC::parseSubUnsubJson(book.in, j, jss::taker_pays, context.j); + err != rpcSUCCESS) + return rpcError(err); - // Parse optional issuer. - if (((taker_pays.isMember(jss::issuer)) && - (!taker_pays[jss::issuer].isString() || - !to_issuer(book.in.account, taker_pays[jss::issuer].asString()))) - // Don't allow illegal issuers. - || (!book.in.currency != !book.in.account) || noAccount() == book.in.account) - { - JLOG(context.j.info()) << "Bad taker_pays issuer."; - return rpcError(rpcSRC_ISR_MALFORMED); - } + if (auto const err = RPC::parseSubUnsubJson(book.out, j, jss::taker_gets, context.j); + err != rpcSUCCESS) + return rpcError(err); - // Parse mandatory currency. - if (!taker_gets.isMember(jss::currency) || - !to_currency(book.out.currency, taker_gets[jss::currency].asString())) - { - JLOG(context.j.info()) << "Bad taker_gets currency."; - return rpcError(rpcDST_AMT_MALFORMED); - } - - // Parse optional issuer. - if (((taker_gets.isMember(jss::issuer)) && - (!taker_gets[jss::issuer].isString() || - !to_issuer(book.out.account, taker_gets[jss::issuer].asString()))) - // Don't allow illegal issuers. - || (!book.out.currency != !book.out.account) || noAccount() == book.out.account) - { - JLOG(context.j.info()) << "Bad taker_gets issuer."; - return rpcError(rpcDST_ISR_MALFORMED); - } - - if (book.in.currency == book.out.currency && book.in.account == book.out.account) + if (book.in == book.out) { JLOG(context.j.info()) << "taker_gets same as taker_pays."; return rpcError(rpcBAD_MARKET); diff --git a/src/xrpld/rpc/handlers/Unsubscribe.cpp b/src/xrpld/rpc/handlers/subscribe/Unsubscribe.cpp similarity index 77% rename from src/xrpld/rpc/handlers/Unsubscribe.cpp rename to src/xrpld/rpc/handlers/subscribe/Unsubscribe.cpp index d3e36cc612..258774d12a 100644 --- a/src/xrpld/rpc/handlers/Unsubscribe.cpp +++ b/src/xrpld/rpc/handlers/subscribe/Unsubscribe.cpp @@ -3,11 +3,18 @@ #include #include +#include +#include +#include +#include #include #include #include +#include #include +#include + namespace xrpl { Json::Value @@ -152,49 +159,15 @@ doUnsubscribe(RPC::JsonContext& context) return rpcError(rpcINVALID_PARAMS); } - Json::Value taker_pays = jv[jss::taker_pays]; - Json::Value taker_gets = jv[jss::taker_gets]; - Book book; - // Parse mandatory currency. - if (!taker_pays.isMember(jss::currency) || - !to_currency(book.in.currency, taker_pays[jss::currency].asString())) - { - JLOG(context.j.info()) << "Bad taker_pays currency."; - return rpcError(rpcSRC_CUR_MALFORMED); - } - // Parse optional issuer. - if (((taker_pays.isMember(jss::issuer)) && - (!taker_pays[jss::issuer].isString() || - !to_issuer(book.in.account, taker_pays[jss::issuer].asString()))) - // Don't allow illegal issuers. - || !isConsistent(book.in) || noAccount() == book.in.account) - { - JLOG(context.j.info()) << "Bad taker_pays issuer."; + if (auto const err = RPC::parseSubUnsubJson(book.in, jv, jss::taker_pays, context.j); + err != rpcSUCCESS) + return rpcError(err); - return rpcError(rpcSRC_ISR_MALFORMED); - } - - // Parse mandatory currency. - if (!taker_gets.isMember(jss::currency) || - !to_currency(book.out.currency, taker_gets[jss::currency].asString())) - { - JLOG(context.j.info()) << "Bad taker_gets currency."; - - return rpcError(rpcDST_AMT_MALFORMED); - } - // Parse optional issuer. - if (((taker_gets.isMember(jss::issuer)) && - (!taker_gets[jss::issuer].isString() || - !to_issuer(book.out.account, taker_gets[jss::issuer].asString()))) - // Don't allow illegal issuers. - || !isConsistent(book.out) || noAccount() == book.out.account) - { - JLOG(context.j.info()) << "Bad taker_gets issuer."; - - return rpcError(rpcDST_ISR_MALFORMED); - } + if (auto const err = RPC::parseSubUnsubJson(book.out, jv, jss::taker_gets, context.j); + err != rpcSUCCESS) + return rpcError(err); if (book.in == book.out) { diff --git a/src/xrpld/rpc/handlers/Simulate.cpp b/src/xrpld/rpc/handlers/transaction/Simulate.cpp similarity index 92% rename from src/xrpld/rpc/handlers/Simulate.cpp rename to src/xrpld/rpc/handlers/transaction/Simulate.cpp index c1d6d7f334..f5004bab13 100644 --- a/src/xrpld/rpc/handlers/Simulate.cpp +++ b/src/xrpld/rpc/handlers/transaction/Simulate.cpp @@ -4,18 +4,41 @@ #include #include #include -#include #include #include -#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include #include +#include #include #include +#include #include +#include +#include +#include +#include #include -#include + +#include +#include +#include +#include +#include +#include +#include +#include namespace xrpl { @@ -339,7 +362,7 @@ doSimulate(RPC::JsonContext& context) { return simulateTxn(context, transaction); } - // LCOV_EXCL_START this is just in case, so rippled doesn't crash + // LCOV_EXCL_START this is just in case, so xrpld doesn't crash catch (std::exception const& e) { Json::Value jvResult = Json::objectValue; diff --git a/src/xrpld/rpc/handlers/Submit.cpp b/src/xrpld/rpc/handlers/transaction/Submit.cpp similarity index 92% rename from src/xrpld/rpc/handlers/Submit.cpp rename to src/xrpld/rpc/handlers/transaction/Submit.cpp index cac7259a00..73260c0661 100644 --- a/src/xrpld/rpc/handlers/Submit.cpp +++ b/src/xrpld/rpc/handlers/transaction/Submit.cpp @@ -1,13 +1,27 @@ #include #include #include +#include #include +#include +#include +#include +#include #include #include +#include +#include +#include +#include +#include #include #include +#include +#include +#include + namespace xrpl { static NetworkOPs::FailHard diff --git a/src/xrpld/rpc/handlers/SubmitMultiSigned.cpp b/src/xrpld/rpc/handlers/transaction/SubmitMultiSigned.cpp similarity index 92% rename from src/xrpld/rpc/handlers/SubmitMultiSigned.cpp rename to src/xrpld/rpc/handlers/transaction/SubmitMultiSigned.cpp index 52213e174a..d121e9a850 100644 --- a/src/xrpld/rpc/handlers/SubmitMultiSigned.cpp +++ b/src/xrpld/rpc/handlers/transaction/SubmitMultiSigned.cpp @@ -2,7 +2,8 @@ #include #include -#include +#include +#include #include namespace xrpl { diff --git a/src/xrpld/rpc/handlers/TransactionEntry.cpp b/src/xrpld/rpc/handlers/transaction/TransactionEntry.cpp similarity index 96% rename from src/xrpld/rpc/handlers/TransactionEntry.cpp rename to src/xrpld/rpc/handlers/transaction/TransactionEntry.cpp index 36f53130fa..67fd2504fe 100644 --- a/src/xrpld/rpc/handlers/TransactionEntry.cpp +++ b/src/xrpld/rpc/handlers/transaction/TransactionEntry.cpp @@ -3,9 +3,14 @@ #include #include +#include +#include +#include #include #include +#include + namespace xrpl { // { diff --git a/src/xrpld/rpc/handlers/Tx.cpp b/src/xrpld/rpc/handlers/transaction/Tx.cpp similarity index 94% rename from src/xrpld/rpc/handlers/Tx.cpp rename to src/xrpld/rpc/handlers/transaction/Tx.cpp index a3ed788060..530b45e225 100644 --- a/src/xrpld/rpc/handlers/Tx.cpp +++ b/src/xrpld/rpc/handlers/transaction/Tx.cpp @@ -5,20 +5,35 @@ #include #include #include -#include #include #include -#include +#include +#include +#include +#include +#include +#include +#include #include +#include #include #include #include +#include +#include +#include #include #include #include -#include +#include +#include +#include +#include +#include +#include +#include namespace xrpl { diff --git a/src/xrpld/rpc/handlers/TxHistory.cpp b/src/xrpld/rpc/handlers/transaction/TxHistory.cpp similarity index 96% rename from src/xrpld/rpc/handlers/TxHistory.cpp rename to src/xrpld/rpc/handlers/transaction/TxHistory.cpp index 3467b1c990..1669d289b4 100644 --- a/src/xrpld/rpc/handlers/TxHistory.cpp +++ b/src/xrpld/rpc/handlers/transaction/TxHistory.cpp @@ -1,4 +1,3 @@ -#include #include #include #include diff --git a/src/xrpld/rpc/handlers/TxReduceRelay.cpp b/src/xrpld/rpc/handlers/transaction/TxReduceRelay.cpp similarity index 100% rename from src/xrpld/rpc/handlers/TxReduceRelay.cpp rename to src/xrpld/rpc/handlers/transaction/TxReduceRelay.cpp diff --git a/src/xrpld/rpc/handlers/Ping.cpp b/src/xrpld/rpc/handlers/utility/Ping.cpp similarity index 87% rename from src/xrpld/rpc/handlers/Ping.cpp rename to src/xrpld/rpc/handlers/utility/Ping.cpp index 4e9b18c4c9..695d90b964 100644 --- a/src/xrpld/rpc/handlers/Ping.cpp +++ b/src/xrpld/rpc/handlers/utility/Ping.cpp @@ -27,7 +27,9 @@ doPing(RPC::JsonContext& context) break; case Role::PROXY: ret[jss::role] = "proxied"; - ret[jss::ip] = std::string{context.headers.forwardedFor}; + if (!context.headers.forwardedFor.empty()) + ret[jss::ip] = std::string{context.headers.forwardedFor}; + break; default:; } diff --git a/src/xrpld/rpc/handlers/Random.cpp b/src/xrpld/rpc/handlers/utility/Random.cpp similarity index 97% rename from src/xrpld/rpc/handlers/Random.cpp rename to src/xrpld/rpc/handlers/utility/Random.cpp index 5ed4426940..e17a8928bb 100644 --- a/src/xrpld/rpc/handlers/Random.cpp +++ b/src/xrpld/rpc/handlers/utility/Random.cpp @@ -6,6 +6,8 @@ #include #include +#include + namespace xrpl { namespace RPC { diff --git a/src/xrpld/rpc/json_body.h b/src/xrpld/rpc/json_body.h index d70fafb2d4..2e9e774992 100644 --- a/src/xrpld/rpc/json_body.h +++ b/src/xrpld/rpc/json_body.h @@ -69,7 +69,7 @@ struct json_body { } - void + static void init(boost::beast::error_code& ec) { ec.assign(0, ec.category()); diff --git a/src/xrpld/shamap/NodeFamily.cpp b/src/xrpld/shamap/NodeFamily.cpp index 3460c68608..2c47515498 100644 --- a/src/xrpld/shamap/NodeFamily.cpp +++ b/src/xrpld/shamap/NodeFamily.cpp @@ -1,8 +1,22 @@ +#include + +#include #include #include #include #include -#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include namespace xrpl { diff --git a/src/xrpld/shamap/NodeFamily.h b/src/xrpld/shamap/NodeFamily.h index ab555919ac..3985a8bdf8 100644 --- a/src/xrpld/shamap/NodeFamily.h +++ b/src/xrpld/shamap/NodeFamily.h @@ -1,5 +1,8 @@ #pragma once +#include + +#include #include namespace xrpl { diff --git a/tests/README.md b/tests/README.md index c4a96005e7..431c2c464a 100644 --- a/tests/README.md +++ b/tests/README.md @@ -1,5 +1,5 @@ # Integration tests This directory contains integration tests for the project. These tests are run -against the `libxrpl` library or `rippled` binary to verify they are working as +against the `libxrpl` library or `xrpld` binary to verify they are working as expected.