mirror of
https://github.com/XRPLF/rippled.git
synced 2026-10-22 11:37:07 +00:00
Compare commits
59
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
66c6f21c60 | ||
|
|
3d526d456e | ||
|
|
f05c9f7913 | ||
|
|
c2a4bc3aa1 | ||
|
|
718185e3b2 | ||
|
|
1d7783bb86 | ||
|
|
9fd2c552f5 | ||
|
|
70b8fd301b | ||
|
|
e6564f553d | ||
|
|
ed96e60ce3 | ||
|
|
2ebd745a1b | ||
|
|
cfcbe45b60 | ||
|
|
9cbf78ba99 | ||
|
|
b8d8738f81 | ||
|
|
63c97e719f | ||
|
|
3ac26f23c7 | ||
|
|
ff9410bc56 | ||
|
|
81e06a9ba8 | ||
|
|
b3141d91fa | ||
|
|
1b1cd4c260 | ||
|
|
a482d6aced | ||
|
|
299627298f | ||
|
|
640798696b | ||
|
|
47512afad5 | ||
|
|
ff3708a757 | ||
|
|
391a1e442c | ||
|
|
9e77212900 | ||
|
|
93f5a0e217 | ||
|
|
71367f361c | ||
|
|
931d21b2a7 | ||
|
|
c99feb82e7 | ||
|
|
7b53a5e0c5 | ||
|
|
4a02518497 | ||
|
|
0b6c3630cc | ||
|
|
4d04ba5be5 | ||
|
|
ed53557d41 | ||
|
|
e4d10393f3 | ||
|
|
fcc7f57e82 | ||
|
|
a25229f154 | ||
|
|
eb6eaf2532 | ||
|
|
ff987fc7c6 | ||
|
|
d21137c4c1 | ||
|
|
ac7db2e621 | ||
|
|
42005a8080 | ||
|
|
1f7b1b3a78 | ||
|
|
80b90544c5 | ||
|
|
00b9a8cd67 | ||
|
|
3be49f814a | ||
|
|
1674fabe81 | ||
|
|
6dfa47ce7a | ||
|
|
bef095be65 | ||
|
|
8e5d774c36 | ||
|
|
fb8fb30f6c | ||
|
|
a553001125 | ||
|
|
57782e84ee | ||
|
|
9d5076c8a9 | ||
|
|
1af379e09f | ||
|
|
1ced0875ae | ||
|
|
53e6d7580a |
No files matched your search
@@ -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"
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"image_tag": "sha-060957e",
|
||||
"image_tag": "sha-ed96e60",
|
||||
"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-ed96e60
|
||||
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-3a2d19f" }'
|
||||
@@ -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-ed96e60
|
||||
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-ed96e60
|
||||
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-ed96e60
|
||||
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-ed96e60
|
||||
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-ed96e60
|
||||
env:
|
||||
REMOTE_NAME: ${{ inputs.remote_name }}
|
||||
CONAN_LOGIN_USERNAME_XRPLF: ${{ secrets.remote_username }}
|
||||
|
||||
@@ -29,12 +29,17 @@ 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
|
||||
|
||||
Version 3.4.0 is not yet released. These changes are available in the 3.4.0 beta releases.
|
||||
|
||||
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 ledger
|
||||
)
|
||||
target_sources(xrpld PRIVATE ${rpcspec_instantiations})
|
||||
|
||||
if(tests)
|
||||
file(
|
||||
GLOB_RECURSE sources
|
||||
|
||||
@@ -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.20#6daa13eeb4e6c92b82ea6d78866e1da8%1790720024.368048",
|
||||
"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.20",
|
||||
"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
|
||||
|
||||
|
||||
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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -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
|
||||
@@ -15,6 +15,7 @@
|
||||
// Add new amendments to the top of this list.
|
||||
// Keep it sorted in reverse chronological order.
|
||||
|
||||
XRPL_FEATURE(DefragDirectories, Supported::No, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(Cosign, Supported::No, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(SmartEscrow, Supported::No, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(LendingProtocolV1_2, Supported::No, VoteBehavior::DefaultNo)
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
#include <xrpl/protocol/AccountID.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 +104,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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
+34
-31
@@ -5,8 +5,8 @@ Core build tools:
|
||||
cmake version 4.4.3
|
||||
/nix/store/q85csxf4s4shx89zif097h1ql4ax1rrz-cmake-4.4.3/bin/cmake
|
||||
✅ conan
|
||||
Conan version 2.32.0
|
||||
/nix/store/921jqsgbilixmr3xchj95si0jl8pi13z-conan-2.32.0/bin/conan
|
||||
Conan version 2.33.0
|
||||
/nix/store/rp0zf22rx3iwnhxp4d53ncirn4lzg68j-conan-2.33.0/bin/conan
|
||||
✅ git
|
||||
git version 2.55.0
|
||||
/nix/store/gw7c7m0dwca5lg4152x9lpmxpj171bzw-git-2.55.0/bin/git
|
||||
@@ -19,17 +19,17 @@ Development tooling:
|
||||
ccache version 4.13.6
|
||||
/nix/store/ydr5nlzxp1djb5256y0zzqa87vz55gsk-ccache-4.13.6/bin/ccache
|
||||
✅ clang
|
||||
clang version 22.1.8
|
||||
/nix/store/dakcxgwz4ixkk6ps50b7vn3dvblyzsnf-clang-wrapper-22.1.8/bin/clang
|
||||
✅ clang-22
|
||||
clang version 22.1.8
|
||||
/nix/store/w826sqb98nnym6kzackg829664iqdlxj-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.8
|
||||
/nix/store/dakcxgwz4ixkk6ps50b7vn3dvblyzsnf-clang-wrapper-22.1.8/bin/clang++
|
||||
✅ clang++-22
|
||||
clang version 22.1.8
|
||||
/nix/store/wnwyw3yfqrhpxij65bl3pa1x65q2zg3m-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/c7jjnw29ra78jdlzjww2m374izhy05n8-clangbuildanalyzer-1.6.0/bin/ClangBuildAnalyzer
|
||||
@@ -39,6 +39,9 @@ Development tooling:
|
||||
✅ 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 710 (PCRE2 regular expressions)
|
||||
/nix/store/3yn3dx5fwg5i0inqqzwnxq7f6lwhpjqa-less-710/bin/less
|
||||
@@ -64,23 +67,23 @@ Development tooling:
|
||||
Zip 3.0
|
||||
/nix/store/dvawr36npnad4xa6dxi2ss0fw44aa82r-zip-3.0/bin/zip
|
||||
✅ clang-apply-replacements
|
||||
clang-apply-replacements version 22.1.8
|
||||
/nix/store/5bij4zn161lagwyndmrr21vmxjg054nv-clang-tools-22.1.8/bin/clang-apply-replacements
|
||||
✅ clang-apply-replacements-22
|
||||
clang-apply-replacements version 22.1.8
|
||||
/nix/store/w4bz034k4l0w719rrmbwnqcgyjbswy81-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.8
|
||||
/nix/store/5bij4zn161lagwyndmrr21vmxjg054nv-clang-tools-22.1.8/bin/clang-format
|
||||
✅ clang-format-22
|
||||
clang-format version 22.1.8
|
||||
/nix/store/w7z346l0r8y36b6lc5i7jzj3w0581l2y-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.8
|
||||
/nix/store/5bij4zn161lagwyndmrr21vmxjg054nv-clang-tools-22.1.8/bin/clang-tidy
|
||||
✅ clang-tidy-22
|
||||
LLVM version 22.1.8
|
||||
/nix/store/0lv25qd3ddgjm3ndn70q61jfr6krlkl7-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 15.1.1 (0)
|
||||
/nix/store/mc99a6bpbk2waym2cnw6ifmxli4ndb4x-graphviz-15.1.1/bin/dot
|
||||
@@ -107,10 +110,10 @@ Development tooling:
|
||||
/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/3xss1mm3mh1hzcmg182na6ki1p5rnxf1-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/w68r9z07hcq4fwbfyq3yvc7qxm8aqfbl-run-clang-tidy-22/bin/run-clang-tidy-22
|
||||
/nix/store/svhfrpgx2j581brsqmkpav9b286v22yy-run-clang-tidy-23/bin/run-clang-tidy-23
|
||||
|
||||
Rust toolchain:
|
||||
✅ cargo
|
||||
@@ -143,4 +146,4 @@ Rust toolchain:
|
||||
|
||||
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.32.0
|
||||
/nix/store/q0g5grbh7zm8gfhhnq5h6fm1rbs9yv3n-conan-2.32.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 version 22.1.8
|
||||
/nix/store/dggxva67vs7virgsmd6xkwd6p7zsc5pn-clang-wrapper-22.1.8/bin/clang
|
||||
✅ clang-22
|
||||
clang version 22.1.7
|
||||
/nix/store/dagc2rq44gfbr7w7yvvqca3yqpc9gqbq-clang-22/bin/clang-22
|
||||
clang version 22.1.8
|
||||
/nix/store/ckz6y10v5dbyswpf9f6hd8kjfv84f920-clang-22/bin/clang-22
|
||||
✅ clang++
|
||||
clang version 22.1.7
|
||||
/nix/store/ff0hrp9r9i3pa5arkdw0sgmzp8d576qi-clang-wrapper-22.1.7/bin/clang++
|
||||
clang version 22.1.8
|
||||
/nix/store/dggxva67vs7virgsmd6xkwd6p7zsc5pn-clang-wrapper-22.1.8/bin/clang++
|
||||
✅ clang++-22
|
||||
clang version 22.1.7
|
||||
/nix/store/l5m8clin1npl605wdkd8mr18ggxww3z4-clang++-22/bin/clang++-22
|
||||
clang version 22.1.8
|
||||
/nix/store/lsishk8fwbdmlwr5fbwyd3yhhjsm67qr-clang++-22/bin/clang++-22
|
||||
✅ 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 version 22.1.8
|
||||
/nix/store/37jhp4j3y339zmkx09qbr3fpfy3q0kl0-clang-tools-22.1.8/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 22.1.8
|
||||
/nix/store/qg0b99zn522gl3vz9hx9ni9ianb11d1r-clang-apply-replacements-22/bin/clang-apply-replacements-22
|
||||
✅ clang-format
|
||||
clang-format version 22.1.7
|
||||
/nix/store/4zp1rjpj2xijrv4kqpwsy3ixwb2r6nlk-clang-tools-22.1.7/bin/clang-format
|
||||
clang-format version 22.1.8
|
||||
/nix/store/37jhp4j3y339zmkx09qbr3fpfy3q0kl0-clang-tools-22.1.8/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 22.1.8
|
||||
/nix/store/m1al4bygpn7i13q1kalavybwh9ldg6rz-clang-format-22/bin/clang-format-22
|
||||
✅ clang-tidy
|
||||
LLVM version 22.1.7
|
||||
/nix/store/4zp1rjpj2xijrv4kqpwsy3ixwb2r6nlk-clang-tools-22.1.7/bin/clang-tidy
|
||||
LLVM version 22.1.8
|
||||
/nix/store/37jhp4j3y339zmkx09qbr3fpfy3q0kl0-clang-tools-22.1.8/bin/clang-tidy
|
||||
✅ clang-tidy-22
|
||||
LLVM version 22.1.7
|
||||
/nix/store/gdrkvpw846lkyzh8y9p3zx50g6ml2v84-clang-tidy-22/bin/clang-tidy-22
|
||||
LLVM version 22.1.8
|
||||
/nix/store/h51xrqf8xgx4zil2rm1ydp909858y4xm-clang-tidy-22/bin/clang-tidy-22
|
||||
✅ 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
|
||||
/nix/store/a760dc8nldq1715410384bkk83idmm00-run-clang-tidy/bin/run-clang-tidy
|
||||
✅ run-clang-tidy-22
|
||||
usage: run-clang-tidy [-h] [-allow-enabling-alpha-checkers]
|
||||
/nix/store/n8yak1ap308gvi7gmrniw0ybsx80fjws-run-clang-tidy-22/bin/run-clang-tidy-22
|
||||
/nix/store/xwh85m9sa5h8fnpnlmqwfnf2sz9l59mm-run-clang-tidy-22/bin/run-clang-tidy-22
|
||||
|
||||
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.32.0
|
||||
/nix/store/vbr044x1zbq062kkw53zm0sm3slv8g56-conan-2.32.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 version 22.1.8
|
||||
/nix/store/7z7by1cxa52mk7hjia1pimjvsd6aizhf-clang-wrapper-22.1.8/bin/clang
|
||||
✅ clang-22
|
||||
clang version 22.1.7
|
||||
/nix/store/vcf6ilfwn57828hwzyp6zlyr24j9j6yw-clang-22/bin/clang-22
|
||||
clang version 22.1.8
|
||||
/nix/store/pqn36q5paasw7v8sjgz5fd90mfpgkkqr-clang-22/bin/clang-22
|
||||
✅ clang++
|
||||
clang version 22.1.7
|
||||
/nix/store/xjqffrq9i7la058s9865ig71l9sp1ys5-clang-wrapper-22.1.7/bin/clang++
|
||||
clang version 22.1.8
|
||||
/nix/store/7z7by1cxa52mk7hjia1pimjvsd6aizhf-clang-wrapper-22.1.8/bin/clang++
|
||||
✅ clang++-22
|
||||
clang version 22.1.7
|
||||
/nix/store/xby0f6gamr7m27zp5cndsvghbp9lgb3c-clang++-22/bin/clang++-22
|
||||
clang version 22.1.8
|
||||
/nix/store/m9a3slb0lcixm2csgjz9s0nvj9m5ysnq-clang++-22/bin/clang++-22
|
||||
✅ 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 version 22.1.8
|
||||
/nix/store/6jr2pbz9mzxznf47lw1w6ss1arqsakw6-clang-tools-22.1.8/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 22.1.8
|
||||
/nix/store/mgcaw2063w2d4s61fz023bd0pf3vq5y7-clang-apply-replacements-22/bin/clang-apply-replacements-22
|
||||
✅ clang-format
|
||||
clang-format version 22.1.7
|
||||
/nix/store/s53p2m776iqaz7acgr5csgpsd18w15h7-clang-tools-22.1.7/bin/clang-format
|
||||
clang-format version 22.1.8
|
||||
/nix/store/6jr2pbz9mzxznf47lw1w6ss1arqsakw6-clang-tools-22.1.8/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 22.1.8
|
||||
/nix/store/2iwvl9784yrjfjhfac46z5ihwmbaavk7-clang-format-22/bin/clang-format-22
|
||||
✅ clang-tidy
|
||||
LLVM version 22.1.7
|
||||
/nix/store/s53p2m776iqaz7acgr5csgpsd18w15h7-clang-tools-22.1.7/bin/clang-tidy
|
||||
LLVM version 22.1.8
|
||||
/nix/store/6jr2pbz9mzxznf47lw1w6ss1arqsakw6-clang-tools-22.1.8/bin/clang-tidy
|
||||
✅ clang-tidy-22
|
||||
LLVM version 22.1.7
|
||||
/nix/store/wdyd6cb9z1lyi37lbzvwldgcc7yv1n5c-clang-tidy-22/bin/clang-tidy-22
|
||||
LLVM version 22.1.8
|
||||
/nix/store/2b7hn8hyp02yfarh8c40c4jsjkm789l0-clang-tidy-22/bin/clang-tidy-22
|
||||
✅ 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
|
||||
/nix/store/b1yccjmij9qiha8a8d9yxayjdh5cg2vq-run-clang-tidy/bin/run-clang-tidy
|
||||
✅ run-clang-tidy-22
|
||||
usage: run-clang-tidy [-h] [-allow-enabling-alpha-checkers]
|
||||
/nix/store/f8m0p9ad40brp9ahy4i0h27kqjkya1j9-run-clang-tidy-22/bin/run-clang-tidy-22
|
||||
/nix/store/v75dyrp7vwdgx5w2fzwsy00lf94qpz0i-run-clang-tidy-22/bin/run-clang-tidy-22
|
||||
|
||||
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.
|
||||
+1
-1
@@ -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; };
|
||||
|
||||
+14
-12
@@ -31,6 +31,9 @@ let
|
||||
};
|
||||
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;
|
||||
|
||||
@@ -86,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 =
|
||||
@@ -110,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;
|
||||
@@ -119,10 +123,10 @@ 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 {
|
||||
@@ -180,8 +184,7 @@ rec {
|
||||
version = gccVersion;
|
||||
versionedTools = gccVersionedTools;
|
||||
extraPackages = [ plainGcov ];
|
||||
warningHook = plainWarningHook;
|
||||
noPatchNixBinary = true;
|
||||
plain = true;
|
||||
};
|
||||
|
||||
clang-plain = makeShell {
|
||||
@@ -190,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
|
||||
|
||||
@@ -26,6 +26,14 @@ namespace xrpl {
|
||||
|
||||
namespace directory {
|
||||
|
||||
struct Gap
|
||||
{
|
||||
uint64_t const page;
|
||||
SLE::pointer node;
|
||||
uint64_t const nextPage;
|
||||
SLE::pointer next;
|
||||
};
|
||||
|
||||
std::uint64_t
|
||||
createRoot(
|
||||
ApplyView& view,
|
||||
@@ -126,7 +134,9 @@ insertPage(
|
||||
if (page == 0)
|
||||
return std::nullopt;
|
||||
if (!view.rules().enabled(fixDirectoryLimit) && page >= kDirNodeMaxPages) // Old pages limit
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// We are about to create a new node; we'll link it to
|
||||
// the chain first:
|
||||
@@ -147,12 +157,8 @@ insertPage(
|
||||
// Save some space by not specifying the value 0 since it's the default.
|
||||
if (page != 1)
|
||||
node->setFieldU64(sfIndexPrevious, page - 1);
|
||||
XRPL_ASSERT_PARTS(!nextPage, "xrpl::directory::insertPage", "nextPage has default value");
|
||||
/* Reserved for future use when directory pages may be inserted in
|
||||
* between two other pages instead of only at the end of the chain.
|
||||
if (nextPage)
|
||||
node->setFieldU64(sfIndexNext, nextPage);
|
||||
*/
|
||||
describe(node);
|
||||
view.insert(node);
|
||||
|
||||
@@ -168,7 +174,7 @@ ApplyView::dirAdd(
|
||||
UInt256 const& key,
|
||||
std::function<void(SLE::Ref)> const& describe)
|
||||
{
|
||||
auto root = peek(directory);
|
||||
auto const root = peek(directory);
|
||||
|
||||
if (!root)
|
||||
{
|
||||
@@ -178,6 +184,43 @@ ApplyView::dirAdd(
|
||||
|
||||
auto [page, node, indexes] = directory::findPreviousPage(*this, directory, root);
|
||||
|
||||
if (rules().enabled(featureDefragDirectories))
|
||||
{
|
||||
// If there are more nodes than just the root, and there's no space in
|
||||
// the last one, walk backwards to find one with space, or to find one
|
||||
// missing.
|
||||
std::optional<directory::Gap> gapPages;
|
||||
while (page && indexes.size() >= kDIR_NODE_MAX_PAGES)
|
||||
{
|
||||
// Find a page with space, or a gap in pages.
|
||||
auto [prevPage, prevNode, prevIndexes] =
|
||||
directory::findPreviousPage(*this, directory, node);
|
||||
if (!gapPages && prevPage != page - 1)
|
||||
gapPages.emplace(prevPage, prevNode, page, node);
|
||||
page = prevPage;
|
||||
node = prevNode;
|
||||
indexes = prevIndexes;
|
||||
}
|
||||
// We looped through all the pages back to the root.
|
||||
if (!page)
|
||||
{
|
||||
// If we found a gap, use it.
|
||||
if (gapPages)
|
||||
{
|
||||
return directory::insertPage(
|
||||
*this,
|
||||
gapPages->page,
|
||||
gapPages->node,
|
||||
gapPages->nextPage,
|
||||
gapPages->next,
|
||||
key,
|
||||
directory,
|
||||
describe);
|
||||
}
|
||||
std::tie(page, node, indexes) = directory::findPreviousPage(*this, directory, root);
|
||||
}
|
||||
}
|
||||
|
||||
// If there's space, we use it:
|
||||
if (indexes.size() < kDirNodeMaxEntries)
|
||||
{
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#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>
|
||||
@@ -640,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)
|
||||
@@ -664,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);
|
||||
@@ -678,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);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#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>
|
||||
@@ -309,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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,15 +6,10 @@
|
||||
#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>
|
||||
@@ -35,60 +30,6 @@ 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)
|
||||
{
|
||||
@@ -106,7 +47,7 @@ EscrowCancel::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
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))
|
||||
|
||||
@@ -10,8 +10,7 @@
|
||||
#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>
|
||||
@@ -19,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>
|
||||
@@ -185,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)
|
||||
{
|
||||
@@ -366,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))
|
||||
@@ -375,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()
|
||||
{
|
||||
|
||||
@@ -13,15 +13,10 @@
|
||||
#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>
|
||||
@@ -137,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)
|
||||
{
|
||||
@@ -223,7 +156,7 @@ EscrowFinish::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
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))
|
||||
|
||||
@@ -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,9 +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>
|
||||
@@ -26,7 +27,6 @@
|
||||
#include <xrpl/tx/Transactor.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
@@ -169,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);
|
||||
@@ -253,7 +251,7 @@ Change::applyAmendment()
|
||||
amendmentObject->setFieldArray(sfMajorities, newMajorities);
|
||||
}
|
||||
|
||||
view().update(amendmentObject);
|
||||
amendmentObject.update();
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
@@ -331,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 = [&] {
|
||||
@@ -409,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>
|
||||
|
||||
+66
-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>
|
||||
@@ -69,7 +68,6 @@
|
||||
#include <xrpl/tx/transactors/payment/Payment.h>
|
||||
#include <xrpl/tx/transactors/system/Batch.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <exception>
|
||||
@@ -120,15 +118,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 +133,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 +3136,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 +3285,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 +3309,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)
|
||||
{
|
||||
@@ -5915,6 +5959,7 @@ class Batch_test : public beast::unit_test::Suite
|
||||
testCheckAllSignatures(features);
|
||||
testAccountSet(features);
|
||||
testAccountDelete(features);
|
||||
testLendingAmendment(features);
|
||||
testLoan(features);
|
||||
testObjectCreateSequence(features);
|
||||
testObjectCreateTicket(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)
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
+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
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -311,6 +311,20 @@ TEST(SpecBridge, shared_spec_warns_on_a_deprecated_field)
|
||||
EXPECT_EQ(warnings[0].code, ::rpc::WarningCode::WarnRpcDeprecated);
|
||||
}
|
||||
|
||||
TEST(SpecBridge, inject_spec_error_uses_default_message)
|
||||
{
|
||||
json::Value out{json::ValueType::Object};
|
||||
rpc::injectSpecError(out, ::rpc::Status{RpcLgrNotFound});
|
||||
EXPECT_EQ(out, rpc::makeError(RpcLgrNotFound));
|
||||
}
|
||||
|
||||
TEST(SpecBridge, inject_spec_error_uses_custom_message)
|
||||
{
|
||||
json::Value out{json::ValueType::Object};
|
||||
rpc::injectSpecError(out, ::rpc::Status{RpcInvalidParams, "Invalid field 'ledger'."});
|
||||
EXPECT_EQ(out, rpc::makeError(RpcInvalidParams, "Invalid field 'ledger'."));
|
||||
}
|
||||
|
||||
TEST(SpecBridge, inject_spec_warnings)
|
||||
{
|
||||
std::string const kDeprecatedBase{
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <xrpld/rpc/MethodNames.h>
|
||||
#include <xrpld/rpc/Role.h>
|
||||
#include <xrpld/rpc/Status.h>
|
||||
#include <xrpld/rpc/detail/SpecBridge.hpp>
|
||||
#include <xrpld/rpc/handlers/Handlers.h>
|
||||
#include <xrpld/rpc/handlers/ledger/Ledger.h>
|
||||
#include <xrpld/rpc/handlers/server_info/Version.h>
|
||||
@@ -12,10 +13,16 @@
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/protocol/ApiVersion.h>
|
||||
#include <xrpl/protocol/ErrorCodes.h>
|
||||
|
||||
#include <rpcspec/Errors.hpp>
|
||||
#include <rpcspec/Types.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <concepts>
|
||||
#include <cstddef>
|
||||
#include <expected>
|
||||
#include <iterator>
|
||||
#include <span>
|
||||
#include <string_view>
|
||||
@@ -49,7 +56,34 @@ byRef(JsonContext& context, json::Value& result)
|
||||
return Status();
|
||||
}
|
||||
|
||||
template <typename HandlerImpl>
|
||||
concept SpecBasedHandler = std::derived_from<HandlerImpl, HandlerFor<typename HandlerImpl::Input>>;
|
||||
|
||||
template <typename HandlerImpl>
|
||||
concept InputlessHandler = requires(HandlerImpl& handler) { handler.process(); };
|
||||
|
||||
template <class HandlerImpl, class Output>
|
||||
Status
|
||||
respond(
|
||||
HandlerImpl const& handler,
|
||||
json::Value& object,
|
||||
std::expected<Output, ::rpc::Status> const& output,
|
||||
::rpc::spec::Warnings const& warnings)
|
||||
{
|
||||
if (not output.has_value())
|
||||
{
|
||||
injectSpecError(object, output.error());
|
||||
return std::get<ErrorCodeI>(output.error().code);
|
||||
}
|
||||
|
||||
handler.writeResult(object, *output);
|
||||
injectSpecWarnings(object, warnings);
|
||||
|
||||
return Status::kOK;
|
||||
}
|
||||
|
||||
template <class HandlerImpl>
|
||||
requires SpecBasedHandler<HandlerImpl> or InputlessHandler<HandlerImpl>
|
||||
Status
|
||||
handle(JsonContext& context, json::Value& object)
|
||||
{
|
||||
@@ -57,18 +91,28 @@ handle(JsonContext& context, json::Value& object)
|
||||
context.apiVersion >= HandlerImpl::minApiVer &&
|
||||
context.apiVersion <= HandlerImpl::maxApiVer,
|
||||
"xrpl::rpc::handle : valid API version");
|
||||
HandlerImpl handler(context);
|
||||
|
||||
auto status = handler.check();
|
||||
if (status)
|
||||
if constexpr (SpecBasedHandler<HandlerImpl>)
|
||||
{
|
||||
status.inject(object);
|
||||
auto const input = HandlerImpl::parseInput(context.params, context.apiVersion);
|
||||
if (not input.has_value())
|
||||
{
|
||||
injectSpecError(object, input.error());
|
||||
return std::get<ErrorCodeI>(input.error().code);
|
||||
}
|
||||
|
||||
HandlerImpl handler(context);
|
||||
auto const output = handler.process(*input);
|
||||
auto const warnings = output.has_value()
|
||||
? HandlerImpl::spec(context.apiVersion).check(context.params)
|
||||
: ::rpc::spec::Warnings{};
|
||||
return respond(handler, object, output, warnings);
|
||||
}
|
||||
else
|
||||
{
|
||||
handler.writeResult(object);
|
||||
HandlerImpl handler(context);
|
||||
return respond(handler, object, handler.process(), /* warnings= */ {});
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
template <typename HandlerImpl>
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/protocol/ErrorCodes.h>
|
||||
#include <xrpl/protocol/LedgerShortcut.h>
|
||||
#include <xrpl/protocol/RPCErr.h>
|
||||
#include <xrpl/protocol/RippleLedgerHash.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
@@ -25,15 +24,28 @@
|
||||
#include <org/xrpl/rpc/v1/get_ledger_data.pb.h> // IWYU pragma: keep
|
||||
#include <org/xrpl/rpc/v1/get_ledger_entry.pb.h> // IWYU pragma: keep
|
||||
#include <org/xrpl/rpc/v1/ledger.pb.h>
|
||||
#include <rpcspec/Ledger.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
|
||||
namespace xrpl::rpc {
|
||||
|
||||
namespace {
|
||||
|
||||
template <typename... Ts>
|
||||
struct Overload : Ts...
|
||||
{
|
||||
using Ts::operator()...;
|
||||
|
||||
constexpr Overload(Ts&&... ts) : Ts(std::forward<Ts>(ts))...
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
bool
|
||||
isValidatedOld(LedgerMaster& ledgerMaster, bool standalone)
|
||||
{
|
||||
@@ -68,13 +80,13 @@ ledgerFromIndex(
|
||||
auto const index = indexValue.asString();
|
||||
|
||||
if (index == "current" || index.empty())
|
||||
return getLedger(ledger, LedgerShortcut::Current, context);
|
||||
return getLedger(ledger, ::rpc::spec::LedgerShortcut::Current, context);
|
||||
|
||||
if (index == "validated")
|
||||
return getLedger(ledger, LedgerShortcut::Validated, context);
|
||||
return getLedger(ledger, ::rpc::spec::LedgerShortcut::Validated, context);
|
||||
|
||||
if (index == "closed")
|
||||
return getLedger(ledger, LedgerShortcut::Closed, context);
|
||||
return getLedger(ledger, ::rpc::spec::LedgerShortcut::Closed, context);
|
||||
|
||||
std::uint32_t iVal = 0;
|
||||
if (!beast::lexicalCastChecked(iVal, index))
|
||||
@@ -146,7 +158,7 @@ ledgerFromRequest(T& ledger, JsonContext const& context)
|
||||
}
|
||||
|
||||
// nothing specified, `index` has a default setting
|
||||
return getLedger(ledger, LedgerShortcut::Current, context);
|
||||
return getLedger(ledger, ::rpc::spec::LedgerShortcut::Current, context);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
@@ -204,17 +216,17 @@ ledgerFromSpecifier(
|
||||
auto const shortcut = specifier.shortcut();
|
||||
if (shortcut == org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_VALIDATED)
|
||||
{
|
||||
return getLedger(ledger, LedgerShortcut::Validated, context);
|
||||
return getLedger(ledger, ::rpc::spec::LedgerShortcut::Validated, context);
|
||||
}
|
||||
|
||||
if (shortcut == org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_CURRENT ||
|
||||
shortcut == org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_UNSPECIFIED)
|
||||
{
|
||||
return getLedger(ledger, LedgerShortcut::Current, context);
|
||||
return getLedger(ledger, ::rpc::spec::LedgerShortcut::Current, context);
|
||||
}
|
||||
if (shortcut == org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_CLOSED)
|
||||
{
|
||||
return getLedger(ledger, LedgerShortcut::Closed, context);
|
||||
return getLedger(ledger, ::rpc::spec::LedgerShortcut::Closed, context);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -263,7 +275,7 @@ getLedger(T& ledger, uint32_t ledgerIndex, Context const& context)
|
||||
|
||||
template <class T>
|
||||
Status
|
||||
getLedger(T& ledger, LedgerShortcut shortcut, Context const& context)
|
||||
getLedger(T& ledger, ::rpc::spec::LedgerShortcut shortcut, Context const& context)
|
||||
{
|
||||
if (isValidatedOld(context.ledgerMaster, context.app.config().standalone()))
|
||||
{
|
||||
@@ -272,7 +284,7 @@ getLedger(T& ledger, LedgerShortcut shortcut, Context const& context)
|
||||
return {RpcNotSynced, "notSynced"};
|
||||
}
|
||||
|
||||
if (shortcut == LedgerShortcut::Validated)
|
||||
if (shortcut == ::rpc::spec::LedgerShortcut::Validated)
|
||||
{
|
||||
ledger = context.ledgerMaster.getValidatedLedger();
|
||||
if (ledger == nullptr)
|
||||
@@ -286,12 +298,12 @@ getLedger(T& ledger, LedgerShortcut shortcut, Context const& context)
|
||||
}
|
||||
else
|
||||
{
|
||||
if (shortcut == LedgerShortcut::Current)
|
||||
if (shortcut == ::rpc::spec::LedgerShortcut::Current)
|
||||
{
|
||||
ledger = context.ledgerMaster.getCurrentLedger();
|
||||
XRPL_ASSERT(ledger->open(), "xrpl::rpc::getLedger : current is open");
|
||||
}
|
||||
else if (shortcut == LedgerShortcut::Closed)
|
||||
else if (shortcut == ::rpc::spec::LedgerShortcut::Closed)
|
||||
{
|
||||
ledger = context.ledgerMaster.getClosedLedger();
|
||||
XRPL_ASSERT(!ledger->open(), "xrpl::rpc::getLedger : closed is not open");
|
||||
@@ -321,16 +333,33 @@ getLedger(T& ledger, LedgerShortcut shortcut, Context const& context)
|
||||
return Status::kOK;
|
||||
}
|
||||
|
||||
// Explicit instantiation of above three functions
|
||||
template <class T>
|
||||
Status
|
||||
getLedger(T& ledger, ::rpc::spec::LedgerSpecifier const& specifier, Context const& context)
|
||||
{
|
||||
return std::visit(
|
||||
Overload{
|
||||
[&](std::monostate) {
|
||||
return getLedger(ledger, ::rpc::spec::kDefaultLedgerShortcut, context);
|
||||
},
|
||||
[&](auto const& value) { return getLedger(ledger, value, context); },
|
||||
},
|
||||
specifier.value);
|
||||
}
|
||||
|
||||
// Explicit instantiation of above four functions
|
||||
template Status
|
||||
getLedger<>(std::shared_ptr<ReadView const>&, uint32_t, Context const&);
|
||||
|
||||
template Status
|
||||
getLedger<>(std::shared_ptr<ReadView const>&, LedgerShortcut shortcut, Context const&);
|
||||
getLedger<>(std::shared_ptr<ReadView const>&, ::rpc::spec::LedgerShortcut shortcut, Context const&);
|
||||
|
||||
template Status
|
||||
getLedger<>(std::shared_ptr<ReadView const>&, UInt256 const&, Context const&);
|
||||
|
||||
template Status
|
||||
getLedger<>(std::shared_ptr<ReadView const>&, ::rpc::spec::LedgerSpecifier const&, Context const&);
|
||||
|
||||
// explicit instantiation of ledgerFromSpecifier
|
||||
template Status
|
||||
ledgerFromSpecifier<>(
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/ledger/Ledger.h>
|
||||
#include <xrpl/protocol/LedgerShortcut.h>
|
||||
#include <xrpl/server/NetworkOPs.h>
|
||||
|
||||
#include <org/xrpl/rpc/v1/ledger.pb.h>
|
||||
#include <rpcspec/Ledger.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
@@ -76,7 +76,22 @@ getLedger(T& ledger, uint32_t ledgerIndex, Context const& context);
|
||||
*/
|
||||
template <class T>
|
||||
Status
|
||||
getLedger(T& ledger, LedgerShortcut shortcut, Context const& context);
|
||||
getLedger(T& ledger, ::rpc::spec::LedgerShortcut shortcut, Context const& context);
|
||||
|
||||
/**
|
||||
* @brief Retrieves the ledger a spec-parsed request selects.
|
||||
*
|
||||
* An unspecified selection means the current ledger.
|
||||
*
|
||||
* @tparam T Type of the ledger pointer to be filled.
|
||||
* @param ledger Reference to the ledger pointer to be filled.
|
||||
* @param specifier The ledger the request selects.
|
||||
* @param context The RPC context.
|
||||
* @return Status indicating success or failure of the operation.
|
||||
*/
|
||||
template <class T>
|
||||
Status
|
||||
getLedger(T& ledger, ::rpc::spec::LedgerSpecifier const& specifier, Context const& context);
|
||||
|
||||
/**
|
||||
* @brief Looks up a ledger from a request and returns a json::Value with either
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpld/rpc/detail/JsonObjectView.hpp>
|
||||
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/protocol/ErrorCodes.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
|
||||
#include <rpcspec/Errors.hpp>
|
||||
#include <rpcspec/HandlerFor.hpp>
|
||||
#include <rpcspec/Types.hpp>
|
||||
|
||||
#include <map>
|
||||
@@ -12,6 +16,22 @@
|
||||
|
||||
namespace xrpl::rpc {
|
||||
|
||||
template <typename InputT>
|
||||
using HandlerFor = ::rpc::spec::HandlerFor<InputT, json::Value>;
|
||||
|
||||
inline void
|
||||
injectSpecError(json::Value& object, ::rpc::Status const& status)
|
||||
{
|
||||
if (auto const code = std::get<ErrorCodeI>(status.code); status.message.empty())
|
||||
{
|
||||
injectError(code, object);
|
||||
}
|
||||
else
|
||||
{
|
||||
injectError(code, status.message, object);
|
||||
}
|
||||
}
|
||||
|
||||
// Warnings are grouped by code into one entry each: the code's standard message followed by
|
||||
// every per-field detail, space separated.
|
||||
//
|
||||
|
||||
@@ -20,13 +20,14 @@
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/ErrorCodes.h>
|
||||
#include <xrpl/protocol/LedgerShortcut.h>
|
||||
#include <xrpl/protocol/RPCErr.h>
|
||||
#include <xrpl/protocol/RippleLedgerHash.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
#include <xrpl/rdb/RelationalDatabase.h>
|
||||
#include <xrpl/resource/Fees.h>
|
||||
|
||||
#include <rpcspec/Ledger.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <memory>
|
||||
@@ -82,9 +83,24 @@ parseDelegateFilter(json::Value const& delegateNode)
|
||||
using TxnsData = RelationalDatabase::AccountTxs;
|
||||
using TxnsDataBinary = RelationalDatabase::MetaTxsList;
|
||||
using TxnDataBinary = RelationalDatabase::TxnMetaLedgerType;
|
||||
using AccountTxArgs = RelationalDatabase::AccountTxArgs;
|
||||
using AccountTxResult = RelationalDatabase::AccountTxResult;
|
||||
using LedgerSpecifier = RelationalDatabase::LedgerSpecifier;
|
||||
|
||||
// Moved here from RelationalDatabase, where nothing but this handler used them, so that
|
||||
// libxrpl does not depend on the spec for LedgerShortcut. Temporary: the spec's
|
||||
// account_tx Input replaces both once account_tx is migrated to the spec.
|
||||
using LedgerSpecifier =
|
||||
std::variant<LedgerRange, ::rpc::spec::LedgerShortcut, std::uint32_t, uint256>;
|
||||
|
||||
struct AccountTxArgs
|
||||
{
|
||||
AccountID account;
|
||||
std::optional<LedgerSpecifier> ledger;
|
||||
bool binary = false;
|
||||
bool forward = false;
|
||||
uint32_t limit = 0;
|
||||
std::optional<RelationalDatabase::AccountTxMarker> marker;
|
||||
std::optional<DelegateFilter> delegate;
|
||||
};
|
||||
|
||||
// parses args into a ledger specifier, or returns a Json object on error
|
||||
std::variant<std::optional<LedgerSpecifier>, json::Value>
|
||||
@@ -148,15 +164,15 @@ parseLedgerArgs(rpc::Context& context, json::Value const& params)
|
||||
|
||||
if (ledgerStr == "current" || ledgerStr.empty())
|
||||
{
|
||||
ledger = LedgerShortcut::Current;
|
||||
ledger = ::rpc::spec::LedgerShortcut::Current;
|
||||
}
|
||||
else if (ledgerStr == "closed")
|
||||
{
|
||||
ledger = LedgerShortcut::Closed;
|
||||
ledger = ::rpc::spec::LedgerShortcut::Closed;
|
||||
}
|
||||
else if (ledgerStr == "validated")
|
||||
{
|
||||
ledger = LedgerShortcut::Validated;
|
||||
ledger = ::rpc::spec::LedgerShortcut::Validated;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <xrpld/rpc/detail/RPCLedgerHelpers.h>
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/protocol/ErrorCodes.h>
|
||||
@@ -19,11 +20,11 @@
|
||||
#include <xrpl/protocol/Serializer.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
#include <xrpl/resource/Fees.h>
|
||||
#include <xrpl/server/LoadFeeTrack.h>
|
||||
#include <xrpl/shamap/SHAMap.h>
|
||||
|
||||
#include <grpcpp/support/status.h>
|
||||
#include <org/xrpl/rpc/v1/get_ledger.pb.h>
|
||||
#include <rpcspec/Errors.hpp>
|
||||
|
||||
#include <chrono>
|
||||
#include <exception>
|
||||
@@ -40,126 +41,83 @@ LedgerHandler::LedgerHandler(JsonContext& context) : context_(context)
|
||||
{
|
||||
}
|
||||
|
||||
Status
|
||||
LedgerHandler::check()
|
||||
std::expected<LedgerHandler::Output, ::rpc::Status>
|
||||
LedgerHandler::process(Input const& input)
|
||||
{
|
||||
auto const& params = context_.params;
|
||||
Output output;
|
||||
output.options = (input.full ? static_cast<int>(LedgerFill::Options::Full) : 0) |
|
||||
(input.expand ? static_cast<int>(LedgerFill::Options::Expand) : 0) |
|
||||
(input.transactions ? static_cast<int>(LedgerFill::Options::DumpTxrp) : 0) |
|
||||
(input.accounts ? static_cast<int>(LedgerFill::Options::DumpState) : 0) |
|
||||
(input.binary ? static_cast<int>(LedgerFill::Options::Binary) : 0) |
|
||||
(input.ownerFunds ? static_cast<int>(LedgerFill::Options::OwnerFunds) : 0) |
|
||||
(input.queue ? static_cast<int>(LedgerFill::Options::DumpQueue) : 0);
|
||||
|
||||
auto getBool = [&](json::StaticString const& field) -> std::expected<bool, Status> {
|
||||
if (!params.isMember(field))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!params[field].isBool())
|
||||
{
|
||||
return std::unexpected(RpcInvalidParams);
|
||||
}
|
||||
if (input.ledger.isUnspecified())
|
||||
return output;
|
||||
|
||||
return params[field].asBool();
|
||||
};
|
||||
if (auto const status = getLedger(output.ledger, input.ledger, context_.get()))
|
||||
return std::unexpected{::rpc::Status{status.toErrorCode(), status.message()}};
|
||||
|
||||
auto const full = getBool(jss::full);
|
||||
auto const transactions = getBool(jss::transactions);
|
||||
auto const accounts = getBool(jss::accounts);
|
||||
auto const expand = getBool(jss::expand);
|
||||
auto const binary = getBool(jss::binary);
|
||||
auto const ownerFunds = getBool(jss::owner_funds);
|
||||
auto const queue = getBool(jss::queue);
|
||||
|
||||
if (!full.has_value())
|
||||
return full.error();
|
||||
if (!transactions.has_value())
|
||||
return transactions.error();
|
||||
if (!accounts.has_value())
|
||||
return accounts.error();
|
||||
if (!expand.has_value())
|
||||
return expand.error();
|
||||
if (!binary.has_value())
|
||||
return binary.error();
|
||||
if (!ownerFunds.has_value())
|
||||
return ownerFunds.error();
|
||||
if (!queue.has_value())
|
||||
return queue.error();
|
||||
|
||||
options_ = (*full ? static_cast<int>(LedgerFill::Options::Full) : 0) |
|
||||
(*expand ? static_cast<int>(LedgerFill::Options::Expand) : 0) |
|
||||
(*transactions ? static_cast<int>(LedgerFill::Options::DumpTxrp) : 0) |
|
||||
(*accounts ? static_cast<int>(LedgerFill::Options::DumpState) : 0) |
|
||||
(*binary ? static_cast<int>(LedgerFill::Options::Binary) : 0) |
|
||||
(*ownerFunds ? static_cast<int>(LedgerFill::Options::OwnerFunds) : 0) |
|
||||
(*queue ? static_cast<int>(LedgerFill::Options::DumpQueue) : 0);
|
||||
|
||||
bool const needsLedger = params.isMember(jss::ledger) || params.isMember(jss::ledger_hash) ||
|
||||
params.isMember(jss::ledger_index);
|
||||
if (!needsLedger)
|
||||
return Status::kOK;
|
||||
if (auto s = lookupLedger(ledger_, context_, result_))
|
||||
return s;
|
||||
|
||||
if (*full || *accounts)
|
||||
if (input.full || input.accounts)
|
||||
{
|
||||
// Until some sane way to get full ledgers has been implemented,
|
||||
// disallow retrieving all state nodes.
|
||||
if (!isUnlimited(context_.role))
|
||||
return RpcNoPermission;
|
||||
if (!isUnlimited(context_.get().role))
|
||||
return std::unexpected{::rpc::Status{RpcNoPermission}};
|
||||
|
||||
if (context_.app.getFeeTrack().isLoadedLocal() && !isUnlimited(context_.role))
|
||||
{
|
||||
return RpcTooBusy;
|
||||
}
|
||||
context_.loadType = binary ? resource::kFeeMediumBurdenRpc : resource::kFeeHeavyBurdenRpc;
|
||||
context_.get().loadType =
|
||||
input.binary ? resource::kFeeMediumBurdenRpc : resource::kFeeHeavyBurdenRpc;
|
||||
}
|
||||
|
||||
if (*queue)
|
||||
if (input.queue)
|
||||
{
|
||||
if (!ledger_ || !ledger_->open())
|
||||
if (!output.ledger || !output.ledger->open())
|
||||
{
|
||||
// It doesn't make sense to request the queue
|
||||
// with a non-existent or closed/validated ledger.
|
||||
return RpcInvalidParams;
|
||||
return std::unexpected{::rpc::Status{RpcInvalidParams}};
|
||||
}
|
||||
|
||||
queueTxs_ = context_.app.getTxQ().getTxs();
|
||||
output.queueTxs = context_.get().app.getTxQ().getTxs();
|
||||
}
|
||||
|
||||
return Status::kOK;
|
||||
return output;
|
||||
}
|
||||
|
||||
void
|
||||
LedgerHandler::writeResult(json::Value& value)
|
||||
LedgerHandler::writeResult(json::Value& value, Output const& output) const
|
||||
{
|
||||
if (ledger_)
|
||||
if (output.ledger)
|
||||
{
|
||||
copyFrom(value, result_);
|
||||
addJson(value, {*ledger_, &context_, options_, queueTxs_});
|
||||
auto const& header = output.ledger->header();
|
||||
|
||||
if (output.ledger->open())
|
||||
{
|
||||
value[jss::ledger_current_index] = header.seq;
|
||||
}
|
||||
else
|
||||
{
|
||||
value[jss::ledger_hash] = to_string(header.hash);
|
||||
value[jss::ledger_index] = header.seq;
|
||||
}
|
||||
|
||||
value[jss::validated] = context_.get().ledgerMaster.isValidated(*output.ledger);
|
||||
|
||||
addJson(value, {*output.ledger, &context_.get(), output.options, output.queueTxs});
|
||||
}
|
||||
else
|
||||
{
|
||||
auto& master = context_.app.getLedgerMaster();
|
||||
auto& master = context_.get().app.getLedgerMaster();
|
||||
{
|
||||
auto& closed = value[jss::closed] = json::ValueType::Object;
|
||||
addJson(closed, {*master.getClosedLedger(), &context_, 0});
|
||||
addJson(closed, {*master.getClosedLedger(), &context_.get(), 0});
|
||||
}
|
||||
{
|
||||
auto& open = value[jss::open] = json::ValueType::Object;
|
||||
addJson(open, {*master.getCurrentLedger(), &context_, 0});
|
||||
addJson(open, {*master.getCurrentLedger(), &context_.get(), 0});
|
||||
}
|
||||
}
|
||||
|
||||
json::Value warnings{json::ValueType::Array};
|
||||
if (context_.params.isMember(jss::type))
|
||||
{
|
||||
json::Value& w = warnings.append(json::ValueType::Object);
|
||||
w[jss::id] = WarnRpcFieldsDeprecated;
|
||||
w[jss::message] =
|
||||
"Some fields from your request are deprecated. Please check the "
|
||||
"documentation at "
|
||||
"https://xrpl.org/docs/references/http-websocket-apis/ "
|
||||
"and update your request. Field `type` is deprecated.";
|
||||
}
|
||||
|
||||
if (warnings.size() != 0u)
|
||||
value[jss::warnings] = std::move(warnings);
|
||||
}
|
||||
|
||||
} // namespace rpc
|
||||
|
||||
@@ -5,13 +5,18 @@
|
||||
#include <xrpld/rpc/Context.h>
|
||||
#include <xrpld/rpc/MethodNames.h>
|
||||
#include <xrpld/rpc/Role.h>
|
||||
#include <xrpld/rpc/Status.h>
|
||||
#include <xrpld/rpc/detail/Handler.h>
|
||||
#include <xrpld/rpc/detail/SpecBridge.hpp>
|
||||
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/ApiVersion.h>
|
||||
|
||||
#include <rpcspec/Errors.hpp>
|
||||
#include <rpcspec/handlers/ledger/Types.hpp>
|
||||
|
||||
#include <expected>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
@@ -26,16 +31,23 @@ struct JsonContext;
|
||||
// full: true | false // optional, defaults to false.
|
||||
// }
|
||||
|
||||
class LedgerHandler
|
||||
class LedgerHandler : public HandlerFor<::rpc::spec::handlers::ledger::Input>
|
||||
{
|
||||
public:
|
||||
struct Output
|
||||
{
|
||||
std::shared_ptr<ReadView const> ledger;
|
||||
std::vector<TxQ::TxDetails> queueTxs;
|
||||
int options = 0;
|
||||
};
|
||||
|
||||
explicit LedgerHandler(JsonContext&);
|
||||
|
||||
Status
|
||||
check();
|
||||
[[nodiscard]] std::expected<Output, ::rpc::Status>
|
||||
process(Input const& input);
|
||||
|
||||
void
|
||||
writeResult(json::Value&);
|
||||
writeResult(json::Value& value, Output const& output) const;
|
||||
|
||||
// NOLINTBEGIN(readability-identifier-naming)
|
||||
static constexpr std::string_view name = method::kLedger;
|
||||
@@ -50,11 +62,7 @@ public:
|
||||
// NOLINTEND(readability-identifier-naming)
|
||||
|
||||
private:
|
||||
JsonContext& context_;
|
||||
std::shared_ptr<ReadView const> ledger_;
|
||||
std::vector<TxQ::TxDetails> queueTxs_;
|
||||
json::Value result_;
|
||||
int options_ = 0;
|
||||
std::reference_wrapper<JsonContext> context_;
|
||||
};
|
||||
|
||||
} // namespace xrpl::rpc
|
||||
@@ -180,7 +180,23 @@ parseCheck(
|
||||
json::StaticString const fieldName,
|
||||
[[maybe_unused]] unsigned const apiVersion)
|
||||
{
|
||||
return parseObjectID(params, fieldName, "hex string");
|
||||
if (!params.isObject())
|
||||
{
|
||||
return parseObjectID(params, fieldName);
|
||||
}
|
||||
|
||||
auto const account =
|
||||
ledger_entry_helpers::requiredAccountID(params, jss::account, "malformedAddress");
|
||||
if (!account)
|
||||
return std::unexpected(account.error());
|
||||
|
||||
auto const sequence =
|
||||
ledger_entry_helpers::requiredUInt32(params, jss::seq, "malformedRequest");
|
||||
if (!sequence)
|
||||
return std::unexpected(sequence.error());
|
||||
|
||||
auto const seqProxy = SeqProxy::rawSequence(*sequence);
|
||||
return keylet::check(*account, seqProxy).key;
|
||||
}
|
||||
|
||||
static std::expected<UInt256, json::Value>
|
||||
@@ -570,7 +586,23 @@ parseNFTokenOffer(
|
||||
json::StaticString const fieldName,
|
||||
[[maybe_unused]] unsigned const apiVersion)
|
||||
{
|
||||
return parseObjectID(params, fieldName, "hex string");
|
||||
if (!params.isObject())
|
||||
{
|
||||
return parseObjectID(params, fieldName);
|
||||
}
|
||||
|
||||
auto const owner =
|
||||
ledger_entry_helpers::requiredAccountID(params, jss::owner, "malformedOwner");
|
||||
if (!owner)
|
||||
return std::unexpected(owner.error());
|
||||
|
||||
auto const sequence =
|
||||
ledger_entry_helpers::requiredUInt32(params, jss::seq, "malformedRequest");
|
||||
if (!sequence)
|
||||
return std::unexpected(sequence.error());
|
||||
|
||||
auto const seqProxy = SeqProxy::rawSequence(*sequence);
|
||||
return keylet::nftokenOffer(*owner, seqProxy).key;
|
||||
}
|
||||
|
||||
static std::expected<UInt256, json::Value>
|
||||
@@ -638,7 +670,28 @@ parsePayChannel(
|
||||
json::StaticString const fieldName,
|
||||
[[maybe_unused]] unsigned const apiVersion)
|
||||
{
|
||||
return parseObjectID(params, fieldName, "hex string");
|
||||
if (!params.isObject())
|
||||
{
|
||||
return parseObjectID(params, fieldName);
|
||||
}
|
||||
|
||||
auto const account =
|
||||
ledger_entry_helpers::requiredAccountID(params, jss::account, "malformedAddress");
|
||||
if (!account)
|
||||
return std::unexpected(account.error());
|
||||
|
||||
auto const destination =
|
||||
ledger_entry_helpers::requiredAccountID(params, jss::destination, "malformedAddress");
|
||||
if (!destination)
|
||||
return std::unexpected(destination.error());
|
||||
|
||||
auto const sequence =
|
||||
ledger_entry_helpers::requiredUInt32(params, jss::seq, "malformedRequest");
|
||||
if (!sequence)
|
||||
return std::unexpected(sequence.error());
|
||||
|
||||
auto const seqProxy = SeqProxy::rawSequence(*sequence);
|
||||
return keylet::payChannel(*account, *destination, seqProxy).key;
|
||||
}
|
||||
|
||||
static std::expected<UInt256, json::Value>
|
||||
@@ -726,7 +779,17 @@ parseSignerList(
|
||||
json::StaticString const fieldName,
|
||||
[[maybe_unused]] unsigned const apiVersion)
|
||||
{
|
||||
return parseObjectID(params, fieldName, "hex string");
|
||||
if (!params.isObject())
|
||||
{
|
||||
return parseObjectID(params, fieldName);
|
||||
}
|
||||
|
||||
auto const id =
|
||||
ledger_entry_helpers::requiredAccountID(params, jss::account, "malformedAddress");
|
||||
if (!id)
|
||||
return std::unexpected(id.error());
|
||||
|
||||
return keylet::signerList(*id).key;
|
||||
}
|
||||
|
||||
static std::expected<UInt256, json::Value>
|
||||
|
||||
@@ -64,6 +64,11 @@ doFeature(rpc::JsonContext& context)
|
||||
if (!isAdmin)
|
||||
return rpcError(RpcNoPermission);
|
||||
|
||||
if (!context.params[jss::vetoed].isBool())
|
||||
{
|
||||
return rpcError(RpcInvalidParams);
|
||||
}
|
||||
|
||||
if (context.params[jss::vetoed].asBool())
|
||||
{
|
||||
table.veto(feature);
|
||||
|
||||
@@ -4,12 +4,16 @@
|
||||
#include <xrpld/rpc/Context.h>
|
||||
#include <xrpld/rpc/MethodNames.h>
|
||||
#include <xrpld/rpc/Role.h>
|
||||
#include <xrpld/rpc/Status.h>
|
||||
#include <xrpld/rpc/detail/Handler.h>
|
||||
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/protocol/ApiVersion.h>
|
||||
|
||||
#include <rpcspec/Errors.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <functional>
|
||||
#include <string_view>
|
||||
|
||||
namespace xrpl::rpc {
|
||||
@@ -17,21 +21,29 @@ namespace xrpl::rpc {
|
||||
class VersionHandler
|
||||
{
|
||||
public:
|
||||
explicit VersionHandler(JsonContext& c)
|
||||
: apiVersion_(c.apiVersion), betaEnabled_(c.app.config().betaRpcApi)
|
||||
struct Output
|
||||
{
|
||||
std::uint32_t apiVersion;
|
||||
bool betaEnabled;
|
||||
};
|
||||
|
||||
explicit VersionHandler(JsonContext& context) : context_(context)
|
||||
{
|
||||
}
|
||||
|
||||
static Status
|
||||
check()
|
||||
[[nodiscard]] std::expected<Output, ::rpc::Status>
|
||||
process() const
|
||||
{
|
||||
return Status::kOK;
|
||||
return Output{
|
||||
.apiVersion = context_.get().apiVersion,
|
||||
.betaEnabled = context_.get().app.config().betaRpcApi,
|
||||
};
|
||||
}
|
||||
|
||||
void
|
||||
writeResult(json::Value& obj) const
|
||||
static void
|
||||
writeResult(json::Value& obj, Output const& output)
|
||||
{
|
||||
setVersion(obj, apiVersion_, betaEnabled_);
|
||||
setVersion(obj, output.apiVersion, output.betaEnabled);
|
||||
}
|
||||
|
||||
// NOLINTBEGIN(readability-identifier-naming)
|
||||
@@ -47,8 +59,7 @@ public:
|
||||
// NOLINTEND(readability-identifier-naming)
|
||||
|
||||
private:
|
||||
unsigned int apiVersion_;
|
||||
bool betaEnabled_;
|
||||
std::reference_wrapper<JsonContext> context_;
|
||||
};
|
||||
|
||||
} // namespace xrpl::rpc
|
||||
Reference in new issue
Block a user