From 0b87a26f04641b1d48a7738d6a2228bc02159e85 Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Mon, 5 Jan 2026 14:01:14 +0000 Subject: [PATCH 01/30] Revert "chore: Pin ruamel.yaml<0.19 in pre-commit-hooks (#6166)" (#6167) This reverts commit 0f23ad820c84d0c8e97e2f0caa7c2e2690397207. --- .pre-commit-config.yaml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 01b0e85930..c85e0798f7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,16 +13,10 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: 3e8a8703264a2f4a69428a0aa4dcb512790b2c8c # frozen: v6.0.0 hooks: - # https://github.com/pre-commit/pre-commit-hooks/issues/1229 - # regarding ruamel.yaml version - id: trailing-whitespace - additional_dependencies: [ruamel.yaml<0.19] - id: end-of-file-fixer - additional_dependencies: [ruamel.yaml<0.19] - id: mixed-line-ending - additional_dependencies: [ruamel.yaml<0.19] - id: check-merge-conflict - additional_dependencies: [ruamel.yaml<0.19] args: [--assume-in-merge] - repo: https://github.com/pre-commit/mirrors-clang-format From 3d1b3a49b3601a0a7037fa0b19d5df7b5e0e2fc1 Mon Sep 17 00:00:00 2001 From: Bart Date: Mon, 5 Jan 2026 09:55:12 -0500 Subject: [PATCH 02/30] refactor: Rename `rippled.cfg` to `xrpld.cfg` (#6098) This change renames all occurrences of `rippled.cfg` to `xrpld.cfg`. It also provides a script to allow developers to replicate the changes in their local branch or fork to avoid conflicts. For the time being it maintains support for `rippled.cfg` as config file, if `xrpld.cfg` does not exist. --- .github/scripts/rename/README.md | 4 + .github/scripts/rename/config.sh | 72 ++++ .github/workflows/reusable-check-rename.yml | 2 + .gitignore | 1 + cfg/validators-example.txt | 2 +- ...{rippled-example.cfg => xrpld-example.cfg} | 108 +++--- cmake/XrplInstall.cmake | 2 +- include/xrpl/core/PerfLog.h | 2 +- include/xrpl/nodestore/README.md | 2 +- src/libxrpl/nodestore/ManagerImp.cpp | 4 +- src/test/core/Config_test.cpp | 310 +++++++++++++----- src/xrpld/app/misc/SHAMapStoreImp.h | 2 +- src/xrpld/core/Config.h | 1 + src/xrpld/core/detail/Config.cpp | 113 +++---- src/xrpld/overlay/README.md | 2 +- src/xrpld/overlay/detail/OverlayImpl.cpp | 2 +- 16 files changed, 433 insertions(+), 196 deletions(-) create mode 100755 .github/scripts/rename/config.sh rename cfg/{rippled-example.cfg => xrpld-example.cfg} (94%) diff --git a/.github/scripts/rename/README.md b/.github/scripts/rename/README.md index 392f0b1efc..8336f23bec 100644 --- a/.github/scripts/rename/README.md +++ b/.github/scripts/rename/README.md @@ -31,6 +31,9 @@ run from the repository root. the `xrpld` binary. 5. `.github/scripts/rename/namespace.sh`: This script will rename the C++ namespaces from `ripple` to `xrpl`. +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. You can run all these scripts from the repository root as follows: @@ -40,4 +43,5 @@ You can run all these scripts from the repository root as follows: ./.github/scripts/rename/cmake.sh . ./.github/scripts/rename/binary.sh . ./.github/scripts/rename/namespace.sh . +./.github/scripts/rename/config.sh . ``` diff --git a/.github/scripts/rename/config.sh b/.github/scripts/rename/config.sh new file mode 100755 index 0000000000..7f36e8fd21 --- /dev/null +++ b/.github/scripts/rename/config.sh @@ -0,0 +1,72 @@ +#!/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 the config from `rippled.cfg` to `xrpld.cfg`, and updates +# the code accordingly. The old filename will still be accepted. +# Usage: .github/scripts/rename/config.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} + +# Add the xrpld.cfg to the .gitignore. +if ! grep -q 'xrpld.cfg' .gitignore; then + ${SED_COMMAND} -i '/rippled.cfg/a\ +/xrpld.cfg' .gitignore +fi + +# Rename the files. +if [ -e rippled.cfg ]; then + mv rippled.cfg xrpld.cfg +fi +if [ -e cfg/rippled-example.cfg ]; then + 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}" + + 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 +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 +${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 + +# Restore an URL. +${SED_COMMAND} -i 's/connect-your-xrpld-to-the-xrp-test-net.html/connect-your-rippled-to-the-xrp-test-net.html/g' cfg/xrpld-example.cfg + +popd +echo "Renaming complete." diff --git a/.github/workflows/reusable-check-rename.yml b/.github/workflows/reusable-check-rename.yml index fb9ed2c6a8..af55084405 100644 --- a/.github/workflows/reusable-check-rename.yml +++ b/.github/workflows/reusable-check-rename.yml @@ -29,6 +29,8 @@ jobs: run: .github/scripts/rename/binary.sh . - name: Check namespaces run: .github/scripts/rename/namespace.sh . + - name: Check config name + run: .github/scripts/rename/config.sh . - name: Check for differences env: MESSAGE: | diff --git a/.gitignore b/.gitignore index 55844462e5..c4e81408bb 100644 --- a/.gitignore +++ b/.gitignore @@ -35,6 +35,7 @@ gmon.out # Customized configs. /rippled.cfg +/xrpld.cfg /validators.txt # Locally patched Conan recipes diff --git a/cfg/validators-example.txt b/cfg/validators-example.txt index dbcff90f12..6eb49da697 100644 --- a/cfg/validators-example.txt +++ b/cfg/validators-example.txt @@ -1,7 +1,7 @@ # # Default validators.txt # -# This file is located in the same folder as your rippled.cfg file +# This file is located in the same folder as your xrpld.cfg file # and defines which validators your server trusts not to collude. # # This file is UTF-8 with DOS, UNIX, or Mac style line endings. diff --git a/cfg/rippled-example.cfg b/cfg/xrpld-example.cfg similarity index 94% rename from cfg/rippled-example.cfg rename to cfg/xrpld-example.cfg index 5db008431d..b5180dce52 100644 --- a/cfg/rippled-example.cfg +++ b/cfg/xrpld-example.cfg @@ -29,18 +29,18 @@ # # Purpose # -# This file documents and provides examples of all rippled server process -# configuration options. When the rippled server instance is launched, it +# This file documents and provides examples of all xrpld server process +# configuration options. When the xrpld server instance is launched, it # looks for a file with the following name: # -# rippled.cfg +# xrpld.cfg # -# For more information on where the rippled server instance searches for the +# For more information on where the xrpld server instance searches for the # file, visit: # # https://xrpl.org/commandline-usage.html#generic-options # -# This file should be named rippled.cfg. This file is UTF-8 with DOS, UNIX, +# This file should be named xrpld.cfg. This file is UTF-8 with DOS, UNIX, # or Mac style end of lines. Blank lines and lines beginning with '#' are # ignored. Undefined sections are reserved. No escapes are currently defined. # @@ -89,8 +89,8 @@ # # # -# rippled offers various server protocols to clients making inbound -# connections. The listening ports rippled uses are "universal" ports +# xrpld offers various server protocols to clients making inbound +# connections. The listening ports xrpld uses are "universal" ports # which may be configured to handshake in one or more of the available # supported protocols. These universal ports simplify administration: # A single open port can be used for multiple protocols. @@ -103,7 +103,7 @@ # # A list of port names and key/value pairs. A port name must start with a # letter and contain only letters and numbers. The name is not case-sensitive. -# For each name in this list, rippled will look for a configuration file +# For each name in this list, xrpld will look for a configuration file # section with the same name and use it to create a listening port. The # name is informational only; the choice of name does not affect the function # of the listening port. @@ -134,7 +134,7 @@ # ip = 127.0.0.1 # protocol = http # -# When rippled is used as a command line client (for example, issuing a +# When xrpld is used as a command line client (for example, issuing a # server stop command), the first port advertising the http or https # protocol will be used to make the connection. # @@ -175,7 +175,7 @@ # same time. It is possible have both Websockets and Secure Websockets # together in one port. # -# NOTE If no ports support the peer protocol, rippled cannot +# NOTE If no ports support the peer protocol, xrpld cannot # receive incoming peer connections or become a superpeer. # # limit = @@ -194,7 +194,7 @@ # required. IP address restrictions, if any, will be checked in addition # to the credentials specified here. # -# When acting in the client role, rippled will supply these credentials +# When acting in the client role, xrpld will supply these credentials # using HTTP's Basic Authentication headers when making outbound HTTP/S # requests. # @@ -237,7 +237,7 @@ # WS, or WSS protocol interfaces. If administrative commands are # disabled for a port, these credentials have no effect. # -# When acting in the client role, rippled will supply these credentials +# When acting in the client role, xrpld will supply these credentials # in the submitted JSON for any administrative command requests when # invoking JSON-RPC commands on remote servers. # @@ -258,7 +258,7 @@ # resource controls will default to those for non-administrative users. # # The secure_gateway IP addresses are intended to represent -# proxies. Since rippled trusts these hosts, they must be +# proxies. Since xrpld trusts these hosts, they must be # responsible for properly authenticating the remote user. # # If some IP addresses are included for both "admin" and @@ -272,7 +272,7 @@ # Use the specified files when configuring SSL on the port. # # NOTE If no files are specified and secure protocols are selected, -# rippled will generate an internal self-signed certificate. +# xrpld will generate an internal self-signed certificate. # # The files have these meanings: # @@ -297,12 +297,12 @@ # Control the ciphers which the server will support over SSL on the port, # specified using the OpenSSL "cipher list format". # -# NOTE If unspecified, rippled will automatically configure a modern +# NOTE If unspecified, xrpld will automatically configure a modern # cipher suite. This default suite should be widely supported. # # You should not modify this string unless you have a specific # reason and cryptographic expertise. Incorrect modification may -# keep rippled from connecting to other instances of rippled or +# keep xrpld from connecting to other instances of xrpld or # prevent RPC and WebSocket clients from connecting. # # send_queue_limit = [1..65535] @@ -382,7 +382,7 @@ #----------------- # # These settings control security and access attributes of the Peer to Peer -# server section of the rippled process. Peer Protocol implements the +# server section of the xrpld process. Peer Protocol implements the # Ripple 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. @@ -396,7 +396,7 @@ # true - enables compression # false - disables compression [default]. # -# The rippled server can save bandwidth by compressing its peer-to-peer communications, +# The xrpld server can save bandwidth by compressing its peer-to-peer communications, # at a cost of greater CPU usage. If you enable link compression, # the server automatically compresses communications with peer servers # that also have link compression enabled. @@ -432,7 +432,7 @@ # # [ips_fixed] # -# List of IP addresses or hostnames to which rippled should always attempt to +# 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 @@ -573,7 +573,7 @@ # # minimum_txn_in_ledger_standalone = # -# Like minimum_txn_in_ledger when rippled is running in standalone +# Like minimum_txn_in_ledger when xrpld is running in standalone # mode. Default: 1000. # # target_txn_in_ledger = @@ -710,7 +710,7 @@ # # [validator_token] # -# This is an alternative to [validation_seed] that allows rippled to perform +# This is an alternative to [validation_seed] that allows xrpld to perform # validation without having to store the validator keys on the network # connected server. The field should contain a single token in the form of a # base64-encoded blob. @@ -745,7 +745,7 @@ # # Specify the file by its name or path. # Unless an absolute path is specified, it will be considered relative to -# the folder in which the rippled.cfg file is located. +# the folder in which the xrpld.cfg file is located. # # Examples: # /home/ripple/validators.txt @@ -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 rippled node only downloads +# acquiring a ledger from the network, a 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. @@ -851,7 +851,7 @@ # #---------------- # -# The rippled server instance uses HTTPS GET requests in a variety of +# 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 # Network address. @@ -891,7 +891,7 @@ # #------------ # -# rippled creates 4 SQLite database to hold bookkeeping information +# xrpld creates 4 SQLite database to hold bookkeeping information # about transactions, local credentials, and various other things. # It also creates the NodeDB, which holds all the objects that # make up the current and historical ledgers. @@ -902,7 +902,7 @@ # the performance of the server. # # Partial pathnames will be considered relative to the location of -# the rippled.cfg file. +# the xrpld.cfg file. # # [node_db] Settings for the Node Database (required) # @@ -920,11 +920,11 @@ # type = NuDB # # NuDB is a high-performance database written by Ripple Labs and optimized -# for rippled and solid-state drives. +# for xrpld and solid-state drives. # # NuDB maintains its high speed regardless of the amount of history # stored. Online delete may be selected, but is not required. NuDB is -# available on all platforms that rippled runs on. +# available on all platforms that xrpld runs on. # # type = RocksDB # @@ -1049,7 +1049,7 @@ # # recovery_wait_seconds # The online delete process checks periodically -# that rippled is still in sync with the network, +# that xrpld is still in sync with the network, # and that the validated ledger is less than # 'age_threshold_seconds' old. If not, then continue # sleeping for this number of seconds and @@ -1069,8 +1069,8 @@ # The server creates and maintains 4 to 5 bookkeeping SQLite databases in # the 'database_path' location. If you omit this configuration setting, # the server creates a directory called "db" located in the same place as -# your rippled.cfg file. -# Partial pathnames are relative to the location of the rippled executable. +# your xrpld.cfg file. +# Partial pathnames are relative to the location of the xrpld executable. # # [sqlite] Tuning settings for the SQLite databases (optional) # @@ -1120,7 +1120,7 @@ # The default is "wal", which uses a write-ahead # log to implement database transactions. # Alternately, "memory" saves disk I/O, but if -# rippled crashes during a transaction, the +# xrpld crashes during a transaction, the # database is likely to be corrupted. # See https://www.sqlite.org/pragma.html#pragma_journal_mode # for more details about the available options. @@ -1130,7 +1130,7 @@ # synchronous Valid values: off, normal, full, extra # The default is "normal", which works well with # the "wal" journal mode. Alternatively, "off" -# allows rippled to continue as soon as data is +# allows xrpld to continue as soon as data is # passed to the OS, which can significantly # increase speed, but risks data corruption if # the host computer crashes before writing that @@ -1144,7 +1144,7 @@ # The default is "file", which will use files # for temporary database tables and indices. # Alternatively, "memory" may save I/O, but -# rippled does not currently use many, if any, +# xrpld does not currently use many, if any, # of these temporary objects. # See https://www.sqlite.org/pragma.html#pragma_temp_store # for more details about the available options. @@ -1173,7 +1173,7 @@ # # These settings are designed to help server administrators diagnose # problems, and obtain detailed information about the activities being -# performed by the rippled process. +# performed by the xrpld process. # # # @@ -1190,7 +1190,7 @@ # # Configuration parameters for the Beast. Insight stats collection module. # -# Insight is a module that collects information from the areas of rippled +# Insight is a module that collects information from the areas of xrpld # that have instrumentation. The configuration parameters control where the # collection metrics are sent. The parameters are expressed as key = value # pairs with no white space. The main parameter is the choice of server: @@ -1199,7 +1199,7 @@ # # Choice of server to send metrics to. Currently the only choice is # "statsd" which sends UDP packets to a StatsD daemon, which must be -# running while rippled is running. More information on StatsD is +# running while xrpld is running. More information on StatsD is # available here: # https://github.com/b/statsd_spec # @@ -1209,7 +1209,7 @@ # in the format, n.n.n.n:port. # # "prefix" A string prepended to each collected metric. This is used -# to distinguish between different running instances of rippled. +# to distinguish between different running instances of xrpld. # # If this section is missing, or the server type is unspecified or unknown, # statistics are not collected or reported. @@ -1236,7 +1236,7 @@ # # Example: # [perf] -# perf_log=/var/log/rippled/perf.log +# perf_log=/var/log/xrpld/perf.log # log_interval=2 # #------------------------------------------------------------------------------- @@ -1246,7 +1246,7 @@ #---------- # # The vote settings configure settings for the entire Ripple network. -# While a single instance of rippled cannot unilaterally enforce network-wide +# 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. # @@ -1260,7 +1260,7 @@ # The reference transaction is the simplest form of transaction. # It represents an XRP payment between two parties. # -# If this parameter is unspecified, rippled will use an internal +# If this parameter is unspecified, xrpld will use an internal # default. Don't change this without understanding the consequences. # # Example: @@ -1272,7 +1272,7 @@ # account's XRP balance that is at or below the reserve may only be # spent on transaction fees, and not transferred out of the account. # -# If this parameter is unspecified, rippled will use an internal +# If this parameter is unspecified, xrpld will use an internal # default. Don't change this without understanding the consequences. # # Example: @@ -1284,7 +1284,7 @@ # each ledger item owned by the account. Ledger items an account may # own include trust lines, open orders, and tickets. # -# If this parameter is unspecified, rippled will use an internal +# If this parameter is unspecified, xrpld will use an internal # default. Don't change this without understanding the consequences. # # Example: @@ -1326,7 +1326,7 @@ # tool instead. # # This flag has no effect on the "sign" and "sign_for" command line options -# that rippled makes available. +# that xrpld makes available. # # The default value of this field is "false" # @@ -1405,7 +1405,7 @@ #-------------------- # # Administrators can use these values as a starting point for configuring -# their instance of rippled, but each value should be checked to make sure +# their instance of xrpld, but each value should be checked to make sure # it meets the business requirements for the organization. # # Server @@ -1415,7 +1415,7 @@ # "peer" # # Peer protocol open to everyone. This is required to accept -# incoming rippled connections. This does not affect automatic +# incoming xrpld connections. This does not affect automatic # or manual outgoing Peer protocol connections. # # "rpc" @@ -1432,7 +1432,7 @@ # # ETL commands for Clio. We recommend setting secure_gateway # in this section to a comma-separated list of the addresses -# of your Clio servers, in order to bypass rippled's rate limiting. +# of your Clio servers, in order to bypass xrpld's rate limiting. # # This port is commented out but can be enabled by removing # the '#' from each corresponding line including the entry under [server] @@ -1449,8 +1449,8 @@ # NOTE # # To accept connections on well known ports such as 80 (HTTP) or -# 443 (HTTPS), most operating systems will require rippled to -# run with administrator privileges, or else rippled will not start. +# 443 (HTTPS), most operating systems will require xrpld to +# run with administrator privileges, or else xrpld will not start. [server] port_rpc_admin_local @@ -1496,7 +1496,7 @@ secure_gateway = 127.0.0.1 #------------------------------------------------------------------------------- -# This is primary persistent datastore for rippled. This includes transaction +# This is primary persistent datastore for xrpld. This includes transaction # metadata, account states, and ledger headers. Helpful information can be # found at https://xrpl.org/capacity-planning.html#node-db-type # type=NuDB is recommended for non-validators with fast SSDs. Validators or @@ -1511,19 +1511,19 @@ secure_gateway = 127.0.0.1 # deletion. [node_db] type=NuDB -path=/var/lib/rippled/db/nudb +path=/var/lib/xrpld/db/nudb nudb_block_size=4096 online_delete=512 advisory_delete=0 [database_path] -/var/lib/rippled/db +/var/lib/xrpld/db # This needs to be an absolute directory reference, not a relative one. # Modify this value as required. [debug_logfile] -/var/log/rippled/debug.log +/var/log/xrpld/debug.log # To use the XRP test network # (see https://xrpl.org/connect-your-rippled-to-the-xrp-test-net.html), @@ -1533,7 +1533,7 @@ advisory_delete=0 # File containing trusted validator keys or validator list publishers. # Unless an absolute path is specified, it will be considered relative to the -# folder in which the rippled.cfg file is located. +# folder in which the xrpld.cfg file is located. [validators_file] validators.txt diff --git a/cmake/XrplInstall.cmake b/cmake/XrplInstall.cmake index 67aca8f048..310436998d 100644 --- a/cmake/XrplInstall.cmake +++ b/cmake/XrplInstall.cmake @@ -62,7 +62,7 @@ if (is_root_project AND TARGET xrpld) message (\"-- Skipping : \$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/\${DEST}/\${NEWNAME}\") endif () endmacro() - copy_if_not_exists(\"${CMAKE_CURRENT_SOURCE_DIR}/cfg/rippled-example.cfg\" etc rippled.cfg) + copy_if_not_exists(\"${CMAKE_CURRENT_SOURCE_DIR}/cfg/xrpld-example.cfg\" etc xrpld.cfg) copy_if_not_exists(\"${CMAKE_CURRENT_SOURCE_DIR}/cfg/validators-example.txt\" etc validators.txt) ") install(CODE " diff --git a/include/xrpl/core/PerfLog.h b/include/xrpl/core/PerfLog.h index eb009de433..f8ca779963 100644 --- a/include/xrpl/core/PerfLog.h +++ b/include/xrpl/core/PerfLog.h @@ -40,7 +40,7 @@ public: using microseconds = std::chrono::microseconds; /** - * Configuration from [perf] section of rippled.cfg. + * Configuration from [perf] section of xrpld.cfg. */ struct Setup { diff --git a/include/xrpl/nodestore/README.md b/include/xrpl/nodestore/README.md index a5d1128d17..83da29d9ce 100644 --- a/include/xrpl/nodestore/README.md +++ b/include/xrpl/nodestore/README.md @@ -130,7 +130,7 @@ newer versions of RocksDB (TBD). ## Discussion RocksDBQuickFactory is intended to provide a testbed for comparing potential -rocksdb performance with the existing recommended configuration in rippled.cfg. +rocksdb performance with the existing recommended configuration in xrpld.cfg. Through various executions and profiling some conclusions are presented below. - If the write ahead log is enabled, insert speed soon clogs up under load. The diff --git a/src/libxrpl/nodestore/ManagerImp.cpp b/src/libxrpl/nodestore/ManagerImp.cpp index b53d03e668..58accd4108 100644 --- a/src/libxrpl/nodestore/ManagerImp.cpp +++ b/src/libxrpl/nodestore/ManagerImp.cpp @@ -18,8 +18,8 @@ void ManagerImp::missing_backend() { Throw( - "Your rippled.cfg is missing a [node_db] entry, " - "please see the rippled-example.cfg file!"); + "Your xrpld.cfg is missing a [node_db] entry, " + "please see the xrpld-example.cfg file!"); } // We shouldn't rely on global variables for lifetime management because their diff --git a/src/test/core/Config_test.cpp b/src/test/core/Config_test.cpp index 86f09e6c02..139e8702eb 100644 --- a/src/test/core/Config_test.cpp +++ b/src/test/core/Config_test.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -18,7 +19,7 @@ namespace detail { std::string configContents(std::string const& dbPath, std::string const& validatorsFile) { - static boost::format configContentsTemplate(R"rippleConfig( + static boost::format configContentsTemplate(R"xrpldConfig( [server] port_rpc port_peer @@ -51,14 +52,14 @@ protocol = wss [node_size] medium -# This is primary persistent datastore for rippled. This includes transaction +# This is primary persistent datastore for xrpld. This includes transaction # metadata, account states, and ledger headers. Helpful information can be # found on https://xrpl.org/capacity-planning.html#node-db-type # delete old ledgers while maintaining at least 2000. Do not require an # external administrative command to initiate deletion. [node_db] type=memory -path=/Users/dummy/ripple/config/db/rocksdb +path=/Users/dummy/xrpld/config/db/rocksdb open_files=2000 filter_bits=12 cache_mb=256 @@ -72,7 +73,7 @@ file_size_mult=2 # This needs to be an absolute directory reference, not a relative one. # Modify this value as required. [debug_logfile] -/Users/dummy/ripple/config/log/debug.log +/Users/dummy/xrpld/config/log/debug.log [sntp_servers] time.windows.com @@ -97,7 +98,7 @@ r.ripple.com 51235 [sqdb] backend=sqlite -)rippleConfig"); +)xrpldConfig"); std::string dbPathSection = dbPath.empty() ? "" : "[database_path]\n" + dbPath; @@ -107,9 +108,9 @@ backend=sqlite } /** - Write a rippled config file and remove when done. + Write a xrpld config file and remove when done. */ -class RippledCfgGuard : public xrpl::detail::FileDirGuard +class FileCfgGuard : public xrpl::detail::FileDirGuard { private: path dataDir_; @@ -119,17 +120,18 @@ private: Config config_; public: - RippledCfgGuard( + FileCfgGuard( beast::unit_test::suite& test, path subDir, path const& dbPath, + path const& configFile, path const& validatorsFile, bool useCounter = true, std::string confContents = "") : FileDirGuard( test, std::move(subDir), - path(Config::configFileName), + configFile, confContents.empty() ? configContents(dbPath.string(), validatorsFile.string()) : confContents, @@ -171,7 +173,7 @@ public: return fileExists(); } - ~RippledCfgGuard() + ~FileCfgGuard() { try { @@ -182,7 +184,7 @@ public: catch (std::exception& e) { // if we throw here, just let it die. - test_.log << "Error in ~RippledCfgGuard: " << e.what() << std::endl; + test_.log << "Error in ~FileCfgGuard: " << e.what() << std::endl; }; } }; @@ -190,7 +192,7 @@ public: std::string valFileContents() { - std::string configContents(R"rippleConfig( + std::string configContents(R"xrpldConfig( [validators] n949f75evCHwgyP4fPVgaHqNHxUVN15PsJEZ3B3HnXPcPjcZAoy7 n9MD5h24qrQqiyBC8aeqqCWvpiBiYQ3jxSr91uiDvmrkyHRdYLUj @@ -204,8 +206,8 @@ nHBu9PTL9dn2GuZtdW4U2WzBwffyX9qsQCd9CNU4Z5YG3PQfViM8 nHUPDdcdb2Y5DZAJne4c2iabFuAP3F34xZUgYQT2NH7qfkdapgnz [validator_list_sites] -recommendedripplevalidators.com -moreripplevalidators.net +recommendedxrplvalidators.com +morexrplvalidators.net [validator_list_keys] 03E74EE14CB525AFBB9F1B7D86CD58ECC4B91452294B42AB4E78F260BD905C091D @@ -213,7 +215,7 @@ moreripplevalidators.net [validator_list_threshold] 2 -)rippleConfig"); +)xrpldConfig"); return configContents; } @@ -270,7 +272,7 @@ public: Config c; - std::string toLoad(R"rippleConfig( + std::string toLoad(R"xrpldConfig( [server] port_rpc port_peer @@ -278,7 +280,7 @@ port_wss_admin [ssl_verify] 0 -)rippleConfig"); +)xrpldConfig"); c.loadFromString(toLoad); @@ -291,6 +293,126 @@ port_wss_admin BEAST_EXPECT(c.legacy("not_in_file") == "new_value"); } void + testConfigFile() + { + testcase("config_file"); + + using namespace boost::filesystem; + auto const cwd = current_path(); + + // Test both config file names. + char const* configFiles[] = { + Config::configFileName, Config::configLegacyName}; + + // Config file in current directory. + for (auto const& configFile : configFiles) + { + // Use a temporary directory for testing. + beast::temp_dir td; + current_path(td.path()); + path const f = td.file(configFile); + std::ofstream o(f.string()); + o << detail::configContents("", ""); + o.close(); + + // Load the config file from the current directory and verify it. + Config c; + c.setup("", true, false, true); + BEAST_EXPECT(c.section(SECTION_DEBUG_LOGFILE).values().size() == 1); + BEAST_EXPECT( + c.section(SECTION_DEBUG_LOGFILE).values()[0] == + "/Users/dummy/xrpld/config/log/debug.log"); + } + + // Config file in HOME or XDG_CONFIG_HOME directory. +#if BOOST_OS_LINUX || BOOST_OS_MACOS + for (auto const& configFile : configFiles) + { + // Point the current working directory to a temporary directory, so + // we don't pick up an actual config file from the repository root. + beast::temp_dir td; + current_path(td.path()); + + // The XDG config directory is set: the config file must be in a + // subdirectory named after the system. + { + beast::temp_dir tc; + + // Set the HOME and XDG_CONFIG_HOME environment variables. The + // HOME variable is not used when XDG_CONFIG_HOME is set, but + // must be set. + char const* h = getenv("HOME"); + setenv("HOME", tc.path().c_str(), 1); + char const* x = getenv("XDG_CONFIG_HOME"); + setenv("XDG_CONFIG_HOME", tc.path().c_str(), 1); + + // Create the config file in '${XDG_CONFIG_HOME}/[systemName]'. + path p = tc.file(systemName()); + create_directory(p); + p = tc.file(systemName() + "/" + configFile); + std::ofstream o(p.string()); + o << detail::configContents("", ""); + o.close(); + + // Load the config file from the config directory and verify it. + Config c; + c.setup("", true, false, true); + BEAST_EXPECT( + c.section(SECTION_DEBUG_LOGFILE).values().size() == 1); + BEAST_EXPECT( + c.section(SECTION_DEBUG_LOGFILE).values()[0] == + "/Users/dummy/xrpld/config/log/debug.log"); + + // Restore the environment variables. + h ? setenv("HOME", h, 1) : unsetenv("HOME"); + x ? setenv("XDG_CONFIG_HOME", x, 1) + : unsetenv("XDG_CONFIG_HOME"); + } + + // The XDG config directory is not set: the config file must be in a + // subdirectory named .config followed by the system name. + { + beast::temp_dir tc; + + // Set only the HOME environment variable. + char const* h = getenv("HOME"); + setenv("HOME", tc.path().c_str(), 1); + char const* x = getenv("XDG_CONFIG_HOME"); + unsetenv("XDG_CONFIG_HOME"); + + // Create the config file in '${HOME}/.config/[systemName]'. + std::string s = ".config"; + path p = tc.file(s); + create_directory(p); + s += "/" + systemName(); + p = tc.file(s); + create_directory(p); + p = tc.file(s + "/" + configFile); + std::ofstream o(p.string()); + o << detail::configContents("", ""); + o.close(); + + // Load the config file from the config directory and verify it. + Config c; + c.setup("", true, false, true); + BEAST_EXPECT( + c.section(SECTION_DEBUG_LOGFILE).values().size() == 1); + BEAST_EXPECT( + c.section(SECTION_DEBUG_LOGFILE).values()[0] == + "/Users/dummy/xrpld/config/log/debug.log"); + + // Restore the environment variables. + h ? setenv("HOME", h, 1) : unsetenv("HOME"); + if (x) + setenv("XDG_CONFIG_HOME", x, 1); + } + } +#endif + + // Restore the current working directory. + current_path(cwd); + } + void testDbPath() { testcase("database_path"); @@ -326,11 +448,16 @@ port_wss_admin { // read from file absolute path auto const cwd = current_path(); - xrpl::detail::DirGuard const g0(*this, "test_db"); + detail::DirGuard const g0(*this, "test_db"); path const dataDirRel("test_data_dir"); path const dataDirAbs(cwd / g0.subdir() / dataDirRel); - detail::RippledCfgGuard const g( - *this, g0.subdir(), dataDirAbs, "", false); + detail::FileCfgGuard const g( + *this, + g0.subdir(), + dataDirAbs, + Config::configFileName, + "", + false); auto const& c(g.config()); BEAST_EXPECT(g.dataDirExists()); BEAST_EXPECT(g.configFileExists()); @@ -339,7 +466,8 @@ port_wss_admin { // read from file relative path std::string const dbPath("my_db"); - detail::RippledCfgGuard const g(*this, "test_db", dbPath, ""); + detail::FileCfgGuard const g( + *this, "test_db", dbPath, Config::configFileName, ""); auto const& c(g.config()); std::string const nativeDbPath = absolute(path(dbPath)).string(); BEAST_EXPECT(g.dataDirExists()); @@ -348,7 +476,8 @@ port_wss_admin } { // read from file no path - detail::RippledCfgGuard const g(*this, "test_db", "", ""); + detail::FileCfgGuard const g( + *this, "test_db", "", Config::configFileName, ""); auto const& c(g.config()); std::string const nativeDbPath = absolute(g.subdir() / path(Config::databaseDirName)).string(); @@ -378,13 +507,13 @@ port_wss_admin { Config c; - static boost::format configTemplate(R"rippleConfig( + static boost::format configTemplate(R"xrpldConfig( [validation_seed] %1% [validator_token] %2% -)rippleConfig"); +)xrpldConfig"); std::string error; auto const expectedError = "Cannot have both [validation_seed] " @@ -410,10 +539,10 @@ port_wss_admin Config c; try { - c.loadFromString(R"rippleConfig( + c.loadFromString(R"xrpldConfig( [network_id] main -)rippleConfig"); +)xrpldConfig"); } catch (std::runtime_error& e) { @@ -425,8 +554,8 @@ main try { - c.loadFromString(R"rippleConfig( -)rippleConfig"); + c.loadFromString(R"xrpldConfig( +)xrpldConfig"); } catch (std::runtime_error& e) { @@ -438,10 +567,10 @@ main try { - c.loadFromString(R"rippleConfig( + c.loadFromString(R"xrpldConfig( [network_id] 255 -)rippleConfig"); +)xrpldConfig"); } catch (std::runtime_error& e) { @@ -453,10 +582,10 @@ main try { - c.loadFromString(R"rippleConfig( + c.loadFromString(R"xrpldConfig( [network_id] 10000 -)rippleConfig"); +)xrpldConfig"); } catch (std::runtime_error& e) { @@ -516,7 +645,7 @@ main { // load validators from config into single section Config c; - std::string toLoad(R"rippleConfig( + std::string toLoad(R"xrpldConfig( [validators] n949f75evCHwgyP4fPVgaHqNHxUVN15PsJEZ3B3HnXPcPjcZAoy7 n9MD5h24qrQqiyBC8aeqqCWvpiBiYQ3jxSr91uiDvmrkyHRdYLUj @@ -525,7 +654,7 @@ n9L81uNCaPgtUJfaHh89gmdvXKAmSt5Gdsw2g1iPWaPkAHW5Nm4C [validator_keys] nHUhG1PgAG8H8myUENypM35JgfqXAKNQvRVVAFDRzJrny5eZN8d5 nHBu9PTL9dn2GuZtdW4U2WzBwffyX9qsQCd9CNU4Z5YG3PQfViM8 -)rippleConfig"); +)xrpldConfig"); c.loadFromString(toLoad); BEAST_EXPECT(c.legacy("validators_file").empty()); BEAST_EXPECT(c.section(SECTION_VALIDATORS).values().size() == 5); @@ -534,9 +663,9 @@ nHBu9PTL9dn2GuZtdW4U2WzBwffyX9qsQCd9CNU4Z5YG3PQfViM8 { // load validator list sites and keys from config Config c; - std::string toLoad(R"rippleConfig( + std::string toLoad(R"xrpldConfig( [validator_list_sites] -ripplevalidators.com +xrplvalidators.com trustthesevalidators.gov [validator_list_keys] @@ -544,13 +673,13 @@ trustthesevalidators.gov [validator_list_threshold] 1 -)rippleConfig"); +)xrpldConfig"); c.loadFromString(toLoad); BEAST_EXPECT( c.section(SECTION_VALIDATOR_LIST_SITES).values().size() == 2); BEAST_EXPECT( c.section(SECTION_VALIDATOR_LIST_SITES).values()[0] == - "ripplevalidators.com"); + "xrplvalidators.com"); BEAST_EXPECT( c.section(SECTION_VALIDATOR_LIST_SITES).values()[1] == "trustthesevalidators.gov"); @@ -570,9 +699,9 @@ trustthesevalidators.gov { // load validator list sites and keys from config Config c; - std::string toLoad(R"rippleConfig( + std::string toLoad(R"xrpldConfig( [validator_list_sites] -ripplevalidators.com +xrplvalidators.com trustthesevalidators.gov [validator_list_keys] @@ -580,13 +709,13 @@ trustthesevalidators.gov [validator_list_threshold] 0 -)rippleConfig"); +)xrpldConfig"); c.loadFromString(toLoad); BEAST_EXPECT( c.section(SECTION_VALIDATOR_LIST_SITES).values().size() == 2); BEAST_EXPECT( c.section(SECTION_VALIDATOR_LIST_SITES).values()[0] == - "ripplevalidators.com"); + "xrplvalidators.com"); BEAST_EXPECT( c.section(SECTION_VALIDATOR_LIST_SITES).values()[1] == "trustthesevalidators.gov"); @@ -607,9 +736,9 @@ trustthesevalidators.gov // load should throw if [validator_list_threshold] is greater than // the number of [validator_list_keys] Config c; - std::string toLoad(R"rippleConfig( + std::string toLoad(R"xrpldConfig( [validator_list_sites] -ripplevalidators.com +xrplvalidators.com trustthesevalidators.gov [validator_list_keys] @@ -617,7 +746,7 @@ trustthesevalidators.gov [validator_list_threshold] 2 -)rippleConfig"); +)xrpldConfig"); std::string error; auto const expectedError = "Value in config section [validator_list_threshold] exceeds " @@ -636,9 +765,9 @@ trustthesevalidators.gov { // load should throw if [validator_list_threshold] is malformed Config c; - std::string toLoad(R"rippleConfig( + std::string toLoad(R"xrpldConfig( [validator_list_sites] -ripplevalidators.com +xrplvalidators.com trustthesevalidators.gov [validator_list_keys] @@ -646,7 +775,7 @@ trustthesevalidators.gov [validator_list_threshold] value = 2 -)rippleConfig"); +)xrpldConfig"); std::string error; auto const expectedError = "Config section [validator_list_threshold] should contain " @@ -665,9 +794,9 @@ value = 2 { // load should throw if [validator_list_threshold] is negative Config c; - std::string toLoad(R"rippleConfig( + std::string toLoad(R"xrpldConfig( [validator_list_sites] -ripplevalidators.com +xrplvalidators.com trustthesevalidators.gov [validator_list_keys] @@ -675,7 +804,7 @@ trustthesevalidators.gov [validator_list_threshold] -1 -)rippleConfig"); +)xrpldConfig"); bool error = false; try { @@ -692,11 +821,11 @@ trustthesevalidators.gov // load should throw if [validator_list_sites] is configured but // [validator_list_keys] is not Config c; - std::string toLoad(R"rippleConfig( + std::string toLoad(R"xrpldConfig( [validator_list_sites] -ripplevalidators.com +xrplvalidators.com trustthesevalidators.gov -)rippleConfig"); +)xrpldConfig"); std::string error; auto const expectedError = "[validator_list_keys] config section is missing"; @@ -736,8 +865,13 @@ trustthesevalidators.gov std::string const valFileName = "validators.txt"; detail::ValidatorsTxtGuard const vtg( *this, "test_cfg", valFileName); - detail::RippledCfgGuard const rcg( - *this, vtg.subdir(), "", valFileName, false); + detail::FileCfgGuard const rcg( + *this, + vtg.subdir(), + "", + Config::configFileName, + valFileName, + false); BEAST_EXPECT(vtg.validatorsFileExists()); BEAST_EXPECT(rcg.configFileExists()); auto const& c(rcg.config()); @@ -758,8 +892,13 @@ trustthesevalidators.gov detail::ValidatorsTxtGuard const vtg( *this, "test_cfg", "validators.txt"); auto const valFilePath = ".." / vtg.subdir() / "validators.txt"; - detail::RippledCfgGuard const rcg( - *this, vtg.subdir(), "", valFilePath, false); + detail::FileCfgGuard const rcg( + *this, + vtg.subdir(), + "", + Config::configFileName, + valFilePath, + false); BEAST_EXPECT(vtg.validatorsFileExists()); BEAST_EXPECT(rcg.configFileExists()); auto const& c(rcg.config()); @@ -778,8 +917,8 @@ trustthesevalidators.gov // load from validators file in default location detail::ValidatorsTxtGuard const vtg( *this, "test_cfg", "validators.txt"); - detail::RippledCfgGuard const rcg( - *this, vtg.subdir(), "", "", false); + detail::FileCfgGuard const rcg( + *this, vtg.subdir(), "", Config::configFileName, "", false); BEAST_EXPECT(vtg.validatorsFileExists()); BEAST_EXPECT(rcg.configFileExists()); auto const& c(rcg.config()); @@ -803,8 +942,13 @@ trustthesevalidators.gov detail::ValidatorsTxtGuard const vtgDefault( *this, vtg.subdir(), "validators.txt", false); BEAST_EXPECT(vtgDefault.validatorsFileExists()); - detail::RippledCfgGuard const rcg( - *this, vtg.subdir(), "", vtg.validatorsFile(), false); + detail::FileCfgGuard const rcg( + *this, + vtg.subdir(), + "", + Config::configFileName, + vtg.validatorsFile(), + false); BEAST_EXPECT(rcg.configFileExists()); auto const& c(rcg.config()); BEAST_EXPECT(c.legacy("validators_file") == vtg.validatorsFile()); @@ -821,7 +965,7 @@ trustthesevalidators.gov { // load validators from both config and validators file - boost::format cc(R"rippleConfig( + boost::format cc(R"xrpldConfig( [validators_file] %1% @@ -837,12 +981,12 @@ nHB1X37qrniVugfQcuBTAjswphC1drx7QjFFojJPZwKHHnt8kU7v nHUkAWDR4cB8AgPg7VXMX6et8xRTQb2KJfgv1aBEXozwrawRKgMB [validator_list_sites] -ripplevalidators.com +xrplvalidators.com trustthesevalidators.gov [validator_list_keys] 021A99A537FDEBC34E4FCA03B39BEADD04299BB19E85097EC92B15A3518801E566 -)rippleConfig"); +)xrpldConfig"); detail::ValidatorsTxtGuard const vtg( *this, "test_cfg", "validators.cfg"); BEAST_EXPECT(vtg.validatorsFileExists()); @@ -861,14 +1005,14 @@ trustthesevalidators.gov } { // load should throw if [validator_list_threshold] is present both - // in rippled cfg and validators file - boost::format cc(R"rippleConfig( + // in xrpld.cfg and validators file + boost::format cc(R"xrpldConfig( [validators_file] %1% [validator_list_threshold] 1 -)rippleConfig"); +)xrpldConfig"); std::string error; detail::ValidatorsTxtGuard const vtg( *this, "test_cfg", "validators.cfg"); @@ -890,7 +1034,7 @@ trustthesevalidators.gov } { // load should throw if [validators], [validator_keys] and - // [validator_list_keys] are missing from rippled cfg and + // [validator_list_keys] are missing from xrpld.cfg and // validators file Config c; boost::format cc("[validators_file]\n%1%\n"); @@ -920,9 +1064,13 @@ trustthesevalidators.gov void testSetup(bool explicitPath) { - detail::RippledCfgGuard const cfg( - *this, "testSetup", explicitPath ? "test_db" : "", ""); - /* RippledCfgGuard has a Config object that gets loaded on + detail::FileCfgGuard const cfg( + *this, + "testSetup", + explicitPath ? "test_db" : "", + Config::configFileName, + ""); + /* FileCfgGuard has a Config object that gets loaded on construction, but Config::setup is not reentrant, so we need a fresh config for every test case, so ignore it. */ @@ -1039,7 +1187,8 @@ trustthesevalidators.gov void testPort() { - detail::RippledCfgGuard const cfg(*this, "testPort", "", ""); + detail::FileCfgGuard const cfg( + *this, "testPort", "", Config::configFileName, ""); auto const& conf = cfg.config(); if (!BEAST_EXPECT(conf.exists("port_rpc"))) return; @@ -1065,8 +1214,14 @@ trustthesevalidators.gov try { - detail::RippledCfgGuard const cfg( - *this, "testPort", "", "", true, contents); + detail::FileCfgGuard const cfg( + *this, + "testPort", + "", + Config::configFileName, + "", + true, + contents); BEAST_EXPECT(false); } catch (std::exception const& ex) @@ -1377,9 +1532,9 @@ r.ripple.com:51235 for (auto& [unit, sec, val, shouldPass] : units) { Config c; - std::string toLoad(R"rippleConfig( + std::string toLoad(R"xrpldConfig( [amendment_majority_time] -)rippleConfig"); +)xrpldConfig"); toLoad += std::to_string(val) + space + unit; space = space == "" ? " " : ""; @@ -1480,6 +1635,7 @@ r.ripple.com:51235 run() override { testLegacy(); + testConfigFile(); testDbPath(); testValidatorKeys(); testValidatorsFile(); diff --git a/src/xrpld/app/misc/SHAMapStoreImp.h b/src/xrpld/app/misc/SHAMapStoreImp.h index e5a7435b0a..aed2343a49 100644 --- a/src/xrpld/app/misc/SHAMapStoreImp.h +++ b/src/xrpld/app/misc/SHAMapStoreImp.h @@ -87,7 +87,7 @@ private: /// If the node is out of sync during an online_delete healthWait() /// call, sleep the thread for this time, and continue checking until /// recovery. - /// See also: "recovery_wait_seconds" in rippled-example.cfg + /// See also: "recovery_wait_seconds" in xrpld-example.cfg std::chrono::seconds recoveryWaitTime_{5}; // these do not exist upon SHAMapStore creation, but do exist diff --git a/src/xrpld/core/Config.h b/src/xrpld/core/Config.h index cfd260787c..e25148aaa9 100644 --- a/src/xrpld/core/Config.h +++ b/src/xrpld/core/Config.h @@ -68,6 +68,7 @@ class Config : public BasicConfig public: // Settings related to the configuration file location and directories static char const* const configFileName; + static char const* const configLegacyName; static char const* const databaseDirName; static char const* const validatorsFileName; diff --git a/src/xrpld/core/detail/Config.cpp b/src/xrpld/core/detail/Config.cpp index e6c61e1471..1eb8d5984e 100644 --- a/src/xrpld/core/detail/Config.cpp +++ b/src/xrpld/core/detail/Config.cpp @@ -221,11 +221,12 @@ getSingleSection( //------------------------------------------------------------------------------ // -// Config (DEPRECATED) +// Config // //------------------------------------------------------------------------------ -char const* const Config::configFileName = "rippled.cfg"; +char const* const Config::configFileName = "xrpld.cfg"; +char const* const Config::configLegacyName = "rippled.cfg"; char const* const Config::databaseDirName = "db"; char const* const Config::validatorsFileName = "validators.txt"; @@ -295,76 +296,78 @@ Config::setup( bool bSilent, bool bStandalone) { - boost::filesystem::path dataDir; - std::string strDbPath, strConfFile; + setupControl(bQuiet, bSilent, bStandalone); // Determine the config and data directories. // If the config file is found in the current working // directory, use the current working directory as the // config directory and that with "db" as the data // directory. - - setupControl(bQuiet, bSilent, bStandalone); - - strDbPath = databaseDirName; - - if (!strConf.empty()) - strConfFile = strConf; - else - strConfFile = configFileName; + boost::filesystem::path dataDir; if (!strConf.empty()) { // --conf= : everything is relative that file. - CONFIG_FILE = strConfFile; + CONFIG_FILE = strConf; CONFIG_DIR = boost::filesystem::absolute(CONFIG_FILE); CONFIG_DIR.remove_filename(); - dataDir = CONFIG_DIR / strDbPath; + dataDir = CONFIG_DIR / databaseDirName; } else { - CONFIG_DIR = boost::filesystem::current_path(); - CONFIG_FILE = CONFIG_DIR / strConfFile; - dataDir = CONFIG_DIR / strDbPath; - - // Construct XDG config and data home. - // http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html - auto const strHome = getEnvVar("HOME"); - auto strXdgConfigHome = getEnvVar("XDG_CONFIG_HOME"); - auto strXdgDataHome = getEnvVar("XDG_DATA_HOME"); - - if (boost::filesystem::exists(CONFIG_FILE) - // Can we figure out XDG dirs? - || (strHome.empty() && - (strXdgConfigHome.empty() || strXdgDataHome.empty()))) + do { - // Current working directory is fine, put dbs in a subdir. - } - else - { - if (strXdgConfigHome.empty()) + // Check if either of the config files exist in the current working + // directory, in which case the databases will be stored in a + // subdirectory. + CONFIG_DIR = boost::filesystem::current_path(); + dataDir = CONFIG_DIR / databaseDirName; + CONFIG_FILE = CONFIG_DIR / configFileName; + if (boost::filesystem::exists(CONFIG_FILE)) + break; + CONFIG_FILE = CONFIG_DIR / configLegacyName; + if (boost::filesystem::exists(CONFIG_FILE)) + break; + + // Check if the home directory is set, and optionally the XDG config + // and/or data directories, as the config may be there. See + // http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html. + auto const strHome = getEnvVar("HOME"); + if (!strHome.empty()) { - // $XDG_CONFIG_HOME was not set, use default based on $HOME. - strXdgConfigHome = strHome + "/.config"; + auto strXdgConfigHome = getEnvVar("XDG_CONFIG_HOME"); + auto strXdgDataHome = getEnvVar("XDG_DATA_HOME"); + if (strXdgConfigHome.empty()) + { + // $XDG_CONFIG_HOME was not set, use default based on $HOME. + strXdgConfigHome = strHome + "/.config"; + } + if (strXdgDataHome.empty()) + { + // $XDG_DATA_HOME was not set, use default based on $HOME. + strXdgDataHome = strHome + "/.local/share"; + } + + // Check if either of the config files exist in the XDG config + // dir. + dataDir = strXdgDataHome + "/" + systemName(); + CONFIG_DIR = strXdgConfigHome + "/" + systemName(); + CONFIG_FILE = CONFIG_DIR / configFileName; + if (boost::filesystem::exists(CONFIG_FILE)) + break; + CONFIG_FILE = CONFIG_DIR / configLegacyName; + if (boost::filesystem::exists(CONFIG_FILE)) + break; } - if (strXdgDataHome.empty()) - { - // $XDG_DATA_HOME was not set, use default based on $HOME. - strXdgDataHome = strHome + "/.local/share"; - } - - CONFIG_DIR = strXdgConfigHome + "/" + systemName(); - CONFIG_FILE = CONFIG_DIR / strConfFile; - dataDir = strXdgDataHome + "/" + systemName(); - - if (!boost::filesystem::exists(CONFIG_FILE)) - { - CONFIG_DIR = "/etc/opt/" + systemName(); - CONFIG_FILE = CONFIG_DIR / strConfFile; - dataDir = "/var/opt/" + systemName(); - } - } + // As a last resort, check the system config directory. + dataDir = "/var/opt/" + systemName(); + CONFIG_DIR = "/etc/opt/" + systemName(); + CONFIG_FILE = CONFIG_DIR / configFileName; + if (boost::filesystem::exists(CONFIG_FILE)) + break; + CONFIG_FILE = CONFIG_DIR / configLegacyName; + } while (false); } // Update default values @@ -374,11 +377,9 @@ Config::setup( std::string const dbPath(legacy("database_path")); if (!dbPath.empty()) dataDir = boost::filesystem::path(dbPath); - else if (RUN_STANDALONE) - dataDir.clear(); } - if (!dataDir.empty()) + if (!RUN_STANDALONE) { boost::system::error_code ec; boost::filesystem::create_directories(dataDir, ec); diff --git a/src/xrpld/overlay/README.md b/src/xrpld/overlay/README.md index cd00488915..51eb96a001 100644 --- a/src/xrpld/overlay/README.md +++ b/src/xrpld/overlay/README.md @@ -373,7 +373,7 @@ command. The key is in the `pubkey_node` value, and is a text string beginning with the letter `n`. The key is maintained across runs in a database. -Cluster members are configured in the `rippled.cfg` file under +Cluster members are configured in the `xrpld.cfg` file under `[cluster_nodes]`. Each member should be configured on a line beginning with the node public key, followed optionally by a space and a friendly name. diff --git a/src/xrpld/overlay/detail/OverlayImpl.cpp b/src/xrpld/overlay/detail/OverlayImpl.cpp index e5f77021d7..7cd02c72e0 100644 --- a/src/xrpld/overlay/detail/OverlayImpl.cpp +++ b/src/xrpld/overlay/detail/OverlayImpl.cpp @@ -514,7 +514,7 @@ OverlayImpl::start() m_peerFinder->addFallbackStrings(base + name, ips); }); - // Add the ips_fixed from the rippled.cfg file + // Add the ips_fixed from the xrpld.cfg file if (!app_.config().standalone() && !app_.config().IPS_FIXED.empty()) { m_resolver.resolve( From 44d21b8f6daf87b286473686c5e75b7b513c7db2 Mon Sep 17 00:00:00 2001 From: Mayukha Vadari Date: Mon, 5 Jan 2026 10:54:24 -0500 Subject: [PATCH 03/30] test: add more tests for `ledger_entry` RPC (#5858) This change adds some basic tests for all the `ledger_entry` helper functions, so each ledger entry type is covered. There are further some minor refactors in `parseAMM` to provide better error messages. Finally, to improve readability, alphabetization was applied in the helper functions. --- src/test/rpc/LedgerEntry_test.cpp | 482 +++++++++++++++++--- src/xrpld/rpc/handlers/LedgerEntry.cpp | 23 +- src/xrpld/rpc/handlers/LedgerEntryHelpers.h | 23 + 3 files changed, 447 insertions(+), 81 deletions(-) diff --git a/src/test/rpc/LedgerEntry_test.cpp b/src/test/rpc/LedgerEntry_test.cpp index ef5abdd800..551e67dc5e 100644 --- a/src/test/rpc/LedgerEntry_test.cpp +++ b/src/test/rpc/LedgerEntry_test.cpp @@ -30,6 +30,7 @@ enum class FieldType { CurrencyField, HashField, HashOrObjectField, + IssueField, ObjectField, StringField, TwoAccountArrayField, @@ -40,6 +41,8 @@ enum class FieldType { std::vector> mappings{ {jss::account, FieldType::AccountField}, {jss::accounts, FieldType::TwoAccountArrayField}, + {jss::asset, FieldType::IssueField}, + {jss::asset2, FieldType::IssueField}, {jss::authorize, FieldType::AccountField}, {jss::authorized, FieldType::AccountField}, {jss::credential_type, FieldType::BlobField}, @@ -74,24 +77,26 @@ getTypeName(FieldType typeID) { switch (typeID) { - case FieldType::UInt32Field: - return "number"; - case FieldType::UInt64Field: - return "number"; - case FieldType::HashField: - return "hex string"; case FieldType::AccountField: return "AccountID"; + case FieldType::ArrayField: + return "array"; case FieldType::BlobField: return "hex string"; case FieldType::CurrencyField: return "Currency"; - case FieldType::ArrayField: - return "array"; + case FieldType::HashField: + return "hex string"; case FieldType::HashOrObjectField: return "hex string or object"; + case FieldType::IssueField: + return "Issue"; case FieldType::TwoAccountArrayField: return "length-2 array of Accounts"; + case FieldType::UInt32Field: + return "number"; + case FieldType::UInt64Field: + return "number"; default: Throw( "unknown type " + std::to_string(static_cast(typeID))); @@ -192,34 +197,37 @@ class LedgerEntry_test : public beast::unit_test::suite return values; }; - static auto const& badUInt32Values = remove({2, 3}); - static auto const& badUInt64Values = remove({2, 3}); - static auto const& badHashValues = remove({2, 3, 7, 8, 16}); static auto const& badAccountValues = remove({12}); + static auto const& badArrayValues = remove({17, 20}); static auto const& badBlobValues = remove({3, 7, 8, 16}); static auto const& badCurrencyValues = remove({14}); - static auto const& badArrayValues = remove({17, 20}); + static auto const& badHashValues = remove({2, 3, 7, 8, 16}); 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({}); switch (fieldType) { - case FieldType::UInt32Field: - return badUInt32Values; - case FieldType::UInt64Field: - return badUInt64Values; - case FieldType::HashField: - return badHashValues; case FieldType::AccountField: return badAccountValues; + case FieldType::ArrayField: + case FieldType::TwoAccountArrayField: + return badArrayValues; case FieldType::BlobField: return badBlobValues; case FieldType::CurrencyField: return badCurrencyValues; - case FieldType::ArrayField: - case FieldType::TwoAccountArrayField: - return badArrayValues; + case FieldType::HashField: + return badHashValues; case FieldType::HashOrObjectField: return badIndexValues; + case FieldType::IssueField: + return badIssueValues; + case FieldType::UInt32Field: + return badUInt32Values; + case FieldType::UInt64Field: + return badUInt64Values; default: Throw( "unknown type " + @@ -236,30 +244,37 @@ class LedgerEntry_test : public beast::unit_test::suite arr[1u] = "r4MrUGTdB57duTnRs6KbsRGQXgkseGb1b5"; return arr; }(); + static Json::Value const issueObject = []() { + Json::Value arr(Json::objectValue); + arr[jss::currency] = "XRP"; + return arr; + }(); auto const typeID = getFieldType(fieldName); switch (typeID) { - case FieldType::UInt32Field: - return 1; - case FieldType::UInt64Field: - return 1; - case FieldType::HashField: - return "5233D68B4D44388F98559DE42903767803EFA7C1F8D01413FC16EE6" - "B01403D6D"; case FieldType::AccountField: return "r4MrUGTdB57duTnRs6KbsRGQXgkseGb1b5"; + case FieldType::ArrayField: + return Json::arrayValue; case FieldType::BlobField: return "ABCDEF"; case FieldType::CurrencyField: return "USD"; - case FieldType::ArrayField: - return Json::arrayValue; + case FieldType::HashField: + return "5233D68B4D44388F98559DE42903767803EFA7C1F8D01413FC16EE6" + "B01403D6D"; + case FieldType::IssueField: + return issueObject; case FieldType::HashOrObjectField: return "5233D68B4D44388F98559DE42903767803EFA7C1F8D01413FC16EE6" "B01403D6D"; case FieldType::TwoAccountArrayField: return twoAccountArray; + case FieldType::UInt32Field: + return 1; + case FieldType::UInt64Field: + return 1; default: Throw( "unknown type " + @@ -444,7 +459,7 @@ class LedgerEntry_test : public beast::unit_test::suite } void - testLedgerEntryInvalid() + testInvalid() { testcase("Invalid requests"); using namespace test::jtx; @@ -526,7 +541,7 @@ class LedgerEntry_test : public beast::unit_test::suite } void - testLedgerEntryAccountRoot() + testAccountRoot() { testcase("AccountRoot"); using namespace test::jtx; @@ -632,7 +647,147 @@ class LedgerEntry_test : public beast::unit_test::suite } void - testLedgerEntryCheck() + testAmendments() + { + testcase("Amendments"); + using namespace test::jtx; + Env env{*this}; + + // positive test + { + Keylet const keylet = keylet::amendments(); + + // easier to hack an object into the ledger than generate it + // legitimately + { + auto const amendments = [&](OpenView& view, + beast::Journal) -> bool { + auto const sle = std::make_shared(keylet); + + // Create Amendments vector (enabled amendments) + std::vector enabledAmendments; + enabledAmendments.push_back( + uint256::fromVoid("42426C4D4F1009EE67080A9B7965B44656D7" + "714D104A72F9B4369F97ABF044EE")); + enabledAmendments.push_back( + uint256::fromVoid("4C97EBA926031A7CF7D7B36FDE3ED66DDA54" + "21192D63DE53FFB46E43B9DC8373")); + enabledAmendments.push_back( + uint256::fromVoid("03BDC0099C4E14163ADA272C1B6F6FABB448" + "CC3E51F522F978041E4B57D9158C")); + enabledAmendments.push_back( + uint256::fromVoid("35291ADD2D79EB6991343BDA0912269C817D" + "0F094B02226C1C14AD2858962ED4")); + sle->setFieldV256( + sfAmendments, STVector256(enabledAmendments)); + + // Create Majorities array + STArray majorities; + + auto majority1 = STObject::makeInnerObject(sfMajority); + majority1.setFieldH256( + sfAmendment, + uint256::fromVoid("7BB62DC13EC72B775091E9C71BF8CF97E122" + "647693B50C5E87A80DFD6FCFAC50")); + majority1.setFieldU32(sfCloseTime, 779561310); + majorities.push_back(std::move(majority1)); + + auto majority2 = STObject::makeInnerObject(sfMajority); + majority2.setFieldH256( + sfAmendment, + uint256::fromVoid("755C971C29971C9F20C6F080F2ED96F87884" + "E40AD19554A5EBECDCEC8A1F77FE")); + majority2.setFieldU32(sfCloseTime, 779561310); + majorities.push_back(std::move(majority2)); + + sle->setFieldArray(sfMajorities, majorities); + + view.rawInsert(sle); + return true; + }; + env.app().openLedger().modify(amendments); + } + + Json::Value jvParams; + jvParams[jss::amendments] = to_string(keylet.key); + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + BEAST_EXPECT( + jrr[jss::node][sfLedgerEntryType.jsonName] == jss::Amendments); + } + + // negative tests + runLedgerEntryTest(env, jss::amendments); + } + + void + testAMM() + { + testcase("AMM"); + using namespace test::jtx; + Env env{*this}; + + // positive test + Account const alice{"alice"}; + env.fund(XRP(10000), alice); + env.close(); + AMM 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); + } + + { + Json::Value jvParams; + Json::Value ammParams(Json::objectValue); + { + Json::Value obj(Json::objectValue); + obj[jss::currency] = "XRP"; + ammParams[jss::asset] = obj; + } + { + 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"}, + }); + } + + void + testCheck() { testcase("Check"); using namespace test::jtx; @@ -684,7 +839,7 @@ class LedgerEntry_test : public beast::unit_test::suite } void - testLedgerEntryCredentials() + testCredentials() { testcase("Credentials"); @@ -752,7 +907,7 @@ class LedgerEntry_test : public beast::unit_test::suite } void - testLedgerEntryDelegate() + testDelegate() { testcase("Delegate"); @@ -807,7 +962,7 @@ class LedgerEntry_test : public beast::unit_test::suite } void - testLedgerEntryDepositPreauth() + testDepositPreauth() { testcase("Deposit Preauth"); @@ -868,7 +1023,7 @@ class LedgerEntry_test : public beast::unit_test::suite } void - testLedgerEntryDepositPreauthCred() + testDepositPreauthCred() { testcase("Deposit Preauth with credentials"); @@ -1149,7 +1304,7 @@ class LedgerEntry_test : public beast::unit_test::suite } void - testLedgerEntryDirectory() + testDirectory() { testcase("Directory"); using namespace test::jtx; @@ -1303,7 +1458,7 @@ class LedgerEntry_test : public beast::unit_test::suite } void - testLedgerEntryEscrow() + testEscrow() { testcase("Escrow"); using namespace test::jtx; @@ -1365,7 +1520,177 @@ class LedgerEntry_test : public beast::unit_test::suite } void - testLedgerEntryOffer() + testFeeSettings() + { + testcase("Fee Settings"); + using namespace test::jtx; + Env env{*this}; + + // positive test + { + Keylet const keylet = keylet::fees(); + Json::Value jvParams; + jvParams[jss::fee] = to_string(keylet.key); + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + BEAST_EXPECT( + jrr[jss::node][sfLedgerEntryType.jsonName] == jss::FeeSettings); + } + + // negative tests + runLedgerEntryTest(env, jss::fee); + } + + void + testLedgerHashes() + { + testcase("Ledger Hashes"); + using namespace test::jtx; + Env env{*this}; + + // positive test + { + Keylet const keylet = keylet::skip(); + Json::Value jvParams; + jvParams[jss::hashes] = to_string(keylet.key); + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + BEAST_EXPECT( + jrr[jss::node][sfLedgerEntryType.jsonName] == + jss::LedgerHashes); + } + + // negative tests + runLedgerEntryTest(env, jss::hashes); + } + + void + testNFTokenOffer() + { + testcase("NFT Offer"); + using namespace test::jtx; + Env env{*this}; + + // positive test + Account const issuer{"issuer"}; + Account const buyer{"buyer"}; + env.fund(XRP(1000), issuer, buyer); + + uint256 const nftokenID0 = + token::getNextID(env, issuer, 0, tfTransferable); + env(token::mint(issuer, 0), txflags(tfTransferable)); + env.close(); + uint256 const offerID = keylet::nftoffer(issuer, env.seq(issuer)).key; + env(token::createOffer(issuer, nftokenID0, drops(1)), + token::destination(buyer), + txflags(tfSellNFToken)); + + { + Json::Value jvParams; + jvParams[jss::nft_offer] = to_string(offerID); + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + BEAST_EXPECT( + jrr[jss::node][sfLedgerEntryType.jsonName] == + jss::NFTokenOffer); + BEAST_EXPECT(jrr[jss::node][sfOwner.jsonName] == issuer.human()); + BEAST_EXPECT( + jrr[jss::node][sfNFTokenID.jsonName] == to_string(nftokenID0)); + BEAST_EXPECT(jrr[jss::node][sfAmount.jsonName] == "1"); + } + + // negative tests + runLedgerEntryTest(env, jss::nft_offer); + } + + void + testNFTokenPage() + { + testcase("NFT Page"); + using namespace test::jtx; + Env env{*this}; + + // positive test + Account const issuer{"issuer"}; + env.fund(XRP(1000), issuer); + + env(token::mint(issuer, 0), txflags(tfTransferable)); + env.close(); + + auto const nftpage = keylet::nftpage_max(issuer); + BEAST_EXPECT(env.le(nftpage) != nullptr); + + { + Json::Value jvParams; + jvParams[jss::nft_page] = to_string(nftpage.key); + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + BEAST_EXPECT( + jrr[jss::node][sfLedgerEntryType.jsonName] == jss::NFTokenPage); + } + + // negative tests + runLedgerEntryTest(env, jss::nft_page); + } + + void + testNegativeUNL() + { + testcase("Negative UNL"); + using namespace test::jtx; + Env env{*this}; + + // positive test + { + Keylet const keylet = keylet::negativeUNL(); + + // easier to hack an object into the ledger than generate it + // legitimately + { + auto const nUNL = [&](OpenView& view, beast::Journal) -> bool { + auto const sle = std::make_shared(keylet); + + // Create DisabledValidators array + STArray disabledValidators; + auto disabledValidator = + STObject::makeInnerObject(sfDisabledValidator); + auto pubKeyBlob = strUnHex( + "ED58F6770DB5DD77E59D28CB650EC3816E2FC95021BB56E720C9A1" + "2DA79C58A3AB"); + disabledValidator.setFieldVL(sfPublicKey, *pubKeyBlob); + disabledValidator.setFieldU32( + sfFirstLedgerSequence, 91371264); + disabledValidators.push_back(std::move(disabledValidator)); + + sle->setFieldArray( + sfDisabledValidators, disabledValidators); + sle->setFieldH256( + sfPreviousTxnID, + uint256::fromVoid("8D47FFE664BE6C335108DF689537625855A6" + "A95160CC6D351341B9" + "2624D9C5E3")); + sle->setFieldU32(sfPreviousTxnLgrSeq, 91442944); + + view.rawInsert(sle); + return true; + }; + env.app().openLedger().modify(nUNL); + } + + Json::Value jvParams; + jvParams[jss::nunl] = to_string(keylet.key); + Json::Value const jrr = env.rpc( + "json", "ledger_entry", to_string(jvParams))[jss::result]; + BEAST_EXPECT( + jrr[jss::node][sfLedgerEntryType.jsonName] == jss::NegativeUNL); + } + + // negative tests + runLedgerEntryTest(env, jss::nunl); + } + + void + testOffer() { testcase("Offer"); using namespace test::jtx; @@ -1413,7 +1738,7 @@ class LedgerEntry_test : public beast::unit_test::suite } void - testLedgerEntryPayChan() + testPayChan() { testcase("Pay Chan"); using namespace test::jtx; @@ -1475,7 +1800,7 @@ class LedgerEntry_test : public beast::unit_test::suite } void - testLedgerEntryRippleState() + testRippleState() { testcase("RippleState"); using namespace test::jtx; @@ -1626,7 +1951,16 @@ class LedgerEntry_test : public beast::unit_test::suite } void - testLedgerEntryTicket() + testSignerList() + { + testcase("Signer List"); + using namespace test::jtx; + Env env{*this}; + runLedgerEntryTest(env, jss::signer_list); + } + + void + testTicket() { testcase("Ticket"); using namespace test::jtx; @@ -1711,7 +2045,7 @@ class LedgerEntry_test : public beast::unit_test::suite } void - testLedgerEntryDID() + testDID() { testcase("DID"); using namespace test::jtx; @@ -1848,7 +2182,7 @@ class LedgerEntry_test : public beast::unit_test::suite } void - testLedgerEntryMPT() + testMPT() { testcase("MPT"); using namespace test::jtx; @@ -1931,7 +2265,7 @@ class LedgerEntry_test : public beast::unit_test::suite } void - testLedgerEntryPermissionedDomain() + testPermissionedDomain() { testcase("PermissionedDomain"); @@ -2010,7 +2344,7 @@ class LedgerEntry_test : public beast::unit_test::suite } void - testLedgerEntryCLI() + testCLI() { testcase("command-line"); using namespace test::jtx; @@ -2040,25 +2374,33 @@ public: void run() override { - testLedgerEntryInvalid(); - testLedgerEntryAccountRoot(); - testLedgerEntryCheck(); - testLedgerEntryCredentials(); - testLedgerEntryDelegate(); - testLedgerEntryDepositPreauth(); - testLedgerEntryDepositPreauthCred(); - testLedgerEntryDirectory(); - testLedgerEntryEscrow(); - testLedgerEntryOffer(); - testLedgerEntryPayChan(); - testLedgerEntryRippleState(); - testLedgerEntryTicket(); - testLedgerEntryDID(); + testInvalid(); + testAccountRoot(); + testAmendments(); + testAMM(); + testCheck(); + testCredentials(); + testDelegate(); + testDepositPreauth(); + testDepositPreauthCred(); + testDirectory(); + testEscrow(); + testFeeSettings(); + testLedgerHashes(); + testNFTokenOffer(); + testNFTokenPage(); + testNegativeUNL(); + testOffer(); + testPayChan(); + testRippleState(); + testSignerList(); + testTicket(); + testDID(); testInvalidOracleLedgerEntry(); testOracleLedgerEntry(); - testLedgerEntryMPT(); - testLedgerEntryPermissionedDomain(); - testLedgerEntryCLI(); + testMPT(); + testPermissionedDomain(); + testCLI(); } }; @@ -2086,7 +2428,7 @@ class LedgerEntry_XChain_test : public beast::unit_test::suite, } void - testLedgerEntryBridge() + testBridge() { testcase("ledger_entry: bridge"); using namespace test::jtx; @@ -2177,7 +2519,7 @@ class LedgerEntry_XChain_test : public beast::unit_test::suite, } void - testLedgerEntryClaimID() + testClaimID() { testcase("ledger_entry: xchain_claim_id"); using namespace test::jtx; @@ -2235,7 +2577,7 @@ class LedgerEntry_XChain_test : public beast::unit_test::suite, } void - testLedgerEntryCreateAccountClaimID() + testCreateAccountClaimID() { testcase("ledger_entry: xchain_create_account_claim_id"); using namespace test::jtx; @@ -2362,9 +2704,9 @@ public: void run() override { - testLedgerEntryBridge(); - testLedgerEntryClaimID(); - testLedgerEntryCreateAccountClaimID(); + testBridge(); + testClaimID(); + testCreateAccountClaimID(); } }; diff --git a/src/xrpld/rpc/handlers/LedgerEntry.cpp b/src/xrpld/rpc/handlers/LedgerEntry.cpp index 5b5db72c22..2e9d5b35bf 100644 --- a/src/xrpld/rpc/handlers/LedgerEntry.cpp +++ b/src/xrpld/rpc/handlers/LedgerEntry.cpp @@ -71,16 +71,17 @@ parseAMM(Json::Value const& params, Json::StaticString const fieldName) return Unexpected(value.error()); } - try - { - auto const issue = issueFromJson(params[jss::asset]); - auto const issue2 = issueFromJson(params[jss::asset2]); - return keylet::amm(issue, issue2).key; - } - catch (std::runtime_error const&) - { - return LedgerEntryHelpers::malformedError("malformedRequest", ""); - } + auto const asset = LedgerEntryHelpers::requiredIssue( + params, jss::asset, "malformedRequest"); + if (!asset) + return Unexpected(asset.error()); + + auto const asset2 = LedgerEntryHelpers::requiredIssue( + params, jss::asset2, "malformedRequest"); + if (!asset2) + return Unexpected(asset2.error()); + + return keylet::amm(*asset, *asset2).key; } static Expected @@ -424,7 +425,7 @@ parseLoan(Json::Value const& params, Json::StaticString const fieldName) } auto const id = LedgerEntryHelpers::requiredUInt256( - params, jss::loan_broker_id, "malformedOwner"); + params, jss::loan_broker_id, "malformedLoanBrokerID"); if (!id) return Unexpected(id.error()); auto const seq = LedgerEntryHelpers::requiredUInt32( diff --git a/src/xrpld/rpc/handlers/LedgerEntryHelpers.h b/src/xrpld/rpc/handlers/LedgerEntryHelpers.h index 0a4453c063..4b656e6b05 100644 --- a/src/xrpld/rpc/handlers/LedgerEntryHelpers.h +++ b/src/xrpld/rpc/handlers/LedgerEntryHelpers.h @@ -218,6 +218,29 @@ requiredUInt192( return required(params, fieldName, err, "Hash192"); } +template <> +std::optional +parse(Json::Value const& param) +{ + try + { + return issueFromJson(param); + } + catch (std::runtime_error const&) + { + return std::nullopt; + } +} + +Expected +requiredIssue( + Json::Value const& params, + Json::StaticString const fieldName, + std::string const& err) +{ + return required(params, fieldName, err, "Issue"); +} + Expected parseBridgeFields(Json::Value const& params) { From d734c8ddddb5ad0e4322f45d7dc47b2d609175b1 Mon Sep 17 00:00:00 2001 From: Bart Date: Tue, 6 Jan 2026 20:34:21 -0500 Subject: [PATCH 04/30] ci: Use ccache to cache build objects for speeding up building (#6104) Right now, each pipeline invocation builds the source code from scratch. Although compiled Conan dependencies are cached in a remote server, the source build objects are not. We are able to further speed up our builds by leveraging `ccache`. This change enables caching of build objects using `ccache` on Linux, macOS, and Windows. --- .github/actions/print-env/action.yml | 24 ++++---- .github/scripts/strategy-matrix/generate.py | 2 + .github/scripts/strategy-matrix/linux.json | 56 +++++++++---------- .github/workflows/on-pr.yml | 3 + .github/workflows/on-trigger.yml | 6 ++ .../workflows/reusable-build-test-config.yml | 52 ++++++++++++++--- .github/workflows/reusable-build-test.yml | 9 +++ .github/workflows/upload-conan-deps.yml | 2 +- CMakeLists.txt | 3 + cmake/Ccache.cmake | 51 +++++++++++++++++ 10 files changed, 159 insertions(+), 49 deletions(-) create mode 100644 cmake/Ccache.cmake diff --git a/.github/actions/print-env/action.yml b/.github/actions/print-env/action.yml index 6019a6de2f..3527ca6f02 100644 --- a/.github/actions/print-env/action.yml +++ b/.github/actions/print-env/action.yml @@ -11,12 +11,6 @@ runs: echo 'Checking environment variables.' set - echo 'Checking CMake version.' - cmake --version - - echo 'Checking Conan version.' - conan --version - - name: Check configuration (Linux and macOS) if: ${{ runner.os == 'Linux' || runner.os == 'macOS' }} shell: bash @@ -27,17 +21,23 @@ runs: echo 'Checking environment variables.' env | sort - echo 'Checking CMake version.' - cmake --version - echo 'Checking compiler version.' ${{ runner.os == 'Linux' && '${CC}' || 'clang' }} --version - echo 'Checking Conan version.' - conan --version - echo 'Checking Ninja version.' ninja --version echo 'Checking nproc version.' nproc --version + + - name: Check configuration (all) + shell: bash + run: | + echo 'Checking Ccache version.' + ccache --version + + echo 'Checking CMake version.' + cmake --version + + echo 'Checking Conan version.' + conan --version diff --git a/.github/scripts/strategy-matrix/generate.py b/.github/scripts/strategy-matrix/generate.py index 79530a1d75..c3d2da1f9f 100755 --- a/.github/scripts/strategy-matrix/generate.py +++ b/.github/scripts/strategy-matrix/generate.py @@ -232,6 +232,8 @@ def generate_strategy_matrix(all: bool, config: Config) -> list: f"-{architecture['platform'][architecture['platform'].find('/') + 1 :]}" ) config_name += f"-{build_type.lower()}" + if "-Dcoverage=ON" in cmake_args: + config_name += "-coverage" if "-Dunity=ON" in cmake_args: config_name += "-unity" diff --git a/.github/scripts/strategy-matrix/linux.json b/.github/scripts/strategy-matrix/linux.json index 748ee031c9..669754554c 100644 --- a/.github/scripts/strategy-matrix/linux.json +++ b/.github/scripts/strategy-matrix/linux.json @@ -15,196 +15,196 @@ "distro_version": "bookworm", "compiler_name": "gcc", "compiler_version": "12", - "image_sha": "0525eae" + "image_sha": "cc09fd3" }, { "distro_name": "debian", "distro_version": "bookworm", "compiler_name": "gcc", "compiler_version": "13", - "image_sha": "0525eae" + "image_sha": "cc09fd3" }, { "distro_name": "debian", "distro_version": "bookworm", "compiler_name": "gcc", "compiler_version": "14", - "image_sha": "0525eae" + "image_sha": "cc09fd3" }, { "distro_name": "debian", "distro_version": "bookworm", "compiler_name": "gcc", "compiler_version": "15", - "image_sha": "0525eae" + "image_sha": "cc09fd3" }, { "distro_name": "debian", "distro_version": "bookworm", "compiler_name": "clang", "compiler_version": "16", - "image_sha": "0525eae" + "image_sha": "cc09fd3" }, { "distro_name": "debian", "distro_version": "bookworm", "compiler_name": "clang", "compiler_version": "17", - "image_sha": "0525eae" + "image_sha": "cc09fd3" }, { "distro_name": "debian", "distro_version": "bookworm", "compiler_name": "clang", "compiler_version": "18", - "image_sha": "0525eae" + "image_sha": "cc09fd3" }, { "distro_name": "debian", "distro_version": "bookworm", "compiler_name": "clang", "compiler_version": "19", - "image_sha": "0525eae" + "image_sha": "cc09fd3" }, { "distro_name": "debian", "distro_version": "bookworm", "compiler_name": "clang", "compiler_version": "20", - "image_sha": "0525eae" + "image_sha": "cc09fd3" }, { "distro_name": "debian", "distro_version": "trixie", "compiler_name": "gcc", "compiler_version": "14", - "image_sha": "0525eae" + "image_sha": "cc09fd3" }, { "distro_name": "debian", "distro_version": "trixie", "compiler_name": "gcc", "compiler_version": "15", - "image_sha": "0525eae" + "image_sha": "cc09fd3" }, { "distro_name": "debian", "distro_version": "trixie", "compiler_name": "clang", "compiler_version": "20", - "image_sha": "0525eae" + "image_sha": "cc09fd3" }, { "distro_name": "debian", "distro_version": "trixie", "compiler_name": "clang", "compiler_version": "21", - "image_sha": "0525eae" + "image_sha": "cc09fd3" }, { "distro_name": "rhel", "distro_version": "8", "compiler_name": "gcc", "compiler_version": "14", - "image_sha": "e1782cd" + "image_sha": "cc09fd3" }, { "distro_name": "rhel", "distro_version": "8", "compiler_name": "clang", "compiler_version": "any", - "image_sha": "e1782cd" + "image_sha": "cc09fd3" }, { "distro_name": "rhel", "distro_version": "9", "compiler_name": "gcc", "compiler_version": "12", - "image_sha": "e1782cd" + "image_sha": "cc09fd3" }, { "distro_name": "rhel", "distro_version": "9", "compiler_name": "gcc", "compiler_version": "13", - "image_sha": "e1782cd" + "image_sha": "cc09fd3" }, { "distro_name": "rhel", "distro_version": "9", "compiler_name": "gcc", "compiler_version": "14", - "image_sha": "e1782cd" + "image_sha": "cc09fd3" }, { "distro_name": "rhel", "distro_version": "9", "compiler_name": "clang", "compiler_version": "any", - "image_sha": "e1782cd" + "image_sha": "cc09fd3" }, { "distro_name": "rhel", "distro_version": "10", "compiler_name": "gcc", "compiler_version": "14", - "image_sha": "e1782cd" + "image_sha": "cc09fd3" }, { "distro_name": "rhel", "distro_version": "10", "compiler_name": "clang", "compiler_version": "any", - "image_sha": "e1782cd" + "image_sha": "cc09fd3" }, { "distro_name": "ubuntu", "distro_version": "jammy", "compiler_name": "gcc", "compiler_version": "12", - "image_sha": "e1782cd" + "image_sha": "cc09fd3" }, { "distro_name": "ubuntu", "distro_version": "noble", "compiler_name": "gcc", "compiler_version": "13", - "image_sha": "e1782cd" + "image_sha": "cc09fd3" }, { "distro_name": "ubuntu", "distro_version": "noble", "compiler_name": "gcc", "compiler_version": "14", - "image_sha": "e1782cd" + "image_sha": "cc09fd3" }, { "distro_name": "ubuntu", "distro_version": "noble", "compiler_name": "clang", "compiler_version": "16", - "image_sha": "e1782cd" + "image_sha": "cc09fd3" }, { "distro_name": "ubuntu", "distro_version": "noble", "compiler_name": "clang", "compiler_version": "17", - "image_sha": "e1782cd" + "image_sha": "cc09fd3" }, { "distro_name": "ubuntu", "distro_version": "noble", "compiler_name": "clang", "compiler_version": "18", - "image_sha": "e1782cd" + "image_sha": "cc09fd3" }, { "distro_name": "ubuntu", "distro_version": "noble", "compiler_name": "clang", "compiler_version": "19", - "image_sha": "e1782cd" + "image_sha": "cc09fd3" } ], "build_type": ["Debug", "Release"], diff --git a/.github/workflows/on-pr.yml b/.github/workflows/on-pr.yml index ff3d25812a..3aa48ac070 100644 --- a/.github/workflows/on-pr.yml +++ b/.github/workflows/on-pr.yml @@ -114,6 +114,9 @@ jobs: matrix: os: [linux, macos, windows] with: + # Enable ccache only for events targeting the XRPLF repository, since + # other accounts will not have access to our remote cache storage. + ccache_enabled: ${{ github.repository_owner == 'XRPLF' }} os: ${{ matrix.os }} secrets: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/on-trigger.yml b/.github/workflows/on-trigger.yml index b5a56fb671..a95402ced7 100644 --- a/.github/workflows/on-trigger.yml +++ b/.github/workflows/on-trigger.yml @@ -66,6 +66,12 @@ jobs: strategy: fail-fast: ${{ github.event_name == 'merge_group' }} matrix: + # Enable ccache only for events targeting the XRPLF repository, since + # other accounts will not have access to our remote cache storage. + # However, we do not enable ccache for events targeting the master or a + # release branch, to protect against the rare case that the output + # produced by ccache is not identical to a regular compilation. + ccache_enabled: ${{ github.repository_owner == 'XRPLF' && !(github.base_ref == 'master' || startsWith(github.base_ref, 'release')) }} os: [linux, macos, windows] with: os: ${{ matrix.os }} diff --git a/.github/workflows/reusable-build-test-config.yml b/.github/workflows/reusable-build-test-config.yml index 98bf107225..575984162e 100644 --- a/.github/workflows/reusable-build-test-config.yml +++ b/.github/workflows/reusable-build-test-config.yml @@ -10,8 +10,14 @@ on: build_type: description: 'The build type to use ("Debug", "Release").' - type: string required: true + type: string + + ccache_enabled: + description: "Whether to enable ccache." + required: false + type: boolean + default: false cmake_args: description: "Additional arguments to pass to CMake." @@ -21,8 +27,8 @@ on: cmake_target: description: "The CMake target to build." - type: string required: true + type: string runs_on: description: Runner to run the job on as a JSON string @@ -66,8 +72,25 @@ jobs: container: ${{ inputs.image != '' && inputs.image || null }} timeout-minutes: 60 env: - ENABLED_VOIDSTAR: ${{ contains(inputs.cmake_args, '-Dvoidstar=ON') }} - ENABLED_COVERAGE: ${{ contains(inputs.cmake_args, '-Dcoverage=ON') }} + # Use a namespace to keep the objects separate for each configuration. + CCACHE_NAMESPACE: ${{ inputs.config_name }} + # Ccache supports both Redis and HTTP endpoints. + # * For Redis, use the following format: redis://ip:port, see + # https://github.com/ccache/ccache/wiki/Redis-storage. Note that TLS is + # not directly supported by ccache, and requires use of a proxy. + # * For HTTP use the following format: http://ip:port/cache when using + # nginx as backend or http://ip:port|layout=bazel when using Bazel + # Remote Cache, see https://github.com/ccache/ccache/wiki/HTTP-storage. + # Note that HTTPS is not directly supported by ccache. + CCACHE_REMOTE_ONLY: true + CCACHE_REMOTE_STORAGE: http://cache.dev.ripplex.io:8080|layout=bazel + # Ignore the creation and modification timestamps on files, since the + # header files are copied into separate directories by CMake, which will + # otherwise result in cache misses. + CCACHE_SLOPPINESS: include_file_ctime,include_file_mtime + # Determine if coverage and voidstar should be enabled. + COVERAGE_ENABLED: ${{ contains(inputs.cmake_args, '-Dcoverage=ON') }} + VOIDSTAR_ENABLED: ${{ contains(inputs.cmake_args, '-Dvoidstar=ON') }} steps: - name: Cleanup workspace (macOS and Windows) if: ${{ runner.os == 'macOS' || runner.os == 'Windows' }} @@ -79,7 +102,11 @@ jobs: - name: Prepare runner uses: XRPLF/actions/prepare-runner@2ece4ec6ab7de266859a6f053571425b2bd684b6 with: - disable_ccache: false + disable_ccache: ${{ !inputs.ccache_enabled }} + + - name: Set ccache log file + if: ${{ inputs.ccache_enabled && runner.debug == '1' }} + run: echo "CCACHE_LOGFILE=${{ runner.temp }}/ccache.log" >> "${GITHUB_ENV}" - name: Print build environment uses: ./.github/actions/print-env @@ -128,6 +155,15 @@ jobs: --parallel "${BUILD_NPROC}" \ --target "${CMAKE_TARGET}" + - name: Show ccache statistics + if: ${{ inputs.ccache_enabled }} + run: | + ccache --show-stats -vv + if [ '${{ runner.debug }}' = '1' ]; then + cat "${CCACHE_LOGFILE}" + curl ${CCACHE_REMOTE_STORAGE%|*}/status || true + fi + - name: Upload the binary (Linux) if: ${{ github.repository_owner == 'XRPLF' && runner.os == 'Linux' }} uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 @@ -150,7 +186,7 @@ jobs: fi - name: Verify presence of instrumentation (Linux) - if: ${{ runner.os == 'Linux' && env.ENABLED_VOIDSTAR == 'true' }} + if: ${{ runner.os == 'Linux' && env.VOIDSTAR_ENABLED == 'true' }} working-directory: ${{ env.BUILD_DIR }} run: | ./xrpld --version | grep libvoidstar @@ -185,7 +221,7 @@ jobs: netstat -an - name: Prepare coverage report - if: ${{ !inputs.build_only && env.ENABLED_COVERAGE == 'true' }} + if: ${{ !inputs.build_only && env.COVERAGE_ENABLED == 'true' }} working-directory: ${{ env.BUILD_DIR }} env: BUILD_NPROC: ${{ steps.nproc.outputs.nproc }} @@ -198,7 +234,7 @@ jobs: --target coverage - name: Upload coverage report - if: ${{ github.repository_owner == 'XRPLF' && !inputs.build_only && env.ENABLED_COVERAGE == 'true' }} + if: ${{ github.repository_owner == 'XRPLF' && !inputs.build_only && env.COVERAGE_ENABLED == 'true' }} uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3 with: disable_search: true diff --git a/.github/workflows/reusable-build-test.yml b/.github/workflows/reusable-build-test.yml index 7f14aacb9b..2b4d38da61 100644 --- a/.github/workflows/reusable-build-test.yml +++ b/.github/workflows/reusable-build-test.yml @@ -8,16 +8,24 @@ name: Build and test on: workflow_call: inputs: + ccache_enabled: + description: "Whether to enable ccache." + required: false + type: boolean + default: false + os: description: 'The operating system to use for the build ("linux", "macos", "windows").' required: true type: string + strategy_matrix: # TODO: Support additional strategies, e.g. "ubuntu" for generating all Ubuntu configurations. description: 'The strategy matrix to use for generating the configurations ("minimal", "all").' required: false type: string default: "minimal" + secrets: CODECOV_TOKEN: description: "The Codecov token to use for uploading coverage reports." @@ -43,6 +51,7 @@ jobs: with: build_only: ${{ matrix.build_only }} build_type: ${{ matrix.build_type }} + ccache_enabled: ${{ inputs.ccache_enabled }} cmake_args: ${{ matrix.cmake_args }} cmake_target: ${{ matrix.cmake_target }} runs_on: ${{ toJSON(matrix.architecture.runner) }} diff --git a/.github/workflows/upload-conan-deps.yml b/.github/workflows/upload-conan-deps.yml index 5024666394..8a9993d37a 100644 --- a/.github/workflows/upload-conan-deps.yml +++ b/.github/workflows/upload-conan-deps.yml @@ -72,7 +72,7 @@ jobs: - name: Prepare runner uses: XRPLF/actions/prepare-runner@2ece4ec6ab7de266859a6f053571425b2bd684b6 with: - disable_ccache: false + disable_ccache: true - name: Print build environment uses: ./.github/actions/print-env diff --git a/CMakeLists.txt b/CMakeLists.txt index ade9c2f995..70bc02c66d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,6 +28,9 @@ elseif(MSVC) add_compile_options(/wd4068) # Ignore unknown pragmas endif() +# Enable ccache to speed up builds. +include(Ccache) + # make GIT_COMMIT_HASH define available to all sources find_package(Git) if(Git_FOUND) diff --git a/cmake/Ccache.cmake b/cmake/Ccache.cmake new file mode 100644 index 0000000000..092212075c --- /dev/null +++ b/cmake/Ccache.cmake @@ -0,0 +1,51 @@ +find_program(CCACHE_PATH "ccache") +if (NOT CCACHE_PATH) + return() +endif () + +# For Linux and macOS we can use the ccache binary directly. +if (NOT MSVC) + set(CMAKE_C_COMPILER_LAUNCHER "${CCACHE_PATH}") + set(CMAKE_CXX_COMPILER_LAUNCHER "${CCACHE_PATH}") + message(STATUS "Found ccache: ${CCACHE_PATH}") + return() +endif () + +# For Windows more effort is required. The code below is a modified version of +# https://github.com/ccache/ccache/wiki/MS-Visual-Studio#usage-with-cmake. +if ("${CCACHE_PATH}" MATCHES "chocolatey") + message(DEBUG "Ccache path: ${CCACHE_PATH}") + # Chocolatey uses a shim executable that we cannot use directly, in which + # case we have to find the executable it points to. If we cannot find the + # target executable then we cannot use ccache. + find_program(BASH_PATH "bash") + if (NOT BASH_PATH) + message(WARNING "Could not find bash.") + return() + endif () + + execute_process( + COMMAND bash -c "export LC_ALL='en_US.UTF-8'; ${CCACHE_PATH} --shimgen-noop | grep -oP 'path to executable: \\K.+' | head -c -1" + OUTPUT_VARIABLE CCACHE_PATH) + + if (NOT CCACHE_PATH) + message(WARNING "Could not find ccache target.") + return() + endif () + file(TO_CMAKE_PATH "${CCACHE_PATH}" CCACHE_PATH) +endif () +message(STATUS "Found ccache: ${CCACHE_PATH}") + +# Tell cmake to use ccache for compiling with Visual Studio. +file(COPY_FILE + ${CCACHE_PATH} ${CMAKE_BINARY_DIR}/cl.exe + ONLY_IF_DIFFERENT) +set(CMAKE_VS_GLOBALS + "CLToolExe=cl.exe" + "CLToolPath=${CMAKE_BINARY_DIR}" + "TrackFileAccess=false" + "UseMultiToolTask=true") + +# By default Visual Studio generators will use /Zi, which is not compatible with +# ccache, so tell it to use /Z7 instead. +set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT "$<$:Embedded>") From f80059e4673ad96010229c14dcdf0ac6cb11d9a0 Mon Sep 17 00:00:00 2001 From: Bart Date: Wed, 7 Jan 2026 06:07:53 -0500 Subject: [PATCH 05/30] ci: Move variable into right place (#6179) This change moves the `enable_ccache` variable in the `on-trigger.yml` file to the correct location. --- .github/workflows/on-trigger.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/on-trigger.yml b/.github/workflows/on-trigger.yml index a95402ced7..2c63c2baa5 100644 --- a/.github/workflows/on-trigger.yml +++ b/.github/workflows/on-trigger.yml @@ -66,14 +66,14 @@ jobs: strategy: fail-fast: ${{ github.event_name == 'merge_group' }} matrix: - # Enable ccache only for events targeting the XRPLF repository, since - # other accounts will not have access to our remote cache storage. - # However, we do not enable ccache for events targeting the master or a - # release branch, to protect against the rare case that the output - # produced by ccache is not identical to a regular compilation. - ccache_enabled: ${{ github.repository_owner == 'XRPLF' && !(github.base_ref == 'master' || startsWith(github.base_ref, 'release')) }} os: [linux, macos, windows] with: + # Enable ccache only for events targeting the XRPLF repository, since + # other accounts will not have access to our remote cache storage. + # However, we do not enable ccache for events targeting the master or a + # release branch, to protect against the rare case that the output + # produced by ccache is not identical to a regular compilation. + ccache_enabled: ${{ github.repository_owner == 'XRPLF' && !(github.base_ref == 'master' || startsWith(github.base_ref, 'release')) }} os: ${{ matrix.os }} strategy_matrix: ${{ github.event_name == 'schedule' && 'all' || 'minimal' }} secrets: From 3c9f5b62525cb1d6ca1153eeb10433db7d7379fd Mon Sep 17 00:00:00 2001 From: Mayukha Vadari Date: Wed, 7 Jan 2026 12:10:19 -0500 Subject: [PATCH 06/30] refactor: Fix typos in comments, configure cspell (#6164) This change sets up a `cspell `configuration and fixes lots of typos in comments. There are no other code changes. --- .config/cspell.config.yaml | 272 ++++++++++++++++++ .github/scripts/levelization/README.md | 2 +- .pre-commit-config.yaml | 15 + CONTRIBUTING.md | 12 +- SECURITY.md | 2 +- cfg/xrpld-example.cfg | 2 +- cmake/XrplCore.cmake | 2 +- docs/0001-negative-unl/README.md | 2 +- .../negativeUNLSqDiagram.puml | 4 +- docs/consensus.md | 4 +- include/xrpl/basics/IntrusiveRefCounts.h | 2 +- include/xrpl/basics/Log.h | 3 +- include/xrpl/beast/core/LexicalCast.h | 2 +- include/xrpl/json/json_reader.h | 4 +- include/xrpl/json/json_value.h | 2 +- include/xrpl/ledger/CredentialHelpers.h | 5 +- include/xrpl/nodestore/DummyScheduler.h | 2 +- include/xrpl/nodestore/README.md | 4 +- .../proto/org/xrpl/rpc/v1/xrp_ledger.proto | 2 +- include/xrpl/protocol/Indexes.h | 2 +- include/xrpl/protocol/Protocol.h | 2 +- include/xrpl/protocol/Quality.h | 2 +- include/xrpl/protocol/TxFlags.h | 2 +- include/xrpl/protocol/XChainAttestations.h | 6 +- include/xrpl/protocol/detail/STVar.h | 2 +- .../xrpl/protocol/detail/ledger_entries.macro | 4 +- include/xrpl/resource/README.md | 2 +- include/xrpl/resource/detail/Logic.h | 2 +- include/xrpl/server/detail/LowestLayer.h | 2 +- include/xrpl/shamap/README.md | 2 +- include/xrpl/shamap/SHAMapItem.h | 4 +- src/libxrpl/basics/Number.cpp | 4 +- src/libxrpl/basics/make_SSLContext.cpp | 4 +- src/libxrpl/json/Writer.cpp | 2 +- src/libxrpl/ledger/View.cpp | 2 +- src/libxrpl/protocol/IOUAmount.cpp | 2 +- src/libxrpl/protocol/STAmount.cpp | 2 +- src/libxrpl/protocol/tokens.cpp | 2 +- src/test/app/AccountDelete_test.cpp | 6 +- src/test/app/AmendmentTable_test.cpp | 2 +- src/test/app/DepositAuth_test.cpp | 20 +- src/test/app/Escrow_test.cpp | 4 +- src/test/app/FixNFTokenPageLinks_test.cpp | 2 +- src/test/app/Invariants_test.cpp | 10 +- src/test/app/LoanBroker_test.cpp | 2 +- src/test/app/MPToken_test.cpp | 8 +- src/test/app/NFTokenBurn_test.cpp | 2 +- src/test/app/NFToken_test.cpp | 14 +- src/test/app/PayChan_test.cpp | 6 +- src/test/app/PermissionedDEX_test.cpp | 2 +- src/test/app/ValidatorSite_test.cpp | 6 +- src/test/basics/IntrusiveShared_test.cpp | 2 +- src/test/basics/XRPAmount_test.cpp | 2 +- .../consensus/ByzantineFailureSim_test.cpp | 2 +- src/test/consensus/Consensus_test.cpp | 12 +- .../DistributedValidatorsSim_test.cpp | 4 +- src/test/consensus/ScaleFreeSim_test.cpp | 2 +- src/test/csf/CollectorRef.h | 2 +- src/test/csf/Histogram.h | 2 +- src/test/jtx/TestHelpers.h | 2 +- src/test/jtx/deposit.h | 2 +- src/test/jtx/rpc.h | 2 +- src/test/protocol/Seed_test.cpp | 2 +- src/test/rpc/AccountObjects_test.cpp | 2 +- src/test/rpc/ManifestRPC_test.cpp | 2 +- src/test/rpc/Subscribe_test.cpp | 2 +- src/tests/libxrpl/basics/base64.cpp | 2 + src/xrpld/app/consensus/RCLConsensus.h | 2 +- .../app/ledger/AbstractFetchPackContainer.h | 2 +- src/xrpld/app/ledger/BookListeners.cpp | 2 +- src/xrpld/app/ledger/README.md | 2 +- src/xrpld/app/main/GRPCServer.cpp | 2 +- src/xrpld/app/main/GRPCServer.h | 2 +- src/xrpld/app/misc/FeeEscalation.md | 4 +- src/xrpld/app/misc/HashRouter.cpp | 2 +- src/xrpld/app/misc/HashRouter.h | 2 +- src/xrpld/app/misc/Manifest.h | 2 +- src/xrpld/app/misc/NetworkOPs.cpp | 14 +- src/xrpld/app/misc/NetworkOPs.h | 6 +- src/xrpld/app/misc/SHAMapStoreImp.cpp | 2 +- src/xrpld/app/misc/Transaction.h | 2 +- src/xrpld/app/misc/TxQ.h | 2 +- src/xrpld/app/misc/detail/LendingHelpers.cpp | 2 +- src/xrpld/app/paths/Pathfinder.cpp | 4 +- src/xrpld/app/paths/detail/DirectStep.cpp | 2 +- src/xrpld/app/rdb/backend/detail/Node.h | 14 +- src/xrpld/app/tx/detail/CreateOffer.cpp | 4 +- src/xrpld/app/tx/detail/InvariantCheck.h | 2 +- src/xrpld/app/tx/detail/NFTokenUtils.cpp | 2 +- src/xrpld/app/tx/detail/Payment.cpp | 4 +- src/xrpld/conditions/detail/PreimageSha256.h | 2 +- src/xrpld/consensus/Consensus.cpp | 2 +- src/xrpld/core/Config.h | 2 +- src/xrpld/core/detail/SociDB.cpp | 2 +- src/xrpld/overlay/detail/PeerImp.cpp | 2 +- src/xrpld/peerfinder/PeerfinderManager.h | 2 +- src/xrpld/peerfinder/README.md | 14 +- src/xrpld/peerfinder/detail/Checker.h | 2 +- src/xrpld/peerfinder/detail/Fixed.h | 2 +- src/xrpld/rpc/GRPCHandlers.h | 2 +- src/xrpld/rpc/detail/Handler.cpp | 2 +- src/xrpld/rpc/detail/InfoSub.cpp | 6 +- src/xrpld/rpc/detail/RPCCall.cpp | 2 +- src/xrpld/rpc/detail/RPCSub.cpp | 2 +- src/xrpld/rpc/detail/TransactionSign.cpp | 4 +- src/xrpld/rpc/handlers/GatewayBalances.cpp | 2 +- src/xrpld/rpc/handlers/NoRippleCheck.cpp | 2 +- 107 files changed, 473 insertions(+), 182 deletions(-) create mode 100644 .config/cspell.config.yaml diff --git a/.config/cspell.config.yaml b/.config/cspell.config.yaml new file mode 100644 index 0000000000..969720a11d --- /dev/null +++ b/.config/cspell.config.yaml @@ -0,0 +1,272 @@ +ignorePaths: + - build/** + - src/libxrpl/crypto + - src/test/** # Will be removed in the future + - CMakeUserPresets.json + - Doxyfile + - docs/**/*.puml + - cmake/** + - LICENSE.md +language: en +allowCompoundWords: true +ignoreRandomStrings: true +minWordLength: 5 +dictionaries: + - cpp + - en_US + - en_GB +ignoreRegExpList: + - /[rs][1-9A-HJ-NP-Za-km-z]{25,34}/g # addresses and seeds + - /(XRPL|BEAST)_[A-Z_0-9]+_H_INCLUDED+/g # include guards + - /(XRPL|BEAST)_[A-Z_0-9]+_H+/g # include guards + - /::[a-z:_]+/g # things from other namespaces + - /lib[a-z]+/g # libraries + - /[0-9]{4}-[0-9]{2}-[0-9]{2}[,:][A-Za-zÀ-ÖØ-öø-ÿ.\s]+/g # copyright dates + - /[0-9]{4}[,:]?\s*[A-Za-zÀ-ÖØ-öø-ÿ.\s]+/g # copyright years + - /\[[A-Za-z0-9-]+\]\(https:\/\/github.com\/[A-Za-z0-9-]+\)/g # Github usernames + - /-[DWw][a-zA-Z0-9_-]+=/g # compile flags + - /[\['"`]-[DWw][a-zA-Z0-9_-]+['"`\]]/g # compile flags +suggestWords: + - xprl->xrpl + - unsynched->unsynced + - synched->synced + - synch->sync +words: + - abempty + - AMMID + - amt + - amts + - asnode + - asynchrony + - attestation + - authorises + - autobridge + - autobridged + - autobridging + - bimap + - bindir + - bookdir + - Bougalis + - Britto + - Btrfs + - canonicality + - checkme + - chrono + - citardauq + - clawback + - clawbacks + - coeffs + - coldwallet + - compr + - conanfile + - conanrun + - connectability + - coro + - coros + - cowid + - cryptocondition + - cryptoconditional + - cryptoconditions + - csprng + - ctid + - currenttxhash + - daria + - dcmake + - dearmor + - deleteme + - demultiplexer + - deserializaton + - desync + - desynced + - determ + - distro + - doxyfile + - dxrpl + - endmacro + - endpointv + - exceptioned + - Falco + - finalizers + - firewalled + - fmtdur + - funclets + - gcov + - gcovr + - Gnutella + - gpgcheck + - gpgkey + - hotwallet + - ifndef + - inequation + - insuf + - insuff + - iou + - ious + - isrdc + - jemalloc + - jlog + - keylet + - keylets + - keyvadb + - ledgerentry + - ledgerhash + - ledgerindex + - leftw + - legleux + - levelization + - levelized + - libpb + - libxrpl + - llection + - LOCALGOOD + - logwstream + - lseq + - lsmf + - ltype + - MEMORYSTATUSEX + - Merkle + - Metafuncton + - misprediction + - mptbalance + - mptflags + - mptid + - mptissuance + - mptissuanceid + - mptoken + - mptokenid + - mptokenissuance + - mptokens + - mpts + - multisig + - multisign + - multisigned + - Nakamoto + - nftid + - nftoffer + - nftoken + - nftokenid + - nftokenpages + - nftokens + - nftpage + - nikb + - nonxrp + - noripple + - nudb + - nullptr + - nunl + - Nyffenegger + - ostr + - partitioner + - paychan + - paychans + - permdex + - perminute + - permissioned + - pointee + - preauth + - preauthorization + - preauthorize + - preauthorizes + - preclaim + - protobuf + - protos + - ptrs + - pyenv + - qalloc + - queuable + - Raphson + - replayer + - rerere + - retriable + - RIPD + - ripdtop + - rippleci + - rippled + - ripplerpc + - rippletest + - RLUSD + - rngfill + - rocksdb + - Rohrs + - roundings + - sahyadri + - Satoshi + - secp + - sendq + - seqit + - sf + - shamap + - shamapitem + - sidechain + - SIGGOOD + - sle + - sles + - soci + - socidb + - sslws + - statsd + - STATSDCOLLECTOR + - stissue + - stnum + - stobj + - stobject + - stpath + - stpathset + - sttx + - stvar + - stvector + - stxchainattestations + - superpeer + - superpeers + - takergets + - takerpays + - ters + - tx + - txid + - txids + - txjson + - txn + - txns + - txs + - umant + - unacquired + - unambiguity + - unauthorizes + - unauthorizing + - unergonomic + - unfetched + - unflatten + - unfund + - unimpair + - unroutable + - unscalable + - unserviced + - unshareable + - unshares + - unsquelch + - unsquelched + - unsquelching + - unvalidated + - unveto + - unvetoed + - upvotes + - USDB + - variadics + - venv + - vfalco + - vinnie + - wextra + - wptr + - writeme + - wsrch + - wthread + - xbridge + - xchain + - ximinez + - XMACRO + - xrpkuwait + - xrpl + - xrpld + - xrplf + - xxhash + - xxhasher diff --git a/.github/scripts/levelization/README.md b/.github/scripts/levelization/README.md index 3b77a192b9..c8954b900e 100644 --- a/.github/scripts/levelization/README.md +++ b/.github/scripts/levelization/README.md @@ -81,7 +81,7 @@ It generates many files of [results](results): - `rawincludes.txt`: The raw dump of the `#includes` - `paths.txt`: A second dump grouping the source module - to the destination module, deduped, and with frequency counts. + to the destination module, de-duped, and with frequency counts. - `includes/`: A directory where each file represents a module and contains a list of modules and counts that the module _includes_. - `includedby/`: Similar to `includes/`, but the other way around. Each diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c85e0798f7..4a1dc159dc 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -36,6 +36,21 @@ repos: hooks: - id: black + # - repo: https://github.com/streetsidesoftware/cspell-cli + # rev: v9.2.0 + # hooks: + # - id: cspell # Spell check changed files + # - id: cspell # Spell check the commit message + # name: check commit message spelling + # args: + # - --no-must-find-files + # - --no-progress + # - --no-summary + # - --files + # - .git/COMMIT_EDITMSG + # stages: [commit-msg] + # always_run: true # This might not be necessary. + exclude: | (?x)^( external/.*| diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5b578b7f13..4f99972713 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -555,16 +555,16 @@ Rippled uses a linear workflow model that can be summarized as: git fetch --multiple upstreams user1 user2 user3 [...] git checkout -B release-next --no-track upstream/develop -# Only do an ff-only merge if prbranch1 is either already +# Only do an ff-only merge if pr-branch1 is either already # squashed, or needs to be merged with separate commits, # and has no merge commits. -# Use -S on the ff-only merge if prbranch1 isn't signed. -git merge [-S] --ff-only user1/prbranch1 +# Use -S on the ff-only merge if pr-branch1 isn't signed. +git merge [-S] --ff-only user1/pr-branch1 -git merge --squash user2/prbranch2 +git merge --squash user2/pr-branch2 git commit -S # Use the commit message provided on the PR -git merge --squash user3/prbranch3 +git merge --squash user3/pr-branch3 git commit -S # Use the commit message provided on the PR [...] @@ -876,7 +876,7 @@ git push --delete upstream-push master-next #### Special cases: point releases, hotfixes, etc. -On occassion, a bug or issue is discovered in a version that already +On occasion, a bug or issue is discovered in a version that already had a final release. Most of the time, development will have started on the next version, and will usually have changes in `develop` and often in `release`. diff --git a/SECURITY.md b/SECURITY.md index 3fd85bad0a..18eec312ed 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -42,7 +42,7 @@ For more information on responsible disclosure, please read this [Wikipedia arti ## Report Handling Process -Please report the bug directly to us and limit further disclosure. If you want to prove that you knew the bug as of a given time, consider using a cryptographic precommitment: hash the content of your report and publish the hash on a medium of your choice (e.g. on Twitter or as a memo in a transaction) as "proof" that you had written the text at a given point in time. +Please report the bug directly to us and limit further disclosure. If you want to prove that you knew the bug as of a given time, consider using a cryptographic pre-commitment: hash the content of your report and publish the hash on a medium of your choice (e.g. on Twitter or as a memo in a transaction) as "proof" that you had written the text at a given point in time. Once we receive a report, we: diff --git a/cfg/xrpld-example.cfg b/cfg/xrpld-example.cfg index b5180dce52..93fab4a9ba 100644 --- a/cfg/xrpld-example.cfg +++ b/cfg/xrpld-example.cfg @@ -218,7 +218,7 @@ # administrative commands. # # NOTE A common configuration value for the admin field is "localhost". -# If you are listening on all IPv4/IPv6 addresses by specifing +# If you are listening on all IPv4/IPv6 addresses by specifying # ip = :: then you can use admin = ::ffff:127.0.0.1,::1 to allow # administrative access from both IPv4 and IPv6 localhost # connections. diff --git a/cmake/XrplCore.cmake b/cmake/XrplCore.cmake index 12ba58d499..0689fbe7b6 100644 --- a/cmake/XrplCore.cmake +++ b/cmake/XrplCore.cmake @@ -206,7 +206,7 @@ if(xrpld) ) exclude_if_included(xrpld) # define a macro for tests that might need to - # be exluded or run differently in CI environment + # be excluded or run differently in CI environment if(is_ci) target_compile_definitions(xrpld PRIVATE XRPL_RUNNING_IN_CI) endif () diff --git a/docs/0001-negative-unl/README.md b/docs/0001-negative-unl/README.md index f28ff63c6f..c863cab9da 100644 --- a/docs/0001-negative-unl/README.md +++ b/docs/0001-negative-unl/README.md @@ -134,7 +134,7 @@ validation messages (_PAV_) received from each validator on the node's UNL. Note that the node will only count the validation messages that agree with its own validations. -We define the **PAV** as the **P**ercentage of **A**greed **V**alidation +We define the **PAV** as the Percentage of Agreed Validation messages received for the last N ledgers, where N = 256 by default. When the PAV drops below the **_low-water mark_**, the validator is considered diff --git a/docs/0001-negative-unl/negativeUNLSqDiagram.puml b/docs/0001-negative-unl/negativeUNLSqDiagram.puml index 9f37d43903..d86b98c01f 100644 --- a/docs/0001-negative-unl/negativeUNLSqDiagram.puml +++ b/docs/0001-negative-unl/negativeUNLSqDiagram.puml @@ -43,14 +43,14 @@ alt phase == OPEN alt sqn%256==0 CA -[#green]> RM: getValidations CA -[#green]> CA: create UNLModify Tx - hnote over CA#lightgreen: use validatations of the last 256 ledgers\nto figure out UNLModify Tx candidates.\nIf any, create UNLModify Tx, and add to TxSet. + hnote over CA#lightgreen: use validations of the last 256 ledgers\nto figure out UNLModify Tx candidates.\nIf any, create UNLModify Tx, and add to TxSet. end CA -> GC GC -> CA: propose deactivate CA end else phase == ESTABLISH - hnote over GC: receive peer postions + hnote over GC: receive peer positions GC -> GC : update our position GC -> CA : propose \n(if position changed) GC -> GC : check if have consensus diff --git a/docs/consensus.md b/docs/consensus.md index 067e15d0c8..23e5e7d5be 100644 --- a/docs/consensus.md +++ b/docs/consensus.md @@ -189,7 +189,7 @@ validations. It checks this on every call to `timerEntry`. - _Wrong Ledger_ indicates the node is not working on the correct prior ledger and does not have it available. It requests that ledger from the network, but continues to work towards consensus this round while waiting. If it had been - _proposing_, it will send a special "bowout" proposal to its peers to indicate + _proposing_, it will send a special "bow-out" proposal to its peers to indicate its change in mode for the rest of this round. For the duration of the round, it defers to peer positions for determining the consensus outcome as if it were just _observing_. @@ -515,7 +515,7 @@ are excerpts of the generic consensus implementation and of helper types that wi interact with the concrete implementing class. ```{.cpp} -// Represents a transction under dispute this round +// Represents a transaction under dispute this round template class DisputedTx; // Represents how the node participates in Consensus this round diff --git a/include/xrpl/basics/IntrusiveRefCounts.h b/include/xrpl/basics/IntrusiveRefCounts.h index 630c08395d..440ecdd19c 100644 --- a/include/xrpl/basics/IntrusiveRefCounts.h +++ b/include/xrpl/basics/IntrusiveRefCounts.h @@ -301,7 +301,7 @@ IntrusiveRefCounts::addWeakReleaseStrongRef() const // change the counts and flags (the count could be atomically changed, but // the flags depend on the current value of the counts). // - // Note: If this becomes a perf bottleneck, the `partialDestoryStartedMask` + // 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) diff --git a/include/xrpl/basics/Log.h b/include/xrpl/basics/Log.h index 9443e8afdf..f10f1ff64c 100644 --- a/include/xrpl/basics/Log.h +++ b/include/xrpl/basics/Log.h @@ -221,7 +221,8 @@ public: private: enum { // Maximum line length for log messages. - // If the message exceeds this length it will be truncated with elipses. + // If the message exceeds this length it will be truncated with + // ellipses. maximumMessageCharacters = 12 * 1024 }; diff --git a/include/xrpl/beast/core/LexicalCast.h b/include/xrpl/beast/core/LexicalCast.h index fea1c35f55..c5b7f5540a 100644 --- a/include/xrpl/beast/core/LexicalCast.h +++ b/include/xrpl/beast/core/LexicalCast.h @@ -18,7 +18,7 @@ namespace beast { namespace detail { -// These specializatons get called by the non-member functions to do the work +// These specializations get called by the non-member functions to do the work template struct LexicalCast; diff --git a/include/xrpl/json/json_reader.h b/include/xrpl/json/json_reader.h index a660d82e2a..963dc0f26e 100644 --- a/include/xrpl/json/json_reader.h +++ b/include/xrpl/json/json_reader.h @@ -1,8 +1,6 @@ #ifndef XRPL_JSON_JSON_READER_H_INCLUDED #define XRPL_JSON_JSON_READER_H_INCLUDED -#define CPPTL_JSON_READER_H_INCLUDED - #include #include @@ -231,4 +229,4 @@ operator>>(std::istream&, Value&); } // namespace Json -#endif // CPPTL_JSON_READER_H_INCLUDED +#endif // XRPL_JSON_JSON_READER_H_INCLUDED diff --git a/include/xrpl/json/json_value.h b/include/xrpl/json/json_value.h index 2e38f2e75e..3daf441592 100644 --- a/include/xrpl/json/json_value.h +++ b/include/xrpl/json/json_value.h @@ -682,4 +682,4 @@ public: } // namespace Json -#endif // CPPTL_JSON_H_INCLUDED +#endif // XRPL_JSON_JSON_VALUE_H_INCLUDED diff --git a/include/xrpl/ledger/CredentialHelpers.h b/include/xrpl/ledger/CredentialHelpers.h index 52ddfe33aa..b90aa44c0d 100644 --- a/include/xrpl/ledger/CredentialHelpers.h +++ b/include/xrpl/ledger/CredentialHelpers.h @@ -15,7 +15,7 @@ namespace xrpl { namespace credentials { // These function will be used by the code that use DepositPreauth / Credentials -// (and any future preauthorization modes) as part of authorization (all the +// (and any future pre-authorization modes) as part of authorization (all the // transfer funds transactions) // Check if credential sfExpiration field has passed ledger's parentCloseTime @@ -41,7 +41,8 @@ checkFields(STTx const& tx, beast::Journal j); // Accessing the ledger to check if provided credentials are valid. Do not use // in doApply (only in preclaim) since it does not remove expired credentials. -// If you call it in prelaim, you also must call verifyDepositPreauth in doApply +// If you call it in preclaim, you also must call verifyDepositPreauth in +// doApply TER valid( STTx const& tx, diff --git a/include/xrpl/nodestore/DummyScheduler.h b/include/xrpl/nodestore/DummyScheduler.h index 9ffe5ad80d..b31613480f 100644 --- a/include/xrpl/nodestore/DummyScheduler.h +++ b/include/xrpl/nodestore/DummyScheduler.h @@ -6,7 +6,7 @@ namespace xrpl { namespace NodeStore { -/** Simple NodeStore Scheduler that just peforms the tasks synchronously. */ +/** Simple NodeStore Scheduler that just performs the tasks synchronously. */ class DummyScheduler : public Scheduler { public: diff --git a/include/xrpl/nodestore/README.md b/include/xrpl/nodestore/README.md index 83da29d9ce..4b228bfc9a 100644 --- a/include/xrpl/nodestore/README.md +++ b/include/xrpl/nodestore/README.md @@ -96,7 +96,7 @@ Facebook's RocksDB database, builds on LevelDB. Use SQLite. -'path' speficies where the backend will store its data files. +'path' specifies where the backend will store its data files. Choices for 'compression' @@ -161,7 +161,7 @@ Through various executions and profiling some conclusions are presented below. - Multiple runs of the benchmarks can yield surprisingly different results. This can perhaps be attributed to the asynchronous nature of rocksdb's compaction - process. The benchmarks are artifical and create highly unlikely write load to + process. The benchmarks are artificial and create highly unlikely write load to create the dataset to measure different read access patterns. Therefore multiple runs of the benchmarks are required to get a feel for the effectiveness of the changes. This contrasts sharply with the keyvadb benchmarking were highly diff --git a/include/xrpl/proto/org/xrpl/rpc/v1/xrp_ledger.proto b/include/xrpl/proto/org/xrpl/rpc/v1/xrp_ledger.proto index 2b8dc471ce..942a4a2135 100644 --- a/include/xrpl/proto/org/xrpl/rpc/v1/xrp_ledger.proto +++ b/include/xrpl/proto/org/xrpl/rpc/v1/xrp_ledger.proto @@ -9,7 +9,7 @@ import "org/xrpl/rpc/v1/get_ledger_entry.proto"; import "org/xrpl/rpc/v1/get_ledger_data.proto"; import "org/xrpl/rpc/v1/get_ledger_diff.proto"; -// These methods are binary only methods for retrieiving arbitrary ledger state +// These methods are binary only methods for retrieving arbitrary ledger state // via gRPC. These methods are used by clio, but can also be // used by any client that wants to extract ledger state in an efficient manner. // They do not directly mimic the JSON equivalent methods. diff --git a/include/xrpl/protocol/Indexes.h b/include/xrpl/protocol/Indexes.h index 1cb22b4d6c..777c3b8aa9 100644 --- a/include/xrpl/protocol/Indexes.h +++ b/include/xrpl/protocol/Indexes.h @@ -18,7 +18,7 @@ namespace xrpl { class SeqProxy; -/** Keylet computation funclets. +/** Keylet computation functions. Entries in the ledger are located using 256-bit locators. The locators are calculated using a wide range of parameters specific to the entry whose diff --git a/include/xrpl/protocol/Protocol.h b/include/xrpl/protocol/Protocol.h index fb315eace4..0c72b80de4 100644 --- a/include/xrpl/protocol/Protocol.h +++ b/include/xrpl/protocol/Protocol.h @@ -179,7 +179,7 @@ static constexpr int loanPaymentsPerFeeIncrement = 5; * * This limit is enforced during the loan payment process, and thus is not * estimated. If the limit is hit, no further payments or overpayments will be - * processed, no matter how much of the transation Amount is left, but the + * processed, no matter how much of the transaction Amount is left, but the * transaction will succeed with the payments that have been processed up to * that point. * diff --git a/include/xrpl/protocol/Quality.h b/include/xrpl/protocol/Quality.h index 1fafa5e321..83a038490a 100644 --- a/include/xrpl/protocol/Quality.h +++ b/include/xrpl/protocol/Quality.h @@ -210,7 +210,7 @@ public: private: // The ceil_in and ceil_out methods that deal in TAmount all convert - // their arguments to STAoumout and convert the result back to TAmount. + // their arguments to STAmount and convert the result back to TAmount. // This helper function takes care of all the conversion operations. template < class In, diff --git a/include/xrpl/protocol/TxFlags.h b/include/xrpl/protocol/TxFlags.h index 194c4c6af1..5527a264e4 100644 --- a/include/xrpl/protocol/TxFlags.h +++ b/include/xrpl/protocol/TxFlags.h @@ -277,7 +277,7 @@ constexpr std::uint32_t const tfLoanOverpayment = 0x00010000; // interest and fees, or it will fail. False: Not a full payment. constexpr std::uint32_t const tfLoanFullPayment = 0x00020000; // tfLoanLatePayment: True, indicates that the payment is late, -// and includes late iterest and fees. If the loan is not late, +// and includes late interest and fees. If the loan is not late, // it will fail. False: not a late payment. If the current payment // is overdue, the transaction will fail. constexpr std::uint32_t const tfLoanLatePayment = 0x00040000; diff --git a/include/xrpl/protocol/XChainAttestations.h b/include/xrpl/protocol/XChainAttestations.h index 4dc9cfcf6a..bd76936b49 100644 --- a/include/xrpl/protocol/XChainAttestations.h +++ b/include/xrpl/protocol/XChainAttestations.h @@ -33,12 +33,12 @@ struct AttestationBase // Account on the sending chain that triggered the event (sent the // transaction) AccountID sendingAccount; - // Amount transfered on the sending chain + // Amount transferred on the sending chain STAmount sendingAmount; // Account on the destination chain that collects a share of the attestation // reward AccountID rewardAccount; - // Amount was transfered on the locking chain + // Amount was transferred on the locking chain bool wasLockingChainSend; explicit AttestationBase( @@ -354,7 +354,7 @@ struct XChainCreateAccountAttestation XChainCreateAccountAttestation const& rhs); }; -// Attestations from witness servers for a particular claimid and bridge. +// Attestations from witness servers for a particular claim ID and bridge. // Only one attestation per signature is allowed. template class XChainAttestationsBase diff --git a/include/xrpl/protocol/detail/STVar.h b/include/xrpl/protocol/detail/STVar.h index 540ba2bf77..219d1ed738 100644 --- a/include/xrpl/protocol/detail/STVar.h +++ b/include/xrpl/protocol/detail/STVar.h @@ -42,7 +42,7 @@ concept ValidConstructSTArgs = class STVar { private: - // The largest "small object" we can accomodate + // The largest "small object" we can accommodate static std::size_t constexpr max_size = 72; std::aligned_storage::type d_; diff --git a/include/xrpl/protocol/detail/ledger_entries.macro b/include/xrpl/protocol/detail/ledger_entries.macro index 1034c35895..de9c41bf52 100644 --- a/include/xrpl/protocol/detail/ledger_entries.macro +++ b/include/xrpl/protocol/detail/ledger_entries.macro @@ -237,7 +237,7 @@ LEDGER_ENTRY(ltOFFER, 0x006f, Offer, offer, ({ {sfAdditionalBooks, soeOPTIONAL}, })) -/** A ledger object which describes a deposit preauthorization. +/** A ledger object which describes a deposit pre-authorization. \sa keylet::depositPreauth */ @@ -577,7 +577,7 @@ LEDGER_ENTRY(ltLOAN, 0x0089, Loan, loan, ({ // - TrueTotalLoanValue = PaymentRemaining * PeriodicPayment // The unrounded true total value of the loan. // - // - TrueTotalPrincialOutstanding can be computed using the algorithm + // - TrueTotalPrincipalOutstanding can be computed using the algorithm // in the ripple::detail::loanPrincipalFromPeriodicPayment function. // // - TrueTotalInterestOutstanding = TrueTotalLoanValue - diff --git a/include/xrpl/resource/README.md b/include/xrpl/resource/README.md index 253e3c7625..e525ce83e3 100644 --- a/include/xrpl/resource/README.md +++ b/include/xrpl/resource/README.md @@ -66,7 +66,7 @@ values over time: this is implemented by the DecayingSample class. Each server in a cluster creates a list of IP addresses of end points that are imposing a significant load. This list is called Gossip, which is passed to other nodes in that cluster. Gossip helps individual -servers in the cluster identify IP addreses that might be unduly loading +servers in the cluster identify IP addresses that might be unduly loading the entire cluster. Again the recourse of the individual servers is to drop connections to those IP addresses that occur commonly in the gossip. diff --git a/include/xrpl/resource/detail/Logic.h b/include/xrpl/resource/detail/Logic.h index b1f90e0282..5bcfa42f31 100644 --- a/include/xrpl/resource/detail/Logic.h +++ b/include/xrpl/resource/detail/Logic.h @@ -61,7 +61,7 @@ private: // List of all active admin entries EntryIntrusiveList admin_; - // List of all inactve entries + // List of all inactive entries EntryIntrusiveList inactive_; // All imported gossip data diff --git a/include/xrpl/server/detail/LowestLayer.h b/include/xrpl/server/detail/LowestLayer.h index 57647867e3..c45d948241 100644 --- a/include/xrpl/server/detail/LowestLayer.h +++ b/include/xrpl/server/detail/LowestLayer.h @@ -9,7 +9,7 @@ namespace xrpl { -// Before boost 1.70, get_lowest_layer required an explicit templat parameter +// Before boost 1.70, get_lowest_layer required an explicit template parameter template decltype(auto) get_lowest_layer(T& t) noexcept diff --git a/include/xrpl/shamap/README.md b/include/xrpl/shamap/README.md index 3bff74e67b..419918c0cb 100644 --- a/include/xrpl/shamap/README.md +++ b/include/xrpl/shamap/README.md @@ -226,7 +226,7 @@ The `fetchNodeNT()` method goes through three phases: Any SHAMapLeafNode that is immutable has a sequence number of zero (sharable). When a mutable `SHAMap` is created then its SHAMapTreeNodes are - given non-zero sequence numbers (unsharable). But all nodes in the + given non-zero sequence numbers (unshareable). But all nodes in the TreeNodeCache are immutable, so if one is found here, its sequence number will be 0. diff --git a/include/xrpl/shamap/SHAMapItem.h b/include/xrpl/shamap/SHAMapItem.h index a69f40113d..e8d95b0684 100644 --- a/include/xrpl/shamap/SHAMapItem.h +++ b/include/xrpl/shamap/SHAMapItem.h @@ -125,13 +125,13 @@ intrusive_ptr_release(SHAMapItem const* x) { auto p = reinterpret_cast(x); - // The SHAMapItem constuctor isn't trivial (because the destructor + // The SHAMapItem constructor isn't trivial (because the destructor // for CountedObject isn't) so we can't avoid calling it here, but // plan for a future where we might not need to. if constexpr (!std::is_trivially_destructible_v) std::destroy_at(x); - // If the slabber doens't claim this pointer, it was allocated + // If the slabber doesn't claim this pointer, it was allocated // manually, so we free it manually. if (!detail::slabber.deallocate(const_cast(p))) delete[] p; diff --git a/src/libxrpl/basics/Number.cpp b/src/libxrpl/basics/Number.cpp index 96c13c9db8..9984b26ffe 100644 --- a/src/libxrpl/basics/Number.cpp +++ b/src/libxrpl/basics/Number.cpp @@ -38,8 +38,8 @@ Number::setround(rounding_mode mode) // Guard -// The Guard class is used to tempoarily add extra digits of -// preicision to an operation. This enables the final result +// The Guard class is used to temporarily add extra digits of +// precision to an operation. This enables the final result // to be correctly rounded to the internal precision of Number. class Number::Guard diff --git a/src/libxrpl/basics/make_SSLContext.cpp b/src/libxrpl/basics/make_SSLContext.cpp index 579edb0f71..ef5996e051 100644 --- a/src/libxrpl/basics/make_SSLContext.cpp +++ b/src/libxrpl/basics/make_SSLContext.cpp @@ -28,7 +28,7 @@ namespace xrpl { namespace openssl { namespace detail { -/** The default strength of self-signed RSA certifices. +/** The default strength of self-signed RSA certificates. Per NIST Special Publication 800-57 Part 3, 2048-bit RSA is still considered acceptably secure. Generally, we would want to go above @@ -131,7 +131,7 @@ initAnonymous(boost::asio::ssl::context& context) LogicError("X509_new failed"); // According to the standards (X.509 et al), the value should be one - // less than the actualy certificate version we want. Since we want + // less than the actually certificate version we want. Since we want // version 3, we must use a 2. X509_set_version(x509, 2); diff --git a/src/libxrpl/json/Writer.cpp b/src/libxrpl/json/Writer.cpp index fcdceb7253..6da29211ca 100644 --- a/src/libxrpl/json/Writer.cpp +++ b/src/libxrpl/json/Writer.cpp @@ -193,7 +193,7 @@ public: } private: - // JSON collections are either arrrays, or objects. + // JSON collections are either arrays, or objects. struct Collection { explicit Collection() = default; diff --git a/src/libxrpl/ledger/View.cpp b/src/libxrpl/ledger/View.cpp index c817a85b65..329d3cfcae 100644 --- a/src/libxrpl/ledger/View.cpp +++ b/src/libxrpl/ledger/View.cpp @@ -3244,7 +3244,7 @@ enforceMPTokenAuthorization( auto const maybeDomainID = sleIssuance->at(~sfDomainID); bool expired = false; bool const authorizedByDomain = [&]() -> bool { - // NOTE: defensive here, shuld be checked in preclaim + // NOTE: defensive here, should be checked in preclaim if (!maybeDomainID.has_value()) return false; // LCOV_EXCL_LINE diff --git a/src/libxrpl/protocol/IOUAmount.cpp b/src/libxrpl/protocol/IOUAmount.cpp index 01283886e1..5c9ab1febc 100644 --- a/src/libxrpl/protocol/IOUAmount.cpp +++ b/src/libxrpl/protocol/IOUAmount.cpp @@ -18,7 +18,7 @@ namespace xrpl { namespace { -// Use a static inside a function to help prevent order-of-initialzation issues +// Use a static inside a function to help prevent order-of-initialization issues LocalValue& getStaticSTNumberSwitchover() { diff --git a/src/libxrpl/protocol/STAmount.cpp b/src/libxrpl/protocol/STAmount.cpp index 824453a4d3..ebccfb3e64 100644 --- a/src/libxrpl/protocol/STAmount.cpp +++ b/src/libxrpl/protocol/STAmount.cpp @@ -662,7 +662,7 @@ STAmount::getFullText() const std::string STAmount::getText() const { - // keep full internal accuracy, but make more human friendly if posible + // keep full internal accuracy, but make more human friendly if possible if (*this == beast::zero) return "0"; diff --git a/src/libxrpl/protocol/tokens.cpp b/src/libxrpl/protocol/tokens.cpp index d4253a2df1..420eb56df0 100644 --- a/src/libxrpl/protocol/tokens.cpp +++ b/src/libxrpl/protocol/tokens.cpp @@ -649,7 +649,7 @@ encodeBase58Token( return detail::b256_to_b58_be(b58Span, out); } // Convert from base 58 to base 256, largest coefficients first -// The input is encoded in XPRL format, with the token in the first +// The input is encoded in XRPL format, with the token in the first // byte and the checksum in the last four bytes. // The decoded base 256 value does not include the token type or checksum. // It is an error if the token type or checksum does not match. diff --git a/src/test/app/AccountDelete_test.cpp b/src/test/app/AccountDelete_test.cpp index 749ed33e28..44d484ac7a 100644 --- a/src/test/app/AccountDelete_test.cpp +++ b/src/test/app/AccountDelete_test.cpp @@ -103,7 +103,7 @@ public: env(trust(becky, gw["USD"](1000))); env.close(); - // Give carol a deposit preauthorization, an offer, a ticket, + // Give carol a deposit pre-authorization, an offer, a ticket, // a signer list, and a DID. Even with all that she's still deletable. env(deposit::auth(carol, becky)); std::uint32_t const carolOfferSeq{env.seq(carol)}; @@ -176,7 +176,7 @@ public: auto const carolOldBalance{env.balance(carol)}; // Verify that Carol's account, directory, deposit - // preauthorization, offer, ticket, and signer list exist. + // pre-authorization, offer, ticket, and signer list exist. BEAST_EXPECT(env.closed()->exists(keylet::account(carol.id()))); BEAST_EXPECT(env.closed()->exists(keylet::ownerDir(carol.id()))); BEAST_EXPECT(env.closed()->exists( @@ -886,7 +886,7 @@ public: env, eaton, carol, credType)[jss::result][jss::index] .asString(); - // fred make preauthorization through authorized account + // fred make pre-authorization through authorized account env(fset(fred, asfDepositAuth)); env.close(); env(deposit::auth(fred, eaton)); diff --git a/src/test/app/AmendmentTable_test.cpp b/src/test/app/AmendmentTable_test.cpp index 9add892073..a320bccfe8 100644 --- a/src/test/app/AmendmentTable_test.cpp +++ b/src/test/app/AmendmentTable_test.cpp @@ -506,7 +506,7 @@ public: // Parameters: // table: Our table of known and vetoed amendments - // validators: The addreses of validators we trust + // validators: The addresses of validators we trust // votes: Amendments and the number of validators who vote for them // ourVotes: The amendments we vote for in our validation // enabled: In/out enabled amendments diff --git a/src/test/app/DepositAuth_test.cpp b/src/test/app/DepositAuth_test.cpp index 4235c8c9ca..775d7b1e81 100644 --- a/src/test/app/DepositAuth_test.cpp +++ b/src/test/app/DepositAuth_test.cpp @@ -15,7 +15,7 @@ reserve(jtx::Env& env, std::uint32_t count) return env.current()->fees().accountReserve(count); } -// Helper function that returns true if acct has the lsfDepostAuth flag set. +// Helper function that returns true if acct has the lsfDepositAuth flag set. static bool hasDepositAuth(jtx::Env const& env, jtx::Account const& acct) { @@ -512,7 +512,7 @@ struct DepositPreauth_test : public beast::unit_test::suite env.require(owners(carol, 1)); env.require(owners(becky, 0)); - // But carol can't meet the reserve for another preauthorization. + // But carol can't meet the reserve for another pre-authorization. env(deposit::auth(carol, alice), ter(tecINSUFFICIENT_RESERVE)); env.close(); env.require(owners(carol, 1)); @@ -724,7 +724,7 @@ struct DepositPreauth_test : public beast::unit_test::suite env.fund(XRP(5000), issuer, bob, alice); env.close(); - // Bob require preauthorization + // Bob require pre-authorization env(fset(bob, asfDepositAuth)); env.close(); @@ -737,7 +737,7 @@ struct DepositPreauth_test : public beast::unit_test::suite env(deposit::auth(bob, alice)); env.close(); - // And alice can't pay with any credentials, amendement is not + // And alice can't pay with any credentials, amendment is not // enabled std::string const invalidIdx = "0E0B04ED60588A758B67E21FBBE95AC5A63598BA951761DC0EC9C08D7E" @@ -765,11 +765,11 @@ struct DepositPreauth_test : public beast::unit_test::suite credentials::ledgerEntry(env, alice, issuer, credType); std::string const credIdx = jv[jss::result][jss::index].asString(); - // Bob require preauthorization + // Bob require pre-authorization env(fset(bob, asfDepositAuth)); env.close(); - // Bob will accept payements from accounts with credentials signed + // Bob will accept payments from accounts with credentials signed // by 'issuer' env(deposit::authCredentials(bob, {{issuer, credType}})); env.close(); @@ -838,12 +838,12 @@ struct DepositPreauth_test : public beast::unit_test::suite std::string const credIdx = jv[jss::result][jss::index].asString(); { - // Success as destination didn't enable preauthorization so + // Success as destination didn't enable pre-authorization so // valid credentials will not fail env(pay(alice, bob, XRP(100)), credentials::ids({credIdx})); } - // Bob require preauthorization + // Bob require pre-authorization env(fset(bob, asfDepositAuth)); env.close(); @@ -1141,7 +1141,7 @@ struct DepositPreauth_test : public beast::unit_test::suite jv = credentials::ledgerEntry(env, alice, issuer, credType2); std::string const credIdx2 = jv[jss::result][jss::index].asString(); - // Bob require preauthorization + // Bob require pre-authorization env(fset(bob, asfDepositAuth)); env.close(); // Bob setup DepositPreauth object @@ -1268,7 +1268,7 @@ struct DepositPreauth_test : public beast::unit_test::suite jv = credentials::ledgerEntry(env, zelda, issuer, credType); std::string const credIdx = jv[jss::result][jss::index].asString(); - // Bob require preauthorization + // Bob require pre-authorization env(fset(bob, asfDepositAuth)); env.close(); // Bob setup DepositPreauth object diff --git a/src/test/app/Escrow_test.cpp b/src/test/app/Escrow_test.cpp index d4a9fc5b9d..25bb03858e 100644 --- a/src/test/app/Escrow_test.cpp +++ b/src/test/app/Escrow_test.cpp @@ -1553,7 +1553,7 @@ struct Escrow_test : public beast::unit_test::suite escrow::finish_time(env.now() + 50s)); env.close(); - // Bob require preauthorization + // Bob require pre-authorization env(fset(bob, asfDepositAuth)); env.close(); @@ -1635,7 +1635,7 @@ struct Escrow_test : public beast::unit_test::suite escrow::finish_time(env.now() + 1s)); env.close(); - // Bob require preauthorization + // Bob require pre-authorization env(fset(bob, asfDepositAuth)); env.close(); env(deposit::authCredentials(bob, {{zelda, credType}})); diff --git a/src/test/app/FixNFTokenPageLinks_test.cpp b/src/test/app/FixNFTokenPageLinks_test.cpp index baa33b2dc5..ad457cc58b 100644 --- a/src/test/app/FixNFTokenPageLinks_test.cpp +++ b/src/test/app/FixNFTokenPageLinks_test.cpp @@ -135,7 +135,7 @@ class FixNFTokenPageLinks_test : public beast::unit_test::suite // Preflight { - // Fail preflight1. Can't combine AcccountTxnID and ticket. + // Fail preflight1. Can't combine AccountTxnID and ticket. Json::Value tx = ledgerStateFix::nftPageLinks(alice, alice); tx[sfAccountTxnID.jsonName] = "00000000000000000000000000000000" diff --git a/src/test/app/Invariants_test.cpp b/src/test/app/Invariants_test.cpp index 8eb3047dc8..61b4e7ba76 100644 --- a/src/test/app/Invariants_test.cpp +++ b/src/test/app/Invariants_test.cpp @@ -888,7 +888,7 @@ class Invariants_test : public beast::unit_test::suite doInvariantCheck( {{"escrow specifies invalid amount"}}, [](Account const& A1, Account const&, ApplyContext& ac) { - // mpissuance outstanding is negative + // mptissuance outstanding is negative auto const sle = ac.view().peek(keylet::account(A1.id())); if (!sle) return false; @@ -906,7 +906,7 @@ class Invariants_test : public beast::unit_test::suite doInvariantCheck( {{"escrow specifies invalid amount"}}, [](Account const& A1, Account const&, ApplyContext& ac) { - // mpissuance locked is less than locked + // mptissuance locked is less than locked auto const sle = ac.view().peek(keylet::account(A1.id())); if (!sle) return false; @@ -924,7 +924,7 @@ class Invariants_test : public beast::unit_test::suite doInvariantCheck( {{"escrow specifies invalid amount"}}, [](Account const& A1, Account const&, ApplyContext& ac) { - // mpissuance outstanding is less than locked + // mptissuance outstanding is less than locked auto const sle = ac.view().peek(keylet::account(A1.id())); if (!sle) return false; @@ -3179,7 +3179,7 @@ class Invariants_test : public beast::unit_test::suite sleShares->at(sfSequence) = sequence; // sleVault->at(sfAccount) = pseudoId; - // Setting wrong pseudo acocunt ID + // Setting wrong pseudo account ID sleVault->at(sfAccount) = A2.id(); sleVault->at(sfFlags) = 0; sleVault->at(sfSequence) = sequence; @@ -3222,7 +3222,7 @@ class Invariants_test : public beast::unit_test::suite sleShares->setFieldU64(sfOwnerNode, *sharesPage); sleShares->at(sfFlags) = 0; - // Setting wrong pseudo acocunt ID + // Setting wrong pseudo account ID sleShares->at(sfIssuer) = AccountID(uint160(42)); sleShares->at(sfOutstandingAmount) = 0; sleShares->at(sfSequence) = sequence; diff --git a/src/test/app/LoanBroker_test.cpp b/src/test/app/LoanBroker_test.cpp index 72a732d043..5915ebae91 100644 --- a/src/test/app/LoanBroker_test.cpp +++ b/src/test/app/LoanBroker_test.cpp @@ -974,7 +974,7 @@ class LoanBroker_test : public beast::unit_test::suite return coverWithdraw(alice, brokerKeylet.key, asset(10)); }); - // preclaim: tecWRONG_ASSSET + // preclaim: tecWRONG_ASSET env(coverWithdraw(alice, brokerKeylet.key, issuer["BAD"](10)), ter(tecWRONG_ASSET), THISLINE); diff --git a/src/test/app/MPToken_test.cpp b/src/test/app/MPToken_test.cpp index 1de142c2ab..62531f2b8f 100644 --- a/src/test/app/MPToken_test.cpp +++ b/src/test/app/MPToken_test.cpp @@ -115,7 +115,7 @@ class MPToken_test : public beast::unit_test::suite .metadata = "", .err = temMALFORMED}); - // MaximumAmout of 0 returns error + // MaximumAmount of 0 returns error mptAlice.create( {.maxAmt = 0, .assetScale = 1, @@ -400,7 +400,7 @@ class MPToken_test : public beast::unit_test::suite // a mptoken yet mptAlice.authorize({.holder = bob, .err = tecOBJECT_NOT_FOUND}); - // alice specifys a holder acct that doesn't exist + // alice specifies a holder acct that doesn't exist mptAlice.authorize({.holder = cindy, .err = tecNO_DST}); // bob now holds a mptoken object @@ -1805,7 +1805,7 @@ class MPToken_test : public beast::unit_test::suite // alice authorizes bob to hold funds mptAlice.authorize({.account = alice, .holder = bob}); - // Bob require preauthorization + // Bob require pre-authorization env(fset(bob, asfDepositAuth)); env.close(); @@ -1884,7 +1884,7 @@ class MPToken_test : public beast::unit_test::suite // alice authorizes bob to hold funds mptAlice.authorize({.account = alice, .holder = bob}); - // Bob require preauthorization + // Bob require pre-authorization env(fset(bob, asfDepositAuth)); env.close(); diff --git a/src/test/app/NFTokenBurn_test.cpp b/src/test/app/NFTokenBurn_test.cpp index 828539f4b1..a1c6ffb6de 100644 --- a/src/test/app/NFTokenBurn_test.cpp +++ b/src/test/app/NFTokenBurn_test.cpp @@ -165,7 +165,7 @@ class NFTokenBurn_test : public beast::unit_test::suite // prevent alice's and minter's NFTs from clustering together // in becky's directory. // - // Use a default initialized mercenne_twister because we want the + // Use a default initialized mersenne_twister because we want the // effect of random numbers, but we want the test to run the same // way each time. std::mt19937 engine; diff --git a/src/test/app/NFToken_test.cpp b/src/test/app/NFToken_test.cpp index 75bf59e70d..59995a4078 100644 --- a/src/test/app/NFToken_test.cpp +++ b/src/test/app/NFToken_test.cpp @@ -429,7 +429,7 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite if (replacement->getFieldU32(sfMintedNFTokens) != 1) return false; // Unexpected test conditions. - // Wequence number is generated by sfFirstNFTokenSequence + + // Sequence number is generated by sfFirstNFTokenSequence + // sfMintedNFTokens. We can replace the two fields with any // numbers as long as they add up to the largest valid number. // In our case, sfFirstNFTokenSequence is set to the largest @@ -1423,7 +1423,7 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite env.close(); // minter1 is no longer alice's minter, so no longer has - // permisson to burn alice's nfts. + // permission to burn alice's nfts. env(token::burn(minter1, burnableID), token::owner(buyer), ter(tecNO_PERMISSION)); @@ -2003,7 +2003,7 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite BEAST_EXPECT(ownerCount(env, becky) == 1); BEAST_EXPECT(ownerCount(env, minter) == 0); - // Just for tidyness, becky burns the token before shutting + // Just for tidiness, becky burns the token before shutting // things down. env(token::burn(becky, nftAliceID)); env.close(); @@ -5853,7 +5853,7 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite // alice tries to delete her account, but is unsuccessful. // Due to authorized minting, alice's account sequence does not // advance while minter mints NFTokens for her. - // The new account deletion retriction enabled by this amendment will enforce // alice to wait for more ledgers to close before she can // delete her account, to prevent duplicate NFTokenIDs @@ -5955,7 +5955,7 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite // alice tries to delete her account, but is unsuccessful. // Due to authorized minting, alice's account sequence does not // advance while minter mints NFTokens for her using tickets. - // The new account deletion retriction enabled by this amendment will enforce // alice to wait for more ledgers to close before she can // delete her account, to prevent duplicate NFTokenIDs @@ -6061,7 +6061,7 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite // alice tries to delete her account, but is unsuccessful. // Due to authorized minting, alice's account sequence does not // advance while minter mints NFTokens for her using tickets. - // The new account deletion retriction enabled by this amendment will enforce // alice to wait for more ledgers to close before she can delete her // account, to prevent duplicate NFTokenIDs @@ -6388,7 +6388,7 @@ class NFTokenBaseUtil_test : public beast::unit_test::suite // // The values of these fields are dependent on the NFTokenID/OfferID // changed in its corresponding transaction. We want to validate each - // transaction to make sure the synethic fields hold the right values. + // transaction to make sure the synthetic fields hold the right values. testcase("Test synthetic fields from JSON response"); diff --git a/src/test/app/PayChan_test.cpp b/src/test/app/PayChan_test.cpp index 20a433b5dd..ed8c3c507e 100644 --- a/src/test/app/PayChan_test.cpp +++ b/src/test/app/PayChan_test.cpp @@ -763,7 +763,7 @@ struct PayChan_test : public beast::unit_test::suite BEAST_EXPECT(env.balance(bob) == preBob + delta - baseFee); } { - // Explore the limits of deposit preauthorization. + // Explore the limits of deposit pre-authorization. auto const delta = XRP(600).value(); auto const sig = signClaimAuth(pk, alice.sk(), chan, delta); @@ -799,7 +799,7 @@ struct PayChan_test : public beast::unit_test::suite env.balance(bob) == preBob + delta - (3 * baseFee)); } { - // bob removes preauthorization of alice. Once again she + // bob removes pre-authorization of alice. Once again she // cannot submit a claim. auto const delta = XRP(800).value(); @@ -874,7 +874,7 @@ struct PayChan_test : public beast::unit_test::suite credentials::ledgerEntry(env, alice, carol, credType); std::string const credIdx = jv[jss::result][jss::index].asString(); - // Bob require preauthorization + // Bob require pre-authorization env(fset(bob, asfDepositAuth)); env.close(); diff --git a/src/test/app/PermissionedDEX_test.cpp b/src/test/app/PermissionedDEX_test.cpp index b5db131db6..c29b5344e2 100644 --- a/src/test/app/PermissionedDEX_test.cpp +++ b/src/test/app/PermissionedDEX_test.cpp @@ -1534,7 +1534,7 @@ public: { FeatureBitset const all{jtx::testable_amendments()}; - // Test domain offer (w/o hyrbid) + // Test domain offer (w/o hybrid) testOfferCreate(all); testPayment(all); testBookStep(all); diff --git a/src/test/app/ValidatorSite_test.cpp b/src/test/app/ValidatorSite_test.cpp index 71a5b7fa73..1118818d7a 100644 --- a/src/test/app/ValidatorSite_test.cpp +++ b/src/test/app/ValidatorSite_test.cpp @@ -422,7 +422,7 @@ public: 1, detail::default_expires, std::chrono::seconds{-90}}}); - // fetch single site with undending redirect (fails to load) + // fetch single site with unending redirect (fails to load) testFetchList( good, {{"/redirect_forever/301", @@ -443,7 +443,7 @@ public: ssl, true, true}}); - // one undending redirect, one not + // one unending redirect, one not testFetchList( good, {{"/validators", "", ssl}, @@ -452,7 +452,7 @@ public: ssl, true, true}}); - // one undending redirect, one not + // one unending redirect, one not testFetchList( good, {{"/validators2", "", ssl}, diff --git a/src/test/basics/IntrusiveShared_test.cpp b/src/test/basics/IntrusiveShared_test.cpp index d5d5d75048..b77325efa9 100644 --- a/src/test/basics/IntrusiveShared_test.cpp +++ b/src/test/basics/IntrusiveShared_test.cpp @@ -394,7 +394,7 @@ public: { // strong goes out of scope while weak is still in scope // This checks that partialDelete has run to completion - // before the desturctor is called. A sleep is inserted + // before the destructor is called. A sleep is inserted // inside the partial delete to make sure the destructor is // given an opportunity to run durring partial delete. BEAST_EXPECT(cur == partiallyDeleted); diff --git a/src/test/basics/XRPAmount_test.cpp b/src/test/basics/XRPAmount_test.cpp index da03a3533f..3f4e68de6e 100644 --- a/src/test/basics/XRPAmount_test.cpp +++ b/src/test/basics/XRPAmount_test.cpp @@ -218,7 +218,7 @@ public: // multiply and divide by values that would overflow if done // naively, and check that it gives the correct answer - big -= 0xf; // Subtract a little so it's divisable by 4 + big -= 0xf; // Subtract a little so it's divisible by 4 BEAST_EXPECT( mulRatio(big, 3, 4, false).value() == (big.value() / 4) * 3); BEAST_EXPECT( diff --git a/src/test/consensus/ByzantineFailureSim_test.cpp b/src/test/consensus/ByzantineFailureSim_test.cpp index 0a988f0473..210333e303 100644 --- a/src/test/consensus/ByzantineFailureSim_test.cpp +++ b/src/test/consensus/ByzantineFailureSim_test.cpp @@ -63,7 +63,7 @@ class ByzantineFailureSim_test : public beast::unit_test::suite { peer->submit(Tx{0}); // Peers 0,1,2,6 will close the next ledger differently by injecting - // a non-consensus approved transaciton + // a non-consensus approved transaction if (byzantineNodes.contains(peer)) { peer->txInjections.emplace( diff --git a/src/test/consensus/Consensus_test.cpp b/src/test/consensus/Consensus_test.cpp index 85dd5e3957..b7bfb302bc 100644 --- a/src/test/consensus/Consensus_test.cpp +++ b/src/test/consensus/Consensus_test.cpp @@ -63,7 +63,7 @@ public: using namespace std::chrono_literals; testcase("check consensus"); - // Use default parameterss + // Use default parameters ConsensusParms const p{}; /////////////// @@ -74,7 +74,7 @@ public: ConsensusState::No == checkConsensus(10, 2, 2, 0, 3s, 2s, false, p, true, journal_)); - // If not enough peers have propsed, ensure + // If not enough peers have proposed, ensure // more time for proposals BEAST_EXPECT( ConsensusState::No == @@ -119,7 +119,7 @@ public: ConsensusState::No == checkConsensus(10, 2, 2, 0, 3s, 2s, true, p, true, journal_)); - // If not enough peers have propsed, ensure + // If not enough peers have proposed, ensure // more time for proposals BEAST_EXPECT( ConsensusState::No == @@ -661,7 +661,7 @@ public: // Run to the ledger *prior* to decreasing the resolution sim.run(increaseLedgerTimeResolutionEvery - 2); - // In order to create the discrepency, we want a case where if + // In order to create the discrepancy, we want a case where if // X = effCloseTime(closeTime, resolution, parentCloseTime) // X != effCloseTime(X, resolution, parentCloseTime) // @@ -906,7 +906,7 @@ public: // for B. // - The network reconnects and the validations for generation 3 ledgers // are observed (D and the 8 C's) - // - In the old approach, 2 votes for D outweights 1 vote for each C' + // - In the old approach, 2 votes for D outweighs 1 vote for each C' // so the network would avalanche towards D and fully validate it // EVEN though C was fully validated by one node // - In the new approach, 2 votes for D are not enough to outweight the @@ -1029,7 +1029,7 @@ public: // The "ahead" validators run normal speed and run ahead validating the // upper chain of ledgers. // - // Due to the uncommited support definition of the preferred branch + // Due to the uncommitted support definition of the preferred branch // protocol, even if the "behind" validators are a majority, the "ahead" // validators cannot jump to the proper branch until the "behind" // validators catch up to the same sequence number. For this test to diff --git a/src/test/consensus/DistributedValidatorsSim_test.cpp b/src/test/consensus/DistributedValidatorsSim_test.cpp index 006c1aab4e..1a088576ce 100644 --- a/src/test/consensus/DistributedValidatorsSim_test.cpp +++ b/src/test/consensus/DistributedValidatorsSim_test.cpp @@ -59,7 +59,7 @@ class DistributedValidators_test : public beast::unit_test::suite // Initial round to set prior state sim.run(1); - // Run for 10 minues, submitting 100 tx/second + // 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}; @@ -163,7 +163,7 @@ class DistributedValidators_test : public beast::unit_test::suite // Initial round to set prior state sim.run(1); - // Run for 10 minues, submitting 100 tx/second + // Run for 10 minutes, submitting 100 tx/second std::chrono::nanoseconds simDuration = 10min; std::chrono::nanoseconds quiet = 10s; Rate rate{100, 1000ms}; diff --git a/src/test/consensus/ScaleFreeSim_test.cpp b/src/test/consensus/ScaleFreeSim_test.cpp index 53c5030f29..dee4c77268 100644 --- a/src/test/consensus/ScaleFreeSim_test.cpp +++ b/src/test/consensus/ScaleFreeSim_test.cpp @@ -56,7 +56,7 @@ class ScaleFreeSim_test : public beast::unit_test::suite // Initialize timers HeartbeatTimer heart(sim.scheduler, seconds(10s)); - // Run for 10 minues, submitting 100 tx/second + // 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}; diff --git a/src/test/csf/CollectorRef.h b/src/test/csf/CollectorRef.h index 3aa2c6495f..022db01008 100644 --- a/src/test/csf/CollectorRef.h +++ b/src/test/csf/CollectorRef.h @@ -8,7 +8,7 @@ namespace xrpl { namespace test { namespace csf { -/** Holds a type-erased reference to an arbitray collector. +/** Holds a type-erased reference to an arbitrary collector. A collector is any class that implements diff --git a/src/test/csf/Histogram.h b/src/test/csf/Histogram.h index e32fded2f2..61ec680939 100644 --- a/src/test/csf/Histogram.h +++ b/src/test/csf/Histogram.h @@ -26,7 +26,7 @@ template > class Histogram { // TODO: Consider logarithmic bins around expected median if this becomes - // unscaleable + // unscalable std::map counts_; std::size_t samples = 0; diff --git a/src/test/jtx/TestHelpers.h b/src/test/jtx/TestHelpers.h index f00b929967..3096a902b1 100644 --- a/src/test/jtx/TestHelpers.h +++ b/src/test/jtx/TestHelpers.h @@ -29,7 +29,7 @@ namespace xrpl { namespace test { namespace jtx { -/** Generic helper class for helper clases that set a field on a JTx. +/** Generic helper class for helper classes that set a field on a JTx. Not every helper will be able to use this because of conversions and other issues, but for classes where it's straightforward, this can simplify things. diff --git a/src/test/jtx/deposit.h b/src/test/jtx/deposit.h index 5ea1e5bf06..e378366563 100644 --- a/src/test/jtx/deposit.h +++ b/src/test/jtx/deposit.h @@ -15,7 +15,7 @@ namespace deposit { Json::Value auth(Account const& account, Account const& auth); -/** Remove preauthorization for deposit. Invoke as deposit::unauth. */ +/** Remove pre-authorization for deposit. Invoke as deposit::unauth. */ Json::Value unauth(Account const& account, Account const& unauth); diff --git a/src/test/jtx/rpc.h b/src/test/jtx/rpc.h index 5a7c205aac..ba3c71074d 100644 --- a/src/test/jtx/rpc.h +++ b/src/test/jtx/rpc.h @@ -49,7 +49,7 @@ public: // always obtained from the lookup into the ErrorInfo lookup table. // // Take advantage of that fact to populate jt.rpcException. The - // check will be aware of whether the rpcExcpetion can be safely + // check will be aware of whether the rpcException can be safely // ignored. jt.rpcCode = { *code_, diff --git a/src/test/protocol/Seed_test.cpp b/src/test/protocol/Seed_test.cpp index 65428295f8..d33f397921 100644 --- a/src/test/protocol/Seed_test.cpp +++ b/src/test/protocol/Seed_test.cpp @@ -292,7 +292,7 @@ public: testcase("Parsing"); // account IDs and node and account public and private - // keys should not be parseable as seeds. + // keys should not be parsable as seeds. auto const node1 = randomKeyPair(KeyType::secp256k1); diff --git a/src/test/rpc/AccountObjects_test.cpp b/src/test/rpc/AccountObjects_test.cpp index e438d50633..4f3e1817e1 100644 --- a/src/test/rpc/AccountObjects_test.cpp +++ b/src/test/rpc/AccountObjects_test.cpp @@ -667,7 +667,7 @@ public: env(deposit::auth(gw, alice)); env.close(); { - // Find the preauthorization. + // Find the pre-authorization. Json::Value const resp = acctObjs(gw, jss::deposit_preauth); BEAST_EXPECT(acctObjsIsSize(resp, 1)); diff --git a/src/test/rpc/ManifestRPC_test.cpp b/src/test/rpc/ManifestRPC_test.cpp index 5d4f4900eb..96326e010f 100644 --- a/src/test/rpc/ManifestRPC_test.cpp +++ b/src/test/rpc/ManifestRPC_test.cpp @@ -30,7 +30,7 @@ public: "Missing field 'public_key'."); } { - // manifest with manlformed public key + // manifest with malformed public key auto const info = env.rpc( "json", "manifest", diff --git a/src/test/rpc/Subscribe_test.cpp b/src/test/rpc/Subscribe_test.cpp index 1637554f5c..5a58c27ea8 100644 --- a/src/test/rpc/Subscribe_test.cpp +++ b/src/test/rpc/Subscribe_test.cpp @@ -1362,7 +1362,7 @@ public: // // The values of these fields are dependent on the NFTokenID/OfferID // changed in its corresponding transaction. We want to validate each - // response to make sure the synethic fields hold the right values. + // response to make sure the synthetic fields hold the right values. testcase("Test synthetic fields from Subscribe response"); diff --git a/src/tests/libxrpl/basics/base64.cpp b/src/tests/libxrpl/basics/base64.cpp index e4581126b4..f6544105d8 100644 --- a/src/tests/libxrpl/basics/base64.cpp +++ b/src/tests/libxrpl/basics/base64.cpp @@ -16,6 +16,7 @@ check(std::string const& in, std::string const& out) TEST_CASE("base64") { + // cspell: disable check("", ""); check("f", "Zg=="); check("fo", "Zm8="); @@ -23,6 +24,7 @@ TEST_CASE("base64") check("foob", "Zm9vYg=="); check("fooba", "Zm9vYmE="); check("foobar", "Zm9vYmFy"); + // cspell: enable check( "Man is distinguished, not only by his reason, but by this " diff --git a/src/xrpld/app/consensus/RCLConsensus.h b/src/xrpld/app/consensus/RCLConsensus.h index e26cde9801..2e33bbae14 100644 --- a/src/xrpld/app/consensus/RCLConsensus.h +++ b/src/xrpld/app/consensus/RCLConsensus.h @@ -59,7 +59,7 @@ class RCLConsensus // The timestamp of the last validation we used NetClock::time_point lastValidationTime_; - // These members are queried via public accesors and are atomic for + // These members are queried via public accessors and are atomic for // thread safety. std::atomic validating_{false}; std::atomic prevProposers_{0}; diff --git a/src/xrpld/app/ledger/AbstractFetchPackContainer.h b/src/xrpld/app/ledger/AbstractFetchPackContainer.h index 85efe09e29..d2cdd1e920 100644 --- a/src/xrpld/app/ledger/AbstractFetchPackContainer.h +++ b/src/xrpld/app/ledger/AbstractFetchPackContainer.h @@ -16,7 +16,7 @@ class AbstractFetchPackContainer public: virtual ~AbstractFetchPackContainer() = default; - /** Retrieves partial ledger data of the coresponding hash from peers.` + /** Retrieves partial ledger data of the corresponding hash from peers.` @param nodeHash The 256-bit hash of the data to fetch. @return `std::nullopt` if the hash isn't cached, diff --git a/src/xrpld/app/ledger/BookListeners.cpp b/src/xrpld/app/ledger/BookListeners.cpp index 69d03058a9..ebd59c3fcc 100644 --- a/src/xrpld/app/ledger/BookListeners.cpp +++ b/src/xrpld/app/ledger/BookListeners.cpp @@ -30,7 +30,7 @@ BookListeners::publish( if (p) { - // Only publish jvObj if this is the first occurence + // Only publish jvObj if this is the first occurrence if (havePublished.emplace(p->getSeq()).second) { jvObj.visit( diff --git a/src/xrpld/app/ledger/README.md b/src/xrpld/app/ledger/README.md index d2afe01e71..cb935897b8 100644 --- a/src/xrpld/app/ledger/README.md +++ b/src/xrpld/app/ledger/README.md @@ -450,7 +450,7 @@ back as the database goes. If requested, it can additionally repair the SQLite entries for transactions in each checked ledger. This was primarily intended to repair incorrect -entries created by a bug (since fixed) that could cause transasctions from a +entries created by a bug (since fixed) that could cause transactions from a ledger other than the fully-validated ledger to appear in the SQLite databases in addition to the transactions from the correct ledger. diff --git a/src/xrpld/app/main/GRPCServer.cpp b/src/xrpld/app/main/GRPCServer.cpp index e415ee14cf..6ed902ec1e 100644 --- a/src/xrpld/app/main/GRPCServer.cpp +++ b/src/xrpld/app/main/GRPCServer.cpp @@ -413,7 +413,7 @@ GRPCServerImpl::handleRpcs() // tells us whether there is any kind of event or cq_ is shutting down. // When cq_.Next(...) returns false, all work has been completed and the // loop can exit. When the server is shutdown, each CallData object that is - // listening for a request is forceably cancelled, and is returned by + // listening for a request is forcibly cancelled, and is returned by // cq_->Next() with ok set to false. Then, each CallData object processing // a request must complete (by sending data to the client), each of which // will be returned from cq_->Next() with ok set to true. After all diff --git a/src/xrpld/app/main/GRPCServer.h b/src/xrpld/app/main/GRPCServer.h index dab71303c4..c2697e26c6 100644 --- a/src/xrpld/app/main/GRPCServer.h +++ b/src/xrpld/app/main/GRPCServer.h @@ -126,7 +126,7 @@ public: getEndpoint() const; private: - // Class encompasing the state and logic needed to serve a request. + // Class encompassing the state and logic needed to serve a request. template class CallData : public Processor, diff --git a/src/xrpld/app/misc/FeeEscalation.md b/src/xrpld/app/misc/FeeEscalation.md index 468ab2b528..7843620320 100644 --- a/src/xrpld/app/misc/FeeEscalation.md +++ b/src/xrpld/app/misc/FeeEscalation.md @@ -237,7 +237,7 @@ often coincides with new ledgers with zero transactions. recover if the problem is temporary. These exact values were chosen experimentally, and can easily change in the future. - _Minimum `lastLedgerMedianFeeLevel`_. The value of 500 was chosen to - ensure that the first escalated fee was more significant and noticable + ensure that the first escalated fee was more significant and noticeable than what the default would allow. This exact value was chosen experimentally, and can easily change in the future. - _Transaction queue size limit_. The limit is computed based on the @@ -291,7 +291,7 @@ single-singed reference transaction. It is up to the user to compute the necessary fees for other types of transactions. (E.g. multiply all drop values by 5 for a multi-signed transaction with 4 signatures.) -The `fee` result is always instantanteous, and relates to the open +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. diff --git a/src/xrpld/app/misc/HashRouter.cpp b/src/xrpld/app/misc/HashRouter.cpp index 0cad01c27e..a2ba41b361 100644 --- a/src/xrpld/app/misc/HashRouter.cpp +++ b/src/xrpld/app/misc/HashRouter.cpp @@ -14,7 +14,7 @@ HashRouter::emplace(uint256 const& key) -> std::pair return std::make_pair(std::ref(iter->second), false); } - // See if any supressions need to be expired + // See if any suppressions need to be expired expire(suppressionMap_, setup_.holdTime); return std::make_pair( diff --git a/src/xrpld/app/misc/HashRouter.h b/src/xrpld/app/misc/HashRouter.h index 1b59797b28..449097a387 100644 --- a/src/xrpld/app/misc/HashRouter.h +++ b/src/xrpld/app/misc/HashRouter.h @@ -193,7 +193,7 @@ public: virtual ~HashRouter() = default; - // VFALCO TODO Replace "Supression" terminology with something more + // VFALCO TODO Replace "Suppression" terminology with something more // semantically meaningful. void addSuppression(uint256 const& key); diff --git a/src/xrpld/app/misc/Manifest.h b/src/xrpld/app/misc/Manifest.h index 24e4f5f71f..c1a77be63b 100644 --- a/src/xrpld/app/misc/Manifest.h +++ b/src/xrpld/app/misc/Manifest.h @@ -303,7 +303,7 @@ public: std::optional getDomain(PublicKey const& pk) const; - /** Returns mainfest corresponding to a given public key + /** Returns manifest corresponding to a given public key @return manifest corresponding to Master public key if present, otherwise std::nullopt diff --git a/src/xrpld/app/misc/NetworkOPs.cpp b/src/xrpld/app/misc/NetworkOPs.cpp index 084a584377..d0f93577da 100644 --- a/src/xrpld/app/misc/NetworkOPs.cpp +++ b/src/xrpld/app/misc/NetworkOPs.cpp @@ -290,7 +290,7 @@ public: * transactions and wait for this transaction to complete. * * @param transaction Transaction object. - * @param bUnliimited Whether a privileged client connection submitted it. + * @param bUnlimited Whether a privileged client connection submitted it. * @param failType fail_hard setting from transaction submission. */ void @@ -1260,7 +1260,7 @@ NetworkOPsImp::preProcessTransaction(std::shared_ptr& transaction) return false; } - // NOTE eahennis - I think this check is redundant, + // NOTE ximinez - I think this check is redundant, // but I'm not 100% sure yet. // If so, only cost is looking up HashRouter flags. auto const [validity, reason] = @@ -2194,7 +2194,7 @@ NetworkOPsImp::endConsensus(std::unique_ptr const& clog) { // check if the ledger is good enough to go to FULL // Note: Do not go to FULL if we don't have the previous ledger - // check if the ledger is bad enough to go to CONNECTE D -- TODO + // check if the ledger is bad enough to go to CONNECTED -- TODO auto current = m_ledgerMaster.getCurrentLedger(); if (app_.timeKeeper().now() < (current->header().parentCloseTime + @@ -3638,7 +3638,7 @@ NetworkOPsImp::subAccount( auto simIterator = subMap.find(naAccountID); if (simIterator == subMap.end()) { - // Not found, note that account has a new single listner. + // Not found, note that account has a new single listener. SubMapType usisElement; usisElement[isrListener->getSeq()] = isrListener; // VFALCO NOTE This is making a needless copy of naAccountID @@ -4578,7 +4578,7 @@ NetworkOPsImp::getBookPage( Rate offerRate = parityRate; if (rate != parityRate - // Have a tranfer fee. + // Have a transfer fee. && uTakerID != book.out.account // Not taking offers of own IOUs. && book.out.account != uOfferOwnerID) @@ -4728,7 +4728,7 @@ NetworkOPsImp::getBookPage( Rate offerRate = parityRate; if (rate != parityRate - // Have a tranfer fee. + // Have a transfer fee. && uTakerID != book.out.account // Not taking offers of own IOUs. && book.out.account != uOfferOwnerID) @@ -4751,7 +4751,7 @@ NetworkOPsImp::getBookPage( saTakerGetsFunded.setJson(jvOffer[jss::taker_gets_funded]); - // TOOD(tom): The result of this expression is not used - what's + // TODO(tom): The result of this expression is not used - what's // going on here? std::min( saTakerPays, diff --git a/src/xrpld/app/misc/NetworkOPs.h b/src/xrpld/app/misc/NetworkOPs.h index 800f473959..5b92e4574b 100644 --- a/src/xrpld/app/misc/NetworkOPs.h +++ b/src/xrpld/app/misc/NetworkOPs.h @@ -31,9 +31,9 @@ class CanonicalTXSet; // should use this interface. The RPC code will primarily be a light wrapper // over this code. // -// Eventually, it will check the node's operating mode (synched, unsynched, -// etectera) and defer to the correct means of processing. The current -// code assumes this node is synched (and will continue to do so until +// Eventually, it will check the node's operating mode (synced, unsynced, +// etcetera) and defer to the correct means of processing. The current +// code assumes this node is synced (and will continue to do so until // there's a functional network. // diff --git a/src/xrpld/app/misc/SHAMapStoreImp.cpp b/src/xrpld/app/misc/SHAMapStoreImp.cpp index 0e33a0bc10..98be038554 100644 --- a/src/xrpld/app/misc/SHAMapStoreImp.cpp +++ b/src/xrpld/app/misc/SHAMapStoreImp.cpp @@ -104,7 +104,7 @@ SHAMapStoreImp::SHAMapStoreImp( get_if_exists(section, "delete_batch", deleteBatch_); std::uint32_t temp; if (get_if_exists(section, "back_off_milliseconds", temp) || - // Included for backward compaibility with an undocumented setting + // Included for backward compatibility with an undocumented setting get_if_exists(section, "backOff", temp)) { backOff_ = std::chrono::milliseconds{temp}; diff --git a/src/xrpld/app/misc/Transaction.h b/src/xrpld/app/misc/Transaction.h index ac801a2cd0..9f75387744 100644 --- a/src/xrpld/app/misc/Transaction.h +++ b/src/xrpld/app/misc/Transaction.h @@ -221,7 +221,7 @@ public: } /** - * @brief setQueued Set this flag once was put into heldtxns queue + * @brief setQueued Set this flag once was put into held-txns queue */ void setQueued() diff --git a/src/xrpld/app/misc/TxQ.h b/src/xrpld/app/misc/TxQ.h index aff7fc89db..fb1b10e886 100644 --- a/src/xrpld/app/misc/TxQ.h +++ b/src/xrpld/app/misc/TxQ.h @@ -95,7 +95,7 @@ public: allowing more than `maximumTxnInLedger` "cheap" transactions into the open ledger. - @todo eahennis. This setting seems to go against our goals and + @todo ximinez. This setting seems to go against our goals and values. Can it be removed? */ std::optional maximumTxnInLedger; diff --git a/src/xrpld/app/misc/detail/LendingHelpers.cpp b/src/xrpld/app/misc/detail/LendingHelpers.cpp index 51e0988bc4..37385583e7 100644 --- a/src/xrpld/app/misc/detail/LendingHelpers.cpp +++ b/src/xrpld/app/misc/detail/LendingHelpers.cpp @@ -1652,7 +1652,7 @@ computeLoanProperties( principalOutstanding = roundToAsset( asset, principalOutstanding, loanScale, Number::to_nearest); - // E diff --git a/src/xrpld/app/tx/detail/CreateOffer.cpp b/src/xrpld/app/tx/detail/CreateOffer.cpp index f81afecd55..848599943a 100644 --- a/src/xrpld/app/tx/detail/CreateOffer.cpp +++ b/src/xrpld/app/tx/detail/CreateOffer.cpp @@ -814,11 +814,11 @@ CreateOffer::applyGuts(Sandbox& sb, Sandbox& sbCancel) Book const book{saTakerPays.issue(), saTakerGets.issue(), domainID}; // Add offer to order book, using the original rate - // before any crossing occured. + // before any crossing occurred. // // Regular offer - BookDirectory points to open directory // - // Domain offer (w/o hyrbid) - BookDirectory points to domain + // Domain offer (w/o hybrid) - BookDirectory points to domain // directory // // Hybrid domain offer - BookDirectory points to domain directory, diff --git a/src/xrpld/app/tx/detail/InvariantCheck.h b/src/xrpld/app/tx/detail/InvariantCheck.h index b7f91a1c46..ef9db373f5 100644 --- a/src/xrpld/app/tx/detail/InvariantCheck.h +++ b/src/xrpld/app/tx/detail/InvariantCheck.h @@ -613,7 +613,7 @@ public: }; /** - * @brief Invariants: Pseudo-accounts have valid and consisent properties + * @brief Invariants: Pseudo-accounts have valid and consistent properties * * Pseudo-accounts have certain properties, and some of those properties are * unique to pseudo-accounts. Check that all pseudo-accounts are following the diff --git a/src/xrpld/app/tx/detail/NFTokenUtils.cpp b/src/xrpld/app/tx/detail/NFTokenUtils.cpp index c737855840..3a43cda6f0 100644 --- a/src/xrpld/app/tx/detail/NFTokenUtils.cpp +++ b/src/xrpld/app/tx/detail/NFTokenUtils.cpp @@ -940,7 +940,7 @@ tokenOfferCreatePreclaim( { // If this is a sell offer, check that the account is allowed to // receive IOUs. If this is a buy offer, we have to check that trustline - // is authorized, even though we previosly checked it's balance via + // is authorized, even though we previously checked it's balance via // accountHolds. This is due to a possibility of existence of // unauthorized trustlines with balance auto const res = nft::checkTrustlineAuthorized( diff --git a/src/xrpld/app/tx/detail/Payment.cpp b/src/xrpld/app/tx/detail/Payment.cpp index 7a14cecc2d..59c7431f4c 100644 --- a/src/xrpld/app/tx/detail/Payment.cpp +++ b/src/xrpld/app/tx/detail/Payment.cpp @@ -316,7 +316,7 @@ Payment::preclaim(PreclaimContext const& ctx) << "Delay transaction: Destination account does not exist. " << "Insufficent payment to create account."; - // TODO: dedupe + // TODO: de-dupe // Another transaction could create the account and then this // transaction would succeed. return tecNO_DST_INSUF_XRP; @@ -558,7 +558,7 @@ Payment::doApply() // If the actual amount delivered is different from the original // amount due to partial payment or transfer fee, we need to update - // DelieveredAmount using the actual delivered amount + // DeliveredAmount using the actual delivered amount if (view().rules().enabled(fixMPTDeliveredAmount) && amountDeliver != dstAmount) ctx_.deliver(amountDeliver); diff --git a/src/xrpld/conditions/detail/PreimageSha256.h b/src/xrpld/conditions/detail/PreimageSha256.h index 642a7afdc0..f495885794 100644 --- a/src/xrpld/conditions/detail/PreimageSha256.h +++ b/src/xrpld/conditions/detail/PreimageSha256.h @@ -37,7 +37,7 @@ public: static std::unique_ptr deserialize(Slice s, std::error_code& ec) { - // Per the RFC, a preimage fulfulliment is defined as + // Per the RFC, a preimage fulfillment is defined as // follows: // // PreimageFulfillment ::= SEQUENCE { diff --git a/src/xrpld/consensus/Consensus.cpp b/src/xrpld/consensus/Consensus.cpp index b71a36d538..46038e9d53 100644 --- a/src/xrpld/consensus/Consensus.cpp +++ b/src/xrpld/consensus/Consensus.cpp @@ -122,7 +122,7 @@ checkConsensusReached( // We only get stalled when there are disputed transactions and all of them // unequivocally have 80% (minConsensusPct) agreement, either for or - // against. That is: either under 20% or over 80% consensus (repectively + // against. That is: either under 20% or over 80% consensus (respectively // "nay" or "yay"). This prevents manipulation by a minority of byzantine // peers of which transactions make the cut to get into the ledger. if (stalled) diff --git a/src/xrpld/core/Config.h b/src/xrpld/core/Config.h index e25148aaa9..29d799bd62 100644 --- a/src/xrpld/core/Config.h +++ b/src/xrpld/core/Config.h @@ -137,7 +137,7 @@ public: // Network parameters uint32_t NETWORK_ID = 0; - // DEPRECATED - Fee units for a reference transction. + // DEPRECATED - Fee units for a reference transaction. // Only provided for backwards compatibility in a couple of places static constexpr std::uint32_t FEE_UNITS_DEPRECATED = 10; diff --git a/src/xrpld/core/detail/SociDB.cpp b/src/xrpld/core/detail/SociDB.cpp index a90e59beed..c04beaff29 100644 --- a/src/xrpld/core/detail/SociDB.cpp +++ b/src/xrpld/core/detail/SociDB.cpp @@ -279,7 +279,7 @@ public: protected: std::uintptr_t const id_; // session is owned by the DatabaseCon parent that holds the checkpointer. - // It is possible (tho rare) for the DatabaseCon class to be destoryed + // It is possible (though rare) for the DatabaseCon class to be destroyed // before the checkpointer. std::weak_ptr session_; std::mutex mutex_; diff --git a/src/xrpld/overlay/detail/PeerImp.cpp b/src/xrpld/overlay/detail/PeerImp.cpp index c92a95149e..53237ed3ae 100644 --- a/src/xrpld/overlay/detail/PeerImp.cpp +++ b/src/xrpld/overlay/detail/PeerImp.cpp @@ -586,7 +586,7 @@ PeerImp::fail(std::string const& reason) if (!socket_.is_open()) return; - // Call to name() locks, log only if the message will be outputed + // Call to name() locks, log only if the message will be outputted if (journal_.active(beast::severities::kWarning)) { std::string const n = name(); diff --git a/src/xrpld/peerfinder/PeerfinderManager.h b/src/xrpld/peerfinder/PeerfinderManager.h index 7a25edcce5..fbb59eab0e 100644 --- a/src/xrpld/peerfinder/PeerfinderManager.h +++ b/src/xrpld/peerfinder/PeerfinderManager.h @@ -97,7 +97,7 @@ struct Config //------------------------------------------------------------------------------ -/** Describes a connectible peer address along with some metadata. */ +/** Describes a connectable peer address along with some metadata. */ struct Endpoint { Endpoint() = default; diff --git a/src/xrpld/peerfinder/README.md b/src/xrpld/peerfinder/README.md index a3f89fd446..806984035b 100644 --- a/src/xrpld/peerfinder/README.md +++ b/src/xrpld/peerfinder/README.md @@ -106,7 +106,7 @@ tuned heuristics. The fields are as follows: precision as an implementation defined percentage of `maxPeers` subject to an implementation defined floor. An instance of the PeerFinder rounds the fractional part up or down using a uniform random number generated at - program startup. This allows the outdegree of the overlay network to be + program startup. This allows the out-degree of the overlay network to be controlled with fractional precision, ensuring that all inbound network connection slots are not consumed (which would make it difficult for new participants to enter the network). @@ -138,7 +138,7 @@ connection test on that neighbor by initiating an outgoing connection to the remote IP address as seen on the connection combined with the port advertised in the Endpoint message. If the test fails, then the peer considers its neighbor firewalled (intentionally or due to misconfiguration) and not forward neighbor -endpoint in Endpoint messages. This prevents poor quality unconnectible +endpoint in Endpoint messages. This prevents poor quality un-connectable addresses from landing in the caches. If the incoming connection test passes, then the peer fills in the Endpoint message with the remote address as seen on the connection before storing it in its cache and forwarding it to other peers. @@ -150,7 +150,7 @@ it no longer has available inbound slots, its address will shortly after stop being handed out by other peers. Livecache entries are very likely to result in both a successful connection establishment and the acquisition of an active outbound slot. Compare this with Bootcache addresses, which are very likely to -be connectible but unlikely to have an open slot. +be connectable but unlikely to have an open slot. Because entries in the Livecache are ephemeral, they are not persisted across launches in the database. The Livecache is continually updated and expired as @@ -186,7 +186,7 @@ since they will have moved towards the core of the overlay over their high uptime. When a connected server is full it will return a handful of new addresses from its Livecache and gracefully close the connection. Addresses from the Livecache are highly likely to have inbound connection slots and be -connectible. +connectable. For security, all information that contributes to the ranking of Bootcache entries is observed locally. PeerFinder never trusts external sources of information. @@ -257,7 +257,7 @@ Slot properties may be combined and are not mutually exclusive. - **Superpeer** (forthcoming) A superpeer slot is a connection to a peer which can accept incoming - connections, meets certain resource availaibility requirements (such as + connections, meets certain resource availability requirements (such as bandwidth, CPU, and storage capacity), and operates full duplex in the overlay. Connections which are not superpeers are by definition leaves. A leaf slot is a connection to a peer which does not route overlay messages to @@ -321,7 +321,7 @@ stage remains active while: PeerFinder makes its best effort to exhaust addresses in the Livecache before moving on to the Bootcache, because Livecache addresses are highly likely -to be connectible (since they are known to have been online within the last +to be connectable (since they are known to have been online within the last minute), and highly likely to have an open slot for an incoming connection (because peers only advertise themselves in the Livecache when they have open slots). @@ -334,7 +334,7 @@ desired. The stage remains active while: - There are addresses in the cache that have not been tried recently. -Entries in the Bootcache are ranked, with highly connectible addresses preferred +Entries in the Bootcache are ranked, with highly connectable addresses preferred over others. Connection attempts to Bootcache addresses are very likely to succeed but unlikely to produce an active connection since the peers likely do not have open slots. Before the remote peer closes the connection it will send diff --git a/src/xrpld/peerfinder/detail/Checker.h b/src/xrpld/peerfinder/detail/Checker.h index 277ce8c74d..700768788a 100644 --- a/src/xrpld/peerfinder/detail/Checker.h +++ b/src/xrpld/peerfinder/detail/Checker.h @@ -14,7 +14,7 @@ namespace xrpl { namespace PeerFinder { -/** Tests remote listening sockets to make sure they are connectible. */ +/** Tests remote listening sockets to make sure they are connectable. */ template class Checker { diff --git a/src/xrpld/peerfinder/detail/Fixed.h b/src/xrpld/peerfinder/detail/Fixed.h index 4c89bc598e..4d4a407873 100644 --- a/src/xrpld/peerfinder/detail/Fixed.h +++ b/src/xrpld/peerfinder/detail/Fixed.h @@ -16,7 +16,7 @@ public: Fixed(Fixed const&) = default; - /** Returns the time after which we shoud allow a connection attempt. */ + /** Returns the time after which we should allow a connection attempt. */ clock_type::time_point const& when() const { diff --git a/src/xrpld/rpc/GRPCHandlers.h b/src/xrpld/rpc/GRPCHandlers.h index f3e8d74f9d..b98b3021a3 100644 --- a/src/xrpld/rpc/GRPCHandlers.h +++ b/src/xrpld/rpc/GRPCHandlers.h @@ -14,7 +14,7 @@ namespace xrpl { * nested inside RPC::GRPCContext, where T is the request type * The return value is the response type, as well as a status * If the status is not Status::OK (meaning an error occurred), then only - * the status will be sent to the client, and the response will be ommitted + * the status will be sent to the client, and the response will be omitted */ std::pair diff --git a/src/xrpld/rpc/detail/Handler.cpp b/src/xrpld/rpc/detail/Handler.cpp index 2000af5e81..1a60c137f7 100644 --- a/src/xrpld/rpc/detail/Handler.cpp +++ b/src/xrpld/rpc/detail/Handler.cpp @@ -173,7 +173,7 @@ Handler const handlerArray[]{ {"validator_info", byRef(&doValidatorInfo), Role::ADMIN, NO_CONDITION}, {"vault_info", byRef(&doVaultInfo), Role::USER, NO_CONDITION}, {"wallet_propose", byRef(&doWalletPropose), Role::ADMIN, NO_CONDITION}, - // Evented methods + // Event methods {"subscribe", byRef(&doSubscribe), Role::USER, NO_CONDITION}, {"unsubscribe", byRef(&doUnsubscribe), Role::USER, NO_CONDITION}, }; diff --git a/src/xrpld/rpc/detail/InfoSub.cpp b/src/xrpld/rpc/detail/InfoSub.cpp index a0869b9d96..c8917b3327 100644 --- a/src/xrpld/rpc/detail/InfoSub.cpp +++ b/src/xrpld/rpc/detail/InfoSub.cpp @@ -8,9 +8,9 @@ namespace xrpl { // should use this interface. The RPC code will primarily be a light wrapper // over this code. -// Eventually, it will check the node's operating mode (synched, unsynched, -// etectera) and defer to the correct means of processing. The current -// code assumes this node is synched (and will continue to do so until +// Eventually, it will check the node's operating mode (synced, unsynced, +// etcetera) and defer to the correct means of processing. The current +// code assumes this node is synced (and will continue to do so until // there's a functional network. InfoSub::InfoSub(Source& source) : m_source(source), mSeq(assign_id()) diff --git a/src/xrpld/rpc/detail/RPCCall.cpp b/src/xrpld/rpc/detail/RPCCall.cpp index fa1a089efb..5eb3943c28 100644 --- a/src/xrpld/rpc/detail/RPCCall.cpp +++ b/src/xrpld/rpc/detail/RPCCall.cpp @@ -1295,7 +1295,7 @@ public: {"wallet_propose", &RPCParser::parseWalletPropose, 0, 1}, {"internal", &RPCParser::parseInternal, 1, -1}, - // Evented methods + // Event methods {"path_find", &RPCParser::parseEvented, -1, -1}, {"subscribe", &RPCParser::parseEvented, -1, -1}, {"unsubscribe", &RPCParser::parseEvented, -1, -1}, diff --git a/src/xrpld/rpc/detail/RPCSub.cpp b/src/xrpld/rpc/detail/RPCSub.cpp index 5d008e4ee6..616911fdfa 100644 --- a/src/xrpld/rpc/detail/RPCSub.cpp +++ b/src/xrpld/rpc/detail/RPCSub.cpp @@ -171,7 +171,7 @@ private: int mSeq; // Next id to allocate. - bool mSending; // Sending threead is active. + bool mSending; // Sending thread is active. std::deque> mDeque; diff --git a/src/xrpld/rpc/detail/TransactionSign.cpp b/src/xrpld/rpc/detail/TransactionSign.cpp index a4ac32ee17..485765c133 100644 --- a/src/xrpld/rpc/detail/TransactionSign.cpp +++ b/src/xrpld/rpc/detail/TransactionSign.cpp @@ -1043,7 +1043,7 @@ transactionSubmit( // Finally, submit the transaction. try { - // FIXME: For performance, should use asynch interface + // FIXME: For performance, should use async interface processTransaction(txn.second, isUnlimited(role), true, failType); } catch (std::exception&) @@ -1439,7 +1439,7 @@ transactionSubmitMultiSigned( // Finally, submit the transaction. try { - // FIXME: For performance, should use asynch interface + // FIXME: For performance, should use async interface processTransaction(txn.second, isUnlimited(role), true, failType); } catch (std::exception&) diff --git a/src/xrpld/rpc/handlers/GatewayBalances.cpp b/src/xrpld/rpc/handlers/GatewayBalances.cpp index 55959d8641..57c73a53e0 100644 --- a/src/xrpld/rpc/handlers/GatewayBalances.cpp +++ b/src/xrpld/rpc/handlers/GatewayBalances.cpp @@ -29,7 +29,7 @@ namespace xrpl { // 3) Object of "assets" indicating accounts that owe the gateway. // (Gateways typically do not hold positive balances. This is unusual.) -// gateway_balances [] [ [] [ [ // limit: integer // optional, number of problems // role: gateway|user // account role to assume -// transactions: true // optional, reccommend transactions +// transactions: true // optional, recommend transactions // } Json::Value doNoRippleCheck(RPC::JsonContext& context) From 2c37ef7762f4b1948f8894bb3ce1541d33f8e05a Mon Sep 17 00:00:00 2001 From: Mayukha Vadari Date: Wed, 7 Jan 2026 14:26:14 -0500 Subject: [PATCH 07/30] refactor: Fix spelling issues in private/local variables and functions (#6182) This change fixes several typos in private/local variables and private functions. There is no functionality change. --- cmake/XrplCompiler.cmake | 2 +- include/xrpl/basics/IntrusivePointer.h | 2 +- include/xrpl/basics/Slice.h | 4 ++-- include/xrpl/basics/StringUtilities.h | 8 ++++---- .../detail/aged_unordered_container.h | 7 ++++--- include/xrpl/beast/hash/hash_append.h | 2 +- include/xrpl/beast/utility/PropertyStream.h | 2 +- include/xrpl/json/json_value.h | 2 +- include/xrpl/json/json_writer.h | 4 ++-- include/xrpl/nodestore/Manager.h | 2 +- include/xrpl/protocol/IOUAmount.h | 2 +- src/libxrpl/core/detail/LoadMonitor.cpp | 2 +- src/libxrpl/json/json_valueiterator.cpp | 18 +++++++++--------- src/libxrpl/json/json_writer.cpp | 8 ++++---- src/libxrpl/ledger/CredentialHelpers.cpp | 2 +- src/libxrpl/net/RegisterSSLCerts.cpp | 4 ++-- src/libxrpl/nodestore/Database.cpp | 2 +- src/libxrpl/protocol/BuildInfo.cpp | 2 +- src/libxrpl/protocol/Indexes.cpp | 4 ++-- src/xrpld/app/paths/Pathfinder.cpp | 2 +- 20 files changed, 41 insertions(+), 40 deletions(-) diff --git a/cmake/XrplCompiler.cmake b/cmake/XrplCompiler.cmake index 110478fadf..622b2d2f74 100644 --- a/cmake/XrplCompiler.cmake +++ b/cmake/XrplCompiler.cmake @@ -149,7 +149,7 @@ elseif (use_gold AND is_gcc) ERROR_QUIET OUTPUT_VARIABLE LD_VERSION) #[=========================================================[ NOTE: THE gold linker inserts -rpath as DT_RUNPATH by - default intead of DT_RPATH, so you might have slightly + default instead of DT_RPATH, so you might have slightly unexpected runtime ld behavior if you were expecting DT_RPATH. Specify --disable-new-dtags to gold if you do not want the default DT_RUNPATH behavior. This rpath diff --git a/include/xrpl/basics/IntrusivePointer.h b/include/xrpl/basics/IntrusivePointer.h index 24521b95a7..101a4b6f16 100644 --- a/include/xrpl/basics/IntrusivePointer.h +++ b/include/xrpl/basics/IntrusivePointer.h @@ -58,7 +58,7 @@ concept CAdoptTag = std::is_same_v || When the strong pointer count goes to zero, the "partialDestructor" is called. This can be used to destroy as much of the object as possible while still retaining the reference counts. For example, for SHAMapInnerNodes the - children may be reset in that function. Note that std::shared_poiner WILL + 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 typically allocate shared pointers with the `make_shared` function. When diff --git a/include/xrpl/basics/Slice.h b/include/xrpl/basics/Slice.h index aa98b0a358..fba4b7733c 100644 --- a/include/xrpl/basics/Slice.h +++ b/include/xrpl/basics/Slice.h @@ -152,8 +152,8 @@ public: /** Return a "sub slice" of given length starting at the given position - Note that the subslice encompasses the range [pos, pos + rcount), - where rcount is the smaller of count and size() - pos. + Note that the subslice encompasses the range [pos, pos + rCount), + where rCount is the smaller of count and size() - pos. @param pos position of the first character @count requested length diff --git a/include/xrpl/basics/StringUtilities.h b/include/xrpl/basics/StringUtilities.h index ebe07e43bc..6314a56103 100644 --- a/include/xrpl/basics/StringUtilities.h +++ b/include/xrpl/basics/StringUtilities.h @@ -31,7 +31,7 @@ template std::optional strUnHex(std::size_t strSize, Iterator begin, Iterator end) { - static constexpr std::array const unxtab = []() { + static constexpr std::array const digitLookupTable = []() { std::array t{}; for (auto& x : t) @@ -57,7 +57,7 @@ strUnHex(std::size_t strSize, Iterator begin, Iterator end) if (strSize & 1) { - int c = unxtab[*iter++]; + int c = digitLookupTable[*iter++]; if (c < 0) return {}; @@ -67,12 +67,12 @@ strUnHex(std::size_t strSize, Iterator begin, Iterator end) while (iter != end) { - int cHigh = unxtab[*iter++]; + int cHigh = digitLookupTable[*iter++]; if (cHigh < 0) return {}; - int cLow = unxtab[*iter++]; + int cLow = digitLookupTable[*iter++]; if (cLow < 0) return {}; diff --git a/include/xrpl/beast/container/detail/aged_unordered_container.h b/include/xrpl/beast/container/detail/aged_unordered_container.h index 6807dad9f7..e2ee3b24e0 100644 --- a/include/xrpl/beast/container/detail/aged_unordered_container.h +++ b/include/xrpl/beast/container/detail/aged_unordered_container.h @@ -3189,11 +3189,12 @@ operator==(aged_unordered_container< { if (size() != other.size()) return false; - for (auto iter(cbegin()), last(cend()), olast(other.cend()); iter != last; + for (auto iter(cbegin()), last(cend()), otherLast(other.cend()); + iter != last; ++iter) { - auto oiter(other.find(extract(*iter))); - if (oiter == olast) + auto otherIter(other.find(extract(*iter))); + if (otherIter == otherLast) return false; } return true; diff --git a/include/xrpl/beast/hash/hash_append.h b/include/xrpl/beast/hash/hash_append.h index fdfedeb935..28444a1280 100644 --- a/include/xrpl/beast/hash/hash_append.h +++ b/include/xrpl/beast/hash/hash_append.h @@ -203,7 +203,7 @@ struct is_contiguously_hashable Throws: Never Effect: - Returns the reslting hash of all the input data. + Returns the resulting hash of all the input data. */ /** @{ */ diff --git a/include/xrpl/beast/utility/PropertyStream.h b/include/xrpl/beast/utility/PropertyStream.h index a5880fb8c6..de1fc567f3 100644 --- a/include/xrpl/beast/utility/PropertyStream.h +++ b/include/xrpl/beast/utility/PropertyStream.h @@ -376,7 +376,7 @@ public: print statement examples "parent.child" prints child and all of its children "parent.child." start at the parent and print down to child - "parent.grandchild" prints nothing- grandchild not direct discendent + "parent.grandchild" prints nothing- grandchild not direct descendent "parent.grandchild." starts at the parent and prints down to grandchild "parent.grandchild.*" starts at parent, print through grandchild children diff --git a/include/xrpl/json/json_value.h b/include/xrpl/json/json_value.h index 3daf441592..979e7fd788 100644 --- a/include/xrpl/json/json_value.h +++ b/include/xrpl/json/json_value.h @@ -44,7 +44,7 @@ enum ValueType { class StaticString { public: - constexpr explicit StaticString(char const* czstring) : str_(czstring) + constexpr explicit StaticString(char const* czString) : str_(czString) { } diff --git a/include/xrpl/json/json_writer.h b/include/xrpl/json/json_writer.h index 9512b17176..08e7b619b2 100644 --- a/include/xrpl/json/json_writer.h +++ b/include/xrpl/json/json_writer.h @@ -90,7 +90,7 @@ private: void writeArrayValue(Value const& value); bool - isMultineArray(Value const& value); + isMultilineArray(Value const& value); void pushValue(std::string const& value); void @@ -157,7 +157,7 @@ private: void writeArrayValue(Value const& value); bool - isMultineArray(Value const& value); + isMultilineArray(Value const& value); void pushValue(std::string const& value); void diff --git a/include/xrpl/nodestore/Manager.h b/include/xrpl/nodestore/Manager.h index fe4b1c614e..72edf2b47e 100644 --- a/include/xrpl/nodestore/Manager.h +++ b/include/xrpl/nodestore/Manager.h @@ -55,7 +55,7 @@ public: HyperLevelDB, LevelDBFactory, SQLite, MDB If the fastBackendParameter is omitted or empty, no ephemeral database - is used. If the scheduler parameter is omited or unspecified, a + is used. If the scheduler parameter is omitted or unspecified, a synchronous scheduler is used which performs all tasks immediately on the caller's thread. diff --git a/include/xrpl/protocol/IOUAmount.h b/include/xrpl/protocol/IOUAmount.h index 60a61a5825..c04cb5cf70 100644 --- a/include/xrpl/protocol/IOUAmount.h +++ b/include/xrpl/protocol/IOUAmount.h @@ -20,7 +20,7 @@ namespace xrpl { Arithmetic operations can throw std::overflow_error during normalization if the amount exceeds the largest representable amount, but underflows - will silently trunctate to zero. + will silently truncate to zero. */ class IOUAmount : private boost::totally_ordered, private boost::additive diff --git a/src/libxrpl/core/detail/LoadMonitor.cpp b/src/libxrpl/core/detail/LoadMonitor.cpp index fe2bb9e359..933fea5877 100644 --- a/src/libxrpl/core/detail/LoadMonitor.cpp +++ b/src/libxrpl/core/detail/LoadMonitor.cpp @@ -66,7 +66,7 @@ LoadMonitor::update() "Imagine if you add 10 to something every second. And you also reduce it by 1/4 every second. It will "idle" at 40, - correponding to 10 counts per second." + corresponding to 10 counts per second." */ do { diff --git a/src/libxrpl/json/json_valueiterator.cpp b/src/libxrpl/json/json_valueiterator.cpp index 7d20265af8..e9c22ac5f7 100644 --- a/src/libxrpl/json/json_valueiterator.cpp +++ b/src/libxrpl/json/json_valueiterator.cpp @@ -89,26 +89,26 @@ ValueIteratorBase::copy(SelfType const& other) Value ValueIteratorBase::key() const { - Value::CZString const czstring = (*current_).first; + Value::CZString const czString = (*current_).first; - if (czstring.c_str()) + if (czString.c_str()) { - if (czstring.isStaticString()) - return Value(StaticString(czstring.c_str())); + if (czString.isStaticString()) + return Value(StaticString(czString.c_str())); - return Value(czstring.c_str()); + return Value(czString.c_str()); } - return Value(czstring.index()); + return Value(czString.index()); } UInt ValueIteratorBase::index() const { - Value::CZString const czstring = (*current_).first; + Value::CZString const czString = (*current_).first; - if (!czstring.c_str()) - return czstring.index(); + if (!czString.c_str()) + return czString.index(); return Value::UInt(-1); } diff --git a/src/libxrpl/json/json_writer.cpp b/src/libxrpl/json/json_writer.cpp index 8983257003..25d09c511a 100644 --- a/src/libxrpl/json/json_writer.cpp +++ b/src/libxrpl/json/json_writer.cpp @@ -347,7 +347,7 @@ StyledWriter::writeArrayValue(Value const& value) pushValue("[]"); else { - bool isArrayMultiLine = isMultineArray(value); + bool isArrayMultiLine = isMultilineArray(value); if (isArrayMultiLine) { @@ -398,7 +398,7 @@ StyledWriter::writeArrayValue(Value const& value) } bool -StyledWriter::isMultineArray(Value const& value) +StyledWriter::isMultilineArray(Value const& value) { int size = value.size(); bool isMultiLine = size * 3 >= rightMargin_; @@ -573,7 +573,7 @@ StyledStreamWriter::writeArrayValue(Value const& value) pushValue("[]"); else { - bool isArrayMultiLine = isMultineArray(value); + bool isArrayMultiLine = isMultilineArray(value); if (isArrayMultiLine) { @@ -624,7 +624,7 @@ StyledStreamWriter::writeArrayValue(Value const& value) } bool -StyledStreamWriter::isMultineArray(Value const& value) +StyledStreamWriter::isMultilineArray(Value const& value) { int size = value.size(); bool isMultiLine = size * 3 >= rightMargin_; diff --git a/src/libxrpl/ledger/CredentialHelpers.cpp b/src/libxrpl/ledger/CredentialHelpers.cpp index a0f1dce7d8..82691238f9 100644 --- a/src/libxrpl/ledger/CredentialHelpers.cpp +++ b/src/libxrpl/ledger/CredentialHelpers.cpp @@ -290,7 +290,7 @@ checkArray(STArray const& credentials, unsigned maxSize, beast::Journal j) if (!ins) { JLOG(j.trace()) << "Malformed transaction: " - "duplicates in credenentials."; + "duplicates in credentials."; return temMALFORMED; } } diff --git a/src/libxrpl/net/RegisterSSLCerts.cpp b/src/libxrpl/net/RegisterSSLCerts.cpp index f489cf29a3..a1321e4c61 100644 --- a/src/libxrpl/net/RegisterSSLCerts.cpp +++ b/src/libxrpl/net/RegisterSSLCerts.cpp @@ -49,11 +49,11 @@ registerSSLCerts( return; } - auto warn = [&](std::string const& mesg) { + auto warn = [&](std::string const& msg) { // Buffer based on asio recommended size char buf[256]; ::ERR_error_string_n(ec.value(), buf, sizeof(buf)); - JLOG(j.warn()) << mesg << " " << buf; + JLOG(j.warn()) << msg << " " << buf; ::ERR_clear_error(); }; diff --git a/src/libxrpl/nodestore/Database.cpp b/src/libxrpl/nodestore/Database.cpp index 41e8ac8632..9ea25785ff 100644 --- a/src/libxrpl/nodestore/Database.cpp +++ b/src/libxrpl/nodestore/Database.cpp @@ -158,7 +158,7 @@ Database::stop() << duration_cast( steady_clock::now() - start) .count() - << " millseconds"; + << " milliseconds"; } void diff --git a/src/libxrpl/protocol/BuildInfo.cpp b/src/libxrpl/protocol/BuildInfo.cpp index 65caa9ecd3..dc56987f3a 100644 --- a/src/libxrpl/protocol/BuildInfo.cpp +++ b/src/libxrpl/protocol/BuildInfo.cpp @@ -34,7 +34,7 @@ char const* const versionString = "3.2.0-b0" #endif #ifdef SANITIZER - BOOST_PP_STRINGIZE(SANITIZER) + BOOST_PP_STRINGIZE(SANITIZER) // cspell: disable-line #endif #endif diff --git a/src/libxrpl/protocol/Indexes.cpp b/src/libxrpl/protocol/Indexes.cpp index 77fcd44a3e..0bdfeefc4d 100644 --- a/src/libxrpl/protocol/Indexes.cpp +++ b/src/libxrpl/protocol/Indexes.cpp @@ -122,9 +122,9 @@ getBookBase(Book const& book) uint256 getQualityNext(uint256 const& uBase) { - static constexpr uint256 nextq( + static constexpr uint256 nextQuality( "0000000000000000000000000000000000000000000000010000000000000000"); - return uBase + nextq; + return uBase + nextQuality; } std::uint64_t diff --git a/src/xrpld/app/paths/Pathfinder.cpp b/src/xrpld/app/paths/Pathfinder.cpp index 546f3d25d9..c99467dade 100644 --- a/src/xrpld/app/paths/Pathfinder.cpp +++ b/src/xrpld/app/paths/Pathfinder.cpp @@ -40,7 +40,7 @@ final paths and the estimated cost are returned. The engine permits the search depth to be selected and the paths table includes the depth at which each path type is found. A search depth of zero causes no searching to be done. Extra paths can also be injected, and this -should be used to preserve previously-found paths across invokations for the +should be used to preserve previously-found paths across invocations for the same path request (particularly if the search depth may change). */ From 07ff532d306cb79e16376c830fad6c041af01132 Mon Sep 17 00:00:00 2001 From: Mayukha Vadari Date: Wed, 7 Jan 2026 16:30:35 -0500 Subject: [PATCH 08/30] refactor: Fix spelling issues in all variables/functions (#6184) This change fixes many typos in comments, variables, and public functions. There is no functionality change. --- include/xrpl/json/json_reader.h | 2 +- include/xrpl/protocol/Permissions.h | 5 +- src/libxrpl/beast/net/IPAddressV4.cpp | 2 +- src/libxrpl/json/json_reader.cpp | 6 +- src/libxrpl/json/json_writer.cpp | 4 +- src/libxrpl/protocol/Permissions.cpp | 10 +- src/libxrpl/protocol/STXChainBridge.cpp | 4 +- src/libxrpl/protocol/TxFormats.cpp | 4 +- src/libxrpl/shamap/SHAMap.cpp | 2 +- src/test/app/AMMClawback_test.cpp | 2 +- src/test/app/AMMExtended_test.cpp | 22 +-- src/test/app/AMM_test.cpp | 20 +-- src/test/app/Check_test.cpp | 6 +- src/test/app/Delegate_test.cpp | 8 +- src/test/app/DeliverMin_test.cpp | 20 +-- src/test/app/EscrowToken_test.cpp | 4 +- src/test/app/Escrow_test.cpp | 4 +- src/test/app/Flow_test.cpp | 6 +- src/test/app/Invariants_test.cpp | 4 +- src/test/app/Loan_test.cpp | 2 +- src/test/app/MPToken_test.cpp | 10 +- src/test/app/MultiSign_test.cpp | 2 +- src/test/app/NetworkOPs_test.cpp | 6 +- src/test/app/Offer_test.cpp | 2 +- src/test/app/TheoreticalQuality_test.cpp | 2 +- src/test/app/TxQ_test.cpp | 4 +- src/test/app/Vault_test.cpp | 6 +- src/test/app/XChain_test.cpp | 28 ++-- src/test/app/tx/apply_test.cpp | 2 +- src/test/beast/xxhasher_test.cpp | 8 +- src/test/consensus/NegativeUNL_test.cpp | 6 +- src/test/core/Config_test.cpp | 4 +- src/test/csf/Digraph.h | 2 +- src/test/csf/Peer.h | 10 +- src/test/csf/PeerGroup.h | 2 +- src/test/jtx/Env_test.cpp | 15 +- src/test/jtx/Oracle.h | 4 +- src/test/jtx/amount.h | 2 +- src/test/jtx/delivermin.h | 4 +- src/test/jtx/impl/AMMTest.cpp | 6 +- src/test/jtx/impl/delivermin.cpp | 2 +- src/test/jtx/impl/memo.cpp | 39 +---- src/test/jtx/memo.h | 60 +------- src/test/protocol/TER_test.cpp | 30 ++-- src/test/rpc/AccountTx_test.cpp | 22 +-- src/test/rpc/Book_test.cpp | 2 +- src/test/rpc/DepositAuthorized_test.cpp | 2 +- src/test/rpc/JSONRPC_test.cpp | 138 +++++++++--------- src/test/rpc/RPCCall_test.cpp | 2 +- src/test/rpc/Simulate_test.cpp | 8 +- src/test/server/ServerStatus_test.cpp | 4 +- src/xrpld/app/ledger/detail/LedgerMaster.cpp | 2 +- src/xrpld/app/misc/FeeVoteImpl.cpp | 9 +- src/xrpld/app/misc/NetworkOPs.cpp | 8 +- src/xrpld/app/misc/SHAMapStoreImp.h | 2 +- src/xrpld/app/misc/TxQ.h | 14 +- src/xrpld/app/misc/ValidatorList.h | 4 +- src/xrpld/app/misc/detail/TxQ.cpp | 40 ++--- src/xrpld/app/misc/detail/ValidatorList.cpp | 6 +- src/xrpld/app/paths/AMMOffer.h | 8 +- src/xrpld/app/paths/PathRequest.h | 2 +- src/xrpld/app/paths/detail/AMMOffer.cpp | 10 +- src/xrpld/app/paths/detail/StrandFlow.h | 14 +- .../app/rdb/backend/detail/SQLiteDatabase.cpp | 17 ++- src/xrpld/app/tx/applySteps.h | 2 +- src/xrpld/app/tx/detail/DelegateSet.cpp | 2 +- src/xrpld/app/tx/detail/InvariantCheck.cpp | 4 +- src/xrpld/app/tx/detail/Offer.h | 18 ++- src/xrpld/app/tx/detail/OfferStream.cpp | 2 +- src/xrpld/app/tx/detail/Transactor.cpp | 2 +- src/xrpld/app/tx/detail/XChainBridge.cpp | 12 +- src/xrpld/app/tx/detail/applySteps.cpp | 16 +- src/xrpld/consensus/DisputedTx.h | 6 +- src/xrpld/consensus/Validations.h | 20 +-- src/xrpld/overlay/Message.h | 2 +- src/xrpld/overlay/Overlay.h | 2 +- src/xrpld/overlay/Slot.h | 6 +- src/xrpld/overlay/detail/OverlayImpl.cpp | 2 +- src/xrpld/overlay/detail/OverlayImpl.h | 2 +- src/xrpld/overlay/detail/PeerImp.h | 2 +- src/xrpld/overlay/detail/ProtocolMessage.h | 6 +- src/xrpld/overlay/detail/ProtocolVersion.h | 2 +- src/xrpld/overlay/detail/TxMetrics.cpp | 6 +- src/xrpld/overlay/detail/TxMetrics.h | 2 +- src/xrpld/peerfinder/detail/Livecache.h | 6 +- src/xrpld/peerfinder/detail/Logic.h | 6 +- src/xrpld/peerfinder/detail/iosformat.h | 8 +- src/xrpld/perflog/detail/PerfLogImp.cpp | 8 +- src/xrpld/rpc/detail/RPCCall.cpp | 8 +- src/xrpld/rpc/detail/RPCHelpers.cpp | 2 +- src/xrpld/rpc/detail/ServerHandler.cpp | 7 +- src/xrpld/rpc/detail/Tuning.h | 2 +- src/xrpld/rpc/handlers/AccountInfo.cpp | 6 +- src/xrpld/rpc/handlers/AccountTx.cpp | 2 +- src/xrpld/rpc/handlers/Subscribe.cpp | 2 +- 95 files changed, 400 insertions(+), 480 deletions(-) diff --git a/include/xrpl/json/json_reader.h b/include/xrpl/json/json_reader.h index 963dc0f26e..2a8ec5a3fe 100644 --- a/include/xrpl/json/json_reader.h +++ b/include/xrpl/json/json_reader.h @@ -66,7 +66,7 @@ public: * error occurred during parsing. */ std::string - getFormatedErrorMessages() const; + getFormattedErrorMessages() const; static constexpr unsigned nest_limit{25}; diff --git a/include/xrpl/protocol/Permissions.h b/include/xrpl/protocol/Permissions.h index 252605e641..319aeb1c28 100644 --- a/include/xrpl/protocol/Permissions.h +++ b/include/xrpl/protocol/Permissions.h @@ -38,7 +38,7 @@ private: std::unordered_map txFeatureMap_; - std::unordered_map delegatableTx_; + std::unordered_map delegableTx_; std::unordered_map granularPermissionMap_; @@ -71,8 +71,7 @@ public: getTxFeature(TxType txType) const; bool - isDelegatable(std::uint32_t const& permissionValue, Rules const& rules) - const; + isDelegable(std::uint32_t const& permissionValue, Rules const& rules) const; // for tx level permission, permission value is equal to tx type plus one uint32_t diff --git a/src/libxrpl/beast/net/IPAddressV4.cpp b/src/libxrpl/beast/net/IPAddressV4.cpp index b4d7181c45..fcb782133e 100644 --- a/src/libxrpl/beast/net/IPAddressV4.cpp +++ b/src/libxrpl/beast/net/IPAddressV4.cpp @@ -24,7 +24,7 @@ is_public(AddressV4 const& addr) char get_class(AddressV4 const& addr) { - static char const* table = "AAAABBCD"; + static char const* table = "AAAABBCD"; // cspell:disable-line return table[(addr.to_uint() & 0xE0000000) >> 29]; } diff --git a/src/libxrpl/json/json_reader.cpp b/src/libxrpl/json/json_reader.cpp index c0843ca929..ca0b5e5eee 100644 --- a/src/libxrpl/json/json_reader.cpp +++ b/src/libxrpl/json/json_reader.cpp @@ -241,7 +241,7 @@ Reader::readToken(Token& token) case 'f': token.type_ = tokenFalse; - ok = match("alse", 4); + ok = match("alse", 4); // cspell:disable-line break; case 'n': @@ -912,7 +912,7 @@ Reader::getLocationLineAndColumn(Location location) const } std::string -Reader::getFormatedErrorMessages() const +Reader::getFormattedErrorMessages() const { std::string formattedMessage; @@ -941,7 +941,7 @@ operator>>(std::istream& sin, Value& root) // XRPL_ASSERT(ok, "Json::operator>>() : parse succeeded"); if (!ok) - xrpl::Throw(reader.getFormatedErrorMessages()); + xrpl::Throw(reader.getFormattedErrorMessages()); return sin; } diff --git a/src/libxrpl/json/json_writer.cpp b/src/libxrpl/json/json_writer.cpp index 25d09c511a..2d0756852f 100644 --- a/src/libxrpl/json/json_writer.cpp +++ b/src/libxrpl/json/json_writer.cpp @@ -79,7 +79,7 @@ valueToString(double value) // of precision requested below. char buffer[32]; // Print into the buffer. We need not request the alternative representation - // that always has a decimal point because JSON doesn't distingish the + // that always has a decimal point because JSON doesn't distinguish the // concepts of reals and integers. #if defined(_MSC_VER) && \ defined(__STDC_SECURE_LIB__) // Use secure version with visual studio 2005 @@ -108,7 +108,7 @@ valueToQuotedString(char const* value) // We have to walk value and escape any special characters. // Appending to std::string is not efficient, but this should be rare. // (Note: forward slashes are *not* rare, but I am not escaping them.) - unsigned maxsize = strlen(value) * 2 + 3; // allescaped+quotes+NULL + unsigned maxsize = strlen(value) * 2 + 3; // all-escaped+quotes+NULL std::string result; result.reserve(maxsize); // to avoid lots of mallocs result += "\""; diff --git a/src/libxrpl/protocol/Permissions.cpp b/src/libxrpl/protocol/Permissions.cpp index a5d447a23c..55b273e246 100644 --- a/src/libxrpl/protocol/Permissions.cpp +++ b/src/libxrpl/protocol/Permissions.cpp @@ -20,7 +20,7 @@ Permission::Permission() #pragma pop_macro("TRANSACTION") }; - delegatableTx_ = { + delegableTx_ = { #pragma push_macro("TRANSACTION") #undef TRANSACTION @@ -142,7 +142,7 @@ Permission::getTxFeature(TxType txType) const } bool -Permission::isDelegatable( +Permission::isDelegable( std::uint32_t const& permissionValue, Rules const& rules) const { @@ -153,15 +153,15 @@ Permission::isDelegatable( return true; auto const txType = permissionToTxType(permissionValue); - auto const it = delegatableTx_.find(txType); + auto const it = delegableTx_.find(txType); - if (it == delegatableTx_.end()) + if (it == delegableTx_.end()) return false; auto const txFeaturesIt = txFeatureMap_.find(txType); XRPL_ASSERT( txFeaturesIt != txFeatureMap_.end(), - "xrpl::Permissions::isDelegatable : tx exists in txFeatureMap_"); + "xrpl::Permissions::isDelegable : tx exists in txFeatureMap_"); // Delegation is only allowed if the required amendment for the transaction // is enabled. For transactions that do not require an amendment, delegation diff --git a/src/libxrpl/protocol/STXChainBridge.cpp b/src/libxrpl/protocol/STXChainBridge.cpp index 065b87558f..413094b0cd 100644 --- a/src/libxrpl/protocol/STXChainBridge.cpp +++ b/src/libxrpl/protocol/STXChainBridge.cpp @@ -65,12 +65,12 @@ STXChainBridge::STXChainBridge(SField const& name, Json::Value const& v) } auto checkExtra = [](Json::Value const& v) { - static auto const jbridge = + static auto const bridgeJson = xrpl::STXChainBridge().getJson(xrpl::JsonOptions::none); for (auto it = v.begin(); it != v.end(); ++it) { std::string const name = it.memberName(); - if (!jbridge.isMember(name)) + if (!bridgeJson.isMember(name)) { Throw( "STXChainBridge extra field detected: " + name); diff --git a/src/libxrpl/protocol/TxFormats.cpp b/src/libxrpl/protocol/TxFormats.cpp index 12f92615cd..c725c1da69 100644 --- a/src/libxrpl/protocol/TxFormats.cpp +++ b/src/libxrpl/protocol/TxFormats.cpp @@ -36,8 +36,8 @@ TxFormats::TxFormats() #undef TRANSACTION #define UNWRAP(...) __VA_ARGS__ -#define TRANSACTION( \ - tag, value, name, delegatable, amendment, privileges, fields) \ +#define TRANSACTION( \ + tag, value, name, delegable, amendment, privileges, fields) \ add(jss::name, tag, UNWRAP fields, commonFields); #include diff --git a/src/libxrpl/shamap/SHAMap.cpp b/src/libxrpl/shamap/SHAMap.cpp index 7cfa66b1ac..2a156dc2d4 100644 --- a/src/libxrpl/shamap/SHAMap.cpp +++ b/src/libxrpl/shamap/SHAMap.cpp @@ -186,7 +186,7 @@ SHAMap::finishFetch( catch (...) { JLOG(journal_.warn()) - << "finishFetch exception: unknonw exception: " << hash; + << "finishFetch exception: unknown exception: " << hash; } return {}; diff --git a/src/test/app/AMMClawback_test.cpp b/src/test/app/AMMClawback_test.cpp index e9882ef174..93fda8fe34 100644 --- a/src/test/app/AMMClawback_test.cpp +++ b/src/test/app/AMMClawback_test.cpp @@ -2043,7 +2043,7 @@ class AMMClawback_test : public beast::unit_test::suite void testSingleDepositAndClawback(FeatureBitset features) { - testcase("test single depoit and clawback"); + testcase("test single deposit and clawback"); using namespace jtx; std::string logs; diff --git a/src/test/app/AMMExtended_test.cpp b/src/test/app/AMMExtended_test.cpp index c3bfb46043..317f6cb63d 100644 --- a/src/test/app/AMMExtended_test.cpp +++ b/src/test/app/AMMExtended_test.cpp @@ -2922,29 +2922,29 @@ private: fund(env, gw, {alice, bob, carol}, XRP(10'000)); env.trust(USD(100), alice, bob, carol); env(pay(alice, bob, USD(10)), - delivermin(USD(10)), + deliver_min(USD(10)), ter(temBAD_AMOUNT)); env(pay(alice, bob, USD(10)), - delivermin(USD(-5)), + deliver_min(USD(-5)), txflags(tfPartialPayment), ter(temBAD_AMOUNT)); env(pay(alice, bob, USD(10)), - delivermin(XRP(5)), + deliver_min(XRP(5)), txflags(tfPartialPayment), ter(temBAD_AMOUNT)); env(pay(alice, bob, USD(10)), - delivermin(Account(carol)["USD"](5)), + deliver_min(Account(carol)["USD"](5)), txflags(tfPartialPayment), ter(temBAD_AMOUNT)); env(pay(alice, bob, USD(10)), - delivermin(USD(15)), + deliver_min(USD(15)), txflags(tfPartialPayment), ter(temBAD_AMOUNT)); env(pay(gw, carol, USD(50))); AMM ammCarol(env, carol, XRP(10), USD(15)); env(pay(alice, bob, USD(10)), paths(XRP), - delivermin(USD(7)), + deliver_min(USD(7)), txflags(tfPartialPayment), sendmax(XRP(5)), ter(tecPATH_PARTIAL)); @@ -2962,7 +2962,7 @@ private: AMM ammBob(env, bob, XRP(1'000), USD(1'100)); env(pay(alice, alice, USD(10'000)), paths(XRP), - delivermin(USD(100)), + deliver_min(USD(100)), txflags(tfPartialPayment), sendmax(XRP(100))); env.require(balance(alice, USD(100))); @@ -2976,13 +2976,13 @@ private: AMM ammBob(env, bob, XRP(5'500), USD(1'200)); env(pay(alice, carol, USD(10'000)), paths(XRP), - delivermin(USD(200)), + deliver_min(USD(200)), txflags(tfPartialPayment), sendmax(XRP(1'000)), ter(tecPATH_PARTIAL)); env(pay(alice, carol, USD(10'000)), paths(XRP), - delivermin(USD(200)), + deliver_min(USD(200)), txflags(tfPartialPayment), sendmax(XRP(1'100))); BEAST_EXPECT( @@ -3005,7 +3005,7 @@ private: { env(pay(alice, carol, USD(10'000)), paths(XRP), - delivermin(USD(200)), + deliver_min(USD(200)), txflags(tfPartialPayment), sendmax(XRP(200))); env.require(balance(bob, USD(0))); @@ -3017,7 +3017,7 @@ private: { env(pay(alice, carol, USD(10'000)), paths(XRP), - delivermin(USD(200)), + deliver_min(USD(200)), txflags(tfPartialPayment), sendmax(XRPAmount(200'000'001))); env.require(balance(bob, USD(0))); diff --git a/src/test/app/AMM_test.cpp b/src/test/app/AMM_test.cpp index 8d64dfed2a..468d5b3ffd 100644 --- a/src/test/app/AMM_test.cpp +++ b/src/test/app/AMM_test.cpp @@ -831,10 +831,10 @@ private: // Tiny deposit testAMM( [&](AMM& ammAlice, Env& env) { - auto const enabledv1_3 = + auto const enabledV1_3 = env.current()->rules().enabled(fixAMMv1_3); auto const err = - !enabledv1_3 ? ter(temBAD_AMOUNT) : ter(tesSUCCESS); + !enabledV1_3 ? ter(temBAD_AMOUNT) : ter(tesSUCCESS); // Pre-amendment XRP deposit side is rounded to 0 // and deposit fails. // Post-amendment XRP deposit side is rounded to 1 @@ -2818,7 +2818,7 @@ private: BEAST_EXPECT(amm.expectAuctionSlot(100, 0, IOUAmount{0})); // gw burns all but one of its LPTokens through a bid transaction - // this transaction suceeds because the bid price is less than + // this transaction succeeds because the bid price is less than // the total outstanding LPToken balance env(amm.bid({ .account = gw, @@ -2872,7 +2872,7 @@ private: ter(temBAD_AMOUNT)); } - // Invlaid Min/Max combination + // Invalid Min/Max combination env(ammAlice.bid({ .account = carol, .bidMin = 200, @@ -3547,13 +3547,13 @@ private: { auto jtx = env.jt(tx, seq(1), fee(baseFee)); env.app().config().features.erase(featureAMM); - PreflightContext pfctx( + PreflightContext pfCtx( env.app(), *jtx.stx, env.current()->rules(), tapNONE, env.journal); - auto pf = Transactor::invokePreflight(pfctx); + auto pf = Transactor::invokePreflight(pfCtx); BEAST_EXPECT(pf == temDISABLED); env.app().config().features.insert(featureAMM); } @@ -3562,13 +3562,13 @@ private: auto jtx = env.jt(tx, seq(1), fee(baseFee)); jtx.jv["TxnSignature"] = "deadbeef"; jtx.stx = env.ust(jtx); - PreflightContext pfctx( + PreflightContext pfCtx( env.app(), *jtx.stx, env.current()->rules(), tapNONE, env.journal); - auto pf = Transactor::invokePreflight(pfctx); + auto pf = Transactor::invokePreflight(pfCtx); BEAST_EXPECT(pf != tesSUCCESS); } @@ -3577,13 +3577,13 @@ private: jtx.jv["Asset2"]["currency"] = "XRP"; jtx.jv["Asset2"].removeMember("issuer"); jtx.stx = env.ust(jtx); - PreflightContext pfctx( + PreflightContext pfCtx( env.app(), *jtx.stx, env.current()->rules(), tapNONE, env.journal); - auto pf = Transactor::invokePreflight(pfctx); + auto pf = Transactor::invokePreflight(pfCtx); BEAST_EXPECT(pf == temBAD_AMM_TOKENS); } } diff --git a/src/test/app/Check_test.cpp b/src/test/app/Check_test.cpp index b3d8249dd0..8eccdc4824 100644 --- a/src/test/app/Check_test.cpp +++ b/src/test/app/Check_test.cpp @@ -11,18 +11,18 @@ namespace jtx { class expiration { private: - std::uint32_t const expry_; + std::uint32_t const expiry_; public: explicit expiration(NetClock::time_point const& expiry) - : expry_{expiry.time_since_epoch().count()} + : expiry_{expiry.time_since_epoch().count()} { } void operator()(Env&, JTx& jt) const { - jt[sfExpiration.jsonName] = expry_; + jt[sfExpiration.jsonName] = expiry_; } }; diff --git a/src/test/app/Delegate_test.cpp b/src/test/app/Delegate_test.cpp index 7d58093e43..4039f60ef0 100644 --- a/src/test/app/Delegate_test.cpp +++ b/src/test/app/Delegate_test.cpp @@ -203,7 +203,7 @@ class Delegate_test : public beast::unit_test::suite ter(tecNO_TARGET)); } - // non-delegatable transaction + // non-delegable transaction { env(delegate::set(gw, alice, {"SetRegularKey"}), ter(temMALFORMED)); env(delegate::set(gw, alice, {"AccountSet"}), ter(temMALFORMED)); @@ -1693,13 +1693,13 @@ class Delegate_test : public beast::unit_test::suite } void - testTxReqireFeatures(FeatureBitset features) + testTxRequireFeatures(FeatureBitset features) { testcase("test delegate disabled tx"); using namespace jtx; // map of tx and required feature. - // non-delegatable tx are not included. + // non-delegable tx are not included. // NFTokenMint, NFTokenBurn, NFTokenCreateOffer, NFTokenCancelOffer, // NFTokenAcceptOffer are not included, they are tested separately. std::unordered_map txRequiredFeatures{ @@ -1803,7 +1803,7 @@ class Delegate_test : public beast::unit_test::suite testMultiSign(); testMultiSignQuorumNotMet(); testPermissionValue(all); - testTxReqireFeatures(all); + testTxRequireFeatures(all); } }; BEAST_DEFINE_TESTSUITE(Delegate, app, xrpl); diff --git a/src/test/app/DeliverMin_test.cpp b/src/test/app/DeliverMin_test.cpp index 4d643dfae1..c49f83b518 100644 --- a/src/test/app/DeliverMin_test.cpp +++ b/src/test/app/DeliverMin_test.cpp @@ -25,29 +25,29 @@ public: env.trust(USD(100), "alice", "bob", "carol"); env.close(); env(pay("alice", "bob", USD(10)), - delivermin(USD(10)), + deliver_min(USD(10)), ter(temBAD_AMOUNT)); env(pay("alice", "bob", USD(10)), - delivermin(USD(-5)), + deliver_min(USD(-5)), txflags(tfPartialPayment), ter(temBAD_AMOUNT)); env(pay("alice", "bob", USD(10)), - delivermin(XRP(5)), + deliver_min(XRP(5)), txflags(tfPartialPayment), ter(temBAD_AMOUNT)); env(pay("alice", "bob", USD(10)), - delivermin(Account("carol")["USD"](5)), + deliver_min(Account("carol")["USD"](5)), txflags(tfPartialPayment), ter(temBAD_AMOUNT)); env(pay("alice", "bob", USD(10)), - delivermin(USD(15)), + deliver_min(USD(15)), txflags(tfPartialPayment), ter(temBAD_AMOUNT)); env(pay(gw, "carol", USD(50))); env(offer("carol", XRP(5), USD(5))); env(pay("alice", "bob", USD(10)), paths(XRP), - delivermin(USD(7)), + deliver_min(USD(7)), txflags(tfPartialPayment), sendmax(XRP(5)), ter(tecPATH_PARTIAL)); @@ -66,7 +66,7 @@ public: env(offer("bob", XRP(100), USD(100))); env(pay("alice", "alice", USD(10000)), paths(XRP), - delivermin(USD(100)), + deliver_min(USD(100)), txflags(tfPartialPayment), sendmax(XRP(100))); env.require(balance("alice", USD(100))); @@ -84,13 +84,13 @@ public: env(offer("bob", XRP(10000), USD(100))); env(pay("alice", "carol", USD(10000)), paths(XRP), - delivermin(USD(200)), + deliver_min(USD(200)), txflags(tfPartialPayment), sendmax(XRP(1000)), ter(tecPATH_PARTIAL)); env(pay("alice", "carol", USD(10000)), paths(XRP), - delivermin(USD(200)), + deliver_min(USD(200)), txflags(tfPartialPayment), sendmax(XRP(1100))); env.require(balance("bob", USD(0))); @@ -110,7 +110,7 @@ public: env(offer("dan", XRP(100), USD(100))); env(pay("alice", "carol", USD(10000)), paths(XRP), - delivermin(USD(200)), + deliver_min(USD(200)), txflags(tfPartialPayment), sendmax(XRP(200))); env.require(balance("bob", USD(0))); diff --git a/src/test/app/EscrowToken_test.cpp b/src/test/app/EscrowToken_test.cpp index ff8b2cfb49..955ca8f449 100644 --- a/src/test/app/EscrowToken_test.cpp +++ b/src/test/app/EscrowToken_test.cpp @@ -1591,7 +1591,7 @@ struct EscrowToken_test : public beast::unit_test::suite BEAST_EXPECT(env.balance(bob, USD) == USD(10125)); } - // test cancel doesnt charge rate + // test cancel doesn't charge rate { Env env{*this, features}; auto const baseFee = env.current()->fees().base; @@ -1986,7 +1986,7 @@ struct EscrowToken_test : public beast::unit_test::suite void testIOUINSF(FeatureBitset features) { - testcase("IOU Insuficient Funds"); + testcase("IOU Insufficient Funds"); using namespace test::jtx; using namespace std::literals; diff --git a/src/test/app/Escrow_test.cpp b/src/test/app/Escrow_test.cpp index 25bb03858e..2b7297009a 100644 --- a/src/test/app/Escrow_test.cpp +++ b/src/test/app/Escrow_test.cpp @@ -1614,12 +1614,12 @@ struct Escrow_test : public beast::unit_test::suite env.close(); env.close(); - // Succeed, Bob doesn't require preauthorization + // Succeed, Bob doesn't require pre-authorization env(escrow::finish(carol, alice, seq), credentials::ids({credIdx})); env.close(); { - char const credType2[] = "fghijk"; + char const credType2[] = "random"; env(credentials::create(bob, zelda, credType2)); env.close(); diff --git a/src/test/app/Flow_test.cpp b/src/test/app/Flow_test.cpp index e16a48e02f..8c5a9615fa 100644 --- a/src/test/app/Flow_test.cpp +++ b/src/test/app/Flow_test.cpp @@ -952,9 +952,9 @@ struct Flow_test : public beast::unit_test::suite } void - testReexecuteDirectStep(FeatureBitset features) + testReExecuteDirectStep(FeatureBitset features) { - testcase("ReexecuteDirectStep"); + testcase("ReExecuteDirectStep"); using namespace jtx; Env env(*this, features); @@ -1291,7 +1291,7 @@ struct Flow_test : public beast::unit_test::suite testSelfFundedXRPEndpoint(false, features); testSelfFundedXRPEndpoint(true, features); testUnfundedOffer(features); - testReexecuteDirectStep(features); + testReExecuteDirectStep(features); testSelfPayLowQualityOffer(features); testTicketPay(features); } diff --git a/src/test/app/Invariants_test.cpp b/src/test/app/Invariants_test.cpp index 61b4e7ba76..9f70538778 100644 --- a/src/test/app/Invariants_test.cpp +++ b/src/test/app/Invariants_test.cpp @@ -1862,7 +1862,7 @@ class Invariants_test : public beast::unit_test::suite for (auto const& mod : mods) { doInvariantCheck( - {{"changed an unchangable field"}}, + {{"changed an unchangeable field"}}, [&](Account const& A1, Account const&, ApplyContext& ac) { auto sle = ac.view().peek(loanBrokerKeylet); if (!sle) @@ -1892,7 +1892,7 @@ class Invariants_test : public beast::unit_test::suite for (auto const& mod : mods) { doInvariantCheck( - {{"changed an unchangable field"}}, + {{"changed an unchangeable field"}}, [&](Account const& A1, Account const&, ApplyContext& ac) { auto sle = ac.view().peek(keylet::account(A1.id())); if (!sle) diff --git a/src/test/app/Loan_test.cpp b/src/test/app/Loan_test.cpp index 22159ba4bb..7c2e83aa19 100644 --- a/src/test/app/Loan_test.cpp +++ b/src/test/app/Loan_test.cpp @@ -2913,7 +2913,7 @@ protected: /* LoanPay fails with tecINVARIANT_FAILED error when loan_broker(also - borrower) tries to do the payment. Here's the sceanrio: Create a XRP + borrower) tries to do the payment. Here's the scenario: Create a XRP loan with loan broker as borrower, loan origination fee and loan service fee. Loan broker makes the first payment with periodic payment and loan service fee. diff --git a/src/test/app/MPToken_test.cpp b/src/test/app/MPToken_test.cpp index 62531f2b8f..ed6d861ffb 100644 --- a/src/test/app/MPToken_test.cpp +++ b/src/test/app/MPToken_test.cpp @@ -975,7 +975,7 @@ class MPToken_test : public beast::unit_test::suite sendmax(XRP(100)), ter(temMALFORMED)); env(pay(alice, carol, MPT(100)), - delivermin(XRP(100)), + deliver_min(XRP(100)), ter(temBAD_AMOUNT)); // sendMax MPT is invalid with IOU or XRP auto const USD = alice["USD"]; @@ -986,10 +986,10 @@ class MPToken_test : public beast::unit_test::suite sendmax(MPT(100)), ter(temMALFORMED)); env(pay(alice, carol, USD(100)), - delivermin(MPT(100)), + deliver_min(MPT(100)), ter(temBAD_AMOUNT)); env(pay(alice, carol, XRP(100)), - delivermin(MPT(100)), + deliver_min(MPT(100)), ter(temBAD_AMOUNT)); // sendmax and amount are different MPT issue test::jtx::MPT const MPT1( @@ -1535,13 +1535,13 @@ class MPToken_test : public beast::unit_test::suite // deliver amount < deliverMin env(pay(bob, alice, MPT(100)), sendmax(MPT(99)), - delivermin(MPT(100)), + deliver_min(MPT(100)), txflags(tfPartialPayment), ter(tecPATH_PARTIAL)); // Payment succeeds if deliver amount >= deliverMin env(pay(bob, alice, MPT(100)), sendmax(MPT(99)), - delivermin(MPT(99)), + deliver_min(MPT(99)), txflags(tfPartialPayment)); } diff --git a/src/test/app/MultiSign_test.cpp b/src/test/app/MultiSign_test.cpp index 6e30ed9c13..6950286b52 100644 --- a/src/test/app/MultiSign_test.cpp +++ b/src/test/app/MultiSign_test.cpp @@ -708,7 +708,7 @@ public: void testHeterogeneousSigners(FeatureBitset features) { - testcase("Heterogenious Signers"); + testcase("Heterogenous Signers"); using namespace jtx; Env env{*this, features}; diff --git a/src/test/app/NetworkOPs_test.cpp b/src/test/app/NetworkOPs_test.cpp index d5616009fa..582f9a8084 100644 --- a/src/test/app/NetworkOPs_test.cpp +++ b/src/test/app/NetworkOPs_test.cpp @@ -38,14 +38,14 @@ public: auto const jtx = env.jt(ticket::create(alice, 1), seq(1), fee(10)); - auto transacionId = jtx.stx->getTransactionID(); + auto transactionId = jtx.stx->getTransactionID(); env.app().getHashRouter().setFlags( - transacionId, HashRouterFlags::HELD); + transactionId, HashRouterFlags::HELD); env(jtx, json(jss::Sequence, 1), ter(terNO_ACCOUNT)); env.app().getHashRouter().setFlags( - transacionId, HashRouterFlags::BAD); + transactionId, HashRouterFlags::BAD); env.close(); } diff --git a/src/test/app/Offer_test.cpp b/src/test/app/Offer_test.cpp index 46a2cabec1..f1d68ece1e 100644 --- a/src/test/app/Offer_test.cpp +++ b/src/test/app/Offer_test.cpp @@ -2290,7 +2290,7 @@ public: // clang-format off TestData const tests[]{ - // acct fundXrp bookAmt preTrust offerAmt tec spentXrp balanceUSD offers owners + // 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. diff --git a/src/test/app/TheoreticalQuality_test.cpp b/src/test/app/TheoreticalQuality_test.cpp index 5cc03247ae..ed4a62c4b0 100644 --- a/src/test/app/TheoreticalQuality_test.cpp +++ b/src/test/app/TheoreticalQuality_test.cpp @@ -277,7 +277,7 @@ class TheoreticalQuality_test : public beast::unit_test::suite if (actualQ != theoreticalQ && !compareClose(actualQ, theoreticalQ)) { BEAST_EXPECT(actualQ == theoreticalQ); // get the failure - log << "\nAcutal != Theoretical\n"; + log << "\nActual != Theoretical\n"; log << "\nTQ: " << prettyQuality(theoreticalQ) << "\n"; log << "AQ: " << prettyQuality(actualQ) << "\n"; logStrand(log, strand); diff --git a/src/test/app/TxQ_test.cpp b/src/test/app/TxQ_test.cpp index f6ab13e04f..9489bb7f04 100644 --- a/src/test/app/TxQ_test.cpp +++ b/src/test/app/TxQ_test.cpp @@ -4311,7 +4311,7 @@ public: } void - testReexecutePreflight() + testReExecutePreflight() { // The TxQ caches preflight results. But there are situations where // that cache must become invalidated, like if amendments change. @@ -5036,7 +5036,7 @@ public: testScaling(); testInLedgerSeq(); testInLedgerTicket(); - testReexecutePreflight(); + testReExecutePreflight(); testQueueFullDropPenalty(); testCancelQueuedOffers(); testZeroReferenceFee(); diff --git a/src/test/app/Vault_test.cpp b/src/test/app/Vault_test.cpp index fbe87ccb5d..f8d76623fd 100644 --- a/src/test/app/Vault_test.cpp +++ b/src/test/app/Vault_test.cpp @@ -1517,7 +1517,7 @@ class Vault_test : public beast::unit_test::suite auto const [asset1, asset2] = std::pair(XRP(10000), USD(10000)); - auto tofund = [&](STAmount const& a) -> STAmount { + auto toFund = [&](STAmount const& a) -> STAmount { if (a.native()) { auto const defXRP = XRP(30000); @@ -1530,8 +1530,8 @@ class Vault_test : public beast::unit_test::suite return defIOU; return a + STAmount{a.issue(), 1000}; }; - auto const toFund1 = tofund(asset1); - auto const toFund2 = tofund(asset2); + auto const toFund1 = toFund(asset1); + auto const toFund2 = toFund(asset2); BEAST_EXPECT(asset1 <= toFund1 && asset2 <= toFund2); if (!asset1.native() && !asset2.native()) diff --git a/src/test/app/XChain_test.cpp b/src/test/app/XChain_test.cpp index 619054b078..3b09845723 100644 --- a/src/test/app/XChain_test.cpp +++ b/src/test/app/XChain_test.cpp @@ -247,7 +247,7 @@ struct BalanceTransfer balance from_; balance to_; - balance payor_; // pays the rewards + balance payer_; // pays the rewards std::vector reward_accounts; // receives the reward XRPAmount txFees_; @@ -255,13 +255,13 @@ struct BalanceTransfer T& env, jtx::Account const& from_acct, jtx::Account const& to_acct, - jtx::Account const& payor, + jtx::Account const& payer, jtx::Account const* payees, size_t num_payees, bool withClaim) : from_(env, from_acct) , to_(env, to_acct) - , payor_(env, payor) + , payer_(env, payer) , reward_accounts([&]() { std::vector r; r.reserve(num_payees); @@ -277,14 +277,14 @@ struct BalanceTransfer T& env, jtx::Account const& from_acct, jtx::Account const& to_acct, - jtx::Account const& payor, + jtx::Account const& payer, std::vector const& payees, bool withClaim) : BalanceTransfer( env, from_acct, to_acct, - payor, + payer, &payees[0], payees.size(), withClaim) @@ -316,14 +316,14 @@ struct BalanceTransfer auto reward_cost = multiply(reward, STAmount(reward_accounts.size()), reward.issue()); return check_most_balances(amt, reward) && - (!check_payer || payor_.diff() == -(reward_cost + txFees_)); + (!check_payer || payer_.diff() == -(reward_cost + txFees_)); } bool has_not_happened() { return check_most_balances(STAmount(0), STAmount(0)) && - payor_.diff() <= txFees_; // could have paid fee for failed claim + payer_.diff() <= txFees_; // could have paid fee for failed claim } }; @@ -592,7 +592,7 @@ struct XChain_test : public beast::unit_test::suite, auto CEUR = C["EUR"]; auto GEUR = scGw["EUR"]; - // Accounts to own single brdiges + // Accounts to own single bridges Account const a1("a1"); Account const a2("a2"); Account const a3("a3"); @@ -2566,7 +2566,7 @@ struct XChain_test : public beast::unit_test::suite, } { // --B5: missing sfAttestationSignerAccount field - // Then submit the one with the field. Should rearch quorum. + // Then submit the one with the field. Should reach quorum. auto att = claim_attestation( scAttester, jvb, @@ -3825,7 +3825,7 @@ struct XChain_test : public beast::unit_test::suite, } // this also checks that only 3 * split_reward was deducted from - // scAlice (the payor account), since we passed alt_payees to + // scAlice (the payer account), since we passed alt_payees to // BalanceTransfer BEAST_EXPECT(transfer.has_happened(amt, split_reward_quorum)); } @@ -3884,7 +3884,7 @@ struct XChain_test : public beast::unit_test::suite, } // this also checks that only 3 * split_reward was deducted from - // scAlice (the payor account), since we passed payees.size() - + // scAlice (the payer account), since we passed payees.size() - // 1 to BalanceTransfer BEAST_EXPECT(transfer.has_happened(amt, split_reward_quorum)); @@ -4548,7 +4548,7 @@ private: enum SmState { st_initial, - st_claimid_created, + st_claim_id_created, st_attesting, st_attested, st_completed, @@ -4864,10 +4864,10 @@ private: { case st_initial: xfer.claim_id = create_claim_id(); - sm_state = st_claimid_created; + sm_state = st_claim_id_created; break; - case st_claimid_created: + case st_claim_id_created: commit(); sm_state = st_attesting; break; diff --git a/src/test/app/tx/apply_test.cpp b/src/test/app/tx/apply_test.cpp index 0b198dcd22..6f9473ce69 100644 --- a/src/test/app/tx/apply_test.cpp +++ b/src/test/app/tx/apply_test.cpp @@ -15,7 +15,7 @@ public: void run() override { - testcase("Require Fully Canonicial Signature"); + testcase("Require Fully Canonical Signature"); testFullyCanonicalSigs(); } diff --git a/src/test/beast/xxhasher_test.cpp b/src/test/beast/xxhasher_test.cpp index 7456fc73c4..f740bf0821 100644 --- a/src/test/beast/xxhasher_test.cpp +++ b/src/test/beast/xxhasher_test.cpp @@ -52,7 +52,7 @@ public: } void - testBigObjectWithMultiupleSmallUpdatesWithoutSeed() + testBigObjectWithMultipleSmallUpdatesWithoutSeed() { testcase("Big object with multiple small updates without seed"); xxhasher hasher{}; @@ -69,7 +69,7 @@ public: } void - testBigObjectWithMultiupleSmallUpdatesWithSeed() + testBigObjectWithMultipleSmallUpdatesWithSeed() { testcase("Big object with multiple small updates with seed"); xxhasher hasher{static_cast(103)}; @@ -199,8 +199,8 @@ public: testWithoutSeed(); testWithSeed(); testWithTwoSeeds(); - testBigObjectWithMultiupleSmallUpdatesWithoutSeed(); - testBigObjectWithMultiupleSmallUpdatesWithSeed(); + testBigObjectWithMultipleSmallUpdatesWithoutSeed(); + testBigObjectWithMultipleSmallUpdatesWithSeed(); testBigObjectWithSmallAndBigUpdatesWithoutSeed(); testBigObjectWithSmallAndBigUpdatesWithSeed(); testBigObjectWithOneUpdateWithoutSeed(); diff --git a/src/test/consensus/NegativeUNL_test.cpp b/src/test/consensus/NegativeUNL_test.cpp index e10d6cbdd0..535aec03bf 100644 --- a/src/test/consensus/NegativeUNL_test.cpp +++ b/src/test/consensus/NegativeUNL_test.cpp @@ -25,7 +25,7 @@ namespace test { /** * Test the size of the negative UNL in a ledger, - * also test if the ledger has ToDisalbe and/or ToReEnable + * also test if the ledger has ToDisable and/or ToReEnable * * @param l the ledger * @param size the expected negative UNL size @@ -553,11 +553,11 @@ struct NetworkHistory bool createLedgerHistory() { - static uint256 fake_amemdment; // So we have different genesis ledgers + static uint256 fake_amendment; // So we have different genesis ledgers auto l = std::make_shared( create_genesis, env.app().config(), - std::vector{fake_amemdment++}, + std::vector{fake_amendment++}, env.app().getNodeFamily()); history.push_back(l); diff --git a/src/test/core/Config_test.cpp b/src/test/core/Config_test.cpp index 139e8702eb..d6ec875982 100644 --- a/src/test/core/Config_test.cpp +++ b/src/test/core/Config_test.cpp @@ -206,8 +206,8 @@ nHBu9PTL9dn2GuZtdW4U2WzBwffyX9qsQCd9CNU4Z5YG3PQfViM8 nHUPDdcdb2Y5DZAJne4c2iabFuAP3F34xZUgYQT2NH7qfkdapgnz [validator_list_sites] -recommendedxrplvalidators.com -morexrplvalidators.net +recommended-xrpl-validators.com +more-xrpl-validators.net [validator_list_keys] 03E74EE14CB525AFBB9F1B7D86CD58ECC4B91452294B42AB4E78F260BD905C091D diff --git a/src/test/csf/Digraph.h b/src/test/csf/Digraph.h index 688d0528d3..a283f2f7cc 100644 --- a/src/test/csf/Digraph.h +++ b/src/test/csf/Digraph.h @@ -199,7 +199,7 @@ public: Save a GraphViz dot description of the graph @param fileName The output file (creates) - @param vertexName A invokable T vertexName(Vertex const &) that + @param vertexName A invocable T vertexName(Vertex const &) that returns the name target use for the vertex in the file T must be ostream-able */ diff --git a/src/test/csf/Peer.h b/src/test/csf/Peer.h index b2a0c3b3ed..b86991ba5f 100644 --- a/src/test/csf/Peer.h +++ b/src/test/csf/Peer.h @@ -722,7 +722,7 @@ struct Peer template struct BroadcastMesg { - M mesg; + M msg; std::size_t seq; PeerID origin; }; @@ -748,7 +748,7 @@ struct Peer // used on the other end if (link.target->router.lastObservedSeq[bm.origin] < bm.seq) { - issue(Relay{link.target->id, bm.mesg}); + issue(Relay{link.target->id, bm.msg}); net.send( this, link.target, @@ -765,12 +765,12 @@ struct Peer void receive(BroadcastMesg const& bm, PeerID from) { - issue(Receive{from, bm.mesg}); + issue(Receive{from, bm.msg}); if (router.lastObservedSeq[bm.origin] < bm.seq) { router.lastObservedSeq[bm.origin] = bm.seq; - schedule(delays.onReceive(bm.mesg), [this, bm, from] { - if (handle(bm.mesg)) + schedule(delays.onReceive(bm.msg), [this, bm, from] { + if (handle(bm.msg)) send(bm, from); }); } diff --git a/src/test/csf/PeerGroup.h b/src/test/csf/PeerGroup.h index e674581546..8a215eaf8d 100644 --- a/src/test/csf/PeerGroup.h +++ b/src/test/csf/PeerGroup.h @@ -193,7 +193,7 @@ public: /** Establish network connections based on trust relations For each peers in this group, create outbound network connection - to the set of peers it trusts. If a coonnection already exists, it is + to the set of peers it trusts. If a connection already exists, it is not recreated. @param delay The fixed messaging delay for all established connections diff --git a/src/test/jtx/Env_test.cpp b/src/test/jtx/Env_test.cpp index ba5a2ee8c5..fbe51c2a8d 100644 --- a/src/test/jtx/Env_test.cpp +++ b/src/test/jtx/Env_test.cpp @@ -33,7 +33,7 @@ public: { using namespace jtx; { - Account a("chenna"); + Account a("chad"); Account b(a); a = b; a = std::move(b); @@ -162,8 +162,8 @@ public: // flags env.fund(n, noripple("xavier")); env.require(nflags("xavier", asfDefaultRipple)); - env.fund(n, "yana"); - env.require(flags("yana", asfDefaultRipple)); + env.fund(n, "zachary"); + env.require(flags("zachary", asfDefaultRipple)); } // trust @@ -596,12 +596,9 @@ public: using namespace jtx; Env env(*this); env.fund(XRP(10000), "alice"); - env(noop("alice"), memodata("data")); - env(noop("alice"), memoformat("format")); - env(noop("alice"), memotype("type")); - env(noop("alice"), memondata("format", "type")); - env(noop("alice"), memonformat("data", "type")); - env(noop("alice"), memontype("data", "format")); + env(noop("alice"), memo_data("data")); + env(noop("alice"), memo_format("format")); + env(noop("alice"), memo_type("type")); env(noop("alice"), memo("data", "format", "type")); env(noop("alice"), memo("data1", "format1", "type1"), diff --git a/src/test/jtx/Oracle.h b/src/test/jtx/Oracle.h index fc7dc89cd1..17d72384f6 100644 --- a/src/test/jtx/Oracle.h +++ b/src/test/jtx/Oracle.h @@ -132,7 +132,7 @@ public: std::optional const& quoteAsset, std::optional const& oracles = std::nullopt, std::optional const& trim = std::nullopt, - std::optional const& timeTreshold = std::nullopt); + std::optional const& timeThreshold = std::nullopt); std::uint32_t documentID() const @@ -150,7 +150,7 @@ public: exists(Env& env, AccountID const& account, std::uint32_t documentID); [[nodiscard]] bool - expectPrice(DataSeries const& pricess) const; + expectPrice(DataSeries const& prices) const; [[nodiscard]] bool expectLastUpdateTime(std::uint32_t lastUpdateTime) const; diff --git a/src/test/jtx/amount.h b/src/test/jtx/amount.h index faf70921be..a310fc5b44 100644 --- a/src/test/jtx/amount.h +++ b/src/test/jtx/amount.h @@ -277,7 +277,7 @@ struct XRP_t } /** Returns an amount of XRP as PrettyAmount, - which is trivially convertable to STAmount + which is trivially convertible to STAmount @param v The Number of XRP (not drops). May be fractional. */ diff --git a/src/test/jtx/delivermin.h b/src/test/jtx/delivermin.h index 70f092c9dc..033b3f20b8 100644 --- a/src/test/jtx/delivermin.h +++ b/src/test/jtx/delivermin.h @@ -10,13 +10,13 @@ namespace test { namespace jtx { /** Sets the DeliverMin on a JTx. */ -class delivermin +class deliver_min { private: STAmount amount_; public: - delivermin(STAmount const& amount) : amount_(amount) + deliver_min(STAmount const& amount) : amount_(amount) { } diff --git a/src/test/jtx/impl/AMMTest.cpp b/src/test/jtx/impl/AMMTest.cpp index 790762d3df..de7ce5504b 100644 --- a/src/test/jtx/impl/AMMTest.cpp +++ b/src/test/jtx/impl/AMMTest.cpp @@ -112,7 +112,7 @@ AMMTestBase::testAMM( auto const [asset1, asset2] = arg.pool ? *arg.pool : std::make_pair(XRP(10000), USD(10000)); - auto tofund = [&](STAmount const& a) -> STAmount { + auto toFund = [&](STAmount const& a) -> STAmount { if (a.native()) { auto const defXRP = XRP(30000); @@ -125,8 +125,8 @@ AMMTestBase::testAMM( return defIOU; return a + STAmount{a.issue(), 1000}; }; - auto const toFund1 = tofund(asset1); - auto const toFund2 = tofund(asset2); + auto const toFund1 = toFund(asset1); + auto const toFund2 = toFund(asset2); BEAST_EXPECT(asset1 <= toFund1 && asset2 <= toFund2); if (!asset1.native() && !asset2.native()) diff --git a/src/test/jtx/impl/delivermin.cpp b/src/test/jtx/impl/delivermin.cpp index 5ad98edd09..a5c1414525 100644 --- a/src/test/jtx/impl/delivermin.cpp +++ b/src/test/jtx/impl/delivermin.cpp @@ -7,7 +7,7 @@ namespace test { namespace jtx { void -delivermin::operator()(Env& env, JTx& jt) const +deliver_min::operator()(Env& env, JTx& jt) const { jt.jv[jss::DeliverMin] = amount_.getJson(JsonOptions::none); } diff --git a/src/test/jtx/impl/memo.cpp b/src/test/jtx/impl/memo.cpp index c815b916d7..e503b9a073 100644 --- a/src/test/jtx/impl/memo.cpp +++ b/src/test/jtx/impl/memo.cpp @@ -17,7 +17,7 @@ memo::operator()(Env&, JTx& jt) const } void -memodata::operator()(Env&, JTx& jt) const +memo_data::operator()(Env&, JTx& jt) const { auto& jv = jt.jv; auto& ma = jv["Memos"]; @@ -27,7 +27,7 @@ memodata::operator()(Env&, JTx& jt) const } void -memoformat::operator()(Env&, JTx& jt) const +memo_format::operator()(Env&, JTx& jt) const { auto& jv = jt.jv; auto& ma = jv["Memos"]; @@ -37,7 +37,7 @@ memoformat::operator()(Env&, JTx& jt) const } void -memotype::operator()(Env&, JTx& jt) const +memo_type::operator()(Env&, JTx& jt) const { auto& jv = jt.jv; auto& ma = jv["Memos"]; @@ -46,39 +46,6 @@ memotype::operator()(Env&, JTx& jt) const m["MemoType"] = strHex(s_); } -void -memondata::operator()(Env&, JTx& jt) const -{ - auto& jv = jt.jv; - auto& ma = jv["Memos"]; - auto& mi = ma[ma.size()]; - auto& m = mi["Memo"]; - m["MemoFormat"] = strHex(format_); - m["MemoType"] = strHex(type_); -} - -void -memonformat::operator()(Env&, JTx& jt) const -{ - auto& jv = jt.jv; - auto& ma = jv["Memos"]; - auto& mi = ma[ma.size()]; - auto& m = mi["Memo"]; - m["MemoData"] = strHex(data_); - m["MemoType"] = strHex(type_); -} - -void -memontype::operator()(Env&, JTx& jt) const -{ - auto& jv = jt.jv; - auto& ma = jv["Memos"]; - auto& mi = ma[ma.size()]; - auto& m = mi["Memo"]; - m["MemoData"] = strHex(data_); - m["MemoFormat"] = strHex(format_); -} - } // namespace jtx } // namespace test } // namespace xrpl diff --git a/src/test/jtx/memo.h b/src/test/jtx/memo.h index 377dcbec4e..2506f063fc 100644 --- a/src/test/jtx/memo.h +++ b/src/test/jtx/memo.h @@ -32,13 +32,13 @@ public: operator()(Env&, JTx& jt) const; }; -class memodata +class memo_data { private: std::string s_; public: - memodata(std::string const& s) : s_(s) + memo_data(std::string const& s) : s_(s) { } @@ -46,13 +46,13 @@ public: operator()(Env&, JTx& jt) const; }; -class memoformat +class memo_format { private: std::string s_; public: - memoformat(std::string const& s) : s_(s) + memo_format(std::string const& s) : s_(s) { } @@ -60,61 +60,13 @@ public: operator()(Env&, JTx& jt) const; }; -class memotype +class memo_type { private: std::string s_; public: - memotype(std::string const& s) : s_(s) - { - } - - void - operator()(Env&, JTx& jt) const; -}; - -class memondata -{ -private: - std::string format_; - std::string type_; - -public: - memondata(std::string const& format, std::string const& type) - : format_(format), type_(type) - { - } - - void - operator()(Env&, JTx& jt) const; -}; - -class memonformat -{ -private: - std::string data_; - std::string type_; - -public: - memonformat(std::string const& data, std::string const& type) - : data_(data), type_(type) - { - } - - void - operator()(Env&, JTx& jt) const; -}; - -class memontype -{ -private: - std::string data_; - std::string format_; - -public: - memontype(std::string const& data, std::string const& format) - : data_(data), format_(format) + memo_type(std::string const& s) : s_(s) { } diff --git a/src/test/protocol/TER_test.cpp b/src/test/protocol/TER_test.cpp index 0d9c6cb96d..56b3b08051 100644 --- a/src/test/protocol/TER_test.cpp +++ b/src/test/protocol/TER_test.cpp @@ -137,7 +137,7 @@ struct TER_test : public beast::unit_test::suite testIterate(terEnums, *this); // Lambda that verifies assignability and convertibility. - auto isConvertable = [](auto from, auto to) { + auto isConvertible = [](auto from, auto to) { using From_t = std::decay_t; using To_t = std::decay_t; static_assert( @@ -150,12 +150,12 @@ struct TER_test : public beast::unit_test::suite // Verify the right types convert to NotTEC. NotTEC const notTec; - isConvertable(telLOCAL_ERROR, notTec); - isConvertable(temMALFORMED, notTec); - isConvertable(tefFAILURE, notTec); - isConvertable(terRETRY, notTec); - isConvertable(tesSUCCESS, notTec); - isConvertable(notTec, notTec); + isConvertible(telLOCAL_ERROR, notTec); + isConvertible(temMALFORMED, notTec); + isConvertible(tefFAILURE, notTec); + isConvertible(terRETRY, notTec); + isConvertible(tesSUCCESS, notTec); + isConvertible(notTec, notTec); // Lambda that verifies types and not assignable or convertible. auto notConvertible = [](auto from, auto to) { @@ -176,14 +176,14 @@ struct TER_test : public beast::unit_test::suite notConvertible(4, notTec); // Verify the right types convert to TER. - isConvertable(telLOCAL_ERROR, ter); - isConvertable(temMALFORMED, ter); - isConvertable(tefFAILURE, ter); - isConvertable(terRETRY, ter); - isConvertable(tesSUCCESS, ter); - isConvertable(tecCLAIM, ter); - isConvertable(notTec, ter); - isConvertable(ter, ter); + isConvertible(telLOCAL_ERROR, ter); + isConvertible(temMALFORMED, ter); + isConvertible(tefFAILURE, ter); + isConvertible(terRETRY, ter); + isConvertible(tesSUCCESS, ter); + isConvertible(tecCLAIM, ter); + isConvertible(notTec, ter); + isConvertible(ter, ter); // Verify that you can't convert from int to ter. notConvertible(4, ter); diff --git a/src/test/rpc/AccountTx_test.cpp b/src/test/rpc/AccountTx_test.cpp index e387e1bc4c..36d29236d1 100644 --- a/src/test/rpc/AccountTx_test.cpp +++ b/src/test/rpc/AccountTx_test.cpp @@ -551,12 +551,12 @@ class AccountTx_test : public beast::unit_test::suite auto escrow = [](Account const& account, Account const& to, STAmount const& amount) { - Json::Value escro; - escro[jss::TransactionType] = jss::EscrowCreate; - escro[jss::Account] = account.human(); - escro[jss::Destination] = to.human(); - escro[jss::Amount] = amount.getJson(JsonOptions::none); - return escro; + Json::Value escrow; + escrow[jss::TransactionType] = jss::EscrowCreate; + escrow[jss::Account] = account.human(); + escrow[jss::Destination] = to.human(); + escrow[jss::Amount] = amount.getJson(JsonOptions::none); + return escrow; }; NetClock::time_point const nextTime{env.now() + 2s}; @@ -651,7 +651,7 @@ class AccountTx_test : public beast::unit_test::suite env.close(); } { - // Deposit preauthorization with a Ticket. + // Deposit pre-authorization with a Ticket. std::uint32_t const tktSeq{env.seq(alice) + 1}; env(ticket::create(alice, 1), sig(alie)); env.close(); @@ -788,19 +788,19 @@ class AccountTx_test : public beast::unit_test::suite BEAST_EXPECT(result[jss::result][jss::transactions].isArray()); // The first two transactions listed in sanity haven't happened yet. - constexpr unsigned int beckyDeletedOffest = 2; + constexpr unsigned int beckyDeletedOffset = 2; BEAST_EXPECT( std::size(sanity) == result[jss::result][jss::transactions].size() + - beckyDeletedOffest); + beckyDeletedOffset); Json::Value const& txs{result[jss::result][jss::transactions]}; - for (unsigned int index = beckyDeletedOffest; + for (unsigned int index = beckyDeletedOffset; index < std::size(sanity); ++index) { - checkSanity(txs[index - beckyDeletedOffest], sanity[index]); + checkSanity(txs[index - beckyDeletedOffset], sanity[index]); } } diff --git a/src/test/rpc/Book_test.cpp b/src/test/rpc/Book_test.cpp index 3c7477745b..ae2aff689d 100644 --- a/src/test/rpc/Book_test.cpp +++ b/src/test/rpc/Book_test.cpp @@ -1719,7 +1719,7 @@ public: BEAST_EXPECT(jrr[jss::offers].isArray()); BEAST_EXPECT( jrr[jss::offers].size() == - (asAdmin ? RPC::Tuning::bookOffers.rdefault : 0u)); + (asAdmin ? RPC::Tuning::bookOffers.rDefault : 0u)); } void diff --git a/src/test/rpc/DepositAuthorized_test.cpp b/src/test/rpc/DepositAuthorized_test.cpp index 968a3fd4dc..3e082a70a1 100644 --- a/src/test/rpc/DepositAuthorized_test.cpp +++ b/src/test/rpc/DepositAuthorized_test.cpp @@ -555,7 +555,7 @@ public: testcase("deposit_authorized with expired credentials"); // check expired credentials - char const credType2[] = "fghijk"; + char const credType2[] = "random"; std::uint32_t const x = env.current() ->header() .parentCloseTime.time_since_epoch() diff --git a/src/test/rpc/JSONRPC_test.cpp b/src/test/rpc/JSONRPC_test.cpp index 4d3f0a5098..6a9fae74a5 100644 --- a/src/test/rpc/JSONRPC_test.cpp +++ b/src/test/rpc/JSONRPC_test.cpp @@ -54,7 +54,7 @@ static constexpr TxnTestData txnTestArray[] = { {"Minimal payment, no Amount only DeliverMax", __LINE__, R"({ - "command": "doesnt_matter", + "command": "dummy_command", "secret": "masterpassphrase", "tx_json": { "Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", @@ -71,7 +71,7 @@ static constexpr TxnTestData txnTestArray[] = { {"Pass in Fee with minimal payment, both Amount and DeliverMax.", __LINE__, R"({ - "command": "doesnt_matter", + "command": "dummy_command", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "secret": "masterpassphrase", "tx_json": { @@ -91,7 +91,7 @@ static constexpr TxnTestData txnTestArray[] = { {"Pass in Sequence, no Amount only DeliverMax", __LINE__, R"({ - "command": "doesnt_matter", + "command": "dummy_command", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "secret": "masterpassphrase", "tx_json": { @@ -111,7 +111,7 @@ static constexpr TxnTestData txnTestArray[] = { "DeliverMax.", __LINE__, R"({ - "command": "doesnt_matter", + "command": "dummy_command", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "secret": "masterpassphrase", "tx_json": { @@ -133,7 +133,7 @@ static constexpr TxnTestData txnTestArray[] = { {"Add 'fee_mult_max' field.", __LINE__, R"({ - "command": "doesnt_matter", + "command": "dummy_command", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "secret": "masterpassphrase", "fee_mult_max": 7, @@ -153,7 +153,7 @@ static constexpr TxnTestData txnTestArray[] = { {"Add 'fee_mult_max' and 'fee_div_max' field.", __LINE__, R"({ - "command": "doesnt_matter", + "command": "dummy_command", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "secret": "masterpassphrase", "fee_mult_max": 7, @@ -174,7 +174,7 @@ static constexpr TxnTestData txnTestArray[] = { {"fee_mult_max is ignored if 'Fee' is present.", __LINE__, R"({ - "command": "doesnt_matter", + "command": "dummy_command", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "secret": "masterpassphrase", "fee_mult_max": 0, @@ -196,7 +196,7 @@ static constexpr TxnTestData txnTestArray[] = { {"fee_div_max is ignored if 'Fee' is present.", __LINE__, R"({ - "command": "doesnt_matter", + "command": "dummy_command", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "secret": "masterpassphrase", "fee_mult_max": 100, @@ -219,7 +219,7 @@ static constexpr TxnTestData txnTestArray[] = { {"Invalid 'fee_mult_max' field.", __LINE__, R"({ - "command": "doesnt_matter", + "command": "dummy_command", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "secret": "masterpassphrase", "fee_mult_max": "NotAFeeMultiplier", @@ -239,7 +239,7 @@ static constexpr TxnTestData txnTestArray[] = { {"Invalid 'fee_div_max' field.", __LINE__, R"({ - "command": "doesnt_matter", + "command": "dummy_command", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "secret": "masterpassphrase", "fee_mult_max": 5, @@ -260,7 +260,7 @@ static constexpr TxnTestData txnTestArray[] = { {"Invalid value for 'fee_mult_max' field.", __LINE__, R"({ - "command": "doesnt_matter", + "command": "dummy_command", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "secret": "masterpassphrase", "fee_mult_max": 0, @@ -280,7 +280,7 @@ static constexpr TxnTestData txnTestArray[] = { {"Invalid value for 'fee_div_max' field.", __LINE__, R"({ - "command": "doesnt_matter", + "command": "dummy_command", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "secret": "masterpassphrase", "fee_mult_max": 4, @@ -301,7 +301,7 @@ static constexpr TxnTestData txnTestArray[] = { {"Invalid zero value for 'fee_div_max' field.", __LINE__, R"({ - "command": "doesnt_matter", + "command": "dummy_command", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "secret": "masterpassphrase", "fee_mult_max": 4, @@ -322,7 +322,7 @@ static constexpr TxnTestData txnTestArray[] = { {"Missing 'Amount'.", __LINE__, R"({ - "command": "doesnt_matter", + "command": "dummy_command", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "secret": "masterpassphrase", "tx_json": { @@ -339,7 +339,7 @@ static constexpr TxnTestData txnTestArray[] = { {"Invalid 'Amount'.", __LINE__, R"({ - "command": "doesnt_matter", + "command": "dummy_command", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "secret": "masterpassphrase", "tx_json": { @@ -357,7 +357,7 @@ static constexpr TxnTestData txnTestArray[] = { {"Missing 'Destination'.", __LINE__, R"({ - "command": "doesnt_matter", + "command": "dummy_command", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "secret": "masterpassphrase", "tx_json": { @@ -374,7 +374,7 @@ static constexpr TxnTestData txnTestArray[] = { {"Invalid 'Destination'.", __LINE__, R"({ - "command": "doesnt_matter", + "command": "dummy_command", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "secret": "masterpassphrase", "tx_json": { @@ -392,7 +392,7 @@ static constexpr TxnTestData txnTestArray[] = { {"Cannot create XRP to XRP paths.", __LINE__, R"({ - "command": "doesnt_matter", + "command": "dummy_command", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "secret": "masterpassphrase", "build_path": 1, @@ -411,7 +411,7 @@ static constexpr TxnTestData txnTestArray[] = { {"Successful 'build_path'.", __LINE__, R"({ - "command": "doesnt_matter", + "command": "dummy_command", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "secret": "masterpassphrase", "build_path": 1, @@ -434,7 +434,7 @@ static constexpr TxnTestData txnTestArray[] = { {"Not valid to include both 'Paths' and 'build_path'.", __LINE__, R"({ - "command": "doesnt_matter", + "command": "dummy_command", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "secret": "masterpassphrase", "build_path": 1, @@ -458,7 +458,7 @@ static constexpr TxnTestData txnTestArray[] = { {"Successful 'SendMax'.", __LINE__, R"({ - "command": "doesnt_matter", + "command": "dummy_command", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "secret": "masterpassphrase", "build_path": 1, @@ -486,7 +486,7 @@ static constexpr TxnTestData txnTestArray[] = { {"'Amount' may not be XRP for pathfinding, but 'SendMax' may be XRP.", __LINE__, R"({ - "command": "doesnt_matter", + "command": "dummy_command", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "secret": "masterpassphrase", "build_path": 1, @@ -510,7 +510,7 @@ static constexpr TxnTestData txnTestArray[] = { {"'secret' must be present.", __LINE__, R"({ - "command": "doesnt_matter", + "command": "dummy_command", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "tx_json": { "Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", @@ -527,7 +527,7 @@ static constexpr TxnTestData txnTestArray[] = { {"'secret' must be non-empty.", __LINE__, R"({ - "command": "doesnt_matter", + "command": "dummy_command", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "secret": "", "tx_json": { @@ -545,7 +545,7 @@ static constexpr TxnTestData txnTestArray[] = { {"Use 'seed' instead of 'secret'.", __LINE__, R"({ - "command": "doesnt_matter", + "command": "dummy_command", "account": "rJrxi4Wxev4bnAGVNP9YCdKPdAoKfAmcsi", "key_type": "ed25519", "seed": "sh1yJfwoi98zCygwijUzuHmJDeVKd", @@ -564,7 +564,7 @@ static constexpr TxnTestData txnTestArray[] = { {"Malformed 'seed'.", __LINE__, R"({ - "command": "doesnt_matter", + "command": "dummy_command", "account": "rJrxi4Wxev4bnAGVNP9YCdKPdAoKfAmcsi", "key_type": "ed25519", "seed": "not a seed", @@ -583,7 +583,7 @@ static constexpr TxnTestData txnTestArray[] = { {"'tx_json' must be present.", __LINE__, R"({ - "command": "doesnt_matter", + "command": "dummy_command", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "secret": "masterpassphrase", "rx_json": { @@ -601,7 +601,7 @@ static constexpr TxnTestData txnTestArray[] = { {"'TransactionType' must be present.", __LINE__, R"({ - "command": "doesnt_matter", + "command": "dummy_command", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "secret": "masterpassphrase", "tx_json": { @@ -618,7 +618,7 @@ static constexpr TxnTestData txnTestArray[] = { {"The 'TransactionType' must be a pre-established transaction type.", __LINE__, R"({ - "command": "doesnt_matter", + "command": "dummy_command", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "secret": "masterpassphrase", "tx_json": { @@ -636,7 +636,7 @@ static constexpr TxnTestData txnTestArray[] = { {"The 'TransactionType' may be represented with an integer.", __LINE__, R"({ - "command": "doesnt_matter", + "command": "dummy_command", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "secret": "masterpassphrase", "tx_json": { @@ -654,7 +654,7 @@ static constexpr TxnTestData txnTestArray[] = { {"'Account' must be present.", __LINE__, R"({ - "command": "doesnt_matter", + "command": "dummy_command", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "secret": "masterpassphrase", "tx_json": { @@ -671,7 +671,7 @@ static constexpr TxnTestData txnTestArray[] = { {"'Account' must be well formed.", __LINE__, R"({ - "command": "doesnt_matter", + "command": "dummy_command", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "secret": "masterpassphrase", "tx_json": { @@ -689,7 +689,7 @@ static constexpr TxnTestData txnTestArray[] = { {"The 'offline' tag may be added to the transaction.", __LINE__, R"({ - "command": "doesnt_matter", + "command": "dummy_command", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "secret": "masterpassphrase", "offline": 0, @@ -708,7 +708,7 @@ static constexpr TxnTestData txnTestArray[] = { {"If 'offline' is true then a 'Sequence' field must be supplied.", __LINE__, R"({ - "command": "doesnt_matter", + "command": "dummy_command", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "secret": "masterpassphrase", "offline": 1, @@ -728,7 +728,7 @@ static constexpr TxnTestData txnTestArray[] = { {"If 'offline' is true then a 'Fee' field must be supplied.", __LINE__, R"({ - "command": "doesnt_matter", + "command": "dummy_command", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "secret": "masterpassphrase", "offline": 1, @@ -748,7 +748,7 @@ static constexpr TxnTestData txnTestArray[] = { {"Valid transaction if 'offline' is true.", __LINE__, R"({ - "command": "doesnt_matter", + "command": "dummy_command", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "secret": "masterpassphrase", "offline": 1, @@ -770,7 +770,7 @@ static constexpr TxnTestData txnTestArray[] = { {"'offline' and 'build_path' are mutually exclusive.", __LINE__, R"({ - "command": "doesnt_matter", + "command": "dummy_command", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "secret": "masterpassphrase", "offline": 1, @@ -792,7 +792,7 @@ static constexpr TxnTestData txnTestArray[] = { {"A 'Flags' field may be specified.", __LINE__, R"({ - "command": "doesnt_matter", + "command": "dummy_command", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "secret": "masterpassphrase", "tx_json": { @@ -811,7 +811,7 @@ static constexpr TxnTestData txnTestArray[] = { {"The 'Flags' field must be numeric.", __LINE__, R"({ - "command": "doesnt_matter", + "command": "dummy_command", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "secret": "masterpassphrase", "tx_json": { @@ -830,7 +830,7 @@ static constexpr TxnTestData txnTestArray[] = { {"It's okay to add a 'debug_signing' field.", __LINE__, R"({ - "command": "doesnt_matter", + "command": "dummy_command", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "secret": "masterpassphrase", "debug_signing": 0, @@ -849,7 +849,7 @@ static constexpr TxnTestData txnTestArray[] = { {"Single-sign a multisigned transaction.", __LINE__, R"({ - "command": "doesnt_matter", + "command": "dummy_command", "account": "rPcNzota6B8YBokhYtcTNqQVCngtbnWfux", "secret": "a", "tx_json": { @@ -879,7 +879,7 @@ static constexpr TxnTestData txnTestArray[] = { {"Minimal sign_for.", __LINE__, R"({ - "command": "doesnt_matter", + "command": "dummy_command", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "secret": "masterpassphrase", "tx_json": { @@ -900,7 +900,7 @@ static constexpr TxnTestData txnTestArray[] = { {"Minimal offline sign_for.", __LINE__, R"({ - "command": "doesnt_matter", + "command": "dummy_command", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "secret": "masterpassphrase", "offline": 1, @@ -919,7 +919,7 @@ static constexpr TxnTestData txnTestArray[] = { {"Offline sign_for using 'seed' instead of 'secret'.", __LINE__, R"({ - "command": "doesnt_matter", + "command": "dummy_command", "account": "rJrxi4Wxev4bnAGVNP9YCdKPdAoKfAmcsi", "key_type": "ed25519", "seed": "sh1yJfwoi98zCygwijUzuHmJDeVKd", @@ -939,7 +939,7 @@ static constexpr TxnTestData txnTestArray[] = { {"Malformed seed in sign_for.", __LINE__, R"({ - "command": "doesnt_matter", + "command": "dummy_command", "account": "rJrxi4Wxev4bnAGVNP9YCdKPdAoKfAmcsi", "key_type": "ed25519", "seed": "sh1yJfwoi98zCygwjUzuHmJDeVKd", @@ -962,7 +962,7 @@ static constexpr TxnTestData txnTestArray[] = { {"Missing 'Account' in sign_for.", __LINE__, R"({ - "command": "doesnt_matter", + "command": "dummy_command", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "secret": "masterpassphrase", "tx_json": { @@ -982,7 +982,7 @@ static constexpr TxnTestData txnTestArray[] = { {"Missing 'Amount' in sign_for.", __LINE__, R"({ - "command": "doesnt_matter", + "command": "dummy_command", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "secret": "masterpassphrase", "tx_json": { @@ -1002,7 +1002,7 @@ static constexpr TxnTestData txnTestArray[] = { {"Missing 'Destination' in sign_for.", __LINE__, R"({ - "command": "doesnt_matter", + "command": "dummy_command", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "secret": "masterpassphrase", "tx_json": { @@ -1022,7 +1022,7 @@ static constexpr TxnTestData txnTestArray[] = { {"Missing 'Destination' in sign_for, use DeliverMax", __LINE__, R"({ - "command": "doesnt_matter", + "command": "dummy_command", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "secret": "masterpassphrase", "tx_json": { @@ -1042,7 +1042,7 @@ static constexpr TxnTestData txnTestArray[] = { {"Missing 'Fee' in sign_for.", __LINE__, R"({ - "command": "doesnt_matter", + "command": "dummy_command", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "secret": "masterpassphrase", "tx_json": { @@ -1062,7 +1062,7 @@ static constexpr TxnTestData txnTestArray[] = { {"Missing 'Sequence' in sign_for.", __LINE__, R"({ - "command": "doesnt_matter", + "command": "dummy_command", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "secret": "masterpassphrase", "tx_json": { @@ -1082,7 +1082,7 @@ static constexpr TxnTestData txnTestArray[] = { {"Missing 'SigningPubKey' in sign_for is automatically filled in.", __LINE__, R"({ - "command": "doesnt_matter", + "command": "dummy_command", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "secret": "masterpassphrase", "tx_json": { @@ -1102,7 +1102,7 @@ static constexpr TxnTestData txnTestArray[] = { {"In sign_for, an account may not sign for itself.", __LINE__, R"({ - "command": "doesnt_matter", + "command": "dummy_command", "account": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA", "secret": "a", "tx_json": { @@ -1123,7 +1123,7 @@ static constexpr TxnTestData txnTestArray[] = { {"Cannot put duplicate accounts in Signers array", __LINE__, R"({ - "command": "doesnt_matter", + "command": "dummy_command", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "secret": "masterpassphrase", "tx_json": { @@ -1154,7 +1154,7 @@ static constexpr TxnTestData txnTestArray[] = { {"Correctly append to pre-established Signers array", __LINE__, R"({ - "command": "doesnt_matter", + "command": "dummy_command", "account": "rPcNzota6B8YBokhYtcTNqQVCngtbnWfux", "secret": "c", "tx_json": { @@ -1181,7 +1181,7 @@ static constexpr TxnTestData txnTestArray[] = { {"Append to pre-established Signers array with bad signature", __LINE__, R"({ - "command": "doesnt_matter", + "command": "dummy_command", "account": "rPcNzota6B8YBokhYtcTNqQVCngtbnWfux", "secret": "c", "tx_json": { @@ -1211,7 +1211,7 @@ static constexpr TxnTestData txnTestArray[] = { {"Non-empty 'SigningPubKey' in sign_for.", __LINE__, R"({ - "command": "doesnt_matter", + "command": "dummy_command", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "secret": "masterpassphrase", "tx_json": { @@ -1232,7 +1232,7 @@ static constexpr TxnTestData txnTestArray[] = { {"Missing 'TransactionType' in sign_for.", __LINE__, R"({ - "command": "doesnt_matter", + "command": "dummy_command", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "secret": "masterpassphrase", "tx_json": { @@ -1252,7 +1252,7 @@ static constexpr TxnTestData txnTestArray[] = { {"TxnSignature in sign_for.", __LINE__, R"({ - "command": "doesnt_matter", + "command": "dummy_command", "account": "rPcNzota6B8YBokhYtcTNqQVCngtbnWfux", "secret": "c", "tx_json": { @@ -1283,7 +1283,7 @@ static constexpr TxnTestData txnTestArray[] = { {"Invalid field 'tx_json': string instead of object", __LINE__, R"({ - "command": "doesnt_matter", + "command": "dummy_command", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "secret": "masterpassphrase", "tx_json": "" @@ -1296,7 +1296,7 @@ static constexpr TxnTestData txnTestArray[] = { {"Invalid field 'tx_json': integer instead of object", __LINE__, R"({ - "command": "doesnt_matter", + "command": "dummy_command", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "secret": "masterpassphrase", "tx_json": 20160331 @@ -1309,7 +1309,7 @@ static constexpr TxnTestData txnTestArray[] = { {"Invalid field 'tx_json': array instead of object", __LINE__, R"({ - "command": "doesnt_matter", + "command": "dummy_command", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "secret": "masterpassphrase", "tx_json": [ "hello", "world" ] @@ -1322,7 +1322,7 @@ static constexpr TxnTestData txnTestArray[] = { {"Pass in Fee with minimal payment, both Amount and DeliverMax.", __LINE__, R"({ - "command": "doesnt_matter", + "command": "dummy_command", "account": "r9zN9x52FiCFAcicCLMQKbj1nxYhxJbbSy", "secret": "ssgN6zTvtM1q9XV8DvJpWm8LBYWiY", "tx_json": { @@ -2003,7 +2003,7 @@ static constexpr TxnTestData txnTestArray[] = { {"Payment cannot specify different DeliverMax and Amount.", __LINE__, R"({ - "command": "doesnt_matter", + "command": "dummy_command", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "secret": "masterpassphrase", "debug_signing": 0, @@ -2025,7 +2025,7 @@ static constexpr TxnTestData txnTestArray[] = { {"Payment cannot specify bad DomainID.", __LINE__, R"({ - "command": "doesnt_matter", + "command": "dummy_command", "account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "secret": "masterpassphrase", "debug_signing": 0, @@ -2048,7 +2048,7 @@ static constexpr TxnTestData txnTestArray[] = { {"Minimal delegated transaction.", __LINE__, R"({ - "command": "doesnt_matter", + "command": "dummy_command", "secret": "a", "tx_json": { "Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", @@ -2066,7 +2066,7 @@ static constexpr TxnTestData txnTestArray[] = { {"Delegate not well formed.", __LINE__, R"({ - "command": "doesnt_matter", + "command": "dummy_command", "secret": "a", "tx_json": { "Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", @@ -2084,7 +2084,7 @@ static constexpr TxnTestData txnTestArray[] = { {"Delegate not in ledger.", __LINE__, R"({ - "command": "doesnt_matter", + "command": "dummy_command", "secret": "a", "tx_json": { "Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", @@ -2102,7 +2102,7 @@ static constexpr TxnTestData txnTestArray[] = { {"Delegate and secret not match.", __LINE__, R"({ - "command": "doesnt_matter", + "command": "dummy_command", "secret": "aa", "tx_json": { "Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", diff --git a/src/test/rpc/RPCCall_test.cpp b/src/test/rpc/RPCCall_test.cpp index 682584f40b..85402ff024 100644 --- a/src/test/rpc/RPCCall_test.cpp +++ b/src/test/rpc/RPCCall_test.cpp @@ -3921,7 +3921,7 @@ static RPCCallTestData const rpcCallTestArray[] = { } ] })"}, - {"log_level: partiton_name.", + {"log_level: partition_name.", __LINE__, {"log_level", "partition_name", "fatal"}, RPCCallTestData::no_exception, diff --git a/src/test/rpc/Simulate_test.cpp b/src/test/rpc/Simulate_test.cpp index 2a85e2a928..7fe90fb9a1 100644 --- a/src/test/rpc/Simulate_test.cpp +++ b/src/test/rpc/Simulate_test.cpp @@ -261,7 +261,7 @@ class Simulate_test : public beast::unit_test::suite { // `seed` field included Json::Value params = Json::objectValue; - params[jss::seed] = "doesnt_matter"; + params[jss::seed] = "random_data"; Json::Value tx_json = Json::objectValue; tx_json[jss::TransactionType] = jss::AccountSet; tx_json[jss::Account] = env.master.human(); @@ -274,7 +274,7 @@ class Simulate_test : public beast::unit_test::suite { // `secret` field included Json::Value params = Json::objectValue; - params[jss::secret] = "doesnt_matter"; + params[jss::secret] = "random_data"; Json::Value tx_json = Json::objectValue; tx_json[jss::TransactionType] = jss::AccountSet; tx_json[jss::Account] = env.master.human(); @@ -287,7 +287,7 @@ class Simulate_test : public beast::unit_test::suite { // `seed_hex` field included Json::Value params = Json::objectValue; - params[jss::seed_hex] = "doesnt_matter"; + params[jss::seed_hex] = "random_data"; Json::Value tx_json = Json::objectValue; tx_json[jss::TransactionType] = jss::AccountSet; tx_json[jss::Account] = env.master.human(); @@ -300,7 +300,7 @@ class Simulate_test : public beast::unit_test::suite { // `passphrase` field included Json::Value params = Json::objectValue; - params[jss::passphrase] = "doesnt_matter"; + params[jss::passphrase] = "random_data"; Json::Value tx_json = Json::objectValue; tx_json[jss::TransactionType] = jss::AccountSet; tx_json[jss::Account] = env.master.human(); diff --git a/src/test/server/ServerStatus_test.cpp b/src/test/server/ServerStatus_test.cpp index 9f35a9162d..a2516126de 100644 --- a/src/test/server/ServerStatus_test.cpp +++ b/src/test/server/ServerStatus_test.cpp @@ -1149,7 +1149,7 @@ class ServerStatus_test : public beast::unit_test::suite, doHTTPRequest(env, yield, false, resp, ec, to_string(jv)); BEAST_EXPECT( resp.result() == boost::beast::http::status::bad_request); - BEAST_EXPECT(resp.body() == "params unparseable\r\n"); + BEAST_EXPECT(resp.body() == "params unparsable\r\n"); } { @@ -1159,7 +1159,7 @@ class ServerStatus_test : public beast::unit_test::suite, doHTTPRequest(env, yield, false, resp, ec, to_string(jv)); BEAST_EXPECT( resp.result() == boost::beast::http::status::bad_request); - BEAST_EXPECT(resp.body() == "params unparseable\r\n"); + BEAST_EXPECT(resp.body() == "params unparsable\r\n"); } } diff --git a/src/xrpld/app/ledger/detail/LedgerMaster.cpp b/src/xrpld/app/ledger/detail/LedgerMaster.cpp index 2a2424995e..d176e85645 100644 --- a/src/xrpld/app/ledger/detail/LedgerMaster.cpp +++ b/src/xrpld/app/ledger/detail/LedgerMaster.cpp @@ -2204,7 +2204,7 @@ LedgerMaster::makeFetchPack( catch (std::exception const& ex) { JLOG(m_journal.warn()) - << "Exception building fetch pach. Exception: " << ex.what(); + << "Exception building fetch pack. Exception: " << ex.what(); } } diff --git a/src/xrpld/app/misc/FeeVoteImpl.cpp b/src/xrpld/app/misc/FeeVoteImpl.cpp index a6b140f691..08a9abf2db 100644 --- a/src/xrpld/app/misc/FeeVoteImpl.cpp +++ b/src/xrpld/app/misc/FeeVoteImpl.cpp @@ -230,12 +230,11 @@ FeeVoteImpl::doVoting( auto const& valueField) { if (auto const field = val->at(~valueField)) { - using xrptype = XRPAmount::value_type; + using XRPType = XRPAmount::value_type; auto const vote = *field; - if (vote <= std::numeric_limits::max() && - isLegalAmountSigned(XRPAmount{unsafe_cast(vote)})) - value.addVote( - XRPAmount{unsafe_cast(vote)}); + if (vote <= std::numeric_limits::max() && + isLegalAmountSigned(XRPAmount{unsafe_cast(vote)})) + value.addVote(XRPAmount{unsafe_cast(vote)}); else // Invalid amounts will be treated as if they're // not provided. Don't throw because this value is diff --git a/src/xrpld/app/misc/NetworkOPs.cpp b/src/xrpld/app/misc/NetworkOPs.cpp index d0f93577da..6a00354b15 100644 --- a/src/xrpld/app/misc/NetworkOPs.cpp +++ b/src/xrpld/app/misc/NetworkOPs.cpp @@ -3461,10 +3461,10 @@ NetworkOPsImp::pubAccountTransaction( } } - if (auto histoIt = mSubAccountHistory.find(affectedAccount); - histoIt != mSubAccountHistory.end()) + if (auto historyIt = mSubAccountHistory.find(affectedAccount); + historyIt != mSubAccountHistory.end()) { - auto& subs = histoIt->second; + auto& subs = historyIt->second; auto it = subs.begin(); while (it != subs.end()) { @@ -3487,7 +3487,7 @@ NetworkOPsImp::pubAccountTransaction( } } if (subs.empty()) - mSubAccountHistory.erase(histoIt); + mSubAccountHistory.erase(historyIt); } } } diff --git a/src/xrpld/app/misc/SHAMapStoreImp.h b/src/xrpld/app/misc/SHAMapStoreImp.h index aed2343a49..38997c2997 100644 --- a/src/xrpld/app/misc/SHAMapStoreImp.h +++ b/src/xrpld/app/misc/SHAMapStoreImp.h @@ -54,7 +54,7 @@ private: // name of state database std::string const dbName_ = "state"; // prefix of on-disk nodestore backend instances - std::string const dbPrefix_ = "rippledb"; + std::string const dbPrefix_ = "rippledb"; // cspell: disable-line // check health/stop status as records are copied std::uint64_t const checkHealthInterval_ = 1000; // minimum # of ledgers to maintain for health of network diff --git a/src/xrpld/app/misc/TxQ.h b/src/xrpld/app/misc/TxQ.h index fb1b10e886..10ed4adeea 100644 --- a/src/xrpld/app/misc/TxQ.h +++ b/src/xrpld/app/misc/TxQ.h @@ -537,12 +537,12 @@ private: /** Cached result of the `preflight` operation. Because `preflight` is expensive, minimize the number of times it needs to be done. - @invariant `pfresult` is never allowed to be empty. The + @invariant `pfResult` is never allowed to be empty. The `std::optional` is leveraged to allow `emplace`d construction and replacement without a copy assignment operation. */ - std::optional pfresult; + std::optional pfResult; /** Starting retry count for newly queued transactions. @@ -577,7 +577,7 @@ private: TxID const& txID, FeeLevel64 feeLevel, ApplyFlags const flags, - PreflightResult const& pfresult); + PreflightResult const& pfResult); /// Attempt to apply the queued transaction to the open ledger. ApplyResult @@ -588,7 +588,7 @@ private: TxConsequences const& consequences() const { - return pfresult->consequences; + return pfResult->consequences; } /// Return a TxDetails based on contained information. @@ -603,7 +603,7 @@ private: seqProxy, txn, retriesRemaining, - pfresult->ter, + pfResult->ter, lastResult}; } }; @@ -802,7 +802,7 @@ private: FeeMultiSet::iterator_type erase(FeeMultiSet::const_iterator_type); /** Erase and return the next entry for the account (if fee level is higher), or next entry in byFee_ (lower fee level). - Used to get the next "applyable" MaybeTx for accept(). + Used to get the next "applicable" MaybeTx for accept(). */ FeeMultiSet::iterator_type eraseAndAdvance( FeeMultiSet::const_iterator_type); @@ -826,7 +826,7 @@ private: AccountMap::iterator const& accountIter, TxQAccount::TxMap::iterator, FeeLevel64 feeLevelPaid, - PreflightResult const& pfresult, + PreflightResult const& pfResult, std::size_t const txExtraCount, ApplyFlags flags, FeeMetrics::Snapshot const& metricsSnapshot, diff --git a/src/xrpld/app/misc/ValidatorList.h b/src/xrpld/app/misc/ValidatorList.h index d6758f8877..45b24e8609 100644 --- a/src/xrpld/app/misc/ValidatorList.h +++ b/src/xrpld/app/misc/ValidatorList.h @@ -237,7 +237,7 @@ class ValidatorList // config file under the title of SECTION_VALIDATORS or [validators]. // This list is not associated with the masterKey of any publisher. - // Appropos PublisherListCollection fields, localPublisherList does not + // Apropos PublisherListCollection fields, localPublisherList does not // have any "remaining" manifests. It is assumed to be perennially // "available". The "validUntil" field is set to the highest possible // value of the field, hence this list is always valid. @@ -365,7 +365,7 @@ public: std::string const& rawManifest, std::map const& blobInfos, std::vector& messages, - std::size_t maxSize = maximiumMessageSize); + std::size_t maxSize = maximumMessageSize); /** Apply multiple published lists of public keys, then broadcast it to all peers that have not seen it or sent it. diff --git a/src/xrpld/app/misc/detail/TxQ.cpp b/src/xrpld/app/misc/detail/TxQ.cpp index 9f260e5f48..01abcc9c7d 100644 --- a/src/xrpld/app/misc/detail/TxQ.cpp +++ b/src/xrpld/app/misc/detail/TxQ.cpp @@ -263,7 +263,7 @@ TxQ::MaybeTx::MaybeTx( TxID const& txID_, FeeLevel64 feeLevel_, ApplyFlags const flags_, - PreflightResult const& pfresult_) + PreflightResult const& pfResult_) : txn(txn_) , feeLevel(feeLevel_) , txID(txID_) @@ -272,7 +272,7 @@ TxQ::MaybeTx::MaybeTx( , seqProxy(txn_->getSeqProxy()) , retriesRemaining(retriesAllowed) , flags(flags_) - , pfresult(pfresult_) + , pfResult(pfResult_) { } @@ -281,20 +281,20 @@ TxQ::MaybeTx::apply(Application& app, OpenView& view, beast::Journal j) { // If the rules or flags change, preflight again XRPL_ASSERT( - pfresult, "xrpl::TxQ::MaybeTx::apply : preflight result is set"); + pfResult, "xrpl::TxQ::MaybeTx::apply : preflight result is set"); NumberSO stNumberSO{view.rules().enabled(fixUniversalNumber)}; - if (pfresult->rules != view.rules() || pfresult->flags != flags) + if (pfResult->rules != view.rules() || pfResult->flags != flags) { JLOG(j.debug()) << "Queued transaction " << txID << " rules or flags have changed. Flags from " - << pfresult->flags << " to " << flags; + << pfResult->flags << " to " << flags; - pfresult.emplace( - preflight(app, view.rules(), pfresult->tx, flags, pfresult->j)); + pfResult.emplace( + preflight(app, view.rules(), pfResult->tx, flags, pfResult->j)); } - auto pcresult = preclaim(*pfresult, app, view); + auto pcresult = preclaim(*pfResult, app, view); return doApply(pcresult, app, view); } @@ -503,7 +503,7 @@ TxQ::tryClearAccountQueueUpThruTx( TxQ::AccountMap::iterator const& accountIter, TxQAccount::TxMap::iterator beginTxIter, FeeLevel64 feeLevelPaid, - PreflightResult const& pfresult, + PreflightResult const& pfResult, std::size_t const txExtraCount, ApplyFlags flags, FeeMetrics::Snapshot const& metricsSnapshot, @@ -578,7 +578,7 @@ TxQ::tryClearAccountQueueUpThruTx( } // Apply the current tx. Because the state of the view has been changed // by the queued txs, we also need to preclaim again. - auto const txResult = doApply(preclaim(pfresult, app, view), app, view); + auto const txResult = doApply(preclaim(pfResult, app, view), app, view); if (txResult.applied) { @@ -720,9 +720,9 @@ TxQ::apply( // See if the transaction is valid, properly formed, // etc. before doing potentially expensive queue // replace and multi-transaction operations. - auto const pfresult = preflight(app, view.rules(), *tx, flags, j); - if (pfresult.ter != tesSUCCESS) - return {pfresult.ter, false}; + auto const pfResult = preflight(app, view.rules(), *tx, flags, j); + if (pfResult.ter != tesSUCCESS) + return {pfResult.ter, false}; // See if the transaction paid a high enough fee that it can go straight // into the ledger. @@ -814,7 +814,7 @@ TxQ::apply( // 1. If the account's queue is empty or // 2. If the blocker replaces the only entry in the account's queue. auto const transactionID = tx->getTransactionID(); - if (pfresult.consequences.isBlocker()) + if (pfResult.consequences.isBlocker()) { if (acctTxCount > 1) { @@ -1040,8 +1040,8 @@ TxQ::apply( // The fee for the candidate transaction _should_ be // counted if it's replacing a transaction in the middle // of the queue. - totalFee += pfresult.consequences.fee(); - potentialSpend += pfresult.consequences.potentialSpend(); + totalFee += pfResult.consequences.fee(); + potentialSpend += pfResult.consequences.potentialSpend(); } } @@ -1144,7 +1144,7 @@ TxQ::apply( // is valid. So we use a special entry point that runs all of the // preclaim checks with the exception of the sequence check. auto const pcresult = - preclaim(pfresult, app, multiTxn ? multiTxn->openView : view); + preclaim(pfResult, app, multiTxn ? multiTxn->openView : view); if (!pcresult.likelyToClaimFee) return {pcresult.ter, false}; @@ -1187,7 +1187,7 @@ TxQ::apply( accountIter, txIter->first, feeLevelPaid, - pfresult, + pfResult, view.txCount(), flags, metricsSnapshot, @@ -1316,12 +1316,12 @@ TxQ::apply( flags &= ~tapRETRY; auto& candidate = accountIter->second.add( - {tx, transactionID, feeLevelPaid, flags, pfresult}); + {tx, transactionID, feeLevelPaid, flags, pfResult}); // Then index it into the byFee lookup. byFee_.insert(candidate); JLOG(j_.debug()) << "Added transaction " << candidate.txID - << " with result " << transToken(pfresult.ter) << " from " + << " with result " << transToken(pfResult.ter) << " from " << (accountIsInQueue ? "existing" : "new") << " account " << candidate.account << " to queue." << " Flags: " << flags; diff --git a/src/xrpld/app/misc/detail/ValidatorList.cpp b/src/xrpld/app/misc/detail/ValidatorList.cpp index db2ee980b8..12ba52fa36 100644 --- a/src/xrpld/app/misc/detail/ValidatorList.cpp +++ b/src/xrpld/app/misc/detail/ValidatorList.cpp @@ -524,7 +524,7 @@ splitMessageParts( smallMsg.set_manifest(blob.manifest()); XRPL_ASSERT( - Message::totalSize(smallMsg) <= maximiumMessageSize, + Message::totalSize(smallMsg) <= maximumMessageSize, "xrpl::splitMessageParts : maximum message size"); messages.emplace_back( @@ -588,7 +588,7 @@ buildValidatorListMessage( msg.set_version(version); XRPL_ASSERT( - Message::totalSize(msg) <= maximiumMessageSize, + Message::totalSize(msg) <= maximumMessageSize, "xrpl::buildValidatorListMessage(ValidatorBlobInfo) : maximum " "message size"); messages.emplace_back( @@ -658,7 +658,7 @@ ValidatorList::buildValidatorListMessages( std::string const& rawManifest, std::map const& blobInfos, std::vector& messages, - std::size_t maxSize /*= maximiumMessageSize*/) + std::size_t maxSize /*= maximumMessageSize*/) { XRPL_ASSERT( !blobInfos.empty(), diff --git a/src/xrpld/app/paths/AMMOffer.h b/src/xrpld/app/paths/AMMOffer.h index 07defc16b1..7a81e53176 100644 --- a/src/xrpld/app/paths/AMMOffer.h +++ b/src/xrpld/app/paths/AMMOffer.h @@ -82,7 +82,7 @@ public: */ TAmounts limitOut( - TAmounts const& offrAmt, + TAmounts const& offerAmount, TOut const& limit, bool roundUp) const; @@ -91,8 +91,10 @@ public: * current quality. */ TAmounts - limitIn(TAmounts const& offrAmt, TIn const& limit, bool roundUp) - const; + limitIn( + TAmounts const& offerAmount, + TIn const& limit, + bool roundUp) const; QualityFunction getQualityFunc() const; diff --git a/src/xrpld/app/paths/PathRequest.h b/src/xrpld/app/paths/PathRequest.h index 3d5564c069..8f63cfa9a9 100644 --- a/src/xrpld/app/paths/PathRequest.h +++ b/src/xrpld/app/paths/PathRequest.h @@ -103,7 +103,7 @@ private: std::function const&); /** Finds and sets a PathSet in the JSON argument. - Returns false if the source currencies are inavlid. + Returns false if the source currencies are invalid. */ bool findPaths( diff --git a/src/xrpld/app/paths/detail/AMMOffer.cpp b/src/xrpld/app/paths/detail/AMMOffer.cpp index a58eafbdbf..1152b673da 100644 --- a/src/xrpld/app/paths/detail/AMMOffer.cpp +++ b/src/xrpld/app/paths/detail/AMMOffer.cpp @@ -61,7 +61,7 @@ AMMOffer::consume( template TAmounts AMMOffer::limitOut( - TAmounts const& offrAmt, + TAmounts const& offerAmount, TOut const& limit, bool roundUp) const { @@ -75,7 +75,7 @@ AMMOffer::limitOut( { // It turns out that the ceil_out implementation has some slop in // it, which ceil_out_strict removes. - return quality().ceil_out_strict(offrAmt, limit, roundUp); + return quality().ceil_out_strict(offerAmount, limit, roundUp); } // Change the offer size according to the conservation function. The offer // quality is increased in this case, but it doesn't matter since there is @@ -86,7 +86,7 @@ AMMOffer::limitOut( template TAmounts AMMOffer::limitIn( - TAmounts const& offrAmt, + TAmounts const& offerAmount, TIn const& limit, bool roundUp) const { @@ -95,9 +95,9 @@ AMMOffer::limitIn( { if (auto const& rules = getCurrentTransactionRules(); rules && rules->enabled(fixReducedOffersV2)) - return quality().ceil_in_strict(offrAmt, limit, roundUp); + return quality().ceil_in_strict(offerAmount, limit, roundUp); - return quality().ceil_in(offrAmt, limit); + return quality().ceil_in(offerAmount, limit); } return {limit, swapAssetIn(balances_, limit, ammLiquidity_.tradingFee())}; } diff --git a/src/xrpld/app/paths/detail/StrandFlow.h b/src/xrpld/app/paths/detail/StrandFlow.h index 5e0a637858..fab92dca35 100644 --- a/src/xrpld/app/paths/detail/StrandFlow.h +++ b/src/xrpld/app/paths/detail/StrandFlow.h @@ -435,8 +435,8 @@ public: cur_.clear(); if (!next_.empty()) { - std::vector> strandQuals; - strandQuals.reserve(next_.size()); + std::vector> strandQualities; + strandQualities.reserve(next_.size()); if (next_.size() > 1) // no need to sort one strand { for (Strand const* strand : next_) @@ -458,21 +458,21 @@ public: // an unusual corner case. continue; } - strandQuals.push_back({*qual, strand}); + strandQualities.push_back({*qual, strand}); } } // must stable sort for deterministic order across different c++ // standard library implementations std::stable_sort( - strandQuals.begin(), - strandQuals.end(), + strandQualities.begin(), + strandQualities.end(), [](auto const& lhs, auto const& rhs) { // higher qualities first return std::get(lhs) > std::get(rhs); }); next_.clear(); - next_.reserve(strandQuals.size()); - for (auto const& sq : strandQuals) + next_.reserve(strandQualities.size()); + for (auto const& sq : strandQualities) { next_.push_back(std::get(sq)); } diff --git a/src/xrpld/app/rdb/backend/detail/SQLiteDatabase.cpp b/src/xrpld/app/rdb/backend/detail/SQLiteDatabase.cpp index 796b030a99..77757419dc 100644 --- a/src/xrpld/app/rdb/backend/detail/SQLiteDatabase.cpp +++ b/src/xrpld/app/rdb/backend/detail/SQLiteDatabase.cpp @@ -155,7 +155,7 @@ private: Application& app_; bool const useTxTables_; beast::Journal j_; - std::unique_ptr lgrdb_, txdb_; + std::unique_ptr ledgerDb_, txdb_; /** * @brief makeLedgerDBs Opens ledger and transaction databases for the node @@ -178,7 +178,7 @@ private: bool existsLedger() { - return static_cast(lgrdb_); + return static_cast(ledgerDb_); } /** @@ -200,7 +200,7 @@ private: auto checkoutLedger() { - return lgrdb_->checkoutDb(); + return ledgerDb_->checkoutDb(); } /** @@ -224,7 +224,7 @@ SQLiteDatabaseImp::makeLedgerDBs( auto [lgr, tx, res] = detail::makeLedgerDBs(config, setup, checkpointerSetup, j_); txdb_ = std::move(tx); - lgrdb_ = std::move(lgr); + ledgerDb_ = std::move(lgr); return res; } @@ -392,7 +392,8 @@ SQLiteDatabaseImp::saveValidatedLedger( { if (existsLedger()) { - if (!detail::saveValidatedLedger(*lgrdb_, txdb_, app_, ledger, current)) + if (!detail::saveValidatedLedger( + *ledgerDb_, txdb_, app_, ledger, current)) return false; } @@ -789,7 +790,7 @@ SQLiteDatabaseImp::getKBUsedAll() { if (existsLedger()) { - return xrpl::getKBUsedAll(lgrdb_->getSession()); + return xrpl::getKBUsedAll(ledgerDb_->getSession()); } return 0; @@ -800,7 +801,7 @@ SQLiteDatabaseImp::getKBUsedLedger() { if (existsLedger()) { - return xrpl::getKBUsedDB(lgrdb_->getSession()); + return xrpl::getKBUsedDB(ledgerDb_->getSession()); } return 0; @@ -823,7 +824,7 @@ SQLiteDatabaseImp::getKBUsedTransaction() void SQLiteDatabaseImp::closeLedgerDB() { - lgrdb_.reset(); + ledgerDb_.reset(); } void diff --git a/src/xrpld/app/tx/applySteps.h b/src/xrpld/app/tx/applySteps.h index c0c530f3ac..0ae3d8790e 100644 --- a/src/xrpld/app/tx/applySteps.h +++ b/src/xrpld/app/tx/applySteps.h @@ -64,7 +64,7 @@ private: public: // Constructor if preflight returns a value other than tesSUCCESS. // Asserts if tesSUCCESS is passed. - explicit TxConsequences(NotTEC pfresult); + explicit TxConsequences(NotTEC pfResult); /// Constructor if the STTx has no notable consequences for the TxQ. explicit TxConsequences(STTx const& tx); diff --git a/src/xrpld/app/tx/detail/DelegateSet.cpp b/src/xrpld/app/tx/detail/DelegateSet.cpp index 8c64547ae1..96d02bf2bc 100644 --- a/src/xrpld/app/tx/detail/DelegateSet.cpp +++ b/src/xrpld/app/tx/detail/DelegateSet.cpp @@ -26,7 +26,7 @@ DelegateSet::preflight(PreflightContext const& ctx) if (!permissionSet.insert(permission[sfPermissionValue]).second) return temMALFORMED; - if (!Permission::getInstance().isDelegatable( + if (!Permission::getInstance().isDelegable( permission[sfPermissionValue], ctx.rules)) return temMALFORMED; } diff --git a/src/xrpld/app/tx/detail/InvariantCheck.cpp b/src/xrpld/app/tx/detail/InvariantCheck.cpp index dadc5a7d74..eb751c817f 100644 --- a/src/xrpld/app/tx/detail/InvariantCheck.cpp +++ b/src/xrpld/app/tx/detail/InvariantCheck.cpp @@ -567,7 +567,7 @@ AccountRootsDeletedClean::finalize( } { - // NFT pages. ntfpage_min and nftpage_max were already explicitly + // NFT pages. nftpage_min and nftpage_max were already explicitly // checked above as entries in directAccountKeylets. This uses // view.succ() to check for any NFT pages in between the two // endpoints. @@ -2321,7 +2321,7 @@ NoModifiedUnmodifiableFields::finalize( if (bad) { JLOG(j.fatal()) - << "Invariant failed: changed an unchangable field for " + << "Invariant failed: changed an unchangeable field for " << tx.getTransactionID(); if (enforce) return false; diff --git a/src/xrpld/app/tx/detail/Offer.h b/src/xrpld/app/tx/detail/Offer.h index b04049b657..75e78e5758 100644 --- a/src/xrpld/app/tx/detail/Offer.h +++ b/src/xrpld/app/tx/detail/Offer.h @@ -121,13 +121,15 @@ public: TAmounts limitOut( - TAmounts const& offrAmt, + TAmounts const& offerAmount, TOut const& limit, bool roundUp) const; TAmounts - limitIn(TAmounts const& offrAmt, TIn const& limit, bool roundUp) - const; + limitIn( + TAmounts const& offerAmount, + TIn const& limit, + bool roundUp) const; template static TER @@ -218,19 +220,19 @@ TOffer::setFieldAmounts() template TAmounts TOffer::limitOut( - TAmounts const& offrAmt, + TAmounts const& offerAmount, TOut const& limit, bool roundUp) const { // It turns out that the ceil_out implementation has some slop in // it, which ceil_out_strict removes. - return quality().ceil_out_strict(offrAmt, limit, roundUp); + return quality().ceil_out_strict(offerAmount, limit, roundUp); } template TAmounts TOffer::limitIn( - TAmounts const& offrAmt, + TAmounts const& offerAmount, TIn const& limit, bool roundUp) const { @@ -240,8 +242,8 @@ TOffer::limitIn( // 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(offrAmt, limit, roundUp); - return m_quality.ceil_in(offrAmt, limit); + return quality().ceil_in_strict(offerAmount, limit, roundUp); + return m_quality.ceil_in(offerAmount, limit); } template diff --git a/src/xrpld/app/tx/detail/OfferStream.cpp b/src/xrpld/app/tx/detail/OfferStream.cpp index 49e45976eb..587badf31a 100644 --- a/src/xrpld/app/tx/detail/OfferStream.cpp +++ b/src/xrpld/app/tx/detail/OfferStream.cpp @@ -341,7 +341,7 @@ TOfferStreamBase::step() } // LCOV_EXCL_START UNREACHABLE( - "rippls::TOfferStreamBase::step::rmSmallIncreasedQOffer : XRP " + "xrpl::TOfferStreamBase::step::rmSmallIncreasedQOffer : XRP " "vs XRP offer"); return false; // LCOV_EXCL_STOP diff --git a/src/xrpld/app/tx/detail/Transactor.cpp b/src/xrpld/app/tx/detail/Transactor.cpp index 7b0cbbbdd4..851712fe90 100644 --- a/src/xrpld/app/tx/detail/Transactor.cpp +++ b/src/xrpld/app/tx/detail/Transactor.cpp @@ -1133,7 +1133,7 @@ Transactor::operator()() // raii classes for the current ledger rules. // fixUniversalNumber predate the rulesGuard and should be replaced. NumberSO stNumberSO{view().rules().enabled(fixUniversalNumber)}; - CurrentTransactionRulesGuard currentTransctionRulesGuard(view().rules()); + CurrentTransactionRulesGuard currentTransactionRulesGuard(view().rules()); #ifdef DEBUG { diff --git a/src/xrpld/app/tx/detail/XChainBridge.cpp b/src/xrpld/app/tx/detail/XChainBridge.cpp index 88cc236e0b..ebfcc8a769 100644 --- a/src/xrpld/app/tx/detail/XChainBridge.cpp +++ b/src/xrpld/app/tx/detail/XChainBridge.cpp @@ -324,7 +324,7 @@ onNewAttestations( return {std::move(r.value()), changed}; }; -// Check if there is a quorurm of attestations for the given amount and +// Check if there is a quorum of attestations for the given amount and // chain. If so return the reward accounts, if not return the tec code (most // likely tecXCHAIN_CLAIM_NO_QUORUM) Expected, TER> @@ -434,7 +434,7 @@ transferHelper( auto const availableBalance = [&]() -> STAmount { STAmount const curBal = (*sleSrc)[sfBalance]; // Checking that account == src and postFeeBalance == curBal is - // not strictly nessisary, but helps protect against future + // not strictly necessary, but helps protect against future // changes if (!submittingAccountInfo || submittingAccountInfo->account != src || @@ -622,7 +622,7 @@ finalizeClaimHelper( // If distributing the reward pool fails, the mainFunds transfer should // be rolled back // - // If the claimid is removed, the rewards should be distributed + // If the claim ID is removed, the rewards should be distributed // even if the mainFunds fails. // // If OnTransferFail::removeClaim, the claim should be removed even if @@ -1190,7 +1190,7 @@ toClaim(STTx const& tx) template NotTEC -attestationpreflight(PreflightContext const& ctx) +attestationPreflight(PreflightContext const& ctx) { if (!publicKeyType(ctx.tx[sfPublicKey])) return temMALFORMED; @@ -2076,7 +2076,7 @@ XChainCreateClaimID::doApply() NotTEC XChainAddClaimAttestation::preflight(PreflightContext const& ctx) { - return attestationpreflight(ctx); + return attestationPreflight(ctx); } TER @@ -2096,7 +2096,7 @@ XChainAddClaimAttestation::doApply() NotTEC XChainAddAccountCreateAttestation::preflight(PreflightContext const& ctx) { - return attestationpreflight(ctx); + return attestationPreflight(ctx); } TER diff --git a/src/xrpld/app/tx/detail/applySteps.cpp b/src/xrpld/app/tx/detail/applySteps.cpp index 7f0d971fdc..e0bd9d0d2d 100644 --- a/src/xrpld/app/tx/detail/applySteps.cpp +++ b/src/xrpld/app/tx/detail/applySteps.cpp @@ -217,7 +217,7 @@ invoke_calculateBaseFee(ReadView const& view, STTx const& tx) } } -TxConsequences::TxConsequences(NotTEC pfresult) +TxConsequences::TxConsequences(NotTEC pfResult) : isBlocker_(false) , fee_(beast::zero) , potentialSpend_(beast::zero) @@ -225,7 +225,7 @@ TxConsequences::TxConsequences(NotTEC pfresult) , sequencesConsumed_(0) { XRPL_ASSERT( - !isTesSuccess(pfresult), + !isTesSuccess(pfResult), "xrpl::TxConsequences::TxConsequences : is not tesSUCCESS"); } @@ -288,15 +288,15 @@ preflight( ApplyFlags flags, beast::Journal j) { - PreflightContext const pfctx(app, tx, rules, flags, j); + PreflightContext const pfCtx(app, tx, rules, flags, j); try { - return {pfctx, invoke_preflight(pfctx)}; + return {pfCtx, invoke_preflight(pfCtx)}; } catch (std::exception const& e) { JLOG(j.fatal()) << "apply (preflight): " << e.what(); - return {pfctx, {tefEXCEPTION, TxConsequences{tx}}}; + return {pfCtx, {tefEXCEPTION, TxConsequences{tx}}}; } } @@ -309,15 +309,15 @@ preflight( ApplyFlags flags, beast::Journal j) { - PreflightContext const pfctx(app, tx, parentBatchId, rules, flags, j); + PreflightContext const pfCtx(app, tx, parentBatchId, rules, flags, j); try { - return {pfctx, invoke_preflight(pfctx)}; + return {pfCtx, invoke_preflight(pfCtx)}; } catch (std::exception const& e) { JLOG(j.fatal()) << "apply (preflight): " << e.what(); - return {pfctx, {tefEXCEPTION, TxConsequences{tx}}}; + return {pfCtx, {tefEXCEPTION, TxConsequences{tx}}}; } } diff --git a/src/xrpld/consensus/DisputedTx.h b/src/xrpld/consensus/DisputedTx.h index f4b841c795..5207f9750e 100644 --- a/src/xrpld/consensus/DisputedTx.h +++ b/src/xrpld/consensus/DisputedTx.h @@ -332,10 +332,10 @@ DisputedTx::getJson() const if (!votes_.empty()) { - Json::Value votesj(Json::objectValue); + Json::Value votes(Json::objectValue); for (auto const& [nodeId, vote] : votes_) - votesj[to_string(nodeId)] = vote; - ret["votes"] = std::move(votesj); + votes[to_string(nodeId)] = vote; + ret["votes"] = std::move(votes); } return ret; diff --git a/src/xrpld/consensus/Validations.h b/src/xrpld/consensus/Validations.h index 15b33c89c4..339612cfe0 100644 --- a/src/xrpld/consensus/Validations.h +++ b/src/xrpld/consensus/Validations.h @@ -455,9 +455,9 @@ private: are checked and any stale validations are flushed from the trie. @param lock Existing lock of mutex_ - @param f Invokable with signature (LedgerTrie &) + @param f Invocable with signature (LedgerTrie &) - @warning The invokable `f` is expected to be a simple transformation of + @warning The invocable `f` is expected to be a simple transformation of its arguments and will be called with mutex_ under lock. */ @@ -476,14 +476,14 @@ private: Iterate current validations, flushing any which are stale. @param lock Existing lock of mutex_ - @param pre Invokable with signature (std::size_t) called prior to + @param pre Invocable with signature (std::size_t) called prior to looping. - @param f Invokable with signature (NodeID const &, Validations const &) + @param f Invocable with signature (NodeID const &, Validations const &) for each current validation. - @note The invokable `pre` is called _prior_ to checking for staleness + @note The invocable `pre` is called _prior_ to checking for staleness and reflects an upper-bound on the number of calls to `f. - @warning The invokable `f` is expected to be a simple transformation of + @warning The invocable `f` is expected to be a simple transformation of its arguments and will be called with mutex_ under lock. */ @@ -517,12 +517,12 @@ private: @param lock Existing lock on mutex_ @param ledgerID The identifier of the ledger - @param pre Invokable with signature(std::size_t) - @param f Invokable with signature (NodeID const &, Validation const &) + @param pre Invocable with signature(std::size_t) + @param f Invocable with signature (NodeID const &, Validation const &) - @note The invokable `pre` is called prior to iterating validations. The + @note The invocable `pre` is called prior to iterating validations. The argument is the number of times `f` will be called. - @warning The invokable f is expected to be a simple transformation of + @warning The invocable f is expected to be a simple transformation of its arguments and will be called with mutex_ under lock. */ template diff --git a/src/xrpld/overlay/Message.h b/src/xrpld/overlay/Message.h index f2b021840d..550c24eef3 100644 --- a/src/xrpld/overlay/Message.h +++ b/src/xrpld/overlay/Message.h @@ -12,7 +12,7 @@ namespace xrpl { -constexpr std::size_t maximiumMessageSize = megabytes(64); +constexpr std::size_t maximumMessageSize = megabytes(64); // VFALCO NOTE If we forward declare Message and write out shared_ptr // instead of using the in-class type alias, we can remove the diff --git a/src/xrpld/overlay/Overlay.h b/src/xrpld/overlay/Overlay.h index c30d0f5205..aaecd50eb6 100644 --- a/src/xrpld/overlay/Overlay.h +++ b/src/xrpld/overlay/Overlay.h @@ -94,7 +94,7 @@ public: size() const = 0; /** Return diagnostics on the status of all peers. - @deprecated This is superceded by PropertyStream + @deprecated This is superseded by PropertyStream */ virtual Json::Value json() = 0; diff --git a/src/xrpld/overlay/Slot.h b/src/xrpld/overlay/Slot.h index 9e717fef9c..66368b60b9 100644 --- a/src/xrpld/overlay/Slot.h +++ b/src/xrpld/overlay/Slot.h @@ -345,14 +345,14 @@ Slot::update( auto it = std::next(considered_.begin(), i); auto id = *it; considered_.erase(it); - auto const& itpeers = peers_.find(id); - if (itpeers == peers_.end()) + auto const& itPeers = peers_.find(id); + if (itPeers == peers_.end()) { JLOG(journal_.error()) << "update: peer not found " << Slice(validator) << " " << id; continue; } - if (now - itpeers->second.lastMessage < IDLED) + if (now - itPeers->second.lastMessage < IDLED) selected.insert(id); } diff --git a/src/xrpld/overlay/detail/OverlayImpl.cpp b/src/xrpld/overlay/detail/OverlayImpl.cpp index 7cd02c72e0..d1d2467b1e 100644 --- a/src/xrpld/overlay/detail/OverlayImpl.cpp +++ b/src/xrpld/overlay/detail/OverlayImpl.cpp @@ -620,7 +620,7 @@ OverlayImpl::onManifests( std::shared_ptr const& from) { auto const n = m->list_size(); - auto const& journal = from->pjournal(); + auto const& journal = from->pJournal(); protocol::TMManifests relay; diff --git a/src/xrpld/overlay/detail/OverlayImpl.h b/src/xrpld/overlay/detail/OverlayImpl.h index dc7e4975a3..786f42184e 100644 --- a/src/xrpld/overlay/detail/OverlayImpl.h +++ b/src/xrpld/overlay/detail/OverlayImpl.h @@ -461,7 +461,7 @@ private: /** Handles validator list requests. Using a /vl/ URL, will retrieve the - latest valdiator list (or UNL) that this node has for that + latest validator list (or UNL) that this node has for that public key, if the node trusts that public key. @return true if the request was handled. diff --git a/src/xrpld/overlay/detail/PeerImp.h b/src/xrpld/overlay/detail/PeerImp.h index 565c3f9d0f..1e28de0c31 100644 --- a/src/xrpld/overlay/detail/PeerImp.h +++ b/src/xrpld/overlay/detail/PeerImp.h @@ -323,7 +323,7 @@ public: virtual ~PeerImp(); beast::Journal const& - pjournal() const + pJournal() const { return p_journal_; } diff --git a/src/xrpld/overlay/detail/ProtocolMessage.h b/src/xrpld/overlay/detail/ProtocolMessage.h index 1a35deb6f0..51dfc1ac7c 100644 --- a/src/xrpld/overlay/detail/ProtocolMessage.h +++ b/src/xrpld/overlay/detail/ProtocolMessage.h @@ -114,7 +114,7 @@ struct MessageHeader std::uint16_t message_type = 0; /** Indicates which compression algorithm the payload is compressed with. - * Currenly only lz4 is supported. If None then the message is not + * Currently only lz4 is supported. If None then the message is not * compressed. */ compression::Algorithm algorithm = compression::Algorithm::None; @@ -340,8 +340,8 @@ invokeProtocolMessage( // whose size exceeds this may result in the connection being dropped. A // larger message size may be supported in the future or negotiated as // part of a protocol upgrade. - if (header->payload_wire_size > maximiumMessageSize || - header->uncompressed_size > maximiumMessageSize) + if (header->payload_wire_size > maximumMessageSize || + header->uncompressed_size > maximumMessageSize) { result.second = make_error_code(boost::system::errc::message_size); return result; diff --git a/src/xrpld/overlay/detail/ProtocolVersion.h b/src/xrpld/overlay/detail/ProtocolVersion.h index ff49c89f1a..9499cd1c1b 100644 --- a/src/xrpld/overlay/detail/ProtocolVersion.h +++ b/src/xrpld/overlay/detail/ProtocolVersion.h @@ -32,7 +32,7 @@ to_string(ProtocolVersion const& p); Given a comma-separated string, extract and return all those that look like valid protocol versions (i.e. XRPL/2.0 and later). Strings that are - not parseable as valid protocol strings are excluded from the result set. + not parsable as valid protocol strings are excluded from the result set. @return A list of all apparently valid protocol versions. diff --git a/src/xrpld/overlay/detail/TxMetrics.cpp b/src/xrpld/overlay/detail/TxMetrics.cpp index 10f14183a3..ba3e7e8ae0 100644 --- a/src/xrpld/overlay/detail/TxMetrics.cpp +++ b/src/xrpld/overlay/detail/TxMetrics.cpp @@ -83,11 +83,11 @@ SingleMetrics::addMetrics(std::uint32_t val) if (timeElapsedInSecs >= 1s) { auto const avg = accum / (perTimeUnit ? timeElapsedInSecs.count() : N); - rollingAvgAggreg.push_back(avg); + rollingAvgAggregate.push_back(avg); auto const total = std::accumulate( - rollingAvgAggreg.begin(), rollingAvgAggreg.end(), 0ull); - rollingAvg = total / rollingAvgAggreg.size(); + rollingAvgAggregate.begin(), rollingAvgAggregate.end(), 0ull); + rollingAvg = total / rollingAvgAggregate.size(); intervalStart = clock_type::now(); accum = 0; diff --git a/src/xrpld/overlay/detail/TxMetrics.h b/src/xrpld/overlay/detail/TxMetrics.h index 3c34aaf9f9..265c9251d7 100644 --- a/src/xrpld/overlay/detail/TxMetrics.h +++ b/src/xrpld/overlay/detail/TxMetrics.h @@ -34,7 +34,7 @@ struct SingleMetrics std::uint64_t rollingAvg{0}; std::uint32_t N{0}; bool perTimeUnit{true}; - boost::circular_buffer rollingAvgAggreg{30, 0ull}; + boost::circular_buffer rollingAvgAggregate{30, 0ull}; /** Add metrics value * @param val metrics value, either bytes or count */ diff --git a/src/xrpld/peerfinder/detail/Livecache.h b/src/xrpld/peerfinder/detail/Livecache.h index 0927593ed1..c04b46dc6d 100644 --- a/src/xrpld/peerfinder/detail/Livecache.h +++ b/src/xrpld/peerfinder/detail/Livecache.h @@ -165,14 +165,14 @@ protected: /** The Livecache holds the short-lived relayed Endpoint messages. Since peers only advertise themselves when they have open slots, - we want these messags to expire rather quickly after the peer becomes + we want these messages to expire rather quickly after the peer becomes full. Addresses added to the cache are not connection-tested to see if - they are connectible (with one small exception regarding neighbors). + they are connectable (with one small exception regarding neighbors). Therefore, these addresses are not suitable for persisting across launches or for bootstrapping, because they do not have verifiable - and locally observed uptime and connectibility information. + and locally observed uptime and connectability information. */ template > class Livecache : protected detail::LivecacheBase diff --git a/src/xrpld/peerfinder/detail/Logic.h b/src/xrpld/peerfinder/detail/Logic.h index 50026cc886..aa56c8098c 100644 --- a/src/xrpld/peerfinder/detail/Logic.h +++ b/src/xrpld/peerfinder/detail/Logic.h @@ -628,7 +628,7 @@ public: This is a temporary measure. Once we know our own IP address, the correct solution is to put it into the Livecache at hops 0, and go through the regular handout path. This way - we avoid handing our address out too frequenty, which this code + we avoid handing our address out too frequently, which this code suffers from. */ // Add an entry for ourselves if: @@ -903,7 +903,7 @@ public: // Address must exist if (iter == connectedAddresses_.end()) LogicError( - "PeerFinder::Logic::remove(): remote_endpont " + "PeerFinder::Logic::remove(): remote_endpoint " "address missing from connectedAddresses_"); connectedAddresses_.erase(iter); @@ -929,7 +929,7 @@ public: auto iter(fixed_.find(slot->remote_endpoint())); if (iter == fixed_.end()) LogicError( - "PeerFinder::Logic::on_closed(): remote_endpont " + "PeerFinder::Logic::on_closed(): remote_endpoint " "missing from fixed_"); iter->second.failure(m_clock.now()); diff --git a/src/xrpld/peerfinder/detail/iosformat.h b/src/xrpld/peerfinder/detail/iosformat.h index 323665c016..a1117f0935 100644 --- a/src/xrpld/peerfinder/detail/iosformat.h +++ b/src/xrpld/peerfinder/detail/iosformat.h @@ -60,7 +60,7 @@ struct divider } }; -/** Creates a padded field with an optiona fill character. */ +/** Creates a padded field with an optional fill character. */ struct fpad { explicit fpad(int width_, int pad_ = 0, char fill_ = ' ') @@ -172,7 +172,7 @@ field(T const& t, int width = 8, int pad = 0, bool right = false) template field_t -rfield( +rField( std::basic_string const& text, int width = 8, int pad = 0) @@ -182,7 +182,7 @@ rfield( template field_t -rfield(CharT const* text, int width = 8, int pad = 0) +rField(CharT const* text, int width = 8, int pad = 0) { return field_t, std::allocator>( std:: @@ -195,7 +195,7 @@ rfield(CharT const* text, int width = 8, int pad = 0) template field_t -rfield(T const& t, int width = 8, int pad = 0) +rField(T const& t, int width = 8, int pad = 0) { std::string const text(detail::to_string(t)); return field(text, width, pad, true); diff --git a/src/xrpld/perflog/detail/PerfLogImp.cpp b/src/xrpld/perflog/detail/PerfLogImp.cpp index 4c0c4d2f6f..ceacb41223 100644 --- a/src/xrpld/perflog/detail/PerfLogImp.cpp +++ b/src/xrpld/perflog/detail/PerfLogImp.cpp @@ -103,7 +103,7 @@ PerfLogImp::Counters::countersJson() const rpcobj[jss::total] = totalRpcJson; } - Json::Value jqobj(Json::objectValue); + Json::Value jobQueueObj(Json::objectValue); // totalJq represents all jobs. All enqueued, started, finished, etc. Jq totalJq; for (auto const& proc : jq_) @@ -132,7 +132,7 @@ PerfLogImp::Counters::countersJson() const j[jss::running_duration_us] = std::to_string(value.runningDuration.count()); totalJq.runningDuration += value.runningDuration; - jqobj[JobTypes::name(proc.first)] = j; + jobQueueObj[JobTypes::name(proc.first)] = j; } if (totalJq.queued) @@ -145,14 +145,14 @@ PerfLogImp::Counters::countersJson() const std::to_string(totalJq.queuedDuration.count()); totalJqJson[jss::running_duration_us] = std::to_string(totalJq.runningDuration.count()); - jqobj[jss::total] = totalJqJson; + jobQueueObj[jss::total] = totalJqJson; } Json::Value counters(Json::objectValue); // Be kind to reporting tools and let them expect rpc and jq objects // even if empty. counters[jss::rpc] = rpcobj; - counters[jss::job_queue] = jqobj; + counters[jss::job_queue] = jobQueueObj; return counters; } diff --git a/src/xrpld/rpc/detail/RPCCall.cpp b/src/xrpld/rpc/detail/RPCCall.cpp index 5eb3943c28..30dae16e29 100644 --- a/src/xrpld/rpc/detail/RPCCall.cpp +++ b/src/xrpld/rpc/detail/RPCCall.cpp @@ -434,7 +434,7 @@ private: return jvRequest; } - // Return an error for attemping to subscribe/unsubscribe via RPC. + // Return an error for attempting to subscribe/unsubscribe via RPC. Json::Value parseEvented(Json::Value const& jvParams) { @@ -1357,7 +1357,7 @@ JSONRPCRequest( namespace { // Special local exception type thrown when request can't be parsed. -class RequestNotParseable : public std::runtime_error +class RequestNotParsable : public std::runtime_error { using std::runtime_error::runtime_error; // Inherit constructors }; @@ -1399,7 +1399,7 @@ struct RPCCallImp 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) - Throw(strData); + Throw(strData); Json::Reader reader; Json::Value jvReply; if (!reader.parse(strData, jvReply)) @@ -1618,7 +1618,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 (RequestNotParseable& e) + catch (RequestNotParsable& e) { jvOutput = rpcError(rpcINVALID_PARAMS); jvOutput["error_what"] = e.what(); diff --git a/src/xrpld/rpc/detail/RPCHelpers.cpp b/src/xrpld/rpc/detail/RPCHelpers.cpp index d10f4f9a5a..187f06a78e 100644 --- a/src/xrpld/rpc/detail/RPCHelpers.cpp +++ b/src/xrpld/rpc/detail/RPCHelpers.cpp @@ -95,7 +95,7 @@ readLimitField( Tuning::LimitRange const& range, JsonContext const& context) { - limit = range.rdefault; + limit = range.rDefault; if (!context.params.isMember(jss::limit) || context.params[jss::limit].isNull()) return std::nullopt; diff --git a/src/xrpld/rpc/detail/ServerHandler.cpp b/src/xrpld/rpc/detail/ServerHandler.cpp index 91b709bc06..bb84b81566 100644 --- a/src/xrpld/rpc/detail/ServerHandler.cpp +++ b/src/xrpld/rpc/detail/ServerHandler.cpp @@ -619,7 +619,8 @@ ServerHandler::processRequest( { HTTPReply( 400, - "Unable to parse request: " + reader.getFormatedErrorMessages(), + "Unable to parse request: " + + reader.getFormattedErrorMessages(), output, rpcJ); return; @@ -814,7 +815,7 @@ ServerHandler::processRequest( else if (!params.isArray() || params.size() != 1) { usage.charge(Resource::feeMalformedRPC); - HTTPReply(400, "params unparseable", output, rpcJ); + HTTPReply(400, "params unparsable", output, rpcJ); return; } else @@ -823,7 +824,7 @@ ServerHandler::processRequest( if (!params.isObjectOrNull()) { usage.charge(Resource::feeMalformedRPC); - HTTPReply(400, "params unparseable", output, rpcJ); + HTTPReply(400, "params unparsable", output, rpcJ); return; } } diff --git a/src/xrpld/rpc/detail/Tuning.h b/src/xrpld/rpc/detail/Tuning.h index 5837146b02..c0b939a4fe 100644 --- a/src/xrpld/rpc/detail/Tuning.h +++ b/src/xrpld/rpc/detail/Tuning.h @@ -11,7 +11,7 @@ namespace Tuning { /** Represents RPC limit parameter values that have a min, default and max. */ struct LimitRange { - unsigned int rmin, rdefault, rmax; + unsigned int rmin, rDefault, rmax; }; /** Limits for the account_lines command. */ diff --git a/src/xrpld/rpc/handlers/AccountInfo.cpp b/src/xrpld/rpc/handlers/AccountInfo.cpp index 30d16e3099..61d0658b4a 100644 --- a/src/xrpld/rpc/handlers/AccountInfo.cpp +++ b/src/xrpld/rpc/handlers/AccountInfo.cpp @@ -216,7 +216,7 @@ doAccountInfo(RPC::JsonContext& context) // Documentation states this is returned as part of the account_info // response, but previously the code put it under account_data. We - // can move this to the documentated location from apiVersion 2 + // can move this to the documented location from apiVersion 2 // onwards. if (context.apiVersion == 1) { @@ -262,7 +262,7 @@ doAccountInfo(RPC::JsonContext& context) { XRPL_ASSERT( prevSeqProxy < tx.seqProxy, - "rpple::doAccountInfo : first sorted proxy"); + "doAccountInfo : first sorted proxy"); prevSeqProxy = tx.seqProxy; jvTx[jss::seq] = tx.seqProxy.value(); ++seqCount; @@ -274,7 +274,7 @@ doAccountInfo(RPC::JsonContext& context) { XRPL_ASSERT( prevSeqProxy < tx.seqProxy, - "rpple::doAccountInfo : second sorted proxy"); + "doAccountInfo : second sorted proxy"); prevSeqProxy = tx.seqProxy; jvTx[jss::ticket] = tx.seqProxy.value(); ++ticketCount; diff --git a/src/xrpld/rpc/handlers/AccountTx.cpp b/src/xrpld/rpc/handlers/AccountTx.cpp index 362bdd0eaa..77d388c190 100644 --- a/src/xrpld/rpc/handlers/AccountTx.cpp +++ b/src/xrpld/rpc/handlers/AccountTx.cpp @@ -340,7 +340,7 @@ populateJsonResponse( // LCOV_EXCL_START UNREACHABLE( "xrpl::populateJsonResponse : missing " - "transaction medatata"); + "transaction metadata"); // LCOV_EXCL_STOP } } diff --git a/src/xrpld/rpc/handlers/Subscribe.cpp b/src/xrpld/rpc/handlers/Subscribe.cpp index 018268defb..09d4a54e55 100644 --- a/src/xrpld/rpc/handlers/Subscribe.cpp +++ b/src/xrpld/rpc/handlers/Subscribe.cpp @@ -334,7 +334,7 @@ doSubscribe(RPC::JsonContext& context) field == jss::asks ? reversed(book) : book, takerID ? *takerID : noAccount(), false, - RPC::Tuning::bookOffers.rdefault, + RPC::Tuning::bookOffers.rDefault, jvMarker, jvOffers); From 17565d21d4836a5673cb5ec20c68aa0a376706ed Mon Sep 17 00:00:00 2001 From: Mayukha Vadari Date: Thu, 8 Jan 2026 08:29:59 -0500 Subject: [PATCH 09/30] refactor: Remove unused credentials signature hash prefix (#6186) This change removes the unused credentials signature hash prefix from `HashPrefix.h`. --- include/xrpl/protocol/HashPrefix.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/include/xrpl/protocol/HashPrefix.h b/include/xrpl/protocol/HashPrefix.h index 77529e9212..39e03bf94f 100644 --- a/include/xrpl/protocol/HashPrefix.h +++ b/include/xrpl/protocol/HashPrefix.h @@ -67,9 +67,6 @@ enum class HashPrefix : std::uint32_t { /** Payment Channel Claim */ paymentChannelClaim = detail::make_hash_prefix('C', 'L', 'M'), - /** Credentials signature */ - credential = detail::make_hash_prefix('C', 'R', 'D'), - /** Batch */ batch = detail::make_hash_prefix('B', 'C', 'H'), }; From 510c0d82e9b3f1a4ba1a455bece99502b52613d5 Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Thu, 8 Jan 2026 08:48:39 -0500 Subject: [PATCH 10/30] fix: Reorder Batch Preflight Errors (#6176) This change fixes https://github.com/XRPLF/rippled/issues/6058. --- src/test/app/Batch_test.cpp | 32 +++++++++++++++++++++++++++++++ src/xrpld/app/tx/detail/Batch.cpp | 20 +++++++++---------- 2 files changed, 42 insertions(+), 10 deletions(-) diff --git a/src/test/app/Batch_test.cpp b/src/test/app/Batch_test.cpp index 06cf5dee0c..6fbec52a93 100644 --- a/src/test/app/Batch_test.cpp +++ b/src/test/app/Batch_test.cpp @@ -402,6 +402,38 @@ class Batch_test : public beast::unit_test::suite env.close(); } + // temBAD_FEE: Inner txn with negative fee + { + auto const seq = env.seq(alice); + auto const batchFee = batch::calcBatchFee(env, 0, 2); + auto tx1 = batch::inner(pay(alice, bob, XRP(1)), seq + 1); + tx1[jss::Fee] = "-1"; + env(batch::outer(alice, seq, batchFee, tfAllOrNothing), + tx1, + batch::inner(pay(alice, bob, XRP(2)), seq + 2), + ter(temBAD_FEE)); + env.close(); + } + + // temBAD_FEE: Inner txn with non-integer fee + { + auto const seq = env.seq(alice); + auto const batchFee = batch::calcBatchFee(env, 0, 2); + auto tx1 = batch::inner(pay(alice, bob, XRP(1)), seq + 1); + tx1[jss::Fee] = "1.5"; + try + { + env(batch::outer(alice, seq, batchFee, tfAllOrNothing), + tx1, + batch::inner(pay(alice, bob, XRP(2)), seq + 2)); + fail("Expected parse_error for fractional fee"); + } + catch (jtx::parse_error const&) + { + BEAST_EXPECT(true); + } + } + // temSEQ_AND_TICKET: Batch: inner txn cannot have both Sequence // and TicketSequence. { diff --git a/src/xrpld/app/tx/detail/Batch.cpp b/src/xrpld/app/tx/detail/Batch.cpp index 277bd4e3b7..a26bc52880 100644 --- a/src/xrpld/app/tx/detail/Batch.cpp +++ b/src/xrpld/app/tx/detail/Batch.cpp @@ -305,6 +305,16 @@ Batch::preflight(PreflightContext const& ctx) } } + // Check that the Fee is native asset (XRP) and zero + if (auto const fee = stx.getFieldAmount(sfFee); + !fee.native() || fee.xrp() != beast::zero) + { + JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: " + << "inner txn must have a fee of 0. " + << "txID: " << hash; + return temBAD_FEE; + } + auto const innerAccount = stx.getAccountID(sfAccount); if (auto const preflightResult = xrpl::preflight( ctx.app, ctx.rules, parentBatchId, stx, tapBATCH, ctx.j); @@ -317,16 +327,6 @@ Batch::preflight(PreflightContext const& ctx) return temINVALID_INNER_BATCH; } - // Check that the fee is zero - if (auto const fee = stx.getFieldAmount(sfFee); - !fee.native() || fee.xrp() != beast::zero) - { - JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: " - << "inner txn must have a fee of 0. " - << "txID: " << hash; - return temBAD_FEE; - } - // Check that Sequence and TicketSequence are not both present if (stx.isFieldPresent(sfTicketSequence) && stx.getFieldU32(sfSequence) != 0) From 53aa5ca90314b6a6ba7ebb43e20a40c09b6ffe60 Mon Sep 17 00:00:00 2001 From: Mayukha Vadari Date: Thu, 8 Jan 2026 10:34:49 -0500 Subject: [PATCH 11/30] refactor: Fix typos, enable cspell pre-commit (#5719) This change fixes the last of the spelling issues, and enables the pre-commit (and CI) check for spelling. There are no functionality changes, but it does rename some enum values. --- .config/cspell.config.yaml | 4 +- .gitattributes | 1 + .github/scripts/levelization/README.md | 2 +- .github/scripts/levelization/generate.sh | 6 +- .github/scripts/rename/README.md | 2 +- .github/scripts/rename/config.sh | 2 +- .github/scripts/rename/copyright.sh | 12 +- .gitignore | 1 + .pre-commit-config.yaml | 28 ++-- include/xrpl/proto/xrpl.proto | 6 +- include/xrpl/protocol/Permissions.h | 2 +- .../xrpl/protocol/detail/transactions.macro | 152 +++++++++--------- src/libxrpl/protocol/Permissions.cpp | 6 +- src/test/app/ValidatorList_test.cpp | 5 +- src/test/overlay/compression_test.cpp | 4 +- .../ledger/detail/LedgerReplayMsgHandler.cpp | 2 +- src/xrpld/app/misc/detail/ValidatorList.cpp | 9 +- src/xrpld/app/tx/detail/InvariantCheck.cpp | 6 +- src/xrpld/overlay/detail/Message.cpp | 4 +- src/xrpld/overlay/detail/ProtocolMessage.h | 8 +- src/xrpld/overlay/detail/TrafficCount.cpp | 4 +- 21 files changed, 136 insertions(+), 130 deletions(-) diff --git a/.config/cspell.config.yaml b/.config/cspell.config.yaml index 969720a11d..0cac82807d 100644 --- a/.config/cspell.config.yaml +++ b/.config/cspell.config.yaml @@ -83,7 +83,6 @@ words: - doxyfile - dxrpl - endmacro - - endpointv - exceptioned - Falco - finalizers @@ -92,6 +91,7 @@ words: - funclets - gcov - gcovr + - ghead - Gnutella - gpgcheck - gpgkey @@ -221,6 +221,8 @@ words: - takergets - takerpays - ters + - TMEndpointv2 + - trixie - tx - txid - txids diff --git a/.gitattributes b/.gitattributes index bdfc989327..7a066d0967 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,5 +1,6 @@ # Set default behaviour, in case users don't have core.autocrlf set. #* text=auto +# cspell: disable # Visual Studio *.sln text eol=crlf diff --git a/.github/scripts/levelization/README.md b/.github/scripts/levelization/README.md index c8954b900e..c3e6ffa4a8 100644 --- a/.github/scripts/levelization/README.md +++ b/.github/scripts/levelization/README.md @@ -84,7 +84,7 @@ It generates many files of [results](results): to the destination module, de-duped, and with frequency counts. - `includes/`: A directory where each file represents a module and contains a list of modules and counts that the module _includes_. -- `includedby/`: Similar to `includes/`, but the other way around. Each +- `included_by/`: Similar to `includes/`, but the other way around. Each file represents a module and contains a list of modules and counts that _include_ the module. - [`loops.txt`](results/loops.txt): A list of direct loops detected diff --git a/.github/scripts/levelization/generate.sh b/.github/scripts/levelization/generate.sh index 775ddf789f..d700c7a206 100755 --- a/.github/scripts/levelization/generate.sh +++ b/.github/scripts/levelization/generate.sh @@ -29,7 +29,7 @@ pushd results oldifs=${IFS} IFS=: mkdir includes -mkdir includedby +mkdir included_by echo Build levelization paths exec 3< ${includes} # open rawincludes.txt for input while read -r -u 3 file include @@ -59,7 +59,7 @@ do echo $level $includelevel | tee -a paths.txt fi done -echo Sort and dedup paths +echo Sort and deduplicate paths sort -ds paths.txt | uniq -c | tee sortedpaths.txt mv sortedpaths.txt paths.txt exec 3>&- #close fd 3 @@ -71,7 +71,7 @@ exec 4&- #close fd 4 diff --git a/.github/scripts/rename/README.md b/.github/scripts/rename/README.md index 8336f23bec..cc004a335f 100644 --- a/.github/scripts/rename/README.md +++ b/.github/scripts/rename/README.md @@ -19,7 +19,7 @@ run from the repository root. 1. `.github/scripts/rename/definitions.sh`: This script will rename all definitions, such as include guards, from `RIPPLE_XXX` and `RIPPLED_XXX` to `XRPL_XXX`. -2. `.github/scripts/rename/copyright.sh`: This script will remove superflous +2. `.github/scripts/rename/copyright.sh`: This script will remove superfluous copyright notices. 3. `.github/scripts/rename/cmake.sh`: This script will rename all CMake files from `RippleXXX.cmake` or `RippledXXX.cmake` to `XrplXXX.cmake`, and any diff --git a/.github/scripts/rename/config.sh b/.github/scripts/rename/config.sh index 7f36e8fd21..f6e914f502 100755 --- a/.github/scripts/rename/config.sh +++ b/.github/scripts/rename/config.sh @@ -56,7 +56,7 @@ for DIRECTORY in "${DIRECTORIES[@]}"; do 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 +${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 diff --git a/.github/scripts/rename/copyright.sh b/.github/scripts/rename/copyright.sh index 41dbdc1f94..c5a9fb2cd3 100755 --- a/.github/scripts/rename/copyright.sh +++ b/.github/scripts/rename/copyright.sh @@ -50,11 +50,11 @@ for DIRECTORY in "${DIRECTORIES[@]}"; do # 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}" + ${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}" + ${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 @@ -83,16 +83,16 @@ 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 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 + 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 + 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 + 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 + 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/.gitignore b/.gitignore index c4e81408bb..b899cf8436 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ # .gitignore +# cspell: disable # Macintosh Desktop Services Store files. .DS_Store diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4a1dc159dc..00bec32ed6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -36,20 +36,20 @@ repos: hooks: - id: black - # - repo: https://github.com/streetsidesoftware/cspell-cli - # rev: v9.2.0 - # hooks: - # - id: cspell # Spell check changed files - # - id: cspell # Spell check the commit message - # name: check commit message spelling - # args: - # - --no-must-find-files - # - --no-progress - # - --no-summary - # - --files - # - .git/COMMIT_EDITMSG - # stages: [commit-msg] - # always_run: true # This might not be necessary. + - repo: https://github.com/streetsidesoftware/cspell-cli + rev: v9.2.0 + hooks: + - id: cspell # Spell check changed files + exclude: .config/cspell.config.yaml + - id: cspell # Spell check the commit message + name: check commit message spelling + args: + - --no-must-find-files + - --no-progress + - --no-summary + - --files + - .git/COMMIT_EDITMSG + stages: [commit-msg] exclude: | (?x)^( diff --git a/include/xrpl/proto/xrpl.proto b/include/xrpl/proto/xrpl.proto index f93ebbc72c..613ef70a1f 100644 --- a/include/xrpl/proto/xrpl.proto +++ b/include/xrpl/proto/xrpl.proto @@ -17,9 +17,9 @@ enum MessageType { mtHAVE_SET = 35; mtVALIDATION = 41; mtGET_OBJECTS = 42; - mtVALIDATORLIST = 54; + mtVALIDATOR_LIST = 54; mtSQUELCH = 55; - mtVALIDATORLISTCOLLECTION = 56; + mtVALIDATOR_LIST_COLLECTION = 56; mtPROOF_PATH_REQ = 57; mtPROOF_PATH_RESPONSE = 58; mtREPLAY_DELTA_REQ = 59; @@ -308,7 +308,7 @@ message TMSquelch { } enum TMLedgerMapType { - lmTRANASCTION = 1; // transaction map + lmTRANSACTION = 1; // transaction map lmACCOUNT_STATE = 2; // account state map } diff --git a/include/xrpl/protocol/Permissions.h b/include/xrpl/protocol/Permissions.h index 319aeb1c28..74f539a3bf 100644 --- a/include/xrpl/protocol/Permissions.h +++ b/include/xrpl/protocol/Permissions.h @@ -29,7 +29,7 @@ enum GranularPermissionType : std::uint32_t { #pragma pop_macro("PERMISSION") }; -enum Delegation { delegatable, notDelegatable }; +enum Delegation { delegable, notDelegable }; class Permission { diff --git a/include/xrpl/protocol/detail/transactions.macro b/include/xrpl/protocol/detail/transactions.macro index 6d2d833440..bc7eefedb5 100644 --- a/include/xrpl/protocol/detail/transactions.macro +++ b/include/xrpl/protocol/detail/transactions.macro @@ -3,7 +3,7 @@ #endif /** - * TRANSACTION(tag, value, name, delegatable, amendments, privileges, fields) + * TRANSACTION(tag, value, name, delegable, amendments, privileges, fields) * * To ease maintenance, you may replace any unneeded values with "..." * e.g. #define TRANSACTION(tag, value, name, ...) @@ -25,7 +25,7 @@ # include #endif TRANSACTION(ttPAYMENT, 0, Payment, - Delegation::delegatable, + Delegation::delegable, uint256{}, createAcct, ({ @@ -45,7 +45,7 @@ TRANSACTION(ttPAYMENT, 0, Payment, # include #endif TRANSACTION(ttESCROW_CREATE, 1, EscrowCreate, - Delegation::delegatable, + Delegation::delegable, uint256{}, noPriv, ({ @@ -59,7 +59,7 @@ TRANSACTION(ttESCROW_CREATE, 1, EscrowCreate, /** This transaction type completes an existing escrow. */ TRANSACTION(ttESCROW_FINISH, 2, EscrowFinish, - Delegation::delegatable, + Delegation::delegable, uint256{}, noPriv, ({ @@ -76,7 +76,7 @@ TRANSACTION(ttESCROW_FINISH, 2, EscrowFinish, # include #endif TRANSACTION(ttACCOUNT_SET, 3, AccountSet, - Delegation::notDelegatable, + Delegation::notDelegable, uint256{}, noPriv, ({ @@ -97,7 +97,7 @@ TRANSACTION(ttACCOUNT_SET, 3, AccountSet, # include #endif TRANSACTION(ttESCROW_CANCEL, 4, EscrowCancel, - Delegation::delegatable, + Delegation::delegable, uint256{}, noPriv, ({ @@ -110,7 +110,7 @@ TRANSACTION(ttESCROW_CANCEL, 4, EscrowCancel, # include #endif TRANSACTION(ttREGULAR_KEY_SET, 5, SetRegularKey, - Delegation::notDelegatable, + Delegation::notDelegable, uint256{}, noPriv, ({ @@ -124,7 +124,7 @@ TRANSACTION(ttREGULAR_KEY_SET, 5, SetRegularKey, # include #endif TRANSACTION(ttOFFER_CREATE, 7, OfferCreate, - Delegation::delegatable, + Delegation::delegable, uint256{}, noPriv, ({ @@ -140,7 +140,7 @@ TRANSACTION(ttOFFER_CREATE, 7, OfferCreate, # include #endif TRANSACTION(ttOFFER_CANCEL, 8, OfferCancel, - Delegation::delegatable, + Delegation::delegable, uint256{}, noPriv, ({ @@ -154,7 +154,7 @@ TRANSACTION(ttOFFER_CANCEL, 8, OfferCancel, # include #endif TRANSACTION(ttTICKET_CREATE, 10, TicketCreate, - Delegation::delegatable, + Delegation::delegable, uint256{}, noPriv, ({ @@ -170,7 +170,7 @@ TRANSACTION(ttTICKET_CREATE, 10, TicketCreate, # include #endif TRANSACTION(ttSIGNER_LIST_SET, 12, SignerListSet, - Delegation::notDelegatable, + Delegation::notDelegable, uint256{}, noPriv, ({ @@ -183,7 +183,7 @@ TRANSACTION(ttSIGNER_LIST_SET, 12, SignerListSet, # include #endif TRANSACTION(ttPAYCHAN_CREATE, 13, PaymentChannelCreate, - Delegation::delegatable, + Delegation::delegable, uint256{}, noPriv, ({ @@ -197,7 +197,7 @@ TRANSACTION(ttPAYCHAN_CREATE, 13, PaymentChannelCreate, /** This transaction type funds an existing unidirectional XRP payment channel. */ TRANSACTION(ttPAYCHAN_FUND, 14, PaymentChannelFund, - Delegation::delegatable, + Delegation::delegable, uint256{}, noPriv, ({ @@ -208,7 +208,7 @@ TRANSACTION(ttPAYCHAN_FUND, 14, PaymentChannelFund, /** This transaction type submits a claim against an existing unidirectional payment channel. */ TRANSACTION(ttPAYCHAN_CLAIM, 15, PaymentChannelClaim, - Delegation::delegatable, + Delegation::delegable, uint256{}, noPriv, ({ @@ -225,7 +225,7 @@ TRANSACTION(ttPAYCHAN_CLAIM, 15, PaymentChannelClaim, # include #endif TRANSACTION(ttCHECK_CREATE, 16, CheckCreate, - Delegation::delegatable, + Delegation::delegable, uint256{}, noPriv, ({ @@ -241,7 +241,7 @@ TRANSACTION(ttCHECK_CREATE, 16, CheckCreate, # include #endif TRANSACTION(ttCHECK_CASH, 17, CheckCash, - Delegation::delegatable, + Delegation::delegable, uint256{}, noPriv, ({ @@ -255,7 +255,7 @@ TRANSACTION(ttCHECK_CASH, 17, CheckCash, # include #endif TRANSACTION(ttCHECK_CANCEL, 18, CheckCancel, - Delegation::delegatable, + Delegation::delegable, uint256{}, noPriv, ({ @@ -267,7 +267,7 @@ TRANSACTION(ttCHECK_CANCEL, 18, CheckCancel, # include #endif TRANSACTION(ttDEPOSIT_PREAUTH, 19, DepositPreauth, - Delegation::delegatable, + Delegation::delegable, uint256{}, noPriv, ({ @@ -282,7 +282,7 @@ TRANSACTION(ttDEPOSIT_PREAUTH, 19, DepositPreauth, # include #endif TRANSACTION(ttTRUST_SET, 20, TrustSet, - Delegation::delegatable, + Delegation::delegable, uint256{}, noPriv, ({ @@ -296,7 +296,7 @@ TRANSACTION(ttTRUST_SET, 20, TrustSet, # include #endif TRANSACTION(ttACCOUNT_DELETE, 21, AccountDelete, - Delegation::notDelegatable, + Delegation::notDelegable, uint256{}, mustDeleteAcct, ({ @@ -312,7 +312,7 @@ TRANSACTION(ttACCOUNT_DELETE, 21, AccountDelete, # include #endif TRANSACTION(ttNFTOKEN_MINT, 25, NFTokenMint, - Delegation::delegatable, + Delegation::delegable, uint256{}, changeNFTCounts, ({ @@ -330,7 +330,7 @@ TRANSACTION(ttNFTOKEN_MINT, 25, NFTokenMint, # include #endif TRANSACTION(ttNFTOKEN_BURN, 26, NFTokenBurn, - Delegation::delegatable, + Delegation::delegable, uint256{}, changeNFTCounts, ({ @@ -343,7 +343,7 @@ TRANSACTION(ttNFTOKEN_BURN, 26, NFTokenBurn, # include #endif TRANSACTION(ttNFTOKEN_CREATE_OFFER, 27, NFTokenCreateOffer, - Delegation::delegatable, + Delegation::delegable, uint256{}, noPriv, ({ @@ -359,7 +359,7 @@ TRANSACTION(ttNFTOKEN_CREATE_OFFER, 27, NFTokenCreateOffer, # include #endif TRANSACTION(ttNFTOKEN_CANCEL_OFFER, 28, NFTokenCancelOffer, - Delegation::delegatable, + Delegation::delegable, uint256{}, noPriv, ({ @@ -371,7 +371,7 @@ TRANSACTION(ttNFTOKEN_CANCEL_OFFER, 28, NFTokenCancelOffer, # include #endif TRANSACTION(ttNFTOKEN_ACCEPT_OFFER, 29, NFTokenAcceptOffer, - Delegation::delegatable, + Delegation::delegable, uint256{}, noPriv, ({ @@ -385,7 +385,7 @@ TRANSACTION(ttNFTOKEN_ACCEPT_OFFER, 29, NFTokenAcceptOffer, # include #endif TRANSACTION(ttCLAWBACK, 30, Clawback, - Delegation::delegatable, + Delegation::delegable, featureClawback, noPriv, ({ @@ -398,7 +398,7 @@ TRANSACTION(ttCLAWBACK, 30, Clawback, # include #endif TRANSACTION(ttAMM_CLAWBACK, 31, AMMClawback, - Delegation::delegatable, + Delegation::delegable, featureAMMClawback, mayDeleteAcct | overrideFreeze, ({ @@ -413,7 +413,7 @@ TRANSACTION(ttAMM_CLAWBACK, 31, AMMClawback, # include #endif TRANSACTION(ttAMM_CREATE, 35, AMMCreate, - Delegation::delegatable, + Delegation::delegable, featureAMM, createPseudoAcct, ({ @@ -427,7 +427,7 @@ TRANSACTION(ttAMM_CREATE, 35, AMMCreate, # include #endif TRANSACTION(ttAMM_DEPOSIT, 36, AMMDeposit, - Delegation::delegatable, + Delegation::delegable, featureAMM, noPriv, ({ @@ -445,7 +445,7 @@ TRANSACTION(ttAMM_DEPOSIT, 36, AMMDeposit, # include #endif TRANSACTION(ttAMM_WITHDRAW, 37, AMMWithdraw, - Delegation::delegatable, + Delegation::delegable, featureAMM, mayDeleteAcct, ({ @@ -462,7 +462,7 @@ TRANSACTION(ttAMM_WITHDRAW, 37, AMMWithdraw, # include #endif TRANSACTION(ttAMM_VOTE, 38, AMMVote, - Delegation::delegatable, + Delegation::delegable, featureAMM, noPriv, ({ @@ -476,7 +476,7 @@ TRANSACTION(ttAMM_VOTE, 38, AMMVote, # include #endif TRANSACTION(ttAMM_BID, 39, AMMBid, - Delegation::delegatable, + Delegation::delegable, featureAMM, noPriv, ({ @@ -492,7 +492,7 @@ TRANSACTION(ttAMM_BID, 39, AMMBid, # include #endif TRANSACTION(ttAMM_DELETE, 40, AMMDelete, - Delegation::delegatable, + Delegation::delegable, featureAMM, mustDeleteAcct, ({ @@ -505,7 +505,7 @@ TRANSACTION(ttAMM_DELETE, 40, AMMDelete, # include #endif TRANSACTION(ttXCHAIN_CREATE_CLAIM_ID, 41, XChainCreateClaimID, - Delegation::delegatable, + Delegation::delegable, featureXChainBridge, noPriv, ({ @@ -516,7 +516,7 @@ TRANSACTION(ttXCHAIN_CREATE_CLAIM_ID, 41, XChainCreateClaimID, /** This transactions initiates a crosschain transaction */ TRANSACTION(ttXCHAIN_COMMIT, 42, XChainCommit, - Delegation::delegatable, + Delegation::delegable, featureXChainBridge, noPriv, ({ @@ -528,7 +528,7 @@ TRANSACTION(ttXCHAIN_COMMIT, 42, XChainCommit, /** This transaction completes a crosschain transaction */ TRANSACTION(ttXCHAIN_CLAIM, 43, XChainClaim, - Delegation::delegatable, + Delegation::delegable, featureXChainBridge, noPriv, ({ @@ -541,7 +541,7 @@ TRANSACTION(ttXCHAIN_CLAIM, 43, XChainClaim, /** This transaction initiates a crosschain account create transaction */ TRANSACTION(ttXCHAIN_ACCOUNT_CREATE_COMMIT, 44, XChainAccountCreateCommit, - Delegation::delegatable, + Delegation::delegable, featureXChainBridge, noPriv, ({ @@ -553,7 +553,7 @@ TRANSACTION(ttXCHAIN_ACCOUNT_CREATE_COMMIT, 44, XChainAccountCreateCommit, /** This transaction adds an attestation to a claim */ TRANSACTION(ttXCHAIN_ADD_CLAIM_ATTESTATION, 45, XChainAddClaimAttestation, - Delegation::delegatable, + Delegation::delegable, featureXChainBridge, createAcct, ({ @@ -574,7 +574,7 @@ TRANSACTION(ttXCHAIN_ADD_CLAIM_ATTESTATION, 45, XChainAddClaimAttestation, /** This transaction adds an attestation to an account */ TRANSACTION(ttXCHAIN_ADD_ACCOUNT_CREATE_ATTESTATION, 46, XChainAddAccountCreateAttestation, - Delegation::delegatable, + Delegation::delegable, featureXChainBridge, createAcct, ({ @@ -595,7 +595,7 @@ TRANSACTION(ttXCHAIN_ADD_ACCOUNT_CREATE_ATTESTATION, 46, /** This transaction modifies a sidechain */ TRANSACTION(ttXCHAIN_MODIFY_BRIDGE, 47, XChainModifyBridge, - Delegation::delegatable, + Delegation::delegable, featureXChainBridge, noPriv, ({ @@ -606,7 +606,7 @@ TRANSACTION(ttXCHAIN_MODIFY_BRIDGE, 47, XChainModifyBridge, /** This transactions creates a sidechain */ TRANSACTION(ttXCHAIN_CREATE_BRIDGE, 48, XChainCreateBridge, - Delegation::delegatable, + Delegation::delegable, featureXChainBridge, noPriv, ({ @@ -620,7 +620,7 @@ TRANSACTION(ttXCHAIN_CREATE_BRIDGE, 48, XChainCreateBridge, # include #endif TRANSACTION(ttDID_SET, 49, DIDSet, - Delegation::delegatable, + Delegation::delegable, featureDID, noPriv, ({ @@ -631,7 +631,7 @@ TRANSACTION(ttDID_SET, 49, DIDSet, /** This transaction type deletes a DID */ TRANSACTION(ttDID_DELETE, 50, DIDDelete, - Delegation::delegatable, + Delegation::delegable, featureDID, noPriv, ({})) @@ -641,7 +641,7 @@ TRANSACTION(ttDID_DELETE, 50, DIDDelete, # include #endif TRANSACTION(ttORACLE_SET, 51, OracleSet, - Delegation::delegatable, + Delegation::delegable, featurePriceOracle, noPriv, ({ @@ -658,7 +658,7 @@ TRANSACTION(ttORACLE_SET, 51, OracleSet, # include #endif TRANSACTION(ttORACLE_DELETE, 52, OracleDelete, - Delegation::delegatable, + Delegation::delegable, featurePriceOracle, noPriv, ({ @@ -670,7 +670,7 @@ TRANSACTION(ttORACLE_DELETE, 52, OracleDelete, # include #endif TRANSACTION(ttLEDGER_STATE_FIX, 53, LedgerStateFix, - Delegation::delegatable, + Delegation::delegable, fixNFTokenPageLinks, noPriv, ({ @@ -683,7 +683,7 @@ TRANSACTION(ttLEDGER_STATE_FIX, 53, LedgerStateFix, # include #endif TRANSACTION(ttMPTOKEN_ISSUANCE_CREATE, 54, MPTokenIssuanceCreate, - Delegation::delegatable, + Delegation::delegable, featureMPTokensV1, createMPTIssuance, ({ @@ -700,7 +700,7 @@ TRANSACTION(ttMPTOKEN_ISSUANCE_CREATE, 54, MPTokenIssuanceCreate, # include #endif TRANSACTION(ttMPTOKEN_ISSUANCE_DESTROY, 55, MPTokenIssuanceDestroy, - Delegation::delegatable, + Delegation::delegable, featureMPTokensV1, destroyMPTIssuance, ({ @@ -712,7 +712,7 @@ TRANSACTION(ttMPTOKEN_ISSUANCE_DESTROY, 55, MPTokenIssuanceDestroy, # include #endif TRANSACTION(ttMPTOKEN_ISSUANCE_SET, 56, MPTokenIssuanceSet, - Delegation::delegatable, + Delegation::delegable, featureMPTokensV1, noPriv, ({ @@ -729,7 +729,7 @@ TRANSACTION(ttMPTOKEN_ISSUANCE_SET, 56, MPTokenIssuanceSet, # include #endif TRANSACTION(ttMPTOKEN_AUTHORIZE, 57, MPTokenAuthorize, - Delegation::delegatable, + Delegation::delegable, featureMPTokensV1, mustAuthorizeMPT, ({ @@ -742,7 +742,7 @@ TRANSACTION(ttMPTOKEN_AUTHORIZE, 57, MPTokenAuthorize, # include #endif TRANSACTION(ttCREDENTIAL_CREATE, 58, CredentialCreate, - Delegation::delegatable, + Delegation::delegable, featureCredentials, noPriv, ({ @@ -754,7 +754,7 @@ TRANSACTION(ttCREDENTIAL_CREATE, 58, CredentialCreate, /** This transaction type accept an Credential object */ TRANSACTION(ttCREDENTIAL_ACCEPT, 59, CredentialAccept, - Delegation::delegatable, + Delegation::delegable, featureCredentials, noPriv, ({ @@ -764,7 +764,7 @@ TRANSACTION(ttCREDENTIAL_ACCEPT, 59, CredentialAccept, /** This transaction type delete an Credential object */ TRANSACTION(ttCREDENTIAL_DELETE, 60, CredentialDelete, - Delegation::delegatable, + Delegation::delegable, featureCredentials, noPriv, ({ @@ -778,7 +778,7 @@ TRANSACTION(ttCREDENTIAL_DELETE, 60, CredentialDelete, # include #endif TRANSACTION(ttNFTOKEN_MODIFY, 61, NFTokenModify, - Delegation::delegatable, + Delegation::delegable, featureDynamicNFT, noPriv, ({ @@ -792,7 +792,7 @@ TRANSACTION(ttNFTOKEN_MODIFY, 61, NFTokenModify, # include #endif TRANSACTION(ttPERMISSIONED_DOMAIN_SET, 62, PermissionedDomainSet, - Delegation::delegatable, + Delegation::delegable, featurePermissionedDomains, noPriv, ({ @@ -805,7 +805,7 @@ TRANSACTION(ttPERMISSIONED_DOMAIN_SET, 62, PermissionedDomainSet, # include #endif TRANSACTION(ttPERMISSIONED_DOMAIN_DELETE, 63, PermissionedDomainDelete, - Delegation::delegatable, + Delegation::delegable, featurePermissionedDomains, noPriv, ({ @@ -817,7 +817,7 @@ TRANSACTION(ttPERMISSIONED_DOMAIN_DELETE, 63, PermissionedDomainDelete, # include #endif TRANSACTION(ttDELEGATE_SET, 64, DelegateSet, - Delegation::notDelegatable, + Delegation::notDelegable, featurePermissionDelegationV1_1, noPriv, ({ @@ -830,7 +830,7 @@ TRANSACTION(ttDELEGATE_SET, 64, DelegateSet, # include #endif TRANSACTION(ttVAULT_CREATE, 65, VaultCreate, - Delegation::delegatable, + Delegation::delegable, featureSingleAssetVault, createPseudoAcct | createMPTIssuance | mustModifyVault, ({ @@ -848,7 +848,7 @@ TRANSACTION(ttVAULT_CREATE, 65, VaultCreate, # include #endif TRANSACTION(ttVAULT_SET, 66, VaultSet, - Delegation::delegatable, + Delegation::delegable, featureSingleAssetVault, mustModifyVault, ({ @@ -863,7 +863,7 @@ TRANSACTION(ttVAULT_SET, 66, VaultSet, # include #endif TRANSACTION(ttVAULT_DELETE, 67, VaultDelete, - Delegation::delegatable, + Delegation::delegable, featureSingleAssetVault, mustDeleteAcct | destroyMPTIssuance | mustModifyVault, ({ @@ -875,7 +875,7 @@ TRANSACTION(ttVAULT_DELETE, 67, VaultDelete, # include #endif TRANSACTION(ttVAULT_DEPOSIT, 68, VaultDeposit, - Delegation::delegatable, + Delegation::delegable, featureSingleAssetVault, mayAuthorizeMPT | mustModifyVault, ({ @@ -888,7 +888,7 @@ TRANSACTION(ttVAULT_DEPOSIT, 68, VaultDeposit, # include #endif TRANSACTION(ttVAULT_WITHDRAW, 69, VaultWithdraw, - Delegation::delegatable, + Delegation::delegable, featureSingleAssetVault, mayDeleteMPT | mayAuthorizeMPT | mustModifyVault, ({ @@ -903,7 +903,7 @@ TRANSACTION(ttVAULT_WITHDRAW, 69, VaultWithdraw, # include #endif TRANSACTION(ttVAULT_CLAWBACK, 70, VaultClawback, - Delegation::delegatable, + Delegation::delegable, featureSingleAssetVault, mayDeleteMPT | mustModifyVault, ({ @@ -917,7 +917,7 @@ TRANSACTION(ttVAULT_CLAWBACK, 70, VaultClawback, # include #endif TRANSACTION(ttBATCH, 71, Batch, - Delegation::notDelegatable, + Delegation::notDelegable, featureBatch, noPriv, ({ @@ -932,7 +932,7 @@ TRANSACTION(ttBATCH, 71, Batch, # include #endif TRANSACTION(ttLOAN_BROKER_SET, 74, LoanBrokerSet, - Delegation::delegatable, + Delegation::delegable, featureLendingProtocol, createPseudoAcct | mayAuthorizeMPT, ({ {sfVaultID, soeREQUIRED}, @@ -949,7 +949,7 @@ TRANSACTION(ttLOAN_BROKER_SET, 74, LoanBrokerSet, # include #endif TRANSACTION(ttLOAN_BROKER_DELETE, 75, LoanBrokerDelete, - Delegation::delegatable, + Delegation::delegable, featureLendingProtocol, mustDeleteAcct | mayAuthorizeMPT, ({ {sfLoanBrokerID, soeREQUIRED}, @@ -960,7 +960,7 @@ TRANSACTION(ttLOAN_BROKER_DELETE, 75, LoanBrokerDelete, # include #endif TRANSACTION(ttLOAN_BROKER_COVER_DEPOSIT, 76, LoanBrokerCoverDeposit, - Delegation::delegatable, + Delegation::delegable, featureLendingProtocol, noPriv, ({ {sfLoanBrokerID, soeREQUIRED}, @@ -972,7 +972,7 @@ TRANSACTION(ttLOAN_BROKER_COVER_DEPOSIT, 76, LoanBrokerCoverDeposit, # include #endif TRANSACTION(ttLOAN_BROKER_COVER_WITHDRAW, 77, LoanBrokerCoverWithdraw, - Delegation::delegatable, + Delegation::delegable, featureLendingProtocol, mayAuthorizeMPT, ({ {sfLoanBrokerID, soeREQUIRED}, @@ -987,7 +987,7 @@ TRANSACTION(ttLOAN_BROKER_COVER_WITHDRAW, 77, LoanBrokerCoverWithdraw, # include #endif TRANSACTION(ttLOAN_BROKER_COVER_CLAWBACK, 78, LoanBrokerCoverClawback, - Delegation::delegatable, + Delegation::delegable, featureLendingProtocol, noPriv, ({ {sfLoanBrokerID, soeOPTIONAL}, @@ -999,7 +999,7 @@ TRANSACTION(ttLOAN_BROKER_COVER_CLAWBACK, 78, LoanBrokerCoverClawback, # include #endif TRANSACTION(ttLOAN_SET, 80, LoanSet, - Delegation::delegatable, + Delegation::delegable, featureLendingProtocol, mayAuthorizeMPT | mustModifyVault, ({ {sfLoanBrokerID, soeREQUIRED}, @@ -1026,7 +1026,7 @@ TRANSACTION(ttLOAN_SET, 80, LoanSet, # include #endif TRANSACTION(ttLOAN_DELETE, 81, LoanDelete, - Delegation::delegatable, + Delegation::delegable, featureLendingProtocol, noPriv, ({ {sfLoanID, soeREQUIRED}, @@ -1037,7 +1037,7 @@ TRANSACTION(ttLOAN_DELETE, 81, LoanDelete, # include #endif TRANSACTION(ttLOAN_MANAGE, 82, LoanManage, - Delegation::delegatable, + Delegation::delegable, featureLendingProtocol, // All of the LoanManage options will modify the vault, but the // transaction can succeed without options, essentially making it @@ -1051,7 +1051,7 @@ TRANSACTION(ttLOAN_MANAGE, 82, LoanManage, # include #endif TRANSACTION(ttLOAN_PAY, 84, LoanPay, - Delegation::delegatable, + Delegation::delegable, featureLendingProtocol, mayAuthorizeMPT | mustModifyVault, ({ {sfLoanID, soeREQUIRED}, @@ -1066,7 +1066,7 @@ TRANSACTION(ttLOAN_PAY, 84, LoanPay, # include #endif TRANSACTION(ttAMENDMENT, 100, EnableAmendment, - Delegation::notDelegatable, + Delegation::notDelegable, uint256{}, noPriv, ({ @@ -1078,7 +1078,7 @@ TRANSACTION(ttAMENDMENT, 100, EnableAmendment, For details, see: https://xrpl.org/fee-voting.html */ TRANSACTION(ttFEE, 101, SetFee, - Delegation::notDelegatable, + Delegation::notDelegable, uint256{}, noPriv, ({ @@ -1099,7 +1099,7 @@ TRANSACTION(ttFEE, 101, SetFee, For details, see: https://xrpl.org/negative-unl.html */ TRANSACTION(ttUNL_MODIFY, 102, UNLModify, - Delegation::notDelegatable, + Delegation::notDelegable, uint256{}, noPriv, ({ diff --git a/src/libxrpl/protocol/Permissions.cpp b/src/libxrpl/protocol/Permissions.cpp index 55b273e246..082a1792a4 100644 --- a/src/libxrpl/protocol/Permissions.cpp +++ b/src/libxrpl/protocol/Permissions.cpp @@ -11,7 +11,7 @@ Permission::Permission() #pragma push_macro("TRANSACTION") #undef TRANSACTION -#define TRANSACTION(tag, value, name, delegatable, amendment, ...) \ +#define TRANSACTION(tag, value, name, delegable, amendment, ...) \ {value, amendment}, #include @@ -24,7 +24,7 @@ Permission::Permission() #pragma push_macro("TRANSACTION") #undef TRANSACTION -#define TRANSACTION(tag, value, name, delegatable, ...) {value, delegatable}, +#define TRANSACTION(tag, value, name, delegable, ...) {value, delegable}, #include @@ -170,7 +170,7 @@ Permission::isDelegable( !rules.enabled(txFeaturesIt->second)) return false; - if (it->second == Delegation::notDelegatable) + if (it->second == Delegation::notDelegable) return false; return true; diff --git a/src/test/app/ValidatorList_test.cpp b/src/test/app/ValidatorList_test.cpp index 163e4f632c..fa3c836cca 100644 --- a/src/test/app/ValidatorList_test.cpp +++ b/src/test/app/ValidatorList_test.cpp @@ -2370,7 +2370,8 @@ private: &extractHeader](Message& message) { auto [header, buffers] = extractHeader(message); if (BEAST_EXPECT(header) && - BEAST_EXPECT(header->message_type == protocol::mtVALIDATORLIST)) + BEAST_EXPECT( + header->message_type == protocol::mtVALIDATOR_LIST)) { auto const msg = detail::parseMessageContent( @@ -2386,7 +2387,7 @@ private: if (BEAST_EXPECT(header) && BEAST_EXPECT( header->message_type == - protocol::mtVALIDATORLISTCOLLECTION)) + protocol::mtVALIDATOR_LIST_COLLECTION)) { auto const msg = detail::parseMessageContent< protocol::TMValidatorListCollection>( diff --git a/src/test/overlay/compression_test.cpp b/src/test/overlay/compression_test.cpp index 2be69a74c9..f2fe07cb33 100644 --- a/src/test/overlay/compression_test.cpp +++ b/src/test/overlay/compression_test.cpp @@ -436,12 +436,12 @@ public: // 895B doTest( buildValidatorList(), - protocol::mtVALIDATORLIST, + protocol::mtVALIDATOR_LIST, 4, "TMValidatorList"); doTest( buildValidatorListCollection(), - protocol::mtVALIDATORLISTCOLLECTION, + protocol::mtVALIDATOR_LIST_COLLECTION, 4, "TMValidatorListCollection"); } diff --git a/src/xrpld/app/ledger/detail/LedgerReplayMsgHandler.cpp b/src/xrpld/app/ledger/detail/LedgerReplayMsgHandler.cpp index e1eb51fda7..9356f17af0 100644 --- a/src/xrpld/app/ledger/detail/LedgerReplayMsgHandler.cpp +++ b/src/xrpld/app/ledger/detail/LedgerReplayMsgHandler.cpp @@ -53,7 +53,7 @@ LedgerReplayMsgHandler::processProofPathRequest( { case protocol::lmACCOUNT_STATE: return ledger->stateMap().getProofPath(key); - case protocol::lmTRANASCTION: + case protocol::lmTRANSACTION: return ledger->txMap().getProofPath(key); default: // should not be here diff --git a/src/xrpld/app/misc/detail/ValidatorList.cpp b/src/xrpld/app/misc/detail/ValidatorList.cpp index 12ba52fa36..5c1c5e80a1 100644 --- a/src/xrpld/app/misc/detail/ValidatorList.cpp +++ b/src/xrpld/app/misc/detail/ValidatorList.cpp @@ -528,7 +528,7 @@ splitMessageParts( "xrpl::splitMessageParts : maximum message size"); messages.emplace_back( - std::make_shared(smallMsg, protocol::mtVALIDATORLIST), + std::make_shared(smallMsg, protocol::mtVALIDATOR_LIST), sha512Half(smallMsg), 1); return messages.back().numVLs; @@ -555,7 +555,7 @@ splitMessageParts( { messages.emplace_back( std::make_shared( - *smallMsg, protocol::mtVALIDATORLISTCOLLECTION), + *smallMsg, protocol::mtVALIDATOR_LIST_COLLECTION), sha512Half(*smallMsg), smallMsg->blobs_size()); return messages.back().numVLs; @@ -592,7 +592,7 @@ buildValidatorListMessage( "xrpl::buildValidatorListMessage(ValidatorBlobInfo) : maximum " "message size"); messages.emplace_back( - std::make_shared(msg, protocol::mtVALIDATORLIST), + std::make_shared(msg, protocol::mtVALIDATOR_LIST), sha512Half(msg), 1); return 1; @@ -640,7 +640,8 @@ buildValidatorListMessage( else { messages.emplace_back( - std::make_shared(msg, protocol::mtVALIDATORLISTCOLLECTION), + std::make_shared( + msg, protocol::mtVALIDATOR_LIST_COLLECTION), sha512Half(msg), msg.blobs_size()); return messages.back().numVLs; diff --git a/src/xrpld/app/tx/detail/InvariantCheck.cpp b/src/xrpld/app/tx/detail/InvariantCheck.cpp index eb751c817f..0b237905e8 100644 --- a/src/xrpld/app/tx/detail/InvariantCheck.cpp +++ b/src/xrpld/app/tx/detail/InvariantCheck.cpp @@ -84,9 +84,9 @@ operator|(Privilege lhs, Privilege rhs) #pragma push_macro("TRANSACTION") #undef TRANSACTION -#define TRANSACTION(tag, value, name, delegatable, amendment, privileges, ...) \ - case tag: { \ - return (privileges) & priv; \ +#define TRANSACTION(tag, value, name, delegable, amendment, privileges, ...) \ + case tag: { \ + return (privileges) & priv; \ } bool diff --git a/src/xrpld/overlay/detail/Message.cpp b/src/xrpld/overlay/detail/Message.cpp index eb7b88894a..d03cd5e0da 100644 --- a/src/xrpld/overlay/detail/Message.cpp +++ b/src/xrpld/overlay/detail/Message.cpp @@ -68,8 +68,8 @@ Message::compress() case protocol::mtGET_LEDGER: case protocol::mtLEDGER_DATA: case protocol::mtGET_OBJECTS: - case protocol::mtVALIDATORLIST: - case protocol::mtVALIDATORLISTCOLLECTION: + case protocol::mtVALIDATOR_LIST: + case protocol::mtVALIDATOR_LIST_COLLECTION: case protocol::mtREPLAY_DELTA_RESPONSE: case protocol::mtTRANSACTIONS: return true; diff --git a/src/xrpld/overlay/detail/ProtocolMessage.h b/src/xrpld/overlay/detail/ProtocolMessage.h index 51dfc1ac7c..77ad368ccd 100644 --- a/src/xrpld/overlay/detail/ProtocolMessage.h +++ b/src/xrpld/overlay/detail/ProtocolMessage.h @@ -63,9 +63,9 @@ protocolMessageName(int type) return "status"; case protocol::mtHAVE_SET: return "have_set"; - case protocol::mtVALIDATORLIST: + case protocol::mtVALIDATOR_LIST: return "validator_list"; - case protocol::mtVALIDATORLISTCOLLECTION: + case protocol::mtVALIDATOR_LIST_COLLECTION: return "validator_list_collection"; case protocol::mtVALIDATION: return "validation"; @@ -411,11 +411,11 @@ invokeProtocolMessage( success = detail::invoke( *header, buffers, handler); break; - case protocol::mtVALIDATORLIST: + case protocol::mtVALIDATOR_LIST: success = detail::invoke( *header, buffers, handler); break; - case protocol::mtVALIDATORLISTCOLLECTION: + case protocol::mtVALIDATOR_LIST_COLLECTION: success = detail::invoke( *header, buffers, handler); break; diff --git a/src/xrpld/overlay/detail/TrafficCount.cpp b/src/xrpld/overlay/detail/TrafficCount.cpp index 6fb397ea71..21b4ba78a4 100644 --- a/src/xrpld/overlay/detail/TrafficCount.cpp +++ b/src/xrpld/overlay/detail/TrafficCount.cpp @@ -9,8 +9,8 @@ std::unordered_map const {protocol::mtMANIFESTS, TrafficCount::category::manifests}, {protocol::mtENDPOINTS, TrafficCount::category::overlay}, {protocol::mtTRANSACTION, TrafficCount::category::transaction}, - {protocol::mtVALIDATORLIST, TrafficCount::category::validatorlist}, - {protocol::mtVALIDATORLISTCOLLECTION, + {protocol::mtVALIDATOR_LIST, TrafficCount::category::validatorlist}, + {protocol::mtVALIDATOR_LIST_COLLECTION, TrafficCount::category::validatorlist}, {protocol::mtVALIDATION, TrafficCount::category::validation}, {protocol::mtPROPOSE_LEDGER, TrafficCount::category::proposal}, From e1d97bea12c6ebc3dc5cda19985c3c28987fb1fe Mon Sep 17 00:00:00 2001 From: Bart Date: Thu, 8 Jan 2026 15:02:59 -0500 Subject: [PATCH 12/30] ci: Use updated prepare-runner in actions and worfklows (#6188) This change updates the XRPLF pre-commit workflow and prepare-runner action to their latest versions. For naming consistency the prepare-runner action changed the disable_ccache variable into enable_ccache, which matches our naming. --- .github/workflows/pre-commit.yml | 2 +- .github/workflows/reusable-build-test-config.yml | 4 ++-- .github/workflows/upload-conan-deps.yml | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index d0a657dd7e..41e82fb6bb 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -9,7 +9,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@34790936fae4c6c751f62ec8c06696f9c1a5753a + uses: XRPLF/actions/.github/workflows/pre-commit.yml@5ca417783f0312ab26d6f48b85c78edf1de99bbd with: runs_on: ubuntu-latest container: '{ "image": "ghcr.io/xrplf/ci/tools-rippled-pre-commit:sha-a8c7be1" }' diff --git a/.github/workflows/reusable-build-test-config.yml b/.github/workflows/reusable-build-test-config.yml index 575984162e..fc80bbd216 100644 --- a/.github/workflows/reusable-build-test-config.yml +++ b/.github/workflows/reusable-build-test-config.yml @@ -100,9 +100,9 @@ jobs: uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 - name: Prepare runner - uses: XRPLF/actions/prepare-runner@2ece4ec6ab7de266859a6f053571425b2bd684b6 + uses: XRPLF/actions/prepare-runner@65da1c59e81965eeb257caa3587b9d45066fb925 with: - disable_ccache: ${{ !inputs.ccache_enabled }} + enable_ccache: ${{ inputs.ccache_enabled }} - name: Set ccache log file if: ${{ inputs.ccache_enabled && runner.debug == '1' }} diff --git a/.github/workflows/upload-conan-deps.yml b/.github/workflows/upload-conan-deps.yml index 8a9993d37a..55a9ab8864 100644 --- a/.github/workflows/upload-conan-deps.yml +++ b/.github/workflows/upload-conan-deps.yml @@ -70,9 +70,9 @@ jobs: uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 - name: Prepare runner - uses: XRPLF/actions/prepare-runner@2ece4ec6ab7de266859a6f053571425b2bd684b6 + uses: XRPLF/actions/prepare-runner@65da1c59e81965eeb257caa3587b9d45066fb925 with: - disable_ccache: true + enable_ccache: false - name: Print build environment uses: ./.github/actions/print-env From c24a6041f7fa8509a584de0c82c439b43197eddf Mon Sep 17 00:00:00 2001 From: oncecelll Date: Sat, 10 Jan 2026 02:15:05 +0800 Subject: [PATCH 13/30] docs: Fix minor spelling issues in comments (#6194) --- src/libxrpl/protocol/SecretKey.cpp | 4 ++-- src/test/app/HashRouter_test.cpp | 2 +- src/test/app/MPToken_test.cpp | 4 ++-- src/test/app/MultiSign_test.cpp | 2 +- src/test/basics/IntrusiveShared_test.cpp | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/libxrpl/protocol/SecretKey.cpp b/src/libxrpl/protocol/SecretKey.cpp index 88404a88a5..2507269407 100644 --- a/src/libxrpl/protocol/SecretKey.cpp +++ b/src/libxrpl/protocol/SecretKey.cpp @@ -77,7 +77,7 @@ deriveDeterministicRootKey(Seed const& seed) std::array buf; std::copy(seed.begin(), seed.end(), buf.begin()); - // The odds that this loop executes more than once are neglible + // The odds that this loop executes more than once are negligible // but *just* in case someone managed to generate a key that required // more iterations loop a few times. for (std::uint32_t seq = 0; seq != 128; ++seq) @@ -137,7 +137,7 @@ private: std::copy(generator_.begin(), generator_.end(), buf.begin()); copy_uint32(buf.data() + 33, seq); - // The odds that this loop executes more than once are neglible + // The odds that this loop executes more than once are negligible // but we impose a maximum limit just in case. for (std::uint32_t subseq = 0; subseq != 128; ++subseq) { diff --git a/src/test/app/HashRouter_test.cpp b/src/test/app/HashRouter_test.cpp index c428917fdc..2d1d37c3e3 100644 --- a/src/test/app/HashRouter_test.cpp +++ b/src/test/app/HashRouter_test.cpp @@ -349,7 +349,7 @@ class HashRouter_test : public beast::unit_test::suite h.set("hold_time", "alice"); h.set("relay_time", "bob"); auto const setup = setup_HashRouter(cfg); - // The set function ignores values that don't covert, so the + // The set function ignores values that don't convert, so the // defaults are left unchanged BEAST_EXPECT(setup.holdTime == 300s); BEAST_EXPECT(setup.relayTime == 30s); diff --git a/src/test/app/MPToken_test.cpp b/src/test/app/MPToken_test.cpp index ed6d861ffb..747f78ef6b 100644 --- a/src/test/app/MPToken_test.cpp +++ b/src/test/app/MPToken_test.cpp @@ -2651,7 +2651,7 @@ class MPToken_test : public beast::unit_test::suite STAmount const amt3{asset3, 10'000}; { - testcase("Test STAmount MPT arithmetics"); + testcase("Test STAmount MPT arithmetic"); using namespace std::string_literals; STAmount res = multiply(amt1, amt2, asset3); BEAST_EXPECT(res == amt3); @@ -2688,7 +2688,7 @@ class MPToken_test : public beast::unit_test::suite } { - testcase("Test MPTAmount arithmetics"); + testcase("Test MPTAmount arithmetic"); MPTAmount mptAmt1{100}; MPTAmount const mptAmt2{100}; BEAST_EXPECT((mptAmt1 += mptAmt2) == MPTAmount{200}); diff --git a/src/test/app/MultiSign_test.cpp b/src/test/app/MultiSign_test.cpp index 6950286b52..5c5404c17e 100644 --- a/src/test/app/MultiSign_test.cpp +++ b/src/test/app/MultiSign_test.cpp @@ -708,7 +708,7 @@ public: void testHeterogeneousSigners(FeatureBitset features) { - testcase("Heterogenous Signers"); + testcase("Heterogeneous Signers"); using namespace jtx; Env env{*this, features}; diff --git a/src/test/basics/IntrusiveShared_test.cpp b/src/test/basics/IntrusiveShared_test.cpp index b77325efa9..500b6e7e39 100644 --- a/src/test/basics/IntrusiveShared_test.cpp +++ b/src/test/basics/IntrusiveShared_test.cpp @@ -396,7 +396,7 @@ public: // This checks that partialDelete has run to completion // before the destructor is called. A sleep is inserted // inside the partial delete to make sure the destructor is - // given an opportunity to run durring partial delete. + // given an opportunity to run during partial delete. BEAST_EXPECT(cur == partiallyDeleted); } if (next == partiallyDeletedStarted) From fc0072383699cf9718af35a22e46df1bad7799bb Mon Sep 17 00:00:00 2001 From: Zhanibek Bakin <50952098+janibakin@users.noreply.github.com> Date: Fri, 9 Jan 2026 23:37:55 +0500 Subject: [PATCH 14/30] fix: Truncate thread name to 15 chars on Linux (#5758) This change: * Truncates thread names if more than 15 chars with `snprintf`. * Adds warnings for truncated thread names if `-DTRUNCATED_THREAD_NAME_LOGS=ON`. * Add a static assert for string literals to stop compiling if > 15 chars. * Shortens `Resource::Manager` to `Resource::Mngr` to fix the static assert failure. * Updates `CurrentThreadName_test` unit test specifically for Linux to verify truncation. --- cmake/XrplSettings.cmake | 15 ++++ include/xrpl/beast/core/CurrentThreadName.h | 27 +++++++ src/libxrpl/beast/core/CurrentThreadName.cpp | 24 +++++- src/libxrpl/resource/ResourceManager.cpp | 2 +- .../beast/beast_CurrentThreadName_test.cpp | 74 ++++++++++++++----- 5 files changed, 121 insertions(+), 21 deletions(-) diff --git a/cmake/XrplSettings.cmake b/cmake/XrplSettings.cmake index a16513afc5..c3f013c575 100644 --- a/cmake/XrplSettings.cmake +++ b/cmake/XrplSettings.cmake @@ -68,6 +68,21 @@ if(is_linux) option(perf "Enables flags that assist with perf recording" OFF) option(use_gold "enables detection of gold (binutils) linker" ON) option(use_mold "enables detection of mold (binutils) linker" ON) + # Set a default value for the log flag based on the build type. + # This provides a sensible default (on for debug, off for release) + # while still allowing the user to override it for any build. + if(CMAKE_BUILD_TYPE STREQUAL "Debug") + set(TRUNCATED_LOGS_DEFAULT ON) + else() + set(TRUNCATED_LOGS_DEFAULT OFF) + endif() + option(TRUNCATED_THREAD_NAME_LOGS + "Show warnings about truncated thread names on Linux." + ${TRUNCATED_LOGS_DEFAULT} + ) + if(TRUNCATED_THREAD_NAME_LOGS) + add_compile_definitions(TRUNCATED_THREAD_NAME_LOGS) + endif() else() # we are not ready to allow shared-libs on windows because it would require # export declarations. On macos it's more feasible, but static openssl diff --git a/include/xrpl/beast/core/CurrentThreadName.h b/include/xrpl/beast/core/CurrentThreadName.h index 8e9d58b649..703246a76a 100644 --- a/include/xrpl/beast/core/CurrentThreadName.h +++ b/include/xrpl/beast/core/CurrentThreadName.h @@ -5,6 +5,8 @@ #ifndef BEAST_CORE_CURRENT_THREAD_NAME_H_INCLUDED #define BEAST_CORE_CURRENT_THREAD_NAME_H_INCLUDED +#include + #include #include @@ -16,6 +18,31 @@ namespace beast { void setCurrentThreadName(std::string_view newThreadName); +#if BOOST_OS_LINUX + +// On Linux, thread names are limited to 16 bytes including the null terminator. +// Maximum number of characters is therefore 15. +constexpr std::size_t maxThreadNameLength = 15; + +/** Sets the name of the caller thread with compile-time size checking. + @tparam N The size of the string literal including null terminator + @param newThreadName A string literal to set as the thread name + + This template overload enforces that thread names are at most 16 characters + (including null terminator) at compile time, matching Linux's limit. +*/ +template +void +setCurrentThreadName(char const (&newThreadName)[N]) +{ + static_assert( + N <= maxThreadNameLength + 1, + "Thread name cannot exceed 15 characters"); + + setCurrentThreadName(std::string_view(newThreadName, N - 1)); +} +#endif + /** Returns the name of the caller thread. The name returned is the name as set by a call to setCurrentThreadName(). diff --git a/src/libxrpl/beast/core/CurrentThreadName.cpp b/src/libxrpl/beast/core/CurrentThreadName.cpp index 42dbb062b4..e8f7b629a7 100644 --- a/src/libxrpl/beast/core/CurrentThreadName.cpp +++ b/src/libxrpl/beast/core/CurrentThreadName.cpp @@ -1,7 +1,5 @@ #include -#include - #include #include @@ -73,12 +71,32 @@ setCurrentThreadNameImpl(std::string_view name) #if BOOST_OS_LINUX #include +#include + namespace beast::detail { inline void setCurrentThreadNameImpl(std::string_view name) { - pthread_setname_np(pthread_self(), name.data()); + // truncate and set the thread name. + char boundedName[maxThreadNameLength + 1]; + std::snprintf( + boundedName, + sizeof(boundedName), + "%.*s", + static_cast(maxThreadNameLength), + name.data()); + + pthread_setname_np(pthread_self(), boundedName); + +#ifdef TRUNCATED_THREAD_NAME_LOGS + if (name.size() > maxThreadNameLength) + { + std::cerr << "WARNING: Thread name \"" << name << "\" (length " + << name.size() << ") exceeds maximum of " + << maxThreadNameLength << " characters on Linux.\n"; + } +#endif } } // namespace beast::detail diff --git a/src/libxrpl/resource/ResourceManager.cpp b/src/libxrpl/resource/ResourceManager.cpp index 8582836611..15d31a558e 100644 --- a/src/libxrpl/resource/ResourceManager.cpp +++ b/src/libxrpl/resource/ResourceManager.cpp @@ -140,7 +140,7 @@ private: void run() { - beast::setCurrentThreadName("Resource::Manager"); + beast::setCurrentThreadName("Resource::Mngr"); for (;;) { logic_.periodicActivity(); diff --git a/src/test/beast/beast_CurrentThreadName_test.cpp b/src/test/beast/beast_CurrentThreadName_test.cpp index 3d33ecb602..dc12883a63 100644 --- a/src/test/beast/beast_CurrentThreadName_test.cpp +++ b/src/test/beast/beast_CurrentThreadName_test.cpp @@ -1,6 +1,8 @@ #include #include +#include + #include namespace xrpl { @@ -37,33 +39,71 @@ private: if (beast::getCurrentThreadName() == myName) *state = 2; } +#if BOOST_OS_LINUX + // Helper function to test a specific name. + // It creates a thread, sets the name, and checks if the OS-level + // name matches the expected (potentially truncated) name. + void + testName(std::string const& nameToSet, std::string const& expectedName) + { + std::thread t([&] { + beast::setCurrentThreadName(nameToSet); + + // Initialize buffer to be safe. + char actualName[beast::maxThreadNameLength + 1] = {}; + pthread_getname_np(pthread_self(), actualName, sizeof(actualName)); + + BEAST_EXPECT(std::string(actualName) == expectedName); + }); + t.join(); + } +#endif public: void run() override { - // Make two different threads with two different names. Make sure - // that the expected thread names are still there when the thread - // exits. - std::atomic stop{false}; + // Make two different threads with two different names. + // Make sure that the expected thread names are still there + // when the thread exits. + { + std::atomic stop{false}; - std::atomic stateA{0}; - std::thread tA(exerciseName, "tA", &stop, &stateA); + std::atomic stateA{0}; + std::thread tA(exerciseName, "tA", &stop, &stateA); - std::atomic stateB{0}; - std::thread tB(exerciseName, "tB", &stop, &stateB); + std::atomic stateB{0}; + std::thread tB(exerciseName, "tB", &stop, &stateB); - // Wait until both threads have set their names. - while (stateA == 0 || stateB == 0) - ; + // Wait until both threads have set their names. + while (stateA == 0 || stateB == 0) + ; - stop = true; - tA.join(); - tB.join(); + stop = true; + tA.join(); + tB.join(); - // Both threads should still have the expected name when they exit. - BEAST_EXPECT(stateA == 2); - BEAST_EXPECT(stateB == 2); + // Both threads should still have the expected name when they exit. + BEAST_EXPECT(stateA == 2); + BEAST_EXPECT(stateB == 2); + } +#if BOOST_OS_LINUX + // On Linux, verify that thread names longer than 15 characters + // are truncated to 15 characters (the 16th character is reserved for + // the null terminator). + { + testName( + "123456789012345", + "123456789012345"); // 15 chars, no truncation + testName( + "1234567890123456", "123456789012345"); // 16 chars, truncated + testName( + "ThisIsAVeryLongThreadNameExceedingLimit", + "ThisIsAVeryLong"); // 39 chars, truncated + testName("", ""); // empty name + testName("short", "short"); // short name, no truncation + } +#endif } }; From 14467fba5e5652d4976fdea0d312d64e6fb61242 Mon Sep 17 00:00:00 2001 From: Vito Tumas <5780819+Tapanito@users.noreply.github.com> Date: Fri, 9 Jan 2026 20:58:02 +0100 Subject: [PATCH 15/30] VaultClawback: Burn shares of an empty vault (#6120) - Adds a mechanism for the vault owner to burn user shares when the vault is stuck. If the Vault has 0 AssetsAvailable and Total, the owner may submit a VaultClawback to reclaim the worthless fees, and thus allow the Vault to be deleted. The Amount must be left off (unless the owner is the asset issuer), specified as 0 Shares, or specified as the number of Shares held. --- src/test/app/Vault_test.cpp | 589 ++++++++++++++++++++- src/xrpld/app/tx/detail/InvariantCheck.cpp | 77 +-- src/xrpld/app/tx/detail/InvariantCheck.h | 1 + src/xrpld/app/tx/detail/VaultClawback.cpp | 473 +++++++++++------ src/xrpld/app/tx/detail/VaultClawback.h | 8 + 5 files changed, 931 insertions(+), 217 deletions(-) diff --git a/src/test/app/Vault_test.cpp b/src/test/app/Vault_test.cpp index f8d76623fd..d0a1450d6c 100644 --- a/src/test/app/Vault_test.cpp +++ b/src/test/app/Vault_test.cpp @@ -21,7 +21,6 @@ #include #include #include -#include #include #include #include @@ -940,25 +939,6 @@ class Vault_test : public beast::unit_test::suite } }); - testCase([&](Env& env, - Account const& issuer, - Account const& owner, - Asset const& asset, - Vault& vault) { - testcase("clawback from self"); - - auto [tx, keylet] = vault.create({.owner = owner, .asset = asset}); - - { - auto tx = vault.clawback( - {.issuer = issuer, - .id = keylet.key, - .holder = issuer, - .amount = asset(10)}); - env(tx, ter{temMALFORMED}); - } - }); - testCase([&](Env& env, Account const&, Account const& owner, @@ -1197,11 +1177,13 @@ class Vault_test : public beast::unit_test::suite auto [tx, keylet] = vault.create({.owner = owner, .asset = asset}); + // Preclaim only checks for native assets. + if (asset.native()) { auto tx = vault.clawback( - {.issuer = owner, + {.issuer = issuer, .id = keylet.key, - .holder = issuer, + .holder = owner, .amount = asset(50)}); env(tx, ter(temMALFORMED)); } @@ -1924,8 +1906,20 @@ class Vault_test : public beast::unit_test::suite env.close(); { - auto tx = vault.clawback( - {.issuer = owner, .id = keylet.key, .holder = depositor}); + auto tx = vault.clawback({ + .issuer = depositor, + .id = keylet.key, + .holder = depositor, + }); + env(tx, ter(tecNO_PERMISSION)); + } + + { + auto tx = vault.clawback({ + .issuer = owner, + .id = keylet.key, + .holder = depositor, + }); env(tx, ter(tecNO_PERMISSION)); } }); @@ -2377,6 +2371,15 @@ class Vault_test : public beast::unit_test::suite env(tx, ter(tecNO_AUTH)); } + { + // Cannot clawback if issuer is the holder + tx = vault.clawback( + {.issuer = issuer, + .id = keylet.key, + .holder = issuer, + .amount = asset(800)}); + env(tx, ter(tecNO_PERMISSION)); + } // Clawback works tx = vault.clawback( {.issuer = issuer, @@ -5243,6 +5246,542 @@ class Vault_test : public beast::unit_test::suite }); } + void + testVaultClawbackBurnShares() + { + using namespace test::jtx; + using namespace loanBroker; + using namespace loan; + Env env(*this, beast::severities::kWarning); + + auto const vaultAssetBalance = [&](Keylet const& vaultKeylet) { + auto const sleVault = env.le(vaultKeylet); + BEAST_EXPECT(sleVault != nullptr); + + return std::make_pair( + sleVault->at(sfAssetsAvailable), sleVault->at(sfAssetsTotal)); + }; + + auto const vaultShareBalance = [&](Keylet const& vaultKeylet) { + auto const sleVault = env.le(vaultKeylet); + BEAST_EXPECT(sleVault != nullptr); + + auto const sleIssuance = + env.le(keylet::mptIssuance(sleVault->at(sfShareMPTID))); + BEAST_EXPECT(sleIssuance != nullptr); + + return sleIssuance->at(sfOutstandingAmount); + }; + + auto const setupVault = + [&](PrettyAsset const& asset, + Account const& owner, + Account const& depositor) -> std::pair { + Vault vault{env}; + + auto const& [tx, vaultKeylet] = + vault.create({.owner = owner, .asset = asset}); + env(tx, ter(tesSUCCESS), THISLINE); + env.close(); + + auto const& vaultSle = env.le(vaultKeylet); + BEAST_EXPECT(vaultSle != nullptr); + + Asset share = vaultSle->at(sfShareMPTID); + + env(vault.deposit( + {.depositor = depositor, + .id = vaultKeylet.key, + .amount = asset(100)}), + ter(tesSUCCESS), + THISLINE); + env.close(); + + auto const& [availablePreDefault, totalPreDefault] = + vaultAssetBalance(vaultKeylet); + BEAST_EXPECT(availablePreDefault == totalPreDefault); + BEAST_EXPECT(availablePreDefault == asset(100).value()); + + // attempt to clawback shares while there are assets fails + env(vault.clawback( + {.issuer = owner, + .id = vaultKeylet.key, + .holder = depositor, + .amount = share(0).value()}), + ter(tecNO_PERMISSION), + THISLINE); + env.close(); + + auto const& sharesAvailable = vaultShareBalance(vaultKeylet); + auto const& brokerKeylet = + keylet::loanbroker(owner.id(), env.seq(owner)); + + env(set(owner, vaultKeylet.key), THISLINE); + env.close(); + + auto const& loanKeylet = keylet::loan(brokerKeylet.key, 1); + + // Create a simple Loan for the full amount of Vault assets + env(set(depositor, brokerKeylet.key, asset(100).value()), + loan::interestRate(TenthBips32(0)), + gracePeriod(10), + paymentInterval(120), + paymentTotal(10), + sig(sfCounterpartySignature, owner), + fee(env.current()->fees().base * 2), + ter(tesSUCCESS), + THISLINE); + env.close(); + + // attempt to clawback shares while there assetsAvailable == 0 and + // assetsTotal > 0 fails + env(vault.clawback( + {.issuer = owner, + .id = vaultKeylet.key, + .holder = depositor, + .amount = share(0).value()}), + ter(tecNO_PERMISSION), + THISLINE); + env.close(); + + env.close(std::chrono::seconds{120 + 10}); + + env(manage(owner, loanKeylet.key, tfLoanDefault), + ter(tesSUCCESS), + THISLINE); + + auto const& [availablePostDefault, totalPostDefault] = + vaultAssetBalance(vaultKeylet); + + BEAST_EXPECT(availablePostDefault == totalPostDefault); + BEAST_EXPECT(availablePostDefault == asset(0).value()); + BEAST_EXPECT(vaultShareBalance(vaultKeylet) == sharesAvailable); + + return std::make_pair(vault, vaultKeylet); + }; + + auto const testCase = [&](PrettyAsset const& asset, + std::string const& prefix, + Account const& owner, + Account const& depositor) { + { + testcase( + "VaultClawback (share) - " + prefix + + " owner asset clawback fails"); + auto [vault, vaultKeylet] = setupVault(asset, owner, depositor); + env(vault.clawback({ + .issuer = owner, + .id = vaultKeylet.key, + .holder = depositor, + .amount = asset(100).value(), + }), + // when asset is XRP or owner is not issuer clawback fail + // when owner is issuer precision loss occurs as vault is + // empty + asset.native() ? ter(temMALFORMED) + : asset.raw().getIssuer() != owner.id() + ? ter(tecNO_PERMISSION) + : ter(tecPRECISION_LOSS), + THISLINE); + env.close(); + } + + { + testcase( + "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) + return; + Asset share = vaultSle->at(sfShareMPTID); + env(vault.clawback({ + .issuer = owner, + .id = vaultKeylet.key, + .holder = depositor, + .amount = share(1).value(), + }), + ter(tecLIMIT_EXCEEDED), + THISLINE); + env.close(); + } + + { + testcase( + "VaultClawback (share) - " + prefix + + " owner implicit complete share clawback"); + auto [vault, vaultKeylet] = setupVault(asset, owner, depositor); + env(vault.clawback({ + .issuer = owner, + .id = vaultKeylet.key, + .holder = depositor, + }), + // when owner is issuer implicit clawback fails + asset.native() || asset.raw().getIssuer() != owner.id() + ? ter(tesSUCCESS) + : ter(tecWRONG_ASSET), + THISLINE); + env.close(); + } + + { + testcase( + "VaultClawback (share) - " + prefix + + " 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) + return; + Asset share = vaultSle->at(sfShareMPTID); + env(vault.clawback({ + .issuer = owner, + .id = vaultKeylet.key, + .holder = depositor, + .amount = share(vaultShareBalance(vaultKeylet)).value(), + }), + ter(tesSUCCESS), + THISLINE); + env.close(); + } + { + 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) + return; + Asset share = vaultSle->at(sfShareMPTID); + env(vault.clawback({ + .issuer = owner, + .id = vaultKeylet.key, + .holder = owner, + .amount = share(vaultShareBalance(vaultKeylet)).value(), + }), + ter(tesSUCCESS), + THISLINE); + env.close(); + } + + { + 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)) + return; + Asset share = vaultSle->at(sfShareMPTID); + env(vault.clawback({ + .issuer = owner, + .id = vaultKeylet.key, + .holder = owner, + .amount = share(vaultShareBalance(vaultKeylet)).value(), + }), + ter(tesSUCCESS), + THISLINE); + + // Now the vault is empty, clawback again fails + env(vault.clawback({ + .issuer = owner, + .id = vaultKeylet.key, + .holder = owner, + }), + ter(tecNO_PERMISSION), + THISLINE); + env.close(); + } + }; + + Account owner{"alice"}; + Account depositor{"bob"}; + Account issuer{"issuer"}; + + env.fund(XRP(10000), issuer, owner, depositor); + env.close(); + + // Test XRP + PrettyAsset xrp = xrpIssue(); + testCase(xrp, "XRP", owner, depositor); + testCase(xrp, "XRP (depositor is owner)", owner, owner); + + // Test IOU + PrettyAsset IOU = issuer["IOU"]; + env(fset(issuer, asfAllowTrustLineClawback)); + env.close(); + + env.trust(IOU(1000), owner); + env.trust(IOU(1000), depositor); + env(pay(issuer, owner, IOU(100))); + env(pay(issuer, depositor, IOU(100))); + env.close(); + testCase(IOU, "IOU", owner, depositor); + testCase(IOU, "IOU (owner is issuer)", issuer, depositor); + + // Test MPT + MPTTester mptt{env, issuer, mptInitNoFund}; + mptt.create( + {.flags = tfMPTCanClawback | tfMPTCanTransfer | tfMPTCanLock}); + PrettyAsset MPT = mptt.issuanceID(); + mptt.authorize({.account = owner}); + mptt.authorize({.account = depositor}); + env(pay(issuer, owner, MPT(1000))); + env(pay(issuer, depositor, MPT(1000))); + env.close(); + testCase(MPT, "MPT", owner, depositor); + testCase(MPT, "MPT (owner is issuer)", issuer, depositor); + } + + void + testVaultClawbackAssets() + { + using namespace test::jtx; + using namespace loanBroker; + using namespace loan; + Env env(*this); + + auto const setupVault = + [&](PrettyAsset const& asset, + Account const& owner, + Account const& depositor, + Account const& issuer) -> std::pair { + Vault vault{env}; + + auto const& [tx, vaultKeylet] = + vault.create({.owner = owner, .asset = asset}); + env(tx, ter(tesSUCCESS), THISLINE); + env.close(); + + auto const& vaultSle = env.le(vaultKeylet); + BEAST_EXPECT(vaultSle != nullptr); + env(vault.deposit( + {.depositor = depositor, + .id = vaultKeylet.key, + .amount = asset(100)}), + ter(tesSUCCESS), + THISLINE); + env.close(); + + return std::make_pair(vault, vaultKeylet); + }; + + auto const testCase = [&](PrettyAsset const& asset, + std::string const& prefix, + Account const& owner, + Account const& depositor, + Account const& issuer) { + if (asset.native()) + { + testcase( + "VaultClawback (asset) - " + prefix + + " issuer XRP clawback fails"); + auto [vault, vaultKeylet] = + setupVault(asset, owner, depositor, issuer); + // If the asset is XRP, clawback with amount fails as malfored + // when asset is specified. + env(vault.clawback({ + .issuer = issuer, + .id = vaultKeylet.key, + .holder = issuer, + .amount = asset(1).value(), + }), + ter(temMALFORMED), + THISLINE); + // When asset is implicit, clawback fails as no permission. + env(vault.clawback({ + .issuer = issuer, + .id = vaultKeylet.key, + .holder = issuer, + }), + ter(tecNO_PERMISSION), + THISLINE); + return; + } + + { + testcase( + "VaultClawback (asset) - " + prefix + + " clawback for different asset fails"); + auto [vault, vaultKeylet] = + setupVault(asset, owner, depositor, issuer); + + Account issuer2{"issuer2"}; + PrettyAsset asset2 = issuer2["FOO"]; + env(vault.clawback({ + .issuer = issuer, + .id = vaultKeylet.key, + .holder = depositor, + .amount = asset2(1).value(), + }), + ter(tecWRONG_ASSET), + THISLINE); + } + + { + testcase( + "VaultClawback (asset) - " + prefix + + " ambiguous owner/issuer asset clawback fails"); + auto [vault, vaultKeylet] = + setupVault(asset, issuer, depositor, issuer); + env(vault.clawback({ + .issuer = issuer, + .id = vaultKeylet.key, + .holder = issuer, + }), + ter(tecWRONG_ASSET), + THISLINE); + } + + { + testcase( + "VaultClawback (asset) - " + prefix + + " non-issuer asset clawback fails"); + auto [vault, vaultKeylet] = + setupVault(asset, owner, depositor, issuer); + + env(vault.clawback({ + .issuer = owner, + .id = vaultKeylet.key, + .holder = depositor, + }), + ter(tecNO_PERMISSION), + THISLINE); + + env(vault.clawback({ + .issuer = owner, + .id = vaultKeylet.key, + .holder = depositor, + .amount = asset(1).value(), + }), + ter(tecNO_PERMISSION), + THISLINE); + } + + { + testcase( + "VaultClawback (asset) - " + prefix + + " issuer clawback from self fails"); + auto [vault, vaultKeylet] = + setupVault(asset, owner, issuer, issuer); + env(vault.clawback({ + .issuer = issuer, + .id = vaultKeylet.key, + .holder = issuer, + }), + ter(tecNO_PERMISSION), + THISLINE); + } + + { + 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) + return; + Asset share = vaultSle->at(sfShareMPTID); + + env(vault.clawback({ + .issuer = issuer, + .id = vaultKeylet.key, + .holder = depositor, + .amount = share(1).value(), + }), + ter(tecNO_PERMISSION), + THISLINE); + } + + { + testcase( + "VaultClawback (asset) - " + prefix + + " partial issuer asset clawback succeeds"); + auto [vault, vaultKeylet] = + setupVault(asset, owner, depositor, issuer); + + env(vault.clawback({ + .issuer = issuer, + .id = vaultKeylet.key, + .holder = depositor, + .amount = asset(1).value(), + }), + ter(tesSUCCESS), + THISLINE); + } + + { + testcase( + "VaultClawback (asset) - " + prefix + + " full issuer asset clawback succeeds"); + auto [vault, vaultKeylet] = + setupVault(asset, owner, depositor, issuer); + + env(vault.clawback({ + .issuer = issuer, + .id = vaultKeylet.key, + .holder = depositor, + .amount = asset(100).value(), + }), + ter(tesSUCCESS), + THISLINE); + } + + { + testcase( + "VaultClawback (asset) - " + prefix + + " implicit full issuer asset clawback succeeds"); + auto [vault, vaultKeylet] = + setupVault(asset, owner, depositor, issuer); + + env(vault.clawback({ + .issuer = issuer, + .id = vaultKeylet.key, + .holder = depositor, + }), + ter(tesSUCCESS), + THISLINE); + } + }; + + Account owner{"alice"}; + Account depositor{"bob"}; + Account issuer{"issuer"}; + + env.fund(XRP(10000), issuer, owner, depositor); + env.close(); + + // Test XRP + PrettyAsset xrp = xrpIssue(); + testCase(xrp, "XRP", owner, depositor, issuer); + + // Test IOU + PrettyAsset 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.close(); + testCase(IOU, "IOU", owner, depositor, issuer); + + // Test MPT + MPTTester mptt{env, issuer, mptInitNoFund}; + mptt.create( + {.flags = tfMPTCanClawback | tfMPTCanTransfer | tfMPTCanLock}); + PrettyAsset MPT = mptt.issuanceID(); + mptt.authorize({.account = owner}); + mptt.authorize({.account = depositor}); + env(pay(issuer, depositor, MPT(1000))); + env.close(); + testCase(MPT, "MPT", owner, depositor, issuer); + } + public: void run() override @@ -5261,6 +5800,8 @@ public: testScaleIOU(); testRPC(); testDelegate(); + testVaultClawbackBurnShares(); + testVaultClawbackAssets(); } }; diff --git a/src/xrpld/app/tx/detail/InvariantCheck.cpp b/src/xrpld/app/tx/detail/InvariantCheck.cpp index 0b237905e8..2e0b3cbfab 100644 --- a/src/xrpld/app/tx/detail/InvariantCheck.cpp +++ b/src/xrpld/app/tx/detail/InvariantCheck.cpp @@ -95,6 +95,7 @@ hasPrivilege(STTx const& tx, Privilege priv) switch (tx.getTxnType()) { #include + // Deprecated types default: return false; @@ -2622,6 +2623,7 @@ ValidVault::Vault::make(SLE const& from) self.key = from.key(); self.asset = from.at(sfAsset); self.pseudoId = from.getAccountID(sfAccount); + self.owner = from.at(sfOwner); self.shareMPTID = from.getFieldH192(sfShareMPTID); self.assetsTotal = from.at(sfAssetsTotal); self.assetsAvailable = from.at(sfAssetsAvailable); @@ -3066,6 +3068,10 @@ ValidVault::finalize( : std::nullopt; }; + auto const vaultHoldsNoAssets = [&](Vault const& vault) { + return vault.assetsAvailable == 0 && vault.assetsTotal == 0; + }; + // Technically this does not need to be a lambda, but it's more // convenient thanks to early "return false"; the not-so-nice // alternatives are several layers of nested if/else or more complex @@ -3448,29 +3454,56 @@ ValidVault::finalize( if (vaultAsset.native() || vaultAsset.getIssuer() != tx[sfAccount]) { - JLOG(j.fatal()) << // - "Invariant failed: clawback may only be performed by " - "the asset issuer"; - return false; // That's all we can do + // The owner can use clawback to force-burn shares when the + // vault is empty but there are outstanding shares + if (!(beforeShares && beforeShares->sharesTotal > 0 && + vaultHoldsNoAssets(beforeVault) && + beforeVault.owner == tx[sfAccount])) + { + JLOG(j.fatal()) << // + "Invariant failed: clawback may only be performed " + "by the asset issuer, or by the vault owner of an " + "empty vault"; + return false; // That's all we can do + } } auto const vaultDeltaAssets = deltaAssets(afterVault.pseudoId); + if (vaultDeltaAssets) + { + if (*vaultDeltaAssets >= zero) + { + JLOG(j.fatal()) << // + "Invariant failed: clawback must decrease vault " + "balance"; + result = false; + } - if (!vaultDeltaAssets) + if (beforeVault.assetsTotal + *vaultDeltaAssets != + afterVault.assetsTotal) + { + JLOG(j.fatal()) << // + "Invariant failed: clawback and assets outstanding " + "must add up"; + result = false; + } + + if (beforeVault.assetsAvailable + *vaultDeltaAssets != + afterVault.assetsAvailable) + { + JLOG(j.fatal()) << // + "Invariant failed: clawback and assets available " + "must add up"; + result = false; + } + } + else if (!vaultHoldsNoAssets(beforeVault)) { JLOG(j.fatal()) << // "Invariant failed: clawback must change vault balance"; return false; // That's all we can do } - if (*vaultDeltaAssets >= zero) - { - JLOG(j.fatal()) << // - "Invariant failed: clawback must decrease vault " - "balance"; - result = false; - } - auto const accountDeltaShares = deltaShares(tx[sfHolder]); if (!accountDeltaShares) { @@ -3503,24 +3536,6 @@ ValidVault::finalize( result = false; } - if (beforeVault.assetsTotal + *vaultDeltaAssets != - afterVault.assetsTotal) - { - JLOG(j.fatal()) << // - "Invariant failed: clawback and assets outstanding " - "must add up"; - result = false; - } - - if (beforeVault.assetsAvailable + *vaultDeltaAssets != - afterVault.assetsAvailable) - { - JLOG(j.fatal()) << // - "Invariant failed: clawback and assets available must " - "add up"; - result = false; - } - return result; } diff --git a/src/xrpld/app/tx/detail/InvariantCheck.h b/src/xrpld/app/tx/detail/InvariantCheck.h index ef9db373f5..87a1afb623 100644 --- a/src/xrpld/app/tx/detail/InvariantCheck.h +++ b/src/xrpld/app/tx/detail/InvariantCheck.h @@ -861,6 +861,7 @@ class ValidVault uint256 key = beast::zero; Asset asset = {}; AccountID pseudoId = {}; + AccountID owner = {}; uint192 shareMPTID = beast::zero; Number assetsTotal = 0; Number assetsAvailable = 0; diff --git a/src/xrpld/app/tx/detail/VaultClawback.cpp b/src/xrpld/app/tx/detail/VaultClawback.cpp index cc7dec993a..2552e8c1ff 100644 --- a/src/xrpld/app/tx/detail/VaultClawback.cpp +++ b/src/xrpld/app/tx/detail/VaultClawback.cpp @@ -1,18 +1,17 @@ #include - +// #include #include #include -#include #include #include #include #include #include -#include + +#include namespace xrpl { - NotTEC VaultClawback::preflight(PreflightContext const& ctx) { @@ -22,15 +21,6 @@ VaultClawback::preflight(PreflightContext const& ctx) return temMALFORMED; } - AccountID const issuer = ctx.tx[sfAccount]; - AccountID const holder = ctx.tx[sfHolder]; - - if (issuer == holder) - { - JLOG(ctx.j.debug()) << "VaultClawback: issuer cannot be holder."; - return temMALFORMED; - } - auto const amount = ctx.tx[~sfAmount]; if (amount) { @@ -42,17 +32,27 @@ VaultClawback::preflight(PreflightContext const& ctx) JLOG(ctx.j.debug()) << "VaultClawback: cannot clawback XRP."; return temMALFORMED; } - else if (amount->asset().getIssuer() != issuer) - { - JLOG(ctx.j.debug()) - << "VaultClawback: only asset issuer can clawback."; - return temMALFORMED; - } } return tesSUCCESS; } +[[nodiscard]] STAmount +clawbackAmount( + std::shared_ptr const& vault, + std::optional const& maybeAmount, + AccountID const& account) +{ + if (maybeAmount) + return *maybeAmount; + + Asset const share = MPTIssue{vault->at(sfShareMPTID)}; + if (account == vault->at(sfOwner)) + return STAmount{share}; + + return STAmount{vault->at(sfAsset)}; +} + TER VaultClawback::preclaim(PreclaimContext const& ctx) { @@ -60,61 +60,264 @@ VaultClawback::preclaim(PreclaimContext const& ctx) if (!vault) return tecNO_ENTRY; - auto account = ctx.tx[sfAccount]; - auto const issuer = ctx.view.read(keylet::account(account)); - if (!issuer) + Asset const vaultAsset = vault->at(sfAsset); + auto const account = ctx.tx[sfAccount]; + auto const holder = ctx.tx[sfHolder]; + auto const maybeAmount = ctx.tx[~sfAmount]; + auto const mptIssuanceID = vault->at(sfShareMPTID); + auto const sleShareIssuance = + ctx.view.read(keylet::mptIssuance(mptIssuanceID)); + if (!sleShareIssuance) { // LCOV_EXCL_START - JLOG(ctx.j.error()) << "VaultClawback: missing issuer account."; + JLOG(ctx.j.error()) + << "VaultClawback: missing issuance of vault shares."; return tefINTERNAL; // LCOV_EXCL_STOP } - Asset const vaultAsset = vault->at(sfAsset); - if (auto const amount = ctx.tx[~sfAmount]; - amount && vaultAsset != amount->asset()) + Asset const share = MPTIssue{mptIssuanceID}; + + // Ambiguous case: If Issuer is Owner they must specify the asset + if (!maybeAmount && !vaultAsset.native() && + vaultAsset.getIssuer() == vault->at(sfOwner)) + { + JLOG(ctx.j.debug()) + << "VaultClawback: must specify amount when issuer is owner."; return tecWRONG_ASSET; - - if (vaultAsset.native()) - { - JLOG(ctx.j.debug()) << "VaultClawback: cannot clawback XRP."; - return tecNO_PERMISSION; // Cannot clawback XRP. - } - else if (vaultAsset.getIssuer() != account) - { - JLOG(ctx.j.debug()) << "VaultClawback: only asset issuer can clawback."; - return tecNO_PERMISSION; // Only issuers can clawback. } - if (vaultAsset.holds()) - { - auto const mpt = vaultAsset.get(); - auto const mptIssue = - ctx.view.read(keylet::mptIssuance(mpt.getMptID())); - if (mptIssue == nullptr) - return tecOBJECT_NOT_FOUND; + auto const amount = clawbackAmount(vault, maybeAmount, account); - std::uint32_t const issueFlags = mptIssue->getFieldU32(sfFlags); - if (!(issueFlags & lsfMPTCanClawback)) + // There is a special case that allows the VaultOwner to use clawback to + // burn shares when Vault assets total and available are zero, but + // shares remain. However, that case is handled in doApply() directly, + // so here we just enforce checks. + if (amount.asset() == share) + { + // Only the Vault Owner may clawback shares + if (account != vault->at(sfOwner)) { JLOG(ctx.j.debug()) - << "VaultClawback: cannot clawback MPT vault asset."; + << "VaultClawback: only vault owner can clawback shares."; return tecNO_PERMISSION; } - } - else if (vaultAsset.holds()) - { - std::uint32_t const issuerFlags = issuer->getFieldU32(sfFlags); - if (!(issuerFlags & lsfAllowTrustLineClawback) || - (issuerFlags & lsfNoFreeze)) + + auto const assetsTotal = vault->at(sfAssetsTotal); + auto const assetsAvailable = vault->at(sfAssetsAvailable); + auto const sharesTotal = sleShareIssuance->at(sfOutstandingAmount); + + // Owner can clawback funds when the vault has shares but no assets + if (sharesTotal == 0 || (assetsTotal != 0 || assetsAvailable != 0)) { JLOG(ctx.j.debug()) - << "VaultClawback: cannot clawback IOU vault asset."; + << "VaultClawback: vault owner can clawback shares only" + " when vault has no assets."; return tecNO_PERMISSION; } + + // If amount is non-zero, the VaultOwner must burn all shares + if (amount != beast::zero) + { + Number const& sharesHeld = accountHolds( + ctx.view, + holder, + share, + FreezeHandling::fhIGNORE_FREEZE, + AuthHandling::ahIGNORE_AUTH, + ctx.j); + + // The VaultOwner must burn all shares + if (amount != sharesHeld) + { + JLOG(ctx.j.debug()) + << "VaultClawback: vault owner must clawback all " + "shares."; + return tecLIMIT_EXCEEDED; + } + } + + return tesSUCCESS; } - return tesSUCCESS; + // The asset that is being clawed back is the vault asset + if (amount.asset() == vaultAsset) + { + // XRP cannot be clawed back + if (vaultAsset.native()) + { + JLOG(ctx.j.debug()) << "VaultClawback: cannot clawback XRP."; + return tecNO_PERMISSION; + } + + // Only the Asset Issuer may clawback the asset + if (account != vaultAsset.getIssuer()) + { + JLOG(ctx.j.debug()) + << "VaultClawback: only asset issuer can clawback asset."; + return tecNO_PERMISSION; + } + + // The issuer cannot clawback from itself + if (account == holder) + { + JLOG(ctx.j.debug()) + << "VaultClawback: issuer cannot be the holder."; + 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; + + 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) + { + 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; + } + } + return tesSUCCESS; + }, + vaultAsset.value()); + } + + // Invalid asset + return tecWRONG_ASSET; +} + +Expected, TER> +VaultClawback::assetsToClawback( + std::shared_ptr const& vault, + std::shared_ptr const& sleShareIssuance, + AccountID const& holder, + STAmount const& clawbackAmount) +{ + if (clawbackAmount.asset() != vault->at(sfAsset)) + { + // preclaim should have blocked this , now it's an internal error + // LCOV_EXCL_START + JLOG(j_.error()) << "VaultClawback: asset mismatch in clawback."; + return Unexpected(tecINTERNAL); + // LCOV_EXCL_STOP + } + + auto const assetsAvailable = vault->at(sfAssetsAvailable); + auto const mptIssuanceID = *vault->at(sfShareMPTID); + MPTIssue const share{mptIssuanceID}; + + if (clawbackAmount == beast::zero) + { + auto const 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 + + return std::make_pair(*maybeAssets, sharesDestroyed); + } + + STAmount sharesDestroyed; + STAmount assetsRecovered = clawbackAmount; + try + { + { + auto const maybeShares = assetsToSharesWithdraw( + vault, sleShareIssuance, assetsRecovered); + 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; + + // Clamp to maximum. + if (assetsRecovered > *assetsAvailable) + { + assetsRecovered = *assetsAvailable; + // Note, it is important to truncate the number of shares, + // otherwise the corresponding assets might breach the + // AssetsAvailable + { + auto const maybeShares = assetsToSharesWithdraw( + vault, + sleShareIssuance, + assetsRecovered, + TruncateShares::yes); + 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; + if (assetsRecovered > *assetsAvailable) + { + // LCOV_EXCL_START + JLOG(j_.error()) + << "VaultClawback: invalid rounding of shares."; + return Unexpected(tecINTERNAL); + // LCOV_EXCL_STOP + } + } + } + catch (std::overflow_error const&) + { + // It's easy to hit this exception from Number with large enough + // Scale so we avoid spamming the log and only use debug here. + JLOG(j_.debug()) // + << "VaultClawback: overflow error with" + << " scale=" << (int)vault->at(sfScale).value() // + << ", assetsTotal=" << vault->at(sfAssetsTotal).value() + << ", sharesTotal=" << sleShareIssuance->at(sfOutstandingAmount) + << ", amount=" << clawbackAmount.value(); + return Unexpected(tecPATH_DRY); + } + + return std::make_pair(assetsRecovered, sharesDestroyed); } TER @@ -125,7 +328,7 @@ VaultClawback::doApply() if (!vault) return tefINTERNAL; // LCOV_EXCL_LINE - auto const mptIssuanceID = *((*vault)[sfShareMPTID]); + auto const mptIssuanceID = *vault->at(sfShareMPTID); auto const sleIssuance = view().read(keylet::mptIssuance(mptIssuanceID)); if (!sleIssuance) { @@ -134,105 +337,47 @@ VaultClawback::doApply() return tefINTERNAL; // LCOV_EXCL_STOP } + MPTIssue const share{mptIssuanceID}; Asset const vaultAsset = vault->at(sfAsset); - STAmount const amount = [&]() -> STAmount { - auto const maybeAmount = tx[~sfAmount]; - if (maybeAmount) - return *maybeAmount; - return {sfAmount, vaultAsset, 0}; - }(); - XRPL_ASSERT( - amount.asset() == vaultAsset, - "xrpl::VaultClawback::doApply : matching asset"); + STAmount const amount = clawbackAmount(vault, tx[~sfAmount], account_); auto assetsAvailable = vault->at(sfAssetsAvailable); auto assetsTotal = vault->at(sfAssetsTotal); + [[maybe_unused]] auto const lossUnrealized = vault->at(sfLossUnrealized); XRPL_ASSERT( lossUnrealized <= (assetsTotal - assetsAvailable), "xrpl::VaultClawback::doApply : loss and assets do balance"); AccountID holder = tx[sfHolder]; - MPTIssue const share{mptIssuanceID}; STAmount sharesDestroyed = {share}; - STAmount assetsRecovered; - try + STAmount assetsRecovered = {vault->at(sfAsset)}; + + // The Owner is burning shares + if (account_ == vault->at(sfOwner) && amount.asset() == share) { - if (amount == beast::zero) - { - sharesDestroyed = accountHolds( - view(), - holder, - share, - FreezeHandling::fhIGNORE_FREEZE, - AuthHandling::ahIGNORE_AUTH, - j_); - - auto const maybeAssets = - sharesToAssetsWithdraw(vault, sleIssuance, sharesDestroyed); - if (!maybeAssets) - return tecINTERNAL; // LCOV_EXCL_LINE - assetsRecovered = *maybeAssets; - } - else - { - assetsRecovered = amount; - { - auto const maybeShares = - assetsToSharesWithdraw(vault, sleIssuance, assetsRecovered); - if (!maybeShares) - return tecINTERNAL; // LCOV_EXCL_LINE - sharesDestroyed = *maybeShares; - } - - auto const maybeAssets = - sharesToAssetsWithdraw(vault, sleIssuance, sharesDestroyed); - if (!maybeAssets) - return tecINTERNAL; // LCOV_EXCL_LINE - assetsRecovered = *maybeAssets; - } - - // Clamp to maximum. - if (assetsRecovered > *assetsAvailable) - { - assetsRecovered = *assetsAvailable; - // Note, it is important to truncate the number of shares, otherwise - // the corresponding assets might breach the AssetsAvailable - { - auto const maybeShares = assetsToSharesWithdraw( - vault, sleIssuance, assetsRecovered, TruncateShares::yes); - if (!maybeShares) - return tecINTERNAL; // LCOV_EXCL_LINE - sharesDestroyed = *maybeShares; - } - - auto const maybeAssets = - sharesToAssetsWithdraw(vault, sleIssuance, sharesDestroyed); - if (!maybeAssets) - return tecINTERNAL; // LCOV_EXCL_LINE - assetsRecovered = *maybeAssets; - if (assetsRecovered > *assetsAvailable) - { - // LCOV_EXCL_START - JLOG(j_.error()) - << "VaultClawback: invalid rounding of shares."; - return tecINTERNAL; - // LCOV_EXCL_STOP - } - } + sharesDestroyed = accountHolds( + view(), + holder, + share, + FreezeHandling::fhIGNORE_FREEZE, + AuthHandling::ahIGNORE_AUTH, + j_); } - catch (std::overflow_error const&) + else // The Issuer is clawbacking vault assets { - // It's easy to hit this exception from Number with large enough Scale - // so we avoid spamming the log and only use debug here. - JLOG(j_.debug()) // - << "VaultClawback: overflow error with" - << " scale=" << (int)vault->at(sfScale).value() // - << ", assetsTotal=" << vault->at(sfAssetsTotal).value() - << ", sharesTotal=" << sleIssuance->at(sfOutstandingAmount) - << ", amount=" << amount.value(); - return tecPATH_DRY; + XRPL_ASSERT( + amount.asset() == vaultAsset, + "xrpl::VaultClawback::doApply : matching asset"); + + auto const clawbackParts = + assetsToClawback(vault, sleIssuance, holder, amount); + if (!clawbackParts) + return clawbackParts.error(); + + assetsRecovered = clawbackParts->first; + sharesDestroyed = clawbackParts->second; } if (sharesDestroyed == beast::zero) @@ -282,30 +427,34 @@ VaultClawback::doApply() // else quietly ignore, holder balance is not zero } - // Transfer assets from vault to issuer. - if (auto const ter = accountSend( - view(), - vaultAccount, - account_, - assetsRecovered, - j_, - WaiveTransferFee::Yes); - !isTesSuccess(ter)) - return ter; - - // Sanity check - if (accountHolds( - view(), - vaultAccount, - assetsRecovered.asset(), - FreezeHandling::fhIGNORE_FREEZE, - AuthHandling::ahIGNORE_AUTH, - j_) < beast::zero) + if (assetsRecovered > beast::zero) { - // LCOV_EXCL_START - JLOG(j_.error()) << "VaultClawback: negative balance of vault assets."; - return tefINTERNAL; - // LCOV_EXCL_STOP + // Transfer assets from vault to issuer. + if (auto const ter = accountSend( + view(), + vaultAccount, + account_, + assetsRecovered, + j_, + WaiveTransferFee::Yes); + !isTesSuccess(ter)) + return ter; + + // Sanity check + if (accountHolds( + view(), + vaultAccount, + assetsRecovered.asset(), + FreezeHandling::fhIGNORE_FREEZE, + AuthHandling::ahIGNORE_AUTH, + j_) < beast::zero) + { + // LCOV_EXCL_START + JLOG(j_.error()) + << "VaultClawback: negative balance of vault assets."; + return tefINTERNAL; + // LCOV_EXCL_STOP + } } return tesSUCCESS; diff --git a/src/xrpld/app/tx/detail/VaultClawback.h b/src/xrpld/app/tx/detail/VaultClawback.h index 80a5f73ad0..d05f280e75 100644 --- a/src/xrpld/app/tx/detail/VaultClawback.h +++ b/src/xrpld/app/tx/detail/VaultClawback.h @@ -22,6 +22,14 @@ public: TER doApply() override; + +private: + Expected, TER> + assetsToClawback( + std::shared_ptr const& vault, + std::shared_ptr const& sleShareIssuance, + AccountID const& holder, + STAmount const& clawbackAmount); }; } // namespace xrpl From 7c1183547abb054878b24dd06fc2aab4e01ca350 Mon Sep 17 00:00:00 2001 From: Bart Date: Fri, 9 Jan 2026 16:44:43 -0500 Subject: [PATCH 16/30] chore: Change `/Zi` to `/Z7` for ccache, remove debug symbols in CI (#6198) As the `/Zi` compiler flag is unsupported by ccache, this change switches it to `/Z7` instead. For CI runs all debug info is omitted. --- .config/cspell.config.yaml | 1 + .github/workflows/reusable-build-test-config.yml | 2 +- cmake/Ccache.cmake | 12 +++++++++--- cmake/XrplCompiler.cmake | 1 + 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/.config/cspell.config.yaml b/.config/cspell.config.yaml index 0cac82807d..edcdcc92ba 100644 --- a/.config/cspell.config.yaml +++ b/.config/cspell.config.yaml @@ -51,6 +51,7 @@ words: - Btrfs - canonicality - checkme + - choco - chrono - citardauq - clawback diff --git a/.github/workflows/reusable-build-test-config.yml b/.github/workflows/reusable-build-test-config.yml index fc80bbd216..ae91a8bf20 100644 --- a/.github/workflows/reusable-build-test-config.yml +++ b/.github/workflows/reusable-build-test-config.yml @@ -100,7 +100,7 @@ jobs: uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 - name: Prepare runner - uses: XRPLF/actions/prepare-runner@65da1c59e81965eeb257caa3587b9d45066fb925 + uses: XRPLF/actions/prepare-runner@121d1de2775d486d46140b9a91b32d5002c08153 with: enable_ccache: ${{ inputs.ccache_enabled }} diff --git a/cmake/Ccache.cmake b/cmake/Ccache.cmake index 092212075c..aa8d3ac59d 100644 --- a/cmake/Ccache.cmake +++ b/cmake/Ccache.cmake @@ -46,6 +46,12 @@ set(CMAKE_VS_GLOBALS "TrackFileAccess=false" "UseMultiToolTask=true") -# By default Visual Studio generators will use /Zi, which is not compatible with -# ccache, so tell it to use /Z7 instead. -set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT "$<$:Embedded>") +# By default Visual Studio generators will use /Zi to capture debug information, +# which is not compatible with ccache, so tell it to use /Z7 instead. +if (MSVC) + foreach (var_ + CMAKE_C_FLAGS_DEBUG CMAKE_C_FLAGS_RELEASE + CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE) + string (REPLACE "/Zi" "/Z7" ${var_} "${${var_}}") + endforeach () +endif () diff --git a/cmake/XrplCompiler.cmake b/cmake/XrplCompiler.cmake index 622b2d2f74..0777bf948c 100644 --- a/cmake/XrplCompiler.cmake +++ b/cmake/XrplCompiler.cmake @@ -44,6 +44,7 @@ if (MSVC) # omit debug info completely under CI (not needed) if (is_ci) string (REPLACE "/Zi" " " ${var_} "${${var_}}") + string (REPLACE "/Z7" " " ${var_} "${${var_}}") endif () endforeach () From b2c5927b488fa0c306117de971f336c8d49317de Mon Sep 17 00:00:00 2001 From: Ed Hennis Date: Fri, 9 Jan 2026 23:10:04 -0400 Subject: [PATCH 17/30] fix: Inner batch transactions never have valid signatures (#6069) - Introduces amendment `fixBatchInnerSigs` - Update Batch unit tests - Fix all the Env instantiations to _use_ the "features" parameter. - testInnerSubmitRPC runs with Batch enabled and disabled. - Add a test to testInnerSubmitRPC for a correctly signed tx incorrectly using the tfInnerBatchTxn flag. - Generalize the submitAndValidate lambda in testInnerSubmitRPC. - With the fix amendment, a transaction never reaches the transaction engine (Transactor and derived classes.) - Test submitting a pseudo-transaction. Stopped before reaching the transaction engine, but with different errors. - The tests verify that without the amendment, a transaction with tfInnerBatchTxn is immediately rejected. Without the amendment, things are safe. The amendment just makes things safer and more future-proof. --- include/xrpl/protocol/detail/features.macro | 1 + src/test/app/Batch_test.cpp | 281 ++++++++++++++------ src/xrpld/app/misc/NetworkOPs.cpp | 2 +- src/xrpld/app/tx/detail/Transactor.cpp | 8 +- src/xrpld/app/tx/detail/apply.cpp | 21 +- 5 files changed, 221 insertions(+), 92 deletions(-) diff --git a/include/xrpl/protocol/detail/features.macro b/include/xrpl/protocol/detail/features.macro index 5c8d2aa198..932668c16f 100644 --- a/include/xrpl/protocol/detail/features.macro +++ b/include/xrpl/protocol/detail/features.macro @@ -16,6 +16,7 @@ // Add new amendments to the top of this list. // Keep it sorted in reverse chronological order. +XRPL_FIX (BatchInnerSigs, Supported::yes, VoteBehavior::DefaultNo) XRPL_FEATURE(LendingProtocol, Supported::no, VoteBehavior::DefaultNo) XRPL_FEATURE(PermissionDelegationV1_1, Supported::no, VoteBehavior::DefaultNo) XRPL_FIX (DirectoryLimit, Supported::yes, VoteBehavior::DefaultNo) diff --git a/src/test/app/Batch_test.cpp b/src/test/app/Batch_test.cpp index 6fbec52a93..68bf7e833b 100644 --- a/src/test/app/Batch_test.cpp +++ b/src/test/app/Batch_test.cpp @@ -148,15 +148,21 @@ class Batch_test : public beast::unit_test::suite void testEnable(FeatureBitset features) { - testcase("enabled"); - using namespace test::jtx; using namespace std::literals; + bool const withInnerSigFix = features[fixBatchInnerSigs]; + for (bool const withBatch : {true, false}) { + testcase << "enabled: Batch " + << (withBatch ? "enabled" : "disabled") + << ", Inner Sig Fix: " + << (withInnerSigFix ? "enabled" : "disabled"); + auto const amend = withBatch ? features : features - featureBatch; - test::jtx::Env env{*this, envconfig(), amend}; + + test::jtx::Env env{*this, amend}; auto const alice = Account("alice"); auto const bob = Account("bob"); @@ -179,7 +185,7 @@ class Batch_test : public beast::unit_test::suite // tfInnerBatchTxn // If the feature is disabled, the transaction fails with - // temINVALID_FLAG If the feature is enabled, the transaction fails + // temINVALID_FLAG. If the feature is enabled, the transaction fails // early in checkValidity() { auto const txResult = @@ -205,7 +211,7 @@ class Batch_test : public beast::unit_test::suite //---------------------------------------------------------------------- // preflight - test::jtx::Env env{*this, envconfig()}; + test::jtx::Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); @@ -617,7 +623,7 @@ class Batch_test : public beast::unit_test::suite //---------------------------------------------------------------------- // preclaim - test::jtx::Env env{*this, envconfig()}; + test::jtx::Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); @@ -858,7 +864,7 @@ class Batch_test : public beast::unit_test::suite using namespace test::jtx; using namespace std::literals; - test::jtx::Env env{*this, envconfig()}; + test::jtx::Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); @@ -949,7 +955,7 @@ class Batch_test : public beast::unit_test::suite using namespace test::jtx; using namespace std::literals; - test::jtx::Env env{*this, envconfig()}; + test::jtx::Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); @@ -1187,7 +1193,7 @@ class Batch_test : public beast::unit_test::suite // Bad Fee Without Signer { - test::jtx::Env env{*this, envconfig()}; + test::jtx::Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); @@ -1209,7 +1215,7 @@ class Batch_test : public beast::unit_test::suite // Bad Fee With MultiSign { - test::jtx::Env env{*this, envconfig()}; + test::jtx::Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); @@ -1236,7 +1242,7 @@ class Batch_test : public beast::unit_test::suite // Bad Fee With MultiSign + BatchSigners { - test::jtx::Env env{*this, envconfig()}; + test::jtx::Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); @@ -1265,7 +1271,7 @@ class Batch_test : public beast::unit_test::suite // Bad Fee With MultiSign + BatchSigners.Signers { - test::jtx::Env env{*this, envconfig()}; + test::jtx::Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); @@ -1297,7 +1303,7 @@ class Batch_test : public beast::unit_test::suite // Bad Fee With BatchSigners { - test::jtx::Env env{*this, envconfig()}; + test::jtx::Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); @@ -1321,7 +1327,7 @@ class Batch_test : public beast::unit_test::suite // Bad Fee Dynamic Fee Calculation { - test::jtx::Env env{*this, envconfig()}; + test::jtx::Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); @@ -1361,7 +1367,7 @@ class Batch_test : public beast::unit_test::suite // telENV_RPC_FAILED: Batch: txns array exceeds 8 entries. { - test::jtx::Env env{*this, envconfig()}; + test::jtx::Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); @@ -1386,7 +1392,7 @@ class Batch_test : public beast::unit_test::suite // temARRAY_TOO_LARGE: Batch: txns array exceeds 8 entries. { - test::jtx::Env env{*this, envconfig()}; + test::jtx::Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); @@ -1419,7 +1425,7 @@ class Batch_test : public beast::unit_test::suite // telENV_RPC_FAILED: Batch: signers array exceeds 8 entries. { - test::jtx::Env env{*this, envconfig()}; + test::jtx::Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); @@ -1438,7 +1444,7 @@ class Batch_test : public beast::unit_test::suite // temARRAY_TOO_LARGE: Batch: signers array exceeds 8 entries. { - test::jtx::Env env{*this, envconfig()}; + test::jtx::Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); @@ -1472,7 +1478,7 @@ class Batch_test : public beast::unit_test::suite using namespace test::jtx; using namespace std::literals; - test::jtx::Env env{*this, envconfig()}; + test::jtx::Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); @@ -1608,7 +1614,7 @@ class Batch_test : public beast::unit_test::suite using namespace test::jtx; using namespace std::literals; - test::jtx::Env env{*this, envconfig()}; + test::jtx::Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); @@ -1840,7 +1846,7 @@ class Batch_test : public beast::unit_test::suite using namespace test::jtx; using namespace std::literals; - test::jtx::Env env{*this, envconfig()}; + test::jtx::Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); @@ -2062,7 +2068,7 @@ class Batch_test : public beast::unit_test::suite using namespace test::jtx; using namespace std::literals; - test::jtx::Env env{*this, envconfig()}; + test::jtx::Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); @@ -2248,14 +2254,26 @@ class Batch_test : public beast::unit_test::suite } void - testInnerSubmitRPC(FeatureBitset features) + doTestInnerSubmitRPC(FeatureBitset features, bool withBatch) { - testcase("inner submit rpc"); + bool const withInnerSigFix = features[fixBatchInnerSigs]; + + std::string const testName = [&]() { + std::stringstream ss; + ss << "inner submit rpc: batch " + << (withBatch ? "enabled" : "disabled") << ", inner sig fix: " + << (withInnerSigFix ? "enabled" : "disabled") << ": "; + return ss.str(); + }(); + + auto const amend = withBatch ? features : features - featureBatch; using namespace test::jtx; using namespace std::literals; - test::jtx::Env env{*this, envconfig()}; + test::jtx::Env env{*this, amend}; + if (!BEAST_EXPECT(amend[featureBatch] == withBatch)) + return; auto const alice = Account("alice"); auto const bob = Account("bob"); @@ -2263,75 +2281,170 @@ class Batch_test : public beast::unit_test::suite env.fund(XRP(10000), alice, bob); env.close(); - auto submitAndValidate = [&](Slice const& slice) { - auto const jrr = env.rpc("submit", strHex(slice))[jss::result]; - BEAST_EXPECT( - jrr[jss::status] == "error" && - jrr[jss::error] == "invalidTransaction" && - jrr[jss::error_exception] == - "fails local checks: Malformed: Invalid inner batch " - "transaction."); - env.close(); - }; + auto submitAndValidate = + [&](std::string caseName, + Slice const& slice, + int line, + std::optional expectedEnabled = std::nullopt, + std::optional expectedDisabled = std::nullopt, + bool expectInvalidFlag = false) { + testcase << testName << caseName + << (expectInvalidFlag + ? " - Expected to reach tx engine!" + : ""); + auto const jrr = env.rpc("submit", strHex(slice))[jss::result]; + auto const expected = withBatch + ? expectedEnabled.value_or( + "fails local checks: Malformed: Invalid inner batch " + "transaction.") + : expectedDisabled.value_or( + "fails local checks: Empty SigningPubKey."); + if (expectInvalidFlag) + { + expect( + jrr[jss::status] == "success" && + jrr[jss::engine_result] == "temINVALID_FLAG", + pretty(jrr), + __FILE__, + line); + } + else + { + expect( + jrr[jss::status] == "error" && + jrr[jss::error] == "invalidTransaction" && + jrr[jss::error_exception] == expected, + pretty(jrr), + __FILE__, + line); + } + env.close(); + }; // Invalid RPC Submission: TxnSignature - // - has `TxnSignature` field + // + has `TxnSignature` field // - has no `SigningPubKey` field // - has no `Signers` field - // - has `tfInnerBatchTxn` flag + // + has `tfInnerBatchTxn` flag { auto txn = batch::inner(pay(alice, bob, XRP(1)), env.seq(alice)); txn[sfTxnSignature] = "DEADBEEF"; STParsedJSONObject parsed("test", txn.getTxn()); Serializer s; parsed.object->add(s); - submitAndValidate(s.slice()); + submitAndValidate("TxnSignature set", s.slice(), __LINE__); } // Invalid RPC Submission: SigningPubKey // - has no `TxnSignature` field - // - has `SigningPubKey` field + // + has `SigningPubKey` field // - has no `Signers` field - // - has `tfInnerBatchTxn` flag + // + has `tfInnerBatchTxn` flag { auto txn = batch::inner(pay(alice, bob, XRP(1)), env.seq(alice)); txn[sfSigningPubKey] = strHex(alice.pk()); STParsedJSONObject parsed("test", txn.getTxn()); Serializer s; parsed.object->add(s); - submitAndValidate(s.slice()); + submitAndValidate( + "SigningPubKey set", + s.slice(), + __LINE__, + std::nullopt, + "fails local checks: Invalid signature."); } // Invalid RPC Submission: Signers // - has no `TxnSignature` field - // - has empty `SigningPubKey` field - // - has `Signers` field - // - has `tfInnerBatchTxn` flag + // + has empty `SigningPubKey` field + // + has `Signers` field + // + has `tfInnerBatchTxn` flag { auto txn = batch::inner(pay(alice, bob, XRP(1)), env.seq(alice)); txn[sfSigners] = Json::arrayValue; STParsedJSONObject parsed("test", txn.getTxn()); Serializer s; parsed.object->add(s); - submitAndValidate(s.slice()); + submitAndValidate( + "Signers set", + s.slice(), + __LINE__, + std::nullopt, + "fails local checks: Invalid Signers array size."); + } + + { + // Fully signed inner batch transaction + auto const txn = + batch::inner(pay(alice, bob, XRP(1)), env.seq(alice)); + auto const jt = env.jt(txn.getTxn()); + + STParsedJSONObject parsed("test", jt.jv); + Serializer s; + parsed.object->add(s); + submitAndValidate( + "Fully signed", + s.slice(), + __LINE__, + std::nullopt, + std::nullopt, + !withBatch); } // Invalid RPC Submission: tfInnerBatchTxn // - has no `TxnSignature` field - // - has empty `SigningPubKey` field + // + has empty `SigningPubKey` field // - has no `Signers` field - // - has `tfInnerBatchTxn` flag + // + has `tfInnerBatchTxn` flag { auto txn = batch::inner(pay(alice, bob, XRP(1)), env.seq(alice)); STParsedJSONObject parsed("test", txn.getTxn()); Serializer s; parsed.object->add(s); - auto const jrr = env.rpc("submit", strHex(s.slice()))[jss::result]; - BEAST_EXPECT( - jrr[jss::status] == "success" && - jrr[jss::engine_result] == "temINVALID_FLAG"); + submitAndValidate( + "No signing fields set", + s.slice(), + __LINE__, + "fails local checks: Empty SigningPubKey.", + "fails local checks: Empty SigningPubKey.", + withBatch && !withInnerSigFix); + } - env.close(); + // Invalid RPC Submission: tfInnerBatchTxn pseudo-transaction + // - has no `TxnSignature` field + // + has empty `SigningPubKey` field + // - has no `Signers` field + // + has `tfInnerBatchTxn` flag + { + STTx amendTx( + ttAMENDMENT, [seq = env.closed()->header().seq + 1](auto& obj) { + obj.setAccountID(sfAccount, AccountID()); + obj.setFieldH256(sfAmendment, fixBatchInnerSigs); + obj.setFieldU32(sfLedgerSequence, seq); + obj.setFieldU32(sfFlags, tfInnerBatchTxn); + }); + auto txn = batch::inner( + amendTx.getJson(JsonOptions::none), env.seq(alice)); + STParsedJSONObject parsed("test", txn.getTxn()); + Serializer s; + parsed.object->add(s); + submitAndValidate( + "Pseudo-transaction", + s.slice(), + __LINE__, + withInnerSigFix + ? "fails local checks: Empty SigningPubKey." + : "fails local checks: Cannot submit pseudo transactions.", + "fails local checks: Empty SigningPubKey."); + } + } + + void + testInnerSubmitRPC(FeatureBitset features) + { + for (bool const withBatch : {true, false}) + { + doTestInnerSubmitRPC(features, withBatch); } } @@ -2343,7 +2456,7 @@ class Batch_test : public beast::unit_test::suite using namespace test::jtx; using namespace std::literals; - test::jtx::Env env{*this, envconfig()}; + test::jtx::Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); @@ -2390,7 +2503,7 @@ class Batch_test : public beast::unit_test::suite using namespace test::jtx; using namespace std::literals; - test::jtx::Env env{*this, envconfig()}; + test::jtx::Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); @@ -2443,7 +2556,7 @@ class Batch_test : public beast::unit_test::suite // tfIndependent: account delete success { - test::jtx::Env env{*this, envconfig()}; + test::jtx::Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); @@ -2484,7 +2597,7 @@ class Batch_test : public beast::unit_test::suite // tfIndependent: account delete fails { - test::jtx::Env env{*this, envconfig()}; + test::jtx::Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); @@ -2529,7 +2642,7 @@ class Batch_test : public beast::unit_test::suite // tfAllOrNothing: account delete fails { - test::jtx::Env env{*this, envconfig()}; + test::jtx::Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); @@ -2581,7 +2694,6 @@ class Batch_test : public beast::unit_test::suite test::jtx::Env env{ *this, - envconfig(), features | featureSingleAssetVault | featureLendingProtocol | featureMPTokensV1}; @@ -2776,7 +2888,7 @@ class Batch_test : public beast::unit_test::suite using namespace test::jtx; using namespace std::literals; - test::jtx::Env env{*this, envconfig()}; + test::jtx::Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); @@ -2889,7 +3001,7 @@ class Batch_test : public beast::unit_test::suite using namespace test::jtx; using namespace std::literals; - test::jtx::Env env{*this, envconfig()}; + test::jtx::Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); @@ -2947,7 +3059,7 @@ class Batch_test : public beast::unit_test::suite using namespace test::jtx; using namespace std::literals; - test::jtx::Env env{*this, envconfig()}; + test::jtx::Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); @@ -3009,7 +3121,7 @@ class Batch_test : public beast::unit_test::suite using namespace test::jtx; using namespace std::literals; - test::jtx::Env env{*this, envconfig()}; + test::jtx::Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); @@ -3058,7 +3170,7 @@ class Batch_test : public beast::unit_test::suite using namespace test::jtx; using namespace std::literals; - test::jtx::Env env{*this, envconfig()}; + test::jtx::Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); @@ -3106,7 +3218,7 @@ class Batch_test : public beast::unit_test::suite using namespace test::jtx; using namespace std::literals; - test::jtx::Env env{*this, envconfig()}; + test::jtx::Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); @@ -3169,7 +3281,7 @@ class Batch_test : public beast::unit_test::suite // overwritten by the payment in the batch transaction. Because the // terPRE_SEQ is outside of the batch this noop transaction will ge // reapplied in the following ledger - test::jtx::Env env{*this, envconfig()}; + test::jtx::Env env{*this, features}; env.fund(XRP(10000), alice, bob, carol); env.close(); @@ -3216,7 +3328,7 @@ class Batch_test : public beast::unit_test::suite // IMPORTANT: The batch txn is applied first, then the noop txn. // Because of this ordering, the noop txn is not applied and is // overwritten by the payment in the batch transaction. - test::jtx::Env env{*this, envconfig()}; + test::jtx::Env env{*this, features}; env.fund(XRP(10000), alice, bob); env.close(); @@ -3258,7 +3370,7 @@ class Batch_test : public beast::unit_test::suite // IMPORTANT: The batch txn is applied first, then the noop txn. // Because of this ordering, the noop txn is not applied and is // overwritten by the payment in the batch transaction. - test::jtx::Env env{*this, envconfig()}; + test::jtx::Env env{*this, features}; env.fund(XRP(10000), alice, bob); env.close(); @@ -3295,7 +3407,7 @@ class Batch_test : public beast::unit_test::suite // Outer Batch terPRE_SEQ { - test::jtx::Env env{*this, envconfig()}; + test::jtx::Env env{*this, features}; env.fund(XRP(10000), alice, bob, carol); env.close(); @@ -3353,7 +3465,7 @@ class Batch_test : public beast::unit_test::suite // IMPORTANT: The batch txn is applied first, then the noop txn. // Because of this ordering, the noop txn is not applied and is // overwritten by the payment in the batch transaction. - test::jtx::Env env{*this, envconfig()}; + test::jtx::Env env{*this, features}; env.fund(XRP(10000), alice, bob); env.close(); @@ -3402,7 +3514,7 @@ class Batch_test : public beast::unit_test::suite // IMPORTANT: The batch txn is applied first, then the noop txn. // Because of this ordering, the noop txn is not applied and is // overwritten by the payment in the batch transaction. - test::jtx::Env env{*this, envconfig()}; + test::jtx::Env env{*this, features}; env.fund(XRP(10000), alice, bob); env.close(); @@ -3464,7 +3576,7 @@ class Batch_test : public beast::unit_test::suite // batch will run in the close ledger process. The batch will be // allied and then retry this transaction in the current ledger. - test::jtx::Env env{*this, envconfig()}; + test::jtx::Env env{*this, features}; env.fund(XRP(10000), alice, bob); env.close(); @@ -3511,7 +3623,7 @@ class Batch_test : public beast::unit_test::suite // Create Object Before Batch Txn { - test::jtx::Env env{*this, envconfig()}; + test::jtx::Env env{*this, features}; env.fund(XRP(10000), alice, bob); env.close(); @@ -3558,7 +3670,7 @@ class Batch_test : public beast::unit_test::suite // batch will run in the close ledger process. The batch will be // applied and then retry this transaction in the current ledger. - test::jtx::Env env{*this, envconfig()}; + test::jtx::Env env{*this, features}; env.fund(XRP(10000), alice, bob); env.close(); @@ -3605,7 +3717,7 @@ class Batch_test : public beast::unit_test::suite using namespace test::jtx; using namespace std::literals; - test::jtx::Env env{*this, envconfig()}; + test::jtx::Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); @@ -3644,7 +3756,7 @@ class Batch_test : public beast::unit_test::suite using namespace test::jtx; using namespace std::literals; - test::jtx::Env env{*this, envconfig()}; + test::jtx::Env env{*this, features}; XRPAmount const baseFee = env.current()->fees().base; auto const alice = Account("alice"); @@ -3725,6 +3837,7 @@ class Batch_test : public beast::unit_test::suite *this, makeSmallQueueConfig( {{"minimum_txn_in_ledger_standalone", "2"}}), + features, nullptr, beast::severities::kError}; @@ -3785,6 +3898,7 @@ class Batch_test : public beast::unit_test::suite *this, makeSmallQueueConfig( {{"minimum_txn_in_ledger_standalone", "2"}}), + features, nullptr, beast::severities::kError}; @@ -3892,7 +4006,7 @@ class Batch_test : public beast::unit_test::suite // delegated non atomic inner { - test::jtx::Env env{*this, envconfig()}; + test::jtx::Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); @@ -3937,7 +4051,7 @@ class Batch_test : public beast::unit_test::suite // delegated atomic inner { - test::jtx::Env env{*this, envconfig()}; + test::jtx::Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); @@ -3989,7 +4103,7 @@ class Batch_test : public beast::unit_test::suite // this also makes sure tfInnerBatchTxn won't block delegated AccountSet // with granular permission { - test::jtx::Env env{*this, envconfig()}; + test::jtx::Env env{*this, features}; auto const alice = Account("alice"); auto const bob = Account("bob"); @@ -4038,7 +4152,7 @@ class Batch_test : public beast::unit_test::suite // this also makes sure tfInnerBatchTxn won't block delegated // MPTokenIssuanceSet with granular permission { - test::jtx::Env env{*this, envconfig()}; + test::jtx::Env env{*this, features}; Account alice{"alice"}; Account bob{"bob"}; env.fund(XRP(100000), alice, bob); @@ -4094,7 +4208,7 @@ class Batch_test : public beast::unit_test::suite // this also makes sure tfInnerBatchTxn won't block delegated TrustSet // with granular permission { - test::jtx::Env env{*this, envconfig()}; + test::jtx::Env env{*this, features}; Account gw{"gw"}; Account alice{"alice"}; Account bob{"bob"}; @@ -4134,7 +4248,7 @@ class Batch_test : public beast::unit_test::suite // inner transaction not authorized by the delegating account. { - test::jtx::Env env{*this, envconfig()}; + test::jtx::Env env{*this, features}; Account gw{"gw"}; Account alice{"alice"}; Account bob{"bob"}; @@ -4182,7 +4296,7 @@ class Batch_test : public beast::unit_test::suite testcase("Validate RPC response"); using namespace jtx; - Env env(*this); + Env env(*this, features); Account const alice("alice"); Account const bob("bob"); env.fund(XRP(10000), alice, bob); @@ -4259,7 +4373,7 @@ class Batch_test : public beast::unit_test::suite testBatchCalculateBaseFee(FeatureBitset features) { using namespace jtx; - Env env(*this); + Env env(*this, features); Account const alice("alice"); Account const bob("bob"); Account const carol("carol"); @@ -4384,6 +4498,7 @@ public: { using namespace test::jtx; auto const sa = testable_amendments(); + testWithFeats(sa - fixBatchInnerSigs); testWithFeats(sa); } }; diff --git a/src/xrpld/app/misc/NetworkOPs.cpp b/src/xrpld/app/misc/NetworkOPs.cpp index 6a00354b15..2422ec4ae6 100644 --- a/src/xrpld/app/misc/NetworkOPs.cpp +++ b/src/xrpld/app/misc/NetworkOPs.cpp @@ -1681,7 +1681,7 @@ NetworkOPsImp::apply(std::unique_lock& batchLock) // only be set if the Batch feature is enabled. If Batch is // not enabled, the flag is always invalid, so don't relay // it regardless. - !sttx.isFlag(tfInnerBatchTxn)) + !(sttx.isFlag(tfInnerBatchTxn))) { protocol::TMTransaction tx; Serializer s; diff --git a/src/xrpld/app/tx/detail/Transactor.cpp b/src/xrpld/app/tx/detail/Transactor.cpp index 851712fe90..a834f7c6c3 100644 --- a/src/xrpld/app/tx/detail/Transactor.cpp +++ b/src/xrpld/app/tx/detail/Transactor.cpp @@ -204,8 +204,14 @@ Transactor::preflight2(PreflightContext const& ctx) // regardless of success or failure return *ret; + // It should be impossible for the InnerBatchTxn flag to be set without + // featureBatch being enabled + XRPL_ASSERT_PARTS( + !ctx.tx.isFlag(tfInnerBatchTxn) || ctx.rules.enabled(featureBatch), + "xrpl::Transactor::preflight2", + "InnerBatch flag only set if feature enabled"); // Skip signature check on batch inner transactions - if (ctx.tx.isFlag(tfInnerBatchTxn) && !ctx.rules.enabled(featureBatch)) + if (ctx.tx.isFlag(tfInnerBatchTxn) && ctx.rules.enabled(featureBatch)) return tesSUCCESS; // Do not add any checks after this point that are relevant for // batch inner transactions. They will be skipped. diff --git a/src/xrpld/app/tx/detail/apply.cpp b/src/xrpld/app/tx/detail/apply.cpp index 5209c46f8f..a75f0cc967 100644 --- a/src/xrpld/app/tx/detail/apply.cpp +++ b/src/xrpld/app/tx/detail/apply.cpp @@ -41,15 +41,22 @@ checkValidity( Validity::SigBad, "Malformed: Invalid inner batch transaction."}; - std::string reason; - if (!passesLocalChecks(tx, reason)) + // This block should probably have never been included in the + // original `Batch` implementation. An inner transaction never + // has a valid signature. + bool const neverValid = rules.enabled(fixBatchInnerSigs); + if (!neverValid) { - router.setFlags(id, SF_LOCALBAD); - return {Validity::SigGoodOnly, reason}; - } + std::string reason; + if (!passesLocalChecks(tx, reason)) + { + router.setFlags(id, SF_LOCALBAD); + return {Validity::SigGoodOnly, reason}; + } - router.setFlags(id, SF_SIGGOOD); - return {Validity::Valid, ""}; + router.setFlags(id, SF_SIGGOOD); + return {Validity::Valid, ""}; + } } if (any(flags & SF_SIGBAD)) From 92d40de4cbb787474f90d6aa6a959963f8299992 Mon Sep 17 00:00:00 2001 From: Bart Date: Mon, 12 Jan 2026 12:53:46 -0500 Subject: [PATCH 18/30] chore: Pin pre-commit hooks to commit hashes (#6205) This change updates and pins the Black and CSpell pre-commit hooks. --- .config/cspell.config.yaml | 4 ++++ .pre-commit-config.yaml | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.config/cspell.config.yaml b/.config/cspell.config.yaml index edcdcc92ba..8f782d9960 100644 --- a/.config/cspell.config.yaml +++ b/.config/cspell.config.yaml @@ -69,6 +69,7 @@ words: - cryptoconditional - cryptoconditions - csprng + - ctest - ctid - currenttxhash - daria @@ -104,6 +105,7 @@ words: - iou - ious - isrdc + - itype - jemalloc - jlog - keylet @@ -192,10 +194,12 @@ words: - roundings - sahyadri - Satoshi + - scons - secp - sendq - seqit - sf + - SFIELD - shamap - shamapitem - sidechain diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 00bec32ed6..603cf39375 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -32,12 +32,12 @@ repos: - id: prettier - repo: https://github.com/psf/black-pre-commit-mirror - rev: 25.11.0 + rev: 831207fd435b47aeffdf6af853097e64322b4d44 # frozen: v25.12.0 hooks: - id: black - repo: https://github.com/streetsidesoftware/cspell-cli - rev: v9.2.0 + rev: 1cfa010f078c354f3ffb8413616280cc28f5ba21 # frozen: v9.4.0 hooks: - id: cspell # Spell check changed files exclude: .config/cspell.config.yaml From 4755bb86068d056258d643274f8e75c878560685 Mon Sep 17 00:00:00 2001 From: Bart Date: Mon, 12 Jan 2026 19:14:39 -0500 Subject: [PATCH 19/30] refactor: Remove unnecessary version number and options in cmake find_package (#6169) This change removes unnecessary version numbers in the OpenSSL and Boost `find_package` CMake statements. An unnecessary OpenSSL definition is removed, while Conan options for SSL are updated to disable insecure ciphers. Moreover, the statements are now ordered alphabetically and more logically. --- BUILD.md | 19 ++++++++----- CMakeLists.txt | 37 ++++++++++--------------- cmake/deps/Boost.cmake | 2 +- conan.lock | 62 +++++++++++++++++++++--------------------- conanfile.py | 6 ++++ 5 files changed, 65 insertions(+), 61 deletions(-) diff --git a/BUILD.md b/BUILD.md index 85b3e3ea74..2d1ac9b134 100644 --- a/BUILD.md +++ b/BUILD.md @@ -148,7 +148,8 @@ function extract_version { } # Define which recipes to export. -recipes=(ed25519 grpc secp256k1 snappy soci) +recipes=('ed25519' 'grpc' 'openssl' 'secp256k1' 'snappy' 'soci') +folders=('all' 'all' '3.x.x' 'all' 'all' 'all') # Selectively check out the recipes from our CCI fork. cd external @@ -157,20 +158,24 @@ cd conan-center-index git init git remote add origin git@github.com:XRPLF/conan-center-index.git git sparse-checkout init -for recipe in ${recipes[@]}; do - echo "Checking out ${recipe}..." - git sparse-checkout add recipes/${recipe}/all +for ((index = 1; index <= ${#recipes[@]}; index++)); do + recipe=${recipes[index]} + folder=${folders[index]} + echo "Checking out recipe '${recipe}' from folder '${folder}'..." + git sparse-checkout add recipes/${recipe}/${folder} done git fetch origin master git checkout master cd ../.. # Export the recipes into the local cache. -for recipe in ${recipes[@]}; do +for ((index = 1; index <= ${#recipes[@]}; index++)); do + recipe=${recipes[index]} + folder=${folders[index]} version=$(extract_version ${recipe}) - echo "Exporting ${recipe}/${version}..." + echo "Exporting '${recipe}/${version}' from '${recipe}/${folder}'..." conan export --version $(extract_version ${recipe}) \ - external/conan-center-index/recipes/${recipe}/all + external/conan-center-index/recipes/${recipe}/${folder} done ``` diff --git a/CMakeLists.txt b/CMakeLists.txt index 70bc02c66d..26fc310d39 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -88,34 +88,18 @@ endif() ### include(deps/Boost) -find_package(OpenSSL 1.1.1 REQUIRED) -set_target_properties(OpenSSL::SSL PROPERTIES - INTERFACE_COMPILE_DEFINITIONS OPENSSL_NO_SSL2 -) add_subdirectory(external/antithesis-sdk) -find_package(gRPC REQUIRED) -find_package(lz4 REQUIRED) -# Target names with :: are not allowed in a generator expression. -# We need to pull the include directories and imported location properties -# from separate targets. -find_package(LibArchive REQUIRED) -find_package(SOCI REQUIRED) -find_package(SQLite3 REQUIRED) - -option(rocksdb "Enable RocksDB" ON) -if(rocksdb) - find_package(RocksDB REQUIRED) - set_target_properties(RocksDB::rocksdb PROPERTIES - INTERFACE_COMPILE_DEFINITIONS XRPL_ROCKSDB_AVAILABLE=1 - ) - target_link_libraries(xrpl_libs INTERFACE RocksDB::rocksdb) -endif() - find_package(date REQUIRED) find_package(ed25519 REQUIRED) +find_package(gRPC REQUIRED) +find_package(LibArchive REQUIRED) +find_package(lz4 REQUIRED) find_package(nudb REQUIRED) +find_package(OpenSSL REQUIRED) find_package(secp256k1 REQUIRED) +find_package(SOCI REQUIRED) +find_package(SQLite3 REQUIRED) find_package(xxHash REQUIRED) target_link_libraries(xrpl_libs INTERFACE @@ -128,6 +112,15 @@ target_link_libraries(xrpl_libs INTERFACE SQLite::SQLite3 ) +option(rocksdb "Enable RocksDB" ON) +if(rocksdb) + find_package(RocksDB REQUIRED) + set_target_properties(RocksDB::rocksdb PROPERTIES + INTERFACE_COMPILE_DEFINITIONS XRPL_ROCKSDB_AVAILABLE=1 + ) + target_link_libraries(xrpl_libs INTERFACE RocksDB::rocksdb) +endif() + # Work around changes to Conan recipe for now. if(TARGET nudb::core) set(nudb nudb::core) diff --git a/cmake/deps/Boost.cmake b/cmake/deps/Boost.cmake index 475c1033b2..19263e0ac9 100644 --- a/cmake/deps/Boost.cmake +++ b/cmake/deps/Boost.cmake @@ -1,4 +1,4 @@ -find_package(Boost 1.82 REQUIRED +find_package(Boost REQUIRED COMPONENTS chrono container diff --git a/conan.lock b/conan.lock index 1385ad05bd..99522e79b2 100644 --- a/conan.lock +++ b/conan.lock @@ -1,44 +1,44 @@ { "version": "0.5", "requires": [ - "zlib/1.3.1#b8bc2603263cf7eccbd6e17e66b0ed76%1756234269.497", - "xxhash/0.8.3#681d36a0a6111fc56e5e45ea182c19cc%1756234289.683", - "sqlite3/3.49.1#8631739a4c9b93bd3d6b753bac548a63%1756234266.869", - "soci/4.0.3#a9f8d773cd33e356b5879a4b0564f287%1756234262.318", - "snappy/1.1.10#968fef506ff261592ec30c574d4a7809%1756234314.246", - "secp256k1/0.7.0#9c4ab67bdc3860c16ea5b36aed8f74ea%1765202256.763", - "rocksdb/10.5.1#4a197eca381a3e5ae8adf8cffa5aacd0%1762797952.535", - "re2/20230301#ca3b241baec15bd31ea9187150e0b333%1764175362.029", - "protobuf/6.32.1#f481fd276fc23a33b85a3ed1e898b693%1764863245.83", - "openssl/3.5.4#a1d5835cc6ed5c5b8f3cd5b9b5d24205%1760106486.594", - "nudb/2.0.9#fb8dfd1a5557f5e0528114c2da17721e%1763150366.909", - "lz4/1.10.0#59fc63cac7f10fbe8e05c7e62c2f3504%1756234228.999", - "libiconv/1.17#1e65319e945f2d31941a9d28cc13c058%1756223727.64", - "libbacktrace/cci.20210118#a7691bfccd8caaf66309df196790a5a1%1756230911.03", - "libarchive/3.8.1#ffee18995c706e02bf96e7a2f7042e0d%1764175360.142", + "zlib/1.3.1#b8bc2603263cf7eccbd6e17e66b0ed76%1765850150.075", + "xxhash/0.8.3#681d36a0a6111fc56e5e45ea182c19cc%1765850149.987", + "sqlite3/3.49.1#8631739a4c9b93bd3d6b753bac548a63%1765850149.926", + "soci/4.0.3#a9f8d773cd33e356b5879a4b0564f287%1765850149.46", + "snappy/1.1.10#968fef506ff261592ec30c574d4a7809%1765850147.878", + "secp256k1/0.7.0#9c4ab67bdc3860c16ea5b36aed8f74ea%1765850147.928", + "rocksdb/10.5.1#4a197eca381a3e5ae8adf8cffa5aacd0%1765850186.86", + "re2/20230301#ca3b241baec15bd31ea9187150e0b333%1765850148.103", + "protobuf/6.32.1#f481fd276fc23a33b85a3ed1e898b693%1765850161.038", + "openssl/3.5.4#58f5173c2ee51d6fc0f0c61b4eddadbb%1768259092.666", + "nudb/2.0.9#fb8dfd1a5557f5e0528114c2da17721e%1765850143.957", + "lz4/1.10.0#59fc63cac7f10fbe8e05c7e62c2f3504%1765850143.914", + "libiconv/1.17#1e65319e945f2d31941a9d28cc13c058%1765842973.492", + "libbacktrace/cci.20210118#a7691bfccd8caaf66309df196790a5a1%1765842973.03", + "libarchive/3.8.1#ffee18995c706e02bf96e7a2f7042e0d%1765850144.736", "jemalloc/5.3.0#e951da9cf599e956cebc117880d2d9f8%1729241615.244", - "grpc/1.72.0#f244a57bff01e708c55a1100b12e1589%1763158050.628", - "ed25519/2015.03#ae761bdc52730a843f0809bdf6c1b1f6%1764270189.893", - "doctest/2.4.12#eb9fb352fb2fdfc8abb17ec270945165%1762797941.757", - "date/3.0.4#862e11e80030356b53c2c38599ceb32b%1763584497.32", - "c-ares/1.34.5#5581c2b62a608b40bb85d965ab3ec7c8%1764175359.429", - "bzip2/1.0.8#c470882369c2d95c5c77e970c0c7e321%1764175359.429", - "boost/1.88.0#8852c0b72ce8271fb8ff7c53456d4983%1756223752.326", - "abseil/20250127.0#9e8e8cfc89a1324139fc0ee3bd4d8c8c%1753819045.301" + "grpc/1.72.0#f244a57bff01e708c55a1100b12e1589%1765850193.734", + "ed25519/2015.03#ae761bdc52730a843f0809bdf6c1b1f6%1765850143.772", + "doctest/2.4.12#eb9fb352fb2fdfc8abb17ec270945165%1765850143.95", + "date/3.0.4#862e11e80030356b53c2c38599ceb32b%1765850143.772", + "c-ares/1.34.5#5581c2b62a608b40bb85d965ab3ec7c8%1765850144.336", + "bzip2/1.0.8#c470882369c2d95c5c77e970c0c7e321%1765850143.837", + "boost/1.88.0#8852c0b72ce8271fb8ff7c53456d4983%1765850172.862", + "abseil/20250127.0#99262a368bd01c0ccca8790dfced9719%1766517936.993" ], "build_requires": [ - "zlib/1.3.1#b8bc2603263cf7eccbd6e17e66b0ed76%1756234269.497", - "strawberryperl/5.32.1.1#707032463aa0620fa17ec0d887f5fe41%1756234281.733", - "protobuf/6.32.1#f481fd276fc23a33b85a3ed1e898b693%1764863245.83", - "nasm/2.16.01#31e26f2ee3c4346ecd347911bd126904%1756234232.901", + "zlib/1.3.1#b8bc2603263cf7eccbd6e17e66b0ed76%1765850150.075", + "strawberryperl/5.32.1.1#707032463aa0620fa17ec0d887f5fe41%1765850165.196", + "protobuf/6.32.1#f481fd276fc23a33b85a3ed1e898b693%1765850161.038", + "nasm/2.16.01#31e26f2ee3c4346ecd347911bd126904%1765850144.707", "msys2/cci.latest#1996656c3c98e5765b25b60ff5cf77b4%1764840888.758", "m4/1.4.19#70dc8bbb33e981d119d2acc0175cf381%1763158052.846", - "cmake/4.2.0#ae0a44f44a1ef9ab68fd4b3e9a1f8671%1764175359.44", - "cmake/3.31.10#313d16a1aa16bbdb2ca0792467214b76%1764175359.429", - "b2/5.3.3#107c15377719889654eb9a162a673975%1756234226.28", + "cmake/4.2.0#ae0a44f44a1ef9ab68fd4b3e9a1f8671%1765850153.937", + "cmake/3.31.10#313d16a1aa16bbdb2ca0792467214b76%1765850153.479", + "b2/5.3.3#107c15377719889654eb9a162a673975%1765850144.355", "automake/1.16.5#b91b7c384c3deaa9d535be02da14d04f%1755524470.56", "autoconf/2.71#51077f068e61700d65bb05541ea1e4b0%1731054366.86", - "abseil/20250127.0#9e8e8cfc89a1324139fc0ee3bd4d8c8c%1753819045.301" + "abseil/20250127.0#99262a368bd01c0ccca8790dfced9719%1766517936.993" ], "python_requires": [], "overrides": { diff --git a/conanfile.py b/conanfile.py index 48e28cb275..96e3384979 100644 --- a/conanfile.py +++ b/conanfile.py @@ -87,7 +87,13 @@ class Xrpl(ConanFile): "libarchive/*:with_xattr": False, "libarchive/*:with_zlib": False, "lz4/*:shared": False, + "openssl/*:no_dtls": True, + "openssl/*:no_ssl": True, + "openssl/*:no_ssl3": True, + "openssl/*:no_tls1": True, + "openssl/*:no_tls1_1": True, "openssl/*:shared": False, + "openssl/*:tls_security_level": 2, "protobuf/*:shared": False, "protobuf/*:with_zlib": True, "rocksdb/*:enable_sse": False, From 0efae5d16e433d504064ff6f6174ed02a37c61ea Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Tue, 13 Jan 2026 16:52:10 +0000 Subject: [PATCH 20/30] ci: Update actions/images to use cmake 4.2.1 and conan 2.24.0 (#6209) --- .github/scripts/strategy-matrix/linux.json | 56 +++++++++---------- .github/workflows/pre-commit.yml | 4 +- .../workflows/reusable-build-test-config.yml | 2 +- .github/workflows/upload-conan-deps.yml | 2 +- 4 files changed, 32 insertions(+), 32 deletions(-) diff --git a/.github/scripts/strategy-matrix/linux.json b/.github/scripts/strategy-matrix/linux.json index 669754554c..e64a05f925 100644 --- a/.github/scripts/strategy-matrix/linux.json +++ b/.github/scripts/strategy-matrix/linux.json @@ -15,196 +15,196 @@ "distro_version": "bookworm", "compiler_name": "gcc", "compiler_version": "12", - "image_sha": "cc09fd3" + "image_sha": "ab4d1f0" }, { "distro_name": "debian", "distro_version": "bookworm", "compiler_name": "gcc", "compiler_version": "13", - "image_sha": "cc09fd3" + "image_sha": "ab4d1f0" }, { "distro_name": "debian", "distro_version": "bookworm", "compiler_name": "gcc", "compiler_version": "14", - "image_sha": "cc09fd3" + "image_sha": "ab4d1f0" }, { "distro_name": "debian", "distro_version": "bookworm", "compiler_name": "gcc", "compiler_version": "15", - "image_sha": "cc09fd3" + "image_sha": "ab4d1f0" }, { "distro_name": "debian", "distro_version": "bookworm", "compiler_name": "clang", "compiler_version": "16", - "image_sha": "cc09fd3" + "image_sha": "ab4d1f0" }, { "distro_name": "debian", "distro_version": "bookworm", "compiler_name": "clang", "compiler_version": "17", - "image_sha": "cc09fd3" + "image_sha": "ab4d1f0" }, { "distro_name": "debian", "distro_version": "bookworm", "compiler_name": "clang", "compiler_version": "18", - "image_sha": "cc09fd3" + "image_sha": "ab4d1f0" }, { "distro_name": "debian", "distro_version": "bookworm", "compiler_name": "clang", "compiler_version": "19", - "image_sha": "cc09fd3" + "image_sha": "ab4d1f0" }, { "distro_name": "debian", "distro_version": "bookworm", "compiler_name": "clang", "compiler_version": "20", - "image_sha": "cc09fd3" + "image_sha": "ab4d1f0" }, { "distro_name": "debian", "distro_version": "trixie", "compiler_name": "gcc", "compiler_version": "14", - "image_sha": "cc09fd3" + "image_sha": "ab4d1f0" }, { "distro_name": "debian", "distro_version": "trixie", "compiler_name": "gcc", "compiler_version": "15", - "image_sha": "cc09fd3" + "image_sha": "ab4d1f0" }, { "distro_name": "debian", "distro_version": "trixie", "compiler_name": "clang", "compiler_version": "20", - "image_sha": "cc09fd3" + "image_sha": "ab4d1f0" }, { "distro_name": "debian", "distro_version": "trixie", "compiler_name": "clang", "compiler_version": "21", - "image_sha": "cc09fd3" + "image_sha": "ab4d1f0" }, { "distro_name": "rhel", "distro_version": "8", "compiler_name": "gcc", "compiler_version": "14", - "image_sha": "cc09fd3" + "image_sha": "ab4d1f0" }, { "distro_name": "rhel", "distro_version": "8", "compiler_name": "clang", "compiler_version": "any", - "image_sha": "cc09fd3" + "image_sha": "ab4d1f0" }, { "distro_name": "rhel", "distro_version": "9", "compiler_name": "gcc", "compiler_version": "12", - "image_sha": "cc09fd3" + "image_sha": "ab4d1f0" }, { "distro_name": "rhel", "distro_version": "9", "compiler_name": "gcc", "compiler_version": "13", - "image_sha": "cc09fd3" + "image_sha": "ab4d1f0" }, { "distro_name": "rhel", "distro_version": "9", "compiler_name": "gcc", "compiler_version": "14", - "image_sha": "cc09fd3" + "image_sha": "ab4d1f0" }, { "distro_name": "rhel", "distro_version": "9", "compiler_name": "clang", "compiler_version": "any", - "image_sha": "cc09fd3" + "image_sha": "ab4d1f0" }, { "distro_name": "rhel", "distro_version": "10", "compiler_name": "gcc", "compiler_version": "14", - "image_sha": "cc09fd3" + "image_sha": "ab4d1f0" }, { "distro_name": "rhel", "distro_version": "10", "compiler_name": "clang", "compiler_version": "any", - "image_sha": "cc09fd3" + "image_sha": "ab4d1f0" }, { "distro_name": "ubuntu", "distro_version": "jammy", "compiler_name": "gcc", "compiler_version": "12", - "image_sha": "cc09fd3" + "image_sha": "ab4d1f0" }, { "distro_name": "ubuntu", "distro_version": "noble", "compiler_name": "gcc", "compiler_version": "13", - "image_sha": "cc09fd3" + "image_sha": "ab4d1f0" }, { "distro_name": "ubuntu", "distro_version": "noble", "compiler_name": "gcc", "compiler_version": "14", - "image_sha": "cc09fd3" + "image_sha": "ab4d1f0" }, { "distro_name": "ubuntu", "distro_version": "noble", "compiler_name": "clang", "compiler_version": "16", - "image_sha": "cc09fd3" + "image_sha": "ab4d1f0" }, { "distro_name": "ubuntu", "distro_version": "noble", "compiler_name": "clang", "compiler_version": "17", - "image_sha": "cc09fd3" + "image_sha": "ab4d1f0" }, { "distro_name": "ubuntu", "distro_version": "noble", "compiler_name": "clang", "compiler_version": "18", - "image_sha": "cc09fd3" + "image_sha": "ab4d1f0" }, { "distro_name": "ubuntu", "distro_version": "noble", "compiler_name": "clang", "compiler_version": "19", - "image_sha": "cc09fd3" + "image_sha": "ab4d1f0" } ], "build_type": ["Debug", "Release"], diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 41e82fb6bb..00754e5eae 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -9,7 +9,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@5ca417783f0312ab26d6f48b85c78edf1de99bbd + uses: XRPLF/actions/.github/workflows/pre-commit.yml@282890f46d6921249d5659dd38babcb0bd8aef48 with: runs_on: ubuntu-latest - container: '{ "image": "ghcr.io/xrplf/ci/tools-rippled-pre-commit:sha-a8c7be1" }' + container: '{ "image": "ghcr.io/xrplf/ci/tools-rippled-pre-commit:sha-ab4d1f0" }' diff --git a/.github/workflows/reusable-build-test-config.yml b/.github/workflows/reusable-build-test-config.yml index ae91a8bf20..bc0717e1a5 100644 --- a/.github/workflows/reusable-build-test-config.yml +++ b/.github/workflows/reusable-build-test-config.yml @@ -100,7 +100,7 @@ jobs: uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 - name: Prepare runner - uses: XRPLF/actions/prepare-runner@121d1de2775d486d46140b9a91b32d5002c08153 + uses: XRPLF/actions/prepare-runner@f05cab7b8541eee6473aa42beb9d2fe35608a190 with: enable_ccache: ${{ inputs.ccache_enabled }} diff --git a/.github/workflows/upload-conan-deps.yml b/.github/workflows/upload-conan-deps.yml index 55a9ab8864..29ae95fce5 100644 --- a/.github/workflows/upload-conan-deps.yml +++ b/.github/workflows/upload-conan-deps.yml @@ -70,7 +70,7 @@ jobs: uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 - name: Prepare runner - uses: XRPLF/actions/prepare-runner@65da1c59e81965eeb257caa3587b9d45066fb925 + uses: XRPLF/actions/prepare-runner@f05cab7b8541eee6473aa42beb9d2fe35608a190 with: enable_ccache: false From 96866049632f9691372ab3cce2a877664db7c056 Mon Sep 17 00:00:00 2001 From: Bart Date: Tue, 13 Jan 2026 12:29:04 -0500 Subject: [PATCH 21/30] fix: Update Conan lock file with changed OpenSSL recipe (#6211) This change updates the `conan.lock` file with a changed OpenSSL recipe that contains a fix regarding options passed to the compiler --- conan.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conan.lock b/conan.lock index 99522e79b2..44dc9031d2 100644 --- a/conan.lock +++ b/conan.lock @@ -10,7 +10,7 @@ "rocksdb/10.5.1#4a197eca381a3e5ae8adf8cffa5aacd0%1765850186.86", "re2/20230301#ca3b241baec15bd31ea9187150e0b333%1765850148.103", "protobuf/6.32.1#f481fd276fc23a33b85a3ed1e898b693%1765850161.038", - "openssl/3.5.4#58f5173c2ee51d6fc0f0c61b4eddadbb%1768259092.666", + "openssl/3.5.4#1b986e61b38fdfda3b40bebc1b234393%1768312656.257", "nudb/2.0.9#fb8dfd1a5557f5e0528114c2da17721e%1765850143.957", "lz4/1.10.0#59fc63cac7f10fbe8e05c7e62c2f3504%1765850143.914", "libiconv/1.17#1e65319e945f2d31941a9d28cc13c058%1765842973.492", From 2601442e16c11b8b174d154758abb8ac4a9a6564 Mon Sep 17 00:00:00 2001 From: Ed Hennis Date: Tue, 13 Jan 2026 15:42:58 -0400 Subject: [PATCH 22/30] Improve and fix bugs in Lending Protocol (#6102) - Spec: XLS-66 Fix overpayment asserts (#6084) MPTTester::operator() parameter should be std::int64_t - Originally defined as uint64_t, but the testIssuerLoan() test called it with a negative number, causing an overflow to a very large number that in some circumstances could be silently cast back to an int64_t, but might not be. I believe this is UB, and we don't want to rely on that. Review feedback from @Tapanito: overpayment value change - In overpayment results, the management fee was being calculated twice: once as part of the value change, and as part of the fees paid. Exclude it from the value change. Fix Overpayment Calculation (#6087) - Adds additional unit tests to cover math calculations. - Removes unused methods. Review feedback from @shawnxie999: even more rounding - Round the initial total value computation upward, unless there is 0-interest. - Rename getVaultScale to getAssetsTotalScale, and convert one incorrect computation to use it. - Use adjustImpreciseNumber for LossUnrealized. - Add some logging to computeLoanProperties. Fix LoanBrokerSet debtMaximum limits (#6116) Fix some minor bugs in Lending Protocol (#6101) - add nodiscard to unimpairLoan, and check result in LoanPay - add a check to verify that issuer exists - improve LoanManage error code for dust amounts Check permissions in LoanSet and LoanPay (#6108) Disallow pseudo accounts to be Destination for LoanBrokerCoverWithdraw (#6106) Ensure vault asset cap is not exceeded (#6124) Fix Overpayment ValueChange calculation in Lending Protocol (#6114) - Adds loan state to LoanProperties. - Cleans up computeLoanProperties. - Fixes missing management fee from overpayment. fix: Enable LP Deposits when the broker is the asset issuer (#6119) * Replace accountHolds with accountSpendable when checking for account funds in VaultDeposit and LoanBrokerCoverDeposit Add a few minor changes (#6158) - Updates or fixes a couple of things I noticed while reviewing changes to the spec. - Rename sfPreviousPaymentDate to sfPreviousPaymentDueDate. - Make the vault asset cap check added in #6124 a little more robust: 1. Check in preflight if the vault is _already_ over the limit. 2. Prevent overflow when checking with the loan value. (Subtract instead of adding, in case the values are near maxint. Both return the same result. Also add a unit test so each case is covered. Add minimum grace period validation (#6133) Fix bugs: frozen pseudo-account, and FLC cutoff (#6170) refactor: Rename raw state to theoretical state (#6187) Check if a withdrawal amount exceeds any applicable receiving limit. (#6117) Fix overpayment result calculation (#6195) Address review feedback from Lending Protocol re-review (#6161) --------- Co-authored-by: Gregory Tsipenyuk Co-authored-by: Bronek Kozicki Co-authored-by: Vito Tumas <5780819+Tapanito@users.noreply.github.com> Co-authored-by: Shawn Xie <35279399+shawnxie999@users.noreply.github.com> Co-authored-by: Jingchen --- .../paths => include/xrpl/ledger}/Credit.h | 4 +- include/xrpl/ledger/View.h | 86 +- .../xrpl/protocol/detail/ledger_entries.macro | 2 +- include/xrpl/protocol/detail/sfields.macro | 2 +- .../app/paths => libxrpl/ledger}/Credit.cpp | 0 src/libxrpl/ledger/View.cpp | 219 +-- src/test/app/LendingHelpers_test.cpp | 1351 +++++++++++++++++ src/test/app/LoanBroker_test.cpp | 468 ++++++ src/test/app/Loan_test.cpp | 765 ++++++++-- src/test/app/Vault_test.cpp | 4 +- src/test/jtx/impl/mpt.cpp | 2 +- src/test/jtx/mpt.h | 2 +- src/xrpld/app/misc/LendingHelpers.h | 180 ++- src/xrpld/app/misc/detail/LendingHelpers.cpp | 526 +++---- src/xrpld/app/paths/Flow.cpp | 2 +- src/xrpld/app/paths/detail/DirectStep.cpp | 2 +- src/xrpld/app/paths/detail/StrandFlow.h | 2 +- .../app/paths/detail/XRPEndpointStep.cpp | 2 +- .../app/tx/detail/LoanBrokerCoverClawback.cpp | 12 +- .../app/tx/detail/LoanBrokerCoverDeposit.cpp | 3 +- .../app/tx/detail/LoanBrokerCoverWithdraw.cpp | 5 + src/xrpld/app/tx/detail/LoanBrokerDelete.cpp | 44 +- src/xrpld/app/tx/detail/LoanBrokerSet.cpp | 19 + src/xrpld/app/tx/detail/LoanDelete.cpp | 2 +- src/xrpld/app/tx/detail/LoanManage.cpp | 78 +- src/xrpld/app/tx/detail/LoanManage.h | 4 +- src/xrpld/app/tx/detail/LoanPay.cpp | 128 +- src/xrpld/app/tx/detail/LoanSet.cpp | 69 +- src/xrpld/app/tx/detail/VaultDeposit.cpp | 8 +- src/xrpld/rpc/handlers/LedgerEntry.cpp | 2 +- 30 files changed, 3242 insertions(+), 751 deletions(-) rename {src/xrpld/app/paths => include/xrpl/ledger}/Credit.h (93%) rename src/{xrpld/app/paths => libxrpl/ledger}/Credit.cpp (100%) create mode 100644 src/test/app/LendingHelpers_test.cpp diff --git a/src/xrpld/app/paths/Credit.h b/include/xrpl/ledger/Credit.h similarity index 93% rename from src/xrpld/app/paths/Credit.h rename to include/xrpl/ledger/Credit.h index 5bdcd70e74..09b65b3dde 100644 --- a/src/xrpld/app/paths/Credit.h +++ b/include/xrpl/ledger/Credit.h @@ -1,5 +1,5 @@ -#ifndef XRPL_APP_PATHS_CREDIT_H_INCLUDED -#define XRPL_APP_PATHS_CREDIT_H_INCLUDED +#ifndef XRPL_LEDGER_CREDIT_H_INCLUDED +#define XRPL_LEDGER_CREDIT_H_INCLUDED #include #include diff --git a/include/xrpl/ledger/View.h b/include/xrpl/ledger/View.h index 767622596b..707a08b890 100644 --- a/include/xrpl/ledger/View.h +++ b/include/xrpl/ledger/View.h @@ -61,6 +61,9 @@ enum FreezeHandling { fhIGNORE_FREEZE, fhZERO_IF_FROZEN }; /** Controls the treatment of unauthorized MPT balances */ enum AuthHandling { ahIGNORE_AUTH, ahZERO_IF_UNAUTHORIZED }; +/** Controls whether to include the account's full spendable balance */ +enum SpendableHandling { shSIMPLE_BALANCE, shFULL_BALANCE }; + [[nodiscard]] bool isGlobalFrozen(ReadView const& view, AccountID const& issuer); @@ -305,86 +308,57 @@ isLPTokenFrozen( Issue const& asset, Issue const& asset2); -// Returns the amount an account can spend without going into debt. +// Returns the amount an account can spend. // -// <-- saAmount: amount of currency held by account. May be negative. -[[nodiscard]] STAmount -accountHolds( - ReadView const& view, - AccountID const& account, - Currency const& currency, - AccountID const& issuer, - FreezeHandling zeroIfFrozen, - beast::Journal j); - -[[nodiscard]] STAmount -accountHolds( - ReadView const& view, - AccountID const& account, - Issue const& issue, - FreezeHandling zeroIfFrozen, - beast::Journal j); - -[[nodiscard]] STAmount -accountHolds( - ReadView const& view, - AccountID const& account, - MPTIssue const& mptIssue, - FreezeHandling zeroIfFrozen, - AuthHandling zeroIfUnauthorized, - beast::Journal j); - -[[nodiscard]] STAmount -accountHolds( - ReadView const& view, - AccountID const& account, - Asset const& asset, - FreezeHandling zeroIfFrozen, - AuthHandling zeroIfUnauthorized, - beast::Journal j); - -// Returns the amount an account can spend total. +// If shSIMPLE_BALANCE is specified, this is the amount the account can spend +// without going into debt. // -// These functions use accountHolds, but unlike accountHolds: -// * The account can go into debt. -// * If the account is the asset issuer the only limit is defined by the asset / +// If shFULL_BALANCE is specified, this is the amount the account can spend +// total. Specifically: +// * The account can go into debt if using a trust line, and the other side has +// a non-zero limit. +// * If the account is the asset issuer the limit is defined by the asset / // issuance. // // <-- saAmount: amount of currency held by account. May be negative. [[nodiscard]] STAmount -accountSpendable( +accountHolds( ReadView const& view, AccountID const& account, Currency const& currency, AccountID const& issuer, FreezeHandling zeroIfFrozen, - beast::Journal j); + beast::Journal j, + SpendableHandling includeFullBalance = shSIMPLE_BALANCE); [[nodiscard]] STAmount -accountSpendable( +accountHolds( ReadView const& view, AccountID const& account, Issue const& issue, FreezeHandling zeroIfFrozen, - beast::Journal j); + beast::Journal j, + SpendableHandling includeFullBalance = shSIMPLE_BALANCE); [[nodiscard]] STAmount -accountSpendable( +accountHolds( ReadView const& view, AccountID const& account, MPTIssue const& mptIssue, FreezeHandling zeroIfFrozen, AuthHandling zeroIfUnauthorized, - beast::Journal j); + beast::Journal j, + SpendableHandling includeFullBalance = shSIMPLE_BALANCE); [[nodiscard]] STAmount -accountSpendable( +accountHolds( ReadView const& view, AccountID const& account, Asset const& asset, FreezeHandling zeroIfFrozen, AuthHandling zeroIfUnauthorized, - beast::Journal j); + beast::Journal j, + SpendableHandling includeFullBalance = shSIMPLE_BALANCE); // Returns the amount an account can spend of the currency type saDefault, or // returns saDefault if this account is the issuer of the currency in @@ -655,7 +629,7 @@ createPseudoAccount( uint256 const& pseudoOwnerKey, SField const& ownerField); -// Returns true iff sleAcct is a pseudo-account or specific +// Returns true if and only if sleAcct is a pseudo-account or specific // pseudo-accounts in pseudoFieldFilter. // // Returns false if sleAcct is @@ -710,13 +684,16 @@ checkDestinationAndTag(SLE::const_ref toSle, bool hasDestinationTag); * - If withdrawing to self, succeed. * - If not, checks if the receiver requires deposit authorization, and if * the sender has it. + * - Checks that the receiver will not exceed the limit (IOU trustline limit + * or MPT MaximumAmount). */ [[nodiscard]] TER canWithdraw( - AccountID const& from, ReadView const& view, + AccountID const& from, AccountID const& to, SLE::const_ref toSle, + STAmount const& amount, bool hasDestinationTag); /** Checks that can withdraw funds from an object to itself or a destination. @@ -730,12 +707,15 @@ canWithdraw( * - If withdrawing to self, succeed. * - If not, checks if the receiver requires deposit authorization, and if * the sender has it. + * - Checks that the receiver will not exceed the limit (IOU trustline limit + * or MPT MaximumAmount). */ [[nodiscard]] TER canWithdraw( - AccountID const& from, ReadView const& view, + AccountID const& from, AccountID const& to, + STAmount const& amount, bool hasDestinationTag); /** Checks that can withdraw funds from an object to itself or a destination. @@ -749,6 +729,8 @@ canWithdraw( * - If withdrawing to self, succeed. * - If not, checks if the receiver requires deposit authorization, and if * the sender has it. + * - Checks that the receiver will not exceed the limit (IOU trustline limit + * or MPT MaximumAmount). */ [[nodiscard]] TER canWithdraw(ReadView const& view, STTx const& tx); diff --git a/include/xrpl/protocol/detail/ledger_entries.macro b/include/xrpl/protocol/detail/ledger_entries.macro index de9c41bf52..216f404bec 100644 --- a/include/xrpl/protocol/detail/ledger_entries.macro +++ b/include/xrpl/protocol/detail/ledger_entries.macro @@ -541,7 +541,7 @@ LEDGER_ENTRY(ltLOAN, 0x0089, Loan, loan, ({ {sfStartDate, soeREQUIRED}, {sfPaymentInterval, soeREQUIRED}, {sfGracePeriod, soeDEFAULT}, - {sfPreviousPaymentDate, soeDEFAULT}, + {sfPreviousPaymentDueDate, soeDEFAULT}, {sfNextPaymentDueDate, soeDEFAULT}, // The loan object tracks these values: // diff --git a/include/xrpl/protocol/detail/sfields.macro b/include/xrpl/protocol/detail/sfields.macro index d5c5d9447f..d0736469e4 100644 --- a/include/xrpl/protocol/detail/sfields.macro +++ b/include/xrpl/protocol/detail/sfields.macro @@ -102,7 +102,7 @@ TYPED_SFIELD(sfMutableFlags, UINT32, 53) TYPED_SFIELD(sfStartDate, UINT32, 54) TYPED_SFIELD(sfPaymentInterval, UINT32, 55) TYPED_SFIELD(sfGracePeriod, UINT32, 56) -TYPED_SFIELD(sfPreviousPaymentDate, UINT32, 57) +TYPED_SFIELD(sfPreviousPaymentDueDate, UINT32, 57) TYPED_SFIELD(sfNextPaymentDueDate, UINT32, 58) TYPED_SFIELD(sfPaymentRemaining, UINT32, 59) TYPED_SFIELD(sfPaymentTotal, UINT32, 60) diff --git a/src/xrpld/app/paths/Credit.cpp b/src/libxrpl/ledger/Credit.cpp similarity index 100% rename from src/xrpld/app/paths/Credit.cpp rename to src/libxrpl/ledger/Credit.cpp diff --git a/src/libxrpl/ledger/View.cpp b/src/libxrpl/ledger/View.cpp index 329d3cfcae..14246baf17 100644 --- a/src/libxrpl/ledger/View.cpp +++ b/src/libxrpl/ledger/View.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -464,7 +465,8 @@ accountHolds( Currency const& currency, AccountID const& issuer, FreezeHandling zeroIfFrozen, - beast::Journal j) + beast::Journal j, + SpendableHandling includeFullBalance) { STAmount amount; if (isXRP(currency)) @@ -472,11 +474,19 @@ accountHolds( return {xrpLiquid(view, account, 0, j)}; } + bool const returnSpendable = (includeFullBalance == shFULL_BALANCE); + if (returnSpendable && account == issuer) + // If the account is the issuer, then their limit is effectively + // infinite + return STAmount{ + Issue{currency, issuer}, STAmount::cMaxValue, STAmount::cMaxOffset}; + // IOU: Return balance on trust line modulo freeze SLE::const_pointer const sle = getLineIfUsable(view, account, currency, issuer, zeroIfFrozen, j); - return getTrustLineBalance(view, sle, account, currency, issuer, false, j); + return getTrustLineBalance( + view, sle, account, currency, issuer, returnSpendable, j); } STAmount @@ -485,10 +495,17 @@ accountHolds( AccountID const& account, Issue const& issue, FreezeHandling zeroIfFrozen, - beast::Journal j) + beast::Journal j, + SpendableHandling includeFullBalance) { return accountHolds( - view, account, issue.currency, issue.account, zeroIfFrozen, j); + view, + account, + issue.currency, + issue.account, + zeroIfFrozen, + j, + includeFullBalance); } STAmount @@ -498,8 +515,28 @@ accountHolds( MPTIssue const& mptIssue, FreezeHandling zeroIfFrozen, AuthHandling zeroIfUnauthorized, - beast::Journal j) + beast::Journal j, + SpendableHandling includeFullBalance) { + bool const returnSpendable = (includeFullBalance == shFULL_BALANCE); + + if (returnSpendable && account == mptIssue.getIssuer()) + { + // if the account is the issuer, and the issuance exists, their limit is + // the issuance limit minus the outstanding value + auto const issuance = + view.read(keylet::mptIssuance(mptIssue.getMptID())); + + if (!issuance) + { + return STAmount{mptIssue}; + } + return STAmount{ + mptIssue, + issuance->at(~sfMaximumAmount).value_or(maxMPTokenAmount) - + issuance->at(sfOutstandingAmount)}; + } + STAmount amount; auto const sleMpt = @@ -547,108 +584,27 @@ accountHolds( Asset const& asset, FreezeHandling zeroIfFrozen, AuthHandling zeroIfUnauthorized, - beast::Journal j) + beast::Journal j, + SpendableHandling includeFullBalance) { return std::visit( - [&](auto const& value) { - if constexpr (std::is_same_v< - std::remove_cvref_t, - Issue>) + [&](TIss const& value) { + if constexpr (std::is_same_v) { - return accountHolds(view, account, value, zeroIfFrozen, j); + return accountHolds( + view, account, value, zeroIfFrozen, j, includeFullBalance); } - return accountHolds( - view, account, value, zeroIfFrozen, zeroIfUnauthorized, j); - }, - asset.value()); -} - -STAmount -accountSpendable( - ReadView const& view, - AccountID const& account, - Currency const& currency, - AccountID const& issuer, - FreezeHandling zeroIfFrozen, - beast::Journal j) -{ - if (isXRP(currency)) - return accountHolds(view, account, currency, issuer, zeroIfFrozen, j); - - if (account == issuer) - // If the account is the issuer, then their limit is effectively - // infinite - return STAmount{ - Issue{currency, issuer}, STAmount::cMaxValue, STAmount::cMaxOffset}; - - // IOU: Return balance on trust line modulo freeze - SLE::const_pointer const sle = - getLineIfUsable(view, account, currency, issuer, zeroIfFrozen, j); - - return getTrustLineBalance(view, sle, account, currency, issuer, true, j); -} - -STAmount -accountSpendable( - ReadView const& view, - AccountID const& account, - Issue const& issue, - FreezeHandling zeroIfFrozen, - beast::Journal j) -{ - return accountSpendable( - view, account, issue.currency, issue.account, zeroIfFrozen, j); -} - -STAmount -accountSpendable( - ReadView const& view, - AccountID const& account, - MPTIssue const& mptIssue, - FreezeHandling zeroIfFrozen, - AuthHandling zeroIfUnauthorized, - beast::Journal j) -{ - if (account == mptIssue.getIssuer()) - { - // if the account is the issuer, and the issuance exists, their limit is - // the issuance limit minus the outstanding value - auto const issuance = - view.read(keylet::mptIssuance(mptIssue.getMptID())); - - if (!issuance) - { - return STAmount{mptIssue}; - } - return STAmount{ - mptIssue, - issuance->at(~sfMaximumAmount).value_or(maxMPTokenAmount) - - issuance->at(sfOutstandingAmount)}; - } - - return accountHolds( - view, account, mptIssue, zeroIfFrozen, zeroIfUnauthorized, j); -} - -[[nodiscard]] STAmount -accountSpendable( - ReadView const& view, - AccountID const& account, - Asset const& asset, - FreezeHandling zeroIfFrozen, - AuthHandling zeroIfUnauthorized, - beast::Journal j) -{ - return std::visit( - [&](auto const& value) { - if constexpr (std::is_same_v< - std::remove_cvref_t, - Issue>) + else if constexpr (std::is_same_v) { - return accountSpendable(view, account, value, zeroIfFrozen, j); + return accountHolds( + view, + account, + value, + zeroIfFrozen, + zeroIfUnauthorized, + j, + includeFullBalance); } - return accountSpendable( - view, account, value, zeroIfFrozen, zeroIfUnauthorized, j); }, asset.value()); } @@ -1205,8 +1161,7 @@ getPseudoAccountFields() // LCOV_EXCL_START LogicError( "xrpl::getPseudoAccountFields : unable to find account root " - "ledger " - "format"); + "ledger format"); // LCOV_EXCL_STOP } auto const& soTemplate = ar->getSOTemplate(); @@ -1342,12 +1297,58 @@ checkDestinationAndTag(SLE::const_ref toSle, bool hasDestinationTag) return tesSUCCESS; } +/* + * Checks if a withdrawal amount into the destination account exceeds + * any applicable receiving limit. + * Called by VaultWithdraw and LoanBrokerCoverWithdraw. + * + * IOU : Performs the trustline check against the destination account's + * credit limit to ensure the account's trust maximum is not exceeded. + * + * MPT: The limit check is effectively skipped (returns true). This is + * because MPT MaximumAmount relates to token supply, and withdrawal does not + * involve minting new tokens that could exceed the global cap. + * On withdrawal, tokens are simply transferred from the vault's pseudo-account + * to the destination account. Since no new MPT tokens are minted during this + * transfer, the withdrawal cannot violate the MPT MaximumAmount/supply cap + * even if `from` is the issuer. + */ +static TER +withdrawToDestExceedsLimit( + ReadView const& view, + AccountID const& from, + AccountID const& to, + STAmount const& amount) +{ + auto const& issuer = amount.getIssuer(); + if (from == to || to == issuer || isXRP(issuer)) + return tesSUCCESS; + + return std::visit( + [&](TIss const& issue) -> TER { + if constexpr (std::is_same_v) + { + 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; + } + } + return tesSUCCESS; + }, + amount.asset().value()); +} + [[nodiscard]] TER canWithdraw( - AccountID const& from, ReadView const& view, + AccountID const& from, AccountID const& to, SLE::const_ref toSle, + STAmount const& amount, bool hasDestinationTag) { if (auto const ret = checkDestinationAndTag(toSle, hasDestinationTag)) @@ -1362,19 +1363,20 @@ canWithdraw( return tecNO_PERMISSION; } - return tesSUCCESS; + return withdrawToDestExceedsLimit(view, from, to, amount); } [[nodiscard]] TER canWithdraw( - AccountID const& from, ReadView const& view, + AccountID const& from, AccountID const& to, + STAmount const& amount, bool hasDestinationTag) { auto const toSle = view.read(keylet::account(to)); - return canWithdraw(from, view, to, toSle, hasDestinationTag); + return canWithdraw(view, from, to, toSle, amount, hasDestinationTag); } [[nodiscard]] TER @@ -1383,7 +1385,8 @@ canWithdraw(ReadView const& view, STTx const& tx) auto const from = tx[sfAccount]; auto const to = tx[~sfDestination].value_or(from); - return canWithdraw(from, view, to, tx.isFieldPresent(sfDestinationTag)); + return canWithdraw( + view, from, to, tx[sfAmount], tx.isFieldPresent(sfDestinationTag)); } TER diff --git a/src/test/app/LendingHelpers_test.cpp b/src/test/app/LendingHelpers_test.cpp new file mode 100644 index 0000000000..50efe0ebe3 --- /dev/null +++ b/src/test/app/LendingHelpers_test.cpp @@ -0,0 +1,1351 @@ +#include +// DO NOT REMOVE +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include + +namespace xrpl { +namespace test { + +class LendingHelpers_test : public beast::unit_test::suite +{ + void + testComputeRaisedRate() + { + using namespace jtx; + using namespace xrpl::detail; + struct TestCase + { + std::string name; + Number periodicRate; + std::uint32_t paymentsRemaining; + Number expectedRaisedRate; + }; + + auto const testCases = std::vector{ + { + .name = "Zero payments remaining", + .periodicRate = Number{5, -2}, + .paymentsRemaining = 0, + .expectedRaisedRate = Number{1}, // (1 + r)^0 = 1 + }, + { + .name = "One payment remaining", + .periodicRate = Number{5, -2}, + .paymentsRemaining = 1, + .expectedRaisedRate = Number{105, -2}, + }, // 1.05^1 + { + .name = "Multiple payments remaining", + .periodicRate = Number{5, -2}, + .paymentsRemaining = 3, + .expectedRaisedRate = Number{1157625, -6}, + }, // 1.05^3 + { + .name = "Zero periodic rate", + .periodicRate = Number{0}, + .paymentsRemaining = 5, + .expectedRaisedRate = Number{1}, // (1 + 0)^5 = 1 + }}; + + for (auto const& tc : testCases) + { + testcase("computeRaisedRate: " + tc.name); + + auto const computedRaisedRate = + computeRaisedRate(tc.periodicRate, tc.paymentsRemaining); + BEAST_EXPECTS( + computedRaisedRate == tc.expectedRaisedRate, + "Raised rate mismatch: expected " + + to_string(tc.expectedRaisedRate) + ", got " + + to_string(computedRaisedRate)); + } + } + + void + testComputePaymentFactor() + { + using namespace jtx; + using namespace xrpl::detail; + struct TestCase + { + std::string name; + Number periodicRate; + std::uint32_t paymentsRemaining; + Number expectedPaymentFactor; + }; + + auto const testCases = std::vector{ + { + .name = "Zero periodic rate", + .periodicRate = Number{0}, + .paymentsRemaining = 4, + .expectedPaymentFactor = Number{25, -2}, + }, // 1/4 = 0.25 + { + .name = "One payment remaining", + .periodicRate = Number{5, -2}, + .paymentsRemaining = 1, + .expectedPaymentFactor = Number{105, -2}, + }, // 0.05/1 = 1.05 + { + .name = "Multiple payments remaining", + .periodicRate = Number{5, -2}, + .paymentsRemaining = 3, + .expectedPaymentFactor = Number{367208564631245, -15}, + }, // from calc + { + .name = "Zero payments remaining", + .periodicRate = Number{5, -2}, + .paymentsRemaining = 0, + .expectedPaymentFactor = Number{0}, + } // edge case + }; + + for (auto const& tc : testCases) + { + testcase("computePaymentFactor: " + tc.name); + + auto const computedPaymentFactor = + computePaymentFactor(tc.periodicRate, tc.paymentsRemaining); + BEAST_EXPECTS( + computedPaymentFactor == tc.expectedPaymentFactor, + "Payment factor mismatch: expected " + + to_string(tc.expectedPaymentFactor) + ", got " + + to_string(computedPaymentFactor)); + } + } + + void + testLoanPeriodicPayment() + { + using namespace jtx; + using namespace xrpl::detail; + + struct TestCase + { + std::string name; + Number principalOutstanding; + Number periodicRate; + std::uint32_t paymentsRemaining; + Number expectedPeriodicPayment; + }; + + auto const testCases = std::vector{ + { + .name = "Zero principal outstanding", + .principalOutstanding = Number{0}, + .periodicRate = Number{5, -2}, + .paymentsRemaining = 5, + .expectedPeriodicPayment = Number{0}, + }, + { + .name = "Zero payments remaining", + .principalOutstanding = Number{1'000}, + .periodicRate = Number{5, -2}, + .paymentsRemaining = 0, + .expectedPeriodicPayment = Number{0}, + }, + { + .name = "Zero periodic rate", + .principalOutstanding = Number{1'000}, + .periodicRate = Number{0}, + .paymentsRemaining = 4, + .expectedPeriodicPayment = Number{250}, + }, + { + .name = "Standard case", + .principalOutstanding = Number{1'000}, + .periodicRate = + loanPeriodicRate(TenthBips32(100'000), 30 * 24 * 60 * 60), + .paymentsRemaining = 3, + .expectedPeriodicPayment = + Number{3895690663961231, -13}, // from calc + }, + }; + + for (auto const& tc : testCases) + { + testcase("loanPeriodicPayment: " + tc.name); + + auto const computedPeriodicPayment = loanPeriodicPayment( + tc.principalOutstanding, tc.periodicRate, tc.paymentsRemaining); + BEAST_EXPECTS( + computedPeriodicPayment == tc.expectedPeriodicPayment, + "Periodic payment mismatch: expected " + + to_string(tc.expectedPeriodicPayment) + ", got " + + to_string(computedPeriodicPayment)); + } + } + + void + testLoanPrincipalFromPeriodicPayment() + { + using namespace jtx; + using namespace xrpl::detail; + + struct TestCase + { + std::string name; + Number periodicPayment; + Number periodicRate; + std::uint32_t paymentsRemaining; + Number expectedPrincipalOutstanding; + }; + + auto const testCases = std::vector{ + { + .name = "Zero periodic payment", + .periodicPayment = Number{0}, + .periodicRate = Number{5, -2}, + .paymentsRemaining = 5, + .expectedPrincipalOutstanding = Number{0}, + }, + { + .name = "Zero payments remaining", + .periodicPayment = Number{1'000}, + .periodicRate = Number{5, -2}, + .paymentsRemaining = 0, + .expectedPrincipalOutstanding = Number{0}, + }, + { + .name = "Zero periodic rate", + .periodicPayment = Number{250}, + .periodicRate = Number{0}, + .paymentsRemaining = 4, + .expectedPrincipalOutstanding = Number{1'000}, + }, + { + .name = "Standard case", + .periodicPayment = Number{3895690663961231, -13}, // from calc + .periodicRate = + loanPeriodicRate(TenthBips32(100'000), 30 * 24 * 60 * 60), + .paymentsRemaining = 3, + .expectedPrincipalOutstanding = Number{1'000}, + }, + }; + + for (auto const& tc : testCases) + { + testcase("loanPrincipalFromPeriodicPayment: " + tc.name); + + auto const computedPrincipalOutstanding = + loanPrincipalFromPeriodicPayment( + tc.periodicPayment, tc.periodicRate, tc.paymentsRemaining); + BEAST_EXPECTS( + computedPrincipalOutstanding == tc.expectedPrincipalOutstanding, + "Principal outstanding mismatch: expected " + + to_string(tc.expectedPrincipalOutstanding) + ", got " + + to_string(computedPrincipalOutstanding)); + } + } + + void + testComputeOverpaymentComponents() + { + testcase("computeOverpaymentComponents"); + using namespace jtx; + using namespace xrpl::detail; + + Account const issuer{"issuer"}; + PrettyAsset const IOU = issuer["IOU"]; + int32_t const loanScale = 1; + auto const overpayment = Number{1'000}; + auto const overpaymentInterestRate = TenthBips32{10'000}; // 10% + auto const overpaymentFeeRate = TenthBips32{50'000}; // 50% + auto const managementFeeRate = TenthBips16{10'000}; // 10% + + auto const expectedOverpaymentFee = Number{500}; // 50% of 1,000 + auto const expectedOverpaymentInterestGross = + Number{100}; // 10% of 1,000 + auto const expectedOverpaymentInterestNet = + Number{90}; // 100 - 10% of 100 + auto const expectedOverpaymentManagementFee = Number{10}; // 10% of 100 + auto const expectedPrincipalPortion = Number{400}; // 1,000 - 100 - 500 + + auto const components = detail::computeOverpaymentComponents( + IOU, + loanScale, + overpayment, + overpaymentInterestRate, + overpaymentFeeRate, + managementFeeRate); + + BEAST_EXPECT( + components.untrackedManagementFee == expectedOverpaymentFee); + + BEAST_EXPECT( + components.untrackedInterest == expectedOverpaymentInterestNet); + + BEAST_EXPECT( + components.trackedInterestPart() == expectedOverpaymentInterestNet); + + BEAST_EXPECT( + components.trackedManagementFeeDelta == + expectedOverpaymentManagementFee); + BEAST_EXPECT( + components.trackedPrincipalDelta == expectedPrincipalPortion); + BEAST_EXPECT( + components.trackedManagementFeeDelta + + components.untrackedInterest == + expectedOverpaymentInterestGross); + + BEAST_EXPECT( + components.trackedManagementFeeDelta + + components.untrackedInterest + + components.trackedPrincipalDelta + + components.untrackedManagementFee == + overpayment); + } + + void + testComputeInterestAndFeeParts() + { + using namespace jtx; + using namespace xrpl::detail; + + struct TestCase + { + std::string name; + Number interest; + TenthBips16 managementFeeRate; + Number expectedInterestPart; + Number expectedFeePart; + }; + + Account const issuer{"issuer"}; + PrettyAsset const IOU = issuer["IOU"]; + std::int32_t const loanScale = 1; + + auto const testCases = std::vector{ + {.name = "Zero interest", + .interest = Number{0}, + .managementFeeRate = TenthBips16{10'000}, + .expectedInterestPart = Number{0}, + .expectedFeePart = Number{0}}, + {.name = "Zero fee rate", + .interest = Number{1'000}, + .managementFeeRate = TenthBips16{0}, + .expectedInterestPart = Number{1'000}, + .expectedFeePart = Number{0}}, + {.name = "10% fee rate", + .interest = Number{1'000}, + .managementFeeRate = TenthBips16{10'000}, + .expectedInterestPart = Number{900}, + .expectedFeePart = Number{100}}, + }; + + for (auto const& tc : testCases) + { + testcase("computeInterestAndFeeParts: " + tc.name); + + auto const [computedInterestPart, computedFeePart] = + computeInterestAndFeeParts( + IOU, tc.interest, tc.managementFeeRate, loanScale); + BEAST_EXPECTS( + computedInterestPart == tc.expectedInterestPart, + "Interest part mismatch: expected " + + to_string(tc.expectedInterestPart) + ", got " + + to_string(computedInterestPart)); + BEAST_EXPECTS( + computedFeePart == tc.expectedFeePart, + "Fee part mismatch: expected " + to_string(tc.expectedFeePart) + + ", got " + to_string(computedFeePart)); + } + } + + void + testLoanLatePaymentInterest() + { + using namespace jtx; + using namespace xrpl::detail; + struct TestCase + { + std::string name; + Number principalOutstanding; + TenthBips32 lateInterestRate; + NetClock::time_point parentCloseTime; + std::uint32_t nextPaymentDueDate; + Number expectedLateInterest; + }; + + auto const testCases = std::vector{ + { + .name = "On-time payment", + .principalOutstanding = Number{1'000}, + .lateInterestRate = TenthBips32{10'000}, // 10% + .parentCloseTime = + NetClock::time_point{NetClock::duration{3'000}}, + .nextPaymentDueDate = 3'000, + .expectedLateInterest = Number{0}, + }, + { + .name = "Early payment", + .principalOutstanding = Number{1'000}, + .lateInterestRate = TenthBips32{10'000}, // 10% + .parentCloseTime = + NetClock::time_point{NetClock::duration{3'000}}, + .nextPaymentDueDate = 4'000, + .expectedLateInterest = Number{0}, + }, + { + .name = "No principal outstanding", + .principalOutstanding = Number{0}, + .lateInterestRate = TenthBips32{10'000}, // 10% + .parentCloseTime = + NetClock::time_point{NetClock::duration{3'000}}, + .nextPaymentDueDate = 2'000, + .expectedLateInterest = Number{0}, + }, + { + .name = "No late interest rate", + .principalOutstanding = Number{1'000}, + .lateInterestRate = TenthBips32{0}, // 0% + .parentCloseTime = + NetClock::time_point{NetClock::duration{3'000}}, + .nextPaymentDueDate = 2'000, + .expectedLateInterest = Number{0}, + }, + { + .name = "Late payment", + .principalOutstanding = Number{1'000}, + .lateInterestRate = TenthBips32{100'000}, // 100% + .parentCloseTime = + NetClock::time_point{NetClock::duration{3'000}}, + .nextPaymentDueDate = 2'000, + .expectedLateInterest = + Number{3170979198376459, -17}, // from calc + }, + }; + + for (auto const& tc : testCases) + { + testcase("loanLatePaymentInterest: " + tc.name); + + auto const computedLateInterest = loanLatePaymentInterest( + tc.principalOutstanding, + tc.lateInterestRate, + tc.parentCloseTime, + tc.nextPaymentDueDate); + BEAST_EXPECTS( + computedLateInterest == tc.expectedLateInterest, + "Late interest mismatch: expected " + + to_string(tc.expectedLateInterest) + ", got " + + to_string(computedLateInterest)); + } + } + + void + testLoanAccruedInterest() + { + using namespace jtx; + using namespace xrpl::detail; + struct TestCase + { + std::string name; + Number principalOutstanding; + Number periodicRate; + NetClock::time_point parentCloseTime; + std::uint32_t startDate; + std::uint32_t prevPaymentDate; + std::uint32_t paymentInterval; + Number expectedAccruedInterest; + }; + + auto const testCases = std::vector{ + { + .name = "Zero principal outstanding", + .principalOutstanding = Number{0}, + .periodicRate = Number{5, -2}, + .parentCloseTime = + NetClock::time_point{NetClock::duration{3'000}}, + .startDate = 2'000, + .prevPaymentDate = 2'500, + .paymentInterval = 30 * 24 * 60 * 60, + .expectedAccruedInterest = Number{0}, + }, + { + .name = "Before start date", + .principalOutstanding = Number{1'000}, + .periodicRate = Number{5, -2}, + .parentCloseTime = + NetClock::time_point{NetClock::duration{1'000}}, + .startDate = 2'000, + .prevPaymentDate = 1'500, + .paymentInterval = 30 * 24 * 60 * 60, + .expectedAccruedInterest = Number{0}, + }, + { + .name = "Zero periodic rate", + .principalOutstanding = Number{1'000}, + .periodicRate = Number{0}, + .parentCloseTime = + NetClock::time_point{NetClock::duration{3'000}}, + .startDate = 2'000, + .prevPaymentDate = 2'500, + .paymentInterval = 30 * 24 * 60 * 60, + .expectedAccruedInterest = Number{0}, + }, + { + .name = "Zero payment interval", + .principalOutstanding = Number{1'000}, + .periodicRate = Number{5, -2}, + .parentCloseTime = + NetClock::time_point{NetClock::duration{3'000}}, + .startDate = 2'000, + .prevPaymentDate = 2'500, + .paymentInterval = 0, + .expectedAccruedInterest = Number{0}, + }, + { + .name = "Standard case", + .principalOutstanding = Number{1'000}, + .periodicRate = Number{5, -2}, + .parentCloseTime = + NetClock::time_point{NetClock::duration{3'000}}, + .startDate = 1'000, + .prevPaymentDate = 2'000, + .paymentInterval = 30 * 24 * 60 * 60, + .expectedAccruedInterest = + Number{1929012345679012, -17}, // from calc + }, + }; + + for (auto const& tc : testCases) + { + testcase("loanAccruedInterest: " + tc.name); + + auto const computedAccruedInterest = loanAccruedInterest( + tc.principalOutstanding, + tc.periodicRate, + tc.parentCloseTime, + tc.startDate, + tc.prevPaymentDate, + tc.paymentInterval); + BEAST_EXPECTS( + computedAccruedInterest == tc.expectedAccruedInterest, + "Accrued interest mismatch: expected " + + to_string(tc.expectedAccruedInterest) + ", got " + + to_string(computedAccruedInterest)); + } + } + + // This test overlaps with testLoanAccruedInterest, the test cases only + // exercise the computeFullPaymentInterest parts unique to it. + void + testComputeFullPaymentInterest() + { + using namespace jtx; + using namespace xrpl::detail; + + struct TestCase + { + std::string name; + Number rawPrincipalOutstanding; + Number periodicRate; + NetClock::time_point parentCloseTime; + std::uint32_t paymentInterval; + std::uint32_t prevPaymentDate; + std::uint32_t startDate; + TenthBips32 closeInterestRate; + Number expectedFullPaymentInterest; + }; + + auto const testCases = std::vector{ + { + .name = "Zero principal outstanding", + .rawPrincipalOutstanding = Number{0}, + .periodicRate = Number{5, -2}, + .parentCloseTime = + NetClock::time_point{NetClock::duration{3'000}}, + .paymentInterval = 30 * 24 * 60 * 60, + .prevPaymentDate = 2'000, + .startDate = 1'000, + .closeInterestRate = TenthBips32{10'000}, + .expectedFullPaymentInterest = Number{0}, + }, + { + .name = "Zero close interest rate", + .rawPrincipalOutstanding = Number{1'000}, + .periodicRate = Number{5, -2}, + .parentCloseTime = + NetClock::time_point{NetClock::duration{3'000}}, + .paymentInterval = 30 * 24 * 60 * 60, + .prevPaymentDate = 2'000, + .startDate = 1'000, + .closeInterestRate = TenthBips32{0}, + .expectedFullPaymentInterest = + Number{1929012345679012, -17}, // from calc + }, + { + .name = "Standard case", + .rawPrincipalOutstanding = Number{1'000}, + .periodicRate = Number{5, -2}, + .parentCloseTime = + NetClock::time_point{NetClock::duration{3'000}}, + .paymentInterval = 30 * 24 * 60 * 60, + .prevPaymentDate = 2'000, + .startDate = 1'000, + .closeInterestRate = TenthBips32{10'000}, + .expectedFullPaymentInterest = + Number{1000192901234568, -13}, // from calc + }, + }; + + for (auto const& tc : testCases) + { + testcase("computeFullPaymentInterest: " + tc.name); + + auto const computedFullPaymentInterest = computeFullPaymentInterest( + tc.rawPrincipalOutstanding, + tc.periodicRate, + tc.parentCloseTime, + tc.paymentInterval, + tc.prevPaymentDate, + tc.startDate, + tc.closeInterestRate); + BEAST_EXPECTS( + computedFullPaymentInterest == tc.expectedFullPaymentInterest, + "Full payment interest mismatch: expected " + + to_string(tc.expectedFullPaymentInterest) + ", got " + + to_string(computedFullPaymentInterest)); + } + } + + void + testTryOverpaymentNoInterestNoFee() + { + // This test ensures that overpayment with no interest works correctly. + testcase("tryOverpayment - No Interest No Fee"); + + using namespace jtx; + using namespace xrpl::detail; + + Env env{*this}; + Account const issuer{"issuer"}; + PrettyAsset const asset = issuer["USD"]; + std::int32_t const loanScale = -5; + TenthBips16 const managementFeeRate{0}; // 0% + TenthBips32 const loanInterestRate{0}; // 0% + Number const loanPrincipal{1'000}; + std::uint32_t const paymentInterval = 30 * 24 * 60 * 60; + std::uint32_t const paymentsRemaining = 10; + auto const periodicRate = + loanPeriodicRate(loanInterestRate, paymentInterval); + Number const overpaymentAmount{50}; + + ExtendedPaymentComponents const overpaymentComponents = + computeOverpaymentComponents( + asset, + loanScale, + overpaymentAmount, + TenthBips32(0), + TenthBips32(0), + managementFeeRate); + + auto const loanProperites = computeLoanProperties( + asset, + loanPrincipal, + loanInterestRate, + paymentInterval, + paymentsRemaining, + managementFeeRate, + loanScale); + + Number const periodicPayment = loanProperites.periodicPayment; + + auto const ret = tryOverpayment( + asset, + loanScale, + overpaymentComponents, + loanProperites.loanState, + periodicPayment, + periodicRate, + paymentsRemaining, + managementFeeRate, + env.journal); + + BEAST_EXPECT(ret); + + auto const& [actualPaymentParts, newLoanProperties] = *ret; + auto const& newState = newLoanProperties.loanState; + + // =========== VALIDATE PAYMENT PARTS =========== + BEAST_EXPECTS( + actualPaymentParts.valueChange == 0, + " valueChange mismatch: expected 0, got " + + to_string(actualPaymentParts.valueChange)); + + BEAST_EXPECTS( + actualPaymentParts.feePaid == 0, + " feePaid mismatch: expected 0, got " + + to_string(actualPaymentParts.feePaid)); + + BEAST_EXPECTS( + actualPaymentParts.interestPaid == 0, + " interestPaid mismatch: expected 0, got " + + to_string(actualPaymentParts.interestPaid)); + + BEAST_EXPECTS( + actualPaymentParts.principalPaid == overpaymentAmount, + " principalPaid mismatch: expected " + + to_string(overpaymentAmount) + ", got " + + to_string(actualPaymentParts.principalPaid)); + + // =========== VALIDATE STATE CHANGES =========== + BEAST_EXPECTS( + loanProperites.loanState.interestDue - newState.interestDue == 0, + " interest change mismatch: expected 0, got " + + to_string( + loanProperites.loanState.interestDue - + newState.interestDue)); + + BEAST_EXPECTS( + loanProperites.loanState.managementFeeDue - + newState.managementFeeDue == + 0, + " management fee change mismatch: expected 0, got " + + to_string( + loanProperites.loanState.managementFeeDue - + newState.managementFeeDue)); + + BEAST_EXPECTS( + actualPaymentParts.principalPaid == + loanProperites.loanState.principalOutstanding - + newState.principalOutstanding, + " principalPaid mismatch: expected " + + to_string( + loanProperites.loanState.principalOutstanding - + newState.principalOutstanding) + + ", got " + to_string(actualPaymentParts.principalPaid)); + } + + void + testTryOverpaymentNoInterestOverpaymentFee() + { + testcase("tryOverpayment - No Interest With Overpayment Fee"); + + using namespace jtx; + using namespace xrpl::detail; + + Env env{*this}; + Account const issuer{"issuer"}; + PrettyAsset const asset = issuer["USD"]; + std::int32_t const loanScale = -5; + TenthBips16 const managementFeeRate{0}; // 0% + TenthBips32 const loanInterestRate{0}; // 0% + Number const loanPrincipal{1'000}; + std::uint32_t const paymentInterval = 30 * 24 * 60 * 60; + std::uint32_t const paymentsRemaining = 10; + auto const periodicRate = + loanPeriodicRate(loanInterestRate, paymentInterval); + + ExtendedPaymentComponents const overpaymentComponents = + computeOverpaymentComponents( + asset, + loanScale, + Number{50, 0}, + TenthBips32(0), + TenthBips32(10'000), // 10% overpayment fee + managementFeeRate); + + auto const loanProperites = computeLoanProperties( + asset, + loanPrincipal, + loanInterestRate, + paymentInterval, + paymentsRemaining, + managementFeeRate, + loanScale); + + Number const periodicPayment = loanProperites.periodicPayment; + + auto const ret = tryOverpayment( + asset, + loanScale, + overpaymentComponents, + loanProperites.loanState, + periodicPayment, + periodicRate, + paymentsRemaining, + managementFeeRate, + env.journal); + + BEAST_EXPECT(ret); + + auto const& [actualPaymentParts, newLoanProperties] = *ret; + auto const& newState = newLoanProperties.loanState; + + // =========== VALIDATE PAYMENT PARTS =========== + BEAST_EXPECTS( + actualPaymentParts.valueChange == 0, + " valueChange mismatch: expected 0, got " + + to_string(actualPaymentParts.valueChange)); + + BEAST_EXPECTS( + actualPaymentParts.feePaid == 5, + " feePaid mismatch: expected 5, got " + + to_string(actualPaymentParts.feePaid)); + + BEAST_EXPECTS( + actualPaymentParts.principalPaid == 45, + " principalPaid mismatch: expected 45, got `" + + to_string(actualPaymentParts.principalPaid)); + + BEAST_EXPECTS( + actualPaymentParts.interestPaid == 0, + " interestPaid mismatch: expected 0, got " + + to_string(actualPaymentParts.interestPaid)); + + // =========== VALIDATE STATE CHANGES =========== + // With no Loan interest, interest outstanding should not change + BEAST_EXPECTS( + loanProperites.loanState.interestDue - newState.interestDue == 0, + " interest change mismatch: expected 0, got " + + to_string( + loanProperites.loanState.interestDue - + newState.interestDue)); + + // With no Loan management fee, management fee due should not change + BEAST_EXPECTS( + loanProperites.loanState.managementFeeDue - + newState.managementFeeDue == + 0, + " management fee change mismatch: expected 0, got " + + to_string( + loanProperites.loanState.managementFeeDue - + newState.managementFeeDue)); + + BEAST_EXPECTS( + actualPaymentParts.principalPaid == + loanProperites.loanState.principalOutstanding - + newState.principalOutstanding, + " principalPaid mismatch: expected " + + to_string( + loanProperites.loanState.principalOutstanding - + newState.principalOutstanding) + + ", got " + to_string(actualPaymentParts.principalPaid)); + } + + void + testTryOverpaymentLoanInterestNoOverpaymentFees() + { + testcase("tryOverpayment - Loan Interest, No Overpayment Fees"); + + using namespace jtx; + using namespace xrpl::detail; + + Env env{*this}; + Account const issuer{"issuer"}; + PrettyAsset const asset = issuer["USD"]; + std::int32_t const loanScale = -5; + TenthBips16 const managementFeeRate{0}; // 0% + TenthBips32 const loanInterestRate{10'000}; // 10% + Number const loanPrincipal{1'000}; + std::uint32_t const paymentInterval = 30 * 24 * 60 * 60; + std::uint32_t const paymentsRemaining = 10; + auto const periodicRate = + loanPeriodicRate(loanInterestRate, paymentInterval); + + ExtendedPaymentComponents const overpaymentComponents = + computeOverpaymentComponents( + asset, + loanScale, + Number{50, 0}, + TenthBips32(0), // no overpayment interest + TenthBips32(0), // 0% overpayment fee + managementFeeRate); + + auto const loanProperites = computeLoanProperties( + asset, + loanPrincipal, + loanInterestRate, + paymentInterval, + paymentsRemaining, + managementFeeRate, + loanScale); + + Number const periodicPayment = loanProperites.periodicPayment; + + auto const ret = tryOverpayment( + asset, + loanScale, + overpaymentComponents, + loanProperites.loanState, + periodicPayment, + periodicRate, + paymentsRemaining, + managementFeeRate, + env.journal); + + BEAST_EXPECT(ret); + + auto const& [actualPaymentParts, newLoanProperties] = *ret; + auto const& newState = newLoanProperties.loanState; + + // =========== VALIDATE PAYMENT PARTS =========== + // with no overpayment interest portion, value change should equal + // interest decrease + BEAST_EXPECTS( + (actualPaymentParts.valueChange == Number{-228802, -5}), + " valueChange mismatch: expected " + + to_string(Number{-228802, -5}) + ", got " + + to_string(actualPaymentParts.valueChange)); + + // with no fee portion, fee paid should be zero + BEAST_EXPECTS( + actualPaymentParts.feePaid == 0, + " feePaid mismatch: expected 0, got " + + to_string(actualPaymentParts.feePaid)); + + BEAST_EXPECTS( + actualPaymentParts.principalPaid == 50, + " principalPaid mismatch: expected 50, got `" + + to_string(actualPaymentParts.principalPaid)); + + // with no interest portion, interest paid should be zero + BEAST_EXPECTS( + actualPaymentParts.interestPaid == 0, + " interestPaid mismatch: expected 0, got " + + to_string(actualPaymentParts.interestPaid)); + + // =========== VALIDATE STATE CHANGES =========== + BEAST_EXPECTS( + actualPaymentParts.principalPaid == + loanProperites.loanState.principalOutstanding - + newState.principalOutstanding, + " principalPaid mismatch: expected " + + to_string( + loanProperites.loanState.principalOutstanding - + newState.principalOutstanding) + + ", got " + to_string(actualPaymentParts.principalPaid)); + + BEAST_EXPECTS( + actualPaymentParts.valueChange == + newState.interestDue - loanProperites.loanState.interestDue, + " valueChange mismatch: expected " + + to_string( + newState.interestDue - + loanProperites.loanState.interestDue) + + ", got " + to_string(actualPaymentParts.valueChange)); + + // With no Loan management fee, management fee due should not change + BEAST_EXPECTS( + loanProperites.loanState.managementFeeDue - + newState.managementFeeDue == + 0, + " management fee change mismatch: expected 0, got " + + to_string( + loanProperites.loanState.managementFeeDue - + newState.managementFeeDue)); + } + + void + testTryOverpaymentLoanInterestOverpaymentInterest() + { + testcase( + "tryOverpayment - Loan Interest, Overpayment Interest, No Fee"); + + using namespace jtx; + using namespace xrpl::detail; + + Env env{*this}; + Account const issuer{"issuer"}; + PrettyAsset const asset = issuer["USD"]; + std::int32_t const loanScale = -5; + TenthBips16 const managementFeeRate{0}; // 0% + TenthBips32 const loanInterestRate{10'000}; // 10% + Number const loanPrincipal{1'000}; + std::uint32_t const paymentInterval = 30 * 24 * 60 * 60; + std::uint32_t const paymentsRemaining = 10; + auto const periodicRate = + loanPeriodicRate(loanInterestRate, paymentInterval); + + ExtendedPaymentComponents const overpaymentComponents = + computeOverpaymentComponents( + asset, + loanScale, + Number{50, 0}, + TenthBips32(10'000), // 10% overpayment interest + TenthBips32(0), // 0% overpayment fee + managementFeeRate); + + auto const loanProperites = computeLoanProperties( + asset, + loanPrincipal, + loanInterestRate, + paymentInterval, + paymentsRemaining, + managementFeeRate, + loanScale); + + Number const periodicPayment = loanProperites.periodicPayment; + + auto const ret = tryOverpayment( + asset, + loanScale, + overpaymentComponents, + loanProperites.loanState, + periodicPayment, + periodicRate, + paymentsRemaining, + managementFeeRate, + env.journal); + + BEAST_EXPECT(ret); + + auto const& [actualPaymentParts, newLoanProperties] = *ret; + auto const& newState = newLoanProperties.loanState; + + // =========== VALIDATE PAYMENT PARTS =========== + // with overpayment interest portion, interest paid should be 5 + BEAST_EXPECTS( + actualPaymentParts.interestPaid == 5, + " interestPaid mismatch: expected 5, got " + + to_string(actualPaymentParts.interestPaid)); + + // With overpayment interest portion, value change should equal the + // interest decrease plus overpayment interest portion + BEAST_EXPECTS( + (actualPaymentParts.valueChange == + Number{-205922, -5} + actualPaymentParts.interestPaid), + " valueChange mismatch: expected " + + to_string( + actualPaymentParts.valueChange - + actualPaymentParts.interestPaid) + + ", got " + to_string(actualPaymentParts.valueChange)); + + // with no fee portion, fee paid should be zero + BEAST_EXPECTS( + actualPaymentParts.feePaid == 0, + " feePaid mismatch: expected 0, got " + + to_string(actualPaymentParts.feePaid)); + + BEAST_EXPECTS( + actualPaymentParts.principalPaid == 45, + " principalPaid mismatch: expected 45, got `" + + to_string(actualPaymentParts.principalPaid)); + + // =========== VALIDATE STATE CHANGES =========== + BEAST_EXPECTS( + actualPaymentParts.principalPaid == + loanProperites.loanState.principalOutstanding - + newState.principalOutstanding, + " principalPaid mismatch: expected " + + to_string( + loanProperites.loanState.principalOutstanding - + newState.principalOutstanding) + + ", got " + to_string(actualPaymentParts.principalPaid)); + + // The change in interest is equal to the value change sans the + // overpayment interest + BEAST_EXPECTS( + actualPaymentParts.valueChange - actualPaymentParts.interestPaid == + newState.interestDue - loanProperites.loanState.interestDue, + " valueChange mismatch: expected " + + to_string( + newState.interestDue - + loanProperites.loanState.interestDue + + actualPaymentParts.interestPaid) + + ", got " + to_string(actualPaymentParts.valueChange)); + + // With no Loan management fee, management fee due should not change + BEAST_EXPECTS( + loanProperites.loanState.managementFeeDue - + newState.managementFeeDue == + 0, + " management fee change mismatch: expected 0, got " + + to_string( + loanProperites.loanState.managementFeeDue - + newState.managementFeeDue)); + } + + void + testTryOverpaymentLoanInterestFeeOverpaymentInterestNoFee() + { + testcase( + "tryOverpayment - Loan Interest and Fee, Overpayment Interest, No " + "Fee"); + + using namespace jtx; + using namespace xrpl::detail; + + Env env{*this}; + Account const issuer{"issuer"}; + PrettyAsset const asset = issuer["USD"]; + std::int32_t const loanScale = -5; + TenthBips16 const managementFeeRate{10'000}; // 10% + TenthBips32 const loanInterestRate{10'000}; // 10% + Number const loanPrincipal{1'000}; + std::uint32_t const paymentInterval = 30 * 24 * 60 * 60; + std::uint32_t const paymentsRemaining = 10; + auto const periodicRate = + loanPeriodicRate(loanInterestRate, paymentInterval); + + ExtendedPaymentComponents const overpaymentComponents = + computeOverpaymentComponents( + asset, + loanScale, + Number{50, 0}, + TenthBips32(10'000), // 10% overpayment interest + TenthBips32(0), // 0% overpayment fee + managementFeeRate); + + auto const loanProperites = computeLoanProperties( + asset, + loanPrincipal, + loanInterestRate, + paymentInterval, + paymentsRemaining, + managementFeeRate, + loanScale); + + Number const periodicPayment = loanProperites.periodicPayment; + + auto const ret = tryOverpayment( + asset, + loanScale, + overpaymentComponents, + loanProperites.loanState, + periodicPayment, + periodicRate, + paymentsRemaining, + managementFeeRate, + env.journal); + + BEAST_EXPECT(ret); + + auto const& [actualPaymentParts, newLoanProperties] = *ret; + auto const& newState = newLoanProperties.loanState; + + // =========== VALIDATE PAYMENT PARTS =========== + + // Since there is loan management fee, the fee is charged against + // overpayment interest portion first, so interest paid remains 4.5 + BEAST_EXPECTS( + (actualPaymentParts.interestPaid == Number{45, -1}), + " interestPaid mismatch: expected 4.5, got " + + to_string(actualPaymentParts.interestPaid)); + + // With overpayment interest portion, value change should equal the + // interest decrease plus overpayment interest portion + BEAST_EXPECTS( + (actualPaymentParts.valueChange == + Number{-18533, -4} + actualPaymentParts.interestPaid), + " valueChange mismatch: expected " + + to_string( + Number{-18533, -4} + actualPaymentParts.interestPaid) + + ", got " + to_string(actualPaymentParts.valueChange)); + + // While there is no overpayment fee, fee paid should equal the + // management fee charged against the overpayment interest portion + BEAST_EXPECTS( + (actualPaymentParts.feePaid == Number{5, -1}), + " feePaid mismatch: expected 0.5, got " + + to_string(actualPaymentParts.feePaid)); + + BEAST_EXPECTS( + actualPaymentParts.principalPaid == 45, + " principalPaid mismatch: expected 45, got `" + + to_string(actualPaymentParts.principalPaid)); + + // =========== VALIDATE STATE CHANGES =========== + BEAST_EXPECTS( + actualPaymentParts.principalPaid == + loanProperites.loanState.principalOutstanding - + newState.principalOutstanding, + " principalPaid mismatch: expected " + + to_string( + loanProperites.loanState.principalOutstanding - + newState.principalOutstanding) + + ", got " + to_string(actualPaymentParts.principalPaid)); + + // Note that the management fee value change is not captured, as this + // value is not needed to correctly update the Vault state. + BEAST_EXPECTS( + (newState.managementFeeDue - + loanProperites.loanState.managementFeeDue == + Number{-20592, -5}), + " management fee change mismatch: expected " + + to_string(Number{-20592, -5}) + ", got " + + to_string( + newState.managementFeeDue - + loanProperites.loanState.managementFeeDue)); + + BEAST_EXPECTS( + actualPaymentParts.valueChange - actualPaymentParts.interestPaid == + newState.interestDue - loanProperites.loanState.interestDue, + " valueChange mismatch: expected " + + to_string( + newState.interestDue - + loanProperites.loanState.interestDue) + + ", got " + + to_string( + actualPaymentParts.valueChange - + actualPaymentParts.interestPaid)); + } + + void + testTryOverpaymentLoanInterestFeeOverpaymentInterestFee() + { + testcase( + "tryOverpayment - Loan Interest, Fee, Overpayment Interest, Fee"); + + using namespace jtx; + using namespace xrpl::detail; + + Env env{*this}; + Account const issuer{"issuer"}; + PrettyAsset const asset = issuer["USD"]; + std::int32_t const loanScale = -5; + TenthBips16 const managementFeeRate{10'000}; // 10% + TenthBips32 const loanInterestRate{10'000}; // 10% + Number const loanPrincipal{1'000}; + std::uint32_t const paymentInterval = 30 * 24 * 60 * 60; + std::uint32_t const paymentsRemaining = 10; + auto const periodicRate = + loanPeriodicRate(loanInterestRate, paymentInterval); + + ExtendedPaymentComponents const overpaymentComponents = + computeOverpaymentComponents( + asset, + loanScale, + Number{50, 0}, + TenthBips32(10'000), // 10% overpayment interest + TenthBips32(10'000), // 10% overpayment fee + managementFeeRate); + + auto const loanProperites = computeLoanProperties( + asset, + loanPrincipal, + loanInterestRate, + paymentInterval, + paymentsRemaining, + managementFeeRate, + loanScale); + + Number const periodicPayment = loanProperites.periodicPayment; + + auto const ret = tryOverpayment( + asset, + loanScale, + overpaymentComponents, + loanProperites.loanState, + periodicPayment, + periodicRate, + paymentsRemaining, + managementFeeRate, + env.journal); + + BEAST_EXPECT(ret); + + auto const& [actualPaymentParts, newLoanProperties] = *ret; + auto const& newState = newLoanProperties.loanState; + + // =========== VALIDATE PAYMENT PARTS =========== + + // Since there is loan management fee, the fee is charged against + // overpayment interest portion first, so interest paid remains 4.5 + BEAST_EXPECTS( + (actualPaymentParts.interestPaid == Number{45, -1}), + " interestPaid mismatch: expected 4.5, got " + + to_string(actualPaymentParts.interestPaid)); + + // With overpayment interest portion, value change should equal the + // interest decrease plus overpayment interest portion + BEAST_EXPECTS( + (actualPaymentParts.valueChange == + Number{-164737, -5} + actualPaymentParts.interestPaid), + " valueChange mismatch: expected " + + to_string( + Number{-164737, -5} + actualPaymentParts.interestPaid) + + ", got " + to_string(actualPaymentParts.valueChange)); + + // While there is no overpayment fee, fee paid should equal the + // management fee charged against the overpayment interest portion + BEAST_EXPECTS( + (actualPaymentParts.feePaid == Number{55, -1}), + " feePaid mismatch: expected 5.5, got " + + to_string(actualPaymentParts.feePaid)); + + BEAST_EXPECTS( + actualPaymentParts.principalPaid == 40, + " principalPaid mismatch: expected 40, got `" + + to_string(actualPaymentParts.principalPaid)); + + // =========== VALIDATE STATE CHANGES =========== + + BEAST_EXPECTS( + actualPaymentParts.principalPaid == + loanProperites.loanState.principalOutstanding - + newState.principalOutstanding, + " principalPaid mismatch: expected " + + to_string( + loanProperites.loanState.principalOutstanding - + newState.principalOutstanding) + + ", got " + to_string(actualPaymentParts.principalPaid)); + + // Note that the management fee value change is not captured, as this + // value is not needed to correctly update the Vault state. + BEAST_EXPECTS( + (newState.managementFeeDue - + loanProperites.loanState.managementFeeDue == + Number{-18304, -5}), + " management fee change mismatch: expected " + + to_string(Number{-18304, -5}) + ", got " + + to_string( + newState.managementFeeDue - + loanProperites.loanState.managementFeeDue)); + + BEAST_EXPECTS( + actualPaymentParts.valueChange - actualPaymentParts.interestPaid == + newState.interestDue - loanProperites.loanState.interestDue, + " valueChange mismatch: expected " + + to_string( + newState.interestDue - + loanProperites.loanState.interestDue) + + ", got " + + to_string( + actualPaymentParts.valueChange - + actualPaymentParts.interestPaid)); + } + +public: + void + run() override + { + testTryOverpaymentNoInterestNoFee(); + testTryOverpaymentNoInterestOverpaymentFee(); + testTryOverpaymentLoanInterestNoOverpaymentFees(); + testTryOverpaymentLoanInterestOverpaymentInterest(); + testTryOverpaymentLoanInterestFeeOverpaymentInterestNoFee(); + testTryOverpaymentLoanInterestFeeOverpaymentInterestFee(); + + testComputeFullPaymentInterest(); + testLoanAccruedInterest(); + testLoanLatePaymentInterest(); + testLoanPeriodicPayment(); + testLoanPrincipalFromPeriodicPayment(); + testComputeRaisedRate(); + testComputePaymentFactor(); + testComputeOverpaymentComponents(); + testComputeInterestAndFeeParts(); + } +}; + +BEAST_DEFINE_TESTSUITE(LendingHelpers, app, xrpl); + +} // namespace test +} // namespace xrpl diff --git a/src/test/app/LoanBroker_test.cpp b/src/test/app/LoanBroker_test.cpp index 5915ebae91..93be28e9e9 100644 --- a/src/test/app/LoanBroker_test.cpp +++ b/src/test/app/LoanBroker_test.cpp @@ -1024,6 +1024,12 @@ class LoanBroker_test : public beast::unit_test::suite destination(dest), ter(tecFROZEN), THISLINE); + + // preclaim: tecPSEUDO_ACCOUNT + env(coverWithdraw(alice, brokerKeylet.key, asset(10)), + destination(vaultInfo.pseudoAccount), + ter(tecPSEUDO_ACCOUNT), + THISLINE); } if (brokerTest == CoverClawback) @@ -1436,10 +1442,467 @@ class LoanBroker_test : public beast::unit_test::suite }); } + void + testLoanBrokerSetDebtMaximum() + { + testcase("testLoanBrokerSetDebtMaximum"); + using namespace jtx; + using namespace loanBroker; + Account const issuer{"issuer"}; + Account const alice{"alice"}; + Env env(*this); + Vault vault{env}; + + env.fund(XRP(100'000), issuer, alice); + env.close(); + + PrettyAsset const asset = [&]() { + env(trust(alice, issuer["IOU"](1'000'000)), THISLINE); + env.close(); + return PrettyAsset(issuer["IOU"]); + }(); + + env(pay(issuer, alice, asset(100'000)), THISLINE); + env.close(); + + auto [tx, vaultKeylet] = vault.create({.owner = alice, .asset = asset}); + env(tx, THISLINE); + env.close(); + auto const le = env.le(vaultKeylet); + VaultInfo vaultInfo = [&]() { + if (BEAST_EXPECT(le)) + return VaultInfo{asset, vaultKeylet.key, le->at(sfAccount)}; + return VaultInfo{asset, {}, {}}; + }(); + if (vaultInfo.vaultID == uint256{}) + return; + + env(vault.deposit( + {.depositor = alice, + .id = vaultKeylet.key, + .amount = asset(50)}), + THISLINE); + env.close(); + + auto const brokerKeylet = + keylet::loanbroker(alice.id(), env.seq(alice)); + env(set(alice, vaultInfo.vaultID), THISLINE); + env.close(); + + Account const borrower{"borrower"}; + env.fund(XRP(1'000), borrower); + env(loan::set(borrower, brokerKeylet.key, asset(50).value()), + sig(sfCounterpartySignature, alice), + fee(env.current()->fees().base * 2), + THISLINE); + auto const broker = env.le(brokerKeylet); + if (!BEAST_EXPECT(broker)) + return; + + BEAST_EXPECT(broker->at(sfDebtTotal) == 50); + auto debtTotal = broker->at(sfDebtTotal); + + auto tx2 = set(alice, vaultInfo.vaultID); + tx2[sfLoanBrokerID] = to_string(brokerKeylet.key); + tx2[sfDebtMaximum] = debtTotal - 1; + env(tx2, ter(tecLIMIT_EXCEEDED), THISLINE); + + tx2[sfDebtMaximum] = debtTotal + 1; + env(tx2, ter(tesSUCCESS), THISLINE); + + tx2[sfDebtMaximum] = 0; + env(tx2, ter(tesSUCCESS), THISLINE); + } + + void + testRIPD4323() + { + testcase << "RIPD-4323"; + using namespace jtx; + Account const issuer("issuer"); + Account const holder("holder"); + Account const& broker = issuer; + + auto test = [&](auto&& getToken) { + Env env(*this); + + env.fund(XRP(1'000), issuer, holder); + env.close(); + + auto const [token, deposit, err] = getToken(env); + + Vault vault(env); + auto const [tx, keylet] = + vault.create({.owner = broker, .asset = token.asset()}); + env(tx); + env.close(); + + env(vault.deposit( + {.depositor = broker, .id = keylet.key, .amount = deposit}), + ter(err)); + env.close(); + + auto const brokerKeylet = + keylet::loanbroker(broker, env.seq(broker)); + + env(loanBroker::set(broker, keylet.key)); + env.close(); + + env(loanBroker::coverDeposit(broker, brokerKeylet.key, deposit), + ter(err)); + env.close(); + }; + + test([&](Env&) { + // issuer can issue any amount + auto const token = issuer["IOU"]; + return std::make_tuple(token, token(1'000), tesSUCCESS); + }); + std::vector, // max amount + std::uint64_t, // deposit amount + TER>> // expected error + mptTests = { + // issuer can issue up to 2'000 tokens + {2'000, 4'000, 1'000, tesSUCCESS}, + // issuer can issue 500 tokens (250 VaultDeposit + + // 250 LoanBrokerCoverDeposit) + {2'000, 2'500, 250, tesSUCCESS}, + // issuer can issue 500 tokens (250 VaultDeposit + + // 250 LoanBrokerCoverDeposit). MaximumAmount is default. + {maxMPTokenAmount - 500, std::nullopt, 250, tesSUCCESS}, + // issuer can issue 500, and fails on depositing 1'000 + {2'000, 2'500, 1'000, tecINSUFFICIENT_FUNDS}, + // issuer has already issued MaximumAmount + {2'000, 2'000, 1'000, tecINSUFFICIENT_FUNDS}, + // issuer has already issued MaximumAmount. MaximumAmount is + // default. + {maxMPTokenAmount, std::nullopt, 250, tecINSUFFICIENT_FUNDS}, + }; + for (auto const& [pay, max, deposit, err] : mptTests) + { + test([&](Env& env) -> std::tuple { + MPT const token = MPTTester( + {.env = env, + .issuer = issuer, + .holders = {holder}, + .pay = pay, + .flags = MPTDEXFlags, + .maxAmt = max}); + return std::make_tuple(token, token(deposit), err); + }); + } + } + + void + testAMB06_VaultFreezeCheckMissing() + { + testcase << "RIPD-4466 - LoanBrokerSet disallows frozen vaults"; + using namespace jtx; + Env env(*this); + + Account const issuer{"issuer"}, lender{"lender"}, borrower{"borrower"}; + env.fund(XRP(20'000), issuer, lender, borrower); + auto const IOU = issuer["IOU"]; + + Vault vault{env}; + auto [tx, vaultKeylet] = + vault.create({.owner = lender, .asset = IOU.asset()}); + env(tx); + env.close(); + + // Get vault pseudo-account and FREEZE it + auto const vaultSle = env.le(vaultKeylet); + auto const vaultPseudo = vaultSle->at(sfAccount); + auto const vaultPseudoAcct = Account("VaultPseudo", vaultPseudo); + env(trust(issuer, vaultPseudoAcct["IOU"](0), tfSetFreeze)); + + env(loanBroker::set(lender, vaultKeylet.key), ter(tecFROZEN)); + } + + void + testRIPD4274IOU() + { + using namespace jtx; + Account issuer("broker"); + Account broker("issuer"); + Account dest("destination"); + auto const token = issuer["IOU"]; + + enum TrustState { + RequireAuth, + ZeroLimit, + ReachedLimit, + NearLimit, + NoTrustLine, + }; + + auto test = [&](TrustState trustState) { + Env env(*this); + + testcase << "RIPD-4274 IOU with state: " + << static_cast(trustState); + + auto setTrustLine = [&](Account const& acct, TrustState state) { + switch (state) + { + case RequireAuth: + env(trust(issuer, token(0), acct, tfSetfAuth)); + break; + case ZeroLimit: { + auto jv = trust(acct, token(0)); + // set QualityIn so that the trustline is not + // auto-deleted + jv[sfQualityIn] = 10'000'000; + env(jv); + } + break; + case ReachedLimit: { + env(trust(acct, token(1'000))); + env(pay(issuer, acct, token(1'000))); + env.close(); + } + break; + case NearLimit: { + env(trust(acct, token(1'000))); + env(pay(issuer, acct, token(950))); + env.close(); + } + break; + case NoTrustLine: + // don't create a trustline + break; + default: + BEAST_EXPECT(false); + } + env.close(); + }; + + env.fund(XRP(1'000), issuer, broker, dest); + env.close(); + + if (trustState == RequireAuth) + { + env(fset(issuer, asfRequireAuth)); + env.close(); + + setTrustLine(broker, RequireAuth); + } + + setTrustLine(dest, trustState); + + env(trust(broker, token(2'000), 0)); + env(pay(issuer, broker, token(2'000))); + env.close(); + + Vault vault(env); + auto const [tx, keylet] = + vault.create({.owner = broker, .asset = token.asset()}); + env(tx); + env.close(); + + // Test Vault withdraw + env(vault.deposit( + {.depositor = broker, + .id = keylet.key, + .amount = token(1'000)})); + env.close(); + + env(vault.withdraw( + {.depositor = broker, + .id = keylet.key, + .amount = token(1'000)}), + loanBroker::destination(dest), + ter(std::ignore)); + BEAST_EXPECT(env.ter() == tecNO_LINE); + env.close(); + + env(vault.withdraw( + {.depositor = broker, + .id = keylet.key, + .amount = token(1'000)})); + + // Test LoanBroker withdraw + auto const brokerKeylet = + keylet::loanbroker(broker, env.seq(broker)); + + env(loanBroker::set(broker, keylet.key)); + env.close(); + + env(loanBroker::coverDeposit( + broker, brokerKeylet.key, token(1'000))); + env.close(); + + env(loanBroker::coverWithdraw(broker, brokerKeylet.key, token(100)), + loanBroker::destination(dest), + ter(std::ignore)); + BEAST_EXPECT(env.ter() == tecNO_LINE); + env.close(); + + // Clearing RequireAuth shouldn't change the result + if (trustState == RequireAuth) + { + env(fclear(issuer, asfRequireAuth)); + env.close(); + + env(loanBroker::coverWithdraw( + broker, brokerKeylet.key, token(100)), + loanBroker::destination(dest), + ter(std::ignore)); + BEAST_EXPECT(env.ter() == tecNO_LINE); + env.close(); + } + }; + + test(RequireAuth); + test(ZeroLimit); + test(ReachedLimit); + test(NearLimit); + test(NoTrustLine); + } + + void + testRIPD4274MPT() + { + using namespace jtx; + Account issuer("broker"); + Account broker("issuer"); + Account dest("destination"); + + enum MPTState { + RequireAuth, + ReachedMAX, + NoMPT, + }; + + auto test = [&](MPTState MPTState) { + Env env(*this); + + testcase << "RIPD-4274 MPT with state: " + << static_cast(MPTState); + + env.fund(XRP(1'000), issuer, broker, dest); + env.close(); + + auto const maybeToken = [&]() -> std::optional { + switch (MPTState) + { + case RequireAuth: { + auto tester = MPTTester( + {.env = env, + .issuer = issuer, + .holders = {broker, dest}, + .pay = 2'000, + .flags = MPTDEXFlags | tfMPTRequireAuth, + .authHolder = true, + .maxAmt = 5'000}); + // unauthorize dest + tester.authorize( + {.account = issuer, + .holder = dest, + .flags = tfMPTUnauthorize}); + return tester; + } + case ReachedMAX: { + auto tester = MPTTester( + {.env = env, + .issuer = issuer, + .holders = {broker, dest}, + .pay = 2'000, + .flags = MPTDEXFlags, + .maxAmt = 4'000}); + BEAST_EXPECT( + env.balance(issuer, tester) == tester(-4'000)); + return tester; + } + case NoMPT: { + return MPTTester( + {.env = env, + .issuer = issuer, + .holders = {broker}, + .pay = 2'000, + .flags = MPTDEXFlags, + .maxAmt = 4'000}); + } + default: + return std::nullopt; + } + }(); + if (!BEAST_EXPECT(maybeToken)) + return; + + auto const& token = *maybeToken; + + Vault vault(env); + auto const [tx, keylet] = + vault.create({.owner = broker, .asset = token.asset()}); + env(tx); + env.close(); + + // Test Vault withdraw + env(vault.deposit( + {.depositor = broker, + .id = keylet.key, + .amount = token(1'000)})); + env.close(); + + env(vault.withdraw( + {.depositor = broker, + .id = keylet.key, + .amount = token(1'000)}), + loanBroker::destination(dest), + ter(std::ignore)); + + // Shouldn't fail if at MaximumAmount since no new tokens are issued + TER const err = + MPTState == ReachedMAX ? TER(tesSUCCESS) : tecNO_AUTH; + BEAST_EXPECT(env.ter() == err); + env.close(); + + if (err != tesSUCCESS) + { + env(vault.withdraw( + {.depositor = broker, + .id = keylet.key, + .amount = token(1'000)})); + } + + // Test LoanBroker withdraw + auto const brokerKeylet = + keylet::loanbroker(broker, env.seq(broker)); + + env(loanBroker::set(broker, keylet.key)); + env.close(); + + env(loanBroker::coverDeposit( + broker, brokerKeylet.key, token(1'000))); + env.close(); + + env(loanBroker::coverWithdraw(broker, brokerKeylet.key, token(100)), + loanBroker::destination(dest), + ter(std::ignore)); + BEAST_EXPECT(env.ter() == err); + env.close(); + }; + + test(RequireAuth); + test(ReachedMAX); + test(NoMPT); + } + + void + testRIPD4274() + { + testRIPD4274IOU(); + testRIPD4274MPT(); + } + public: void run() override { + testLoanBrokerSetDebtMaximum(); testLoanBrokerCoverDepositNullVault(); testDisabled(); @@ -1451,6 +1914,11 @@ public: testInvalidLoanBrokerSet(); testRequireAuth(); + testRIPD4323(); + testAMB06_VaultFreezeCheckMissing(); + + testRIPD4274(); + // TODO: Write clawback failure tests with an issuer / MPT that doesn't // have the right flags set. } diff --git a/src/test/app/Loan_test.cpp b/src/test/app/Loan_test.cpp index 7c2e83aa19..e4f5360043 100644 --- a/src/test/app/Loan_test.cpp +++ b/src/test/app/Loan_test.cpp @@ -11,6 +11,8 @@ #include #include +#include + namespace xrpl { namespace test { @@ -141,7 +143,7 @@ protected: using namespace jtx; auto const vaultSle = env.le(keylet::vault(vaultID)); - return getVaultScale(vaultSle); + return getAssetsTotalScale(vaultSle); } }; @@ -372,7 +374,7 @@ protected: if (auto loan = env.le(loanKeylet); env.test.BEAST_EXPECT(loan)) { env.test.BEAST_EXPECT( - loan->at(sfPreviousPaymentDate) == previousPaymentDate); + loan->at(sfPreviousPaymentDueDate) == previousPaymentDate); env.test.BEAST_EXPECT( loan->at(sfPaymentRemaining) == paymentRemaining); env.test.BEAST_EXPECT( @@ -507,7 +509,7 @@ protected: if (auto loan = env.le(loanKeylet); BEAST_EXPECT(loan)) { return LoanState{ - .previousPaymentDate = loan->at(sfPreviousPaymentDate), + .previousPaymentDate = loan->at(sfPreviousPaymentDueDate), .startDate = tp{d{loan->at(sfStartDate)}}, .nextPaymentDate = loan->at(sfNextPaymentDueDate), .paymentRemaining = loan->at(sfPaymentRemaining), @@ -551,12 +553,15 @@ protected: broker.vaultScale(env), state.principalOutstanding.exponent()))); BEAST_EXPECT(state.paymentInterval == 600); - BEAST_EXPECT( - state.totalValue == - roundToAsset( - broker.asset, - state.periodicPayment * state.paymentRemaining, - state.loanScale)); + { + NumberRoundModeGuard mg(Number::upward); + BEAST_EXPECT( + state.totalValue == + roundToAsset( + broker.asset, + state.periodicPayment * state.paymentRemaining, + state.loanScale)); + } BEAST_EXPECT( state.managementFeeOutstanding == computeManagementFee( @@ -589,7 +594,7 @@ protected: auto const unrealizedLoss = vaultSle->at(sfLossUnrealized) + state.totalValue - state.managementFeeOutstanding; - if (unrealizedLoss > assetsUnavailable) + if (!BEAST_EXPECT(unrealizedLoss <= assetsUnavailable)) { return false; } @@ -705,8 +710,9 @@ protected: << "\tManagement Fee Rate: " << feeRate << std::endl << "\tTotal Payments: " << total << std::endl << "\tPeriodic Payment: " << props.periodicPayment << std::endl - << "\tTotal Value: " << props.totalValueOutstanding << std::endl - << "\tManagement Fee: " << props.managementFeeOwedToBroker + << "\tTotal Value: " << props.loanState.valueOutstanding + << std::endl + << "\tManagement Fee: " << props.loanState.managementFeeDue << std::endl << "\tLoan Scale: " << props.loanScale << std::endl << "\tFirst payment principal: " << props.firstPaymentPrincipal @@ -856,9 +862,6 @@ protected: using namespace std::chrono_literals; using d = NetClock::duration; - // Account const evan{"evan"}; - // Account const alice{"alice"}; - bool const showStepBalances = paymentParams.showStepBalances; auto const currencyLabel = getCurrencyLabel(broker.asset); @@ -911,7 +914,7 @@ protected: state.principalOutstanding, state.managementFeeOutstanding); { - auto const raw = computeRawLoanState( + auto const raw = computeTheoreticalLoanState( state.periodicPayment, periodicRate, state.paymentRemaining, @@ -964,7 +967,7 @@ protected: Number totalFeesPaid = 0; std::size_t totalPaymentsMade = 0; - xrpl::LoanState currentTrueState = computeRawLoanState( + xrpl::LoanState currentTrueState = computeTheoreticalLoanState( state.periodicPayment, periodicRate, state.paymentRemaining, @@ -1019,7 +1022,7 @@ protected: paymentComponents.trackedInterestPart() + paymentComponents.trackedManagementFeeDelta); - xrpl::LoanState const nextTrueState = computeRawLoanState( + xrpl::LoanState const nextTrueState = computeTheoreticalLoanState( state.periodicPayment, periodicRate, state.paymentRemaining - 1, @@ -1271,7 +1274,8 @@ protected: verifyLoanStatus, issuer, lender, - borrower); + borrower, + PaymentParameters{.showStepBalances = true}); } /** Runs through the complete lifecycle of a loan @@ -1452,7 +1456,7 @@ protected: BEAST_EXPECT( loan->at(sfPaymentInterval) == *loanParams.payInterval); BEAST_EXPECT(loan->at(sfGracePeriod) == *loanParams.gracePd); - BEAST_EXPECT(loan->at(sfPreviousPaymentDate) == 0); + BEAST_EXPECT(loan->at(sfPreviousPaymentDueDate) == 0); BEAST_EXPECT( loan->at(sfNextPaymentDueDate) == startDate + *loanParams.payInterval); @@ -1484,9 +1488,9 @@ protected: startDate + *loanParams.payInterval, *loanParams.payTotal, state.loanScale, - loanProperties.totalValueOutstanding, + loanProperties.loanState.valueOutstanding, principalRequestAmount, - loanProperties.managementFeeOwedToBroker, + loanProperties.loanState.managementFeeDue, loanProperties.periodicPayment, loanFlags | 0); @@ -1541,9 +1545,9 @@ protected: nextDueDate, *loanParams.payTotal, loanProperties.loanScale, - loanProperties.totalValueOutstanding, + loanProperties.loanState.valueOutstanding, principalRequestAmount, - loanProperties.managementFeeOwedToBroker, + loanProperties.loanState.managementFeeDue, loanProperties.periodicPayment, loanFlags | 0); @@ -2448,13 +2452,18 @@ protected: // Make all the payments in one transaction // service fee is 2 auto const startingPayments = state.paymentRemaining; - auto const rawPayoff = startingPayments * - (state.periodicPayment + broker.asset(2).value()); - STAmount const payoffAmount{broker.asset, rawPayoff}; - BEAST_EXPECT( - payoffAmount == - broker.asset(Number(1024014840139457, -12))); - BEAST_EXPECT(payoffAmount > state.principalOutstanding); + STAmount const payoffAmount = [&]() { + NumberRoundModeGuard mg(Number::upward); + auto const rawPayoff = startingPayments * + (state.periodicPayment + broker.asset(2).value()); + STAmount const payoffAmount{broker.asset, rawPayoff}; + BEAST_EXPECTS( + payoffAmount == + broker.asset(Number(1024014840139457, -12)), + to_string(payoffAmount)); + BEAST_EXPECT(payoffAmount > state.principalOutstanding); + return payoffAmount; + }(); singlePayment( loanKeylet, @@ -2662,7 +2671,7 @@ protected: Number::upward)); { - auto const raw = computeRawLoanState( + auto const raw = computeTheoreticalLoanState( state.periodicPayment, periodicRate, state.paymentRemaining, @@ -2705,7 +2714,7 @@ protected: Number totalInterestPaid = 0; std::size_t totalPaymentsMade = 0; - xrpl::LoanState currentTrueState = computeRawLoanState( + xrpl::LoanState currentTrueState = computeTheoreticalLoanState( state.periodicPayment, periodicRate, state.paymentRemaining, @@ -2730,11 +2739,12 @@ protected: paymentComponents.trackedValueDelta <= roundedPeriodicPayment); - xrpl::LoanState const nextTrueState = computeRawLoanState( - state.periodicPayment, - periodicRate, - state.paymentRemaining - 1, - broker.params.managementFeeRate); + xrpl::LoanState const nextTrueState = + computeTheoreticalLoanState( + state.periodicPayment, + periodicRate, + state.paymentRemaining - 1, + broker.params.managementFeeRate); detail::LoanStateDeltas const deltas = currentTrueState - nextTrueState; @@ -3453,11 +3463,12 @@ protected: ter{tecNO_AUTH}); env.close(); - // Can create loan without origination fee + // Cannot create loan, even without an origination fee env(set(borrower, broker.brokerID, principalRequest), counterparty(lender), sig(sfCounterpartySignature, lender), - fee(env.current()->fees().base * 5)); + fee(env.current()->fees().base * 5), + ter{tecNO_AUTH}); env.close(); // No MPToken for lender - no authorization and no payment @@ -3578,6 +3589,52 @@ protected: fee(env.current()->fees().base * 5)); }, CaseArgs{.requireAuth = true, .authorizeBorrower = true}); + + testCase( + [&, this](Env& env, BrokerInfo const& broker, auto&) { + using namespace loan; + Number const principalRequest = broker.asset(1'000).value(); + Vault vault{env}; + auto tx = vault.set({.owner = lender, .id = broker.vaultID}); + tx[sfAssetsMaximum] = BrokerParameters::defaults().vaultDeposit; + env(tx); + env.close(); + + testcase("Vault at maximum value"); + env(set(issuer, broker.brokerID, principalRequest), + counterparty(lender), + interestRate(TenthBips32(10'000)), + sig(sfCounterpartySignature, lender), + fee(env.current()->fees().base * 5), + ter(tecLIMIT_EXCEEDED), + THISLINE); + }, + nullptr); + + testCase( + [&, this](Env& env, BrokerInfo const& broker, auto&) { + using namespace loan; + Number const principalRequest = broker.asset(1'000).value(); + Vault vault{env}; + auto tx = vault.set({.owner = lender, .id = broker.vaultID}); + tx[sfAssetsMaximum] = + BrokerParameters::defaults().vaultDeposit + + broker.asset(1).number(); + env(tx); + env.close(); + + testcase("Vault maximum value exceeded"); + env(set(issuer, broker.brokerID, principalRequest), + counterparty(lender), + interestRate(TenthBips32(100'000)), + sig(sfCounterpartySignature, lender), + fee(env.current()->fees().base * 5), + paymentTotal(2), + paymentInterval(3600 * 24), + ter(tecLIMIT_EXCEEDED), + THISLINE); + }, + nullptr); } void @@ -3813,7 +3870,7 @@ protected: BEAST_EXPECT(loan[sfPaymentInterval] == 60); BEAST_EXPECT(loan[sfPeriodicPayment] == "1000000000"); BEAST_EXPECT(loan[sfPaymentRemaining] == 1); - BEAST_EXPECT(!loan.isMember(sfPreviousPaymentDate)); + BEAST_EXPECT(!loan.isMember(sfPreviousPaymentDueDate)); BEAST_EXPECT(loan[sfPrincipalOutstanding] == "1000000000"); BEAST_EXPECT(loan[sfTotalValueOutstanding] == "1000000000"); BEAST_EXPECT(!loan.isMember(sfLoanScale)); @@ -3994,7 +4051,6 @@ protected: createJson["CloseInterestRate"] = 55374; createJson["ClosePaymentFee"] = "3825205248"; - createJson["GracePeriod"] = 0; createJson["LatePaymentFee"] = "237"; createJson["LoanOriginationFee"] = "0"; createJson["OverpaymentFee"] = 35167; @@ -4009,7 +4065,7 @@ protected: createJson = env.json(createJson, sig(sfCounterpartySignature, lender)); // Fails in preclaim because principal requested can't be // represented as XRP - env(createJson, ter(tecPRECISION_LOSS)); + env(createJson, ter(tecPRECISION_LOSS), THISLINE); env.close(); BEAST_EXPECT(!env.le(keylet)); @@ -4021,7 +4077,7 @@ protected: createJson = env.json(createJson, sig(sfCounterpartySignature, lender)); // Fails in doApply because the payment is too small to be // represented as XRP. - env(createJson, ter(tecPRECISION_LOSS)); + env(createJson, ter(tecPRECISION_LOSS), THISLINE); env.close(); } @@ -4455,15 +4511,6 @@ protected: }; } - void - testBasicMath() - { - // Test the functions defined in LendingHelpers.h - testcase("Basic Math"); - - pass(); - } - void testIssuerLoan() { @@ -4679,7 +4726,30 @@ protected: jtx::fee const& loanSetFee, Number const& debtMaximumRequest) { // first temBAD_SIGNER: TODO + // invalid grace period + { + // zero grace period + env(set(borrower, brokerInfo.brokerID, debtMaximumRequest), + sig(sfCounterpartySignature, lender), + gracePeriod(0), + loanSetFee, + ter(temINVALID)); + // grace period less than default minimum + env(set(borrower, brokerInfo.brokerID, debtMaximumRequest), + sig(sfCounterpartySignature, lender), + gracePeriod(LoanSet::defaultGracePeriod - 1), + loanSetFee, + ter(temINVALID)); + + // grace period greater than payment interval + env(set(borrower, brokerInfo.brokerID, debtMaximumRequest), + sig(sfCounterpartySignature, lender), + paymentInterval(120), + gracePeriod(121), + loanSetFee, + ter(temINVALID)); + } // empty/zero broker ID { auto jv = set(borrower, uint256{}, debtMaximumRequest); @@ -4980,7 +5050,6 @@ protected: createJson["CloseInterestRate"] = 47299; createJson["ClosePaymentFee"] = "3985819770"; - createJson["GracePeriod"] = 0; createJson["InterestRate"] = 92; createJson["LatePaymentFee"] = "3866894865"; createJson["LoanOriginationFee"] = "0"; @@ -4996,7 +5065,7 @@ protected: auto const keylet = keylet::loan(broker.brokerID, loanSequence); createJson = env.json(createJson, sig(sfCounterpartySignature, lender)); - env(createJson, ter(tecPRECISION_LOSS)); + env(createJson, ter(tecPRECISION_LOSS), THISLINE); env.close(startDate); auto loanPayTx = env.json( @@ -5133,7 +5202,6 @@ protected: json(sfCounterpartySignature, Json::objectValue)); createJson["ClosePaymentFee"] = "0"; - createJson["GracePeriod"] = 0; createJson["InterestRate"] = 24346; createJson["LateInterestRate"] = 65535; createJson["LatePaymentFee"] = "0"; @@ -5253,7 +5321,6 @@ protected: json(sfCounterpartySignature, Json::objectValue)); createJson["ClosePaymentFee"] = "0"; - createJson["GracePeriod"] = 0; createJson["InterestRate"] = 12833; createJson["LateInterestRate"] = 77048; createJson["LatePaymentFee"] = "0"; @@ -5347,7 +5414,7 @@ protected: set(borrower, broker.brokerID, Number{55524'81, -2}), fee(loanSetFee), closePaymentFee(0), - gracePeriod(0), + gracePeriod(LoanSet::defaultGracePeriod), interestRate(TenthBips32(12833)), lateInterestRate(TenthBips32(77048)), latePaymentFee(0), @@ -5851,7 +5918,7 @@ protected: auto const periodicRate = loanPeriodicRate(interestRateValue, state.paymentInterval); - auto const rawLoanState = computeRawLoanState( + auto const rawLoanState = computeTheoreticalLoanState( state.periodicPayment, periodicRate, state.paymentRemaining, @@ -6029,7 +6096,7 @@ protected: { // --- PoC Summary ---------------------------------------------------- // Scenario: Borrower makes one periodic payment early (before next due) - // so doPayment sets sfPreviousPaymentDate to the (future) + // so doPayment sets sfPreviousPaymentDueDate to the (future) // sfNextPaymentDueDate and advances sfNextPaymentDueDate by one // interval. Borrower then immediately performs a full-payment // (tfLoanFullPayment). Why it matters: Full-payment interest accrual @@ -6144,15 +6211,16 @@ protected: // Accrued + prepayment-penalty interest based on current periodic // schedule auto const fullPaymentInterest = computeFullPaymentInterest( - after.periodicPayment, + detail::loanPrincipalFromPeriodicPayment( + after.periodicPayment, periodicRate2, after.paymentRemaining), periodicRate2, - after.paymentRemaining, env.current()->parentCloseTime(), after.paymentInterval, after.previousPaymentDate, static_cast( after.startDate.time_since_epoch().count()), closeInterestRate); + // Round to asset scale and split interest/fee parts auto const roundedInterest = roundToAsset(asset.raw(), fullPaymentInterest, after.loanScale); @@ -6180,9 +6248,9 @@ protected: // window by clamping prevPaymentDate to 'now' for the full-pay path. auto const prevClamped = std::min(after.previousPaymentDate, nowSecs); auto const fullPaymentInterestClamped = computeFullPaymentInterest( - after.periodicPayment, + detail::loanPrincipalFromPeriodicPayment( + after.periodicPayment, periodicRate2, after.paymentRemaining), periodicRate2, - after.paymentRemaining, env.current()->parentCloseTime(), after.paymentInterval, prevClamped, @@ -6436,8 +6504,7 @@ protected: .lateFee = Number{200, -6}, .interest = TenthBips32{50'000}, .payTotal = 10, - .payInterval = 150, - .gracePd = 0}; + .payInterval = 150}; auto const assetType = AssetType::XRP; @@ -6458,9 +6525,6 @@ protected: auto state = getCurrentState(env, broker, loanKeylet); if (auto loan = env.le(loanKeylet); BEAST_EXPECT(loan)) { - // log << "loan after create: " << to_string(loan->getJson()) - // << std::endl; - env.close(tp{d{ loan->at(sfNextPaymentDueDate) + loan->at(sfGracePeriod) + 1}}); } @@ -6475,16 +6539,10 @@ protected: { auto const submitParam = to_string(jv); - // log << "about to submit: " << submitParam << std::endl; auto const jr = env.rpc("submit", borrower.name(), submitParam); - // log << jr << std::endl; BEAST_EXPECT(jr.isMember(jss::result)); auto const jResult = jr[jss::result]; - // BEAST_EXPECT(jResult[jss::error] == "invalidTransaction"); - // BEAST_EXPECT( - // jResult[jss::error_exception] == - // "fails local checks: Transaction has bad signature."); } env.close(); @@ -6520,8 +6578,7 @@ protected: .counter = borrower, .principalRequest = Number{100'000, -4}, .interest = TenthBips32{100'000}, - .payTotal = 10, - .gracePd = 0}; + .payTotal = 10}; auto const assetType = AssetType::MPT; @@ -7007,11 +7064,8 @@ protected: env.close(); PaymentParameters paymentParams{ - //.overpaymentFactor = Number{15, -1}, - //.overpaymentExtra = Number{1, -6}, - //.flags = tfLoanOverpayment, - .showStepBalances = true, - //.validateBalances = false, + .showStepBalances = false, + .validateBalances = true, }; makeLoanPayments( @@ -7026,6 +7080,532 @@ protected: paymentParams); } + void + testOverpaymentManagementFee() + { + testcase("testOverpaymentManagementFee"); + + using namespace jtx; + using namespace loan; + + Env env(*this, all); + + Account const lender{"lender"}, borrower{"borrower"}; + + env.fund(XRP(10'000'000), lender, borrower); + env.close(); + + PrettyAsset const asset{xrpIssue(), 1000}; + + auto const result = createVaultAndBroker( + env, + asset, + lender, + { + .vaultDeposit = asset(100'000).value(), + .managementFeeRate = TenthBips16(10'000), + }); + + auto const loanSetFee = fee(env.current()->fees().base * 2); + + auto const loanKeylet = keylet::loan( + result.brokerKeylet().key, + (env.le(result.brokerKeylet()))->at(sfLoanSequence)); + env(loan::set( + borrower, + result.brokerKeylet().key, + asset(10'000).value(), + tfLoanOverpayment), + sig(sfCounterpartySignature, lender), + loan::paymentInterval(86400 * 30), + loan::paymentTotal(3), + loan::overpaymentInterestRate( + TenthBips32(percentageToTenthBips(20))), + loanSetFee); + + // From calculator + auto const expectedOverpaymentManagementFee = Number{33333, 0}; + auto const loanBrokerBalanceBefore = env.balance(lender); + + auto const loanPayFee = fee(env.current()->fees().base * 2); + env(pay(borrower, + loanKeylet.key, + asset(5'000).value(), + tfLoanOverpayment), + loanPayFee); + env.close(); + + BEAST_EXPECTS( + env.balance(lender) - loanBrokerBalanceBefore == + expectedOverpaymentManagementFee, + "overpayment management fee missmatch; expected:" + + to_string(expectedOverpaymentManagementFee) + " got: " + + to_string(env.balance(lender) - loanBrokerBalanceBefore)); + } + + void + testLoanPayBrokerOwnerMissingTrustline() + { + testcase << "LoanPay Broker Owner Missing Trustline (PoC)"; + using namespace jtx; + using namespace loan; + Account const issuer("issuer"); + Account const borrower("borrower"); + Account const broker("broker"); + auto const IOU = issuer["IOU"]; + Env env(*this, all); + env.fund(XRP(20'000), issuer, broker, borrower); + env.close(); + // Set up trustlines and fund accounts + env(trust(broker, IOU(20'000'000))); + env(trust(borrower, IOU(20'000'000))); + env(pay(issuer, broker, IOU(10'000'000))); + env(pay(issuer, borrower, IOU(1'000))); + env.close(); + // Create vault and broker + auto const brokerInfo = createVaultAndBroker(env, IOU, broker); + // Create a loan first (this creates debt) + auto const keylet = keylet::loan(brokerInfo.brokerID, 1); + env(set(borrower, brokerInfo.brokerID, 10'000), + sig(sfCounterpartySignature, broker), + loanServiceFee(IOU(100).value()), + paymentInterval(100), + fee(XRP(100))); + env.close(); + // Ensure broker has sufficient cover so brokerPayee == brokerOwner + // We need coverAvailable >= (debtTotal * coverRateMinimum) + // Deposit enough cover to ensure the fee goes to broker owner + // The default coverRateMinimum is 10%, so for a 10,000 loan we need + // at least 1,000 cover. Default cover is 1,000, so we add more to be + // safe. + auto const additionalCover = IOU(50'000).value(); + env(loanBroker::coverDeposit( + broker, brokerInfo.brokerID, STAmount{IOU, additionalCover})); + env.close(); + // Verify broker owner has a trustline + auto const brokerTrustline = keylet::line(broker, IOU); + BEAST_EXPECT(env.le(brokerTrustline) != nullptr); + // Broker owner deletes their trustline + // First, pay any positive balance to issuer to zero it out + auto const brokerBalance = env.balance(broker, IOU); + env(pay(broker, issuer, brokerBalance)); + env.close(); + // Remove the trustline by setting limit to 0 + env(trust(broker, IOU(0))); + env.close(); + // Verify trustline is deleted + BEAST_EXPECT(env.le(brokerTrustline) == nullptr); + // Now borrower tries to make a payment + // We should get a tesSUCCESS instead of a tecNO_LINE. + env(pay(borrower, keylet.key, IOU(10'100)), + fee(XRP(100)), + ter(tesSUCCESS)); + env.close(); + // Verify trustline is still deleted + BEAST_EXPECT(env.le(brokerTrustline) == nullptr); + // Verify the service fee went to the broker pseudo-account + if (auto const brokerSle = + env.le(keylet::loanbroker(brokerInfo.brokerID)); + BEAST_EXPECT(brokerSle)) + { + Account const pseudo("pseudo-account", brokerSle->at(sfAccount)); + auto const balance = env.balance(pseudo, IOU); + // 1,000 default + 50,000 extra + 100 service fee from LoanPay + BEAST_EXPECTS( + balance == IOU(51'100), to_string(Json::Value(balance))); + } + } + + void + testLoanPayBrokerOwnerUnauthorizedMPT() + { + testcase << "LoanPay Broker Owner MPT unauthorized"; + using namespace jtx; + using namespace loan; + + Account const issuer("issuer"); + Account const borrower("borrower"); + Account const broker("broker"); + + Env env(*this, all); + env.fund(XRP(20'000), issuer, broker, borrower); + env.close(); + + MPTTester mptt{env, issuer, mptInitNoFund}; + mptt.create( + {.flags = tfMPTCanClawback | tfMPTCanTransfer | tfMPTCanLock}); + + PrettyAsset const MPT{mptt.issuanceID()}; + + // Authorize broker and borrower + mptt.authorize({.account = broker}); + mptt.authorize({.account = borrower}); + + env.close(); + + // Fund accounts + env(pay(issuer, broker, MPT(10'000'000))); + env(pay(issuer, borrower, MPT(1'000))); + env.close(); + + // Create vault and broker + auto const brokerInfo = createVaultAndBroker(env, MPT, broker); + // Create a loan first (this creates debt) + auto const keylet = keylet::loan(brokerInfo.brokerID, 1); + env(set(borrower, brokerInfo.brokerID, 10'000), + sig(sfCounterpartySignature, broker), + loanServiceFee(MPT(100).value()), + paymentInterval(100), + fee(XRP(100))); + env.close(); + // Ensure broker has sufficient cover so brokerPayee == brokerOwner + // We need coverAvailable >= (debtTotal * coverRateMinimum) + // Deposit enough cover to ensure the fee goes to broker owner + // The default coverRateMinimum is 10%, so for a 10,000 loan we need + // at least 1,000 cover. Default cover is 1,000, so we add more to be + // safe. + auto const additionalCover = MPT(50'000).value(); + env(loanBroker::coverDeposit( + broker, brokerInfo.brokerID, STAmount{MPT, additionalCover})); + env.close(); + // Verify broker owner is authorized + auto const brokerMpt = keylet::mptoken(mptt.issuanceID(), broker); + BEAST_EXPECT(env.le(brokerMpt) != nullptr); + // Broker owner unauthorizes. + // First, pay any positive balance to issuer to zero it out + auto const brokerBalance = env.balance(broker, MPT); + env(pay(broker, issuer, brokerBalance)); + env.close(); + // Then, unauthorize the MPT. + mptt.authorize({.account = broker, .flags = tfMPTUnauthorize}); + env.close(); + // Verify the MPT is unauthorized. + BEAST_EXPECT(env.le(brokerMpt) == nullptr); + // Now borrower tries to make a payment + // We should get a tesSUCCESS instead of a tecNO_AUTH. + auto const borrowerBalance = env.balance(borrower, MPT); + env(pay(borrower, keylet.key, MPT(10'100)), + fee(XRP(100)), + ter(tesSUCCESS)); + env.close(); + // Verify the MPT is still unauthorized. + BEAST_EXPECT(env.le(brokerMpt) == nullptr); + // Verify the service fee went to the broker pseudo-account + if (auto const brokerSle = + env.le(keylet::loanbroker(brokerInfo.brokerID)); + BEAST_EXPECT(brokerSle)) + { + Account const pseudo("pseudo-account", brokerSle->at(sfAccount)); + auto const balance = env.balance(pseudo, MPT); + // 1,000 default + 50,000 extra + 100 service fee from LoanPay + BEAST_EXPECTS( + balance == MPT(51'100), to_string(Json::Value(balance))); + } + } + + void + testLoanPayBrokerOwnerNoPermissionedDomainMPT() + { + testcase + << "LoanPay Broker Owner without permissioned domain of the MPT"; + using namespace jtx; + using namespace loan; + + Account const issuer("issuer"); + Account const borrower("borrower"); + Account const broker("broker"); + + Env env(*this, all); + env.fund(XRP(20'000), issuer, broker, borrower); + env.close(); + + auto credType = "credential1"; + + pdomain::Credentials const credentials1{{issuer, credType}}; + env(pdomain::setTx(issuer, credentials1)); + env.close(); + + auto domainID = pdomain::getNewDomain(env.meta()); + + env(credentials::create(broker, issuer, credType)); + env(credentials::accept(broker, issuer, credType)); + env.close(); + + env(credentials::create(borrower, issuer, credType)); + env(credentials::accept(borrower, issuer, credType)); + env.close(); + + MPTTester mptt{env, issuer, mptInitNoFund}; + mptt.create({ + .flags = tfMPTCanClawback | tfMPTRequireAuth | tfMPTCanTransfer | + tfMPTCanLock, + .domainID = domainID, + }); + + PrettyAsset const MPT{mptt.issuanceID()}; + + // Authorize broker and borrower + mptt.authorize({.account = broker}); + mptt.authorize({.account = borrower}); + + env.close(); + + // Fund accounts + env(pay(issuer, broker, MPT(10'000'000))); + env(pay(issuer, borrower, MPT(1'000))); + env.close(); + + // Create vault and broker + auto const brokerInfo = createVaultAndBroker(env, MPT, broker); + // Create a loan first (this creates debt) + auto const keylet = keylet::loan(brokerInfo.brokerID, 1); + env(set(borrower, brokerInfo.brokerID, 10'000), + sig(sfCounterpartySignature, broker), + loanServiceFee(MPT(100).value()), + paymentInterval(100), + fee(XRP(100))); + env.close(); + // Ensure broker has sufficient cover so brokerPayee == brokerOwner + // We need coverAvailable >= (debtTotal * coverRateMinimum) + // Deposit enough cover to ensure the fee goes to broker owner + // The default coverRateMinimum is 10%, so for a 10,000 loan we need + // at least 1,000 cover. Default cover is 1,000, so we add more to be + // safe. + auto const additionalCover = MPT(50'000).value(); + env(loanBroker::coverDeposit( + broker, brokerInfo.brokerID, STAmount{MPT, additionalCover})); + env.close(); + // Verify broker owner is authorized + auto const brokerMpt = keylet::mptoken(mptt.issuanceID(), broker); + BEAST_EXPECT(env.le(brokerMpt) != nullptr); + // Remove the credentials for the Broker owner. + // First, pay any positive balance to issuer to zero it out + auto const brokerBalance = env.balance(broker, MPT); + env(pay(broker, issuer, brokerBalance)); + env.close(); + + env(credentials::deleteCred(broker, broker, issuer, credType)); + env.close(); + + // Make sure the broker is not authorized to hold the MPT after we + // deleted the credentials + env(pay(issuer, broker, MPT(1'000)), ter(tecNO_AUTH)); + + // Now borrower tries to make a payment + // We should get a tesSUCCESS instead of a tecNO_AUTH. + auto const borrowerBalance = env.balance(borrower, MPT); + env(pay(borrower, keylet.key, MPT(10'100)), + fee(XRP(100)), + ter(tesSUCCESS)); + env.close(); + // Verify broker is still not authorized + env(pay(issuer, broker, MPT(1'000)), ter(tecNO_AUTH)); + // Verify the service fee went to the broker pseudo-account + if (auto const brokerSle = + env.le(keylet::loanbroker(brokerInfo.brokerID)); + BEAST_EXPECT(brokerSle)) + { + Account const pseudo("pseudo-account", brokerSle->at(sfAccount)); + auto const balance = env.balance(pseudo, MPT); + // 1,000 default + 50,000 extra + 100 service fee from LoanPay + BEAST_EXPECTS( + balance == MPT(51'100), to_string(Json::Value(balance))); + } + } + + void + testLoanSetBrokerOwnerNoPermissionedDomainMPT() + { + testcase + << "LoanSet Broker Owner without permissioned domain of the MPT"; + using namespace jtx; + using namespace loan; + + Account const issuer("issuer"); + Account const borrower("borrower"); + Account const broker("broker"); + + Env env(*this, all); + env.fund(XRP(20'000), issuer, broker, borrower); + env.close(); + + auto credType = "credential1"; + + pdomain::Credentials const credentials1{{issuer, credType}}; + env(pdomain::setTx(issuer, credentials1)); + env.close(); + + auto domainID = pdomain::getNewDomain(env.meta()); + + // Add credentials for the broker and borrower + env(credentials::create(broker, issuer, credType)); + env(credentials::accept(broker, issuer, credType)); + env.close(); + + env(credentials::create(borrower, issuer, credType)); + env(credentials::accept(borrower, issuer, credType)); + env.close(); + + MPTTester mptt{env, issuer, mptInitNoFund}; + mptt.create({ + .flags = tfMPTCanClawback | tfMPTRequireAuth | tfMPTCanTransfer | + tfMPTCanLock, + .domainID = domainID, + }); + + PrettyAsset const MPT{mptt.issuanceID()}; + + // Authorize broker and borrower + mptt.authorize({.account = broker}); + mptt.authorize({.account = borrower}); + env.close(); + + // Fund accounts + env(pay(issuer, broker, MPT(10'000'000))); + env(pay(issuer, borrower, MPT(1'000))); + env.close(); + + // Create vault and broker + auto const brokerInfo = createVaultAndBroker(env, MPT, broker); + + // Remove the credentials for the Broker owner. + // Clear the balance first. + auto const brokerBalance = env.balance(broker, MPT); + env(pay(broker, issuer, brokerBalance)); + env.close(); + // Delete the credentials + env(credentials::deleteCred(broker, broker, issuer, credType)); + env.close(); + + // Create a loan, this should fail for tecNO_AUTH + env(set(borrower, brokerInfo.brokerID, 10'000), + sig(sfCounterpartySignature, broker), + loanServiceFee(MPT(100).value()), + paymentInterval(100), + fee(XRP(100)), + ter(tecNO_AUTH)); + env.close(); + } + + void + testSequentialFLCDepletion() + { + testcase << "First-Loss Capital Depletion on Sequential Defaults"; + + using namespace jtx; + using namespace loan; + using namespace loanBroker; + + Env env(*this, all); + + Account const issuer{"issuer"}; + Account const lender{"lender"}; + Account const borrowerA{"borrowerA"}; + Account const borrowerB{"borrowerB"}; + + env.fund(XRP(1'000'000), issuer, lender, borrowerA, borrowerB); + env.close(); + + PrettyAsset const asset = xrpIssue(); + auto const vaultDepositAmount = + asset(200'000); // Enough for 2 x 50k loans plus interest/fees + + auto const brokerInfo = createVaultAndBroker( + env, + asset, + lender, + { + .vaultDeposit = vaultDepositAmount.value(), + .debtMax = 0, + .coverRateMin = TenthBips32(20000), // 20% + .coverDeposit = 21'000, + .managementFeeRate = TenthBips16(100), // 0.1% + .coverRateLiquidation = TenthBips32(100000), + }); + auto const brokerKeylet = brokerInfo.brokerKeylet(); + + // Create two identical loans: each 50,000 XRP principal (scaled down to + // avoid funding issues) Total DebtTotal will be ~100,000 XRP (principal + // + interest) Formula will calculate cover as: 100% × (20% × 100,000) = + // 20,000 XRP So we need FLC = 20,000 XRP to be fully consumed by first + // default + auto const principalAmount = Number(50'000); + auto const loanPaymentInterval = 2592000; // 30 days + auto const loanGracePeriod = 604800; // 7 days + + // Create Loan A + auto loanATx = env.jt( + set(borrowerA, brokerKeylet.key, principalAmount), + sig(sfCounterpartySignature, lender), + interestRate(TenthBips32(500)), // 5% + paymentTotal(12), + loan::paymentInterval(loanPaymentInterval), + loan::gracePeriod(loanGracePeriod), + fee(XRP(10))); // Sufficient fee for multi-sig transaction + env(loanATx); + env.close(); + + auto const loanAKeylet = keylet::loan(brokerKeylet.key, 1); + + // Create Loan B + auto loanBTx = env.jt( + set(borrowerB, brokerKeylet.key, principalAmount), + sig(sfCounterpartySignature, lender), + interestRate(TenthBips32(500)), // 5% + paymentTotal(12), + loan::paymentInterval(loanPaymentInterval), + loan::gracePeriod(loanGracePeriod), + fee(XRP(10))); // Sufficient fee for multi-sig transaction + env(loanBTx); + env.close(); + + auto const loanBKeylet = keylet::loan(brokerKeylet.key, 2); + + auto loanASle = env.le(loanAKeylet); + if (!BEAST_EXPECT(loanASle)) + return; + + // Advance time past grace period for both loans to be defaultable + auto const loanANextDue = loanASle->at(sfNextPaymentDueDate); + auto const loanAGrace = loanASle->at(sfGracePeriod); + env.close(std::chrono::seconds{loanANextDue + loanAGrace + 60}); + + env(manage(lender, loanAKeylet.key, tfLoanDefault), ter(tesSUCCESS)); + env.close(); + + // Verify Loan A is defaulted + loanASle = env.le(loanAKeylet); + if (!BEAST_EXPECT(loanASle)) + return; + BEAST_EXPECT(loanASle->isFlag(lsfLoanDefault)); + BEAST_EXPECT(loanASle->at(sfPaymentRemaining) == 0); + + // Check broker state after first default (from committed ledger) + auto brokerSle = env.le(brokerKeylet); + if (!BEAST_EXPECT(brokerSle)) + return; + auto const afterFirstDebtTotal = brokerSle->at(sfDebtTotal); + auto const afterFirstCoverAvailable = brokerSle->at(sfCoverAvailable); + + // DebtTotal should have decreased by Loan A's debt + BEAST_EXPECT(afterFirstDebtTotal == 50'134); + + // CoverAvailable should have decreased significantly + BEAST_EXPECT(afterFirstCoverAvailable == 946); + + env(manage(lender, loanBKeylet.key, tfLoanDefault), ter(tesSUCCESS)); + + brokerSle = env.le(brokerKeylet); + if (!BEAST_EXPECT(brokerSle)) + return; + auto const afterSecondDebtTotal = brokerSle->at(sfDebtTotal); + auto const afterSecondCoverAvailable = brokerSle->at(sfCoverAvailable); + + BEAST_EXPECT(afterSecondDebtTotal == 0); + + BEAST_EXPECT(afterSecondCoverAvailable == 0); + } + public: void run() override @@ -7034,6 +7614,8 @@ public: testLoanPayLateFullPaymentBypassesPenalties(); testLoanCoverMinimumRoundingExploit(); #endif + testInvalidLoanSet(); + testCoverDepositWithdrawNonTransferableMPT(); testPoC_UnsignedUnderflowOnFullPayAfterEarlyPeriodic(); @@ -7045,12 +7627,9 @@ public: testServiceFeeOnBrokerDeepFreeze(); testRPC(); - testBasicMath(); - testInvalidLoanDelete(); testInvalidLoanManage(); testInvalidLoanPay(); - testInvalidLoanSet(); testBatchBypassCounterparty(); testLoanPayComputePeriodicPaymentValidRateInvariant(); @@ -7074,6 +7653,12 @@ public: testBorrowerIsBroker(); testIssuerIsBorrower(); testLimitExceeded(); + testOverpaymentManagementFee(); + testLoanPayBrokerOwnerMissingTrustline(); + testLoanPayBrokerOwnerUnauthorizedMPT(); + testLoanPayBrokerOwnerNoPermissionedDomainMPT(); + testLoanSetBrokerOwnerNoPermissionedDomainMPT(); + testSequentialFLCDepletion(); } }; @@ -7193,15 +7778,15 @@ class LoanArbitrary_test : public LoanBatch_test .vaultDeposit = 10000, .debtMax = 0, .coverRateMin = TenthBips32{0}, - // .managementFeeRate = TenthBips16{5919}, + .managementFeeRate = TenthBips16{0}, .coverRateLiquidation = TenthBips32{0}}; LoanParameters const loanParams{ .account = Account("lender"), .counter = Account("borrower"), - .principalRequest = Number{10000, 0}, - // .interest = TenthBips32{0}, - // .payTotal = 5816, - .payInterval = 150}; + .principalRequest = Number{200000, -6}, + .interest = TenthBips32{50000}, + .payTotal = 2, + .payInterval = 200}; runLoan(AssetType::XRP, brokerParams, loanParams); } diff --git a/src/test/app/Vault_test.cpp b/src/test/app/Vault_test.cpp index d0a1450d6c..a6d08b6531 100644 --- a/src/test/app/Vault_test.cpp +++ b/src/test/app/Vault_test.cpp @@ -5324,7 +5324,7 @@ class Vault_test : public beast::unit_test::suite // Create a simple Loan for the full amount of Vault assets env(set(depositor, brokerKeylet.key, asset(100).value()), loan::interestRate(TenthBips32(0)), - gracePeriod(10), + gracePeriod(60), paymentInterval(120), paymentTotal(10), sig(sfCounterpartySignature, owner), @@ -5344,7 +5344,7 @@ class Vault_test : public beast::unit_test::suite THISLINE); env.close(); - env.close(std::chrono::seconds{120 + 10}); + env.close(std::chrono::seconds{120 + 60}); env(manage(owner, loanKeylet.key, tfLoanDefault), ter(tesSUCCESS), diff --git a/src/test/jtx/impl/mpt.cpp b/src/test/jtx/impl/mpt.cpp index adffa8548a..ceb60eb319 100644 --- a/src/test/jtx/impl/mpt.cpp +++ b/src/test/jtx/impl/mpt.cpp @@ -644,7 +644,7 @@ MPTTester::operator[](std::string const& name) const } PrettyAmount -MPTTester::operator()(std::uint64_t amount) const +MPTTester::operator()(std::int64_t amount) const { return MPT("", issuanceID())(amount); } diff --git a/src/test/jtx/mpt.h b/src/test/jtx/mpt.h index 2f6bbb9ea8..3eea362b58 100644 --- a/src/test/jtx/mpt.h +++ b/src/test/jtx/mpt.h @@ -272,7 +272,7 @@ public: operator[](std::string const& name) const; PrettyAmount - operator()(std::uint64_t amount) const; + operator()(std::int64_t amount) const; operator Asset() const; diff --git a/src/xrpld/app/misc/LendingHelpers.h b/src/xrpld/app/misc/LendingHelpers.h index 071466f05c..79fc617569 100644 --- a/src/xrpld/app/misc/LendingHelpers.h +++ b/src/xrpld/app/misc/LendingHelpers.h @@ -84,50 +84,10 @@ struct LoanPaymentParts operator==(LoanPaymentParts const& other) const; }; -/* Describes the initial computed properties of a loan. - * - * This structure contains the fundamental calculated values that define a - * loan's payment structure and amortization schedule. These properties are - * computed: - * - At loan creation (LoanSet transaction) - * - When loan terms change (e.g., after an overpayment that reduces the loan - * balance) - */ -struct LoanProperties -{ - // The unrounded amount to be paid at each regular payment period. - // Calculated using the standard amortization formula based on principal, - // interest rate, and number of payments. - // The actual amount paid in the LoanPay transaction must be rounded up to - // the precision of the asset and loan. - Number periodicPayment; - - // The total amount the borrower will pay over the life of the loan. - // Equal to periodicPayment * paymentsRemaining. - // This includes principal, interest, and management fees. - Number totalValueOutstanding; - - // The total management fee that will be paid to the broker over the - // loan's lifetime. This is a percentage of the total interest (gross) - // as specified by the broker's management fee rate. - Number managementFeeOwedToBroker; - - // The scale (decimal places) used for rounding all loan amounts. - // This is the maximum of: - // - The asset's native scale - // - A minimum scale required to represent the periodic payment accurately - // All loan state values (principal, interest, fees) are rounded to this - // scale. - std::int32_t loanScale; - - // The principal portion of the first payment. - Number firstPaymentPrincipal; -}; - /** This structure captures the parts of a loan state. * - * Whether the values are raw (unrounded) or rounded will depend on how it was - * computed. + * Whether the values are theoretical (unrounded) or rounded will depend on how + * it was computed. * * Many of the fields can be derived from each other, but they're all provided * here to reduce code duplication and possible mistakes. @@ -161,6 +121,39 @@ struct LoanState } }; +/* Describes the initial computed properties of a loan. + * + * This structure contains the fundamental calculated values that define a + * loan's payment structure and amortization schedule. These properties are + * computed: + * - At loan creation (LoanSet transaction) + * - When loan terms change (e.g., after an overpayment that reduces the loan + * balance) + */ +struct LoanProperties +{ + // The unrounded amount to be paid at each regular payment period. + // Calculated using the standard amortization formula based on principal, + // interest rate, and number of payments. + // The actual amount paid in the LoanPay transaction must be rounded up to + // the precision of the asset and loan. + Number periodicPayment; + + // The loan's current state, with all values rounded to the loan's scale. + LoanState loanState; + + // The scale (decimal places) used for rounding all loan amounts. + // This is the maximum of: + // - The asset's native scale + // - A minimum scale required to represent the periodic payment accurately + // All loan state values (principal, interest, fees) are rounded to this + // scale. + std::int32_t loanScale; + + // The principal portion of the first payment. + Number firstPaymentPrincipal; +}; + // Some values get re-rounded to the vault scale any time they are adjusted. In // addition, they are prevented from ever going below zero. This helps avoid // accumulated rounding errors and leftover dust amounts. @@ -179,11 +172,12 @@ adjustImpreciseNumber( } inline int -getVaultScale(SLE::const_ref vaultSle) +getAssetsTotalScale(SLE::const_ref vaultSle) { if (!vaultSle) return Number::minExponent - 1; // LCOV_EXCL_LINE - return vaultSle->at(sfAssetsTotal).exponent(); + return STAmount{vaultSle->at(sfAsset), vaultSle->at(sfAssetsTotal)} + .exponent(); } TER @@ -196,20 +190,12 @@ checkLoanGuards( beast::Journal j); LoanState -computeRawLoanState( +computeTheoreticalLoanState( Number const& periodicPayment, Number const& periodicRate, std::uint32_t const paymentRemaining, TenthBips32 const managementFeeRate); -LoanState -computeRawLoanState( - Number const& periodicPayment, - TenthBips32 interestRate, - std::uint32_t paymentInterval, - std::uint32_t const paymentRemaining, - TenthBips32 const managementFeeRate); - // Constructs a valid LoanState object from arbitrary inputs LoanState constructLoanState( @@ -231,7 +217,7 @@ computeManagementFee( Number computeFullPaymentInterest( - Number const& rawPrincipalOutstanding, + Number const& theoreticalPrincipalOutstanding, Number const& periodicRate, NetClock::time_point parentCloseTime, std::uint32_t paymentInterval, @@ -239,17 +225,6 @@ computeFullPaymentInterest( std::uint32_t startDate, TenthBips32 closeInterestRate); -Number -computeFullPaymentInterest( - Number const& periodicPayment, - Number const& periodicRate, - std::uint32_t paymentRemaining, - NetClock::time_point parentCloseTime, - std::uint32_t paymentInterval, - std::uint32_t prevPaymentDate, - std::uint32_t startDate, - TenthBips32 closeInterestRate); - namespace detail { // These classes and functions should only be accessed by LendingHelper // functions and unit tests @@ -387,6 +362,70 @@ struct LoanStateDeltas nonNegative(); }; +Expected, TER> +tryOverpayment( + Asset const& asset, + std::int32_t loanScale, + ExtendedPaymentComponents const& overpaymentComponents, + LoanState const& roundedLoanState, + Number const& periodicPayment, + Number const& periodicRate, + std::uint32_t paymentRemaining, + TenthBips16 const managementFeeRate, + beast::Journal j); + +Number +computeRaisedRate(Number const& periodicRate, std::uint32_t paymentsRemaining); + +Number +computePaymentFactor( + Number const& periodicRate, + std::uint32_t paymentsRemaining); + +std::pair +computeInterestAndFeeParts( + Asset const& asset, + Number const& interest, + TenthBips16 managementFeeRate, + std::int32_t loanScale); + +Number +loanPeriodicPayment( + Number const& principalOutstanding, + Number const& periodicRate, + std::uint32_t paymentsRemaining); + +Number +loanPrincipalFromPeriodicPayment( + Number const& periodicPayment, + Number const& periodicRate, + std::uint32_t paymentsRemaining); + +Number +loanLatePaymentInterest( + Number const& principalOutstanding, + TenthBips32 lateInterestRate, + NetClock::time_point parentCloseTime, + std::uint32_t nextPaymentDueDate); + +Number +loanAccruedInterest( + Number const& principalOutstanding, + Number const& periodicRate, + NetClock::time_point parentCloseTime, + std::uint32_t startDate, + std::uint32_t prevPaymentDate, + std::uint32_t paymentInterval); + +ExtendedPaymentComponents +computeOverpaymentComponents( + Asset const& asset, + int32_t const loanScale, + Number const& overpayment, + TenthBips32 const overpaymentInterestRate, + TenthBips32 const overpaymentFeeRate, + TenthBips16 const managementFeeRate); + PaymentComponents computePaymentComponents( Asset const& asset, @@ -413,13 +452,22 @@ operator+(LoanState const& lhs, detail::LoanStateDeltas const& rhs); LoanProperties computeLoanProperties( Asset const& asset, - Number principalOutstanding, + Number const& principalOutstanding, TenthBips32 interestRate, std::uint32_t paymentInterval, std::uint32_t paymentsRemaining, TenthBips32 managementFeeRate, std::int32_t minimumScale); +LoanProperties +computeLoanProperties( + Asset const& asset, + Number const& principalOutstanding, + Number const& periodicRate, + std::uint32_t paymentsRemaining, + TenthBips32 managementFeeRate, + std::int32_t minimumScale); + bool isRounded(Asset const& asset, Number const& value, std::int32_t scale); diff --git a/src/xrpld/app/misc/detail/LendingHelpers.cpp b/src/xrpld/app/misc/detail/LendingHelpers.cpp index 37385583e7..a8354ff049 100644 --- a/src/xrpld/app/misc/detail/LendingHelpers.cpp +++ b/src/xrpld/app/misc/detail/LendingHelpers.cpp @@ -100,6 +100,9 @@ computePaymentFactor( Number const& periodicRate, std::uint32_t paymentsRemaining) { + if (paymentsRemaining == 0) + return numZero; + // For zero interest, payment factor is simply 1/paymentsRemaining if (periodicRate == beast::zero) return Number{1} / paymentsRemaining; @@ -132,27 +135,6 @@ loanPeriodicPayment( computePaymentFactor(periodicRate, paymentsRemaining); } -/* Calculates the periodic payment amount from annualized interest rate. - * Converts the annual rate to periodic rate before computing payment. - * - * Equation (7) from XLS-66 spec, Section A-2 Equation Glossary - */ -Number -loanPeriodicPayment( - Number const& principalOutstanding, - TenthBips32 interestRate, - std::uint32_t paymentInterval, - std::uint32_t paymentsRemaining) -{ - if (principalOutstanding == 0 || paymentsRemaining == 0) - return 0; - - Number const periodicRate = loanPeriodicRate(interestRate, paymentInterval); - - return loanPeriodicPayment( - principalOutstanding, periodicRate, paymentsRemaining); -} - /* Reverse-calculates principal from periodic payment amount. * Used to determine theoretical principal at any point in the schedule. * @@ -164,6 +146,9 @@ loanPrincipalFromPeriodicPayment( Number const& periodicRate, std::uint32_t paymentsRemaining) { + if (paymentsRemaining == 0) + return numZero; + if (periodicRate == 0) return periodicPayment * paymentsRemaining; @@ -171,21 +156,6 @@ loanPrincipalFromPeriodicPayment( computePaymentFactor(periodicRate, paymentsRemaining); } -/* Splits gross interest into net interest (to vault) and management fee (to - * broker). Returns pair of (net interest, management fee). - * - * Equation (33) from XLS-66 spec, Section A-2 Equation Glossary - */ -std::pair -computeInterestAndFeeParts( - Number const& interest, - TenthBips16 managementFeeRate) -{ - auto const fee = tenthBipsOfValue(interest, managementFeeRate); - - return std::make_pair(interest - fee, fee); -} - /* * Computes the interest and management fee parts from interest amount. * @@ -216,6 +186,12 @@ loanLatePaymentInterest( NetClock::time_point parentCloseTime, std::uint32_t nextPaymentDueDate) { + if (principalOutstanding == beast::zero) + return numZero; + + if (lateInterestRate == TenthBips32{0}) + return numZero; + auto const now = parentCloseTime.time_since_epoch().count(); // If the payment is not late by any amount of time, then there's no late @@ -248,6 +224,9 @@ loanAccruedInterest( if (periodicRate == beast::zero) return numZero; + if (paymentInterval == 0) + return numZero; + auto const lastPaymentDate = std::max(prevPaymentDate, startDate); auto const now = parentCloseTime.time_since_epoch().count(); @@ -401,42 +380,33 @@ doPayment( * The function preserves accumulated rounding errors across the re-amortization * to ensure the loan state remains consistent with its payment history. */ -Expected +Expected, TER> tryOverpayment( Asset const& asset, std::int32_t loanScale, ExtendedPaymentComponents const& overpaymentComponents, - Number& totalValueOutstanding, - Number& principalOutstanding, - Number& managementFeeOutstanding, - Number& periodicPayment, - TenthBips32 interestRate, - std::uint32_t paymentInterval, + LoanState const& roundedOldState, + Number const& periodicPayment, Number const& periodicRate, std::uint32_t paymentRemaining, - std::uint32_t prevPaymentDate, - std::optional nextDueDate, TenthBips16 const managementFeeRate, beast::Journal j) { // Calculate what the loan state SHOULD be theoretically (at full precision) - auto const raw = computeRawLoanState( + auto const theoreticalState = computeTheoreticalLoanState( periodicPayment, periodicRate, paymentRemaining, managementFeeRate); - // Get the actual loan state (with accumulated rounding from past payments) - auto const rounded = constructLoanState( - totalValueOutstanding, principalOutstanding, managementFeeOutstanding); - // Calculate the accumulated rounding errors. These need to be preserved // across the re-amortization to maintain consistency with the loan's // payment history. Without preserving these errors, the loan could end // up with a different total value than what the borrower has actually paid. - auto const errors = rounded - raw; + auto const errors = roundedOldState - theoreticalState; - // Compute the new principal by applying the overpayment to the raw - // (theoretical) principal. Use max with 0 to ensure we never go negative. - auto const newRawPrincipal = std::max( - raw.principalOutstanding - overpaymentComponents.trackedPrincipalDelta, + // Compute the new principal by applying the overpayment to the theoretical + // principal. Use max with 0 to ensure we never go negative. + auto const newTheoreticalPrincipal = std::max( + theoreticalState.principalOutstanding - + overpaymentComponents.trackedPrincipalDelta, Number{0}); // Compute new loan properties based on the reduced principal. This @@ -444,9 +414,8 @@ tryOverpayment( // for the remaining payment schedule. auto newLoanProperties = computeLoanProperties( asset, - newRawPrincipal, - interestRate, - paymentInterval, + newTheoreticalPrincipal, + periodicRate, paymentRemaining, managementFeeRate, loanScale); @@ -454,56 +423,60 @@ tryOverpayment( JLOG(j.debug()) << "new periodic payment: " << newLoanProperties.periodicPayment << ", new total value: " - << newLoanProperties.totalValueOutstanding + << newLoanProperties.loanState.valueOutstanding << ", first payment principal: " << newLoanProperties.firstPaymentPrincipal; // Calculate what the new loan state should be with the new periodic payment - auto const newRaw = computeRawLoanState( - newLoanProperties.periodicPayment, - periodicRate, - paymentRemaining, - managementFeeRate) + + // including rounding errors + auto const newTheoreticalState = computeTheoreticalLoanState( + newLoanProperties.periodicPayment, + periodicRate, + paymentRemaining, + managementFeeRate) + errors; - JLOG(j.debug()) << "new raw value: " << newRaw.valueOutstanding - << ", principal: " << newRaw.principalOutstanding - << ", interest gross: " << newRaw.interestOutstanding(); - // Update the loan state variables with the new values PLUS the preserved - // rounding errors. This ensures the loan's tracked state remains - // consistent with its payment history. + JLOG(j.debug()) << "new theoretical value: " + << newTheoreticalState.valueOutstanding << ", principal: " + << newTheoreticalState.principalOutstanding + << ", interest gross: " + << newTheoreticalState.interestOutstanding(); - principalOutstanding = std::clamp( - roundToAsset( - asset, newRaw.principalOutstanding, loanScale, Number::upward), - numZero, - rounded.principalOutstanding); - totalValueOutstanding = std::clamp( + // Update the loan state variables with the new values that include the + // preserved rounding errors. This ensures the loan's tracked state remains + // consistent with its payment history. + auto const principalOutstanding = std::clamp( roundToAsset( asset, - principalOutstanding + newRaw.interestOutstanding(), + newTheoreticalState.principalOutstanding, loanScale, Number::upward), numZero, - rounded.valueOutstanding); - managementFeeOutstanding = std::clamp( - roundToAsset(asset, newRaw.managementFeeDue, loanScale), + roundedOldState.principalOutstanding); + auto const totalValueOutstanding = std::clamp( + roundToAsset( + asset, + principalOutstanding + newTheoreticalState.interestOutstanding(), + loanScale, + Number::upward), numZero, - rounded.managementFeeDue); + roundedOldState.valueOutstanding); + auto const managementFeeOutstanding = std::clamp( + roundToAsset(asset, newTheoreticalState.managementFeeDue, loanScale), + numZero, + roundedOldState.managementFeeDue); - auto const newRounded = constructLoanState( + auto const roundedNewState = constructLoanState( totalValueOutstanding, principalOutstanding, managementFeeOutstanding); // Update newLoanProperties so that checkLoanGuards can make an accurate // evaluation. - newLoanProperties.totalValueOutstanding = newRounded.valueOutstanding; + newLoanProperties.loanState = roundedNewState; - JLOG(j.debug()) << "new rounded value: " << newRounded.valueOutstanding - << ", principal: " << newRounded.principalOutstanding - << ", interest gross: " << newRounded.interestOutstanding(); - - // Update the periodic payment to reflect the re-amortized schedule - periodicPayment = newLoanProperties.periodicPayment; + JLOG(j.debug()) << "new rounded value: " << roundedNewState.valueOutstanding + << ", principal: " << roundedNewState.principalOutstanding + << ", interest gross: " + << roundedNewState.interestOutstanding(); // check that the loan is still valid if (auto const ter = checkLoanGuards( @@ -513,7 +486,7 @@ tryOverpayment( // small interest amounts, that may have already been paid // off. Check what's still outstanding. This should // guarantee that the interest checks pass. - newRounded.interestOutstanding() != beast::zero, + roundedNewState.interestOutstanding() != beast::zero, paymentRemaining, newLoanProperties, j)) @@ -527,32 +500,40 @@ tryOverpayment( // Validate that all computed properties are reasonable. These checks should // never fail under normal circumstances, but we validate defensively. if (newLoanProperties.periodicPayment <= 0 || - newLoanProperties.totalValueOutstanding <= 0 || - newLoanProperties.managementFeeOwedToBroker < 0) + newLoanProperties.loanState.valueOutstanding <= 0 || + newLoanProperties.loanState.managementFeeDue < 0) { // LCOV_EXCL_START JLOG(j.warn()) << "Overpayment not allowed: Computed loan " "properties are invalid. Does " "not compute. TotalValueOutstanding: " - << newLoanProperties.totalValueOutstanding + << newLoanProperties.loanState.valueOutstanding << ", PeriodicPayment : " << newLoanProperties.periodicPayment << ", ManagementFeeOwedToBroker: " - << newLoanProperties.managementFeeOwedToBroker; + << newLoanProperties.loanState.managementFeeDue; return Unexpected(tesSUCCESS); // LCOV_EXCL_STOP } - auto const deltas = rounded - newRounded; + auto const deltas = roundedOldState - roundedNewState; - auto const hypotheticalValueOutstanding = - rounded.valueOutstanding - deltas.principal; + // The change in loan management fee is equal to the change between the old + // and the new outstanding management fees + XRPL_ASSERT_PARTS( + deltas.managementFee == + roundedOldState.managementFeeDue - managementFeeOutstanding, + "xrpl::detail::tryOverpayment", + "no fee change"); // Calculate how the loan's value changed due to the overpayment. // This should be negative (value decreased) or zero. A principal // overpayment should never increase the loan's value. - auto const valueChange = - newRounded.valueOutstanding - hypotheticalValueOutstanding; + // The value change is derived from the reduction in interest due to + // the lower principal. + // We do not consider the change in management fee here, since + // management fees are excluded from the valueOutstanding. + auto const valueChange = -deltas.interest; if (valueChange > 0) { JLOG(j.warn()) << "Principal overpayment would increase the value of " @@ -560,21 +541,23 @@ tryOverpayment( return Unexpected(tesSUCCESS); } - return LoanPaymentParts{ - // Principal paid is the reduction in principal outstanding - .principalPaid = deltas.principal, - // Interest paid is the reduction in interest due - .interestPaid = - deltas.interest + overpaymentComponents.untrackedInterest, - // Value change includes both the reduction from paying down principal - // (negative) and any untracked interest penalties (positive, e.g., if - // the overpayment itself incurs a fee) - .valueChange = - valueChange + overpaymentComponents.trackedInterestPart(), - // Fee paid includes both the reduction in tracked management fees and - // any untracked fees on the overpayment itself - .feePaid = deltas.managementFee + - overpaymentComponents.untrackedManagementFee}; + return std::make_pair( + LoanPaymentParts{ + // Principal paid is the reduction in principal outstanding + .principalPaid = deltas.principal, + // Interest paid is the reduction in interest due + .interestPaid = overpaymentComponents.untrackedInterest, + // Value change includes both the reduction from paying down + // principal (negative) and any untracked interest penalties + // (positive, e.g., if the overpayment itself incurs a fee) + .valueChange = + valueChange + overpaymentComponents.untrackedInterest, + // Fee paid includes both the reduction in tracked management fees + // and any untracked fees on the overpayment itself + .feePaid = overpaymentComponents.untrackedManagementFee + + overpaymentComponents.trackedManagementFeeDelta, + }, + newLoanProperties); } /* Validates and applies an overpayment to the loan state. @@ -598,23 +581,16 @@ doOverpayment( NumberProxy& principalOutstandingProxy, NumberProxy& managementFeeOutstandingProxy, NumberProxy& periodicPaymentProxy, - TenthBips32 const interestRate, - std::uint32_t const paymentInterval, Number const& periodicRate, std::uint32_t const paymentRemaining, - std::uint32_t const prevPaymentDate, - std::optional const nextDueDate, TenthBips16 const managementFeeRate, beast::Journal j) { - // Create temporary copies of the loan state that can be safely modified - // and discarded if the overpayment doesn't work out. This prevents - // corrupting the actual ledger data if validation fails. - Number totalValueOutstanding = totalValueOutstandingProxy; - Number principalOutstanding = principalOutstandingProxy; - Number managementFeeOutstanding = managementFeeOutstandingProxy; - Number periodicPayment = periodicPaymentProxy; - + auto const loanState = constructLoanState( + totalValueOutstandingProxy, + principalOutstandingProxy, + managementFeeOutstandingProxy); + auto const periodicPayment = periodicPaymentProxy; JLOG(j.debug()) << "overpayment components:" << ", totalValue before: " << *totalValueOutstandingProxy @@ -633,33 +609,28 @@ doOverpayment( asset, loanScale, overpaymentComponents, - totalValueOutstanding, - principalOutstanding, - managementFeeOutstanding, + loanState, periodicPayment, - interestRate, - paymentInterval, periodicRate, paymentRemaining, - prevPaymentDate, - nextDueDate, managementFeeRate, j); if (!ret) return Unexpected(ret.error()); - auto const& loanPaymentParts = *ret; + auto const& [loanPaymentParts, newLoanProperties] = *ret; + auto const newRoundedLoanState = newLoanProperties.loanState; // Safety check: the principal must have decreased. If it didn't (or // increased!), something went wrong in the calculation and we should // reject the overpayment. - if (principalOutstandingProxy <= principalOutstanding) + if (principalOutstandingProxy <= newRoundedLoanState.principalOutstanding) { // LCOV_EXCL_START JLOG(j.warn()) << "Overpayment not allowed: principal " << "outstanding did not decrease. Before: " - << *principalOutstandingProxy - << ". After: " << principalOutstanding; + << *principalOutstandingProxy << ". After: " + << newRoundedLoanState.principalOutstanding; return Unexpected(tesSUCCESS); // LCOV_EXCL_STOP } @@ -670,34 +641,29 @@ doOverpayment( XRPL_ASSERT_PARTS( overpaymentComponents.trackedPrincipalDelta == - principalOutstandingProxy - principalOutstanding, + principalOutstandingProxy - + newRoundedLoanState.principalOutstanding, "xrpl::detail::doOverpayment", "principal change agrees"); - XRPL_ASSERT_PARTS( - overpaymentComponents.trackedManagementFeeDelta == - managementFeeOutstandingProxy - managementFeeOutstanding, - "xrpl::detail::doOverpayment", - "no fee change"); - // I'm not 100% sure the following asserts are correct. If in doubt, and // everything else works, remove any that cause trouble. - JLOG(j.debug()) << "valueChange: " << loanPaymentParts.valueChange - << ", totalValue before: " << *totalValueOutstandingProxy - << ", totalValue after: " << totalValueOutstanding - << ", totalValue delta: " - << (totalValueOutstandingProxy - totalValueOutstanding) - << ", principalDelta: " - << overpaymentComponents.trackedPrincipalDelta - << ", principalPaid: " << loanPaymentParts.principalPaid - << ", Computed difference: " - << overpaymentComponents.trackedPrincipalDelta - - (totalValueOutstandingProxy - totalValueOutstanding); + JLOG(j.debug()) + << "valueChange: " << loanPaymentParts.valueChange + << ", totalValue before: " << *totalValueOutstandingProxy + << ", totalValue after: " << newRoundedLoanState.valueOutstanding + << ", totalValue delta: " + << (totalValueOutstandingProxy - newRoundedLoanState.valueOutstanding) + << ", principalDelta: " << overpaymentComponents.trackedPrincipalDelta + << ", principalPaid: " << loanPaymentParts.principalPaid + << ", Computed difference: " + << overpaymentComponents.trackedPrincipalDelta - + (totalValueOutstandingProxy - newRoundedLoanState.valueOutstanding); XRPL_ASSERT_PARTS( loanPaymentParts.valueChange == - totalValueOutstanding - + newRoundedLoanState.valueOutstanding - (totalValueOutstandingProxy - overpaymentComponents.trackedPrincipalDelta) + overpaymentComponents.trackedInterestPart(), @@ -710,19 +676,12 @@ doOverpayment( "xrpl::detail::doOverpayment", "principal payment matches"); - XRPL_ASSERT_PARTS( - loanPaymentParts.feePaid == - overpaymentComponents.untrackedManagementFee + - overpaymentComponents.trackedManagementFeeDelta, - "xrpl::detail::doOverpayment", - "fee payment matches"); - // All validations passed, so update the proxy objects (which will // modify the actual Loan ledger object) - totalValueOutstandingProxy = totalValueOutstanding; - principalOutstandingProxy = principalOutstanding; - managementFeeOutstandingProxy = managementFeeOutstanding; - periodicPaymentProxy = periodicPayment; + totalValueOutstandingProxy = newRoundedLoanState.valueOutstanding; + principalOutstandingProxy = newRoundedLoanState.principalOutstanding; + managementFeeOutstandingProxy = newRoundedLoanState.managementFeeDue; + periodicPaymentProxy = newLoanProperties.periodicPayment; return loanPaymentParts; } @@ -789,25 +748,21 @@ computeLatePayment( // this to keep the logic clear. This preserves all the other fields without // having to enumerate them. - ExtendedPaymentComponents const late = [&]() { - auto inner = periodic; + ExtendedPaymentComponents const late{ + periodic, + // Untracked management fee includes: + // 1. Regular service fee (from periodic.untrackedManagementFee) + // 2. Late payment fee (fixed penalty) + // 3. Management fee portion of late interest + periodic.untrackedManagementFee + latePaymentFee + + roundedLateManagementFee, - return ExtendedPaymentComponents{ - inner, - // Untracked management fee includes: - // 1. Regular service fee (from periodic.untrackedManagementFee) - // 2. Late payment fee (fixed penalty) - // 3. Management fee portion of late interest - periodic.untrackedManagementFee + latePaymentFee + - roundedLateManagementFee, - - // Untracked interest includes: - // 1. Any untracked interest from the regular payment (usually 0) - // 2. Late penalty interest (increases loan value) - // This positive value indicates the loan's value increased due - // to the late payment. - periodic.untrackedInterest + roundedLateInterest}; - }(); + // Untracked interest includes: + // 1. Any untracked interest from the regular payment (usually 0) + // 2. Late penalty interest (increases loan value) + // This positive value indicates the loan's value increased due + // to the late payment. + periodic.untrackedInterest + roundedLateInterest}; XRPL_ASSERT_PARTS( isRounded(asset, late.totalDue, loanScale), @@ -875,15 +830,16 @@ computeFullPayment( } // Calculate the theoretical principal based on the payment schedule. - // This raw (unrounded) value is used to compute interest and penalties - // accurately. - Number const rawPrincipalOutstanding = loanPrincipalFromPeriodicPayment( - periodicPayment, periodicRate, paymentRemaining); + // This theoretical (unrounded) value is used to compute interest and + // penalties accurately. + Number const theoreticalPrincipalOutstanding = + loanPrincipalFromPeriodicPayment( + periodicPayment, periodicRate, paymentRemaining); // Full payment interest includes both accrued interest (time since last // payment) and prepayment penalty (for closing early). auto const fullPaymentInterest = computeFullPaymentInterest( - rawPrincipalOutstanding, + theoreticalPrincipalOutstanding, periodicRate, view.parentCloseTime(), paymentInterval, @@ -896,9 +852,8 @@ computeFullPayment( auto const [roundedFullInterest, roundedFullManagementFee] = [&]() { auto const interest = roundToAsset( asset, fullPaymentInterest, loanScale, Number::downward); - auto const parts = computeInterestAndFeeParts( + return computeInterestAndFeeParts( asset, interest, managementFeeRate, loanScale); - return std::make_tuple(parts.first, parts.second); }(); ExtendedPaymentComponents const full{ @@ -943,7 +898,8 @@ computeFullPayment( JLOG(j.trace()) << "computeFullPayment result: periodicPayment: " << periodicPayment << ", periodicRate: " << periodicRate << ", paymentRemaining: " << paymentRemaining - << ", rawPrincipalOutstanding: " << rawPrincipalOutstanding + << ", theoreticalPrincipalOutstanding: " + << theoreticalPrincipalOutstanding << ", fullPaymentInterest: " << fullPaymentInterest << ", roundedFullInterest: " << roundedFullInterest << ", roundedFullManagementFee: " @@ -980,6 +936,8 @@ PaymentComponents::trackedInterestPart() const * * Special handling for the final payment: all remaining balances are paid off * regardless of the periodic payment amount. + * + * Implements the pseudo-code function `compute_payment_due()`. */ PaymentComponents computePaymentComponents( @@ -1023,7 +981,7 @@ computePaymentComponents( // Calculate what the loan state SHOULD be after this payment (the target). // This is computed at full precision using the theoretical amortization. - LoanState const trueTarget = computeRawLoanState( + LoanState const trueTarget = computeTheoreticalLoanState( periodicPayment, periodicRate, paymentRemaining - 1, managementFeeRate); // Round the target to the loan's scale to match how actual loan values @@ -1229,17 +1187,12 @@ computeOverpaymentComponents( // This interest doesn't follow the normal amortization schedule - it's // a one-time charge for paying early. // Equation (20) and (21) from XLS-66 spec, Section A-2 Equation Glossary - auto const [rawOverpaymentInterest, _] = [&]() { - Number const interest = - tenthBipsOfValue(overpayment, overpaymentInterestRate); - return detail::computeInterestAndFeeParts(interest, managementFeeRate); - }(); - - // Round the penalty interest components to the loan scale auto const [roundedOverpaymentInterest, roundedOverpaymentManagementFee] = [&]() { - Number const interest = - roundToAsset(asset, rawOverpaymentInterest, loanScale); + auto const interest = roundToAsset( + asset, + tenthBipsOfValue(overpayment, overpaymentInterestRate), + loanScale); return detail::computeInterestAndFeeParts( asset, interest, managementFeeRate, loanScale); }(); @@ -1256,12 +1209,11 @@ computeOverpaymentComponents( .specialCase = detail::PaymentSpecialCase::extra}, // Untracked management fee is the fixed overpayment fee overpaymentFee, - // Untracked interest is the penalty interest charged for - // overpaying. - // This is positive, representing a one-time cost, but it's - // typically - // much smaller than the interest savings from reducing - // principal. + // Untracked interest is the penalty interest charged for overpaying. + // This is positive, representing a one-time cost, but it's typically + // much smaller than the interest savings from reducing principal. + // It is equal to the paymentComponents.trackedInterestPart() + // but is kept separate for clarity. roundedOverpaymentInterest}; XRPL_ASSERT_PARTS( result.trackedInterestPart() == roundedOverpaymentInterest, @@ -1320,7 +1272,7 @@ checkLoanGuards( beast::Journal j) { auto const totalInterestOutstanding = - properties.totalValueOutstanding - principalRequested; + properties.loanState.valueOutstanding - principalRequested; // Guard 1: if there is no computed total interest over the life of the // loan for a non-zero interest rate, we cannot properly amortize the // loan @@ -1375,13 +1327,13 @@ checkLoanGuards( NumberRoundModeGuard mg(Number::upward); if (std::int64_t const computedPayments{ - properties.totalValueOutstanding / roundedPayment}; + properties.loanState.valueOutstanding / roundedPayment}; computedPayments != paymentTotal) { JLOG(j.warn()) << "Loan Periodic payment (" << properties.periodicPayment << ") rounding (" << roundedPayment << ") on a total value of " - << properties.totalValueOutstanding + << properties.loanState.valueOutstanding << " can not complete the loan in the specified " "number of payments (" << computedPayments << " != " << paymentTotal << ")"; @@ -1399,7 +1351,7 @@ checkLoanGuards( */ Number computeFullPaymentInterest( - Number const& rawPrincipalOutstanding, + Number const& theoreticalPrincipalOutstanding, Number const& periodicRate, NetClock::time_point parentCloseTime, std::uint32_t paymentInterval, @@ -1408,7 +1360,7 @@ computeFullPaymentInterest( TenthBips32 closeInterestRate) { auto const accruedInterest = detail::loanAccruedInterest( - rawPrincipalOutstanding, + theoreticalPrincipalOutstanding, periodicRate, parentCloseTime, startDate, @@ -1422,7 +1374,7 @@ computeFullPaymentInterest( // Equation (28) from XLS-66 spec, Section A-2 Equation Glossary auto const prepaymentPenalty = closeInterestRate == beast::zero ? Number{} - : tenthBipsOfValue(rawPrincipalOutstanding, closeInterestRate); + : tenthBipsOfValue(theoreticalPrincipalOutstanding, closeInterestRate); XRPL_ASSERT( prepaymentPenalty >= 0, @@ -1433,42 +1385,17 @@ computeFullPaymentInterest( return accruedInterest + prepaymentPenalty; } -Number -computeFullPaymentInterest( - Number const& periodicPayment, - Number const& periodicRate, - std::uint32_t paymentRemaining, - NetClock::time_point parentCloseTime, - std::uint32_t paymentInterval, - std::uint32_t prevPaymentDate, - std::uint32_t startDate, - TenthBips32 closeInterestRate) -{ - Number const rawPrincipalOutstanding = - detail::loanPrincipalFromPeriodicPayment( - periodicPayment, periodicRate, paymentRemaining); - - return computeFullPaymentInterest( - rawPrincipalOutstanding, - periodicRate, - parentCloseTime, - paymentInterval, - prevPaymentDate, - startDate, - closeInterestRate); -} - /* Calculates the theoretical loan state at maximum precision for a given point * in the amortization schedule. * * This function computes what the loan's outstanding balances should be based * on the periodic payment amount and number of payments remaining, * without considering any rounding that may have been applied to the actual - * Loan object's state. This "raw" (unrounded) state is used as a target for - * computing payment components and validating that the loan's tracked state + * Loan object's state. This "theoretical" (unrounded) state is used as a target + * for computing payment components and validating that the loan's tracked state * hasn't drifted too far from the theoretical values. * - * The raw state serves several purposes: + * The theoretical state serves several purposes: * 1. Computing the expected payment breakdown (principal, interest, fees) * 2. Detecting and correcting rounding errors that accumulate over time * 3. Validating that overpayments are calculated correctly @@ -1476,9 +1403,12 @@ computeFullPaymentInterest( * * If paymentRemaining is 0, returns a fully zeroed-out LoanState, * representing a completely paid-off loan. + * + * Implements the `calculate_true_loan_state` function from the XLS-66 spec + * section 3.2.4.4 Transaction Pseudo-code */ LoanState -computeRawLoanState( +computeTheoreticalLoanState( Number const& periodicPayment, Number const& periodicRate, std::uint32_t const paymentRemaining, @@ -1494,55 +1424,42 @@ computeRawLoanState( } // Equation (30) from XLS-66 spec, Section A-2 Equation Glossary - Number const rawTotalValueOutstanding = periodicPayment * paymentRemaining; + Number const totalValueOutstanding = periodicPayment * paymentRemaining; - Number const rawPrincipalOutstanding = + Number const principalOutstanding = detail::loanPrincipalFromPeriodicPayment( periodicPayment, periodicRate, paymentRemaining); // Equation (31) from XLS-66 spec, Section A-2 Equation Glossary - Number const rawInterestOutstandingGross = - rawTotalValueOutstanding - rawPrincipalOutstanding; + Number const interestOutstandingGross = + totalValueOutstanding - principalOutstanding; // Equation (32) from XLS-66 spec, Section A-2 Equation Glossary - Number const rawManagementFeeOutstanding = - tenthBipsOfValue(rawInterestOutstandingGross, managementFeeRate); + Number const managementFeeOutstanding = + tenthBipsOfValue(interestOutstandingGross, managementFeeRate); // Equation (33) from XLS-66 spec, Section A-2 Equation Glossary - Number const rawInterestOutstandingNet = - rawInterestOutstandingGross - rawManagementFeeOutstanding; + Number const interestOutstandingNet = + interestOutstandingGross - managementFeeOutstanding; return LoanState{ - .valueOutstanding = rawTotalValueOutstanding, - .principalOutstanding = rawPrincipalOutstanding, - .interestDue = rawInterestOutstandingNet, - .managementFeeDue = rawManagementFeeOutstanding}; + .valueOutstanding = totalValueOutstanding, + .principalOutstanding = principalOutstanding, + .interestDue = interestOutstandingNet, + .managementFeeDue = managementFeeOutstanding, + }; }; -LoanState -computeRawLoanState( - Number const& periodicPayment, - TenthBips32 interestRate, - std::uint32_t paymentInterval, - std::uint32_t const paymentRemaining, - TenthBips32 const managementFeeRate) -{ - return computeRawLoanState( - periodicPayment, - loanPeriodicRate(interestRate, paymentInterval), - paymentRemaining, - managementFeeRate); -} - /* Constructs a LoanState from rounded Loan ledger object values. * * This function creates a LoanState structure from the three tracked values - * stored in a Loan ledger object. Unlike calculateRawLoanState(), which + * stored in a Loan ledger object. Unlike calculateTheoreticalLoanState(), which * computes theoretical unrounded values, this function works with values * that have already been rounded to the loan's scale. * - * The key difference from calculateRawLoanState(): - * - calculateRawLoanState: Computes theoretical values at full precision + * The key difference from calculateTheoreticalLoanState(): + * - calculateTheoreticalLoanState: Computes theoretical values at full + * precision * - constructRoundedLoanState: Builds state from actual rounded ledger values * * The interestDue field is derived from the other three values rather than @@ -1600,11 +1517,16 @@ computeManagementFee( /* * Given the loan parameters, compute the derived properties of the loan. + * + * Pulls together several formulas from the XLS-66 spec, which are noted at each + * step, plus the concepts from 3.2.4.3 Conceptual Loan Value. They are used for + * to check some of the conditions in 3.2.1.5 Failure Conditions for the LoanSet + * transaction. */ LoanProperties computeLoanProperties( Asset const& asset, - Number principalOutstanding, + Number const& principalOutstanding, TenthBips32 interestRate, std::uint32_t paymentInterval, std::uint32_t paymentsRemaining, @@ -1615,12 +1537,39 @@ computeLoanProperties( XRPL_ASSERT( interestRate == 0 || periodicRate > 0, "xrpl::computeLoanProperties : valid rate"); + return computeLoanProperties( + asset, + principalOutstanding, + periodicRate, + paymentsRemaining, + managementFeeRate, + minimumScale); +} +/* + * Given the loan parameters, compute the derived properties of the loan. + * + * Pulls together several formulas from the XLS-66 spec, which are noted at each + * step, plus the concepts from 3.2.4.3 Conceptual Loan Value. They are used for + * to check some of the conditions in 3.2.1.5 Failure Conditions for the LoanSet + * transaction. + */ +LoanProperties +computeLoanProperties( + Asset const& asset, + Number const& principalOutstanding, + Number const& periodicRate, + std::uint32_t paymentsRemaining, + TenthBips32 managementFeeRate, + std::int32_t minimumScale) +{ auto const periodicPayment = detail::loanPeriodicPayment( principalOutstanding, periodicRate, paymentsRemaining); auto const [totalValueOutstanding, loanScale] = [&]() { - NumberRoundModeGuard mg(Number::to_nearest); + // only round up if there should be interest + NumberRoundModeGuard mg( + periodicRate == 0 ? Number::to_nearest : Number::upward); // Use STAmount's internal rounding instead of roundToAsset, because // we're going to use this result to determine the scale for all the // other rounding. @@ -1641,7 +1590,7 @@ computeLoanProperties( // We may need to truncate the total value because of the minimum // scale - amount = roundToAsset(asset, amount, loanScale, Number::to_nearest); + amount = roundToAsset(asset, amount, loanScale); return std::make_pair(amount, loanScale); }(); @@ -1649,12 +1598,12 @@ computeLoanProperties( // Since we just figured out the loan scale, we haven't been able to // validate that the principal fits in it, so to allow this function to // succeed, round it here, and let the caller do the validation. - principalOutstanding = roundToAsset( + auto const roundedPrincipalOutstanding = roundToAsset( asset, principalOutstanding, loanScale, Number::to_nearest); // Equation (31) from XLS-66 spec, Section A-2 Equation Glossary auto const totalInterestOutstanding = - totalValueOutstanding - principalOutstanding; + totalValueOutstanding - roundedPrincipalOutstanding; auto const feeOwedToBroker = computeManagementFee( asset, totalInterestOutstanding, managementFeeRate, loanScale); @@ -1664,13 +1613,13 @@ computeLoanProperties( auto const firstPaymentPrincipal = [&]() { // Compute the parts for the first payment. Ensure that the // principal payment will actually change the principal. - auto const startingState = computeRawLoanState( + auto const startingState = computeTheoreticalLoanState( periodicPayment, periodicRate, paymentsRemaining, managementFeeRate); - auto const firstPaymentState = computeRawLoanState( + auto const firstPaymentState = computeTheoreticalLoanState( periodicPayment, periodicRate, paymentsRemaining - 1, @@ -1684,10 +1633,13 @@ computeLoanProperties( return LoanProperties{ .periodicPayment = periodicPayment, - .totalValueOutstanding = totalValueOutstanding, - .managementFeeOwedToBroker = feeOwedToBroker, + .loanState = constructLoanState( + totalValueOutstanding, + roundedPrincipalOutstanding, + feeOwedToBroker), .loanScale = loanScale, - .firstPaymentPrincipal = firstPaymentPrincipal}; + .firstPaymentPrincipal = firstPaymentPrincipal, + }; } /* @@ -1740,7 +1692,7 @@ loanMakePayment( Number const periodicPayment = loan->at(sfPeriodicPayment); - auto prevPaymentDateProxy = loan->at(sfPreviousPaymentDate); + auto prevPaymentDateProxy = loan->at(sfPreviousPaymentDueDate); std::uint32_t const startDate = loan->at(sfStartDate); std::uint32_t const paymentInterval = loan->at(sfPaymentInterval); @@ -2016,12 +1968,8 @@ loanMakePayment( principalOutstandingProxy, managementFeeOutstandingProxy, periodicPaymentProxy, - interestRate, - paymentInterval, periodicRate, paymentRemainingProxy, - prevPaymentDateProxy, - nextDueDateProxy, managementFeeRate, j)) totalParts += *overResult; diff --git a/src/xrpld/app/paths/Flow.cpp b/src/xrpld/app/paths/Flow.cpp index a102e44854..b5088d15b3 100644 --- a/src/xrpld/app/paths/Flow.cpp +++ b/src/xrpld/app/paths/Flow.cpp @@ -1,11 +1,11 @@ #include -#include #include #include #include #include #include +#include #include #include diff --git a/src/xrpld/app/paths/detail/DirectStep.cpp b/src/xrpld/app/paths/detail/DirectStep.cpp index 4e701d348f..3d3a76f42d 100644 --- a/src/xrpld/app/paths/detail/DirectStep.cpp +++ b/src/xrpld/app/paths/detail/DirectStep.cpp @@ -1,8 +1,8 @@ -#include #include #include #include +#include #include #include #include diff --git a/src/xrpld/app/paths/detail/StrandFlow.h b/src/xrpld/app/paths/detail/StrandFlow.h index fab92dca35..ca4b18f0a3 100644 --- a/src/xrpld/app/paths/detail/StrandFlow.h +++ b/src/xrpld/app/paths/detail/StrandFlow.h @@ -3,7 +3,6 @@ #include #include -#include #include #include #include @@ -11,6 +10,7 @@ #include #include +#include #include #include #include diff --git a/src/xrpld/app/paths/detail/XRPEndpointStep.cpp b/src/xrpld/app/paths/detail/XRPEndpointStep.cpp index 83271321be..ed1866bf24 100644 --- a/src/xrpld/app/paths/detail/XRPEndpointStep.cpp +++ b/src/xrpld/app/paths/detail/XRPEndpointStep.cpp @@ -1,9 +1,9 @@ -#include #include #include #include #include +#include #include #include #include diff --git a/src/xrpld/app/tx/detail/LoanBrokerCoverClawback.cpp b/src/xrpld/app/tx/detail/LoanBrokerCoverClawback.cpp index 32c8fecf20..1cc6b1f5ae 100644 --- a/src/xrpld/app/tx/detail/LoanBrokerCoverClawback.cpp +++ b/src/xrpld/app/tx/detail/LoanBrokerCoverClawback.cpp @@ -270,7 +270,7 @@ LoanBrokerCoverClawback::preclaim(PreclaimContext const& ctx) JLOG(ctx.j.warn()) << "LoanBroker cover is already at minimum."; return findClawAmount.error(); } - STAmount const clawAmount = *findClawAmount; + STAmount const& clawAmount = *findClawAmount; // Explicitly check the balance of the trust line / MPT to make sure the // balance is actually there. It should always match `sfCoverAvailable`, so @@ -287,6 +287,14 @@ LoanBrokerCoverClawback::preclaim(PreclaimContext const& ctx) // Check if the vault asset issuer has the correct flags auto const sleIssuer = ctx.view.read(keylet::account(vaultAsset.getIssuer())); + if (!sleIssuer) + { + // LCOV_EXCL_START + JLOG(ctx.j.fatal()) << "Issuer account does not exist."; + return tefBAD_LEDGER; + // LCOV_EXCL_STOP + } + return std::visit( [&](T const&) { return preclaimHelper(ctx, *sleIssuer, clawAmount); @@ -321,7 +329,7 @@ LoanBrokerCoverClawback::doApply() determineClawAmount(*sleBroker, vaultAsset, amount); if (!findClawAmount) return tecINTERNAL; // LCOV_EXCL_LINE - STAmount const clawAmount = *findClawAmount; + STAmount const& clawAmount = *findClawAmount; // Just for paranoia's sake if (clawAmount.native()) return tecINTERNAL; // LCOV_EXCL_LINE diff --git a/src/xrpld/app/tx/detail/LoanBrokerCoverDeposit.cpp b/src/xrpld/app/tx/detail/LoanBrokerCoverDeposit.cpp index c894df2c2b..b68cf46a00 100644 --- a/src/xrpld/app/tx/detail/LoanBrokerCoverDeposit.cpp +++ b/src/xrpld/app/tx/detail/LoanBrokerCoverDeposit.cpp @@ -81,7 +81,8 @@ LoanBrokerCoverDeposit::preclaim(PreclaimContext const& ctx) vaultAsset, FreezeHandling::fhZERO_IF_FROZEN, AuthHandling::ahZERO_IF_UNAUTHORIZED, - ctx.j) < amount) + ctx.j, + SpendableHandling::shFULL_BALANCE) < amount) return tecINSUFFICIENT_FUNDS; return tesSUCCESS; diff --git a/src/xrpld/app/tx/detail/LoanBrokerCoverWithdraw.cpp b/src/xrpld/app/tx/detail/LoanBrokerCoverWithdraw.cpp index 4c0b3e9af5..830f9e26c1 100644 --- a/src/xrpld/app/tx/detail/LoanBrokerCoverWithdraw.cpp +++ b/src/xrpld/app/tx/detail/LoanBrokerCoverWithdraw.cpp @@ -48,6 +48,11 @@ LoanBrokerCoverWithdraw::preclaim(PreclaimContext const& ctx) auto const dstAcct = tx[~sfDestination].value_or(account); + if (isPseudoAccount(ctx.view, dstAcct)) + { + JLOG(ctx.j.warn()) << "Trying to withdraw into a pseudo-account."; + return tecPSEUDO_ACCOUNT; + } auto const sleBroker = ctx.view.read(keylet::loanbroker(brokerID)); if (!sleBroker) { diff --git a/src/xrpld/app/tx/detail/LoanBrokerDelete.cpp b/src/xrpld/app/tx/detail/LoanBrokerDelete.cpp index 76773037fa..227bad10a9 100644 --- a/src/xrpld/app/tx/detail/LoanBrokerDelete.cpp +++ b/src/xrpld/app/tx/detail/LoanBrokerDelete.cpp @@ -46,30 +46,6 @@ LoanBrokerDelete::preclaim(PreclaimContext const& ctx) JLOG(ctx.j.warn()) << "LoanBrokerDelete: Owner count is " << ownerCount; return tecHAS_OBLIGATIONS; } - if (auto const debtTotal = sleBroker->at(sfDebtTotal); - debtTotal != beast::zero) - { - // Any remaining debt should have been wiped out by the last Loan - // Delete. This check is purely defensive. - auto const vault = - ctx.view.read(keylet::vault(sleBroker->at(sfVaultID))); - if (!vault) - return tefINTERNAL; // LCOV_EXCL_LINE - auto const asset = vault->at(sfAsset); - auto const scale = getVaultScale(vault); - - auto const rounded = - roundToAsset(asset, debtTotal, scale, Number::towards_zero); - - if (rounded != beast::zero) - { - // LCOV_EXCL_START - JLOG(ctx.j.warn()) << "LoanBrokerDelete: Debt total is " - << debtTotal << ", which rounds to " << rounded; - return tecHAS_OBLIGATIONS; - // LCOV_EXCL_START - } - } auto const vault = ctx.view.read(keylet::vault(sleBroker->at(sfVaultID))); if (!vault) @@ -82,6 +58,26 @@ LoanBrokerDelete::preclaim(PreclaimContext const& ctx) Asset const asset = vault->at(sfAsset); + if (auto const debtTotal = sleBroker->at(sfDebtTotal); + debtTotal != beast::zero) + { + // Any remaining debt should have been wiped out by the last Loan + // Delete. This check is purely defensive. + auto const scale = getAssetsTotalScale(vault); + + auto const rounded = + roundToAsset(asset, debtTotal, scale, Number::towards_zero); + + if (rounded != beast::zero) + { + // LCOV_EXCL_START + JLOG(ctx.j.warn()) << "LoanBrokerDelete: Debt total is " + << debtTotal << ", which rounds to " << rounded; + return tecHAS_OBLIGATIONS; + // LCOV_EXCL_STOP + } + } + auto const coverAvailable = STAmount{asset, sleBroker->at(sfCoverAvailable)}; // If there are assets in the cover, broker will receive them on deletion. diff --git a/src/xrpld/app/tx/detail/LoanBrokerSet.cpp b/src/xrpld/app/tx/detail/LoanBrokerSet.cpp index 7b12a6cf39..0cbae4d779 100644 --- a/src/xrpld/app/tx/detail/LoanBrokerSet.cpp +++ b/src/xrpld/app/tx/detail/LoanBrokerSet.cpp @@ -89,6 +89,18 @@ LoanBrokerSet::preclaim(PreclaimContext const& ctx) JLOG(ctx.j.warn()) << "Account is not the owner of the LoanBroker."; return tecNO_PERMISSION; } + + if (auto const debtMax = tx[~sfDebtMaximum]) + { + // Can't reduce the debt maximum below the current total debt + auto const currentDebtTotal = sleBroker->at(sfDebtTotal); + if (*debtMax != 0 && *debtMax < currentDebtTotal) + { + JLOG(ctx.j.warn()) + << "Cannot reduce DebtMaximum below current DebtTotal."; + return tecLIMIT_EXCEEDED; + } + } } else { @@ -105,6 +117,13 @@ LoanBrokerSet::preclaim(PreclaimContext const& ctx) } if (auto const ter = canAddHolding(ctx.view, sleVault->at(sfAsset))) return ter; + + if (auto const ter = checkFrozen( + ctx.view, sleVault->at(sfAccount), sleVault->at(sfAsset))) + { + JLOG(ctx.j.warn()) << "Vault pseudo-account is frozen."; + return ter; + } } return tesSUCCESS; } diff --git a/src/xrpld/app/tx/detail/LoanDelete.cpp b/src/xrpld/app/tx/detail/LoanDelete.cpp index 659f0bba8b..3643e6331b 100644 --- a/src/xrpld/app/tx/detail/LoanDelete.cpp +++ b/src/xrpld/app/tx/detail/LoanDelete.cpp @@ -115,7 +115,7 @@ LoanDelete::doApply() roundToAsset( vaultSle->at(sfAsset), debtTotalProxy, - getVaultScale(vaultSle), + getAssetsTotalScale(vaultSle), Number::towards_zero) == beast::zero, "xrpl::LoanDelete::doApply", "last loan, remaining debt rounds to zero"); diff --git a/src/xrpld/app/tx/detail/LoanManage.cpp b/src/xrpld/app/tx/detail/LoanManage.cpp index 2d405f204b..bd0539ae4e 100644 --- a/src/xrpld/app/tx/detail/LoanManage.cpp +++ b/src/xrpld/app/tx/detail/LoanManage.cpp @@ -106,7 +106,7 @@ LoanManage::preclaim(PreclaimContext const& ctx) if (loanBrokerSle->at(sfOwner) != account) { JLOG(ctx.j.warn()) - << "LoanBroker for Loan does not belong to the account. LoanModify " + << "LoanBroker for Loan does not belong to the account. LoanManage " "can only be submitted by the Loan Broker."; return tecNO_PERMISSION; } @@ -158,7 +158,7 @@ LoanManage::defaultLoan( auto const minimumCover = tenthBipsOfValue(brokerDebtTotalProxy.value(), coverRateMinimum); // Round the liquidation amount up, too - return roundToAsset( + auto const covered = roundToAsset( vaultAsset, /* * This formula is from the XLS-66 spec, section 3.2.3.2 (State @@ -169,6 +169,9 @@ LoanManage::defaultLoan( tenthBipsOfValue(minimumCover, coverRateLiquidation), totalDefaultAmount), loanScale); + auto const coverAvailable = *brokerSle->at(sfCoverAvailable); + + return std::min(covered, coverAvailable); }(); auto const vaultDefaultAmount = totalDefaultAmount - defaultCovered; @@ -178,7 +181,7 @@ LoanManage::defaultLoan( // The vault may be at a different scale than the loan. Reduce rounding // errors during the accounting by rounding some of the values to that // scale. - auto const vaultScale = getVaultScale(vaultSle); + auto const vaultScale = getAssetsTotalScale(vaultSle); { // Decrease the Total Value of the Vault: @@ -223,11 +226,13 @@ LoanManage::defaultLoan( } if (*vaultAvailableProxy > *vaultTotalProxy) { - JLOG(j.warn()) << "Vault assets available must not be greater " - "than assets outstanding. Available: " - << *vaultAvailableProxy - << ", Total: " << *vaultTotalProxy; - return tecLIMIT_EXCEEDED; + // LCOV_EXCL_START + JLOG(j.fatal()) + << "Vault assets available must not be greater " + "than assets outstanding. Available: " + << *vaultAvailableProxy << ", Total: " << *vaultTotalProxy; + return tecINTERNAL; + // LCOV_EXCL_STOP } // The loss has been realized @@ -242,7 +247,11 @@ LoanManage::defaultLoan( return tefBAD_LEDGER; // LCOV_EXCL_STOP } - vaultLossUnrealizedProxy -= totalDefaultAmount; + adjustImpreciseNumber( + vaultLossUnrealizedProxy, + -totalDefaultAmount, + vaultAsset, + vaultScale); } view.update(vaultSle); } @@ -250,11 +259,9 @@ LoanManage::defaultLoan( // Update the LoanBroker object: { - auto const asset = *vaultSle->at(sfAsset); - // Decrease the Debt of the LoanBroker: adjustImpreciseNumber( - brokerDebtTotalProxy, -totalDefaultAmount, asset, vaultScale); + brokerDebtTotalProxy, -totalDefaultAmount, vaultAsset, vaultScale); // Decrease the First-Loss Capital Cover Available: auto coverAvailableProxy = brokerSle->at(sfCoverAvailable); if (coverAvailableProxy < defaultCovered) @@ -297,13 +304,20 @@ LoanManage::impairLoan( ApplyView& view, SLE::ref loanSle, SLE::ref vaultSle, + Asset const& vaultAsset, beast::Journal j) { Number const lossUnrealized = owedToVault(loanSle); + // The vault may be at a different scale than the loan. Reduce rounding + // errors during the accounting by rounding some of the values to that + // scale. + auto const vaultScale = getAssetsTotalScale(vaultSle); + // Update the Vault object(set "paper loss") auto vaultLossUnrealizedProxy = vaultSle->at(sfLossUnrealized); - vaultLossUnrealizedProxy += lossUnrealized; + adjustImpreciseNumber( + vaultLossUnrealizedProxy, lossUnrealized, vaultAsset, vaultScale); if (vaultLossUnrealizedProxy > vaultSle->at(sfAssetsTotal) - vaultSle->at(sfAssetsAvailable)) { @@ -329,13 +343,19 @@ LoanManage::impairLoan( return tesSUCCESS; } -TER +[[nodiscard]] TER LoanManage::unimpairLoan( ApplyView& view, SLE::ref loanSle, SLE::ref vaultSle, + Asset const& vaultAsset, beast::Journal j) { + // The vault may be at a different scale than the loan. Reduce rounding + // errors during the accounting by rounding some of the values to that + // scale. + auto const vaultScale = getAssetsTotalScale(vaultSle); + // Update the Vault object(clear "paper loss") auto vaultLossUnrealizedProxy = vaultSle->at(sfLossUnrealized); Number const lossReversed = owedToVault(loanSle); @@ -347,14 +367,18 @@ LoanManage::unimpairLoan( return tefBAD_LEDGER; // LCOV_EXCL_STOP } - vaultLossUnrealizedProxy -= lossReversed; + // Reverse the "paper loss" + adjustImpreciseNumber( + vaultLossUnrealizedProxy, -lossReversed, vaultAsset, vaultScale); + view.update(vaultSle); // Update the Loan object loanSle->clearFlag(lsfLoanImpaired); auto const paymentInterval = loanSle->at(sfPaymentInterval); auto const normalPaymentDueDate = - std::max(loanSle->at(sfPreviousPaymentDate), loanSle->at(sfStartDate)) + + std::max( + loanSle->at(sfPreviousPaymentDueDate), loanSle->at(sfStartDate)) + paymentInterval; if (!hasExpired(view, normalPaymentDueDate)) { @@ -396,22 +420,12 @@ LoanManage::doApply() // Valid flag combinations are checked in preflight. No flags is valid - // just a noop. if (tx.isFlag(tfLoanDefault)) - { - if (auto const ter = - defaultLoan(view, loanSle, brokerSle, vaultSle, vaultAsset, j_)) - return ter; - } - else if (tx.isFlag(tfLoanImpair)) - { - if (auto const ter = impairLoan(view, loanSle, vaultSle, j_)) - return ter; - } - else if (tx.isFlag(tfLoanUnimpair)) - { - if (auto const ter = unimpairLoan(view, loanSle, vaultSle, j_)) - return ter; - } - + 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; } diff --git a/src/xrpld/app/tx/detail/LoanManage.h b/src/xrpld/app/tx/detail/LoanManage.h index 7a02c7a16f..155611580f 100644 --- a/src/xrpld/app/tx/detail/LoanManage.h +++ b/src/xrpld/app/tx/detail/LoanManage.h @@ -44,15 +44,17 @@ public: ApplyView& view, SLE::ref loanSle, SLE::ref vaultSle, + Asset const& vaultAsset, beast::Journal j); /** Helper function that might be needed by other transactors */ - static TER + [[nodiscard]] static TER unimpairLoan( ApplyView& view, SLE::ref loanSle, SLE::ref vaultSle, + Asset const& vaultAsset, beast::Journal j); TER diff --git a/src/xrpld/app/tx/detail/LoanPay.cpp b/src/xrpld/app/tx/detail/LoanPay.cpp index d34a766d70..f3973d9488 100644 --- a/src/xrpld/app/tx/detail/LoanPay.cpp +++ b/src/xrpld/app/tx/detail/LoanPay.cpp @@ -152,9 +152,7 @@ LoanPay::preclaim(PreclaimContext const& ctx) } auto const principalOutstanding = loanSle->at(sfPrincipalOutstanding); - TenthBips32 const interestRate{loanSle->at(sfInterestRate)}; auto const paymentRemaining = loanSle->at(sfPaymentRemaining); - TenthBips32 const lateInterestRate{loanSle->at(sfLateInterestRate)}; if (paymentRemaining == 0 || principalOutstanding == 0) { @@ -211,13 +209,14 @@ LoanPay::preclaim(PreclaimContext const& ctx) // Do not support "partial payments" - if the transaction says to pay X, // then the account must have X available, even if the loan payment takes // less. - if (auto const balance = accountSpendable( + if (auto const balance = accountHolds( ctx.view, account, asset, fhZERO_IF_FROZEN, ahZERO_IF_UNAUTHORIZED, - ctx.j); + ctx.j, + SpendableHandling::shFULL_BALANCE); balance < amount) { JLOG(ctx.j.warn()) << "Payment amount too large. Amount: " @@ -262,11 +261,12 @@ LoanPay::doApply() auto debtTotalProxy = brokerSle->at(sfDebtTotal); // Send the broker fee to the owner if they have sufficient cover available, - // _and_ if the owner can receive funds. If not, so as not to block the - // payment, add it to the cover balance (send it to the broker pseudo - // account). + // _and_ if the owner can receive funds + // _and_ if the broker is authorized to hold funds. If not, so as not to + // block the payment, add it to the cover balance (send it to the broker + // pseudo account). // - // Normally freeze status is checked in preflight, but we do it here to + // Normally freeze status is checked in preclaim, but we do it here to // avoid duplicating the check. It'll claim a fee either way. bool const sendBrokerFeeToOwner = [&]() { // Round the minimum required cover up to be conservative. This ensures @@ -278,7 +278,8 @@ LoanPay::doApply() asset, tenthBipsOfValue(debtTotalProxy.value(), coverRateMinimum), loanScale) && - !isDeepFrozen(view, brokerOwner, asset); + !isDeepFrozen(view, brokerOwner, asset) && + !requireAuth(view, asset, brokerOwner, AuthType::StrongAuth); }(); auto const brokerPayee = @@ -305,7 +306,12 @@ LoanPay::doApply() // change will be discarded. if (loanSle->isFlag(lsfLoanImpaired)) { - LoanManage::unimpairLoan(view, loanSle, vaultSle, j_); + if (auto const ret = + LoanManage::unimpairLoan(view, loanSle, vaultSle, asset, j_)) + { + JLOG(j_.fatal()) << "Failed to unimpair loan before payment."; + return ret; // LCOV_EXCL_LINE + } } LoanPaymentType const paymentType = [&tx]() { @@ -377,7 +383,7 @@ LoanPay::doApply() // The vault may be at a different scale than the loan. Reduce rounding // errors during the payment by rounding some of the values to that scale. - auto const vaultScale = assetsTotalProxy.value().exponent(); + auto const vaultScale = getAssetsTotalScale(vaultSle); auto const totalPaidToVaultRaw = paymentParts->principalPaid + paymentParts->interestPaid; @@ -421,35 +427,41 @@ LoanPay::doApply() // Vault object state changes view.update(vaultSle); - Number const assetsAvailableBefore = *assetsAvailableProxy; - Number const pseudoAccountBalanceBefore = accountHolds( - view, - vaultPseudoAccount, - asset, - FreezeHandling::fhIGNORE_FREEZE, - AuthHandling::ahIGNORE_AUTH, - j_); - +#if !NDEBUG { + Number const assetsAvailableBefore = *assetsAvailableProxy; + Number const pseudoAccountBalanceBefore = accountHolds( + view, + vaultPseudoAccount, + asset, + FreezeHandling::fhIGNORE_FREEZE, + AuthHandling::ahIGNORE_AUTH, + j_); + XRPL_ASSERT_PARTS( assetsAvailableBefore == pseudoAccountBalanceBefore, "xrpl::LoanPay::doApply", "vault pseudo balance agrees before"); + } +#endif - assetsAvailableProxy += totalPaidToVaultRounded; - assetsTotalProxy += paymentParts->valueChange; + assetsAvailableProxy += totalPaidToVaultRounded; + assetsTotalProxy += paymentParts->valueChange; - XRPL_ASSERT_PARTS( - *assetsAvailableProxy <= *assetsTotalProxy, - "xrpl::LoanPay::doApply", - "assets available must not be greater than assets outstanding"); + XRPL_ASSERT_PARTS( + *assetsAvailableProxy <= *assetsTotalProxy, + "xrpl::LoanPay::doApply", + "assets available must not be greater than assets outstanding"); - if (*assetsAvailableProxy > *assetsTotalProxy) - { - // LCOV_EXCL_START - return tecINTERNAL; - // LCOV_EXCL_STOP - } + if (*assetsAvailableProxy > *assetsTotalProxy) + { + // LCOV_EXCL_START + JLOG(j_.fatal()) << "Vault assets available must not be greater " + "than assets outstanding. Available: " + << *assetsAvailableProxy + << ", Total: " << *assetsTotalProxy; + return tecINTERNAL; + // LCOV_EXCL_STOP } JLOG(j_.debug()) << "total paid to vault raw: " << totalPaidToVaultRaw @@ -474,21 +486,34 @@ LoanPay::doApply() } #if !NDEBUG - auto const accountBalanceBefore = accountSpendable( - view, account_, asset, fhIGNORE_FREEZE, ahIGNORE_AUTH, j_); + auto const accountBalanceBefore = accountHolds( + view, + account_, + asset, + fhIGNORE_FREEZE, + ahIGNORE_AUTH, + j_, + SpendableHandling::shFULL_BALANCE); auto const vaultBalanceBefore = account_ == vaultPseudoAccount ? STAmount{asset, 0} - : accountSpendable( + : accountHolds( view, vaultPseudoAccount, asset, fhIGNORE_FREEZE, ahIGNORE_AUTH, - j_); + j_, + SpendableHandling::shFULL_BALANCE); auto const brokerBalanceBefore = account_ == brokerPayee ? STAmount{asset, 0} - : accountSpendable( - view, brokerPayee, asset, fhIGNORE_FREEZE, ahIGNORE_AUTH, j_); + : accountHolds( + view, + brokerPayee, + asset, + fhIGNORE_FREEZE, + ahIGNORE_AUTH, + j_, + SpendableHandling::shFULL_BALANCE); #endif if (totalPaidToVaultRounded != beast::zero) @@ -529,6 +554,7 @@ LoanPay::doApply() WaiveTransferFee::Yes)) return ter; +#if !NDEBUG Number const assetsAvailableAfter = *assetsAvailableProxy; Number const pseudoAccountBalanceAfter = accountHolds( view, @@ -542,22 +568,34 @@ LoanPay::doApply() "xrpl::LoanPay::doApply", "vault pseudo balance agrees after"); -#if !NDEBUG - auto const accountBalanceAfter = accountSpendable( - view, account_, asset, fhIGNORE_FREEZE, ahIGNORE_AUTH, j_); + auto const accountBalanceAfter = accountHolds( + view, + account_, + asset, + fhIGNORE_FREEZE, + ahIGNORE_AUTH, + j_, + SpendableHandling::shFULL_BALANCE); auto const vaultBalanceAfter = account_ == vaultPseudoAccount ? STAmount{asset, 0} - : accountSpendable( + : accountHolds( view, vaultPseudoAccount, asset, fhIGNORE_FREEZE, ahIGNORE_AUTH, - j_); + j_, + SpendableHandling::shFULL_BALANCE); auto const brokerBalanceAfter = account_ == brokerPayee ? STAmount{asset, 0} - : accountSpendable( - view, brokerPayee, asset, fhIGNORE_FREEZE, ahIGNORE_AUTH, j_); + : accountHolds( + view, + brokerPayee, + asset, + fhIGNORE_FREEZE, + ahIGNORE_AUTH, + j_, + SpendableHandling::shFULL_BALANCE); XRPL_ASSERT_PARTS( accountBalanceBefore + vaultBalanceBefore + brokerBalanceBefore == diff --git a/src/xrpld/app/tx/detail/LoanSet.cpp b/src/xrpld/app/tx/detail/LoanSet.cpp index e8fe76a48f..4c14cde421 100644 --- a/src/xrpld/app/tx/detail/LoanSet.cpp +++ b/src/xrpld/app/tx/detail/LoanSet.cpp @@ -88,10 +88,12 @@ LoanSet::preflight(PreflightContext const& ctx) if (auto const paymentInterval = tx[~sfPaymentInterval]; !validNumericMinimum(paymentInterval, LoanSet::minPaymentInterval)) return temINVALID; - - else if (!validNumericRange( - tx[~sfGracePeriod], - paymentInterval.value_or(LoanSet::defaultPaymentInterval))) + // Grace period is between min default value and payment interval + else if (auto const gracePeriod = tx[~sfGracePeriod]; // + !validNumericRange( + gracePeriod, + paymentInterval.value_or(LoanSet::defaultPaymentInterval), + defaultGracePeriod)) return temINVALID; // Copied from preflight2 @@ -282,6 +284,15 @@ LoanSet::preclaim(PreclaimContext const& ctx) if (!vault) // Should be impossible return tefBAD_LEDGER; // LCOV_EXCL_LINE + + if (vault->at(sfAssetsMaximum) != 0 && + vault->at(sfAssetsTotal) >= vault->at(sfAssetsMaximum)) + { + JLOG(ctx.j.warn()) + << "Vault at maximum assets limit. Can't add another loan."; + return tecLIMIT_EXCEEDED; + } + Asset const asset = vault->at(sfAsset); auto const vaultPseudo = vault->at(sfAccount); @@ -383,7 +394,7 @@ LoanSet::doApply() auto vaultAvailableProxy = vaultSle->at(sfAssetsAvailable); auto vaultTotalProxy = vaultSle->at(sfAssetsTotal); - auto const vaultScale = getVaultScale(vaultSle); + auto const vaultScale = getAssetsTotalScale(vaultSle); if (vaultAvailableProxy < principalRequested) { JLOG(j_.warn()) @@ -406,6 +417,21 @@ LoanSet::doApply() TenthBips16{brokerSle->at(sfManagementFeeRate)}, vaultScale); + LoanState const state = constructLoanState( + properties.loanState.valueOutstanding, + principalRequested, + properties.loanState.managementFeeDue); + + auto const vaultMaximum = *vaultSle->at(sfAssetsMaximum); + XRPL_ASSERT_PARTS( + vaultMaximum == 0 || vaultMaximum > *vaultTotalProxy, + "xrpl::LoanSet::doApply", + "Vault is below maximum limit"); + if (vaultMaximum != 0 && state.interestDue > vaultMaximum - vaultTotalProxy) + { + JLOG(j_.warn()) << "Loan would exceed the maximum assets of the vault"; + return tecLIMIT_EXCEEDED; + } // Check that relevant values won't lose precision. This is mostly only // relevant for IOU assets. { @@ -417,8 +443,8 @@ LoanSet::doApply() JLOG(j_.warn()) << field.f->getName() << " (" << *value << ") has too much precision. Total loan value is " - << properties.totalValueOutstanding << " with a scale of " - << properties.loanScale; + << properties.loanState.valueOutstanding + << " with a scale of " << properties.loanScale; return tecPRECISION_LOSS; } } @@ -434,22 +460,20 @@ LoanSet::doApply() return ret; // Check that the other computed values are valid - if (properties.managementFeeOwedToBroker < 0 || - properties.totalValueOutstanding <= 0 || + if (properties.loanState.managementFeeDue < 0 || + properties.loanState.valueOutstanding <= 0 || properties.periodicPayment <= 0) { // LCOV_EXCL_START JLOG(j_.warn()) - << "Computed loan properties are invalid. Does not compute."; + << "Computed loan properties are invalid. Does not compute." + << " Management fee: " << properties.loanState.managementFeeDue + << ". Total Value: " << properties.loanState.valueOutstanding + << ". PeriodicPayment: " << properties.periodicPayment; return tecINTERNAL; // LCOV_EXCL_STOP } - LoanState const state = constructLoanState( - properties.totalValueOutstanding, - principalRequested, - properties.managementFeeOwedToBroker); - auto const originationFee = tx[~sfLoanOriginationFee].value_or(Number{}); auto const loanAssetsToBorrower = principalRequested - originationFee; @@ -534,12 +558,12 @@ LoanSet::doApply() // ignore tecDUPLICATE. That means the holding already exists, // and is fine here return ter; - - if (auto const ter = requireAuth( - view, vaultAsset, brokerOwner, AuthType::StrongAuth)) - return ter; } + if (auto const ter = + requireAuth(view, vaultAsset, brokerOwner, AuthType::StrongAuth)) + return ter; + if (auto const ter = accountSendMulti( view, vaultPseudo, @@ -588,9 +612,10 @@ LoanSet::doApply() // Set dynamic / computed fields to their initial values loan->at(sfPrincipalOutstanding) = principalRequested; loan->at(sfPeriodicPayment) = properties.periodicPayment; - loan->at(sfTotalValueOutstanding) = properties.totalValueOutstanding; - loan->at(sfManagementFeeOutstanding) = properties.managementFeeOwedToBroker; - loan->at(sfPreviousPaymentDate) = 0; + loan->at(sfTotalValueOutstanding) = properties.loanState.valueOutstanding; + loan->at(sfManagementFeeOutstanding) = + properties.loanState.managementFeeDue; + loan->at(sfPreviousPaymentDueDate) = 0; loan->at(sfNextPaymentDueDate) = startDate + paymentInterval; loan->at(sfPaymentRemaining) = paymentTotal; view.insert(loan); diff --git a/src/xrpld/app/tx/detail/VaultDeposit.cpp b/src/xrpld/app/tx/detail/VaultDeposit.cpp index d9471a38f5..51b38afc36 100644 --- a/src/xrpld/app/tx/detail/VaultDeposit.cpp +++ b/src/xrpld/app/tx/detail/VaultDeposit.cpp @@ -115,16 +115,14 @@ VaultDeposit::preclaim(PreclaimContext const& ctx) !isTesSuccess(ter)) return ter; - // Asset issuer does not have any balance, they can just create funds by - // depositing in the vault. - if ((vaultAsset.native() || vaultAsset.getIssuer() != account) && - accountHolds( + if (accountHolds( ctx.view, account, vaultAsset, FreezeHandling::fhZERO_IF_FROZEN, AuthHandling::ahZERO_IF_UNAUTHORIZED, - ctx.j) < assets) + ctx.j, + SpendableHandling::shFULL_BALANCE) < assets) return tecINSUFFICIENT_FUNDS; return tesSUCCESS; diff --git a/src/xrpld/rpc/handlers/LedgerEntry.cpp b/src/xrpld/rpc/handlers/LedgerEntry.cpp index 2e9d5b35bf..d9ae357b1a 100644 --- a/src/xrpld/rpc/handlers/LedgerEntry.cpp +++ b/src/xrpld/rpc/handlers/LedgerEntry.cpp @@ -425,7 +425,7 @@ parseLoan(Json::Value const& params, Json::StaticString const fieldName) } auto const id = LedgerEntryHelpers::requiredUInt256( - params, jss::loan_broker_id, "malformedLoanBrokerID"); + params, jss::loan_broker_id, "malformedBroker"); if (!id) return Unexpected(id.error()); auto const seq = LedgerEntryHelpers::requiredUInt32( From 33f4c92b6122ef9072b189ff8e49d161229dd7c0 Mon Sep 17 00:00:00 2001 From: Ed Hennis Date: Tue, 13 Jan 2026 17:01:11 -0400 Subject: [PATCH 23/30] Expand Number to support the full integer range (#6025) - Refactor Number internals away from int64 to uint64 & a sign flag - ctors and accessors use `rep`. Very few things expose `internalrep`. - An exception is "unchecked" and the new "normalized", which explicitly take an internalrep. But with those special control flags, it's easier to distinguish and control when they are used. - For now, skip the larger mantissas in AMM transactions and tests - Remove trailing zeros from scientific notation Number strings - Update tests. This has the happy side effect of making some of the string representations _more_ consistent between the small and large mantissa ranges. - Add semi-automatic rounding of STNumbers based on Asset types - Create a new SField metadata enum, sMD_NeedsAsset, which indicates the field should be associated with an Asset so it can be rounded. - Add a new STTakesAsset intermediate class to handle the Asset association to a derived ST class. Currently only used in STNumber, but could be used by other types in the future. - Add "associateAsset" which takes an SLE and an Asset, finds the sMD_NeedsAsset fields, and associates the Asset to them. In the case of STNumber, that both stores the Asset, and rounds the value immediately. - Transactors only need to add a call to associateAsset _after_ all of the STNumbers have been set. Unfortunately, the inner workings of STObject do not do the association correctly with uninitialized fields. - When serializing an STNumber that has an Asset, round it before serializing. - Add an override of roundToAsset, which rounds a Number value in place to an Asset, but without any additional scale. - Update and fix a bunch of Loan-related tests to accommodate the expanded Number class. --------- Co-authored-by: Vito <5780819+Tapanito@users.noreply.github.com> --- include/xrpl/basics/Number.h | 529 ++++++- include/xrpl/protocol/AmountConversions.h | 2 +- include/xrpl/protocol/IOUAmount.h | 21 +- include/xrpl/protocol/Issue.h | 3 + include/xrpl/protocol/MPTIssue.h | 6 + include/xrpl/protocol/Protocol.h | 1 + include/xrpl/protocol/SField.h | 5 +- include/xrpl/protocol/STAmount.h | 71 +- include/xrpl/protocol/STNumber.h | 17 +- include/xrpl/protocol/STTakesAsset.h | 63 + include/xrpl/protocol/SystemParameters.h | 2 + include/xrpl/protocol/detail/sfields.macro | 20 +- src/libxrpl/basics/Number.cpp | 677 ++++++-- src/libxrpl/protocol/IOUAmount.cpp | 32 +- src/libxrpl/protocol/Issue.cpp | 6 + src/libxrpl/protocol/Rules.cpp | 14 +- src/libxrpl/protocol/STAmount.cpp | 39 +- src/libxrpl/protocol/STNumber.cpp | 94 +- src/libxrpl/protocol/STTakesAsset.cpp | 29 + src/test/app/AMMClawback_test.cpp | 5 +- src/test/app/AMMExtended_test.cpp | 42 +- src/test/app/AMM_test.cpp | 53 +- src/test/app/EscrowToken_test.cpp | 20 +- src/test/app/LendingHelpers_test.cpp | 15 +- src/test/app/LoanBroker_test.cpp | 89 +- src/test/app/Loan_test.cpp | 91 +- src/test/app/MPToken_test.cpp | 4 +- src/test/basics/IOUAmount_test.cpp | 31 +- src/test/basics/Number_test.cpp | 1402 ++++++++++++++--- src/test/jtx/AMMTest.h | 16 +- src/test/jtx/amount.h | 16 + src/test/jtx/impl/AMMTest.cpp | 7 +- src/test/protocol/STNumber_test.cpp | 59 +- src/test/rpc/GetAggregatePrice_test.cpp | 68 +- .../app/tx/detail/LoanBrokerCoverClawback.cpp | 4 + .../app/tx/detail/LoanBrokerCoverDeposit.cpp | 10 + .../app/tx/detail/LoanBrokerCoverWithdraw.cpp | 9 + src/xrpld/app/tx/detail/LoanBrokerDelete.cpp | 4 + src/xrpld/app/tx/detail/LoanBrokerSet.cpp | 66 +- src/xrpld/app/tx/detail/LoanBrokerSet.h | 3 + src/xrpld/app/tx/detail/LoanDelete.cpp | 10 +- src/xrpld/app/tx/detail/LoanManage.cpp | 8 +- src/xrpld/app/tx/detail/LoanPay.cpp | 11 + src/xrpld/app/tx/detail/LoanSet.cpp | 40 +- src/xrpld/app/tx/detail/Transactor.cpp | 6 +- src/xrpld/app/tx/detail/VaultClawback.cpp | 3 + src/xrpld/app/tx/detail/VaultCreate.cpp | 3 + src/xrpld/app/tx/detail/VaultDelete.cpp | 4 + src/xrpld/app/tx/detail/VaultDeposit.cpp | 4 + src/xrpld/app/tx/detail/VaultSet.cpp | 5 + src/xrpld/app/tx/detail/VaultWithdraw.cpp | 4 + src/xrpld/app/tx/detail/applySteps.cpp | 118 +- 52 files changed, 3151 insertions(+), 710 deletions(-) create mode 100644 include/xrpl/protocol/STTakesAsset.h create mode 100644 src/libxrpl/protocol/STTakesAsset.cpp diff --git a/include/xrpl/basics/Number.h b/include/xrpl/basics/Number.h index 4420530239..d1ef749784 100644 --- a/include/xrpl/basics/Number.h +++ b/include/xrpl/basics/Number.h @@ -1,8 +1,11 @@ #ifndef XRPL_BASICS_NUMBER_H_INCLUDED #define XRPL_BASICS_NUMBER_H_INCLUDED +#include + #include #include +#include #include #include @@ -13,42 +16,252 @@ class Number; std::string to_string(Number const& amount); +template +constexpr std::optional +logTen(T value) +{ + int log = 0; + while (value >= 10 && value % 10 == 0) + { + value /= 10; + ++log; + } + if (value == 1) + return log; + return std::nullopt; +} + template constexpr bool isPowerOfTen(T value) { - while (value >= 10 && value % 10 == 0) - value /= 10; - return value == 1; + return logTen(value).has_value(); } +/** MantissaRange defines a range for the mantissa of a normalized Number. + * + * The mantissa is in the range [min, max], where + * * min is a power of 10, and + * * max = min * 10 - 1. + * + * The mantissa_scale enum indicates whether the range is "small" or "large". + * This intentionally restricts the number of MantissaRanges that can be + * instantiated to two: one for each scale. + * + * The "small" scale is based on the behavior of STAmount for IOUs. It has a min + * value of 10^15, and a max value of 10^16-1. This was sufficient for + * uses before Lending Protocol was implemented, mostly related to AMM. + * + * However, it does not have sufficient precision to represent the full integer + * range of int64_t values (-2^63 to 2^63-1), which are needed for XRP and MPT + * values. The implementation of SingleAssetVault, and LendingProtocol need to + * represent those integer values accurately and precisely, both for the + * STNumber field type, and for internal calculations. That necessitated the + * "large" scale. + * + * The "large" scale is intended to represent all values that can be represented + * by an STAmount - IOUs, XRP, and MPTs. It has a min value of 10^18, and a max + * value of 10^19-1. + * + * Note that if the mentioned amendments are eventually retired, this class + * should be left in place, but the "small" scale option should be removed. This + * will allow for future expansion beyond 64-bits if it is ever needed. + */ +struct MantissaRange +{ + using rep = std::uint64_t; + enum mantissa_scale { small, large }; + + explicit constexpr MantissaRange(mantissa_scale scale_) + : min(getMin(scale_)) + , max(min * 10 - 1) + , log(logTen(min).value_or(-1)) + , scale(scale_) + { + } + + rep min; + rep max; + int log; + mantissa_scale scale; + +private: + static constexpr rep + getMin(mantissa_scale scale_) + { + switch (scale_) + { + case small: + return 1'000'000'000'000'000ULL; + case large: + return 1'000'000'000'000'000'000ULL; + default: + // Since this can never be called outside a non-constexpr + // context, this throw assures that the build fails if an + // invalid scale is used. + throw std::runtime_error("Unknown mantissa scale"); + } + } +}; + +// Like std::integral, but only 64-bit integral types. +template +concept Integral64 = + std::is_same_v || std::is_same_v; + +/** Number is a floating point type that can represent a wide range of values. + * + * It can represent all values that can be represented by an STAmount - + * regardless of asset type - XRPAmount, MPTAmount, and IOUAmount, with at least + * as much precision as those types require. + * + * ---- Internal Representation ---- + * + * Internally, Number is represented with three values: + * 1. a bool sign flag, + * 2. a std::uint64_t mantissa, + * 3. an int exponent. + * + * The internal mantissa is an unsigned integer in the range defined by the + * current MantissaRange. The exponent is an integer in the range + * [minExponent, maxExponent]. + * + * See the description of MantissaRange for more details on the ranges. + * + * A non-zero mantissa is (almost) always normalized, meaning it and the + * exponent are grown or shrunk until the mantissa is in the range + * [MantissaRange.min, MantissaRange.max]. + * + * Note: + * 1. Normalization can be disabled by using the "unchecked" ctor tag. This + * should only be used at specific conversion points, some constexpr + * values, and in unit tests. + * 2. The max of the "large" range, 10^19-1, is the largest 10^X-1 value that + * fits in an unsigned 64-bit number. (10^19-1 < 2^64-1 and + * 10^20-1 > 2^64-1). This avoids under- and overflows. + * + * ---- External Interface ---- + * + * The external interface of Number consists of a std::int64_t mantissa, which + * is restricted to 63-bits, and an int exponent, which must be in the range + * [minExponent, maxExponent]. The range of the mantissa depends on which + * MantissaRange is currently active. For the "short" range, the mantissa will + * be between 10^15 and 10^16-1. For the "large" range, the mantissa will be + * between -(2^63-1) and 2^63-1. As noted above, the "large" range is needed to + * represent the full range of valid XRP and MPT integer values accurately. + * + * Note: + * 1. 2^63-1 is between 10^18 and 10^19-1, which are the limits of the "large" + * mantissa range. + * 2. The functions mantissa() and exponent() return the external view of the + * Number value, specifically using a signed 63-bit mantissa. This may + * require altering the internal representation to fit into that range + * before the value is returned. The interface guarantees consistency of + * the two values. + * 3. Number cannot represent -2^63 (std::numeric_limits::min()) + * as an exact integer, but it doesn't need to, because all asset values + * on-ledger are non-negative. This is due to implementation details of + * several operations which use unsigned arithmetic internally. This is + * sufficient to represent all valid XRP values (where the absolute value + * can not exceed INITIAL_XRP: 10^17), and MPT values (where the absolute + * value can not exceed maxMPTokenAmount: 2^63-1). + * + * ---- Mantissa Range Switching ---- + * + * The mantissa range may be changed at runtime via setMantissaScale(). The + * default mantissa range is "large". The range is updated whenever transaction + * processing begins, based on whether SingleAssetVault or LendingProtocol are + * enabled. If either is enabled, the mantissa range is set to "large". If not, + * it is set to "small", preserving backward compatibility and correct + * "amendment-gating". + * + * It is extremely unlikely that any more calls to setMantissaScale() will be + * needed outside of unit tests. + * + * ---- Usage With Different Ranges ---- + * + * Outside of unit tests, and existing checks, code that uses Number should not + * know or care which mantissa range is active. + * + * The results of computations using Numbers with a small mantissa may differ + * from computations using Numbers with a large mantissa, specifically as it + * effects the results after rounding. That is why the large mantissa range is + * amendment gated in transaction processing. + * + * It is extremely unlikely that any more calls to getMantissaScale() will be + * needed outside of unit tests. + * + * Code that uses Number should not assume or check anything about the + * mantissa() or exponent() except that they fit into the "large" range + * specified in the "External Interface" section. + * + * ----- Unit Tests ----- + * + * Within unit tests, it may be useful to explicitly switch between the two + * ranges, or to check which range is active when checking the results of + * computations. If the test is doing the math directly, the + * set/getMantissaScale() functions may be most appropriate. However, if the + * test has anything to do with transaction processing, it should enable or + * disable the amendments that control the mantissa range choice + * (SingleAssetVault and LendingProtocol), and/or check if either of those + * amendments are enabled to determine which result to expect. + * + */ class Number { using rep = std::int64_t; - rep mantissa_{0}; + using internalrep = MantissaRange::rep; + + bool negative_{false}; + internalrep mantissa_{0}; int exponent_{std::numeric_limits::lowest()}; public: - // The range for the mantissa when normalized - constexpr static std::int64_t minMantissa = 1'000'000'000'000'000LL; - static_assert(isPowerOfTen(minMantissa)); - constexpr static std::int64_t maxMantissa = minMantissa * 10 - 1; - static_assert(maxMantissa == 9'999'999'999'999'999LL); - // The range for the exponent when normalized constexpr static int minExponent = -32768; constexpr static int maxExponent = 32768; + constexpr static internalrep maxRep = std::numeric_limits::max(); + static_assert(maxRep == 9'223'372'036'854'775'807); + static_assert(-maxRep == std::numeric_limits::min() + 1); + + // May need to make unchecked private struct unchecked { explicit unchecked() = default; }; + // Like unchecked, normalized is used with the ctors that take an + // internalrep mantissa. Unlike unchecked, those ctors will normalize the + // value. + // Only unit tests are expected to use this class + struct normalized + { + explicit normalized() = default; + }; + explicit constexpr Number() = default; Number(rep mantissa); explicit Number(rep mantissa, int exponent); - explicit constexpr Number(rep mantissa, int exponent, unchecked) noexcept; + explicit constexpr Number( + bool negative, + internalrep mantissa, + int exponent, + unchecked) noexcept; + // Assume unsigned values are... unsigned. i.e. positive + explicit constexpr Number( + internalrep mantissa, + int exponent, + unchecked) noexcept; + // Only unit tests are expected to use this ctor + explicit Number( + bool negative, + internalrep mantissa, + int exponent, + normalized); + // Assume unsigned values are... unsigned. i.e. positive + explicit Number(internalrep mantissa, int exponent, normalized); constexpr rep mantissa() const noexcept; @@ -78,11 +291,11 @@ public: Number& operator/=(Number const& x); - static constexpr Number + static Number min() noexcept; - static constexpr Number + static Number max() noexcept; - static constexpr Number + static Number lowest() noexcept; /** Conversions to Number are implicit and conversions away from Number @@ -96,7 +309,8 @@ public: friend constexpr bool operator==(Number const& x, Number const& y) noexcept { - return x.mantissa_ == y.mantissa_ && x.exponent_ == y.exponent_; + return x.negative_ == y.negative_ && x.mantissa_ == y.mantissa_ && + x.exponent_ == y.exponent_; } friend constexpr bool @@ -110,8 +324,8 @@ public: { // If the two amounts have different signs (zero is treated as positive) // then the comparison is true iff the left is negative. - bool const lneg = x.mantissa_ < 0; - bool const rneg = y.mantissa_ < 0; + bool const lneg = x.negative_; + bool const rneg = y.negative_; if (lneg != rneg) return lneg; @@ -139,7 +353,7 @@ public: constexpr int signum() const noexcept { - return (mantissa_ < 0) ? -1 : (mantissa_ ? 1 : 0); + return negative_ ? -1 : (mantissa_ ? 1 : 0); } Number @@ -169,6 +383,15 @@ public: return os << to_string(x); } + friend std::string + to_string(Number const& amount); + + friend Number + root(Number f, unsigned d); + + friend Number + root2(Number f); + // Thread local rounding control. Default is to_nearest enum rounding_mode { to_nearest, towards_zero, downward, upward }; static rounding_mode @@ -177,44 +400,206 @@ public: static rounding_mode setround(rounding_mode mode); + /** Returns which mantissa scale is currently in use for normalization. + * + * If you think you need to call this outside of unit tests, no you don't. + */ + static MantissaRange::mantissa_scale + getMantissaScale(); + /** Changes which mantissa scale is used for normalization. + * + * If you think you need to call this outside of unit tests, no you don't. + */ + static void + setMantissaScale(MantissaRange::mantissa_scale scale); + + inline static internalrep + minMantissa() + { + return range_.get().min; + } + + inline static internalrep + maxMantissa() + { + return range_.get().max; + } + + inline static int + mantissaLog() + { + return range_.get().log; + } + + /// oneSmall is needed because the ranges are private + constexpr static Number + oneSmall(); + /// oneLarge is needed because the ranges are private + constexpr static Number + oneLarge(); + + // And one is needed because it needs to choose between oneSmall and + // oneLarge based on the current range + static Number + one(); + + template + [[nodiscard]] + std::pair + normalizeToRange(T minMantissa, T maxMantissa) const; + private: static thread_local rounding_mode mode_; + // The available ranges for mantissa + + constexpr static MantissaRange smallRange{MantissaRange::small}; + static_assert(isPowerOfTen(smallRange.min)); + static_assert(smallRange.min == 1'000'000'000'000'000LL); + static_assert(smallRange.max == 9'999'999'999'999'999LL); + static_assert(smallRange.log == 15); + static_assert(smallRange.min < maxRep); + static_assert(smallRange.max < maxRep); + constexpr static MantissaRange largeRange{MantissaRange::large}; + static_assert(isPowerOfTen(largeRange.min)); + static_assert(largeRange.min == 1'000'000'000'000'000'000ULL); + static_assert(largeRange.max == internalrep(9'999'999'999'999'999'999ULL)); + static_assert(largeRange.log == 18); + static_assert(largeRange.min < maxRep); + static_assert(largeRange.max > maxRep); + + // The range for the mantissa when normalized. + // Use reference_wrapper to avoid making copies, and prevent accidentally + // changing the values inside the range. + static thread_local std::reference_wrapper range_; void normalize(); - constexpr bool + + /** Normalize Number components to an arbitrary range. + * + * min/maxMantissa are parameters because this function is used by both + * normalize(), which reads from range_, and by normalizeToRange, + * which is public and can accept an arbitrary range from the caller. + */ + template + static void + normalize( + bool& negative, + T& mantissa, + int& exponent, + internalrep const& minMantissa, + internalrep const& maxMantissa); + + template + friend void + doNormalize( + bool& negative, + T& mantissa_, + int& exponent_, + MantissaRange::rep const& minMantissa, + MantissaRange::rep const& maxMantissa); + + bool isnormal() const noexcept; + // Copy the number, but modify the exponent by "exponentDelta". Because the + // mantissa doesn't change, the result will be "mostly" normalized, but the + // exponent could go out of range, so it will be checked. + Number + shiftExponent(int exponentDelta) const; + + // Safely convert rep (int64) mantissa to internalrep (uint64). If the rep + // is negative, returns the positive value. This takes a little extra work + // because converting std::numeric_limits::min() flirts with + // UB, and can vary across compilers. + static internalrep + externalToInternal(rep mantissa); + class Guard; }; +inline 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 + : Number(false, mantissa, exponent, unchecked{}) +{ +} + constexpr static Number numZero{}; -inline constexpr Number::Number(rep mantissa, int exponent, unchecked) noexcept - : mantissa_{mantissa}, exponent_{exponent} +inline Number::Number( + bool negative, + internalrep mantissa, + int exponent, + normalized) + : Number(negative, mantissa, exponent, unchecked{}) +{ + normalize(); +} + +inline Number::Number(internalrep mantissa, int exponent, normalized) + : Number(false, mantissa, exponent, normalized{}) { } inline Number::Number(rep mantissa, int exponent) - : mantissa_{mantissa}, exponent_{exponent} + : Number(mantissa < 0, externalToInternal(mantissa), exponent, normalized{}) { - normalize(); } inline Number::Number(rep mantissa) : Number{mantissa, 0} { } +/** Returns the mantissa of the external view of the Number. + * + * 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 Number::mantissa() const noexcept { - return mantissa_; + auto m = mantissa_; + if (m > maxRep) + { + XRPL_ASSERT_PARTS( + !isnormal() || (m % 10 == 0 && m / 10 <= maxRep), + "xrpl::Number::mantissa", + "large normalized mantissa has no remainder"); + m /= 10; + } + auto const sign = negative_ ? -1 : 1; + return sign * static_cast(m); } +/** Returns the exponent of the external view of the Number. + * + * Please see the "---- External Interface ----" section of the class + * documentation for an explanation of why the internal value may be modified. + */ inline constexpr int Number::exponent() const noexcept { - return exponent_; + auto e = exponent_; + if (mantissa_ > maxRep) + { + XRPL_ASSERT_PARTS( + !isnormal() || (mantissa_ % 10 == 0 && mantissa_ / 10 <= maxRep), + "xrpl::Number::exponent", + "large normalized mantissa has no remainder"); + ++e; + } + return e; } inline constexpr Number @@ -226,15 +611,17 @@ Number::operator+() const noexcept inline constexpr Number Number::operator-() const noexcept { + if (mantissa_ == 0) + return Number{}; auto x = *this; - x.mantissa_ = -x.mantissa_; + x.negative_ = !x.negative_; return x; } inline Number& Number::operator++() { - *this += Number{1000000000000000, -15, unchecked{}}; + *this += one(); return *this; } @@ -249,7 +636,7 @@ Number::operator++(int) inline Number& Number::operator--() { - *this -= Number{1000000000000000, -15, unchecked{}}; + *this -= one(); return *this; } @@ -299,30 +686,54 @@ operator/(Number const& x, Number const& y) return z; } -inline constexpr Number +inline Number Number::min() noexcept { - return Number{minMantissa, minExponent, unchecked{}}; + return Number{false, range_.get().min, minExponent, unchecked{}}; } -inline constexpr Number +inline Number Number::max() noexcept { - return Number{maxMantissa, maxExponent, unchecked{}}; + return Number{ + false, std::min(range_.get().max, maxRep), maxExponent, unchecked{}}; } -inline constexpr Number +inline Number Number::lowest() noexcept { - return -Number{maxMantissa, maxExponent, unchecked{}}; + return Number{ + true, std::min(range_.get().max, maxRep), maxExponent, unchecked{}}; } -inline constexpr bool +inline bool Number::isnormal() const noexcept { - auto const abs_m = mantissa_ < 0 ? -mantissa_ : mantissa_; - return minMantissa <= abs_m && abs_m <= maxMantissa && - minExponent <= exponent_ && exponent_ <= maxExponent; + MantissaRange const& range = range_; + auto const abs_m = mantissa_; + return *this == Number{} || + (range.min <= abs_m && abs_m <= range.max && + (abs_m <= maxRep || abs_m % 10 == 0) && minExponent <= exponent_ && + exponent_ <= maxExponent); +} + +template +std::pair +Number::normalizeToRange(T minMantissa, T maxMantissa) const +{ + bool negative = negative_; + internalrep mantissa = mantissa_; + 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 @@ -364,6 +775,20 @@ squelch(Number const& x, Number const& limit) noexcept return x; } +inline std::string +to_string(MantissaRange::mantissa_scale const& scale) +{ + switch (scale) + { + case MantissaRange::small: + return "small"; + case MantissaRange::large: + return "large"; + default: + throw std::runtime_error("Bad scale"); + } +} + class saveNumberRoundMode { Number::rounding_mode mode_; @@ -402,6 +827,34 @@ public: operator=(NumberRoundModeGuard const&) = delete; }; +/** Sets the new scale and restores the old scale when it leaves scope. + * + * If you think you need to use this class outside of unit tests, no you don't. + * + */ +class NumberMantissaScaleGuard +{ + MantissaRange::mantissa_scale const saved_; + +public: + explicit NumberMantissaScaleGuard( + MantissaRange::mantissa_scale scale) noexcept + : saved_{Number::getMantissaScale()} + { + Number::setMantissaScale(scale); + } + + ~NumberMantissaScaleGuard() + { + Number::setMantissaScale(saved_); + } + + NumberMantissaScaleGuard(NumberMantissaScaleGuard const&) = delete; + + NumberMantissaScaleGuard& + operator=(NumberMantissaScaleGuard const&) = delete; +}; + } // namespace xrpl #endif // XRPL_BASICS_NUMBER_H_INCLUDED diff --git a/include/xrpl/protocol/AmountConversions.h b/include/xrpl/protocol/AmountConversions.h index 195e373fa0..2cdccecabb 100644 --- a/include/xrpl/protocol/AmountConversions.h +++ b/include/xrpl/protocol/AmountConversions.h @@ -121,7 +121,7 @@ toAmount( { if (isXRP(issue)) return STAmount(issue, static_cast(n)); - return STAmount(issue, n.mantissa(), n.exponent()); + return STAmount(issue, n); } else { diff --git a/include/xrpl/protocol/IOUAmount.h b/include/xrpl/protocol/IOUAmount.h index c04cb5cf70..405de18e29 100644 --- a/include/xrpl/protocol/IOUAmount.h +++ b/include/xrpl/protocol/IOUAmount.h @@ -26,8 +26,10 @@ class IOUAmount : private boost::totally_ordered, private boost::additive { private: - std::int64_t mantissa_; - int exponent_; + using mantissa_type = std::int64_t; + using exponent_type = int; + mantissa_type mantissa_; + exponent_type exponent_; /** Adjusts the mantissa and exponent to the proper range. @@ -38,11 +40,14 @@ private: void normalize(); + static IOUAmount + fromNumber(Number const& number); + public: IOUAmount() = default; explicit IOUAmount(Number const& other); IOUAmount(beast::Zero); - IOUAmount(std::int64_t mantissa, int exponent); + IOUAmount(mantissa_type mantissa, exponent_type exponent); IOUAmount& operator=(beast::Zero); @@ -71,10 +76,10 @@ public: int signum() const noexcept; - int + exponent_type exponent() const noexcept; - std::int64_t + mantissa_type mantissa() const noexcept; static IOUAmount @@ -92,7 +97,7 @@ inline IOUAmount::IOUAmount(beast::Zero) *this = beast::zero; } -inline IOUAmount::IOUAmount(std::int64_t mantissa, int exponent) +inline IOUAmount::IOUAmount(mantissa_type mantissa, exponent_type exponent) : mantissa_(mantissa), exponent_(exponent) { normalize(); @@ -149,13 +154,13 @@ IOUAmount::signum() const noexcept return (mantissa_ < 0) ? -1 : (mantissa_ ? 1 : 0); } -inline int +inline IOUAmount::exponent_type IOUAmount::exponent() const noexcept { return exponent_; } -inline std::int64_t +inline IOUAmount::mantissa_type IOUAmount::mantissa() const noexcept { return mantissa_; diff --git a/include/xrpl/protocol/Issue.h b/include/xrpl/protocol/Issue.h index 519d7a96f3..a76b7a8316 100644 --- a/include/xrpl/protocol/Issue.h +++ b/include/xrpl/protocol/Issue.h @@ -37,6 +37,9 @@ public: bool native() const; + bool + integral() const; + friend constexpr std::weak_ordering operator<=>(Issue const& lhs, Issue const& rhs); }; diff --git a/include/xrpl/protocol/MPTIssue.h b/include/xrpl/protocol/MPTIssue.h index b89b59ee0d..ca81548a29 100644 --- a/include/xrpl/protocol/MPTIssue.h +++ b/include/xrpl/protocol/MPTIssue.h @@ -46,6 +46,12 @@ public: { return false; } + + bool + integral() const + { + return true; + } }; constexpr bool diff --git a/include/xrpl/protocol/Protocol.h b/include/xrpl/protocol/Protocol.h index 0c72b80de4..43be9d3b45 100644 --- a/include/xrpl/protocol/Protocol.h +++ b/include/xrpl/protocol/Protocol.h @@ -233,6 +233,7 @@ std::size_t constexpr maxMPTokenMetadataLength = 1024; /** The maximum amount of MPTokenIssuance */ std::uint64_t constexpr maxMPTokenAmount = 0x7FFF'FFFF'FFFF'FFFFull; +static_assert(Number::maxRep >= maxMPTokenAmount); /** The maximum length of Data payload */ std::size_t constexpr maxDataPayloadLength = 256; diff --git a/include/xrpl/protocol/SField.h b/include/xrpl/protocol/SField.h index b1d353196d..7f404b4d5f 100644 --- a/include/xrpl/protocol/SField.h +++ b/include/xrpl/protocol/SField.h @@ -135,7 +135,10 @@ public: sMD_Always = 0x10, // value when node containing it is affected at all sMD_BaseTen = 0x20, // value is treated as base 10, overriding behavior sMD_PseudoAccount = 0x40, // if this field is set in an ACCOUNT_ROOT - // _only_, then it is a pseudo-account + // _only_, then it is a pseudo-account + sMD_NeedsAsset = 0x80, // This field needs to be associated with an + // asset before it is serialized as a ledger + // object. Intended for STNumber. sMD_Default = sMD_ChangeOrig | sMD_ChangeNew | sMD_DeleteFinal | sMD_Create }; diff --git a/include/xrpl/protocol/STAmount.h b/include/xrpl/protocol/STAmount.h index 79cbf51436..4d86aed2ec 100644 --- a/include/xrpl/protocol/STAmount.h +++ b/include/xrpl/protocol/STAmount.h @@ -138,7 +138,7 @@ public: template STAmount(A const& asset, Number const& number) - : STAmount(asset, number.mantissa(), number.exponent()) + : STAmount(fromNumber(asset, number)) { } @@ -282,6 +282,10 @@ public: mpt() const; private: + template + static STAmount + fromNumber(A const& asset, Number const& number); + static std::unique_ptr construct(SerialIter&, SField const& name); @@ -345,10 +349,19 @@ STAmount::STAmount( , mIsNegative(negative) { // mValue is uint64, but needs to fit in the range of int64 - XRPL_ASSERT( - mValue <= std::numeric_limits::max(), - "xrpl::STAmount::STAmount(SField, A, std::uint64_t, int, bool) : " - "maximum mantissa input"); + if (Number::getMantissaScale() == MantissaRange::small) + { + XRPL_ASSERT( + mValue <= std::numeric_limits::max(), + "xrpl::STAmount::STAmount(SField, A, std::uint64_t, int, bool) : " + "maximum mantissa input"); + } + else + { + if (integral() && mValue > std::numeric_limits::max()) + throw std::overflow_error( + "STAmount mantissa is too large " + std::to_string(mantissa)); + } canonicalize(); } @@ -542,14 +555,23 @@ STAmount::operator=(XRPAmount const& amount) return *this; } -inline STAmount& -STAmount::operator=(Number const& number) +template +inline STAmount +STAmount::fromNumber(A const& a, Number const& number) { - mIsNegative = number.mantissa() < 0; - mValue = mIsNegative ? -number.mantissa() : number.mantissa(); - mOffset = number.exponent(); - canonicalize(); - return *this; + bool const negative = number.mantissa() < 0; + Number const working{negative ? -number : number}; + Asset asset{a}; + if (asset.integral()) + { + std::uint64_t const intValue = static_cast(working); + return STAmount{asset, intValue, 0, negative}; + } + + auto const [mantissa, exponent] = + working.normalizeToRange(cMinValue, cMaxValue); + + return STAmount{asset, mantissa, exponent, negative}; } inline void @@ -699,17 +721,32 @@ getRate(STAmount const& offerOut, STAmount const& offerIn); * @param rounding Optional Number rounding mode * */ -STAmount +[[nodiscard]] STAmount roundToScale( STAmount const& value, std::int32_t scale, Number::rounding_mode rounding = Number::getround()); +/** Round an arbitrary precision Number IN PLACE to the precision of a given + * Asset. + * + * This is used to ensure that calculations do not collect dust for IOUs, or + * fractional amounts for the integral types XRP and MPT. + * + * @param asset The relevant asset + * @param value The lvalue to be rounded + */ +template +void +roundToAsset(A const& asset, Number& value) +{ + value = STAmount{asset, value}; +} + /** Round an arbitrary precision Number to the precision of a given Asset. * - * This is used to ensure that calculations do not collect dust beyond the - * precision of the reference value for IOUs, or fractional amounts for the - * integral types XRP and MPT. + * This is used to ensure that calculations do not collect dust beyond specified + * scale for IOUs, or fractional amounts for the integral types XRP and MPT. * * @param asset The relevant asset * @param value The value to be rounded @@ -718,7 +755,7 @@ roundToScale( * @param rounding Optional Number rounding mode */ template -Number +[[nodiscard]] Number roundToAsset( A const& asset, Number const& value, diff --git a/include/xrpl/protocol/STNumber.h b/include/xrpl/protocol/STNumber.h index dfdb16af93..39b0c3b042 100644 --- a/include/xrpl/protocol/STNumber.h +++ b/include/xrpl/protocol/STNumber.h @@ -4,6 +4,7 @@ #include #include #include +#include #include @@ -19,8 +20,19 @@ namespace xrpl { * it can represent a value of any token type (XRP, IOU, or MPT) * without paying the storage cost of duplicating asset information * that may be deduced from the context. + * + * STNumber derives from STTakesAsset, so that it can be associated with the + * related Asset during transaction processing. Which asset is relevant depends + * on the object and transaction. As of this writing, only Vault, LoanBroker, + * and Loan objects use STNumber fields. All of those fields represent amounts + * of the Vault's Asset, so they should be associated with the Vault's Asset. + * + * e.g. + * associateAsset(*loanSle, asset); + * associateAsset(*brokerSle, asset); + * associateAsset(*vaultSle, asset); */ -class STNumber : public STBase, public CountedObject +class STNumber : public STTakesAsset, public CountedObject { private: Number value_; @@ -56,6 +68,9 @@ public: bool isDefault() const override; + void + associateAsset(Asset const& a) override; + operator Number() const { return value_; diff --git a/include/xrpl/protocol/STTakesAsset.h b/include/xrpl/protocol/STTakesAsset.h new file mode 100644 index 0000000000..767223b97d --- /dev/null +++ b/include/xrpl/protocol/STTakesAsset.h @@ -0,0 +1,63 @@ +#ifndef XRPL_PROTOCOL_STTAKESASSET_H_INCLUDED +#define XRPL_PROTOCOL_STTAKESASSET_H_INCLUDED + +#include +#include + +namespace xrpl { + +/** Intermediate class for any STBase-derived class to store an Asset. + * + * In the class definition, this class should be specified as a base class + * _instead_ of STBase. + * + * Specifically, the Asset is only stored and used at runtime. It should not be + * serialized to the ledger. + * + * The derived class decides what to do with the Asset, and when. It will not + * necessarily be set at any given time. As of this writing, only STNumber uses + * it to round the stored Number to the Asset's precision both when associated, + * and when serializing the Number. + */ +class STTakesAsset : public STBase +{ +protected: + std::optional asset_; + +public: + using STBase::STBase; + using STBase::operator=; + + virtual void + associateAsset(Asset const& a); +}; + +inline void +STTakesAsset::associateAsset(Asset const& a) +{ + asset_.emplace(a); +} + +class STLedgerEntry; + +/** Associate an Asset with all sMD_NeedsAsset fields in a ledger entry. + * + * This function iterates over all fields in the given ledger entry. For each + * field that is set and has the SField::sMD_NeedsAsset metadata flag, it calls + * `associateAsset` on that field with the given Asset. Such field must be + * derived from STTakesAsset - if it is not, the conversion will throw. + * + * Typically, associateAsset should be called near the end of doApply() of any + * Transactor classes on the SLEs of any new or modified ledger entries + * containing STNumber fields, after doing all of the modifications t the SLEs. + * + * @param sle The ledger entry whose fields will be updated. + * @param asset The Asset to associate with the relevant fields. + * + */ +void +associateAsset(STLedgerEntry& sle, Asset const& asset); + +} // namespace xrpl + +#endif diff --git a/include/xrpl/protocol/SystemParameters.h b/include/xrpl/protocol/SystemParameters.h index c0732bc9fe..c2f66e9ea1 100644 --- a/include/xrpl/protocol/SystemParameters.h +++ b/include/xrpl/protocol/SystemParameters.h @@ -23,6 +23,8 @@ systemName() /** Number of drops in the genesis account. */ constexpr XRPAmount INITIAL_XRP{100'000'000'000 * DROPS_PER_XRP}; +static_assert(INITIAL_XRP.drops() == 100'000'000'000'000'000); +static_assert(Number::maxRep >= INITIAL_XRP.drops()); /** Returns true if the amount does not exceed the initial XRP in existence. */ inline bool diff --git a/include/xrpl/protocol/detail/sfields.macro b/include/xrpl/protocol/detail/sfields.macro index d0736469e4..712cf568af 100644 --- a/include/xrpl/protocol/detail/sfields.macro +++ b/include/xrpl/protocol/detail/sfields.macro @@ -207,22 +207,22 @@ TYPED_SFIELD(sfLoanID, UINT256, 38) // number (common) TYPED_SFIELD(sfNumber, NUMBER, 1) -TYPED_SFIELD(sfAssetsAvailable, NUMBER, 2) -TYPED_SFIELD(sfAssetsMaximum, NUMBER, 3) -TYPED_SFIELD(sfAssetsTotal, NUMBER, 4) -TYPED_SFIELD(sfLossUnrealized, NUMBER, 5) -TYPED_SFIELD(sfDebtTotal, NUMBER, 6) -TYPED_SFIELD(sfDebtMaximum, NUMBER, 7) -TYPED_SFIELD(sfCoverAvailable, NUMBER, 8) +TYPED_SFIELD(sfAssetsAvailable, NUMBER, 2, SField::sMD_NeedsAsset | SField::sMD_Default) +TYPED_SFIELD(sfAssetsMaximum, NUMBER, 3, SField::sMD_NeedsAsset | SField::sMD_Default) +TYPED_SFIELD(sfAssetsTotal, NUMBER, 4, SField::sMD_NeedsAsset | SField::sMD_Default) +TYPED_SFIELD(sfLossUnrealized, NUMBER, 5, SField::sMD_NeedsAsset | SField::sMD_Default) +TYPED_SFIELD(sfDebtTotal, NUMBER, 6, SField::sMD_NeedsAsset | SField::sMD_Default) +TYPED_SFIELD(sfDebtMaximum, NUMBER, 7, SField::sMD_NeedsAsset | SField::sMD_Default) +TYPED_SFIELD(sfCoverAvailable, NUMBER, 8, SField::sMD_NeedsAsset | SField::sMD_Default) TYPED_SFIELD(sfLoanOriginationFee, NUMBER, 9) TYPED_SFIELD(sfLoanServiceFee, NUMBER, 10) TYPED_SFIELD(sfLatePaymentFee, NUMBER, 11) TYPED_SFIELD(sfClosePaymentFee, NUMBER, 12) -TYPED_SFIELD(sfPrincipalOutstanding, NUMBER, 13) +TYPED_SFIELD(sfPrincipalOutstanding, NUMBER, 13, SField::sMD_NeedsAsset | SField::sMD_Default) TYPED_SFIELD(sfPrincipalRequested, NUMBER, 14) -TYPED_SFIELD(sfTotalValueOutstanding, NUMBER, 15) +TYPED_SFIELD(sfTotalValueOutstanding, NUMBER, 15, SField::sMD_NeedsAsset | SField::sMD_Default) TYPED_SFIELD(sfPeriodicPayment, NUMBER, 16) -TYPED_SFIELD(sfManagementFeeOutstanding, NUMBER, 17) +TYPED_SFIELD(sfManagementFeeOutstanding, NUMBER, 17, SField::sMD_NeedsAsset | SField::sMD_Default) // int32 TYPED_SFIELD(sfLoanScale, INT32, 1) diff --git a/src/libxrpl/basics/Number.cpp b/src/libxrpl/basics/Number.cpp index 9984b26ffe..436ebf6779 100644 --- a/src/libxrpl/basics/Number.cpp +++ b/src/libxrpl/basics/Number.cpp @@ -1,4 +1,6 @@ #include +// Keep Number.h first to ensure it can build without hidden dependencies +#include #include #include @@ -13,16 +15,20 @@ #include #ifdef _MSC_VER -#pragma message("Using boost::multiprecision::uint128_t") +#pragma message("Using boost::multiprecision::uint128_t and int128_t") #include using uint128_t = boost::multiprecision::uint128_t; +using int128_t = boost::multiprecision::int128_t; #else // !defined(_MSC_VER) using uint128_t = __uint128_t; +using int128_t = __int128_t; #endif // !defined(_MSC_VER) namespace xrpl { thread_local Number::rounding_mode Number::mode_ = Number::to_nearest; +thread_local std::reference_wrapper Number::range_ = + largeRange; Number::rounding_mode Number::getround() @@ -36,12 +42,30 @@ Number::setround(rounding_mode mode) return std::exchange(mode_, mode); } +MantissaRange::mantissa_scale +Number::getMantissaScale() +{ + return range_.get().scale; +} + +void +Number::setMantissaScale(MantissaRange::mantissa_scale scale) +{ + if (scale != MantissaRange::small && scale != MantissaRange::large) + LogicError("Unknown mantissa scale"); + range_ = scale == MantissaRange::small ? smallRange : largeRange; +} + // Guard // The Guard class is used to temporarily add extra digits of // precision to an operation. This enables the final result // to be correctly rounded to the internal precision of Number. +template +concept UnsignedMantissa = + std::is_unsigned_v || std::is_same_v; + class Number::Guard { std::uint64_t digits_; // 16 decimal guard digits @@ -62,8 +86,9 @@ public: is_negative() const noexcept; // add a digit + template void - push(unsigned d) noexcept; + push(T d) noexcept; // recover a digit unsigned @@ -76,16 +101,40 @@ public: round() noexcept; // Modify the result to the correctly rounded value + template void - doRoundUp(rep& mantissa, int& exponent, std::string location); + doRoundUp( + bool& negative, + T& mantissa, + int& exponent, + internalrep const& minMantissa, + internalrep const& maxMantissa, + std::string location); + + // Modify the result to the correctly rounded value + template + void + doRoundDown( + bool& negative, + T& mantissa, + int& exponent, + internalrep const& minMantissa); // Modify the result to the correctly rounded value void - doRoundDown(rep& mantissa, int& exponent); + doRound(rep& drops, std::string location); - // Modify the result to the correctly rounded value +private: void - doRound(rep& drops); + doPush(unsigned d) noexcept; + + template + void + bringIntoRange( + bool& negative, + T& mantissa, + int& exponent, + internalrep const& minMantissa); }; inline void @@ -107,13 +156,20 @@ Number::Guard::is_negative() const noexcept } inline void -Number::Guard::push(unsigned d) noexcept +Number::Guard::doPush(unsigned d) noexcept { - xbit_ = xbit_ || (digits_ & 0x0000'0000'0000'000F) != 0; + xbit_ = xbit_ || ((digits_ & 0x0000'0000'0000'000F) != 0); digits_ >>= 4; digits_ |= (d & 0x0000'0000'0000'000FULL) << 60; } +template +inline void +Number::Guard::push(T d) noexcept +{ + doPush(static_cast(d)); +} + inline unsigned Number::Guard::pop() noexcept { @@ -163,30 +219,65 @@ Number::Guard::round() noexcept return 0; } +template void -Number::Guard::doRoundUp(rep& mantissa, int& exponent, std::string location) +Number::Guard::bringIntoRange( + bool& negative, + T& mantissa, + int& exponent, + internalrep const& minMantissa) +{ + // Bring mantissa back into the minMantissa / maxMantissa range AFTER + // rounding + if (mantissa < minMantissa) + { + mantissa *= 10; + --exponent; + } + if (exponent < minExponent) + { + constexpr Number zero = Number{}; + + negative = zero.negative_; + mantissa = zero.mantissa_; + exponent = zero.exponent_; + } +} + +template +void +Number::Guard::doRoundUp( + bool& negative, + T& mantissa, + int& exponent, + internalrep const& minMantissa, + internalrep const& maxMantissa, + std::string location) { auto r = round(); if (r == 1 || (r == 0 && (mantissa & 1) == 1)) { ++mantissa; - if (mantissa > maxMantissa) + // Ensure mantissa after incrementing fits within both the + // min/maxMantissa range and is a valid "rep". + if (mantissa > maxMantissa || mantissa > maxRep) { mantissa /= 10; ++exponent; } } - if (exponent < minExponent) - { - mantissa = 0; - exponent = Number{}.exponent_; - } + bringIntoRange(negative, mantissa, exponent, minMantissa); if (exponent > maxExponent) throw std::overflow_error(location); } +template void -Number::Guard::doRoundDown(rep& mantissa, int& exponent) +Number::Guard::doRoundDown( + bool& negative, + T& mantissa, + int& exponent, + internalrep const& minMantissa) { auto r = round(); if (r == 1 || (r == 0 && (mantissa & 1) == 1)) @@ -198,20 +289,27 @@ Number::Guard::doRoundDown(rep& mantissa, int& exponent) --exponent; } } - if (exponent < minExponent) - { - mantissa = 0; - exponent = Number{}.exponent_; - } + bringIntoRange(negative, mantissa, exponent, minMantissa); } // Modify the result to the correctly rounded value void -Number::Guard::doRound(rep& drops) +Number::Guard::doRound(rep& drops, std::string location) { auto r = round(); if (r == 1 || (r == 0 && (drops & 1) == 1)) { + if (drops >= maxRep) + { + static_assert(sizeof(internalrep) == sizeof(rep)); + // This should be impossible, because it's impossible to represent + // "maxRep + 0.6" in Number, regardless of the scale. There aren't + // enough digits available. You'd either get a mantissa of "maxRep" + // or "(maxRep + 1) / 10", neither of which will round up when + // converting to rep, though the latter might overflow _before_ + // rounding. + throw std::overflow_error(location); // LCOV_EXCL_LINE + } ++drops; } if (is_negative()) @@ -220,20 +318,88 @@ Number::Guard::doRound(rep& drops) // Number -constexpr Number one{1000000000000000, -15, Number::unchecked{}}; - -void -Number::normalize() +// Safely convert rep (int64) mantissa to internalrep (uint64). If the rep is +// negative, returns the positive value. This takes a little extra work because +// converting std::numeric_limits::min() flirts with UB, and can +// vary across compilers. +Number::internalrep +Number::externalToInternal(rep mantissa) { + // If the mantissa is already positive, just return it + if (mantissa >= 0) + return mantissa; + // If the mantissa is negative, but fits within the positive range of rep, + // return it negated + if (mantissa >= -std::numeric_limits::max()) + return -mantissa; + + // If the mantissa doesn't fit within the positive range, convert to + // int128_t, negate that, and cast it back down to the internalrep + // In practice, this is only going to cover the case of + // std::numeric_limits::min(). + int128_t temp = mantissa; + return static_cast(-temp); +} + +constexpr Number +Number::oneSmall() +{ + return Number{ + false, + Number::smallRange.min, + -Number::smallRange.log, + Number::unchecked{}}; +}; + +constexpr Number oneSml = Number::oneSmall(); + +constexpr Number +Number::oneLarge() +{ + return Number{ + false, + Number::largeRange.min, + -Number::largeRange.log, + Number::unchecked{}}; +}; + +constexpr Number oneLrg = Number::oneLarge(); + +Number +Number::one() +{ + if (&range_.get() == &smallRange) + return oneSml; + XRPL_ASSERT(&range_.get() == &largeRange, "Number::one() : valid range_"); + return oneLrg; +} + +// Use the member names in this static function for now so the diff is cleaner +// TODO: Rename the function parameters to get rid of the "_" suffix +template +void +doNormalize( + bool& negative, + T& mantissa_, + int& exponent_, + MantissaRange::rep const& minMantissa, + MantissaRange::rep const& maxMantissa) +{ + auto constexpr minExponent = Number::minExponent; + auto constexpr maxExponent = Number::maxExponent; + auto constexpr maxRep = Number::maxRep; + + using Guard = Number::Guard; + + constexpr Number zero = Number{}; if (mantissa_ == 0) { - *this = Number{}; + mantissa_ = zero.mantissa_; + exponent_ = zero.exponent_; + negative = zero.negative_; return; } - bool const negative = (mantissa_ < 0); - auto m = static_cast>(mantissa_); - if (negative) - m = -m; + auto m = mantissa_; while ((m < minMantissa) && (exponent_ > minExponent)) { m *= 10; @@ -250,57 +416,161 @@ Number::normalize() m /= 10; ++exponent_; } - mantissa_ = m; - if ((exponent_ < minExponent) || (mantissa_ < minMantissa)) + if ((exponent_ < minExponent) || (m < minMantissa)) { - *this = Number{}; + mantissa_ = zero.mantissa_; + exponent_ = zero.exponent_; + negative = zero.negative_; return; } - g.doRoundUp(mantissa_, exponent_, "Number::normalize 2"); + // When using the largeRange, "m" needs fit within an int64, even if + // the final mantissa_ is going to end up larger to fit within the + // MantissaRange. Cut it down here so that the rounding will be done while + // it's smaller. + // + // Example: 9,900,000,000,000,123,456 > 9,223,372,036,854,775,807, + // so "m" will be modified to 990,000,000,000,012,345. Then that value + // will be rounded to 990,000,000,000,012,345 or + // 990,000,000,000,012,346, depending on the rounding mode. Finally, + // mantissa_ will be "m*10" so it fits within the range, and end up as + // 9,900,000,000,000,123,450 or 9,900,000,000,000,123,460. + // mantissa() will return mantissa_ / 10, and exponent() will return + // exponent_ + 1. + if (m > maxRep) + { + if (exponent_ >= maxExponent) + throw std::overflow_error("Number::normalize 1.5"); + g.push(m % 10); + m /= 10; + ++exponent_; + } + // Before modification, m should be within the min/max range. After + // modification, it must be less than maxRep. In other words, the original + // value should have been no more than maxRep * 10. + // (maxRep * 10 > maxMantissa) + XRPL_ASSERT_PARTS( + m <= maxRep, + "xrpl::doNormalize", + "intermediate mantissa fits in int64"); + mantissa_ = m; - if (negative) - mantissa_ = -mantissa_; + g.doRoundUp( + negative, + mantissa_, + exponent_, + minMantissa, + maxMantissa, + "Number::normalize 2"); + XRPL_ASSERT_PARTS( + mantissa_ >= minMantissa && mantissa_ <= maxMantissa, + "xrpl::doNormalize", + "final mantissa fits in range"); +} + +template <> +void +Number::normalize( + bool& negative, + uint128_t& mantissa, + int& exponent, + internalrep const& minMantissa, + internalrep const& maxMantissa) +{ + doNormalize(negative, mantissa, exponent, minMantissa, maxMantissa); +} + +template <> +void +Number::normalize( + bool& negative, + unsigned long long& mantissa, + int& exponent, + internalrep const& minMantissa, + internalrep const& maxMantissa) +{ + doNormalize(negative, mantissa, exponent, minMantissa, maxMantissa); +} + +template <> +void +Number::normalize( + bool& negative, + unsigned long& mantissa, + int& exponent, + internalrep const& minMantissa, + internalrep const& maxMantissa) +{ + doNormalize(negative, mantissa, exponent, minMantissa, maxMantissa); +} + +void +Number::normalize() +{ + auto const& range = range_.get(); + normalize(negative_, mantissa_, exponent_, range.min, range.max); +} + +// Copy the number, but set a new exponent. Because the mantissa doesn't change, +// the result will be "mostly" normalized, but the exponent could go out of +// range. +Number +Number::shiftExponent(int exponentDelta) const +{ + XRPL_ASSERT_PARTS(isnormal(), "xrpl::Number::shiftExponent", "normalized"); + auto const newExponent = exponent_ + exponentDelta; + if (newExponent >= maxExponent) + throw std::overflow_error("Number::shiftExponent"); + if (newExponent < minExponent) + { + return Number{}; + } + Number const result{negative_, mantissa_, newExponent, unchecked{}}; + XRPL_ASSERT_PARTS( + result.isnormal(), + "xrpl::Number::shiftExponent", + "result is normalized"); + return result; } Number& Number::operator+=(Number const& y) { - if (y == Number{}) + constexpr Number zero = Number{}; + if (y == zero) return *this; - if (*this == Number{}) + if (*this == zero) { *this = y; return *this; } if (*this == -y) { - *this = Number{}; + *this = zero; return *this; } + XRPL_ASSERT( isnormal() && y.isnormal(), "xrpl::Number::operator+=(Number) : is normal"); - auto xm = mantissa(); - auto xe = exponent(); - int xn = 1; - if (xm < 0) - { - xm = -xm; - xn = -1; - } - auto ym = y.mantissa(); - auto ye = y.exponent(); - int yn = 1; - if (ym < 0) - { - ym = -ym; - yn = -1; - } + // *n = negative + // *s = sign + // *m = mantissa + // *e = exponent + + // Need to use uint128_t, because large mantissas can overflow when added + // together. + bool xn = negative_; + uint128_t xm = mantissa_; + auto xe = exponent_; + + bool yn = y.negative_; + uint128_t ym = y.mantissa_; + auto ye = y.exponent_; Guard g; if (xe < ye) { - if (xn == -1) + if (xn) g.set_negative(); do { @@ -311,7 +581,7 @@ Number::operator+=(Number const& y) } else if (xe > ye) { - if (yn == -1) + if (yn) g.set_negative(); do { @@ -320,16 +590,22 @@ Number::operator+=(Number const& y) ++ye; } while (xe > ye); } + + auto const& range = range_.get(); + auto const& minMantissa = range.min; + auto const& maxMantissa = range.max; + if (xn == yn) { xm += ym; - if (xm > maxMantissa) + if (xm > maxMantissa || xm > maxRep) { g.push(xm % 10); xm /= 10; ++xe; } - g.doRoundUp(xm, xe, "Number::addition overflow"); + g.doRoundUp( + xn, xm, xe, minMantissa, maxMantissa, "Number::addition overflow"); } else { @@ -343,16 +619,19 @@ Number::operator+=(Number const& y) xe = ye; xn = yn; } - while (xm < minMantissa) + while (xm < minMantissa && xm * 10 <= maxRep) { xm *= 10; xm -= g.pop(); --xe; } - g.doRoundDown(xm, xe); + g.doRoundDown(xn, xm, xe, minMantissa); } - mantissa_ = xm * xn; + + negative_ = xn; + mantissa_ = static_cast(xm); exponent_ = xe; + normalize(); return *this; } @@ -387,39 +666,42 @@ divu10(uint128_t& u) Number& Number::operator*=(Number const& y) { - if (*this == Number{}) + constexpr Number zero = Number{}; + if (*this == zero) return *this; - if (y == Number{}) + if (y == zero) { *this = y; return *this; } - XRPL_ASSERT( - isnormal() && y.isnormal(), - "xrpl::Number::operator*=(Number) : is normal"); - auto xm = mantissa(); - auto xe = exponent(); - int xn = 1; - if (xm < 0) - { - xm = -xm; - xn = -1; - } - auto ym = y.mantissa(); - auto ye = y.exponent(); - int yn = 1; - if (ym < 0) - { - ym = -ym; - yn = -1; - } + // *n = negative + // *s = sign + // *m = mantissa + // *e = exponent + + bool xn = negative_; + int xs = xn ? -1 : 1; + internalrep xm = mantissa_; + auto xe = exponent_; + + bool yn = y.negative_; + int ys = yn ? -1 : 1; + internalrep ym = y.mantissa_; + auto ye = y.exponent_; + auto zm = uint128_t(xm) * uint128_t(ym); auto ze = xe + ye; - auto zn = xn * yn; + auto zs = xs * ys; + bool zn = (zs == -1); Guard g; - if (zn == -1) + if (zn) g.set_negative(); - while (zm > maxMantissa) + + auto const& range = range_.get(); + auto const& minMantissa = range.min; + auto const& maxMantissa = range.max; + + while (zm > maxMantissa || zm > maxRep) { // The following is optimization for: // g.push(static_cast(zm % 10)); @@ -427,61 +709,129 @@ Number::operator*=(Number const& y) g.push(divu10(zm)); ++ze; } - xm = static_cast(zm); + xm = static_cast(zm); xe = ze; g.doRoundUp( + zn, xm, xe, + minMantissa, + maxMantissa, "Number::multiplication overflow : exponent is " + std::to_string(xe)); - mantissa_ = xm * zn; + negative_ = zn; + mantissa_ = xm; exponent_ = xe; - XRPL_ASSERT( - isnormal() || *this == Number{}, - "xrpl::Number::operator*=(Number) : result is normal"); + + normalize(); return *this; } Number& Number::operator/=(Number const& y) { - if (y == Number{}) + constexpr Number zero = Number{}; + if (y == zero) throw std::overflow_error("Number: divide by 0"); - if (*this == Number{}) + if (*this == zero) return *this; - int np = 1; - auto nm = mantissa(); - auto ne = exponent(); - if (nm < 0) + // n* = numerator + // d* = denominator + // *p = negative (positive?) + // *s = sign + // *m = mantissa + // *e = exponent + + bool np = negative_; + int ns = (np ? -1 : 1); + auto nm = mantissa_; + auto ne = exponent_; + + bool dp = y.negative_; + int ds = (dp ? -1 : 1); + auto dm = y.mantissa_; + auto de = y.exponent_; + + auto const& range = range_.get(); + auto const& minMantissa = range.min; + auto const& maxMantissa = range.max; + + // Shift by 10^17 gives greatest precision while not overflowing + // uint128_t or the cast back to int64_t + // TODO: Can/should this be made bigger for largeRange? + // log(2^128,10) ~ 38.5 + // largeRange.log = 18, fits in 10^19 + // f can be up to 10^(38-19) = 10^19 safely + static_assert(smallRange.log == 15); + static_assert(largeRange.log == 18); + bool small = Number::getMantissaScale() == MantissaRange::small; + uint128_t const f = + small ? 100'000'000'000'000'000 : 10'000'000'000'000'000'000ULL; + XRPL_ASSERT_PARTS( + f >= minMantissa * 10, "Number::operator/=", "factor expected size"); + + // unsigned denominator + auto const dmu = static_cast(dm); + // correctionFactor can be anything between 10 and f, depending on how much + // extra precision we want to only use for rounding with the + // largeRange. Three digits seems like plenty, and is more than + // the smallRange uses. + uint128_t const correctionFactor = 1'000; + + auto const numerator = uint128_t(nm) * f; + + auto zm = numerator / dmu; + auto ze = ne - de - (small ? 17 : 19); + bool zn = (ns * ds) < 0; + if (!small) { - nm = -nm; - np = -1; + // Virtually multiply numerator by correctionFactor. Since that would + // overflow in the existing uint128_t, we'll do that part separately. + // The math for this would work for small mantissas, but we need to + // preserve existing behavior. + // + // Consider: + // ((numerator * correctionFactor) / dmu) / correctionFactor + // = ((numerator / dmu) * correctionFactor) / correctionFactor) + // + // But that assumes infinite precision. With integer math, this is + // equivalent to + // + // = ((numerator / dmu * correctionFactor) + // + ((numerator % dmu) * correctionFactor) / dmu) / correctionFactor + // + // We have already set `mantissa_ = numerator / dmu`. Now we + // compute `remainder = numerator % dmu`, and if it is + // nonzero, we do the rest of the arithmetic. If it's zero, we can skip + // it. + auto const remainder = (numerator % dmu); + if (remainder != 0) + { + zm *= correctionFactor; + auto const correction = remainder * correctionFactor / dmu; + zm += correction; + // divide by 1000 by moving the exponent, so we don't lose the + // integer value we just computed + ze -= 3; + } } - int dp = 1; - auto dm = y.mantissa(); - auto de = y.exponent(); - if (dm < 0) - { - dm = -dm; - dp = -1; - } - // Shift by 10^17 gives greatest precision while not overflowing uint128_t - // or the cast back to int64_t - uint128_t const f = 100'000'000'000'000'000; - mantissa_ = static_cast(uint128_t(nm) * f / uint128_t(dm)); - exponent_ = ne - de - 17; - mantissa_ *= np * dp; - normalize(); + normalize(zn, zm, ze, minMantissa, maxMantissa); + negative_ = zn; + mantissa_ = static_cast(zm); + exponent_ = ze; + XRPL_ASSERT_PARTS( + isnormal(), "xrpl::Number::operator/=", "result is normalized"); + return *this; } Number::operator rep() const { - rep drops = mantissa_; - int offset = exponent_; + rep drops = mantissa(); + int offset = exponent(); Guard g; if (drops != 0) { - if (drops < 0) + if (negative_) { g.set_negative(); drops = -drops; @@ -493,11 +843,11 @@ Number::operator rep() const } for (; offset > 0; --offset) { - if (drops > std::numeric_limits::max() / 10) + if (drops > maxRep / 10) throw std::overflow_error("Number::operator rep() overflow"); drops *= 10; } - g.doRound(drops); + g.doRound(drops, "Number::operator rep() rounding overflow"); } return drops; } @@ -524,34 +874,37 @@ std::string to_string(Number const& amount) { // keep full internal accuracy, but make more human friendly if possible - if (amount == Number{}) + constexpr Number zero = Number{}; + if (amount == zero) return "0"; - auto const exponent = amount.exponent(); - auto mantissa = amount.mantissa(); + auto exponent = amount.exponent_; + auto mantissa = amount.mantissa_; + bool const negative = amount.negative_; // Use scientific notation for exponents that are too small or too large - if (((exponent != 0) && ((exponent < -25) || (exponent > -5)))) + auto const rangeLog = Number::mantissaLog(); + if (((exponent != 0) && + ((exponent < -(rangeLog + 10)) || (exponent > -(rangeLog - 10))))) { - std::string ret = std::to_string(mantissa); + while (mantissa != 0 && mantissa % 10 == 0 && + exponent < Number::maxExponent) + { + mantissa /= 10; + ++exponent; + } + std::string ret = negative ? "-" : ""; + ret.append(std::to_string(mantissa)); ret.append(1, 'e'); ret.append(std::to_string(exponent)); return ret; } - bool negative = false; - - if (mantissa < 0) - { - mantissa = -mantissa; - negative = true; - } - XRPL_ASSERT( exponent + 43 > 0, "xrpl::to_string(Number) : minimum exponent"); - ptrdiff_t const pad_prefix = 27; - ptrdiff_t const pad_suffix = 23; + ptrdiff_t const pad_prefix = rangeLog + 12; + ptrdiff_t const pad_suffix = rangeLog + 8; std::string const raw_value(std::to_string(mantissa)); std::string val; @@ -561,7 +914,7 @@ to_string(Number const& amount) val.append(raw_value); val.append(pad_suffix, '0'); - ptrdiff_t const offset(exponent + 43); + ptrdiff_t const offset(exponent + pad_prefix + rangeLog + 1); auto pre_from(val.begin()); auto const pre_to(val.begin() + offset); @@ -621,7 +974,7 @@ Number power(Number const& f, unsigned n) { if (n == 0) - return one; + return Number::one(); if (n == 1) return f; auto r = power(f, n / 2); @@ -643,6 +996,9 @@ power(Number const& f, unsigned n) Number root(Number f, unsigned d) { + constexpr Number zero = Number{}; + auto const one = Number::one(); + if (f == one || d == 1) return f; if (d == 0) @@ -650,16 +1006,16 @@ root(Number f, unsigned d) if (f == -one) return one; if (abs(f) < one) - return Number{}; + return zero; throw std::overflow_error("Number::root infinity"); } - if (f < Number{} && d % 2 == 0) + if (f < zero && d % 2 == 0) throw std::overflow_error("Number::root nan"); - if (f == Number{}) + if (f == zero) return f; // Scale f into the range (0, 1) such that f's exponent is a multiple of d - auto e = f.exponent() + 16; + auto e = f.exponent_ + Number::mantissaLog() + 1; auto const di = static_cast(d); auto ex = [e = e, di = di]() // Euclidean remainder of e/d { @@ -670,9 +1026,12 @@ root(Number f, unsigned d) return di - k2; }(); e += ex; - f = Number{f.mantissa(), f.exponent() - e}; // f /= 10^e; + f = f.shiftExponent(-e); // f /= 10^e; + + XRPL_ASSERT_PARTS( + f.isnormal(), "xrpl::root(Number, unsigned)", "f is normalized"); bool neg = false; - if (f < Number{}) + if (f < zero) { neg = true; f = -f; @@ -702,24 +1061,33 @@ root(Number f, unsigned d) } while (r != rm1 && r != rm2); // return r * 10^(e/d) to reverse scaling - return Number{r.mantissa(), r.exponent() + e / di}; + auto const result = r.shiftExponent(e / di); + XRPL_ASSERT_PARTS( + result.isnormal(), + "xrpl::root(Number, unsigned)", + "result is normalized"); + return result; } Number root2(Number f) { + constexpr Number zero = Number{}; + auto const one = Number::one(); + if (f == one) return f; - if (f < Number{}) + if (f < zero) throw std::overflow_error("Number::root nan"); - if (f == Number{}) + if (f == zero) return f; // Scale f into the range (0, 1) such that f's exponent is a multiple of d - auto e = f.exponent() + 16; + auto e = f.exponent_ + Number::mantissaLog() + 1; if (e % 2 != 0) ++e; - f = Number{f.mantissa(), f.exponent() - e}; // f /= 10^e; + f = f.shiftExponent(-e); // f /= 10^e; + XRPL_ASSERT_PARTS(f.isnormal(), "xrpl::root2(Number)", "f is normalized"); // Quadratic least squares curve fit of f^(1/d) in the range [0, 1] auto const D = 105; @@ -740,7 +1108,11 @@ root2(Number f) } while (r != rm1 && r != rm2); // return r * 10^(e/2) to reverse scaling - return Number{r.mantissa(), r.exponent() + e / 2}; + auto const result = r.shiftExponent(e / 2); + XRPL_ASSERT_PARTS( + result.isnormal(), "xrpl::root2(Number)", "result is normalized"); + + return result; } // Returns f^(n/d) @@ -748,6 +1120,9 @@ root2(Number f) Number power(Number const& f, unsigned n, unsigned d) { + constexpr Number zero = Number{}; + auto const one = Number::one(); + if (f == one) return f; auto g = std::gcd(n, d); @@ -758,7 +1133,7 @@ power(Number const& f, unsigned n, unsigned d) if (f == -one) return one; if (abs(f) < one) - return Number{}; + return zero; // abs(f) > one throw std::overflow_error("Number::power infinity"); } @@ -766,7 +1141,7 @@ power(Number const& f, unsigned n, unsigned d) return one; n /= g; d /= g; - if ((n % 2) == 1 && (d % 2) == 0 && f < Number{}) + if ((n % 2) == 1 && (d % 2) == 0 && f < zero) throw std::overflow_error("Number::power nan"); return root(power(f, n), d); } diff --git a/src/libxrpl/protocol/IOUAmount.cpp b/src/libxrpl/protocol/IOUAmount.cpp index 5c9ab1febc..297c2bac12 100644 --- a/src/libxrpl/protocol/IOUAmount.cpp +++ b/src/libxrpl/protocol/IOUAmount.cpp @@ -1,8 +1,11 @@ +#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 @@ -40,11 +43,24 @@ setSTNumberSwitchover(bool v) } /* The range for the mantissa when normalized */ -static std::int64_t constexpr minMantissa = 1000000000000000ull; -static std::int64_t constexpr maxMantissa = 9999999999999999ull; +// log(2^63,10) ~ 18.96 +// +static std::int64_t constexpr minMantissa = STAmount::cMinValue; +static std::int64_t constexpr maxMantissa = STAmount::cMaxValue; /* The range for the exponent when normalized */ -static int constexpr minExponent = -96; -static int constexpr maxExponent = 80; +static int constexpr minExponent = STAmount::cMinOffset; +static int constexpr maxExponent = STAmount::cMaxOffset; + +IOUAmount +IOUAmount::fromNumber(Number const& number) +{ + // Need to create a default IOUAmount and assign directly so it doesn't try + // to normalize, which calls fromNumber + IOUAmount result{}; + std::tie(result.mantissa_, result.exponent_) = + number.normalizeToRange(minMantissa, maxMantissa); + return result; +} IOUAmount IOUAmount::minPositiveAmount() @@ -64,8 +80,7 @@ IOUAmount::normalize() if (getSTNumberSwitchover()) { Number const v{mantissa_, exponent_}; - mantissa_ = v.mantissa(); - exponent_ = v.exponent(); + *this = fromNumber(v); if (exponent_ > maxExponent) Throw("value overflow"); if (exponent_ < minExponent) @@ -106,8 +121,7 @@ IOUAmount::normalize() mantissa_ = -mantissa_; } -IOUAmount::IOUAmount(Number const& other) - : mantissa_(other.mantissa()), exponent_(other.exponent()) +IOUAmount::IOUAmount(Number const& other) : IOUAmount(fromNumber(other)) { if (exponent_ > maxExponent) Throw("value overflow"); diff --git a/src/libxrpl/protocol/Issue.cpp b/src/libxrpl/protocol/Issue.cpp index b858a31e3e..ca5bf35e8b 100644 --- a/src/libxrpl/protocol/Issue.cpp +++ b/src/libxrpl/protocol/Issue.cpp @@ -49,6 +49,12 @@ Issue::native() const return *this == xrpIssue(); } +bool +Issue::integral() const +{ + return native(); +} + bool isConsistent(Issue const& ac) { diff --git a/src/libxrpl/protocol/Rules.cpp b/src/libxrpl/protocol/Rules.cpp index b1f2c2d631..3710322699 100644 --- a/src/libxrpl/protocol/Rules.cpp +++ b/src/libxrpl/protocol/Rules.cpp @@ -1,10 +1,13 @@ +#include +// Do not remove. Forces Rules.h to stay first, to verify it can compile +// without any hidden dependencies #include +#include #include #include #include #include #include -#include #include #include @@ -33,6 +36,15 @@ getCurrentTransactionRules() void setCurrentTransactionRules(std::optional r) { + // Make global changes associated with the rules before the value is moved. + // Push the appropriate setting, instead of having the class pull every time + // the value is needed. That could get expensive fast. + bool enableLargeNumbers = !r || + (r->enabled(featureSingleAssetVault) || + r->enabled(featureLendingProtocol)); + Number::setMantissaScale( + enableLargeNumbers ? MantissaRange::large : MantissaRange::small); + *getCurrentTransactionRulesRef() = std::move(r); } diff --git a/src/libxrpl/protocol/STAmount.cpp b/src/libxrpl/protocol/STAmount.cpp index ebccfb3e64..ec60971e63 100644 --- a/src/libxrpl/protocol/STAmount.cpp +++ b/src/libxrpl/protocol/STAmount.cpp @@ -11,11 +11,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include #include @@ -310,7 +312,8 @@ STAmount& STAmount::operator=(IOUAmount const& iou) { XRPL_ASSERT( - native() == false, "xrpl::STAmount::operator=(IOUAmount) : is not XRP"); + integral() == false, + "xrpl::STAmount::operator=(IOUAmount) : is not integral"); mOffset = iou.exponent(); mIsNegative = iou < beast::zero; if (mIsNegative) @@ -320,6 +323,26 @@ STAmount::operator=(IOUAmount const& iou) return *this; } +STAmount& +STAmount::operator=(Number const& number) +{ + if (!getCurrentTransactionRules() || + isFeatureEnabled(featureSingleAssetVault) || + isFeatureEnabled(featureLendingProtocol)) + { + *this = fromNumber(mAsset, number); + } + else + { + auto const originalMantissa = number.mantissa(); + mIsNegative = originalMantissa < 0; + mValue = mIsNegative ? -originalMantissa : originalMantissa; + mOffset = number.exponent(); + } + canonicalize(); + return *this; +} + //------------------------------------------------------------------------------ // // Operators @@ -849,11 +872,11 @@ STAmount::canonicalize() if (getSTNumberSwitchover()) { - Number num( - mIsNegative ? -mValue : mValue, mOffset, Number::unchecked{}); + Number num(mIsNegative, mValue, mOffset, Number::unchecked{}); auto set = [&](auto const& val) { - mIsNegative = val.value() < 0; - mValue = mIsNegative ? -val.value() : val.value(); + auto const value = val.value(); + mIsNegative = value < 0; + mValue = mIsNegative ? -value : value; }; if (native()) set(XRPAmount{num}); @@ -1323,7 +1346,7 @@ multiply(STAmount const& v1, STAmount const& v2, Asset const& asset) if (getSTNumberSwitchover()) { auto const r = Number{v1} * Number{v2}; - return STAmount{asset, r.mantissa(), r.exponent()}; + return STAmount{asset, r}; } std::uint64_t value1 = v1.mantissa(); @@ -1471,6 +1494,10 @@ roundToScale( if (value.integral()) return value; + // Nothing to do for zero. + if (value == beast::zero) + return value; + // If the value's exponent is greater than or equal to the scale, then // rounding will do nothing, and might even lose precision, so just return // the value. diff --git a/src/libxrpl/protocol/STNumber.cpp b/src/libxrpl/protocol/STNumber.cpp index f85bb48e0a..2f2dae7493 100644 --- a/src/libxrpl/protocol/STNumber.cpp +++ b/src/libxrpl/protocol/STNumber.cpp @@ -1,9 +1,13 @@ +#include +// Do not remove. Keep STNumber.h first #include #include #include +#include #include +#include #include -#include +#include #include #include @@ -17,11 +21,11 @@ namespace xrpl { STNumber::STNumber(SField const& field, Number const& value) - : STBase(field), value_(value) + : STTakesAsset(field), value_(value) { } -STNumber::STNumber(SerialIter& sit, SField const& field) : STBase(field) +STNumber::STNumber(SerialIter& sit, SField const& field) : STTakesAsset(field) { // We must call these methods in separate statements // to guarantee their order of execution. @@ -42,6 +46,19 @@ STNumber::getText() const return to_string(value_); } +void +STNumber::associateAsset(Asset const& a) +{ + STTakesAsset::associateAsset(a); + + XRPL_ASSERT_PARTS( + getFName().shouldMeta(SField::sMD_NeedsAsset), + "STNumber::associateAsset", + "field needs asset"); + + roundToAsset(a, value_); +} + void STNumber::add(Serializer& s) const { @@ -49,8 +66,49 @@ STNumber::add(Serializer& s) const XRPL_ASSERT( getFName().fieldType == getSType(), "xrpl::STNumber::add : field type match"); - s.add64(value_.mantissa()); - s.add32(value_.exponent()); + + auto value = value_; + auto const mantissa = value.mantissa(); + auto const exponent = value.exponent(); + + SField const& field = getFName(); + if (field.shouldMeta(SField::sMD_NeedsAsset)) + { + // asset is defined in the STTakesAsset base class + if (asset_) + { + // The number should be rounded to the asset's precision, but round + // it here if it has an asset assigned. + roundToAsset(*asset_, value); + XRPL_ASSERT_PARTS( + value_ == value, + "xrpl::STNumber::add", + "value is already rounded"); + } + else + { +#if !NDEBUG + // There are circumstances where an already-rounded Number is + // serialized without being touched by a transactor, and thus + // without an asset. We can't know if it's rounded, because it could + // represent _anything_, particularly when serializing user-provided + // Json. Regardless, the only time we should be serializing an + // STNumber is when the scale is large. + XRPL_ASSERT_PARTS( + Number::getMantissaScale() == MantissaRange::large, + "xrpl::STNumber::add", + "STNumber only used with large mantissa scale"); +#endif + } + } + + XRPL_ASSERT_PARTS( + mantissa <= std::numeric_limits::max() && + mantissa >= std::numeric_limits::min(), + "xrpl::STNumber::add", + "mantissa in valid range"); + s.add64(mantissa); + s.add32(exponent); } Number const& @@ -179,20 +237,30 @@ numberFromJson(SField const& field, Json::Value const& value) else if (value.isString()) { parts = partsFromString(value.asString()); - // Only strings can represent out-of-range values. - if (parts.mantissa > std::numeric_limits::max()) - Throw("too high"); + + XRPL_ASSERT_PARTS( + !getCurrentTransactionRules(), + "xrpld::numberFromJson", + "Not in a Transactor context"); + + // Number mantissas are much bigger than the allowable parsed values, so + // it can't be out of range. + static_assert( + std::numeric_limits::max() >= + std::numeric_limits::max()); } else { Throw("not a number"); } - std::int64_t mantissa = parts.mantissa; - if (parts.negative) - mantissa = -mantissa; - - return STNumber{field, Number{mantissa, parts.exponent}}; + return STNumber{ + field, + Number{ + parts.negative, + parts.mantissa, + parts.exponent, + Number::normalized{}}}; } } // namespace xrpl diff --git a/src/libxrpl/protocol/STTakesAsset.cpp b/src/libxrpl/protocol/STTakesAsset.cpp new file mode 100644 index 0000000000..d43e7b04a1 --- /dev/null +++ b/src/libxrpl/protocol/STTakesAsset.cpp @@ -0,0 +1,29 @@ +#include +// Do not remove. Force STTakesAsset.h first +#include + +namespace xrpl { + +void +associateAsset(SLE& sle, Asset const& asset) +{ + // Iterating by offset is the only way to get non-const references + for (int i = 0; i < sle.getCount(); ++i) + { + STBase& entry = sle.getIndex(i); + SField const& field = entry.getFName(); + if (field.shouldMeta(SField::sMD_NeedsAsset)) + { + auto const type = entry.getSType(); + // If the field is not set or present, skip it. + if (type == STI_NOTPRESENT) + continue; + // If the type doesn't downcast, then the flag shouldn't be on the + // SField + auto& ta = entry.downcast(); + ta.associateAsset(asset); + } + } +} + +} // namespace xrpl diff --git a/src/test/app/AMMClawback_test.cpp b/src/test/app/AMMClawback_test.cpp index 93fda8fe34..52f05f9ed5 100644 --- a/src/test/app/AMMClawback_test.cpp +++ b/src/test/app/AMMClawback_test.cpp @@ -2425,7 +2425,10 @@ class AMMClawback_test : public beast::unit_test::suite void run() override { - FeatureBitset const all = jtx::testable_amendments(); + // For now, just disable SAV entirely, which locks in the small Number + // mantissas + FeatureBitset const all = jtx::testable_amendments() - + featureSingleAssetVault - featureLendingProtocol; testInvalidRequest(); testFeatureDisabled(all - featureAMMClawback); diff --git a/src/test/app/AMMExtended_test.cpp b/src/test/app/AMMExtended_test.cpp index 317f6cb63d..d1816df51b 100644 --- a/src/test/app/AMMExtended_test.cpp +++ b/src/test/app/AMMExtended_test.cpp @@ -26,6 +26,9 @@ namespace test { */ struct AMMExtended_test : public jtx::AMMTest { + // Use small Number mantissas for the life of this test. + NumberMantissaScaleGuard const sg_{xrpl::MantissaRange::small}; + private: void testRmFundedOffer(FeatureBitset features) @@ -42,6 +45,7 @@ private: // funded and not used for the payment. using namespace jtx; + Env env{*this, features}; fund( @@ -1418,7 +1422,12 @@ private: testOffers() { using namespace jtx; - FeatureBitset const all{testable_amendments()}; + // For now, just disable SAV entirely, which locks in the small Number + // mantissas + FeatureBitset const all{ + testable_amendments() - featureSingleAssetVault - + featureLendingProtocol}; + testRmFundedOffer(all); testRmFundedOffer(all - fixAMMv1_1 - fixAMMv1_3); testEnforceNoRipple(all); @@ -3746,7 +3755,11 @@ private: testFlow() { using namespace jtx; - FeatureBitset const all{testable_amendments()}; + // For now, just disable SAV entirely, which locks in the small Number + // mantissas in the transaction engine + FeatureBitset const all{ + testable_amendments() - featureSingleAssetVault - + featureLendingProtocol}; testFalseDry(all); testBookStep(all); @@ -3760,7 +3773,11 @@ private: testCrossingLimits() { using namespace jtx; - FeatureBitset const all{testable_amendments()}; + // For now, just disable SAV entirely, which locks in the small Number + // mantissas in the transaction engine + FeatureBitset const all{ + testable_amendments() - featureSingleAssetVault - + featureLendingProtocol}; testStepLimit(all); testStepLimit(all - fixAMMv1_1 - fixAMMv1_3); } @@ -3769,7 +3786,11 @@ private: testDeliverMin() { using namespace jtx; - FeatureBitset const all{testable_amendments()}; + // For now, just disable SAV entirely, which locks in the small Number + // mantissas in the transaction engine + FeatureBitset const all{ + testable_amendments() - featureSingleAssetVault - + featureLendingProtocol}; test_convert_all_of_an_asset(all); test_convert_all_of_an_asset(all - fixAMMv1_1 - fixAMMv1_3); } @@ -3777,7 +3798,12 @@ private: void testDepositAuth() { - testPayment(jtx::testable_amendments()); + // For now, just disable SAV entirely, which locks in the small Number + // mantissas in the transaction engine + FeatureBitset const all{ + jtx::testable_amendments() - featureSingleAssetVault - + featureLendingProtocol}; + testPayment(all); testPayIOU(); } @@ -3785,7 +3811,11 @@ private: testFreeze() { using namespace test::jtx; - auto const sa = testable_amendments(); + // For now, just disable SAV entirely, which locks in the small Number + // mantissas in the transaction engine + FeatureBitset const sa{ + testable_amendments() - featureSingleAssetVault - + featureLendingProtocol}; testRippleState(sa); testGlobalFreeze(sa); testOffersWhenFrozen(sa); diff --git a/src/test/app/AMM_test.cpp b/src/test/app/AMM_test.cpp index 468d5b3ffd..55bf4aa0a3 100644 --- a/src/test/app/AMM_test.cpp +++ b/src/test/app/AMM_test.cpp @@ -30,7 +30,19 @@ namespace test { */ struct AMM_test : public jtx::AMMTest { + // Use small Number mantissas for the life of this test. + NumberMantissaScaleGuard const sg_{xrpl::MantissaRange::small}; + private: + static FeatureBitset + testable_amendments() + { + // For now, just disable SAV entirely, which locks in the small Number + // mantissas + return jtx::testable_amendments() - featureSingleAssetVault - + featureLendingProtocol; + } + void testInstanceCreate() { @@ -38,6 +50,7 @@ private: using namespace jtx; +#if NUMBERTODO // XRP to IOU, with featureSingleAssetVault testAMM( [&](AMM& ammAlice, Env&) { @@ -48,6 +61,7 @@ private: 0, {}, {testable_amendments() | featureSingleAssetVault}); +#endif // XRP to IOU, without featureSingleAssetVault testAMM( @@ -1365,8 +1379,8 @@ private: { testcase("Deposit"); - using namespace jtx; auto const all = testable_amendments(); + using namespace jtx; // Equal deposit: 1000000 tokens, 10% of the current pool testAMM([&](AMM& ammAlice, Env& env) { @@ -1384,15 +1398,14 @@ private: // equal asset deposit: unit test to exercise the rounding-down of // LPTokens in the AMMHelpers.cpp: adjustLPTokens calculations // The LPTokens need to have 16 significant digits and a fractional part - for (Number const deltaLPTokens : + for (Number const& deltaLPTokens : {Number{UINT64_C(100000'0000000009), -10}, Number{UINT64_C(100000'0000000001), -10}}) { testAMM([&](AMM& ammAlice, Env& env) { // initial LPToken balance IOUAmount const initLPToken = ammAlice.getLPTokensBalance(); - IOUAmount const newLPTokens{ - deltaLPTokens.mantissa(), deltaLPTokens.exponent()}; + IOUAmount const newLPTokens{deltaLPTokens}; // carol performs a two-asset deposit ammAlice.deposit( @@ -1417,11 +1430,9 @@ private: Number const deltaXRP = fr * 1e10; Number const deltaUSD = fr * 1e4; - STAmount const depositUSD = - STAmount{USD, deltaUSD.mantissa(), deltaUSD.exponent()}; + STAmount const depositUSD = STAmount{USD, deltaUSD}; - STAmount const depositXRP = - STAmount{XRP, deltaXRP.mantissa(), deltaXRP.exponent()}; + STAmount const depositXRP = STAmount{XRP, deltaXRP}; // initial LPTokens (1e7) + newLPTokens BEAST_EXPECT(ammAlice.expectBalances( @@ -1487,7 +1498,7 @@ private: }); // Single deposit: 100000 tokens worth of XRP - testAMM([&](AMM& ammAlice, Env&) { + testAMM([&](AMM& ammAlice, Env& env) { ammAlice.deposit(carol, 100'000, XRP(205)); BEAST_EXPECT(ammAlice.expectBalances( XRP(10'201), USD(10'000), IOUAmount{10'100'000, 0})); @@ -1668,8 +1679,8 @@ private: { testcase("Invalid Withdraw"); - using namespace jtx; auto const all = testable_amendments(); + using namespace jtx; testAMM( [&](AMM& ammAlice, Env& env) { @@ -2248,8 +2259,8 @@ private: { testcase("Withdraw"); - using namespace jtx; auto const all = testable_amendments(); + using namespace jtx; // Equal withdrawal by Carol: 1000000 of tokens, 10% of the current // pool @@ -2669,8 +2680,8 @@ private: testFeeVote() { testcase("Fee Vote"); - using namespace jtx; auto const all = testable_amendments(); + using namespace jtx; // One vote sets fee to 1%. testAMM([&](AMM& ammAlice, Env& env) { @@ -3014,6 +3025,10 @@ private: using namespace jtx; using namespace std::chrono; + // For now, just disable SAV entirely, which locks in the small Number + // mantissas + features = features - featureSingleAssetVault - featureLendingProtocol; + // Auction slot initially is owned by AMM creator, who pays 0 price. // Bid 110 tokens. Pay bidMin. @@ -3758,6 +3773,11 @@ private: testcase("Basic Payment"); using namespace jtx; + // For now, just disable SAV entirely, which locks in the small Number + // mantissas + features = features - featureSingleAssetVault - featureLendingProtocol - + featureLendingProtocol; + // Payment 100USD for 100XRP. // Force one path with tfNoRippleDirect. testAMM( @@ -4836,12 +4856,12 @@ private: testAmendment() { testcase("Amendment"); - using namespace jtx; FeatureBitset const all{testable_amendments()}; FeatureBitset const noAMM{all - featureAMM}; FeatureBitset const noNumber{all - fixUniversalNumber}; FeatureBitset const noAMMAndNumber{ all - featureAMM - fixUniversalNumber}; + using namespace jtx; for (auto const& feature : {noAMM, noNumber, noAMMAndNumber}) { @@ -6476,6 +6496,8 @@ private: Env env(*this, features, std::make_unique(&logs)); auto rules = env.current()->rules(); CurrentTransactionRulesGuard rg(rules); + NumberMantissaScaleGuard sg(MantissaRange::small); + for (auto const& t : tests) { auto getPool = [&](std::string const& v, bool isXRP) { @@ -7025,7 +7047,7 @@ private: {{xrpPool, iouPool}}, 889, std::nullopt, - {jtx::testable_amendments() | fixAMMv1_1}); + {testable_amendments() | fixAMMv1_1}); } void @@ -7566,6 +7588,7 @@ private: { auto const [amount, amount2, lptBalance] = amm.balances(GBP, EUR); + NumberMantissaScaleGuard sg(MantissaRange::small); NumberRoundModeGuard g( env.enabled(fixAMMv1_3) ? Number::upward : Number::getround()); auto const res = root2(amount * amount2); @@ -7880,7 +7903,7 @@ private: void run() override { - FeatureBitset const all{jtx::testable_amendments()}; + FeatureBitset const all{testable_amendments()}; testInvalidInstance(); testInstanceCreate(); testInvalidDeposit(all); diff --git a/src/test/app/EscrowToken_test.cpp b/src/test/app/EscrowToken_test.cpp index 955ca8f449..589a8b474e 100644 --- a/src/test/app/EscrowToken_test.cpp +++ b/src/test/app/EscrowToken_test.cpp @@ -559,12 +559,15 @@ struct EscrowToken_test : public beast::unit_test::suite env(pay(gw, bob, USD(1))); env.close(); + bool const largeMantissa = features[featureSingleAssetVault] || + features[featureLendingProtocol]; + // alice cannot create escrow for 1/10 iou - precision loss env(escrow::create(alice, bob, USD(1)), escrow::condition(escrow::cb1), escrow::finish_time(env.now() + 1s), fee(baseFee * 150), - ter(tecPRECISION_LOSS)); + ter(largeMantissa ? (TER)tesSUCCESS : (TER)tecPRECISION_LOSS)); env.close(); } } @@ -2076,12 +2079,15 @@ struct EscrowToken_test : public beast::unit_test::suite env(pay(gw, bob, USD(1))); env.close(); + bool const largeMantissa = features[featureSingleAssetVault] || + features[featureLendingProtocol]; + // alice cannot create escrow for 1/10 iou - precision loss env(escrow::create(alice, bob, USD(1)), escrow::condition(escrow::cb1), escrow::finish_time(env.now() + 1s), fee(baseFee * 150), - ter(tecPRECISION_LOSS)); + ter(largeMantissa ? (TER)tesSUCCESS : (TER)tecPRECISION_LOSS)); env.close(); auto const seq1 = env.seq(alice); @@ -3924,9 +3930,13 @@ public: { using namespace test::jtx; FeatureBitset const all{testable_amendments()}; - testIOUWithFeats(all); - testMPTWithFeats(all); - testMPTWithFeats(all - fixTokenEscrowV1); + for (FeatureBitset const& feats : + {all - featureSingleAssetVault - featureLendingProtocol, all}) + { + testIOUWithFeats(feats); + testMPTWithFeats(feats); + testMPTWithFeats(feats - fixTokenEscrowV1); + } } }; diff --git a/src/test/app/LendingHelpers_test.cpp b/src/test/app/LendingHelpers_test.cpp index 50efe0ebe3..55fffad6b0 100644 --- a/src/test/app/LendingHelpers_test.cpp +++ b/src/test/app/LendingHelpers_test.cpp @@ -104,7 +104,7 @@ class LendingHelpers_test : public beast::unit_test::suite .name = "Multiple payments remaining", .periodicRate = Number{5, -2}, .paymentsRemaining = 3, - .expectedPaymentFactor = Number{367208564631245, -15}, + .expectedPaymentFactor = Number{3672085646312450436, -19}, }, // from calc { .name = "Zero payments remaining", @@ -172,7 +172,7 @@ class LendingHelpers_test : public beast::unit_test::suite loanPeriodicRate(TenthBips32(100'000), 30 * 24 * 60 * 60), .paymentsRemaining = 3, .expectedPeriodicPayment = - Number{3895690663961231, -13}, // from calc + Number{389569066396123265, -15}, // from calc }, }; @@ -229,7 +229,8 @@ class LendingHelpers_test : public beast::unit_test::suite }, { .name = "Standard case", - .periodicPayment = Number{3895690663961231, -13}, // from calc + .periodicPayment = + Number{389569066396123265, -15}, // from calc .periodicRate = loanPeriodicRate(TenthBips32(100'000), 30 * 24 * 60 * 60), .paymentsRemaining = 3, @@ -426,7 +427,7 @@ class LendingHelpers_test : public beast::unit_test::suite NetClock::time_point{NetClock::duration{3'000}}, .nextPaymentDueDate = 2'000, .expectedLateInterest = - Number{3170979198376459, -17}, // from calc + Number{317097919837645865, -19}, // from calc }, }; @@ -519,7 +520,7 @@ class LendingHelpers_test : public beast::unit_test::suite .prevPaymentDate = 2'000, .paymentInterval = 30 * 24 * 60 * 60, .expectedAccruedInterest = - Number{1929012345679012, -17}, // from calc + Number{1929012345679012346, -20}, // from calc }, }; @@ -587,7 +588,7 @@ class LendingHelpers_test : public beast::unit_test::suite .startDate = 1'000, .closeInterestRate = TenthBips32{0}, .expectedFullPaymentInterest = - Number{1929012345679012, -17}, // from calc + Number{1929012345679012346, -20}, // from calc }, { .name = "Standard case", @@ -600,7 +601,7 @@ class LendingHelpers_test : public beast::unit_test::suite .startDate = 1'000, .closeInterestRate = TenthBips32{10'000}, .expectedFullPaymentInterest = - Number{1000192901234568, -13}, // from calc + Number{1000192901234567901, -16}, // from calc }, }; diff --git a/src/test/app/LoanBroker_test.cpp b/src/test/app/LoanBroker_test.cpp index 93be28e9e9..769ed40321 100644 --- a/src/test/app/LoanBroker_test.cpp +++ b/src/test/app/LoanBroker_test.cpp @@ -752,30 +752,36 @@ class LoanBroker_test : public beast::unit_test::suite // LoanBrokerID env(set(alice, vault.vaultID), loanBrokerID(nextKeylet.key), - ter(tecNO_ENTRY)); + ter(tecNO_ENTRY), + THISLINE); // VaultID env(set(alice, nextKeylet.key), loanBrokerID(broker->key()), - ter(tecNO_PERMISSION)); + ter(tecNO_ENTRY), + THISLINE); // Owner env(set(evan, vault.vaultID), loanBrokerID(broker->key()), - ter(tecNO_PERMISSION)); + ter(tecNO_PERMISSION), + THISLINE); // ManagementFeeRate env(set(alice, vault.vaultID), loanBrokerID(broker->key()), managementFeeRate(maxManagementFeeRate), - ter(temINVALID)); + ter(temINVALID), + THISLINE); // CoverRateMinimum env(set(alice, vault.vaultID), loanBrokerID(broker->key()), coverRateMinimum(maxManagementFeeRate), - ter(temINVALID)); + ter(temINVALID), + THISLINE); // CoverRateLiquidation env(set(alice, vault.vaultID), loanBrokerID(broker->key()), coverRateLiquidation(maxManagementFeeRate), - ter(temINVALID)); + ter(temINVALID), + THISLINE); // fields that can be changed testData = "Test Data 1234"; @@ -783,23 +789,43 @@ class LoanBroker_test : public beast::unit_test::suite env(set(alice, vault.vaultID), loanBrokerID(broker->key()), data(std::string(maxDataPayloadLength + 1, 'W')), - ter(temINVALID)); + ter(temINVALID), + THISLINE); // Bad debt maximum env(set(alice, vault.vaultID), loanBrokerID(broker->key()), debtMaximum(Number(-175, -1)), - ter(temINVALID)); + ter(temINVALID), + THISLINE); + Number debtMax{175, -1}; + if (vault.asset.integral()) + { + env(set(alice, vault.vaultID), + loanBrokerID(broker->key()), + data(testData), + debtMaximum(debtMax), + ter(tecPRECISION_LOSS), + THISLINE); + roundToAsset(vault.asset, debtMax); + } // Data & Debt maximum env(set(alice, vault.vaultID), loanBrokerID(broker->key()), data(testData), - debtMaximum(Number(175, -1))); + debtMaximum(debtMax), + THISLINE); }, [&](SLE::const_ref broker) { // Check the updated fields BEAST_EXPECT(checkVL(broker->at(sfData), testData)); - BEAST_EXPECT(broker->at(sfDebtMaximum) == Number(175, -1)); + Number const expected = + STAmount{vault.asset, Number(175, -1)}; + auto const actual = broker->at(sfDebtMaximum); + BEAST_EXPECTS( + actual == expected, + "Expected: " + to_string(expected) + + ", Actual: " + to_string(actual)); }); lifecycle( @@ -1457,9 +1483,14 @@ class LoanBroker_test : public beast::unit_test::suite env.close(); PrettyAsset const asset = [&]() { - env(trust(alice, issuer["IOU"](1'000'000)), THISLINE); + MPTTester mptt{env, issuer, mptInitNoFund}; + mptt.create( + {.flags = tfMPTCanClawback | tfMPTCanTransfer | tfMPTCanLock}); env.close(); - return PrettyAsset(issuer["IOU"]); + PrettyAsset const mptAsset = mptt["MPT"]; + mptt.authorize({.account = alice}); + env.close(); + return mptAsset; }(); env(pay(issuer, alice, asset(100'000)), THISLINE); @@ -1512,6 +1543,40 @@ class LoanBroker_test : public beast::unit_test::suite tx2[sfDebtMaximum] = 0; env(tx2, ter(tesSUCCESS), THISLINE); + + tx2[sfDebtMaximum] = Json::Value::maxInt; + env(tx2, ter(tesSUCCESS), THISLINE); + + { + auto const dm = power(2, 64) - 1; + BEAST_EXPECT(dm > maxMPTokenAmount); + tx2[sfDebtMaximum] = dm; + env(tx2, ter(temINVALID), THISLINE); + } + + { + auto const dm = power(2, 63) - 1; + BEAST_EXPECTS(dm > maxMPTokenAmount, to_string(dm)); + tx2[sfDebtMaximum] = dm; + env(tx2, ter(temINVALID), THISLINE); + } + + { + auto const dm = power(2, 63) - 3; + BEAST_EXPECTS(dm == maxMPTokenAmount, to_string(dm)); + tx2[sfDebtMaximum] = dm; + env(tx2, ter(tesSUCCESS), THISLINE); + } + + { + auto const dm = 2 * (power(2, 62) - 1) + 1; + BEAST_EXPECTS(dm == maxMPTokenAmount, to_string(dm)); + tx2[sfDebtMaximum] = dm; + env(tx2, ter(tesSUCCESS), THISLINE); + } + + tx2[sfDebtMaximum] = Number{9223372036854775807, 0}; + env(tx2, ter(tesSUCCESS), THISLINE); } void diff --git a/src/test/app/Loan_test.cpp b/src/test/app/Loan_test.cpp index e4f5360043..e9780211de 100644 --- a/src/test/app/Loan_test.cpp +++ b/src/test/app/Loan_test.cpp @@ -352,8 +352,14 @@ protected: env.balance(account, broker.asset) - (balanceBefore - balanceChangeAmount), borrowerScale); - env.test.BEAST_EXPECT( - roundToScale(difference, loanScale) >= beast::zero); + env.test.expect( + roundToScale(difference, loanScale) >= beast::zero, + "Balance before: " + to_string(balanceBefore.value()) + + ", expected change: " + to_string(balanceChangeAmount) + + ", difference (balance after - expected): " + + to_string(difference), + __FILE__, + __LINE__); } } @@ -2396,7 +2402,7 @@ protected: interval * Number(12, -2) / secondsInYear; BEAST_EXPECT( periodicRate == - Number(2283105022831050, -21, Number::unchecked{})); + Number(2283105022831050228ULL, -24, Number::normalized{})); STAmount const principalOutstanding{ broker.asset, state.principalOutstanding}; STAmount const accruedInterest{ @@ -2449,6 +2455,10 @@ protected: getCurrentState(env, broker, loanKeylet, verifyLoanStatus); env.close(); + BEAST_EXPECT( + STAmount(broker.asset, state.periodicPayment) == + broker.asset(Number(8333457002039338267, -17))); + // Make all the payments in one transaction // service fee is 2 auto const startingPayments = state.paymentRemaining; @@ -2456,15 +2466,28 @@ protected: NumberRoundModeGuard mg(Number::upward); auto const rawPayoff = startingPayments * (state.periodicPayment + broker.asset(2).value()); - STAmount const payoffAmount{broker.asset, rawPayoff}; + STAmount payoffAmount{broker.asset, rawPayoff}; BEAST_EXPECTS( payoffAmount == - broker.asset(Number(1024014840139457, -12)), + broker.asset(Number(1024014840244721, -12)), to_string(payoffAmount)); BEAST_EXPECT(payoffAmount > state.principalOutstanding); + + payoffAmount = roundToScale(payoffAmount, state.loanScale); + return payoffAmount; }(); + auto const totalPayoffValue = state.totalValue + + startingPayments * broker.asset(2).value(); + STAmount const totalPayoffAmount{ + broker.asset, totalPayoffValue}; + + BEAST_EXPECTS( + totalPayoffAmount == payoffAmount, + "Payoff amount: " + to_string(payoffAmount) + + ". Total Value: " + to_string(totalPayoffAmount)); + singlePayment( loanKeylet, verifyLoanStatus, @@ -2633,7 +2656,7 @@ protected: interval * Number(12, -2) / secondsInYear; BEAST_EXPECT( periodicRate == - Number(2283105022831050, -21, Number::unchecked{})); + Number(2283105022831050228, -24, Number::normalized{})); STAmount const roundedPeriodicPayment{ broker.asset, roundPeriodicPayment( @@ -2651,7 +2674,7 @@ protected: roundedPeriodicPayment == roundToScale( broker.asset( - Number(8333457001162141, -14), Number::upward), + Number(8333457002039338267, -17), Number::upward), state.loanScale, Number::upward)); // 83334570.01162141 @@ -2666,7 +2689,7 @@ protected: totalDue == roundToScale( broker.asset( - Number(8533457001162141, -14), Number::upward), + Number(8533457002039338267, -17), Number::upward), state.loanScale, Number::upward)); @@ -2702,7 +2725,7 @@ protected: transactionAmount == roundToScale( broker.asset( - Number(9533457001162141, -14), Number::upward), + Number(9533457002039400, -14), Number::upward), state.loanScale, Number::upward)); @@ -2735,9 +2758,15 @@ protected: state.paymentRemaining, broker.params.managementFeeRate); - BEAST_EXPECT( - paymentComponents.trackedValueDelta <= - roundedPeriodicPayment); + BEAST_EXPECTS( + paymentComponents.specialCase == + detail::PaymentSpecialCase::final || + paymentComponents.trackedValueDelta <= + roundedPeriodicPayment, + "Delta: " + + to_string(paymentComponents.trackedValueDelta) + + ", periodic payment: " + + to_string(roundedPeriodicPayment)); xrpl::LoanState const nextTrueState = computeTheoreticalLoanState( @@ -2792,8 +2821,10 @@ protected: paymentComponents.trackedInterestPart() + paymentComponents.trackedManagementFeeDelta); BEAST_EXPECT( + paymentComponents.specialCase == + detail::PaymentSpecialCase::final || paymentComponents.trackedValueDelta <= - roundedPeriodicPayment); + roundedPeriodicPayment); BEAST_EXPECT( state.paymentRemaining < 12 || @@ -2804,7 +2835,7 @@ protected: Number::upward) == roundToScale( broker.asset( - Number(8333228695260180, -14), + Number(8333228691531218890, -17), Number::upward), state.loanScale, Number::upward)); @@ -3689,7 +3720,7 @@ protected: env(pay(issuer, borrower, mptAsset(10'000))); env.close(); - std::array const assets{xrpAsset, mptAsset, iouAsset}; + std::array const assets{iouAsset, xrpAsset, mptAsset}; // Create vaults and loan brokers std::vector brokers; @@ -5041,7 +5072,6 @@ protected: auto const loanSetFee = fee(env.current()->fees().base * 2); Number const principalRequest{1, 3}; - auto const startDate = env.now() + 60s; auto createJson = env.json( set(borrower, broker.brokerID, principalRequest), @@ -5065,13 +5095,13 @@ protected: auto const keylet = keylet::loan(broker.brokerID, loanSequence); createJson = env.json(createJson, sig(sfCounterpartySignature, lender)); - env(createJson, ter(tecPRECISION_LOSS), THISLINE); - env.close(startDate); + env(createJson, THISLINE); + env.close(); auto loanPayTx = env.json( pay(borrower, keylet.key, STAmount{broker.asset, Number{}})); loanPayTx["Amount"]["value"] = "0.000281284125490196"; - env(loanPayTx, ter(tecNO_ENTRY)); + env(loanPayTx, ter(tecINSUFFICIENT_PAYMENT), THISLINE); env.close(); } @@ -5640,21 +5670,26 @@ protected: BEAST_EXPECT(beforeState.periodicPayment > 0); // pay all but the last payment - Number const payment = beforeState.periodicPayment * (total - 1); - XRPAmount const payFee{ - baseFee * ((total - 1) / loanPaymentsPerFeeIncrement + 1)}; - auto loanPayTx = env.json( - pay(borrower, keylet.key, STAmount{broker.asset, payment}), - fee(payFee)); - env(loanPayTx, ter(tesSUCCESS)); - env.close(); + { + NumberRoundModeGuard mg{Number::upward}; + Number const payment = + beforeState.periodicPayment * (total - 1); + XRPAmount const payFee{ + baseFee * ((total - 1) / loanPaymentsPerFeeIncrement + 1)}; + STAmount const paymentAmount = roundToScale( + STAmount{broker.asset, payment}, beforeState.loanScale); + auto loanPayTx = env.json( + pay(borrower, keylet.key, paymentAmount), fee(payFee)); + env(loanPayTx, ter(tesSUCCESS)); + env.close(); + } // The loan is on the last payment auto const afterState = getCurrentState(env, broker, keylet); + BEAST_EXPECT(afterState.paymentRemaining == 1); BEAST_EXPECT(afterState.nextPaymentDate == maxTime - grace); BEAST_EXPECT( afterState.previousPaymentDate == maxTime - grace - interval); - BEAST_EXPECT(afterState.paymentRemaining == 1); } } diff --git a/src/test/app/MPToken_test.cpp b/src/test/app/MPToken_test.cpp index 747f78ef6b..65cb753755 100644 --- a/src/test/app/MPToken_test.cpp +++ b/src/test/app/MPToken_test.cpp @@ -1594,7 +1594,7 @@ class MPToken_test : public beast::unit_test::suite jv[jss::secret] = alice.name(); jv[jss::tx_json] = pay(alice, bob, mpt); jv[jss::tx_json][jss::Amount][jss::value] = - to_string(maxMPTokenAmount + 1); + std::to_string(maxMPTokenAmount + 1); auto const jrr = env.rpc("json", "submit", to_string(jv)); BEAST_EXPECT(jrr[jss::result][jss::error] == "invalidParams"); } @@ -2474,7 +2474,7 @@ class MPToken_test : public beast::unit_test::suite alice.name(), makeMptID(env.seq(alice), alice)); Json::Value jv = claw(alice, mpt(1), bob); - jv[jss::Amount][jss::value] = to_string(maxMPTokenAmount + 1); + jv[jss::Amount][jss::value] = std::to_string(maxMPTokenAmount + 1); Json::Value jv1; jv1[jss::secret] = alice.name(); jv1[jss::tx_json] = jv; diff --git a/src/test/basics/IOUAmount_test.cpp b/src/test/basics/IOUAmount_test.cpp index 305f2c83a1..d299f439d4 100644 --- a/src/test/basics/IOUAmount_test.cpp +++ b/src/test/basics/IOUAmount_test.cpp @@ -141,15 +141,28 @@ public: { testcase("IOU strings"); - BEAST_EXPECT(to_string(IOUAmount(-2, 0)) == "-2"); - BEAST_EXPECT(to_string(IOUAmount(0, 0)) == "0"); - BEAST_EXPECT(to_string(IOUAmount(2, 0)) == "2"); - BEAST_EXPECT(to_string(IOUAmount(25, -3)) == "0.025"); - BEAST_EXPECT(to_string(IOUAmount(-25, -3)) == "-0.025"); - BEAST_EXPECT(to_string(IOUAmount(25, 1)) == "250"); - BEAST_EXPECT(to_string(IOUAmount(-25, 1)) == "-250"); - BEAST_EXPECT(to_string(IOUAmount(2, 20)) == "2000000000000000e5"); - BEAST_EXPECT(to_string(IOUAmount(-2, -20)) == "-2000000000000000e-35"); + auto test = [this](IOUAmount const& n, std::string const& expected) { + auto const result = to_string(n); + std::stringstream ss; + ss << "to_string(" << result << "). Expected: " << expected; + BEAST_EXPECTS(result == expected, ss.str()); + }; + + for (auto const mantissaSize : + {MantissaRange::small, MantissaRange::large}) + { + NumberMantissaScaleGuard mg(mantissaSize); + + test(IOUAmount(-2, 0), "-2"); + test(IOUAmount(0, 0), "0"); + test(IOUAmount(2, 0), "2"); + test(IOUAmount(25, -3), "0.025"); + test(IOUAmount(-25, -3), "-0.025"); + test(IOUAmount(25, 1), "250"); + test(IOUAmount(-25, 1), "-250"); + test(IOUAmount(2, 20), "2e20"); + test(IOUAmount(-2, -20), "-2e-20"); + } } void diff --git a/src/test/basics/Number_test.cpp b/src/test/basics/Number_test.cpp index b7c5ee45b7..1fa5ae6e8f 100644 --- a/src/test/basics/Number_test.cpp +++ b/src/test/basics/Number_test.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include @@ -14,46 +15,84 @@ public: void testZero() { - testcase("zero"); + testcase << "zero " << to_string(Number::getMantissaScale()); - Number const z{0, 0}; + for (Number const& z : {Number{0, 0}, Number{0}}) + { + BEAST_EXPECT(z.mantissa() == 0); + BEAST_EXPECT(z.exponent() == Number{}.exponent()); - BEAST_EXPECT(z.mantissa() == 0); - BEAST_EXPECT(z.exponent() == Number{}.exponent()); - - BEAST_EXPECT((z + z) == z); - BEAST_EXPECT((z - z) == z); - BEAST_EXPECT(z == -z); + BEAST_EXPECT((z + z) == z); + BEAST_EXPECT((z - z) == z); + BEAST_EXPECT(z == -z); + } } void test_limits() { - testcase("test_limits"); + auto const scale = Number::getMantissaScale(); + testcase << "test_limits " << to_string(scale); bool caught = false; + auto const minMantissa = Number::minMantissa(); try { - Number x{10'000'000'000'000'000, 32768}; + Number x = + Number{false, minMantissa * 10, 32768, Number::normalized{}}; } catch (std::overflow_error const&) { caught = true; } BEAST_EXPECT(caught); - Number x{10'000'000'000'000'000, 32767}; - BEAST_EXPECT((x == Number{1'000'000'000'000'000, 32768})); - Number z{1'000'000'000'000'000, -32769}; - BEAST_EXPECT(z == Number{}); - Number y{1'000'000'000'000'001'500, 32000}; - BEAST_EXPECT((y == Number{1'000'000'000'000'002, 32003})); - Number m{std::numeric_limits::min()}; - BEAST_EXPECT((m == Number{-9'223'372'036'854'776, 3})); - Number M{std::numeric_limits::max()}; - BEAST_EXPECT((M == Number{9'223'372'036'854'776, 3})); + + auto test = [this](auto const& x, auto const& y, int line) { + auto const result = x == y; + std::stringstream ss; + ss << x << " == " << y << " -> " << (result ? "true" : "false"); + expect(result, ss.str(), __FILE__, line); + }; + + test( + Number{false, minMantissa * 10, 32767, Number::normalized{}}, + Number{false, minMantissa, 32768, Number::normalized{}}, + __LINE__); + test( + Number{false, minMantissa, -32769, Number::normalized{}}, + Number{}, + __LINE__); + test( + Number{false, minMantissa, 32000, Number::normalized{}} * 1'000 + + Number{false, 1'500, 32000, Number::normalized{}}, + Number{false, minMantissa + 2, 32003, Number::normalized{}}, + __LINE__); + // 9,223,372,036,854,775,808 + + test( + Number{std::numeric_limits::min()}, + scale == MantissaRange::small + ? Number{-9'223'372'036'854'776, 3} + : Number{true, 9'223'372'036'854'775'808ULL, 0, Number::normalized{}}, + __LINE__); + test( + Number{std::numeric_limits::min() + 1}, + scale == MantissaRange::small ? Number{-9'223'372'036'854'776, 3} + : Number{-9'223'372'036'854'775'807}, + __LINE__); + test( + Number{std::numeric_limits::max()}, + Number{ + scale == MantissaRange::small + ? 9'223'372'036'854'776 + : std::numeric_limits::max(), + 18 - Number::mantissaLog()}, + __LINE__); caught = false; try { - Number q{99'999'999'999'999'999, 32767}; + [[maybe_unused]] + Number q = + Number{false, minMantissa, 32767, Number::normalized{}} * 100; } catch (std::overflow_error const&) { @@ -65,76 +104,307 @@ public: void test_add() { - testcase("test_add"); + auto const scale = Number::getMantissaScale(); + testcase << "test_add " << to_string(scale); + using Case = std::tuple; - Case c[]{ - {Number{1'000'000'000'000'000, -15}, - Number{6'555'555'555'555'555, -29}, - Number{1'000'000'000'000'066, -15}}, - {Number{-1'000'000'000'000'000, -15}, - Number{-6'555'555'555'555'555, -29}, - Number{-1'000'000'000'000'066, -15}}, - {Number{-1'000'000'000'000'000, -15}, - Number{6'555'555'555'555'555, -29}, - Number{-9'999'999'999'999'344, -16}}, - {Number{-6'555'555'555'555'555, -29}, - Number{1'000'000'000'000'000, -15}, - Number{9'999'999'999'999'344, -16}}, - {Number{}, Number{5}, Number{5}}, - {Number{5'555'555'555'555'555, -32768}, - Number{-5'555'555'555'555'554, -32768}, - Number{0}}, - {Number{-9'999'999'999'999'999, -31}, - Number{1'000'000'000'000'000, -15}, - Number{9'999'999'999'999'990, -16}}}; - for (auto const& [x, y, z] : c) - BEAST_EXPECT(x + y == z); - bool caught = false; - try + auto const cSmall = std::to_array( + {{Number{1'000'000'000'000'000, -15}, + Number{6'555'555'555'555'555, -29}, + Number{1'000'000'000'000'066, -15}}, + {Number{-1'000'000'000'000'000, -15}, + Number{-6'555'555'555'555'555, -29}, + Number{-1'000'000'000'000'066, -15}}, + {Number{-1'000'000'000'000'000, -15}, + Number{6'555'555'555'555'555, -29}, + Number{-9'999'999'999'999'344, -16}}, + {Number{-6'555'555'555'555'555, -29}, + Number{1'000'000'000'000'000, -15}, + Number{9'999'999'999'999'344, -16}}, + {Number{}, Number{5}, Number{5}}, + {Number{5}, Number{}, Number{5}}, + {Number{5'555'555'555'555'555, -32768}, + Number{-5'555'555'555'555'554, -32768}, + Number{0}}, + {Number{-9'999'999'999'999'999, -31}, + Number{1'000'000'000'000'000, -15}, + Number{9'999'999'999'999'990, -16}}}); + auto const cLarge = std::to_array( + // Note that items with extremely large mantissas need to be + // calculated, because otherwise they overflow uint64. Items from C + // with larger mantissa + { + {Number{1'000'000'000'000'000, -15}, + Number{6'555'555'555'555'555, -29}, + Number{1'000'000'000'000'065'556, -18}}, + {Number{-1'000'000'000'000'000, -15}, + Number{-6'555'555'555'555'555, -29}, + Number{-1'000'000'000'000'065'556, -18}}, + {Number{-1'000'000'000'000'000, -15}, + Number{6'555'555'555'555'555, -29}, + Number{ + true, + 9'999'999'999'999'344'444ULL, + -19, + Number::normalized{}}}, + {Number{-6'555'555'555'555'555, -29}, + Number{1'000'000'000'000'000, -15}, + Number{ + false, + 9'999'999'999'999'344'444ULL, + -19, + Number::normalized{}}}, + {Number{}, Number{5}, Number{5}}, + {Number{5}, Number{}, Number{5}}, + {Number{5'555'555'555'555'555'000, -32768}, + Number{-5'555'555'555'555'554'000, -32768}, + Number{0}}, + {Number{-9'999'999'999'999'999, -31}, + Number{1'000'000'000'000'000, -15}, + Number{9'999'999'999'999'990, -16}}, + // Items from cSmall expanded for the larger mantissa + {Number{1'000'000'000'000'000'000, -18}, + Number{6'555'555'555'555'555'555, -35}, + Number{1'000'000'000'000'000'066, -18}}, + {Number{-1'000'000'000'000'000'000, -18}, + Number{-6'555'555'555'555'555'555, -35}, + Number{-1'000'000'000'000'000'066, -18}}, + {Number{-1'000'000'000'000'000'000, -18}, + Number{6'555'555'555'555'555'555, -35}, + Number{ + true, + 9'999'999'999'999'999'344ULL, + -19, + Number::normalized{}}}, + {Number{-6'555'555'555'555'555'555, -35}, + Number{1'000'000'000'000'000'000, -18}, + Number{ + false, + 9'999'999'999'999'999'344ULL, + -19, + Number::normalized{}}}, + {Number{}, Number{5}, Number{5}}, + {Number{5'555'555'555'555'555'555, -32768}, + Number{-5'555'555'555'555'555'554, -32768}, + Number{0}}, + {Number{ + true, + 9'999'999'999'999'999'999ULL, + -37, + Number::normalized{}}, + Number{1'000'000'000'000'000'000, -18}, + Number{ + false, + 9'999'999'999'999'999'990ULL, + -19, + Number::normalized{}}}, + {Number{Number::maxRep}, + Number{6, -1}, + Number{Number::maxRep / 10, 1}}, + {Number{Number::maxRep - 1}, + Number{1, 0}, + Number{Number::maxRep}}, + // Test extremes + { + // Each Number operand rounds up, so the actual mantissa is + // minMantissa + Number{ + false, + 9'999'999'999'999'999'999ULL, + 0, + Number::normalized{}}, + Number{ + false, + 9'999'999'999'999'999'999ULL, + 0, + Number::normalized{}}, + Number{2, 19}, + }, + { + // Does not round. Mantissas are going to be > maxRep, so if + // added together as uint64_t's, the result will overflow. + // With addition using uint128_t, there's no problem. After + // normalizing, the resulting mantissa ends up less than + // maxRep. + Number{ + false, + 9'999'999'999'999'999'990ULL, + 0, + Number::normalized{}}, + Number{ + false, + 9'999'999'999'999'999'990ULL, + 0, + Number::normalized{}}, + Number{ + false, + 1'999'999'999'999'999'998ULL, + 1, + Number::normalized{}}, + }, + }); + auto test = [this](auto const& c) { + for (auto const& [x, y, z] : c) + { + auto const result = x + y; + std::stringstream ss; + ss << x << " + " << y << " = " << result << ". Expected: " << z; + BEAST_EXPECTS(result == z, ss.str()); + } + }; + if (scale == MantissaRange::small) + test(cSmall); + else + test(cLarge); { - Number{9'999'999'999'999'999, 32768} + - Number{5'000'000'000'000'000, 32767}; + bool caught = false; + try + { + Number{ + false, Number::maxMantissa(), 32768, Number::normalized{}} + + Number{ + false, + Number::minMantissa(), + 32767, + Number::normalized{}} * + 5; + } + catch (std::overflow_error const&) + { + caught = true; + } + BEAST_EXPECT(caught); } - catch (std::overflow_error const&) - { - caught = true; - } - BEAST_EXPECT(caught); } void test_sub() { - testcase("test_sub"); + auto const scale = Number::getMantissaScale(); + testcase << "test_sub " << to_string(scale); + using Case = std::tuple; - Case c[]{ - {Number{1'000'000'000'000'000, -15}, - Number{6'555'555'555'555'555, -29}, - Number{9'999'999'999'999'344, -16}}, - {Number{6'555'555'555'555'555, -29}, - Number{1'000'000'000'000'000, -15}, - Number{-9'999'999'999'999'344, -16}}, - {Number{1'000'000'000'000'000, -15}, - Number{1'000'000'000'000'000, -15}, - Number{0}}, - {Number{1'000'000'000'000'000, -15}, - Number{1'000'000'000'000'001, -15}, - Number{-1'000'000'000'000'000, -30}}, - {Number{1'000'000'000'000'001, -15}, - Number{1'000'000'000'000'000, -15}, - Number{1'000'000'000'000'000, -30}}}; - for (auto const& [x, y, z] : c) - BEAST_EXPECT(x - y == z); + auto const cSmall = std::to_array( + {{Number{1'000'000'000'000'000, -15}, + Number{6'555'555'555'555'555, -29}, + Number{9'999'999'999'999'344, -16}}, + {Number{6'555'555'555'555'555, -29}, + Number{1'000'000'000'000'000, -15}, + Number{-9'999'999'999'999'344, -16}}, + {Number{1'000'000'000'000'000, -15}, + Number{1'000'000'000'000'000, -15}, + Number{0}}, + {Number{1'000'000'000'000'000, -15}, + Number{1'000'000'000'000'001, -15}, + Number{-1'000'000'000'000'000, -30}}, + {Number{1'000'000'000'000'001, -15}, + Number{1'000'000'000'000'000, -15}, + Number{1'000'000'000'000'000, -30}}}); + auto const cLarge = std::to_array( + // Note that items with extremely large mantissas need to be + // calculated, because otherwise they overflow uint64. Items from C + // with larger mantissa + { + {Number{1'000'000'000'000'000, -15}, + Number{6'555'555'555'555'555, -29}, + Number{ + false, + 9'999'999'999'999'344'444ULL, + -19, + Number::normalized{}}}, + {Number{6'555'555'555'555'555, -29}, + Number{1'000'000'000'000'000, -15}, + Number{ + true, + 9'999'999'999'999'344'444ULL, + -19, + Number::normalized{}}}, + {Number{1'000'000'000'000'000, -15}, + Number{1'000'000'000'000'000, -15}, + Number{0}}, + {Number{1'000'000'000'000'000, -15}, + Number{1'000'000'000'000'001, -15}, + Number{-1'000'000'000'000'000, -30}}, + {Number{1'000'000'000'000'001, -15}, + Number{1'000'000'000'000'000, -15}, + Number{1'000'000'000'000'000, -30}}, + // Items from cSmall expanded for the larger mantissa + {Number{1'000'000'000'000'000'000, -18}, + Number{6'555'555'555'555'555'555, -32}, + Number{ + false, + 9'999'999'999'999'344'444ULL, + -19, + Number::normalized{}}}, + {Number{6'555'555'555'555'555'555, -32}, + Number{1'000'000'000'000'000'000, -18}, + Number{ + true, + 9'999'999'999'999'344'444ULL, + -19, + Number::normalized{}}}, + {Number{1'000'000'000'000'000'000, -18}, + Number{1'000'000'000'000'000'000, -18}, + Number{0}}, + {Number{1'000'000'000'000'000'000, -18}, + Number{1'000'000'000'000'000'001, -18}, + Number{-1'000'000'000'000'000'000, -36}}, + {Number{1'000'000'000'000'000'001, -18}, + Number{1'000'000'000'000'000'000, -18}, + Number{1'000'000'000'000'000'000, -36}}, + {Number{Number::maxRep}, + Number{6, -1}, + Number{Number::maxRep - 1}}, + {Number{false, Number::maxRep + 1, 0, Number::normalized{}}, + Number{1, 0}, + Number{Number::maxRep / 10 + 1, 1}}, + {Number{false, Number::maxRep + 1, 0, Number::normalized{}}, + Number{3, 0}, + Number{Number::maxRep}}, + {power(2, 63), Number{3, 0}, Number{Number::maxRep}}, + }); + auto test = [this](auto const& c) { + for (auto const& [x, y, z] : c) + { + auto const result = x - y; + std::stringstream ss; + ss << x << " - " << y << " = " << result << ". Expected: " << z; + BEAST_EXPECTS(result == z, ss.str()); + } + }; + if (scale == MantissaRange::small) + test(cSmall); + else + test(cLarge); } void test_mul() { - testcase("test_mul"); + auto const scale = Number::getMantissaScale(); + testcase << "test_mul " << to_string(scale); + using Case = std::tuple; + auto test = [this](auto const& c) { + for (auto const& [x, y, z] : c) + { + auto const result = x * y; + std::stringstream ss; + ss << x << " * " << y << " = " << result << ". Expected: " << z; + BEAST_EXPECTS(result == z, ss.str()); + } + }; + auto tests = [&](auto const& cSmall, auto const& cLarge) { + if (scale == MantissaRange::small) + test(cSmall); + else + test(cLarge); + }; + auto const maxMantissa = Number::maxMantissa(); + saveNumberRoundMode save{Number::setround(Number::to_nearest)}; { - Case c[]{ + auto const cSmall = std::to_array({ {Number{7}, Number{8}, Number{56}}, {Number{1414213562373095, -15}, Number{1414213562373095, -15}, @@ -150,166 +420,520 @@ public: Number{1000000000000000, -14}}, {Number{1000000000000000, -32768}, Number{1000000000000000, -32768}, - Number{0}}}; - for (auto const& [x, y, z] : c) - BEAST_EXPECT(x * y == z); + Number{0}}, + // Maximum mantissa range + {Number{9'999'999'999'999'999, 0}, + Number{9'999'999'999'999'999, 0}, + Number{9'999'999'999'999'998, 16}}, + }); + auto const cLarge = std::to_array({ + // Note that items with extremely large mantissas need to be + // calculated, because otherwise they overflow uint64. Items + // from C with larger mantissa + {Number{7}, Number{8}, Number{56}}, + {Number{1414213562373095, -15}, + Number{1414213562373095, -15}, + Number{1999999999999999862, -18}}, + {Number{-1414213562373095, -15}, + Number{1414213562373095, -15}, + Number{-1999999999999999862, -18}}, + {Number{-1414213562373095, -15}, + Number{-1414213562373095, -15}, + Number{1999999999999999862, -18}}, + {Number{3214285714285706, -15}, + Number{3111111111111119, -15}, + Number{ + false, + 9'999'999'999'999'999'579ULL, + -18, + Number::normalized{}}}, + {Number{1000000000000000000, -32768}, + Number{1000000000000000000, -32768}, + Number{0}}, + // Items from cSmall expanded for the larger mantissa, + // except duplicates. Sadly, it looks like sqrt(2)^2 != 2 + // with higher precision + {Number{1414213562373095049, -18}, + Number{1414213562373095049, -18}, + Number{2000000000000000001, -18}}, + {Number{-1414213562373095048, -18}, + Number{1414213562373095048, -18}, + Number{-1999999999999999998, -18}}, + {Number{-1414213562373095048, -18}, + Number{-1414213562373095049, -18}, + Number{1999999999999999999, -18}}, + {Number{3214285714285714278, -18}, + Number{3111111111111111119, -18}, + Number{10, 0}}, + // Maximum mantissa range - rounds up to 1e19 + {Number{false, maxMantissa, 0, Number::normalized{}}, + Number{false, maxMantissa, 0, Number::normalized{}}, + Number{1, 38}}, + // Maximum int64 range + {Number{Number::maxRep, 0}, + Number{Number::maxRep, 0}, + Number{85'070'591'730'234'615'85, 19}}, + }); + tests(cSmall, cLarge); } Number::setround(Number::towards_zero); + testcase << "test_mul " << to_string(Number::getMantissaScale()) + << " towards_zero"; { - Case c[]{ - {Number{7}, Number{8}, Number{56}}, - {Number{1414213562373095, -15}, - Number{1414213562373095, -15}, - Number{1999999999999999, -15}}, - {Number{-1414213562373095, -15}, - Number{1414213562373095, -15}, - Number{-1999999999999999, -15}}, - {Number{-1414213562373095, -15}, - Number{-1414213562373095, -15}, - Number{1999999999999999, -15}}, - {Number{3214285714285706, -15}, - Number{3111111111111119, -15}, - Number{9999999999999999, -15}}, - {Number{1000000000000000, -32768}, - Number{1000000000000000, -32768}, - Number{0}}}; - for (auto const& [x, y, z] : c) - BEAST_EXPECT(x * y == z); + auto const cSmall = std::to_array( + {{Number{7}, Number{8}, Number{56}}, + {Number{1414213562373095, -15}, + Number{1414213562373095, -15}, + Number{1999999999999999, -15}}, + {Number{-1414213562373095, -15}, + Number{1414213562373095, -15}, + Number{-1999999999999999, -15}}, + {Number{-1414213562373095, -15}, + Number{-1414213562373095, -15}, + Number{1999999999999999, -15}}, + {Number{3214285714285706, -15}, + Number{3111111111111119, -15}, + Number{9999999999999999, -15}}, + {Number{1000000000000000, -32768}, + Number{1000000000000000, -32768}, + Number{0}}}); + auto const cLarge = std::to_array( + // Note that items with extremely large mantissas need to be + // calculated, because otherwise they overflow uint64. Items + // from C with larger mantissa + { + {Number{7}, Number{8}, Number{56}}, + {Number{1414213562373095, -15}, + Number{1414213562373095, -15}, + Number{1999999999999999861, -18}}, + {Number{-1414213562373095, -15}, + Number{1414213562373095, -15}, + Number{-1999999999999999861, -18}}, + {Number{-1414213562373095, -15}, + Number{-1414213562373095, -15}, + Number{1999999999999999861, -18}}, + {Number{3214285714285706, -15}, + Number{3111111111111119, -15}, + Number{ + false, + 9999999999999999579ULL, + -18, + Number::normalized{}}}, + {Number{1000000000000000000, -32768}, + Number{1000000000000000000, -32768}, + Number{0}}, + // Items from cSmall expanded for the larger mantissa, + // except duplicates. Sadly, it looks like sqrt(2)^2 != 2 + // with higher precision + {Number{1414213562373095049, -18}, + Number{1414213562373095049, -18}, + Number{2, 0}}, + {Number{-1414213562373095048, -18}, + Number{1414213562373095048, -18}, + Number{-1999999999999999997, -18}}, + {Number{-1414213562373095048, -18}, + Number{-1414213562373095049, -18}, + Number{1999999999999999999, -18}}, + {Number{3214285714285714278, -18}, + Number{3111111111111111119, -18}, + Number{10, 0}}, + // Maximum mantissa range - rounds down to maxMantissa/10e1 + // 99'999'999'999'999'999'800'000'000'000'000'000'100 + {Number{false, maxMantissa, 0, Number::normalized{}}, + Number{false, maxMantissa, 0, Number::normalized{}}, + Number{ + false, + maxMantissa / 10 - 1, + 20, + Number::normalized{}}}, + // Maximum int64 range + // 85'070'591'730'234'615'847'396'907'784'232'501'249 + {Number{Number::maxRep, 0}, + Number{Number::maxRep, 0}, + Number{85'070'591'730'234'615'84, 19}}, + }); + tests(cSmall, cLarge); } Number::setround(Number::downward); + testcase << "test_mul " << to_string(Number::getMantissaScale()) + << " downward"; { - Case c[]{ - {Number{7}, Number{8}, Number{56}}, - {Number{1414213562373095, -15}, - Number{1414213562373095, -15}, - Number{1999999999999999, -15}}, - {Number{-1414213562373095, -15}, - Number{1414213562373095, -15}, - Number{-2000000000000000, -15}}, - {Number{-1414213562373095, -15}, - Number{-1414213562373095, -15}, - Number{1999999999999999, -15}}, - {Number{3214285714285706, -15}, - Number{3111111111111119, -15}, - Number{9999999999999999, -15}}, - {Number{1000000000000000, -32768}, - Number{1000000000000000, -32768}, - Number{0}}}; - for (auto const& [x, y, z] : c) - BEAST_EXPECT(x * y == z); + auto const cSmall = std::to_array( + {{Number{7}, Number{8}, Number{56}}, + {Number{1414213562373095, -15}, + Number{1414213562373095, -15}, + Number{1999999999999999, -15}}, + {Number{-1414213562373095, -15}, + Number{1414213562373095, -15}, + Number{-2000000000000000, -15}}, + {Number{-1414213562373095, -15}, + Number{-1414213562373095, -15}, + Number{1999999999999999, -15}}, + {Number{3214285714285706, -15}, + Number{3111111111111119, -15}, + Number{9999999999999999, -15}}, + {Number{1000000000000000, -32768}, + Number{1000000000000000, -32768}, + Number{0}}}); + auto const cLarge = std::to_array( + // Note that items with extremely large mantissas need to be + // calculated, because otherwise they overflow uint64. Items + // from C with larger mantissa + { + {Number{7}, Number{8}, Number{56}}, + {Number{1414213562373095, -15}, + Number{1414213562373095, -15}, + Number{1999999999999999861, -18}}, + {Number{-1414213562373095, -15}, + Number{1414213562373095, -15}, + Number{-1999999999999999862, -18}}, + {Number{-1414213562373095, -15}, + Number{-1414213562373095, -15}, + Number{1999999999999999861, -18}}, + {Number{3214285714285706, -15}, + Number{3111111111111119, -15}, + Number{ + false, + 9'999'999'999'999'999'579ULL, + -18, + Number::normalized{}}}, + {Number{1000000000000000000, -32768}, + Number{1000000000000000000, -32768}, + Number{0}}, + // Items from cSmall expanded for the larger mantissa, + // except duplicates. Sadly, it looks like sqrt(2)^2 != 2 + // with higher precision + {Number{1414213562373095049, -18}, + Number{1414213562373095049, -18}, + Number{2, 0}}, + {Number{-1414213562373095048, -18}, + Number{1414213562373095048, -18}, + Number{-1999999999999999998, -18}}, + {Number{-1414213562373095048, -18}, + Number{-1414213562373095049, -18}, + Number{1999999999999999999, -18}}, + {Number{3214285714285714278, -18}, + Number{3111111111111111119, -18}, + Number{10, 0}}, + // Maximum mantissa range - rounds down to maxMantissa/10e1 + // 99'999'999'999'999'999'800'000'000'000'000'000'100 + {Number{false, maxMantissa, 0, Number::normalized{}}, + Number{false, maxMantissa, 0, Number::normalized{}}, + Number{ + false, + maxMantissa / 10 - 1, + 20, + Number::normalized{}}}, + // Maximum int64 range + // 85'070'591'730'234'615'847'396'907'784'232'501'249 + {Number{Number::maxRep, 0}, + Number{Number::maxRep, 0}, + Number{85'070'591'730'234'615'84, 19}}, + }); + tests(cSmall, cLarge); } Number::setround(Number::upward); + testcase << "test_mul " << to_string(Number::getMantissaScale()) + << " upward"; { - Case c[]{ - {Number{7}, Number{8}, Number{56}}, - {Number{1414213562373095, -15}, - Number{1414213562373095, -15}, - Number{2000000000000000, -15}}, - {Number{-1414213562373095, -15}, - Number{1414213562373095, -15}, - Number{-1999999999999999, -15}}, - {Number{-1414213562373095, -15}, - Number{-1414213562373095, -15}, - Number{2000000000000000, -15}}, - {Number{3214285714285706, -15}, - Number{3111111111111119, -15}, - Number{1000000000000000, -14}}, - {Number{1000000000000000, -32768}, - Number{1000000000000000, -32768}, - Number{0}}}; - for (auto const& [x, y, z] : c) - BEAST_EXPECT(x * y == z); + auto const cSmall = std::to_array( + {{Number{7}, Number{8}, Number{56}}, + {Number{1414213562373095, -15}, + Number{1414213562373095, -15}, + Number{2000000000000000, -15}}, + {Number{-1414213562373095, -15}, + Number{1414213562373095, -15}, + Number{-1999999999999999, -15}}, + {Number{-1414213562373095, -15}, + Number{-1414213562373095, -15}, + Number{2000000000000000, -15}}, + {Number{3214285714285706, -15}, + Number{3111111111111119, -15}, + Number{1000000000000000, -14}}, + {Number{1000000000000000, -32768}, + Number{1000000000000000, -32768}, + Number{0}}}); + auto const cLarge = std::to_array( + // Note that items with extremely large mantissas need to be + // calculated, because otherwise they overflow uint64. Items + // from C with larger mantissa + { + {Number{7}, Number{8}, Number{56}}, + {Number{1414213562373095, -15}, + Number{1414213562373095, -15}, + Number{1999999999999999862, -18}}, + {Number{-1414213562373095, -15}, + Number{1414213562373095, -15}, + Number{-1999999999999999861, -18}}, + {Number{-1414213562373095, -15}, + Number{-1414213562373095, -15}, + Number{1999999999999999862, -18}}, + {Number{3214285714285706, -15}, + Number{3111111111111119, -15}, + Number{999999999999999958, -17}}, + {Number{1000000000000000000, -32768}, + Number{1000000000000000000, -32768}, + Number{0}}, + // Items from cSmall expanded for the larger mantissa, + // except duplicates. Sadly, it looks like sqrt(2)^2 != 2 + // with higher precision + {Number{1414213562373095049, -18}, + Number{1414213562373095049, -18}, + Number{2000000000000000001, -18}}, + {Number{-1414213562373095048, -18}, + Number{1414213562373095048, -18}, + Number{-1999999999999999997, -18}}, + {Number{-1414213562373095048, -18}, + Number{-1414213562373095049, -18}, + Number{2, 0}}, + {Number{3214285714285714278, -18}, + Number{3111111111111111119, -18}, + Number{1000000000000000001, -17}}, + // Maximum mantissa range - rounds up to minMantissa*10 + // 1e19*1e19=1e38 + {Number{false, maxMantissa, 0, Number::normalized{}}, + Number{false, maxMantissa, 0, Number::normalized{}}, + Number{1, 38}}, + // Maximum int64 range + // 85'070'591'730'234'615'847'396'907'784'232'501'249 + {Number{Number::maxRep, 0}, + Number{Number::maxRep, 0}, + Number{85'070'591'730'234'615'85, 19}}, + }); + tests(cSmall, cLarge); } - bool caught = false; - try + testcase << "test_mul " << to_string(Number::getMantissaScale()) + << " overflow"; { - Number{9'999'999'999'999'999, 32768} * - Number{5'000'000'000'000'000, 32767}; + bool caught = false; + try + { + Number{false, maxMantissa, 32768, Number::normalized{}} * + Number{ + false, + Number::minMantissa() * 5, + 32767, + Number::normalized{}}; + } + catch (std::overflow_error const&) + { + caught = true; + } + BEAST_EXPECT(caught); } - catch (std::overflow_error const&) - { - caught = true; - } - BEAST_EXPECT(caught); } void test_div() { - testcase("test_div"); + auto const scale = Number::getMantissaScale(); + testcase << "test_div " << to_string(scale); + using Case = std::tuple; + auto test = [this](auto const& c) { + for (auto const& [x, y, z] : c) + { + auto const result = x / y; + std::stringstream ss; + ss << x << " / " << y << " = " << result << ". Expected: " << z; + BEAST_EXPECTS(result == z, ss.str()); + } + }; + auto const maxMantissa = Number::maxMantissa(); + auto tests = [&](auto const& cSmall, auto const& cLarge) { + if (scale == MantissaRange::small) + test(cSmall); + else + test(cLarge); + }; saveNumberRoundMode save{Number::setround(Number::to_nearest)}; { - Case c[]{ - {Number{1}, Number{2}, Number{5, -1}}, - {Number{1}, Number{10}, Number{1, -1}}, - {Number{1}, Number{-10}, Number{-1, -1}}, - {Number{0}, Number{100}, Number{0}}, - {Number{1414213562373095, -10}, - Number{1414213562373095, -10}, - Number{1}}, - {Number{9'999'999'999'999'999}, - Number{1'000'000'000'000'000}, - Number{9'999'999'999'999'999, -15}}, - {Number{2}, Number{3}, Number{6'666'666'666'666'667, -16}}, - {Number{-2}, Number{3}, Number{-6'666'666'666'666'667, -16}}}; - for (auto const& [x, y, z] : c) - BEAST_EXPECT(x / y == z); + auto const cSmall = std::to_array( + {{Number{1}, Number{2}, Number{5, -1}}, + {Number{1}, Number{10}, Number{1, -1}}, + {Number{1}, Number{-10}, Number{-1, -1}}, + {Number{0}, Number{100}, Number{0}}, + {Number{1414213562373095, -10}, + Number{1414213562373095, -10}, + Number{1}}, + {Number{9'999'999'999'999'999}, + Number{1'000'000'000'000'000}, + Number{9'999'999'999'999'999, -15}}, + {Number{2}, Number{3}, Number{6'666'666'666'666'667, -16}}, + {Number{-2}, Number{3}, Number{-6'666'666'666'666'667, -16}}, + {Number{1}, Number{7}, Number{1'428'571'428'571'428, -16}}}); + auto const cLarge = std::to_array( + // Note that items with extremely large mantissas need to be + // calculated, because otherwise they overflow uint64. Items + // from C with larger mantissa + {{Number{1}, Number{2}, Number{5, -1}}, + {Number{1}, Number{10}, Number{1, -1}}, + {Number{1}, Number{-10}, Number{-1, -1}}, + {Number{0}, Number{100}, Number{0}}, + {Number{1414213562373095, -10}, + Number{1414213562373095, -10}, + Number{1}}, + {Number{9'999'999'999'999'999}, + Number{1'000'000'000'000'000}, + Number{9'999'999'999'999'999, -15}}, + {Number{2}, Number{3}, Number{6'666'666'666'666'666'667, -19}}, + {Number{-2}, + Number{3}, + Number{-6'666'666'666'666'666'667, -19}}, + {Number{1}, Number{7}, Number{1'428'571'428'571'428'571, -19}}, + // Items from cSmall expanded for the larger mantissa, except + // duplicates. + {Number{1414213562373095049, -13}, + Number{1414213562373095049, -13}, + Number{1}}, + {Number{false, maxMantissa, 0, Number::normalized{}}, + Number{1'000'000'000'000'000'000}, + Number{false, maxMantissa, -18, Number::normalized{}}}}); + tests(cSmall, cLarge); } + testcase << "test_div " << to_string(Number::getMantissaScale()) + << " towards_zero"; Number::setround(Number::towards_zero); { - Case c[]{ - {Number{1}, Number{2}, Number{5, -1}}, - {Number{1}, Number{10}, Number{1, -1}}, - {Number{1}, Number{-10}, Number{-1, -1}}, - {Number{0}, Number{100}, Number{0}}, - {Number{1414213562373095, -10}, - Number{1414213562373095, -10}, - Number{1}}, - {Number{9'999'999'999'999'999}, - Number{1'000'000'000'000'000}, - Number{9'999'999'999'999'999, -15}}, - {Number{2}, Number{3}, Number{6'666'666'666'666'666, -16}}, - {Number{-2}, Number{3}, Number{-6'666'666'666'666'666, -16}}}; - for (auto const& [x, y, z] : c) - BEAST_EXPECT(x / y == z); + auto const cSmall = std::to_array( + {{Number{1}, Number{2}, Number{5, -1}}, + {Number{1}, Number{10}, Number{1, -1}}, + {Number{1}, Number{-10}, Number{-1, -1}}, + {Number{0}, Number{100}, Number{0}}, + {Number{1414213562373095, -10}, + Number{1414213562373095, -10}, + Number{1}}, + {Number{9'999'999'999'999'999}, + Number{1'000'000'000'000'000}, + Number{9'999'999'999'999'999, -15}}, + {Number{2}, Number{3}, Number{6'666'666'666'666'666, -16}}, + {Number{-2}, Number{3}, Number{-6'666'666'666'666'666, -16}}, + {Number{1}, Number{7}, Number{1'428'571'428'571'428, -16}}}); + auto const cLarge = std::to_array( + // Note that items with extremely large mantissas need to be + // calculated, because otherwise they overflow uint64. Items + // from C with larger mantissa + {{Number{1}, Number{2}, Number{5, -1}}, + {Number{1}, Number{10}, Number{1, -1}}, + {Number{1}, Number{-10}, Number{-1, -1}}, + {Number{0}, Number{100}, Number{0}}, + {Number{1414213562373095, -10}, + Number{1414213562373095, -10}, + Number{1}}, + {Number{9'999'999'999'999'999}, + Number{1'000'000'000'000'000}, + Number{9'999'999'999'999'999, -15}}, + {Number{2}, Number{3}, Number{6'666'666'666'666'666'666, -19}}, + {Number{-2}, + Number{3}, + Number{-6'666'666'666'666'666'666, -19}}, + {Number{1}, Number{7}, Number{1'428'571'428'571'428'571, -19}}, + // Items from cSmall expanded for the larger mantissa, except + // duplicates. + {Number{1414213562373095049, -13}, + Number{1414213562373095049, -13}, + Number{1}}, + {Number{false, maxMantissa, 0, Number::normalized{}}, + Number{1'000'000'000'000'000'000}, + Number{false, maxMantissa, -18, Number::normalized{}}}}); + tests(cSmall, cLarge); } + testcase << "test_div " << to_string(Number::getMantissaScale()) + << " downward"; Number::setround(Number::downward); { - Case c[]{ - {Number{1}, Number{2}, Number{5, -1}}, - {Number{1}, Number{10}, Number{1, -1}}, - {Number{1}, Number{-10}, Number{-1, -1}}, - {Number{0}, Number{100}, Number{0}}, - {Number{1414213562373095, -10}, - Number{1414213562373095, -10}, - Number{1}}, - {Number{9'999'999'999'999'999}, - Number{1'000'000'000'000'000}, - Number{9'999'999'999'999'999, -15}}, - {Number{2}, Number{3}, Number{6'666'666'666'666'666, -16}}, - {Number{-2}, Number{3}, Number{-6'666'666'666'666'667, -16}}}; - for (auto const& [x, y, z] : c) - BEAST_EXPECT(x / y == z); + auto const cSmall = std::to_array( + {{Number{1}, Number{2}, Number{5, -1}}, + {Number{1}, Number{10}, Number{1, -1}}, + {Number{1}, Number{-10}, Number{-1, -1}}, + {Number{0}, Number{100}, Number{0}}, + {Number{1414213562373095, -10}, + Number{1414213562373095, -10}, + Number{1}}, + {Number{9'999'999'999'999'999}, + Number{1'000'000'000'000'000}, + Number{9'999'999'999'999'999, -15}}, + {Number{2}, Number{3}, Number{6'666'666'666'666'666, -16}}, + {Number{-2}, Number{3}, Number{-6'666'666'666'666'667, -16}}, + {Number{1}, Number{7}, Number{1'428'571'428'571'428, -16}}}); + auto const cLarge = std::to_array( + // Note that items with extremely large mantissas need to be + // calculated, because otherwise they overflow uint64. Items + // from C with larger mantissa + {{Number{1}, Number{2}, Number{5, -1}}, + {Number{1}, Number{10}, Number{1, -1}}, + {Number{1}, Number{-10}, Number{-1, -1}}, + {Number{0}, Number{100}, Number{0}}, + {Number{1414213562373095, -10}, + Number{1414213562373095, -10}, + Number{1}}, + {Number{9'999'999'999'999'999}, + Number{1'000'000'000'000'000}, + Number{9'999'999'999'999'999, -15}}, + {Number{2}, Number{3}, Number{6'666'666'666'666'666'666, -19}}, + {Number{-2}, + Number{3}, + Number{-6'666'666'666'666'666'667, -19}}, + {Number{1}, Number{7}, Number{1'428'571'428'571'428'571, -19}}, + // Items from cSmall expanded for the larger mantissa, except + // duplicates. + {Number{1414213562373095049, -13}, + Number{1414213562373095049, -13}, + Number{1}}, + {Number{false, maxMantissa, 0, Number::normalized{}}, + Number{1'000'000'000'000'000'000}, + Number{false, maxMantissa, -18, Number::normalized{}}}}); + tests(cSmall, cLarge); } + testcase << "test_div " << to_string(Number::getMantissaScale()) + << " upward"; Number::setround(Number::upward); { - Case c[]{ - {Number{1}, Number{2}, Number{5, -1}}, - {Number{1}, Number{10}, Number{1, -1}}, - {Number{1}, Number{-10}, Number{-1, -1}}, - {Number{0}, Number{100}, Number{0}}, - {Number{1414213562373095, -10}, - Number{1414213562373095, -10}, - Number{1}}, - {Number{9'999'999'999'999'999}, - Number{1'000'000'000'000'000}, - Number{9'999'999'999'999'999, -15}}, - {Number{2}, Number{3}, Number{6'666'666'666'666'667, -16}}, - {Number{-2}, Number{3}, Number{-6'666'666'666'666'666, -16}}}; - for (auto const& [x, y, z] : c) - BEAST_EXPECT(x / y == z); + auto const cSmall = std::to_array( + {{Number{1}, Number{2}, Number{5, -1}}, + {Number{1}, Number{10}, Number{1, -1}}, + {Number{1}, Number{-10}, Number{-1, -1}}, + {Number{0}, Number{100}, Number{0}}, + {Number{1414213562373095, -10}, + Number{1414213562373095, -10}, + Number{1}}, + {Number{9'999'999'999'999'999}, + Number{1'000'000'000'000'000}, + Number{9'999'999'999'999'999, -15}}, + {Number{2}, Number{3}, Number{6'666'666'666'666'667, -16}}, + {Number{-2}, Number{3}, Number{-6'666'666'666'666'666, -16}}, + {Number{1}, Number{7}, Number{1'428'571'428'571'429, -16}}}); + auto const cLarge = std::to_array( + // Note that items with extremely large mantissas need to be + // calculated, because otherwise they overflow uint64. Items + // from C with larger mantissa + {{Number{1}, Number{2}, Number{5, -1}}, + {Number{1}, Number{10}, Number{1, -1}}, + {Number{1}, Number{-10}, Number{-1, -1}}, + {Number{0}, Number{100}, Number{0}}, + {Number{1414213562373095, -10}, + Number{1414213562373095, -10}, + Number{1}}, + {Number{9'999'999'999'999'999}, + Number{1'000'000'000'000'000}, + Number{9'999'999'999'999'999, -15}}, + {Number{2}, Number{3}, Number{6'666'666'666'666'666'667, -19}}, + {Number{-2}, + Number{3}, + Number{-6'666'666'666'666'666'666, -19}}, + {Number{1}, Number{7}, Number{1'428'571'428'571'428'572, -19}}, + // Items from cSmall expanded for the larger mantissa, except + // duplicates. + {Number{1414213562373095049, -13}, + Number{1414213562373095049, -13}, + Number{1}}, + {Number{false, maxMantissa, 0, Number::normalized{}}, + Number{1'000'000'000'000'000'000}, + Number{false, maxMantissa, -18, Number::normalized{}}}}); + tests(cSmall, cLarge); } + testcase << "test_div " << to_string(Number::getMantissaScale()) + << " overflow"; bool caught = false; try { @@ -325,20 +949,59 @@ public: void test_root() { - testcase("test_root"); + auto const scale = Number::getMantissaScale(); + testcase << "test_root " << to_string(scale); + using Case = std::tuple; - Case c[]{ - {Number{2}, 2, Number{1414213562373095, -15}}, - {Number{2'000'000}, 2, Number{1414213562373095, -12}}, - {Number{2, -30}, 2, Number{1414213562373095, -30}}, - {Number{-27}, 3, Number{-3}}, - {Number{1}, 5, Number{1}}, - {Number{-1}, 0, Number{1}}, - {Number{5, -1}, 0, Number{0}}, - {Number{0}, 5, Number{0}}, - {Number{5625, -4}, 2, Number{75, -2}}}; - for (auto const& [x, y, z] : c) - BEAST_EXPECT((root(x, y) == z)); + auto test = [this](auto const& c) { + for (auto const& [x, y, z] : c) + { + auto const result = root(x, y); + std::stringstream ss; + ss << "root(" << x << ", " << y << ") = " << result + << ". Expected: " << z; + BEAST_EXPECTS(result == z, ss.str()); + } + }; + /* + auto tests = [&](auto const& cSmall, auto const& cLarge) { + test(cSmall); + if (scale != MantissaRange::small) + test(cLarge); + }; + */ + + auto const cSmall = std::to_array( + {{Number{2}, 2, Number{1414213562373095049, -18}}, + {Number{2'000'000}, 2, Number{1414213562373095049, -15}}, + {Number{2, -30}, 2, Number{1414213562373095049, -33}}, + {Number{-27}, 3, Number{-3}}, + {Number{1}, 5, Number{1}}, + {Number{-1}, 0, Number{1}}, + {Number{5, -1}, 0, Number{0}}, + {Number{0}, 5, Number{0}}, + {Number{5625, -4}, 2, Number{75, -2}}}); + auto const cLarge = std::to_array({ + {Number{false, Number::maxMantissa() - 9, -1, Number::normalized{}}, + 2, + Number{false, 999'999'999'999'999'999, -9, Number::normalized{}}}, + {Number{false, Number::maxMantissa() - 9, 0, Number::normalized{}}, + 2, + Number{ + false, 3'162'277'660'168'379'330, -9, Number::normalized{}}}, + {Number{Number::maxRep}, + 2, + Number{false, 3'037'000'499'976049692, -9, Number::normalized{}}}, + {Number{Number::maxRep}, + 4, + Number{false, 55'108'98747006743627, -14, Number::normalized{}}}, + }); + test(cSmall); + if (Number::getMantissaScale() != MantissaRange::small) + { + NumberRoundModeGuard mg(Number::towards_zero); + test(cLarge); + } bool caught = false; try { @@ -361,10 +1024,52 @@ public: BEAST_EXPECT(caught); } + void + test_root2() + { + auto const scale = Number::getMantissaScale(); + testcase << "test_root2 " << to_string(scale); + + auto test = [this](auto const& c) { + for (auto const& x : c) + { + auto const expected = root(x, 2); + auto const result = root2(x); + std::stringstream ss; + ss << "root2(" << x << ") = " << result + << ". Expected: " << expected; + BEAST_EXPECTS(result == expected, ss.str()); + } + }; + + auto const cSmall = std::to_array({ + Number{2}, + Number{2'000'000}, + Number{2, -30}, + Number{27}, + Number{1}, + Number{5, -1}, + Number{0}, + Number{5625, -4}, + Number{Number::maxRep}, + }); + test(cSmall); + bool caught = false; + try + { + (void)root2(Number{-2}); + } + catch (std::overflow_error const&) + { + caught = true; + } + BEAST_EXPECT(caught); + } + void test_power1() { - testcase("test_power1"); + testcase << "test_power1 " << to_string(Number::getMantissaScale()); using Case = std::tuple; Case c[]{ {Number{64}, 0, Number{1}}, @@ -372,7 +1077,13 @@ public: {Number{64}, 2, Number{4096}}, {Number{-64}, 2, Number{4096}}, {Number{64}, 3, Number{262144}}, - {Number{-64}, 3, Number{-262144}}}; + {Number{-64}, 3, Number{-262144}}, + {Number{64}, + 11, + Number{false, 7378697629483820646ULL, 1, Number::normalized{}}}, + {Number{-64}, + 11, + Number{true, 7378697629483820646ULL, 1, Number::normalized{}}}}; for (auto const& [x, y, z] : c) BEAST_EXPECT((power(x, y) == z)); } @@ -380,7 +1091,7 @@ public: void test_power2() { - testcase("test_power2"); + testcase << "test_power2 " << to_string(Number::getMantissaScale()); using Case = std::tuple; Case c[]{ {Number{1}, 3, 7, Number{1}}, @@ -426,7 +1137,7 @@ public: void testConversions() { - testcase("testConversions"); + testcase << "testConversions " << to_string(Number::getMantissaScale()); IOUAmount x{5, 6}; Number y = x; @@ -452,7 +1163,7 @@ public: void test_to_integer() { - testcase("test_to_integer"); + testcase << "test_to_integer " << to_string(Number::getMantissaScale()); using Case = std::tuple; saveNumberRoundMode save{Number::setround(Number::to_nearest)}; { @@ -620,7 +1331,7 @@ public: void test_squelch() { - testcase("test_squelch"); + testcase << "test_squelch " << to_string(Number::getMantissaScale()); Number limit{1, -6}; BEAST_EXPECT((squelch(Number{2, -6}, limit) == Number{2, -6})); BEAST_EXPECT((squelch(Number{1, -6}, limit) == Number{1, -6})); @@ -633,22 +1344,129 @@ public: void testToString() { - testcase("testToString"); - BEAST_EXPECT(to_string(Number(-2, 0)) == "-2"); - BEAST_EXPECT(to_string(Number(0, 0)) == "0"); - BEAST_EXPECT(to_string(Number(2, 0)) == "2"); - BEAST_EXPECT(to_string(Number(25, -3)) == "0.025"); - BEAST_EXPECT(to_string(Number(-25, -3)) == "-0.025"); - BEAST_EXPECT(to_string(Number(25, 1)) == "250"); - BEAST_EXPECT(to_string(Number(-25, 1)) == "-250"); - BEAST_EXPECT(to_string(Number(2, 20)) == "2000000000000000e5"); - BEAST_EXPECT(to_string(Number(-2, -20)) == "-2000000000000000e-35"); + auto const scale = Number::getMantissaScale(); + testcase << "testToString " << to_string(scale); + + auto test = [this](Number const& n, std::string const& expected) { + auto const result = to_string(n); + std::stringstream ss; + ss << "to_string(" << result << "). Expected: " << expected; + BEAST_EXPECTS(result == expected, ss.str()); + }; + + test(Number(-2, 0), "-2"); + test(Number(0, 0), "0"); + test(Number(2, 0), "2"); + test(Number(25, -3), "0.025"); + test(Number(-25, -3), "-0.025"); + test(Number(25, 1), "250"); + test(Number(-25, 1), "-250"); + test(Number(2, 20), "2e20"); + test(Number(-2, -20), "-2e-20"); + // Test the edges + // ((exponent < -(25)) || (exponent > -(5))))) + // or ((exponent < -(28)) || (exponent > -(8))))) + test(Number(2, -10), "0.0000000002"); + test(Number(2, -11), "2e-11"); + + test(Number(-2, 10), "-20000000000"); + test(Number(-2, 11), "-2e11"); + + switch (scale) + { + case MantissaRange::small: + + test(Number::min(), "1e-32753"); + test(Number::max(), "9999999999999999e32768"); + test(Number::lowest(), "-9999999999999999e32768"); + { + NumberRoundModeGuard mg(Number::towards_zero); + + auto const maxMantissa = Number::maxMantissa(); + BEAST_EXPECT(maxMantissa == 9'999'999'999'999'999); + test( + Number{ + false, + maxMantissa * 1000 + 999, + -3, + Number::normalized()}, + "9999999999999999"); + test( + Number{ + true, + maxMantissa * 1000 + 999, + -3, + Number::normalized()}, + "-9999999999999999"); + + test( + Number{std::numeric_limits::max(), -3}, + "9223372036854775"); + test( + -(Number{std::numeric_limits::max(), -3}), + "-9223372036854775"); + + test( + Number{std::numeric_limits::min(), 0}, + "-9223372036854775e3"); + test( + -(Number{std::numeric_limits::min(), 0}), + "9223372036854775e3"); + } + break; + case MantissaRange::large: + // Test the edges + // ((exponent < -(28)) || (exponent > -(8))))) + test(Number::min(), "1e-32750"); + test(Number::max(), "9223372036854775807e32768"); + test(Number::lowest(), "-9223372036854775807e32768"); + { + NumberRoundModeGuard mg(Number::towards_zero); + + auto const maxMantissa = Number::maxMantissa(); + BEAST_EXPECT(maxMantissa == 9'999'999'999'999'999'999ULL); + test( + Number{false, maxMantissa, 0, Number::normalized{}}, + "9999999999999999990"); + test( + Number{true, maxMantissa, 0, Number::normalized{}}, + "-9999999999999999990"); + + test( + Number{std::numeric_limits::max(), 0}, + "9223372036854775807"); + test( + -(Number{std::numeric_limits::max(), 0}), + "-9223372036854775807"); + + // Because the absolute value of min is larger than max, it + // will be scaled down to fit under max. Since we're + // rounding towards zero, the 8 at the end is dropped. + test( + Number{std::numeric_limits::min(), 0}, + "-9223372036854775800"); + test( + -(Number{std::numeric_limits::min(), 0}), + "9223372036854775800"); + } + + test( + Number{std::numeric_limits::max(), 0} + 1, + "9223372036854775810"); + test( + -(Number{std::numeric_limits::max(), 0} + 1), + "-9223372036854775810"); + break; + default: + BEAST_EXPECT(false); + } } void test_relationals() { - testcase("test_relationals"); + testcase << "test_relationals " + << to_string(Number::getMantissaScale()); BEAST_EXPECT(!(Number{100} < Number{10})); BEAST_EXPECT(Number{100} > Number{10}); BEAST_EXPECT(Number{100} >= Number{10}); @@ -658,7 +1476,7 @@ public: void test_stream() { - testcase("test_stream"); + testcase << "test_stream " << to_string(Number::getMantissaScale()); Number x{100}; std::ostringstream os; os << x; @@ -668,7 +1486,7 @@ public: void test_inc_dec() { - testcase("test_inc_dec"); + testcase << "test_inc_dec " << to_string(Number::getMantissaScale()); Number x{100}; Number y = +x; BEAST_EXPECT(x == y); @@ -685,19 +1503,19 @@ public: Issue const issue; Number const n{7'518'783'80596, -5}; saveNumberRoundMode const save{Number::setround(Number::to_nearest)}; - auto res2 = STAmount{issue, n.mantissa(), n.exponent()}; + auto res2 = STAmount{issue, n}; BEAST_EXPECT(res2 == STAmount{7518784}); Number::setround(Number::towards_zero); - res2 = STAmount{issue, n.mantissa(), n.exponent()}; + res2 = STAmount{issue, n}; BEAST_EXPECT(res2 == STAmount{7518783}); Number::setround(Number::downward); - res2 = STAmount{issue, n.mantissa(), n.exponent()}; + res2 = STAmount{issue, n}; BEAST_EXPECT(res2 == STAmount{7518783}); Number::setround(Number::upward); - res2 = STAmount{issue, n.mantissa(), n.exponent()}; + res2 = STAmount{issue, n}; BEAST_EXPECT(res2 == STAmount{7518784}); } @@ -834,28 +1652,94 @@ public: } } + void + testInt64() + { + auto const scale = Number::getMantissaScale(); + testcase << "std::int64_t " << to_string(scale); + + // Control case + BEAST_EXPECT(Number::maxMantissa() > 10); + Number ten{10}; + BEAST_EXPECT(ten.exponent() <= 0); + + if (scale == MantissaRange::small) + { + BEAST_EXPECT( + std::numeric_limits::max() > INITIAL_XRP.drops()); + BEAST_EXPECT(Number::maxMantissa() < INITIAL_XRP.drops()); + Number const initalXrp{INITIAL_XRP}; + BEAST_EXPECT(initalXrp.exponent() > 0); + + Number const maxInt64{Number::maxRep}; + BEAST_EXPECT(maxInt64.exponent() > 0); + // 85'070'591'730'234'615'865'843'651'857'942'052'864 - 38 digits + BEAST_EXPECT( + (power(maxInt64, 2) == Number{85'070'591'730'234'62, 22})); + + Number const max = + Number{false, Number::maxMantissa(), 0, Number::normalized{}}; + BEAST_EXPECT(max.exponent() <= 0); + // 99'999'999'999'999'980'000'000'000'000'001 - 32 digits + BEAST_EXPECT((power(max, 2) == Number{99'999'999'999'999'98, 16})); + } + else + { + BEAST_EXPECT( + std::numeric_limits::max() > INITIAL_XRP.drops()); + BEAST_EXPECT(Number::maxMantissa() > INITIAL_XRP.drops()); + Number const initalXrp{INITIAL_XRP}; + BEAST_EXPECT(initalXrp.exponent() <= 0); + + Number const maxInt64{Number::maxRep}; + BEAST_EXPECT(maxInt64.exponent() <= 0); + // 85'070'591'730'234'615'847'396'907'784'232'501'249 - 38 digits + BEAST_EXPECT( + (power(maxInt64, 2) == Number{85'070'591'730'234'615'85, 19})); + + NumberRoundModeGuard mg(Number::towards_zero); + + auto const maxMantissa = Number::maxMantissa(); + Number const max = + Number{false, maxMantissa, 0, Number::normalized{}}; + BEAST_EXPECT(max.mantissa() == maxMantissa / 10); + BEAST_EXPECT(max.exponent() == 1); + // 99'999'999'999'999'999'800'000'000'000'000'000'100 - also 38 + // digits + BEAST_EXPECT(( + power(max, 2) == + Number{false, maxMantissa / 10 - 1, 20, Number::normalized{}})); + } + } + void run() override { - testZero(); - test_limits(); - test_add(); - test_sub(); - test_mul(); - test_div(); - test_root(); - test_power1(); - test_power2(); - testConversions(); - test_to_integer(); - test_squelch(); - testToString(); - test_relationals(); - test_stream(); - test_inc_dec(); - test_toSTAmount(); - test_truncate(); - testRounding(); + for (auto const scale : {MantissaRange::small, MantissaRange::large}) + { + NumberMantissaScaleGuard sg(scale); + testZero(); + test_limits(); + testToString(); + test_add(); + test_sub(); + test_mul(); + test_div(); + test_root(); + test_root2(); + test_power1(); + test_power2(); + testConversions(); + test_to_integer(); + test_squelch(); + test_relationals(); + test_stream(); + test_inc_dec(); + test_toSTAmount(); + test_truncate(); + testRounding(); + testInt64(); + } } }; diff --git a/src/test/jtx/AMMTest.h b/src/test/jtx/AMMTest.h index 83366d61e2..208e3c4e5f 100644 --- a/src/test/jtx/AMMTest.h +++ b/src/test/jtx/AMMTest.h @@ -21,7 +21,12 @@ struct TestAMMArg std::optional> pool = std::nullopt; std::uint16_t tfee = 0; std::optional ter = std::nullopt; - std::vector features = {testable_amendments()}; + std::vector features = { + // For now, just disable SAV entirely, which locks in the small Number + // mantissas + jtx::testable_amendments() - featureSingleAssetVault - + featureLendingProtocol}; + bool noLog = false; }; @@ -66,6 +71,15 @@ protected: public: AMMTestBase(); + static FeatureBitset + testable_amendments() + { + // For now, just disable SAV entirely, which locks in the small Number + // mantissas + return jtx::testable_amendments() - featureSingleAssetVault - + featureLendingProtocol; + } + protected: /** testAMM() funds 30,000XRP and 30,000IOU * for each non-XRP asset to Alice and Carol diff --git a/src/test/jtx/amount.h b/src/test/jtx/amount.h index a310fc5b44..147307f7b7 100644 --- a/src/test/jtx/amount.h +++ b/src/test/jtx/amount.h @@ -261,6 +261,12 @@ struct XRP_t return xrpIssue(); } + bool + integral() const + { + return true; + } + /** Returns an amount of XRP as PrettyAmount, which is trivially convertible to STAmount @@ -400,6 +406,11 @@ public: { return issue(); } + bool + integral() const + { + return issue().integral(); + } /** Implicit conversion to Issue or Asset. @@ -490,6 +501,11 @@ public: { return mptIssue(); } + bool + integral() const + { + return true; + } /** Implicit conversion to MPTIssue or asset. diff --git a/src/test/jtx/impl/AMMTest.cpp b/src/test/jtx/impl/AMMTest.cpp index de7ce5504b..139b9113b9 100644 --- a/src/test/jtx/impl/AMMTest.cpp +++ b/src/test/jtx/impl/AMMTest.cpp @@ -105,9 +105,14 @@ AMMTestBase::testAMM( for (auto const& features : arg.features) { + // Use small Number mantissas for the life of this test. + NumberMantissaScaleGuard const sg{xrpl::MantissaRange::small}; + + // For now, just disable SAV entirely, which locks in the small Number + // mantissas Env env{ *this, - features, + features - featureSingleAssetVault - featureLendingProtocol, arg.noLog ? std::make_unique(&logs) : nullptr}; auto const [asset1, asset2] = diff --git a/src/test/protocol/STNumber_test.cpp b/src/test/protocol/STNumber_test.cpp index 1275c756cf..4e7a8388ee 100644 --- a/src/test/protocol/STNumber_test.cpp +++ b/src/test/protocol/STNumber_test.cpp @@ -29,10 +29,8 @@ struct STNumber_test : public beast::unit_test::suite } void - run() override + doRun() { - static_assert(!std::is_convertible_v); - { STNumber const stnum{sfNumber}; BEAST_EXPECT(stnum.getSType() == STI_NUMBER); @@ -127,6 +125,41 @@ struct STNumber_test : public beast::unit_test::suite BEAST_EXPECT( numberFromJson(sfNumber, "-0.000e6") == STNumber(sfNumber, 0)); + { + NumberRoundModeGuard mg(Number::towards_zero); + // maxint64 9,223,372,036,854,775,807 + auto const maxInt = + std::to_string(std::numeric_limits::max()); + // minint64 -9,223,372,036,854,775,808 + auto const minInt = + std::to_string(std::numeric_limits::min()); + if (Number::getMantissaScale() == MantissaRange::small) + { + BEAST_EXPECT( + numberFromJson(sfNumber, maxInt) == + STNumber(sfNumber, Number{9'223'372'036'854'775, 3})); + BEAST_EXPECT( + numberFromJson(sfNumber, minInt) == + STNumber(sfNumber, Number{-9'223'372'036'854'775, 3})); + } + else + { + BEAST_EXPECT( + numberFromJson(sfNumber, maxInt) == + STNumber( + sfNumber, Number{9'223'372'036'854'775'807, 0})); + BEAST_EXPECT( + numberFromJson(sfNumber, minInt) == + STNumber( + sfNumber, + Number{ + true, + 9'223'372'036'854'775'808ULL, + 0, + Number::normalized{}})); + } + } + constexpr auto imin = std::numeric_limits::min(); BEAST_EXPECT( numberFromJson(sfNumber, imin) == @@ -279,15 +312,21 @@ struct STNumber_test : public beast::unit_test::suite } } } + + void + run() override + { + static_assert(!std::is_convertible_v); + + for (auto const scale : {MantissaRange::small, MantissaRange::large}) + { + NumberMantissaScaleGuard sg(scale); + testcase << to_string(Number::getMantissaScale()); + doRun(); + } + } }; BEAST_DEFINE_TESTSUITE(STNumber, protocol, xrpl); -void -testCompile(std::ostream& out) -{ - STNumber number{sfNumber, 42}; - out << number; -} - } // namespace xrpl diff --git a/src/test/rpc/GetAggregatePrice_test.cpp b/src/test/rpc/GetAggregatePrice_test.cpp index 52f82ffc6c..0ffefc6cb6 100644 --- a/src/test/rpc/GetAggregatePrice_test.cpp +++ b/src/test/rpc/GetAggregatePrice_test.cpp @@ -191,18 +191,38 @@ public: // Aggregate data set includes all price oracle instances, no trimming // or time threshold { - Env env(*this); - OraclesData oracles; - prep(env, oracles); - // entire and trimmed stats - auto ret = Oracle::aggregatePrice(env, "XRP", "USD", oracles); - BEAST_EXPECT(ret[jss::entire_set][jss::mean] == "74.45"); - BEAST_EXPECT(ret[jss::entire_set][jss::size].asUInt() == 10); - BEAST_EXPECT( - ret[jss::entire_set][jss::standard_deviation] == - "0.3027650354097492"); - BEAST_EXPECT(ret[jss::median] == "74.45"); - BEAST_EXPECT(ret[jss::time] == 946694900); + auto const all = testable_amendments(); + for (auto const& feats : + {all - featureSingleAssetVault - featureLendingProtocol, all}) + { + for (auto const mantissaSize : + {MantissaRange::small, MantissaRange::large}) + { + // Regardless of the features enabled, RPC is controlled by + // the global mantissa size. And since it's a thread-local, + // overriding it locally won't make a difference either. + // This will mean all RPC will use the default of "large". + NumberMantissaScaleGuard mg(mantissaSize); + + Env env(*this, feats); + OraclesData oracles; + prep(env, oracles); + // entire and trimmed stats + auto ret = + Oracle::aggregatePrice(env, "XRP", "USD", oracles); + BEAST_EXPECT(ret[jss::entire_set][jss::mean] == "74.45"); + BEAST_EXPECT( + ret[jss::entire_set][jss::size].asUInt() == 10); + // Short: 0.3027650354097492 + BEAST_EXPECTS( + ret[jss::entire_set][jss::standard_deviation] == + "0.3027650354097491666", + ret[jss::entire_set][jss::standard_deviation] + .asString()); + BEAST_EXPECT(ret[jss::median] == "74.45"); + BEAST_EXPECT(ret[jss::time] == 946694900); + } + } } // Aggregate data set includes all price oracle instances @@ -215,15 +235,19 @@ public: Oracle::aggregatePrice(env, "XRP", "USD", oracles, 20, 100); BEAST_EXPECT(ret[jss::entire_set][jss::mean] == "74.45"); BEAST_EXPECT(ret[jss::entire_set][jss::size].asUInt() == 10); - BEAST_EXPECT( + // Short: "0.3027650354097492", + BEAST_EXPECTS( ret[jss::entire_set][jss::standard_deviation] == - "0.3027650354097492"); + "0.3027650354097491666", + ret[jss::entire_set][jss::standard_deviation].asString()); BEAST_EXPECT(ret[jss::median] == "74.45"); BEAST_EXPECT(ret[jss::trimmed_set][jss::mean] == "74.45"); BEAST_EXPECT(ret[jss::trimmed_set][jss::size].asUInt() == 6); - BEAST_EXPECT( + // Short: "0.187082869338697", + BEAST_EXPECTS( ret[jss::trimmed_set][jss::standard_deviation] == - "0.187082869338697"); + "0.1870828693386970693", + ret[jss::trimmed_set][jss::standard_deviation].asString()); BEAST_EXPECT(ret[jss::time] == 946694900); } @@ -274,15 +298,19 @@ public: Oracle::aggregatePrice(env, "XRP", "USD", oracles, 20, "200"); BEAST_EXPECT(ret[jss::entire_set][jss::mean] == "74.6"); BEAST_EXPECT(ret[jss::entire_set][jss::size].asUInt() == 7); - BEAST_EXPECT( + // Short: 0.2160246899469287 + BEAST_EXPECTS( ret[jss::entire_set][jss::standard_deviation] == - "0.2160246899469287"); + "0.2160246899469286744", + ret[jss::entire_set][jss::standard_deviation].asString()); BEAST_EXPECT(ret[jss::median] == "74.6"); BEAST_EXPECT(ret[jss::trimmed_set][jss::mean] == "74.6"); BEAST_EXPECT(ret[jss::trimmed_set][jss::size].asUInt() == 5); - BEAST_EXPECT( + // Short: 0.158113883008419 + BEAST_EXPECTS( ret[jss::trimmed_set][jss::standard_deviation] == - "0.158113883008419"); + "0.1581138830084189666", + ret[jss::trimmed_set][jss::standard_deviation].asString()); BEAST_EXPECT(ret[jss::time] == 946694900); } diff --git a/src/xrpld/app/tx/detail/LoanBrokerCoverClawback.cpp b/src/xrpld/app/tx/detail/LoanBrokerCoverClawback.cpp index 1cc6b1f5ae..c61145dda1 100644 --- a/src/xrpld/app/tx/detail/LoanBrokerCoverClawback.cpp +++ b/src/xrpld/app/tx/detail/LoanBrokerCoverClawback.cpp @@ -2,6 +2,8 @@ // #include +#include + namespace xrpl { bool @@ -338,6 +340,8 @@ LoanBrokerCoverClawback::doApply() sleBroker->at(sfCoverAvailable) -= clawAmount; view().update(sleBroker); + associateAsset(*sleBroker, vaultAsset); + // Transfer assets from pseudo-account to depositor. return accountSend( view(), brokerPseudoID, account, clawAmount, j_, WaiveTransferFee::Yes); diff --git a/src/xrpld/app/tx/detail/LoanBrokerCoverDeposit.cpp b/src/xrpld/app/tx/detail/LoanBrokerCoverDeposit.cpp index b68cf46a00..ed47d40631 100644 --- a/src/xrpld/app/tx/detail/LoanBrokerCoverDeposit.cpp +++ b/src/xrpld/app/tx/detail/LoanBrokerCoverDeposit.cpp @@ -2,6 +2,8 @@ // #include +#include + namespace xrpl { bool @@ -100,6 +102,12 @@ LoanBrokerCoverDeposit::doApply() if (!broker) return tecINTERNAL; // LCOV_EXCL_LINE + auto const vault = view().read(keylet::vault(broker->at(sfVaultID))); + if (!vault) + return tecINTERNAL; // LCOV_EXCL_LINE + + auto const vaultAsset = vault->at(sfAsset); + auto const brokerPseudoID = broker->at(sfAccount); // Transfer assets from depositor to pseudo-account. @@ -116,6 +124,8 @@ LoanBrokerCoverDeposit::doApply() broker->at(sfCoverAvailable) += amount; view().update(broker); + associateAsset(*broker, vaultAsset); + return tesSUCCESS; } diff --git a/src/xrpld/app/tx/detail/LoanBrokerCoverWithdraw.cpp b/src/xrpld/app/tx/detail/LoanBrokerCoverWithdraw.cpp index 830f9e26c1..5d4d2053ed 100644 --- a/src/xrpld/app/tx/detail/LoanBrokerCoverWithdraw.cpp +++ b/src/xrpld/app/tx/detail/LoanBrokerCoverWithdraw.cpp @@ -4,6 +4,7 @@ #include #include +#include namespace xrpl { @@ -156,12 +157,20 @@ LoanBrokerCoverWithdraw::doApply() if (!broker) return tecINTERNAL; // LCOV_EXCL_LINE + auto const vault = view().read(keylet::vault(broker->at(sfVaultID))); + if (!vault) + return tecINTERNAL; // LCOV_EXCL_LINE + + auto const vaultAsset = vault->at(sfAsset); + auto const brokerPseudoID = *broker->at(sfAccount); // Decrease the LoanBroker's CoverAvailable by Amount broker->at(sfCoverAvailable) -= amount; view().update(broker); + associateAsset(*broker, vaultAsset); + return doWithdraw( view(), tx, diff --git a/src/xrpld/app/tx/detail/LoanBrokerDelete.cpp b/src/xrpld/app/tx/detail/LoanBrokerDelete.cpp index 227bad10a9..f3f57f8659 100644 --- a/src/xrpld/app/tx/detail/LoanBrokerDelete.cpp +++ b/src/xrpld/app/tx/detail/LoanBrokerDelete.cpp @@ -2,6 +2,8 @@ // #include +#include + namespace xrpl { bool @@ -185,6 +187,8 @@ LoanBrokerDelete::doApply() adjustOwnerCount(view(), owner, -2, j_); } + associateAsset(*broker, vaultAsset); + return tesSUCCESS; } diff --git a/src/xrpld/app/tx/detail/LoanBrokerSet.cpp b/src/xrpld/app/tx/detail/LoanBrokerSet.cpp index 0cbae4d779..b7e9e4c79f 100644 --- a/src/xrpld/app/tx/detail/LoanBrokerSet.cpp +++ b/src/xrpld/app/tx/detail/LoanBrokerSet.cpp @@ -2,6 +2,8 @@ // #include +#include + namespace xrpl { bool @@ -62,6 +64,15 @@ LoanBrokerSet::preflight(PreflightContext const& ctx) return tesSUCCESS; } +std::vector> const& +LoanBrokerSet::getValueFields() +{ + static std::vector> const valueFields{ + ~sfDebtMaximum}; + + return valueFields; +} + TER LoanBrokerSet::preclaim(PreclaimContext const& ctx) { @@ -70,8 +81,24 @@ LoanBrokerSet::preclaim(PreclaimContext const& ctx) auto const account = tx[sfAccount]; auto const vaultID = tx[sfVaultID]; + auto const sleVault = ctx.view.read(keylet::vault(vaultID)); + if (!sleVault) + { + JLOG(ctx.j.warn()) << "Vault does not exist."; + return tecNO_ENTRY; + } + Asset const asset = sleVault->at(sfAsset); + + if (account != sleVault->at(sfOwner)) + { + JLOG(ctx.j.warn()) << "Account is not the owner of the Vault."; + return tecNO_PERMISSION; + } + if (auto const brokerID = tx[~sfLoanBrokerID]) { + // Updating an existing Broker + auto const sleBroker = ctx.view.read(keylet::loanbroker(*brokerID)); if (!sleBroker) { @@ -104,18 +131,7 @@ LoanBrokerSet::preclaim(PreclaimContext const& ctx) } else { - auto const sleVault = ctx.view.read(keylet::vault(vaultID)); - if (!sleVault) - { - JLOG(ctx.j.warn()) << "Vault does not exist."; - return tecNO_ENTRY; - } - if (account != sleVault->at(sfOwner)) - { - JLOG(ctx.j.warn()) << "Account is not the owner of the Vault."; - return tecNO_PERMISSION; - } - if (auto const ter = canAddHolding(ctx.view, sleVault->at(sfAsset))) + if (auto const ter = canAddHolding(ctx.view, asset)) return ter; if (auto const ter = checkFrozen( @@ -125,6 +141,21 @@ LoanBrokerSet::preclaim(PreclaimContext const& ctx) return ter; } } + + // Check that relevant values can be represented as the vault asset + // type. This is mostly only relevant for integral (non-IOU) types + for (auto const& field : getValueFields()) + { + if (auto const value = tx[field]; + value && STAmount{asset, *value} != *value) + { + JLOG(ctx.j.warn()) << field.f->getName() << " (" << *value + << ") can not be represented as a(n) " + << to_string(asset) << "."; + return tecPRECISION_LOSS; + } + } + return tesSUCCESS; } @@ -147,12 +178,20 @@ LoanBrokerSet::doApply() // LCOV_EXCL_STOP } + auto const vault = view.read(keylet::vault(broker->at(sfVaultID))); + if (!vault) + return tecINTERNAL; // LCOV_EXCL_LINE + + auto const vaultAsset = vault->at(sfAsset); + if (auto const data = tx[~sfData]) broker->at(sfData) = *data; if (auto const debtMax = tx[~sfDebtMaximum]) broker->at(sfDebtMaximum) = *debtMax; view.update(broker); + + associateAsset(*broker, vaultAsset); } else { @@ -168,6 +207,7 @@ LoanBrokerSet::doApply() // LCOV_EXCL_STOP } auto const vaultPseudoID = sleVault->at(sfAccount); + auto const vaultAsset = sleVault->at(sfAsset); auto const sequence = tx.getSeqValue(); auto owner = view.peek(keylet::account(account_)); @@ -224,6 +264,8 @@ LoanBrokerSet::doApply() broker->at(sfCoverRateLiquidation) = *coverLiq; view.insert(broker); + + associateAsset(*broker, vaultAsset); } return tesSUCCESS; diff --git a/src/xrpld/app/tx/detail/LoanBrokerSet.h b/src/xrpld/app/tx/detail/LoanBrokerSet.h index 625c0adeb2..57170b9cb9 100644 --- a/src/xrpld/app/tx/detail/LoanBrokerSet.h +++ b/src/xrpld/app/tx/detail/LoanBrokerSet.h @@ -20,6 +20,9 @@ public: static NotTEC preflight(PreflightContext const& ctx); + static std::vector> const& + getValueFields(); + static TER preclaim(PreclaimContext const& ctx); diff --git a/src/xrpld/app/tx/detail/LoanDelete.cpp b/src/xrpld/app/tx/detail/LoanDelete.cpp index 3643e6331b..ddb286db12 100644 --- a/src/xrpld/app/tx/detail/LoanDelete.cpp +++ b/src/xrpld/app/tx/detail/LoanDelete.cpp @@ -2,6 +2,8 @@ // #include +#include + namespace xrpl { bool @@ -78,9 +80,10 @@ LoanDelete::doApply() return tefBAD_LEDGER; // LCOV_EXCL_LINE auto const brokerPseudoAccount = brokerSle->at(sfAccount); - auto const vaultSle = view.peek(keylet ::vault(brokerSle->at(sfVaultID))); + auto const vaultSle = view.peek(keylet::vault(brokerSle->at(sfVaultID))); if (!vaultSle) return tefBAD_LEDGER; // LCOV_EXCL_LINE + auto const vaultAsset = vaultSle->at(sfAsset); // Remove LoanID from Directory of the LoanBroker pseudo-account. if (!view.dirRemove( @@ -125,6 +128,11 @@ LoanDelete::doApply() // Decrement the borrower's owner count adjustOwnerCount(view, borrowerSle, -1, j_); + // These associations shouldn't do anything, but do them just to be safe + associateAsset(*loanSle, vaultAsset); + associateAsset(*brokerSle, vaultAsset); + associateAsset(*vaultSle, vaultAsset); + return tesSUCCESS; } diff --git a/src/xrpld/app/tx/detail/LoanManage.cpp b/src/xrpld/app/tx/detail/LoanManage.cpp index bd0539ae4e..17dee8ffe8 100644 --- a/src/xrpld/app/tx/detail/LoanManage.cpp +++ b/src/xrpld/app/tx/detail/LoanManage.cpp @@ -2,6 +2,7 @@ // #include +#include #include namespace xrpl { @@ -412,7 +413,7 @@ LoanManage::doApply() if (!brokerSle) return tefBAD_LEDGER; // LCOV_EXCL_LINE - auto const vaultSle = view.peek(keylet ::vault(brokerSle->at(sfVaultID))); + auto const vaultSle = view.peek(keylet::vault(brokerSle->at(sfVaultID))); if (!vaultSle) return tefBAD_LEDGER; // LCOV_EXCL_LINE auto const vaultAsset = vaultSle->at(sfAsset); @@ -426,6 +427,11 @@ LoanManage::doApply() if (tx.isFlag(tfLoanUnimpair)) return unimpairLoan(view, loanSle, vaultSle, vaultAsset, j_); // Noop, as described above. + + associateAsset(*loanSle, vaultAsset); + associateAsset(*brokerSle, vaultAsset); + associateAsset(*vaultSle, vaultAsset); + return tesSUCCESS; } diff --git a/src/xrpld/app/tx/detail/LoanPay.cpp b/src/xrpld/app/tx/detail/LoanPay.cpp index f3973d9488..13f62d4c0d 100644 --- a/src/xrpld/app/tx/detail/LoanPay.cpp +++ b/src/xrpld/app/tx/detail/LoanPay.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -485,6 +486,16 @@ LoanPay::doApply() coverAvailableProxy += totalPaidToBroker; } + associateAsset(*loanSle, asset); + associateAsset(*brokerSle, asset); + associateAsset(*vaultSle, asset); + + // Duplicate some checks after rounding + XRPL_ASSERT_PARTS( + *assetsAvailableProxy <= *assetsTotalProxy, + "xrpl::LoanPay::doApply", + "assets available must not be greater than assets outstanding"); + #if !NDEBUG auto const accountBalanceBefore = accountHolds( view, diff --git a/src/xrpld/app/tx/detail/LoanSet.cpp b/src/xrpld/app/tx/detail/LoanSet.cpp index 4c14cde421..0b83d3009f 100644 --- a/src/xrpld/app/tx/detail/LoanSet.cpp +++ b/src/xrpld/app/tx/detail/LoanSet.cpp @@ -2,6 +2,7 @@ // #include +#include #include namespace xrpl { @@ -301,17 +302,15 @@ LoanSet::preclaim(PreclaimContext const& ctx) // This check is almost duplicated in doApply, but that check is done after // the overall loan scale is known. This is mostly only relevant for // integral (non-IOU) types + for (auto const& field : getValueFields()) { - for (auto const& field : getValueFields()) + if (auto const value = tx[field]; + value && STAmount{asset, *value} != *value) { - if (auto const value = tx[field]; - value && STAmount{asset, *value} != *value) - { - JLOG(ctx.j.warn()) << field.f->getName() << " (" << *value - << ") can not be represented as a(n) " - << to_string(asset) << "."; - return tecPRECISION_LOSS; - } + JLOG(ctx.j.warn()) << field.f->getName() << " (" << *value + << ") can not be represented as a(n) " + << to_string(asset) << "."; + return tecPRECISION_LOSS; } } @@ -434,19 +433,16 @@ LoanSet::doApply() } // Check that relevant values won't lose precision. This is mostly only // relevant for IOU assets. + for (auto const& field : getValueFields()) { - for (auto const& field : getValueFields()) + if (auto const value = tx[field]; + value && !isRounded(vaultAsset, *value, properties.loanScale)) { - if (auto const value = tx[field]; - value && !isRounded(vaultAsset, *value, properties.loanScale)) - { - JLOG(j_.warn()) - << field.f->getName() << " (" << *value - << ") has too much precision. Total loan value is " - << properties.loanState.valueOutstanding - << " with a scale of " << properties.loanScale; - return tecPRECISION_LOSS; - } + JLOG(j_.warn()) << field.f->getName() << " (" << *value + << ") has too much precision. Total loan value is " + << properties.loanState.valueOutstanding + << " with a scale of " << properties.loanScale; + return tecPRECISION_LOSS; } } @@ -649,6 +645,10 @@ LoanSet::doApply() if (auto const ter = dirLink(view, borrower, loan, sfOwnerNode)) return ter; + associateAsset(*vaultSle, vaultAsset); + associateAsset(*brokerSle, vaultAsset); + associateAsset(*loan, vaultAsset); + return tesSUCCESS; } diff --git a/src/xrpld/app/tx/detail/Transactor.cpp b/src/xrpld/app/tx/detail/Transactor.cpp index a834f7c6c3..691443ed93 100644 --- a/src/xrpld/app/tx/detail/Transactor.cpp +++ b/src/xrpld/app/tx/detail/Transactor.cpp @@ -1136,6 +1136,10 @@ Transactor::operator()() { JLOG(j_.trace()) << "apply: " << ctx_.tx.getTransactionID(); + // These global updates really should have been for every Transaction + // step: preflight, preclaim, and doApply. And even calculateBaseFee. See + // with_txn_type(). + // // raii classes for the current ledger rules. // fixUniversalNumber predate the rulesGuard and should be replaced. NumberSO stNumberSO{view().rules().enabled(fixUniversalNumber)}; @@ -1152,7 +1156,7 @@ Transactor::operator()() { // LCOV_EXCL_START JLOG(j_.fatal()) << "Transaction serdes mismatch"; - JLOG(j_.info()) << to_string(ctx_.tx.getJson(JsonOptions::none)); + JLOG(j_.fatal()) << ctx_.tx.getJson(JsonOptions::none); JLOG(j_.fatal()) << s2.getJson(JsonOptions::none); UNREACHABLE( "xrpl::Transactor::operator() : transaction serdes mismatch"); diff --git a/src/xrpld/app/tx/detail/VaultClawback.cpp b/src/xrpld/app/tx/detail/VaultClawback.cpp index 2552e8c1ff..dbdf4440ec 100644 --- a/src/xrpld/app/tx/detail/VaultClawback.cpp +++ b/src/xrpld/app/tx/detail/VaultClawback.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -457,6 +458,8 @@ VaultClawback::doApply() } } + associateAsset(*vault, vaultAsset); + return tesSUCCESS; } diff --git a/src/xrpld/app/tx/detail/VaultCreate.cpp b/src/xrpld/app/tx/detail/VaultCreate.cpp index 893a1108fa..402d877a00 100644 --- a/src/xrpld/app/tx/detail/VaultCreate.cpp +++ b/src/xrpld/app/tx/detail/VaultCreate.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -230,6 +231,8 @@ VaultCreate::doApply() return err; } + associateAsset(*vault, asset); + return tesSUCCESS; } diff --git a/src/xrpld/app/tx/detail/VaultDelete.cpp b/src/xrpld/app/tx/detail/VaultDelete.cpp index 756e7b94e6..9b63c7766b 100644 --- a/src/xrpld/app/tx/detail/VaultDelete.cpp +++ b/src/xrpld/app/tx/detail/VaultDelete.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -85,6 +86,7 @@ VaultDelete::doApply() // Destroy the asset holding. auto asset = vault->at(sfAsset); + if (auto ter = removeEmptyHolding(view(), vault->at(sfAccount), asset, j_); !isTesSuccess(ter)) return ter; @@ -205,6 +207,8 @@ VaultDelete::doApply() // Destroy the vault. view().erase(vault); + associateAsset(*vault, asset); + return tesSUCCESS; } diff --git a/src/xrpld/app/tx/detail/VaultDeposit.cpp b/src/xrpld/app/tx/detail/VaultDeposit.cpp index 51b38afc36..02ef4afad1 100644 --- a/src/xrpld/app/tx/detail/VaultDeposit.cpp +++ b/src/xrpld/app/tx/detail/VaultDeposit.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -134,6 +135,7 @@ VaultDeposit::doApply() auto const vault = view().peek(keylet::vault(ctx_.tx[sfVaultID])); if (!vault) return tefINTERNAL; // LCOV_EXCL_LINE + auto const vaultAsset = vault->at(sfAsset); auto const amount = ctx_.tx[sfAmount]; // Make sure the depositor can hold shares. @@ -282,6 +284,8 @@ VaultDeposit::doApply() !isTesSuccess(ter)) return ter; + associateAsset(*vault, vaultAsset); + return tesSUCCESS; } diff --git a/src/xrpld/app/tx/detail/VaultSet.cpp b/src/xrpld/app/tx/detail/VaultSet.cpp index 648ac12c3d..13c8ad5db8 100644 --- a/src/xrpld/app/tx/detail/VaultSet.cpp +++ b/src/xrpld/app/tx/detail/VaultSet.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -128,6 +129,8 @@ VaultSet::doApply() if (!vault) return tefINTERNAL; // LCOV_EXCL_LINE + auto const vaultAsset = vault->at(sfAsset); + auto const mptIssuanceID = (*vault)[sfShareMPTID]; auto const sleIssuance = view().peek(keylet::mptIssuance(mptIssuanceID)); if (!sleIssuance) @@ -172,6 +175,8 @@ VaultSet::doApply() // to verify the operation. view().update(vault); + associateAsset(*vault, vaultAsset); + return tesSUCCESS; } diff --git a/src/xrpld/app/tx/detail/VaultWithdraw.cpp b/src/xrpld/app/tx/detail/VaultWithdraw.cpp index f8b7a1a739..9a4334e435 100644 --- a/src/xrpld/app/tx/detail/VaultWithdraw.cpp +++ b/src/xrpld/app/tx/detail/VaultWithdraw.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -115,6 +116,7 @@ VaultWithdraw::doApply() auto const amount = ctx_.tx[sfAmount]; Asset const vaultAsset = vault->at(sfAsset); + MPTIssue const share{mptIssuanceID}; STAmount sharesRedeemed = {share}; STAmount assetsWithdrawn; @@ -239,6 +241,8 @@ VaultWithdraw::doApply() auto const dstAcct = ctx_.tx[~sfDestination].value_or(account_); + associateAsset(*vault, vaultAsset); + return doWithdraw( view(), ctx_.tx, diff --git a/src/xrpld/app/tx/detail/applySteps.cpp b/src/xrpld/app/tx/detail/applySteps.cpp index e0bd9d0d2d..0fae1a15e0 100644 --- a/src/xrpld/app/tx/detail/applySteps.cpp +++ b/src/xrpld/app/tx/detail/applySteps.cpp @@ -34,8 +34,38 @@ struct UnknownTxnType : std::exception // throw an "UnknownTxnType" exception on error template auto -with_txn_type(TxType txnType, F&& f) +with_txn_type(Rules const& rules, TxType txnType, F&& f) { + // These global updates really should have been for every Transaction + // step: preflight, preclaim, calculateBaseFee, and doApply. Unfortunately, + // they were only included in doApply (via Transactor::operator()). That may + // have been sufficient when the changes were only related to operations + // that mutated data, but some features will now change how they read data, + // so these need to be more global. + // + // To prevent unintentional side effects on existing checks, they will be + // set for every operation only once SingleAssetVault (or later + // LendingProtocol) are enabled. + // + // See also Transactor::operator(). + // + std::optional stNumberSO; + std::optional rulesGuard; + std::optional mantissaScaleGuard; + if (rules.enabled(featureSingleAssetVault) || + rules.enabled(featureLendingProtocol)) + { + // raii classes for the current ledger rules. + // fixUniversalNumber predates the rulesGuard and should be replaced. + stNumberSO.emplace(rules.enabled(fixUniversalNumber)); + rulesGuard.emplace(rules); + } + else + { + // Without those features enabled, always use the old number rules. + mantissaScaleGuard.emplace(MantissaRange::small); + } + switch (txnType) { #pragma push_macro("TRANSACTION") @@ -99,7 +129,7 @@ invoke_preflight(PreflightContext const& ctx) { try { - return with_txn_type(ctx.tx.getTxnType(), [&]() { + return with_txn_type(ctx.rules, ctx.tx.getTxnType(), [&]() { auto const tec = Transactor::invokePreflight(ctx); return std::make_pair( tec, @@ -126,50 +156,51 @@ invoke_preclaim(PreclaimContext const& ctx) { // use name hiding to accomplish compile-time polymorphism of static // class functions for Transactor and derived classes. - return with_txn_type(ctx.tx.getTxnType(), [&]() -> TER { - // preclaim functionality is divided into two sections: - // 1. Up to and including the signature check: returns NotTEC. - // All transaction checks before and including checkSign - // MUST return NotTEC, or something more restrictive. - // Allowing tec results in these steps risks theft or - // destruction of funds, as a fee will be charged before the - // signature is checked. - // 2. After the signature check: returns TER. + return with_txn_type( + ctx.view.rules(), ctx.tx.getTxnType(), [&]() -> TER { + // preclaim functionality is divided into two sections: + // 1. Up to and including the signature check: returns NotTEC. + // All transaction checks before and including checkSign + // MUST return NotTEC, or something more restrictive. + // Allowing tec results in these steps risks theft or + // destruction of funds, as a fee will be charged before the + // signature is checked. + // 2. After the signature check: returns TER. - // If the transactor requires a valid account and the - // transaction doesn't list one, preflight will have already - // a flagged a failure. - auto const id = ctx.tx.getAccountID(sfAccount); + // If the transactor requires a valid account and the + // transaction doesn't list one, preflight will have already + // a flagged a failure. + auto const id = ctx.tx.getAccountID(sfAccount); - if (id != beast::zero) - { - if (NotTEC const preSigResult = [&]() -> NotTEC { - if (NotTEC const result = - T::checkSeqProxy(ctx.view, ctx.tx, ctx.j)) - return result; + if (id != beast::zero) + { + if (NotTEC const preSigResult = [&]() -> NotTEC { + if (NotTEC const result = + T::checkSeqProxy(ctx.view, ctx.tx, ctx.j)) + return result; - if (NotTEC const result = - T::checkPriorTxAndLastLedger(ctx)) - return result; + if (NotTEC const result = + T::checkPriorTxAndLastLedger(ctx)) + return result; - if (NotTEC const result = - T::checkPermission(ctx.view, ctx.tx)) - return result; + if (NotTEC const result = + T::checkPermission(ctx.view, ctx.tx)) + return result; - if (NotTEC const result = T::checkSign(ctx)) - return result; + if (NotTEC const result = T::checkSign(ctx)) + return result; - return tesSUCCESS; - }()) - return preSigResult; + return tesSUCCESS; + }()) + return preSigResult; - if (TER const result = - T::checkFee(ctx, calculateBaseFee(ctx.view, ctx.tx))) - return result; - } + if (TER const result = T::checkFee( + ctx, calculateBaseFee(ctx.view, ctx.tx))) + return result; + } - return T::preclaim(ctx); - }); + return T::preclaim(ctx); + }); } catch (UnknownTxnType const& e) { @@ -204,7 +235,7 @@ invoke_calculateBaseFee(ReadView const& view, STTx const& tx) { try { - return with_txn_type(tx.getTxnType(), [&]() { + return with_txn_type(view.rules(), tx.getTxnType(), [&]() { return T::calculateBaseFee(view, tx); }); } @@ -263,10 +294,11 @@ invoke_apply(ApplyContext& ctx) { try { - return with_txn_type(ctx.tx.getTxnType(), [&]() { - T p(ctx); - return p(); - }); + return with_txn_type( + ctx.view().rules(), ctx.tx.getTxnType(), [&]() { + T p(ctx); + return p(); + }); } catch (UnknownTxnType const& e) { From efa57e872badfacc82e4d95c65b64d0b95dc6bf3 Mon Sep 17 00:00:00 2001 From: Ed Hennis Date: Tue, 13 Jan 2026 17:53:40 -0400 Subject: [PATCH 24/30] Change LendingProtocol feature and dependencies to supported (#6146) --- include/xrpl/protocol/detail/features.macro | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/xrpl/protocol/detail/features.macro b/include/xrpl/protocol/detail/features.macro index 932668c16f..0c952bf59b 100644 --- a/include/xrpl/protocol/detail/features.macro +++ b/include/xrpl/protocol/detail/features.macro @@ -17,7 +17,7 @@ // Keep it sorted in reverse chronological order. XRPL_FIX (BatchInnerSigs, Supported::yes, VoteBehavior::DefaultNo) -XRPL_FEATURE(LendingProtocol, Supported::no, VoteBehavior::DefaultNo) +XRPL_FEATURE(LendingProtocol, Supported::yes, VoteBehavior::DefaultNo) XRPL_FEATURE(PermissionDelegationV1_1, Supported::no, VoteBehavior::DefaultNo) XRPL_FIX (DirectoryLimit, Supported::yes, VoteBehavior::DefaultNo) XRPL_FIX (IncludeKeyletFields, Supported::yes, VoteBehavior::DefaultNo) @@ -31,7 +31,7 @@ XRPL_FIX (EnforceNFTokenTrustlineV2, Supported::yes, VoteBehavior::DefaultNo XRPL_FIX (AMMv1_3, Supported::yes, VoteBehavior::DefaultNo) XRPL_FEATURE(PermissionedDEX, Supported::yes, VoteBehavior::DefaultNo) XRPL_FEATURE(Batch, Supported::yes, VoteBehavior::DefaultNo) -XRPL_FEATURE(SingleAssetVault, Supported::no, VoteBehavior::DefaultNo) +XRPL_FEATURE(SingleAssetVault, Supported::yes, VoteBehavior::DefaultNo) XRPL_FIX (PayChanCancelAfter, Supported::yes, VoteBehavior::DefaultNo) // Check flags in Credential transactions XRPL_FIX (InvalidTxFlags, Supported::yes, VoteBehavior::DefaultNo) From ebcfd6645db82e0485fdb380ecdf9f413a4c1044 Mon Sep 17 00:00:00 2001 From: Mayukha Vadari Date: Wed, 14 Jan 2026 14:40:07 -0500 Subject: [PATCH 25/30] test: Replace `failed` string in Vault test case (#6214) The word `failed` in the test case makes it hard to search through the test logs when an actual test failure occurs, so this change renames the word to just `fail` instead. --- src/test/app/Vault_test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/app/Vault_test.cpp b/src/test/app/Vault_test.cpp index a6d08b6531..41a4fc2b3b 100644 --- a/src/test/app/Vault_test.cpp +++ b/src/test/app/Vault_test.cpp @@ -2076,7 +2076,7 @@ class Vault_test : public beast::unit_test::suite PrettyAsset const& asset, Vault& vault, MPTTester& mptt) { - testcase("MPT failed reserve to re-create MPToken"); + testcase("MPT fail reserve to re-create MPToken"); auto [tx, keylet] = vault.create({.owner = owner, .asset = asset}); From c9458b72cab68d3cbbf533cc87d14309c2eb93b7 Mon Sep 17 00:00:00 2001 From: Ed Hennis Date: Wed, 14 Jan 2026 19:45:00 -0400 Subject: [PATCH 26/30] test: Suppress "parse failed" message in Batch tests (#6207) --- src/test/app/Batch_test.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/test/app/Batch_test.cpp b/src/test/app/Batch_test.cpp index 68bf7e833b..67b0933ae2 100644 --- a/src/test/app/Batch_test.cpp +++ b/src/test/app/Batch_test.cpp @@ -427,6 +427,7 @@ class Batch_test : public beast::unit_test::suite auto const batchFee = batch::calcBatchFee(env, 0, 2); auto tx1 = batch::inner(pay(alice, bob, XRP(1)), seq + 1); tx1[jss::Fee] = "1.5"; + env.set_parse_failure_expected(true); try { env(batch::outer(alice, seq, batchFee, tfAllOrNothing), @@ -438,6 +439,7 @@ class Batch_test : public beast::unit_test::suite { BEAST_EXPECT(true); } + env.set_parse_failure_expected(false); } // temSEQ_AND_TICKET: Batch: inner txn cannot have both Sequence From ec44347ffc23db2dba3332c730a94e73e22054a6 Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Thu, 15 Jan 2026 13:36:13 +0000 Subject: [PATCH 27/30] test: Use gtest instead of doctest (#6216) This change switches over the doctest framework to the gtest framework. --- .config/cspell.config.yaml | 1 + conan.lock | 2 +- conanfile.py | 2 +- src/tests/README.md | 4 +- src/tests/libxrpl/CMakeLists.txt | 4 +- src/tests/libxrpl/basics/RangeSet.cpp | 100 +- src/tests/libxrpl/basics/Slice.cpp | 42 +- src/tests/libxrpl/basics/base64.cpp | 10 +- src/tests/libxrpl/basics/contract.cpp | 12 +- src/tests/libxrpl/basics/main.cpp | 10 +- src/tests/libxrpl/basics/mulDiv.cpp | 38 +- src/tests/libxrpl/basics/scope.cpp | 44 +- src/tests/libxrpl/basics/tagged_integer.cpp | 98 +- src/tests/libxrpl/crypto/csprng.cpp | 8 +- src/tests/libxrpl/crypto/main.cpp | 10 +- src/tests/libxrpl/json/Output.cpp | 16 +- src/tests/libxrpl/json/Value.cpp | 1061 +++++++++---------- src/tests/libxrpl/json/Writer.cpp | 40 +- src/tests/libxrpl/json/main.cpp | 10 +- src/tests/libxrpl/net/HTTPClient.cpp | 40 +- src/tests/libxrpl/net/main.cpp | 10 +- 21 files changed, 782 insertions(+), 780 deletions(-) diff --git a/.config/cspell.config.yaml b/.config/cspell.config.yaml index 8f782d9960..3bd295c14b 100644 --- a/.config/cspell.config.yaml +++ b/.config/cspell.config.yaml @@ -270,6 +270,7 @@ words: - xbridge - xchain - ximinez + - EXPECT_STREQ - XMACRO - xrpkuwait - xrpl diff --git a/conan.lock b/conan.lock index 44dc9031d2..f3fa1e5f6e 100644 --- a/conan.lock +++ b/conan.lock @@ -17,9 +17,9 @@ "libbacktrace/cci.20210118#a7691bfccd8caaf66309df196790a5a1%1765842973.03", "libarchive/3.8.1#ffee18995c706e02bf96e7a2f7042e0d%1765850144.736", "jemalloc/5.3.0#e951da9cf599e956cebc117880d2d9f8%1729241615.244", + "gtest/1.17.0#5224b3b3ff3b4ce1133cbdd27d53ee7d%1768312129.152", "grpc/1.72.0#f244a57bff01e708c55a1100b12e1589%1765850193.734", "ed25519/2015.03#ae761bdc52730a843f0809bdf6c1b1f6%1765850143.772", - "doctest/2.4.12#eb9fb352fb2fdfc8abb17ec270945165%1765850143.95", "date/3.0.4#862e11e80030356b53c2c38599ceb32b%1765850143.772", "c-ares/1.34.5#5581c2b62a608b40bb85d965ab3ec7c8%1765850144.336", "bzip2/1.0.8#c470882369c2d95c5c77e970c0c7e321%1765850143.837", diff --git a/conanfile.py b/conanfile.py index 96e3384979..8501909ce3 100644 --- a/conanfile.py +++ b/conanfile.py @@ -39,7 +39,7 @@ class Xrpl(ConanFile): ] test_requires = [ - "doctest/2.4.12", + "gtest/1.17.0", ] tool_requires = [ diff --git a/src/tests/README.md b/src/tests/README.md index 7c4cc5edf8..2a642a7633 100644 --- a/src/tests/README.md +++ b/src/tests/README.md @@ -1,5 +1,5 @@ # Unit tests This directory contains unit tests for the project. The difference from existing `src/test` folder -is that we switch to 3rd party testing framework (doctest). We intend to gradually move existing tests -from our own framework to doctest and such tests will be moved to this new folder. +is that we switch to 3rd party testing framework (`gtest`). We intend to gradually move existing tests +from our own framework to `gtest` and such tests will be moved to this new folder. diff --git a/src/tests/libxrpl/CMakeLists.txt b/src/tests/libxrpl/CMakeLists.txt index a2374698d9..74dc184700 100644 --- a/src/tests/libxrpl/CMakeLists.txt +++ b/src/tests/libxrpl/CMakeLists.txt @@ -1,14 +1,14 @@ include(XrplAddTest) # Test requirements. -find_package(doctest REQUIRED) +find_package(GTest REQUIRED) # Custom target for all tests defined in this file add_custom_target(xrpl.tests) # Common library dependencies for the rest of the tests. add_library(xrpl.imports.test INTERFACE) -target_link_libraries(xrpl.imports.test INTERFACE doctest::doctest xrpl.libxrpl) +target_link_libraries(xrpl.imports.test INTERFACE gtest::gtest xrpl.libxrpl) # One test for each module. xrpl_add_test(basics) diff --git a/src/tests/libxrpl/basics/RangeSet.cpp b/src/tests/libxrpl/basics/RangeSet.cpp index 8c43b26758..d0fc656368 100644 --- a/src/tests/libxrpl/basics/RangeSet.cpp +++ b/src/tests/libxrpl/basics/RangeSet.cpp @@ -1,15 +1,13 @@ #include -#include +#include #include #include using namespace xrpl; -TEST_SUITE_BEGIN("RangeSet"); - -TEST_CASE("prevMissing") +TEST(RangeSet, prevMissing) { // Set will include: // [ 0, 5] @@ -31,80 +29,78 @@ TEST_CASE("prevMissing") expected = ((i % 10) > 6) ? (i - 1) : oneBelowRange; } - CHECK(prevMissing(set, i) == expected); + EXPECT_EQ(prevMissing(set, i), expected); } } -TEST_CASE("toString") +TEST(RangeSet, toString) { RangeSet set; - CHECK(to_string(set) == "empty"); + EXPECT_EQ(to_string(set), "empty"); set.insert(1); - CHECK(to_string(set) == "1"); + EXPECT_EQ(to_string(set), "1"); set.insert(range(4u, 6u)); - CHECK(to_string(set) == "1,4-6"); + EXPECT_EQ(to_string(set), "1,4-6"); set.insert(2); - CHECK(to_string(set) == "1-2,4-6"); + EXPECT_EQ(to_string(set), "1-2,4-6"); set.erase(range(4u, 5u)); - CHECK(to_string(set) == "1-2,6"); + EXPECT_EQ(to_string(set), "1-2,6"); } -TEST_CASE("fromString") +TEST(RangeSet, fromString) { RangeSet set; - CHECK(!from_string(set, "")); - CHECK(boost::icl::length(set) == 0); + EXPECT_FALSE(from_string(set, "")); + EXPECT_EQ(boost::icl::length(set), 0); - CHECK(!from_string(set, "#")); - CHECK(boost::icl::length(set) == 0); + EXPECT_FALSE(from_string(set, "#")); + EXPECT_EQ(boost::icl::length(set), 0); - CHECK(!from_string(set, ",")); - CHECK(boost::icl::length(set) == 0); + EXPECT_FALSE(from_string(set, ",")); + EXPECT_EQ(boost::icl::length(set), 0); - CHECK(!from_string(set, ",-")); - CHECK(boost::icl::length(set) == 0); + EXPECT_FALSE(from_string(set, ",-")); + EXPECT_EQ(boost::icl::length(set), 0); - CHECK(!from_string(set, "1,,2")); - CHECK(boost::icl::length(set) == 0); + EXPECT_FALSE(from_string(set, "1,,2")); + EXPECT_EQ(boost::icl::length(set), 0); - CHECK(from_string(set, "1")); - CHECK(boost::icl::length(set) == 1); - CHECK(boost::icl::first(set) == 1); + EXPECT_TRUE(from_string(set, "1")); + EXPECT_EQ(boost::icl::length(set), 1); + EXPECT_EQ(boost::icl::first(set), 1); - CHECK(from_string(set, "1,1")); - CHECK(boost::icl::length(set) == 1); - CHECK(boost::icl::first(set) == 1); + EXPECT_TRUE(from_string(set, "1,1")); + EXPECT_EQ(boost::icl::length(set), 1); + EXPECT_EQ(boost::icl::first(set), 1); - CHECK(from_string(set, "1-1")); - CHECK(boost::icl::length(set) == 1); - CHECK(boost::icl::first(set) == 1); + EXPECT_TRUE(from_string(set, "1-1")); + EXPECT_EQ(boost::icl::length(set), 1); + EXPECT_EQ(boost::icl::first(set), 1); - CHECK(from_string(set, "1,4-6")); - CHECK(boost::icl::length(set) == 4); - CHECK(boost::icl::first(set) == 1); - CHECK(!boost::icl::contains(set, 2)); - CHECK(!boost::icl::contains(set, 3)); - CHECK(boost::icl::contains(set, 4)); - CHECK(boost::icl::contains(set, 5)); - CHECK(boost::icl::last(set) == 6); + EXPECT_TRUE(from_string(set, "1,4-6")); + EXPECT_EQ(boost::icl::length(set), 4); + EXPECT_EQ(boost::icl::first(set), 1); + EXPECT_FALSE(boost::icl::contains(set, 2)); + EXPECT_FALSE(boost::icl::contains(set, 3)); + EXPECT_TRUE(boost::icl::contains(set, 4)); + EXPECT_TRUE(boost::icl::contains(set, 5)); + EXPECT_EQ(boost::icl::last(set), 6); - CHECK(from_string(set, "1-2,4-6")); - CHECK(boost::icl::length(set) == 5); - CHECK(boost::icl::first(set) == 1); - CHECK(boost::icl::contains(set, 2)); - CHECK(boost::icl::contains(set, 4)); - CHECK(boost::icl::last(set) == 6); + EXPECT_TRUE(from_string(set, "1-2,4-6")); + EXPECT_EQ(boost::icl::length(set), 5); + EXPECT_EQ(boost::icl::first(set), 1); + EXPECT_TRUE(boost::icl::contains(set, 2)); + EXPECT_TRUE(boost::icl::contains(set, 4)); + EXPECT_EQ(boost::icl::last(set), 6); - CHECK(from_string(set, "1-2,6")); - CHECK(boost::icl::length(set) == 3); - CHECK(boost::icl::first(set) == 1); - CHECK(boost::icl::contains(set, 2)); - CHECK(boost::icl::last(set) == 6); + EXPECT_TRUE(from_string(set, "1-2,6")); + EXPECT_EQ(boost::icl::length(set), 3); + EXPECT_EQ(boost::icl::first(set), 1); + EXPECT_TRUE(boost::icl::contains(set, 2)); + EXPECT_EQ(boost::icl::last(set), 6); } - -TEST_SUITE_END(); diff --git a/src/tests/libxrpl/basics/Slice.cpp b/src/tests/libxrpl/basics/Slice.cpp index 03d89ff174..b36abe596d 100644 --- a/src/tests/libxrpl/basics/Slice.cpp +++ b/src/tests/libxrpl/basics/Slice.cpp @@ -1,6 +1,6 @@ #include -#include +#include #include #include @@ -12,37 +12,35 @@ static std::uint8_t const data[] = { 0x18, 0xb4, 0x70, 0xcb, 0xf5, 0xac, 0x2d, 0x89, 0x4d, 0x19, 0x9c, 0xf0, 0x2c, 0x15, 0xd1, 0xf9, 0x9b, 0x66, 0xd2, 0x30, 0xd3}; -TEST_SUITE_BEGIN("Slice"); - -TEST_CASE("equality & inequality") +TEST(Slice, equality_and_inequality) { Slice const s0{}; - CHECK(s0.size() == 0); - CHECK(s0.data() == nullptr); - CHECK(s0 == s0); + EXPECT_EQ(s0.size(), 0); + EXPECT_EQ(s0.data(), nullptr); + EXPECT_EQ(s0, s0); // Test slices of equal and unequal size pointing to same data: for (std::size_t i = 0; i != sizeof(data); ++i) { Slice const s1{data, i}; - CHECK(s1.size() == i); - CHECK(s1.data() != nullptr); + EXPECT_EQ(s1.size(), i); + EXPECT_NE(s1.data(), nullptr); if (i == 0) - CHECK(s1 == s0); + EXPECT_EQ(s1, s0); else - CHECK(s1 != s0); + EXPECT_NE(s1, s0); for (std::size_t j = 0; j != sizeof(data); ++j) { Slice const s2{data, j}; if (i == j) - CHECK(s1 == s2); + EXPECT_EQ(s1, s2); else - CHECK(s1 != s2); + EXPECT_NE(s1, s2); } } @@ -53,22 +51,22 @@ TEST_CASE("equality & inequality") for (std::size_t i = 0; i != sizeof(data); ++i) a[i] = b[i] = data[i]; - CHECK(makeSlice(a) == makeSlice(b)); + EXPECT_EQ(makeSlice(a), makeSlice(b)); b[7]++; - CHECK(makeSlice(a) != makeSlice(b)); + EXPECT_NE(makeSlice(a), makeSlice(b)); a[7]++; - CHECK(makeSlice(a) == makeSlice(b)); + EXPECT_EQ(makeSlice(a), makeSlice(b)); } -TEST_CASE("indexing") +TEST(Slice, indexing) { Slice const s{data, sizeof(data)}; for (std::size_t i = 0; i != sizeof(data); ++i) - CHECK(s[i] == data[i]); + EXPECT_EQ(s[i], data[i]); } -TEST_CASE("advancing") +TEST(Slice, advancing) { for (std::size_t i = 0; i < sizeof(data); ++i) { @@ -77,10 +75,8 @@ TEST_CASE("advancing") Slice s(data + i, sizeof(data) - i); s += j; - CHECK(s.data() == data + i + j); - CHECK(s.size() == sizeof(data) - i - j); + EXPECT_EQ(s.data(), data + i + j); + EXPECT_EQ(s.size(), sizeof(data) - i - j); } } } - -TEST_SUITE_END(); diff --git a/src/tests/libxrpl/basics/base64.cpp b/src/tests/libxrpl/basics/base64.cpp index f6544105d8..cfffadf660 100644 --- a/src/tests/libxrpl/basics/base64.cpp +++ b/src/tests/libxrpl/basics/base64.cpp @@ -1,6 +1,6 @@ #include -#include +#include #include @@ -10,11 +10,11 @@ static void check(std::string const& in, std::string const& out) { auto const encoded = base64_encode(in); - CHECK(encoded == out); - CHECK(base64_decode(encoded) == in); + EXPECT_EQ(encoded, out); + EXPECT_EQ(base64_decode(encoded), in); } -TEST_CASE("base64") +TEST(base64, base64) { // cspell: disable check("", ""); @@ -46,5 +46,5 @@ TEST_CASE("base64") std::string const notBase64 = "not_base64!!"; std::string const truncated = "not"; - CHECK(base64_decode(notBase64) == base64_decode(truncated)); + EXPECT_EQ(base64_decode(notBase64), base64_decode(truncated)); } diff --git a/src/tests/libxrpl/basics/contract.cpp b/src/tests/libxrpl/basics/contract.cpp index a1f6f0b777..d9b729e85d 100644 --- a/src/tests/libxrpl/basics/contract.cpp +++ b/src/tests/libxrpl/basics/contract.cpp @@ -1,13 +1,13 @@ #include -#include +#include #include #include using namespace xrpl; -TEST_CASE("contract") +TEST(contract, contract) { try { @@ -15,7 +15,7 @@ TEST_CASE("contract") } catch (std::runtime_error const& e1) { - CHECK(std::string(e1.what()) == "Throw test"); + EXPECT_STREQ(e1.what(), "Throw test"); try { @@ -23,15 +23,15 @@ TEST_CASE("contract") } catch (std::runtime_error const& e2) { - CHECK(std::string(e2.what()) == "Throw test"); + EXPECT_STREQ(e2.what(), "Throw test"); } catch (...) { - CHECK(false); + FAIL() << "std::runtime_error should have been re-caught"; } } catch (...) { - CHECK(false); + FAIL() << "std::runtime_error should have been caught the first time"; } } diff --git a/src/tests/libxrpl/basics/main.cpp b/src/tests/libxrpl/basics/main.cpp index 0a3f254ea8..5142bbe08a 100644 --- a/src/tests/libxrpl/basics/main.cpp +++ b/src/tests/libxrpl/basics/main.cpp @@ -1,2 +1,8 @@ -#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN -#include +#include + +int +main(int argc, char** argv) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/src/tests/libxrpl/basics/mulDiv.cpp b/src/tests/libxrpl/basics/mulDiv.cpp index d3c58ea2f4..c98c3fd61a 100644 --- a/src/tests/libxrpl/basics/mulDiv.cpp +++ b/src/tests/libxrpl/basics/mulDiv.cpp @@ -1,45 +1,45 @@ #include -#include +#include #include #include using namespace xrpl; -TEST_CASE("mulDiv") +TEST(mulDiv, mulDiv) { auto const max = std::numeric_limits::max(); std::uint64_t const max32 = std::numeric_limits::max(); auto result = mulDiv(85, 20, 5); - REQUIRE(result); - CHECK(*result == 340); + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(*result, 340); result = mulDiv(20, 85, 5); - REQUIRE(result); - CHECK(*result == 340); + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(*result, 340); result = mulDiv(0, max - 1, max - 3); - REQUIRE(result); - CHECK(*result == 0); + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(*result, 0); result = mulDiv(max - 1, 0, max - 3); - REQUIRE(result); - CHECK(*result == 0); + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(*result, 0); result = mulDiv(max, 2, max / 2); - REQUIRE(result); - CHECK(*result == 4); + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(*result, 4); result = mulDiv(max, 1000, max / 1000); - REQUIRE(result); - CHECK(*result == 1000000); + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(*result, 1000000); result = mulDiv(max, 1000, max / 1001); - REQUIRE(result); - CHECK(*result == 1001000); + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(*result, 1001000); result = mulDiv(max32 + 1, max32 + 1, 5); - REQUIRE(result); - CHECK(*result == 3689348814741910323); + ASSERT_TRUE(result.has_value()); + EXPECT_EQ(*result, 3689348814741910323); // Overflow result = mulDiv(max - 1, max - 2, 5); - CHECK(!result); + EXPECT_FALSE(result.has_value()); } diff --git a/src/tests/libxrpl/basics/scope.cpp b/src/tests/libxrpl/basics/scope.cpp index b3774d54bd..309a41ec04 100644 --- a/src/tests/libxrpl/basics/scope.cpp +++ b/src/tests/libxrpl/basics/scope.cpp @@ -1,10 +1,10 @@ #include -#include +#include using namespace xrpl; -TEST_CASE("scope_exit") +TEST(scope, scope_exit) { // scope_exit always executes the functor on destruction, // unless release() is called @@ -12,23 +12,23 @@ TEST_CASE("scope_exit") { scope_exit x{[&i]() { i = 1; }}; } - CHECK(i == 1); + EXPECT_EQ(i, 1); { scope_exit x{[&i]() { i = 2; }}; x.release(); } - CHECK(i == 1); + EXPECT_EQ(i, 1); { scope_exit x{[&i]() { i += 2; }}; auto x2 = std::move(x); } - CHECK(i == 3); + EXPECT_EQ(i, 3); { scope_exit x{[&i]() { i = 4; }}; x.release(); auto x2 = std::move(x); } - CHECK(i == 3); + EXPECT_EQ(i, 3); { try { @@ -39,7 +39,7 @@ TEST_CASE("scope_exit") { } } - CHECK(i == 5); + EXPECT_EQ(i, 5); { try { @@ -51,10 +51,10 @@ TEST_CASE("scope_exit") { } } - CHECK(i == 5); + EXPECT_EQ(i, 5); } -TEST_CASE("scope_fail") +TEST(scope, scope_fail) { // scope_fail executes the functor on destruction only // if an exception is unwinding, unless release() is called @@ -62,23 +62,23 @@ TEST_CASE("scope_fail") { scope_fail x{[&i]() { i = 1; }}; } - CHECK(i == 0); + EXPECT_EQ(i, 0); { scope_fail x{[&i]() { i = 2; }}; x.release(); } - CHECK(i == 0); + EXPECT_EQ(i, 0); { scope_fail x{[&i]() { i = 3; }}; auto x2 = std::move(x); } - CHECK(i == 0); + EXPECT_EQ(i, 0); { scope_fail x{[&i]() { i = 4; }}; x.release(); auto x2 = std::move(x); } - CHECK(i == 0); + EXPECT_EQ(i, 0); { try { @@ -89,7 +89,7 @@ TEST_CASE("scope_fail") { } } - CHECK(i == 5); + EXPECT_EQ(i, 5); { try { @@ -101,10 +101,10 @@ TEST_CASE("scope_fail") { } } - CHECK(i == 5); + EXPECT_EQ(i, 5); } -TEST_CASE("scope_success") +TEST(scope, scope_success) { // scope_success executes the functor on destruction only // if an exception is not unwinding, unless release() is called @@ -112,23 +112,23 @@ TEST_CASE("scope_success") { scope_success x{[&i]() { i = 1; }}; } - CHECK(i == 1); + EXPECT_EQ(i, 1); { scope_success x{[&i]() { i = 2; }}; x.release(); } - CHECK(i == 1); + EXPECT_EQ(i, 1); { scope_success x{[&i]() { i += 2; }}; auto x2 = std::move(x); } - CHECK(i == 3); + EXPECT_EQ(i, 3); { scope_success x{[&i]() { i = 4; }}; x.release(); auto x2 = std::move(x); } - CHECK(i == 3); + EXPECT_EQ(i, 3); { try { @@ -139,7 +139,7 @@ TEST_CASE("scope_success") { } } - CHECK(i == 3); + EXPECT_EQ(i, 3); { try { @@ -151,5 +151,5 @@ TEST_CASE("scope_success") { } } - CHECK(i == 3); + EXPECT_EQ(i, 3); } diff --git a/src/tests/libxrpl/basics/tagged_integer.cpp b/src/tests/libxrpl/basics/tagged_integer.cpp index 45efc579ab..09f8b6787b 100644 --- a/src/tests/libxrpl/basics/tagged_integer.cpp +++ b/src/tests/libxrpl/basics/tagged_integer.cpp @@ -1,6 +1,6 @@ #include -#include +#include #include @@ -102,127 +102,123 @@ static_assert( !std::is_convertible::value, "TagUInt2 should not be convertible to a TagUInt3"); -TEST_SUITE_BEGIN("tagged_integer"); - using TagInt = tagged_integer; -TEST_CASE("comparison operators") +TEST(tagged_integer, comparison_operators) { TagInt const zero(0); TagInt const one(1); - CHECK(one == one); - CHECK(!(one == zero)); + EXPECT_TRUE(one == one); + EXPECT_FALSE(one == zero); - CHECK(one != zero); - CHECK(!(one != one)); + EXPECT_TRUE(one != zero); + EXPECT_FALSE(one != one); - CHECK(zero < one); - CHECK(!(one < zero)); + EXPECT_TRUE(zero < one); + EXPECT_FALSE(one < zero); - CHECK(one > zero); - CHECK(!(zero > one)); + EXPECT_TRUE(one > zero); + EXPECT_FALSE(zero > one); - CHECK(one >= one); - CHECK(one >= zero); - CHECK(!(zero >= one)); + EXPECT_TRUE(one >= one); + EXPECT_TRUE(one >= zero); + EXPECT_FALSE(zero >= one); - CHECK(zero <= one); - CHECK(zero <= zero); - CHECK(!(one <= zero)); + EXPECT_TRUE(zero <= one); + EXPECT_TRUE(zero <= zero); + EXPECT_FALSE(one <= zero); } -TEST_CASE("increment / decrement operators") +TEST(tagged_integer, increment_decrement_operators) { TagInt const zero(0); TagInt const one(1); TagInt a{0}; ++a; - CHECK(a == one); + EXPECT_EQ(a, one); --a; - CHECK(a == zero); + EXPECT_EQ(a, zero); a++; - CHECK(a == one); + EXPECT_EQ(a, one); a--; - CHECK(a == zero); + EXPECT_EQ(a, zero); } -TEST_CASE("arithmetic operators") +TEST(tagged_integer, arithmetic_operators) { TagInt a{-2}; - CHECK(+a == TagInt{-2}); - CHECK(-a == TagInt{2}); - CHECK(TagInt{-3} + TagInt{4} == TagInt{1}); - CHECK(TagInt{-3} - TagInt{4} == TagInt{-7}); - CHECK(TagInt{-3} * TagInt{4} == TagInt{-12}); - CHECK(TagInt{8} / TagInt{4} == TagInt{2}); - CHECK(TagInt{7} % TagInt{4} == TagInt{3}); + EXPECT_EQ(+a, TagInt{-2}); + EXPECT_EQ(-a, TagInt{2}); + EXPECT_EQ(TagInt{-3} + TagInt{4}, TagInt{1}); + EXPECT_EQ(TagInt{-3} - TagInt{4}, TagInt{-7}); + EXPECT_EQ(TagInt{-3} * TagInt{4}, TagInt{-12}); + EXPECT_EQ(TagInt{8} / TagInt{4}, TagInt{2}); + EXPECT_EQ(TagInt{7} % TagInt{4}, TagInt{3}); - CHECK(~TagInt{8} == TagInt{~TagInt::value_type{8}}); - CHECK((TagInt{6} & TagInt{3}) == TagInt{2}); - CHECK((TagInt{6} | TagInt{3}) == TagInt{7}); - CHECK((TagInt{6} ^ TagInt{3}) == TagInt{5}); + EXPECT_EQ(~TagInt{8}, TagInt{~TagInt::value_type{8}}); + EXPECT_EQ((TagInt{6} & TagInt{3}), TagInt{2}); + EXPECT_EQ((TagInt{6} | TagInt{3}), TagInt{7}); + EXPECT_EQ((TagInt{6} ^ TagInt{3}), TagInt{5}); - CHECK((TagInt{4} << TagInt{2}) == TagInt{16}); - CHECK((TagInt{16} >> TagInt{2}) == TagInt{4}); + EXPECT_EQ((TagInt{4} << TagInt{2}), TagInt{16}); + EXPECT_EQ((TagInt{16} >> TagInt{2}), TagInt{4}); } -TEST_CASE("assignment operators") +TEST(tagged_integer, assignment_operators) { TagInt a{-2}; TagInt b{0}; b = a; - CHECK(b == TagInt{-2}); + EXPECT_EQ(b, TagInt{-2}); // -3 + 4 == 1 a = TagInt{-3}; a += TagInt{4}; - CHECK(a == TagInt{1}); + EXPECT_EQ(a, TagInt{1}); // -3 - 4 == -7 a = TagInt{-3}; a -= TagInt{4}; - CHECK(a == TagInt{-7}); + EXPECT_EQ(a, TagInt{-7}); // -3 * 4 == -12 a = TagInt{-3}; a *= TagInt{4}; - CHECK(a == TagInt{-12}); + EXPECT_EQ(a, TagInt{-12}); // 8/4 == 2 a = TagInt{8}; a /= TagInt{4}; - CHECK(a == TagInt{2}); + EXPECT_EQ(a, TagInt{2}); // 7 % 4 == 3 a = TagInt{7}; a %= TagInt{4}; - CHECK(a == TagInt{3}); + EXPECT_EQ(a, TagInt{3}); // 6 & 3 == 2 a = TagInt{6}; a /= TagInt{3}; - CHECK(a == TagInt{2}); + EXPECT_EQ(a, TagInt{2}); // 6 | 3 == 7 a = TagInt{6}; a |= TagInt{3}; - CHECK(a == TagInt{7}); + EXPECT_EQ(a, TagInt{7}); // 6 ^ 3 == 5 a = TagInt{6}; a ^= TagInt{3}; - CHECK(a == TagInt{5}); + EXPECT_EQ(a, TagInt{5}); // 4 << 2 == 16 a = TagInt{4}; a <<= TagInt{2}; - CHECK(a == TagInt{16}); + EXPECT_EQ(a, TagInt{16}); // 16 >> 2 == 4 a = TagInt{16}; a >>= TagInt{2}; - CHECK(a == TagInt{4}); + EXPECT_EQ(a, TagInt{4}); } - -TEST_SUITE_END(); diff --git a/src/tests/libxrpl/crypto/csprng.cpp b/src/tests/libxrpl/crypto/csprng.cpp index e59c8a555a..41dcfd57a9 100644 --- a/src/tests/libxrpl/crypto/csprng.cpp +++ b/src/tests/libxrpl/crypto/csprng.cpp @@ -1,15 +1,15 @@ #include -#include +#include using namespace xrpl; -TEST_CASE("get values") +TEST(csprng, get_values) { auto& engine = crypto_prng(); auto rand_val = engine(); - CHECK(rand_val >= engine.min()); - CHECK(rand_val <= engine.max()); + EXPECT_GE(rand_val, engine.min()); + EXPECT_LE(rand_val, engine.max()); uint16_t twoByte{0}; engine(&twoByte, sizeof(uint16_t)); } diff --git a/src/tests/libxrpl/crypto/main.cpp b/src/tests/libxrpl/crypto/main.cpp index 0a3f254ea8..5142bbe08a 100644 --- a/src/tests/libxrpl/crypto/main.cpp +++ b/src/tests/libxrpl/crypto/main.cpp @@ -1,2 +1,8 @@ -#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN -#include +#include + +int +main(int argc, char** argv) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/src/tests/libxrpl/json/Output.cpp b/src/tests/libxrpl/json/Output.cpp index 6e6c20a0e5..96d7369d51 100644 --- a/src/tests/libxrpl/json/Output.cpp +++ b/src/tests/libxrpl/json/Output.cpp @@ -2,31 +2,29 @@ #include #include -#include +#include #include using namespace xrpl; using namespace Json; -TEST_SUITE_BEGIN("JsonOutput"); - static void checkOutput(std::string const& valueDesc) { std::string output; Json::Value value; - REQUIRE(Json::Reader().parse(valueDesc, value)); + ASSERT_TRUE(Json::Reader().parse(valueDesc, value)); auto out = stringOutput(output); outputJson(value, out); auto expected = Json::FastWriter().write(value); - CHECK(output == expected); - CHECK(output == valueDesc); - CHECK(output == jsonAsString(value)); + EXPECT_EQ(output, expected); + EXPECT_EQ(output, valueDesc); + EXPECT_EQ(output, jsonAsString(value)); } -TEST_CASE("output cases") +TEST(JsonOutput, output_cases) { checkOutput("{}"); checkOutput("[]"); @@ -36,5 +34,3 @@ TEST_CASE("output cases") checkOutput("[[]]"); checkOutput(R"({"array":[{"12":23},{},null,false,0.5]})"); } - -TEST_SUITE_END(); diff --git a/src/tests/libxrpl/json/Value.cpp b/src/tests/libxrpl/json/Value.cpp index 25bd2f548d..4db1274a4d 100644 --- a/src/tests/libxrpl/json/Value.cpp +++ b/src/tests/libxrpl/json/Value.cpp @@ -4,7 +4,7 @@ #include #include -#include +#include #include #include @@ -14,9 +14,7 @@ namespace xrpl { -TEST_SUITE_BEGIN("json_value"); - -TEST_CASE("limits") +TEST(json_value, limits) { using namespace Json; static_assert(Value::minInt == Int(~(UInt(-1) / 2))); @@ -24,31 +22,31 @@ TEST_CASE("limits") static_assert(Value::maxUInt == UInt(-1)); } -TEST_CASE("construct and compare Json::StaticString") +TEST(json_value, construct_and_compare_Json_StaticString) { static constexpr char sample[]{"Contents of a Json::StaticString"}; static constexpr Json::StaticString test1(sample); char const* addrTest1{test1}; - CHECK(addrTest1 == &sample[0]); - CHECK(test1.c_str() == &sample[0]); + EXPECT_EQ(addrTest1, &sample[0]); + EXPECT_EQ(test1.c_str(), &sample[0]); static constexpr Json::StaticString test2{ "Contents of a Json::StaticString"}; static constexpr Json::StaticString test3{"Another StaticString"}; - CHECK(test1 == test2); - CHECK(test1 != test3); + EXPECT_EQ(test1, test2); + EXPECT_NE(test1, test3); std::string str{sample}; - CHECK(str == test2); - CHECK(str != test3); - CHECK(test2 == str); - CHECK(test3 != str); + EXPECT_EQ(str, test2); + EXPECT_NE(str, test3); + EXPECT_EQ(test2, str); + EXPECT_NE(test3, str); } -TEST_CASE("different types") +TEST(json_value, different_types) { // Exercise ValueType constructor static constexpr Json::StaticString staticStr{"staticStr"}; @@ -56,166 +54,166 @@ TEST_CASE("different types") auto testCopy = [](Json::ValueType typ) { Json::Value val{typ}; Json::Value cpy{val}; - CHECK(val.type() == typ); - CHECK(cpy.type() == typ); + EXPECT_EQ(val.type(), typ); + EXPECT_EQ(cpy.type(), typ); return val; }; { Json::Value const nullV{testCopy(Json::nullValue)}; - CHECK(nullV.isNull()); - CHECK(!nullV.isBool()); - CHECK(!nullV.isInt()); - CHECK(!nullV.isUInt()); - CHECK(!nullV.isIntegral()); - CHECK(!nullV.isDouble()); - CHECK(!nullV.isNumeric()); - CHECK(!nullV.isString()); - CHECK(!nullV.isArray()); - CHECK(nullV.isArrayOrNull()); - CHECK(!nullV.isObject()); - CHECK(nullV.isObjectOrNull()); + EXPECT_TRUE(nullV.isNull()); + EXPECT_FALSE(nullV.isBool()); + EXPECT_FALSE(nullV.isInt()); + EXPECT_FALSE(nullV.isUInt()); + EXPECT_FALSE(nullV.isIntegral()); + EXPECT_FALSE(nullV.isDouble()); + EXPECT_FALSE(nullV.isNumeric()); + EXPECT_FALSE(nullV.isString()); + EXPECT_FALSE(nullV.isArray()); + EXPECT_TRUE(nullV.isArrayOrNull()); + EXPECT_FALSE(nullV.isObject()); + EXPECT_TRUE(nullV.isObjectOrNull()); } { Json::Value const intV{testCopy(Json::intValue)}; - CHECK(!intV.isNull()); - CHECK(!intV.isBool()); - CHECK(intV.isInt()); - CHECK(!intV.isUInt()); - CHECK(intV.isIntegral()); - CHECK(!intV.isDouble()); - CHECK(intV.isNumeric()); - CHECK(!intV.isString()); - CHECK(!intV.isArray()); - CHECK(!intV.isArrayOrNull()); - CHECK(!intV.isObject()); - CHECK(!intV.isObjectOrNull()); + EXPECT_FALSE(intV.isNull()); + EXPECT_FALSE(intV.isBool()); + EXPECT_TRUE(intV.isInt()); + EXPECT_FALSE(intV.isUInt()); + EXPECT_TRUE(intV.isIntegral()); + EXPECT_FALSE(intV.isDouble()); + EXPECT_TRUE(intV.isNumeric()); + EXPECT_FALSE(intV.isString()); + EXPECT_FALSE(intV.isArray()); + EXPECT_FALSE(intV.isArrayOrNull()); + EXPECT_FALSE(intV.isObject()); + EXPECT_FALSE(intV.isObjectOrNull()); } { Json::Value const uintV{testCopy(Json::uintValue)}; - CHECK(!uintV.isNull()); - CHECK(!uintV.isBool()); - CHECK(!uintV.isInt()); - CHECK(uintV.isUInt()); - CHECK(uintV.isIntegral()); - CHECK(!uintV.isDouble()); - CHECK(uintV.isNumeric()); - CHECK(!uintV.isString()); - CHECK(!uintV.isArray()); - CHECK(!uintV.isArrayOrNull()); - CHECK(!uintV.isObject()); - CHECK(!uintV.isObjectOrNull()); + EXPECT_FALSE(uintV.isNull()); + EXPECT_FALSE(uintV.isBool()); + EXPECT_FALSE(uintV.isInt()); + EXPECT_TRUE(uintV.isUInt()); + EXPECT_TRUE(uintV.isIntegral()); + EXPECT_FALSE(uintV.isDouble()); + EXPECT_TRUE(uintV.isNumeric()); + EXPECT_FALSE(uintV.isString()); + EXPECT_FALSE(uintV.isArray()); + EXPECT_FALSE(uintV.isArrayOrNull()); + EXPECT_FALSE(uintV.isObject()); + EXPECT_FALSE(uintV.isObjectOrNull()); } { Json::Value const realV{testCopy(Json::realValue)}; - CHECK(!realV.isNull()); - CHECK(!realV.isBool()); - CHECK(!realV.isInt()); - CHECK(!realV.isUInt()); - CHECK(!realV.isIntegral()); - CHECK(realV.isDouble()); - CHECK(realV.isNumeric()); - CHECK(!realV.isString()); - CHECK(!realV.isArray()); - CHECK(!realV.isArrayOrNull()); - CHECK(!realV.isObject()); - CHECK(!realV.isObjectOrNull()); + EXPECT_FALSE(realV.isNull()); + EXPECT_FALSE(realV.isBool()); + EXPECT_FALSE(realV.isInt()); + EXPECT_FALSE(realV.isUInt()); + EXPECT_FALSE(realV.isIntegral()); + EXPECT_TRUE(realV.isDouble()); + EXPECT_TRUE(realV.isNumeric()); + EXPECT_FALSE(realV.isString()); + EXPECT_FALSE(realV.isArray()); + EXPECT_FALSE(realV.isArrayOrNull()); + EXPECT_FALSE(realV.isObject()); + EXPECT_FALSE(realV.isObjectOrNull()); } { Json::Value const stringV{testCopy(Json::stringValue)}; - CHECK(!stringV.isNull()); - CHECK(!stringV.isBool()); - CHECK(!stringV.isInt()); - CHECK(!stringV.isUInt()); - CHECK(!stringV.isIntegral()); - CHECK(!stringV.isDouble()); - CHECK(!stringV.isNumeric()); - CHECK(stringV.isString()); - CHECK(!stringV.isArray()); - CHECK(!stringV.isArrayOrNull()); - CHECK(!stringV.isObject()); - CHECK(!stringV.isObjectOrNull()); + EXPECT_FALSE(stringV.isNull()); + EXPECT_FALSE(stringV.isBool()); + EXPECT_FALSE(stringV.isInt()); + EXPECT_FALSE(stringV.isUInt()); + EXPECT_FALSE(stringV.isIntegral()); + EXPECT_FALSE(stringV.isDouble()); + EXPECT_FALSE(stringV.isNumeric()); + EXPECT_TRUE(stringV.isString()); + EXPECT_FALSE(stringV.isArray()); + EXPECT_FALSE(stringV.isArrayOrNull()); + EXPECT_FALSE(stringV.isObject()); + EXPECT_FALSE(stringV.isObjectOrNull()); } { Json::Value const staticStrV{staticStr}; { Json::Value cpy{staticStrV}; - CHECK(staticStrV.type() == Json::stringValue); - CHECK(cpy.type() == Json::stringValue); + EXPECT_EQ(staticStrV.type(), Json::stringValue); + EXPECT_EQ(cpy.type(), Json::stringValue); } - CHECK(!staticStrV.isNull()); - CHECK(!staticStrV.isBool()); - CHECK(!staticStrV.isInt()); - CHECK(!staticStrV.isUInt()); - CHECK(!staticStrV.isIntegral()); - CHECK(!staticStrV.isDouble()); - CHECK(!staticStrV.isNumeric()); - CHECK(staticStrV.isString()); - CHECK(!staticStrV.isArray()); - CHECK(!staticStrV.isArrayOrNull()); - CHECK(!staticStrV.isObject()); - CHECK(!staticStrV.isObjectOrNull()); + EXPECT_FALSE(staticStrV.isNull()); + EXPECT_FALSE(staticStrV.isBool()); + EXPECT_FALSE(staticStrV.isInt()); + EXPECT_FALSE(staticStrV.isUInt()); + EXPECT_FALSE(staticStrV.isIntegral()); + EXPECT_FALSE(staticStrV.isDouble()); + EXPECT_FALSE(staticStrV.isNumeric()); + EXPECT_TRUE(staticStrV.isString()); + EXPECT_FALSE(staticStrV.isArray()); + EXPECT_FALSE(staticStrV.isArrayOrNull()); + EXPECT_FALSE(staticStrV.isObject()); + EXPECT_FALSE(staticStrV.isObjectOrNull()); } { Json::Value const boolV{testCopy(Json::booleanValue)}; - CHECK(!boolV.isNull()); - CHECK(boolV.isBool()); - CHECK(!boolV.isInt()); - CHECK(!boolV.isUInt()); - CHECK(boolV.isIntegral()); - CHECK(!boolV.isDouble()); - CHECK(boolV.isNumeric()); - CHECK(!boolV.isString()); - CHECK(!boolV.isArray()); - CHECK(!boolV.isArrayOrNull()); - CHECK(!boolV.isObject()); - CHECK(!boolV.isObjectOrNull()); + EXPECT_FALSE(boolV.isNull()); + EXPECT_TRUE(boolV.isBool()); + EXPECT_FALSE(boolV.isInt()); + EXPECT_FALSE(boolV.isUInt()); + EXPECT_TRUE(boolV.isIntegral()); + EXPECT_FALSE(boolV.isDouble()); + EXPECT_TRUE(boolV.isNumeric()); + EXPECT_FALSE(boolV.isString()); + EXPECT_FALSE(boolV.isArray()); + EXPECT_FALSE(boolV.isArrayOrNull()); + EXPECT_FALSE(boolV.isObject()); + EXPECT_FALSE(boolV.isObjectOrNull()); } { Json::Value const arrayV{testCopy(Json::arrayValue)}; - CHECK(!arrayV.isNull()); - CHECK(!arrayV.isBool()); - CHECK(!arrayV.isInt()); - CHECK(!arrayV.isUInt()); - CHECK(!arrayV.isIntegral()); - CHECK(!arrayV.isDouble()); - CHECK(!arrayV.isNumeric()); - CHECK(!arrayV.isString()); - CHECK(arrayV.isArray()); - CHECK(arrayV.isArrayOrNull()); - CHECK(!arrayV.isObject()); - CHECK(!arrayV.isObjectOrNull()); + EXPECT_FALSE(arrayV.isNull()); + EXPECT_FALSE(arrayV.isBool()); + EXPECT_FALSE(arrayV.isInt()); + EXPECT_FALSE(arrayV.isUInt()); + EXPECT_FALSE(arrayV.isIntegral()); + EXPECT_FALSE(arrayV.isDouble()); + EXPECT_FALSE(arrayV.isNumeric()); + EXPECT_FALSE(arrayV.isString()); + EXPECT_TRUE(arrayV.isArray()); + EXPECT_TRUE(arrayV.isArrayOrNull()); + EXPECT_FALSE(arrayV.isObject()); + EXPECT_FALSE(arrayV.isObjectOrNull()); } { Json::Value const objectV{testCopy(Json::objectValue)}; - CHECK(!objectV.isNull()); - CHECK(!objectV.isBool()); - CHECK(!objectV.isInt()); - CHECK(!objectV.isUInt()); - CHECK(!objectV.isIntegral()); - CHECK(!objectV.isDouble()); - CHECK(!objectV.isNumeric()); - CHECK(!objectV.isString()); - CHECK(!objectV.isArray()); - CHECK(!objectV.isArrayOrNull()); - CHECK(objectV.isObject()); - CHECK(objectV.isObjectOrNull()); + EXPECT_FALSE(objectV.isNull()); + EXPECT_FALSE(objectV.isBool()); + EXPECT_FALSE(objectV.isInt()); + EXPECT_FALSE(objectV.isUInt()); + EXPECT_FALSE(objectV.isIntegral()); + EXPECT_FALSE(objectV.isDouble()); + EXPECT_FALSE(objectV.isNumeric()); + EXPECT_FALSE(objectV.isString()); + EXPECT_FALSE(objectV.isArray()); + EXPECT_FALSE(objectV.isArrayOrNull()); + EXPECT_TRUE(objectV.isObject()); + EXPECT_TRUE(objectV.isObjectOrNull()); } } -TEST_CASE("compare strings") +TEST(json_value, compare_strings) { auto doCompare = [&](Json::Value const& lhs, Json::Value const& rhs, bool lhsEqRhs, bool lhsLtRhs, int line) { - CAPTURE(line); - CHECK((lhs == rhs) == lhsEqRhs); - CHECK((lhs != rhs) != lhsEqRhs); - CHECK((lhs < rhs) == (!(lhsEqRhs || !lhsLtRhs))); - CHECK((lhs <= rhs) == (lhsEqRhs || lhsLtRhs)); - CHECK((lhs >= rhs) == (lhsEqRhs || !lhsLtRhs)); - CHECK((lhs > rhs) == (!(lhsEqRhs || lhsLtRhs))); + SCOPED_TRACE(line); + EXPECT_EQ((lhs == rhs), lhsEqRhs); + EXPECT_NE((lhs != rhs), lhsEqRhs); + EXPECT_EQ((lhs < rhs), (!(lhsEqRhs || !lhsLtRhs))); + EXPECT_EQ((lhs <= rhs), (lhsEqRhs || lhsLtRhs)); + EXPECT_EQ((lhs >= rhs), (lhsEqRhs || !lhsLtRhs)); + EXPECT_EQ((lhs > rhs), (!(lhsEqRhs || lhsLtRhs))); }; Json::Value const null0; @@ -556,40 +554,40 @@ TEST_CASE("compare strings") #pragma pop_macro("DO_COMPARE") } -TEST_CASE("bool") +TEST(json_value, bool) { - CHECK(!Json::Value()); + EXPECT_FALSE(Json::Value()); - CHECK(!Json::Value("")); + EXPECT_FALSE(Json::Value("")); - CHECK(bool(Json::Value("empty"))); - CHECK(bool(Json::Value(false))); - CHECK(bool(Json::Value(true))); - CHECK(bool(Json::Value(0))); - CHECK(bool(Json::Value(1))); + EXPECT_TRUE(bool(Json::Value("empty"))); + EXPECT_TRUE(bool(Json::Value(false))); + EXPECT_TRUE(bool(Json::Value(true))); + EXPECT_TRUE(bool(Json::Value(0))); + EXPECT_TRUE(bool(Json::Value(1))); Json::Value array(Json::arrayValue); - CHECK(!array); + EXPECT_FALSE(array); array.append(0); - CHECK(bool(array)); + EXPECT_TRUE(bool(array)); Json::Value object(Json::objectValue); - CHECK(!object); + EXPECT_FALSE(object); object[""] = false; - CHECK(bool(object)); + EXPECT_TRUE(bool(object)); } -TEST_CASE("bad json") +TEST(json_value, bad_json) { char const* s(R"({"method":"ledger","params":[{"ledger_index":1e300}]})"); Json::Value j; Json::Reader r; - CHECK(r.parse(s, j)); + EXPECT_TRUE(r.parse(s, j)); } -TEST_CASE("edge cases") +TEST(json_value, edge_cases) { std::uint32_t max_uint = std::numeric_limits::max(); std::int32_t max_int = std::numeric_limits::max(); @@ -611,28 +609,27 @@ TEST_CASE("edge cases") Json::Value j1; Json::Reader r1; - CHECK(r1.parse(json, j1)); - CHECK(j1["max_uint"].asUInt() == max_uint); - CHECK(j1["max_uint"].asAbsUInt() == max_uint); - CHECK(j1["max_int"].asInt() == max_int); - CHECK(j1["max_int"].asAbsUInt() == max_int); - CHECK(j1["min_int"].asInt() == min_int); - CHECK( - j1["min_int"].asAbsUInt() == - static_cast(min_int) * -1); - CHECK(j1["a_uint"].asUInt() == a_uint); - CHECK(j1["a_uint"].asAbsUInt() == a_uint); - CHECK(j1["a_uint"] > a_large_int); - CHECK(j1["a_uint"] > a_small_int); - CHECK(j1["a_large_int"].asInt() == a_large_int); - CHECK(j1["a_large_int"].asAbsUInt() == a_large_int); - CHECK(j1["a_large_int"].asUInt() == a_large_int); - CHECK(j1["a_large_int"] < a_uint); - CHECK(j1["a_small_int"].asInt() == a_small_int); - CHECK( - j1["a_small_int"].asAbsUInt() == + EXPECT_TRUE(r1.parse(json, j1)); + EXPECT_EQ(j1["max_uint"].asUInt(), max_uint); + EXPECT_EQ(j1["max_uint"].asAbsUInt(), max_uint); + EXPECT_EQ(j1["max_int"].asInt(), max_int); + EXPECT_EQ(j1["max_int"].asAbsUInt(), max_int); + EXPECT_EQ(j1["min_int"].asInt(), min_int); + EXPECT_EQ( + j1["min_int"].asAbsUInt(), static_cast(min_int) * -1); + EXPECT_EQ(j1["a_uint"].asUInt(), a_uint); + EXPECT_EQ(j1["a_uint"].asAbsUInt(), a_uint); + EXPECT_GT(j1["a_uint"], a_large_int); + EXPECT_GT(j1["a_uint"], a_small_int); + EXPECT_EQ(j1["a_large_int"].asInt(), a_large_int); + EXPECT_EQ(j1["a_large_int"].asAbsUInt(), a_large_int); + EXPECT_EQ(j1["a_large_int"].asUInt(), a_large_int); + EXPECT_LT(j1["a_large_int"], a_uint); + EXPECT_EQ(j1["a_small_int"].asInt(), a_small_int); + EXPECT_EQ( + j1["a_small_int"].asAbsUInt(), static_cast(a_small_int) * -1); - CHECK(j1["a_small_int"] < a_uint); + EXPECT_LT(j1["a_small_int"], a_uint); } std::uint64_t overflow = std::uint64_t(max_uint) + 1; @@ -644,7 +641,7 @@ TEST_CASE("edge cases") Json::Value j2; Json::Reader r2; - CHECK(!r2.parse(json, j2)); + EXPECT_FALSE(r2.parse(json, j2)); } std::int64_t underflow = std::int64_t(min_int) - 1; @@ -656,167 +653,167 @@ TEST_CASE("edge cases") Json::Value j3; Json::Reader r3; - CHECK(!r3.parse(json, j3)); + EXPECT_FALSE(r3.parse(json, j3)); } { Json::Value intString{std::to_string(overflow)}; - CHECK_THROWS_AS(intString.asUInt(), beast::BadLexicalCast); - CHECK_THROWS_AS(intString.asAbsUInt(), Json::error); + EXPECT_THROW(intString.asUInt(), beast::BadLexicalCast); + EXPECT_THROW(intString.asAbsUInt(), Json::error); intString = "4294967295"; - CHECK(intString.asUInt() == 4294967295u); - CHECK(intString.asAbsUInt() == 4294967295u); + EXPECT_EQ(intString.asUInt(), 4294967295u); + EXPECT_EQ(intString.asAbsUInt(), 4294967295u); intString = "0"; - CHECK(intString.asUInt() == 0); - CHECK(intString.asAbsUInt() == 0); + EXPECT_EQ(intString.asUInt(), 0); + EXPECT_EQ(intString.asAbsUInt(), 0); intString = "-1"; - CHECK_THROWS_AS(intString.asUInt(), beast::BadLexicalCast); - CHECK(intString.asAbsUInt() == 1); + EXPECT_THROW(intString.asUInt(), beast::BadLexicalCast); + EXPECT_EQ(intString.asAbsUInt(), 1); intString = "-4294967295"; - CHECK(intString.asAbsUInt() == 4294967295); + EXPECT_EQ(intString.asAbsUInt(), 4294967295); intString = "-4294967296"; - CHECK_THROWS_AS(intString.asAbsUInt(), Json::error); + EXPECT_THROW(intString.asAbsUInt(), Json::error); intString = "2147483648"; - CHECK_THROWS_AS(intString.asInt(), beast::BadLexicalCast); - CHECK(intString.asAbsUInt() == 2147483648); + EXPECT_THROW(intString.asInt(), beast::BadLexicalCast); + EXPECT_EQ(intString.asAbsUInt(), 2147483648); intString = "2147483647"; - CHECK(intString.asInt() == 2147483647); - CHECK(intString.asAbsUInt() == 2147483647); + EXPECT_EQ(intString.asInt(), 2147483647); + EXPECT_EQ(intString.asAbsUInt(), 2147483647); intString = "-2147483648"; - CHECK(intString.asInt() == -2147483648LL); // MSVC wants the LL - CHECK(intString.asAbsUInt() == 2147483648LL); + EXPECT_EQ(intString.asInt(), -2147483648LL); // MSVC wants the LL + EXPECT_EQ(intString.asAbsUInt(), 2147483648LL); intString = "-2147483649"; - CHECK_THROWS_AS(intString.asInt(), beast::BadLexicalCast); - CHECK(intString.asAbsUInt() == 2147483649); + EXPECT_THROW(intString.asInt(), beast::BadLexicalCast); + EXPECT_EQ(intString.asAbsUInt(), 2147483649); } { Json::Value intReal{4294967297.0}; - CHECK_THROWS_AS(intReal.asUInt(), Json::error); - CHECK_THROWS_AS(intReal.asAbsUInt(), Json::error); + EXPECT_THROW(intReal.asUInt(), Json::error); + EXPECT_THROW(intReal.asAbsUInt(), Json::error); intReal = 4294967295.0; - CHECK(intReal.asUInt() == 4294967295u); - CHECK(intReal.asAbsUInt() == 4294967295u); + EXPECT_EQ(intReal.asUInt(), 4294967295u); + EXPECT_EQ(intReal.asAbsUInt(), 4294967295u); intReal = 0.0; - CHECK(intReal.asUInt() == 0); - CHECK(intReal.asAbsUInt() == 0); + EXPECT_EQ(intReal.asUInt(), 0); + EXPECT_EQ(intReal.asAbsUInt(), 0); intReal = -1.0; - CHECK_THROWS_AS(intReal.asUInt(), Json::error); - CHECK(intReal.asAbsUInt() == 1); + EXPECT_THROW(intReal.asUInt(), Json::error); + EXPECT_EQ(intReal.asAbsUInt(), 1); intReal = -4294967295.0; - CHECK(intReal.asAbsUInt() == 4294967295); + EXPECT_EQ(intReal.asAbsUInt(), 4294967295); intReal = -4294967296.0; - CHECK_THROWS_AS(intReal.asAbsUInt(), Json::error); + EXPECT_THROW(intReal.asAbsUInt(), Json::error); intReal = 2147483648.0; - CHECK_THROWS_AS(intReal.asInt(), Json::error); - CHECK(intReal.asAbsUInt() == 2147483648); + EXPECT_THROW(intReal.asInt(), Json::error); + EXPECT_EQ(intReal.asAbsUInt(), 2147483648); intReal = 2147483647.0; - CHECK(intReal.asInt() == 2147483647); - CHECK(intReal.asAbsUInt() == 2147483647); + EXPECT_EQ(intReal.asInt(), 2147483647); + EXPECT_EQ(intReal.asAbsUInt(), 2147483647); intReal = -2147483648.0; - CHECK(intReal.asInt() == -2147483648LL); // MSVC wants the LL - CHECK(intReal.asAbsUInt() == 2147483648LL); + EXPECT_EQ(intReal.asInt(), -2147483648LL); // MSVC wants the LL + EXPECT_EQ(intReal.asAbsUInt(), 2147483648LL); intReal = -2147483649.0; - CHECK_THROWS_AS(intReal.asInt(), Json::error); - CHECK(intReal.asAbsUInt() == 2147483649); + EXPECT_THROW(intReal.asInt(), Json::error); + EXPECT_EQ(intReal.asAbsUInt(), 2147483649); } } -TEST_CASE("copy") +TEST(json_value, copy) { Json::Value v1{2.5}; - CHECK(v1.isDouble()); - CHECK(v1.asDouble() == 2.5); + EXPECT_TRUE(v1.isDouble()); + EXPECT_EQ(v1.asDouble(), 2.5); Json::Value v2 = v1; - CHECK(v1.isDouble()); - CHECK(v1.asDouble() == 2.5); - CHECK(v2.isDouble()); - CHECK(v2.asDouble() == 2.5); - CHECK(v1 == v2); + EXPECT_TRUE(v1.isDouble()); + EXPECT_EQ(v1.asDouble(), 2.5); + EXPECT_TRUE(v2.isDouble()); + EXPECT_EQ(v2.asDouble(), 2.5); + EXPECT_EQ(v1, v2); v1 = v2; - CHECK(v1.isDouble()); - CHECK(v1.asDouble() == 2.5); - CHECK(v2.isDouble()); - CHECK(v2.asDouble() == 2.5); - CHECK(v1 == v2); + EXPECT_TRUE(v1.isDouble()); + EXPECT_EQ(v1.asDouble(), 2.5); + EXPECT_TRUE(v2.isDouble()); + EXPECT_EQ(v2.asDouble(), 2.5); + EXPECT_EQ(v1, v2); } -TEST_CASE("move") +TEST(json_value, move) { Json::Value v1{2.5}; - CHECK(v1.isDouble()); - CHECK(v1.asDouble() == 2.5); + EXPECT_TRUE(v1.isDouble()); + EXPECT_EQ(v1.asDouble(), 2.5); Json::Value v2 = std::move(v1); - CHECK(!v1); - CHECK(v2.isDouble()); - CHECK(v2.asDouble() == 2.5); - CHECK(v1 != v2); + EXPECT_FALSE(v1); + EXPECT_TRUE(v2.isDouble()); + EXPECT_EQ(v2.asDouble(), 2.5); + EXPECT_NE(v1, v2); v1 = std::move(v2); - CHECK(v1.isDouble()); - CHECK(v1.asDouble() == 2.5); - CHECK(!v2); - CHECK(v1 != v2); + EXPECT_TRUE(v1.isDouble()); + EXPECT_EQ(v1.asDouble(), 2.5); + EXPECT_FALSE(v2); + EXPECT_NE(v1, v2); } -TEST_CASE("comparisons") +TEST(json_value, comparisons) { Json::Value a, b; auto testEquals = [&](std::string const& name) { - CHECK(a == b); - CHECK(a <= b); - CHECK(a >= b); + EXPECT_TRUE(a == b); + EXPECT_TRUE(a <= b); + EXPECT_TRUE(a >= b); - CHECK(!(a != b)); - CHECK(!(a < b)); - CHECK(!(a > b)); + EXPECT_FALSE(a != b); + EXPECT_FALSE(a < b); + EXPECT_FALSE(a > b); - CHECK(b == a); - CHECK(b <= a); - CHECK(b >= a); + EXPECT_TRUE(b == a); + EXPECT_TRUE(b <= a); + EXPECT_TRUE(b >= a); - CHECK(!(b != a)); - CHECK(!(b < a)); - CHECK(!(b > a)); + EXPECT_FALSE(b != a); + EXPECT_FALSE(b < a); + EXPECT_FALSE(b > a); }; auto testGreaterThan = [&](std::string const& name) { - CHECK(!(a == b)); - CHECK(!(a <= b)); - CHECK(a >= b); + EXPECT_FALSE(a == b); + EXPECT_FALSE(a <= b); + EXPECT_TRUE(a >= b); - CHECK(a != b); - CHECK(!(a < b)); - CHECK(a > b); + EXPECT_TRUE(a != b); + EXPECT_FALSE(a < b); + EXPECT_TRUE(a > b); - CHECK(!(b == a)); - CHECK(b <= a); - CHECK(!(b >= a)); + EXPECT_FALSE(b == a); + EXPECT_TRUE(b <= a); + EXPECT_FALSE(b >= a); - CHECK(b != a); - CHECK(b < a); - CHECK(!(b > a)); + EXPECT_TRUE(b != a); + EXPECT_TRUE(b < a); + EXPECT_FALSE(b > a); }; a["a"] = Json::UInt(0); @@ -835,7 +832,7 @@ TEST_CASE("comparisons") testGreaterThan("big"); } -TEST_CASE("compact") +TEST(json_value, compact) { Json::Value j; Json::Reader r; @@ -847,20 +844,20 @@ TEST_CASE("compact") }); }; - CHECK(r.parse(s, j)); + EXPECT_TRUE(r.parse(s, j)); { std::stringstream ss; ss << j; - CHECK(countLines(ss.str()) > 1); + EXPECT_GT(countLines(ss.str()), 1); } { std::stringstream ss; ss << Json::Compact(std::move(j)); - CHECK(countLines(ss.str()) == 1); + EXPECT_EQ(countLines(ss.str()), 1); } } -TEST_CASE("conversions") +TEST(json_value, conversions) { // We have Json::Int, but not Json::Double or Json::Real. // We have Json::Int, Json::Value::Int, and Json::ValueType::intValue. @@ -869,336 +866,336 @@ TEST_CASE("conversions") { // null Json::Value val; - CHECK(val.isNull()); + EXPECT_TRUE(val.isNull()); // val.asCString() should trigger an assertion failure - CHECK(val.asString() == ""); - CHECK(val.asInt() == 0); - CHECK(val.asUInt() == 0); - CHECK(val.asAbsUInt() == 0); - CHECK(val.asDouble() == 0.0); - CHECK(val.asBool() == false); + EXPECT_EQ(val.asString(), ""); + EXPECT_EQ(val.asInt(), 0); + EXPECT_EQ(val.asUInt(), 0); + EXPECT_EQ(val.asAbsUInt(), 0); + EXPECT_EQ(val.asDouble(), 0.0); + EXPECT_FALSE(val.asBool()); - CHECK(val.isConvertibleTo(Json::nullValue)); - CHECK(val.isConvertibleTo(Json::intValue)); - CHECK(val.isConvertibleTo(Json::uintValue)); - CHECK(val.isConvertibleTo(Json::realValue)); - CHECK(val.isConvertibleTo(Json::stringValue)); - CHECK(val.isConvertibleTo(Json::booleanValue)); - CHECK(val.isConvertibleTo(Json::arrayValue)); - CHECK(val.isConvertibleTo(Json::objectValue)); + EXPECT_TRUE(val.isConvertibleTo(Json::nullValue)); + EXPECT_TRUE(val.isConvertibleTo(Json::intValue)); + EXPECT_TRUE(val.isConvertibleTo(Json::uintValue)); + EXPECT_TRUE(val.isConvertibleTo(Json::realValue)); + EXPECT_TRUE(val.isConvertibleTo(Json::stringValue)); + EXPECT_TRUE(val.isConvertibleTo(Json::booleanValue)); + EXPECT_TRUE(val.isConvertibleTo(Json::arrayValue)); + EXPECT_TRUE(val.isConvertibleTo(Json::objectValue)); } { // int Json::Value val = -1234; - CHECK(val.isInt()); + EXPECT_TRUE(val.isInt()); // val.asCString() should trigger an assertion failure - CHECK(val.asString() == "-1234"); - CHECK(val.asInt() == -1234); - CHECK_THROWS_AS(val.asUInt(), Json::error); - CHECK(val.asAbsUInt() == 1234u); - CHECK(val.asDouble() == -1234.0); - CHECK(val.asBool() == true); + EXPECT_EQ(val.asString(), "-1234"); + EXPECT_EQ(val.asInt(), -1234); + EXPECT_THROW(val.asUInt(), Json::error); + EXPECT_EQ(val.asAbsUInt(), 1234u); + EXPECT_EQ(val.asDouble(), -1234.0); + EXPECT_TRUE(val.asBool()); - CHECK(!val.isConvertibleTo(Json::nullValue)); - CHECK(val.isConvertibleTo(Json::intValue)); - CHECK(!val.isConvertibleTo(Json::uintValue)); - CHECK(val.isConvertibleTo(Json::realValue)); - CHECK(val.isConvertibleTo(Json::stringValue)); - CHECK(val.isConvertibleTo(Json::booleanValue)); - CHECK(!val.isConvertibleTo(Json::arrayValue)); - CHECK(!val.isConvertibleTo(Json::objectValue)); + EXPECT_FALSE(val.isConvertibleTo(Json::nullValue)); + EXPECT_TRUE(val.isConvertibleTo(Json::intValue)); + EXPECT_FALSE(val.isConvertibleTo(Json::uintValue)); + EXPECT_TRUE(val.isConvertibleTo(Json::realValue)); + EXPECT_TRUE(val.isConvertibleTo(Json::stringValue)); + EXPECT_TRUE(val.isConvertibleTo(Json::booleanValue)); + EXPECT_FALSE(val.isConvertibleTo(Json::arrayValue)); + EXPECT_FALSE(val.isConvertibleTo(Json::objectValue)); } { // uint Json::Value val = 1234U; - CHECK(val.isUInt()); + EXPECT_TRUE(val.isUInt()); // val.asCString() should trigger an assertion failure - CHECK(val.asString() == "1234"); - CHECK(val.asInt() == 1234); - CHECK(val.asUInt() == 1234u); - CHECK(val.asAbsUInt() == 1234u); - CHECK(val.asDouble() == 1234.0); - CHECK(val.asBool() == true); + EXPECT_EQ(val.asString(), "1234"); + EXPECT_EQ(val.asInt(), 1234); + EXPECT_EQ(val.asUInt(), 1234u); + EXPECT_EQ(val.asAbsUInt(), 1234u); + EXPECT_EQ(val.asDouble(), 1234.0); + EXPECT_TRUE(val.asBool()); - CHECK(!val.isConvertibleTo(Json::nullValue)); - CHECK(val.isConvertibleTo(Json::intValue)); - CHECK(val.isConvertibleTo(Json::uintValue)); - CHECK(val.isConvertibleTo(Json::realValue)); - CHECK(val.isConvertibleTo(Json::stringValue)); - CHECK(val.isConvertibleTo(Json::booleanValue)); - CHECK(!val.isConvertibleTo(Json::arrayValue)); - CHECK(!val.isConvertibleTo(Json::objectValue)); + EXPECT_FALSE(val.isConvertibleTo(Json::nullValue)); + EXPECT_TRUE(val.isConvertibleTo(Json::intValue)); + EXPECT_TRUE(val.isConvertibleTo(Json::uintValue)); + EXPECT_TRUE(val.isConvertibleTo(Json::realValue)); + EXPECT_TRUE(val.isConvertibleTo(Json::stringValue)); + EXPECT_TRUE(val.isConvertibleTo(Json::booleanValue)); + EXPECT_FALSE(val.isConvertibleTo(Json::arrayValue)); + EXPECT_FALSE(val.isConvertibleTo(Json::objectValue)); } { // real Json::Value val = 2.0; - CHECK(val.isDouble()); + EXPECT_TRUE(val.isDouble()); // val.asCString() should trigger an assertion failure - CHECK(std::regex_match(val.asString(), std::regex("^2\\.0*$"))); - CHECK(val.asInt() == 2); - CHECK(val.asUInt() == 2u); - CHECK(val.asAbsUInt() == 2u); - CHECK(val.asDouble() == 2.0); - CHECK(val.asBool() == true); + EXPECT_TRUE(std::regex_match(val.asString(), std::regex("^2\\.0*$"))); + EXPECT_EQ(val.asInt(), 2); + EXPECT_EQ(val.asUInt(), 2u); + EXPECT_EQ(val.asAbsUInt(), 2u); + EXPECT_EQ(val.asDouble(), 2.0); + EXPECT_TRUE(val.asBool()); - CHECK(!val.isConvertibleTo(Json::nullValue)); - CHECK(val.isConvertibleTo(Json::intValue)); - CHECK(val.isConvertibleTo(Json::uintValue)); - CHECK(val.isConvertibleTo(Json::realValue)); - CHECK(val.isConvertibleTo(Json::stringValue)); - CHECK(val.isConvertibleTo(Json::booleanValue)); - CHECK(!val.isConvertibleTo(Json::arrayValue)); - CHECK(!val.isConvertibleTo(Json::objectValue)); + EXPECT_FALSE(val.isConvertibleTo(Json::nullValue)); + EXPECT_TRUE(val.isConvertibleTo(Json::intValue)); + EXPECT_TRUE(val.isConvertibleTo(Json::uintValue)); + EXPECT_TRUE(val.isConvertibleTo(Json::realValue)); + EXPECT_TRUE(val.isConvertibleTo(Json::stringValue)); + EXPECT_TRUE(val.isConvertibleTo(Json::booleanValue)); + EXPECT_FALSE(val.isConvertibleTo(Json::arrayValue)); + EXPECT_FALSE(val.isConvertibleTo(Json::objectValue)); } { // numeric string Json::Value val = "54321"; - CHECK(val.isString()); - CHECK(strcmp(val.asCString(), "54321") == 0); - CHECK(val.asString() == "54321"); - CHECK(val.asInt() == 54321); - CHECK(val.asUInt() == 54321u); - CHECK(val.asAbsUInt() == 54321); - CHECK_THROWS_AS(val.asDouble(), Json::error); - CHECK(val.asBool() == true); + EXPECT_TRUE(val.isString()); + EXPECT_EQ(strcmp(val.asCString(), "54321"), 0); + EXPECT_EQ(val.asString(), "54321"); + EXPECT_EQ(val.asInt(), 54321); + EXPECT_EQ(val.asUInt(), 54321u); + EXPECT_EQ(val.asAbsUInt(), 54321); + EXPECT_THROW(val.asDouble(), Json::error); + EXPECT_TRUE(val.asBool()); - CHECK(!val.isConvertibleTo(Json::nullValue)); - CHECK(!val.isConvertibleTo(Json::intValue)); - CHECK(!val.isConvertibleTo(Json::uintValue)); - CHECK(!val.isConvertibleTo(Json::realValue)); - CHECK(val.isConvertibleTo(Json::stringValue)); - CHECK(!val.isConvertibleTo(Json::booleanValue)); - CHECK(!val.isConvertibleTo(Json::arrayValue)); - CHECK(!val.isConvertibleTo(Json::objectValue)); + EXPECT_FALSE(val.isConvertibleTo(Json::nullValue)); + EXPECT_FALSE(val.isConvertibleTo(Json::intValue)); + EXPECT_FALSE(val.isConvertibleTo(Json::uintValue)); + EXPECT_FALSE(val.isConvertibleTo(Json::realValue)); + EXPECT_TRUE(val.isConvertibleTo(Json::stringValue)); + EXPECT_FALSE(val.isConvertibleTo(Json::booleanValue)); + EXPECT_FALSE(val.isConvertibleTo(Json::arrayValue)); + EXPECT_FALSE(val.isConvertibleTo(Json::objectValue)); } { // non-numeric string Json::Value val(Json::stringValue); - CHECK(val.isString()); - CHECK(val.asCString() == nullptr); - CHECK(val.asString() == ""); - CHECK_THROWS_AS(val.asInt(), std::exception); - CHECK_THROWS_AS(val.asUInt(), std::exception); - CHECK_THROWS_AS(val.asAbsUInt(), std::exception); - CHECK_THROWS_AS(val.asDouble(), std::exception); - CHECK(val.asBool() == false); + EXPECT_TRUE(val.isString()); + EXPECT_EQ(val.asCString(), nullptr); + EXPECT_EQ(val.asString(), ""); + EXPECT_THROW(val.asInt(), std::exception); + EXPECT_THROW(val.asUInt(), std::exception); + EXPECT_THROW(val.asAbsUInt(), std::exception); + EXPECT_THROW(val.asDouble(), std::exception); + EXPECT_TRUE(val.asBool() == false); - CHECK(val.isConvertibleTo(Json::nullValue)); - CHECK(!val.isConvertibleTo(Json::intValue)); - CHECK(!val.isConvertibleTo(Json::uintValue)); - CHECK(!val.isConvertibleTo(Json::realValue)); - CHECK(val.isConvertibleTo(Json::stringValue)); - CHECK(!val.isConvertibleTo(Json::booleanValue)); - CHECK(!val.isConvertibleTo(Json::arrayValue)); - CHECK(!val.isConvertibleTo(Json::objectValue)); + EXPECT_TRUE(val.isConvertibleTo(Json::nullValue)); + EXPECT_FALSE(val.isConvertibleTo(Json::intValue)); + EXPECT_FALSE(val.isConvertibleTo(Json::uintValue)); + EXPECT_FALSE(val.isConvertibleTo(Json::realValue)); + EXPECT_TRUE(val.isConvertibleTo(Json::stringValue)); + EXPECT_FALSE(val.isConvertibleTo(Json::booleanValue)); + EXPECT_FALSE(val.isConvertibleTo(Json::arrayValue)); + EXPECT_FALSE(val.isConvertibleTo(Json::objectValue)); } { // bool false Json::Value val = false; - CHECK(val.isBool()); + EXPECT_TRUE(val.isBool()); // val.asCString() should trigger an assertion failure - CHECK(val.asString() == "false"); - CHECK(val.asInt() == 0); - CHECK(val.asUInt() == 0); - CHECK(val.asAbsUInt() == 0); - CHECK(val.asDouble() == 0.0); - CHECK(val.asBool() == false); + EXPECT_EQ(val.asString(), "false"); + EXPECT_EQ(val.asInt(), 0); + EXPECT_EQ(val.asUInt(), 0); + EXPECT_EQ(val.asAbsUInt(), 0); + EXPECT_EQ(val.asDouble(), 0.0); + EXPECT_FALSE(val.asBool()); - CHECK(val.isConvertibleTo(Json::nullValue)); - CHECK(val.isConvertibleTo(Json::intValue)); - CHECK(val.isConvertibleTo(Json::uintValue)); - CHECK(val.isConvertibleTo(Json::realValue)); - CHECK(val.isConvertibleTo(Json::stringValue)); - CHECK(val.isConvertibleTo(Json::booleanValue)); - CHECK(!val.isConvertibleTo(Json::arrayValue)); - CHECK(!val.isConvertibleTo(Json::objectValue)); + EXPECT_TRUE(val.isConvertibleTo(Json::nullValue)); + EXPECT_TRUE(val.isConvertibleTo(Json::intValue)); + EXPECT_TRUE(val.isConvertibleTo(Json::uintValue)); + EXPECT_TRUE(val.isConvertibleTo(Json::realValue)); + EXPECT_TRUE(val.isConvertibleTo(Json::stringValue)); + EXPECT_TRUE(val.isConvertibleTo(Json::booleanValue)); + EXPECT_FALSE(val.isConvertibleTo(Json::arrayValue)); + EXPECT_FALSE(val.isConvertibleTo(Json::objectValue)); } { // bool true Json::Value val = true; - CHECK(val.isBool()); + EXPECT_TRUE(val.isBool()); // val.asCString() should trigger an assertion failure - CHECK(val.asString() == "true"); - CHECK(val.asInt() == 1); - CHECK(val.asUInt() == 1); - CHECK(val.asAbsUInt() == 1); - CHECK(val.asDouble() == 1.0); - CHECK(val.asBool() == true); + EXPECT_EQ(val.asString(), "true"); + EXPECT_EQ(val.asInt(), 1); + EXPECT_EQ(val.asUInt(), 1); + EXPECT_EQ(val.asAbsUInt(), 1); + EXPECT_EQ(val.asDouble(), 1.0); + EXPECT_TRUE(val.asBool()); - CHECK(!val.isConvertibleTo(Json::nullValue)); - CHECK(val.isConvertibleTo(Json::intValue)); - CHECK(val.isConvertibleTo(Json::uintValue)); - CHECK(val.isConvertibleTo(Json::realValue)); - CHECK(val.isConvertibleTo(Json::stringValue)); - CHECK(val.isConvertibleTo(Json::booleanValue)); - CHECK(!val.isConvertibleTo(Json::arrayValue)); - CHECK(!val.isConvertibleTo(Json::objectValue)); + EXPECT_FALSE(val.isConvertibleTo(Json::nullValue)); + EXPECT_TRUE(val.isConvertibleTo(Json::intValue)); + EXPECT_TRUE(val.isConvertibleTo(Json::uintValue)); + EXPECT_TRUE(val.isConvertibleTo(Json::realValue)); + EXPECT_TRUE(val.isConvertibleTo(Json::stringValue)); + EXPECT_TRUE(val.isConvertibleTo(Json::booleanValue)); + EXPECT_FALSE(val.isConvertibleTo(Json::arrayValue)); + EXPECT_FALSE(val.isConvertibleTo(Json::objectValue)); } { // array type Json::Value val(Json::arrayValue); - CHECK(val.isArray()); + EXPECT_TRUE(val.isArray()); // val.asCString should trigger an assertion failure - CHECK_THROWS_AS(val.asString(), Json::error); - CHECK_THROWS_AS(val.asInt(), Json::error); - CHECK_THROWS_AS(val.asUInt(), Json::error); - CHECK_THROWS_AS(val.asAbsUInt(), Json::error); - CHECK_THROWS_AS(val.asDouble(), Json::error); - CHECK(val.asBool() == false); // empty or not + EXPECT_THROW(val.asString(), Json::error); + EXPECT_THROW(val.asInt(), Json::error); + EXPECT_THROW(val.asUInt(), Json::error); + EXPECT_THROW(val.asAbsUInt(), Json::error); + EXPECT_THROW(val.asDouble(), Json::error); + EXPECT_FALSE(val.asBool()); // empty or not - CHECK(val.isConvertibleTo(Json::nullValue)); - CHECK(!val.isConvertibleTo(Json::intValue)); - CHECK(!val.isConvertibleTo(Json::uintValue)); - CHECK(!val.isConvertibleTo(Json::realValue)); - CHECK(!val.isConvertibleTo(Json::stringValue)); - CHECK(!val.isConvertibleTo(Json::booleanValue)); - CHECK(val.isConvertibleTo(Json::arrayValue)); - CHECK(!val.isConvertibleTo(Json::objectValue)); + EXPECT_TRUE(val.isConvertibleTo(Json::nullValue)); + EXPECT_FALSE(val.isConvertibleTo(Json::intValue)); + EXPECT_FALSE(val.isConvertibleTo(Json::uintValue)); + EXPECT_FALSE(val.isConvertibleTo(Json::realValue)); + EXPECT_FALSE(val.isConvertibleTo(Json::stringValue)); + EXPECT_FALSE(val.isConvertibleTo(Json::booleanValue)); + EXPECT_TRUE(val.isConvertibleTo(Json::arrayValue)); + EXPECT_FALSE(val.isConvertibleTo(Json::objectValue)); } { // object type Json::Value val(Json::objectValue); - CHECK(val.isObject()); + EXPECT_TRUE(val.isObject()); // val.asCString should trigger an assertion failure - CHECK_THROWS_AS(val.asString(), Json::error); - CHECK_THROWS_AS(val.asInt(), Json::error); - CHECK_THROWS_AS(val.asUInt(), Json::error); - CHECK_THROWS_AS(val.asAbsUInt(), Json::error); - CHECK_THROWS_AS(val.asDouble(), Json::error); - CHECK(val.asBool() == false); // empty or not + EXPECT_THROW(val.asString(), Json::error); + EXPECT_THROW(val.asInt(), Json::error); + EXPECT_THROW(val.asUInt(), Json::error); + EXPECT_THROW(val.asAbsUInt(), Json::error); + EXPECT_THROW(val.asDouble(), Json::error); + EXPECT_FALSE(val.asBool()); // empty or not - CHECK(val.isConvertibleTo(Json::nullValue)); - CHECK(!val.isConvertibleTo(Json::intValue)); - CHECK(!val.isConvertibleTo(Json::uintValue)); - CHECK(!val.isConvertibleTo(Json::realValue)); - CHECK(!val.isConvertibleTo(Json::stringValue)); - CHECK(!val.isConvertibleTo(Json::booleanValue)); - CHECK(!val.isConvertibleTo(Json::arrayValue)); - CHECK(val.isConvertibleTo(Json::objectValue)); + EXPECT_TRUE(val.isConvertibleTo(Json::nullValue)); + EXPECT_FALSE(val.isConvertibleTo(Json::intValue)); + EXPECT_FALSE(val.isConvertibleTo(Json::uintValue)); + EXPECT_FALSE(val.isConvertibleTo(Json::realValue)); + EXPECT_FALSE(val.isConvertibleTo(Json::stringValue)); + EXPECT_FALSE(val.isConvertibleTo(Json::booleanValue)); + EXPECT_FALSE(val.isConvertibleTo(Json::arrayValue)); + EXPECT_TRUE(val.isConvertibleTo(Json::objectValue)); } } -TEST_CASE("access members") +TEST(json_value, access_members) { Json::Value val; - CHECK(val.type() == Json::nullValue); - CHECK(val.size() == 0); - CHECK(!val.isValidIndex(0)); - CHECK(!val.isMember("key")); + EXPECT_EQ(val.type(), Json::nullValue); + EXPECT_EQ(val.size(), 0); + EXPECT_FALSE(val.isValidIndex(0)); + EXPECT_FALSE(val.isMember("key")); { Json::Value const constVal = val; - CHECK(constVal[7u].type() == Json::nullValue); - CHECK(!constVal.isMember("key")); - CHECK(constVal["key"].type() == Json::nullValue); - CHECK(constVal.getMemberNames().empty()); - CHECK(constVal.get(1u, "default0") == "default0"); - CHECK(constVal.get(std::string("not"), "oh") == "oh"); - CHECK(constVal.get("missing", "default2") == "default2"); + EXPECT_EQ(constVal[7u].type(), Json::nullValue); + EXPECT_FALSE(constVal.isMember("key")); + EXPECT_EQ(constVal["key"].type(), Json::nullValue); + EXPECT_TRUE(constVal.getMemberNames().empty()); + EXPECT_EQ(constVal.get(1u, "default0"), "default0"); + EXPECT_EQ(constVal.get(std::string("not"), "oh"), "oh"); + EXPECT_EQ(constVal.get("missing", "default2"), "default2"); } val = -7; - CHECK(val.type() == Json::intValue); - CHECK(val.size() == 0); - CHECK(!val.isValidIndex(0)); - CHECK(!val.isMember("key")); + EXPECT_EQ(val.type(), Json::intValue); + EXPECT_EQ(val.size(), 0); + EXPECT_FALSE(val.isValidIndex(0)); + EXPECT_FALSE(val.isMember("key")); val = 42u; - CHECK(val.type() == Json::uintValue); - CHECK(val.size() == 0); - CHECK(!val.isValidIndex(0)); - CHECK(!val.isMember("key")); + EXPECT_EQ(val.type(), Json::uintValue); + EXPECT_EQ(val.size(), 0); + EXPECT_FALSE(val.isValidIndex(0)); + EXPECT_FALSE(val.isMember("key")); val = 3.14159; - CHECK(val.type() == Json::realValue); - CHECK(val.size() == 0); - CHECK(!val.isValidIndex(0)); - CHECK(!val.isMember("key")); + EXPECT_EQ(val.type(), Json::realValue); + EXPECT_EQ(val.size(), 0); + EXPECT_FALSE(val.isValidIndex(0)); + EXPECT_FALSE(val.isMember("key")); val = true; - CHECK(val.type() == Json::booleanValue); - CHECK(val.size() == 0); - CHECK(!val.isValidIndex(0)); - CHECK(!val.isMember("key")); + EXPECT_EQ(val.type(), Json::booleanValue); + EXPECT_EQ(val.size(), 0); + EXPECT_FALSE(val.isValidIndex(0)); + EXPECT_FALSE(val.isMember("key")); val = "string"; - CHECK(val.type() == Json::stringValue); - CHECK(val.size() == 0); - CHECK(!val.isValidIndex(0)); - CHECK(!val.isMember("key")); + EXPECT_EQ(val.type(), Json::stringValue); + EXPECT_EQ(val.size(), 0); + EXPECT_FALSE(val.isValidIndex(0)); + EXPECT_FALSE(val.isMember("key")); val = Json::Value(Json::objectValue); - CHECK(val.type() == Json::objectValue); - CHECK(val.size() == 0); + EXPECT_EQ(val.type(), Json::objectValue); + EXPECT_EQ(val.size(), 0); static Json::StaticString const staticThree("three"); val[staticThree] = 3; val["two"] = 2; - CHECK(val.size() == 2); - CHECK(val.isValidIndex(1)); - CHECK(!val.isValidIndex(2)); - CHECK(val[staticThree] == 3); - CHECK(val.isMember("two")); - CHECK(val.isMember(staticThree)); - CHECK(!val.isMember("key")); + EXPECT_EQ(val.size(), 2); + EXPECT_TRUE(val.isValidIndex(1)); + EXPECT_FALSE(val.isValidIndex(2)); + EXPECT_EQ(val[staticThree], 3); + EXPECT_TRUE(val.isMember("two")); + EXPECT_TRUE(val.isMember(staticThree)); + EXPECT_FALSE(val.isMember("key")); { Json::Value const constVal = val; - CHECK(constVal["two"] == 2); - CHECK(constVal["four"].type() == Json::nullValue); - CHECK(constVal[staticThree] == 3); - CHECK(constVal.isMember("two")); - CHECK(constVal.isMember(staticThree)); - CHECK(!constVal.isMember("key")); - CHECK(val.get(std::string("two"), "backup") == 2); - CHECK(val.get("missing", "default2") == "default2"); + EXPECT_EQ(constVal["two"], 2); + EXPECT_EQ(constVal["four"].type(), Json::nullValue); + EXPECT_EQ(constVal[staticThree], 3); + EXPECT_TRUE(constVal.isMember("two")); + EXPECT_TRUE(constVal.isMember(staticThree)); + EXPECT_FALSE(constVal.isMember("key")); + EXPECT_EQ(val.get(std::string("two"), "backup"), 2); + EXPECT_EQ(val.get("missing", "default2"), "default2"); } val = Json::Value(Json::arrayValue); - CHECK(val.type() == Json::arrayValue); - CHECK(val.size() == 0); + EXPECT_EQ(val.type(), Json::arrayValue); + EXPECT_EQ(val.size(), 0); val[0u] = "zero"; val[1u] = "one"; - CHECK(val.size() == 2); - CHECK(val.isValidIndex(1)); - CHECK(!val.isValidIndex(2)); - CHECK(val[20u].type() == Json::nullValue); - CHECK(!val.isMember("key")); + EXPECT_EQ(val.size(), 2); + EXPECT_TRUE(val.isValidIndex(1)); + EXPECT_FALSE(val.isValidIndex(2)); + EXPECT_EQ(val[20u].type(), Json::nullValue); + EXPECT_FALSE(val.isMember("key")); { Json::Value const constVal = val; - CHECK(constVal[0u] == "zero"); - CHECK(constVal[2u].type() == Json::nullValue); - CHECK(!constVal.isMember("key")); - CHECK(val.get(1u, "default0") == "one"); - CHECK(val.get(3u, "default1") == "default1"); + EXPECT_EQ(constVal[0u], "zero"); + EXPECT_EQ(constVal[2u].type(), Json::nullValue); + EXPECT_FALSE(constVal.isMember("key")); + EXPECT_EQ(val.get(1u, "default0"), "one"); + EXPECT_EQ(val.get(3u, "default1"), "default1"); } } -TEST_CASE("remove members") +TEST(json_value, remove_members) { Json::Value val; - CHECK(val.removeMember(std::string("member")).type() == Json::nullValue); + EXPECT_EQ(val.removeMember(std::string("member")).type(), Json::nullValue); val = Json::Value(Json::objectValue); static Json::StaticString const staticThree("three"); val[staticThree] = 3; val["two"] = 2; - CHECK(val.size() == 2); + EXPECT_EQ(val.size(), 2); - CHECK(val.removeMember(std::string("six")).type() == Json::nullValue); - CHECK(val.size() == 2); + EXPECT_EQ(val.removeMember(std::string("six")).type(), Json::nullValue); + EXPECT_EQ(val.size(), 2); - CHECK(val.removeMember(staticThree) == 3); - CHECK(val.size() == 1); + EXPECT_EQ(val.removeMember(staticThree), 3); + EXPECT_EQ(val.size(), 1); - CHECK(val.removeMember(staticThree).type() == Json::nullValue); - CHECK(val.size() == 1); + EXPECT_EQ(val.removeMember(staticThree).type(), Json::nullValue); + EXPECT_EQ(val.size(), 1); - CHECK(val.removeMember(std::string("two")) == 2); - CHECK(val.size() == 0); + EXPECT_EQ(val.removeMember(std::string("two")), 2); + EXPECT_EQ(val.size(), 0); - CHECK(val.removeMember(std::string("two")).type() == Json::nullValue); - CHECK(val.size() == 0); + EXPECT_EQ(val.removeMember(std::string("two")).type(), Json::nullValue); + EXPECT_EQ(val.size(), 0); } -TEST_CASE("iterator") +TEST(json_value, iterator) { { // Iterating an array. @@ -1215,27 +1212,27 @@ TEST_CASE("iterator") Json::ValueIterator i2 = e; --i2; - // key(), index(), and memberName() on an object iterator. - CHECK(b != e); - CHECK(!(b == e)); - CHECK(i1.key() == 0); - CHECK(i2.key() == 3); - CHECK(i1.index() == 0); - CHECK(i2.index() == 3); - CHECK(std::strcmp(i1.memberName(), "") == 0); - CHECK(std::strcmp(i2.memberName(), "") == 0); + // key(), index(), and memberName() on an array iterator. + EXPECT_TRUE(b != e); + EXPECT_FALSE(b == e); + EXPECT_EQ(i1.key(), 0); + EXPECT_EQ(i2.key(), 3); + EXPECT_EQ(i1.index(), 0); + EXPECT_EQ(i2.index(), 3); + EXPECT_STREQ(i1.memberName(), ""); + EXPECT_STREQ(i2.memberName(), ""); // Pre and post increment and decrement. *i1++ = "0"; - CHECK(*i1 == "one"); + EXPECT_EQ(*i1, "one"); *i1 = "1"; ++i1; *i2-- = "3"; - CHECK(*i2 == "two"); - CHECK(i1 == i2); + EXPECT_EQ(*i2, "two"); + EXPECT_EQ(i1, i2); *i2 = "2"; - CHECK(*i1 == "2"); + EXPECT_EQ(*i1, "2"); } { // Iterating a const object. @@ -1253,38 +1250,38 @@ TEST_CASE("iterator") --i2; // key(), index(), and memberName() on an object iterator. - CHECK(i1 != i2); - CHECK(!(i1 == i2)); - CHECK(i1.key() == "0"); - CHECK(i2.key() == "3"); - CHECK(i1.index() == -1); - CHECK(i2.index() == -1); - CHECK(std::strcmp(i1.memberName(), "0") == 0); - CHECK(std::strcmp(i2.memberName(), "3") == 0); + EXPECT_TRUE(i1 != i2); + EXPECT_FALSE(i1 == i2); + EXPECT_EQ(i1.key(), "0"); + EXPECT_EQ(i2.key(), "3"); + EXPECT_EQ(i1.index(), -1); + EXPECT_EQ(i2.index(), -1); + EXPECT_STREQ(i1.memberName(), "0"); + EXPECT_STREQ(i2.memberName(), "3"); // Pre and post increment and decrement. - CHECK(*i1++ == 0); - CHECK(*i1 == 1); + EXPECT_EQ(*i1++, 0); + EXPECT_EQ(*i1, 1); ++i1; - CHECK(*i2-- == 3); - CHECK(*i2 == 2); - CHECK(i1 == i2); - CHECK(*i1 == 2); + EXPECT_EQ(*i2--, 3); + EXPECT_EQ(*i2, 2); + EXPECT_EQ(i1, i2); + EXPECT_EQ(*i1, 2); } { // Iterating a non-const null object. Json::Value nul{}; - CHECK(nul.begin() == nul.end()); + EXPECT_EQ(nul.begin(), nul.end()); } { // Iterating a const Int. Json::Value const i{-3}; - CHECK(i.begin() == i.end()); + EXPECT_EQ(i.begin(), i.end()); } } -TEST_CASE("nest limits") +TEST(json_value, nest_limits) { Json::Reader r; { @@ -1302,14 +1299,14 @@ TEST_CASE("nest limits") // Within object nest limit auto json{nest(std::min(10u, Json::Reader::nest_limit))}; Json::Value j; - CHECK(r.parse(json, j)); + EXPECT_TRUE(r.parse(json, j)); } { // Exceed object nest limit auto json{nest(Json::Reader::nest_limit + 1)}; Json::Value j; - CHECK(!r.parse(json, j)); + EXPECT_FALSE(r.parse(json, j)); } } @@ -1326,41 +1323,39 @@ TEST_CASE("nest limits") // Exceed array nest limit auto json{nest(Json::Reader::nest_limit + 1)}; Json::Value j; - CHECK(!r.parse(json, j)); + EXPECT_FALSE(r.parse(json, j)); } } -TEST_CASE("memory leak") +TEST(json_value, memory_leak) { // When run with the address sanitizer, this test confirms there is no // memory leak with the scenarios below. { Json::Value a; a[0u] = 1; - CHECK(a.type() == Json::arrayValue); - CHECK(a[0u].type() == Json::intValue); + EXPECT_EQ(a.type(), Json::arrayValue); + EXPECT_EQ(a[0u].type(), Json::intValue); a = std::move(a[0u]); - CHECK(a.type() == Json::intValue); + EXPECT_EQ(a.type(), Json::intValue); } { Json::Value b; Json::Value temp; temp["a"] = "Probably avoids the small string optimization"; temp["b"] = "Also probably avoids the small string optimization"; - CHECK(temp.type() == Json::objectValue); + EXPECT_EQ(temp.type(), Json::objectValue); b.append(temp); - CHECK(temp.type() == Json::objectValue); - CHECK(b.size() == 1); + EXPECT_EQ(temp.type(), Json::objectValue); + EXPECT_EQ(b.size(), 1); b.append(std::move(temp)); - CHECK(b.size() == 2); + EXPECT_EQ(b.size(), 2); // Note that the type() == nullValue check is implementation // specific and not guaranteed to be valid in the future. - CHECK(temp.type() == Json::nullValue); + EXPECT_EQ(temp.type(), Json::nullValue); } } -TEST_SUITE_END(); - } // namespace xrpl diff --git a/src/tests/libxrpl/json/Writer.cpp b/src/tests/libxrpl/json/Writer.cpp index 9637184c95..7016b4322d 100644 --- a/src/tests/libxrpl/json/Writer.cpp +++ b/src/tests/libxrpl/json/Writer.cpp @@ -1,7 +1,7 @@ #include -#include #include +#include #include #include @@ -9,14 +9,14 @@ using namespace xrpl; using namespace Json; -TEST_SUITE_BEGIN("JsonWriter"); - -struct WriterFixture +class WriterFixture : public ::testing::Test { +protected: std::string output; std::unique_ptr writer; - WriterFixture() + void + SetUp() override { writer = std::make_unique(stringOutput(output)); } @@ -31,7 +31,7 @@ struct WriterFixture void expectOutput(std::string const& expected) const { - CHECK(output == expected); + EXPECT_EQ(output, expected); } void @@ -42,20 +42,20 @@ struct WriterFixture } }; -TEST_CASE_FIXTURE(WriterFixture, "trivial") +TEST_F(WriterFixture, trivial) { - CHECK(output.empty()); + EXPECT_TRUE(output.empty()); checkOutputAndReset(""); } -TEST_CASE_FIXTURE(WriterFixture, "near trivial") +TEST_F(WriterFixture, near_trivial) { - CHECK(output.empty()); + EXPECT_TRUE(output.empty()); writer->output(0); checkOutputAndReset("0"); } -TEST_CASE_FIXTURE(WriterFixture, "primitives") +TEST_F(WriterFixture, primitives) { writer->output(true); checkOutputAndReset("true"); @@ -79,7 +79,7 @@ TEST_CASE_FIXTURE(WriterFixture, "primitives") checkOutputAndReset("null"); } -TEST_CASE_FIXTURE(WriterFixture, "empty") +TEST_F(WriterFixture, empty) { writer->startRoot(Writer::array); writer->finish(); @@ -90,7 +90,7 @@ TEST_CASE_FIXTURE(WriterFixture, "empty") checkOutputAndReset("{}"); } -TEST_CASE_FIXTURE(WriterFixture, "escaping") +TEST_F(WriterFixture, escaping) { writer->output("\\"); checkOutputAndReset(R"("\\")"); @@ -108,7 +108,7 @@ TEST_CASE_FIXTURE(WriterFixture, "escaping") checkOutputAndReset(R"("\b\f\n\r\t")"); } -TEST_CASE_FIXTURE(WriterFixture, "array") +TEST_F(WriterFixture, array) { writer->startRoot(Writer::array); writer->append(12); @@ -116,7 +116,7 @@ TEST_CASE_FIXTURE(WriterFixture, "array") checkOutputAndReset("[12]"); } -TEST_CASE_FIXTURE(WriterFixture, "long array") +TEST_F(WriterFixture, long_array) { writer->startRoot(Writer::array); writer->append(12); @@ -126,7 +126,7 @@ TEST_CASE_FIXTURE(WriterFixture, "long array") checkOutputAndReset(R"([12,true,"hello"])"); } -TEST_CASE_FIXTURE(WriterFixture, "embedded array simple") +TEST_F(WriterFixture, embedded_array_simple) { writer->startRoot(Writer::array); writer->startAppend(Writer::array); @@ -135,7 +135,7 @@ TEST_CASE_FIXTURE(WriterFixture, "embedded array simple") checkOutputAndReset("[[]]"); } -TEST_CASE_FIXTURE(WriterFixture, "object") +TEST_F(WriterFixture, object) { writer->startRoot(Writer::object); writer->set("hello", "world"); @@ -143,7 +143,7 @@ TEST_CASE_FIXTURE(WriterFixture, "object") checkOutputAndReset(R"({"hello":"world"})"); } -TEST_CASE_FIXTURE(WriterFixture, "complex object") +TEST_F(WriterFixture, complex_object) { writer->startRoot(Writer::object); writer->set("hello", "world"); @@ -160,7 +160,7 @@ TEST_CASE_FIXTURE(WriterFixture, "complex object") R"({"hello":"world","array":[true,12,[{"goodbye":"cruel world.","subarray":[23.5]}]]})"); } -TEST_CASE_FIXTURE(WriterFixture, "json value") +TEST_F(WriterFixture, json_value) { Json::Value value(Json::objectValue); value["foo"] = 23; @@ -169,5 +169,3 @@ TEST_CASE_FIXTURE(WriterFixture, "json value") writer->finish(); checkOutputAndReset(R"({"hello":{"foo":23}})"); } - -TEST_SUITE_END(); diff --git a/src/tests/libxrpl/json/main.cpp b/src/tests/libxrpl/json/main.cpp index 0a3f254ea8..5142bbe08a 100644 --- a/src/tests/libxrpl/json/main.cpp +++ b/src/tests/libxrpl/json/main.cpp @@ -1,2 +1,8 @@ -#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN -#include +#include + +int +main(int argc, char** argv) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/src/tests/libxrpl/net/HTTPClient.cpp b/src/tests/libxrpl/net/HTTPClient.cpp index 5a484c1f56..cfd206edde 100644 --- a/src/tests/libxrpl/net/HTTPClient.cpp +++ b/src/tests/libxrpl/net/HTTPClient.cpp @@ -7,7 +7,7 @@ #include #include -#include +#include #include #include @@ -217,7 +217,7 @@ runHTTPTest( } // anonymous namespace -TEST_CASE("HTTPClient case insensitive Content-Length") +TEST(HTTPClient, case_insensitive_content_length) { // Test different cases of Content-Length header std::vector header_cases = { @@ -249,14 +249,14 @@ TEST_CASE("HTTPClient case insensitive Content-Length") result_error); // Verify results - CHECK(test_completed); - CHECK(!result_error); - CHECK(result_status == 200); - CHECK(result_data == test_body); + EXPECT_TRUE(test_completed); + EXPECT_FALSE(result_error); + EXPECT_EQ(result_status, 200); + EXPECT_EQ(result_data, test_body); } } -TEST_CASE("HTTPClient basic HTTP request") +TEST(HTTPClient, basic_http_request) { TestHTTPServer server; std::string test_body = "Test response body"; @@ -271,13 +271,13 @@ TEST_CASE("HTTPClient basic HTTP request") bool test_completed = runHTTPTest( server, "/basic", completed, result_status, result_data, result_error); - CHECK(test_completed); - CHECK(!result_error); - CHECK(result_status == 200); - CHECK(result_data == test_body); + EXPECT_TRUE(test_completed); + EXPECT_FALSE(result_error); + EXPECT_EQ(result_status, 200); + EXPECT_EQ(result_data, test_body); } -TEST_CASE("HTTPClient empty response") +TEST(HTTPClient, empty_response) { TestHTTPServer server; server.setResponseBody(""); // Empty body @@ -291,13 +291,13 @@ TEST_CASE("HTTPClient empty response") bool test_completed = runHTTPTest( server, "/empty", completed, result_status, result_data, result_error); - CHECK(test_completed); - CHECK(!result_error); - CHECK(result_status == 200); - CHECK(result_data.empty()); + EXPECT_TRUE(test_completed); + EXPECT_FALSE(result_error); + EXPECT_EQ(result_status, 200); + EXPECT_TRUE(result_data.empty()); } -TEST_CASE("HTTPClient different status codes") +TEST(HTTPClient, different_status_codes) { std::vector status_codes = {200, 404, 500}; @@ -320,8 +320,8 @@ TEST_CASE("HTTPClient different status codes") result_data, result_error); - CHECK(test_completed); - CHECK(!result_error); - CHECK(result_status == static_cast(status)); + EXPECT_TRUE(test_completed); + EXPECT_FALSE(result_error); + EXPECT_EQ(result_status, static_cast(status)); } } diff --git a/src/tests/libxrpl/net/main.cpp b/src/tests/libxrpl/net/main.cpp index 0a3f254ea8..5142bbe08a 100644 --- a/src/tests/libxrpl/net/main.cpp +++ b/src/tests/libxrpl/net/main.cpp @@ -1,2 +1,8 @@ -#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN -#include +#include + +int +main(int argc, char** argv) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} From 96d17b7f664e593fb8e7c10dfa65a3d655a1011d Mon Sep 17 00:00:00 2001 From: Pratik Mankawde <3397372+pratikmankawde@users.noreply.github.com> Date: Thu, 15 Jan 2026 16:18:14 +0000 Subject: [PATCH 28/30] ci: Add sanitizers to CI builds (#5996) This change adds support for sanitizer build options in CI builds workflow. Currently `asan+ubsan` is enabled, while `tsan+ubsan` is left disabled as more changes are required. --- .config/cspell.config.yaml | 6 + .github/actions/build-deps/action.yml | 6 + .github/actions/setup-conan/action.yml | 2 +- .github/scripts/strategy-matrix/generate.py | 60 ++++- .../workflows/reusable-build-test-config.yml | 19 +- .github/workflows/reusable-build-test.yml | 1 + BUILD.md | 38 ++- CMakeLists.txt | 9 +- cmake/CompilationEnv.cmake | 54 ++++ cmake/XrplCompiler.cmake | 13 +- cmake/XrplInterface.cmake | 18 +- cmake/XrplSanitizers.cmake | 198 +++++++++++++++ cmake/XrplSanity.cmake | 13 +- cmake/XrplSettings.cmake | 35 +-- cmake/deps/Boost.cmake | 5 +- conan/profiles/ci | 1 + conan/profiles/sanitizers | 59 +++++ docs/build/sanitizers.md | 207 +++++++++++++++ sanitizers/suppressions/asan.supp | 29 +++ sanitizers/suppressions/lsan.supp | 16 ++ .../suppressions/sanitizer-ignorelist.txt | 29 +++ sanitizers/suppressions/tsan.supp | 102 ++++++++ sanitizers/suppressions/ubsan.supp | 237 ++++++++++++++++++ src/libxrpl/ledger/View.cpp | 10 +- src/libxrpl/protocol/BuildInfo.cpp | 10 +- 25 files changed, 1076 insertions(+), 101 deletions(-) create mode 100644 cmake/CompilationEnv.cmake create mode 100644 cmake/XrplSanitizers.cmake create mode 100644 conan/profiles/ci create mode 100644 conan/profiles/sanitizers create mode 100644 docs/build/sanitizers.md create mode 100644 sanitizers/suppressions/asan.supp create mode 100644 sanitizers/suppressions/lsan.supp create mode 100644 sanitizers/suppressions/sanitizer-ignorelist.txt create mode 100644 sanitizers/suppressions/tsan.supp create mode 100644 sanitizers/suppressions/ubsan.supp diff --git a/.config/cspell.config.yaml b/.config/cspell.config.yaml index 3bd295c14b..73b0417d79 100644 --- a/.config/cspell.config.yaml +++ b/.config/cspell.config.yaml @@ -28,6 +28,7 @@ ignoreRegExpList: - /[\['"`]-[DWw][a-zA-Z0-9_-]+['"`\]]/g # compile flags suggestWords: - xprl->xrpl + - xprld->xrpld - unsynched->unsynced - synched->synced - synch->sync @@ -61,6 +62,7 @@ words: - compr - conanfile - conanrun + - confs - connectability - coro - coros @@ -90,6 +92,7 @@ words: - finalizers - firewalled - fmtdur + - fsanitize - funclets - gcov - gcovr @@ -126,6 +129,7 @@ words: - lseq - lsmf - ltype + - mcmodel - MEMORYSTATUSEX - Merkle - Metafuncton @@ -235,6 +239,8 @@ words: - txn - txns - txs + - UBSAN + - ubsan - umant - unacquired - unambiguity diff --git a/.github/actions/build-deps/action.yml b/.github/actions/build-deps/action.yml index d1fb980dac..9d52be1998 100644 --- a/.github/actions/build-deps/action.yml +++ b/.github/actions/build-deps/action.yml @@ -18,6 +18,10 @@ inputs: description: "The logging verbosity." required: false default: "verbose" + sanitizers: + description: "The sanitizers to enable." + required: false + default: "" runs: using: composite @@ -29,9 +33,11 @@ runs: BUILD_OPTION: ${{ inputs.force_build == 'true' && '*' || 'missing' }} BUILD_TYPE: ${{ inputs.build_type }} LOG_VERBOSITY: ${{ inputs.log_verbosity }} + SANITIZERS: ${{ inputs.sanitizers }} run: | echo 'Installing dependencies.' conan install \ + --profile ci \ --build="${BUILD_OPTION}" \ --options:host='&:tests=True' \ --options:host='&:xrpld=True' \ diff --git a/.github/actions/setup-conan/action.yml b/.github/actions/setup-conan/action.yml index 1184cfd3d9..dedf53f109 100644 --- a/.github/actions/setup-conan/action.yml +++ b/.github/actions/setup-conan/action.yml @@ -28,7 +28,7 @@ runs: shell: bash run: | echo 'Installing profile.' - conan config install conan/profiles/default -tf $(conan config home)/profiles/ + conan config install conan/profiles/ -tf $(conan config home)/profiles/ echo 'Conan profile:' conan profile show diff --git a/.github/scripts/strategy-matrix/generate.py b/.github/scripts/strategy-matrix/generate.py index c3d2da1f9f..bf5bf5d3ba 100755 --- a/.github/scripts/strategy-matrix/generate.py +++ b/.github/scripts/strategy-matrix/generate.py @@ -229,7 +229,7 @@ def generate_strategy_matrix(all: bool, config: Config) -> list: if (n := os["compiler_version"]) != "": config_name += f"-{n}" config_name += ( - f"-{architecture['platform'][architecture['platform'].find('/') + 1 :]}" + f"-{architecture['platform'][architecture['platform'].find('/')+1:]}" ) config_name += f"-{build_type.lower()}" if "-Dcoverage=ON" in cmake_args: @@ -240,17 +240,53 @@ def generate_strategy_matrix(all: bool, config: Config) -> list: # Add the configuration to the list, with the most unique fields first, # so that they are easier to identify in the GitHub Actions UI, as long # names get truncated. - configurations.append( - { - "config_name": config_name, - "cmake_args": cmake_args, - "cmake_target": cmake_target, - "build_only": build_only, - "build_type": build_type, - "os": os, - "architecture": architecture, - } - ) + # 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 + if ( + os["distro_version"] == "bookworm" + and f"{os['compiler_name']}-{os['compiler_version']}" == "clang-20" + ): + # Add ASAN + UBSAN configuration. + configurations.append( + { + "config_name": config_name + "-asan-ubsan", + "cmake_args": cmake_args, + "cmake_target": cmake_target, + "build_only": build_only, + "build_type": build_type, + "os": os, + "architecture": architecture, + "sanitizers": "address,undefinedbehavior", + } + ) + # TSAN is deactivated due to seg faults with latest compilers. + activate_tsan = False + if activate_tsan: + configurations.append( + { + "config_name": config_name + "-tsan-ubsan", + "cmake_args": cmake_args, + "cmake_target": cmake_target, + "build_only": build_only, + "build_type": build_type, + "os": os, + "architecture": architecture, + "sanitizers": "thread,undefinedbehavior", + } + ) + else: + configurations.append( + { + "config_name": config_name, + "cmake_args": cmake_args, + "cmake_target": cmake_target, + "build_only": build_only, + "build_type": build_type, + "os": os, + "architecture": architecture, + "sanitizers": "", + } + ) return configurations diff --git a/.github/workflows/reusable-build-test-config.yml b/.github/workflows/reusable-build-test-config.yml index bc0717e1a5..b8c82aa94d 100644 --- a/.github/workflows/reusable-build-test-config.yml +++ b/.github/workflows/reusable-build-test-config.yml @@ -51,6 +51,12 @@ on: type: number default: 2 + sanitizers: + description: "The sanitizers to enable." + required: false + type: string + default: "" + secrets: CODECOV_TOKEN: description: "The Codecov token to use for uploading coverage reports." @@ -91,6 +97,7 @@ jobs: # Determine if coverage and voidstar should be enabled. COVERAGE_ENABLED: ${{ contains(inputs.cmake_args, '-Dcoverage=ON') }} VOIDSTAR_ENABLED: ${{ contains(inputs.cmake_args, '-Dvoidstar=ON') }} + SANITIZERS_ENABLED: ${{ inputs.sanitizers != '' }} steps: - name: Cleanup workspace (macOS and Windows) if: ${{ runner.os == 'macOS' || runner.os == 'Windows' }} @@ -128,11 +135,13 @@ jobs: # Set the verbosity to "quiet" for Windows to avoid an excessive # amount of logs. For other OSes, the "verbose" logs are more useful. log_verbosity: ${{ runner.os == 'Windows' && 'quiet' || 'verbose' }} + sanitizers: ${{ inputs.sanitizers }} - name: Configure CMake working-directory: ${{ env.BUILD_DIR }} env: BUILD_TYPE: ${{ inputs.build_type }} + SANITIZERS: ${{ inputs.sanitizers }} CMAKE_ARGS: ${{ inputs.cmake_args }} run: | cmake \ @@ -174,7 +183,7 @@ jobs: if-no-files-found: error - name: Check linking (Linux) - if: ${{ runner.os == 'Linux' }} + if: ${{ runner.os == 'Linux' && env.SANITIZERS_ENABLED == 'false' }} working-directory: ${{ env.BUILD_DIR }} run: | ldd ./xrpld @@ -191,6 +200,14 @@ jobs: run: | ./xrpld --version | grep libvoidstar + - name: Set sanitizer options + if: ${{ !inputs.build_only && env.SANITIZERS_ENABLED == 'true' }} + run: | + echo "ASAN_OPTIONS=print_stacktrace=1:detect_container_overflow=0:suppressions=${GITHUB_WORKSPACE}/sanitizers/suppressions/asan.supp" >> ${GITHUB_ENV} + echo "TSAN_OPTIONS=second_deadlock_stack=1:halt_on_error=0:suppressions=${GITHUB_WORKSPACE}/sanitizers/suppressions/tsan.supp" >> ${GITHUB_ENV} + echo "UBSAN_OPTIONS=suppressions=${GITHUB_WORKSPACE}/sanitizers/suppressions/ubsan.supp" >> ${GITHUB_ENV} + echo "LSAN_OPTIONS=suppressions=${GITHUB_WORKSPACE}/sanitizers/suppressions/lsan.supp" >> ${GITHUB_ENV} + - name: Run the separate tests if: ${{ !inputs.build_only }} working-directory: ${{ env.BUILD_DIR }} diff --git a/.github/workflows/reusable-build-test.yml b/.github/workflows/reusable-build-test.yml index 2b4d38da61..0086cbbfb5 100644 --- a/.github/workflows/reusable-build-test.yml +++ b/.github/workflows/reusable-build-test.yml @@ -57,5 +57,6 @@ jobs: runs_on: ${{ toJSON(matrix.architecture.runner) }} image: ${{ contains(matrix.architecture.platform, 'linux') && format('ghcr.io/xrplf/ci/{0}-{1}:{2}-{3}-sha-{4}', matrix.os.distro_name, matrix.os.distro_version, matrix.os.compiler_name, matrix.os.compiler_version, matrix.os.image_sha) || '' }} config_name: ${{ matrix.config_name }} + sanitizers: ${{ matrix.sanitizers }} secrets: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/BUILD.md b/BUILD.md index 2d1ac9b134..b239b10be6 100644 --- a/BUILD.md +++ b/BUILD.md @@ -1,5 +1,5 @@ -| :warning: **WARNING** :warning: -|---| +| :warning: **WARNING** :warning: | +| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | These instructions assume you have a C++ development environment ready with Git, Python, Conan, CMake, and a C++ compiler. For help setting one up on Linux, macOS, or Windows, [see this guide](./docs/build/environment.md). | > These instructions also assume a basic familiarity with Conan and CMake. @@ -523,18 +523,32 @@ stored inside the build directory, as either of: - file named `coverage.`_extension_, with a suitable extension for the report format, or - directory named `coverage`, with the `index.html` and other files inside, for the `html-details` or `html-nested` report formats. +## Sanitizers + +To build dependencies and xrpld with sanitizer instrumentation, set the +`SANITIZERS` environment variable (only once before running conan and cmake) and use the `sanitizers` profile in conan: + +```bash +export SANITIZERS=address,undefinedbehavior + +conan install .. --output-folder . --profile:all sanitizers --build missing --settings build_type=Debug + +cmake -DCMAKE_TOOLCHAIN_FILE:FILEPATH=build/generators/conan_toolchain.cmake -DCMAKE_BUILD_TYPE=Debug -Dxrpld=ON -Dtests=ON .. +``` + +See [Sanitizers docs](./docs/build/sanitizers.md) for more details. + ## Options -| Option | Default Value | Description | -| ---------- | ------------- | ------------------------------------------------------------------ | -| `assert` | OFF | Enable assertions. | -| `coverage` | OFF | Prepare the coverage report. | -| `san` | N/A | Enable a sanitizer with Clang. Choices are `thread` and `address`. | -| `tests` | OFF | Build tests. | -| `unity` | OFF | Configure a unity build. | -| `xrpld` | OFF | Build the xrpld application, and not just the libxrpl library. | -| `werr` | OFF | Treat compilation warnings as errors | -| `wextra` | OFF | Enable additional compilation warnings | +| Option | Default Value | Description | +| ---------- | ------------- | -------------------------------------------------------------- | +| `assert` | OFF | Enable assertions. | +| `coverage` | OFF | Prepare the coverage report. | +| `tests` | OFF | Build tests. | +| `unity` | OFF | Configure a unity build. | +| `xrpld` | OFF | Build the xrpld application, and not just the libxrpl library. | +| `werr` | OFF | Treat compilation warnings as errors | +| `wextra` | OFF | Enable additional compilation warnings | [Unity builds][5] may be faster for the first build (at the cost of much more memory) since they concatenate sources into fewer diff --git a/CMakeLists.txt b/CMakeLists.txt index 26fc310d39..ee0484e79d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,14 +16,16 @@ set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) -if(CMAKE_CXX_COMPILER_ID MATCHES "GNU") +include(CompilationEnv) + +if(is_gcc) # GCC-specific fixes add_compile_options(-Wno-unknown-pragmas -Wno-subobject-linkage) # -Wno-subobject-linkage can be removed when we upgrade GCC version to at least 13.3 -elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang") +elseif(is_clang) # Clang-specific fixes add_compile_options(-Wno-unknown-warning-option) # Ignore unknown warning options -elseif(MSVC) +elseif(is_msvc) # MSVC-specific fixes add_compile_options(/wd4068) # Ignore unknown pragmas endif() @@ -77,6 +79,7 @@ if (packages_only) return () endif () include(XrplCompiler) +include(XrplSanitizers) include(XrplInterface) option(only_docs "Include only the docs target?" FALSE) diff --git a/cmake/CompilationEnv.cmake b/cmake/CompilationEnv.cmake new file mode 100644 index 0000000000..345e4cdd62 --- /dev/null +++ b/cmake/CompilationEnv.cmake @@ -0,0 +1,54 @@ + # Shared detection of compiler, operating system, and architecture. + # + # This module centralizes environment detection so that other + # CMake modules can use the same variables instead of repeating + # checks on CMAKE_* and built-in platform variables. + +# Only run once per configure step. +include_guard(GLOBAL) + +# -------------------------------------------------------------------- +# Compiler detection (C++) +# -------------------------------------------------------------------- +set(is_clang FALSE) +set(is_gcc FALSE) +set(is_msvc FALSE) + +if(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") # Clang or AppleClang + set(is_clang TRUE) +elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + set(is_gcc TRUE) +elseif(MSVC) + set(is_msvc TRUE) +else() + message(FATAL_ERROR "Unsupported C++ compiler: ${CMAKE_CXX_COMPILER_ID}") +endif() + + +# -------------------------------------------------------------------- +# Operating system detection +# -------------------------------------------------------------------- +set(is_linux FALSE) +set(is_windows FALSE) +set(is_macos FALSE) + +if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + set(is_linux TRUE) +elseif(CMAKE_SYSTEM_NAME STREQUAL "Windows") + set(is_windows TRUE) +elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + set(is_macos TRUE) +endif() + +# -------------------------------------------------------------------- +# Architecture +# -------------------------------------------------------------------- +set(is_amd64 FALSE) +set(is_arm64 FALSE) +if(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|AMD64") + set(is_amd64 TRUE) +elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|arm64") + set(is_arm64 TRUE) +else() + message(FATAL_ERROR "Unknown architecture: ${CMAKE_SYSTEM_PROCESSOR}") +endif() diff --git a/cmake/XrplCompiler.cmake b/cmake/XrplCompiler.cmake index 0777bf948c..a7c5ff0423 100644 --- a/cmake/XrplCompiler.cmake +++ b/cmake/XrplCompiler.cmake @@ -2,16 +2,23 @@ setup project-wide compiler settings #]===================================================================] +include(CompilationEnv) + #[=========================================================[ TODO some/most of these common settings belong in a toolchain file, especially the ABI-impacting ones #]=========================================================] add_library (common INTERFACE) add_library (Xrpl::common ALIAS common) +include(XrplSanitizers) # add a single global dependency on this interface lib link_libraries (Xrpl::common) +# Respect CMAKE_POSITION_INDEPENDENT_CODE setting (may be set by Conan toolchain) +if(NOT DEFINED CMAKE_POSITION_INDEPENDENT_CODE) + set(CMAKE_POSITION_INDEPENDENT_CODE ON) +endif() set_target_properties (common - PROPERTIES INTERFACE_POSITION_INDEPENDENT_CODE ON) + PROPERTIES INTERFACE_POSITION_INDEPENDENT_CODE ${CMAKE_POSITION_INDEPENDENT_CODE}) set(CMAKE_CXX_EXTENSIONS OFF) target_compile_definitions (common INTERFACE @@ -116,8 +123,8 @@ else () # link to static libc/c++ iff: # * static option set and # * NOT APPLE (AppleClang does not support static libc/c++) and - # * NOT san (sanitizers typically don't work with static libc/c++) - $<$,$>,$>>: + # * NOT SANITIZERS (sanitizers typically don't work with static libc/c++) + $<$,$>,$>>: -static-libstdc++ -static-libgcc >) diff --git a/cmake/XrplInterface.cmake b/cmake/XrplInterface.cmake index 9847f39fba..6e0203c099 100644 --- a/cmake/XrplInterface.cmake +++ b/cmake/XrplInterface.cmake @@ -2,6 +2,8 @@ xrpld compile options/settings via an interface library #]===================================================================] +include(CompilationEnv) + add_library (opts INTERFACE) add_library (Xrpl::opts ALIAS opts) target_compile_definitions (opts @@ -42,22 +44,6 @@ if(jemalloc) target_link_libraries(opts INTERFACE jemalloc::jemalloc) endif () -if (san) - target_compile_options (opts - INTERFACE - # sanitizers recommend minimum of -O1 for reasonable performance - $<$:-O1> - ${SAN_FLAG} - -fno-omit-frame-pointer) - target_compile_definitions (opts - INTERFACE - $<$:SANITIZER=ASAN> - $<$:SANITIZER=TSAN> - $<$:SANITIZER=MSAN> - $<$:SANITIZER=UBSAN>) - target_link_libraries (opts INTERFACE ${SAN_FLAG} ${SAN_LIB}) -endif () - #[===================================================================[ xrpld transitive library deps via an interface library #]===================================================================] diff --git a/cmake/XrplSanitizers.cmake b/cmake/XrplSanitizers.cmake new file mode 100644 index 0000000000..050a5ef6f0 --- /dev/null +++ b/cmake/XrplSanitizers.cmake @@ -0,0 +1,198 @@ +#[===================================================================[ + Configure sanitizers based on environment variables. + + This module reads the following environment variables: + - SANITIZERS: The sanitizers to enable. Possible values: + - "address" + - "address,undefinedbehavior" + - "thread" + - "thread,undefinedbehavior" + - "undefinedbehavior" + + The compiler type and platform are detected in CompilationEnv.cmake. + The sanitizer compile options are applied to the 'common' interface library + which is linked to all targets in the project. + + Internal flag variables set by this module: + + - SANITIZER_TYPES: List of sanitizer types to enable (e.g., "address", + "thread", "undefined"). And two more flags for undefined behavior sanitizer (e.g., "float-divide-by-zero", "unsigned-integer-overflow"). + This list is joined with commas and passed to -fsanitize=. + + - SANITIZERS_COMPILE_FLAGS: Compiler flags for sanitizer instrumentation. + Includes: + * -fno-omit-frame-pointer: Preserves frame pointers for stack traces + * -O1: Minimum optimization for reasonable performance + * -fsanitize=: Enables sanitizer instrumentation + * -fsanitize-ignorelist=: (Clang only) Compile-time ignorelist + * -mcmodel=large/medium: (GCC only) Code model for large binaries + * -Wno-stringop-overflow: (GCC only) Suppresses false positive warnings + * -Wno-tsan: (For GCC TSAN combination only) Suppresses atomic_thread_fence warnings + + - SANITIZERS_LINK_FLAGS: Linker flags for sanitizer runtime libraries. + Includes: + * -fsanitize=: Links sanitizer runtime libraries + * -mcmodel=large/medium: (GCC only) Matches compile-time code model + + - SANITIZERS_RELOCATION_FLAGS: (GCC only) Code model flags for linking. + Used to handle large instrumented binaries on x86_64: + * -mcmodel=large: For AddressSanitizer (prevents relocation errors) + * -mcmodel=medium: For ThreadSanitizer (large model is incompatible) +#]===================================================================] + +include(CompilationEnv) + +# Read environment variable +set(SANITIZERS $ENV{SANITIZERS}) + +# Set SANITIZERS_ENABLED flag for use in other modules +if(SANITIZERS MATCHES "address|thread|undefinedbehavior") + set(SANITIZERS_ENABLED TRUE) +else() + set(SANITIZERS_ENABLED FALSE) + return() +endif() + +# Sanitizers are not supported on Windows/MSVC +if(is_msvc) + message(FATAL_ERROR "Sanitizers are not supported on Windows/MSVC. " + "Please unset the SANITIZERS environment variable.") +endif() + +message(STATUS "Configuring sanitizers: ${SANITIZERS}") + +# Parse SANITIZERS value to determine which sanitizers to enable +set(enable_asan FALSE) +set(enable_tsan FALSE) +set(enable_ubsan FALSE) + +# Normalize SANITIZERS into a list +set(san_list "${SANITIZERS}") +string(REPLACE "," ";" san_list "${san_list}") +separate_arguments(san_list) + +foreach(san IN LISTS san_list) + if(san STREQUAL "address") + set(enable_asan TRUE) + elseif(san STREQUAL "thread") + set(enable_tsan TRUE) + elseif(san STREQUAL "undefinedbehavior") + set(enable_ubsan TRUE) + else() + message(FATAL_ERROR "Unsupported sanitizer type: ${san}" + "Supported: address, thread, undefinedbehavior and their combinations.") + endif() +endforeach() + +# Validate sanitizer compatibility +if(enable_asan AND enable_tsan) + message(FATAL_ERROR "AddressSanitizer and ThreadSanitizer are incompatible and cannot be enabled simultaneously. " + "Use 'address' or 'thread', optionally with 'undefinedbehavior'.") +endif() + +# Frame pointer is required for meaningful stack traces. Sanitizers recommend minimum of -O1 for reasonable performance +set(SANITIZERS_COMPILE_FLAGS "-fno-omit-frame-pointer" "-O1") + +# Build the sanitizer flags list +set(SANITIZER_TYPES) + +if(enable_asan) + list(APPEND SANITIZER_TYPES "address") +elseif(enable_tsan) + list(APPEND SANITIZER_TYPES "thread") +endif() + +if(enable_ubsan) + # UB sanitizer flags + list(APPEND SANITIZER_TYPES "undefined" "float-divide-by-zero") + if(is_clang) + # Clang supports additional UB checks. More info here https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html + list(APPEND SANITIZER_TYPES "unsigned-integer-overflow") + endif() +endif() + +# Configure code model for GCC on amd64 +# Use large code model for ASAN to avoid relocation errors +# Use medium code model for TSAN (large is not compatible with TSAN) +set(SANITIZERS_RELOCATION_FLAGS) + +# Compiler-specific configuration +if(is_gcc) + # Disable mold, gold and lld linkers for GCC with sanitizers + # Use default linker (bfd/ld) which is more lenient with mixed code models + # This is needed since the size of instrumented binary exceeds the limits set by mold, lld and gold linkers + set(use_mold OFF CACHE BOOL "Use mold linker" FORCE) + set(use_gold OFF CACHE BOOL "Use gold linker" FORCE) + set(use_lld OFF CACHE BOOL "Use lld linker" FORCE) + message(STATUS " Disabled mold, gold, and lld linkers for GCC with sanitizers") + + # Suppress false positive warnings in GCC with stringop-overflow + list(APPEND SANITIZERS_COMPILE_FLAGS "-Wno-stringop-overflow") + + if(is_amd64 AND enable_asan) + message(STATUS " Using large code model (-mcmodel=large)") + list(APPEND SANITIZERS_COMPILE_FLAGS "-mcmodel=large") + list(APPEND SANITIZERS_RELOCATION_FLAGS "-mcmodel=large") + elseif(enable_tsan) + # GCC doesn't support atomic_thread_fence with tsan. Suppress warnings. + list(APPEND SANITIZERS_COMPILE_FLAGS "-Wno-tsan") + message(STATUS " Using medium code model (-mcmodel=medium)") + list(APPEND SANITIZERS_COMPILE_FLAGS "-mcmodel=medium") + list(APPEND SANITIZERS_RELOCATION_FLAGS "-mcmodel=medium") + endif() + + # Join sanitizer flags with commas for -fsanitize option + list(JOIN SANITIZER_TYPES "," SANITIZER_TYPES_STR) + + # Add sanitizer to compile and link flags + list(APPEND SANITIZERS_COMPILE_FLAGS "-fsanitize=${SANITIZER_TYPES_STR}") + set(SANITIZERS_LINK_FLAGS "${SANITIZERS_RELOCATION_FLAGS}" "-fsanitize=${SANITIZER_TYPES_STR}") + +elseif(is_clang) + # Add ignorelist for Clang (GCC doesn't support this) + # Use CMAKE_SOURCE_DIR to get the path to the ignorelist + set(IGNORELIST_PATH "${CMAKE_SOURCE_DIR}/sanitizers/suppressions/sanitizer-ignorelist.txt") + if(NOT EXISTS "${IGNORELIST_PATH}") + message(FATAL_ERROR "Sanitizer ignorelist not found: ${IGNORELIST_PATH}") + endif() + + list(APPEND SANITIZERS_COMPILE_FLAGS "-fsanitize-ignorelist=${IGNORELIST_PATH}") + message(STATUS " Using sanitizer ignorelist: ${IGNORELIST_PATH}") + + # Join sanitizer flags with commas for -fsanitize option + list(JOIN SANITIZER_TYPES "," SANITIZER_TYPES_STR) + + # Add sanitizer to compile and link flags + list(APPEND SANITIZERS_COMPILE_FLAGS "-fsanitize=${SANITIZER_TYPES_STR}") + set(SANITIZERS_LINK_FLAGS "-fsanitize=${SANITIZER_TYPES_STR}") +endif() + +message(STATUS " Compile flags: ${SANITIZERS_COMPILE_FLAGS}") +message(STATUS " Link flags: ${SANITIZERS_LINK_FLAGS}") + +# Apply the sanitizer flags to the 'common' interface library +# This is the same library used by XrplCompiler.cmake +target_compile_options(common INTERFACE + $<$:${SANITIZERS_COMPILE_FLAGS}> + $<$:${SANITIZERS_COMPILE_FLAGS}> +) + +# Apply linker flags +target_link_options(common INTERFACE ${SANITIZERS_LINK_FLAGS}) + +# Define SANITIZERS macro for BuildInfo.cpp +set(sanitizers_list) +if(enable_asan) + list(APPEND sanitizers_list "ASAN") +endif() +if(enable_tsan) + list(APPEND sanitizers_list "TSAN") +endif() +if(enable_ubsan) + list(APPEND sanitizers_list "UBSAN") +endif() + +if(sanitizers_list) + list(JOIN sanitizers_list "." sanitizers_str) + target_compile_definitions(common INTERFACE SANITIZERS=${sanitizers_str}) +endif() diff --git a/cmake/XrplSanity.cmake b/cmake/XrplSanity.cmake index db983da73d..7464ca396c 100644 --- a/cmake/XrplSanity.cmake +++ b/cmake/XrplSanity.cmake @@ -2,6 +2,8 @@ sanity checks #]===================================================================] +include(CompilationEnv) + get_property(is_multiconfig GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) set (CMAKE_CONFIGURATION_TYPES "Debug;Release" CACHE STRING "" FORCE) @@ -16,14 +18,12 @@ if (NOT is_multiconfig) endif () endif () -if ("${CMAKE_CXX_COMPILER_ID}" MATCHES ".*Clang") # both Clang and AppleClang - set (is_clang TRUE) +if (is_clang) # both Clang and AppleClang if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 16.0) message (FATAL_ERROR "This project requires clang 16 or later") endif () -elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") - set (is_gcc TRUE) +elseif (is_gcc) if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 12.0) message (FATAL_ERROR "This project requires GCC 12 or later") endif () @@ -40,11 +40,6 @@ if (MSVC AND CMAKE_GENERATOR_PLATFORM STREQUAL "Win32") message (FATAL_ERROR "Visual Studio 32-bit build is not supported.") endif () -if (NOT CMAKE_SIZEOF_VOID_P EQUAL 8) - message (FATAL_ERROR "Xrpld requires a 64 bit target architecture.\n" - "The most likely cause of this warning is trying to build xrpld with a 32-bit OS.") -endif () - if (APPLE AND NOT HOMEBREW) find_program (HOMEBREW brew) endif () diff --git a/cmake/XrplSettings.cmake b/cmake/XrplSettings.cmake index c3f013c575..647e95837d 100644 --- a/cmake/XrplSettings.cmake +++ b/cmake/XrplSettings.cmake @@ -2,11 +2,7 @@ declare options and variables #]===================================================================] -if(CMAKE_SYSTEM_NAME STREQUAL "Linux") - set (is_linux TRUE) -else() - set(is_linux FALSE) -endif() +include(CompilationEnv) if("$ENV{CI}" STREQUAL "true" OR "$ENV{CONTINUOUS_INTEGRATION}" STREQUAL "true") set(is_ci TRUE) @@ -62,7 +58,7 @@ else() set(wextra OFF CACHE BOOL "gcc/clang only" FORCE) endif() -if(is_linux) +if(is_linux AND NOT SANITIZER) option(BUILD_SHARED_LIBS "build shared xrpl libraries" OFF) option(static "link protobuf, openssl, libc++, and boost statically" ON) option(perf "Enables flags that assist with perf recording" OFF) @@ -107,33 +103,6 @@ option(local_protobuf option(local_grpc "Force a local build of gRPC instead of looking for an installed version." OFF) -# this one is a string and therefore can't be an option -set(san "" CACHE STRING "On gcc & clang, add sanitizer instrumentation") -set_property(CACHE san PROPERTY STRINGS ";undefined;memory;address;thread") -if(san) - string(TOLOWER ${san} san) - set(SAN_FLAG "-fsanitize=${san}") - set(SAN_LIB "") - if(is_gcc) - if(san STREQUAL "address") - set(SAN_LIB "asan") - elseif(san STREQUAL "thread") - set(SAN_LIB "tsan") - elseif(san STREQUAL "memory") - set(SAN_LIB "msan") - elseif(san STREQUAL "undefined") - set(SAN_LIB "ubsan") - endif() - endif() - set(_saved_CRL ${CMAKE_REQUIRED_LIBRARIES}) - set(CMAKE_REQUIRED_LIBRARIES "${SAN_FLAG};${SAN_LIB}") - check_cxx_compiler_flag(${SAN_FLAG} COMPILER_SUPPORTS_SAN) - set(CMAKE_REQUIRED_LIBRARIES ${_saved_CRL}) - if(NOT COMPILER_SUPPORTS_SAN) - message(FATAL_ERROR "${san} sanitizer does not seem to be supported by your compiler") - endif() -endif() - # the remaining options are obscure and rarely used option(beast_no_unit_test_inline "Prevents unit test definitions from being inserted into global table" diff --git a/cmake/deps/Boost.cmake b/cmake/deps/Boost.cmake index 19263e0ac9..b73698efd8 100644 --- a/cmake/deps/Boost.cmake +++ b/cmake/deps/Boost.cmake @@ -1,3 +1,6 @@ +include(CompilationEnv) +include(XrplSanitizers) + find_package(Boost REQUIRED COMPONENTS chrono @@ -32,7 +35,7 @@ target_link_libraries(xrpl_boost if(Boost_COMPILER) target_link_libraries(xrpl_boost INTERFACE Boost::disable_autolinking) endif() -if(san AND is_clang) +if(SANITIZERS_ENABLED AND is_clang) # TODO: gcc does not support -fsanitize-blacklist...can we do something else # for gcc ? if(NOT Boost_INCLUDE_DIRS AND TARGET Boost::headers) diff --git a/conan/profiles/ci b/conan/profiles/ci new file mode 100644 index 0000000000..c4c0898ad5 --- /dev/null +++ b/conan/profiles/ci @@ -0,0 +1 @@ + include(sanitizers) diff --git a/conan/profiles/sanitizers b/conan/profiles/sanitizers new file mode 100644 index 0000000000..d7a622359a --- /dev/null +++ b/conan/profiles/sanitizers @@ -0,0 +1,59 @@ +include(default) +{% set compiler, version, compiler_exe = detect_api.detect_default_compiler() %} +{% set sanitizers = os.getenv("SANITIZERS") %} + +[conf] +{% if sanitizers %} + {% if compiler == "gcc" %} + {% if "address" in sanitizers or "thread" in sanitizers or "undefinedbehavior" in sanitizers %} + {% set sanitizer_list = [] %} + {% set model_code = "" %} + {% set extra_cxxflags = ["-fno-omit-frame-pointer", "-O1", "-Wno-stringop-overflow"] %} + + {% if "address" in sanitizers %} + {% set _ = sanitizer_list.append("address") %} + {% set model_code = "-mcmodel=large" %} + {% elif "thread" in sanitizers %} + {% set _ = sanitizer_list.append("thread") %} + {% set model_code = "-mcmodel=medium" %} + {% set _ = extra_cxxflags.append("-Wno-tsan") %} + {% endif %} + + {% if "undefinedbehavior" in sanitizers %} + {% set _ = sanitizer_list.append("undefined") %} + {% set _ = sanitizer_list.append("float-divide-by-zero") %} + {% endif %} + + {% set sanitizer_flags = "-fsanitize=" ~ ",".join(sanitizer_list) ~ " " ~ model_code %} + + tools.build:cxxflags+=['{{sanitizer_flags}} {{" ".join(extra_cxxflags)}}'] + tools.build:sharedlinkflags+=['{{sanitizer_flags}}'] + tools.build:exelinkflags+=['{{sanitizer_flags}}'] + {% endif %} + {% elif compiler == "apple-clang" or compiler == "clang" %} + {% if "address" in sanitizers or "thread" in sanitizers or "undefinedbehavior" in sanitizers %} + {% set sanitizer_list = [] %} + {% set extra_cxxflags = ["-fno-omit-frame-pointer", "-O1"] %} + + {% if "address" in sanitizers %} + {% set _ = sanitizer_list.append("address") %} + {% elif "thread" in sanitizers %} + {% set _ = sanitizer_list.append("thread") %} + {% endif %} + + {% if "undefinedbehavior" in sanitizers %} + {% set _ = sanitizer_list.append("undefined") %} + {% set _ = sanitizer_list.append("float-divide-by-zero") %} + {% set _ = sanitizer_list.append("unsigned-integer-overflow") %} + {% endif %} + + {% set sanitizer_flags = "-fsanitize=" ~ ",".join(sanitizer_list) %} + + tools.build:cxxflags+=['{{sanitizer_flags}} {{" ".join(extra_cxxflags)}}'] + tools.build:sharedlinkflags+=['{{sanitizer_flags}}'] + tools.build:exelinkflags+=['{{sanitizer_flags}}'] + {% endif %} + {% endif %} +{% endif %} + +tools.info.package_id:confs+=["tools.build:cxxflags", "tools.build:exelinkflags", "tools.build:sharedlinkflags"] diff --git a/docs/build/sanitizers.md b/docs/build/sanitizers.md new file mode 100644 index 0000000000..3f9809ae98 --- /dev/null +++ b/docs/build/sanitizers.md @@ -0,0 +1,207 @@ +# Sanitizer Configuration for Rippled + +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) + - [Building with Sanitizers](#building-with-sanitizers) + - [Summary](#summary) + - [Build steps:](#build-steps) + - [Install dependencies](#install-dependencies) + - [Call CMake](#call-cmake) + - [Build](#build) + - [Running Tests with Sanitizers](#running-tests-with-sanitizers) + - [AddressSanitizer (ASAN)](#addresssanitizer-asan) + - [ThreadSanitizer (TSan)](#threadsanitizer-tsan) + - [LeakSanitizer (LSan)](#leaksanitizer-lsan) + - [UndefinedBehaviorSanitizer (UBSan)](#undefinedbehaviorsanitizer-ubsan) + - [Suppression Files](#suppression-files) + - [`asan.supp`](#asansupp) + - [`lsan.supp`](#lsansupp) + - [`ubsan.supp`](#ubsansupp) + - [`tsan.supp`](#tsansupp) + - [`sanitizer-ignorelist.txt`](#sanitizer-ignorelisttxt) + - [Troubleshooting](#troubleshooting) + - ["ASAN is ignoring requested \_\_asan_handle_no_return" warnings](#asan-is-ignoring-requested-__asan_handle_no_return-warnings) + - [Sanitizer Mismatch Errors](#sanitizer-mismatch-errors) + - [References](#references) + +## Building with Sanitizers + +### Summary + +Follow the same instructions as mentioned in [BUILD.md](../../BUILD.md) but with the following changes: + +1. Make sure you have a clean build directory. +2. Set the `SANITIZERS` environment variable before calling conan install and cmake. Only set it once. Make sure both conan and cmake read the same values. + Example: `export SANITIZERS=address,undefinedbehavior` +3. Optionally use `--profile:all sanitizers` with Conan to build dependencies with sanitizer instrumentation. [!NOTE]Building with sanitizer-instrumented dependencies is slower but produces fewer false positives. +4. Set `ASAN_OPTIONS`, `LSAN_OPTIONS`, `UBSAN_OPTIONS` and `TSAN_OPTIONS` environment variables to configure sanitizer behavior when running executables. [More details below](#running-tests-with-sanitizers). + +--- + +### Build steps: + +```bash +cd /path/to/rippled +rm -rf .build +mkdir .build +cd .build +``` + +#### Install dependencies + +The `SANITIZERS` environment variable is used by both Conan and CMake. + +```bash +export SANITIZERS=address,undefinedbehavior +# Standard build (without instrumenting dependencies) +conan install .. --output-folder . --build missing --settings build_type=Debug + +# Or with sanitizer-instrumented dependencies (takes longer but fewer false positives) +conan install .. --output-folder . --profile:all sanitizers --build missing --settings build_type=Debug +``` + +[!CAUTION] +Do not mix Address and Thread sanitizers - they are incompatible. + +Since you already set the `SANITIZERS` environment variable when running Conan, same values will be read for the next part. + +#### Call CMake + +```bash +cmake .. -G Ninja \ + -DCMAKE_TOOLCHAIN_FILE:FILEPATH=build/generators/conan_toolchain.cmake \ + -DCMAKE_BUILD_TYPE=Debug \ + -Dtests=ON -Dxrpld=ON +``` + +#### Build + +```bash +cmake --build . --parallel 4 +``` + +## Running Tests with Sanitizers + +### AddressSanitizer (ASAN) + +**IMPORTANT**: ASAN with Boost produces many false positives. Use these options: + +```bash +export ASAN_OPTIONS="print_stacktrace=1:detect_container_overflow=0:suppressions=path/to/asan.supp:halt_on_error=0:log_path=asan.log" +export LSAN_OPTIONS="suppressions=path/to/lsan.supp:halt_on_error=0:log_path=lsan.log" + +# Run tests +./xrpld --unittest --unittest-jobs=5 +``` + +**Why `detect_container_overflow=0`?** + +- 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. +- 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) + +### ThreadSanitizer (TSan) + +```bash +export TSAN_OPTIONS="suppressions=path/to/tsan.supp halt_on_error=0 log_path=tsan.log" + +# Run tests +./xrpld --unittest --unittest-jobs=5 +``` + +More details [here](https://github.com/google/sanitizers/wiki/ThreadSanitizerCppManual). + +### LeakSanitizer (LSan) + +LSan is automatically enabled with ASAN. To disable it: + +```bash +export ASAN_OPTIONS="detect_leaks=0" +``` + +More details [here](https://github.com/google/sanitizers/wiki/AddressSanitizerLeakSanitizer). + +### UndefinedBehaviorSanitizer (UBSan) + +```bash +export UBSAN_OPTIONS="suppressions=path/to/ubsan.supp:print_stacktrace=1:halt_on_error=0:log_path=ubsan.log" + +# Run tests +./xrpld --unittest --unittest-jobs=5 +``` + +More details [here](https://clang.llvm.org/docs/undefinedbehaviorSanitizer.html). + +## Suppression Files + +[!NOTE] Attached files contain more details. + +### [`asan.supp`](../../sanitizers/suppressions/asan.supp) + +- **Purpose**: Suppress AddressSanitizer (ASAN) errors only +- **Format**: `interceptor_name:` where pattern matches file names. Supported suppression types are: + - interceptor_name + - interceptor_via_fun + - interceptor_via_lib + - odr_violation +- **More info**: [AddressSanitizer](https://github.com/google/sanitizers/wiki/AddressSanitizer) +- **Note**: Cannot suppress stack-buffer-overflow, container-overflow, etc. + +### [`lsan.supp`](../../sanitizers/suppressions/lsan.supp) + +- **Purpose**: Suppress LeakSanitizer (LSan) errors only +- **Format**: `leak:` where pattern matches function/file names +- **More info**: [LeakSanitizer](https://github.com/google/sanitizers/wiki/AddressSanitizerLeakSanitizer) + +### [`ubsan.supp`](../../sanitizers/suppressions/ubsan.supp) + +- **Purpose**: Suppress undefinedbehaviorSanitizer errors +- **Format**: `:` (e.g., `unsigned-integer-overflow:protobuf`) +- **Covers**: Intentional overflows in sanitizers/suppressions libraries (protobuf, gRPC, stdlib) +- More info [UBSan suppressions](https://clang.llvm.org/docs/SanitizerSpecialCaseList.html). + +### [`tsan.supp`](../../sanitizers/suppressions/tsan.supp) + +- **Purpose**: Suppress ThreadSanitizer data race warnings +- **Format**: `race:` where pattern matches function/file names +- **More info**: [ThreadSanitizer suppressions](https://github.com/google/sanitizers/wiki/ThreadSanitizerSuppressions) + +### [`sanitizer-ignorelist.txt`](../../sanitizers/suppressions/sanitizer-ignorelist.txt) + +- **Purpose**: Compile-time ignorelist for all sanitizers +- **Usage**: Passed via `-fsanitize-ignorelist=absolute/path/to/sanitizer-ignorelist.txt` +- **Format**: `:` (e.g., `src:Workers.cpp`) + +## Troubleshooting + +### "ASAN is ignoring requested \_\_asan_handle_no_return" warnings + +These warnings appear when using Boost context switching and are harmless. They indicate potential false positives. + +### Sanitizer Mismatch Errors + +If you see undefined symbols like `___tsan_atomic_load` when building with ASAN: + +**Problem**: Dependencies were built with a different sanitizer than the main project. + +**Solution**: Rebuild everything with the same sanitizer: + +```bash +rm -rf .build +# Then follow the build instructions above +``` + +Then review the log files: `asan.log.*`, `ubsan.log.*`, `tsan.log.*` + +## References + +- [AddressSanitizer Wiki](https://github.com/google/sanitizers/wiki/AddressSanitizer) +- [AddressSanitizer Flags](https://github.com/google/sanitizers/wiki/AddressSanitizerFlags) +- [Container Overflow Detection](https://github.com/google/sanitizers/wiki/AddressSanitizerContainerOverflow) +- [UndefinedBehavior Sanitizer](https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html) +- [ThreadSanitizer](https://github.com/google/sanitizers/wiki/ThreadSanitizerCppManual) diff --git a/sanitizers/suppressions/asan.supp b/sanitizers/suppressions/asan.supp new file mode 100644 index 0000000000..7ba98766bd --- /dev/null +++ b/sanitizers/suppressions/asan.supp @@ -0,0 +1,29 @@ +# The idea is to empty this file gradually by fixing the underlying issues and removing suppressions. +# +# ASAN_OPTIONS="print_stacktrace=1:detect_container_overflow=0:suppressions=sanitizers/suppressions/asan.supp:halt_on_error=0" +# +# The detect_container_overflow=0 option disables false positives from: +# - Boost intrusive containers (slist_iterator.hpp, hashtable.hpp, aged_unordered_container.h) +# - Boost context/coroutine stack switching (Workers.cpp, thread.h) +# +# See: https://github.com/google/sanitizers/wiki/AddressSanitizerContainerOverflow + +# Boost +interceptor_name:boost/asio + +# Leaks in Doctest tests: xrpl.test.* +interceptor_name:src/libxrpl/net/HTTPClient.cpp +interceptor_name:src/libxrpl/net/RegisterSSLCerts.cpp +interceptor_name:src/tests/libxrpl/net/HTTPClient.cpp +interceptor_name:xrpl/net/AutoSocket.h +interceptor_name:xrpl/net/HTTPClient.h +interceptor_name:xrpl/net/HTTPClientSSLContext.h +interceptor_name:xrpl/net/RegisterSSLCerts.h + +# Suppress false positive stack-buffer errors in thread stack allocation +# Related to ASan's __asan_handle_no_return warnings (github.com/google/sanitizers/issues/189) +# These occur during multi-threaded test initialization on macOS +interceptor_name:memcpy +interceptor_name:__bzero +interceptor_name:__asan_memset +interceptor_name:__asan_memcpy diff --git a/sanitizers/suppressions/lsan.supp b/sanitizers/suppressions/lsan.supp new file mode 100644 index 0000000000..a81d7d89fa --- /dev/null +++ b/sanitizers/suppressions/lsan.supp @@ -0,0 +1,16 @@ +# The idea is to empty this file gradually by fixing the underlying issues and removing suppresions. + +# Suppress leaks detected by asan in rippled code. +leak:src/libxrpl/net/HTTPClient.cpp +leak:src/libxrpl/net/RegisterSSLCerts.cpp +leak:src/tests/libxrpl/net/HTTPClient.cpp +leak:xrpl/net/AutoSocket.h +leak:xrpl/net/HTTPClient.h +leak:xrpl/net/HTTPClientSSLContext.h +leak:xrpl/net/RegisterSSLCerts.h +leak:ripple::HTTPClient +leak:ripple::HTTPClientImp + +# Suppress leaks detected by asan in boost code. +leak:boost::asio +leak:boost/asio diff --git a/sanitizers/suppressions/sanitizer-ignorelist.txt b/sanitizers/suppressions/sanitizer-ignorelist.txt new file mode 100644 index 0000000000..5dbead48a2 --- /dev/null +++ b/sanitizers/suppressions/sanitizer-ignorelist.txt @@ -0,0 +1,29 @@ +# We were seeing some false positives and some repeated errors(since these are library files) in following files. +# Clang will skip instrumenting the files added here. +# We should fix the underlying issues(if any) and remove these entries. + +deadlock:libxrpl/beast/utility/beast_Journal.cpp +deadlock:libxrpl/beast/utility/beast_PropertyStream.cpp +deadlock:test/beast/beast_PropertyStream_test.cpp +deadlock:xrpld/core/detail/Workers.cpp +deadlock:xrpld/core/JobQueue.cpp + +race:libxrpl/beast/utility/beast_Journal.cpp +race:libxrpl/beast/utility/beast_PropertyStream.cpp +race:test/beast/beast_PropertyStream_test.cpp +race:xrpld/core/detail/Workers.cpp +race:xrpld/core/JobQueue.cpp + +signal:libxrpl/beast/utility/beast_Journal.cpp +signal:libxrpl/beast/utility/beast_PropertyStream.cpp +signal:test/beast/beast_PropertyStream_test.cpp +signal:xrpld/core/detail/Workers.cpp +signal:xrpld/core/JobQueue.cpp + +src:beast/utility/beast_Journal.cpp +src:beast/utility/beast_PropertyStream.cpp +src:core/detail/Workers.cpp +src:core/JobQueue.cpp +src:libxrpl/beast/utility/beast_Journal.cpp +src:test/beast/beast_PropertyStream_test.cpp +src:src/test/app/Invariants_test.cpp diff --git a/sanitizers/suppressions/tsan.supp b/sanitizers/suppressions/tsan.supp new file mode 100644 index 0000000000..74f3371e68 --- /dev/null +++ b/sanitizers/suppressions/tsan.supp @@ -0,0 +1,102 @@ +# The idea is to empty this file gradually by fixing the underlying issues and removing suppresions. + +# Suppress race in Boost ASIO scheduler detected by GCC-15 +# This is a false positive in Boost's internal pipe() synchronization +race:boost/asio/ +race:boost/context/ +race:boost/asio/executor.hpp +race:boost::asio + +# Suppress tsan related issues in rippled code. +race:src/libxrpl/basics/make_SSLContext.cpp +race:src/libxrpl/basics/Number.cpp +race:src/libxrpl/json/json_value.cpp +race:src/libxrpl/json/to_string.cpp +race:src/libxrpl/ledger/OpenView.cpp +race:src/libxrpl/net/HTTPClient.cpp +race:src/libxrpl/nodestore/backend/NuDBFactory.cpp +race:src/libxrpl/protocol/InnerObjectFormats.cpp +race:src/libxrpl/protocol/STParsedJSON.cpp +race:src/libxrpl/resource/ResourceManager.cpp +race:src/test/app/Flow_test.cpp +race:src/test/app/LedgerReplay_test.cpp +race:src/test/app/NFToken_test.cpp +race:src/test/app/Offer_test.cpp +race:src/test/app/ValidatorSite_test.cpp +race:src/test/consensus/NegativeUNL_test.cpp +race:src/test/jtx/impl/Env.cpp +race:src/test/jtx/impl/JSONRPCClient.cpp +race:src/test/jtx/impl/pay.cpp +race:src/test/jtx/impl/token.cpp +race:src/test/rpc/Book_test.cpp +race:src/xrpld/app/ledger/detail/InboundTransactions.cpp +race:src/xrpld/app/main/Application.cpp +race:src/xrpld/app/main/BasicApp.cpp +race:src/xrpld/app/main/GRPCServer.cpp +race:src/xrpld/app/misc/detail/AmendmentTable.cpp +race:src/xrpld/app/misc/FeeVoteImpl.cpp +race:src/xrpld/app/rdb/detail/Wallet.cpp +race:src/xrpld/overlay/detail/OverlayImpl.cpp +race:src/xrpld/peerfinder/detail/PeerfinderManager.cpp +race:src/xrpld/peerfinder/detail/SourceStrings.cpp +race:src/xrpld/rpc/detail/ServerHandler.cpp +race:xrpl/server/detail/Door.h +race:xrpl/server/detail/Spawn.h +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 + +# More suppressions in external library code. +race:crtstuff.c +race:pipe + +# Deadlock / lock-order-inversion suppressions +# Note: GCC's TSAN may not fully support all deadlock suppression patterns +deadlock:src/libxrpl/beast/utility/beast_Journal.cpp +deadlock:src/libxrpl/beast/utility/beast_PropertyStream.cpp +deadlock:src/test/beast/beast_PropertyStream_test.cpp +deadlock:src/xrpld/core/detail/Workers.cpp +deadlock:src/xrpld/app/misc/detail/Manifest.cpp +deadlock:src/xrpld/app/misc/detail/ValidatorList.cpp +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 + +# Aggressive suppressing of deadlock tsan errors +deadlock:pthread_create +deadlock:pthread_rwlock_rdlock +deadlock:boost::asio + +# Suppress SEGV crashes in TSAN itself during stringbuf operations +# This appears to be a GCC-15 TSAN instrumentation issue with basic_stringbuf::str() +# Commonly triggered in beast::Journal::ScopedStream destructor +signal:std::__cxx11::basic_stringbuf +signal:basic_stringbuf +signal:basic_ostringstream + +called_from_lib:libclang_rt +race:ostreambuf_iterator +race:basic_ostream + +# Suppress SEGV in Boost ASIO memory allocation with GCC-15 TSAN +signal:boost::asio::aligned_new +signal:boost::asio::detail::memory + +# Suppress SEGV in execute_native_thread_routine +signal:execute_native_thread_routine + +# Suppress data race in Boost Context fiber management +# This is a false positive in Boost's exception state management during fiber context switching +race:__cxxabiv1::manage_exception_state +race:boost::context::fiber::resume +race:boost::asio::detail::spawned_fiber_thread +race:boost::asio::detail::spawned_fiber_thread::suspend_with +race:boost::asio::detail::spawned_fiber_thread::destroy + +# Suppress data race in __tsan_memcpy called from Boost fiber operations +race:__tsan_memcpy diff --git a/sanitizers/suppressions/ubsan.supp b/sanitizers/suppressions/ubsan.supp new file mode 100644 index 0000000000..1504ef685f --- /dev/null +++ b/sanitizers/suppressions/ubsan.supp @@ -0,0 +1,237 @@ +# The idea is to empty this file gradually by fixing the underlying issues and removing suppresions. + +# Suppress UBSan errors in external code by source file path +# This matches any source file under the external/ directory +alignment:external +bool:external +bounds:external +cfi:external +enum:external +float-cast-overflow:external +float-divide-by-zero:external +function:external +implicit-integer-sign-change:external +implicit-signed-integer-truncation::external +implicit-signed-integer-truncation:external +implicit-unsigned-integer-truncation:external +integer-divide-by-zero:external +invalid-builtin-use:external +invalid-objc-cast:external +nonnull-attribute:external +null:external +nullability-arg:external +nullability-assign:external +nullability-return:external +object-size:external +pointer-overflow:external +return:external +returns-nonnull-attribute:external +shift-base:external +shift-exponent:external +signed-integer-overflow:external +undefined:external +unreachable:external +unsigned-integer-overflow:external +vla-bound:external +vptr_check:external +vptr:external + +# Suppress all UBSan errors in Boost libraries +# This matches any files containing "boost" in its path or name +alignment:boost +bool:boost +bounds:boost +cfi:boost +enum:boost +float-cast-overflow:boost +float-divide-by-zero:boost +function:boost +implicit-integer-sign-change:boost +implicit-signed-integer-truncation:boost +implicit-unsigned-integer-truncation:boost +integer-divide-by-zero:boost +invalid-builtin-use:boost +invalid-objc-cast:boost +nonnull-attribute:boost +null:boost +nullability-arg:boost +nullability-assign:boost +nullability-return:boost +object-size:boost +pointer-overflow:boost +return:boost +returns-nonnull-attribute:boost +shift-base:boost +shift-exponent:boost +signed-integer-overflow:boost +undefined:boost +unreachable:boost +unsigned-integer-overflow:boost +vla-bound:boost +vptr_check:boost +vptr:boost + +# Google protobuf +undefined:protobuf + +# Suppress UBSan errors in rippled code by source file path +undefined:src/libxrpl/basics/base64.cpp +undefined:src/libxrpl/basics/Number.cpp +undefined:src/libxrpl/beast/utility/beast_Journal.cpp +undefined:src/libxrpl/crypto/RFC1751.cpp +undefined:src/libxrpl/ledger/ApplyView.cpp +undefined:src/libxrpl/ledger/View.cpp +undefined:src/libxrpl/protocol/Permissions.cpp +undefined:src/libxrpl/protocol/STAmount.cpp +undefined:src/libxrpl/protocol/STPathSet.cpp +undefined:src/libxrpl/protocol/tokens.cpp +undefined:src/libxrpl/shamap/SHAMap.cpp +undefined:src/test/app/Batch_test.cpp +undefined:src/test/app/Invariants_test.cpp +undefined:src/test/app/NFToken_test.cpp +undefined:src/test/app/Offer_test.cpp +undefined:src/test/app/Path_test.cpp +undefined:src/test/basics/XRPAmount_test.cpp +undefined:src/test/beast/LexicalCast_test.cpp +undefined:src/test/jtx/impl/acctdelete.cpp +undefined:src/test/ledger/SkipList_test.cpp +undefined:src/test/rpc/Subscribe_test.cpp +undefined:src/tests/libxrpl/basics/RangeSet.cpp +undefined:src/xrpld/app/main/BasicApp.cpp +undefined:src/xrpld/app/main/BasicApp.cpp +undefined:src/xrpld/app/misc/detail/AmendmentTable.cpp +undefined:src/xrpld/app/misc/NetworkOPs.cpp +undefined:src/libxrpl/json/json_value.cpp +undefined:src/xrpld/app/paths/detail/StrandFlow.h +undefined:src/xrpld/app/tx/detail/NFTokenMint.cpp +undefined:src/xrpld/app/tx/detail/SetOracle.cpp +undefined:src/xrpld/core/detail/JobQueue.cpp +undefined:src/xrpld/core/detail/Workers.cpp +undefined:src/xrpld/rpc/detail/Role.cpp +undefined:src/xrpld/rpc/handlers/GetAggregatePrice.cpp +undefined:xrpl/basics/base_uint.h +undefined:xrpl/basics/DecayingSample.h +undefined:xrpl/beast/test/yield_to.h +undefined:xrpl/beast/xor_shift_engine.h +undefined:xrpl/nodestore/detail/varint.h +undefined:xrpl/peerfinder/detail/Counts.h +undefined:xrpl/protocol/nft.h + +# basic_string.h:483:51: runtime error: unsigned integer overflow +unsigned-integer-overflow:basic_string.h +unsigned-integer-overflow:bits/chrono.h +unsigned-integer-overflow:bits/random.h +unsigned-integer-overflow:bits/random.tcc +unsigned-integer-overflow:bits/stl_algobase.h +unsigned-integer-overflow:bits/uniform_int_dist.h +unsigned-integer-overflow:string_view + +# runtime error: unsigned integer overflow: 0 - 1 cannot be represented in type 'std::size_t' (aka 'unsigned long') +unsigned-integer-overflow:src/libxrpl/basics/base64.cpp +unsigned-integer-overflow:src/libxrpl/basics/Number.cpp +unsigned-integer-overflow:src/libxrpl/crypto/RFC1751.cpp +unsigned-integer-overflow:rc/libxrpl/json/json_value.cpp +unsigned-integer-overflow:src/libxrpl/ledger/ApplyView.cpp +unsigned-integer-overflow:src/libxrpl/ledger/View.cpp +unsigned-integer-overflow:src/libxrpl/protocol/Permissions.cpp +unsigned-integer-overflow:src/libxrpl/protocol/STAmount.cpp +unsigned-integer-overflow:src/libxrpl/protocol/STPathSet.cpp +unsigned-integer-overflow:src/libxrpl/protocol/tokens.cpp +unsigned-integer-overflow:src/libxrpl/shamap/SHAMap.cpp +unsigned-integer-overflow:src/test/app/Batch_test.cpp +unsigned-integer-overflow:src/test/app/Invariants_test.cpp +unsigned-integer-overflow:src/test/app/NFToken_test.cpp +unsigned-integer-overflow:src/test/app/Offer_test.cpp +unsigned-integer-overflow:src/test/app/Path_test.cpp +unsigned-integer-overflow:src/test/basics/XRPAmount_test.cpp +unsigned-integer-overflow:src/test/beast/LexicalCast_test.cpp +unsigned-integer-overflow:src/test/jtx/impl/acctdelete.cpp +unsigned-integer-overflow:src/test/ledger/SkipList_test.cpp +unsigned-integer-overflow:src/test/rpc/Subscribe_test.cpp +unsigned-integer-overflow:src/tests/libxrpl/basics/RangeSet.cpp +unsigned-integer-overflow:src/xrpld/app/main/BasicApp.cpp +unsigned-integer-overflow:src/xrpld/app/misc/detail/AmendmentTable.cpp +unsigned-integer-overflow:src/xrpld/app/misc/NetworkOPs.cpp +unsigned-integer-overflow:src/xrpld/app/paths/detail/StrandFlow.h +unsigned-integer-overflow:src/xrpld/app/tx/detail/NFTokenMint.cpp +unsigned-integer-overflow:src/xrpld/app/tx/detail/SetOracle.cpp +unsigned-integer-overflow:src/xrpld/rpc/detail/Role.cpp +unsigned-integer-overflow:src/xrpld/rpc/handlers/GetAggregatePrice.cpp +unsigned-integer-overflow:xrpl/basics/base_uint.h +unsigned-integer-overflow:xrpl/basics/DecayingSample.h +unsigned-integer-overflow:xrpl/beast/test/yield_to.h +unsigned-integer-overflow:xrpl/beast/xor_shift_engine.h +unsigned-integer-overflow:xrpl/nodestore/detail/varint.h +unsigned-integer-overflow:xrpl/peerfinder/detail/Counts.h +unsigned-integer-overflow:xrpl/protocol/nft.h + +# Rippled intentional overflows and operations +# STAmount uses intentional negation of INT64_MIN and overflow in arithmetic +signed-integer-overflow:src/libxrpl/protocol/STAmount.cpp +unsigned-integer-overflow:src/libxrpl/protocol/STAmount.cpp + +# XRPAmount test intentional overflows +signed-integer-overflow:src/test/basics/XRPAmount_test.cpp + +# Peerfinder intentional overflow in counter arithmetic +unsigned-integer-overflow:src/xrpld/peerfinder/detail/Counts.h + +# Signed integer overflow suppressions +signed-integer-overflow:src/test/beast/LexicalCast_test.cpp + +# External library suppressions +unsigned-integer-overflow:nudb/detail/xxhash.hpp + +# Protobuf intentional overflows in hash functions +# Protobuf uses intentional unsigned overflow for hash computation (stringpiece.h:393) +unsigned-integer-overflow:google/protobuf/stubs/stringpiece.h + +# gRPC intentional overflows +# gRPC uses intentional overflow in timer calculations +unsigned-integer-overflow:grpc +unsigned-integer-overflow:timer_manager.cc + +# Standard library intentional overflows +# These are intentional overflows in random number generation and character conversion +unsigned-integer-overflow:__random/seed_seq.h +unsigned-integer-overflow:__charconv/traits.h + + +# Suppress errors in RocksDB +# RocksDB uses intentional unsigned integer overflows in hash functions and CRC calculations +unsigned-integer-overflow:rocks*/*/util/xxhash.h +unsigned-integer-overflow:rocks*/*/util/xxph3.h +unsigned-integer-overflow:rocks*/*/util/hash.cc +unsigned-integer-overflow:rocks*/*/util/crc32c.cc +unsigned-integer-overflow:rocks*/*/util/crc32c.h +unsigned-integer-overflow:rocks*/*/include/rocksdb/utilities/options_type.h +unsigned-integer-overflow:rocks*/*/table/format.h +unsigned-integer-overflow:rocks*/*/table/format.cc +unsigned-integer-overflow:rocks*/*/table/block_based/block_based_table_builder.cc +unsigned-integer-overflow:rocks*/*/table/block_based/reader_common.cc +unsigned-integer-overflow:rocks*/*/db/version_set.cc + +# RocksDB misaligned loads (intentional for performance on ARM64) +alignment:rocks*/*/util/crc32c_arm64.cc + +# nudb intentional overflows in hash functions +unsigned-integer-overflow:nudb/detail/xxhash.hpp +alignment:nudb/detail/xxhash.hpp + +# Snappy compression library intentional overflows +unsigned-integer-overflow:snappy.cc + +# Abseil intentional overflows +unsigned-integer-overflow:absl/strings/numbers.cc +unsigned-integer-overflow:absl/strings/internal/cord_rep_flat.h +unsigned-integer-overflow:absl/base/internal/low_level_alloc.cc +unsigned-integer-overflow:absl/hash/internal/hash.h +unsigned-integer-overflow:absl/container/internal/raw_hash_set.h + +# Standard library intentional overflows in chrono duration arithmetic +unsigned-integer-overflow:__chrono/duration.h + +# Suppress undefined errors in RocksDB and nudb +undefined:rocks.*/*/util/crc32c_arm64.cc +undefined:rocks.*/*/util/xxhash.h +undefined:nudb diff --git a/src/libxrpl/ledger/View.cpp b/src/libxrpl/ledger/View.cpp index 14246baf17..16ec23ffab 100644 --- a/src/libxrpl/ledger/View.cpp +++ b/src/libxrpl/ledger/View.cpp @@ -451,9 +451,8 @@ getTrustLineBalance( amount.clear(Issue{currency, issuer}); } - JLOG(j.trace()) << "getTrustLineBalance:" - << " account=" << to_string(account) - << " amount=" << amount.getFullText(); + JLOG(j.trace()) << "getTrustLineBalance:" << " account=" + << to_string(account) << " amount=" << amount.getFullText(); return view.balanceHook(account, issuer, amount); } @@ -700,8 +699,7 @@ xrpLiquid( STAmount const amount = (balance < reserve) ? STAmount{0} : balance - reserve; - JLOG(j.trace()) << "accountHolds:" - << " account=" << to_string(id) + JLOG(j.trace()) << "accountHolds:" << " account=" << to_string(id) << " amount=" << amount.getFullText() << " fullBalance=" << fullBalance.getFullText() << " balance=" << balance.getFullText() @@ -1107,7 +1105,7 @@ adjustOwnerCount( std::function describeOwnerDir(AccountID const& account) { - return [&account](std::shared_ptr const& sle) { + return [account](std::shared_ptr const& sle) { (*sle)[sfOwner] = account; }; } diff --git a/src/libxrpl/protocol/BuildInfo.cpp b/src/libxrpl/protocol/BuildInfo.cpp index dc56987f3a..b5e2354165 100644 --- a/src/libxrpl/protocol/BuildInfo.cpp +++ b/src/libxrpl/protocol/BuildInfo.cpp @@ -3,6 +3,8 @@ #include #include +#include + #include #include #include @@ -20,7 +22,7 @@ namespace BuildInfo { char const* const versionString = "3.2.0-b0" // clang-format on -#if defined(DEBUG) || defined(SANITIZER) +#if defined(DEBUG) || defined(SANITIZERS) "+" #ifdef GIT_COMMIT_HASH GIT_COMMIT_HASH @@ -28,13 +30,13 @@ char const* const versionString = "3.2.0-b0" #endif #ifdef DEBUG "DEBUG" -#ifdef SANITIZER +#ifdef SANITIZERS "." #endif #endif -#ifdef SANITIZER - BOOST_PP_STRINGIZE(SANITIZER) // cspell: disable-line +#ifdef SANITIZERS + BOOST_PP_STRINGIZE(SANITIZERS) // cspell: disable-line #endif #endif From 00d3cee6ccc5dd87c6005c55ace5bf3ce064a8c5 Mon Sep 17 00:00:00 2001 From: Ed Hennis Date: Fri, 16 Jan 2026 13:26:30 -0400 Subject: [PATCH 29/30] Improve ledger_entry lookups for fee, amendments, NUNL, and hashes (#5644) These "fixed location" objects can be found in multiple ways: 1. The lookup parameters use the same format as other ledger objects, but the only valid value is true or the valid index of the object: - Amendments: "amendments" : true - FeeSettings: "fee" : true - NegativeUNL: "nunl" : true - LedgerHashes: "hashes" : true (For the "short" list. See below.) 2. With RPC API >= 3, using special case values to "index", such as "index" : "amendments". Uses the same names as above. Note that for "hashes", this option will only return the recent ledger hashes / "short" skip list. 3. LedgerHashes has two types: "short", which stores recent ledger hashes, and "long", which stores the flag ledger hashes for a particular ledger range. - To find a "long" LedgerHashes object, request '"hashes" : '. must be a number that evaluates to an unsigned integer. - To find the "short" LedgerHashes object, request "hashes": true as with the other fixed objects. The following queries are all functionally equivalent: - "amendments" : true - "index" : "amendments" (API >=3 only) - "amendments" : "7DB0788C020F02780A673DC74757F23823FA3014C1866E72CC4CD8B226CD6EF4" - "index" : "7DB0788C020F02780A673DC74757F23823FA3014C1866E72CC4CD8B226CD6EF4" Finally, whether the object is found or not, if a valid index is computed, that index will be returned. This can be used to confirm the query was valid, or to save the index for future use. --- src/test/rpc/LedgerEntry_test.cpp | 469 ++++++++++++++++++++++++- src/xrpld/rpc/handlers/LedgerEntry.cpp | 237 ++++++++++--- 2 files changed, 649 insertions(+), 57 deletions(-) diff --git a/src/test/rpc/LedgerEntry_test.cpp b/src/test/rpc/LedgerEntry_test.cpp index 551e67dc5e..1b7079341c 100644 --- a/src/test/rpc/LedgerEntry_test.cpp +++ b/src/test/rpc/LedgerEntry_test.cpp @@ -5,6 +5,8 @@ #include #include +#include + #include #include #include @@ -30,6 +32,7 @@ enum class FieldType { CurrencyField, HashField, HashOrObjectField, + FixedHashField, IssueField, ObjectField, StringField, @@ -86,6 +89,7 @@ getTypeName(FieldType typeID) case FieldType::CurrencyField: return "Currency"; case FieldType::HashField: + case FieldType::FixedHashField: return "hex string"; case FieldType::HashOrObjectField: return "hex string or object"; @@ -202,6 +206,7 @@ class LedgerEntry_test : public beast::unit_test::suite static auto const& badBlobValues = remove({3, 7, 8, 16}); static auto const& badCurrencyValues = remove({14}); static auto const& badHashValues = remove({2, 3, 7, 8, 16}); + static auto const& badFixedHashValues = remove({1, 2, 3, 4, 7, 8, 16}); static auto const& badIndexValues = remove({12, 16, 18, 19}); static auto const& badUInt32Values = remove({2, 3}); static auto const& badUInt64Values = remove({2, 3}); @@ -222,6 +227,8 @@ class LedgerEntry_test : public beast::unit_test::suite return badHashValues; case FieldType::HashOrObjectField: return badIndexValues; + case FieldType::FixedHashField: + return badFixedHashValues; case FieldType::IssueField: return badIssueValues; case FieldType::UInt32Field: @@ -717,7 +724,12 @@ class LedgerEntry_test : public beast::unit_test::suite } // negative tests - runLedgerEntryTest(env, jss::amendments); + testMalformedField( + env, + Json::Value{}, + jss::amendments, + FieldType::FixedHashField, + "malformedRequest"); } void @@ -1538,7 +1550,12 @@ class LedgerEntry_test : public beast::unit_test::suite } // negative tests - runLedgerEntryTest(env, jss::fee); + testMalformedField( + env, + Json::Value{}, + jss::fee, + FieldType::FixedHashField, + "malformedRequest"); } void @@ -1561,7 +1578,12 @@ class LedgerEntry_test : public beast::unit_test::suite } // negative tests - runLedgerEntryTest(env, jss::hashes); + testMalformedField( + env, + Json::Value{}, + jss::hashes, + FieldType::FixedHashField, + "malformedRequest"); } void @@ -1686,7 +1708,12 @@ class LedgerEntry_test : public beast::unit_test::suite } // negative tests - runLedgerEntryTest(env, jss::nunl); + testMalformedField( + env, + Json::Value{}, + jss::nunl, + FieldType::FixedHashField, + "malformedRequest"); } void @@ -2343,6 +2370,438 @@ class LedgerEntry_test : public beast::unit_test::suite } } + /// Test the ledger entry types that don't take parameters + void + testFixed() + { + using namespace test::jtx; + + Account const alice{"alice"}; + Account const bob{"bob"}; + + Env env{*this, envconfig([](auto cfg) { + cfg->START_UP = Config::FRESH; + return cfg; + })}; + + env.close(); + + /** Verifies that the RPC result has the expected data + * + * @param good: Indicates that the request should have succeeded + * and returned a ledger object of `expectedType` type. + * @param jv: The RPC result Json value + * @param expectedType: The type that the ledger object should + * have if "good". + * @param expectedError: Optional. The expected error if not + * good. Defaults to "entryNotFound". + */ + auto checkResult = + [&](bool good, + Json::Value const& jv, + Json::StaticString const& expectedType, + std::optional const& expectedError = {}) { + if (good) + { + BEAST_EXPECTS( + jv.isObject() && jv.isMember(jss::result) && + !jv[jss::result].isMember(jss::error) && + jv[jss::result].isMember(jss::node) && + jv[jss::result][jss::node].isMember( + sfLedgerEntryType.jsonName) && + jv[jss::result][jss::node] + [sfLedgerEntryType.jsonName] == expectedType, + to_string(jv)); + } + else + { + BEAST_EXPECTS( + jv.isObject() && jv.isMember(jss::result) && + jv[jss::result].isMember(jss::error) && + !jv[jss::result].isMember(jss::node) && + jv[jss::result][jss::error] == + expectedError.value_or("entryNotFound"), + to_string(jv)); + } + }; + + /** Runs a series of tests for a given fixed-position ledger + * entry. + * + * @param field: The Json request field to use. + * @param expectedType: The type that the ledger object should + * have if "good". + * @param expectedKey: The keylet of the fixed object. + * @param good: Indicates whether the object is expected to + * exist. + */ + auto test = [&](Json::StaticString const& field, + Json::StaticString const& expectedType, + Keylet const& expectedKey, + bool good) { + testcase << expectedType.c_str() << (good ? "" : " not") + << " found"; + + auto const hexKey = strHex(expectedKey.key); + + { + // Test bad values + // "field":null + Json::Value params; + params[jss::ledger_index] = jss::validated; + params[field] = Json::nullValue; + auto const jv = + env.rpc("json", "ledger_entry", to_string(params)); + checkResult(false, jv, expectedType, "malformedRequest"); + BEAST_EXPECT(!jv[jss::result].isMember(jss::index)); + } + + { + Json::Value params; + // "field":"string" + params[jss::ledger_index] = jss::validated; + params[field] = "arbitrary string"; + auto const jv = + env.rpc("json", "ledger_entry", to_string(params)); + checkResult(false, jv, expectedType, "malformedRequest"); + BEAST_EXPECT(!jv[jss::result].isMember(jss::index)); + } + + { + Json::Value params; + // "field":false + params[jss::ledger_index] = jss::validated; + params[field] = false; + auto const jv = + env.rpc("json", "ledger_entry", to_string(params)); + checkResult(false, jv, expectedType, "invalidParams"); + BEAST_EXPECT(!jv[jss::result].isMember(jss::index)); + } + + { + Json::Value params; + + // "field":[incorrect index hash] + auto const badKey = strHex(expectedKey.key + uint256{1}); + params[jss::ledger_index] = jss::validated; + params[field] = badKey; + auto const jv = + env.rpc("json", "ledger_entry", to_string(params)); + checkResult(false, jv, expectedType, "entryNotFound"); + BEAST_EXPECTS( + jv[jss::result][jss::index] == badKey, to_string(jv)); + } + + { + Json::Value params; + // "index":"field" using API 2 + params[jss::ledger_index] = jss::validated; + params[jss::index] = field; + params[jss::api_version] = 2; + auto const jv = + env.rpc("json", "ledger_entry", to_string(params)); + checkResult(false, jv, expectedType, "malformedRequest"); + BEAST_EXPECT(!jv[jss::result].isMember(jss::index)); + } + + std::string const pdIdx = [&]() { + { + Json::Value params; + // Test good values + // Use the "field":true notation + params[jss::ledger_index] = jss::validated; + params[field] = true; + auto const jv = + env.rpc("json", "ledger_entry", to_string(params)); + // Index will always be returned for valid parameters. + std::string const pdIdx = + jv[jss::result][jss::index].asString(); + BEAST_EXPECTS(hexKey == pdIdx, to_string(jv)); + checkResult(good, jv, expectedType); + + return pdIdx; + } + }(); + + { + Json::Value params; + // "field":"[index hash]" + params[jss::ledger_index] = jss::validated; + params[field] = hexKey; + auto const jv = + env.rpc("json", "ledger_entry", to_string(params)); + checkResult(good, jv, expectedType); + BEAST_EXPECT(jv[jss::result][jss::index].asString() == hexKey); + } + + { + // Bad value + // Use the "index":"field" notation with API 2 + Json::Value params; + params[jss::ledger_index] = jss::validated; + params[jss::index] = field; + params[jss::api_version] = 2; + auto const jv = + env.rpc("json", "ledger_entry", to_string(params)); + checkResult(false, jv, expectedType, "malformedRequest"); + BEAST_EXPECT(!jv[jss::result].isMember(jss::index)); + } + + { + Json::Value params; + // Use the "index":"field" notation with API 3 + params[jss::ledger_index] = jss::validated; + params[jss::index] = field; + params[jss::api_version] = 3; + auto const jv = + env.rpc("json", "ledger_entry", to_string(params)); + // Index is correct either way + BEAST_EXPECT(jv[jss::result][jss::index].asString() == hexKey); + checkResult(good, jv, expectedType); + } + + { + Json::Value params; + // Use the "index":"[index hash]" notation + params[jss::ledger_index] = jss::validated; + params[jss::index] = pdIdx; + auto const jv = + env.rpc("json", "ledger_entry", to_string(params)); + // Index is correct either way + BEAST_EXPECT(jv[jss::result][jss::index].asString() == hexKey); + checkResult(good, jv, expectedType); + } + }; + + test(jss::amendments, jss::Amendments, keylet::amendments(), true); + test(jss::fee, jss::FeeSettings, keylet::fees(), true); + // There won't be an nunl + test(jss::nunl, jss::NegativeUNL, keylet::negativeUNL(), false); + // Can only get the short skip list this way + test(jss::hashes, jss::LedgerHashes, keylet::skip(), true); + } + + void + testHashes() + { + using namespace test::jtx; + + Account const alice{"alice"}; + Account const bob{"bob"}; + + Env env{*this, envconfig([](auto cfg) { + cfg->START_UP = Config::FRESH; + return cfg; + })}; + + env.close(); + + /** Verifies that the RPC result has the expected data + * + * @param good: Indicates that the request should have succeeded + * and returned a ledger object of `expectedType` type. + * @param jv: The RPC result Json value + * @param expectedCount: The number of Hashes expected in the + * object if "good". + * @param expectedError: Optional. The expected error if not + * good. Defaults to "entryNotFound". + */ + auto checkResult = + [&](bool good, + Json::Value const& jv, + int expectedCount, + std::optional const& expectedError = {}) { + if (good) + { + BEAST_EXPECTS( + jv.isObject() && jv.isMember(jss::result) && + !jv[jss::result].isMember(jss::error) && + jv[jss::result].isMember(jss::node) && + jv[jss::result][jss::node].isMember( + sfLedgerEntryType.jsonName) && + jv[jss::result][jss::node] + [sfLedgerEntryType.jsonName] == jss::LedgerHashes, + to_string(jv)); + BEAST_EXPECTS( + jv[jss::result].isMember(jss::node) && + jv[jss::result][jss::node].isMember("Hashes") && + jv[jss::result][jss::node]["Hashes"].size() == + expectedCount, + to_string(jv[jss::result][jss::node]["Hashes"].size())); + } + else + { + BEAST_EXPECTS( + jv.isObject() && jv.isMember(jss::result) && + jv[jss::result].isMember(jss::error) && + !jv[jss::result].isMember(jss::node) && + jv[jss::result][jss::error] == + expectedError.value_or("entryNotFound"), + to_string(jv)); + } + }; + + /** Runs a series of tests for a given ledger index. + * + * @param ledger: The ledger index value of the "hashes" request + * parameter. May not necessarily be a number. + * @param expectedKey: The expected keylet of the object. + * @param good: Indicates whether the object is expected to + * exist. + * @param expectedCount: The number of Hashes expected in the + * object if "good". + */ + auto test = [&](Json::Value ledger, + Keylet const& expectedKey, + bool good, + int expectedCount = 0) { + testcase << "LedgerHashes: seq: " << env.current()->header().seq + << " \"hashes\":" << to_string(ledger) + << (good ? "" : " not") << " found"; + + auto const hexKey = strHex(expectedKey.key); + + { + // Test bad values + // "hashes":null + Json::Value params; + params[jss::ledger_index] = jss::validated; + params[jss::hashes] = Json::nullValue; + auto jv = env.rpc("json", "ledger_entry", to_string(params)); + checkResult(false, jv, 0, "malformedRequest"); + BEAST_EXPECT(!jv[jss::result].isMember(jss::index)); + } + + { + Json::Value params; + // "hashes":"non-uint string" + params[jss::ledger_index] = jss::validated; + params[jss::hashes] = "arbitrary string"; + auto const jv = + env.rpc("json", "ledger_entry", to_string(params)); + checkResult(false, jv, 0, "malformedRequest"); + BEAST_EXPECT(!jv[jss::result].isMember(jss::index)); + } + + { + Json::Value params; + // "hashes":"uint string" is invalid, too + params[jss::ledger_index] = jss::validated; + params[jss::hashes] = "10"; + auto const jv = + env.rpc("json", "ledger_entry", to_string(params)); + checkResult(false, jv, 0, "malformedRequest"); + BEAST_EXPECT(!jv[jss::result].isMember(jss::index)); + } + + { + Json::Value params; + // "hashes":false + params[jss::ledger_index] = jss::validated; + params[jss::hashes] = false; + auto const jv = + env.rpc("json", "ledger_entry", to_string(params)); + checkResult(false, jv, 0, "invalidParams"); + BEAST_EXPECT(!jv[jss::result].isMember(jss::index)); + } + + { + Json::Value params; + // "hashes":-1 + params[jss::ledger_index] = jss::validated; + params[jss::hashes] = -1; + auto const jv = + env.rpc("json", "ledger_entry", to_string(params)); + checkResult(false, jv, 0, "internal"); + BEAST_EXPECT(!jv[jss::result].isMember(jss::index)); + } + + // "hashes":[incorrect index hash] + { + Json::Value params; + auto const badKey = strHex(expectedKey.key + uint256{1}); + params[jss::ledger_index] = jss::validated; + params[jss::hashes] = badKey; + auto const jv = + env.rpc("json", "ledger_entry", to_string(params)); + checkResult(false, jv, 0, "entryNotFound"); + BEAST_EXPECT(jv[jss::result][jss::index] == badKey); + } + + { + Json::Value params; + // Test good values + // Use the "hashes":ledger notation + params[jss::ledger_index] = jss::validated; + params[jss::hashes] = ledger; + auto const jv = + env.rpc("json", "ledger_entry", to_string(params)); + checkResult(good, jv, expectedCount); + // Index will always be returned for valid parameters. + std::string const pdIdx = + jv[jss::result][jss::index].asString(); + BEAST_EXPECTS(hexKey == pdIdx, strHex(pdIdx)); + } + + { + Json::Value params; + // "hashes":"[index hash]" + params[jss::ledger_index] = jss::validated; + params[jss::hashes] = hexKey; + auto const jv = + env.rpc("json", "ledger_entry", to_string(params)); + checkResult(good, jv, expectedCount); + // Index is correct either way + BEAST_EXPECTS( + hexKey == jv[jss::result][jss::index].asString(), + strHex(jv[jss::result][jss::index].asString())); + } + + { + Json::Value params; + // Use the "index":"[index hash]" notation + params[jss::ledger_index] = jss::validated; + params[jss::index] = hexKey; + auto const jv = + env.rpc("json", "ledger_entry", to_string(params)); + checkResult(good, jv, expectedCount); + // Index is correct either way + BEAST_EXPECTS( + hexKey == jv[jss::result][jss::index].asString(), + strHex(jv[jss::result][jss::index].asString())); + } + }; + + // short skip list + test(true, keylet::skip(), true, 2); + // long skip list at index 0 + test(1, keylet::skip(1), false); + // long skip list at index 1 + test(1 << 17, keylet::skip(1 << 17), false); + + // Close more ledgers, but stop short of the flag ledger + for (auto i = env.current()->seq(); i <= 250; ++i) + env.close(); + + // short skip list + test(true, keylet::skip(), true, 249); + // long skip list at index 0 + test(1, keylet::skip(1), false); + // long skip list at index 1 + test(1 << 17, keylet::skip(1 << 17), false); + + // Close a flag ledger so the first "long" skip list is created + for (auto i = env.current()->seq(); i <= 260; ++i) + env.close(); + + // short skip list + test(true, keylet::skip(), true, 256); + // long skip list at index 0 + test(1, keylet::skip(1), true, 1); + // long skip list at index 1 + test(1 << 17, keylet::skip(1 << 17), false); + } + void testCLI() { @@ -2400,6 +2859,8 @@ public: testOracleLedgerEntry(); testMPT(); testPermissionedDomain(); + testFixed(); + testHashes(); testCLI(); } }; diff --git a/src/xrpld/rpc/handlers/LedgerEntry.cpp b/src/xrpld/rpc/handlers/LedgerEntry.cpp index d9ae357b1a..17d8e5e7bc 100644 --- a/src/xrpld/rpc/handlers/LedgerEntry.cpp +++ b/src/xrpld/rpc/handlers/LedgerEntry.cpp @@ -18,6 +18,32 @@ namespace xrpl { +using FunctionType = std::function( + Json::Value const&, + Json::StaticString const, + unsigned const apiVersion)>; + +static Expected +parseFixed( + Keylet const& keylet, + Json::Value const& params, + Json::StaticString const& fieldName, + unsigned const apiVersion); + +// Helper function to return FunctionType for objects that have a fixed +// location. That is, they don't take parameters to compute the index. +// e.g. amendments, fees, negative UNL, etc. +static FunctionType +fixed(Keylet const& keylet) +{ + return [keylet]( + Json::Value const& params, + Json::StaticString const fieldName, + unsigned const apiVersion) -> Expected { + return parseFixed(keylet, params, fieldName, apiVersion); + }; +} + static Expected parseObjectID( Json::Value const& params, @@ -33,13 +59,33 @@ parseObjectID( } static Expected -parseIndex(Json::Value const& params, Json::StaticString const fieldName) +parseIndex( + Json::Value const& params, + Json::StaticString const fieldName, + unsigned const apiVersion) { + if (apiVersion > 2u && params.isString()) + { + std::string const index = params.asString(); + if (index == jss::amendments.c_str()) + return keylet::amendments().key; + if (index == jss::fee.c_str()) + return keylet::fees().key; + if (index == jss::nunl) + return keylet::negativeUNL().key; + if (index == jss::hashes) + // Note this only finds the "short" skip list. Use "hashes":index to + // get the long list. + return keylet::skip().key; + } return parseObjectID(params, fieldName, "hex string"); } static Expected -parseAccountRoot(Json::Value const& params, Json::StaticString const fieldName) +parseAccountRoot( + Json::Value const& params, + Json::StaticString const fieldName, + [[maybe_unused]] unsigned const apiVersion) { if (auto const account = LedgerEntryHelpers::parse(params)) { @@ -50,14 +96,13 @@ parseAccountRoot(Json::Value const& params, Json::StaticString const fieldName) "malformedAddress", fieldName, "AccountID"); } -static Expected -parseAmendments(Json::Value const& params, Json::StaticString const fieldName) -{ - return parseObjectID(params, fieldName, "hex string"); -} +auto const parseAmendments = fixed(keylet::amendments()); static Expected -parseAMM(Json::Value const& params, Json::StaticString const fieldName) +parseAMM( + Json::Value const& params, + Json::StaticString const fieldName, + [[maybe_unused]] unsigned const apiVersion) { if (!params.isObject()) { @@ -85,7 +130,10 @@ parseAMM(Json::Value const& params, Json::StaticString const fieldName) } static Expected -parseBridge(Json::Value const& params, Json::StaticString const fieldName) +parseBridge( + Json::Value const& params, + Json::StaticString const fieldName, + [[maybe_unused]] unsigned const apiVersion) { if (!params.isMember(jss::bridge)) { @@ -116,13 +164,19 @@ parseBridge(Json::Value const& params, Json::StaticString const fieldName) } static Expected -parseCheck(Json::Value const& params, Json::StaticString const fieldName) +parseCheck( + Json::Value const& params, + Json::StaticString const fieldName, + [[maybe_unused]] unsigned const apiVersion) { return parseObjectID(params, fieldName, "hex string"); } static Expected -parseCredential(Json::Value const& cred, Json::StaticString const fieldName) +parseCredential( + Json::Value const& cred, + Json::StaticString const fieldName, + [[maybe_unused]] unsigned const apiVersion) { if (!cred.isObject()) { @@ -153,7 +207,10 @@ parseCredential(Json::Value const& cred, Json::StaticString const fieldName) } static Expected -parseDelegate(Json::Value const& params, Json::StaticString const fieldName) +parseDelegate( + Json::Value const& params, + Json::StaticString const fieldName, + [[maybe_unused]] unsigned const apiVersion) { if (!params.isObject()) { @@ -244,7 +301,10 @@ parseAuthorizeCredentials(Json::Value const& jv) } static Expected -parseDepositPreauth(Json::Value const& dp, Json::StaticString const fieldName) +parseDepositPreauth( + Json::Value const& dp, + Json::StaticString const fieldName, + [[maybe_unused]] unsigned const apiVersion) { if (!dp.isObject()) { @@ -297,7 +357,10 @@ parseDepositPreauth(Json::Value const& dp, Json::StaticString const fieldName) } static Expected -parseDID(Json::Value const& params, Json::StaticString const fieldName) +parseDID( + Json::Value const& params, + Json::StaticString const fieldName, + [[maybe_unused]] unsigned const apiVersion) { auto const account = LedgerEntryHelpers::parse(params); if (!account) @@ -312,7 +375,8 @@ parseDID(Json::Value const& params, Json::StaticString const fieldName) static Expected parseDirectoryNode( Json::Value const& params, - Json::StaticString const fieldName) + Json::StaticString const fieldName, + [[maybe_unused]] unsigned const apiVersion) { if (!params.isObject()) { @@ -365,7 +429,10 @@ parseDirectoryNode( } static Expected -parseEscrow(Json::Value const& params, Json::StaticString const fieldName) +parseEscrow( + Json::Value const& params, + Json::StaticString const fieldName, + [[maybe_unused]] unsigned const apiVersion) { if (!params.isObject()) { @@ -384,20 +451,53 @@ parseEscrow(Json::Value const& params, Json::StaticString const fieldName) return keylet::escrow(*id, *seq).key; } +auto const parseFeeSettings = fixed(keylet::fees()); + static Expected -parseFeeSettings(Json::Value const& params, Json::StaticString const fieldName) +parseFixed( + Keylet const& keylet, + Json::Value const& params, + Json::StaticString const& fieldName, + [[maybe_unused]] unsigned const apiVersion) { - return parseObjectID(params, fieldName, "hex string"); + if (!params.isBool()) + { + return parseObjectID(params, fieldName, "hex string"); + } + if (!params.asBool()) + { + return LedgerEntryHelpers::invalidFieldError( + "invalidParams", fieldName, "true"); + } + + return keylet.key; } static Expected -parseLedgerHashes(Json::Value const& params, Json::StaticString const fieldName) +parseLedgerHashes( + Json::Value const& params, + Json::StaticString const fieldName, + unsigned const apiVersion) { - return parseObjectID(params, fieldName, "hex string"); + if (params.isUInt() || params.isInt()) + { + // If the index doesn't parse as a UInt, throw + auto const index = params.asUInt(); + + // Return the "long" skip list for the given ledger index. + auto const keylet = keylet::skip(index); + return keylet.key; + } + // Return the key in `params` or the "short" skip list, which contains + // hashes since the last flag ledger. + return parseFixed(keylet::skip(), params, fieldName, apiVersion); } static Expected -parseLoanBroker(Json::Value const& params, Json::StaticString const fieldName) +parseLoanBroker( + Json::Value const& params, + Json::StaticString const fieldName, + [[maybe_unused]] unsigned const apiVersion) { if (!params.isObject()) { @@ -417,7 +517,10 @@ parseLoanBroker(Json::Value const& params, Json::StaticString const fieldName) } static Expected -parseLoan(Json::Value const& params, Json::StaticString const fieldName) +parseLoan( + Json::Value const& params, + Json::StaticString const fieldName, + [[maybe_unused]] unsigned const apiVersion) { if (!params.isObject()) { @@ -437,7 +540,10 @@ parseLoan(Json::Value const& params, Json::StaticString const fieldName) } static Expected -parseMPToken(Json::Value const& params, Json::StaticString const fieldName) +parseMPToken( + Json::Value const& params, + Json::StaticString const fieldName, + [[maybe_unused]] unsigned const apiVersion) { if (!params.isObject()) { @@ -460,7 +566,8 @@ parseMPToken(Json::Value const& params, Json::StaticString const fieldName) static Expected parseMPTokenIssuance( Json::Value const& params, - Json::StaticString const fieldName) + Json::StaticString const fieldName, + [[maybe_unused]] unsigned const apiVersion) { auto const mptIssuanceID = LedgerEntryHelpers::parse(params); if (!mptIssuanceID) @@ -471,25 +578,30 @@ parseMPTokenIssuance( } static Expected -parseNFTokenOffer(Json::Value const& params, Json::StaticString const fieldName) +parseNFTokenOffer( + Json::Value const& params, + Json::StaticString const fieldName, + [[maybe_unused]] unsigned const apiVersion) { return parseObjectID(params, fieldName, "hex string"); } static Expected -parseNFTokenPage(Json::Value const& params, Json::StaticString const fieldName) +parseNFTokenPage( + Json::Value const& params, + Json::StaticString const fieldName, + [[maybe_unused]] unsigned const apiVersion) { return parseObjectID(params, fieldName, "hex string"); } -static Expected -parseNegativeUNL(Json::Value const& params, Json::StaticString const fieldName) -{ - return parseObjectID(params, fieldName, "hex string"); -} +auto const parseNegativeUNL = fixed(keylet::negativeUNL()); static Expected -parseOffer(Json::Value const& params, Json::StaticString const fieldName) +parseOffer( + Json::Value const& params, + Json::StaticString const fieldName, + [[maybe_unused]] unsigned const apiVersion) { if (!params.isObject()) { @@ -510,7 +622,10 @@ parseOffer(Json::Value const& params, Json::StaticString const fieldName) } static Expected -parseOracle(Json::Value const& params, Json::StaticString const fieldName) +parseOracle( + Json::Value const& params, + Json::StaticString const fieldName, + [[maybe_unused]] unsigned const apiVersion) { if (!params.isObject()) { @@ -531,7 +646,10 @@ parseOracle(Json::Value const& params, Json::StaticString const fieldName) } static Expected -parsePayChannel(Json::Value const& params, Json::StaticString const fieldName) +parsePayChannel( + Json::Value const& params, + Json::StaticString const fieldName, + [[maybe_unused]] unsigned const apiVersion) { return parseObjectID(params, fieldName, "hex string"); } @@ -539,7 +657,8 @@ parsePayChannel(Json::Value const& params, Json::StaticString const fieldName) static Expected parsePermissionedDomain( Json::Value const& pd, - Json::StaticString const fieldName) + Json::StaticString const fieldName, + [[maybe_unused]] unsigned const apiVersion) { if (pd.isString()) { @@ -568,7 +687,8 @@ parsePermissionedDomain( static Expected parseRippleState( Json::Value const& jvRippleState, - Json::StaticString const fieldName) + Json::StaticString const fieldName, + [[maybe_unused]] unsigned const apiVersion) { Currency uCurrency; @@ -618,13 +738,19 @@ parseRippleState( } static Expected -parseSignerList(Json::Value const& params, Json::StaticString const fieldName) +parseSignerList( + Json::Value const& params, + Json::StaticString const fieldName, + [[maybe_unused]] unsigned const apiVersion) { return parseObjectID(params, fieldName, "hex string"); } static Expected -parseTicket(Json::Value const& params, Json::StaticString const fieldName) +parseTicket( + Json::Value const& params, + Json::StaticString const fieldName, + [[maybe_unused]] unsigned const apiVersion) { if (!params.isObject()) { @@ -645,7 +771,10 @@ parseTicket(Json::Value const& params, Json::StaticString const fieldName) } static Expected -parseVault(Json::Value const& params, Json::StaticString const fieldName) +parseVault( + Json::Value const& params, + Json::StaticString const fieldName, + [[maybe_unused]] unsigned const apiVersion) { if (!params.isObject()) { @@ -668,7 +797,8 @@ parseVault(Json::Value const& params, Json::StaticString const fieldName) static Expected parseXChainOwnedClaimID( Json::Value const& claim_id, - Json::StaticString const fieldName) + Json::StaticString const fieldName, + [[maybe_unused]] unsigned const apiVersion) { if (!claim_id.isObject()) { @@ -693,7 +823,8 @@ parseXChainOwnedClaimID( static Expected parseXChainOwnedCreateAccountClaimID( Json::Value const& claim_id, - Json::StaticString const fieldName) + Json::StaticString const fieldName, + [[maybe_unused]] unsigned const apiVersion) { if (!claim_id.isObject()) { @@ -717,10 +848,6 @@ parseXChainOwnedCreateAccountClaimID( return keylet.key; } -using FunctionType = Expected (*)( - Json::Value const&, - Json::StaticString const); - struct LedgerEntry { Json::StaticString fieldName; @@ -753,7 +880,7 @@ doLedgerEntry(RPC::JsonContext& context) {jss::ripple_state, parseRippleState, ltRIPPLE_STATE}, }); - auto hasMoreThanOneMember = [&]() { + auto const hasMoreThanOneMember = [&]() { int count = 0; for (auto const& ledgerEntry : ledgerEntryParsers) @@ -797,8 +924,8 @@ doLedgerEntry(RPC::JsonContext& context) Json::Value const& params = ledgerEntry.fieldName == jss::bridge ? context.params : context.params[ledgerEntry.fieldName]; - auto const result = - ledgerEntry.parseFunction(params, ledgerEntry.fieldName); + auto const result = ledgerEntry.parseFunction( + params, ledgerEntry.fieldName, context.apiVersion); if (!result) return result.error(); @@ -829,9 +956,13 @@ doLedgerEntry(RPC::JsonContext& context) throw; } + // Return the computed index regardless of whether the node exists. + jvResult[jss::index] = to_string(uNodeIndex); + if (uNodeIndex.isZero()) { - return RPC::make_error(rpcENTRY_NOT_FOUND); + RPC::inject_error(rpcENTRY_NOT_FOUND, jvResult); + return jvResult; } auto const sleNode = lpLedger->read(keylet::unchecked(uNodeIndex)); @@ -843,12 +974,14 @@ doLedgerEntry(RPC::JsonContext& context) if (!sleNode) { // Not found. - return RPC::make_error(rpcENTRY_NOT_FOUND); + RPC::inject_error(rpcENTRY_NOT_FOUND, jvResult); + return jvResult; } if ((expectedType != ltANY) && (expectedType != sleNode->getType())) { - return RPC::make_error(rpcUNEXPECTED_LEDGER_TYPE); + RPC::inject_error(rpcUNEXPECTED_LEDGER_TYPE, jvResult); + return jvResult; } if (bNodeBinary) @@ -858,12 +991,10 @@ doLedgerEntry(RPC::JsonContext& context) sleNode->add(s); jvResult[jss::node_binary] = strHex(s.peekData()); - jvResult[jss::index] = to_string(uNodeIndex); } else { jvResult[jss::node] = sleNode->getJson(JsonOptions::none); - jvResult[jss::index] = to_string(uNodeIndex); } return jvResult; From 12c0d67ff62f141f4c1d25ece56800f22cb3861e Mon Sep 17 00:00:00 2001 From: Bart Date: Fri, 16 Jan 2026 15:01:53 -0500 Subject: [PATCH 30/30] ci: remove 'master' branch as a trigger (#6234) This change removes the `master` branch as a trigger for the CI pipelines, and updates comments accordingly. It also fixes the pre-commit workflow, so it will run on all release branches. --- .github/scripts/strategy-matrix/generate.py | 4 ++-- .github/workflows/on-pr.yml | 2 +- .github/workflows/on-trigger.yml | 20 +++++++++----------- .github/workflows/pre-commit.yml | 4 +++- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/scripts/strategy-matrix/generate.py b/.github/scripts/strategy-matrix/generate.py index bf5bf5d3ba..0e44b1be54 100755 --- a/.github/scripts/strategy-matrix/generate.py +++ b/.github/scripts/strategy-matrix/generate.py @@ -20,8 +20,8 @@ class Config: Generate a strategy matrix for GitHub Actions CI. On each PR commit we will build a selection of Debian, RHEL, Ubuntu, MacOS, and -Windows configurations, while upon merge into the develop, release, or master -branches, we will build all configurations, and test most of them. +Windows configurations, while upon merge into the develop or release branches, +we will build all configurations, and test most of them. We will further set additional CMake arguments as follows: - All builds will have the `tests`, `werr`, and `xrpld` options. diff --git a/.github/workflows/on-pr.yml b/.github/workflows/on-pr.yml index 3aa48ac070..dad211f94f 100644 --- a/.github/workflows/on-pr.yml +++ b/.github/workflows/on-pr.yml @@ -125,7 +125,7 @@ jobs: needs: - should-run - build-test - if: ${{ needs.should-run.outputs.go == 'true' && (startsWith(github.base_ref, 'release') || github.base_ref == 'master') }} + if: ${{ needs.should-run.outputs.go == 'true' && startsWith(github.ref, 'refs/heads/release') }} uses: ./.github/workflows/reusable-notify-clio.yml secrets: clio_notify_token: ${{ secrets.CLIO_NOTIFY_TOKEN }} diff --git a/.github/workflows/on-trigger.yml b/.github/workflows/on-trigger.yml index 2c63c2baa5..ef7bf41fa2 100644 --- a/.github/workflows/on-trigger.yml +++ b/.github/workflows/on-trigger.yml @@ -1,9 +1,8 @@ -# This workflow runs all workflows to build the dependencies required for the -# project on various Linux flavors, as well as on MacOS and Windows, on a -# scheduled basis, on merge into the 'develop', 'release', or 'master' branches, -# or manually. The missing commits check is only run when the code is merged -# into the 'develop' or 'release' branches, and the documentation is built when -# the code is merged into the 'develop' branch. +# This workflow runs all workflows to build and test the code on various Linux +# flavors, as well as on MacOS and Windows, on a scheduled basis, on merge into +# the 'develop' or 'release*' branches, or when requested manually. Upon +# successful completion, it also uploads the built libxrpl package to the Conan +# remote. name: Trigger on: @@ -11,7 +10,6 @@ on: branches: - "develop" - "release*" - - "master" paths: # These paths are unique to `on-trigger.yml`. - ".github/workflows/on-trigger.yml" @@ -70,10 +68,10 @@ jobs: with: # Enable ccache only for events targeting the XRPLF repository, since # other accounts will not have access to our remote cache storage. - # However, we do not enable ccache for events targeting the master or a - # release branch, to protect against the rare case that the output - # produced by ccache is not identical to a regular compilation. - ccache_enabled: ${{ github.repository_owner == 'XRPLF' && !(github.base_ref == 'master' || startsWith(github.base_ref, 'release')) }} + # However, we do not enable ccache for events targeting a release branch, + # to protect against the rare case that the output produced by ccache is + # not identical to a regular compilation. + ccache_enabled: ${{ github.repository_owner == 'XRPLF' && !startsWith(github.ref, 'refs/heads/release') }} os: ${{ matrix.os }} strategy_matrix: ${{ github.event_name == 'schedule' && 'all' || 'minimal' }} secrets: diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 00754e5eae..6b8fd9955e 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -3,7 +3,9 @@ name: Run pre-commit hooks on: pull_request: push: - branches: [develop, release, master] + branches: + - "develop" + - "release*" workflow_dispatch: jobs: