mirror of
https://github.com/XRPLF/rippled.git
synced 2026-10-22 19:47:17 +00:00
Compare commits
38
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6cb72aa5e0 | ||
|
|
70764e9362 | ||
|
|
1f6679938e | ||
|
|
d343b542f1 | ||
|
|
7acd719bf7 | ||
|
|
b4564d5301 | ||
|
|
60195e6d37 | ||
|
|
3d526d456e | ||
|
|
f05c9f7913 | ||
|
|
c2a4bc3aa1 | ||
|
|
718185e3b2 | ||
|
|
1d7783bb86 | ||
|
|
9fd2c552f5 | ||
|
|
70b8fd301b | ||
|
|
e6564f553d | ||
|
|
ed96e60ce3 | ||
|
|
2ebd745a1b | ||
|
|
cfcbe45b60 | ||
|
|
9cbf78ba99 | ||
|
|
b8d8738f81 | ||
|
|
63c97e719f | ||
|
|
3ac26f23c7 | ||
|
|
ff9410bc56 | ||
|
|
1798262d7a | ||
|
|
cd633b9ac9 | ||
|
|
5c5f7d315a | ||
|
|
0f7493ce50 | ||
|
|
40f61f828a | ||
|
|
108277f4b7 | ||
|
|
b3abd3243c | ||
|
|
a99ee034b9 | ||
|
|
2b069f863a | ||
|
|
d0dbd1527a | ||
|
|
fa5baf2cc4 | ||
|
|
775759eddf | ||
|
|
17777928c7 | ||
|
|
26da18be9f | ||
|
|
b1a9539a6c |
No files matched your search
@@ -108,3 +108,75 @@ endfunction()
|
||||
|
||||
function(patch_nix_binary target)
|
||||
endfunction()
|
||||
|
||||
function(rpcspec_generate_instantiations)
|
||||
set(options)
|
||||
set(oneValueArgs OUT_VAR VALUE_TYPE VIEW_HEADER INCLUDE_DIR)
|
||||
set(multiValueArgs HANDLERS)
|
||||
cmake_parse_arguments(
|
||||
THIS_FUNCTION_PREFIX
|
||||
"${options}"
|
||||
"${oneValueArgs}"
|
||||
"${multiValueArgs}"
|
||||
${ARGN}
|
||||
)
|
||||
endfunction()
|
||||
|
||||
function(corrosion_import_crate)
|
||||
set(options
|
||||
ALL_FEATURES
|
||||
NO_DEFAULT_FEATURES
|
||||
NO_STD
|
||||
NO_LINKER_OVERRIDE
|
||||
NO_USES_TERMINAL
|
||||
LOCKED
|
||||
FROZEN
|
||||
)
|
||||
set(oneValueArgs MANIFEST_PATH PROFILE IMPORTED_CRATES)
|
||||
set(multiValueArgs
|
||||
CRATE_TYPES
|
||||
CRATES
|
||||
FEATURES
|
||||
FLAGS
|
||||
OVERRIDE_CRATE_TYPE
|
||||
)
|
||||
cmake_parse_arguments(
|
||||
THIS_FUNCTION_PREFIX
|
||||
"${options}"
|
||||
"${oneValueArgs}"
|
||||
"${multiValueArgs}"
|
||||
${ARGN}
|
||||
)
|
||||
endfunction()
|
||||
|
||||
function(corrosion_set_env_vars target_name env_var)
|
||||
endfunction()
|
||||
|
||||
function(corrosion_add_cxxbridge cxx_target)
|
||||
set(options)
|
||||
set(oneValueArgs CRATE)
|
||||
set(multiValueArgs FILES)
|
||||
cmake_parse_arguments(
|
||||
THIS_FUNCTION_PREFIX
|
||||
"${options}"
|
||||
"${oneValueArgs}"
|
||||
"${multiValueArgs}"
|
||||
${ARGN}
|
||||
)
|
||||
endfunction()
|
||||
|
||||
function(_unlink_libgcc_s crate)
|
||||
endfunction()
|
||||
|
||||
function(add_xrpl_crate name)
|
||||
set(options)
|
||||
set(oneValueArgs CRATE)
|
||||
set(multiValueArgs FILES)
|
||||
cmake_parse_arguments(
|
||||
THIS_FUNCTION_PREFIX
|
||||
"${options}"
|
||||
"${oneValueArgs}"
|
||||
"${multiValueArgs}"
|
||||
${ARGN}
|
||||
)
|
||||
endfunction()
|
||||
@@ -44,4 +44,12 @@ runs:
|
||||
--conf:all tools.build:jobs=${BUILD_NPROC} \
|
||||
--conf:all tools.build:verbosity="${LOG_VERBOSITY}" \
|
||||
--conf:all tools.compilation:verbosity="${LOG_VERBOSITY}" \
|
||||
.
|
||||
--format=json \
|
||||
. >"${RUNNER_TEMP}/conan-graph.json"
|
||||
|
||||
# Tools that run during the build may only load glibc from the Nix store,
|
||||
# as their package ID survives a GCC runtime update.
|
||||
- name: Check build-context packages for Nix store dependencies (Linux)
|
||||
if: ${{ runner.os == 'Linux' }}
|
||||
shell: bash
|
||||
run: ./bin/check-build-context-runtime.sh "${RUNNER_TEMP}/conan-graph.json"
|
||||
@@ -75,6 +75,7 @@ test.app > xrpl.json
|
||||
test.app > xrpl.ledger
|
||||
test.app > xrpl.nodestore
|
||||
test.app > xrpl.protocol
|
||||
test.app > xrpl.rdb
|
||||
test.app > xrpl.resource
|
||||
test.app > xrpl.server
|
||||
test.app > xrpl.shamap
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"image_tag": "sha-060957e",
|
||||
"image_tag": "sha-3d526d4",
|
||||
"configs": {
|
||||
"ubuntu": [
|
||||
{
|
||||
@@ -74,7 +74,7 @@
|
||||
"extra_cmake_args": "-Dvalidator_keys=ON",
|
||||
"package": {
|
||||
"type": "deb",
|
||||
"image": "ghcr.io/xrplf/xrpld/packaging-debian:sha-3a2d19f"
|
||||
"image": "ghcr.io/xrplf/xrpld/packaging-debian:sha-e6055dd"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -86,7 +86,7 @@
|
||||
"extra_cmake_args": "-Dvalidator_keys=ON -Dassert=ON",
|
||||
"package": {
|
||||
"type": "deb",
|
||||
"image": "ghcr.io/xrplf/xrpld/packaging-debian:sha-3a2d19f",
|
||||
"image": "ghcr.io/xrplf/xrpld/packaging-debian:sha-e6055dd",
|
||||
"variant": "assert"
|
||||
}
|
||||
}
|
||||
@@ -101,7 +101,7 @@
|
||||
"extra_cmake_args": "-Dvalidator_keys=ON",
|
||||
"package": {
|
||||
"type": "rpm",
|
||||
"image": "ghcr.io/xrplf/xrpld/packaging-rhel:sha-3a2d19f"
|
||||
"image": "ghcr.io/xrplf/xrpld/packaging-rhel:sha-e6055dd"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -13,7 +13,6 @@ on:
|
||||
- "!nix/devshell.nix"
|
||||
- "!nix/check-tools/**"
|
||||
- "bin/default-loader-path.sh"
|
||||
- "bin/install-sanitizer-libs.sh"
|
||||
pull_request:
|
||||
paths:
|
||||
- ".github/workflows/build-nix-images.yml"
|
||||
|
||||
@@ -5,7 +5,6 @@ on:
|
||||
branches:
|
||||
- develop
|
||||
paths:
|
||||
- ".github/workflows/build-packaging-images.yml"
|
||||
- "bin/install-packaging-tools.sh"
|
||||
- "package/images/packaging/**"
|
||||
pull_request:
|
||||
|
||||
@@ -5,7 +5,6 @@ on:
|
||||
branches:
|
||||
- develop
|
||||
paths:
|
||||
- ".github/workflows/build-pre-commit-image.yml"
|
||||
- "bin/pre-commit/Dockerfile"
|
||||
- "rust-toolchain.toml"
|
||||
pull_request:
|
||||
|
||||
@@ -34,7 +34,7 @@ permissions:
|
||||
jobs:
|
||||
audit:
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-060957e
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-3d526d4
|
||||
permissions:
|
||||
contents: read
|
||||
# Needed to open an issue on scheduled failures.
|
||||
|
||||
@@ -17,4 +17,4 @@ jobs:
|
||||
uses: XRPLF/actions/.github/workflows/pre-commit.yml@279ec358f4a1be4088be3e024b07916fa97c75b6
|
||||
with:
|
||||
runs_on: ubuntu-latest
|
||||
container: '{ "image": "ghcr.io/xrplf/xrpld/pre-commit:sha-473fe44" }'
|
||||
container: '{ "image": "ghcr.io/xrplf/xrpld/pre-commit:sha-70b8fd3" }'
|
||||
@@ -41,7 +41,7 @@ env:
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-060957e
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-3d526d4
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
needs: [determine-files]
|
||||
if: ${{ needs.determine-files.outputs.cpp_changed_files != '' || needs.determine-files.outputs.need_full_run == 'true' }}
|
||||
runs-on: ["self-hosted", "Linux", "X64", "heavy"]
|
||||
container: "ghcr.io/xrplf/xrpld/nix-debian:sha-060957e"
|
||||
container: "ghcr.io/xrplf/xrpld/nix-debian:sha-ed96e60"
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
|
||||
@@ -28,7 +28,7 @@ permissions:
|
||||
jobs:
|
||||
clippy:
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-060957e
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-3d526d4
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
@@ -41,7 +41,7 @@ jobs:
|
||||
|
||||
coverage:
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-060957e
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-3d526d4
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
@@ -70,7 +70,7 @@ jobs:
|
||||
|
||||
doc:
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-060957e
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-3d526d4
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
@@ -40,7 +40,7 @@ defaults:
|
||||
jobs:
|
||||
upload:
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-060957e
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-3d526d4
|
||||
env:
|
||||
REMOTE_NAME: ${{ inputs.remote_name }}
|
||||
CONAN_LOGIN_USERNAME_XRPLF: ${{ secrets.remote_username }}
|
||||
|
||||
@@ -60,7 +60,7 @@ repos:
|
||||
types_or: [c++, c]
|
||||
|
||||
- repo: https://github.com/pre-commit/mirrors-clang-format
|
||||
rev: e2b496dc2bd8340c2524cb9a2d2a943cde1bb6df # frozen: v23.1.1
|
||||
rev: a9a8a861f30ed207ead7d5a3b7e8032283ba5da7 # frozen: v23.1.2
|
||||
hooks:
|
||||
- id: clang-format
|
||||
args: [--style=file]
|
||||
@@ -82,9 +82,10 @@ repos:
|
||||
files: ^crates/.*\.rs$
|
||||
|
||||
- repo: https://github.com/BlankSpruce/gersemi-pre-commit
|
||||
rev: f1c4833f8cf23c6d952673abc73525411a5719e8 # frozen: 0.29.1
|
||||
rev: 28010ddd6016e1a0f7bd232acb6536ef996ae897 # frozen: 0.29.2
|
||||
hooks:
|
||||
- id: gersemi
|
||||
args: [-i, --warnings-as-errors]
|
||||
|
||||
- repo: https://github.com/rbubley/mirrors-prettier
|
||||
rev: ef4a397f916211b4a39ccf9d3d9cbb6562157251 # frozen: v3.9.9
|
||||
@@ -95,19 +96,19 @@ repos:
|
||||
# Scoped to package/: the rest of the repo's Python has pre-existing findings,
|
||||
# so widening these is its own change.
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: a56c0b927e6465d37cae3e97d35d4d18ab2b96cd # frozen: v0.16.9
|
||||
rev: f12be1ebaa5351c1fc76472de98db2c3446c8253 # frozen: v0.16.10
|
||||
hooks:
|
||||
- id: ruff-check
|
||||
args: [--fix]
|
||||
files: ^package/.*\.py$
|
||||
|
||||
- repo: https://github.com/psf/black-pre-commit-mirror
|
||||
rev: 4160603246a6b365d4a2af661c6d71b0a0f50478 # frozen: 26.5.1
|
||||
rev: 96ae3e5802f3fe2d551e703e18f0a367d1a81ac2 # frozen: 26.10.0
|
||||
hooks:
|
||||
- id: black
|
||||
|
||||
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||
rev: 7ff8d35ae36a7d2b968f2f90b4c723e292e594ee # frozen: v2.3.1
|
||||
rev: 2834ec6639549dd6796205c8f011dedcd587288b # frozen: v2.4.0
|
||||
hooks:
|
||||
- id: mypy
|
||||
args: [--strict]
|
||||
|
||||
@@ -29,6 +29,16 @@ Version 3.5.0 is not yet released.
|
||||
### Additions in 3.5.0
|
||||
|
||||
- `subscribe`, `unsubscribe`: Added an optional `mpt_issuances` request field, an array of MPT issuance IDs (hex strings). Subscribers receive a message with `type` `mptTransaction` for each validated transaction whose metadata affects a subscribed issuance; the message has the same fields as the `transactions` stream. MPT issuance subscriptions count toward the per-connection subscription limit. An empty array, a non-array value, or an invalid ID returns `invalidParams`. ([#5671](https://github.com/XRPLF/rippled/pull/5671))
|
||||
- `ledger_entry`: Add full support for checks, NFT offers, payment channels, and signer lists. ([#6319](https://github.com/XRPLF/rippled/pull/6319))
|
||||
|
||||
### Bugfixes in 3.5.0
|
||||
|
||||
- `channel_authorize`: The `channel_id` field now returns an `invalidParams` error if the value is not a string. [#7582](https://github.com/XRPLF/rippled/pull/7582)
|
||||
- `channel_verify`: The `channel_id` and `signature` fields now return an `invalidParams` error if the value is not a string. [#7582](https://github.com/XRPLF/rippled/pull/7582)
|
||||
|
||||
### Bugfixes in 3.5.0
|
||||
|
||||
- `feature`: The admin-only `vetoed` field now returns `invalidParams` unless its value is a boolean. [#7583](https://github.com/XRPLF/rippled/pull/7583)
|
||||
|
||||
## XRP Ledger server version 3.4.0
|
||||
|
||||
@@ -50,6 +60,7 @@ Version 3.4.0 is not yet released. These changes are available in the 3.4.0 beta
|
||||
- `account_lines`: The `peer` field now returns an error if the value is not a string. [#7728](https://github.com/XRPLF/rippled/pull/7728)
|
||||
- `ledger`: `delivered_amount` is now included in the metadata of successful `AccountDelete` transactions when transactions are expanded (`expand`, or admin-only `full`). Previously it was only added for `Payment` and `CheckCash`, which made `ledger` inconsistent with `tx` and `account_tx`. [#5706](https://github.com/XRPLF/rippled/pull/5706)
|
||||
- `noripple_check`: The `transactions` field is no longer included in error responses; it is still returned (possibly as an empty array) whenever `transactions` is `true` and the request succeeds. A malformed `account` is now rejected before the ledger is looked up, so that error response no longer carries the `ledger_hash`, `ledger_index`, and `validated` fields ([#6303](https://github.com/XRPLF/rippled/pull/6303)).
|
||||
- `transaction_entry`: An object or an array in `tx_hash` now returns `malformedRequest`, like any other value that is not a hex hash, instead of an `internal` error.
|
||||
|
||||
## XRP Ledger server version 3.3.0
|
||||
|
||||
@@ -71,6 +82,7 @@ This release contains bug fixes only and no API changes.
|
||||
|
||||
### Additions in 3.2.0
|
||||
|
||||
- `tx`: A validated `Batch` transaction now includes `inner_results`, an array in `RawTransactions` order with one entry per inner transaction that was evaluated, whether or not it was applied: `hash`, `result` (the transaction result code) and `applied` (whether its changes ended up in the ledger; `false` for an inner transaction that failed with a `ter`/`tem`/`tef` code, and for every inner transaction of a `tfAllOrNothing` batch that rolled back). `tfOnlyOne` stops evaluating after the first inner that returns `tesSUCCESS`, not merely applies (an applied `tec` does not stop it), and `tfUntilFailure` stops after the first inner that fails, so later `RawTransactions` entries have no corresponding `inner_results` entry. Inner transactions that fail are not in the ledger, so this is the only record of them. The field is present only when this node built the validated ledger itself and has `[ledger_tx_tables]` enabled; a node that acquired the ledger from the network omits it.
|
||||
- `ledger_entry`, `account_objects`: The `Delegate` ledger entry now includes an optional `DestinationNode` field, which stores the index into the authorized account's owner directory. This field is present on entries created after bidirectional directory tracking was introduced and may appear in RPC responses for those entries. ([#6681](https://github.com/XRPLF/rippled/pull/6681))
|
||||
- `server_definitions`: Added the following new sections to the response ([#6321](https://github.com/XRPLF/rippled/pull/6321)):
|
||||
- `TRANSACTION_FORMATS`: Describes the fields and their optionality for each transaction type, including common fields shared across all transactions.
|
||||
|
||||
Executable
+65
@@ -0,0 +1,65 @@
|
||||
#!/usr/bin/env bash
|
||||
# Fail if a binary of a build-context Conan package loads anything from the Nix
|
||||
# store other than glibc, or cannot resolve a library at all.
|
||||
#
|
||||
# Only binaries linked by the Nix toolchain are checked, i.e. those recording a
|
||||
# store path as their interpreter or RUNPATH. Prebuilt upstream binaries (such
|
||||
# as the ones the cmake package ships) use the system loader instead.
|
||||
#
|
||||
# Build-context packages provide the tools that run during the build (protoc,
|
||||
# grpc_cpp_plugin, ...). Their package ID does not change when a Nix toolchain
|
||||
# update moves the GCC runtime to a new store path, so a cached binary has to
|
||||
# get by with the pinned glibc alone. See docs/build/nix.md.
|
||||
#
|
||||
# Usage: bin/check-build-context-runtime.sh <graph.json>
|
||||
# <graph.json> is the output of `conan install --format=json`.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
if [ "$#" -ne 1 ]; then
|
||||
echo "usage: $0 <graph.json>" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
if [ "$(uname -s)" != "Linux" ]; then
|
||||
echo "$0: Linux only" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
folders="$(jq -r '.graph.nodes[] | select(.context == "build" and .package_folder) | .package_folder' "$1" | sort -u)"
|
||||
|
||||
checked=0
|
||||
failed=0
|
||||
|
||||
while IFS= read -r file; do
|
||||
case "$(file -b "${file}")" in
|
||||
ELF*) ;;
|
||||
*) continue ;;
|
||||
esac
|
||||
[[ "$(readelf -ldW "${file}")" == */nix/store/* ]] || continue
|
||||
checked=$((checked + 1))
|
||||
|
||||
# `ldd` lists the interpreter and every library as the loader resolves them.
|
||||
if deps="$(ldd "${file}" 2>&1)"; then
|
||||
bad="$(printf '%s\n' "${deps}" |
|
||||
grep -E 'not found|/nix/store/' |
|
||||
grep -vE '/nix/store/[^/]+-glibc-[^/]+/' || true)"
|
||||
else
|
||||
case "${deps}" in
|
||||
*"not a dynamic executable"*) continue ;;
|
||||
esac
|
||||
bad="${deps}"
|
||||
fi
|
||||
if [ -n "${bad}" ]; then
|
||||
failed=$((failed + 1))
|
||||
echo "::error file=${file}::loads a library from the Nix store other than glibc"
|
||||
echo "${file}"
|
||||
echo "${bad}" | sed 's/^/ /'
|
||||
fi
|
||||
done < <(
|
||||
# shellcheck disable=SC2086 # one folder per line, no spaces in Conan paths
|
||||
[ -z "${folders}" ] || find ${folders} -type f \( -perm -u+x -o -name '*.so*' \)
|
||||
)
|
||||
|
||||
echo "Build-context packages: checked ${checked} binaries, ${failed} failed."
|
||||
[ "${failed}" -eq 0 ]
|
||||
+17
-6
@@ -20,9 +20,11 @@
|
||||
# development setups, but not in the macOS CI environment. They are checked
|
||||
# everywhere except when running in CI on macOS.
|
||||
#
|
||||
# Tools that Nix also exposes under a version-suffixed name (`clang-tidy-22`,
|
||||
# `g++-15`, ...) are probed under both names: a suffixed name can break while
|
||||
# the plain one still works (see mkVersionedToolLinks in nix/packages.nix).
|
||||
# Tools that Nix also exposes under a version-suffixed name
|
||||
# (`clang-tidy-<v>`, `g++-<v>`, ...) are probed under both names:
|
||||
# a suffixed name can break while the plain one still works
|
||||
# (see mkVersionedToolLinks in nix/packages.nix).
|
||||
# The suffix is the major version of the plain `clang` / `gcc` on PATH.
|
||||
#
|
||||
# Tools scoped to a single dev shell rather than to commonPackages are checked
|
||||
# only in that shell, keyed off XRPL_DEVSHELL.
|
||||
@@ -34,9 +36,16 @@
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
# Version suffixes of the Nix tool links, tracking nix/packages.nix.
|
||||
gcc_version=15
|
||||
llvm_version=22
|
||||
# major_version <compiler>
|
||||
# Major version of a compiler on PATH, or "unknown" when it isn't there.
|
||||
major_version() {
|
||||
local version
|
||||
version="$("$1" -dumpversion 2>/dev/null)" || version=""
|
||||
version="${version%%.*}"
|
||||
printf '%s' "${version:-unknown}"
|
||||
}
|
||||
|
||||
llvm_version="$(major_version clang)"
|
||||
|
||||
missing=()
|
||||
checked=0
|
||||
@@ -112,6 +121,7 @@ if [ "${os}" = "linux" ] || [ "${os}" = "macos" ]; then
|
||||
check ClangBuildAnalyzer
|
||||
check curl
|
||||
check file
|
||||
check jq
|
||||
check less
|
||||
check make
|
||||
# net-tools netstat reports "net-tools X.Y"; macOS ships BSD netstat with no
|
||||
@@ -180,6 +190,7 @@ fi
|
||||
if [ "${os}" = "linux" ]; then
|
||||
echo
|
||||
echo "GCC toolchain:"
|
||||
gcc_version="$(major_version gcc)"
|
||||
check gcc
|
||||
check "gcc-${gcc_version}"
|
||||
check g++
|
||||
|
||||
@@ -23,7 +23,7 @@ apt-get clean
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
EOF
|
||||
|
||||
ARG PRE_COMMIT_VERSION=4.6.0
|
||||
ARG PRE_COMMIT_VERSION=4.6.2
|
||||
RUN pip install --no-cache --break-system-packages \
|
||||
pre-commit==${PRE_COMMIT_VERSION}
|
||||
|
||||
|
||||
@@ -26,8 +26,6 @@ import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
CLANG_TIDY_VERSION = 22
|
||||
|
||||
# Extensions run-clang-tidy can analyse: `.cpp` translation units and, thanks to
|
||||
# the `verify_headers` build option, `.h`/`.hpp` headers (each has its own
|
||||
# compile_commands.json entry). `.ipp` fragments have no entry and are skipped.
|
||||
@@ -39,8 +37,21 @@ TIDY_EXTENSIONS = {".cpp", ".h", ".hpp"}
|
||||
FILEPATH_RE = re.compile(r"^(\s*(?:-\s+)?FilePath:\s*)'((?:[^']|'')*)'\s*$")
|
||||
|
||||
|
||||
def find_tool(name: str) -> str | None:
|
||||
for candidate in (f"{name}-{CLANG_TIDY_VERSION}", name):
|
||||
def clang_tidy_major() -> str | None:
|
||||
"""Major version of the `clang-tidy` on PATH, which run-clang-tidy invokes."""
|
||||
if not (clang_tidy := shutil.which("clang-tidy")):
|
||||
return None
|
||||
output = subprocess.run(
|
||||
[clang_tidy, "--version"], capture_output=True, text=True
|
||||
).stdout
|
||||
m = re.search(r"LLVM version (\d+)", output)
|
||||
return m.group(1) if m else None
|
||||
|
||||
|
||||
def find_tool(name: str, version: str | None) -> str | None:
|
||||
"""Prefer `<name>-<version>`, so a host tool of another version can't win."""
|
||||
candidates = ([f"{name}-{version}"] if version else []) + [name]
|
||||
for candidate in candidates:
|
||||
if path := shutil.which(candidate):
|
||||
return path
|
||||
return None
|
||||
@@ -103,8 +114,9 @@ def main():
|
||||
if not files:
|
||||
return 0
|
||||
|
||||
run_clang_tidy = find_tool("run-clang-tidy")
|
||||
clang_apply_replacements = find_tool("clang-apply-replacements")
|
||||
version = clang_tidy_major()
|
||||
run_clang_tidy = find_tool("run-clang-tidy", version)
|
||||
clang_apply_replacements = find_tool("clang-apply-replacements", version)
|
||||
missing = [
|
||||
name
|
||||
for name, path in (
|
||||
@@ -114,9 +126,10 @@ def main():
|
||||
if not path
|
||||
]
|
||||
if missing:
|
||||
tried = f" (tried the '-{version}' suffix too)" if version else ""
|
||||
print(
|
||||
f"clang-tidy check failed: TIDY is enabled but {' and '.join(missing)} "
|
||||
f"was not found in PATH (tried the '-{CLANG_TIDY_VERSION}' suffix too).",
|
||||
f"was not found in PATH{tried}.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return 1
|
||||
|
||||
@@ -292,6 +292,14 @@ if(xrpld)
|
||||
)
|
||||
target_sources(xrpld PRIVATE ${sources})
|
||||
|
||||
rpcspec_generate_instantiations(
|
||||
OUT_VAR rpcspec_instantiations
|
||||
VALUE_TYPE "::json::Value"
|
||||
VIEW_HEADER "xrpld/rpc/detail/JsonObjectView.hpp"
|
||||
HANDLERS book_changes ledger transaction_entry
|
||||
)
|
||||
target_sources(xrpld PRIVATE ${rpcspec_instantiations})
|
||||
|
||||
if(tests)
|
||||
file(
|
||||
GLOB_RECURSE sources
|
||||
@@ -322,6 +330,7 @@ if(xrpld)
|
||||
# antithesis_instrumentation.h, which is not exported as INTERFACE
|
||||
target_include_directories(
|
||||
xrpld
|
||||
SYSTEM
|
||||
PRIVATE ${CMAKE_SOURCE_DIR}/external/antithesis-sdk
|
||||
)
|
||||
endif()
|
||||
|
||||
@@ -77,19 +77,24 @@ if(is_clang)
|
||||
message(STATUS " Ignorelist: ${ignorelist_path}")
|
||||
endif()
|
||||
|
||||
# Define SANITIZERS macro for BuildInfo.cpp
|
||||
# Define the SANITIZERS macro for BuildInfo.cpp, plus one of XRPL_ASAN,
|
||||
# XRPL_TSAN and XRPL_UBSAN per active sanitizer, so that code can test for a
|
||||
# specific one with #ifdef instead of parsing the dot-joined SANITIZERS string.
|
||||
set(sanitizers_list)
|
||||
if(SANITIZERS MATCHES "address")
|
||||
set(enable_asan ON)
|
||||
list(APPEND sanitizers_list "ASAN")
|
||||
target_compile_definitions(common INTERFACE XRPL_ASAN)
|
||||
endif()
|
||||
if(SANITIZERS MATCHES "thread")
|
||||
set(enable_tsan ON)
|
||||
list(APPEND sanitizers_list "TSAN")
|
||||
target_compile_definitions(common INTERFACE XRPL_TSAN)
|
||||
endif()
|
||||
if(SANITIZERS MATCHES "undefinedbehavior")
|
||||
set(enable_ubsan ON)
|
||||
list(APPEND sanitizers_list "UBSAN")
|
||||
target_compile_definitions(common INTERFACE XRPL_UBSAN)
|
||||
endif()
|
||||
|
||||
if(sanitizers_list)
|
||||
|
||||
+6
-2
@@ -3,7 +3,7 @@
|
||||
"requires": [
|
||||
"zlib/1.3.2#1cb806da49011867778ffb6ac7190fcb%1782392402.122708",
|
||||
"xxhash/0.8.3#681d36a0a6111fc56e5e45ea182c19cc%1782392402.420688",
|
||||
"xrpl-rpc-spec/0.1.19#870b2d3abcfbbf13b61c2d3c69495060%1790348286.187549",
|
||||
"xrpl-rpc-spec/0.1.21#d536f87a2ae7d313452746cfa3ac4404%1790869005.122975",
|
||||
"sqlite3/3.53.0#324ada52333108388a9a6108bfa96734%1782392403.185447",
|
||||
"soci/4.0.3#e726491a03468795453f7c83fc924a96%1782392402.679521",
|
||||
"snappy/1.1.10#968fef506ff261592ec30c574d4a7809%1782307151.633168",
|
||||
@@ -20,7 +20,7 @@
|
||||
"libarchive/3.8.7#c446109bd1f1d8ba7936c94189bc50e6%1782392403.066892",
|
||||
"jemalloc/5.3.1#1fc58d55316041f10fbc1e8a2eae632a%1776700028.228",
|
||||
"gtest/1.17.0#5224b3b3ff3b4ce1133cbdd27d53ee7d%1782392402.791979",
|
||||
"grpc/1.81.1#b87796a4269034856cbc1a2522db16eb%1788275071.530512",
|
||||
"grpc/1.81.1#aaa93ab6cda2f2baa6a84490582c8adf%1791284951.826256",
|
||||
"fast_float/8.2.10#f6f28d6bb22112078e7dbda611caf681%1785888854.601666",
|
||||
"ed25519/2015.03#ae761bdc52730a843f0809bdf6c1b1f6%1782307148.15562",
|
||||
"date/3.0.4#862e11e80030356b53c2c38599ceb32b%1782392402.538492",
|
||||
@@ -34,11 +34,15 @@
|
||||
"build_requires": [
|
||||
"zlib/1.3.2#1cb806da49011867778ffb6ac7190fcb%1782392402.122708",
|
||||
"strawberryperl/5.32.1.1#8d114504d172cfea8ea1662d09b6333e%1782395692.540639",
|
||||
"re2/20251105#8579cfd0bda4daf0683f9e3898f964b4%1782392402.431897",
|
||||
"protobuf/6.33.5#ff253ead763bd8d9904a52979cd21e81%1782392410.233933",
|
||||
"openssl/3.6.3#f806de8933e3bf6f01016c6a888cee2e%1783945160.863288",
|
||||
"nasm/2.16.01#31e26f2ee3c4346ecd347911bd126904%1782395690.33162",
|
||||
"msys2/cci.latest#d22fe7b2808f5fd34d0a7923ace9c54f%1770657326.649",
|
||||
"m4/1.4.19#1727f439cf74e83826ec96d0b4904eee%1784541921.659",
|
||||
"grpc/1.81.1#aaa93ab6cda2f2baa6a84490582c8adf%1791284951.826256",
|
||||
"cmake/4.3.3#840cf00ea09777e05c2050a50a82c722%1782392418.696091",
|
||||
"c-ares/1.34.6#545240bb1c40e2cacd4362d6b8967650%1782392402.681654",
|
||||
"b2/5.4.2#ffd6084a119587e70f11cd45d1a386e2%1782392402.624226",
|
||||
"automake/1.16.5#b91b7c384c3deaa9d535be02da14d04f%1755524470.56",
|
||||
"autoconf/2.71#51077f068e61700d65bb05541ea1e4b0%1731054366.86",
|
||||
|
||||
+9
-1
@@ -3,5 +3,13 @@
|
||||
core:non_interactive=True
|
||||
core.download:parallel={{ os.cpu_count() }}
|
||||
core.upload:parallel={{ os.cpu_count() }}
|
||||
tools.files.download:retry=5
|
||||
# Fall back to Conan Center's source backups when a recipe's upstream URL is down
|
||||
# (e.g. the GNU FTP mirrors), see
|
||||
# https://github.com/conan-io/conan-center-index/issues/28147#issuecomment-3183544772
|
||||
# The backups are only tried once every upstream URL has used up its retries,
|
||||
# so keep the retries low.
|
||||
core.sources:download_urls=["origin", "https://c3i.jfrog.io/artifactory/conan-center-backup-sources"]
|
||||
tools.files.download:retry=1
|
||||
tools.files.download:retry_wait=10
|
||||
# Fail fast on unreachable hosts (default connect timeout is 30s), keep the 60s read timeout.
|
||||
core.net.http:timeout=(5, 60)
|
||||
+24
-3
@@ -29,9 +29,9 @@ os.version={{ min_macos_version }}
|
||||
[conf]
|
||||
{# The Boost recipe builds with b2, which doesn't use Conan's toolchain files. #}
|
||||
{# Instead it hand-rolls the compiler for user-config.jam, #}
|
||||
{# and its fallback probes a version-suffixed binary (e.g. `g++-15`) before plain `g++`. #}
|
||||
{# Inside the Nix shell the wrapper only provides `g++`/`gcc` (no `-15` suffix), #}
|
||||
{# so on a host that also has a system `g++-15` the probe escapes Nix #}
|
||||
{# and its fallback probes a version-suffixed binary (e.g. `g++-<major>`) before plain `g++`. #}
|
||||
{# Inside the Nix shell the wrapper only provides `g++`/`gcc` (no `-<major>` suffix), #}
|
||||
{# so on a host that also has a system `g++-<major>` the probe escapes Nix #}
|
||||
{# and picks the system compiler, which is mismatched with the Nix libraries #}
|
||||
{# and breaks the build (e.g. Boost.Stacktrace link checks fail). #}
|
||||
{# Pinning the executables here short-circuits that probe so Boost (and the rest of the toolchain) #}
|
||||
@@ -49,6 +49,17 @@ tools.build:compiler_executables={'c':'{{ cc_exe }}','cpp':'{{ cxx_exe }}'}
|
||||
user.package:cppstd_version=23
|
||||
tools.info.package_id:confs+=["user.package:cppstd_version"]
|
||||
|
||||
{% if os == "Linux" and context == "build" %}
|
||||
{# Build-context executables (protoc, grpc_cpp_plugin, build tools) run during the build #}
|
||||
{# and would otherwise load libstdc++/libgcc from a Nix store path #}
|
||||
{# that might change with a Nix toolchain update. #}
|
||||
{# --as-needed drops the ones they link but don't use, #}
|
||||
{# such as libatomic for grpc_cpp_plugin on arm64. #}
|
||||
{% set static_runtime_flags = ["-static-libstdc++", "-static-libgcc", "-Wl,--as-needed"] %}
|
||||
tools.build:exelinkflags+={{ static_runtime_flags }}
|
||||
tools.info.package_id:confs+=["tools.build:exelinkflags"]
|
||||
{% endif %}
|
||||
|
||||
{% if os == "Macos" %}
|
||||
[buildenv]
|
||||
{# os.version adds -mmacosx-version-min to compiler command lines, #}
|
||||
@@ -58,3 +69,13 @@ tools.info.package_id:confs+=["user.package:cppstd_version"]
|
||||
{# Scoped to boost/* since it is the only gap. #}
|
||||
boost/*:MACOSX_DEPLOYMENT_TARGET={{ min_macos_version }}
|
||||
{% endif %}
|
||||
|
||||
{% if os == "Linux" and context == "build" %}
|
||||
{# b2 links itself with its own script, which ignores exelinkflags #}
|
||||
{# and only takes CXXFLAGS when use_cxx_env is set. #}
|
||||
[options]
|
||||
b2/*:use_cxx_env=True
|
||||
|
||||
[buildenv]
|
||||
b2/*:CXXFLAGS={{ static_runtime_flags | join(" ") }}
|
||||
{% endif %}
|
||||
@@ -5,6 +5,9 @@ include(default)
|
||||
|
||||
{% if not sanitizers %}
|
||||
{# Sanitizers not configured; no additional settings needed #}
|
||||
{% elif context == "build" %}
|
||||
{# Build-context packages are tools we run, not code we test, #}
|
||||
{# so don't instrument them #}
|
||||
{% else %}
|
||||
|
||||
{% if compiler == "msvc" %}
|
||||
|
||||
+7
-2
@@ -40,7 +40,7 @@ class Xrpl(ConanFile):
|
||||
"nudb/2.0.9",
|
||||
"openssl/3.6.3",
|
||||
"soci/4.0.3",
|
||||
"xrpl-rpc-spec/0.1.19",
|
||||
"xrpl-rpc-spec/0.1.21",
|
||||
"zlib/1.3.2",
|
||||
]
|
||||
|
||||
@@ -49,7 +49,8 @@ class Xrpl(ConanFile):
|
||||
]
|
||||
|
||||
tool_requires = [
|
||||
"protobuf/6.33.5",
|
||||
"grpc/<host_version>",
|
||||
"protobuf/<host_version>",
|
||||
]
|
||||
|
||||
default_options = {
|
||||
@@ -123,6 +124,10 @@ class Xrpl(ConanFile):
|
||||
"xxhash/*:shared": False,
|
||||
}
|
||||
|
||||
# default_options only reach the host context;
|
||||
# give tool_requires (and their dependencies) the same dependency options.
|
||||
default_build_options = {k: v for k, v in default_options.items() if "/" in k}
|
||||
|
||||
def set_version(self):
|
||||
self.version = self.version or DEV_VERSION
|
||||
|
||||
|
||||
@@ -80,13 +80,12 @@ function(add_xrpl_crate name)
|
||||
# `cc` picks its runtime flag from `crt-static` alone, so it compiles a
|
||||
# crate's C++ with `-MT`; Debug needs `-MTd` (to match cmake/XrplCompiler.cmake).
|
||||
if(is_msvc)
|
||||
corrosion_set_env_vars(
|
||||
${ARG_CRATE}
|
||||
"$<$<CONFIG:Debug>:CXXFLAGS=-MTd>"
|
||||
)
|
||||
corrosion_set_env_vars(${ARG_CRATE} "$<$<CONFIG:Debug>:CXXFLAGS=-MTd>")
|
||||
endif()
|
||||
corrosion_add_cxxbridge(${name}_cxxbridge CRATE ${ARG_CRATE} FILES
|
||||
${ARG_FILES}
|
||||
corrosion_add_cxxbridge(
|
||||
${name}_cxxbridge
|
||||
CRATE ${ARG_CRATE}
|
||||
FILES ${ARG_FILES}
|
||||
)
|
||||
# Generated cxxbridge headers don't exist at configure time; CMake 3.28+
|
||||
# validates INTERFACE_SOURCES on consuming targets. Clear it to skip the
|
||||
|
||||
Vendored
+9
-7
@@ -10,14 +10,15 @@ This document explains how to set one up.
|
||||
support it — see [compiler support for C++23][cpp23-support].
|
||||
The versions currently tested in CI are:
|
||||
|
||||
| Compiler | Version |
|
||||
| ----------- | ------------------ |
|
||||
| GCC | 15.2 |
|
||||
| Clang | 22 |
|
||||
| Apple Clang | 21 |
|
||||
| MSVC | Visual Studio 2026 |
|
||||
| Compiler | Version |
|
||||
| ----------- | ------------------------------- |
|
||||
| GCC | `gccVersion` in [packages.nix] |
|
||||
| Clang | `llvmVersion` in [packages.nix] |
|
||||
| Apple Clang | 21 |
|
||||
| MSVC | Visual Studio 2026 |
|
||||
|
||||
LLVM tools (`clang-tidy` and `clang-format`) are also pinned to version 22.
|
||||
LLVM tools (`clang-tidy` and `clang-format`)
|
||||
come from the same LLVM release as Clang.
|
||||
|
||||
### Older compilers
|
||||
|
||||
@@ -156,3 +157,4 @@ version out of the box — run it via `run-clang-tidy`. No separate installation
|
||||
is needed.
|
||||
|
||||
[cpp23-support]: https://en.cppreference.com/w/cpp/compiler_support/23
|
||||
[packages.nix]: ../../nix/packages.nix
|
||||
Vendored
+16
-4
@@ -194,10 +194,22 @@ bin/check-nix-store-refs.sh ~/.conan2-nix
|
||||
```
|
||||
|
||||
It works on Linux too, but asserts something narrower there: the toolchain always
|
||||
writes the store into `PT_INTERP` and `RUNPATH`, and CI builds inside an image
|
||||
whose store is fixed for its lifetime, so that is fine. Only the binaries
|
||||
[`PatchNixBinary.cmake`](../../cmake/PatchNixBinary.cmake) retargets to the
|
||||
system loader have to be clean, and those are what CI checks:
|
||||
writes the store into `PT_INTERP` and `RUNPATH`. That is fine for the pinned
|
||||
glibc, whose path does not move, but not for the GCC runtime, which moves with
|
||||
every GCC update. So [`conan/profiles/default`](../../conan/profiles/default)
|
||||
links build-context packages, whose executables run during the build, with
|
||||
`-static-libstdc++ -static-libgcc -Wl,--as-needed`, and
|
||||
[`conan/profiles/sanitizers`](../../conan/profiles/sanitizers) does not
|
||||
instrument them. CI checks that they load nothing from the store but glibc, from
|
||||
the graph `conan install --format=json` writes:
|
||||
|
||||
```bash
|
||||
bin/check-build-context-runtime.sh graph.json
|
||||
```
|
||||
|
||||
Only the binaries [`PatchNixBinary.cmake`](../../cmake/PatchNixBinary.cmake)
|
||||
retargets to the system loader have to be fully clean, and those are what CI
|
||||
checks:
|
||||
|
||||
```bash
|
||||
bin/check-nix-store-refs.sh build/xrpld
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ project(antithesis-sdk-cpp VERSION 0.4.4 LANGUAGES CXX)
|
||||
add_library(antithesis-sdk-cpp INTERFACE antithesis_sdk.h)
|
||||
|
||||
# Note, both sections below created by xrpld project
|
||||
target_include_directories(antithesis-sdk-cpp INTERFACE
|
||||
target_include_directories(antithesis-sdk-cpp SYSTEM INTERFACE
|
||||
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
|
||||
)
|
||||
|
||||
Generated
+6
-6
@@ -2,11 +2,11 @@
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1781173989,
|
||||
"narHash": "sha256-fnzKKPvS+oieI/pTzotA5tkoM47EB1NpaBcgk4R97hE=",
|
||||
"lastModified": 1791130267,
|
||||
"narHash": "sha256-1sjwcQMcgAbmiip/VivBSiK8p2bTkQGOvCuO7vnwfx4=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "8c91a71d13451abc40eb9dae8910f972f979852f",
|
||||
"rev": "9013764fcc0ea99fa16cf7aa7decf8a5c3889dd2",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -47,11 +47,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1784611586,
|
||||
"narHash": "sha256-OfqgY+0hp/zseZB7uyH0U8kIDPS4scZZCyAurEplvG0=",
|
||||
"lastModified": 1791189933,
|
||||
"narHash": "sha256-Y8eN7ki0Urx4Ojf4w1xSpWg8uk/3lJqK+E0Oth77rH0=",
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"rev": "14f58845249f3552a89b07772626b8d3c632fa86",
|
||||
"rev": "e60029353d0c48d216bc4b065168ccd4079c166f",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
namespace xrpl {
|
||||
|
||||
template <typename Key>
|
||||
static std::size_t
|
||||
std::size_t
|
||||
extract(Key const& key)
|
||||
{
|
||||
return key;
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace beast::unit_test {
|
||||
namespace detail {
|
||||
|
||||
template <class String>
|
||||
static std::string
|
||||
std::string
|
||||
makeReason(String const& reason, char const* file, int line)
|
||||
{
|
||||
std::string s(reason);
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/CachedView.h>
|
||||
#include <xrpl/ledger/RawView.h>
|
||||
#include <xrpl/protocol/BatchInnerResult.h>
|
||||
#include <xrpl/protocol/Fees.h>
|
||||
#include <xrpl/protocol/Keylet.h>
|
||||
#include <xrpl/protocol/LedgerHeader.h>
|
||||
@@ -325,6 +326,24 @@ public:
|
||||
bool
|
||||
addSLE(SLE const& sle);
|
||||
|
||||
/**
|
||||
* @brief Inner Batch outcomes observed while this node built the ledger.
|
||||
*
|
||||
* Empty for a ledger acquired from the network or loaded from the database. Not part of
|
||||
* the ledger hash; persisted to the transaction tables when the ledger is saved.
|
||||
*/
|
||||
void
|
||||
setBatchInnerResults(std::vector<BatchInnerResult> results)
|
||||
{
|
||||
batchInnerResults_ = std::move(results);
|
||||
}
|
||||
|
||||
std::vector<BatchInnerResult> const&
|
||||
batchInnerResults() const
|
||||
{
|
||||
return batchInnerResults_;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
void
|
||||
@@ -433,6 +452,7 @@ private:
|
||||
Rules rules_;
|
||||
LedgerHeader header_;
|
||||
beast::Journal j_;
|
||||
std::vector<BatchInnerResult> batchInnerResults_;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/basics/chrono.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/entries/SLEBase.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
@@ -25,6 +31,52 @@ public:
|
||||
: Base(keylet::amendments(), view, j)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the set of amendments this entry reports as enabled.
|
||||
*
|
||||
* @return the set of enabled amendments.
|
||||
*/
|
||||
[[nodiscard]] std::set<UInt256>
|
||||
enabledAmendments() const
|
||||
{
|
||||
std::set<UInt256> amendments;
|
||||
|
||||
if (this->exists() && (*this)->isFieldPresent(sfAmendments))
|
||||
{
|
||||
auto const& v = (*this)->getFieldV256(sfAmendments);
|
||||
amendments.insert_range(v);
|
||||
}
|
||||
|
||||
return amendments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a map of amendments that have achieved majority, to the time
|
||||
* majority was reached.
|
||||
*
|
||||
* @return a map of amendment to the time majority was reached.
|
||||
*/
|
||||
[[nodiscard]] std::map<UInt256, NetClock::time_point>
|
||||
majorityAmendments() const
|
||||
{
|
||||
std::map<UInt256, NetClock::time_point> ret;
|
||||
|
||||
if (this->exists() && (*this)->isFieldPresent(sfMajorities))
|
||||
{
|
||||
using TimePoint = NetClock::time_point;
|
||||
using Duration = TimePoint::duration;
|
||||
|
||||
auto const majorities = (*this)->getFieldArray(sfMajorities);
|
||||
|
||||
for (auto const& m : majorities)
|
||||
{
|
||||
ret[m.getFieldH256(sfAmendment)] = TimePoint(Duration(m.getFieldU32(sfCloseTime)));
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
using AmendmentsEntryR = AmendmentsEntry<ReadView>;
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/entries/SLEBase.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STVector256.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <optional>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
@@ -25,6 +31,31 @@ public:
|
||||
: Base(keylet::skip(), view, j)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up a hash `diff` slots back from the most recent entry in the
|
||||
* sfHashes vector (diff == 0 is the most recent entry).
|
||||
*
|
||||
* Callers decide which skip list entry to read and how to translate a
|
||||
* target ledger sequence into `diff`; this only does the bounds check
|
||||
* and vector indexing shared by both the recent (stride 1) and distant
|
||||
* (stride 256) skip lists.
|
||||
*
|
||||
* @param diff how many slots back from the most recent hash to look up;
|
||||
* 0 is the most recent hash.
|
||||
* @return the hash at that slot, or std::nullopt if the entry does not
|
||||
* exist or `diff` is out of range.
|
||||
*/
|
||||
[[nodiscard]] std::optional<UInt256>
|
||||
hashAt(std::size_t diff) const
|
||||
{
|
||||
if (!this->exists())
|
||||
return std::nullopt;
|
||||
STVector256 const vec = (*this)->getFieldV256(sfHashes);
|
||||
if (vec.size() > diff)
|
||||
return vec[vec.size() - diff - 1];
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
|
||||
using LedgerHashesEntryR = LedgerHashesEntry<ReadView>;
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/beast/utility/Zero.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
|
||||
@@ -27,6 +29,291 @@
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
* Validate that @p account may lock @p amount of a token for later delivery
|
||||
* to @p dest.
|
||||
*
|
||||
* The lock-side counterpart of escrowUnlockPreclaimHelper: every issuer
|
||||
* control (locking opt-in, authorization, freeze/lock, transferability,
|
||||
* spendable balance) that gates locking token value lives here, so any
|
||||
* transactor that locks funds applies the same rules. The signature is
|
||||
* view-based rather than PreclaimContext-based so it can also run from
|
||||
* doApply.
|
||||
*/
|
||||
template <ValidIssueType T>
|
||||
TER
|
||||
escrowLockPreclaimHelper(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
AccountID const& dest,
|
||||
STAmount const& amount,
|
||||
beast::Journal j);
|
||||
|
||||
template <>
|
||||
inline TER
|
||||
escrowLockPreclaimHelper<Issue>(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
AccountID const& dest,
|
||||
STAmount const& amount,
|
||||
beast::Journal j)
|
||||
{
|
||||
auto const& issue = amount.get<Issue>();
|
||||
auto const& issuer = amount.getIssuer();
|
||||
// If the issuer is the same as the account, return tecNO_PERMISSION
|
||||
if (issuer == account)
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// If the lsfAllowTrustLineLocking is not enabled, return tecNO_PERMISSION
|
||||
auto const sleIssuer = view.read(keylet::account(issuer));
|
||||
if (!sleIssuer)
|
||||
return tecNO_ISSUER;
|
||||
if (!sleIssuer->isFlag(lsfAllowTrustLineLocking))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// If the account does not have a trustline to the issuer, return tecNO_LINE
|
||||
auto const sleRippleState = view.read(keylet::trustLine(account, issuer, issue.currency));
|
||||
if (!sleRippleState)
|
||||
return tecNO_LINE;
|
||||
|
||||
STAmount const balance = (*sleRippleState)[sfBalance];
|
||||
|
||||
// If balance is positive, issuer must have higher address than account
|
||||
if (balance > beast::kZero && issuer < account)
|
||||
return tecNO_PERMISSION; // LCOV_EXCL_LINE
|
||||
|
||||
// If balance is negative, issuer must have lower address than account
|
||||
if (balance < beast::kZero && issuer > account)
|
||||
return tecNO_PERMISSION; // LCOV_EXCL_LINE
|
||||
|
||||
// If the issuer has requireAuth set, check if the account is authorized
|
||||
if (auto const ter = requireAuth(view, issue, account); !isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
// If the issuer has requireAuth set, check if the destination is authorized
|
||||
if (auto const ter = requireAuth(view, issue, dest); !isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
// If the issuer has frozen the account, return tecFROZEN
|
||||
if (isFrozen(view, account, issue))
|
||||
return tecFROZEN;
|
||||
|
||||
// If the issuer has frozen the destination, return tecFROZEN
|
||||
if (isFrozen(view, dest, issue))
|
||||
return tecFROZEN;
|
||||
|
||||
STAmount const spendableAmount =
|
||||
accountHolds(view, account, issue.currency, issuer, FreezeHandling::IgnoreFreeze, j);
|
||||
|
||||
// If the balance is less than or equal to 0, return tecINSUFFICIENT_FUNDS
|
||||
if (spendableAmount <= beast::kZero)
|
||||
return tecINSUFFICIENT_FUNDS;
|
||||
|
||||
// If the spendable amount is less than the amount, return
|
||||
// tecINSUFFICIENT_FUNDS
|
||||
if (spendableAmount < amount)
|
||||
return tecINSUFFICIENT_FUNDS;
|
||||
|
||||
// If the amount is not addable to the balance, return tecPRECISION_LOSS
|
||||
if (!canAdd(spendableAmount, amount))
|
||||
return tecPRECISION_LOSS;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline TER
|
||||
escrowLockPreclaimHelper<MPTIssue>(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
AccountID const& dest,
|
||||
STAmount const& amount,
|
||||
beast::Journal j)
|
||||
{
|
||||
AccountID const issuer = amount.getIssuer();
|
||||
// If the issuer is the same as the account, return tecNO_PERMISSION
|
||||
if (issuer == account)
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// If the mpt does not exist, return tecOBJECT_NOT_FOUND
|
||||
auto const issuanceKey = keylet::mptokenIssuance(amount.get<MPTIssue>().getMptID());
|
||||
auto const sleIssuance = view.read(issuanceKey);
|
||||
if (!sleIssuance)
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
// If the lsfMPTCanEscrow is not enabled, return tecNO_PERMISSION
|
||||
if (!sleIssuance->isFlag(lsfMPTCanEscrow))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// If the issuer is not the same as the issuer of the mpt, return
|
||||
// tecNO_PERMISSION
|
||||
if (sleIssuance->getAccountID(sfIssuer) != issuer)
|
||||
return tecNO_PERMISSION; // LCOV_EXCL_LINE
|
||||
|
||||
// If the account does not have the mpt, return tecOBJECT_NOT_FOUND
|
||||
if (!view.exists(keylet::mptoken(issuanceKey.key, account)))
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
// If the issuer has requireAuth set, check if the account is
|
||||
// authorized
|
||||
auto const& mptIssue = amount.get<MPTIssue>();
|
||||
if (auto const ter = requireAuth(view, mptIssue, account, AuthType::WeakAuth);
|
||||
!isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
// If the issuer has requireAuth set, check if the destination is
|
||||
// authorized
|
||||
if (auto const ter = requireAuth(view, mptIssue, dest, AuthType::WeakAuth); !isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
// If the issuer has frozen the account, return tecLOCKED
|
||||
if (isFrozen(view, account, *sleIssuance))
|
||||
return tecLOCKED;
|
||||
|
||||
// If the issuer has frozen the destination, return tecLOCKED
|
||||
if (isFrozen(view, dest, *sleIssuance))
|
||||
return tecLOCKED;
|
||||
|
||||
// If the mpt cannot be transferred, return tecNO_AUTH
|
||||
if (auto const ter = canTransfer(view, mptIssue, account, dest); !isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
STAmount const spendableAmount = accountHolds(
|
||||
view,
|
||||
account,
|
||||
amount.get<MPTIssue>(),
|
||||
FreezeHandling::IgnoreFreeze,
|
||||
AuthHandling::IgnoreAuth,
|
||||
j);
|
||||
|
||||
// If the balance is less than or equal to 0, return tecINSUFFICIENT_FUNDS
|
||||
if (spendableAmount <= beast::kZero)
|
||||
return tecINSUFFICIENT_FUNDS;
|
||||
|
||||
// If the spendable amount is less than the amount, return
|
||||
// tecINSUFFICIENT_FUNDS
|
||||
if (spendableAmount < amount)
|
||||
return tecINSUFFICIENT_FUNDS;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
template <ValidIssueType T>
|
||||
TER
|
||||
escrowLockApplyHelper(
|
||||
ApplyView& view,
|
||||
AccountID const& issuer,
|
||||
AccountID const& sender,
|
||||
STAmount const& amount,
|
||||
beast::Journal journal);
|
||||
|
||||
template <>
|
||||
inline TER
|
||||
escrowLockApplyHelper<Issue>(
|
||||
ApplyView& view,
|
||||
AccountID const& issuer,
|
||||
AccountID const& sender,
|
||||
STAmount const& amount,
|
||||
beast::Journal journal)
|
||||
{
|
||||
// Defensive: Issuer cannot create an escrow
|
||||
if (issuer == sender)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const ter =
|
||||
directSendNoFee(view, sender, issuer, amount, !amount.holds<MPTIssue>(), journal);
|
||||
if (!isTesSuccess(ter))
|
||||
return ter; // LCOV_EXCL_LINE
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline TER
|
||||
escrowLockApplyHelper<MPTIssue>(
|
||||
ApplyView& view,
|
||||
AccountID const& issuer,
|
||||
AccountID const& sender,
|
||||
STAmount const& amount,
|
||||
beast::Journal journal)
|
||||
{
|
||||
// Defensive: Issuer cannot create an escrow
|
||||
if (issuer == sender)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const ter = lockEscrowMPT(view, sender, amount, journal);
|
||||
if (!isTesSuccess(ter))
|
||||
return ter; // LCOV_EXCL_LINE
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
template <ValidIssueType T>
|
||||
TER
|
||||
escrowUnlockPreclaimHelper(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
STAmount const& amount,
|
||||
bool checkFreeze = true);
|
||||
|
||||
template <>
|
||||
inline TER
|
||||
escrowUnlockPreclaimHelper<Issue>(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
STAmount const& amount,
|
||||
bool checkFreeze)
|
||||
{
|
||||
AccountID const& issuer = amount.getIssuer();
|
||||
// If the issuer is the same as the account, return tesSUCCESS
|
||||
if (issuer == account)
|
||||
return tesSUCCESS;
|
||||
|
||||
// If the issuer has requireAuth set, check if the destination is authorized
|
||||
if (auto const ter = requireAuth(view, amount.get<Issue>(), account); !isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
// If the issuer has deep frozen the destination, return tecFROZEN
|
||||
if (checkFreeze &&
|
||||
isDeepFrozen(view, account, amount.get<Issue>().currency, amount.getIssuer()))
|
||||
return tecFROZEN;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline TER
|
||||
escrowUnlockPreclaimHelper<MPTIssue>(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
STAmount const& amount,
|
||||
bool checkFreeze)
|
||||
{
|
||||
AccountID const& issuer = amount.getIssuer();
|
||||
// If the issuer is the same as the account, return tesSUCCESS
|
||||
if (issuer == account)
|
||||
return tesSUCCESS;
|
||||
|
||||
// If the mpt does not exist, return tecOBJECT_NOT_FOUND
|
||||
auto const issuanceKey = keylet::mptokenIssuance(amount.get<MPTIssue>().getMptID());
|
||||
auto const sleIssuance = view.read(issuanceKey);
|
||||
if (!sleIssuance)
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
// If the issuer has requireAuth set, check if the account is
|
||||
// authorized
|
||||
auto const& mptIssue = amount.get<MPTIssue>();
|
||||
if (auto const ter = requireAuth(view, mptIssue, account, AuthType::WeakAuth);
|
||||
!isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
// If the issuer has frozen the account, return tecLOCKED
|
||||
if (checkFreeze && isFrozen(view, account, *sleIssuance))
|
||||
return tecLOCKED;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
template <ValidIssueType T>
|
||||
TER
|
||||
escrowUnlockApplyHelper(
|
||||
@@ -55,9 +342,6 @@ escrowUnlockApplyHelper<Issue>(
|
||||
bool createAsset,
|
||||
beast::Journal journal)
|
||||
{
|
||||
auto const& issue = amount.get<Issue>();
|
||||
Keylet const trustLineKey = keylet::trustLine(receiver, issue);
|
||||
bool const recvLow = issuer > receiver;
|
||||
bool const senderIssuer = issuer == sender;
|
||||
bool const receiverIssuer = issuer == receiver;
|
||||
|
||||
@@ -67,6 +351,10 @@ escrowUnlockApplyHelper<Issue>(
|
||||
if (receiverIssuer)
|
||||
return tesSUCCESS;
|
||||
|
||||
auto const& issue = amount.get<Issue>();
|
||||
Keylet const trustLineKey = keylet::trustLine(receiver, issue);
|
||||
bool const recvLow = issuer > receiver;
|
||||
|
||||
if (!ctx.view.exists(trustLineKey) && createAsset)
|
||||
{
|
||||
// Can the account cover the trust line's reserve?
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
* @brief Outcome of one inner transaction of a Batch, recorded while the outer Batch was
|
||||
* applied.
|
||||
*
|
||||
* Inner transactions that fail with a ter/tem/tef result are never applied and leave no
|
||||
* metadata in the ledger, and a tfAllOrNothing rollback discards the ones that did apply.
|
||||
* This record is the only place those outcomes survive. It is node-local and not part of
|
||||
* the ledger hash.
|
||||
*/
|
||||
struct BatchInnerResult
|
||||
{
|
||||
uint256 parentBatchId;
|
||||
uint256 innerTxId;
|
||||
|
||||
/**
|
||||
* @brief Position in the outer transaction's RawTransactions.
|
||||
*/
|
||||
std::uint32_t index = 0;
|
||||
|
||||
TER ter = tesSUCCESS;
|
||||
|
||||
/**
|
||||
* @brief True if the inner transaction's changes ended up in the ledger.
|
||||
*/
|
||||
bool applied = false;
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -1,28 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
* @brief Enumeration of ledger shortcuts for specifying which ledger to use.
|
||||
*
|
||||
* These shortcuts provide a convenient way to reference commonly used ledgers
|
||||
* without needing to specify their exact hash or sequence number.
|
||||
*/
|
||||
enum class LedgerShortcut {
|
||||
/**
|
||||
* The current working ledger (open, not yet closed)
|
||||
*/
|
||||
Current,
|
||||
|
||||
/**
|
||||
* The most recently closed ledger (may not be validated)
|
||||
*/
|
||||
Closed,
|
||||
|
||||
/**
|
||||
* The most recently validated ledger
|
||||
*/
|
||||
Validated
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -300,6 +300,7 @@ JSS(in); // out: OverlayImpl
|
||||
JSS(inLedger); // out: tx/Transaction
|
||||
JSS(inbound); // out: PeerImp
|
||||
JSS(index); // in: LedgerEntry
|
||||
JSS(inner_results); // out: Tx
|
||||
// out: STLedgerEntry, LedgerEntry, TxHistory, LedgerData
|
||||
JSS(info); // out: ServerInfo, ConsensusInfo, FetchInfo
|
||||
JSS(initial_sync_duration_us); //
|
||||
|
||||
@@ -72,7 +72,7 @@ inline constexpr std::array<char const*, 5> kLgrDbInit{
|
||||
// Transaction database holds transactions and public keys
|
||||
inline constexpr auto kTxDbName{"transaction.db"};
|
||||
|
||||
inline constexpr std::array<char const*, 8> kTxDbInit{
|
||||
inline constexpr std::array<char const*, 10> kTxDbInit{
|
||||
{"BEGIN TRANSACTION;",
|
||||
|
||||
"CREATE TABLE IF NOT EXISTS Transactions ( \
|
||||
@@ -101,6 +101,21 @@ inline constexpr std::array<char const*, 8> kTxDbInit{
|
||||
"CREATE INDEX IF NOT EXISTS AcctLgrIndex ON \
|
||||
AccountTransactions(LedgerSeq, Account, TransID);",
|
||||
|
||||
// Outcome of each inner transaction of a Batch this node applied while building a
|
||||
// validated ledger. Inner transactions that failed are not in the ledger, so this is
|
||||
// the only record of them. See BatchInnerResult.
|
||||
"CREATE TABLE IF NOT EXISTS BatchInnerResults ( \
|
||||
ParentBatchID CHARACTER(64) NOT NULL, \
|
||||
InnerTxnID CHARACTER(64) NOT NULL, \
|
||||
TxnIndex INTEGER NOT NULL, \
|
||||
LedgerSeq BIGINT UNSIGNED NOT NULL, \
|
||||
TERResult INTEGER NOT NULL, \
|
||||
Applied INTEGER NOT NULL, \
|
||||
PRIMARY KEY (ParentBatchID, InnerTxnID) \
|
||||
);",
|
||||
"CREATE INDEX IF NOT EXISTS BatchInnerLgrIndex ON \
|
||||
BatchInnerResults(LedgerSeq);",
|
||||
|
||||
"END TRANSACTION;"}};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/core/ServiceRegistry.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/BatchInnerResult.h>
|
||||
#include <xrpl/protocol/ErrorCodes.h>
|
||||
#include <xrpl/protocol/LedgerHeader.h>
|
||||
#include <xrpl/protocol/LedgerShortcut.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/TxMeta.h>
|
||||
#include <xrpl/protocol/TxSearched.h>
|
||||
@@ -105,21 +105,6 @@ public:
|
||||
using TxnMetaLedgerType = std::tuple<Blob, Blob, std::uint32_t>;
|
||||
using MetaTxsList = std::vector<TxnMetaLedgerType>;
|
||||
|
||||
using LedgerSequence = uint32_t;
|
||||
using LedgerHash = UInt256;
|
||||
using LedgerSpecifier = std::variant<LedgerRange, LedgerShortcut, LedgerSequence, LedgerHash>;
|
||||
|
||||
struct AccountTxArgs
|
||||
{
|
||||
AccountID account;
|
||||
std::optional<LedgerSpecifier> ledger;
|
||||
bool binary = false;
|
||||
bool forward = false;
|
||||
uint32_t limit = 0;
|
||||
std::optional<AccountTxMarker> marker;
|
||||
std::optional<DelegateFilter> delegate;
|
||||
};
|
||||
|
||||
struct AccountTxResult
|
||||
{
|
||||
std::variant<AccountTxs, MetaTxsList> transactions;
|
||||
@@ -228,6 +213,18 @@ public:
|
||||
virtual std::optional<LedgerIndex>
|
||||
getAccountTransactionsMinLedgerSeq() = 0;
|
||||
|
||||
/**
|
||||
* @brief getBatchInnerResults Returns the recorded inner-transaction outcomes of a
|
||||
* Batch this node applied while building a validated ledger.
|
||||
* @param parentBatchId Hash of the outer Batch transaction.
|
||||
* @param ledgerSeq Ledger sequence the outer Batch transaction is validated in; rows
|
||||
* recorded for any other sequence are not this ledger's and are excluded.
|
||||
* @return Outcomes in RawTransactions order; empty if the tx tables are disabled or
|
||||
* nothing was recorded (a ledger acquired from the network records nothing).
|
||||
*/
|
||||
virtual std::vector<BatchInnerResult>
|
||||
getBatchInnerResults(uint256 const& parentBatchId, LedgerIndex ledgerSeq) = 0;
|
||||
|
||||
/**
|
||||
* @brief deleteTransactionByLedgerSeq Deletes transactions from the ledger
|
||||
* with the given sequence.
|
||||
|
||||
@@ -4,12 +4,14 @@
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/OpenView.h>
|
||||
#include <xrpl/protocol/BatchInnerResult.h>
|
||||
#include <xrpl/protocol/Rules.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/tx/applySteps.h>
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
@@ -142,6 +144,7 @@ enum class ApplyTransactionResult {
|
||||
* Provides more detailed logging and decodes the
|
||||
* correct behavior based on the `TER` type
|
||||
*
|
||||
* @param innerResults If set, receives one entry per inner transaction the outer Batch ran.
|
||||
* @see ApplyTransactionResult
|
||||
*/
|
||||
ApplyTransactionResult
|
||||
@@ -151,6 +154,7 @@ applyTransaction(
|
||||
STTx const& tx,
|
||||
bool retryAssured,
|
||||
ApplyFlags flags,
|
||||
beast::Journal journal);
|
||||
beast::Journal journal,
|
||||
std::vector<BatchInnerResult>* innerResults = nullptr);
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -42,6 +42,14 @@ class ValidMPTIssuance
|
||||
*/
|
||||
bool referenceHoldingMutated_ = false;
|
||||
|
||||
/**
|
||||
* Flags cleared on an existing MPTokenIssuance, except lsfMPTLocked,
|
||||
* which tfMPTUnlock clears legitimately. Every other issuance flag is
|
||||
* fixed at creation or set-once via MPTokenIssuanceSet, so any bit
|
||||
* accumulated here is a bug. Enforced post-fixCleanup3_5_0.
|
||||
*/
|
||||
std::uint32_t issuanceFlagsCleared_ = 0;
|
||||
|
||||
/**
|
||||
* MPTokens and RippleStates deleted during apply. finalize() checks each
|
||||
* holder's AccountRoot to detect vault pseudo-account holdings deleted
|
||||
|
||||
@@ -57,6 +57,12 @@ public:
|
||||
ReadView const& view,
|
||||
beast::Journal const& j) override;
|
||||
|
||||
/**
|
||||
* Inner transaction types that preflight rejects before LendingProtocolV1_2.
|
||||
*
|
||||
* Once the amendment is enabled the list only matters when replaying older
|
||||
* ledgers. Remove it when LendingProtocolV1_2 is retired.
|
||||
*/
|
||||
static constexpr auto kDisabledTxTypes = std::to_array<TxType>({
|
||||
ttVAULT_CREATE,
|
||||
ttVAULT_SET,
|
||||
|
||||
+83
-80
@@ -2,145 +2,148 @@ Detected OS: macos (Darwin arm64)
|
||||
|
||||
Core build tools:
|
||||
✅ cmake
|
||||
cmake version 4.1.2
|
||||
/nix/store/gvabsb4yqb5xsqzqph54rijnn4zpihnp-cmake-4.1.2/bin/cmake
|
||||
cmake version 4.4.3
|
||||
/nix/store/q85csxf4s4shx89zif097h1ql4ax1rrz-cmake-4.4.3/bin/cmake
|
||||
✅ conan
|
||||
Conan version 2.28.1
|
||||
/nix/store/9jiyxmkpwmn6dcqs0765s83riw3l5ail-conan-2.28.1/bin/conan
|
||||
Conan version 2.33.0
|
||||
/nix/store/rp0zf22rx3iwnhxp4d53ncirn4lzg68j-conan-2.33.0/bin/conan
|
||||
✅ git
|
||||
git version 2.54.0
|
||||
/nix/store/a14yxcqvv9x2l9mllgpirzhvz93pgprg-git-2.54.0/bin/git
|
||||
git version 2.55.0
|
||||
/nix/store/gw7c7m0dwca5lg4152x9lpmxpj171bzw-git-2.55.0/bin/git
|
||||
✅ python3
|
||||
Python 3.13.13
|
||||
/nix/store/ygxqin6ydzjfawywqpp5pal8wv6sf5bh-python3-3.13.13/bin/python3.13
|
||||
Python 3.14.7
|
||||
/nix/store/0kcws5blwbdyxqxjd1w8hjipz61xny3k-python3-3.14.7/bin/python3.14
|
||||
|
||||
Development tooling:
|
||||
✅ ccache
|
||||
ccache version 4.13.6
|
||||
/nix/store/57davyvs6p6dkrl3svzwg1ph18wsy4cz-ccache-4.13.6/bin/ccache
|
||||
/nix/store/ydr5nlzxp1djb5256y0zzqa87vz55gsk-ccache-4.13.6/bin/ccache
|
||||
✅ clang
|
||||
clang version 22.1.7
|
||||
/nix/store/192glrb2cldvziyf3378mzjqbzx3ih4g-clang-wrapper-22.1.7/bin/clang
|
||||
✅ clang-22
|
||||
clang version 22.1.7
|
||||
/nix/store/rbap7zqq7mw00fyqa02p5rj7gqjp4w5i-clang-22/bin/clang-22
|
||||
clang version 23.1.0
|
||||
/nix/store/2c16z6akvv65znb8330sl5qpklaf3194-clang-wrapper-23.1.0/bin/clang
|
||||
✅ clang-23
|
||||
clang version 23.1.0
|
||||
/nix/store/vfd0q2395vyn0bsjsh1b4068icy1x9ph-clang-23/bin/clang-23
|
||||
✅ clang++
|
||||
clang version 22.1.7
|
||||
/nix/store/192glrb2cldvziyf3378mzjqbzx3ih4g-clang-wrapper-22.1.7/bin/clang++
|
||||
✅ clang++-22
|
||||
clang version 22.1.7
|
||||
/nix/store/v9haf787f7bcz0mq1sad4bpyx21pj6li-clang++-22/bin/clang++-22
|
||||
clang version 23.1.0
|
||||
/nix/store/2c16z6akvv65znb8330sl5qpklaf3194-clang-wrapper-23.1.0/bin/clang++
|
||||
✅ clang++-23
|
||||
clang version 23.1.0
|
||||
/nix/store/0i6kfnif2v4sqyv5fzfg8a9n09rw9lkp-clang++-23/bin/clang++-23
|
||||
✅ ClangBuildAnalyzer
|
||||
ClangBuildAnalyzer 1.6.0
|
||||
/nix/store/4l50ds9fa2mkvh7wg8qzrlbmjs12sb8l-clangbuildanalyzer-1.6.0/bin/ClangBuildAnalyzer
|
||||
/nix/store/c7jjnw29ra78jdlzjww2m374izhy05n8-clangbuildanalyzer-1.6.0/bin/ClangBuildAnalyzer
|
||||
✅ curl
|
||||
curl 8.20.0 (aarch64-apple-darwin25.3.0) libcurl/8.20.0 OpenSSL/3.6.2 zlib/1.3.2 libssh2/1.11.1 nghttp2/1.69.0 mit-krb5/1.22.1
|
||||
/nix/store/kclq0czaxvsgh4ym9ld7b6iwy50l1snk-curl-8.20.0-bin/bin/curl
|
||||
curl 8.22.0 (aarch64-apple-darwin25.6.0) libcurl/8.22.0 OpenSSL/3.5.8 zlib/1.3.2 libssh2/1.11.1 nghttp2/1.70.0 mit-krb5/1.22.2
|
||||
/nix/store/pq1h17abqy6q5y6k5dn9qj0fwb89flba-curl-8.22.0-bin/bin/curl
|
||||
✅ file
|
||||
file-5.47
|
||||
/nix/store/dax63li7wwcbqxxkkgzc4g2rx7d4w86x-file-5.47/bin/file
|
||||
file-5.48
|
||||
/nix/store/y44vpziwbzfm8020dzlqqbybyyy7lb6g-file-5.48/bin/file
|
||||
✅ jq
|
||||
jq-1.8.2
|
||||
/nix/store/wm7v59a2lhj1w2k8jz53spcs1iizm9lv-jq-1.8.2-bin/bin/jq
|
||||
✅ less
|
||||
less 692 (PCRE2 regular expressions)
|
||||
/nix/store/lvr16y75r1pdxpdv0aph5ak2yd0hkvqm-less-692/bin/less
|
||||
less 710 (PCRE2 regular expressions)
|
||||
/nix/store/3yn3dx5fwg5i0inqqzwnxq7f6lwhpjqa-less-710/bin/less
|
||||
✅ make
|
||||
GNU Make 4.4.1
|
||||
/nix/store/8wwiw8pwyhrkzyq28hqzxfl4z84lks81-gnumake-4.4.1/bin/make
|
||||
/nix/store/8l4wgxmhdxfvc9g18y76bnxl1ghgcp7n-gnumake-4.4.1/bin/make
|
||||
✅ netstat
|
||||
present
|
||||
/nix/store/qsd1kzqb0ahrk433vmyl245gp623j19s-network_cmds-730.80.3/bin/netstat
|
||||
/nix/store/57fqvgdav8lrmzndkwm7qrgksl01yj23-network_cmds-741.100.2/bin/netstat
|
||||
✅ ninja
|
||||
1.13.2
|
||||
/nix/store/bqykhrblarkj4fl0hz2mf8ngwfv6x6bz-ninja-1.13.2/bin/ninja
|
||||
/nix/store/infm4vlx71q83bqz0k559w1gn4b0rq4i-ninja-1.13.2/bin/ninja
|
||||
✅ perl
|
||||
v5.42.0
|
||||
/nix/store/js13ri9fvm0ajk1fpd3acigys2a9whdv-perl-5.42.0/bin/perl
|
||||
v5.42.3
|
||||
/nix/store/7rvr6qjfzk2ybzn1imjw6c6qq1n1a9cr-perl-5.42.3/bin/perl
|
||||
✅ pkg-config
|
||||
0.29.2
|
||||
/nix/store/lzrwr375jqhhbca116kja96xf1md83l8-pkg-config-wrapper-0.29.2/bin/pkg-config
|
||||
/nix/store/nhgylvj98klqggy54h7gbc3b2ia4a00y-pkg-config-wrapper-0.29.2/bin/pkg-config
|
||||
✅ vim
|
||||
VIM - Vi IMproved 9.2 (2026 Feb 14, compiled Jan 01 1980 00:00:00)
|
||||
/nix/store/6vbkykg92w603c0sw3mkk7p7mfaawbns-vim-9.2.0389/bin/vim
|
||||
/nix/store/akaa9kqm3b8rlkv61kvsqr2l2j70rvxb-vim-9.2.1001/bin/vim
|
||||
✅ zip
|
||||
Zip 3.0
|
||||
/nix/store/z6ph729vcakbvz3wh8ln1wk6mi06w487-zip-3.0/bin/zip
|
||||
/nix/store/dvawr36npnad4xa6dxi2ss0fw44aa82r-zip-3.0/bin/zip
|
||||
✅ clang-apply-replacements
|
||||
clang-apply-replacements version 22.1.7
|
||||
/nix/store/vzyyjf3cm1hbj9wcr2qcb66x6j98zpy7-clang-tools-22.1.7/bin/clang-apply-replacements
|
||||
✅ clang-apply-replacements-22
|
||||
clang-apply-replacements version 22.1.7
|
||||
/nix/store/m3ii69rca4077lf4wlk7m3jcag1fs577-clang-apply-replacements-22/bin/clang-apply-replacements-22
|
||||
clang-apply-replacements version 23.1.0
|
||||
/nix/store/mva6ngy45m9in3m2lsfccj1y0z6n1f5s-clang-tools-23.1.0/bin/clang-apply-replacements
|
||||
✅ clang-apply-replacements-23
|
||||
clang-apply-replacements version 23.1.0
|
||||
/nix/store/872k02mqr21hxwcpfwaqkxljfj60aak0-clang-apply-replacements-23/bin/clang-apply-replacements-23
|
||||
✅ clang-format
|
||||
clang-format version 22.1.7
|
||||
/nix/store/vzyyjf3cm1hbj9wcr2qcb66x6j98zpy7-clang-tools-22.1.7/bin/clang-format
|
||||
✅ clang-format-22
|
||||
clang-format version 22.1.7
|
||||
/nix/store/4fawqy6ngqcsqd2ygyyzm93q0xy3f5gs-clang-format-22/bin/clang-format-22
|
||||
clang-format version 23.1.0
|
||||
/nix/store/mva6ngy45m9in3m2lsfccj1y0z6n1f5s-clang-tools-23.1.0/bin/clang-format
|
||||
✅ clang-format-23
|
||||
clang-format version 23.1.0
|
||||
/nix/store/wf30x6lcya44n2y1fagd6q7x7p4gyl6f-clang-format-23/bin/clang-format-23
|
||||
✅ clang-tidy
|
||||
LLVM version 22.1.7
|
||||
/nix/store/vzyyjf3cm1hbj9wcr2qcb66x6j98zpy7-clang-tools-22.1.7/bin/clang-tidy
|
||||
✅ clang-tidy-22
|
||||
LLVM version 22.1.7
|
||||
/nix/store/jqw4280saixaxxihwdba9ldm2fsm6dr3-clang-tidy-22/bin/clang-tidy-22
|
||||
LLVM version 23.1.0
|
||||
/nix/store/mva6ngy45m9in3m2lsfccj1y0z6n1f5s-clang-tools-23.1.0/bin/clang-tidy
|
||||
✅ clang-tidy-23
|
||||
LLVM version 23.1.0
|
||||
/nix/store/kgx00aass66g02p1s4gqqf65kq39m4ci-clang-tidy-23/bin/clang-tidy-23
|
||||
✅ dot
|
||||
dot - graphviz version 12.2.1 (0)
|
||||
/nix/store/ijb4fbnqa6wzlpqnhb6q9knqpf7qqn5z-graphviz-12.2.1/bin/dot
|
||||
dot - graphviz version 15.1.1 (0)
|
||||
/nix/store/mc99a6bpbk2waym2cnw6ifmxli4ndb4x-graphviz-15.1.1/bin/dot
|
||||
✅ doxygen
|
||||
1.16.1
|
||||
/nix/store/kbryjdpq9jizjb0ws0nzbf2h2ymbdiwm-doxygen-1.16.1/bin/doxygen
|
||||
1.17.0
|
||||
/nix/store/kz43yaj0wg6fx6cbrcmi2442z1ramy0c-doxygen-1.17.0/bin/doxygen
|
||||
✅ gcovr
|
||||
gcovr 8.4
|
||||
/nix/store/wn8jiyh9p0bybs96s4163qp3k8vfmczx-python3.13-gcovr-8.4/bin/gcovr
|
||||
/nix/store/czj1vxsizrbba280gg22cys40qxkqjgr-python3.14-gcovr-8.4/bin/gcovr
|
||||
✅ gh
|
||||
gh version 2.94.0 (nixpkgs)
|
||||
/nix/store/fhnpw0hs0gjms1ha6ap02jq7rx13gkbp-gh-2.94.0/bin/gh
|
||||
gh version 2.102.0 (2026-09-30)
|
||||
/nix/store/s8bahl6zssvry93ihivfmk1l6c58byrr-gh-2.102.0/bin/gh
|
||||
✅ git-cliff
|
||||
git-cliff 2.13.1
|
||||
/nix/store/cy0wwhgxa7yvrz97zydbq6sqmixc90fq-git-cliff-2.13.1/bin/git-cliff
|
||||
git-cliff 2.14.2
|
||||
/nix/store/k4m5yk0qb7hfslwsq1zjajz1d94j12bj-git-cliff-2.14.2/bin/git-cliff
|
||||
✅ git-lfs
|
||||
git-lfs/3.7.1 (3.7.1; darwin arm64; go 1.26.3)
|
||||
/nix/store/k9r7zjfjplqa4d5s71cqvf2iv73jd9mc-git-lfs-3.7.1/bin/git-lfs
|
||||
git-lfs/3.8.0 (3.8.0; darwin arm64; go 1.26.8)
|
||||
/nix/store/wzixjr3yb40a13m2q654hm3bdjn6sal3-git-lfs-3.8.0/bin/git-lfs
|
||||
✅ gpg
|
||||
gpg (GnuPG) 2.4.9
|
||||
/nix/store/cgh6iwzz5jgx9z5whka4vgj210i6npc6-gnupg-2.4.9/bin/gpg
|
||||
/nix/store/0ldfwnwx385i5kh7cyvxx4mkvgjhs5rh-gnupg-2.4.9/bin/gpg
|
||||
✅ pre-commit
|
||||
pre-commit 4.5.1
|
||||
/nix/store/z3cca68620w0w10f090szgzdnmh1waf2-pre-commit-4.5.1/bin/pre-commit
|
||||
pre-commit 4.6.2
|
||||
/nix/store/ivh54xypg9qvd3ha9lis0i8p4nkf8n7a-pre-commit-4.6.2/bin/pre-commit
|
||||
✅ run-clang-tidy
|
||||
usage: run-clang-tidy [-h] [-allow-enabling-alpha-checkers]
|
||||
/nix/store/4x28x911z2f9y7adqlh3qspp4a16dig7-run-clang-tidy/bin/run-clang-tidy
|
||||
✅ run-clang-tidy-22
|
||||
/nix/store/zjbzr154xan5xg9pmjiv0zkwklyb94wv-run-clang-tidy/bin/run-clang-tidy
|
||||
✅ run-clang-tidy-23
|
||||
usage: run-clang-tidy [-h] [-allow-enabling-alpha-checkers]
|
||||
/nix/store/x8iymrh76sk5q91ryg5pa7i32s6gfh34-run-clang-tidy-22/bin/run-clang-tidy-22
|
||||
/nix/store/svhfrpgx2j581brsqmkpav9b286v22yy-run-clang-tidy-23/bin/run-clang-tidy-23
|
||||
|
||||
Rust toolchain:
|
||||
✅ cargo
|
||||
cargo 1.97.1 (c980f4866 2026-06-30)
|
||||
/nix/store/bnfk1sl4s9angb0vj1cj9a5y5zvqinwy-rust-minimal-1.97.1/bin/cargo
|
||||
/nix/store/gklji8yq8yrx1k039c10kwz60vqf2dxj-rust-minimal-1.97.1/bin/cargo
|
||||
✅ cargo-audit
|
||||
cargo-audit-audit 0.22.1
|
||||
/nix/store/snwkga2f5gyf404h7mmp9wriwxb8v65f-cargo-audit-0.22.1/bin/cargo-audit
|
||||
cargo-audit-audit 0.22.2
|
||||
/nix/store/7jgrwj557l91940aj0ag4i924ima1cqn-cargo-audit-0.22.2/bin/cargo-audit
|
||||
✅ cargo-llvm-cov
|
||||
cargo-llvm-cov 0.8.5
|
||||
/nix/store/fpiqdh91gwyxalqp409ynm0s0g086w7w-cargo-llvm-cov-0.8.5/bin/cargo-llvm-cov
|
||||
cargo-llvm-cov 0.9.0
|
||||
/nix/store/5q1h285z8zzkw2qxig9a0b1avcsk1cf0-cargo-llvm-cov-0.9.0/bin/cargo-llvm-cov
|
||||
✅ cargo-nextest
|
||||
cargo-nextest 0.9.137
|
||||
/nix/store/ylz7m947mhkgsp6i7611id3s3gcd58nq-cargo-nextest-0.9.137/bin/cargo-nextest
|
||||
cargo-nextest 0.9.146
|
||||
/nix/store/69v3hx3k597hcxd2zy7yad7fg0aw5yxd-cargo-nextest-0.9.146/bin/cargo-nextest
|
||||
✅ clippy-driver
|
||||
clippy 0.1.97 (8bab26f4f6 2026-07-14)
|
||||
/nix/store/bnfk1sl4s9angb0vj1cj9a5y5zvqinwy-rust-minimal-1.97.1/bin/clippy-driver
|
||||
/nix/store/gklji8yq8yrx1k039c10kwz60vqf2dxj-rust-minimal-1.97.1/bin/clippy-driver
|
||||
✅ rust-analyzer
|
||||
rust-analyzer 1.97.1 (8bab26f4 2026-07-14)
|
||||
/nix/store/j6apc5pmd0giy15da9p650r8zklslmvi-rust-analyzer-preview-1.97.1-aarch64-apple-darwin/bin/rust-analyzer
|
||||
/nix/store/30rbyk8gx7v53fxqhs4pp0fvxfjycspr-rust-analyzer-preview-1.97.1-aarch64-apple-darwin/bin/rust-analyzer
|
||||
✅ rust-nightly
|
||||
rustc 1.99.0-nightly (87e5904f5 2026-07-20)
|
||||
/nix/store/fqpjz4l0nsnji8b2pz57mnj0akbp6hcl-rust-nightly/bin/rust-nightly
|
||||
rustc 1.101.0-nightly (282215592 2026-10-04)
|
||||
/nix/store/a9552rf3pylcnkijc1z0hf1lcnv50djq-rust-nightly/bin/rust-nightly
|
||||
✅ rustc
|
||||
rustc 1.97.1 (8bab26f4f 2026-07-14)
|
||||
/nix/store/bnfk1sl4s9angb0vj1cj9a5y5zvqinwy-rust-minimal-1.97.1/bin/rustc
|
||||
/nix/store/gklji8yq8yrx1k039c10kwz60vqf2dxj-rust-minimal-1.97.1/bin/rustc
|
||||
✅ rustfmt
|
||||
rustfmt 1.9.0-stable (8bab26f4f6 2026-07-14)
|
||||
/nix/store/5ymwgr9jqjz7zzbmj0j5vqbwcd3kp0vm-rustfmt-preview-1.97.1-aarch64-apple-darwin/bin/rustfmt
|
||||
/nix/store/0jmdipyn8266j9kqx2xxm2qkxlrlrjfc-rustfmt-preview-1.97.1-aarch64-apple-darwin/bin/rustfmt
|
||||
|
||||
Skipping git-over-HTTPS check (CHECK_TOOLS_SKIP_CLONE is set).
|
||||
|
||||
✅ All 45 checked tools are present and runnable.
|
||||
✅ All 46 checked tools are present and runnable.
|
||||
@@ -2,173 +2,176 @@ Detected OS: linux (Linux x86_64)
|
||||
|
||||
Core build tools:
|
||||
✅ cmake
|
||||
cmake version 4.1.2
|
||||
/nix/store/r9941n32g4wyvggz2703dlplbdq8a6rd-cmake-4.1.2/bin/cmake
|
||||
cmake version 4.4.3
|
||||
/nix/store/jcvvpih1046akxcwh9hdiak5q24jqcf0-cmake-4.4.3/bin/cmake
|
||||
✅ conan
|
||||
Conan version 2.28.1
|
||||
/nix/store/lxny9y4jvjdws7hgz1mygvb7hjrpmna5-conan-2.28.1/bin/conan
|
||||
Conan version 2.33.0
|
||||
/nix/store/b4lnzrqc60glhl9al1nakfc0vd20kdna-conan-2.33.0/bin/conan
|
||||
✅ git
|
||||
git version 2.54.0
|
||||
/nix/store/bcnisk3ydfgv26v2gw3zlky24g00yww2-git-2.54.0/bin/git
|
||||
git version 2.55.0
|
||||
/nix/store/lg46w7hrjx7kylsh6645c9l975i8h43b-git-2.55.0/bin/git
|
||||
✅ python3
|
||||
Python 3.13.13
|
||||
/nix/store/60m4rxhg2fldqaak400c0lry96ijrzqn-python3-3.13.13/bin/python3.13
|
||||
Python 3.14.7
|
||||
/nix/store/lb41b0anx1f98y9y5s9mdv97gjgsq740-python3-3.14.7/bin/python3.14
|
||||
|
||||
Development tooling:
|
||||
✅ ccache
|
||||
ccache version 4.13.6
|
||||
/nix/store/c9wwl7s5i6rsfwvf4v0xbbmzx5m6jgfr-ccache-4.13.6/bin/ccache
|
||||
/nix/store/p1a1s700dqsw1b3b8d2ba64nqw68zgpy-ccache-4.13.6/bin/ccache
|
||||
✅ clang
|
||||
clang version 22.1.7
|
||||
/nix/store/ff0hrp9r9i3pa5arkdw0sgmzp8d576qi-clang-wrapper-22.1.7/bin/clang
|
||||
✅ clang-22
|
||||
clang version 22.1.7
|
||||
/nix/store/dagc2rq44gfbr7w7yvvqca3yqpc9gqbq-clang-22/bin/clang-22
|
||||
clang version 23.1.0
|
||||
/nix/store/mlqh1xrp0zi3i5g9b0zjcz6zlbbzdgb8-clang-wrapper-23.1.0/bin/clang
|
||||
✅ clang-23
|
||||
clang version 23.1.0
|
||||
/nix/store/xgi7q1bady9dk89zvjmiicjzzp1gvm7x-clang-23/bin/clang-23
|
||||
✅ clang++
|
||||
clang version 22.1.7
|
||||
/nix/store/ff0hrp9r9i3pa5arkdw0sgmzp8d576qi-clang-wrapper-22.1.7/bin/clang++
|
||||
✅ clang++-22
|
||||
clang version 22.1.7
|
||||
/nix/store/l5m8clin1npl605wdkd8mr18ggxww3z4-clang++-22/bin/clang++-22
|
||||
clang version 23.1.0
|
||||
/nix/store/mlqh1xrp0zi3i5g9b0zjcz6zlbbzdgb8-clang-wrapper-23.1.0/bin/clang++
|
||||
✅ clang++-23
|
||||
clang version 23.1.0
|
||||
/nix/store/4slsaz29v72xgc4g36cp94f0p41a81pv-clang++-23/bin/clang++-23
|
||||
✅ ClangBuildAnalyzer
|
||||
ClangBuildAnalyzer 1.6.0
|
||||
/nix/store/bshlmn8fqw55nsnm581xqlfbahfkykxx-clangbuildanalyzer-1.6.0/bin/ClangBuildAnalyzer
|
||||
/nix/store/gvx8im1c89vspwb8ixa91lr1dagbi241-clangbuildanalyzer-1.6.0/bin/ClangBuildAnalyzer
|
||||
✅ curl
|
||||
curl 8.20.0 (x86_64-pc-linux-gnu) libcurl/8.20.0 OpenSSL/3.6.2 zlib/1.3.2 libssh2/1.11.1 nghttp2/1.69.0 mit-krb5/1.22.1
|
||||
/nix/store/zbwymrp4lcfjc4kkk0n4779v0kjjz58z-curl-8.20.0-bin/bin/curl
|
||||
curl 8.22.0 (x86_64-pc-linux-gnu) libcurl/8.22.0 OpenSSL/3.5.8 zlib/1.3.2 libssh2/1.11.1 nghttp2/1.70.0 mit-krb5/1.22.2
|
||||
/nix/store/305w9ipyg497mz9dq31abdf61qk58k29-curl-8.22.0-bin/bin/curl
|
||||
✅ file
|
||||
file-5.47
|
||||
/nix/store/bizyfqdw0h67wzqmp10knmf9s2pqahdb-file-5.47/bin/file
|
||||
file-5.48
|
||||
/nix/store/g0fbnccaan8q0rxlw43xnbyb90i94vlc-file-5.48/bin/file
|
||||
✅ jq
|
||||
jq-1.8.2
|
||||
/nix/store/m3y7hqmy20nd1ps6qy8410a326255bf0-jq-1.8.2-bin/bin/jq
|
||||
✅ less
|
||||
less 692 (PCRE2 regular expressions)
|
||||
/nix/store/c6bacbn93qg4a7g9n4czww8rg24dvysr-less-692/bin/less
|
||||
less 710 (PCRE2 regular expressions)
|
||||
/nix/store/zi4d0awnc6crz18s177bv9y2yz9al3lq-less-710/bin/less
|
||||
✅ make
|
||||
GNU Make 4.4.1
|
||||
/nix/store/d3bwqm6bymhy3pdgbvf7vxjqfp31m3j1-gnumake-4.4.1/bin/make
|
||||
/nix/store/z565rgmn9vccv2h0g87z725pw610jmm8-gnumake-4.4.1/bin/make
|
||||
✅ netstat
|
||||
net-tools 2.10
|
||||
/nix/store/jmyzqvgflnswmws7rnxx6g3zbj680xvd-net-tools-2.10/bin/netstat
|
||||
/nix/store/9i6j9lrsliyz1ww2f8xx18z36wc9n38a-net-tools-2.10/bin/netstat
|
||||
✅ ninja
|
||||
1.13.2
|
||||
/nix/store/7a235m7crqbb4h49sak20fqxpw3n7hr0-ninja-1.13.2/bin/ninja
|
||||
/nix/store/4nsdla28c4177ssp0hlyv5l2yzj8wcba-ninja-1.13.2/bin/ninja
|
||||
✅ perl
|
||||
v5.42.0
|
||||
/nix/store/6plwsm6pkq79yjv4xvy8csk2pd4hzr67-perl-5.42.0/bin/perl
|
||||
v5.42.3
|
||||
/nix/store/8g4dmf7l8q1mf9w4wfmlcl8gvdgmhb8v-perl-5.42.3/bin/perl
|
||||
✅ pkg-config
|
||||
0.29.2
|
||||
/nix/store/1m05k7xgfnw6jc21xxk5681ni3ar97wf-pkg-config-wrapper-0.29.2/bin/pkg-config
|
||||
/nix/store/b9izmrfjp0qnnyahdwwrzdwi87s1h179-pkg-config-wrapper-0.29.2/bin/pkg-config
|
||||
✅ vim
|
||||
VIM - Vi IMproved 9.2 (2026 Feb 14, compiled Jan 01 1980 00:00:00)
|
||||
/nix/store/hvyqx52g4g2fxhgpans3fksjj6lmlyaw-vim-9.2.0389/bin/vim
|
||||
/nix/store/9swnd5pf8p8w1gwcbhdpm2yx705kbp38-vim-9.2.1001/bin/vim
|
||||
✅ zip
|
||||
Zip 3.0
|
||||
/nix/store/qnd2ag67hrjj0b6vbmisdshf50r6s72n-zip-3.0/bin/zip
|
||||
/nix/store/5ypvkry3qra47dylc9kfk36r1q9vha80-zip-3.0/bin/zip
|
||||
✅ clang-apply-replacements
|
||||
clang-apply-replacements version 22.1.7
|
||||
/nix/store/4zp1rjpj2xijrv4kqpwsy3ixwb2r6nlk-clang-tools-22.1.7/bin/clang-apply-replacements
|
||||
✅ clang-apply-replacements-22
|
||||
clang-apply-replacements version 22.1.7
|
||||
/nix/store/py2wihg0a96qcppv4hjmww547xabr0fb-clang-apply-replacements-22/bin/clang-apply-replacements-22
|
||||
clang-apply-replacements version 23.1.0
|
||||
/nix/store/ipnj8b3s6f00ngi26fi7p5h9lxv6a3s3-clang-tools-23.1.0/bin/clang-apply-replacements
|
||||
✅ clang-apply-replacements-23
|
||||
clang-apply-replacements version 23.1.0
|
||||
/nix/store/lbyc9hrkkymybah7b6n8c7sijlgldfz0-clang-apply-replacements-23/bin/clang-apply-replacements-23
|
||||
✅ clang-format
|
||||
clang-format version 22.1.7
|
||||
/nix/store/4zp1rjpj2xijrv4kqpwsy3ixwb2r6nlk-clang-tools-22.1.7/bin/clang-format
|
||||
✅ clang-format-22
|
||||
clang-format version 22.1.7
|
||||
/nix/store/kz820ccifjlwqnwqjsx7kbiajrgsmbrh-clang-format-22/bin/clang-format-22
|
||||
clang-format version 23.1.0
|
||||
/nix/store/ipnj8b3s6f00ngi26fi7p5h9lxv6a3s3-clang-tools-23.1.0/bin/clang-format
|
||||
✅ clang-format-23
|
||||
clang-format version 23.1.0
|
||||
/nix/store/40w351b7i7aqhpl5wmicfnp2ij8k12g9-clang-format-23/bin/clang-format-23
|
||||
✅ clang-tidy
|
||||
LLVM version 22.1.7
|
||||
/nix/store/4zp1rjpj2xijrv4kqpwsy3ixwb2r6nlk-clang-tools-22.1.7/bin/clang-tidy
|
||||
✅ clang-tidy-22
|
||||
LLVM version 22.1.7
|
||||
/nix/store/gdrkvpw846lkyzh8y9p3zx50g6ml2v84-clang-tidy-22/bin/clang-tidy-22
|
||||
LLVM version 23.1.0
|
||||
/nix/store/ipnj8b3s6f00ngi26fi7p5h9lxv6a3s3-clang-tools-23.1.0/bin/clang-tidy
|
||||
✅ clang-tidy-23
|
||||
LLVM version 23.1.0
|
||||
/nix/store/40rj38k5ygq7sawkiv0cdaljclbspwz9-clang-tidy-23/bin/clang-tidy-23
|
||||
✅ dot
|
||||
dot - graphviz version 12.2.1 (0)
|
||||
/nix/store/12rgns2296s4qcja778gvcbx61z77rc4-graphviz-12.2.1/bin/dot
|
||||
dot - graphviz version 15.1.1 (0)
|
||||
/nix/store/f7r47nc0d13xa4c2xaaxa6dp3pzm9sp1-graphviz-15.1.1/bin/dot
|
||||
✅ doxygen
|
||||
1.16.1
|
||||
/nix/store/k0vzr5lvgq1byraknzwvk51wcgpnsrkh-doxygen-1.16.1/bin/doxygen
|
||||
1.17.0
|
||||
/nix/store/q6yx2nsimj9xmckmgw4br2cnapbgnqpk-doxygen-1.17.0/bin/doxygen
|
||||
✅ gcovr
|
||||
gcovr 8.4
|
||||
/nix/store/iyzi7fpyclqrha054adnizvif02lg49x-python3.13-gcovr-8.4/bin/gcovr
|
||||
/nix/store/shx7mfl8n4kx11icvwyj5x7cmf6z5ahq-python3.14-gcovr-8.4/bin/gcovr
|
||||
✅ gh
|
||||
gh version 2.94.0 (nixpkgs)
|
||||
/nix/store/pidh15szlsb1vc41xdsa3xbdghdazvby-gh-2.94.0/bin/gh
|
||||
gh version 2.102.0 (2026-09-30)
|
||||
/nix/store/66h5dr9v94x04myh2kabx06rq9763jfc-gh-2.102.0/bin/gh
|
||||
✅ git-cliff
|
||||
git-cliff 2.13.1
|
||||
/nix/store/1q851fs62shgjhc03fxxdkpzxdjg7k11-git-cliff-2.13.1/bin/git-cliff
|
||||
git-cliff 2.14.2
|
||||
/nix/store/sl19b42d69b3w2vb9pxp6jbqk841z8vj-git-cliff-2.14.2/bin/git-cliff
|
||||
✅ git-lfs
|
||||
git-lfs/3.7.1 (3.7.1; linux amd64; go 1.26.3)
|
||||
/nix/store/6ljwpal7b1756708m33vj0crpral7mvl-git-lfs-3.7.1/bin/git-lfs
|
||||
git-lfs/3.8.0 (3.8.0; linux amd64; go 1.26.8)
|
||||
/nix/store/rp9ld1f6h83xmf2rb5fpib5wmr46fy9f-git-lfs-3.8.0/bin/git-lfs
|
||||
✅ gpg
|
||||
gpg (GnuPG) 2.4.9
|
||||
/nix/store/wx7vk8babxkgy813r70yc67vcwnmagbx-gnupg-2.4.9/bin/gpg
|
||||
/nix/store/8gm9aq3sspqmvvh7c0x7djcivj2x7yvn-gnupg-2.4.9/bin/gpg
|
||||
✅ pre-commit
|
||||
pre-commit 4.5.1
|
||||
/nix/store/bj6i9vl34cij5h0r165y40hrjqak0bmz-pre-commit-4.5.1/bin/pre-commit
|
||||
pre-commit 4.6.2
|
||||
/nix/store/6blf72f7sdmlrqjggxvk5ij4cxb7fk4c-pre-commit-4.6.2/bin/pre-commit
|
||||
✅ run-clang-tidy
|
||||
usage: run-clang-tidy [-h] [-allow-enabling-alpha-checkers]
|
||||
/nix/store/sbg911hs9dbclrzlp04br3iyfpgnaj6r-run-clang-tidy/bin/run-clang-tidy
|
||||
✅ run-clang-tidy-22
|
||||
/nix/store/y7isn3dr8bildnp78vk5nhcav63g80dz-run-clang-tidy/bin/run-clang-tidy
|
||||
✅ run-clang-tidy-23
|
||||
usage: run-clang-tidy [-h] [-allow-enabling-alpha-checkers]
|
||||
/nix/store/n8yak1ap308gvi7gmrniw0ybsx80fjws-run-clang-tidy-22/bin/run-clang-tidy-22
|
||||
/nix/store/pm5a8465hs9zb5i116b39i0y3xfpw6n3-run-clang-tidy-23/bin/run-clang-tidy-23
|
||||
|
||||
Rust toolchain:
|
||||
✅ cargo
|
||||
cargo 1.97.1 (c980f4866 2026-06-30)
|
||||
/nix/store/88abzp43ywyzql1rhf8jh5aj5n5j7xzr-cargo-1.97.1-x86_64-unknown-linux-gnu/bin/cargo
|
||||
/nix/store/60b0cz400q34wj2gzav5a41i7y6jzlw3-cargo-1.97.1-x86_64-unknown-linux-gnu/bin/cargo
|
||||
✅ cargo-audit
|
||||
cargo-audit-audit 0.22.1
|
||||
/nix/store/2w9if868piw98xz057sz97jnjvf7hnvf-cargo-audit-0.22.1/bin/cargo-audit
|
||||
cargo-audit-audit 0.22.2
|
||||
/nix/store/kksgynfwir3pfii9qkbj3gcc61an0d4s-cargo-audit-0.22.2/bin/cargo-audit
|
||||
✅ cargo-llvm-cov
|
||||
cargo-llvm-cov 0.8.5
|
||||
/nix/store/jjpdf1l6izz6607a346ykra9sndzaw7h-cargo-llvm-cov-0.8.5/bin/cargo-llvm-cov
|
||||
cargo-llvm-cov 0.9.0
|
||||
/nix/store/4y8qxx0mlm22hnxlw2cmbcwpxispw02h-cargo-llvm-cov-0.9.0/bin/cargo-llvm-cov
|
||||
✅ cargo-nextest
|
||||
cargo-nextest 0.9.137
|
||||
/nix/store/jhkr7gwyrchkml33gyns9cy0yn7b57qc-cargo-nextest-0.9.137/bin/cargo-nextest
|
||||
cargo-nextest 0.9.146
|
||||
/nix/store/q0anzqrazmdr8n1049ca28098q6lv2y8-cargo-nextest-0.9.146/bin/cargo-nextest
|
||||
✅ clippy-driver
|
||||
clippy 0.1.97 (8bab26f4f6 2026-07-14)
|
||||
/nix/store/40d3mzka7r1ps71l0yv2fs6616nbw85m-rust-minimal-1.97.1/bin/clippy-driver
|
||||
/nix/store/kk0kqdc4f7am21yp30rr8jcrnib5xqzy-rust-minimal-1.97.1/bin/clippy-driver
|
||||
✅ rust-analyzer
|
||||
rust-analyzer 1.97.1 (8bab26f 2026-07-14)
|
||||
/nix/store/lr3m97p3hx1k22a7c44pb0wa7rbayhfi-rust-analyzer-preview-1.97.1-x86_64-unknown-linux-gnu/bin/rust-analyzer
|
||||
/nix/store/4mglk7qw9jqbv5nwlx9ahdnpcn5sz70r-rust-analyzer-preview-1.97.1-x86_64-unknown-linux-gnu/bin/rust-analyzer
|
||||
✅ rust-nightly
|
||||
rustc 1.99.0-nightly (87e5904f5 2026-07-20)
|
||||
/nix/store/j7kf7a5h4xypzp6x1skg4dsdx2k4fwb3-rust-nightly/bin/rust-nightly
|
||||
rustc 1.101.0-nightly (282215592 2026-10-04)
|
||||
/nix/store/qwyzkvba3f9569wjj45b4iyb7nz7qbnr-rust-nightly/bin/rust-nightly
|
||||
✅ rustc
|
||||
rustc 1.97.1 (8bab26f4f 2026-07-14)
|
||||
/nix/store/40d3mzka7r1ps71l0yv2fs6616nbw85m-rust-minimal-1.97.1/bin/rustc
|
||||
/nix/store/kk0kqdc4f7am21yp30rr8jcrnib5xqzy-rust-minimal-1.97.1/bin/rustc
|
||||
✅ rustfmt
|
||||
rustfmt 1.9.0-stable (8bab26f4f6 2026-07-14)
|
||||
/nix/store/6f1icmb2za20kxn30pgmbv5jq9fnbf4z-rustfmt-preview-1.97.1-x86_64-unknown-linux-gnu/bin/rustfmt
|
||||
/nix/store/y5gf8a3hix7pnwzdwcfpf3acqhzv3xy6-rustfmt-preview-1.97.1-x86_64-unknown-linux-gnu/bin/rustfmt
|
||||
|
||||
GCC toolchain:
|
||||
✅ gcc
|
||||
gcc (GCC) 15.2.0
|
||||
/nix/store/3dd6y3pq00i3r85l45jvz63wjya403nl-gcc-wrapper-15.2.0/bin/gcc
|
||||
gcc (GCC) 15.3.0
|
||||
/nix/store/bslmkbaiymxxdc6lwcdb1qyw745nds5j-gcc-wrapper-15.3.0/bin/gcc
|
||||
✅ gcc-15
|
||||
gcc (GCC) 15.2.0
|
||||
/nix/store/d6iri2s6bzqq5ac3fg25j6hgnn1lz44f-gcc-15/bin/gcc-15
|
||||
gcc (GCC) 15.3.0
|
||||
/nix/store/h6f2qc8kgwf7r81gwb10csvpjqfc2404-gcc-15/bin/gcc-15
|
||||
✅ g++
|
||||
g++ (GCC) 15.2.0
|
||||
/nix/store/3dd6y3pq00i3r85l45jvz63wjya403nl-gcc-wrapper-15.2.0/bin/g++
|
||||
g++ (GCC) 15.3.0
|
||||
/nix/store/bslmkbaiymxxdc6lwcdb1qyw745nds5j-gcc-wrapper-15.3.0/bin/g++
|
||||
✅ g++-15
|
||||
g++ (GCC) 15.2.0
|
||||
/nix/store/gm3msmmxq055lm9gprkfjj9d2gdz1mpg-g++-15/bin/g++-15
|
||||
g++ (GCC) 15.3.0
|
||||
/nix/store/jwncv76v61ld9l99v0b1dkmf810if65s-g++-15/bin/g++-15
|
||||
✅ cpp
|
||||
cpp (GCC) 15.2.0
|
||||
/nix/store/3dd6y3pq00i3r85l45jvz63wjya403nl-gcc-wrapper-15.2.0/bin/cpp
|
||||
cpp (GCC) 15.3.0
|
||||
/nix/store/bslmkbaiymxxdc6lwcdb1qyw745nds5j-gcc-wrapper-15.3.0/bin/cpp
|
||||
✅ cpp-15
|
||||
cpp (GCC) 15.2.0
|
||||
/nix/store/bn3gmn0m7g4gn2i0yml46fljc7mghiq5-cpp-15/bin/cpp-15
|
||||
cpp (GCC) 15.3.0
|
||||
/nix/store/jmi558xj8v7lz2fm6wrradh40dp48zx9-cpp-15/bin/cpp-15
|
||||
✅ gcov
|
||||
gcov (GCC) 15.2.0
|
||||
/nix/store/xvv5sm5i8x0ks6ypfkzl7c4j9srnxz7k-gcc-15.2.0/bin/gcov
|
||||
gcov (GCC) 15.3.0
|
||||
/nix/store/n2ab7kf7mm84hna35x463mizcy52pf1h-gcc-15.3.0/bin/gcov
|
||||
|
||||
Mold:
|
||||
✅ mold
|
||||
mold 2.41.0 (compatible with GNU ld)
|
||||
/nix/store/2w6fpgxjzzyqmd25wzplm23dfa49a0p2-mold-unwrapped-wrapper-2.41.0/bin/mold
|
||||
mold 2.42.1 (compatible with GNU ld)
|
||||
/nix/store/9dcddrfxlfl9n3wfibi5jkmp22bjkvkh-mold-unwrapped-wrapper-2.42.1/bin/mold
|
||||
|
||||
Skipping git-over-HTTPS check (CHECK_TOOLS_SKIP_CLONE is set).
|
||||
|
||||
✅ All 53 checked tools are present and runnable.
|
||||
✅ All 54 checked tools are present and runnable.
|
||||
@@ -2,173 +2,176 @@ Detected OS: linux (Linux aarch64)
|
||||
|
||||
Core build tools:
|
||||
✅ cmake
|
||||
cmake version 4.1.2
|
||||
/nix/store/nkcpxjifkambzlrwh27a8igvhnbchibg-cmake-4.1.2/bin/cmake
|
||||
cmake version 4.4.3
|
||||
/nix/store/39cq2g3d3flq2lmb6hc09grxs4xaax8p-cmake-4.4.3/bin/cmake
|
||||
✅ conan
|
||||
Conan version 2.28.1
|
||||
/nix/store/8i2gyqgc00xvxg9xm6y7n0ilncdv8imw-conan-2.28.1/bin/conan
|
||||
Conan version 2.33.0
|
||||
/nix/store/grz6a77mllqzzmg198qagx1r9zhvfijw-conan-2.33.0/bin/conan
|
||||
✅ git
|
||||
git version 2.54.0
|
||||
/nix/store/ixp98f9avf8ikpdrmp40cj33g0dazyp9-git-2.54.0/bin/git
|
||||
git version 2.55.0
|
||||
/nix/store/kj6aff4gmz3snpp35rl3gysbl9srd81a-git-2.55.0/bin/git
|
||||
✅ python3
|
||||
Python 3.13.13
|
||||
/nix/store/lqn6mbgzzdrqq2qkwddcmxj9z6amdd86-python3-3.13.13/bin/python3.13
|
||||
Python 3.14.7
|
||||
/nix/store/81ag9nxz1flw3xjkv1vvqg8y2ipx2ins-python3-3.14.7/bin/python3.14
|
||||
|
||||
Development tooling:
|
||||
✅ ccache
|
||||
ccache version 4.13.6
|
||||
/nix/store/2q39xi2kbi04ibga7635f2sl148d1mzv-ccache-4.13.6/bin/ccache
|
||||
/nix/store/nzb1lir9sw06fn4635kfx055myqanaz8-ccache-4.13.6/bin/ccache
|
||||
✅ clang
|
||||
clang version 22.1.7
|
||||
/nix/store/xjqffrq9i7la058s9865ig71l9sp1ys5-clang-wrapper-22.1.7/bin/clang
|
||||
✅ clang-22
|
||||
clang version 22.1.7
|
||||
/nix/store/vcf6ilfwn57828hwzyp6zlyr24j9j6yw-clang-22/bin/clang-22
|
||||
clang version 23.1.0
|
||||
/nix/store/g8sc13kz6jarzip643hk01832x40v8ms-clang-wrapper-23.1.0/bin/clang
|
||||
✅ clang-23
|
||||
clang version 23.1.0
|
||||
/nix/store/j8qvcg9150s5n09qg8p62q5z3ci02hd9-clang-23/bin/clang-23
|
||||
✅ clang++
|
||||
clang version 22.1.7
|
||||
/nix/store/xjqffrq9i7la058s9865ig71l9sp1ys5-clang-wrapper-22.1.7/bin/clang++
|
||||
✅ clang++-22
|
||||
clang version 22.1.7
|
||||
/nix/store/xby0f6gamr7m27zp5cndsvghbp9lgb3c-clang++-22/bin/clang++-22
|
||||
clang version 23.1.0
|
||||
/nix/store/g8sc13kz6jarzip643hk01832x40v8ms-clang-wrapper-23.1.0/bin/clang++
|
||||
✅ clang++-23
|
||||
clang version 23.1.0
|
||||
/nix/store/n62cn5w7fgbslamy3dgm8zhl5yly7zq0-clang++-23/bin/clang++-23
|
||||
✅ ClangBuildAnalyzer
|
||||
ClangBuildAnalyzer 1.6.0
|
||||
/nix/store/h893hd4q1bb6ily2lby5dzyfrrzd2nvj-clangbuildanalyzer-1.6.0/bin/ClangBuildAnalyzer
|
||||
/nix/store/ml2991pbdhh0qcxqj9331lhpsg449vky-clangbuildanalyzer-1.6.0/bin/ClangBuildAnalyzer
|
||||
✅ curl
|
||||
curl 8.20.0 (aarch64-unknown-linux-gnu) libcurl/8.20.0 OpenSSL/3.6.2 zlib/1.3.2 libssh2/1.11.1 nghttp2/1.69.0 mit-krb5/1.22.1
|
||||
/nix/store/i1s0lqwlrmjd2dxzgy2p84cxqqsb0bmk-curl-8.20.0-bin/bin/curl
|
||||
curl 8.22.0 (aarch64-unknown-linux-gnu) libcurl/8.22.0 OpenSSL/3.5.8 zlib/1.3.2 libssh2/1.11.1 nghttp2/1.70.0 mit-krb5/1.22.2
|
||||
/nix/store/94c2x2bl3xb7mx43sg1bs1hd7pnw83ac-curl-8.22.0-bin/bin/curl
|
||||
✅ file
|
||||
file-5.47
|
||||
/nix/store/dx973zg9km2w9albsib2vw9wyvacfrlw-file-5.47/bin/file
|
||||
file-5.48
|
||||
/nix/store/66sd1x1hls0yijibn00iz08jh4fnas9z-file-5.48/bin/file
|
||||
✅ jq
|
||||
jq-1.8.2
|
||||
/nix/store/kr5py0dcqc9f3l9148qd1axh0z77lld3-jq-1.8.2-bin/bin/jq
|
||||
✅ less
|
||||
less 692 (PCRE2 regular expressions)
|
||||
/nix/store/1blb3s7hhsr77wqi598m6k1qkfp3ms0w-less-692/bin/less
|
||||
less 710 (PCRE2 regular expressions)
|
||||
/nix/store/n6z9dv3yjlgvfx1nmrx6qzyv8vzim1l2-less-710/bin/less
|
||||
✅ make
|
||||
GNU Make 4.4.1
|
||||
/nix/store/9ngw1ippk25jjj5fjxv36xbp6iq7rxdx-gnumake-4.4.1/bin/make
|
||||
/nix/store/q07jxvck60qgrg4p1ywypddh5zz6i9s2-gnumake-4.4.1/bin/make
|
||||
✅ netstat
|
||||
net-tools 2.10
|
||||
/nix/store/7vdsz21f0s499s5yyqzp5s4676q4yxdd-net-tools-2.10/bin/netstat
|
||||
/nix/store/rfmhd3f8drg3wqyr2mhyrlkd7vyp2pxv-net-tools-2.10/bin/netstat
|
||||
✅ ninja
|
||||
1.13.2
|
||||
/nix/store/8ksx98gsbn5lmlizcmw57yd4sg0k2p58-ninja-1.13.2/bin/ninja
|
||||
/nix/store/lfkdyggn1ffslfd830l35jrvapr5lzri-ninja-1.13.2/bin/ninja
|
||||
✅ perl
|
||||
v5.42.0
|
||||
/nix/store/5wnly69vv1i3y97al4v3xrqymf9hlzgq-perl-5.42.0/bin/perl
|
||||
v5.42.3
|
||||
/nix/store/nyp5q4d2cb0pghi4i7nscb97bqkb6kp3-perl-5.42.3/bin/perl
|
||||
✅ pkg-config
|
||||
0.29.2
|
||||
/nix/store/c7vwy0gl1q0agl2h22gi0m9dg7xxad2l-pkg-config-wrapper-0.29.2/bin/pkg-config
|
||||
/nix/store/h82n3d0wdrgskdc3yvmz5l3c50h2snvp-pkg-config-wrapper-0.29.2/bin/pkg-config
|
||||
✅ vim
|
||||
VIM - Vi IMproved 9.2 (2026 Feb 14, compiled Jan 01 1980 00:00:00)
|
||||
/nix/store/v8c7pvx26irvy9k5sbwd183cyvckzzb3-vim-9.2.0389/bin/vim
|
||||
/nix/store/h39h0qq4i80sp3yd7sxjn2wf497v5rln-vim-9.2.1001/bin/vim
|
||||
✅ zip
|
||||
Zip 3.0
|
||||
/nix/store/5mh19mvbv9ym2sm9vymyyaac5l2cj2jq-zip-3.0/bin/zip
|
||||
/nix/store/lfwhq17wfcmjy823fldhlhxj1hjchdzc-zip-3.0/bin/zip
|
||||
✅ clang-apply-replacements
|
||||
clang-apply-replacements version 22.1.7
|
||||
/nix/store/s53p2m776iqaz7acgr5csgpsd18w15h7-clang-tools-22.1.7/bin/clang-apply-replacements
|
||||
✅ clang-apply-replacements-22
|
||||
clang-apply-replacements version 22.1.7
|
||||
/nix/store/bg4kn8z81hk7b9284rjqvr51wpfjqc24-clang-apply-replacements-22/bin/clang-apply-replacements-22
|
||||
clang-apply-replacements version 23.1.0
|
||||
/nix/store/jh9xhz6gwxyz3rfx7vy5g4j5d71w940r-clang-tools-23.1.0/bin/clang-apply-replacements
|
||||
✅ clang-apply-replacements-23
|
||||
clang-apply-replacements version 23.1.0
|
||||
/nix/store/1ajzrfr62m3cianfh9i97r30a7vp1xmb-clang-apply-replacements-23/bin/clang-apply-replacements-23
|
||||
✅ clang-format
|
||||
clang-format version 22.1.7
|
||||
/nix/store/s53p2m776iqaz7acgr5csgpsd18w15h7-clang-tools-22.1.7/bin/clang-format
|
||||
✅ clang-format-22
|
||||
clang-format version 22.1.7
|
||||
/nix/store/79v57mzcw8ng8kl7p961ck08ymhp31v7-clang-format-22/bin/clang-format-22
|
||||
clang-format version 23.1.0
|
||||
/nix/store/jh9xhz6gwxyz3rfx7vy5g4j5d71w940r-clang-tools-23.1.0/bin/clang-format
|
||||
✅ clang-format-23
|
||||
clang-format version 23.1.0
|
||||
/nix/store/5nfddq7xgbp4zmj8jn7xk1pxyyn5wiwl-clang-format-23/bin/clang-format-23
|
||||
✅ clang-tidy
|
||||
LLVM version 22.1.7
|
||||
/nix/store/s53p2m776iqaz7acgr5csgpsd18w15h7-clang-tools-22.1.7/bin/clang-tidy
|
||||
✅ clang-tidy-22
|
||||
LLVM version 22.1.7
|
||||
/nix/store/wdyd6cb9z1lyi37lbzvwldgcc7yv1n5c-clang-tidy-22/bin/clang-tidy-22
|
||||
LLVM version 23.1.0
|
||||
/nix/store/jh9xhz6gwxyz3rfx7vy5g4j5d71w940r-clang-tools-23.1.0/bin/clang-tidy
|
||||
✅ clang-tidy-23
|
||||
LLVM version 23.1.0
|
||||
/nix/store/cawfgsx0vsslc1f007cavmy8md9drb1r-clang-tidy-23/bin/clang-tidy-23
|
||||
✅ dot
|
||||
dot - graphviz version 12.2.1 (0)
|
||||
/nix/store/58rrk4yzwpmyxvl8cqm18h3dhv24zf00-graphviz-12.2.1/bin/dot
|
||||
dot - graphviz version 15.1.1 (0)
|
||||
/nix/store/vz4zb8d1rfigzzdn3i0hd4kff28vk02l-graphviz-15.1.1/bin/dot
|
||||
✅ doxygen
|
||||
1.16.1
|
||||
/nix/store/hq32kzwpl89wgr49iq0gmqn9r5n072zq-doxygen-1.16.1/bin/doxygen
|
||||
1.17.0
|
||||
/nix/store/6q4z332wc9jnvad71wjj87lgjxidhgzl-doxygen-1.17.0/bin/doxygen
|
||||
✅ gcovr
|
||||
gcovr 8.4
|
||||
/nix/store/sml3xbbfhhlhk6h7jnlg19pdbx9b764b-python3.13-gcovr-8.4/bin/gcovr
|
||||
/nix/store/v548fjz51k2nmr257vjnd0zp3bxpc2r1-python3.14-gcovr-8.4/bin/gcovr
|
||||
✅ gh
|
||||
gh version 2.94.0 (nixpkgs)
|
||||
/nix/store/7hh2qi0gj2ifbxbl56cjzbiyfc379bji-gh-2.94.0/bin/gh
|
||||
gh version 2.102.0 (2026-09-30)
|
||||
/nix/store/vgg6gnq74h76b4r2ac2q9366f2s5qxwx-gh-2.102.0/bin/gh
|
||||
✅ git-cliff
|
||||
git-cliff 2.13.1
|
||||
/nix/store/bidn3pz53yd6qlg711917xx0q10hqmqv-git-cliff-2.13.1/bin/git-cliff
|
||||
git-cliff 2.14.2
|
||||
/nix/store/hrlgliq7l3fl2qrdlc197mvscs4n03rd-git-cliff-2.14.2/bin/git-cliff
|
||||
✅ git-lfs
|
||||
git-lfs/3.7.1 (3.7.1; linux arm64; go 1.26.3)
|
||||
/nix/store/4rsklvkbac5bayy0zv12kxyvspi4sshd-git-lfs-3.7.1/bin/git-lfs
|
||||
git-lfs/3.8.0 (3.8.0; linux arm64; go 1.26.8)
|
||||
/nix/store/pa2g4xdvr4a73m6mv4cqs0pik1ka1xp5-git-lfs-3.8.0/bin/git-lfs
|
||||
✅ gpg
|
||||
gpg (GnuPG) 2.4.9
|
||||
/nix/store/ka4i8zz5ni3rzqnzcxbfvwr95fk8pn6q-gnupg-2.4.9/bin/gpg
|
||||
/nix/store/3qc90b7qkxz7na3xnpd2qb8yvcq8zdp6-gnupg-2.4.9/bin/gpg
|
||||
✅ pre-commit
|
||||
pre-commit 4.5.1
|
||||
/nix/store/n981w6hjfar2l81kxbxs2wxl64vwa5kj-pre-commit-4.5.1/bin/pre-commit
|
||||
pre-commit 4.6.2
|
||||
/nix/store/5f3av75nc7n7xbf2hlvlagzpp2g8g562-pre-commit-4.6.2/bin/pre-commit
|
||||
✅ run-clang-tidy
|
||||
usage: run-clang-tidy [-h] [-allow-enabling-alpha-checkers]
|
||||
/nix/store/4z2fyklg78klallr7x9j02kz92hnxp4m-run-clang-tidy/bin/run-clang-tidy
|
||||
✅ run-clang-tidy-22
|
||||
/nix/store/nlg35d9ccslsq5qny7raxyyyrxgk7vsq-run-clang-tidy/bin/run-clang-tidy
|
||||
✅ run-clang-tidy-23
|
||||
usage: run-clang-tidy [-h] [-allow-enabling-alpha-checkers]
|
||||
/nix/store/f8m0p9ad40brp9ahy4i0h27kqjkya1j9-run-clang-tidy-22/bin/run-clang-tidy-22
|
||||
/nix/store/a50nf8hp8p8l8pgcf9fs0j25hij5acw3-run-clang-tidy-23/bin/run-clang-tidy-23
|
||||
|
||||
Rust toolchain:
|
||||
✅ cargo
|
||||
cargo 1.97.1 (c980f4866 2026-06-30)
|
||||
/nix/store/6hch2qrr86n2sa2m90lrpxrfxxwbkayl-cargo-1.97.1-aarch64-unknown-linux-gnu/bin/cargo
|
||||
/nix/store/nci0zxxj2fiq0dlkpqzbnqfh27wz4sq4-cargo-1.97.1-aarch64-unknown-linux-gnu/bin/cargo
|
||||
✅ cargo-audit
|
||||
cargo-audit-audit 0.22.1
|
||||
/nix/store/9rxbrn9aa2r1z96186s69pc7vzizyfch-cargo-audit-0.22.1/bin/cargo-audit
|
||||
cargo-audit-audit 0.22.2
|
||||
/nix/store/zldaj03mfz9f21yiby5yymq7v8vqygd7-cargo-audit-0.22.2/bin/cargo-audit
|
||||
✅ cargo-llvm-cov
|
||||
cargo-llvm-cov 0.8.5
|
||||
/nix/store/vwjsi159n89szrx4yh5pc3jlf2gp4fld-cargo-llvm-cov-0.8.5/bin/cargo-llvm-cov
|
||||
cargo-llvm-cov 0.9.0
|
||||
/nix/store/iwpcfjah31fgn99ph32ajzz7i22j6al5-cargo-llvm-cov-0.9.0/bin/cargo-llvm-cov
|
||||
✅ cargo-nextest
|
||||
cargo-nextest 0.9.137
|
||||
/nix/store/qb6bcg2fjvm3r9s9j98nmffmf9xwh45s-cargo-nextest-0.9.137/bin/cargo-nextest
|
||||
cargo-nextest 0.9.146
|
||||
/nix/store/p5svqr8x1rr7b1i8595pr8qafsr7s6fm-cargo-nextest-0.9.146/bin/cargo-nextest
|
||||
✅ clippy-driver
|
||||
clippy 0.1.97 (8bab26f4f6 2026-07-14)
|
||||
/nix/store/a6p27cg6b8szfixfyvkssx6l0c345zw8-rust-minimal-1.97.1/bin/clippy-driver
|
||||
/nix/store/ny64zks16ms5d70jk1qsiwsk0fr33ipp-rust-minimal-1.97.1/bin/clippy-driver
|
||||
✅ rust-analyzer
|
||||
rust-analyzer 1.97.1 (8bab26f 2026-07-14)
|
||||
/nix/store/262830dlw2517lnagfx7i7agqgl4fmsd-rust-analyzer-preview-1.97.1-aarch64-unknown-linux-gnu/bin/rust-analyzer
|
||||
/nix/store/d9s5skhvi1qxj940mylwym2rjicnc2kq-rust-analyzer-preview-1.97.1-aarch64-unknown-linux-gnu/bin/rust-analyzer
|
||||
✅ rust-nightly
|
||||
rustc 1.99.0-nightly (87e5904f5 2026-07-20)
|
||||
/nix/store/c59pxk1yikdlf129qwyg4fplmxcrha0k-rust-nightly/bin/rust-nightly
|
||||
rustc 1.101.0-nightly (282215592 2026-10-04)
|
||||
/nix/store/rbsf1gr6f9ikv2izlyh8im117w6hs7h5-rust-nightly/bin/rust-nightly
|
||||
✅ rustc
|
||||
rustc 1.97.1 (8bab26f4f 2026-07-14)
|
||||
/nix/store/a6p27cg6b8szfixfyvkssx6l0c345zw8-rust-minimal-1.97.1/bin/rustc
|
||||
/nix/store/ny64zks16ms5d70jk1qsiwsk0fr33ipp-rust-minimal-1.97.1/bin/rustc
|
||||
✅ rustfmt
|
||||
rustfmt 1.9.0-stable (8bab26f4f6 2026-07-14)
|
||||
/nix/store/nd8g81wv1smnvdpy4whpcyv2siwjmaan-rustfmt-preview-1.97.1-aarch64-unknown-linux-gnu/bin/rustfmt
|
||||
/nix/store/dplvgjikmv8909rk0dkqr90ryjg7xn0j-rustfmt-preview-1.97.1-aarch64-unknown-linux-gnu/bin/rustfmt
|
||||
|
||||
GCC toolchain:
|
||||
✅ gcc
|
||||
gcc (GCC) 15.2.0
|
||||
/nix/store/rn6svg593xsmn8qcjzk8x9pa1i62c4kb-gcc-wrapper-15.2.0/bin/gcc
|
||||
gcc (GCC) 15.3.0
|
||||
/nix/store/g7z555pl99vnwif4jxa05nk1qdc33i1n-gcc-wrapper-15.3.0/bin/gcc
|
||||
✅ gcc-15
|
||||
gcc (GCC) 15.2.0
|
||||
/nix/store/h489d1rmjisfbxh5kmsb0a7c35j8qsdf-gcc-15/bin/gcc-15
|
||||
gcc (GCC) 15.3.0
|
||||
/nix/store/vbdsfjk3j1xy5z329sl2d8vp6mq6x09i-gcc-15/bin/gcc-15
|
||||
✅ g++
|
||||
g++ (GCC) 15.2.0
|
||||
/nix/store/rn6svg593xsmn8qcjzk8x9pa1i62c4kb-gcc-wrapper-15.2.0/bin/g++
|
||||
g++ (GCC) 15.3.0
|
||||
/nix/store/g7z555pl99vnwif4jxa05nk1qdc33i1n-gcc-wrapper-15.3.0/bin/g++
|
||||
✅ g++-15
|
||||
g++ (GCC) 15.2.0
|
||||
/nix/store/9ywmhz8bmzknrn3pn84g46z8hj3vrmw5-g++-15/bin/g++-15
|
||||
g++ (GCC) 15.3.0
|
||||
/nix/store/9ghl998ry4cxlsg6hnq5rn2iipnlkccm-g++-15/bin/g++-15
|
||||
✅ cpp
|
||||
cpp (GCC) 15.2.0
|
||||
/nix/store/rn6svg593xsmn8qcjzk8x9pa1i62c4kb-gcc-wrapper-15.2.0/bin/cpp
|
||||
cpp (GCC) 15.3.0
|
||||
/nix/store/g7z555pl99vnwif4jxa05nk1qdc33i1n-gcc-wrapper-15.3.0/bin/cpp
|
||||
✅ cpp-15
|
||||
cpp (GCC) 15.2.0
|
||||
/nix/store/vmjilh1b830qz9yh0a1jj5ads0jxizdk-cpp-15/bin/cpp-15
|
||||
cpp (GCC) 15.3.0
|
||||
/nix/store/wj0mv2fv2lhglvpjzhscqpbigkvik6br-cpp-15/bin/cpp-15
|
||||
✅ gcov
|
||||
gcov (GCC) 15.2.0
|
||||
/nix/store/rmwf5hpi1y2m1wpnfvlxmrhksm4djk2j-gcc-15.2.0/bin/gcov
|
||||
gcov (GCC) 15.3.0
|
||||
/nix/store/mszfsj00qnv9ygqcqz3w02qzxxskhvlb-gcc-15.3.0/bin/gcov
|
||||
|
||||
Mold:
|
||||
✅ mold
|
||||
mold 2.41.0 (compatible with GNU ld)
|
||||
/nix/store/f5qh5a0bx1dslmnf5n5gx0s6aljbswq3-mold-unwrapped-wrapper-2.41.0/bin/mold
|
||||
mold 2.42.1 (compatible with GNU ld)
|
||||
/nix/store/gbq334xbibaz4gn184j7gs58m2spgwi7-mold-unwrapped-wrapper-2.42.1/bin/mold
|
||||
|
||||
Skipping git-over-HTTPS check (CHECK_TOOLS_SKIP_CLONE is set).
|
||||
|
||||
✅ All 53 checked tools are present and runnable.
|
||||
✅ All 54 checked tools are present and runnable.
|
||||
+3
-2
@@ -7,7 +7,7 @@
|
||||
...
|
||||
}:
|
||||
let
|
||||
inherit (import ./packages.nix { inherit pkgs; }) commonPackages;
|
||||
inherit (import ./packages.nix { inherit pkgs customGlibc; }) commonPackages;
|
||||
|
||||
# Each forces something absent on the other platform, so both stay lazy.
|
||||
linux = import ./linux.nix { inherit pkgs customGlibc; };
|
||||
@@ -21,7 +21,8 @@ let
|
||||
)
|
||||
);
|
||||
|
||||
toolchain = if pkgs.stdenv.isLinux then linux.toolchain else (darwin.toolchain ++ [ darwinEnv ]);
|
||||
toolchain =
|
||||
if pkgs.stdenv.hostPlatform.isLinux then linux.toolchain else (darwin.toolchain ++ [ darwinEnv ]);
|
||||
in
|
||||
{
|
||||
default = pkgs.buildEnv {
|
||||
|
||||
+21
-18
@@ -20,15 +20,19 @@ let
|
||||
|
||||
# Custom-glibc stdenvs, matching the CI environment. darwin has no custom
|
||||
# glibc, so there they fall back to the plain nixpkgs stdenvs.
|
||||
customGccStdenv = if pkgs.stdenv.isLinux then linux.gccStdenv else plainGccStdenv;
|
||||
customClangStdenv = if pkgs.stdenv.isLinux then linux.clangStdenv else plainClangStdenv;
|
||||
customGccStdenv = if pkgs.stdenv.hostPlatform.isLinux then linux.gccStdenv else plainGccStdenv;
|
||||
customClangStdenv =
|
||||
if pkgs.stdenv.hostPlatform.isLinux then linux.clangStdenv else plainClangStdenv;
|
||||
|
||||
# gcov matching each gcc shell, so `-Dcoverage=ON` builds work in the shell.
|
||||
plainGcov = mkGcov {
|
||||
name = "plain";
|
||||
cc = gccPackage.cc;
|
||||
};
|
||||
customGccGcov = if pkgs.stdenv.isLinux then linux.gcov else plainGcov;
|
||||
customGccGcov = if pkgs.stdenv.hostPlatform.isLinux then linux.gcov else plainGcov;
|
||||
|
||||
# commonPackages whose clang tools parse with the custom toolchain's headers.
|
||||
customCommonPackages = (import ./packages.nix { inherit pkgs customGlibc; }).commonPackages;
|
||||
|
||||
# Whole directory: init.sh locates the profiles relative to itself.
|
||||
conanDir = ../conan;
|
||||
@@ -51,7 +55,7 @@ let
|
||||
|
||||
# Not sdkEnv: a shell's stdenv already sets that up. Prepended so the stub
|
||||
# beats the nixpkgs libresolv this shell's tooling drags in.
|
||||
darwinLibresolvHook = pkgs.lib.optionalString pkgs.stdenv.isDarwin (
|
||||
darwinLibresolvHook = pkgs.lib.optionalString pkgs.stdenv.hostPlatform.isDarwin (
|
||||
pkgs.lib.concatLines (
|
||||
pkgs.lib.mapAttrsToList (
|
||||
name: value: ''export ${name}="${value} ''${${name}:-}"''
|
||||
@@ -85,11 +89,11 @@ let
|
||||
version ? null,
|
||||
versionedTools ? [ ],
|
||||
extraPackages ? [ ],
|
||||
warningHook ? "",
|
||||
# Opt out of PatchNixBinary.cmake retargeting binaries to the system
|
||||
# loader. The plain toolchain links a newer glibc, so it must not be
|
||||
# patched; the custom toolchain patches by default.
|
||||
noPatchNixBinary ? false,
|
||||
# The stock nixpkgs toolchain: warn that it doesn't match CI, keep the
|
||||
# clang tools off the custom toolchain, and opt out of PatchNixBinary.cmake
|
||||
# retargeting binaries to the system loader (the plain toolchain links a
|
||||
# newer glibc, so it must not be patched).
|
||||
plain ? false,
|
||||
}:
|
||||
let
|
||||
compilerVersionHook =
|
||||
@@ -109,7 +113,8 @@ let
|
||||
in
|
||||
(pkgs.mkShell.override { inherit stdenv; }) (
|
||||
{
|
||||
packages = commonPackages ++ versionedLinks ++ extraPackages;
|
||||
packages =
|
||||
(if plain then commonPackages else customCommonPackages) ++ versionedLinks ++ extraPackages;
|
||||
# Marks a managed dev shell, so the build (XrplSanity.cmake) can tell an
|
||||
# intentional Nix toolchain from one leaked into a bare shell.
|
||||
XRPL_DEVSHELL = shellName;
|
||||
@@ -118,15 +123,15 @@ let
|
||||
${compilerVersionHook}
|
||||
${darwinLibresolvHook}
|
||||
${conanHook}
|
||||
${warningHook}
|
||||
${pkgs.lib.optionalString plain plainWarningHook}
|
||||
'';
|
||||
}
|
||||
// pkgs.lib.optionalAttrs noPatchNixBinary { XRPLD_NO_PATCH_NIX_BINARY = "1"; }
|
||||
// pkgs.lib.optionalAttrs plain { XRPLD_NO_PATCH_NIX_BINARY = "1"; }
|
||||
);
|
||||
in
|
||||
rec {
|
||||
# macOS: Nix Clang. Linux: Nix GCC.
|
||||
default = if pkgs.stdenv.isDarwin then clang else gcc;
|
||||
default = if pkgs.stdenv.hostPlatform.isDarwin then clang else gcc;
|
||||
|
||||
# gcc/clang use the custom-glibc toolchain, matching CI. On darwin there is no
|
||||
# custom glibc, so they fall back to the plain nixpkgs toolchain.
|
||||
@@ -171,7 +176,7 @@ rec {
|
||||
# The *-plain shells (stock nixpkgs toolchain) exist only on Linux: on darwin
|
||||
# gcc/clang are already plain, so these would be redundant and are omitted, which
|
||||
# makes `nix develop .#gcc-plain` fail there rather than silently aliasing gcc.
|
||||
// pkgs.lib.optionalAttrs pkgs.stdenv.isLinux {
|
||||
// pkgs.lib.optionalAttrs pkgs.stdenv.hostPlatform.isLinux {
|
||||
gcc-plain = makeShell {
|
||||
shellName = "gcc-plain";
|
||||
stdenv = plainGccStdenv;
|
||||
@@ -179,8 +184,7 @@ rec {
|
||||
version = gccVersion;
|
||||
versionedTools = gccVersionedTools;
|
||||
extraPackages = [ plainGcov ];
|
||||
warningHook = plainWarningHook;
|
||||
noPatchNixBinary = true;
|
||||
plain = true;
|
||||
};
|
||||
|
||||
clang-plain = makeShell {
|
||||
@@ -189,7 +193,6 @@ rec {
|
||||
compilerName = "clang";
|
||||
version = llvmVersion;
|
||||
versionedTools = clangVersionedTools;
|
||||
warningHook = plainWarningHook;
|
||||
noPatchNixBinary = true;
|
||||
plain = true;
|
||||
};
|
||||
}
|
||||
@@ -146,6 +146,7 @@ in
|
||||
})
|
||||
];
|
||||
|
||||
clang = customClang;
|
||||
gccStdenv = customStdenv;
|
||||
clangStdenv = pkgs.stdenvAdapters.overrideCC pkgs.stdenv customClang;
|
||||
gcov = customGcov;
|
||||
|
||||
+38
-7
@@ -1,14 +1,28 @@
|
||||
{ pkgs }:
|
||||
{
|
||||
pkgs,
|
||||
# With the custom glibc, the clang tools (clang-tidy, ...) parse code with the
|
||||
# Linux custom toolchain's headers, i.e. the same glibc and libstdc++ as the
|
||||
# build. Without it, they use the nixpkgs default compiler's.
|
||||
customGlibc ? null,
|
||||
}:
|
||||
let
|
||||
# Compiler versions used across the dev shell and the CI environment.
|
||||
# Docs link here and scripts read the version off the tools;
|
||||
# only the clang-format rev in .pre-commit-config.yaml
|
||||
# has to be bumped alongside llvmVersion.
|
||||
gccVersion = 15;
|
||||
llvmVersion = 22;
|
||||
llvmVersion = 23;
|
||||
|
||||
gccPackage = pkgs."gcc${toString gccVersion}";
|
||||
llvmPackages = pkgs."llvmPackages_${toString llvmVersion}";
|
||||
|
||||
# Bound explicitly so it tracks llvmPackages above, not the `with pkgs` default.
|
||||
clangTools = llvmPackages.clang-tools;
|
||||
# isLinux first: darwin must not evaluate the custom glibc.
|
||||
clangTools = llvmPackages.clang-tools.override (
|
||||
pkgs.lib.optionalAttrs (pkgs.stdenv.hostPlatform.isLinux && customGlibc != null) {
|
||||
inherit (import ./linux.nix { inherit pkgs customGlibc; }) clang;
|
||||
}
|
||||
);
|
||||
|
||||
# In LLVM 22, run-clang-tidy.py moved from share/clang/ to bin/, so nixpkgs
|
||||
# clang-tools no longer links it. Wrap it manually.
|
||||
@@ -16,6 +30,21 @@ let
|
||||
exec ${pkgs.python3}/bin/python3 ${llvmPackages.clang-unwrapped}/bin/run-clang-tidy "$@"
|
||||
'';
|
||||
|
||||
# Conan 2.33 is the first release whose settings.yml accepts clang 23.
|
||||
# TODO: drop once nixpkgs ships it.
|
||||
conan = pkgs.conan.overridePythonAttrs (old: {
|
||||
version = "2.33.0";
|
||||
src = pkgs.fetchFromGitHub {
|
||||
owner = "conan-io";
|
||||
repo = "conan";
|
||||
tag = "2.33.0";
|
||||
hash = "sha256-FDJjesqvPiAUAEjeqQt088Vnet/KXHbUyEPytP9MyG4=";
|
||||
};
|
||||
dependencies = old.dependencies ++ [ pkgs.python3Packages.truststore ];
|
||||
# The upstream test suite takes long and needs network access.
|
||||
doCheck = false;
|
||||
});
|
||||
|
||||
rust = import ./rust.nix { inherit pkgs; };
|
||||
|
||||
# Nix wraps its toolchain so that binaries are exposed only under unsuffixed
|
||||
@@ -25,10 +54,11 @@ let
|
||||
# - Conan's Boost recipe looks up `g++-<major>` before plain `g++`.
|
||||
# - bin/pre-commit/clang_tidy_check.py looks up `run-clang-tidy-<v>` and
|
||||
# `clang-apply-replacements-<v>` before the unsuffixed names.
|
||||
# On a host that also has the matching system binary (e.g. Ubuntu's
|
||||
# `/usr/bin/g++-15` or `clang-tidy-22`) the probe escapes Nix and mixes a
|
||||
# system tool into the Nix environment. Generate version-suffixed symlinks
|
||||
# next to a package's tools so those probes resolve to the Nix ones.
|
||||
# On a host that also has the matching system binary
|
||||
# (e.g. Ubuntu's `/usr/bin/g++-<v>` or `clang-tidy-<v>`),
|
||||
# the probe escapes Nix and mixes a system tool into the Nix environment.
|
||||
# Generate version-suffixed symlinks next to a package's tools
|
||||
# so those probes resolve to the Nix ones.
|
||||
#
|
||||
# Compiler links must point at whichever compiler is active in a given
|
||||
# environment (the plain stdenv compiler in the dev shell, the custom-glibc
|
||||
@@ -112,6 +142,7 @@ in
|
||||
gnumake
|
||||
gnupg # needed for signing commits & codecov/codecov-action
|
||||
graphviz
|
||||
jq
|
||||
less # needed for git diff
|
||||
mold
|
||||
nettools # provides netstat, used to debug failures in CI
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/LedgerTiming.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/entries/FeeSettingsEntry.h>
|
||||
#include <xrpl/ledger/entries/NegativeUNLEntry.h>
|
||||
#include <xrpl/nodestore/NodeObject.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Fees.h>
|
||||
@@ -560,7 +562,7 @@ Ledger::setup()
|
||||
|
||||
try
|
||||
{
|
||||
if (auto const sle = read(keylet::feeSettings()))
|
||||
if (auto const sle = FeeSettingsEntryR(*this))
|
||||
{
|
||||
bool oldFees = false;
|
||||
bool newFees = false;
|
||||
@@ -639,7 +641,7 @@ HashSet<PublicKey>
|
||||
Ledger::negativeUNL() const
|
||||
{
|
||||
HashSet<PublicKey> negUnl;
|
||||
if (auto sle = read(keylet::negativeUNL()); sle && sle->isFieldPresent(sfDisabledValidators))
|
||||
if (auto const sle = NegativeUNLEntryR(*this); sle && sle->isFieldPresent(sfDisabledValidators))
|
||||
{
|
||||
auto const& nUnlData = sle->getFieldArray(sfDisabledValidators);
|
||||
for (auto const& n : nUnlData)
|
||||
@@ -663,7 +665,7 @@ Ledger::negativeUNL() const
|
||||
std::optional<PublicKey>
|
||||
Ledger::validatorToDisable() const
|
||||
{
|
||||
if (auto sle = read(keylet::negativeUNL()); sle && sle->isFieldPresent(sfValidatorToDisable))
|
||||
if (auto const sle = NegativeUNLEntryR(*this); sle && sle->isFieldPresent(sfValidatorToDisable))
|
||||
{
|
||||
auto d = sle->getFieldVL(sfValidatorToDisable);
|
||||
auto s = makeSlice(d);
|
||||
@@ -677,7 +679,8 @@ Ledger::validatorToDisable() const
|
||||
std::optional<PublicKey>
|
||||
Ledger::validatorToReEnable() const
|
||||
{
|
||||
if (auto sle = read(keylet::negativeUNL()); sle && sle->isFieldPresent(sfValidatorToReEnable))
|
||||
if (auto const sle = NegativeUNLEntryR(*this);
|
||||
sle && sle->isFieldPresent(sfValidatorToReEnable))
|
||||
{
|
||||
auto d = sle->getFieldVL(sfValidatorToReEnable);
|
||||
auto s = makeSlice(d);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/beast/hash/uhash.h>
|
||||
#include <xrpl/ledger/detail/ReadViewFwdRange.h>
|
||||
#include <xrpl/ledger/entries/AmendmentsEntry.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Keylet.h>
|
||||
#include <xrpl/protocol/Rules.h>
|
||||
@@ -72,9 +73,8 @@ makeRulesGivenLedger(
|
||||
std::optional const digest = ledger.digest(k.key);
|
||||
if (digest)
|
||||
{
|
||||
auto const sle = ledger.read(k);
|
||||
if (sle)
|
||||
return Rules(presets, digest, sle->getFieldV256(sfAmendments));
|
||||
if (AmendmentsEntryR const entry(k, ledger); entry)
|
||||
return Rules(presets, digest, entry->getFieldV256(sfAmendments));
|
||||
}
|
||||
return Rules(presets);
|
||||
}
|
||||
|
||||
+11
-39
@@ -9,6 +9,8 @@
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/entries/AmendmentsEntry.h>
|
||||
#include <xrpl/ledger/entries/LedgerHashesEntry.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/CredentialHelpers.h>
|
||||
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
|
||||
@@ -308,40 +310,13 @@ areCompatible(
|
||||
std::set<UInt256>
|
||||
getEnabledAmendments(ReadView const& view)
|
||||
{
|
||||
std::set<UInt256> amendments;
|
||||
|
||||
if (auto const sle = view.read(keylet::amendments()))
|
||||
{
|
||||
if (sle->isFieldPresent(sfAmendments))
|
||||
{
|
||||
auto const& v = sle->getFieldV256(sfAmendments);
|
||||
amendments.insert(v.begin(), v.end());
|
||||
}
|
||||
}
|
||||
|
||||
return amendments;
|
||||
return AmendmentsEntryR(view).enabledAmendments();
|
||||
}
|
||||
|
||||
MajorityAmendmentsT
|
||||
getMajorityAmendments(ReadView const& view)
|
||||
{
|
||||
MajorityAmendmentsT ret;
|
||||
|
||||
if (auto const sle = view.read(keylet::amendments()))
|
||||
{
|
||||
if (sle->isFieldPresent(sfMajorities))
|
||||
{
|
||||
using Tp = NetClock::time_point;
|
||||
using D = Tp::duration;
|
||||
|
||||
auto const majorities = sle->getFieldArray(sfMajorities);
|
||||
|
||||
for (auto const& m : majorities)
|
||||
ret[m.getFieldH256(sfAmendment)] = Tp(D(m.getFieldU32(sfCloseTime)));
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
return AmendmentsEntryR(view).majorityAmendments();
|
||||
}
|
||||
|
||||
std::optional<UInt256>
|
||||
@@ -361,17 +336,16 @@ hashOfSeq(ReadView const& ledger, LedgerIndex seq, beast::Journal journal)
|
||||
if (int const diff = ledger.seq() - seq; diff <= 256)
|
||||
{
|
||||
// Within 256...
|
||||
auto const hashIndex = ledger.read(keylet::skip());
|
||||
LedgerHashesEntryR const hashIndex(ledger, journal);
|
||||
if (hashIndex)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
hashIndex->getFieldU32(sfLastLedgerSequence) == (ledger.seq() - 1),
|
||||
"xrpl::hashOfSeq : matching ledger sequence");
|
||||
STVector256 vec = hashIndex->getFieldV256(sfHashes);
|
||||
if (vec.size() >= diff)
|
||||
return vec[vec.size() - diff];
|
||||
if (auto const hash = hashIndex.hashAt(diff - 1))
|
||||
return hash;
|
||||
JLOG(journal.warn()) << "Ledger " << ledger.seq() << " missing hash for " << seq << " ("
|
||||
<< vec.size() << "," << diff << ")";
|
||||
<< hashIndex->getFieldV256(sfHashes).size() << "," << diff << ")";
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -387,16 +361,14 @@ hashOfSeq(ReadView const& ledger, LedgerIndex seq, beast::Journal journal)
|
||||
}
|
||||
|
||||
// in skiplist
|
||||
auto const hashIndex = ledger.read(keylet::skip(seq));
|
||||
LedgerHashesEntryR const hashIndex(keylet::skip(seq), ledger, journal);
|
||||
if (hashIndex)
|
||||
{
|
||||
auto const lastSeq = hashIndex->getFieldU32(sfLastLedgerSequence);
|
||||
XRPL_ASSERT(lastSeq >= seq, "xrpl::hashOfSeq : minimum last ledger");
|
||||
XRPL_ASSERT((lastSeq & 0xff) == 0, "xrpl::hashOfSeq : valid last ledger");
|
||||
auto const diff = (lastSeq - seq) >> 8;
|
||||
STVector256 vec = hashIndex->getFieldV256(sfHashes);
|
||||
if (vec.size() > diff)
|
||||
return vec[vec.size() - diff - 1];
|
||||
if (auto const hash = hashIndex.hashAt((lastSeq - seq) >> 8))
|
||||
return hash;
|
||||
}
|
||||
JLOG(journal.warn()) << "Can't get seq " << seq << " from " << ledger.seq() << " error";
|
||||
return std::nullopt;
|
||||
|
||||
@@ -18,8 +18,10 @@
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Issue.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/MPTAmount.h>
|
||||
#include <xrpl/protocol/MPTIssue.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/Quality.h>
|
||||
#include <xrpl/protocol/Rate.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
@@ -1337,9 +1339,26 @@ directSendNoLimitMPT(
|
||||
}
|
||||
|
||||
// Sending 3rd party MPTs: transit.
|
||||
saActual = (waiveFee == WaiveTransferFee::Yes)
|
||||
? saAmount
|
||||
: multiply(saAmount, transferRate(view, saAmount.get<MPTIssue>().getMptID()));
|
||||
if (waiveFee == WaiveTransferFee::Yes)
|
||||
{
|
||||
saActual = saAmount;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto const rate = transferRate(view, saAmount.get<MPTIssue>().getMptID());
|
||||
if (view.rules().enabled(fixCleanup3_5_0))
|
||||
{
|
||||
// Number math loses precision on large MPT amounts, which can
|
||||
// overcharge the sender. MPTs are integral, so compute the cost
|
||||
// exactly and round it up, matching the payment engine.
|
||||
auto const cost = mulRatio(saAmount.mpt(), rate.value, QUALITY_ONE, true);
|
||||
saActual = STAmount(saAmount.asset(), cost.value());
|
||||
}
|
||||
else
|
||||
{
|
||||
saActual = multiply(saAmount, rate);
|
||||
}
|
||||
}
|
||||
|
||||
JLOG(j.debug()) << "directSendNoLimitMPT> " << to_string(uSenderID) << " - > "
|
||||
<< to_string(uReceiverID) << " : deliver=" << saAmount.getFullText()
|
||||
@@ -1448,9 +1467,23 @@ directSendNoLimitMultiMPT(
|
||||
}
|
||||
|
||||
// Sending 3rd party MPTs: transit.
|
||||
STAmount const actualSend = (waiveFee == WaiveTransferFee::Yes)
|
||||
? amount
|
||||
: multiply(amount, transferRate(view, amount.get<MPTIssue>().getMptID()));
|
||||
STAmount actualSend = amount;
|
||||
if (waiveFee != WaiveTransferFee::Yes)
|
||||
{
|
||||
auto const rate = transferRate(view, amount.get<MPTIssue>().getMptID());
|
||||
if (view.rules().enabled(fixCleanup3_5_0))
|
||||
{
|
||||
// Number math loses precision on large MPT amounts, which can
|
||||
// overcharge the sender. MPTs are integral, so compute the
|
||||
// cost exactly and round it up, matching the payment engine.
|
||||
auto const cost = mulRatio(amount.mpt(), rate.value, QUALITY_ONE, true);
|
||||
actualSend = STAmount(amount.asset(), cost.value());
|
||||
}
|
||||
else
|
||||
{
|
||||
actualSend = multiply(amount, rate);
|
||||
}
|
||||
}
|
||||
actual += actualSend;
|
||||
takeFromSender += actualSend;
|
||||
|
||||
|
||||
@@ -1642,7 +1642,9 @@ divRoundImpl(STAmount const& num, STAmount const& den, Asset const& asset, bool
|
||||
|
||||
bool const resultNegative = (num.negative() != den.negative());
|
||||
|
||||
if (asset.holds<MPTIssue>() && isFeatureEnabled(featureMPTokensV2, false))
|
||||
// fixCleanup3_5_0: the legacy path below overflows on large MPT amounts.
|
||||
if (asset.holds<MPTIssue>() &&
|
||||
(isFeatureEnabled(featureMPTokensV2, true) || isFeatureEnabled(fixCleanup3_5_0, true)))
|
||||
{
|
||||
// Match the multiply path above: Number performs the rounded
|
||||
// operation, then STAmount materializes the final MPT amount using the
|
||||
|
||||
+60
-18
@@ -8,6 +8,7 @@
|
||||
#include <xrpl/core/ServiceRegistry.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/OpenView.h>
|
||||
#include <xrpl/protocol/BatchInnerResult.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Rules.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
@@ -18,9 +19,11 @@
|
||||
#include <xrpl/protocol/TxFormats.h>
|
||||
#include <xrpl/tx/applySteps.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <exception>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
@@ -203,7 +206,8 @@ applyBatchTransactions(
|
||||
ServiceRegistry& registry,
|
||||
OpenView& batchView,
|
||||
STTx const& batchTxn,
|
||||
beast::Journal j)
|
||||
beast::Journal j,
|
||||
std::vector<BatchInnerResult>* innerResults)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
batchTxn.getTxnType() == ttBATCH && !batchTxn.getFieldArray(sfRawTransactions).empty(),
|
||||
@@ -211,8 +215,9 @@ applyBatchTransactions(
|
||||
|
||||
auto const parentBatchId = batchTxn.getTransactionID();
|
||||
auto const mode = batchTxn.getFlags();
|
||||
std::uint32_t index = 0;
|
||||
|
||||
auto applyOneTransaction = [®istry, &j, &parentBatchId, &batchView](STTx const& tx) {
|
||||
auto applyOneTransaction = [&](STTx const& tx) {
|
||||
OpenView perTxBatchView(kBatchView, batchView);
|
||||
|
||||
auto const ret = apply(registry, perTxBatchView, parentBatchId, tx, TapBatch, j);
|
||||
@@ -223,6 +228,17 @@ applyBatchTransactions(
|
||||
JLOG(j.debug()) << "BatchTrace[" << parentBatchId << "]: " << tx.getTransactionID() << " "
|
||||
<< (ret.applied ? "applied" : "failure") << ": " << transToken(ret.ter);
|
||||
|
||||
if (innerResults != nullptr)
|
||||
{
|
||||
innerResults->push_back(
|
||||
{.parentBatchId = parentBatchId,
|
||||
.innerTxId = tx.getTransactionID(),
|
||||
.index = index,
|
||||
.ter = ret.ter,
|
||||
.applied = ret.applied});
|
||||
}
|
||||
++index;
|
||||
|
||||
// If the transaction should be applied push its changes to the
|
||||
// whole-batch view.
|
||||
// NOTE: each inner tx is individually capped at kOversizeMetaDataCap;
|
||||
@@ -235,28 +251,53 @@ applyBatchTransactions(
|
||||
};
|
||||
|
||||
int applied = 0;
|
||||
auto const firstResult = innerResults != nullptr ? innerResults->size() : 0;
|
||||
|
||||
for (auto const& stx : batchTxn.getBatchTransactions())
|
||||
{
|
||||
auto const result = applyOneTransaction(*stx);
|
||||
XRPL_ASSERT(
|
||||
result.applied == (isTesSuccess(result.ter) || isTecClaim(result.ter)),
|
||||
"Outer Batch failure, inner transaction should not be applied");
|
||||
|
||||
if (result.applied)
|
||||
++applied;
|
||||
|
||||
if (!isTesSuccess(result.ter))
|
||||
try
|
||||
{
|
||||
if ((mode & tfAllOrNothing) != 0u)
|
||||
return false;
|
||||
auto const result = applyOneTransaction(*stx);
|
||||
XRPL_ASSERT(
|
||||
result.applied == (isTesSuccess(result.ter) || isTecClaim(result.ter)),
|
||||
"Outer Batch failure, inner transaction should not be applied");
|
||||
|
||||
if ((mode & tfUntilFailure) != 0u)
|
||||
if (result.applied)
|
||||
++applied;
|
||||
|
||||
if (!isTesSuccess(result.ter))
|
||||
{
|
||||
if ((mode & tfAllOrNothing) != 0u)
|
||||
{
|
||||
// The whole-batch view is discarded, so nothing recorded so far reached the
|
||||
// ledger.
|
||||
if (innerResults != nullptr)
|
||||
{
|
||||
for (auto i = firstResult; i < innerResults->size(); ++i)
|
||||
{
|
||||
(*innerResults)[i].applied = false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((mode & tfUntilFailure) != 0u)
|
||||
break;
|
||||
}
|
||||
else if ((mode & tfOnlyOne) != 0u)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if ((mode & tfOnlyOne) != 0u)
|
||||
catch (std::exception const& ex)
|
||||
{
|
||||
break;
|
||||
// Drop this batch's results before the exception unwinds: the whole-batch
|
||||
// view never reaches the ledger and the outer Batch is reported as failed.
|
||||
JLOG(j.warn()) << "BatchTrace[" << parentBatchId << "]: " << stx->getTransactionID()
|
||||
<< " throws: " << ex.what();
|
||||
if (innerResults != nullptr)
|
||||
innerResults->resize(firstResult);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,7 +311,8 @@ applyTransaction(
|
||||
STTx const& txn,
|
||||
bool retryAssured,
|
||||
ApplyFlags flags,
|
||||
beast::Journal j)
|
||||
beast::Journal j,
|
||||
std::vector<BatchInnerResult>* innerResults)
|
||||
{
|
||||
// Returns false if the transaction has need not be retried.
|
||||
if (retryAssured)
|
||||
@@ -292,7 +334,7 @@ applyTransaction(
|
||||
{
|
||||
OpenView wholeBatchView(kBatchView, view);
|
||||
|
||||
if (applyBatchTransactions(registry, wholeBatchView, txn, j))
|
||||
if (applyBatchTransactions(registry, wholeBatchView, txn, j, innerResults))
|
||||
wholeBatchView.apply(view);
|
||||
}
|
||||
|
||||
|
||||
@@ -89,17 +89,24 @@ ValidMPTIssuance::visitEntry(bool isDelete, SLE::ConstRef before, SLE::ConstRef
|
||||
if (fix320Enabled && after->isFieldPresent(sfReferenceHolding))
|
||||
referenceHoldingSetOnCreate_ = true;
|
||||
}
|
||||
else if (fix320Enabled)
|
||||
else
|
||||
{
|
||||
// Modified issuance: detect any change to sfReferenceHolding.
|
||||
bool const beforePresent = before->isFieldPresent(sfReferenceHolding);
|
||||
bool const afterPresent = after->isFieldPresent(sfReferenceHolding);
|
||||
if (beforePresent != afterPresent ||
|
||||
(afterPresent &&
|
||||
before->getFieldH256(sfReferenceHolding) !=
|
||||
after->getFieldH256(sfReferenceHolding)))
|
||||
// lsfMPTLocked is the only issuance flag with a legal clear path
|
||||
// (tfMPTUnlock); the rest are fixed at creation or set-once.
|
||||
issuanceFlagsCleared_ |= before->getFlags() & ~after->getFlags() & ~lsfMPTLocked;
|
||||
|
||||
if (fix320Enabled)
|
||||
{
|
||||
referenceHoldingMutated_ = true;
|
||||
// Modified issuance: detect any change to sfReferenceHolding.
|
||||
bool const beforePresent = before->isFieldPresent(sfReferenceHolding);
|
||||
bool const afterPresent = after->isFieldPresent(sfReferenceHolding);
|
||||
if (beforePresent != afterPresent ||
|
||||
(afterPresent &&
|
||||
before->getFieldH256(sfReferenceHolding) !=
|
||||
after->getFieldH256(sfReferenceHolding)))
|
||||
{
|
||||
referenceHoldingMutated_ = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -199,6 +206,16 @@ ValidMPTIssuance::finalize(
|
||||
return false;
|
||||
}
|
||||
|
||||
// Post-fixCleanup3_5_0: no transaction may clear an issuance flag other
|
||||
// than lsfMPTLocked, so downstream code can trust set-once flags such as
|
||||
// lsfMPTCanTransfer.
|
||||
if (rules.enabled(fixCleanup3_5_0) && issuanceFlagsCleared_ != 0)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: immutable MPTokenIssuance flag cleared: "
|
||||
<< issuanceFlagsCleared_;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Deleting an MPToken with a non-zero MPTAmount is rejected.
|
||||
if (rules.enabled(fixCleanup3_5_0))
|
||||
{
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/IOUAmount.h>
|
||||
#include <xrpl/protocol/MPTAmount.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/Quality.h>
|
||||
#include <xrpl/protocol/Rules.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
@@ -100,28 +99,6 @@ AMMLiquidity<TIn, TOut>::generateFibSeqOffer(TAmounts<TIn, TOut> const& balances
|
||||
}
|
||||
|
||||
namespace {
|
||||
template <typename T>
|
||||
constexpr T
|
||||
maxAmount()
|
||||
{
|
||||
if constexpr (std::is_same_v<T, XRPAmount>)
|
||||
{
|
||||
return XRPAmount(STAmount::kMaxNative);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, IOUAmount>)
|
||||
{
|
||||
return IOUAmount(STAmount::kMaxValue / 2, STAmount::kMaxOffset);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, STAmount>)
|
||||
{
|
||||
return STAmount(STAmount::kMaxValue / 2, STAmount::kMaxOffset);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, MPTAmount>)
|
||||
{
|
||||
return MPTAmount(kMaxMpTokenAmount);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T
|
||||
maxOut(T const& out, Asset const& asset)
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/beast/utility/Zero.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/entries/CheckEntry.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
@@ -30,7 +32,7 @@ CheckCancel::preflight(PreflightContext const& ctx)
|
||||
TER
|
||||
CheckCancel::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
auto const sleCheck = ctx.view.read(keylet::check(ctx.tx[sfCheckID]));
|
||||
CheckEntryR const sleCheck(ctx.tx[sfCheckID], ctx.view);
|
||||
if (!sleCheck)
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Check does not exist.";
|
||||
@@ -59,7 +61,7 @@ CheckCancel::preclaim(PreclaimContext const& ctx)
|
||||
TER
|
||||
CheckCancel::doApply()
|
||||
{
|
||||
auto const sleCheck = view().peek(keylet::check(ctx_.tx[sfCheckID]));
|
||||
CheckEntryW sleCheck(ctx_.tx[sfCheckID], view(), j_);
|
||||
if (!sleCheck)
|
||||
{
|
||||
// Error should have been caught in preclaim.
|
||||
@@ -96,10 +98,10 @@ CheckCancel::doApply()
|
||||
}
|
||||
|
||||
// If we succeeded, update the check owner's reserve.
|
||||
decreaseOwnerCountForObject(view(), srcId, sleCheck, 1, viewJ);
|
||||
decreaseOwnerCountForObject(view(), srcId, sleCheck.mutableRawSle(), 1, viewJ);
|
||||
|
||||
// Remove check from ledger.
|
||||
view().erase(sleCheck);
|
||||
sleCheck.erase();
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/PaymentSandbox.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/entries/CheckEntry.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
|
||||
@@ -87,7 +88,7 @@ CheckCash::preflight(PreflightContext const& ctx)
|
||||
TER
|
||||
CheckCash::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
auto const sleCheck = ctx.view.read(keylet::check(ctx.tx[sfCheckID]));
|
||||
CheckEntryR const sleCheck(ctx.tx[sfCheckID], ctx.view);
|
||||
if (!sleCheck)
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Check does not exist.";
|
||||
@@ -296,8 +297,9 @@ CheckCash::doApply()
|
||||
// directly on a View.
|
||||
PaymentSandbox psb(&ctx_.view());
|
||||
|
||||
auto sleCheck = psb.peek(keylet::check(ctx_.tx[sfCheckID]));
|
||||
if (!sleCheck)
|
||||
std::optional<CheckEntryW> sleCheck;
|
||||
sleCheck.emplace(ctx_.tx[sfCheckID], psb, j_);
|
||||
if (!*sleCheck)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j_.fatal()) << "Precheck did not verify check's existence.";
|
||||
@@ -305,7 +307,7 @@ CheckCash::doApply()
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
AccountID const srcId{sleCheck->getAccountID(sfAccount)};
|
||||
AccountID const srcId{(*sleCheck)->getAccountID(sfAccount)};
|
||||
if (!psb.exists(keylet::account(srcId)) || !psb.exists(keylet::account(accountID_)))
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
@@ -314,7 +316,7 @@ CheckCash::doApply()
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
auto const sponsorCheckSle = getLedgerEntryReserveSponsor(psb, sleCheck);
|
||||
auto const sponsorCheckSle = getLedgerEntryReserveSponsor(psb, sleCheck->rawSle());
|
||||
|
||||
// Preclaim already checked that source has at least the requested
|
||||
// funds.
|
||||
@@ -331,7 +333,7 @@ CheckCash::doApply()
|
||||
|
||||
if (srcId != accountID_)
|
||||
{
|
||||
STAmount const sendMax = sleCheck->at(sfSendMax);
|
||||
STAmount const sendMax = (*sleCheck)->at(sfSendMax);
|
||||
|
||||
// Flow() doesn't do XRP to XRP transfers.
|
||||
if (sendMax.native())
|
||||
@@ -569,7 +571,7 @@ CheckCash::doApply()
|
||||
true, // owner pays transfer fee
|
||||
OfferCrossing::No,
|
||||
std::nullopt,
|
||||
sleCheck->getFieldAmount(sfSendMax),
|
||||
(*sleCheck)->getFieldAmount(sfSendMax),
|
||||
std::nullopt, // check does not support domain
|
||||
viewJ);
|
||||
|
||||
@@ -594,7 +596,7 @@ CheckCash::doApply()
|
||||
// for DeliverMin.
|
||||
ctx_.deliver(result.actualAmountOut);
|
||||
|
||||
sleCheck = psb.peek(keylet::check(ctx_.tx[sfCheckID]));
|
||||
sleCheck.emplace(ctx_.tx[sfCheckID], psb, j_);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -602,7 +604,10 @@ CheckCash::doApply()
|
||||
// check link from destination directory.
|
||||
if (srcId != accountID_ &&
|
||||
!psb.dirRemove(
|
||||
keylet::ownerDir(accountID_), sleCheck->at(sfDestinationNode), sleCheck->key(), true))
|
||||
keylet::ownerDir(accountID_),
|
||||
(*sleCheck)->at(sfDestinationNode),
|
||||
(*sleCheck)->key(),
|
||||
true))
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j_.fatal()) << "Unable to delete check from destination.";
|
||||
@@ -611,7 +616,8 @@ CheckCash::doApply()
|
||||
}
|
||||
|
||||
// Remove check from check owner's directory.
|
||||
if (!psb.dirRemove(keylet::ownerDir(srcId), sleCheck->at(sfOwnerNode), sleCheck->key(), true))
|
||||
if (!psb.dirRemove(
|
||||
keylet::ownerDir(srcId), (*sleCheck)->at(sfOwnerNode), (*sleCheck)->key(), true))
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j_.fatal()) << "Unable to delete check from owner.";
|
||||
@@ -620,10 +626,10 @@ CheckCash::doApply()
|
||||
}
|
||||
|
||||
// If we succeeded, update the check owner's reserve.
|
||||
decreaseOwnerCountForObject(psb, srcId, sleCheck, 1, viewJ);
|
||||
decreaseOwnerCountForObject(psb, srcId, sleCheck->mutableRawSle(), 1, viewJ);
|
||||
|
||||
// Remove check from ledger.
|
||||
psb.erase(sleCheck);
|
||||
sleCheck->erase();
|
||||
|
||||
psb.apply(ctx_.rawView());
|
||||
return tesSUCCESS;
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/core/ServiceRegistry.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/entries/CheckEntry.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
@@ -25,7 +26,6 @@
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
#include <xrpl/tx/Transactor.h>
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
namespace xrpl {
|
||||
@@ -201,8 +201,9 @@ CheckCreate::doApply()
|
||||
// Note that we use the value from the sequence or ticket as the
|
||||
// Check sequence. For more explanation see comments in SeqProxy.h.
|
||||
auto const seq = ctx_.tx.getSeqProxy();
|
||||
Keylet const checkKeylet = keylet::check(accountID_, seq);
|
||||
auto sleCheck = std::make_shared<SLE>(checkKeylet);
|
||||
CheckEntryW sleCheck(accountID_, seq, view(), j_);
|
||||
Keylet const checkKeylet = sleCheck.keylet();
|
||||
sleCheck.newSLE();
|
||||
|
||||
sleCheck->setAccountID(sfAccount, accountID_);
|
||||
AccountID const dstAccountId = ctx_.tx[sfDestination];
|
||||
@@ -218,7 +219,7 @@ CheckCreate::doApply()
|
||||
if (auto const expiry = ctx_.tx[~sfExpiration])
|
||||
sleCheck->setFieldU32(sfExpiration, *expiry);
|
||||
|
||||
view().insert(sleCheck);
|
||||
sleCheck.insert();
|
||||
|
||||
auto viewJ = ctx_.registry.get().getJournal("View");
|
||||
// If it's not a self-send (and it shouldn't be), add Check to the
|
||||
@@ -252,7 +253,7 @@ CheckCreate::doApply()
|
||||
// If we succeeded, the new entry counts against the creator's reserve.
|
||||
|
||||
increaseOwnerCount(ctx_.getApplyViewContext(), sle, 1, viewJ);
|
||||
addSponsorToLedgerEntry(ctx_.getApplyViewContext(), sleCheck);
|
||||
addSponsorToLedgerEntry(ctx_.getApplyViewContext(), sleCheck.mutableRawSle());
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,17 +3,13 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/entries/EscrowEntry.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/EscrowHelpers.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Concepts.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Issue.h>
|
||||
#include <xrpl/protocol/MPTIssue.h>
|
||||
#include <xrpl/protocol/Rate.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
@@ -34,79 +30,24 @@ EscrowCancel::preflight(PreflightContext const& ctx)
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
template <ValidIssueType T>
|
||||
static TER
|
||||
escrowCancelPreclaimHelper(
|
||||
PreclaimContext const& ctx,
|
||||
AccountID const& account,
|
||||
STAmount const& amount);
|
||||
|
||||
template <>
|
||||
TER
|
||||
escrowCancelPreclaimHelper<Issue>(
|
||||
PreclaimContext const& ctx,
|
||||
AccountID const& account,
|
||||
STAmount const& amount)
|
||||
{
|
||||
AccountID const& issuer = amount.getIssuer();
|
||||
// If the issuer is the same as the account, return tecINTERNAL
|
||||
if (issuer == account)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
// If the issuer has requireAuth set, check if the account is authorized
|
||||
if (auto const ter = requireAuth(ctx.view, amount.get<Issue>(), account); !isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
template <>
|
||||
TER
|
||||
escrowCancelPreclaimHelper<MPTIssue>(
|
||||
PreclaimContext const& ctx,
|
||||
AccountID const& account,
|
||||
STAmount const& amount)
|
||||
{
|
||||
AccountID const issuer = amount.getIssuer();
|
||||
// If the issuer is the same as the account, return tecINTERNAL
|
||||
if (issuer == account)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
// If the mpt does not exist, return tecOBJECT_NOT_FOUND
|
||||
auto const issuanceKey = keylet::mptokenIssuance(amount.get<MPTIssue>().getMptID());
|
||||
auto const sleIssuance = ctx.view.read(issuanceKey);
|
||||
if (!sleIssuance)
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
// If the issuer has requireAuth set, check if the account is
|
||||
// authorized
|
||||
auto const& mptIssue = amount.get<MPTIssue>();
|
||||
if (auto const ter = requireAuth(ctx.view, mptIssue, account, AuthType::WeakAuth);
|
||||
!isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
EscrowCancel::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
if (ctx.view.rules().enabled(featureTokenEscrow))
|
||||
{
|
||||
auto const seqProxy = SeqProxy::rawSequence(ctx.tx[sfOfferSequence]);
|
||||
auto const k = keylet::escrow(ctx.tx[sfOwner], seqProxy);
|
||||
auto const slep = ctx.view.read(k);
|
||||
if (!slep)
|
||||
EscrowEntryR const escrow(ctx.tx[sfOwner], seqProxy, ctx.view);
|
||||
if (!escrow)
|
||||
return tecNO_TARGET;
|
||||
|
||||
AccountID const account = (*slep)[sfAccount];
|
||||
STAmount const amount = (*slep)[sfAmount];
|
||||
AccountID const account = (*escrow)[sfAccount];
|
||||
STAmount const amount = (*escrow)[sfAmount];
|
||||
|
||||
if (!isXRP(amount))
|
||||
{
|
||||
if (auto const ret = std::visit(
|
||||
[&]<typename T>(T const&) {
|
||||
return escrowCancelPreclaimHelper<T>(ctx, account, amount);
|
||||
return escrowUnlockPreclaimHelper<T>(ctx.view, account, amount, false);
|
||||
},
|
||||
amount.asset().value());
|
||||
!isTesSuccess(ret))
|
||||
@@ -120,9 +61,8 @@ TER
|
||||
EscrowCancel::doApply()
|
||||
{
|
||||
auto const seqProxy = SeqProxy::rawSequence(ctx_.tx[sfOfferSequence]);
|
||||
auto const k = keylet::escrow(ctx_.tx[sfOwner], seqProxy);
|
||||
auto const slep = ctx_.view().peek(k);
|
||||
if (!slep)
|
||||
EscrowEntryW escrow(ctx_.tx[sfOwner], seqProxy, ctx_.view(), j_);
|
||||
if (!escrow)
|
||||
{
|
||||
if (ctx_.view().rules().enabled(featureTokenEscrow))
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
@@ -133,19 +73,19 @@ EscrowCancel::doApply()
|
||||
auto const now = ctx_.view().header().parentCloseTime;
|
||||
|
||||
// No cancel time specified: can't execute at all.
|
||||
if (!(*slep)[~sfCancelAfter])
|
||||
if (!(*escrow)[~sfCancelAfter])
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// Too soon: can't execute before the cancel time.
|
||||
if (!after(now, (*slep)[sfCancelAfter]))
|
||||
if (!after(now, (*escrow)[sfCancelAfter]))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
AccountID const account = (*slep)[sfAccount];
|
||||
AccountID const account = (*escrow)[sfAccount];
|
||||
|
||||
// Remove escrow from owner directory
|
||||
{
|
||||
auto const page = (*slep)[sfOwnerNode];
|
||||
if (!ctx_.view().dirRemove(keylet::ownerDir(account), page, k.key, true))
|
||||
auto const page = (*escrow)[sfOwnerNode];
|
||||
if (!ctx_.view().dirRemove(keylet::ownerDir(account), page, escrow.key(), true))
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j_.fatal()) << "Unable to delete Escrow from owner.";
|
||||
@@ -155,9 +95,10 @@ EscrowCancel::doApply()
|
||||
}
|
||||
|
||||
// Remove escrow from recipient's owner directory, if present.
|
||||
if (auto const optPage = (*slep)[~sfDestinationNode]; optPage)
|
||||
if (auto const optPage = (*escrow)[~sfDestinationNode]; optPage)
|
||||
{
|
||||
if (!ctx_.view().dirRemove(keylet::ownerDir((*slep)[sfDestination]), *optPage, k.key, true))
|
||||
if (!ctx_.view().dirRemove(
|
||||
keylet::ownerDir((*escrow)[sfDestination]), *optPage, escrow.key(), true))
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j_.fatal()) << "Unable to delete Escrow from recipient.";
|
||||
@@ -167,13 +108,13 @@ EscrowCancel::doApply()
|
||||
}
|
||||
|
||||
auto const sle = ctx_.view().peek(keylet::account(account));
|
||||
STAmount const amount = slep->getFieldAmount(sfAmount);
|
||||
STAmount const amount = escrow->getFieldAmount(sfAmount);
|
||||
|
||||
// The return can re-create a holding the owner deleted while the escrow
|
||||
// was pending; the removed escrow must not be counted against its reserve.
|
||||
bool const recycleReserve = ctx_.view().rules().enabled(fixCleanup3_4_0);
|
||||
if (recycleReserve)
|
||||
decreaseOwnerCountForObject(ctx_.view(), sle, slep, 1, ctx_.journal);
|
||||
decreaseOwnerCountForObject(ctx_.view(), sle, escrow.mutableRawSle(), 1, ctx_.journal);
|
||||
|
||||
// Transfer amount back to the owner
|
||||
if (isXRP(amount))
|
||||
@@ -192,7 +133,7 @@ EscrowCancel::doApply()
|
||||
return escrowUnlockApplyHelper<T>(
|
||||
ctx_.getApplyViewContext(),
|
||||
kParityRate,
|
||||
ctx_.view().rules().enabled(fixCleanup3_2_0) ? sle : slep,
|
||||
ctx_.view().rules().enabled(fixCleanup3_2_0) ? sle : escrow.mutableRawSle(),
|
||||
preFeeBalance_,
|
||||
amount,
|
||||
issuer,
|
||||
@@ -206,9 +147,9 @@ EscrowCancel::doApply()
|
||||
return ret; // LCOV_EXCL_LINE
|
||||
|
||||
// Remove escrow from issuers owner directory, if present.
|
||||
if (auto const optPage = (*slep)[~sfIssuerNode]; optPage)
|
||||
if (auto const optPage = (*escrow)[~sfIssuerNode]; optPage)
|
||||
{
|
||||
if (!ctx_.view().dirRemove(keylet::ownerDir(issuer), *optPage, k.key, true))
|
||||
if (!ctx_.view().dirRemove(keylet::ownerDir(issuer), *optPage, escrow.key(), true))
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j_.fatal()) << "Unable to delete Escrow from recipient.";
|
||||
@@ -219,10 +160,10 @@ EscrowCancel::doApply()
|
||||
}
|
||||
|
||||
if (!recycleReserve)
|
||||
decreaseOwnerCountForObject(ctx_.view(), sle, slep, 1, ctx_.journal);
|
||||
decreaseOwnerCountForObject(ctx_.view(), sle, escrow.mutableRawSle(), 1, ctx_.journal);
|
||||
|
||||
// Remove escrow from ledger
|
||||
ctx_.view().erase(slep);
|
||||
escrow.erase();
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
#include <xrpl/core/ServiceRegistry.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/entries/EscrowEntry.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
|
||||
#include <xrpl/ledger/helpers/EscrowHelpers.h>
|
||||
#include <xrpl/ledger/helpers/SponsorHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
@@ -18,6 +18,7 @@
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Issue.h>
|
||||
#include <xrpl/protocol/Keylet.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/MPTAmount.h>
|
||||
#include <xrpl/protocol/MPTIssue.h>
|
||||
@@ -184,162 +185,6 @@ EscrowCreate::preflight(PreflightContext const& ctx)
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
template <ValidIssueType T>
|
||||
static TER
|
||||
escrowCreatePreclaimHelper(
|
||||
PreclaimContext const& ctx,
|
||||
AccountID const& account,
|
||||
AccountID const& dest,
|
||||
STAmount const& amount);
|
||||
|
||||
template <>
|
||||
TER
|
||||
escrowCreatePreclaimHelper<Issue>(
|
||||
PreclaimContext const& ctx,
|
||||
AccountID const& account,
|
||||
AccountID const& dest,
|
||||
STAmount const& amount)
|
||||
{
|
||||
auto const& issue = amount.get<Issue>();
|
||||
AccountID const& issuer = amount.getIssuer();
|
||||
// If the issuer is the same as the account, return tecNO_PERMISSION
|
||||
if (issuer == account)
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// If the lsfAllowTrustLineLocking is not enabled, return tecNO_PERMISSION
|
||||
auto const sleIssuer = ctx.view.read(keylet::account(issuer));
|
||||
if (!sleIssuer)
|
||||
return tecNO_ISSUER;
|
||||
if (!sleIssuer->isFlag(lsfAllowTrustLineLocking))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// If the account does not have a trustline to the issuer, return tecNO_LINE
|
||||
auto const sleRippleState = ctx.view.read(keylet::trustLine(account, issuer, issue.currency));
|
||||
if (!sleRippleState)
|
||||
return tecNO_LINE;
|
||||
|
||||
STAmount const balance = (*sleRippleState)[sfBalance];
|
||||
|
||||
// If balance is positive, issuer must have higher address than account
|
||||
if (balance > beast::kZero && issuer < account)
|
||||
return tecNO_PERMISSION; // LCOV_EXCL_LINE
|
||||
|
||||
// If balance is negative, issuer must have lower address than account
|
||||
if (balance < beast::kZero && issuer > account)
|
||||
return tecNO_PERMISSION; // LCOV_EXCL_LINE
|
||||
|
||||
// If the issuer has requireAuth set, check if the account is authorized
|
||||
if (auto const ter = requireAuth(ctx.view, issue, account); !isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
// If the issuer has requireAuth set, check if the destination is authorized
|
||||
if (auto const ter = requireAuth(ctx.view, issue, dest); !isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
// If the issuer has frozen the account, return tecFROZEN
|
||||
if (isFrozen(ctx.view, account, issue))
|
||||
return tecFROZEN;
|
||||
|
||||
// If the issuer has frozen the destination, return tecFROZEN
|
||||
if (isFrozen(ctx.view, dest, issue))
|
||||
return tecFROZEN;
|
||||
|
||||
STAmount const spendableAmount = accountHolds(
|
||||
ctx.view, account, issue.currency, issuer, FreezeHandling::IgnoreFreeze, ctx.j);
|
||||
|
||||
// If the balance is less than or equal to 0, return tecINSUFFICIENT_FUNDS
|
||||
if (spendableAmount <= beast::kZero)
|
||||
return tecINSUFFICIENT_FUNDS;
|
||||
|
||||
// If the spendable amount is less than the amount, return
|
||||
// tecINSUFFICIENT_FUNDS
|
||||
if (spendableAmount < amount)
|
||||
return tecINSUFFICIENT_FUNDS;
|
||||
|
||||
// If the amount is not addable to the balance, return tecPRECISION_LOSS
|
||||
if (!canAdd(spendableAmount, amount))
|
||||
return tecPRECISION_LOSS;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
template <>
|
||||
TER
|
||||
escrowCreatePreclaimHelper<MPTIssue>(
|
||||
PreclaimContext const& ctx,
|
||||
AccountID const& account,
|
||||
AccountID const& dest,
|
||||
STAmount const& amount)
|
||||
{
|
||||
AccountID const issuer = amount.getIssuer();
|
||||
// If the issuer is the same as the account, return tecNO_PERMISSION
|
||||
if (issuer == account)
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// If the mpt does not exist, return tecOBJECT_NOT_FOUND
|
||||
auto const issuanceKey = keylet::mptokenIssuance(amount.get<MPTIssue>().getMptID());
|
||||
auto const sleIssuance = ctx.view.read(issuanceKey);
|
||||
if (!sleIssuance)
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
// If the lsfMPTCanEscrow is not enabled, return tecNO_PERMISSION
|
||||
if (!sleIssuance->isFlag(lsfMPTCanEscrow))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// If the issuer is not the same as the issuer of the mpt, return
|
||||
// tecNO_PERMISSION
|
||||
if (sleIssuance->getAccountID(sfIssuer) != issuer)
|
||||
return tecNO_PERMISSION; // LCOV_EXCL_LINE
|
||||
|
||||
// If the account does not have the mpt, return tecOBJECT_NOT_FOUND
|
||||
if (!ctx.view.exists(keylet::mptoken(issuanceKey.key, account)))
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
// If the issuer has requireAuth set, check if the account is
|
||||
// authorized
|
||||
auto const& mptIssue = amount.get<MPTIssue>();
|
||||
if (auto const ter = requireAuth(ctx.view, mptIssue, account, AuthType::WeakAuth);
|
||||
!isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
// If the issuer has requireAuth set, check if the destination is
|
||||
// authorized
|
||||
if (auto const ter = requireAuth(ctx.view, mptIssue, dest, AuthType::WeakAuth);
|
||||
!isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
// If the issuer has frozen the account, return tecLOCKED
|
||||
if (isFrozen(ctx.view, account, *sleIssuance))
|
||||
return tecLOCKED;
|
||||
|
||||
// If the issuer has frozen the destination, return tecLOCKED
|
||||
if (isFrozen(ctx.view, dest, *sleIssuance))
|
||||
return tecLOCKED;
|
||||
|
||||
// If the mpt cannot be transferred, return tecNO_AUTH
|
||||
if (auto const ter = canTransfer(ctx.view, mptIssue, account, dest); !isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
STAmount const spendableAmount = accountHolds(
|
||||
ctx.view,
|
||||
account,
|
||||
amount.get<MPTIssue>(),
|
||||
FreezeHandling::IgnoreFreeze,
|
||||
AuthHandling::IgnoreAuth,
|
||||
ctx.j);
|
||||
|
||||
// If the balance is less than or equal to 0, return tecINSUFFICIENT_FUNDS
|
||||
if (spendableAmount <= beast::kZero)
|
||||
return tecINSUFFICIENT_FUNDS;
|
||||
|
||||
// If the spendable amount is less than the amount, return
|
||||
// tecINSUFFICIENT_FUNDS
|
||||
if (spendableAmount < amount)
|
||||
return tecINSUFFICIENT_FUNDS;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
EscrowCreate::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
@@ -365,7 +210,7 @@ EscrowCreate::preclaim(PreclaimContext const& ctx)
|
||||
|
||||
if (auto const ret = std::visit(
|
||||
[&]<typename T>(T const&) {
|
||||
return escrowCreatePreclaimHelper<T>(ctx, account, dest, amount);
|
||||
return escrowLockPreclaimHelper<T>(ctx.view, account, dest, amount, ctx.j);
|
||||
},
|
||||
amount.asset().value());
|
||||
!isTesSuccess(ret))
|
||||
@@ -374,54 +219,6 @@ EscrowCreate::preclaim(PreclaimContext const& ctx)
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
template <ValidIssueType T>
|
||||
static TER
|
||||
escrowLockApplyHelper(
|
||||
ApplyView& view,
|
||||
AccountID const& issuer,
|
||||
AccountID const& sender,
|
||||
STAmount const& amount,
|
||||
beast::Journal journal);
|
||||
|
||||
template <>
|
||||
TER
|
||||
escrowLockApplyHelper<Issue>(
|
||||
ApplyView& view,
|
||||
AccountID const& issuer,
|
||||
AccountID const& sender,
|
||||
STAmount const& amount,
|
||||
beast::Journal journal)
|
||||
{
|
||||
// Defensive: Issuer cannot create an escrow
|
||||
if (issuer == sender)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const ter =
|
||||
directSendNoFee(view, sender, issuer, amount, !amount.holds<MPTIssue>(), journal);
|
||||
if (!isTesSuccess(ter))
|
||||
return ter; // LCOV_EXCL_LINE
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
template <>
|
||||
TER
|
||||
escrowLockApplyHelper<MPTIssue>(
|
||||
ApplyView& view,
|
||||
AccountID const& issuer,
|
||||
AccountID const& sender,
|
||||
STAmount const& amount,
|
||||
beast::Journal journal)
|
||||
{
|
||||
// Defensive: Issuer cannot create an escrow
|
||||
if (issuer == sender)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const ter = lockEscrowMPT(view, sender, amount, journal);
|
||||
if (!isTesSuccess(ter))
|
||||
return ter; // LCOV_EXCL_LINE
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
EscrowCreate::doApply()
|
||||
{
|
||||
@@ -481,30 +278,31 @@ EscrowCreate::doApply()
|
||||
|
||||
// Create escrow in ledger. Note that we use the value from the
|
||||
// sequence or ticket. For more explanation see comments in SeqProxy.h.
|
||||
Keylet const escrowKeylet = keylet::escrow(accountID_, ctx_.tx.getSeqProxy());
|
||||
auto const slep = std::make_shared<SLE>(escrowKeylet);
|
||||
(*slep)[sfAmount] = amount;
|
||||
(*slep)[sfAccount] = accountID_;
|
||||
(*slep)[~sfCondition] = ctx_.tx[~sfCondition];
|
||||
(*slep)[~sfSourceTag] = ctx_.tx[~sfSourceTag];
|
||||
(*slep)[sfDestination] = ctx_.tx[sfDestination];
|
||||
(*slep)[~sfCancelAfter] = ctx_.tx[~sfCancelAfter];
|
||||
(*slep)[~sfFinishAfter] = ctx_.tx[~sfFinishAfter];
|
||||
(*slep)[~sfDestinationTag] = ctx_.tx[~sfDestinationTag];
|
||||
EscrowEntryW escrow(accountID_, ctx_.tx.getSeqProxy(), ctx_.view(), j_);
|
||||
Keylet const escrowKeylet = escrow.keylet();
|
||||
escrow.newSLE();
|
||||
(*escrow)[sfAmount] = amount;
|
||||
(*escrow)[sfAccount] = accountID_;
|
||||
(*escrow)[~sfCondition] = ctx_.tx[~sfCondition];
|
||||
(*escrow)[~sfSourceTag] = ctx_.tx[~sfSourceTag];
|
||||
(*escrow)[sfDestination] = ctx_.tx[sfDestination];
|
||||
(*escrow)[~sfCancelAfter] = ctx_.tx[~sfCancelAfter];
|
||||
(*escrow)[~sfFinishAfter] = ctx_.tx[~sfFinishAfter];
|
||||
(*escrow)[~sfDestinationTag] = ctx_.tx[~sfDestinationTag];
|
||||
|
||||
if (ctx_.view().rules().enabled(fixIncludeKeyletFields))
|
||||
{
|
||||
(*slep)[sfSequence] = ctx_.tx.getSeqProxy().value();
|
||||
(*escrow)[sfSequence] = ctx_.tx.getSeqProxy().value();
|
||||
}
|
||||
|
||||
if (ctx_.view().rules().enabled(featureTokenEscrow) && !isXRP(amount))
|
||||
{
|
||||
auto const xferRate = transferRate(ctx_.view(), amount);
|
||||
if (xferRate != kParityRate)
|
||||
(*slep)[sfTransferRate] = xferRate.value;
|
||||
(*escrow)[sfTransferRate] = xferRate.value;
|
||||
}
|
||||
|
||||
ctx_.view().insert(slep);
|
||||
escrow.insert();
|
||||
|
||||
// Add escrow to sender's owner directory
|
||||
{
|
||||
@@ -512,7 +310,7 @@ EscrowCreate::doApply()
|
||||
keylet::ownerDir(accountID_), escrowKeylet, describeOwnerDir(accountID_));
|
||||
if (!page)
|
||||
return tecDIR_FULL; // LCOV_EXCL_LINE
|
||||
(*slep)[sfOwnerNode] = *page;
|
||||
(*escrow)[sfOwnerNode] = *page;
|
||||
}
|
||||
|
||||
// If it's not a self-send, add escrow to recipient's owner directory.
|
||||
@@ -523,7 +321,7 @@ EscrowCreate::doApply()
|
||||
ctx_.view().dirInsert(keylet::ownerDir(dest), escrowKeylet, describeOwnerDir(dest));
|
||||
if (!page)
|
||||
return tecDIR_FULL; // LCOV_EXCL_LINE
|
||||
(*slep)[sfDestinationNode] = *page;
|
||||
(*escrow)[sfDestinationNode] = *page;
|
||||
}
|
||||
|
||||
// IOU escrow objects are added to the issuer's owner directory to help
|
||||
@@ -536,7 +334,7 @@ EscrowCreate::doApply()
|
||||
ctx_.view().dirInsert(keylet::ownerDir(issuer), escrowKeylet, describeOwnerDir(issuer));
|
||||
if (!page)
|
||||
return tecDIR_FULL; // LCOV_EXCL_LINE
|
||||
(*slep)[sfIssuerNode] = *page;
|
||||
(*escrow)[sfIssuerNode] = *page;
|
||||
}
|
||||
|
||||
// Deduct owner's balance
|
||||
@@ -559,7 +357,7 @@ EscrowCreate::doApply()
|
||||
|
||||
// increment owner count
|
||||
increaseOwnerCount(ctx_.getApplyViewContext(), sle, 1, ctx_.journal);
|
||||
addSponsorToLedgerEntry(ctx_.getApplyViewContext(), slep);
|
||||
addSponsorToLedgerEntry(ctx_.getApplyViewContext(), escrow.mutableRawSle());
|
||||
ctx_.view().update(sle);
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
@@ -9,18 +9,14 @@
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/entries/EscrowEntry.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/CredentialHelpers.h>
|
||||
#include <xrpl/ledger/helpers/EscrowHelpers.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Concepts.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Issue.h>
|
||||
#include <xrpl/protocol/MPTIssue.h>
|
||||
#include <xrpl/protocol/Rate.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
@@ -136,68 +132,6 @@ EscrowFinish::calculateBaseFee(ReadView const& view, STTx const& tx)
|
||||
return Transactor::calculateBaseFee(view, tx) + extraFee;
|
||||
}
|
||||
|
||||
template <ValidIssueType T>
|
||||
static TER
|
||||
escrowFinishPreclaimHelper(
|
||||
PreclaimContext const& ctx,
|
||||
AccountID const& dest,
|
||||
STAmount const& amount);
|
||||
|
||||
template <>
|
||||
TER
|
||||
escrowFinishPreclaimHelper<Issue>(
|
||||
PreclaimContext const& ctx,
|
||||
AccountID const& dest,
|
||||
STAmount const& amount)
|
||||
{
|
||||
AccountID const& issuer = amount.getIssuer();
|
||||
// If the issuer is the same as the account, return tesSUCCESS
|
||||
if (issuer == dest)
|
||||
return tesSUCCESS;
|
||||
|
||||
// If the issuer has requireAuth set, check if the destination is authorized
|
||||
if (auto const ter = requireAuth(ctx.view, amount.get<Issue>(), dest); !isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
// If the issuer has deep frozen the destination, return tecFROZEN
|
||||
if (isDeepFrozen(ctx.view, dest, amount.get<Issue>().currency, amount.getIssuer()))
|
||||
return tecFROZEN;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
template <>
|
||||
TER
|
||||
escrowFinishPreclaimHelper<MPTIssue>(
|
||||
PreclaimContext const& ctx,
|
||||
AccountID const& dest,
|
||||
STAmount const& amount)
|
||||
{
|
||||
AccountID const& issuer = amount.getIssuer();
|
||||
// If the issuer is the same as the dest, return tesSUCCESS
|
||||
if (issuer == dest)
|
||||
return tesSUCCESS;
|
||||
|
||||
// If the mpt does not exist, return tecOBJECT_NOT_FOUND
|
||||
auto const issuanceKey = keylet::mptokenIssuance(amount.get<MPTIssue>().getMptID());
|
||||
auto const sleIssuance = ctx.view.read(issuanceKey);
|
||||
if (!sleIssuance)
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
// If the issuer has requireAuth set, check if the destination is
|
||||
// authorized
|
||||
auto const& mptIssue = amount.get<MPTIssue>();
|
||||
if (auto const ter = requireAuth(ctx.view, mptIssue, dest, AuthType::WeakAuth);
|
||||
!isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
// If the issuer has frozen the destination, return tecLOCKED
|
||||
if (isFrozen(ctx.view, dest, *sleIssuance))
|
||||
return tecLOCKED;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
EscrowFinish::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
@@ -211,19 +145,18 @@ EscrowFinish::preclaim(PreclaimContext const& ctx)
|
||||
if (ctx.view.rules().enabled(featureTokenEscrow))
|
||||
{
|
||||
auto const seqProxy = SeqProxy::rawSequence(ctx.tx[sfOfferSequence]);
|
||||
auto const k = keylet::escrow(ctx.tx[sfOwner], seqProxy);
|
||||
auto const slep = ctx.view.read(k);
|
||||
if (!slep)
|
||||
EscrowEntryR const escrow(ctx.tx[sfOwner], seqProxy, ctx.view);
|
||||
if (!escrow)
|
||||
return tecNO_TARGET;
|
||||
|
||||
AccountID const dest = (*slep)[sfDestination];
|
||||
STAmount const amount = (*slep)[sfAmount];
|
||||
AccountID const dest = (*escrow)[sfDestination];
|
||||
STAmount const amount = (*escrow)[sfAmount];
|
||||
|
||||
if (!isXRP(amount))
|
||||
{
|
||||
if (auto const ret = std::visit(
|
||||
[&]<typename T>(T const&) {
|
||||
return escrowFinishPreclaimHelper<T>(ctx, dest, amount);
|
||||
return escrowUnlockPreclaimHelper<T>(ctx.view, dest, amount);
|
||||
},
|
||||
amount.asset().value());
|
||||
!isTesSuccess(ret))
|
||||
@@ -237,9 +170,8 @@ TER
|
||||
EscrowFinish::doApply()
|
||||
{
|
||||
auto const seqProxy = SeqProxy::rawSequence(ctx_.tx[sfOfferSequence]);
|
||||
auto const k = keylet::escrow(ctx_.tx[sfOwner], seqProxy);
|
||||
auto const slep = ctx_.view().peek(k);
|
||||
if (!slep)
|
||||
EscrowEntryW escrow(ctx_.tx[sfOwner], seqProxy, ctx_.view(), j_);
|
||||
if (!escrow)
|
||||
{
|
||||
if (ctx_.view().rules().enabled(featureTokenEscrow))
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
@@ -252,11 +184,11 @@ EscrowFinish::doApply()
|
||||
auto const now = ctx_.view().header().parentCloseTime;
|
||||
|
||||
// Too soon: can't execute before the finish time
|
||||
if ((*slep)[~sfFinishAfter] && !after(now, (*slep)[sfFinishAfter]))
|
||||
if ((*escrow)[~sfFinishAfter] && !after(now, (*escrow)[sfFinishAfter]))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// Too late: can't execute after the cancel time
|
||||
if ((*slep)[~sfCancelAfter] && after(now, (*slep)[sfCancelAfter]))
|
||||
if ((*escrow)[~sfCancelAfter] && after(now, (*escrow)[sfCancelAfter]))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// Check cryptocondition fulfillment
|
||||
@@ -296,7 +228,7 @@ EscrowFinish::doApply()
|
||||
return tecCRYPTOCONDITION_ERROR;
|
||||
|
||||
// Check against condition in the ledger entry:
|
||||
auto const cond = (*slep)[~sfCondition];
|
||||
auto const cond = (*escrow)[~sfCondition];
|
||||
|
||||
// If a condition wasn't specified during creation,
|
||||
// one shouldn't be included now.
|
||||
@@ -313,7 +245,7 @@ EscrowFinish::doApply()
|
||||
}
|
||||
|
||||
// NOTE: Escrow payments cannot be used to fund accounts.
|
||||
AccountID const destID = (*slep)[sfDestination];
|
||||
AccountID const destID = (*escrow)[sfDestination];
|
||||
auto const sled = ctx_.view().peek(keylet::account(destID));
|
||||
if (!sled)
|
||||
return tecNO_DST;
|
||||
@@ -323,12 +255,12 @@ EscrowFinish::doApply()
|
||||
!isTesSuccess(err))
|
||||
return err;
|
||||
|
||||
AccountID const account = (*slep)[sfAccount];
|
||||
AccountID const account = (*escrow)[sfAccount];
|
||||
|
||||
// Remove escrow from owner directory
|
||||
{
|
||||
auto const page = (*slep)[sfOwnerNode];
|
||||
if (!ctx_.view().dirRemove(keylet::ownerDir(account), page, k.key, true))
|
||||
auto const page = (*escrow)[sfOwnerNode];
|
||||
if (!ctx_.view().dirRemove(keylet::ownerDir(account), page, escrow.key(), true))
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j_.fatal()) << "Unable to delete Escrow from owner.";
|
||||
@@ -338,9 +270,9 @@ EscrowFinish::doApply()
|
||||
}
|
||||
|
||||
// Remove escrow from recipient's owner directory, if present.
|
||||
if (auto const optPage = (*slep)[~sfDestinationNode])
|
||||
if (auto const optPage = (*escrow)[~sfDestinationNode])
|
||||
{
|
||||
if (!ctx_.view().dirRemove(keylet::ownerDir(destID), *optPage, k.key, true))
|
||||
if (!ctx_.view().dirRemove(keylet::ownerDir(destID), *optPage, escrow.key(), true))
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j_.fatal()) << "Unable to delete Escrow from recipient.";
|
||||
@@ -355,9 +287,9 @@ EscrowFinish::doApply()
|
||||
bool const recycleReserve =
|
||||
ctx_.view().rules().enabled(featureSponsor) || ctx_.view().rules().enabled(fixCleanup3_4_0);
|
||||
if (recycleReserve)
|
||||
decreaseOwnerCountForObject(ctx_.view(), account, slep, 1, ctx_.journal);
|
||||
decreaseOwnerCountForObject(ctx_.view(), account, escrow.mutableRawSle(), 1, ctx_.journal);
|
||||
|
||||
STAmount const amount = slep->getFieldAmount(sfAmount);
|
||||
STAmount const amount = escrow->getFieldAmount(sfAmount);
|
||||
// Transfer amount to destination
|
||||
if (isXRP(amount))
|
||||
{
|
||||
@@ -368,8 +300,8 @@ EscrowFinish::doApply()
|
||||
if (!ctx_.view().rules().enabled(featureTokenEscrow))
|
||||
return temDISABLED; // LCOV_EXCL_LINE
|
||||
|
||||
Rate lockedRate = slep->isFieldPresent(sfTransferRate)
|
||||
? xrpl::Rate(slep->getFieldU32(sfTransferRate))
|
||||
Rate lockedRate = escrow->isFieldPresent(sfTransferRate)
|
||||
? xrpl::Rate(escrow->getFieldU32(sfTransferRate))
|
||||
: kParityRate;
|
||||
auto const issuer = amount.getIssuer();
|
||||
bool const createAsset = destID == accountID_;
|
||||
@@ -392,9 +324,9 @@ EscrowFinish::doApply()
|
||||
return ret;
|
||||
|
||||
// Remove escrow from issuers owner directory, if present.
|
||||
if (auto const optPage = (*slep)[~sfIssuerNode]; optPage)
|
||||
if (auto const optPage = (*escrow)[~sfIssuerNode]; optPage)
|
||||
{
|
||||
if (!ctx_.view().dirRemove(keylet::ownerDir(issuer), *optPage, k.key, true))
|
||||
if (!ctx_.view().dirRemove(keylet::ownerDir(issuer), *optPage, escrow.key(), true))
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j_.fatal()) << "Unable to delete Escrow from recipient.";
|
||||
@@ -407,10 +339,10 @@ EscrowFinish::doApply()
|
||||
ctx_.view().update(sled);
|
||||
|
||||
if (!recycleReserve)
|
||||
decreaseOwnerCountForObject(ctx_.view(), account, slep, 1, ctx_.journal);
|
||||
decreaseOwnerCountForObject(ctx_.view(), account, escrow.mutableRawSle(), 1, ctx_.journal);
|
||||
|
||||
// Remove escrow from ledger
|
||||
ctx_.view().erase(slep);
|
||||
escrow.erase();
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Issue.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/MPTAmount.h>
|
||||
#include <xrpl/protocol/MPTIssue.h>
|
||||
#include <xrpl/protocol/Permissions.h>
|
||||
#include <xrpl/protocol/Quality.h>
|
||||
@@ -715,22 +716,55 @@ Payment::doApply()
|
||||
|
||||
// Amount to deliver.
|
||||
STAmount amountDeliver = dstAmount;
|
||||
// Factor in the transfer rate.
|
||||
// No rounding. It'll change once MPT integrated into DEX.
|
||||
STAmount requiredMaxSourceAmount = multiply(dstAmount, rate);
|
||||
// True if the sender would pay more than SendMax.
|
||||
bool exceedsSendMax = false;
|
||||
|
||||
// Send more than the account wants to pay or less than
|
||||
// the account wants to deliver (if no SendMax).
|
||||
// Adjust the amount to deliver.
|
||||
if (partialPaymentAllowed && requiredMaxSourceAmount > maxSourceAmount)
|
||||
if (view().rules().enabled(fixCleanup3_5_0))
|
||||
{
|
||||
requiredMaxSourceAmount = maxSourceAmount;
|
||||
// The legacy multiply() and divide() can overflow on large MPT
|
||||
// amounts, and Number rounding can charge more than SendMax.
|
||||
// MPTs are integral, so use exact integer arithmetic. The cost
|
||||
// is rounded up, matching the transfer fee that accountSend
|
||||
// charges. If the cost overflows, it exceeds any SendMax.
|
||||
auto const requiredMaxSourceAmount =
|
||||
tryMulRatio(dstAmount.mpt(), rate.value, QUALITY_ONE, true);
|
||||
exceedsSendMax =
|
||||
!requiredMaxSourceAmount || maxSourceAmount.mpt() < *requiredMaxSourceAmount;
|
||||
|
||||
// Send more than the account wants to pay or less than
|
||||
// the account wants to deliver (if no SendMax).
|
||||
// Adjust the amount to deliver.
|
||||
if (partialPaymentAllowed && exceedsSendMax)
|
||||
{
|
||||
// Round the delivered amount down so that the sender is
|
||||
// never charged more than SendMax.
|
||||
auto const delivered =
|
||||
mulRatio(maxSourceAmount.mpt(), QUALITY_ONE, rate.value, false);
|
||||
amountDeliver = STAmount(maxSourceAmount.asset(), delivered.value());
|
||||
if (amountDeliver <= beast::kZero)
|
||||
return tecPATH_PARTIAL;
|
||||
exceedsSendMax = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Factor in the transfer rate.
|
||||
// No rounding. It'll change once MPT integrated into DEX.
|
||||
amountDeliver = divide(maxSourceAmount, rate);
|
||||
STAmount requiredMaxSourceAmount = multiply(dstAmount, rate);
|
||||
|
||||
// Send more than the account wants to pay or less than
|
||||
// the account wants to deliver (if no SendMax).
|
||||
// Adjust the amount to deliver.
|
||||
if (partialPaymentAllowed && requiredMaxSourceAmount > maxSourceAmount)
|
||||
{
|
||||
requiredMaxSourceAmount = maxSourceAmount;
|
||||
// No rounding. It'll change once MPT integrated into DEX.
|
||||
amountDeliver = divide(maxSourceAmount, rate);
|
||||
}
|
||||
exceedsSendMax = requiredMaxSourceAmount > maxSourceAmount;
|
||||
}
|
||||
|
||||
if (requiredMaxSourceAmount > maxSourceAmount ||
|
||||
(deliverMin && amountDeliver < *deliverMin))
|
||||
if (exceedsSendMax || (deliverMin && amountDeliver < *deliverMin))
|
||||
return tecPATH_PARTIAL;
|
||||
|
||||
PaymentSandbox pv(&view());
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/helpers/SponsorHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STAccount.h> // IWYU pragma: keep
|
||||
@@ -297,11 +298,13 @@ Batch::preflight(PreflightContext const& ctx)
|
||||
}
|
||||
|
||||
auto const txType = stx.getFieldU16(sfTransactionType);
|
||||
if (std::ranges::any_of(
|
||||
kDisabledTxTypes, [txType](auto const& disabled) { return txType == disabled; }))
|
||||
{
|
||||
// Pre-LendingProtocolV1_2: SAV and Lending transactions cannot be Batch inners.
|
||||
// Post-LendingProtocolV1_2: they continue through the normal Batch checks.
|
||||
bool const rejectedPreV12 = !ctx.rules.enabled(featureLendingProtocolV1_2) &&
|
||||
std::ranges::any_of(
|
||||
kDisabledTxTypes, [txType](auto const& disabled) { return txType == disabled; });
|
||||
if (rejectedPreV12)
|
||||
return temINVALID_INNER_BATCH;
|
||||
}
|
||||
|
||||
if (!stx.isFlag(tfInnerBatchTxn))
|
||||
{
|
||||
|
||||
@@ -8,8 +8,10 @@
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/core/ServiceRegistry.h>
|
||||
#include <xrpl/ledger/AmendmentTable.h>
|
||||
#include <xrpl/ledger/entries/AmendmentsEntry.h>
|
||||
#include <xrpl/ledger/entries/FeeSettingsEntry.h>
|
||||
#include <xrpl/ledger/entries/NegativeUNLEntry.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/PublicKey.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
@@ -25,7 +27,6 @@
|
||||
#include <xrpl/tx/Transactor.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
@@ -168,14 +169,12 @@ Change::applyAmendment()
|
||||
{
|
||||
UInt256 const amendment(ctx_.tx.getFieldH256(sfAmendment));
|
||||
|
||||
auto const k = keylet::amendments();
|
||||
|
||||
SLE::pointer amendmentObject = view().peek(k);
|
||||
AmendmentsEntryW amendmentObject(view(), j_);
|
||||
|
||||
if (!amendmentObject)
|
||||
{
|
||||
amendmentObject = std::make_shared<SLE>(k);
|
||||
view().insert(amendmentObject);
|
||||
amendmentObject.newSLE();
|
||||
amendmentObject.insert();
|
||||
}
|
||||
|
||||
STVector256 amendments = amendmentObject->getFieldV256(sfAmendments);
|
||||
@@ -252,7 +251,7 @@ Change::applyAmendment()
|
||||
amendmentObject->setFieldArray(sfMajorities, newMajorities);
|
||||
}
|
||||
|
||||
view().update(amendmentObject);
|
||||
amendmentObject.update();
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
@@ -260,16 +259,14 @@ Change::applyAmendment()
|
||||
TER
|
||||
Change::applyFee()
|
||||
{
|
||||
auto const k = keylet::feeSettings();
|
||||
|
||||
SLE::pointer feeObject = view().peek(k);
|
||||
FeeSettingsEntryW feeObject(view());
|
||||
|
||||
if (!feeObject)
|
||||
{
|
||||
feeObject = std::make_shared<SLE>(k);
|
||||
view().insert(feeObject);
|
||||
feeObject.newSLE();
|
||||
feeObject.insert();
|
||||
}
|
||||
auto set = [](SLE::pointer& feeObject, STTx const& tx, auto const& field) {
|
||||
auto set = [](FeeSettingsEntryW& feeObject, STTx const& tx, auto const& field) {
|
||||
feeObject->at(field) = tx[field];
|
||||
};
|
||||
if (view().rules().enabled(featureXRPFees))
|
||||
@@ -291,7 +288,7 @@ Change::applyFee()
|
||||
set(feeObject, ctx_.tx, sfReserveIncrement);
|
||||
}
|
||||
|
||||
view().update(feeObject);
|
||||
feeObject.update();
|
||||
|
||||
JLOG(j_.warn()) << "Fees have been changed";
|
||||
return tesSUCCESS;
|
||||
@@ -332,12 +329,11 @@ Change::applyUNLModify()
|
||||
JLOG(j_.info()) << "N-UNL: applyUNLModify, " << (disabling ? "ToDisable" : "ToReEnable")
|
||||
<< " seq=" << seq << " validator data:" << strHex(validator);
|
||||
|
||||
auto const k = keylet::negativeUNL();
|
||||
SLE::pointer negUnlObject = view().peek(k);
|
||||
NegativeUNLEntryW negUnlObject(view(), j_);
|
||||
if (!negUnlObject)
|
||||
{
|
||||
negUnlObject = std::make_shared<SLE>(k);
|
||||
view().insert(negUnlObject);
|
||||
negUnlObject.newSLE();
|
||||
negUnlObject.insert();
|
||||
}
|
||||
|
||||
bool const found = [&] {
|
||||
@@ -410,7 +406,7 @@ Change::applyUNLModify()
|
||||
negUnlObject->setFieldVL(sfValidatorToReEnable, validator);
|
||||
}
|
||||
|
||||
view().update(negUnlObject);
|
||||
negUnlObject.update();
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <test/jtx/offer.h>
|
||||
#include <test/jtx/paths.h>
|
||||
#include <test/jtx/pay.h>
|
||||
#include <test/jtx/paychan.h>
|
||||
#include <test/jtx/rate.h>
|
||||
#include <test/jtx/sendmax.h>
|
||||
#include <test/jtx/seq.h>
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <test/jtx/offer.h>
|
||||
#include <test/jtx/paths.h>
|
||||
#include <test/jtx/pay.h>
|
||||
#include <test/jtx/paychan.h>
|
||||
#include <test/jtx/rate.h>
|
||||
#include <test/jtx/sendmax.h>
|
||||
#include <test/jtx/seq.h>
|
||||
|
||||
+175
-21
@@ -47,7 +47,6 @@
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Issue.h>
|
||||
#include <xrpl/protocol/KeyType.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/MPTIssue.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
@@ -63,13 +62,13 @@
|
||||
#include <xrpl/protocol/Units.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
#include <xrpl/rdb/RelationalDatabase.h>
|
||||
#include <xrpl/server/NetworkOPs.h>
|
||||
#include <xrpl/tx/Transactor.h>
|
||||
#include <xrpl/tx/apply.h>
|
||||
#include <xrpl/tx/transactors/payment/Payment.h>
|
||||
#include <xrpl/tx/transactors/system/Batch.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <exception>
|
||||
@@ -120,15 +119,6 @@ class Batch_test : public beast::unit_test::Suite
|
||||
return env.rpc("json", "ledger", to_string(params));
|
||||
}
|
||||
|
||||
void
|
||||
validateInnerTxn(jtx::Env& env, std::string const& batchID, TestLedgerData const& ledgerResult)
|
||||
{
|
||||
json::Value const jrr = env.rpc("tx", ledgerResult.txHash)[jss::result];
|
||||
BEAST_EXPECT(jrr[sfTransactionType.jsonName] == ledgerResult.txType);
|
||||
BEAST_EXPECT(jrr[jss::meta][sfTransactionResult.jsonName] == ledgerResult.result);
|
||||
BEAST_EXPECT(jrr[jss::meta][sfParentBatchID.jsonName] == batchID);
|
||||
}
|
||||
|
||||
void
|
||||
validateClosedLedger(jtx::Env& env, std::vector<TestLedgerData> const& ledgerResults)
|
||||
{
|
||||
@@ -144,7 +134,14 @@ class Batch_test : public beast::unit_test::Suite
|
||||
BEAST_EXPECT(txn[sfTransactionType.jsonName] == ledgerResult.txType);
|
||||
BEAST_EXPECT(meta[sfTransactionResult.jsonName] == ledgerResult.result);
|
||||
if (ledgerResult.batchID)
|
||||
validateInnerTxn(env, *ledgerResult.batchID, ledgerResult);
|
||||
{
|
||||
jtx::batch::validateInnerTxn(
|
||||
env,
|
||||
*ledgerResult.batchID,
|
||||
ledgerResult.txHash,
|
||||
ledgerResult.txType,
|
||||
ledgerResult.result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3140,9 +3137,7 @@ class Batch_test : public beast::unit_test::Suite
|
||||
{
|
||||
testcase("loan");
|
||||
|
||||
bool const lendingBatchEnabled = !std::ranges::any_of(
|
||||
Batch::kDisabledTxTypes,
|
||||
[](auto const& disabled) { return disabled == ttLOAN_BROKER_SET; });
|
||||
bool const lendingBatchEnabled = features[featureLendingProtocolV1_2];
|
||||
|
||||
using namespace test::jtx;
|
||||
|
||||
@@ -3291,8 +3286,12 @@ class Batch_test : public beast::unit_test::Suite
|
||||
batch::Sig(borrower));
|
||||
}
|
||||
env.close();
|
||||
BEAST_EXPECT(env.le(brokerKeylet));
|
||||
BEAST_EXPECT(!env.le(loanKeylet));
|
||||
auto const brokerSleBefore = env.le(brokerKeylet);
|
||||
if (!BEAST_EXPECT(brokerSleBefore))
|
||||
return;
|
||||
auto const coverAvailableBefore = brokerSleBefore->at(sfCoverAvailable);
|
||||
auto const coverDepositAmount = asset(100).value();
|
||||
{
|
||||
// LoanSet normally charges at least 2x base fee, but since the
|
||||
// signature check is done by the batch, it only charges the
|
||||
@@ -3311,19 +3310,65 @@ class Batch_test : public beast::unit_test::Suite
|
||||
Fee(kNone),
|
||||
Seq(kNone)),
|
||||
lenderSeq + 1),
|
||||
batch::Inner(manage(lender, loanKeylet.key, tfLoanImpair), lenderSeq + 2),
|
||||
batch::Inner(
|
||||
loan_broker::coverDeposit(lender, brokerKeylet.key, coverDepositAmount),
|
||||
lenderSeq + 2),
|
||||
batch::Sig(borrower));
|
||||
}
|
||||
env.close();
|
||||
BEAST_EXPECT(env.le(brokerKeylet));
|
||||
if (auto const sleLoan = env.le(loanKeylet);
|
||||
lendingBatchEnabled ? BEAST_EXPECT(sleLoan) : !BEAST_EXPECT(!sleLoan))
|
||||
BEAST_EXPECT(static_cast<bool>(env.le(loanKeylet)) == lendingBatchEnabled);
|
||||
if (auto const brokerSle = env.le(brokerKeylet); BEAST_EXPECT(brokerSle))
|
||||
{
|
||||
BEAST_EXPECT(sleLoan->isFlag(lsfLoanImpaired));
|
||||
auto const expectedCover = lendingBatchEnabled
|
||||
? coverAvailableBefore + coverDepositAmount
|
||||
: coverAvailableBefore;
|
||||
BEAST_EXPECT(brokerSle->at(sfCoverAvailable) == expectedCover);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testLendingAmendment(FeatureBitset features)
|
||||
{
|
||||
testcase("lending amendment");
|
||||
|
||||
using namespace test::jtx;
|
||||
|
||||
// Before LendingProtocolV1_2 a Vault inner transaction rejects the whole batch.
|
||||
auto const checkVaultBatch = [this](FeatureBitset amendments) {
|
||||
bool const lendingBatchEnabled = amendments[featureLendingProtocolV1_2];
|
||||
Env env{*this, amendments};
|
||||
|
||||
Account const payer{"payer"};
|
||||
Account const lender{"lender"};
|
||||
env.fund(XRP(100'000), payer, lender);
|
||||
env.close();
|
||||
|
||||
Vault const vault{env};
|
||||
auto [create, vaultKeylet] = vault.create({.owner = lender, .asset = xrpIssue()});
|
||||
|
||||
auto const payerSeq = env.seq(payer);
|
||||
auto const lenderSeq = env.seq(lender);
|
||||
auto const batchFee = batch::calcBatchFee(env, 1, 2);
|
||||
submitBatch(
|
||||
env,
|
||||
lendingBatchEnabled ? TER{tesSUCCESS} : TER{temINVALID_INNER_BATCH},
|
||||
batch::outer(payer, payerSeq, batchFee, tfAllOrNothing),
|
||||
batch::Inner(create, lenderSeq),
|
||||
batch::Inner(
|
||||
vault.deposit(
|
||||
{.depositor = lender, .id = vaultKeylet.key, .amount = XRP(1'000)}),
|
||||
lenderSeq + 1),
|
||||
batch::Sig(lender));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(static_cast<bool>(env.le(vaultKeylet)) == lendingBatchEnabled);
|
||||
};
|
||||
|
||||
checkVaultBatch(features - featureLendingProtocolV1_2);
|
||||
checkVaultBatch(features);
|
||||
}
|
||||
|
||||
void
|
||||
testObjectCreateSequence(FeatureBitset features)
|
||||
{
|
||||
@@ -5896,6 +5941,113 @@ class Batch_test : public beast::unit_test::Suite
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testInnerResults(FeatureBitset features)
|
||||
{
|
||||
// `tx` on a validated Batch reports each inner transaction's outcome from the
|
||||
// BatchInnerResults table, including inner transactions that never reached the
|
||||
// ledger.
|
||||
testcase("inner results");
|
||||
|
||||
using namespace test::jtx;
|
||||
|
||||
Env env{*this, features};
|
||||
auto const alice = Account("alice");
|
||||
auto const bob = Account("bob");
|
||||
env.fund(XRP(10000), alice, bob);
|
||||
env.close();
|
||||
|
||||
// tfAllOrNothing with a future-sequence inner: the first inner applies to the batch
|
||||
// view and is then rolled back, the second fails with terPRE_SEQ. Neither is in the
|
||||
// ledger; both are recorded, both applied == false.
|
||||
{
|
||||
auto const aliceSeq = env.seq(alice);
|
||||
auto const batchFee = batch::calcBatchFee(env, 1, 2);
|
||||
auto const [txIDs, batchID] = submitBatch(
|
||||
env,
|
||||
tesSUCCESS,
|
||||
batch::outer(alice, aliceSeq, batchFee, tfAllOrNothing),
|
||||
batch::Inner(pay(alice, bob, XRP(10)), aliceSeq + 1),
|
||||
batch::Inner(pay(alice, bob, XRP(5)), aliceSeq + 10));
|
||||
env.close();
|
||||
|
||||
auto const jrr = env.rpc("tx", batchID)[jss::result];
|
||||
BEAST_EXPECT(jrr[jss::validated] == true);
|
||||
if (BEAST_EXPECT(jrr.isMember(jss::inner_results)))
|
||||
{
|
||||
auto const& inner = jrr[jss::inner_results];
|
||||
BEAST_EXPECT(inner.isArray());
|
||||
if (BEAST_EXPECT(inner.size() == 2))
|
||||
{
|
||||
BEAST_EXPECT(inner[0u][jss::hash] == txIDs[0]);
|
||||
BEAST_EXPECT(inner[0u][jss::result] == "tesSUCCESS");
|
||||
BEAST_EXPECT(inner[0u][jss::applied] == false);
|
||||
BEAST_EXPECT(inner[1u][jss::hash] == txIDs[1]);
|
||||
BEAST_EXPECT(inner[1u][jss::result] == "terPRE_SEQ");
|
||||
BEAST_EXPECT(inner[1u][jss::applied] == false);
|
||||
}
|
||||
}
|
||||
// The rolled-back inner transaction is not in the ledger.
|
||||
BEAST_EXPECT(env.rpc("tx", txIDs[0])[jss::result][jss::error] == "txnNotFound");
|
||||
|
||||
// getBatchInnerResults is scoped to the Batch's own ledger sequence: the
|
||||
// rows it just wrote come back for that sequence and for no other, so a
|
||||
// stale row from a different sequence can never surface through `tx`.
|
||||
UInt256 parentBatchId;
|
||||
BEAST_EXPECT(parentBatchId.parseHex(batchID));
|
||||
auto const batchSeq = env.closed()->seq();
|
||||
BEAST_EXPECT(
|
||||
env.app()
|
||||
.getRelationalDatabase()
|
||||
.getBatchInnerResults(parentBatchId, batchSeq)
|
||||
.size() == 2);
|
||||
BEAST_EXPECT(env.app()
|
||||
.getRelationalDatabase()
|
||||
.getBatchInnerResults(parentBatchId, batchSeq + 1)
|
||||
.empty());
|
||||
}
|
||||
|
||||
// tfIndependent, both succeed: both applied, in RawTransactions order.
|
||||
{
|
||||
auto const aliceSeq = env.seq(alice);
|
||||
auto const batchFee = batch::calcBatchFee(env, 1, 2);
|
||||
auto const [txIDs, batchID] = submitBatch(
|
||||
env,
|
||||
tesSUCCESS,
|
||||
batch::outer(alice, aliceSeq, batchFee, tfIndependent),
|
||||
batch::Inner(pay(alice, bob, XRP(10)), aliceSeq + 1),
|
||||
batch::Inner(pay(alice, bob, XRP(5)), aliceSeq + 2));
|
||||
env.close();
|
||||
|
||||
auto const jrr = env.rpc("tx", batchID)[jss::result];
|
||||
if (BEAST_EXPECT(jrr.isMember(jss::inner_results)))
|
||||
{
|
||||
auto const& inner = jrr[jss::inner_results];
|
||||
if (BEAST_EXPECT(inner.size() == 2))
|
||||
{
|
||||
for (unsigned i = 0; i < 2; ++i)
|
||||
{
|
||||
BEAST_EXPECT(inner[i][jss::hash] == txIDs[i]);
|
||||
BEAST_EXPECT(inner[i][jss::result] == "tesSUCCESS");
|
||||
BEAST_EXPECT(inner[i][jss::applied] == true);
|
||||
}
|
||||
}
|
||||
}
|
||||
// The inner transactions themselves carry no inner_results.
|
||||
BEAST_EXPECT(!env.rpc("tx", txIDs[0])[jss::result].isMember(jss::inner_results));
|
||||
}
|
||||
|
||||
// A non-Batch transaction never has the field.
|
||||
{
|
||||
auto const jt = env.jt(pay(alice, bob, XRP(1)));
|
||||
env(jt);
|
||||
env.close();
|
||||
auto const jrr = env.rpc("tx", strHex(jt.stx->getTransactionID()))[jss::result];
|
||||
BEAST_EXPECT(jrr[jss::validated] == true);
|
||||
BEAST_EXPECT(!jrr.isMember(jss::inner_results));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testWithFeats(FeatureBitset features)
|
||||
{
|
||||
@@ -5915,6 +6067,7 @@ class Batch_test : public beast::unit_test::Suite
|
||||
testCheckAllSignatures(features);
|
||||
testAccountSet(features);
|
||||
testAccountDelete(features);
|
||||
testLendingAmendment(features);
|
||||
testLoan(features);
|
||||
testObjectCreateSequence(features);
|
||||
testObjectCreateTicket(features);
|
||||
@@ -5931,6 +6084,7 @@ class Batch_test : public beast::unit_test::Suite
|
||||
testBatchDelegateConsent(features);
|
||||
testValidateRPCResponse(features);
|
||||
testBatchCalculateBaseFee(features);
|
||||
testInnerResults(features);
|
||||
testStandaloneInnerBatchFlag(features);
|
||||
testOuterBinding(features);
|
||||
testUnsortedBatchSigners(features);
|
||||
|
||||
@@ -18,10 +18,12 @@
|
||||
#include <test/jtx/offer.h>
|
||||
#include <test/jtx/paths.h>
|
||||
#include <test/jtx/pay.h>
|
||||
#include <test/jtx/permissioned_domains.h>
|
||||
#include <test/jtx/rate.h>
|
||||
#include <test/jtx/regkey.h>
|
||||
#include <test/jtx/sendmax.h>
|
||||
#include <test/jtx/sig.h>
|
||||
#include <test/jtx/sponsor.h>
|
||||
#include <test/jtx/ter.h>
|
||||
#include <test/jtx/trust.h>
|
||||
#include <test/jtx/txflags.h>
|
||||
@@ -1035,6 +1037,15 @@ class Delegate_test : public beast::unit_test::Suite
|
||||
env(delegate::set(gw, bob, {"PaymentMint"}));
|
||||
env.close();
|
||||
|
||||
// Cross-currency sfSendMax without any flag passes the granular
|
||||
// template and is rejected by the granular semantic check, because
|
||||
// the sfSendMax asset (XRP) is not the same as the sfAmount asset (USD).
|
||||
env(pay(gw, alice, usd(500)),
|
||||
Sendmax(XRP(1001)),
|
||||
delegate::As(bob),
|
||||
Ter(terNO_DELEGATE_PERMISSION));
|
||||
BEAST_EXPECT(expectOffers(env, carol, 1));
|
||||
|
||||
// bob can not send cross currency payment on behalf of the gw,
|
||||
// even with PaymentMint permission and gw being the issuer.
|
||||
env(pay(gw, alice, usd(5000)),
|
||||
@@ -1063,6 +1074,15 @@ class Delegate_test : public beast::unit_test::Suite
|
||||
env(delegate::set(alice, bob, {"PaymentBurn"}));
|
||||
env.close();
|
||||
|
||||
// Cross-currency sfSendMax without any flag passes the granular
|
||||
// template and is rejected by the granular semantic check, because
|
||||
// the sfSendMax asset (XRP) is not the same as the sfAmount asset (USD).
|
||||
env(pay(alice, gw, usd(500)),
|
||||
Sendmax(XRP(1001)),
|
||||
delegate::As(bob),
|
||||
Ter(terNO_DELEGATE_PERMISSION));
|
||||
BEAST_EXPECT(expectOffers(env, bob, 1));
|
||||
|
||||
// bob can not send cross currency payment on behalf of alice,
|
||||
// even with PaymentBurn permission and gw being the issuer.
|
||||
env(pay(alice, gw, usd(5000)),
|
||||
@@ -1263,6 +1283,34 @@ class Delegate_test : public beast::unit_test::Suite
|
||||
Domain(UInt256{1}),
|
||||
delegate::As(bob),
|
||||
Ter(terNO_DELEGATE_PERMISSION));
|
||||
|
||||
// sfPaths is not in the PaymentMint or PaymentBurn template.
|
||||
env(pay(gw, alice, usd(100)),
|
||||
Path(~XRP),
|
||||
delegate::As(bob),
|
||||
Ter(terNO_DELEGATE_PERMISSION));
|
||||
env(pay(alice, gw, usd(50)),
|
||||
Path(~XRP),
|
||||
delegate::As(bob),
|
||||
Ter(terNO_DELEGATE_PERMISSION));
|
||||
|
||||
// Valid Payment flags that are not in the PaymentMint or PaymentBurn template.
|
||||
for (auto const flag : {tfPartialPayment, tfNoRippleDirect, tfLimitQuality})
|
||||
{
|
||||
env(pay(gw, alice, usd(100)),
|
||||
Txflags(flag),
|
||||
delegate::As(bob),
|
||||
Ter(terNO_DELEGATE_PERMISSION));
|
||||
env(pay(alice, gw, usd(50)),
|
||||
Txflags(flag),
|
||||
delegate::As(bob),
|
||||
Ter(terNO_DELEGATE_PERMISSION));
|
||||
}
|
||||
|
||||
// The same payments without the extra field or flag succeed.
|
||||
env(pay(gw, alice, usd(100)), delegate::As(bob));
|
||||
env(pay(alice, gw, usd(50)), delegate::As(bob));
|
||||
env.require(Balance(alice, usd(50)));
|
||||
}
|
||||
|
||||
// Delegate account holds no granular permissions for the tx type:
|
||||
@@ -2266,6 +2314,107 @@ class Delegate_test : public beast::unit_test::Suite
|
||||
// terNO_DELEGATE_PERMISSION. The set of permitted flags for delegation is defined in
|
||||
// permissions.macro.
|
||||
}
|
||||
|
||||
// Fields and flags not in permissions.macro template are not permitted for delegation.
|
||||
{
|
||||
Env env(*this);
|
||||
Account const alice{"alice"};
|
||||
Account const bob{"bob"};
|
||||
Account const credIssuer{"credIssuer"};
|
||||
env.fund(XRP(100000), alice, bob, credIssuer);
|
||||
env.close();
|
||||
|
||||
env(pdomain::setTx(credIssuer, {{.issuer = credIssuer, .credType = "credential"}}));
|
||||
auto const domainID = pdomain::getNewDomain(env.meta());
|
||||
env.close();
|
||||
|
||||
MPTTester mpt(env, alice, {.fund = false});
|
||||
mpt.create({.flags = tfMPTCanLock | tfMPTCanTransfer});
|
||||
env.close();
|
||||
|
||||
// bob holds every granular permission defined for MPTokenIssuanceSet
|
||||
env(delegate::set(alice, bob, {"MPTokenIssuanceLock", "MPTokenIssuanceUnlock"}));
|
||||
env.close();
|
||||
|
||||
// Any of those fields or flags not defined in permissions.macro are not permitted for
|
||||
// delegation.
|
||||
std::vector<MPTSet> const args = {
|
||||
{.transferFee = 100},
|
||||
{.metadata = "test"},
|
||||
{.domainID = domainID},
|
||||
{.flags = tfMPTSetCanTrade},
|
||||
{.flags = tfMPTSetCanClawback},
|
||||
{.immutableFlags = tifMPTTransferFee},
|
||||
// a permitted flag does not unlock the extra fields
|
||||
{.flags = tfMPTLock, .domainID = domainID},
|
||||
};
|
||||
|
||||
for (auto arg : args)
|
||||
{
|
||||
arg.account = alice;
|
||||
arg.delegate = bob;
|
||||
arg.err = terNO_DELEGATE_PERMISSION;
|
||||
mpt.set(arg);
|
||||
env.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testGranularPermissionWithSponsorCommonFields()
|
||||
{
|
||||
testcase("test granular permission with sponsor common fields");
|
||||
using namespace jtx;
|
||||
|
||||
Account const alice{"alice"};
|
||||
Account const bob{"bob"};
|
||||
Account const gw{"gw"};
|
||||
Account const sponsor{"sponsor"};
|
||||
auto const usd = gw["USD"];
|
||||
auto const feeAmt = XRP(10);
|
||||
|
||||
// Sponsor fields are common fields, so they are permitted in every granular template.
|
||||
// Fee sponsorship is allowed for a granular delegated transaction.
|
||||
Env env(*this);
|
||||
env.fund(XRP(10000), alice, bob, gw, sponsor);
|
||||
env.trust(usd(200), alice);
|
||||
env.close();
|
||||
|
||||
env(delegate::set(gw, bob, {"PaymentMint"}));
|
||||
env.close();
|
||||
|
||||
// Co-signed: the sponsor account pays the fee.
|
||||
auto const gwBalance = env.balance(gw);
|
||||
auto const bobBalance = env.balance(bob);
|
||||
auto const sponsorBalance = env.balance(sponsor);
|
||||
env(pay(gw, alice, usd(50)),
|
||||
delegate::As(bob),
|
||||
Fee(feeAmt),
|
||||
sponsor::As(sponsor, spfSponsorFee),
|
||||
Sig(sfSponsorSignature, sponsor));
|
||||
env.close();
|
||||
env.require(Balance(alice, usd(50)));
|
||||
BEAST_EXPECT(env.balance(gw) == gwBalance);
|
||||
BEAST_EXPECT(env.balance(bob) == bobBalance);
|
||||
BEAST_EXPECT(env.balance(sponsor) == sponsorBalance - feeAmt);
|
||||
|
||||
// Pre-funded: sponsorship(sponsor, bob) pays the fee, bob is sfDelegate.
|
||||
env(sponsor::set_fee(sponsor, 0, XRP(100)), sponsor::SponseeAcc(bob));
|
||||
env.close();
|
||||
auto const sponsorFee = sponsor::sponsorshipFeeBalance(env, sponsor, bob);
|
||||
env(pay(gw, alice, usd(50)),
|
||||
delegate::As(bob),
|
||||
Fee(feeAmt),
|
||||
sponsor::As(sponsor, spfSponsorFee));
|
||||
env.close();
|
||||
env.require(Balance(alice, usd(100)));
|
||||
BEAST_EXPECT(env.balance(gw) == gwBalance);
|
||||
BEAST_EXPECT(env.balance(bob) == bobBalance);
|
||||
BEAST_EXPECT(sponsor::sponsorshipFeeBalance(env, sponsor, bob) == sponsorFee - feeAmt);
|
||||
|
||||
// Reserve sponsorship is not tested here: checkSponsor rejects it with temINVALID for any
|
||||
// delegated transaction before the permission check, regardless of the delegate's
|
||||
// permissions. It is covered by testDelegateBlockReserveSponsor in Sponsor_test.
|
||||
}
|
||||
|
||||
void
|
||||
@@ -3002,6 +3151,7 @@ class Delegate_test : public beast::unit_test::Suite
|
||||
testTrustSetGranular();
|
||||
testAccountSetGranular();
|
||||
testMPTokenIssuanceSetGranular();
|
||||
testGranularPermissionWithSponsorCommonFields();
|
||||
testSingleSign();
|
||||
testSingleSignBadSecret();
|
||||
testMultiSign();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
|
||||
#include <test/jtx/Account.h>
|
||||
#include <test/jtx/Env.h>
|
||||
#include <test/jtx/TestHelpers.h>
|
||||
#include <test/jtx/amount.h>
|
||||
#include <test/jtx/balance.h> // IWYU pragma: keep
|
||||
#include <test/jtx/escrow.h>
|
||||
@@ -17,7 +18,6 @@
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/core/ServiceRegistry.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/json/to_string.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/Dir.h>
|
||||
#include <xrpl/ledger/OpenView.h>
|
||||
@@ -25,7 +25,6 @@
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Issue.h>
|
||||
#include <xrpl/protocol/MPTIssue.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
@@ -46,52 +45,6 @@ namespace xrpl::test {
|
||||
|
||||
struct EscrowToken_test : public beast::unit_test::Suite
|
||||
{
|
||||
static uint64_t
|
||||
mptEscrowed(jtx::Env const& env, jtx::Account const& account, jtx::MPT const& mpt)
|
||||
{
|
||||
auto const sle = env.le(keylet::mptoken(mpt.mpt(), account));
|
||||
if (sle && sle->isFieldPresent(sfLockedAmount))
|
||||
return (*sle)[sfLockedAmount];
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint64_t
|
||||
issuerMPTEscrowed(jtx::Env const& env, jtx::MPT const& mpt)
|
||||
{
|
||||
auto const sle = env.le(keylet::mptokenIssuance(mpt.mpt()));
|
||||
if (sle && sle->isFieldPresent(sfLockedAmount))
|
||||
return (*sle)[sfLockedAmount];
|
||||
return 0;
|
||||
}
|
||||
|
||||
static jtx::PrettyAmount
|
||||
issuerBalance(jtx::Env& env, jtx::Account const& account, Issue const& issue)
|
||||
{
|
||||
json::Value params;
|
||||
params[jss::account] = account.human();
|
||||
auto jrr = env.rpc("json", "gateway_balances", to_string(params));
|
||||
auto const result = jrr[jss::result];
|
||||
auto const obligations = result[jss::obligations][to_string(issue.currency)];
|
||||
if (obligations.isNull())
|
||||
return {STAmount(issue, 0), account.name()};
|
||||
STAmount const amount = amountFromString(issue, obligations.asString());
|
||||
return {amount, account.name()};
|
||||
}
|
||||
|
||||
static jtx::PrettyAmount
|
||||
issuerEscrowed(jtx::Env& env, jtx::Account const& account, Issue const& issue)
|
||||
{
|
||||
json::Value params;
|
||||
params[jss::account] = account.human();
|
||||
auto jrr = env.rpc("json", "gateway_balances", to_string(params));
|
||||
auto const result = jrr[jss::result];
|
||||
auto const locked = result[jss::locked][to_string(issue.currency)];
|
||||
if (locked.isNull())
|
||||
return {STAmount(issue, 0), account.name()};
|
||||
STAmount const amount = amountFromString(issue, locked.asString());
|
||||
return {amount, account.name()};
|
||||
}
|
||||
|
||||
void
|
||||
testIOUEnablement(FeatureBitset features)
|
||||
{
|
||||
@@ -3856,10 +3809,15 @@ struct EscrowToken_test : public beast::unit_test::Suite
|
||||
auto const gw = Account("gw");
|
||||
|
||||
for (auto const testFeatures :
|
||||
{features - featureMPTokensV2 - fixCleanup3_4_0,
|
||||
{features - featureMPTokensV2 - fixCleanup3_4_0 - fixCleanup3_5_0,
|
||||
features - featureMPTokensV2 - fixCleanup3_4_0,
|
||||
features - featureMPTokensV2 - fixCleanup3_5_0,
|
||||
features - featureMPTokensV2,
|
||||
(features | featureMPTokensV2) - fixCleanup3_4_0 - fixCleanup3_5_0,
|
||||
(features | featureMPTokensV2) - fixCleanup3_4_0,
|
||||
features | featureMPTokensV2})
|
||||
(features | featureMPTokensV2) - fixCleanup3_5_0,
|
||||
features | featureMPTokensV2,
|
||||
features | fixCleanup3_5_0})
|
||||
{
|
||||
bool const mptV2 = testFeatures[featureMPTokensV2];
|
||||
bool const tokenEscrowV1 = testFeatures[fixTokenEscrowV1];
|
||||
@@ -3867,10 +3825,11 @@ struct EscrowToken_test : public beast::unit_test::Suite
|
||||
// legacy divideRound(amount, lockedRate, ...) path, which runs when
|
||||
// fixCleanup3_4_0 is disabled. With fixCleanup3_4_0 the split uses
|
||||
// mulRatio (128-bit intermediate), which cannot overflow. Without
|
||||
// it, this large amount overflows unless the MPTokensV2 Number path
|
||||
// is active. So the finish succeeds when either amendment is enabled.
|
||||
// it, this large amount overflows unless divideRound takes the
|
||||
// Number path, which MPTokensV2 or fixCleanup3_5_0 enables. So the
|
||||
// finish succeeds when any of these amendments is enabled.
|
||||
bool const cleanup340 = testFeatures[fixCleanup3_4_0];
|
||||
bool const noOverflow = cleanup340 || mptV2;
|
||||
bool const noOverflow = cleanup340 || mptV2 || testFeatures[fixCleanup3_5_0];
|
||||
auto const expectedErr = noOverflow ? Ter(tesSUCCESS) : Ter(tefEXCEPTION);
|
||||
|
||||
// Finish with a large MPT amount and non-zero transfer fee. When the
|
||||
|
||||
@@ -3864,14 +3864,16 @@ class MPToken_test : public beast::unit_test::Suite
|
||||
|
||||
// Bob can pay carol
|
||||
MPT const mptc = mptAlice;
|
||||
if (!features[featureMPTokensV2])
|
||||
if (!features[featureMPTokensV2] && !features[fixCleanup3_5_0])
|
||||
{
|
||||
mptAlice.pay(bob, carol, 50);
|
||||
BEAST_EXPECT(env.balance(carol, mptc) == mptc(50));
|
||||
}
|
||||
else
|
||||
{
|
||||
// The difference is due to the rounding in MPT/DEX.
|
||||
// The transfer fee (0.05) is rounded up, so delivering 50
|
||||
// costs 51, which exceeds the implicit SendMax.
|
||||
mptAlice.pay(bob, carol, 50, tecPATH_PARTIAL);
|
||||
// 1 MPTC is the transfer fee paid by bob to the issuer.
|
||||
env(pay(bob, carol, mptAlice(50)), Txflags(tfPartialPayment));
|
||||
BEAST_EXPECT(env.balance(carol, mptc) == mptc(49));
|
||||
@@ -7707,6 +7709,318 @@ class MPToken_test : public beast::unit_test::Suite
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testPartialPaymentRounding(FeatureBitset features)
|
||||
{
|
||||
testcase("Partial payment rounding");
|
||||
|
||||
// With fixCleanup3_5_0, the legacy direct-MPT partial payment rounds the
|
||||
// delivered amount down instead of to nearest and the transfer fee up,
|
||||
// so the sender is never charged more than SendMax. This matches V2.
|
||||
|
||||
using namespace test::jtx;
|
||||
Account const alice("alice"); // issuer
|
||||
Account const bob("bob"); // holder / sender
|
||||
Account const carol("carol"); // holder / receiver
|
||||
|
||||
Env env{*this, features};
|
||||
|
||||
MPTTester mptAlice(env, alice, {.holders = {bob, carol}});
|
||||
|
||||
// Transfer fee is 10%
|
||||
mptAlice.create(
|
||||
{.transferFee = 10'000, .ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer});
|
||||
|
||||
mptAlice.authorize({.account = bob});
|
||||
mptAlice.authorize({.account = carol});
|
||||
mptAlice.pay(alice, bob, 780);
|
||||
|
||||
auto const mpt = mptAlice["MPT"];
|
||||
env(pay(bob, carol, mpt(100)), Sendmax(mpt(90)), Txflags(tfPartialPayment));
|
||||
if (features[featureMPTokensV2] || features[fixCleanup3_5_0])
|
||||
{
|
||||
// 81 to carol, 9 to issuer (90 / 1.1 ~ 81.81 (rounded down) =
|
||||
// 81, 81 * 1.1 = 89.1 (rounded up) = 90). In V2 the payments are
|
||||
// executed via the payment engine, which rounds the same way.
|
||||
BEAST_EXPECT(mptAlice.checkMPTokenAmount(bob, 690));
|
||||
BEAST_EXPECT(mptAlice.checkMPTokenAmount(carol, 81));
|
||||
}
|
||||
else
|
||||
{
|
||||
// 82 to carol, 8 to issuer (90 / 1.1 ~ 81.81 (rounded to nearest) =
|
||||
// 82)
|
||||
BEAST_EXPECT(mptAlice.checkMPTokenAmount(bob, 690));
|
||||
BEAST_EXPECT(mptAlice.checkMPTokenAmount(carol, 82));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testPartialPaymentDivideOverflow(FeatureBitset features)
|
||||
{
|
||||
testcase("Partial payment divide overflow");
|
||||
|
||||
// Regression: legacy MPT partial-payment divide rounding overflow.
|
||||
//
|
||||
// Under MPTokensV1 (V2 disabled), a holder-to-holder partial Payment can
|
||||
// hit STAmount::divide's unchecked `muldiv(...) + 5` wrap and return a
|
||||
// zero delivered amount that is nevertheless accepted as tesSUCCESS by
|
||||
// the legacy direct-MPT branch. fixCleanup3_5_0 computes the
|
||||
// delivered amount with mulRatio (exact 128-bit arithmetic) instead.
|
||||
// Under MPTokensV2 the payment is routed through RippleCalc, which uses
|
||||
// Number.
|
||||
|
||||
using namespace test::jtx;
|
||||
Account const alice("alice"); // issuer
|
||||
Account const bob("bob"); // holder / sender
|
||||
Account const carol("carol"); // holder / receiver
|
||||
|
||||
// The precise sendMax chosen so that
|
||||
// muldiv(N * 10^17, D) == UINT64_MAX - 2
|
||||
// where D is the canonicalized rate mantissa for transferFee = 3
|
||||
// (D == 1'000'030'000'000'000). The subsequent +5 wraps to 2, and
|
||||
// the resulting STAmount (mantissa=2, offset=-2) canonicalizes to 0
|
||||
// for an integral MPT asset.
|
||||
constexpr std::int64_t sendMax = 184'472'974'760'317'629;
|
||||
// The largest integer k such that k * 1.00003 <= sendMax.
|
||||
constexpr std::int64_t expectedDelivered = 184'467'440'737'095'516;
|
||||
|
||||
bool const fixed = features[featureMPTokensV2] || features[fixCleanup3_5_0];
|
||||
|
||||
{
|
||||
Env env{*this, features};
|
||||
|
||||
MPTTester mptAlice(env, alice, {.holders = {bob, carol}});
|
||||
mptAlice.create(
|
||||
{.maxAmt = kMaxMpTokenAmount,
|
||||
.transferFee = 3,
|
||||
.ownerCount = 1,
|
||||
.holderCount = 0,
|
||||
.flags = tfMPTCanTransfer});
|
||||
|
||||
mptAlice.authorize({.account = bob});
|
||||
mptAlice.authorize({.account = carol});
|
||||
mptAlice.pay(alice, bob, sendMax);
|
||||
|
||||
auto const mpt = mptAlice["MPT"];
|
||||
|
||||
env(pay(bob, carol, mpt(sendMax)), Sendmax(mpt(sendMax)), Txflags(tfPartialPayment));
|
||||
|
||||
auto const bobBal = mptAlice.getBalance(bob);
|
||||
auto const carolBal = mptAlice.getBalance(carol);
|
||||
|
||||
if (!fixed)
|
||||
{
|
||||
// divide(sendMax, rate) wraps to MPT(0), accountSendMPT(0)
|
||||
// short-circuits to tesSUCCESS, and no balances change.
|
||||
BEAST_EXPECT(bobBal == sendMax);
|
||||
BEAST_EXPECT(carolBal == 0);
|
||||
BEAST_EXPECT(mptAlice.checkMPTokenOutstandingAmount(sendMax));
|
||||
}
|
||||
else
|
||||
{
|
||||
// The transfer fee is redeemed to the issuer, so
|
||||
// OutstandingAmount shrinks by that fee.
|
||||
BEAST_EXPECT(carolBal == expectedDelivered);
|
||||
BEAST_EXPECT(bobBal == 0);
|
||||
BEAST_EXPECT(mptAlice.checkMPTokenOutstandingAmount(bobBal + carolBal));
|
||||
}
|
||||
|
||||
auto const meta = env.meta();
|
||||
BEAST_EXPECT(meta->isFieldPresent(sfDeliveredAmount));
|
||||
if (meta->isFieldPresent(sfDeliveredAmount))
|
||||
{
|
||||
BEAST_EXPECT(
|
||||
meta->getFieldAmount(sfDeliveredAmount) ==
|
||||
mpt(fixed ? expectedDelivered : 0).value());
|
||||
}
|
||||
}
|
||||
|
||||
// A larger sendMax makes muldiv(N * 10^17, D) itself exceed
|
||||
// UINT64_MAX, so the legacy divide() throws instead of wrapping.
|
||||
{
|
||||
constexpr std::int64_t largeSendMax = 200'000'000'000'000'000;
|
||||
// The largest integer k such that k * 1.00003 <= largeSendMax.
|
||||
constexpr std::int64_t largeExpectedDelivered = 199'994'000'179'994'600;
|
||||
|
||||
Env env{*this, features};
|
||||
|
||||
MPTTester mptAlice(env, alice, {.holders = {bob, carol}});
|
||||
mptAlice.create(
|
||||
{.maxAmt = kMaxMpTokenAmount,
|
||||
.transferFee = 3,
|
||||
.ownerCount = 1,
|
||||
.holderCount = 0,
|
||||
.flags = tfMPTCanTransfer});
|
||||
|
||||
mptAlice.authorize({.account = bob});
|
||||
mptAlice.authorize({.account = carol});
|
||||
mptAlice.pay(alice, bob, largeSendMax);
|
||||
|
||||
auto const mpt = mptAlice["MPT"];
|
||||
|
||||
env(pay(bob, carol, mpt(largeSendMax)),
|
||||
Sendmax(mpt(largeSendMax)),
|
||||
Txflags(tfPartialPayment),
|
||||
fixed ? Ter(tesSUCCESS) : Ter(tefEXCEPTION));
|
||||
|
||||
auto const bobBal = mptAlice.getBalance(bob);
|
||||
auto const carolBal = mptAlice.getBalance(carol);
|
||||
|
||||
if (!fixed)
|
||||
{
|
||||
BEAST_EXPECT(bobBal == largeSendMax);
|
||||
BEAST_EXPECT(carolBal == 0);
|
||||
BEAST_EXPECT(mptAlice.checkMPTokenOutstandingAmount(largeSendMax));
|
||||
}
|
||||
else
|
||||
{
|
||||
BEAST_EXPECT(carolBal == largeExpectedDelivered);
|
||||
BEAST_EXPECT(bobBal == 0 || bobBal == 1);
|
||||
BEAST_EXPECT(mptAlice.checkMPTokenOutstandingAmount(bobBal + carolBal));
|
||||
}
|
||||
}
|
||||
|
||||
// The cost of delivering kMaxMpTokenAmount with a transfer fee doesn't
|
||||
// fit in an MPT amount. Without the fix, the legacy
|
||||
// multiply(Amount, rate) throws. With it, or under MPTokensV2, the
|
||||
// overflowing cost is treated as exceeding SendMax.
|
||||
{
|
||||
Env env{*this, features};
|
||||
|
||||
MPTTester mptAlice(env, alice, {.holders = {bob, carol}});
|
||||
mptAlice.create(
|
||||
{.maxAmt = kMaxMpTokenAmount,
|
||||
.transferFee = 3,
|
||||
.ownerCount = 1,
|
||||
.holderCount = 0,
|
||||
.flags = tfMPTCanTransfer});
|
||||
|
||||
mptAlice.authorize({.account = bob});
|
||||
mptAlice.authorize({.account = carol});
|
||||
mptAlice.pay(alice, bob, 1'000);
|
||||
|
||||
auto const mpt = mptAlice["MPT"];
|
||||
|
||||
// Not a partial payment: the cost exceeds SendMax.
|
||||
env(pay(bob, carol, mpt(kMaxMpTokenAmount)),
|
||||
Sendmax(mpt(1'000)),
|
||||
fixed ? Ter(tecPATH_PARTIAL) : Ter(tefEXCEPTION));
|
||||
BEAST_EXPECT(mptAlice.getBalance(bob) == 1'000);
|
||||
BEAST_EXPECT(mptAlice.getBalance(carol) == 0);
|
||||
|
||||
// Partial payment: deliver floor(1000 / 1.00003) = 999 for a cost
|
||||
// of ceil(999 * 1.00003) = 1000.
|
||||
env(pay(bob, carol, mpt(kMaxMpTokenAmount)),
|
||||
Sendmax(mpt(1'000)),
|
||||
Txflags(tfPartialPayment),
|
||||
fixed ? Ter(tesSUCCESS) : Ter(tefEXCEPTION));
|
||||
BEAST_EXPECT(mptAlice.getBalance(bob) == (fixed ? 0 : 1'000));
|
||||
BEAST_EXPECT(mptAlice.getBalance(carol) == (fixed ? 999 : 0));
|
||||
}
|
||||
|
||||
// With the fix, a partial payment that would deliver zero fails
|
||||
// instead of succeeding without moving any funds.
|
||||
if (!features[featureMPTokensV2] && features[fixCleanup3_5_0])
|
||||
{
|
||||
Env env{*this, features};
|
||||
|
||||
MPTTester mptAlice(env, alice, {.holders = {bob, carol}});
|
||||
mptAlice.create(
|
||||
{.transferFee = 3, .ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer});
|
||||
|
||||
mptAlice.authorize({.account = bob});
|
||||
mptAlice.authorize({.account = carol});
|
||||
mptAlice.pay(alice, bob, 10);
|
||||
|
||||
auto const mpt = mptAlice["MPT"];
|
||||
|
||||
env(pay(bob, carol, mpt(10)),
|
||||
Sendmax(mpt(1)),
|
||||
Txflags(tfPartialPayment),
|
||||
Ter(tecPATH_PARTIAL));
|
||||
|
||||
BEAST_EXPECT(mptAlice.getBalance(bob) == 10);
|
||||
BEAST_EXPECT(mptAlice.getBalance(carol) == 0);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testPartialPaymentSendMax(FeatureBitset features)
|
||||
{
|
||||
testcase("Partial payment does not exceed SendMax");
|
||||
|
||||
// A partial payment must not charge bob more than SendMax. Without
|
||||
// fixCleanup3_5_0, the cost is computed with Number, which rounds large
|
||||
// amounts when it has only 16 digits (SingleAssetVault and
|
||||
// LendingProtocol disabled). bob is funded with 1000 extra so that an
|
||||
// overcharge shows up in his balance.
|
||||
//
|
||||
// SendMax = 122'354'741'735'175'090, transfer fee 3 (rate 1.00003):
|
||||
//
|
||||
// | Config | bob pays | carol receives |
|
||||
// |---------------------------------|--------------|------------------|
|
||||
// | fixCleanup3_5_0 or MPTokensV2 | SendMax | ...998 (floor) |
|
||||
// | neither, Number with 19 digits | SendMax | ...999 (nearest) |
|
||||
// | neither, Number with 16 digits | SendMax + 10 | ...999 (nearest) |
|
||||
|
||||
using namespace test::jtx;
|
||||
Account const alice("alice"); // issuer
|
||||
Account const bob("bob"); // holder / sender
|
||||
Account const carol("carol"); // holder / receiver
|
||||
|
||||
bool const fixed = features[featureMPTokensV2] || features[fixCleanup3_5_0];
|
||||
bool const smallNumber =
|
||||
!features[featureSingleAssetVault] && !features[featureLendingProtocol];
|
||||
|
||||
// Small enough that the legacy divide() doesn't overflow. Larger
|
||||
// amounts are covered by testPartialPaymentDivideOverflow.
|
||||
constexpr std::int64_t sendMax = 122'354'741'735'175'090;
|
||||
|
||||
Env env{*this, features};
|
||||
|
||||
MPTTester mptAlice(env, alice, {.holders = {bob, carol}});
|
||||
mptAlice.create(
|
||||
{.maxAmt = kMaxMpTokenAmount,
|
||||
.transferFee = 3,
|
||||
.ownerCount = 1,
|
||||
.holderCount = 0,
|
||||
.flags = tfMPTCanTransfer});
|
||||
|
||||
mptAlice.authorize({.account = bob});
|
||||
mptAlice.authorize({.account = carol});
|
||||
mptAlice.pay(alice, bob, sendMax + 1'000);
|
||||
|
||||
auto const mpt = mptAlice["MPT"];
|
||||
|
||||
env(pay(bob, carol, mpt(sendMax)), Sendmax(mpt(sendMax)), Txflags(tfPartialPayment));
|
||||
|
||||
if (fixed)
|
||||
{
|
||||
// fixCleanup3_5_0 or MPTokensV2: bob pays exactly SendMax, carol
|
||||
// receives floor(SendMax / rate)
|
||||
BEAST_EXPECT(mptAlice.getBalance(bob) == 1'000);
|
||||
BEAST_EXPECT(mptAlice.getBalance(carol) == 122'351'071'203'038'998);
|
||||
}
|
||||
else if (!smallNumber)
|
||||
{
|
||||
// Neither, Number with 19 digits: the Number cost is precise
|
||||
// enough, so bob pays exactly SendMax
|
||||
BEAST_EXPECT(mptAlice.getBalance(bob) == 1'000);
|
||||
// The legacy divide() rounds to nearest, so carol receives one
|
||||
// more than floor(SendMax / rate)
|
||||
BEAST_EXPECT(mptAlice.getBalance(carol) == 122'351'071'203'038'999);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Neither, Number with 16 digits: the Number cost is rounded 10
|
||||
// above SendMax
|
||||
BEAST_EXPECT(mptAlice.getBalance(bob) == 1'000 - 10);
|
||||
// The legacy divide() rounds to nearest, so carol receives one
|
||||
// more than floor(SendMax / rate)
|
||||
BEAST_EXPECT(mptAlice.getBalance(carol) == 122'351'071'203'038'999);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
void
|
||||
run() override
|
||||
@@ -7767,8 +8081,8 @@ public:
|
||||
// Test Direct Payment
|
||||
testPayment(all);
|
||||
testPayment(all | featureSingleAssetVault);
|
||||
testPayment((all | featureSingleAssetVault) - featureMPTokensV2);
|
||||
testPayment(all - featureMPTokensV2);
|
||||
testPayment((all | featureSingleAssetVault) - featureMPTokensV2 - fixCleanup3_5_0);
|
||||
testPayment(all - featureMPTokensV2 - fixCleanup3_5_0);
|
||||
|
||||
testDepositPreauth(all);
|
||||
testDepositPreauth(all - featureCredentials);
|
||||
@@ -7826,6 +8140,28 @@ public:
|
||||
testLockedMPTokenDestroyedIssuance(all - fixCleanup3_4_0);
|
||||
testLockedMPTokenDestroyedIssuance(all - featureSingleAssetVault);
|
||||
testLockedMPTokenDestroyedIssuance(all - featureSingleAssetVault - fixCleanup3_4_0);
|
||||
testPartialPaymentRounding(all);
|
||||
testPartialPaymentRounding(all - featureMPTokensV2);
|
||||
testPartialPaymentRounding(all - featureMPTokensV2 - fixCleanup3_5_0);
|
||||
testPartialPaymentDivideOverflow(all);
|
||||
testPartialPaymentDivideOverflow(all - featureMPTokensV2);
|
||||
#ifndef XRPL_UBSAN
|
||||
// Without the fix, the legacy divide() relies on unsigned wraparound,
|
||||
// which UBSan reports as unsigned-integer-overflow.
|
||||
testPartialPaymentDivideOverflow(all - featureMPTokensV2 - fixCleanup3_5_0);
|
||||
#endif
|
||||
|
||||
// Number has a 16-digit mantissa without these amendments.
|
||||
auto const smallNumber =
|
||||
all - featureMPTokensV2 - featureSingleAssetVault - featureLendingProtocol;
|
||||
testPartialPaymentRounding(smallNumber);
|
||||
testPartialPaymentRounding(smallNumber - fixCleanup3_5_0);
|
||||
testPartialPaymentSendMax(smallNumber);
|
||||
testPartialPaymentSendMax(smallNumber - fixCleanup3_5_0);
|
||||
testPartialPaymentSendMax(smallNumber | featureMPTokensV2);
|
||||
testPartialPaymentSendMax((smallNumber | featureMPTokensV2) - fixCleanup3_5_0);
|
||||
testPartialPaymentSendMax(all);
|
||||
testPartialPaymentSendMax(all - featureMPTokensV2 - fixCleanup3_5_0);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <test/jtx/deposit.h>
|
||||
#include <test/jtx/fee.h>
|
||||
#include <test/jtx/flags.h>
|
||||
#include <test/jtx/paychan.h>
|
||||
#include <test/jtx/ter.h>
|
||||
#include <test/jtx/ticket.h>
|
||||
#include <test/jtx/txflags.h>
|
||||
@@ -17,7 +18,6 @@
|
||||
#include <xrpld/rpc/Role.h>
|
||||
#include <xrpld/rpc/handlers/Handlers.h>
|
||||
|
||||
#include <xrpl/basics/Buffer.h>
|
||||
#include <xrpl/basics/Slice.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/basics/chrono.h>
|
||||
@@ -35,13 +35,10 @@
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/KeyType.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/PayChan.h>
|
||||
#include <xrpl/protocol/PublicKey.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/SecretKey.h>
|
||||
#include <xrpl/protocol/SeqProxy.h>
|
||||
#include <xrpl/protocol/Serializer.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
@@ -77,27 +74,6 @@ struct PayChan_test : public beast::unit_test::Suite
|
||||
return {k.key, view.read(k)};
|
||||
}
|
||||
|
||||
static Buffer
|
||||
signClaimAuth(
|
||||
PublicKey const& pk,
|
||||
SecretKey const& sk,
|
||||
UInt256 const& channel,
|
||||
STAmount const& authAmt)
|
||||
{
|
||||
Serializer msg;
|
||||
serializePayChanAuthorization(msg, channel, authAmt.xrp());
|
||||
return sign(pk, sk, msg.slice());
|
||||
}
|
||||
|
||||
static STAmount
|
||||
channelAmount(ReadView const& view, UInt256 const& chan)
|
||||
{
|
||||
auto const slep = view.read({ltPAYCHAN, chan});
|
||||
if (!slep)
|
||||
return XRPAmount{-1};
|
||||
return (*slep)[sfAmount];
|
||||
}
|
||||
|
||||
static std::optional<std::int64_t>
|
||||
channelExpiration(ReadView const& view, UInt256 const& chan)
|
||||
{
|
||||
@@ -1404,6 +1380,30 @@ struct PayChan_test : public beast::unit_test::Suite
|
||||
rv = env.rpc("channel_authorize", "alice", chan1StrBad, "1000");
|
||||
BEAST_EXPECT(rv[jss::error] == "channelMalformed");
|
||||
}
|
||||
{
|
||||
// channel_id is not a string.
|
||||
json::Value args{json::ValueType::Object};
|
||||
args[jss::amount] = "2000";
|
||||
args[jss::channel_id] = 2000;
|
||||
args[jss::key_type] = "secp256k1";
|
||||
args[jss::passphrase] = "passphrase_can_be_anything";
|
||||
|
||||
auto const ra =
|
||||
env.rpc("json", "channel_authorize", args.toStyledString())[jss::result];
|
||||
BEAST_EXPECT(ra[jss::error] == "invalidParams");
|
||||
}
|
||||
{
|
||||
// channel_id is not a string.
|
||||
json::Value args{json::ValueType::Object};
|
||||
args[jss::public_key] = chan1PkStr;
|
||||
args[jss::channel_id] = 2000;
|
||||
args[jss::amount] = "1000";
|
||||
args[jss::signature] = sig;
|
||||
|
||||
auto const rv =
|
||||
env.rpc("json", "channel_verify", args.toStyledString())[jss::result];
|
||||
BEAST_EXPECT(rv[jss::error] == "invalidParams");
|
||||
}
|
||||
{
|
||||
// give an ill formed base 58 public key
|
||||
auto illFormedPk = chan1PkStr.substr(0, chan1PkStr.size() - 1);
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include <test/jtx/offer.h>
|
||||
#include <test/jtx/paths.h>
|
||||
#include <test/jtx/pay.h>
|
||||
#include <test/jtx/paychan.h>
|
||||
#include <test/jtx/sendmax.h>
|
||||
#include <test/jtx/seq.h>
|
||||
#include <test/jtx/sig.h>
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
#include <functional>
|
||||
#include <initializer_list>
|
||||
#include <memory>
|
||||
#include <ranges>
|
||||
#include <source_location>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
@@ -1099,6 +1100,62 @@ class InvariantsMPT_test : public InvariantsBase
|
||||
});
|
||||
}
|
||||
|
||||
// lsfMPTLocked is exempt because tfMPTUnlock clears it legitimately.
|
||||
// Pre-fixCleanup3_5_0: clearing another issuance flag is allowed.
|
||||
// Post-fixCleanup3_5_0: clearing another issuance flag trips the
|
||||
// invariant.
|
||||
{
|
||||
std::uint32_t allFlags = 0;
|
||||
for (auto const flag : std::views::values(getMPTokenIssuanceFlags()))
|
||||
allFlags |= flag;
|
||||
allFlags &= ~lsfMPTLocked;
|
||||
|
||||
MPTID id{};
|
||||
Preclose const setup = [&](Account const&, Account const&, Env& env) {
|
||||
Account const issuer{"issuer"};
|
||||
env.fund(XRP(10'000), issuer);
|
||||
env.close();
|
||||
MPTTester mptt{env, issuer, kMptInitNoFund};
|
||||
mptt.create({.flags = allFlags});
|
||||
id = mptt.issuanceID();
|
||||
env.close();
|
||||
return true;
|
||||
};
|
||||
STTx const tx{ttACCOUNT_SET, [](STObject&) {}};
|
||||
|
||||
for (auto const flag : std::views::values(getMPTokenIssuanceFlags()))
|
||||
{
|
||||
if (flag == lsfMPTLocked)
|
||||
continue;
|
||||
Precheck const clearFlag = [&, flag](
|
||||
Account const&, Account const&, ApplyContext& ac) {
|
||||
auto sleIssuance = ac.view().peek(keylet::mptokenIssuance(id));
|
||||
if (!sleIssuance)
|
||||
return false;
|
||||
sleIssuance->setFieldU32(sfFlags, sleIssuance->getFlags() & ~flag);
|
||||
ac.view().update(sleIssuance);
|
||||
return true;
|
||||
};
|
||||
|
||||
doInvariantCheck(
|
||||
makeEnv(all_ - fixCleanup3_5_0),
|
||||
{},
|
||||
clearFlag,
|
||||
XRPAmount{},
|
||||
tx,
|
||||
{tesSUCCESS, tesSUCCESS},
|
||||
setup);
|
||||
doInvariantCheck(
|
||||
makeEnv(all_),
|
||||
{{"immutable MPTokenIssuance flag cleared"}},
|
||||
clearFlag,
|
||||
XRPAmount{},
|
||||
tx,
|
||||
{tecINVARIANT_FAILED, tefINVARIANT_FAILED},
|
||||
setup);
|
||||
}
|
||||
}
|
||||
|
||||
// A vault pseudo-account's MPToken cannot be deleted by anything
|
||||
// other than a VaultDelete transaction. Set up a vault, then have
|
||||
// an arbitrary tx erase the pseudo's MPToken in precheck.
|
||||
@@ -1166,6 +1223,12 @@ class InvariantsMPT_test : public InvariantsBase
|
||||
0u})
|
||||
{
|
||||
MPTID id{};
|
||||
// Issuance flags cannot be cleared after creation (and
|
||||
// ValidMPTIssuance now rejects it), so the CanTransfer /
|
||||
// CanTrade rows create the issuance without the bit
|
||||
// instead of stripping it in precheck.
|
||||
std::uint32_t const createFlags =
|
||||
(flag == 0u || flag == lsfMPTLocked) ? kMptDexFlags : (kMptDexFlags & flag);
|
||||
auto const isSuccess = !gates.any() || flag == 0 ||
|
||||
(tx == ttPAYMENT && !crossCurrencyPayment && (flag == ~lsfMPTCanTrade)) ||
|
||||
(tx == ttAMM_WITHDRAW &&
|
||||
@@ -1187,14 +1250,9 @@ class InvariantsMPT_test : public InvariantsBase
|
||||
auto issuanceSle = ac.view().peek(keylet::mptokenIssuance(id));
|
||||
if (!issuanceSle)
|
||||
return false;
|
||||
auto const flags = issuanceSle->at(sfFlags);
|
||||
if (flag == lsfMPTLocked)
|
||||
{
|
||||
issuanceSle->at(sfFlags) = flags | lsfMPTLocked;
|
||||
}
|
||||
else if (flag != 0u)
|
||||
{
|
||||
issuanceSle->at(sfFlags) = flags & flag;
|
||||
issuanceSle->at(sfFlags) = issuanceSle->at(sfFlags) | lsfMPTLocked;
|
||||
}
|
||||
issuanceSle->at(sfOutstandingAmount) = 200;
|
||||
ac.view().update(issuanceSle);
|
||||
@@ -1215,7 +1273,11 @@ class InvariantsMPT_test : public InvariantsBase
|
||||
Account const gw("gw");
|
||||
env.fund(XRP(1'000), gw);
|
||||
MPTTester const usd(
|
||||
{.env = env, .issuer = gw, .holders = {a1, a2}, .pay = 100});
|
||||
{.env = env,
|
||||
.issuer = gw,
|
||||
.holders = {a1, a2},
|
||||
.pay = 100,
|
||||
.flags = createFlags});
|
||||
id = usd.issuanceID();
|
||||
// Either gate enforces, so both must be off to stay
|
||||
// advisory. Disable after setting up the MPT; the
|
||||
|
||||
@@ -0,0 +1,857 @@
|
||||
#include <test/app/lending/LoanTestBase.h>
|
||||
#include <test/jtx/Account.h>
|
||||
#include <test/jtx/Env.h>
|
||||
#include <test/jtx/JTx.h>
|
||||
#include <test/jtx/TestHelpers.h>
|
||||
#include <test/jtx/amount.h>
|
||||
#include <test/jtx/batch.h>
|
||||
#include <test/jtx/fee.h>
|
||||
#include <test/jtx/flags.h>
|
||||
#include <test/jtx/offer.h>
|
||||
#include <test/jtx/owners.h>
|
||||
#include <test/jtx/pay.h>
|
||||
#include <test/jtx/seq.h>
|
||||
#include <test/jtx/sig.h>
|
||||
#include <test/jtx/tags.h>
|
||||
#include <test/jtx/ter.h>
|
||||
#include <test/jtx/trust.h>
|
||||
#include <test/jtx/vault.h>
|
||||
|
||||
#include <xrpl/basics/Number.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/basics/chrono.h>
|
||||
#include <xrpl/basics/strHex.h>
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Issue.h>
|
||||
#include <xrpl/protocol/Keylet.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/SeqProxy.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/protocol/Units.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
// Vault and Lending transactions used as Batch inners under LendingProtocolV1_2.
|
||||
class LendingBatch_test : public LoanTestBase
|
||||
{
|
||||
private:
|
||||
// Asserts a loan has been paid off in full: no principal, value, or
|
||||
// remaining payments left on the loan; no debt left on the broker; and the
|
||||
// vault's available assets back to `depositValue`.
|
||||
void
|
||||
assertLoanFullyRepaid(
|
||||
jtx::Env const& env,
|
||||
Keylet const& brokerKeylet,
|
||||
Keylet const& vaultKeylet,
|
||||
Keylet const& loanKeylet,
|
||||
STAmount const& depositValue)
|
||||
{
|
||||
if (auto const loanSle = env.le(loanKeylet); BEAST_EXPECT(loanSle))
|
||||
{
|
||||
BEAST_EXPECT(loanSle->at(sfPrincipalOutstanding) == 0);
|
||||
BEAST_EXPECT(loanSle->at(sfTotalValueOutstanding) == 0);
|
||||
BEAST_EXPECT(loanSle->at(sfPaymentRemaining) == 0);
|
||||
}
|
||||
if (auto const brokerSle = env.le(brokerKeylet); BEAST_EXPECT(brokerSle))
|
||||
BEAST_EXPECT(brokerSle->at(sfDebtTotal) == 0);
|
||||
if (auto const vaultSle = env.le(vaultKeylet); BEAST_EXPECT(vaultSle))
|
||||
BEAST_EXPECT(vaultSle->at(sfAssetsAvailable) == depositValue);
|
||||
}
|
||||
|
||||
// LoanSet as a batch inner: the counterparty signs the outer batch, so
|
||||
// this inner carries no signature of its own. Pass paymentTotal >= 2
|
||||
// wherever the same batch also submits an early tfLoanFullPayment:
|
||||
// computeFullPayment refuses it with tecKILLED when only the final
|
||||
// scheduled payment is left.
|
||||
static json::Value
|
||||
loanSetInner(
|
||||
jtx::Env& env,
|
||||
jtx::Account const& account,
|
||||
uint256 const& brokerID,
|
||||
jtx::Account const& counterparty,
|
||||
STAmount const& principal,
|
||||
std::uint32_t paymentTotal)
|
||||
{
|
||||
using namespace jtx;
|
||||
using namespace jtx::loan;
|
||||
|
||||
return env.json(
|
||||
set(account, brokerID, principal.value()),
|
||||
kCounterparty(counterparty.id()),
|
||||
kPaymentTotal(paymentTotal),
|
||||
Sig(kNone),
|
||||
Fee(kNone),
|
||||
Seq(kNone));
|
||||
}
|
||||
|
||||
// Runs VaultCreate -> Deposit -> Withdraw -> Delete in one tfAllOrNothing
|
||||
// batch and checks the vault is gone, the owner paid only the batch fee,
|
||||
// and both owner count and sequence are back where they belong.
|
||||
void
|
||||
runVaultLifecycleBatch(jtx::Env& env, jtx::Account const& owner, json::Value const& createTx)
|
||||
{
|
||||
using namespace jtx;
|
||||
|
||||
Vault const vault{env};
|
||||
|
||||
auto const seq = env.seq(owner);
|
||||
auto const balanceBefore = env.balance(owner);
|
||||
auto const ownerCountBefore = env.ownerCount(owner);
|
||||
|
||||
// The outer Batch consumes owner's current sequence, so the first
|
||||
// inner (VaultCreate) lands on seq + 1.
|
||||
auto const vaultKeylet = keylet::vault(owner.id(), SeqProxy::rawSequence(seq + 1));
|
||||
auto const depositAmount = XRP(1'000);
|
||||
|
||||
auto const batchFee = batch::calcBatchFee(env, 0, 4);
|
||||
env(batch::outer(owner, seq, batchFee, tfAllOrNothing),
|
||||
batch::Inner(createTx, seq + 1),
|
||||
batch::Inner(
|
||||
vault.deposit({.depositor = owner, .id = vaultKeylet.key, .amount = depositAmount}),
|
||||
seq + 2),
|
||||
batch::Inner(
|
||||
vault.withdraw(
|
||||
{.depositor = owner, .id = vaultKeylet.key, .amount = depositAmount}),
|
||||
seq + 3),
|
||||
batch::Inner(vault.del({.owner = owner, .id = vaultKeylet.key}), seq + 4),
|
||||
Ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(!env.le(vaultKeylet));
|
||||
BEAST_EXPECT(env.balance(owner) == balanceBefore - batchFee);
|
||||
BEAST_EXPECT(env.ownerCount(owner) == ownerCountBefore);
|
||||
BEAST_EXPECT(env.seq(owner) == seq + 5);
|
||||
}
|
||||
|
||||
void
|
||||
testVaultLifecycle(FeatureBitset features)
|
||||
{
|
||||
// A single tfAllOrNothing batch runs an open-ended XRP vault through
|
||||
// its whole lifecycle: create, deposit, withdraw everything back out,
|
||||
// and delete.
|
||||
testcase("vault lifecycle in a batch");
|
||||
|
||||
using namespace jtx;
|
||||
|
||||
Env env(*this, features);
|
||||
|
||||
Account const owner{"owner"};
|
||||
env.fund(XRP(100'000), owner);
|
||||
env.close();
|
||||
|
||||
auto const createTx = std::get<0>(Vault{env}.create({.owner = owner, .asset = xrpIssue()}));
|
||||
runVaultLifecycleBatch(env, owner, createTx);
|
||||
}
|
||||
|
||||
void
|
||||
testClosedEndedVaultLifecycle(FeatureBitset features)
|
||||
{
|
||||
// Same lifecycle as testVaultLifecycle, but on a closed-ended vault
|
||||
// submitted while it is still in the Subscription phase, where
|
||||
// LendingProtocolV1_1 allows both deposit and withdraw.
|
||||
testcase("vault lifecycle in a batch, closed-ended vault, subscription phase");
|
||||
|
||||
using namespace jtx;
|
||||
|
||||
Env env(*this, features);
|
||||
|
||||
Account const owner{"owner"};
|
||||
env.fund(XRP(100'000), owner);
|
||||
env.close();
|
||||
|
||||
auto const createTx = std::get<0>(Vault{env}.createClosedEnded(
|
||||
{.owner = owner, .asset = xrpIssue(), .subscriptionOffset = std::chrono::seconds{60}}));
|
||||
runVaultLifecycleBatch(env, owner, createTx);
|
||||
}
|
||||
|
||||
void
|
||||
testLoanLifecycleOpenEndedVault(FeatureBitset features)
|
||||
{
|
||||
// With LendingProtocolV1_1 off, an open-ended vault can host a broker,
|
||||
// so vault creation, deposit, broker setup, loan origination, and an
|
||||
// early full payoff all fit in one atomic batch. The borrower signs the
|
||||
// outer as counterparty to the LoanSet inner and as the account behind
|
||||
// the LoanPay inner.
|
||||
testcase("loan lifecycle in a batch, open-ended vault");
|
||||
|
||||
using namespace jtx;
|
||||
|
||||
Env env(*this, features);
|
||||
|
||||
Account const lender{"lender"};
|
||||
Account const borrower{"borrower"};
|
||||
env.fund(XRP(1'000'000), lender, borrower);
|
||||
env.close();
|
||||
|
||||
PrettyAsset const asset{xrpIssue(), 1'000'000};
|
||||
Vault const vault{env};
|
||||
|
||||
auto const lenderSeq = env.seq(lender);
|
||||
auto const borrowerSeq = env.seq(borrower);
|
||||
auto const lenderBalanceBefore = env.balance(lender);
|
||||
auto const borrowerBalanceBefore = env.balance(borrower);
|
||||
|
||||
auto const depositAmount = asset(50'000);
|
||||
auto const principal = asset(1'000);
|
||||
|
||||
auto const vaultKeylet = keylet::vault(lender.id(), SeqProxy::rawSequence(lenderSeq + 1));
|
||||
auto const brokerKeylet =
|
||||
keylet::loanBroker(lender.id(), SeqProxy::rawSequence(lenderSeq + 3));
|
||||
auto const loanKeylet = keylet::loan(brokerKeylet.key, SeqProxy::rawSequence(1));
|
||||
|
||||
auto const createTx = std::get<0>(vault.create({.owner = lender, .asset = xrpIssue()}));
|
||||
|
||||
// CoverRateMinimum/CoverRateLiquidation are left unset on the
|
||||
// LoanBrokerSet inner below, which defaults both to zero, so this
|
||||
// loan needs no first-loss cover deposit from the broker.
|
||||
auto const loanSetTx = loanSetInner(env, lender, brokerKeylet.key, borrower, principal, 2);
|
||||
auto const loanPayTx =
|
||||
loan::pay(borrower, loanKeylet.key, principal.value(), tfLoanFullPayment);
|
||||
|
||||
auto const batchFee = batch::calcBatchFee(env, 1, 5);
|
||||
env(batch::outer(lender, lenderSeq, batchFee, tfAllOrNothing),
|
||||
batch::Inner(createTx, lenderSeq + 1),
|
||||
batch::Inner(
|
||||
vault.deposit(
|
||||
{.depositor = lender, .id = vaultKeylet.key, .amount = depositAmount.value()}),
|
||||
lenderSeq + 2),
|
||||
batch::Inner(loan_broker::set(lender, vaultKeylet.key), lenderSeq + 3),
|
||||
batch::Inner(loanSetTx, lenderSeq + 4),
|
||||
batch::Inner(loanPayTx, borrowerSeq),
|
||||
batch::Sig(borrower),
|
||||
Ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
assertLoanFullyRepaid(env, brokerKeylet, vaultKeylet, loanKeylet, depositAmount.value());
|
||||
|
||||
// The borrower borrowed the principal and repaid it in full at zero
|
||||
// interest and fees, so their balance nets to zero; the batch fee is
|
||||
// paid entirely by the lender, who also funded the vault deposit.
|
||||
BEAST_EXPECT(env.balance(borrower) == borrowerBalanceBefore);
|
||||
BEAST_EXPECT(env.balance(lender) == lenderBalanceBefore - batchFee - depositAmount.value());
|
||||
}
|
||||
|
||||
void
|
||||
testLoanLifecycleClosedEndedVault(FeatureBitset features)
|
||||
{
|
||||
// Under LendingProtocolV1_1 a broker can attach only to a closed-ended
|
||||
// vault, and LoanSet is refused outside the Investment phase, so the
|
||||
// vault/broker setup and the loan/payoff run as two batches split at
|
||||
// the Subscription -> Investment boundary.
|
||||
testcase("loan lifecycle in a batch, closed-ended vault");
|
||||
|
||||
using namespace jtx;
|
||||
|
||||
Env env(*this, features);
|
||||
|
||||
Account const lender{"lender"};
|
||||
Account const borrower{"borrower"};
|
||||
env.fund(XRP(1'000'000), lender, borrower);
|
||||
env.close();
|
||||
|
||||
PrettyAsset const asset{xrpIssue(), 1'000'000};
|
||||
Vault const vault{env};
|
||||
|
||||
auto const lenderSeq1 = env.seq(lender);
|
||||
auto const depositAmount = asset(50'000);
|
||||
|
||||
auto const vaultKeylet = keylet::vault(lender.id(), SeqProxy::rawSequence(lenderSeq1 + 1));
|
||||
auto const brokerKeylet =
|
||||
keylet::loanBroker(lender.id(), SeqProxy::rawSequence(lenderSeq1 + 3));
|
||||
|
||||
auto const created = vault.createClosedEnded(
|
||||
{.owner = lender, .asset = xrpIssue(), .subscriptionOffset = std::chrono::seconds{60}});
|
||||
auto const createTx = std::get<0>(created);
|
||||
auto const subscriptionDate = std::get<2>(created);
|
||||
|
||||
// LoanBrokerSet checks only the vault's kind, not its phase, so setting
|
||||
// up the broker is allowed here in the same batch, while the vault is
|
||||
// still in the Subscription phase.
|
||||
auto const batch1Fee = batch::calcBatchFee(env, 0, 3);
|
||||
env(batch::outer(lender, lenderSeq1, batch1Fee, tfAllOrNothing),
|
||||
batch::Inner(createTx, lenderSeq1 + 1),
|
||||
batch::Inner(
|
||||
vault.deposit(
|
||||
{.depositor = lender, .id = vaultKeylet.key, .amount = depositAmount.value()}),
|
||||
lenderSeq1 + 2),
|
||||
batch::Inner(loan_broker::set(lender, vaultKeylet.key), lenderSeq1 + 3),
|
||||
Ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
if (auto const vaultSle = env.le(vaultKeylet); BEAST_EXPECT(vaultSle))
|
||||
BEAST_EXPECT(vaultSle->at(sfAssetsAvailable) == depositAmount.value());
|
||||
BEAST_EXPECT(env.le(brokerKeylet));
|
||||
|
||||
vault.closePastSubscription(subscriptionDate);
|
||||
|
||||
auto const loanKeylet = keylet::loan(brokerKeylet.key, SeqProxy::rawSequence(1));
|
||||
auto const principal = asset(1'000);
|
||||
|
||||
auto const lenderSeq2 = env.seq(lender);
|
||||
auto const borrowerSeq = env.seq(borrower);
|
||||
auto const lenderBalanceBefore = env.balance(lender);
|
||||
auto const borrowerBalanceBefore = env.balance(borrower);
|
||||
|
||||
auto const loanSetTx = loanSetInner(env, lender, brokerKeylet.key, borrower, principal, 2);
|
||||
auto const loanPayTx =
|
||||
loan::pay(borrower, loanKeylet.key, principal.value(), tfLoanFullPayment);
|
||||
|
||||
auto const batch2Fee = batch::calcBatchFee(env, 1, 2);
|
||||
env(batch::outer(lender, lenderSeq2, batch2Fee, tfAllOrNothing),
|
||||
batch::Inner(loanSetTx, lenderSeq2 + 1),
|
||||
batch::Inner(loanPayTx, borrowerSeq),
|
||||
batch::Sig(borrower),
|
||||
Ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
assertLoanFullyRepaid(env, brokerKeylet, vaultKeylet, loanKeylet, depositAmount.value());
|
||||
|
||||
BEAST_EXPECT(env.balance(borrower) == borrowerBalanceBefore);
|
||||
// The deposit already left the lender's balance in batch 1; batch 2
|
||||
// only costs the batch fee, since LoanSet/LoanPay net to zero XRP.
|
||||
BEAST_EXPECT(env.balance(lender) == lenderBalanceBefore - batch2Fee);
|
||||
}
|
||||
|
||||
// Handles shared by testArbitrage and testArbitrageRollback.
|
||||
struct ArbitrageSetup
|
||||
{
|
||||
jtx::PrettyAsset iou;
|
||||
jtx::Account issuer;
|
||||
jtx::Account lender;
|
||||
jtx::Account borrower;
|
||||
jtx::Account mm1;
|
||||
jtx::Account mm2;
|
||||
BrokerInfo broker;
|
||||
};
|
||||
|
||||
// Broker with zero cover rates and no debt cap, so LoanSet needs no cover
|
||||
// deposit. The borrower holds a trust line but no IOU, so the profit is
|
||||
// exact. mm1 rests an offer selling 400 XRP for 1000 IOU; mm2 is funded
|
||||
// but places its offer only inside testArbitrage's batch.
|
||||
ArbitrageSetup
|
||||
setupArbitrage(jtx::Env& env)
|
||||
{
|
||||
using namespace jtx;
|
||||
|
||||
Account const issuer{"issuer"};
|
||||
Account const lender{"lender"};
|
||||
Account const borrower{"borrower"};
|
||||
Account const mm1{"mm1"};
|
||||
Account const mm2{"mm2"};
|
||||
env.fund(XRP(1'000'000), issuer, lender, borrower, mm1, mm2);
|
||||
env(fset(issuer, asfDefaultRipple));
|
||||
env.close();
|
||||
|
||||
PrettyAsset const iou = issuer[iouCurrency_];
|
||||
STAmount const iouLimit = iou(1'000'000'000);
|
||||
env(trust(lender, iouLimit));
|
||||
env(trust(borrower, iouLimit));
|
||||
env(trust(mm1, iouLimit));
|
||||
env(trust(mm2, iouLimit));
|
||||
env.close();
|
||||
|
||||
BrokerParameters params = BrokerParameters::defaults();
|
||||
params.vaultDeposit = 100'000;
|
||||
params.coverRateMin = TenthBips32{0};
|
||||
params.coverDeposit = 0;
|
||||
params.debtMax = 0;
|
||||
params.coverRateLiquidation = TenthBips32{0};
|
||||
|
||||
env(pay(issuer, lender, iou(params.vaultDeposit)));
|
||||
env(pay(issuer, mm2, iou(20'000)));
|
||||
env.close();
|
||||
|
||||
BrokerInfo const broker{createVaultAndBroker(env, iou, lender, params)};
|
||||
|
||||
env(offer(mm1, iou(1'000), XRP(400)));
|
||||
env.close();
|
||||
|
||||
return {
|
||||
.iou = iou,
|
||||
.issuer = issuer,
|
||||
.lender = lender,
|
||||
.borrower = borrower,
|
||||
.mm1 = mm1,
|
||||
.mm2 = mm2,
|
||||
.broker = broker};
|
||||
}
|
||||
|
||||
void
|
||||
testArbitrage(FeatureBitset features)
|
||||
{
|
||||
// A borrower profits risk-free from a loan and two crossing DEX
|
||||
// offers in one atomic batch. Crossed offers execute the moment both
|
||||
// rest on the book, so mm2's offer is itself a batch inner, placed
|
||||
// right after the borrower's buy leg consumes mm1.
|
||||
testcase("arbitrage across a loan and two crossing offers in a batch");
|
||||
|
||||
using namespace jtx;
|
||||
|
||||
Env env(*this, features);
|
||||
auto const setup = setupArbitrage(env);
|
||||
auto const& iou = setup.iou;
|
||||
auto const& lender = setup.lender;
|
||||
auto const& borrower = setup.borrower;
|
||||
auto const& broker = setup.broker;
|
||||
auto const& mm2 = setup.mm2;
|
||||
|
||||
auto const loanKeylet = nextLoanKeylet(env, broker);
|
||||
auto const principal = iou(1'000);
|
||||
|
||||
BEAST_EXPECT(env.balance(borrower, iou) == iou(0));
|
||||
|
||||
auto const borrowerSeq = env.seq(borrower);
|
||||
auto const mm2Seq = env.seq(mm2);
|
||||
|
||||
auto const loanSetTx = loanSetInner(env, borrower, broker.brokerID, lender, principal, 2);
|
||||
auto const loanPayTx =
|
||||
loan::pay(borrower, loanKeylet.key, principal.value(), tfLoanFullPayment);
|
||||
|
||||
auto const batchFee = batch::calcBatchFee(env, 2, 5);
|
||||
env(batch::outer(borrower, borrowerSeq, batchFee, tfAllOrNothing),
|
||||
batch::Inner(loanSetTx, borrowerSeq + 1),
|
||||
batch::Inner(
|
||||
offer(borrower, XRP(400), principal.value(), tfFillOrKill), borrowerSeq + 2),
|
||||
batch::Inner(offer(mm2, XRP(400), iou(1'008)), mm2Seq),
|
||||
batch::Inner(
|
||||
offer(borrower, iou(1'008).value(), XRP(400), tfFillOrKill), borrowerSeq + 3),
|
||||
batch::Inner(loanPayTx, borrowerSeq + 4),
|
||||
batch::Sig(lender, mm2),
|
||||
Ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
if (auto const loanSle = env.le(loanKeylet); BEAST_EXPECT(loanSle))
|
||||
BEAST_EXPECT(loanSle->at(sfPrincipalOutstanding) == 0);
|
||||
BEAST_EXPECT(env.balance(borrower, iou) == iou(8));
|
||||
env.require(Offers(setup.mm1, 0));
|
||||
env.require(Offers(mm2, 0));
|
||||
if (auto const brokerSle = env.le(broker.brokerKeylet()); BEAST_EXPECT(brokerSle))
|
||||
BEAST_EXPECT(brokerSle->at(sfDebtTotal) == 0);
|
||||
if (auto const vaultSle = env.le(broker.vaultKeylet()); BEAST_EXPECT(vaultSle))
|
||||
{
|
||||
BEAST_EXPECT(
|
||||
vaultSle->at(sfAssetsAvailable) == iou(broker.params.vaultDeposit).value());
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testArbitrageRollback(FeatureBitset features)
|
||||
{
|
||||
// Without a second market maker willing to buy XRP back at the
|
||||
// higher price, the sell leg's tfFillOrKill offer cannot fill and
|
||||
// returns tecKILLED. tfAllOrNothing then rolls back every inner,
|
||||
// including the loan origination; the outer batch itself still
|
||||
// returns tesSUCCESS.
|
||||
testcase("arbitrage batch rolls back when the sell offer cannot fill");
|
||||
|
||||
using namespace jtx;
|
||||
|
||||
Env env(*this, features);
|
||||
auto const setup = setupArbitrage(env);
|
||||
auto const& iou = setup.iou;
|
||||
auto const& lender = setup.lender;
|
||||
auto const& borrower = setup.borrower;
|
||||
auto const& broker = setup.broker;
|
||||
|
||||
auto const brokerSleBefore = env.le(broker.brokerKeylet());
|
||||
auto const vaultSleBefore = env.le(broker.vaultKeylet());
|
||||
if (!BEAST_EXPECT(brokerSleBefore) || !BEAST_EXPECT(vaultSleBefore))
|
||||
return;
|
||||
auto const loanSequenceBefore = brokerSleBefore->at(sfLoanSequence);
|
||||
auto const assetsAvailableBefore = vaultSleBefore->at(sfAssetsAvailable);
|
||||
|
||||
auto const loanKeylet = nextLoanKeylet(env, broker);
|
||||
auto const principal = iou(1'000);
|
||||
|
||||
auto const borrowerSeq = env.seq(borrower);
|
||||
auto const borrowerBalanceBefore = env.balance(borrower);
|
||||
|
||||
auto const loanSetTx = loanSetInner(env, borrower, broker.brokerID, lender, principal, 2);
|
||||
auto const loanPayTx =
|
||||
loan::pay(borrower, loanKeylet.key, principal.value(), tfLoanFullPayment);
|
||||
|
||||
auto const batchFee = batch::calcBatchFee(env, 1, 4);
|
||||
env(batch::outer(borrower, borrowerSeq, batchFee, tfAllOrNothing),
|
||||
batch::Inner(loanSetTx, borrowerSeq + 1),
|
||||
batch::Inner(
|
||||
offer(borrower, XRP(400), principal.value(), tfFillOrKill), borrowerSeq + 2),
|
||||
batch::Inner(
|
||||
offer(borrower, iou(1'008).value(), XRP(400), tfFillOrKill), borrowerSeq + 3),
|
||||
batch::Inner(loanPayTx, borrowerSeq + 4),
|
||||
batch::Sig(lender),
|
||||
Ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(!env.le(loanKeylet));
|
||||
if (auto const brokerSle = env.le(broker.brokerKeylet()); BEAST_EXPECT(brokerSle))
|
||||
{
|
||||
BEAST_EXPECT(brokerSle->at(sfLoanSequence) == loanSequenceBefore);
|
||||
BEAST_EXPECT(brokerSle->at(sfDebtTotal) == 0);
|
||||
}
|
||||
BEAST_EXPECT(env.balance(borrower, iou) == iou(0));
|
||||
BEAST_EXPECT(env.balance(borrower) == borrowerBalanceBefore - batchFee);
|
||||
env.require(Offers(setup.mm1, 1));
|
||||
if (auto const vaultSle = env.le(broker.vaultKeylet()); BEAST_EXPECT(vaultSle))
|
||||
BEAST_EXPECT(vaultSle->at(sfAssetsAvailable) == assetsAvailableBefore);
|
||||
}
|
||||
|
||||
// Originates a loan outside any batch: one payment due in a day, a day of
|
||||
// grace, zero interest. PaymentTotal 1 is fine because callers only
|
||||
// impair or default the loan and never pay it off early.
|
||||
static void
|
||||
originateLoan(
|
||||
jtx::Env& env,
|
||||
BrokerInfo const& broker,
|
||||
jtx::Account const& lender,
|
||||
jtx::Account const& borrower,
|
||||
STAmount const& principal)
|
||||
{
|
||||
using namespace jtx;
|
||||
using namespace jtx::loan;
|
||||
|
||||
env(set(lender, broker.brokerID, principal),
|
||||
kCounterparty(borrower.id()),
|
||||
kPaymentTotal(1),
|
||||
kPaymentInterval(86400),
|
||||
kGracePeriod(86400),
|
||||
Sig(sfCounterpartySignature, borrower),
|
||||
Fee(env.current()->fees().base * 2));
|
||||
env.close();
|
||||
}
|
||||
|
||||
void
|
||||
testImpairAndCoverDeposit(FeatureBitset features)
|
||||
{
|
||||
// Impairing a loan records its exposure as the vault's unrealized
|
||||
// loss; the broker tops up first-loss cover in the same batch. Under
|
||||
// fixCleanup3_4_0 LoanManage refuses tfLoanImpair with tecTOO_SOON
|
||||
// until a payment is late, so the impairment cannot share a batch
|
||||
// with the LoanSet that creates the loan.
|
||||
testcase("loan impairment and cover deposit in a batch");
|
||||
|
||||
using namespace jtx;
|
||||
|
||||
Env env(*this, features);
|
||||
|
||||
Account const lender{"lender"};
|
||||
Account const borrower{"borrower"};
|
||||
env.fund(XRP(100'000'000), lender, borrower);
|
||||
env.close();
|
||||
|
||||
PrettyAsset const asset{xrpIssue(), 1'000'000};
|
||||
auto const broker = createVaultAndBroker(env, asset, lender);
|
||||
|
||||
auto const loanKeylet = nextLoanKeylet(env, broker);
|
||||
originateLoan(env, broker, lender, borrower, asset(1'000).value());
|
||||
|
||||
auto const loanSleBefore = env.le(loanKeylet);
|
||||
if (!BEAST_EXPECT(loanSleBefore))
|
||||
return;
|
||||
|
||||
// Advance past NextPaymentDueDate so the payment is late.
|
||||
using D = NetClock::duration;
|
||||
using Tp = NetClock::time_point;
|
||||
env.close(Tp{D{loanSleBefore->at(sfNextPaymentDueDate) + 1}});
|
||||
|
||||
auto const brokerSleBefore = env.le(broker.brokerKeylet());
|
||||
auto const vaultSleBefore = env.le(broker.vaultKeylet());
|
||||
if (!BEAST_EXPECT(brokerSleBefore) || !BEAST_EXPECT(vaultSleBefore))
|
||||
return;
|
||||
auto const coverAvailableBefore = brokerSleBefore->at(sfCoverAvailable);
|
||||
auto const lossUnrealizedBefore = vaultSleBefore->at(sfLossUnrealized);
|
||||
auto const additionalCover = asset(500).value();
|
||||
|
||||
auto const lenderSeq = env.seq(lender);
|
||||
auto const batchFee = batch::calcBatchFee(env, 0, 2);
|
||||
env(batch::outer(lender, lenderSeq, batchFee, tfAllOrNothing),
|
||||
batch::Inner(loan::manage(lender, loanKeylet.key, tfLoanImpair), lenderSeq + 1),
|
||||
batch::Inner(
|
||||
loan_broker::coverDeposit(lender, broker.brokerID, additionalCover), lenderSeq + 2),
|
||||
Ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
if (auto const loanSle = env.le(loanKeylet); BEAST_EXPECT(loanSle))
|
||||
BEAST_EXPECT(loanSle->isFlag(lsfLoanImpaired));
|
||||
// Zero interest and fees make the exposure equal the outstanding
|
||||
// principal under both accounting modes.
|
||||
if (auto const vaultSle = env.le(broker.vaultKeylet()); BEAST_EXPECT(vaultSle))
|
||||
{
|
||||
BEAST_EXPECT(
|
||||
vaultSle->at(sfLossUnrealized) ==
|
||||
lossUnrealizedBefore + loanSleBefore->at(sfPrincipalOutstanding));
|
||||
}
|
||||
if (auto const brokerSle = env.le(broker.brokerKeylet()); BEAST_EXPECT(brokerSle))
|
||||
BEAST_EXPECT(brokerSle->at(sfCoverAvailable) == coverAvailableBefore + additionalCover);
|
||||
}
|
||||
|
||||
void
|
||||
testDefaultAndCoverWithdraw(FeatureBitset features)
|
||||
{
|
||||
// A defaulted loan burns first-loss cover per CoverRateLiquidation;
|
||||
// the remainder can be withdrawn once DebtTotal drops to zero. The
|
||||
// default runs in its own batch: LoanManage refuses tfLoanDefault
|
||||
// with tecTOO_SOON until the grace period elapses, so it cannot share
|
||||
// a batch with the LoanSet that creates the loan.
|
||||
testcase("loan default and cover withdraw in a batch");
|
||||
|
||||
using namespace jtx;
|
||||
|
||||
Env env(*this, features);
|
||||
|
||||
Account const lender{"lender"};
|
||||
Account const borrower{"borrower"};
|
||||
env.fund(XRP(100'000'000), lender, borrower);
|
||||
env.close();
|
||||
|
||||
PrettyAsset const asset{xrpIssue(), 1'000'000};
|
||||
auto const broker = createVaultAndBroker(env, asset, lender);
|
||||
|
||||
auto const loanKeylet = nextLoanKeylet(env, broker);
|
||||
originateLoan(env, broker, lender, borrower, asset(1'000).value());
|
||||
|
||||
auto const loanSleBefore = env.le(loanKeylet);
|
||||
if (!BEAST_EXPECT(loanSleBefore))
|
||||
return;
|
||||
|
||||
// Advance past NextPaymentDueDate + GracePeriod so the default is no
|
||||
// longer refused with tecTOO_SOON.
|
||||
auto const dueDate = loanSleBefore->at(sfNextPaymentDueDate);
|
||||
auto const gracePeriod = loanSleBefore->at(sfGracePeriod);
|
||||
using D = NetClock::duration;
|
||||
using Tp = NetClock::time_point;
|
||||
env.close(Tp{D{dueDate + gracePeriod + 1}});
|
||||
|
||||
auto const brokerSleBefore = env.le(broker.brokerKeylet());
|
||||
auto const vaultSleBefore = env.le(broker.vaultKeylet());
|
||||
if (!BEAST_EXPECT(brokerSleBefore) || !BEAST_EXPECT(vaultSleBefore))
|
||||
return;
|
||||
|
||||
auto const coverAvailableBefore = brokerSleBefore->at(sfCoverAvailable);
|
||||
auto const assetsAvailableBefore = vaultSleBefore->at(sfAssetsAvailable);
|
||||
auto const assetsTotalBefore = vaultSleBefore->at(sfAssetsTotal);
|
||||
|
||||
TenthBips32 const coverRateMin{brokerSleBefore->at(sfCoverRateMinimum)};
|
||||
TenthBips32 const coverRateLiquidation{brokerSleBefore->at(sfCoverRateLiquidation)};
|
||||
Number const totalDefaultAmount{loanSleBefore->at(sfPrincipalOutstanding)};
|
||||
|
||||
// DefaultCovered = min(MinimumCover x CoverRateLiquidation,
|
||||
// DefaultAmount, CoverAvailable), matching LoanManage::defaultLoan.
|
||||
// Zero interest and fees make DefaultAmount equal the outstanding
|
||||
// principal under both accounting modes, so no
|
||||
// featureLendingProtocolV1_1 branch is needed here.
|
||||
Number defaultCovered;
|
||||
{
|
||||
NumberRoundModeGuard const mg(Number::RoundingMode::Upward);
|
||||
auto const minimumCover =
|
||||
tenthBipsOfValue(Number{brokerSleBefore->at(sfDebtTotal)}, coverRateMin);
|
||||
defaultCovered = std::min(
|
||||
roundToAsset(
|
||||
asset.raw(),
|
||||
std::min(
|
||||
tenthBipsOfValue(minimumCover, coverRateLiquidation), totalDefaultAmount),
|
||||
loanSleBefore->at(sfLoanScale)),
|
||||
Number{coverAvailableBefore});
|
||||
}
|
||||
auto const vaultDefaultAmount = totalDefaultAmount - defaultCovered;
|
||||
|
||||
auto const lenderSeq = env.seq(lender);
|
||||
auto const lenderBalanceBefore = env.balance(lender);
|
||||
auto const withdrawAmount = STAmount{asset.raw(), coverAvailableBefore - defaultCovered};
|
||||
|
||||
auto const batchFee = batch::calcBatchFee(env, 0, 2);
|
||||
env(batch::outer(lender, lenderSeq, batchFee, tfAllOrNothing),
|
||||
batch::Inner(loan::manage(lender, loanKeylet.key, tfLoanDefault), lenderSeq + 1),
|
||||
batch::Inner(
|
||||
loan_broker::coverWithdraw(lender, broker.brokerID, withdrawAmount), lenderSeq + 2),
|
||||
Ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
if (auto const loanSle = env.le(loanKeylet); BEAST_EXPECT(loanSle))
|
||||
{
|
||||
BEAST_EXPECT(loanSle->isFlag(lsfLoanDefault));
|
||||
BEAST_EXPECT(loanSle->at(sfPrincipalOutstanding) == 0);
|
||||
}
|
||||
if (auto const brokerSle = env.le(broker.brokerKeylet()); BEAST_EXPECT(brokerSle))
|
||||
{
|
||||
BEAST_EXPECT(brokerSle->at(sfCoverAvailable) == 0);
|
||||
BEAST_EXPECT(brokerSle->at(sfDebtTotal) == 0);
|
||||
}
|
||||
BEAST_EXPECT(env.balance(lender) == lenderBalanceBefore - batchFee + withdrawAmount);
|
||||
// The default moves defaultCovered from the broker's cover to the
|
||||
// vault; the cover withdraw pays out of the broker's own
|
||||
// pseudo-account and does not touch the vault again.
|
||||
if (auto const vaultSle = env.le(broker.vaultKeylet()); BEAST_EXPECT(vaultSle))
|
||||
{
|
||||
BEAST_EXPECT(vaultSle->at(sfAssetsAvailable) == assetsAvailableBefore + defaultCovered);
|
||||
BEAST_EXPECT(vaultSle->at(sfAssetsTotal) == assetsTotalBefore - vaultDefaultAmount);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testIndependentVaultChain(FeatureBitset features)
|
||||
{
|
||||
// tfIndependent runs every inner regardless of earlier failures.
|
||||
// VaultWithdraw fails before anything is deposited: the empty vault
|
||||
// converts the amount to zero shares (tecPRECISION_LOSS). VaultDelete fails
|
||||
// once the vault holds a deposit (tecHAS_OBLIGATIONS); both are
|
||||
// tec-class results, so neither stops the inners that follow.
|
||||
testcase("independent batch runs every inner despite failing inners");
|
||||
|
||||
using namespace jtx;
|
||||
|
||||
Env env(*this, features);
|
||||
|
||||
Account const owner{"owner"};
|
||||
env.fund(XRP(100'000), owner);
|
||||
env.close();
|
||||
|
||||
Vault const vault{env};
|
||||
|
||||
auto const seq = env.seq(owner);
|
||||
auto const vaultKeylet = keylet::vault(owner.id(), SeqProxy::rawSequence(seq + 1));
|
||||
auto const createTx = std::get<0>(vault.create({.owner = owner, .asset = xrpIssue()}));
|
||||
auto const amount = XRP(1'000);
|
||||
|
||||
auto const batchFee = batch::calcBatchFee(env, 0, 4);
|
||||
auto const batchTx = env.jt(
|
||||
batch::outer(owner, seq, batchFee, tfIndependent),
|
||||
batch::Inner(createTx, seq + 1),
|
||||
batch::Inner(
|
||||
vault.withdraw({.depositor = owner, .id = vaultKeylet.key, .amount = amount}),
|
||||
seq + 2),
|
||||
batch::Inner(
|
||||
vault.deposit({.depositor = owner, .id = vaultKeylet.key, .amount = amount}),
|
||||
seq + 3),
|
||||
batch::Inner(vault.del({.owner = owner, .id = vaultKeylet.key}), seq + 4));
|
||||
env(batchTx, Ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
auto const batchID = strHex(batchTx.stx->getTransactionID());
|
||||
auto const& innerIDs = batchTx.stx->getBatchTransactionIDs();
|
||||
if (!BEAST_EXPECT(innerIDs.size() == 4))
|
||||
return;
|
||||
batch::validateInnerTxn(env, batchID, strHex(innerIDs[0]), "VaultCreate", "tesSUCCESS");
|
||||
batch::validateInnerTxn(
|
||||
env, batchID, strHex(innerIDs[1]), "VaultWithdraw", "tecPRECISION_LOSS");
|
||||
batch::validateInnerTxn(env, batchID, strHex(innerIDs[2]), "VaultDeposit", "tesSUCCESS");
|
||||
batch::validateInnerTxn(
|
||||
env, batchID, strHex(innerIDs[3]), "VaultDelete", "tecHAS_OBLIGATIONS");
|
||||
|
||||
if (auto const vaultSle = env.le(vaultKeylet); BEAST_EXPECT(vaultSle))
|
||||
BEAST_EXPECT(vaultSle->at(sfAssetsTotal) == amount.value());
|
||||
BEAST_EXPECT(env.seq(owner) == seq + 5);
|
||||
}
|
||||
|
||||
void
|
||||
testIndependentLoanChain(FeatureBitset features)
|
||||
{
|
||||
// Same claim as testIndependentVaultChain, on a loan chain. The
|
||||
// LoanSet inner leaves the borrower holding only the 1,000 principal,
|
||||
// so a LoanPay for 2,000 fails preclaim's balance check
|
||||
// (tecINSUFFICIENT_FUNDS); the cover deposit submitted after it
|
||||
// still lands.
|
||||
testcase("independent batch runs every loan inner despite a failing payment");
|
||||
|
||||
using namespace jtx;
|
||||
|
||||
Env env(*this, features);
|
||||
|
||||
Account const issuer{"issuer"};
|
||||
Account const lender{"lender"};
|
||||
Account const borrower{"borrower"};
|
||||
env.fund(XRP(1'000'000), issuer, lender, borrower);
|
||||
env(fset(issuer, asfDefaultRipple));
|
||||
env.close();
|
||||
|
||||
PrettyAsset const iou = issuer[iouCurrency_];
|
||||
STAmount const iouLimit = iou(1'000'000'000);
|
||||
env(trust(lender, iouLimit));
|
||||
env(trust(borrower, iouLimit));
|
||||
env.close();
|
||||
|
||||
env(pay(issuer, lender, iou(2'000'000)));
|
||||
env.close();
|
||||
|
||||
auto const broker = createVaultAndBroker(env, iou, lender);
|
||||
|
||||
auto const loanKeylet = nextLoanKeylet(env, broker);
|
||||
auto const principal = iou(1'000);
|
||||
auto const brokerSleBefore = env.le(broker.brokerKeylet());
|
||||
if (!BEAST_EXPECT(brokerSleBefore))
|
||||
return;
|
||||
auto const coverAvailableBefore = brokerSleBefore->at(sfCoverAvailable);
|
||||
|
||||
auto const lenderSeq = env.seq(lender);
|
||||
auto const borrowerSeq = env.seq(borrower);
|
||||
|
||||
auto const loanSetTx = loanSetInner(env, lender, broker.brokerID, borrower, principal, 2);
|
||||
auto const loanPayTx =
|
||||
loan::pay(borrower, loanKeylet.key, iou(2'000).value(), tfLoanFullPayment);
|
||||
auto const additionalCover = iou(500);
|
||||
|
||||
auto const batchFee = batch::calcBatchFee(env, 1, 3);
|
||||
auto const batchTx = env.jt(
|
||||
batch::outer(lender, lenderSeq, batchFee, tfIndependent),
|
||||
batch::Inner(loanSetTx, lenderSeq + 1),
|
||||
batch::Inner(loanPayTx, borrowerSeq),
|
||||
batch::Inner(
|
||||
loan_broker::coverDeposit(lender, broker.brokerID, additionalCover), lenderSeq + 2),
|
||||
batch::Sig(borrower));
|
||||
env(batchTx, Ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
auto const batchID = strHex(batchTx.stx->getTransactionID());
|
||||
auto const& innerIDs = batchTx.stx->getBatchTransactionIDs();
|
||||
if (!BEAST_EXPECT(innerIDs.size() == 3))
|
||||
return;
|
||||
batch::validateInnerTxn(env, batchID, strHex(innerIDs[0]), "LoanSet", "tesSUCCESS");
|
||||
batch::validateInnerTxn(
|
||||
env, batchID, strHex(innerIDs[1]), "LoanPay", "tecINSUFFICIENT_FUNDS");
|
||||
batch::validateInnerTxn(
|
||||
env, batchID, strHex(innerIDs[2]), "LoanBrokerCoverDeposit", "tesSUCCESS");
|
||||
|
||||
if (auto const loanSle = env.le(loanKeylet); BEAST_EXPECT(loanSle))
|
||||
BEAST_EXPECT(loanSle->at(sfPrincipalOutstanding) == principal.value());
|
||||
if (auto const brokerSle = env.le(broker.brokerKeylet()); BEAST_EXPECT(brokerSle))
|
||||
{
|
||||
BEAST_EXPECT(
|
||||
brokerSle->at(sfCoverAvailable) == coverAvailableBefore + additionalCover.value());
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
void
|
||||
run() override
|
||||
{
|
||||
// Batch rejects Vault and Lending inners before LendingProtocolV1_2.
|
||||
FeatureBitset const lendingBatch{all_ | featureLendingProtocolV1_2};
|
||||
for (auto const& features : {lendingBatch, lendingBatch | featureLendingProtocolV1_1})
|
||||
{
|
||||
testVaultLifecycle(features);
|
||||
testArbitrage(features);
|
||||
testArbitrageRollback(features);
|
||||
testImpairAndCoverDeposit(features);
|
||||
testDefaultAndCoverWithdraw(features);
|
||||
testIndependentVaultChain(features);
|
||||
testIndependentLoanChain(features);
|
||||
}
|
||||
testClosedEndedVaultLifecycle(lendingBatch | featureLendingProtocolV1_1);
|
||||
testLoanLifecycleOpenEndedVault(lendingBatch);
|
||||
testLoanLifecycleClosedEndedVault(lendingBatch | featureLendingProtocolV1_1);
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(LendingBatch, tx, xrpl);
|
||||
|
||||
} // namespace xrpl::test
|
||||
@@ -30,11 +30,8 @@
|
||||
#include <xrpl/protocol/Serializer.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/protocol/TxFormats.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
#include <xrpl/tx/transactors/system/Batch.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
@@ -498,8 +495,7 @@ private:
|
||||
// From FIND-001
|
||||
testcase << "Batch Bypass Counterparty";
|
||||
|
||||
bool const lendingBatchEnabled = !std::ranges::any_of(
|
||||
Batch::kDisabledTxTypes, [](auto const& disabled) { return disabled == ttLOAN_SET; });
|
||||
bool const lendingBatchEnabled = features[featureLendingProtocolV1_2];
|
||||
|
||||
using namespace jtx;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace beast {
|
||||
|
||||
class Journal_test : public unit_test::Suite
|
||||
{
|
||||
public:
|
||||
class TestSink : public Journal::Sink
|
||||
{
|
||||
private:
|
||||
int count_{0};
|
||||
|
||||
public:
|
||||
TestSink() : Sink(Severity::Warning, false)
|
||||
{
|
||||
}
|
||||
|
||||
[[nodiscard]] int
|
||||
count() const
|
||||
{
|
||||
return count_;
|
||||
}
|
||||
|
||||
void
|
||||
reset()
|
||||
{
|
||||
count_ = 0;
|
||||
}
|
||||
|
||||
void
|
||||
write(Severity level, std::string const&) override
|
||||
{
|
||||
if (level >= threshold())
|
||||
++count_;
|
||||
}
|
||||
|
||||
void
|
||||
writeAlways(Severity level, std::string const&) override
|
||||
{
|
||||
++count_;
|
||||
}
|
||||
};
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
TestSink sink;
|
||||
|
||||
sink.threshold(Severity::Info);
|
||||
|
||||
Journal const j(sink);
|
||||
|
||||
j.trace() << " ";
|
||||
BEAST_EXPECT(sink.count() == 0);
|
||||
j.debug() << " ";
|
||||
BEAST_EXPECT(sink.count() == 0);
|
||||
j.info() << " ";
|
||||
BEAST_EXPECT(sink.count() == 1);
|
||||
j.warn() << " ";
|
||||
BEAST_EXPECT(sink.count() == 2);
|
||||
j.error() << " ";
|
||||
BEAST_EXPECT(sink.count() == 3);
|
||||
j.fatal() << " ";
|
||||
BEAST_EXPECT(sink.count() == 4);
|
||||
|
||||
sink.reset();
|
||||
|
||||
sink.threshold(Severity::Debug);
|
||||
|
||||
j.trace() << " ";
|
||||
BEAST_EXPECT(sink.count() == 0);
|
||||
j.debug() << " ";
|
||||
BEAST_EXPECT(sink.count() == 1);
|
||||
j.info() << " ";
|
||||
BEAST_EXPECT(sink.count() == 2);
|
||||
j.warn() << " ";
|
||||
BEAST_EXPECT(sink.count() == 3);
|
||||
j.error() << " ";
|
||||
BEAST_EXPECT(sink.count() == 4);
|
||||
j.fatal() << " ";
|
||||
BEAST_EXPECT(sink.count() == 5);
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(Journal, beast, beast);
|
||||
|
||||
} // namespace beast
|
||||
@@ -1,19 +0,0 @@
|
||||
#include <xrpl/beast/clock/basic_seconds_clock.h>
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
|
||||
namespace beast {
|
||||
|
||||
class BasicSecondsClock_test : public unit_test::Suite
|
||||
{
|
||||
public:
|
||||
void
|
||||
run() override
|
||||
{
|
||||
BasicSecondsClock::now();
|
||||
pass();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(BasicSecondsClock, beast, beast);
|
||||
|
||||
} // namespace beast
|
||||
@@ -1,196 +0,0 @@
|
||||
#include <xrpl/beast/hash/xxhasher.h>
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace beast {
|
||||
|
||||
class XXHasher_test : public unit_test::Suite
|
||||
{
|
||||
public:
|
||||
void
|
||||
testWithoutSeed()
|
||||
{
|
||||
testcase("Without seed");
|
||||
|
||||
Xxhasher hasher{};
|
||||
|
||||
std::string objectToHash{"Hello, xxHash!"};
|
||||
hasher(objectToHash.data(), objectToHash.size());
|
||||
|
||||
BEAST_EXPECT(static_cast<Xxhasher::result_type>(hasher) == 16042857369214894119ULL);
|
||||
}
|
||||
|
||||
void
|
||||
testWithSeed()
|
||||
{
|
||||
testcase("With seed");
|
||||
|
||||
Xxhasher hasher{static_cast<std::uint32_t>(102)};
|
||||
|
||||
std::string objectToHash{"Hello, xxHash!"};
|
||||
hasher(objectToHash.data(), objectToHash.size());
|
||||
|
||||
BEAST_EXPECT(static_cast<Xxhasher::result_type>(hasher) == 14440132435660934800ULL);
|
||||
}
|
||||
|
||||
void
|
||||
testWithTwoSeeds()
|
||||
{
|
||||
testcase("With two seeds");
|
||||
Xxhasher hasher{static_cast<std::uint32_t>(102), static_cast<std::uint32_t>(103)};
|
||||
|
||||
std::string objectToHash{"Hello, xxHash!"};
|
||||
hasher(objectToHash.data(), objectToHash.size());
|
||||
|
||||
BEAST_EXPECT(static_cast<Xxhasher::result_type>(hasher) == 14440132435660934800ULL);
|
||||
}
|
||||
|
||||
void
|
||||
testBigObjectWithMultipleSmallUpdatesWithoutSeed()
|
||||
{
|
||||
testcase("Big object with multiple small updates without seed");
|
||||
Xxhasher hasher{};
|
||||
|
||||
std::string objectToHash{"Hello, xxHash!"};
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
hasher(objectToHash.data(), objectToHash.size());
|
||||
}
|
||||
|
||||
BEAST_EXPECT(static_cast<Xxhasher::result_type>(hasher) == 15296278154063476002ULL);
|
||||
}
|
||||
|
||||
void
|
||||
testBigObjectWithMultipleSmallUpdatesWithSeed()
|
||||
{
|
||||
testcase("Big object with multiple small updates with seed");
|
||||
Xxhasher hasher{static_cast<std::uint32_t>(103)};
|
||||
|
||||
std::string objectToHash{"Hello, xxHash!"};
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
hasher(objectToHash.data(), objectToHash.size());
|
||||
}
|
||||
|
||||
BEAST_EXPECT(static_cast<Xxhasher::result_type>(hasher) == 17285302196561698791ULL);
|
||||
}
|
||||
|
||||
void
|
||||
testBigObjectWithSmallAndBigUpdatesWithoutSeed()
|
||||
{
|
||||
testcase("Big object with small and big updates without seed");
|
||||
Xxhasher hasher{};
|
||||
|
||||
std::string objectToHash{"Hello, xxHash!"};
|
||||
std::string bigObject;
|
||||
for (int i = 0; i < 20; i++)
|
||||
{
|
||||
bigObject += "Hello, xxHash!";
|
||||
}
|
||||
hasher(objectToHash.data(), objectToHash.size());
|
||||
hasher(bigObject.data(), bigObject.size());
|
||||
hasher(objectToHash.data(), objectToHash.size());
|
||||
|
||||
BEAST_EXPECT(static_cast<Xxhasher::result_type>(hasher) == 1865045178324729219ULL);
|
||||
}
|
||||
|
||||
void
|
||||
testBigObjectWithSmallAndBigUpdatesWithSeed()
|
||||
{
|
||||
testcase("Big object with small and big updates with seed");
|
||||
Xxhasher hasher{static_cast<std::uint32_t>(103)};
|
||||
|
||||
std::string objectToHash{"Hello, xxHash!"};
|
||||
std::string bigObject;
|
||||
for (int i = 0; i < 20; i++)
|
||||
{
|
||||
bigObject += "Hello, xxHash!";
|
||||
}
|
||||
hasher(objectToHash.data(), objectToHash.size());
|
||||
hasher(bigObject.data(), bigObject.size());
|
||||
hasher(objectToHash.data(), objectToHash.size());
|
||||
|
||||
BEAST_EXPECT(static_cast<Xxhasher::result_type>(hasher) == 16189862915636005281ULL);
|
||||
}
|
||||
|
||||
void
|
||||
testBigObjectWithOneUpdateWithoutSeed()
|
||||
{
|
||||
testcase("Big object with one update without seed");
|
||||
Xxhasher hasher{};
|
||||
|
||||
std::string objectToHash;
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
objectToHash += "Hello, xxHash!";
|
||||
}
|
||||
hasher(objectToHash.data(), objectToHash.size());
|
||||
|
||||
BEAST_EXPECT(static_cast<Xxhasher::result_type>(hasher) == 15296278154063476002ULL);
|
||||
}
|
||||
|
||||
void
|
||||
testBigObjectWithOneUpdateWithSeed()
|
||||
{
|
||||
testcase("Big object with one update with seed");
|
||||
Xxhasher hasher{static_cast<std::uint32_t>(103)};
|
||||
|
||||
std::string objectToHash;
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
objectToHash += "Hello, xxHash!";
|
||||
}
|
||||
hasher(objectToHash.data(), objectToHash.size());
|
||||
|
||||
BEAST_EXPECT(static_cast<Xxhasher::result_type>(hasher) == 17285302196561698791ULL);
|
||||
}
|
||||
|
||||
void
|
||||
testOperatorResultTypeDoesNotChangeInternalState()
|
||||
{
|
||||
testcase("Operator result type doesn't change the internal state");
|
||||
{
|
||||
Xxhasher hasher;
|
||||
|
||||
std::string object{"Hello xxhash"};
|
||||
hasher(object.data(), object.size());
|
||||
auto xxhashResult1 = static_cast<Xxhasher::result_type>(hasher);
|
||||
auto xxhashResult2 = static_cast<Xxhasher::result_type>(hasher);
|
||||
|
||||
BEAST_EXPECT(xxhashResult1 == xxhashResult2);
|
||||
}
|
||||
{
|
||||
Xxhasher hasher;
|
||||
|
||||
std::string object;
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
object += "Hello, xxHash!";
|
||||
}
|
||||
hasher(object.data(), object.size());
|
||||
auto xxhashResult1 = hasher.operator Xxhasher::result_type();
|
||||
auto xxhashResult2 = hasher.operator Xxhasher::result_type();
|
||||
|
||||
BEAST_EXPECT(xxhashResult1 == xxhashResult2);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testWithoutSeed();
|
||||
testWithSeed();
|
||||
testWithTwoSeeds();
|
||||
testBigObjectWithMultipleSmallUpdatesWithoutSeed();
|
||||
testBigObjectWithMultipleSmallUpdatesWithSeed();
|
||||
testBigObjectWithSmallAndBigUpdatesWithoutSeed();
|
||||
testBigObjectWithSmallAndBigUpdatesWithSeed();
|
||||
testBigObjectWithOneUpdateWithoutSeed();
|
||||
testBigObjectWithOneUpdateWithSeed();
|
||||
testOperatorResultTypeDoesNotChangeInternalState();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(XXHasher, beast_core, beast);
|
||||
} // namespace beast
|
||||
+15
-61
@@ -19,8 +19,8 @@
|
||||
#include <xrpl/protocol/Asset.h>
|
||||
#include <xrpl/protocol/Book.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Issue.h>
|
||||
#include <xrpl/protocol/PathAsset.h>
|
||||
#include <xrpl/protocol/PublicKey.h>
|
||||
#include <xrpl/protocol/Quality.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
@@ -367,6 +367,20 @@ checkArraySize(json::Value const& val, unsigned int size);
|
||||
std::uint32_t
|
||||
ownerCount(test::jtx::Env const& env, test::jtx::Account const& account);
|
||||
|
||||
/* Token (IOU/MPT) Locking */
|
||||
/******************************************************************************/
|
||||
uint64_t
|
||||
mptEscrowed(jtx::Env const& env, jtx::Account const& account, jtx::MPT const& mpt);
|
||||
|
||||
uint64_t
|
||||
issuerMPTEscrowed(jtx::Env const& env, jtx::MPT const& mpt);
|
||||
|
||||
jtx::PrettyAmount
|
||||
issuerBalance(jtx::Env& env, jtx::Account const& account, Issue const& issue);
|
||||
|
||||
jtx::PrettyAmount
|
||||
issuerEscrowed(jtx::Env& env, jtx::Account const& account, Issue const& issue);
|
||||
|
||||
// Helper function that returns the sponsored owner count on an account.
|
||||
std::uint32_t
|
||||
sponsoredOwnerCount(test::jtx::Env const& env, test::jtx::Account const& account);
|
||||
@@ -583,66 +597,6 @@ accountBalance(Env& env, Account const& acct);
|
||||
[[nodiscard]] bool
|
||||
expectLedgerEntryRoot(Env& env, Account const& acct, STAmount const& expectedValue);
|
||||
|
||||
/* Payment Channel */
|
||||
/******************************************************************************/
|
||||
namespace paychan {
|
||||
|
||||
json::Value
|
||||
create(
|
||||
AccountID const& account,
|
||||
AccountID const& to,
|
||||
STAmount const& amount,
|
||||
NetClock::duration const& settleDelay,
|
||||
PublicKey const& pk,
|
||||
std::optional<NetClock::time_point> const& cancelAfter = std::nullopt,
|
||||
std::optional<std::uint32_t> const& dstTag = std::nullopt);
|
||||
|
||||
inline json::Value
|
||||
create(
|
||||
Account const& account,
|
||||
Account const& to,
|
||||
STAmount const& amount,
|
||||
NetClock::duration const& settleDelay,
|
||||
PublicKey const& pk,
|
||||
std::optional<NetClock::time_point> const& cancelAfter = std::nullopt,
|
||||
std::optional<std::uint32_t> const& dstTag = std::nullopt)
|
||||
{
|
||||
return create(account.id(), to.id(), amount, settleDelay, pk, cancelAfter, dstTag);
|
||||
}
|
||||
|
||||
json::Value
|
||||
fund(
|
||||
AccountID const& account,
|
||||
UInt256 const& channel,
|
||||
STAmount const& amount,
|
||||
std::optional<NetClock::time_point> const& expiration = std::nullopt);
|
||||
|
||||
json::Value
|
||||
claim(
|
||||
AccountID const& account,
|
||||
UInt256 const& channel,
|
||||
std::optional<STAmount> const& balance = std::nullopt,
|
||||
std::optional<STAmount> const& amount = std::nullopt,
|
||||
std::optional<Slice> const& signature = std::nullopt,
|
||||
std::optional<PublicKey> const& pk = std::nullopt);
|
||||
|
||||
UInt256
|
||||
channel(AccountID const& account, AccountID const& dst, std::uint32_t seqProxyValue);
|
||||
|
||||
inline UInt256
|
||||
channel(Account const& account, Account const& dst, std::uint32_t seqProxyValue)
|
||||
{
|
||||
return channel(account.id(), dst.id(), seqProxyValue);
|
||||
}
|
||||
|
||||
STAmount
|
||||
channelBalance(ReadView const& view, UInt256 const& chan);
|
||||
|
||||
bool
|
||||
channelExists(ReadView const& view, UInt256 const& chan);
|
||||
|
||||
} // namespace paychan
|
||||
|
||||
/* Crossing Limits */
|
||||
/******************************************************************************/
|
||||
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
#include <concepts>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <source_location>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
@@ -59,6 +61,29 @@ calcConfidentialBatchFee(jtx::Env const& env, uint32_t const& numSigners, uint32
|
||||
json::Value
|
||||
outer(jtx::Account const& account, uint32_t seq, STAmount const& fee, std::uint32_t flags);
|
||||
|
||||
/**
|
||||
* @brief Expect an inner Batch transaction to be recorded with the given
|
||||
* type and result, and with ParentBatchID pointing at its outer Batch.
|
||||
*
|
||||
* Looks the inner transaction up with the `tx` RPC, so the ledger holding the
|
||||
* batch must already be closed. Failures are reported at the caller's line.
|
||||
*
|
||||
* @param env The test environment.
|
||||
* @param batchID Hash of the outer Batch transaction.
|
||||
* @param txHash Hash of the inner transaction.
|
||||
* @param txType Expected TransactionType, e.g. "Payment".
|
||||
* @param result Expected TransactionResult, e.g. "tesSUCCESS".
|
||||
* @param loc Call site to report failures at.
|
||||
*/
|
||||
void
|
||||
validateInnerTxn(
|
||||
Env& env,
|
||||
std::string const& batchID,
|
||||
std::string const& txHash,
|
||||
std::string const& txType,
|
||||
std::string const& result,
|
||||
std::source_location const& loc = std::source_location::current());
|
||||
|
||||
/**
|
||||
* @brief Adds an inner Batch transaction to a JTx and autofills it.
|
||||
*/
|
||||
|
||||
@@ -16,18 +16,15 @@
|
||||
#include <xrpld/rpc/Role.h>
|
||||
|
||||
#include <xrpl/basics/Number.h>
|
||||
#include <xrpl/basics/Slice.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/basics/chrono.h>
|
||||
#include <xrpl/basics/contract.h>
|
||||
#include <xrpl/basics/strHex.h>
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/core/Job.h>
|
||||
#include <xrpl/core/ServiceRegistry.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/json/to_string.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/ApiVersion.h>
|
||||
@@ -37,13 +34,11 @@
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/MPTIssue.h>
|
||||
#include <xrpl/protocol/PathAsset.h>
|
||||
#include <xrpl/protocol/PublicKey.h>
|
||||
#include <xrpl/protocol/Quality.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/STParsedJSON.h>
|
||||
#include <xrpl/protocol/STPathSet.h>
|
||||
#include <xrpl/protocol/SeqProxy.h>
|
||||
#include <xrpl/protocol/UintTypes.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
@@ -53,7 +48,6 @@
|
||||
#include <xrpl/tx/paths/detail/Steps.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
@@ -95,6 +89,54 @@ ownerCount(Env const& env, Account const& account)
|
||||
return env.ownerCount(account);
|
||||
}
|
||||
|
||||
/* Token (IOU/MPT) Locking */
|
||||
/******************************************************************************/
|
||||
uint64_t
|
||||
mptEscrowed(jtx::Env const& env, jtx::Account const& account, jtx::MPT const& mpt)
|
||||
{
|
||||
auto const sle = env.le(keylet::mptoken(mpt.mpt(), account));
|
||||
if (sle && sle->isFieldPresent(sfLockedAmount))
|
||||
return (*sle)[sfLockedAmount];
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint64_t
|
||||
issuerMPTEscrowed(jtx::Env const& env, jtx::MPT const& mpt)
|
||||
{
|
||||
auto const sle = env.le(keylet::mptokenIssuance(mpt.mpt()));
|
||||
if (sle && sle->isFieldPresent(sfLockedAmount))
|
||||
return (*sle)[sfLockedAmount];
|
||||
return 0;
|
||||
}
|
||||
|
||||
jtx::PrettyAmount
|
||||
issuerBalance(jtx::Env& env, jtx::Account const& account, Issue const& issue)
|
||||
{
|
||||
json::Value params;
|
||||
params[jss::account] = account.human();
|
||||
auto jrr = env.rpc("json", "gateway_balances", to_string(params));
|
||||
auto const result = jrr[jss::result];
|
||||
auto const obligations = result[jss::obligations][to_string(issue.currency)];
|
||||
if (obligations.isNull())
|
||||
return {STAmount(issue, 0), account.name()};
|
||||
STAmount const amount = amountFromString(issue, obligations.asString());
|
||||
return {amount, account.name()};
|
||||
}
|
||||
|
||||
jtx::PrettyAmount
|
||||
issuerEscrowed(jtx::Env& env, jtx::Account const& account, Issue const& issue)
|
||||
{
|
||||
json::Value params;
|
||||
params[jss::account] = account.human();
|
||||
auto jrr = env.rpc("json", "gateway_balances", to_string(params));
|
||||
auto const result = jrr[jss::result];
|
||||
auto const locked = result[jss::locked][to_string(issue.currency)];
|
||||
if (locked.isNull())
|
||||
return {STAmount(issue, 0), account.name()};
|
||||
STAmount const amount = amountFromString(issue, locked.asString());
|
||||
return {amount, account.name()};
|
||||
}
|
||||
|
||||
std::uint32_t
|
||||
sponsoredOwnerCount(Env const& env, Account const& account)
|
||||
{
|
||||
@@ -500,101 +542,6 @@ expectLedgerEntryRoot(Env& env, Account const& acct, STAmount const& expectedVal
|
||||
return accountBalance(env, acct) == to_string(expectedValue.xrp());
|
||||
}
|
||||
|
||||
/* Payment Channel */
|
||||
/******************************************************************************/
|
||||
namespace paychan {
|
||||
|
||||
json::Value
|
||||
create(
|
||||
AccountID const& account,
|
||||
AccountID const& to,
|
||||
STAmount const& amount,
|
||||
NetClock::duration const& settleDelay,
|
||||
PublicKey const& pk,
|
||||
std::optional<NetClock::time_point> const& cancelAfter,
|
||||
std::optional<std::uint32_t> const& dstTag)
|
||||
{
|
||||
json::Value jv;
|
||||
jv[jss::TransactionType] = jss::PaymentChannelCreate;
|
||||
jv[jss::Account] = to_string(account);
|
||||
jv[jss::Destination] = to_string(to);
|
||||
jv[jss::Amount] = amount.getJson(JsonOptions::Values::None);
|
||||
jv[jss::SettleDelay] = settleDelay.count();
|
||||
jv[sfPublicKey.fieldName] = strHex(pk.slice());
|
||||
if (cancelAfter)
|
||||
jv[sfCancelAfter.fieldName] = cancelAfter->time_since_epoch().count();
|
||||
if (dstTag)
|
||||
jv[sfDestinationTag.fieldName] = *dstTag;
|
||||
return jv;
|
||||
}
|
||||
|
||||
json::Value
|
||||
fund(
|
||||
AccountID const& account,
|
||||
UInt256 const& channel,
|
||||
STAmount const& amount,
|
||||
std::optional<NetClock::time_point> const& expiration)
|
||||
{
|
||||
json::Value jv;
|
||||
jv[jss::TransactionType] = jss::PaymentChannelFund;
|
||||
jv[jss::Account] = to_string(account);
|
||||
jv[sfChannel.fieldName] = to_string(channel);
|
||||
jv[jss::Amount] = amount.getJson(JsonOptions::Values::None);
|
||||
if (expiration)
|
||||
jv[sfExpiration.fieldName] = expiration->time_since_epoch().count();
|
||||
return jv;
|
||||
}
|
||||
|
||||
json::Value
|
||||
claim(
|
||||
AccountID const& account,
|
||||
UInt256 const& channel,
|
||||
std::optional<STAmount> const& balance,
|
||||
std::optional<STAmount> const& amount,
|
||||
std::optional<Slice> const& signature,
|
||||
std::optional<PublicKey> const& pk)
|
||||
{
|
||||
json::Value jv;
|
||||
jv[jss::TransactionType] = jss::PaymentChannelClaim;
|
||||
jv[jss::Account] = to_string(account);
|
||||
jv["Channel"] = to_string(channel);
|
||||
if (amount)
|
||||
jv[jss::Amount] = amount->getJson(JsonOptions::Values::None);
|
||||
if (balance)
|
||||
jv["Balance"] = balance->getJson(JsonOptions::Values::None);
|
||||
if (signature)
|
||||
jv["Signature"] = strHex(*signature);
|
||||
if (pk)
|
||||
jv["PublicKey"] = strHex(pk->slice());
|
||||
return jv;
|
||||
}
|
||||
|
||||
UInt256
|
||||
channel(AccountID const& account, AccountID const& dst, std::uint32_t seqProxyValue)
|
||||
{
|
||||
auto const seqProxy = SeqProxy::rawSequence(seqProxyValue);
|
||||
auto const k = keylet::payChannel(account, dst, seqProxy);
|
||||
return k.key;
|
||||
}
|
||||
|
||||
STAmount
|
||||
channelBalance(ReadView const& view, UInt256 const& chan)
|
||||
{
|
||||
auto const slep = view.read({ltPAYCHAN, chan});
|
||||
if (!slep)
|
||||
return XRPAmount{-1};
|
||||
return (*slep)[sfBalance];
|
||||
}
|
||||
|
||||
bool
|
||||
channelExists(ReadView const& view, UInt256 const& chan)
|
||||
{
|
||||
auto const slep = view.read({ltPAYCHAN, chan});
|
||||
return bool(slep);
|
||||
}
|
||||
|
||||
} // namespace paychan
|
||||
|
||||
/* Crossing Limits */
|
||||
/******************************************************************************/
|
||||
|
||||
|
||||
@@ -27,6 +27,8 @@
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <ostream>
|
||||
#include <source_location>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace xrpl::test::jtx::batch {
|
||||
@@ -62,6 +64,30 @@ outer(jtx::Account const& account, uint32_t seq, STAmount const& fee, std::uint3
|
||||
return jv;
|
||||
}
|
||||
|
||||
void
|
||||
validateInnerTxn(
|
||||
Env& env,
|
||||
std::string const& batchID,
|
||||
std::string const& txHash,
|
||||
std::string const& txType,
|
||||
std::string const& result,
|
||||
std::source_location const& loc)
|
||||
{
|
||||
json::Value const jrr = env.rpc("tx", txHash)[jss::result];
|
||||
env.test.expect(
|
||||
jrr[sfTransactionType.jsonName] == txType, "TransactionType", loc.file_name(), loc.line());
|
||||
env.test.expect(
|
||||
jrr[jss::meta][sfTransactionResult.jsonName] == result,
|
||||
"TransactionResult",
|
||||
loc.file_name(),
|
||||
loc.line());
|
||||
env.test.expect(
|
||||
jrr[jss::meta][sfParentBatchID.jsonName] == batchID,
|
||||
"ParentBatchID",
|
||||
loc.file_name(),
|
||||
loc.line());
|
||||
}
|
||||
|
||||
void
|
||||
Inner::operator()(Env& env, JTx& jt) const
|
||||
{
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
#include <test/jtx/paychan.h>
|
||||
|
||||
#include <xrpl/basics/Buffer.h>
|
||||
#include <xrpl/basics/Slice.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/basics/chrono.h>
|
||||
#include <xrpl/basics/strHex.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/PayChan.h>
|
||||
#include <xrpl/protocol/PublicKey.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/SecretKey.h>
|
||||
#include <xrpl/protocol/SeqProxy.h>
|
||||
#include <xrpl/protocol/Serializer.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
|
||||
/**
|
||||
* Paychan operations.
|
||||
*/
|
||||
namespace xrpl::test::jtx::paychan {
|
||||
|
||||
json::Value
|
||||
create(
|
||||
AccountID const& account,
|
||||
AccountID const& to,
|
||||
STAmount const& amount,
|
||||
NetClock::duration const& settleDelay,
|
||||
PublicKey const& pk,
|
||||
std::optional<NetClock::time_point> const& cancelAfter,
|
||||
std::optional<std::uint32_t> const& dstTag)
|
||||
{
|
||||
json::Value jv;
|
||||
jv[jss::TransactionType] = jss::PaymentChannelCreate;
|
||||
jv[jss::Account] = to_string(account);
|
||||
jv[jss::Destination] = to_string(to);
|
||||
jv[jss::Amount] = amount.getJson(JsonOptions::Values::None);
|
||||
jv[jss::SettleDelay] = settleDelay.count();
|
||||
jv[sfPublicKey.fieldName] = strHex(pk.slice());
|
||||
if (cancelAfter)
|
||||
jv[sfCancelAfter.fieldName] = cancelAfter->time_since_epoch().count();
|
||||
if (dstTag)
|
||||
jv[sfDestinationTag.fieldName] = *dstTag;
|
||||
return jv;
|
||||
}
|
||||
|
||||
json::Value
|
||||
fund(
|
||||
AccountID const& account,
|
||||
uint256 const& channel,
|
||||
STAmount const& amount,
|
||||
std::optional<NetClock::time_point> const& expiration)
|
||||
{
|
||||
json::Value jv;
|
||||
jv[jss::TransactionType] = jss::PaymentChannelFund;
|
||||
jv[jss::Account] = to_string(account);
|
||||
jv[sfChannel.fieldName] = to_string(channel);
|
||||
jv[jss::Amount] = amount.getJson(JsonOptions::Values::None);
|
||||
if (expiration)
|
||||
jv[sfExpiration.fieldName] = expiration->time_since_epoch().count();
|
||||
return jv;
|
||||
}
|
||||
|
||||
json::Value
|
||||
claim(
|
||||
AccountID const& account,
|
||||
uint256 const& channel,
|
||||
std::optional<STAmount> const& balance,
|
||||
std::optional<STAmount> const& amount,
|
||||
std::optional<Slice> const& signature,
|
||||
std::optional<PublicKey> const& pk)
|
||||
{
|
||||
json::Value jv;
|
||||
jv[jss::TransactionType] = jss::PaymentChannelClaim;
|
||||
jv[jss::Account] = to_string(account);
|
||||
jv["Channel"] = to_string(channel);
|
||||
if (amount)
|
||||
jv[jss::Amount] = amount->getJson(JsonOptions::Values::None);
|
||||
if (balance)
|
||||
jv["Balance"] = balance->getJson(JsonOptions::Values::None);
|
||||
if (signature)
|
||||
jv["Signature"] = strHex(*signature);
|
||||
if (pk)
|
||||
jv["PublicKey"] = strHex(pk->slice());
|
||||
return jv;
|
||||
}
|
||||
|
||||
uint256
|
||||
channel(AccountID const& account, AccountID const& dst, std::uint32_t seqProxyValue)
|
||||
{
|
||||
auto const seqProxy = SeqProxy::rawSequence(seqProxyValue);
|
||||
auto const k = keylet::payChannel(account, dst, seqProxy);
|
||||
return k.key;
|
||||
}
|
||||
|
||||
STAmount
|
||||
channelBalance(ReadView const& view, uint256 const& chan)
|
||||
{
|
||||
auto const slep = view.read({ltPAYCHAN, chan});
|
||||
if (!slep)
|
||||
return XRPAmount{-1};
|
||||
return (*slep)[sfBalance];
|
||||
}
|
||||
|
||||
STAmount
|
||||
channelAmount(ReadView const& view, uint256 const& chan)
|
||||
{
|
||||
auto const slep = view.read({ltPAYCHAN, chan});
|
||||
if (!slep)
|
||||
return XRPAmount{-1};
|
||||
return (*slep)[sfAmount];
|
||||
}
|
||||
|
||||
bool
|
||||
channelExists(ReadView const& view, uint256 const& chan)
|
||||
{
|
||||
auto const slep = view.read({ltPAYCHAN, chan});
|
||||
return bool(slep);
|
||||
}
|
||||
|
||||
Buffer
|
||||
signClaimAuth(
|
||||
PublicKey const& pk,
|
||||
SecretKey const& sk,
|
||||
uint256 const& channel,
|
||||
STAmount const& authAmt)
|
||||
{
|
||||
Serializer msg;
|
||||
serializePayChanAuthorization(msg, channel, authAmt.xrp());
|
||||
return sign(pk, sk, msg.slice());
|
||||
}
|
||||
|
||||
} // namespace xrpl::test::jtx::paychan
|
||||
@@ -0,0 +1,88 @@
|
||||
#pragma once
|
||||
|
||||
#include <test/jtx/Account.h>
|
||||
|
||||
#include <xrpl/basics/Buffer.h>
|
||||
#include <xrpl/basics/Slice.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/basics/chrono.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/PublicKey.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/SecretKey.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
|
||||
/**
|
||||
* Paychan operations.
|
||||
*/
|
||||
namespace xrpl::test::jtx::paychan {
|
||||
|
||||
json::Value
|
||||
create(
|
||||
AccountID const& account,
|
||||
AccountID const& to,
|
||||
STAmount const& amount,
|
||||
NetClock::duration const& settleDelay,
|
||||
PublicKey const& pk,
|
||||
std::optional<NetClock::time_point> const& cancelAfter = std::nullopt,
|
||||
std::optional<std::uint32_t> const& dstTag = std::nullopt);
|
||||
|
||||
inline json::Value
|
||||
create(
|
||||
Account const& account,
|
||||
Account const& to,
|
||||
STAmount const& amount,
|
||||
NetClock::duration const& settleDelay,
|
||||
PublicKey const& pk,
|
||||
std::optional<NetClock::time_point> const& cancelAfter = std::nullopt,
|
||||
std::optional<std::uint32_t> const& dstTag = std::nullopt)
|
||||
{
|
||||
return create(account.id(), to.id(), amount, settleDelay, pk, cancelAfter, dstTag);
|
||||
}
|
||||
|
||||
json::Value
|
||||
fund(
|
||||
AccountID const& account,
|
||||
uint256 const& channel,
|
||||
STAmount const& amount,
|
||||
std::optional<NetClock::time_point> const& expiration = std::nullopt);
|
||||
|
||||
json::Value
|
||||
claim(
|
||||
AccountID const& account,
|
||||
uint256 const& channel,
|
||||
std::optional<STAmount> const& balance = std::nullopt,
|
||||
std::optional<STAmount> const& amount = std::nullopt,
|
||||
std::optional<Slice> const& signature = std::nullopt,
|
||||
std::optional<PublicKey> const& pk = std::nullopt);
|
||||
|
||||
uint256
|
||||
channel(AccountID const& account, AccountID const& dst, std::uint32_t seqProxyValue);
|
||||
|
||||
inline uint256
|
||||
channel(Account const& account, Account const& dst, std::uint32_t seqProxyValue)
|
||||
{
|
||||
return channel(account.id(), dst.id(), seqProxyValue);
|
||||
}
|
||||
|
||||
STAmount
|
||||
channelBalance(ReadView const& view, uint256 const& chan);
|
||||
|
||||
STAmount
|
||||
channelAmount(ReadView const& view, uint256 const& chan);
|
||||
|
||||
bool
|
||||
channelExists(ReadView const& view, uint256 const& chan);
|
||||
|
||||
Buffer
|
||||
signClaimAuth(
|
||||
PublicKey const& pk,
|
||||
SecretKey const& sk,
|
||||
uint256 const& channel,
|
||||
STAmount const& authAmt);
|
||||
|
||||
} // namespace xrpl::test::jtx::paychan
|
||||
@@ -319,6 +319,22 @@ class AccountTx_test : public beast::unit_test::Suite
|
||||
BEAST_EXPECT(isErr(env.rpc("json", "account_tx", to_string(p)), RpcLgrNotFound));
|
||||
}
|
||||
|
||||
{
|
||||
json::Value p{jParams};
|
||||
|
||||
p[jss::ledger_index] = "validated";
|
||||
BEAST_EXPECT(hasTxs(env.rpc(apiVersion, "json", "account_tx", to_string(p))));
|
||||
|
||||
p[jss::ledger_index] = "closed";
|
||||
BEAST_EXPECT(hasTxs(env.rpc(apiVersion, "json", "account_tx", to_string(p))));
|
||||
|
||||
p[jss::ledger_index] = "current";
|
||||
BEAST_EXPECT(isErr(env.rpc("json", "account_tx", to_string(p)), RpcLgrNotValidated));
|
||||
|
||||
p[jss::ledger_index] = "";
|
||||
BEAST_EXPECT(isErr(env.rpc("json", "account_tx", to_string(p)), RpcLgrNotValidated));
|
||||
}
|
||||
|
||||
// Ledger Hash
|
||||
{
|
||||
json::Value p{jParams};
|
||||
|
||||
@@ -506,7 +506,18 @@ class Feature_test : public beast::unit_test::Suite
|
||||
feature = *(jrr.begin());
|
||||
BEAST_EXPECTS(feature[jss::name] == kFeatureName, "name");
|
||||
BEAST_EXPECTS(feature[jss::vetoed].isBool() && !feature[jss::vetoed].asBool(), "vetoed");
|
||||
auto testInvalidVetoed = [&](auto const& vetoed) {
|
||||
json::Value params;
|
||||
params[jss::feature] = kFeatureName;
|
||||
params[jss::vetoed] = vetoed;
|
||||
|
||||
auto const result = env.rpc("json", "feature", to_string(params))[jss::result];
|
||||
BEAST_EXPECT(result[jss::error] == "invalidParams");
|
||||
BEAST_EXPECT(result[jss::error_message] == "Invalid parameters.");
|
||||
};
|
||||
|
||||
testInvalidVetoed("false");
|
||||
testInvalidVetoed(json::Value(json::ValueType::Null));
|
||||
// anything other than accept or reject is an error
|
||||
jrr = env.rpc("feature", kFeatureName, "maybe");
|
||||
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
|
||||
|
||||
@@ -92,6 +92,7 @@ std::vector<std::pair<json::StaticString, FieldType>> gMappings{
|
||||
{jss::authorized, FieldType::AccountField},
|
||||
{jss::credential_type, FieldType::BlobField},
|
||||
{jss::currency, FieldType::CurrencyField},
|
||||
{jss::destination, FieldType::AccountField},
|
||||
{jss::issuer, FieldType::AccountField},
|
||||
{jss::oracle_document_id, FieldType::UInt32Field},
|
||||
{jss::owner, FieldType::AccountField},
|
||||
@@ -813,7 +814,8 @@ class LedgerEntry_test : public beast::unit_test::Suite
|
||||
env.fund(XRP(10000), alice);
|
||||
env.close();
|
||||
|
||||
auto const checkId = keylet::check(env.master, SeqProxy::rawSequence(env.seq(env.master)));
|
||||
auto const checkSeq = env.seq(env.master);
|
||||
auto const checkId = keylet::check(env.master, SeqProxy::rawSequence(checkSeq));
|
||||
|
||||
env(check::create(env.master, alice, XRP(100)));
|
||||
env.close();
|
||||
@@ -829,6 +831,30 @@ class LedgerEntry_test : public beast::unit_test::Suite
|
||||
BEAST_EXPECT(jrr[jss::node][sfLedgerEntryType.jsonName] == jss::Check);
|
||||
BEAST_EXPECT(jrr[jss::node][sfSendMax.jsonName] == "100000000");
|
||||
}
|
||||
{
|
||||
// Request a check by account and seq.
|
||||
json::Value jvParams;
|
||||
jvParams[jss::check] = json::ValueType::Object;
|
||||
jvParams[jss::check][jss::account] = env.master.human();
|
||||
jvParams[jss::check][jss::seq] = checkSeq;
|
||||
jvParams[jss::ledger_hash] = ledgerHash;
|
||||
json::Value const jrr =
|
||||
env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
|
||||
BEAST_EXPECT(jrr[jss::node][sfLedgerEntryType.jsonName] == jss::Check);
|
||||
BEAST_EXPECT(jrr[jss::node][sfSendMax.jsonName] == "100000000");
|
||||
BEAST_EXPECT(jrr[jss::index] == to_string(checkId.key));
|
||||
}
|
||||
{
|
||||
// Request a non-existent check by account and seq.
|
||||
json::Value jvParams;
|
||||
jvParams[jss::check] = json::ValueType::Object;
|
||||
jvParams[jss::check][jss::account] = env.master.human();
|
||||
jvParams[jss::check][jss::seq] = checkSeq + 1000;
|
||||
jvParams[jss::ledger_hash] = ledgerHash;
|
||||
json::Value const jrr =
|
||||
env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
|
||||
checkErrorValue(jrr, "entryNotFound", "Entry not found.");
|
||||
}
|
||||
{
|
||||
// Request an index that is not a check. We'll use alice's
|
||||
// account root index.
|
||||
@@ -849,7 +875,13 @@ class LedgerEntry_test : public beast::unit_test::Suite
|
||||
}
|
||||
{
|
||||
// Check malformed cases
|
||||
runLedgerEntryTest(env, jss::check);
|
||||
runLedgerEntryTest(
|
||||
env,
|
||||
jss::check,
|
||||
{
|
||||
{.fieldName = jss::account, .malformedErrorMsg = "malformedAddress"},
|
||||
{.fieldName = jss::seq, .malformedErrorMsg = "malformedRequest"},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1533,8 +1565,8 @@ class LedgerEntry_test : public beast::unit_test::Suite
|
||||
UInt256 const nftokenID0 = token::getNextID(env, issuer, 0, tfTransferable);
|
||||
env(token::mint(issuer, 0), Txflags(tfTransferable));
|
||||
env.close();
|
||||
UInt256 const offerID =
|
||||
keylet::nftokenOffer(issuer, SeqProxy::rawSequence(env.seq(issuer))).key;
|
||||
std::uint32_t const offerSeq = env.seq(issuer);
|
||||
UInt256 const offerID = keylet::nftokenOffer(issuer, SeqProxy::rawSequence(offerSeq)).key;
|
||||
env(token::createOffer(issuer, nftokenID0, drops(1)),
|
||||
token::Destination(buyer),
|
||||
Txflags(tfSellNFToken));
|
||||
@@ -1549,9 +1581,39 @@ class LedgerEntry_test : public beast::unit_test::Suite
|
||||
BEAST_EXPECT(jrr[jss::node][sfNFTokenID.jsonName] == to_string(nftokenID0));
|
||||
BEAST_EXPECT(jrr[jss::node][sfAmount.jsonName] == "1");
|
||||
}
|
||||
{
|
||||
// Request by owner and seq.
|
||||
json::Value jvParams;
|
||||
jvParams[jss::nft_offer] = json::ValueType::Object;
|
||||
jvParams[jss::nft_offer][jss::owner] = issuer.human();
|
||||
jvParams[jss::nft_offer][jss::seq] = offerSeq;
|
||||
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");
|
||||
BEAST_EXPECT(jrr[jss::index] == to_string(offerID));
|
||||
}
|
||||
{
|
||||
// Request a non-existent offer by owner and seq.
|
||||
json::Value jvParams;
|
||||
jvParams[jss::nft_offer] = json::ValueType::Object;
|
||||
jvParams[jss::nft_offer][jss::owner] = issuer.human();
|
||||
jvParams[jss::nft_offer][jss::seq] = offerSeq + 1000;
|
||||
json::Value const jrr =
|
||||
env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
|
||||
checkErrorValue(jrr, "entryNotFound", "Entry not found.");
|
||||
}
|
||||
|
||||
// negative tests
|
||||
runLedgerEntryTest(env, jss::nft_offer);
|
||||
runLedgerEntryTest(
|
||||
env,
|
||||
jss::nft_offer,
|
||||
{
|
||||
{.fieldName = jss::owner, .malformedErrorMsg = "malformedOwner"},
|
||||
{.fieldName = jss::seq, .malformedErrorMsg = "malformedRequest"},
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
@@ -1713,13 +1775,14 @@ class LedgerEntry_test : public beast::unit_test::Suite
|
||||
return jv;
|
||||
};
|
||||
|
||||
auto const payChanSeq = env.seq(alice);
|
||||
env(payChanCreate(alice, env.master, XRP(57), 18s, alice.pk()));
|
||||
env.close();
|
||||
|
||||
std::string const ledgerHash{to_string(env.closed()->header().hash)};
|
||||
|
||||
UInt256 const payChanIndex{
|
||||
keylet::payChannel(alice, env.master, SeqProxy::rawSequence(env.seq(alice) - 1)).key};
|
||||
keylet::payChannel(alice, env.master, SeqProxy::rawSequence(payChanSeq)).key};
|
||||
{
|
||||
// Request the payment channel using its index.
|
||||
json::Value jvParams;
|
||||
@@ -1731,6 +1794,33 @@ class LedgerEntry_test : public beast::unit_test::Suite
|
||||
BEAST_EXPECT(jrr[jss::node][sfBalance.jsonName] == "0");
|
||||
BEAST_EXPECT(jrr[jss::node][sfSettleDelay.jsonName] == 18);
|
||||
}
|
||||
{
|
||||
// Request the payment channel by account, destination, and seq.
|
||||
json::Value jvParams;
|
||||
jvParams[jss::payment_channel] = json::ValueType::Object;
|
||||
jvParams[jss::payment_channel][jss::account] = alice.human();
|
||||
jvParams[jss::payment_channel][jss::destination] = env.master.human();
|
||||
jvParams[jss::payment_channel][jss::seq] = payChanSeq;
|
||||
jvParams[jss::ledger_hash] = ledgerHash;
|
||||
json::Value const jrr =
|
||||
env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
|
||||
BEAST_EXPECT(jrr[jss::node][sfAmount.jsonName] == "57000000");
|
||||
BEAST_EXPECT(jrr[jss::node][sfBalance.jsonName] == "0");
|
||||
BEAST_EXPECT(jrr[jss::node][sfSettleDelay.jsonName] == 18);
|
||||
BEAST_EXPECT(jrr[jss::index] == to_string(payChanIndex));
|
||||
}
|
||||
{
|
||||
// Request a non-existent payment channel by account, destination, and seq.
|
||||
json::Value jvParams;
|
||||
jvParams[jss::payment_channel] = json::ValueType::Object;
|
||||
jvParams[jss::payment_channel][jss::account] = alice.human();
|
||||
jvParams[jss::payment_channel][jss::destination] = env.master.human();
|
||||
jvParams[jss::payment_channel][jss::seq] = payChanSeq + 1000;
|
||||
jvParams[jss::ledger_hash] = ledgerHash;
|
||||
json::Value const jrr =
|
||||
env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
|
||||
checkErrorValue(jrr, "entryNotFound", "Entry not found.");
|
||||
}
|
||||
{
|
||||
// Request an index that is not a payment channel.
|
||||
json::Value jvParams;
|
||||
@@ -1743,7 +1833,14 @@ class LedgerEntry_test : public beast::unit_test::Suite
|
||||
|
||||
{
|
||||
// Malformed paychan field
|
||||
runLedgerEntryTest(env, jss::payment_channel);
|
||||
runLedgerEntryTest(
|
||||
env,
|
||||
jss::payment_channel,
|
||||
{
|
||||
{.fieldName = jss::account, .malformedErrorMsg = "malformedAddress"},
|
||||
{.fieldName = jss::destination, .malformedErrorMsg = "malformedAddress"},
|
||||
{.fieldName = jss::seq, .malformedErrorMsg = "malformedRequest"},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1892,7 +1989,70 @@ class LedgerEntry_test : public beast::unit_test::Suite
|
||||
testcase("Signer List");
|
||||
using namespace test::jtx;
|
||||
Env env{*this};
|
||||
runLedgerEntryTest(env, jss::signer_list);
|
||||
Account const alice{"alice"};
|
||||
Account const bogie{"bogie"};
|
||||
Account const demon{"demon"};
|
||||
|
||||
env.fund(XRP(10000), alice);
|
||||
env.close();
|
||||
|
||||
// Attach phantom signers to alice.
|
||||
env(signers(alice, 1, {{bogie, 1}, {demon, 1}}));
|
||||
env.close();
|
||||
|
||||
std::string const ledgerHash{to_string(env.closed()->header().hash)};
|
||||
auto const signerListIndex = keylet::signerList(alice).key;
|
||||
{
|
||||
// Request by hash.
|
||||
json::Value jvParams;
|
||||
jvParams[jss::signer_list] = to_string(signerListIndex);
|
||||
jvParams[jss::ledger_hash] = ledgerHash;
|
||||
json::Value const jrr =
|
||||
env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
|
||||
BEAST_EXPECT(jrr[jss::node][sfLedgerEntryType.jsonName] == jss::SignerList);
|
||||
BEAST_EXPECT(jrr[jss::node][sfSignerQuorum.jsonName] == 1);
|
||||
}
|
||||
{
|
||||
// Request by account.
|
||||
json::Value jvParams;
|
||||
jvParams[jss::signer_list] = json::ValueType::Object;
|
||||
jvParams[jss::signer_list][jss::account] = alice.human();
|
||||
jvParams[jss::ledger_hash] = ledgerHash;
|
||||
json::Value const jrr =
|
||||
env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
|
||||
BEAST_EXPECT(jrr[jss::node][sfLedgerEntryType.jsonName] == jss::SignerList);
|
||||
BEAST_EXPECT(jrr[jss::node][sfSignerQuorum.jsonName] == 1);
|
||||
BEAST_EXPECT(jrr[jss::index] == to_string(signerListIndex));
|
||||
}
|
||||
{
|
||||
// Request a non-existent signer list by account.
|
||||
json::Value jvParams;
|
||||
jvParams[jss::signer_list] = json::ValueType::Object;
|
||||
jvParams[jss::signer_list][jss::account] = env.master.human();
|
||||
jvParams[jss::ledger_hash] = ledgerHash;
|
||||
json::Value const jrr =
|
||||
env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
|
||||
checkErrorValue(jrr, "entryNotFound", "Entry not found.");
|
||||
}
|
||||
{
|
||||
// Request an index that is not a signer list.
|
||||
json::Value jvParams;
|
||||
jvParams[jss::signer_list] = ledgerHash;
|
||||
jvParams[jss::ledger_hash] = ledgerHash;
|
||||
json::Value const jrr =
|
||||
env.rpc("json", "ledger_entry", to_string(jvParams))[jss::result];
|
||||
checkErrorValue(jrr, "entryNotFound", "Entry not found.");
|
||||
}
|
||||
|
||||
{
|
||||
// Malformed signer_list fields
|
||||
runLedgerEntryTest(
|
||||
env,
|
||||
jss::signer_list,
|
||||
{
|
||||
{.fieldName = jss::account, .malformedErrorMsg = "malformedAddress"},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -103,6 +103,19 @@ class LedgerRPC_test : public beast::unit_test::Suite
|
||||
jrr[jss::ledger][jss::ledger_index] == std::to_string(env.current()->header().seq));
|
||||
BEAST_EXPECT(jrr[jss::ledger_current_index] == env.current()->header().seq);
|
||||
}
|
||||
|
||||
{
|
||||
auto const jrr = env.rpc("json", "ledger", "{}")[jss::result];
|
||||
BEAST_EXPECT(!jrr.isMember(jss::ledger));
|
||||
BEAST_EXPECT(jrr[jss::closed][jss::ledger][jss::closed] == true);
|
||||
BEAST_EXPECT(
|
||||
jrr[jss::closed][jss::ledger][jss::ledger_index] ==
|
||||
std::to_string(env.closed()->header().seq));
|
||||
BEAST_EXPECT(jrr[jss::open][jss::ledger][jss::closed] == false);
|
||||
BEAST_EXPECT(
|
||||
jrr[jss::open][jss::ledger][jss::ledger_index] ==
|
||||
std::to_string(env.current()->header().seq));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#include <xrpld/rpc/Status.h>
|
||||
#include <xrpld/rpc/detail/RPCHelpers.h>
|
||||
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
@@ -24,49 +23,50 @@ public:
|
||||
// Test no type.
|
||||
json::Value tx = json::ValueType::Object;
|
||||
auto result = rpc::chooseLedgerEntryType(tx);
|
||||
BEAST_EXPECT(result.first == rpc::Status::kOK);
|
||||
BEAST_EXPECT(result.first == ::rpc::Status::kOK);
|
||||
BEAST_EXPECT(result.second == 0);
|
||||
|
||||
// Test empty type.
|
||||
tx[jss::type] = "";
|
||||
result = rpc::chooseLedgerEntryType(tx);
|
||||
BEAST_EXPECT(result.first == rpc::Status{RpcInvalidParams});
|
||||
BEAST_EXPECT(result.first == ::rpc::Status(RpcInvalidParams, "Invalid field 'type'."));
|
||||
BEAST_EXPECT(result.second == 0);
|
||||
|
||||
// Test type using canonical name in mixedcase.
|
||||
tx[jss::type] = "MPTokenIssuance";
|
||||
result = rpc::chooseLedgerEntryType(tx);
|
||||
BEAST_EXPECT(result.first == rpc::Status::kOK);
|
||||
BEAST_EXPECT(result.first == ::rpc::Status::kOK);
|
||||
BEAST_EXPECT(result.second == ltMPTOKEN_ISSUANCE);
|
||||
|
||||
// Test type using canonical name in lowercase.
|
||||
tx[jss::type] = "mptokenissuance";
|
||||
result = rpc::chooseLedgerEntryType(tx);
|
||||
BEAST_EXPECT(result.first == rpc::Status::kOK);
|
||||
BEAST_EXPECT(result.first == ::rpc::Status::kOK);
|
||||
BEAST_EXPECT(result.second == ltMPTOKEN_ISSUANCE);
|
||||
|
||||
// Test type using RPC name with exact match.
|
||||
tx[jss::type] = "mpt_issuance";
|
||||
result = rpc::chooseLedgerEntryType(tx);
|
||||
BEAST_EXPECT(result.first == rpc::Status::kOK);
|
||||
BEAST_EXPECT(result.first == ::rpc::Status::kOK);
|
||||
BEAST_EXPECT(result.second == ltMPTOKEN_ISSUANCE);
|
||||
|
||||
// Test type using RPC name with inexact match.
|
||||
tx[jss::type] = "MPT_Issuance";
|
||||
result = rpc::chooseLedgerEntryType(tx);
|
||||
BEAST_EXPECT(result.first == rpc::Status{RpcInvalidParams});
|
||||
BEAST_EXPECT(result.first == ::rpc::Status(RpcInvalidParams, "Invalid field 'type'."));
|
||||
BEAST_EXPECT(result.second == 0);
|
||||
|
||||
// Test invalid type.
|
||||
tx[jss::type] = 1234;
|
||||
result = rpc::chooseLedgerEntryType(tx);
|
||||
BEAST_EXPECT(result.first == rpc::Status{RpcInvalidParams});
|
||||
BEAST_EXPECT(
|
||||
result.first == ::rpc::Status(RpcInvalidParams, "Invalid field 'type', not string."));
|
||||
BEAST_EXPECT(result.second == 0);
|
||||
|
||||
// Test unknown type.
|
||||
tx[jss::type] = "unknown";
|
||||
result = rpc::chooseLedgerEntryType(tx);
|
||||
BEAST_EXPECT(result.first == rpc::Status{RpcInvalidParams});
|
||||
BEAST_EXPECT(result.first == ::rpc::Status(RpcInvalidParams, "Invalid field 'type'."));
|
||||
BEAST_EXPECT(result.second == 0);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,207 +0,0 @@
|
||||
#include <xrpld/rpc/Status.h>
|
||||
|
||||
#include <xrpl/basics/contract.h>
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/protocol/ErrorCodes.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <exception>
|
||||
#include <string>
|
||||
|
||||
namespace xrpl::rpc {
|
||||
|
||||
class codeString_test : public beast::unit_test::Suite
|
||||
{
|
||||
private:
|
||||
template <typename Type>
|
||||
std::string
|
||||
codeString(Type t)
|
||||
{
|
||||
return Status(t).codeString();
|
||||
}
|
||||
|
||||
void
|
||||
testOk()
|
||||
{
|
||||
testcase("OK");
|
||||
{
|
||||
auto s = codeString(Status());
|
||||
expect(s.empty(), "String for OK status");
|
||||
}
|
||||
|
||||
{
|
||||
auto s = codeString(Status::kOK);
|
||||
expect(s.empty(), "String for OK status");
|
||||
}
|
||||
|
||||
{
|
||||
auto s = codeString(0);
|
||||
expect(s.empty(), "String for 0 status");
|
||||
}
|
||||
|
||||
{
|
||||
auto s = codeString(tesSUCCESS);
|
||||
expect(s.empty(), "String for tesSUCCESS");
|
||||
}
|
||||
|
||||
{
|
||||
auto s = codeString(RpcSuccess);
|
||||
expect(s.empty(), "String for rpcSUCCESS");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testError()
|
||||
{
|
||||
testcase("error");
|
||||
{
|
||||
auto s = codeString(23);
|
||||
expect(s == "23", s);
|
||||
}
|
||||
|
||||
{
|
||||
auto s = codeString(temBAD_AMOUNT);
|
||||
expect(s == "temBAD_AMOUNT: Malformed: Bad amount.", s);
|
||||
}
|
||||
|
||||
{
|
||||
auto s = codeString(RpcBadSyntax);
|
||||
expect(s == "badSyntax: Syntax error.", s);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testOk();
|
||||
testError();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(codeString, rpc, RPC);
|
||||
|
||||
class fillJson_test : public beast::unit_test::Suite
|
||||
{
|
||||
private:
|
||||
json::Value value_;
|
||||
|
||||
template <typename Type>
|
||||
void
|
||||
fillJson(Type t)
|
||||
{
|
||||
value_.clear();
|
||||
Status(t).fillJson(value_);
|
||||
}
|
||||
|
||||
void
|
||||
testOk()
|
||||
{
|
||||
testcase("OK");
|
||||
fillJson(Status());
|
||||
expect(!value_, "Value for empty status");
|
||||
|
||||
fillJson(0);
|
||||
expect(!value_, "Value for 0 status");
|
||||
|
||||
fillJson(Status::kOK);
|
||||
expect(!value_, "Value for OK status");
|
||||
|
||||
fillJson(tesSUCCESS);
|
||||
expect(!value_, "Value for tesSUCCESS");
|
||||
|
||||
fillJson(RpcSuccess);
|
||||
expect(!value_, "Value for rpcSUCCESS");
|
||||
}
|
||||
|
||||
template <typename Type>
|
||||
void
|
||||
expectFill(
|
||||
std::string const& label,
|
||||
Type status,
|
||||
Status::Strings messages,
|
||||
std::string const& message)
|
||||
{
|
||||
value_.clear();
|
||||
fillJson(Status(status, messages));
|
||||
|
||||
auto prefix = label + ": ";
|
||||
expect(bool(value_), prefix + "No value");
|
||||
|
||||
auto error = value_[jss::error];
|
||||
expect(bool(error), prefix + "No error.");
|
||||
|
||||
auto code = error[jss::code].asInt();
|
||||
expect(
|
||||
status == code,
|
||||
prefix + "Wrong status " + std::to_string(code) + " != " + std::to_string(status));
|
||||
|
||||
auto m = error[jss::message].asString();
|
||||
expect(m == message, m + " != " + message);
|
||||
|
||||
auto d = error[jss::data];
|
||||
size_t const s1 = d.size();
|
||||
size_t const s2 = messages.size();
|
||||
expect(
|
||||
s1 == s2,
|
||||
prefix + "Data sizes differ " + std::to_string(s1) + " != " + std::to_string(s2));
|
||||
for (auto i = 0; i < std::min(s1, s2); ++i)
|
||||
{
|
||||
auto ds = d[i].asString();
|
||||
expect(ds == messages[i], prefix + ds + " != " + messages[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testError()
|
||||
{
|
||||
testcase("error");
|
||||
expectFill("temBAD_AMOUNT", temBAD_AMOUNT, {}, "temBAD_AMOUNT: Malformed: Bad amount.");
|
||||
|
||||
expectFill(
|
||||
"RpcBadSyntax",
|
||||
RpcBadSyntax,
|
||||
{"An error.", "Another error."},
|
||||
"badSyntax: Syntax error.");
|
||||
|
||||
expectFill("integer message", 23, {"Stuff."}, "23");
|
||||
}
|
||||
|
||||
void
|
||||
testThrow()
|
||||
{
|
||||
testcase("throw");
|
||||
try
|
||||
{
|
||||
Throw<Status>(Status(temBAD_PATH, {"path=sdcdfd"}));
|
||||
}
|
||||
catch (Status const& s)
|
||||
{
|
||||
expect(s.toTER() == temBAD_PATH, "temBAD_PATH wasn't thrown");
|
||||
auto msgs = s.messages();
|
||||
expect(msgs.size() == 1, "Wrong number of messages");
|
||||
expect(msgs[0] == "path=sdcdfd", msgs[0]);
|
||||
}
|
||||
catch (std::exception const&)
|
||||
{
|
||||
expect(false, "Didn't catch a Status");
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testOk();
|
||||
testError();
|
||||
testThrow();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(fillJson, rpc, xrpl);
|
||||
|
||||
} // namespace xrpl::rpc
|
||||
@@ -59,6 +59,19 @@ class TransactionEntry_test : public beast::unit_test::Suite
|
||||
BEAST_EXPECT(result[jss::status] == "error");
|
||||
}
|
||||
|
||||
{
|
||||
// No ledger selects the current one, not any ledger.
|
||||
json::Value params{json::ValueType::Object};
|
||||
params[jss::tx_hash] =
|
||||
"E2FE8D4AF3FCC3944DDF6CD8CDDC5E3F0AD50863EF8919AFEF10CB6408CD4D05";
|
||||
auto const result = env.client().invoke("transaction_entry", params)[jss::result];
|
||||
BEAST_EXPECT(result[jss::error] == "notYetImplemented");
|
||||
BEAST_EXPECT(result[jss::status] == "error");
|
||||
BEAST_EXPECT(result.isMember(jss::ledger_current_index));
|
||||
BEAST_EXPECT(!result.isMember(jss::ledger_hash));
|
||||
BEAST_EXPECT(result[jss::validated] == false);
|
||||
}
|
||||
|
||||
{
|
||||
json::Value params{json::ValueType::Object};
|
||||
params[jss::ledger] = "closed";
|
||||
@@ -69,6 +82,16 @@ class TransactionEntry_test : public beast::unit_test::Suite
|
||||
BEAST_EXPECT(result[jss::status] == "error");
|
||||
}
|
||||
|
||||
for (auto const type : {json::ValueType::Object, json::ValueType::Array})
|
||||
{
|
||||
json::Value params{json::ValueType::Object};
|
||||
params[jss::ledger] = "closed";
|
||||
params[jss::tx_hash] = json::Value{type};
|
||||
auto const result = env.client().invoke("transaction_entry", params)[jss::result];
|
||||
BEAST_EXPECT(result[jss::error] == "malformedRequest");
|
||||
BEAST_EXPECT(result[jss::status] == "error");
|
||||
}
|
||||
|
||||
std::string const txHash{
|
||||
"E2FE8D4AF3FCC3944DDF6CD8CDDC5E3F0AD50863EF8919AFEF10CB6408CD4D05"};
|
||||
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
|
||||
namespace beast {
|
||||
namespace {
|
||||
|
||||
// Counts the messages that actually reach the sink, so the tests can assert on
|
||||
// which severities the journal let through.
|
||||
class CountingSink : public Journal::Sink
|
||||
{
|
||||
std::size_t count_{0};
|
||||
|
||||
public:
|
||||
CountingSink() : Sink(Severity::Warning, false)
|
||||
{
|
||||
}
|
||||
|
||||
[[nodiscard]] std::size_t
|
||||
count() const
|
||||
{
|
||||
return count_;
|
||||
}
|
||||
|
||||
void
|
||||
write(Severity level, std::string const&) override
|
||||
{
|
||||
if (level >= threshold())
|
||||
++count_;
|
||||
}
|
||||
|
||||
void
|
||||
writeAlways(Severity, std::string const&) override
|
||||
{
|
||||
++count_;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(Journal, info_threshold)
|
||||
{
|
||||
CountingSink sink;
|
||||
sink.threshold(Severity::Info);
|
||||
Journal const j(sink);
|
||||
|
||||
j.trace() << " ";
|
||||
EXPECT_EQ(sink.count(), 0uz);
|
||||
j.debug() << " ";
|
||||
EXPECT_EQ(sink.count(), 0uz);
|
||||
j.info() << " ";
|
||||
EXPECT_EQ(sink.count(), 1uz);
|
||||
j.warn() << " ";
|
||||
EXPECT_EQ(sink.count(), 2uz);
|
||||
j.error() << " ";
|
||||
EXPECT_EQ(sink.count(), 3uz);
|
||||
j.fatal() << " ";
|
||||
EXPECT_EQ(sink.count(), 4uz);
|
||||
}
|
||||
|
||||
TEST(Journal, debug_threshold)
|
||||
{
|
||||
CountingSink sink;
|
||||
sink.threshold(Severity::Debug);
|
||||
Journal const j(sink);
|
||||
|
||||
j.trace() << " ";
|
||||
EXPECT_EQ(sink.count(), 0uz);
|
||||
j.debug() << " ";
|
||||
EXPECT_EQ(sink.count(), 1uz);
|
||||
j.info() << " ";
|
||||
EXPECT_EQ(sink.count(), 2uz);
|
||||
j.warn() << " ";
|
||||
EXPECT_EQ(sink.count(), 3uz);
|
||||
j.error() << " ";
|
||||
EXPECT_EQ(sink.count(), 4uz);
|
||||
j.fatal() << " ";
|
||||
EXPECT_EQ(sink.count(), 5uz);
|
||||
}
|
||||
|
||||
// A Journal holds a reference to its sink rather than a copy of the threshold,
|
||||
// so lowering the threshold has to take effect on a Journal that already exists.
|
||||
// The two tests above each build the Journal after setting the threshold, so
|
||||
// neither of them would notice if that stopped working.
|
||||
TEST(Journal, threshold_change_applies_to_an_existing_journal)
|
||||
{
|
||||
CountingSink sink;
|
||||
sink.threshold(Severity::Info);
|
||||
Journal const j(sink);
|
||||
|
||||
j.debug() << " ";
|
||||
EXPECT_EQ(sink.count(), 0uz);
|
||||
|
||||
sink.threshold(Severity::Debug);
|
||||
|
||||
j.debug() << " ";
|
||||
EXPECT_EQ(sink.count(), 1uz);
|
||||
}
|
||||
|
||||
} // namespace beast
|
||||
@@ -0,0 +1,14 @@
|
||||
#include <xrpl/beast/clock/basic_seconds_clock.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace beast {
|
||||
|
||||
// The clock keeps a lazily started background thread; this only asserts that
|
||||
// asking it for the time does not throw or crash.
|
||||
TEST(BasicSecondsClock, now)
|
||||
{
|
||||
EXPECT_NO_THROW({ BasicSecondsClock::now(); });
|
||||
}
|
||||
|
||||
} // namespace beast
|
||||
@@ -0,0 +1,147 @@
|
||||
#include <xrpl/beast/hash/xxhasher.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace beast {
|
||||
namespace {
|
||||
|
||||
constexpr std::string_view kInput{"Hello, xxHash!"};
|
||||
constexpr std::uint32_t kSeed{102};
|
||||
constexpr std::uint32_t kOtherSeed{103};
|
||||
|
||||
// The multiple-update and one-update cases hash the same kRepeatCount copies of
|
||||
// kInput, so their expected digests must agree.
|
||||
constexpr std::size_t kRepeatCount{100};
|
||||
constexpr std::size_t kBigObjectRepeat{20};
|
||||
|
||||
// Returns text repeated the given number of times.
|
||||
//
|
||||
// Used by the cases that hash a large object in one update, so that their
|
||||
// expected digest can be compared against the cases that feed the same bytes
|
||||
// in as many small updates.
|
||||
std::string
|
||||
repeat(std::string_view text, std::size_t times)
|
||||
{
|
||||
std::string out;
|
||||
out.reserve(text.size() * times);
|
||||
for (std::size_t i = 0; i < times; ++i)
|
||||
out += text;
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(XXHasher, without_seed)
|
||||
{
|
||||
Xxhasher hasher{};
|
||||
hasher(kInput.data(), kInput.size());
|
||||
|
||||
EXPECT_EQ(static_cast<Xxhasher::result_type>(hasher), 16042857369214894119ULL);
|
||||
}
|
||||
|
||||
TEST(XXHasher, with_seed)
|
||||
{
|
||||
Xxhasher hasher{kSeed};
|
||||
hasher(kInput.data(), kInput.size());
|
||||
|
||||
EXPECT_EQ(static_cast<Xxhasher::result_type>(hasher), 14440132435660934800ULL);
|
||||
}
|
||||
|
||||
TEST(XXHasher, with_two_seeds)
|
||||
{
|
||||
Xxhasher hasher{kSeed, kOtherSeed};
|
||||
hasher(kInput.data(), kInput.size());
|
||||
|
||||
// The second seed is ignored, so this matches the single-seed result.
|
||||
EXPECT_EQ(static_cast<Xxhasher::result_type>(hasher), 14440132435660934800ULL);
|
||||
}
|
||||
|
||||
// Feeds the bytes in as kRepeatCount small updates, never materializing the full object.
|
||||
// big_object_with_one_update_without_seed below hashes the same bytes in a single
|
||||
// update and must agree, which is what exercises xxHash's internal buffering.
|
||||
TEST(XXHasher, big_object_with_multiple_small_updates_without_seed)
|
||||
{
|
||||
Xxhasher hasher{};
|
||||
for (std::size_t i = 0; i < kRepeatCount; ++i)
|
||||
hasher(kInput.data(), kInput.size());
|
||||
|
||||
EXPECT_EQ(static_cast<Xxhasher::result_type>(hasher), 15296278154063476002ULL);
|
||||
}
|
||||
|
||||
TEST(XXHasher, big_object_with_multiple_small_updates_with_seed)
|
||||
{
|
||||
Xxhasher hasher{kOtherSeed};
|
||||
for (std::size_t i = 0; i < kRepeatCount; ++i)
|
||||
hasher(kInput.data(), kInput.size());
|
||||
|
||||
EXPECT_EQ(static_cast<Xxhasher::result_type>(hasher), 17285302196561698791ULL);
|
||||
}
|
||||
|
||||
TEST(XXHasher, big_object_with_small_and_big_updates_without_seed)
|
||||
{
|
||||
Xxhasher hasher{};
|
||||
std::string const bigObject = repeat(kInput, kBigObjectRepeat);
|
||||
|
||||
hasher(kInput.data(), kInput.size());
|
||||
hasher(bigObject.data(), bigObject.size());
|
||||
hasher(kInput.data(), kInput.size());
|
||||
|
||||
EXPECT_EQ(static_cast<Xxhasher::result_type>(hasher), 1865045178324729219ULL);
|
||||
}
|
||||
|
||||
TEST(XXHasher, big_object_with_small_and_big_updates_with_seed)
|
||||
{
|
||||
Xxhasher hasher{kOtherSeed};
|
||||
std::string const bigObject = repeat(kInput, kBigObjectRepeat);
|
||||
|
||||
hasher(kInput.data(), kInput.size());
|
||||
hasher(bigObject.data(), bigObject.size());
|
||||
hasher(kInput.data(), kInput.size());
|
||||
|
||||
EXPECT_EQ(static_cast<Xxhasher::result_type>(hasher), 16189862915636005281ULL);
|
||||
}
|
||||
|
||||
TEST(XXHasher, big_object_with_one_update_without_seed)
|
||||
{
|
||||
Xxhasher hasher{};
|
||||
std::string const object = repeat(kInput, kRepeatCount);
|
||||
hasher(object.data(), object.size());
|
||||
|
||||
// Hashing the whole object at once must match hashing it in kRepeatCount pieces.
|
||||
EXPECT_EQ(static_cast<Xxhasher::result_type>(hasher), 15296278154063476002ULL);
|
||||
}
|
||||
|
||||
TEST(XXHasher, big_object_with_one_update_with_seed)
|
||||
{
|
||||
Xxhasher hasher{kOtherSeed};
|
||||
std::string const object = repeat(kInput, kRepeatCount);
|
||||
hasher(object.data(), object.size());
|
||||
|
||||
EXPECT_EQ(static_cast<Xxhasher::result_type>(hasher), 17285302196561698791ULL);
|
||||
}
|
||||
|
||||
TEST(XXHasher, operator_result_type_does_not_change_internal_state)
|
||||
{
|
||||
{
|
||||
Xxhasher hasher;
|
||||
std::string const object{"Hello xxhash"};
|
||||
hasher(object.data(), object.size());
|
||||
|
||||
EXPECT_EQ(
|
||||
static_cast<Xxhasher::result_type>(hasher), static_cast<Xxhasher::result_type>(hasher));
|
||||
}
|
||||
{
|
||||
Xxhasher hasher;
|
||||
std::string const object = repeat(kInput, kRepeatCount);
|
||||
hasher(object.data(), object.size());
|
||||
|
||||
EXPECT_EQ(hasher.operator Xxhasher::result_type(), hasher.operator Xxhasher::result_type());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace beast
|
||||
@@ -38,6 +38,7 @@
|
||||
#include <xrpl/ledger/entries/XChainOwnedClaimIDEntry.h> // IWYU pragma: keep
|
||||
#include <xrpl/ledger/entries/XChainOwnedCreateAccountClaimIDEntry.h> // IWYU pragma: keep
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Keylet.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
@@ -55,11 +56,11 @@
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
// The entry classes have no consumers yet, and an un-instantiated class
|
||||
// template is barely type-checked. Instantiate every one explicitly so the
|
||||
// compiler actually checks them. Keep this block even once real call sites
|
||||
// exist: it is what catches a new ledger entry type being added without its
|
||||
// wrapper class, or the wrapper class existing but never actually being used.
|
||||
// An un-instantiated class template is barely type-checked. Name every entry
|
||||
// class in a static_assert below so its class definition is instantiated and
|
||||
// type-checked; the per-type suites check the member bodies. Keep this block
|
||||
// even once real call sites exist: it is what catches a new ledger entry type
|
||||
// being added without its wrapper class.
|
||||
//
|
||||
// Driving this off ledger_entries.macro keeps it exhaustive by construction:
|
||||
// adding a ledger entry type without adding its entry class stops compiling
|
||||
@@ -76,12 +77,22 @@ template class SLEBase<ApplyView>;
|
||||
#pragma push_macro("LEDGER_ENTRY")
|
||||
#undef LEDGER_ENTRY
|
||||
|
||||
// No explicit instantiation here, neither a definition nor a declaration.
|
||||
// A definition would instantiate an entry a second time once its member
|
||||
// bodies live in its own .cpp (MSVC rejects that with C4661 under /WX), and a
|
||||
// declaration (`extern template`) stops clang from emitting the inline
|
||||
// constructors of the header-only entries at -O0, which leaves them undefined
|
||||
// at link time in the debug builds. Naming the class in a static_assert is
|
||||
// enough to instantiate its definition, which is all this exhaustiveness
|
||||
// check needs; the per-type suites exercise the members.
|
||||
#define LEDGER_ENTRY(tag, value, name, ...) \
|
||||
template class name##Entry<ReadView>; \
|
||||
template class name##Entry<ApplyView>; \
|
||||
static_assert( \
|
||||
name##Entry<ReadView>::kEntryType == tag && name##Entry<ApplyView>::kEntryType == tag, \
|
||||
#name "Entry must be bound to " #tag);
|
||||
#name "Entry must be bound to " #tag); \
|
||||
static_assert( \
|
||||
std::is_constructible_v<name##Entry<ReadView>, Keylet const&, ReadView const&> && \
|
||||
std::is_constructible_v<name##Entry<ApplyView>, Keylet const&, ApplyView&>, \
|
||||
#name "Entry must be constructible from a Keylet and a view");
|
||||
|
||||
#include <xrpl/protocol/detail/ledger_entries.macro>
|
||||
|
||||
|
||||
Loaded 100 of 139 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user