mirror of
https://github.com/Xahau/xahaud.git
synced 2026-10-21 19:28:10 +00:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
68387927e0 | ||
|
|
aab273c575 |
No files matched your search
@@ -19,15 +19,6 @@ coverage:
|
||||
default:
|
||||
target: auto
|
||||
threshold: 2%
|
||||
paths:
|
||||
# PeerImp is historically hard to exercise in the current unit-test
|
||||
# harness. Keep this list narrow; new testable code should remain
|
||||
# covered by the default patch gate.
|
||||
- "!src/xrpld/overlay/detail/PeerImp.cpp"
|
||||
historically-untested:
|
||||
target: 0%
|
||||
paths:
|
||||
- "src/xrpld/overlay/detail/PeerImp.cpp"
|
||||
changes: false
|
||||
|
||||
github_checks:
|
||||
|
||||
@@ -50,10 +50,6 @@ inputs:
|
||||
options:
|
||||
- libstdcxx
|
||||
- libcxx
|
||||
conan_deps_cxxflags:
|
||||
description: 'Extra cxxflags applied to Conan dependency package builds only (NOT the rippled build). JSON object keyed by Conan package pattern, e.g. {"grpc/*":["-Wno-foo"]}. Maps to <pattern>:tools.build:cxxflags.'
|
||||
required: false
|
||||
default: '{}'
|
||||
|
||||
outputs:
|
||||
cache-hit:
|
||||
@@ -85,8 +81,6 @@ runs:
|
||||
|
||||
- name: Configure Conan
|
||||
shell: bash
|
||||
env:
|
||||
CONAN_DEPS_CXXFLAGS: ${{ inputs.conan_deps_cxxflags }}
|
||||
run: |
|
||||
# Create the default profile directory if it doesn't exist
|
||||
mkdir -p ~/.conan2/profiles
|
||||
@@ -111,14 +105,7 @@ runs:
|
||||
os=${{ inputs.os }}
|
||||
EOF
|
||||
|
||||
# [buildenv] + [conf] sections.
|
||||
# Linux pins compiler executables; macOS uses the system toolchain.
|
||||
# conan_deps_cxxflags (matrix-driven) optionally adds package-pattern
|
||||
# scoped tools.build:cxxflags for Conan dependency builds only - typically
|
||||
# grpc workarounds for newer clang's stricter diagnostics. Because these
|
||||
# are profile-pattern scoped (e.g. grpc/*:...), they do NOT affect the
|
||||
# consumer/rippled toolchain generated for the main build.
|
||||
NEED_CONF=0
|
||||
# Add buildenv and conf sections for Linux (not needed for macOS)
|
||||
if [ "${{ inputs.os }}" = "Linux" ] && [ -n "${{ inputs.cc }}" ]; then
|
||||
cat >> ~/.conan2/profiles/default <<EOF
|
||||
|
||||
@@ -129,38 +116,16 @@ runs:
|
||||
[conf]
|
||||
tools.build:compiler_executables={"c": "/usr/bin/${{ inputs.cc }}", "cpp": "/usr/bin/${{ inputs.cxx }}"}
|
||||
EOF
|
||||
NEED_CONF=1
|
||||
fi
|
||||
|
||||
if [ -n "${CONAN_DEPS_CXXFLAGS}" ] && [ "${CONAN_DEPS_CXXFLAGS}" != "{}" ]; then
|
||||
CONAN_DEPS_CXXFLAGS_LINES="$(python3 - <<'PY'
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
# Add macOS-specific conf if needed
|
||||
if [ "${{ inputs.os }}" = "Macos" ]; then
|
||||
cat >> ~/.conan2/profiles/default <<EOF
|
||||
|
||||
raw = os.environ["CONAN_DEPS_CXXFLAGS"]
|
||||
data = json.loads(raw)
|
||||
if not isinstance(data, dict):
|
||||
sys.exit("conan_deps_cxxflags must be a JSON object like {\"grpc/*\": [\"-Wno-...\"]}")
|
||||
|
||||
for pattern, flags in data.items():
|
||||
if not isinstance(pattern, str) or not pattern:
|
||||
sys.exit("conan_deps_cxxflags keys must be non-empty Conan package patterns")
|
||||
if pattern == "&":
|
||||
sys.exit("conan_deps_cxxflags must target dependency package patterns, not the consumer (&)")
|
||||
if not isinstance(flags, list) or not all(isinstance(flag, str) for flag in flags):
|
||||
sys.exit(f"{pattern}: cxxflags must be a JSON string list")
|
||||
if flags:
|
||||
print(f"{pattern}:tools.build:cxxflags={json.dumps(flags, separators=(',', ':'))}")
|
||||
PY
|
||||
)"
|
||||
if [ -n "${CONAN_DEPS_CXXFLAGS_LINES}" ] && [ "$NEED_CONF" = "0" ]; then
|
||||
echo "" >> ~/.conan2/profiles/default
|
||||
echo "[conf]" >> ~/.conan2/profiles/default
|
||||
fi
|
||||
if [ -n "${CONAN_DEPS_CXXFLAGS_LINES}" ]; then
|
||||
printf '%s\n' "${CONAN_DEPS_CXXFLAGS_LINES}" >> ~/.conan2/profiles/default
|
||||
fi
|
||||
[conf]
|
||||
# Workaround for gRPC with newer Apple Clang
|
||||
tools.build:cxxflags=["-Wno-missing-template-arg-list-after-template-kw"]
|
||||
EOF
|
||||
fi
|
||||
|
||||
# Display profile for verification
|
||||
|
||||
@@ -107,9 +107,6 @@ jobs:
|
||||
compiler: apple-clang
|
||||
compiler_version: ${{ steps.detect-compiler.outputs.compiler_version }}
|
||||
stdlib: libcxx
|
||||
# grpc 1.50.1 trips clang-19+ -Werror=missing-template-arg-list-after-template-kw
|
||||
# on Apple Clang. Drop when grpc is bumped past the fix.
|
||||
conan_deps_cxxflags: '{"grpc/*":["-Wno-missing-template-arg-list-after-template-kw"]}'
|
||||
|
||||
- name: Build
|
||||
uses: ./.github/actions/xahau-ga-build
|
||||
|
||||
@@ -72,26 +72,15 @@ jobs:
|
||||
"job_type": "build"
|
||||
},
|
||||
{
|
||||
# Latest stable Clang for the most accurate source-based
|
||||
# coverage mapping (newer language features, fewer bugs in
|
||||
# llvm-cov region inference). Pulled from apt.llvm.org since
|
||||
# Ubuntu 24.04 default repos cap at clang-18.
|
||||
"compiler_id": "clang-20-libcxx",
|
||||
"compiler": "clang",
|
||||
"cc": "clang-20",
|
||||
"cxx": "clang++-20",
|
||||
"compiler_version": 20,
|
||||
"stdlib": "libcxx",
|
||||
"compiler_id": "gcc-13-libstdcxx",
|
||||
"compiler": "gcc",
|
||||
"cc": "gcc-13",
|
||||
"cxx": "g++-13",
|
||||
"gcov": "gcov-13",
|
||||
"compiler_version": 13,
|
||||
"stdlib": "default",
|
||||
"configuration": "Debug",
|
||||
"job_type": "coverage",
|
||||
"coverage_tool": "llvm",
|
||||
"coverage_format": "lcov",
|
||||
# grpc 1.50.1 uses `Foo::template Bar(...)` without an
|
||||
# angle-bracket arg list; clang-19+ promoted that to
|
||||
# -Werror. Drop when grpc is bumped past the fix.
|
||||
"conan_deps_cxxflags": {
|
||||
"grpc/*": ["-Wno-missing-template-arg-list-after-template-kw"]
|
||||
}
|
||||
"job_type": "coverage"
|
||||
},
|
||||
{
|
||||
"compiler_id": "clang-14-libstdcxx-gcc11",
|
||||
@@ -142,7 +131,7 @@ jobs:
|
||||
# Minimal matrix for PRs and feature branches
|
||||
minimal_matrix = [
|
||||
full_matrix[1], # gcc-13 (middle-ground gcc)
|
||||
full_matrix[2], # clang-20 llvm-cov coverage
|
||||
full_matrix[2], # gcc-13 coverage
|
||||
full_matrix[3] # clang-14 (mature, stable clang)
|
||||
]
|
||||
|
||||
@@ -218,9 +207,9 @@ jobs:
|
||||
# Select the appropriate matrix
|
||||
if use_full:
|
||||
if force_full:
|
||||
print(f"Using FULL matrix (7 configs (build x6 + clang-20 llvm-cov coverage)) - forced by [ci-nix-full-matrix] tag")
|
||||
print(f"Using FULL matrix (7 configs) - forced by [ci-nix-full-matrix] tag")
|
||||
else:
|
||||
print(f"Using FULL matrix (7 configs (build x6 + clang-20 llvm-cov coverage)) - targeting main branch")
|
||||
print(f"Using FULL matrix (7 configs) - targeting main branch")
|
||||
matrix = full_matrix
|
||||
else:
|
||||
print(f"Using MINIMAL matrix (3 configs) - feature branch/PR")
|
||||
@@ -268,25 +257,9 @@ jobs:
|
||||
|
||||
- name: Install build dependencies
|
||||
run: |
|
||||
# Bump apt's default 3 retries; papers over short upstream blips
|
||||
# like the recurring ppa.launchpadcontent.net outages.
|
||||
echo 'Acquire::Retries "5";' > /etc/apt/apt.conf.d/80-retries
|
||||
apt-get update
|
||||
apt-get install -y software-properties-common
|
||||
add-apt-repository ppa:ubuntu-toolchain-r/test -y
|
||||
|
||||
# apt.llvm.org for Clang versions newer than what Ubuntu 24.04 ships
|
||||
# (24.04 default repos cap at clang-18). The bootstrap script adds
|
||||
# the LLVM apt source for the requested version and runs apt-get update.
|
||||
if [ "${{ matrix.compiler }}" = "clang" ] && [ "${{ matrix.compiler_version }}" -ge 19 ]; then
|
||||
apt-get install -y wget gnupg lsb-release
|
||||
wget -qO /tmp/llvm.sh https://apt.llvm.org/llvm.sh
|
||||
chmod +x /tmp/llvm.sh
|
||||
# `all` installs clang + libllvm + lldb + lld + the llvm-N package
|
||||
# (which provides llvm-profdata-N / llvm-cov-N for coverage runs).
|
||||
/tmp/llvm.sh ${{ matrix.compiler_version }} all
|
||||
fi
|
||||
|
||||
apt-get update
|
||||
apt-get install -y git python3 python-is-python3 pipx
|
||||
pipx ensurepath
|
||||
@@ -359,16 +332,10 @@ jobs:
|
||||
pipx install "conan>=2.0,<3"
|
||||
echo "$HOME/.local/bin" >> $GITHUB_PATH
|
||||
|
||||
# Install coverage tooling
|
||||
# Install gcovr for coverage jobs
|
||||
if [ "${{ matrix.job_type }}" = "coverage" ]; then
|
||||
pipx install "gcovr>=7,<9"
|
||||
apt-get install -y curl lcov
|
||||
if [ "${{ matrix.coverage_tool }}" = "llvm" ]; then
|
||||
# Native LLVM source-based coverage: llvm-profdata + llvm-cov.
|
||||
# The clang-N package doesn't pull these in; the llvm-N package does.
|
||||
apt-get install -y "llvm-${{ matrix.compiler_version }}"
|
||||
else
|
||||
pipx install "gcovr>=7,<9"
|
||||
fi
|
||||
fi
|
||||
|
||||
- name: Check environment
|
||||
@@ -381,15 +348,10 @@ jobs:
|
||||
which ${{ matrix.cxx }} && ${{ matrix.cxx }} --version || echo "${{ matrix.cxx }} not found"
|
||||
which ccache && ccache --version || echo "ccache not found"
|
||||
|
||||
# Check coverage tooling
|
||||
# Check gcovr for coverage jobs
|
||||
if [ "${{ matrix.job_type }}" = "coverage" ]; then
|
||||
if [ "${{ matrix.coverage_tool }}" = "llvm" ]; then
|
||||
which "llvm-profdata-${{ matrix.compiler_version }}" && "llvm-profdata-${{ matrix.compiler_version }}" --version || echo "llvm-profdata not found"
|
||||
which "llvm-cov-${{ matrix.compiler_version }}" && "llvm-cov-${{ matrix.compiler_version }}" --version || echo "llvm-cov not found"
|
||||
else
|
||||
which gcov && gcov --version || echo "gcov not found"
|
||||
which gcovr && gcovr --version || echo "gcovr not found"
|
||||
fi
|
||||
which gcov && gcov --version || echo "gcov not found"
|
||||
which gcovr && gcovr --version || echo "gcovr not found"
|
||||
fi
|
||||
|
||||
echo "---- Full Environment ----"
|
||||
@@ -416,7 +378,6 @@ jobs:
|
||||
cc: ${{ matrix.cc }}
|
||||
cxx: ${{ matrix.cxx }}
|
||||
stdlib: ${{ matrix.stdlib }}
|
||||
conan_deps_cxxflags: ${{ matrix.conan_deps_cxxflags && toJson(matrix.conan_deps_cxxflags) || '{}' }}
|
||||
gha_cache_enabled: 'false' # Disable caching for self hosted runner
|
||||
|
||||
- name: Build
|
||||
@@ -449,9 +410,8 @@ jobs:
|
||||
cache_version: ${{ env.CACHE_VERSION }}
|
||||
main_branch: ${{ env.MAIN_BRANCH_NAME }}
|
||||
stdlib: ${{ matrix.stdlib }}
|
||||
# Coverage builds are slower due to instrumentation; use fewer parallel jobs to avoid flakiness.
|
||||
# Use *_FLAGS_DEBUG so the build action's stdlib flag (e.g. -stdlib=libc++) in CMAKE_CXX_FLAGS isn't clobbered.
|
||||
cmake-args: '-Dcoverage=ON -Dcoverage_tool=${{ matrix.coverage_tool }} -Dcoverage_format=${{ matrix.coverage_format }} -Dcoverage_test_parallelism=$(($(nproc)/2)) -DCODE_COVERAGE_VERBOSE=ON -DCMAKE_CXX_FLAGS_DEBUG="-g -O0" -DCMAKE_C_FLAGS_DEBUG="-g -O0"'
|
||||
# Coverage builds are slower due to instrumentation; use fewer parallel jobs to avoid flakiness
|
||||
cmake-args: '-Dcoverage=ON -Dcoverage_format=xml -Dcoverage_test_parallelism=$(($(nproc)/2)) -DCODE_COVERAGE_VERBOSE=ON -DCMAKE_CXX_FLAGS="-O0" -DCMAKE_C_FLAGS="-O0"'
|
||||
cmake-target: 'coverage'
|
||||
ccache_max_size: '100G'
|
||||
|
||||
@@ -483,26 +443,22 @@ jobs:
|
||||
- name: Move coverage report
|
||||
if: matrix.job_type == 'coverage'
|
||||
shell: bash
|
||||
env:
|
||||
COVERAGE_FILE: ${{ matrix.coverage_tool == 'llvm' && 'coverage.lcov' || 'coverage.xml' }}
|
||||
run: |
|
||||
mv "${{ env.build_dir }}/${COVERAGE_FILE}" ./
|
||||
echo "COVERAGE_FILE=${COVERAGE_FILE}" >> "$GITHUB_ENV"
|
||||
mv "${{ env.build_dir }}/coverage.xml" ./
|
||||
|
||||
- name: Archive coverage report
|
||||
if: matrix.job_type == 'coverage'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ env.COVERAGE_FILE }}-${{ matrix.compiler_id }}
|
||||
path: ${{ env.COVERAGE_FILE }}
|
||||
name: coverage.xml
|
||||
path: coverage.xml
|
||||
retention-days: 30
|
||||
|
||||
- name: Upload coverage report
|
||||
if: matrix.job_type == 'coverage'
|
||||
uses: codecov/codecov-action@v5
|
||||
with:
|
||||
files: ${{ env.COVERAGE_FILE }}
|
||||
flags: ${{ matrix.coverage_tool }}
|
||||
files: coverage.xml
|
||||
fail_ci_if_error: true
|
||||
disable_search: true
|
||||
verbose: true
|
||||
|
||||
@@ -1,156 +0,0 @@
|
||||
#[===================================================================[
|
||||
Native LLVM source-based code coverage helper.
|
||||
|
||||
Drives the -fprofile-instr-generate / -fcoverage-mapping pipeline:
|
||||
1. Run instrumented binary with LLVM_PROFILE_FILE=...%m-%p.profraw
|
||||
2. llvm-profdata merge -sparse -> coverage.profdata
|
||||
3. llvm-cov export/show/report -> final report
|
||||
|
||||
Output filename per coverage_format:
|
||||
lcov -> coverage.lcov
|
||||
json -> coverage.json
|
||||
txt | text -> coverage.txt
|
||||
html | html-details -> <NAME>/index.html
|
||||
#]===================================================================]
|
||||
|
||||
include(CMakeParseArguments)
|
||||
|
||||
# Locate llvm-profdata / llvm-cov, preferring versioned variants matching the
|
||||
# Clang we're building with so we don't accidentally pair clang-20 with
|
||||
# llvm-cov-14 (profile format mismatch -> hard failure).
|
||||
function(_find_llvm_cov_tools)
|
||||
if(LLVM_PROFDATA_PATH AND LLVM_COV_PATH)
|
||||
return()
|
||||
endif()
|
||||
|
||||
string(REGEX MATCH "^[0-9]+" _major "${CMAKE_CXX_COMPILER_VERSION}")
|
||||
|
||||
set(_pd_names llvm-profdata)
|
||||
set(_cov_names llvm-cov)
|
||||
if(_major)
|
||||
list(PREPEND _pd_names "llvm-profdata-${_major}")
|
||||
list(PREPEND _cov_names "llvm-cov-${_major}")
|
||||
endif()
|
||||
|
||||
# Only delegate to xcrun when the *compiler* is AppleClang. On macOS with
|
||||
# Homebrew/system clang-N, xcrun would resolve to Xcode's llvm tools which
|
||||
# could be a different version - exactly the mismatch we want to avoid.
|
||||
if(APPLE AND CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang")
|
||||
execute_process(COMMAND xcrun -f llvm-profdata
|
||||
OUTPUT_VARIABLE _pd_xcrun OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
ERROR_QUIET RESULT_VARIABLE _pd_rc)
|
||||
if(_pd_rc EQUAL 0 AND _pd_xcrun)
|
||||
set(LLVM_PROFDATA_PATH "${_pd_xcrun}" CACHE FILEPATH "llvm-profdata" FORCE)
|
||||
endif()
|
||||
execute_process(COMMAND xcrun -f llvm-cov
|
||||
OUTPUT_VARIABLE _cov_xcrun OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
ERROR_QUIET RESULT_VARIABLE _cov_rc)
|
||||
if(_cov_rc EQUAL 0 AND _cov_xcrun)
|
||||
set(LLVM_COV_PATH "${_cov_xcrun}" CACHE FILEPATH "llvm-cov" FORCE)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(NOT LLVM_PROFDATA_PATH)
|
||||
find_program(LLVM_PROFDATA_PATH NAMES ${_pd_names})
|
||||
endif()
|
||||
if(NOT LLVM_COV_PATH)
|
||||
find_program(LLVM_COV_PATH NAMES ${_cov_names})
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(setup_target_for_coverage_llvm)
|
||||
set(oneValueArgs NAME FORMAT)
|
||||
set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES)
|
||||
cmake_parse_arguments(Cov "" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
|
||||
|
||||
_find_llvm_cov_tools()
|
||||
if(NOT LLVM_PROFDATA_PATH)
|
||||
message(FATAL_ERROR "llvm-profdata not found (needed for coverage_tool=llvm)")
|
||||
endif()
|
||||
if(NOT LLVM_COV_PATH)
|
||||
message(FATAL_ERROR "llvm-cov not found (needed for coverage_tool=llvm)")
|
||||
endif()
|
||||
|
||||
if(NOT Cov_FORMAT)
|
||||
set(Cov_FORMAT lcov)
|
||||
endif()
|
||||
|
||||
set(_profraw_dir "${PROJECT_BINARY_DIR}/${Cov_NAME}-profraw")
|
||||
set(_profdata "${PROJECT_BINARY_DIR}/${Cov_NAME}.profdata")
|
||||
|
||||
# Resolve binary path: accept either an absolute path or a bare target name
|
||||
# (resolved against PROJECT_BINARY_DIR). Splice the resolved path back into
|
||||
# Cov_EXECUTABLE so the run command invokes it via absolute path - bare
|
||||
# names aren't on PATH and the build dir isn't `.` either.
|
||||
list(GET Cov_EXECUTABLE 0 _exec_name)
|
||||
if(IS_ABSOLUTE "${_exec_name}")
|
||||
set(_binary "${_exec_name}")
|
||||
else()
|
||||
set(_binary "${PROJECT_BINARY_DIR}/${_exec_name}")
|
||||
list(REMOVE_AT Cov_EXECUTABLE 0)
|
||||
list(PREPEND Cov_EXECUTABLE "${_binary}")
|
||||
endif()
|
||||
|
||||
# llvm-cov takes a single -ignore-filename-regex; OR our excludes together.
|
||||
set(_ignore_regex "")
|
||||
foreach(EXC IN LISTS Cov_EXCLUDE)
|
||||
if(_ignore_regex)
|
||||
string(APPEND _ignore_regex "|")
|
||||
endif()
|
||||
string(APPEND _ignore_regex "${EXC}")
|
||||
endforeach()
|
||||
set(_filter "")
|
||||
if(_ignore_regex)
|
||||
set(_filter "-ignore-filename-regex='${_ignore_regex}'")
|
||||
endif()
|
||||
|
||||
# Pick llvm-cov subcommand + output file for the requested format. Each
|
||||
# branch builds a single shell command string that we'll hand to bash -c.
|
||||
if(Cov_FORMAT STREQUAL "lcov")
|
||||
set(_output "${PROJECT_BINARY_DIR}/coverage.lcov")
|
||||
set(_report_sh "${LLVM_COV_PATH} export -instr-profile='${_profdata}' -format=lcov ${_filter} '${_binary}' > '${_output}'")
|
||||
elseif(Cov_FORMAT STREQUAL "json")
|
||||
set(_output "${PROJECT_BINARY_DIR}/coverage.json")
|
||||
set(_report_sh "${LLVM_COV_PATH} export -instr-profile='${_profdata}' -format=text ${_filter} '${_binary}' > '${_output}'")
|
||||
elseif(Cov_FORMAT STREQUAL "txt" OR Cov_FORMAT STREQUAL "text")
|
||||
set(_output "${PROJECT_BINARY_DIR}/coverage.txt")
|
||||
set(_report_sh "${LLVM_COV_PATH} report -instr-profile='${_profdata}' ${_filter} '${_binary}' > '${_output}'")
|
||||
elseif(Cov_FORMAT STREQUAL "html" OR Cov_FORMAT STREQUAL "html-details")
|
||||
set(_output "${PROJECT_BINARY_DIR}/${Cov_NAME}/index.html")
|
||||
set(_report_sh "${LLVM_COV_PATH} show -instr-profile='${_profdata}' -format=html -output-dir='${PROJECT_BINARY_DIR}/${Cov_NAME}' ${_filter} '${_binary}'")
|
||||
else()
|
||||
message(FATAL_ERROR "coverage_tool=llvm: unsupported coverage_format '${Cov_FORMAT}' (use lcov|json|txt|html)")
|
||||
endif()
|
||||
|
||||
set(_merge_sh "${LLVM_PROFDATA_PATH} merge -sparse -o '${_profdata}' '${_profraw_dir}'/*.profraw")
|
||||
|
||||
if(CODE_COVERAGE_VERBOSE)
|
||||
message(STATUS "[coverage:llvm] binary: ${_binary}")
|
||||
message(STATUS "[coverage:llvm] profraw: ${_profraw_dir}")
|
||||
message(STATUS "[coverage:llvm] profdata: ${_profdata}")
|
||||
message(STATUS "[coverage:llvm] format: ${Cov_FORMAT}")
|
||||
message(STATUS "[coverage:llvm] output: ${_output}")
|
||||
if(_ignore_regex)
|
||||
message(STATUS "[coverage:llvm] ignore: ${_ignore_regex}")
|
||||
endif()
|
||||
message(STATUS "[coverage:llvm] merge: ${_merge_sh}")
|
||||
message(STATUS "[coverage:llvm] report: ${_report_sh}")
|
||||
endif()
|
||||
|
||||
# %m: hash of the binary, %p: pid. Wipe the dir up front so stale profraw
|
||||
# files can't leak into a fresh merge.
|
||||
add_custom_target(${Cov_NAME}
|
||||
COMMAND ${CMAKE_COMMAND} -E rm -rf "${_profraw_dir}"
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory "${_profraw_dir}"
|
||||
COMMAND ${CMAKE_COMMAND} -E env
|
||||
"LLVM_PROFILE_FILE=${_profraw_dir}/rippled-%m-%p.profraw"
|
||||
${Cov_EXECUTABLE} ${Cov_EXECUTABLE_ARGS}
|
||||
COMMAND bash -c "${_merge_sh}"
|
||||
COMMAND bash -c "${_report_sh}"
|
||||
BYPRODUCTS ${_output}
|
||||
WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
|
||||
DEPENDS ${Cov_DEPENDENCIES}
|
||||
VERBATIM
|
||||
COMMENT "Running llvm-cov (${Cov_FORMAT}) -> ${_output}"
|
||||
)
|
||||
endfunction()
|
||||
@@ -11,21 +11,6 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
|
||||
return()
|
||||
endif()
|
||||
|
||||
if(coverage_tool STREQUAL "llvm")
|
||||
include(CodeCoverageLLVM)
|
||||
|
||||
setup_target_for_coverage_llvm(
|
||||
NAME coverage
|
||||
FORMAT ${coverage_format}
|
||||
EXECUTABLE rippled
|
||||
EXECUTABLE_ARGS --unittest$<$<BOOL:${coverage_test}>:=${coverage_test}> --unittest-jobs ${coverage_test_parallelism} --quiet --unittest-log
|
||||
EXCLUDE "src/test" "include/xrpl/beast/test" "include/xrpl/beast/unit_test" "${CMAKE_BINARY_DIR}/pb-xrpl.libpb"
|
||||
DEPENDENCIES rippled
|
||||
)
|
||||
return()
|
||||
endif()
|
||||
|
||||
# coverage_tool == "gcov" (default): existing gcovr-driven pipeline.
|
||||
include(CodeCoverage)
|
||||
|
||||
# The instructions for these commands come from the `CodeCoverage` module,
|
||||
|
||||
@@ -28,17 +28,15 @@ target_compile_options (opts
|
||||
$<$<AND:$<BOOL:${is_gcc}>,$<COMPILE_LANGUAGE:CXX>>:-Wsuggest-override>
|
||||
$<$<BOOL:${is_gcc}>:-Wno-maybe-uninitialized>
|
||||
$<$<BOOL:${perf}>:-fno-omit-frame-pointer>
|
||||
$<$<AND:$<BOOL:${is_gcc}>,$<BOOL:${coverage}>,$<STREQUAL:${coverage_tool},gcov>>:-g --coverage -fprofile-abs-path>
|
||||
$<$<AND:$<BOOL:${is_clang}>,$<BOOL:${coverage}>,$<STREQUAL:${coverage_tool},gcov>>:-g --coverage>
|
||||
$<$<AND:$<BOOL:${is_clang}>,$<BOOL:${coverage}>,$<STREQUAL:${coverage_tool},llvm>>:-g -fprofile-instr-generate -fcoverage-mapping>
|
||||
$<$<AND:$<BOOL:${is_gcc}>,$<BOOL:${coverage}>>:-g --coverage -fprofile-abs-path>
|
||||
$<$<AND:$<BOOL:${is_clang}>,$<BOOL:${coverage}>>:-g --coverage>
|
||||
$<$<BOOL:${profile}>:-pg>
|
||||
$<$<AND:$<BOOL:${is_gcc}>,$<BOOL:${profile}>>:-p>)
|
||||
|
||||
target_link_libraries (opts
|
||||
INTERFACE
|
||||
$<$<AND:$<BOOL:${is_gcc}>,$<BOOL:${coverage}>,$<STREQUAL:${coverage_tool},gcov>>:-g --coverage -fprofile-abs-path>
|
||||
$<$<AND:$<BOOL:${is_clang}>,$<BOOL:${coverage}>,$<STREQUAL:${coverage_tool},gcov>>:-g --coverage>
|
||||
$<$<AND:$<BOOL:${is_clang}>,$<BOOL:${coverage}>,$<STREQUAL:${coverage_tool},llvm>>:-g -fprofile-instr-generate -fcoverage-mapping>
|
||||
$<$<AND:$<BOOL:${is_gcc}>,$<BOOL:${coverage}>>:-g --coverage -fprofile-abs-path>
|
||||
$<$<AND:$<BOOL:${is_clang}>,$<BOOL:${coverage}>>:-g --coverage>
|
||||
$<$<BOOL:${profile}>:-pg>
|
||||
$<$<AND:$<BOOL:${is_gcc}>,$<BOOL:${profile}>>:-p>)
|
||||
|
||||
|
||||
@@ -29,25 +29,13 @@ if(is_gcc OR is_clang)
|
||||
"Unit tests parallelism for the purpose of coverage report.")
|
||||
set(coverage_format "html-details" CACHE STRING
|
||||
"Output format of the coverage report.")
|
||||
set(coverage_tool "gcov" CACHE STRING
|
||||
"Coverage instrumentation tool: 'gcov' (default, gcc/clang via --coverage + gcovr) or 'llvm' (clang only, native source-based coverage via -fprofile-instr-generate).")
|
||||
set_property(CACHE coverage_tool PROPERTY STRINGS "gcov" "llvm")
|
||||
if(NOT coverage_tool MATCHES "^(gcov|llvm)$")
|
||||
message(FATAL_ERROR "coverage_tool must be 'gcov' or 'llvm', got '${coverage_tool}'")
|
||||
endif()
|
||||
set(coverage_extra_args "" CACHE STRING
|
||||
"Additional arguments to pass to gcovr (gcov tool only).")
|
||||
"Additional arguments to pass to gcovr.")
|
||||
set(coverage_test "" CACHE STRING
|
||||
"On gcc & clang, the specific unit test(s) to run for coverage. Default is all tests.")
|
||||
if(coverage_test AND NOT coverage)
|
||||
set(coverage ON CACHE BOOL "gcc/clang only" FORCE)
|
||||
endif()
|
||||
# Validate after coverage_test may have flipped coverage on, otherwise
|
||||
# `-Dcoverage_tool=llvm -Dcoverage_test=Foo` on gcc would silently slip
|
||||
# past the Clang guard and produce a broken instrumentation combo.
|
||||
if(coverage AND coverage_tool STREQUAL "llvm" AND NOT is_clang)
|
||||
message(FATAL_ERROR "coverage_tool=llvm requires Clang (got ${CMAKE_CXX_COMPILER_ID})")
|
||||
endif()
|
||||
option(wextra "compile with extra gcc/clang warnings enabled" ON)
|
||||
else()
|
||||
set(profile OFF CACHE BOOL "gcc/clang only" FORCE)
|
||||
|
||||
@@ -244,13 +244,7 @@
|
||||
<< "Tried to accept/rollback but specified " \
|
||||
"memory outside of the wasm instance " \
|
||||
<< "limit when specifying a reason string"; \
|
||||
if (!view.rules().enabled(fixHookExitOutOfBounds)) \
|
||||
return OUT_OF_BOUNDS; \
|
||||
/* fixHookExitOutOfBounds: terminate as a rollback so the \
|
||||
failure is visible in HookExecution metadata */ \
|
||||
hookCtx.result.exitType = hook_api::ExitType::ROLLBACK; \
|
||||
hookCtx.result.exitCode = (int64_t)OUT_OF_BOUNDS; \
|
||||
return RC_ROLLBACK; \
|
||||
return OUT_OF_BOUNDS; \
|
||||
} \
|
||||
/* assembly script and some other languages use utf16 for strings \
|
||||
*/ \
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2025 XRPL Labs
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_PROTOCOL_UTF8_H_INCLUDED
|
||||
#define RIPPLE_PROTOCOL_UTF8_H_INCLUDED
|
||||
|
||||
#include <xrpl/basics/Slice.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
/** Check that a byte sequence is well-formed UTF-8, excluding the two
|
||||
noncharacters U+FFFE and U+FFFF.
|
||||
|
||||
Rejects:
|
||||
- overlong encodings;
|
||||
- UTF-16 surrogate halves (U+D800..U+DFFF);
|
||||
- scalar values above U+10FFFF, and 0xF5..0xFF lead bytes;
|
||||
- stray continuation bytes;
|
||||
- multi-byte sequences truncated by the end of the buffer;
|
||||
- U+FFFE and U+FFFF (EF BF BE, EF BF BF).
|
||||
|
||||
U+FFFE and U+FFFF are well-formed, but have no use in a URI or a name
|
||||
and exist to be misread: U+FFFE is a byte-swapped BOM, so a client that
|
||||
transcodes to UTF-16/32 and sniffs the byte order can be pushed into
|
||||
decoding the rest of the string with the wrong endianness, and both are
|
||||
commonly used as in-band sentinels. They have always been rejected by
|
||||
URIToken and HookName validation, and this function preserves that.
|
||||
|
||||
This is consensus-critical. For every input the previous decoder could
|
||||
evaluate without reading past the end of the buffer, the result is
|
||||
identical to it. The only difference is that truncated trailing
|
||||
sequences, where the previous decoder read out of bounds, are now always
|
||||
rejected. Any further change to what this accepts needs an amendment.
|
||||
|
||||
@param s The bytes to check.
|
||||
|
||||
@note Total: every byte read is within s.
|
||||
*/
|
||||
bool
|
||||
isValidUTF8(Slice const& s) noexcept;
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
@@ -34,11 +34,6 @@
|
||||
// If you add an amendment here, then do not forget to increment `numFeatures`
|
||||
// in include/xrpl/protocol/Feature.h.
|
||||
|
||||
XRPL_FIX (20261001, Supported::yes, VoteBehavior::DefaultYes)
|
||||
XRPL_FIX (HookNameValidation, Supported::yes, VoteBehavior::DefaultYes)
|
||||
XRPL_FIX (HookExitOutOfBounds, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (HookAPISType, Supported::yes, VoteBehavior::DefaultYes)
|
||||
XRPL_FEATURE(HookOnV2_1, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(OnChainManifests, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (HookMap, Supported::yes, VoteBehavior::DefaultYes)
|
||||
XRPL_FIX (GuardDepth32, Supported::yes, VoteBehavior::DefaultNo)
|
||||
@@ -50,7 +45,7 @@ XRPL_FEATURE(HookAPISerializedType240, Supported::yes, VoteBehavior::DefaultNo
|
||||
XRPL_FEATURE(PermissionedDomains, Supported::no, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(DynamicNFT, Supported::no, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(Credentials, Supported::no, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(AMMClawback, Supported::no, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(AMMClawback, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(MPTokensV1, Supported::no, VoteBehavior::DefaultNo)
|
||||
// InvariantsV1_1 will be changes to Supported::yes when all the
|
||||
// invariants expected to be included under it are complete.
|
||||
@@ -70,8 +65,7 @@ XRPL_FEATURE(XChainBridge, Supported::no, VoteBehavior::DefaultNo
|
||||
XRPL_FEATURE(AMM, Supported::no, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (ReducedOffersV1, Supported::yes, VoteBehavior::DefaultYes)
|
||||
XRPL_FEATURE(HooksUpdate2, Supported::yes, VoteBehavior::DefaultNo)
|
||||
// replaced to HookOnV2_1
|
||||
XRPL_FEATURE(HookOnV2, Supported::no, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(HookOnV2, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (HookAPI20251128, Supported::yes, VoteBehavior::DefaultYes)
|
||||
XRPL_FIX (CronStacking, Supported::yes, VoteBehavior::DefaultYes)
|
||||
XRPL_FEATURE(ExtendedHookState, Supported::yes, VoteBehavior::DefaultNo)
|
||||
|
||||
@@ -292,9 +292,7 @@ JSS(duration_us); // out: NetworkOPs
|
||||
JSS(effective); // out: ValidatorList
|
||||
// in: UNL
|
||||
JSS(elapsed_seconds);
|
||||
JSS(enabled); // out: AmendmentTable (on-ledger);
|
||||
// ServerDefinitions (this server)
|
||||
JSS(ledger_enabled); // out: ServerDefinitions (on-ledger)
|
||||
JSS(enabled); // out: AmendmentTable
|
||||
JSS(engine_result); // out: NetworkOPs, TransactionSign, Submit
|
||||
JSS(engine_result_code); // out: NetworkOPs, TransactionSign, Submit
|
||||
JSS(engine_result_message); // out: NetworkOPs, TransactionSign, Submit
|
||||
|
||||
@@ -300,13 +300,7 @@ BaseHTTPPeer<Handler, Impl>::do_read(yield_context do_yield)
|
||||
{
|
||||
complete_ = false;
|
||||
error_code ec;
|
||||
// Do not treat an established keep-alive waiting for its next request as
|
||||
// an unfinished loopback request.
|
||||
if (request_count_ == 0)
|
||||
start_timer();
|
||||
else
|
||||
boost::beast::get_lowest_layer(impl().stream_)
|
||||
.expires_after(std::chrono::seconds(timeoutSeconds));
|
||||
start_timer();
|
||||
boost::beast::http::async_read(
|
||||
impl().stream_, read_buf_, message_, do_yield[ec]);
|
||||
cancel_timer();
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2025 XRPL Labs
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <xrpl/protocol/UTF8.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
bool
|
||||
isValidUTF8(Slice const& s) noexcept
|
||||
{
|
||||
std::size_t const size = s.size();
|
||||
|
||||
// Markus Kuhn's utf8_check.c
|
||||
// (https://www.cl.cam.ac.uk/~mgk25/ucs/utf8_check.c), the decoder
|
||||
// URIToken has always used, with one change: each branch establishes
|
||||
// that the whole sequence is present before reading any continuation
|
||||
// byte, so a truncated tail is invalid rather than an out-of-bounds read.
|
||||
std::size_t i = 0;
|
||||
while (i < size)
|
||||
{
|
||||
std::uint8_t const c0 = s[i];
|
||||
|
||||
if (c0 < 0x80)
|
||||
{
|
||||
// 0xxxxxxx
|
||||
++i;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((c0 & 0xE0) == 0xC0)
|
||||
{
|
||||
// 110xxxxx 10xxxxxx
|
||||
if (size - i < 2)
|
||||
return false;
|
||||
if ((s[i + 1] & 0xC0) != 0x80)
|
||||
return false;
|
||||
if ((c0 & 0xFE) == 0xC0) // overlong
|
||||
return false;
|
||||
i += 2;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((c0 & 0xF0) == 0xE0)
|
||||
{
|
||||
// 1110xxxx 10xxxxxx 10xxxxxx
|
||||
if (size - i < 3)
|
||||
return false;
|
||||
std::uint8_t const c1 = s[i + 1];
|
||||
if ((c1 & 0xC0) != 0x80 || (s[i + 2] & 0xC0) != 0x80)
|
||||
return false;
|
||||
if (c0 == 0xE0 && (c1 & 0xE0) == 0x80) // overlong
|
||||
return false;
|
||||
if (c0 == 0xED && (c1 & 0xE0) == 0xA0) // UTF-16 surrogate
|
||||
return false;
|
||||
if (c0 == 0xEF && c1 == 0xBF &&
|
||||
(s[i + 2] & 0xFE) == 0xBE) // U+FFFE or U+FFFF
|
||||
return false;
|
||||
i += 3;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((c0 & 0xF8) == 0xF0)
|
||||
{
|
||||
// 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
|
||||
if (size - i < 4)
|
||||
return false;
|
||||
std::uint8_t const c1 = s[i + 1];
|
||||
if ((c1 & 0xC0) != 0x80 || (s[i + 2] & 0xC0) != 0x80 ||
|
||||
(s[i + 3] & 0xC0) != 0x80)
|
||||
return false;
|
||||
if (c0 == 0xF0 && (c1 & 0xF0) == 0x80) // overlong
|
||||
return false;
|
||||
if (c0 > 0xF4 || (c0 == 0xF4 && c1 > 0x8F)) // > U+10FFFF
|
||||
return false;
|
||||
i += 4;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Stray continuation byte or 0xF8..0xFF.
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
@@ -150,15 +150,12 @@ private:
|
||||
|
||||
env.fund(XRP(20'000), alice, bob, carol, gw1, gw2);
|
||||
env.fund(XRP(20'000), dan);
|
||||
env.close();
|
||||
env.trust(USD1(20'000), alice, bob, carol, dan);
|
||||
env.trust(USD2(1'000), alice, bob, carol, dan);
|
||||
env.close();
|
||||
|
||||
env(pay(gw1, dan, USD1(10'050)));
|
||||
env(pay(gw1, bob, USD1(50)));
|
||||
env(pay(gw2, bob, USD2(50)));
|
||||
env.close();
|
||||
|
||||
AMM ammDan(env, dan, XRP(10'000), USD1(10'050));
|
||||
|
||||
@@ -1993,7 +1990,6 @@ private:
|
||||
|
||||
env.fund(XRP(10'000), alice, carol, gw);
|
||||
env.fund(XRP(10'000), bob);
|
||||
env.close();
|
||||
env.trust(USD(1'000), alice, bob, carol);
|
||||
env.trust(BTC(1'000), alice, bob, carol);
|
||||
env.trust(EUR(1'000), alice, bob, carol);
|
||||
@@ -3645,7 +3641,7 @@ private:
|
||||
USD(10'000),
|
||||
false,
|
||||
0,
|
||||
ammCrtFee(env).drops() + 2 * env.current()->fees().base.drops(),
|
||||
ammCrtFee(env).drops(),
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
ms,
|
||||
|
||||
@@ -5004,8 +5004,8 @@ private:
|
||||
alice,
|
||||
STAmount{USD, UINT64_C(30'000'0000000003), -10}));
|
||||
// alice XRP balance is 30,000initial - 50 ammcreate fee -
|
||||
// 10drops ammcreate base fee - 10drops fee
|
||||
BEAST_EXPECT(accountBalance(env, alice) == "29949999980");
|
||||
// 10drops fee
|
||||
BEAST_EXPECT(accountBalance(env, alice) == "29949999990");
|
||||
},
|
||||
std::nullopt,
|
||||
0,
|
||||
@@ -5081,7 +5081,7 @@ private:
|
||||
(xrpBalance + drops(5)).getText());
|
||||
BEAST_EXPECT(
|
||||
accountBalance(env, alice) ==
|
||||
std::to_string(29'950'000'000 - 2 * baseFee + 80));
|
||||
std::to_string(29'950'000'000 - baseFee + 80));
|
||||
},
|
||||
std::nullopt,
|
||||
0,
|
||||
|
||||
+319
-188
@@ -3941,142 +3941,310 @@ public:
|
||||
|
||||
auto const _source_object =
|
||||
*strUnHex("81140000000000000000000000000000000000000000");
|
||||
|
||||
std::vector<std::pair<uint32_t, std::string>> data_list = {
|
||||
{// UINT16 Version
|
||||
sfVersion.getCode(),
|
||||
"10100001"},
|
||||
{// UINT32 Sequence
|
||||
sfSequence.getCode(),
|
||||
"2400000001"},
|
||||
// UINT64
|
||||
{// Uint64 ExchangeRate
|
||||
sfExchangeRate.getCode(),
|
||||
"360000000000000001"},
|
||||
{// UINT256 EmailHash
|
||||
sfEmailHash.getCode(),
|
||||
"4100000000000000000000000000000000"},
|
||||
{// UINT256 ObjectID
|
||||
sfObjectID.getCode(),
|
||||
"5E000000000000000000000000000000000000000000000000000000000000000"
|
||||
"0"},
|
||||
{// Amount NativeAmount
|
||||
sfAmount.getCode(),
|
||||
"61999999999999999999999999999999999999999999999999999999999999999"
|
||||
"999999999999999999999999999999999"},
|
||||
{// Amount IOUAmount
|
||||
sfAmount.getCode(),
|
||||
"614999999999999999"},
|
||||
{// Object AmountEntry
|
||||
sfAmountEntry.getCode(),
|
||||
"E05B614000000000000064E1"},
|
||||
{// Array Memos
|
||||
sfMemos.getCode(),
|
||||
"F9EA7D04DEADBEEFE1F1"},
|
||||
{// UINT8 TickSize
|
||||
sfTickSize.getCode(),
|
||||
"00101001"},
|
||||
{// UINT160 TakerPaysCurrency
|
||||
sfTakerPaysCurrency.getCode(),
|
||||
"01110000000000000000000000005553440000000000"},
|
||||
{// PathSet Paths
|
||||
sfPaths.getCode(),
|
||||
"0112300000000000000000000000005553440000000000054F6F784A58F9EFB0A"
|
||||
"9EB90B83464F9D166461900"},
|
||||
{// Vector256 Amendments
|
||||
sfAmendments.getCode(),
|
||||
"03132042426C4D4F1009EE67080A9B7965B44656D7714D104A72F9B4369F97ABF"
|
||||
"044EE"},
|
||||
{// Issue ClaimCurrency
|
||||
sfClaimCurrency.getCode(),
|
||||
"05180000000000000000000000005553440000000000054F6F784A58F9EFB0A9E"
|
||||
"B90B83464F9D1664619"},
|
||||
{// XChainBridge Native-Native
|
||||
sfXChainBridge.getCode(),
|
||||
"011914AE123A8556F3CF91154711376AFB0F894F832B3D000000000000"
|
||||
"000000000000000000000000000014AE123A8556F3CF91154711376AFB"
|
||||
"0F894F832B3D0000000000000000000000000000000000000000"},
|
||||
{// Currency BaseAsset
|
||||
sfBaseAsset.getCode(),
|
||||
"011A0000000000000000000000005553440000000000"},
|
||||
};
|
||||
if (env.closed()->rules().enabled(fixHookAPISType))
|
||||
{
|
||||
// encodings the legacy parser measures incorrectly
|
||||
data_list.insert(
|
||||
data_list.end(),
|
||||
{
|
||||
// MPT is not supported yet
|
||||
// {// Issue ClaimCurrency MPT
|
||||
// sfClaimCurrency.getCode(),
|
||||
// "0518AE123A8556F3CF91154711376AFB0F894F832B3D0000000000"
|
||||
// "00000000000000000000000000000100000001"},
|
||||
{// XChainBridge default door + IOU, Native
|
||||
sfXChainBridge.getCode(),
|
||||
"0119000000000000000000000000005553440000000000AE123A85"
|
||||
"56F3CF91154711376AFB0F894F832B3D14AE123A8556F3CF911547"
|
||||
"11376AFB0F894F832B3D00000000000000000000000000000000000"
|
||||
"00000"},
|
||||
});
|
||||
}
|
||||
bool const fixEnabled = env.closed()->rules().enabled(fixHookAPISType);
|
||||
for (auto const& data : data_list)
|
||||
{
|
||||
// use UINT16
|
||||
auto source_object = _source_object;
|
||||
auto field_object = *strUnHex(data.second);
|
||||
auto field_id = data.first;
|
||||
auto field_object = *strUnHex("10100001");
|
||||
// Version: 1
|
||||
|
||||
// test invalid insert: trailing byte after the field
|
||||
auto ft = field_object;
|
||||
ft.push_back(0);
|
||||
auto const result3 = api.sto_emplace(source_object, ft, field_id);
|
||||
// legacy parser ignores trailing bytes in the field
|
||||
if (fixEnabled)
|
||||
BEAST_EXPECTS(!result3.has_value(), data.second);
|
||||
else
|
||||
BEAST_EXPECTS(result3.has_value(), data.second);
|
||||
auto const result = api.sto_emplace(
|
||||
source_object, field_object, sfVersion.getCode());
|
||||
BEAST_EXPECT(result.has_value());
|
||||
BEAST_EXPECT(
|
||||
result.value().size() ==
|
||||
source_object.size() + field_object.size());
|
||||
field_object.insert(
|
||||
field_object.end(), source_object.begin(), source_object.end());
|
||||
BEAST_EXPECT(result.value() == field_object);
|
||||
|
||||
// truncated field / source are only exercised after the fix;
|
||||
// the legacy parser may read past the end of these buffers
|
||||
if (!fixEnabled)
|
||||
continue;
|
||||
|
||||
auto const fo = Blob{field_object.begin(), field_object.end() - 1};
|
||||
auto const result1 = api.sto_emplace(source_object, fo, field_id);
|
||||
BEAST_EXPECTS(!result1.has_value(), data.second);
|
||||
|
||||
auto const so =
|
||||
Blob{source_object.begin(), source_object.end() - 1};
|
||||
auto const result2 = api.sto_emplace(so, field_object, field_id);
|
||||
BEAST_EXPECTS(!result2.has_value(), data.second);
|
||||
auto const erase_result = api.sto_emplace(
|
||||
result.value(), std::nullopt, sfVersion.getCode());
|
||||
BEAST_EXPECT(erase_result.has_value());
|
||||
BEAST_EXPECT(erase_result.value().size() == _source_object.size());
|
||||
BEAST_EXPECT(erase_result.value() == _source_object);
|
||||
}
|
||||
|
||||
for (auto const& data : data_list)
|
||||
{
|
||||
// use UINT32
|
||||
auto source_object = _source_object;
|
||||
auto field_object = *strUnHex(data.second);
|
||||
auto field_id = data.first;
|
||||
auto field_object = *strUnHex("2400000001");
|
||||
// Sequence: 1
|
||||
|
||||
auto const result = api.sto_emplace(
|
||||
source_object, field_object, sfSequence.getCode());
|
||||
BEAST_EXPECT(result.has_value());
|
||||
BEAST_EXPECT(
|
||||
result.value().size() ==
|
||||
source_object.size() + field_object.size());
|
||||
field_object.insert(
|
||||
field_object.end(), source_object.begin(), source_object.end());
|
||||
BEAST_EXPECT(result.value() == field_object);
|
||||
|
||||
auto const erase_result = api.sto_emplace(
|
||||
result.value(), std::nullopt, sfSequence.getCode());
|
||||
BEAST_EXPECT(erase_result.has_value());
|
||||
BEAST_EXPECT(erase_result.value().size() == _source_object.size());
|
||||
BEAST_EXPECT(erase_result.value() == _source_object);
|
||||
}
|
||||
|
||||
{
|
||||
// use UINT64
|
||||
auto source_object = _source_object;
|
||||
auto field_object = *strUnHex("360000000000000001");
|
||||
// ExchangeRate: 1
|
||||
|
||||
auto const result = api.sto_emplace(
|
||||
source_object, field_object, sfExchangeRate.getCode());
|
||||
BEAST_EXPECT(result.has_value());
|
||||
BEAST_EXPECT(
|
||||
result.value().size() ==
|
||||
source_object.size() + field_object.size());
|
||||
field_object.insert(
|
||||
field_object.end(), source_object.begin(), source_object.end());
|
||||
BEAST_EXPECT(result.value() == field_object);
|
||||
|
||||
auto const erase_result = api.sto_emplace(
|
||||
result.value(), std::nullopt, sfExchangeRate.getCode());
|
||||
BEAST_EXPECT(erase_result.has_value());
|
||||
BEAST_EXPECT(erase_result.value().size() == _source_object.size());
|
||||
BEAST_EXPECT(erase_result.value() == _source_object);
|
||||
}
|
||||
|
||||
{
|
||||
// use UINT128
|
||||
auto source_object = _source_object;
|
||||
auto field_object = *strUnHex("4100000000000000000000000000000000");
|
||||
// EmailHash: 1
|
||||
|
||||
auto const result = api.sto_emplace(
|
||||
source_object, field_object, sfEmailHash.getCode());
|
||||
BEAST_EXPECT(result.has_value());
|
||||
BEAST_EXPECT(
|
||||
result.value().size() ==
|
||||
source_object.size() + field_object.size());
|
||||
field_object.insert(
|
||||
field_object.end(), source_object.begin(), source_object.end());
|
||||
BEAST_EXPECT(result.value() == field_object);
|
||||
|
||||
auto const erase_result = api.sto_emplace(
|
||||
result.value(), std::nullopt, sfEmailHash.getCode());
|
||||
BEAST_EXPECT(erase_result.has_value());
|
||||
BEAST_EXPECT(erase_result.value().size() == _source_object.size());
|
||||
BEAST_EXPECT(erase_result.value() == _source_object);
|
||||
}
|
||||
|
||||
{
|
||||
// use UINT256
|
||||
auto source_object = _source_object;
|
||||
auto field_object = *strUnHex(
|
||||
"5E000000000000000000000000000000000000000000000000000000000000"
|
||||
"0000");
|
||||
// ObjectID:
|
||||
// "0000000000000000000000000000000000000000000000000000000000000000"
|
||||
|
||||
auto const result = api.sto_emplace(
|
||||
source_object, field_object, sfObjectID.getCode());
|
||||
BEAST_EXPECT(result.has_value());
|
||||
BEAST_EXPECT(
|
||||
result.value().size() ==
|
||||
source_object.size() + field_object.size());
|
||||
field_object.insert(
|
||||
field_object.end(), source_object.begin(), source_object.end());
|
||||
BEAST_EXPECT(result.value() == field_object);
|
||||
|
||||
auto const erase_result = api.sto_emplace(
|
||||
result.value(), std::nullopt, sfObjectID.getCode());
|
||||
BEAST_EXPECT(erase_result.has_value());
|
||||
BEAST_EXPECT(erase_result.value().size() == _source_object.size());
|
||||
BEAST_EXPECT(erase_result.value() == _source_object);
|
||||
}
|
||||
|
||||
{
|
||||
// use AMOUNT
|
||||
auto source_object = _source_object;
|
||||
|
||||
auto nativeamount = *strUnHex(
|
||||
"61999999999999999999999999999999999999999999999999999999999999"
|
||||
"999999999999999999999999999999999999");
|
||||
auto iouamount = *strUnHex("614999999999999999");
|
||||
|
||||
for (auto field_object : {nativeamount, iouamount})
|
||||
{
|
||||
auto const result = api.sto_emplace(
|
||||
source_object, field_object, sfAmount.getCode());
|
||||
BEAST_EXPECT(result.has_value());
|
||||
BEAST_EXPECT(
|
||||
result.value().size() ==
|
||||
source_object.size() + field_object.size());
|
||||
field_object.insert(
|
||||
field_object.end(),
|
||||
source_object.begin(),
|
||||
source_object.end());
|
||||
BEAST_EXPECT(result.value() == field_object);
|
||||
|
||||
auto const erase_result = api.sto_emplace(
|
||||
result.value(), std::nullopt, sfAmount.getCode());
|
||||
BEAST_EXPECT(erase_result.has_value());
|
||||
BEAST_EXPECT(
|
||||
erase_result.value().size() == _source_object.size());
|
||||
BEAST_EXPECT(erase_result.value() == _source_object);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// OBJECT
|
||||
auto source_object = _source_object;
|
||||
auto field_object = *strUnHex("E05B614000000000000064E1");
|
||||
// {"AmountEntry": {"Amount": "100"}}
|
||||
|
||||
auto const result = api.sto_emplace(
|
||||
source_object, field_object, sfAmountEntry.getCode());
|
||||
BEAST_EXPECT(result.has_value());
|
||||
BEAST_EXPECT(
|
||||
result.value().size() ==
|
||||
source_object.size() + field_object.size());
|
||||
source_object.insert(
|
||||
source_object.end(), field_object.begin(), field_object.end());
|
||||
BEAST_EXPECT(result.value() == source_object);
|
||||
|
||||
auto const erase_result = api.sto_emplace(
|
||||
result.value(), std::nullopt, sfAmountEntry.getCode());
|
||||
BEAST_EXPECT(erase_result.has_value());
|
||||
BEAST_EXPECT(erase_result.value().size() == _source_object.size());
|
||||
BEAST_EXPECT(erase_result.value() == _source_object);
|
||||
}
|
||||
|
||||
{
|
||||
// ARRAY
|
||||
auto source_object = _source_object;
|
||||
auto field_object = *strUnHex("F9EA7D04DEADBEEFE1F1");
|
||||
// {"Memos": [{"Memo":{ "MemoData": "DEADBEEF" }}]}
|
||||
|
||||
auto const result =
|
||||
api.sto_emplace(source_object, field_object, field_id);
|
||||
BEAST_EXPECTS(result.has_value(), data.second);
|
||||
auto const r = result.value();
|
||||
api.sto_emplace(source_object, field_object, sfMemos.getCode());
|
||||
BEAST_EXPECT(result.has_value());
|
||||
BEAST_EXPECT(
|
||||
r.size() == source_object.size() + field_object.size());
|
||||
field_object.insert(
|
||||
field_id < sfAccount.getCode() ? field_object.end()
|
||||
: field_object.begin(),
|
||||
source_object.begin(),
|
||||
source_object.end());
|
||||
BEAST_EXPECT(r == field_object);
|
||||
result.value().size() ==
|
||||
source_object.size() + field_object.size());
|
||||
source_object.insert(
|
||||
source_object.end(), field_object.begin(), field_object.end());
|
||||
BEAST_EXPECT(result.value() == source_object);
|
||||
|
||||
// test erase
|
||||
auto const erase_result =
|
||||
api.sto_emplace(result.value(), std::nullopt, field_id);
|
||||
BEAST_EXPECTS(erase_result.has_value(), data.second);
|
||||
auto const er = erase_result.value();
|
||||
BEAST_EXPECT(er.size() == _source_object.size());
|
||||
BEAST_EXPECT(er == _source_object);
|
||||
auto const erase_result = api.sto_emplace(
|
||||
result.value(), std::nullopt, sfMemos.getCode());
|
||||
BEAST_EXPECT(erase_result.has_value());
|
||||
BEAST_EXPECT(erase_result.value().size() == _source_object.size());
|
||||
BEAST_EXPECT(erase_result.value() == _source_object);
|
||||
}
|
||||
|
||||
{
|
||||
// UINT8
|
||||
auto source_object = _source_object;
|
||||
auto field_object = *strUnHex("00101001");
|
||||
// {"TickSize": 1}
|
||||
|
||||
auto const result = api.sto_emplace(
|
||||
source_object, field_object, sfTickSize.getCode());
|
||||
BEAST_EXPECT(result.has_value());
|
||||
BEAST_EXPECT(
|
||||
result.value().size() ==
|
||||
source_object.size() + field_object.size());
|
||||
source_object.insert(
|
||||
source_object.end(), field_object.begin(), field_object.end());
|
||||
BEAST_EXPECT(result.value() == source_object);
|
||||
|
||||
auto const erase_result = api.sto_emplace(
|
||||
result.value(), std::nullopt, sfTickSize.getCode());
|
||||
BEAST_EXPECT(erase_result.has_value());
|
||||
BEAST_EXPECT(erase_result.value().size() == _source_object.size());
|
||||
BEAST_EXPECT(erase_result.value() == _source_object);
|
||||
}
|
||||
|
||||
{
|
||||
// UINT160
|
||||
auto source_object = _source_object;
|
||||
auto field_object =
|
||||
*strUnHex("01110000000000000000000000005553440000000000");
|
||||
// {"TakerPaysCurrency": "0000000000000000000000005553440000000000"}
|
||||
|
||||
auto const result = api.sto_emplace(
|
||||
source_object, field_object, sfTakerPaysCurrency.getCode());
|
||||
BEAST_EXPECT(result.has_value());
|
||||
BEAST_EXPECT(
|
||||
result.value().size() ==
|
||||
source_object.size() + field_object.size());
|
||||
source_object.insert(
|
||||
source_object.end(), field_object.begin(), field_object.end());
|
||||
BEAST_EXPECT(result.value() == source_object);
|
||||
|
||||
auto const erase_result = api.sto_emplace(
|
||||
result.value(), std::nullopt, sfTakerPaysCurrency.getCode());
|
||||
BEAST_EXPECT(erase_result.has_value());
|
||||
BEAST_EXPECT(erase_result.value().size() == _source_object.size());
|
||||
BEAST_EXPECT(erase_result.value() == _source_object);
|
||||
}
|
||||
|
||||
{
|
||||
// PATHSET
|
||||
auto source_object = _source_object;
|
||||
auto field_object = *strUnHex(
|
||||
"0112300000000000000000000000005553440000000000054F6F784A58F9EF"
|
||||
"B0A9EB90B83464F9D166461900");
|
||||
// {"Paths": [[{ "currency": "USD", "issuer":
|
||||
// "rVnYNK9yuxBz4uP8zC8LEFokM2nqH3poc" }]]}
|
||||
|
||||
auto const result =
|
||||
api.sto_emplace(source_object, field_object, sfPaths.getCode());
|
||||
BEAST_EXPECT(result.has_value());
|
||||
BEAST_EXPECT(
|
||||
result.value().size() ==
|
||||
source_object.size() + field_object.size());
|
||||
source_object.insert(
|
||||
source_object.end(), field_object.begin(), field_object.end());
|
||||
BEAST_EXPECT(result.value() == source_object);
|
||||
|
||||
auto const erase_result = api.sto_emplace(
|
||||
result.value(), std::nullopt, sfPaths.getCode());
|
||||
BEAST_EXPECT(erase_result.has_value());
|
||||
BEAST_EXPECT(erase_result.value().size() == _source_object.size());
|
||||
BEAST_EXPECT(erase_result.value() == _source_object);
|
||||
}
|
||||
|
||||
{
|
||||
// VECTOR256
|
||||
auto source_object = _source_object;
|
||||
auto field_object = *strUnHex(
|
||||
"03132042426C4D4F1009EE67080A9B7965B44656D7714D104A72F9B4369F97"
|
||||
"ABF044EE");
|
||||
// {"Amendments":["42426C4D4F1009EE67080A9B7965B44656D7714D104A72F9B4369F97ABF044EE"]}
|
||||
|
||||
auto const result = api.sto_emplace(
|
||||
source_object, field_object, sfAmendments.getCode());
|
||||
BEAST_EXPECT(result.has_value());
|
||||
BEAST_EXPECT(
|
||||
result.value().size() ==
|
||||
source_object.size() + field_object.size());
|
||||
source_object.insert(
|
||||
source_object.end(), field_object.begin(), field_object.end());
|
||||
BEAST_EXPECT(result.value() == source_object);
|
||||
|
||||
auto const erase_result = api.sto_emplace(
|
||||
result.value(), std::nullopt, sfAmendments.getCode());
|
||||
BEAST_EXPECT(erase_result.has_value());
|
||||
BEAST_EXPECT(erase_result.value().size() == _source_object.size());
|
||||
BEAST_EXPECT(erase_result.value() == _source_object);
|
||||
}
|
||||
|
||||
{
|
||||
// UINT96
|
||||
}
|
||||
|
||||
{
|
||||
// UINT384
|
||||
}
|
||||
|
||||
{
|
||||
// UINT512
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4115,38 +4283,6 @@ public:
|
||||
PARSE_ERROR);
|
||||
}
|
||||
|
||||
{
|
||||
// Invalid: wrapped array has an invalid end marker.
|
||||
// should be "F9EA7D02BEEFE1F1" {Memos:[{Memo:{MemoData:"BEEF"}}]}
|
||||
auto const invalid_end_marker = *strUnHex("F9EA7D02BEEFE100");
|
||||
auto const result = api.sto_subarray(invalid_end_marker, 0);
|
||||
if (env.closed()->rules().enabled(fixHookAPISType))
|
||||
{
|
||||
BEAST_EXPECT(!result.has_value());
|
||||
BEAST_EXPECT(result.error() == PARSE_ERROR);
|
||||
}
|
||||
else
|
||||
{
|
||||
BEAST_EXPECT(result.value() == std::make_pair(1u, 6u));
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// Invalid: the first element is valid but a following element is
|
||||
// truncated.
|
||||
auto const invalid_tail = *strUnHex("F9EA7D02BEEFE1EA7D02BEEFF1");
|
||||
auto const result = api.sto_subarray(invalid_tail, 0);
|
||||
if (env.closed()->rules().enabled(fixHookAPISType))
|
||||
{
|
||||
BEAST_EXPECT(!result.has_value());
|
||||
BEAST_EXPECT(result.error() == PARSE_ERROR);
|
||||
}
|
||||
else
|
||||
{
|
||||
BEAST_EXPECT(result.value() == std::make_pair(1u, 6u));
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// doesn't found
|
||||
// { Memos: [{Memo: {MemoData: "BEEF"}}] }
|
||||
@@ -4202,25 +4338,6 @@ public:
|
||||
PARSE_ERROR);
|
||||
}
|
||||
|
||||
{
|
||||
// Invalid: the requested field is valid but a later field is
|
||||
// truncated.
|
||||
auto const data = *strUnHex(
|
||||
"240000000181140000000000000000000000000000000000000000");
|
||||
auto const invalid_tail = Blob{data.begin(), data.end() - 1};
|
||||
auto const result =
|
||||
api.sto_subfield(invalid_tail, sfSequence.getCode());
|
||||
if (env.closed()->rules().enabled(fixHookAPISType))
|
||||
{
|
||||
BEAST_EXPECT(!result.has_value());
|
||||
BEAST_EXPECT(result.error() == PARSE_ERROR);
|
||||
}
|
||||
else
|
||||
{
|
||||
BEAST_EXPECT(result.value() == std::make_pair(1u, 4u));
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// doesn't found
|
||||
// { Memo: {MemoData: "BEEF"} }
|
||||
@@ -4280,17 +4397,34 @@ public:
|
||||
// { AmountEntry: {Amount: "100"} }
|
||||
auto const amountEntry = *strUnHex("E05B614000000000000064E1");
|
||||
|
||||
for (auto const& data : {memos, amounts, memo, amountEntry})
|
||||
{
|
||||
// Valid data
|
||||
BEAST_EXPECT(api.sto_validate(data).value() == true);
|
||||
BEAST_EXPECT(api.sto_validate(memos).value() == true);
|
||||
BEAST_EXPECT(api.sto_validate(amounts).value() == true);
|
||||
BEAST_EXPECT(api.sto_validate(memo).value() == true);
|
||||
BEAST_EXPECT(api.sto_validate(amountEntry).value() == true);
|
||||
|
||||
// Invalid data
|
||||
auto const partial_data = Blob{data.begin(), data.end() - 1};
|
||||
BEAST_EXPECT(api.sto_validate(partial_data).value() == false);
|
||||
auto const partial_data2 = Blob{data.begin() + 1, data.end()};
|
||||
BEAST_EXPECT(api.sto_validate(partial_data2).value() == false);
|
||||
}
|
||||
// Invalid data
|
||||
BEAST_EXPECT(
|
||||
api.sto_validate(Bytes{0xFF, 0xFF, 0xFF, 0xFF}).value() == false);
|
||||
|
||||
Bytes const i_memos(&memos[0], &memos[memos.size() - 1]);
|
||||
Bytes const i_amounts(&amounts[0], &amounts[amounts.size() - 1]);
|
||||
Bytes const i_memo(&memo[0], &memo[memo.size() - 1]);
|
||||
Bytes const i_amountEntry(
|
||||
&amountEntry[0], &amountEntry[amountEntry.size() - 1]);
|
||||
BEAST_EXPECT(api.sto_validate(i_memos).value() == false);
|
||||
BEAST_EXPECT(api.sto_validate(i_amounts).value() == false);
|
||||
BEAST_EXPECT(api.sto_validate(i_memo).value() == false);
|
||||
BEAST_EXPECT(api.sto_validate(i_amountEntry).value() == false);
|
||||
|
||||
Bytes const i2_memos(&memos[1], &memos[memos.size()]);
|
||||
Bytes const i2_amounts(&amounts[1], &amounts[amounts.size()]);
|
||||
Bytes const i2_memo(&memo[1], &memo[memo.size()]);
|
||||
Bytes const i2_amountEntry(
|
||||
&amountEntry[1], &amountEntry[amountEntry.size()]);
|
||||
BEAST_EXPECT(api.sto_validate(i_memos).value() == false);
|
||||
BEAST_EXPECT(api.sto_validate(i_amounts).value() == false);
|
||||
BEAST_EXPECT(api.sto_validate(i_memo).value() == false);
|
||||
BEAST_EXPECT(api.sto_validate(i_amountEntry).value() == false);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -4783,12 +4917,9 @@ public:
|
||||
test_state_set(features);
|
||||
|
||||
test_sto_emplace(features);
|
||||
test_sto_emplace(features - fixHookAPISType);
|
||||
// test_sto_erase(features); // tested in test_sto_emplace
|
||||
test_sto_subarray(features);
|
||||
test_sto_subarray(features - fixHookAPISType);
|
||||
test_sto_subfield(features);
|
||||
test_sto_subfield(features - fixHookAPISType);
|
||||
test_sto_validate(features);
|
||||
|
||||
test_trace(features);
|
||||
|
||||
@@ -104,8 +104,8 @@ class LedgerMaster_test : public beast::unit_test::suite
|
||||
startLegSeq, txnIndex);
|
||||
BEAST_EXPECT(
|
||||
*result ==
|
||||
uint256("DCAECE52F028B9D0F7CA43CBE15AB4EF7B84A8EF5D455238992E1E"
|
||||
"DF2BDA1637"));
|
||||
uint256("0CC11AD1AD89661689F6B6148D82CB6A7101DA4D66EC843670262D"
|
||||
"0B618F5745"));
|
||||
}
|
||||
// success (second tx)
|
||||
{
|
||||
@@ -114,8 +114,8 @@ class LedgerMaster_test : public beast::unit_test::suite
|
||||
startLegSeq + 1, txnIndex);
|
||||
BEAST_EXPECT(
|
||||
*result ==
|
||||
uint256("722BD5EC9E4AE36E724B6223F5A5887927AADFD0B18A3053AAE57F"
|
||||
"434F8C9FE8"));
|
||||
uint256("DCAECE52F028B9D0F7CA43CBE15AB4EF7B84A8EF5D455238992E1E"
|
||||
"DF2BDA1637"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1174,11 +1174,11 @@ class MPToken_test : public beast::unit_test::suite
|
||||
JsonOptions::none)[sfAffectedNodes.fieldName];
|
||||
// Issuer got 10 in the transfer fees
|
||||
BEAST_EXPECT(
|
||||
meta[0u][sfModifiedNode.fieldName][sfFinalFields.fieldName]
|
||||
meta[2u][sfModifiedNode.fieldName][sfFinalFields.fieldName]
|
||||
[sfOutstandingAmount.fieldName] == "9990");
|
||||
// Destination account got 9'990
|
||||
BEAST_EXPECT(
|
||||
meta[3u][sfModifiedNode.fieldName][sfFinalFields.fieldName]
|
||||
meta[0u][sfModifiedNode.fieldName][sfFinalFields.fieldName]
|
||||
[sfMPTAmount.fieldName] == "9990");
|
||||
// Source account spent 10'000
|
||||
BEAST_EXPECT(
|
||||
@@ -1910,8 +1910,8 @@ class MPToken_test : public beast::unit_test::suite
|
||||
env.tx()->getJson(JsonOptions::none)[jss::hash].asString()};
|
||||
BEAST_EXPECTS(
|
||||
txHash ==
|
||||
"61041108F0DAD50BAFD2F7B1102AC70B283EBDCED194CEBAF04D0184E4A70B"
|
||||
"02",
|
||||
"42175D43E3D236A86B0F1B07182A1B2C78AD382372CFA09A48239C395FFFEE"
|
||||
"30",
|
||||
txHash);
|
||||
Json::Value const meta = env.rpc("tx", txHash)[jss::result][jss::meta];
|
||||
auto const id = meta[jss::mpt_issuance_id].asString();
|
||||
@@ -1919,7 +1919,7 @@ class MPToken_test : public beast::unit_test::suite
|
||||
BEAST_EXPECT(meta.isMember(jss::mpt_issuance_id));
|
||||
BEAST_EXPECT(id == to_string(mptAlice.issuanceID()));
|
||||
BEAST_EXPECTS(
|
||||
id == "00000002AE123A8556F3CF91154711376AFB0F894F832B3D", id);
|
||||
id == "00000001AE123A8556F3CF91154711376AFB0F894F832B3D", id);
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -8279,16 +8279,16 @@ private:
|
||||
|
||||
// validate the emitted txn ids
|
||||
std::vector<std::string> const txIds = {
|
||||
"2FBEF981BAC322225D13C274D0118FECC1B93A89EDBC0AB3CBD8B30A1591024E",
|
||||
"3C0A12F6A322486ABDA4180DC2B424BD252FD2FA6A33553B0B5D1526C020C475",
|
||||
"AFFD55D757136FC0BE665F16E0D8CFBDC0D37CF85B5D3329BE1DBD3A44BB281C",
|
||||
"BBE43C01068CE511DBF24F84A928E481451FF448AF2493E1A9BE099628D1F523",
|
||||
"45970753F7B52448C23D66E5E19DBBC65F5BE6055C04AC3BA9896D7D67891DB1",
|
||||
"97F4E09B2AEF4D798D378B1390B0624A68F304247B358334167E93217D502A16",
|
||||
"770D3B786C8E3BEF560D9589B02557B0BDFC1C7FD15C6CF42E86068A6A411EFC",
|
||||
"239DFA86EE8391418170D7FDF5AA8B8EEE0BBEF5E9F928BC29C23EEF6F1858AD",
|
||||
"9FAA93AC6340C87B5C722FF0E8D4D3F39B9A0CA5E9799EC4CE316BFD5E379540",
|
||||
"8672B2D8AA5E9EF86929B8662FE436541B2F739052732020B874BC304601C4DB",
|
||||
"9610F73CDD6590EB6B3C82E5EC55D4B4C80CD7128B98AA556F7EC9DD96AE7056",
|
||||
"2F4582A29272390C0C25A80D4A3BCE5A14ACE6D86D8D0CB2C57719EB6FA881AE",
|
||||
"89A301CFEF0DD781AB9032A6A2DCE0937BC0119D2CDD06033B8B2FD80968E519",
|
||||
"DD8721B59024E168480B4DF8F8E93778601F0BD2E77FC991F3DA1182F5AD8B1E",
|
||||
"5D735C2EE3CB8289F8E11621FDC9565F9D6D67F3AE59D65332EACE591D67945F",
|
||||
"F02470E01731C968881AF4CBDEC90BB9E1F7AB0BE1CC22AF15451FB6D191096D",
|
||||
"8AD65E541DECD49B1693F8C17DFD8A2B906F49C673C4FD2034FF772E2BE50C30",
|
||||
"9F225229059CCC6257814D03C107884CF588C1C246A89ADFC16E50DF671B834C",
|
||||
"13C2A54A14BADF3648CED05175E1CCAD713F7E5EA56D9735CF8813CD5551F281",
|
||||
"87C60F41A96554587CED289F83F52DEE3CF670EEB189B067E6066B9A06056ADF",
|
||||
};
|
||||
Json::Value params;
|
||||
params[jss::transaction] =
|
||||
|
||||
+78
-540
@@ -1355,16 +1355,11 @@ public:
|
||||
auto const baseFee = drops[jss::base_fee_no_hooks];
|
||||
BEAST_EXPECT(baseFee == to_string(feeDrops));
|
||||
auto const openLedgerFee = drops[jss::open_ledger_fee];
|
||||
BEAST_EXPECTS(
|
||||
openLedgerFee == expected,
|
||||
"openLedgerFee: " + openLedgerFee.asString() +
|
||||
", expected: " + expected);
|
||||
BEAST_EXPECT(openLedgerFee == expected);
|
||||
|
||||
// verify hooks fee
|
||||
auto const hooksFee = jrr[jss::result][jss::fee_hooks_feeunits];
|
||||
BEAST_EXPECTS(
|
||||
hooksFee == expected,
|
||||
"hooksFee: " + hooksFee.asString() + ", expected: " + expected);
|
||||
BEAST_EXPECT(hooksFee == expected);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -1374,8 +1369,7 @@ public:
|
||||
using namespace jtx;
|
||||
Env env{*this, features};
|
||||
|
||||
bool const hookOnV2 = env.current()->rules().enabled(featureHookOnV2) ||
|
||||
env.current()->rules().enabled(featureHookOnV2_1);
|
||||
bool const hookOnV2 = env.current()->rules().enabled(featureHookOnV2);
|
||||
|
||||
auto const alice = Account{"alice"};
|
||||
auto const bob = Account{"bob"};
|
||||
@@ -1538,10 +1532,12 @@ public:
|
||||
jv.removeMember(jss::HookOn);
|
||||
jv[jss::HookOnIncoming] =
|
||||
"0000000000000000000000000000000000000000000000000000000000"
|
||||
"000000";
|
||||
"0000"
|
||||
"00";
|
||||
jv[jss::HookOnOutgoing] =
|
||||
"0000000000000000000000000000000000000000000000000000000000"
|
||||
"000001";
|
||||
"0000"
|
||||
"01";
|
||||
env(ripple::test::jtx::hook(alice, {{jv}}, 0),
|
||||
M("Execution: Install"),
|
||||
HSFEE);
|
||||
@@ -1606,10 +1602,12 @@ public:
|
||||
jv.removeMember(jss::HookOn);
|
||||
jv[jss::HookOnIncoming] =
|
||||
"fffffffffffffffffffffffffffffffffffffff7ffffffffffffffffff"
|
||||
"bfffff"; // Invoke high
|
||||
"bfff"
|
||||
"ff"; // Invoke high
|
||||
jv[jss::HookOnOutgoing] =
|
||||
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
|
||||
"bffffe"; // Payment high
|
||||
"bfff"
|
||||
"fe"; // Payment high
|
||||
env(ripple::test::jtx::hook(alice, {{jv}}, 0),
|
||||
M("Execution: Install"),
|
||||
HSFEE);
|
||||
@@ -1620,7 +1618,8 @@ public:
|
||||
jv[jss::Flags] = hsfOVERRIDE;
|
||||
jv[jss::HookOn] =
|
||||
"0000000000000000000000000000000000000000000000000000000000"
|
||||
"000000";
|
||||
"0000"
|
||||
"00";
|
||||
env(ripple::test::jtx::hook(alice, {{jv}}, 0),
|
||||
M("Execution: Install"),
|
||||
HSFEE);
|
||||
@@ -1654,285 +1653,18 @@ public:
|
||||
deleteHook(alice);
|
||||
}
|
||||
|
||||
for (auto const& withFix : {false, true})
|
||||
{
|
||||
// test featureHookOnV2_1 preflight
|
||||
auto f = features - featureHookOnV2 - featureHookOnV2_1;
|
||||
if (withFix)
|
||||
f = f | featureHookOnV2_1;
|
||||
else
|
||||
f = f | featureHookOnV2;
|
||||
Env env{*this, f};
|
||||
|
||||
env.fund(XRP(10000), alice, bob);
|
||||
env.close();
|
||||
|
||||
TER const expected =
|
||||
env.current()->rules().enabled(featureHookOnV2_1)
|
||||
? TER(temMALFORMED)
|
||||
: TER(tesSUCCESS);
|
||||
|
||||
// 0. only one direction HookOn is not allowed
|
||||
for (auto const& key : {jss::HookOnIncoming, jss::HookOnOutgoing})
|
||||
{
|
||||
auto noopJv = Json::Value{};
|
||||
noopJv[key] =
|
||||
"fffffffffffffffffffffffffffffffffffffff7ffffffffffffffffff"
|
||||
"bfffff";
|
||||
|
||||
env(ripple::test::jtx::hook(alice, {{noopJv}}, 0),
|
||||
M("Lone direction HookOn NOOP"),
|
||||
HSFEE,
|
||||
ter(expected));
|
||||
env.close();
|
||||
|
||||
if (!withFix)
|
||||
BEAST_EXPECT(!env.le(keylet::hook(alice)));
|
||||
}
|
||||
|
||||
// 1. Create Hook with HookOnIncoming/Outgoing to alice
|
||||
auto jv = hso(accept_wasm);
|
||||
jv.removeMember(jss::HookOn);
|
||||
jv[jss::HookOnIncoming] =
|
||||
"fffffffffffffffffffffffffffffffffffffff7ffffffffffffffffff"
|
||||
"bfffff"; // Invoke high
|
||||
jv[jss::HookOnOutgoing] =
|
||||
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
|
||||
"bffffe"; // Payment high
|
||||
env(ripple::test::jtx::hook(alice, {{jv}}, 0), HSFEE);
|
||||
env.close();
|
||||
|
||||
// 2. Install Hook with HookOn, Incoming/Outgoing to bob
|
||||
jv = Json::Value{};
|
||||
jv[jss::HookHash] = accept_hash_str;
|
||||
jv[jss::Flags] = hsfOVERRIDE;
|
||||
jv[jss::HookOn] =
|
||||
"0000000000000000000000000000000000000000000000000000000000"
|
||||
"000000";
|
||||
jv[jss::HookOnIncoming] =
|
||||
"fffffffffffffffffffffffffffffffffffffff7ffffffffffffffffff"
|
||||
"bfffff"; // Invoke high
|
||||
jv[jss::HookOnOutgoing] =
|
||||
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
|
||||
"bffffe"; // Payment high
|
||||
env(ripple::test::jtx::hook(bob, {{jv}}, 0), HSFEE, ter(expected));
|
||||
env.close();
|
||||
|
||||
// 3. Update Hook with HookOn, Incoming/Outgoing to alice
|
||||
jv = Json::Value{};
|
||||
jv[jss::HookOn] =
|
||||
"0000000000000000000000000000000000000000000000000000000000"
|
||||
"000000";
|
||||
jv[jss::HookOnIncoming] =
|
||||
"fffffffffffffffffffffffffffffffffffffff7ffffffffffffffffff"
|
||||
"bfffff"; // Invoke high
|
||||
jv[jss::HookOnOutgoing] =
|
||||
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
|
||||
"bffffe"; // Payment high
|
||||
env(ripple::test::jtx::hook(alice, {{jv}}, 0),
|
||||
HSFEE,
|
||||
ter(expected));
|
||||
env.close();
|
||||
}
|
||||
|
||||
for (auto const& withFix : {false, true})
|
||||
{
|
||||
// test featureHookOnV2_1 install/update
|
||||
auto f = features - featureHookOnV2 - featureHookOnV2_1;
|
||||
if (withFix)
|
||||
f = f | featureHookOnV2_1;
|
||||
else
|
||||
f = f | featureHookOnV2;
|
||||
|
||||
{
|
||||
// install:: HookDefinition: HookOn -> Hook:Incoming/Outgoing
|
||||
// update:: Hook: HookOn -> Hook:Incoming/Outgoing
|
||||
Env env{*this, f};
|
||||
|
||||
env.fund(XRP(10000), alice, bob);
|
||||
env.close();
|
||||
|
||||
// Create Hook with HookOn to alice
|
||||
auto jv = hso(accept_wasm);
|
||||
jv.removeMember(jss::HookOn);
|
||||
jv[jss::HookOn] =
|
||||
"0000000000000000000000000000000000000000000000000000000000"
|
||||
"000000";
|
||||
env(ripple::test::jtx::hook(alice, {{jv}}, 0), HSFEE);
|
||||
env.close();
|
||||
|
||||
// Install Hook with Incoming/Outgoing to bob
|
||||
jv = Json::Value{};
|
||||
jv[jss::HookHash] = accept_hash_str;
|
||||
jv[jss::Flags] = hsfOVERRIDE;
|
||||
jv[jss::HookOnIncoming] =
|
||||
"fffffffffffffffffffffffffffffffffffffff7ffffffffffffffffff"
|
||||
"bfffff"; // Invoke high
|
||||
jv[jss::HookOnOutgoing] =
|
||||
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
|
||||
"bffffe"; // Payment high
|
||||
env(ripple::test::jtx::hook(bob, {{jv}}, 0), HSFEE);
|
||||
env.close();
|
||||
{
|
||||
auto const hooksObj = env.le(keylet::hook(bob));
|
||||
BEAST_EXPECT(hooksObj && hooksObj->isFieldPresent(sfHooks));
|
||||
auto const& hooks = hooksObj->getFieldArray(sfHooks);
|
||||
BEAST_EXPECT(hooks.size() == 1);
|
||||
auto const& h = hooks[0];
|
||||
BEAST_EXPECT(!h.isFieldPresent(sfHookOn));
|
||||
BEAST_EXPECT(h.isFieldPresent(sfHookOnOutgoing));
|
||||
BEAST_EXPECT(h.isFieldPresent(sfHookOnIncoming));
|
||||
}
|
||||
|
||||
// Update Hook with HookOn, Incoming/Outgoing to alice
|
||||
jv = Json::Value{};
|
||||
jv[jss::HookOnIncoming] =
|
||||
"fffffffffffffffffffffffffffffffffffffff7ffffffffffffffffff"
|
||||
"bfffff"; // Invoke high
|
||||
jv[jss::HookOnOutgoing] =
|
||||
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
|
||||
"bffffe"; // Payment high
|
||||
env(ripple::test::jtx::hook(alice, {{jv}}, 0), HSFEE);
|
||||
env.close();
|
||||
{
|
||||
auto const hooksObj = env.le(keylet::hook(alice));
|
||||
BEAST_EXPECT(hooksObj && hooksObj->isFieldPresent(sfHooks));
|
||||
auto const& hooks = hooksObj->getFieldArray(sfHooks);
|
||||
BEAST_EXPECT(hooks.size() == 1);
|
||||
auto const& h = hooks[0];
|
||||
BEAST_EXPECT(!h.isFieldPresent(sfHookOn));
|
||||
BEAST_EXPECT(h.isFieldPresent(sfHookOnOutgoing));
|
||||
BEAST_EXPECT(h.isFieldPresent(sfHookOnIncoming));
|
||||
}
|
||||
|
||||
// Re-update Hook with HookOn, Incoming/Outgoing to alice
|
||||
jv = Json::Value{};
|
||||
jv[jss::HookOn] =
|
||||
"fffffffffffffffffffffffffffffffffffffff7ffffffffffffffffff"
|
||||
"bfffff";
|
||||
env(ripple::test::jtx::hook(alice, {{jv}}, 0), HSFEE);
|
||||
env.close();
|
||||
{
|
||||
auto const hooksObj = env.le(keylet::hook(alice));
|
||||
BEAST_EXPECT(hooksObj && hooksObj->isFieldPresent(sfHooks));
|
||||
auto const& hooks = hooksObj->getFieldArray(sfHooks);
|
||||
BEAST_EXPECT(hooks.size() == 1);
|
||||
auto const& h = hooks[0];
|
||||
BEAST_EXPECT(h.isFieldPresent(sfHookOn));
|
||||
if (withFix)
|
||||
{
|
||||
BEAST_EXPECT(!h.isFieldPresent(sfHookOnOutgoing));
|
||||
BEAST_EXPECT(!h.isFieldPresent(sfHookOnIncoming));
|
||||
}
|
||||
else
|
||||
{
|
||||
BEAST_EXPECT(h.isFieldPresent(sfHookOnOutgoing));
|
||||
BEAST_EXPECT(h.isFieldPresent(sfHookOnIncoming));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// install:: HookDefinition: Incoming/Outgoing -> Hook:HookOn
|
||||
// update:: Hook: Incoming/Outgoing -> Hook:HookOn
|
||||
Env env{*this, f};
|
||||
|
||||
env.fund(XRP(10000), alice, bob);
|
||||
env.close();
|
||||
|
||||
// Create Hook with HookOn to alice
|
||||
auto jv = hso(accept_wasm);
|
||||
jv.removeMember(jss::HookOn);
|
||||
jv[jss::HookOnIncoming] =
|
||||
"fffffffffffffffffffffffffffffffffffffff7ffffffffffffffffff"
|
||||
"bfffff"; // Invoke high
|
||||
jv[jss::HookOnOutgoing] =
|
||||
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
|
||||
"bffffe"; // Payment high
|
||||
env(ripple::test::jtx::hook(alice, {{jv}}, 0), HSFEE);
|
||||
env.close();
|
||||
|
||||
// Install Hook with Incoming/Outgoing to bob
|
||||
jv = Json::Value{};
|
||||
jv[jss::HookHash] = accept_hash_str;
|
||||
jv[jss::Flags] = hsfOVERRIDE;
|
||||
jv[jss::HookOn] =
|
||||
"0000000000000000000000000000000000000000000000000000000000"
|
||||
"000000";
|
||||
env(ripple::test::jtx::hook(bob, {{jv}}, 0), HSFEE);
|
||||
env.close();
|
||||
{
|
||||
auto const hooksObj = env.le(keylet::hook(bob));
|
||||
BEAST_EXPECT(hooksObj && hooksObj->isFieldPresent(sfHooks));
|
||||
auto const& hooks = hooksObj->getFieldArray(sfHooks);
|
||||
BEAST_EXPECT(hooks.size() == 1);
|
||||
auto const& h = hooks[0];
|
||||
BEAST_EXPECT(h.isFieldPresent(sfHookOn));
|
||||
BEAST_EXPECT(!h.isFieldPresent(sfHookOnOutgoing));
|
||||
BEAST_EXPECT(!h.isFieldPresent(sfHookOnIncoming));
|
||||
}
|
||||
|
||||
// Update Hook with HookOn, Incoming/Outgoing to alice
|
||||
jv = Json::Value{};
|
||||
jv[jss::HookOn] =
|
||||
"0000000000000000000000000000000000000000000000000000000000"
|
||||
"000000";
|
||||
env(ripple::test::jtx::hook(alice, {{jv}}, 0), HSFEE);
|
||||
env.close();
|
||||
{
|
||||
auto const hooksObj = env.le(keylet::hook(alice));
|
||||
BEAST_EXPECT(hooksObj && hooksObj->isFieldPresent(sfHooks));
|
||||
auto const& hooks = hooksObj->getFieldArray(sfHooks);
|
||||
BEAST_EXPECT(hooks.size() == 1);
|
||||
auto const& h = hooks[0];
|
||||
if (withFix)
|
||||
BEAST_EXPECT(h.isFieldPresent(sfHookOn));
|
||||
// Cause different results between gcc and clang due to
|
||||
// undefined behavior so we don't test it
|
||||
// else
|
||||
// BEAST_EXPECT(!h.isFieldPresent(sfHookOn));
|
||||
BEAST_EXPECT(!h.isFieldPresent(sfHookOnOutgoing));
|
||||
BEAST_EXPECT(!h.isFieldPresent(sfHookOnIncoming));
|
||||
}
|
||||
|
||||
// Re-update Hook with HookOn, Incoming/Outgoing to alice
|
||||
jv = Json::Value{};
|
||||
jv[jss::HookOnIncoming] =
|
||||
"fffffffffffffffffffffffffffffffffffffff7ffffffffffffffffff"
|
||||
"bffff0"; // Invoke high
|
||||
jv[jss::HookOnOutgoing] =
|
||||
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
|
||||
"bffff1"; // Payment high
|
||||
env(ripple::test::jtx::hook(alice, {{jv}}, 0), HSFEE);
|
||||
env.close();
|
||||
{
|
||||
auto const hooksObj = env.le(keylet::hook(alice));
|
||||
BEAST_EXPECT(hooksObj && hooksObj->isFieldPresent(sfHooks));
|
||||
auto const& hooks = hooksObj->getFieldArray(sfHooks);
|
||||
BEAST_EXPECT(hooks.size() == 1);
|
||||
auto const& h = hooks[0];
|
||||
if (withFix)
|
||||
BEAST_EXPECT(!h.isFieldPresent(sfHookOn));
|
||||
// Cause different results between gcc and clang due to
|
||||
// undefined behavior so we don't test it
|
||||
// else
|
||||
// BEAST_EXPECT(h.isFieldPresent(sfHookOn));
|
||||
BEAST_EXPECT(h.isFieldPresent(sfHookOnOutgoing));
|
||||
BEAST_EXPECT(h.isFieldPresent(sfHookOnIncoming));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fee RPC
|
||||
{
|
||||
auto jv = hso(accept_wasm);
|
||||
jv.removeMember(jss::HookOn);
|
||||
jv[jss::HookOnIncoming] =
|
||||
"fffffffffffffffffffffffffffffffffffffff7ffffffffffffffffff"
|
||||
"bfffff"; // Invoke high
|
||||
"bfff"
|
||||
"ff"; // Invoke high
|
||||
jv[jss::HookOnOutgoing] =
|
||||
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
|
||||
"bffffe"; // Payment high
|
||||
"bfff"
|
||||
"fe"; // Payment high
|
||||
env(ripple::test::jtx::hook(alice, {{jv}}, 0), HSFEE);
|
||||
env.close();
|
||||
|
||||
@@ -2268,64 +2000,11 @@ public:
|
||||
BEAST_EXPECT(!env.meta()->isFieldPresent(sfHookEmissions));
|
||||
}
|
||||
|
||||
// Call named hook with the wrong hook name (size == 0)
|
||||
for (auto const fix : {true, false})
|
||||
{
|
||||
auto f = features - fixHookNameValidation;
|
||||
if (fix)
|
||||
f = f | fixHookNameValidation;
|
||||
Env env{*this, f};
|
||||
|
||||
env.fund(XRP(10000), alice);
|
||||
// execute both named and non-named hooks
|
||||
|
||||
auto jv = invoke::invoke(alice);
|
||||
jv[jss::HookName] = "";
|
||||
|
||||
auto const expected = fix ? ter(temMALFORMED) : ter(tesSUCCESS);
|
||||
env(jv,
|
||||
M("Call named hook with the wrong hook name (size == 0)"),
|
||||
HSFEE,
|
||||
ter(expected));
|
||||
env.close();
|
||||
}
|
||||
|
||||
// Call a named hook with a HookName containing a noncharacter
|
||||
// (U+FFFF followed by 'A'). Well-formed UTF-8, but always rejected.
|
||||
{
|
||||
Env env{*this, features};
|
||||
env.fund(XRP(10000), alice);
|
||||
|
||||
auto jv = invoke::invoke(alice);
|
||||
jv[jss::HookName] = "EFBFBF41";
|
||||
env(jv,
|
||||
M("Call named hook with a noncharacter in the hook name"),
|
||||
HSFEE,
|
||||
ter(temMALFORMED));
|
||||
env.close();
|
||||
}
|
||||
|
||||
// Call a named hook with a HookName ending in a truncated sequence.
|
||||
{
|
||||
Env env{*this, features};
|
||||
env.fund(XRP(10000), alice);
|
||||
|
||||
auto jv = invoke::invoke(alice);
|
||||
jv[jss::HookName] = "414243E0";
|
||||
env(jv,
|
||||
M("Call named hook with a truncated hook name"),
|
||||
HSFEE,
|
||||
ter(temMALFORMED));
|
||||
env.close();
|
||||
}
|
||||
|
||||
// Call named hook with the wrong hook name (size > 0)
|
||||
// Call named hook with the wrong hook name
|
||||
{
|
||||
auto jv = invoke::invoke(alice);
|
||||
jv[jss::HookName] = "41424345";
|
||||
env(jv,
|
||||
M("Call named hook with the wrong hook name (size > 0)"),
|
||||
HSFEE);
|
||||
env(jv, M("Call named hook with the wrong hook name"), HSFEE);
|
||||
env.close();
|
||||
// execute only non-named hook
|
||||
BEAST_EXPECT(!env.meta()->isFieldPresent(sfHookEmissions));
|
||||
@@ -3324,37 +3003,25 @@ public:
|
||||
testInferHookSetOperation()
|
||||
{
|
||||
testcase("Test operation inference");
|
||||
using namespace jtx;
|
||||
|
||||
auto const alice = Account{"alice"};
|
||||
Env env{*this};
|
||||
env.fund(XRP(10000), alice);
|
||||
env(noop(alice));
|
||||
|
||||
SetHookCtx shCtx{
|
||||
.j = env.app().journal("SetHook"),
|
||||
.tx = *(env.tx()),
|
||||
.app = env.app(),
|
||||
.rules = env.current()->rules()};
|
||||
|
||||
// hsoNOOP
|
||||
{
|
||||
STObject hso{sfHook};
|
||||
BEAST_EXPECT(SetHook::inferOperation(shCtx, hso) == hsoNOOP);
|
||||
BEAST_EXPECT(SetHook::inferOperation(hso) == hsoNOOP);
|
||||
}
|
||||
|
||||
// hsoCREATE
|
||||
{
|
||||
STObject hso{sfHook};
|
||||
hso.setFieldVL(sfCreateCode, {1}); // non-empty create code
|
||||
BEAST_EXPECT(SetHook::inferOperation(shCtx, hso) == hsoCREATE);
|
||||
BEAST_EXPECT(SetHook::inferOperation(hso) == hsoCREATE);
|
||||
}
|
||||
|
||||
// hsoDELETE
|
||||
{
|
||||
STObject hso{sfHook};
|
||||
hso.setFieldVL(sfCreateCode, ripple::Blob{}); // empty create code
|
||||
BEAST_EXPECT(SetHook::inferOperation(shCtx, hso) == hsoDELETE);
|
||||
BEAST_EXPECT(SetHook::inferOperation(hso) == hsoDELETE);
|
||||
}
|
||||
|
||||
// hsoINSTALL
|
||||
@@ -3362,7 +3029,7 @@ public:
|
||||
STObject hso{sfHook};
|
||||
hso.setFieldH256(
|
||||
sfHookHash, uint256{beast::zero}); // all zeros hook hash
|
||||
BEAST_EXPECT(SetHook::inferOperation(shCtx, hso) == hsoINSTALL);
|
||||
BEAST_EXPECT(SetHook::inferOperation(hso) == hsoINSTALL);
|
||||
}
|
||||
|
||||
// hsoNSDELETE
|
||||
@@ -3371,14 +3038,14 @@ public:
|
||||
hso.setFieldH256(
|
||||
sfHookNamespace, uint256{beast::zero}); // all zeros hook hash
|
||||
hso.setFieldU32(sfFlags, hsfNSDELETE);
|
||||
BEAST_EXPECT(SetHook::inferOperation(shCtx, hso) == hsoNSDELETE);
|
||||
BEAST_EXPECT(SetHook::inferOperation(hso) == hsoNSDELETE);
|
||||
}
|
||||
|
||||
// hsoUPDATE
|
||||
{
|
||||
STObject hso{sfHook};
|
||||
hso.setFieldH256(sfHookOn, UINT256_BIT[0]);
|
||||
BEAST_EXPECT(SetHook::inferOperation(shCtx, hso) == hsoUPDATE);
|
||||
BEAST_EXPECT(SetHook::inferOperation(hso) == hsoUPDATE);
|
||||
}
|
||||
|
||||
// hsoINVALID
|
||||
@@ -3387,39 +3054,7 @@ public:
|
||||
hso.setFieldVL(sfCreateCode, {1}); // non-empty create code
|
||||
hso.setFieldH256(
|
||||
sfHookHash, uint256{beast::zero}); // all zeros hook hash
|
||||
BEAST_EXPECT(SetHook::inferOperation(shCtx, hso) == hsoINVALID);
|
||||
|
||||
for (auto fix : {true, false})
|
||||
{
|
||||
auto feature = supported_amendments() - featureHookOnV2 -
|
||||
featureHookOnV2_1;
|
||||
if (fix)
|
||||
feature = feature | featureHookOnV2_1;
|
||||
else
|
||||
feature = feature | featureHookOnV2;
|
||||
Env env{*this, feature};
|
||||
SetHookCtx shCtx{
|
||||
.j = env.app().journal("SetHook"),
|
||||
.tx = *env.tx(),
|
||||
.app = env.app(),
|
||||
.rules = env.current()->rules()};
|
||||
|
||||
STObject hso2{sfHook};
|
||||
hso2.setFieldH256(
|
||||
sfHookOnOutgoing,
|
||||
uint256{beast::zero}); // all zeros hook on outgoing
|
||||
BEAST_EXPECT(
|
||||
SetHook::inferOperation(shCtx, hso2) ==
|
||||
(fix ? hsoINVALID : hsoNOOP));
|
||||
|
||||
STObject hso3{sfHook};
|
||||
hso3.setFieldH256(
|
||||
sfHookOnIncoming,
|
||||
uint256{beast::zero}); // all zeros hook on incoming
|
||||
BEAST_EXPECT(
|
||||
SetHook::inferOperation(shCtx, hso3) ==
|
||||
(fix ? hsoINVALID : hsoNOOP));
|
||||
}
|
||||
BEAST_EXPECT(SetHook::inferOperation(hso) == hsoINVALID);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3491,81 +3126,6 @@ public:
|
||||
env.close();
|
||||
}
|
||||
|
||||
void
|
||||
test_exit_out_of_bounds(FeatureBitset features)
|
||||
{
|
||||
testcase("Test accept()/rollback() with out of bounds reason");
|
||||
using namespace jtx;
|
||||
|
||||
// reason string pointer outside of wasm memory, then fall through
|
||||
TestHook accept_oob_wasm = wasm[R"[test.hook](
|
||||
#include <stdint.h>
|
||||
extern int32_t _g (uint32_t id, uint32_t maxiter);
|
||||
extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code);
|
||||
int64_t hook(uint32_t reserved )
|
||||
{
|
||||
_g(1,1);
|
||||
accept(0xFFFFFF00U, 32, 42);
|
||||
return 0;
|
||||
}
|
||||
)[test.hook]"];
|
||||
|
||||
TestHook rollback_oob_wasm = wasm[R"[test.hook](
|
||||
#include <stdint.h>
|
||||
extern int32_t _g (uint32_t id, uint32_t maxiter);
|
||||
extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code);
|
||||
int64_t hook(uint32_t reserved )
|
||||
{
|
||||
_g(1,1);
|
||||
rollback(0xFFFFFF00U, 32, 42);
|
||||
return 0;
|
||||
}
|
||||
)[test.hook]"];
|
||||
|
||||
for (bool const withFix : {true, false})
|
||||
{
|
||||
for (auto const& hook_wasm : {accept_oob_wasm, rollback_oob_wasm})
|
||||
{
|
||||
Env env{
|
||||
*this,
|
||||
withFix ? features : features - fixHookExitOutOfBounds};
|
||||
|
||||
auto const alice = Account{"alice"};
|
||||
auto const bob = Account{"bob"};
|
||||
env.fund(XRP(10000), alice);
|
||||
env.fund(XRP(10000), bob);
|
||||
|
||||
env(ripple::test::jtx::hook(alice, {{hso(hook_wasm)}}, 0),
|
||||
M("Install OOB exit hook"),
|
||||
HSFEE);
|
||||
env.close();
|
||||
|
||||
env(pay(bob, alice, XRP(1)),
|
||||
M("Test OOB exit hook"),
|
||||
fee(XRP(1)),
|
||||
ter(tecHOOK_REJECTED));
|
||||
env.close();
|
||||
|
||||
auto const meta = env.meta();
|
||||
BEAST_REQUIRE(meta && meta->isFieldPresent(sfHookExecutions));
|
||||
auto const& execs = meta->getFieldArray(sfHookExecutions);
|
||||
BEAST_REQUIRE(execs.size() == 1);
|
||||
|
||||
// with the fix: ROLLBACK with OUT_OF_BOUNDS (-1)
|
||||
// without: UNSET (fixXahauV3) with default -1
|
||||
BEAST_EXPECT(
|
||||
execs[0].getFieldU8(sfHookResult) ==
|
||||
static_cast<uint8_t>(
|
||||
withFix ? hook_api::ExitType::ROLLBACK
|
||||
: hook_api::ExitType::UNSET));
|
||||
BEAST_EXPECT(
|
||||
execs[0].getFieldU64(sfHookReturnCode) ==
|
||||
0x8000000000000001ULL);
|
||||
BEAST_EXPECT(execs[0].getFieldVL(sfHookReturnString).empty());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testGuards(FeatureBitset features)
|
||||
{
|
||||
@@ -4717,10 +4277,10 @@ public:
|
||||
|
||||
uint8_t expected1[49] = {
|
||||
0xEDU, 0x20U, 0x2EU, 0x00U, 0x00U, 0x00U, 0x01U, 0x3DU, 0x00U, 0x00U,
|
||||
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x01U, 0x5BU, 0xBCU, 0x09U, 0x95U,
|
||||
0xFDU, 0x03U, 0x0FU, 0xF1U, 0x85U, 0xF4U, 0xECU, 0xACU, 0x94U, 0xDBU,
|
||||
0x02U, 0x19U, 0x26U, 0xFEU, 0x58U, 0xD4U, 0x8DU, 0x48U, 0x21U, 0xCCU,
|
||||
0x82U, 0x2EU, 0x4AU, 0x6AU, 0xC9U, 0xC8U, 0xC6U, 0x7CU, 0x88U
|
||||
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x01U, 0x5BU, 0xB8U, 0x05U, 0xD6U,
|
||||
0xC3U, 0x52U, 0xDFU, 0x7AU, 0x27U, 0x76U, 0x6DU, 0xC0U, 0x20U, 0x47U,
|
||||
0xB7U, 0x64U, 0x22U, 0x5AU, 0xB7U, 0x5DU, 0xF3U, 0xFAU, 0x0DU, 0xE3U,
|
||||
0xBDU, 0xC6U, 0x40U, 0xBAU, 0xD0U, 0x0AU, 0x66U, 0xEBU, 0x68U,
|
||||
};
|
||||
// 0x5CU
|
||||
// EmitNonce 32bytes
|
||||
@@ -12495,77 +12055,57 @@ public:
|
||||
)[test.hook]"];
|
||||
HASH_WASM(hook);
|
||||
|
||||
for (bool const with20251128 : {true, false})
|
||||
for (auto isfixHookAPI20251128 : {true, false})
|
||||
{
|
||||
for (bool const withSType : {true, false})
|
||||
Env env{
|
||||
*this,
|
||||
isfixHookAPI20251128 ? features | fixHookAPI20251128
|
||||
: features - fixHookAPI20251128};
|
||||
env.fund(XRP(10000), alice, bob);
|
||||
env.close();
|
||||
|
||||
// install the hook on alice
|
||||
env(ripple::test::jtx::hook(
|
||||
alice, {{hso(hook_wasm, overrideFlag)}}, 0),
|
||||
M("set sto_subarray"),
|
||||
HSFEE);
|
||||
env.close();
|
||||
EXPECT_HOOK_FEE(hook, 19);
|
||||
|
||||
// invoke the hook
|
||||
env(pay(bob, alice, XRP(1)),
|
||||
M("test sto_subarray"),
|
||||
fee(XRP(1)));
|
||||
env.close();
|
||||
|
||||
auto const meta = env.meta();
|
||||
BEAST_REQUIRE(meta);
|
||||
BEAST_REQUIRE(meta->isFieldPresent(sfHookExecutions));
|
||||
auto const hookExecutions =
|
||||
meta->getFieldArray(sfHookExecutions);
|
||||
BEAST_REQUIRE(hookExecutions.size() == 1);
|
||||
auto const hookExecution = hookExecutions[0];
|
||||
BEAST_REQUIRE(hookExecution.isFieldPresent(sfHookReturnCode));
|
||||
auto const returnCode =
|
||||
hookExecution.getFieldU64(sfHookReturnCode);
|
||||
if (isfixHookAPI20251128)
|
||||
{
|
||||
auto feats =
|
||||
features - fixHookAPI20251128 - fixHookAPISType;
|
||||
if (with20251128)
|
||||
feats = feats | fixHookAPI20251128;
|
||||
if (withSType)
|
||||
feats = feats | fixHookAPISType;
|
||||
|
||||
Env env{*this, feats};
|
||||
env.fund(XRP(10000), alice, bob);
|
||||
env.close();
|
||||
|
||||
// install the hook on alice
|
||||
env(ripple::test::jtx::hook(
|
||||
alice, {{hso(hook_wasm, overrideFlag)}}, 0),
|
||||
M("set sto_subarray"),
|
||||
HSFEE);
|
||||
env.close();
|
||||
EXPECT_HOOK_FEE(hook, 19);
|
||||
|
||||
// invoke the hook
|
||||
env(pay(bob, alice, XRP(1)),
|
||||
M("test sto_subarray"),
|
||||
fee(XRP(1)));
|
||||
env.close();
|
||||
|
||||
auto const meta = env.meta();
|
||||
BEAST_REQUIRE(meta);
|
||||
BEAST_REQUIRE(meta->isFieldPresent(sfHookExecutions));
|
||||
auto const hookExecutions =
|
||||
meta->getFieldArray(sfHookExecutions);
|
||||
BEAST_REQUIRE(hookExecutions.size() == 1);
|
||||
auto const hookExecution = hookExecutions[0];
|
||||
BEAST_REQUIRE(
|
||||
hookExecution.isFieldPresent(sfHookReturnCode));
|
||||
auto const returnCode =
|
||||
hookExecution.getFieldU64(sfHookReturnCode);
|
||||
auto const doesntExistError = -5;
|
||||
auto const position = 2;
|
||||
auto const length = 12;
|
||||
BEAST_REQUIRE(
|
||||
returnCode ==
|
||||
(doesntExistError + ((int64_t)position << 32) +
|
||||
length));
|
||||
}
|
||||
else
|
||||
{
|
||||
auto const parseError = -18;
|
||||
if (with20251128)
|
||||
{
|
||||
auto const position = 2;
|
||||
auto const length = 12;
|
||||
// unwrapped correctly: 1 element at pos 2, len 12
|
||||
BEAST_REQUIRE(
|
||||
returnCode ==
|
||||
(doesntExistError + ((int64_t)position << 32) +
|
||||
length));
|
||||
}
|
||||
else if (withSType)
|
||||
{
|
||||
// legacy unwrap leaves 0x5C as a 32 byte field header,
|
||||
// which is rejected as out of bounds.
|
||||
// negative codes are stored as sign bit + magnitude
|
||||
BEAST_REQUIRE(
|
||||
returnCode ==
|
||||
(0x8000'0000'0000'0000ULL |
|
||||
-(parseError + parseError)));
|
||||
}
|
||||
else
|
||||
{
|
||||
auto const position = 1;
|
||||
auto const length = 33;
|
||||
// legacy: 32 byte field overruns the 13 byte buffer
|
||||
BEAST_REQUIRE(
|
||||
returnCode ==
|
||||
(parseError + ((int64_t)position << 32) + length));
|
||||
}
|
||||
auto const position = 1;
|
||||
auto const length = 33;
|
||||
BEAST_REQUIRE(
|
||||
returnCode ==
|
||||
(parseError + ((int64_t)position << 32) + length));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15535,8 +15075,7 @@ public:
|
||||
testNSDeletePartial(features);
|
||||
testPageCap(features);
|
||||
|
||||
testHookOnV2((features | featureHookOnV2) - featureHookOnV2_1);
|
||||
testHookOnV2((features | featureHookOnV2_1) - featureHookOnV2);
|
||||
testHookOnV2(features);
|
||||
testHookName(features);
|
||||
|
||||
testFillCopy(features);
|
||||
@@ -15544,7 +15083,6 @@ public:
|
||||
testWasm(features);
|
||||
test_accept(features);
|
||||
test_rollback(features);
|
||||
test_exit_out_of_bounds(features);
|
||||
|
||||
testGuards(features);
|
||||
|
||||
|
||||
@@ -189,64 +189,6 @@ std::map<std::string, std::vector<uint8_t>> wasm = {
|
||||
0x3DU, 0x20U, 0x54U, 0x4FU, 0x4FU, 0x5FU, 0x42U, 0x49U, 0x47U, 0x00U,
|
||||
}},
|
||||
|
||||
{R"[test.hook](
|
||||
#include <stdint.h>
|
||||
extern int32_t _g (uint32_t id, uint32_t maxiter);
|
||||
extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code);
|
||||
int64_t hook(uint32_t reserved )
|
||||
{
|
||||
_g(1,1);
|
||||
accept(0xFFFFFF00U, 32, 42);
|
||||
return 0;
|
||||
}
|
||||
)[test.hook]",
|
||||
{
|
||||
0x00U, 0x61U, 0x73U, 0x6DU, 0x01U, 0x00U, 0x00U, 0x00U, 0x01U, 0x13U,
|
||||
0x03U, 0x60U, 0x02U, 0x7FU, 0x7FU, 0x01U, 0x7FU, 0x60U, 0x03U, 0x7FU,
|
||||
0x7FU, 0x7EU, 0x01U, 0x7EU, 0x60U, 0x01U, 0x7FU, 0x01U, 0x7EU, 0x02U,
|
||||
0x17U, 0x02U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x02U, 0x5FU, 0x67U, 0x00U,
|
||||
0x00U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x06U, 0x61U, 0x63U, 0x63U, 0x65U,
|
||||
0x70U, 0x74U, 0x00U, 0x01U, 0x03U, 0x02U, 0x01U, 0x02U, 0x05U, 0x03U,
|
||||
0x01U, 0x00U, 0x02U, 0x06U, 0x21U, 0x05U, 0x7FU, 0x01U, 0x41U, 0x80U,
|
||||
0x88U, 0x04U, 0x0BU, 0x7FU, 0x00U, 0x41U, 0x80U, 0x08U, 0x0BU, 0x7FU,
|
||||
0x00U, 0x41U, 0x80U, 0x08U, 0x0BU, 0x7FU, 0x00U, 0x41U, 0x80U, 0x88U,
|
||||
0x04U, 0x0BU, 0x7FU, 0x00U, 0x41U, 0x80U, 0x08U, 0x0BU, 0x07U, 0x08U,
|
||||
0x01U, 0x04U, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x00U, 0x02U, 0x0AU, 0xA1U,
|
||||
0x80U, 0x00U, 0x01U, 0x9DU, 0x80U, 0x00U, 0x00U, 0x41U, 0x01U, 0x41U,
|
||||
0x01U, 0x10U, 0x80U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x41U, 0x80U,
|
||||
0x7EU, 0x41U, 0x20U, 0x42U, 0x2AU, 0x10U, 0x81U, 0x80U, 0x80U, 0x80U,
|
||||
0x00U, 0x1AU, 0x42U, 0x00U, 0x0BU,
|
||||
}},
|
||||
|
||||
{R"[test.hook](
|
||||
#include <stdint.h>
|
||||
extern int32_t _g (uint32_t id, uint32_t maxiter);
|
||||
extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code);
|
||||
int64_t hook(uint32_t reserved )
|
||||
{
|
||||
_g(1,1);
|
||||
rollback(0xFFFFFF00U, 32, 42);
|
||||
return 0;
|
||||
}
|
||||
)[test.hook]",
|
||||
{
|
||||
0x00U, 0x61U, 0x73U, 0x6DU, 0x01U, 0x00U, 0x00U, 0x00U, 0x01U, 0x13U,
|
||||
0x03U, 0x60U, 0x02U, 0x7FU, 0x7FU, 0x01U, 0x7FU, 0x60U, 0x03U, 0x7FU,
|
||||
0x7FU, 0x7EU, 0x01U, 0x7EU, 0x60U, 0x01U, 0x7FU, 0x01U, 0x7EU, 0x02U,
|
||||
0x19U, 0x02U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x02U, 0x5FU, 0x67U, 0x00U,
|
||||
0x00U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x08U, 0x72U, 0x6FU, 0x6CU, 0x6CU,
|
||||
0x62U, 0x61U, 0x63U, 0x6BU, 0x00U, 0x01U, 0x03U, 0x02U, 0x01U, 0x02U,
|
||||
0x05U, 0x03U, 0x01U, 0x00U, 0x02U, 0x06U, 0x21U, 0x05U, 0x7FU, 0x01U,
|
||||
0x41U, 0x80U, 0x88U, 0x04U, 0x0BU, 0x7FU, 0x00U, 0x41U, 0x80U, 0x08U,
|
||||
0x0BU, 0x7FU, 0x00U, 0x41U, 0x80U, 0x08U, 0x0BU, 0x7FU, 0x00U, 0x41U,
|
||||
0x80U, 0x88U, 0x04U, 0x0BU, 0x7FU, 0x00U, 0x41U, 0x80U, 0x08U, 0x0BU,
|
||||
0x07U, 0x08U, 0x01U, 0x04U, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x00U, 0x02U,
|
||||
0x0AU, 0xA1U, 0x80U, 0x00U, 0x01U, 0x9DU, 0x80U, 0x00U, 0x00U, 0x41U,
|
||||
0x01U, 0x41U, 0x01U, 0x10U, 0x80U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU,
|
||||
0x41U, 0x80U, 0x7EU, 0x41U, 0x20U, 0x42U, 0x2AU, 0x10U, 0x81U, 0x80U,
|
||||
0x80U, 0x80U, 0x00U, 0x1AU, 0x42U, 0x00U, 0x0BU,
|
||||
}},
|
||||
|
||||
{R"[test.hook](
|
||||
(module
|
||||
(type (;0;) (func (param i32 i32) (result i64)))
|
||||
@@ -1671,10 +1613,10 @@ std::map<std::string, std::vector<uint8_t>> wasm = {
|
||||
|
||||
uint8_t expected1[49] = {
|
||||
0xEDU, 0x20U, 0x2EU, 0x00U, 0x00U, 0x00U, 0x01U, 0x3DU, 0x00U, 0x00U,
|
||||
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x01U, 0x5BU, 0xBCU, 0x09U, 0x95U,
|
||||
0xFDU, 0x03U, 0x0FU, 0xF1U, 0x85U, 0xF4U, 0xECU, 0xACU, 0x94U, 0xDBU,
|
||||
0x02U, 0x19U, 0x26U, 0xFEU, 0x58U, 0xD4U, 0x8DU, 0x48U, 0x21U, 0xCCU,
|
||||
0x82U, 0x2EU, 0x4AU, 0x6AU, 0xC9U, 0xC8U, 0xC6U, 0x7CU, 0x88U
|
||||
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x01U, 0x5BU, 0xB8U, 0x05U, 0xD6U,
|
||||
0xC3U, 0x52U, 0xDFU, 0x7AU, 0x27U, 0x76U, 0x6DU, 0xC0U, 0x20U, 0x47U,
|
||||
0xB7U, 0x64U, 0x22U, 0x5AU, 0xB7U, 0x5DU, 0xF3U, 0xFAU, 0x0DU, 0xE3U,
|
||||
0xBDU, 0xC6U, 0x40U, 0xBAU, 0xD0U, 0x0AU, 0x66U, 0xEBU, 0x68U,
|
||||
};
|
||||
// 0x5CU
|
||||
// EmitNonce 32bytes
|
||||
@@ -1821,10 +1763,10 @@ std::map<std::string, std::vector<uint8_t>> wasm = {
|
||||
0x36U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
|
||||
0x00U, 0x00U, 0x00U, 0xEDU, 0x20U, 0x2EU, 0x00U, 0x00U, 0x00U, 0x01U,
|
||||
0x3DU, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x01U, 0x5BU,
|
||||
0xBCU, 0x09U, 0x95U, 0xFDU, 0x03U, 0x0FU, 0xF1U, 0x85U, 0xF4U, 0xECU,
|
||||
0xACU, 0x94U, 0xDBU, 0x02U, 0x19U, 0x26U, 0xFEU, 0x58U, 0xD4U, 0x8DU,
|
||||
0x48U, 0x21U, 0xCCU, 0x82U, 0x2EU, 0x4AU, 0x6AU, 0xC9U, 0xC8U, 0xC6U,
|
||||
0x7CU, 0x88U, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x5FU, 0x68U, 0x61U, 0x73U,
|
||||
0xB8U, 0x05U, 0xD6U, 0xC3U, 0x52U, 0xDFU, 0x7AU, 0x27U, 0x76U, 0x6DU,
|
||||
0xC0U, 0x20U, 0x47U, 0xB7U, 0x64U, 0x22U, 0x5AU, 0xB7U, 0x5DU, 0xF3U,
|
||||
0xFAU, 0x0DU, 0xE3U, 0xBDU, 0xC6U, 0x40U, 0xBAU, 0xD0U, 0x0AU, 0x66U,
|
||||
0xEBU, 0x68U, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x5FU, 0x68U, 0x61U, 0x73U,
|
||||
0x68U, 0x28U, 0x28U, 0x75U, 0x69U, 0x6EU, 0x74U, 0x33U, 0x32U, 0x5FU,
|
||||
0x74U, 0x29U, 0x65U, 0x78U, 0x70U, 0x65U, 0x63U, 0x74U, 0x65U, 0x64U,
|
||||
0x5FU, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x5FU, 0x68U, 0x61U, 0x73U, 0x68U,
|
||||
|
||||
+114
-164
@@ -24,7 +24,6 @@
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/protocol/UTF8.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
|
||||
#include <chrono>
|
||||
@@ -981,7 +980,6 @@ struct URIToken_test : public beast::unit_test::suite
|
||||
|
||||
// setup env
|
||||
env.fund(XRP(1000), alice, bob, gw);
|
||||
env.close();
|
||||
env.trust(USD(100000), alice, bob);
|
||||
env.close();
|
||||
env(pay(gw, alice, USD(1000)));
|
||||
@@ -2312,16 +2310,12 @@ struct URIToken_test : public beast::unit_test::suite
|
||||
auto const bob = Account("bob");
|
||||
|
||||
Env env{*this, features};
|
||||
|
||||
env.fund(XRP(10000), alice, bob);
|
||||
env.close();
|
||||
|
||||
std::string uri = "";
|
||||
|
||||
// =========================================================================
|
||||
// Cases that should succeed:
|
||||
// valid well-formed UTF-8 that is not a noncharacter
|
||||
// =========================================================================
|
||||
// test utf-8 success
|
||||
{
|
||||
// case: kosme
|
||||
uri = "κόσμε";
|
||||
@@ -2354,83 +2348,133 @@ struct URIToken_test : public beast::unit_test::suite
|
||||
// case: ipfs metadata url
|
||||
uri = "https://example.com/ipfs/";
|
||||
env(uritoken::mint(alice, uri));
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Cases that should fail:
|
||||
// genuinely malformed UTF-8 (well-formedness failures)
|
||||
// =========================================================================
|
||||
{
|
||||
// BOUNDRY - START
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
// case: 5 bytes (U-00200000) - beyond valid UTF-8 range
|
||||
// case: 1 byte (U-00000000)
|
||||
uri = "\x00";
|
||||
env(uritoken::mint(alice, uri),
|
||||
ter(temMALFORMED)); // TODO: REVIEW - SHOULD NOT FAIL
|
||||
// case: 2 bytes (U-00000080)
|
||||
uri = "\xC2\x80";
|
||||
env(uritoken::mint(alice, uri));
|
||||
// case: 3 bytes (U-00000800)
|
||||
uri = "\xE0\xA0\x80";
|
||||
env(uritoken::mint(alice, uri));
|
||||
// case: 4 bytes (U-00010000)
|
||||
uri = "\xF0\x90\x80\x80";
|
||||
env(uritoken::mint(alice, uri));
|
||||
// case: 5 bytes (U-00200000)
|
||||
uri = "\xF8\x88\x80\x80\x80";
|
||||
env(uritoken::mint(alice, uri), ter(temMALFORMED));
|
||||
|
||||
// case: 6 bytes (U-04000000) - beyond valid UTF-8 range
|
||||
env(uritoken::mint(alice, uri),
|
||||
ter(temMALFORMED)); // TODO: REVIEW - SHOULD NOT FAIL
|
||||
// case: 6 bytes (U-04000000)
|
||||
uri = "\xFC\x84\x80\x80\x80\x80";
|
||||
env(uritoken::mint(alice, uri), ter(temMALFORMED));
|
||||
env(uritoken::mint(alice, uri),
|
||||
ter(temMALFORMED)); // TODO: REVIEW - SHOULD NOT FAIL
|
||||
|
||||
// BOUNDRY - END
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
// case: 3 bytes max (U-0000FFFF) - but this is U+FFFF
|
||||
// noncharacter, rejected, see below
|
||||
|
||||
// case: 4 bytes max (U-001FFFFF) - beyond valid BMP
|
||||
// case: 1 byte (U-0000007F)
|
||||
uri = "\x7F";
|
||||
env(uritoken::mint(alice, uri));
|
||||
// case: 2 bytes (U-000007FF)
|
||||
uri = "\xDF\xBF";
|
||||
env(uritoken::mint(alice, uri));
|
||||
// case: 3 bytes (U-0000FFFF)
|
||||
uri = "\xEF\xBF\xBF";
|
||||
env(uritoken::mint(alice, uri),
|
||||
ter(temMALFORMED)); // TODO: REVIEW - SHOULD NOT FAIL
|
||||
// case: 4 bytes (U-001FFFFF)
|
||||
uri = "\xF7\xBF\xBF\xBF";
|
||||
env(uritoken::mint(alice, uri), ter(temMALFORMED));
|
||||
|
||||
// case: 5 bytes max (U-03FFFFFF) - beyond valid UTF-8
|
||||
env(uritoken::mint(alice, uri),
|
||||
ter(temMALFORMED)); // TODO: REVIEW - SHOULD NOT FAIL
|
||||
// case: 5 bytes (U-03FFFFFF)
|
||||
uri = "\xFB\xBF\xBF\xBF\xBF";
|
||||
env(uritoken::mint(alice, uri), ter(temMALFORMED));
|
||||
|
||||
// case: 6 bytes max (U-7FFFFFFF) - beyond valid UTF-8
|
||||
env(uritoken::mint(alice, uri),
|
||||
ter(temMALFORMED)); // TODO: REVIEW - SHOULD NOT FAIL
|
||||
// case: 6 bytes (U-7FFFFFFF)
|
||||
uri = "\xFD\xBF\xBF\xBF\xBF\xBF";
|
||||
env(uritoken::mint(alice, uri), ter(temMALFORMED));
|
||||
env(uritoken::mint(alice, uri),
|
||||
ter(temMALFORMED)); // TODO: REVIEW - SHOULD NOT FAIL
|
||||
|
||||
// BOUNDRY - OTHER
|
||||
// // BOUNDRY - OTHER
|
||||
// ----------------------------------------------------------------
|
||||
// case: 1 bytes (U-0000D7FF) - incomplete 2-byte sequence
|
||||
// case: 1 bytes (U-0000D7FF)
|
||||
uri = "\xD7\xFF";
|
||||
env(uritoken::mint(alice, uri), ter(temMALFORMED));
|
||||
|
||||
// case: 4 bytes (U-00110000) - beyond valid UTF-8 range
|
||||
env(uritoken::mint(alice, uri),
|
||||
ter(temMALFORMED)); // TODO: REVIEW - SHOULD NOT FAIL
|
||||
// case: 2 bytes (U-0000E000)
|
||||
uri = "\xEE\x80\x80";
|
||||
env(uritoken::mint(alice, uri));
|
||||
// case: 3 bytes (U-0000FFFD)
|
||||
uri = "\xEF\xBF\xBD";
|
||||
env(uritoken::mint(alice, uri));
|
||||
// // case: 4 bytes (U-0010FFFF)
|
||||
uri = "\xF4\x8F\xBF\xBF";
|
||||
env(uritoken::mint(alice, uri));
|
||||
// // case: 4 bytes (U-00110000)
|
||||
uri = "\xF4\x90\x80\x80";
|
||||
env(uritoken::mint(alice, uri), ter(temMALFORMED));
|
||||
env(uritoken::mint(alice, uri),
|
||||
ter(temMALFORMED)); // TODO: REVIEW - SHOULD NOT FAIL
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Genuinely malformed UTF-8 sequences - always fail
|
||||
// =========================================================================
|
||||
// test utf8 malformed
|
||||
{
|
||||
// MALFORMED - START
|
||||
// MALFORMED - END
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
// First continuation byte 0x80:
|
||||
uri = "\x80";
|
||||
env(uritoken::mint(alice, uri), ter(temMALFORMED));
|
||||
|
||||
// Last continuation byte 0xbf
|
||||
uri = "\xBF";
|
||||
env(uritoken::mint(alice, uri), ter(temMALFORMED));
|
||||
|
||||
// 2 continuation bytes
|
||||
uri = "��";
|
||||
env(uritoken::mint(alice, uri)); // TODO: REVIEW - SHOULD FAIL
|
||||
|
||||
// 3 continuation bytes
|
||||
uri = "���";
|
||||
env(uritoken::mint(alice, uri)); // TODO: REVIEW - SHOULD FAIL
|
||||
|
||||
// 4 continuation bytes
|
||||
uri = "����";
|
||||
env(uritoken::mint(alice, uri)); // TODO: REVIEW - SHOULD FAIL
|
||||
|
||||
// 5 continuation bytes
|
||||
uri = "�����";
|
||||
env(uritoken::mint(alice, uri)); // TODO: REVIEW - SHOULD FAIL
|
||||
|
||||
// 6 continuation bytes
|
||||
uri = "������";
|
||||
env(uritoken::mint(alice, uri)); // TODO: REVIEW - SHOULD FAIL
|
||||
|
||||
// 7 continuation bytes
|
||||
uri = "�������";
|
||||
env(uritoken::mint(alice, uri)); // TODO: REVIEW - SHOULD FAIL
|
||||
|
||||
// Sequence of all 64 possible continuation bytes (0x80-0xbf)
|
||||
uri =
|
||||
"\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8A\x8B\x8C\x8D"
|
||||
"\x8E"
|
||||
"\x8F\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9A\x9B\x9C"
|
||||
"\x9D"
|
||||
"\x9E\x9F\xA0\xA1\xA2\xA3\xA4\xA5\xA6\xA7\xA8\xA9\xAA\xAB"
|
||||
"\xAC"
|
||||
"\xAD\xAE\xAF\xB0\xB1\xB2\xB3\xB4\xB5\xB6\xB7\xB8\xB9\xBA"
|
||||
"\xBB"
|
||||
"\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8A\x8B\x8C\x8D\x8E"
|
||||
"\x8F\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9A\x9B\x9C\x9D"
|
||||
"\x9E\x9F\xA0\xA1\xA2\xA3\xA4\xA5\xA6\xA7\xA8\xA9\xAA\xAB\xAC"
|
||||
"\xAD\xAE\xAF\xB0\xB1\xB2\xB3\xB4\xB5\xB6\xB7\xB8\xB9\xBA\xBB"
|
||||
"\xBC\xBD\xBE\xBF";
|
||||
env(uritoken::mint(alice, uri), ter(temMALFORMED));
|
||||
|
||||
// All 16 first bytes of 3-byte sequences (0xe0-0xef), each
|
||||
// followed by a space character
|
||||
// TODO: REVIEW - THIS IS NOT THE CORRECT 32 byte sequence.
|
||||
// All 32 first bytes of 2-byte sequences (0xc0-0xdf), each followed
|
||||
// by a space character
|
||||
// uri = "\xE0\x80\x80 \xE0\x80\x81 \xE0\x80\x82 \xE0\x80\x83
|
||||
// \xE0\x80\x84 \xE0\x80\x85 \xE0\x80\x86 \xE0\x80\x87 \xE0\x80\x88
|
||||
// \xE0\x80\x89 \xE0\x80\x8A \xE0\x80\x8B \xE0\x80\x8C \xE0\x80\x8D
|
||||
// \xE0\x80\x8E \xE0\x80\x8F \xE0\x80\x90";
|
||||
// env(uritoken::mint(alice, uri), ter(temMALFORMED));
|
||||
|
||||
// All 16 first bytes of 3-byte sequences (0xe0-0xef), each followed
|
||||
// by a space character
|
||||
uri =
|
||||
"\xE0\x80\x80 \xE0\x80\x81 \xE0\x80\x82 \xE0\x80\x83 "
|
||||
"\xE0\x80\x84 \xE0\x80\x85 \xE0\x80\x86 \xE0\x80\x87 "
|
||||
@@ -2439,26 +2483,31 @@ struct URIToken_test : public beast::unit_test::suite
|
||||
"\xE0\x80\x90";
|
||||
env(uritoken::mint(alice, uri), ter(temMALFORMED));
|
||||
|
||||
// All 8 first bytes of 4-byte sequences (0xf0-0xf7), each
|
||||
// followed by a space character
|
||||
// All 8 first bytes of 4-byte sequences (0xf0-0xf7), each followed
|
||||
// by a space character
|
||||
uri =
|
||||
"\xF0\x90\x80\x80 \xF0\x90\x80\x81 \xF0\x90\x80\x82 "
|
||||
"\xF0\x90\x80\x83 \xF0\x90\x80\x84 \xF0\x90\x80\x85 "
|
||||
"\xF0\x90\x80\x86 \xF0\x90\x80\x87";
|
||||
env(uritoken::mint(alice, uri));
|
||||
env(uritoken::mint(alice, uri)); // TODO: REVIEW - SHOULD FAIL
|
||||
|
||||
// All 4 first bytes of 5-byte sequences (0xf8-0xfb), each
|
||||
// followed by a space character
|
||||
// All 4 first bytes of 5-byte sequences (0xf8-0xfb), each followed
|
||||
// by a space character
|
||||
uri =
|
||||
"\xF8\x88\x80\x80\x80 \xF8\x88\x80\x80\x81 "
|
||||
"\xF8\x88\x80\x80\x82 \xF8\x88\x80\x80\x83";
|
||||
env(uritoken::mint(alice, uri), ter(temMALFORMED));
|
||||
env(uritoken::mint(alice, uri),
|
||||
ter(temMALFORMED)); // TODO: REVIEW - SHOULD FAIL
|
||||
|
||||
// All 2 first bytes of 6-byte sequences (0xfc-0xfd), each
|
||||
// followed by a space character
|
||||
// All 2 first bytes of 6-byte sequences (0xfc-0xfd), each followed
|
||||
// by a space character
|
||||
uri = "\xFC\x84\x80\x80\x80\x80 \xFC\x84\x80\x80\x80\x81";
|
||||
env(uritoken::mint(alice, uri), ter(temMALFORMED));
|
||||
|
||||
// Sequences with last continuation byte missing
|
||||
|
||||
// Concatenation of incomplete sequences
|
||||
|
||||
// Impossible bytes
|
||||
uri = "\xFE";
|
||||
env(uritoken::mint(alice, uri), ter(temMALFORMED));
|
||||
@@ -2567,113 +2616,15 @@ struct URIToken_test : public beast::unit_test::suite
|
||||
uri = "\xED\xAF\xBF\xED\xBF\xBF";
|
||||
env(uritoken::mint(alice, uri), ter(temMALFORMED));
|
||||
|
||||
// MALFORMED - END
|
||||
// ----------------------------------------------------------------
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Noncharacters U+FFFE and U+FFFF: well-formed UTF-8, but
|
||||
// deliberately rejected (U+FFFE is a byte-swapped BOM).
|
||||
// =========================================================================
|
||||
{
|
||||
uri = "\xEF\xBF\xBE"; // U+FFFE
|
||||
// problematic noncharacters in 16-bit applications
|
||||
// case: (U+FFFE)
|
||||
uri = "\xEF\xBF\xBE";
|
||||
env(uritoken::mint(alice, uri), ter(temMALFORMED));
|
||||
|
||||
uri = "\xEF\xBF\xBF"; // U+FFFF
|
||||
// case: (U+FFFF)
|
||||
uri = "\xEF\xBF\xBF";
|
||||
env(uritoken::mint(alice, uri), ter(temMALFORMED));
|
||||
|
||||
uri = "ipfs://x\xEF\xBF\xBE"; // not only at the start
|
||||
env(uritoken::mint(alice, uri), ter(temMALFORMED));
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Valid boundary cases that should always succeed
|
||||
// =========================================================================
|
||||
{
|
||||
// BOUNDRY - START (valid minimums)
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
// case: 1 byte (U-00000000). Must use the string
|
||||
// constructor, not a C-string literal, to avoid strlen
|
||||
// truncation to an empty URI.
|
||||
uri = std::string(1, '\0');
|
||||
env(uritoken::mint(alice, uri));
|
||||
// case: 2 bytes (U-00000080)
|
||||
uri = "\xC2\x80";
|
||||
env(uritoken::mint(alice, uri));
|
||||
// case: 3 bytes (U-00000800)
|
||||
uri = "\xE0\xA0\x80";
|
||||
env(uritoken::mint(alice, uri));
|
||||
// case: 4 bytes (U-00010000)
|
||||
uri = "\xF0\x90\x80\x80";
|
||||
env(uritoken::mint(alice, uri));
|
||||
|
||||
// BOUNDRY - END (valid maximums)
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
// case: 1 byte (U-0000007F)
|
||||
uri = "\x7F";
|
||||
env(uritoken::mint(alice, uri));
|
||||
// case: 2 bytes (U-000007FF)
|
||||
uri = "\xDF\xBF";
|
||||
env(uritoken::mint(alice, uri));
|
||||
// case: 3 bytes (U-0000FFFF) - noncharacter, rejected, see
|
||||
// above.
|
||||
// case: 4 bytes (U-001FFFFF) - beyond U+10FFFF, always
|
||||
// rejected, see above.
|
||||
|
||||
// BOUNDRY - OTHER
|
||||
// ----------------------------------------------------------------
|
||||
// case: 2 bytes (U-0000E000) - above surrogates
|
||||
uri = "\xEE\x80\x80";
|
||||
env(uritoken::mint(alice, uri));
|
||||
// case: 3 bytes (U-0000FFFD) - replacement character, valid
|
||||
uri = "\xEF\xBF\xBD";
|
||||
env(uritoken::mint(alice, uri));
|
||||
// case: 4 bytes (U-0010FFFF) - max valid code point
|
||||
uri = "\xF4\x8F\xBF\xBF";
|
||||
env(uritoken::mint(alice, uri));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testUTF8Validator()
|
||||
{
|
||||
testcase("uri_utf8 validator");
|
||||
|
||||
auto const check = [](std::string const& s) {
|
||||
return isValidUTF8(makeSlice(s));
|
||||
};
|
||||
|
||||
// Sequences truncated by the end of the buffer are invalid. The
|
||||
// previous decoder read past the end of the vector here.
|
||||
BEAST_EXPECT(!check("a\xC2"));
|
||||
BEAST_EXPECT(!check("a\xE0\xA4"));
|
||||
BEAST_EXPECT(!check("a\xEF\xBF"));
|
||||
BEAST_EXPECT(!check("a\xF0\x90\x8D"));
|
||||
BEAST_EXPECT(!check(std::string("\xC2", 1)));
|
||||
BEAST_EXPECT(!check(std::string("\xF4\x8F\xBF", 3)));
|
||||
|
||||
// The complete sequences are fine.
|
||||
BEAST_EXPECT(check("a\xC2\xA2"));
|
||||
BEAST_EXPECT(check("a\xE0\xA4\xB9"));
|
||||
BEAST_EXPECT(check("a\xF0\x90\x8D\x88"));
|
||||
BEAST_EXPECT(check("a\xF4\x8F\xBF\xBF"));
|
||||
BEAST_EXPECT(check(""));
|
||||
|
||||
// U+FFFE and U+FFFF are rejected wherever they appear.
|
||||
BEAST_EXPECT(!check("\xEF\xBF\xBE"));
|
||||
BEAST_EXPECT(!check("ab\xEF\xBF\xBF"));
|
||||
BEAST_EXPECT(!check("\xE2\x82\xAC\xEF\xBF\xBEz"));
|
||||
|
||||
// Their neighbours are not, and nor are the other noncharacters,
|
||||
// which the validator has never rejected.
|
||||
BEAST_EXPECT(check("\xEF\xBF\xBD")); // U+FFFD
|
||||
BEAST_EXPECT(check("\xEF\xBE\xBF")); // U+FFBF
|
||||
BEAST_EXPECT(check("\xEF\xBB\xBF")); // U+FEFF (BOM)
|
||||
BEAST_EXPECT(check("\xF0\x9F\xBF\xBE")); // U+1FFFE
|
||||
}
|
||||
|
||||
void
|
||||
testWithFeats(FeatureBitset features)
|
||||
{
|
||||
@@ -2707,7 +2658,6 @@ public:
|
||||
{
|
||||
using namespace test::jtx;
|
||||
auto const sa = supported_amendments();
|
||||
testUTF8Validator();
|
||||
testWithFeats(sa);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -28,9 +28,7 @@
|
||||
#include <xrpl/protocol/jss.h>
|
||||
|
||||
#include <boost/lexical_cast.hpp>
|
||||
#include <chrono>
|
||||
#include <optional>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
|
||||
namespace ripple {
|
||||
@@ -908,24 +906,6 @@ public:
|
||||
pass();
|
||||
}
|
||||
|
||||
void
|
||||
testJSONRPCClientKeepAlive()
|
||||
{
|
||||
testcase("JSON-RPC keep-alive survives test work");
|
||||
using namespace std::chrono_literals;
|
||||
using namespace jtx;
|
||||
|
||||
Env env{*this};
|
||||
env.client().invoke("server_info", {});
|
||||
|
||||
// The normal loopback message timeout is three seconds. Established
|
||||
// Env connections have a separate idle lease because test work can
|
||||
// legitimately take longer between RPCs.
|
||||
std::this_thread::sleep_for(4s);
|
||||
auto const response = env.client().invoke("server_info", {});
|
||||
BEAST_EXPECT(response[jss::result][jss::status] == "success");
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
@@ -953,7 +933,6 @@ public:
|
||||
testSignAndSubmit(all);
|
||||
testFeatures(all);
|
||||
testExceptionalShutdown();
|
||||
testJSONRPCClientKeepAlive();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -141,9 +141,7 @@ AMM::create(
|
||||
if (fee_ != 0)
|
||||
jv[sfFee] = std::to_string(fee_);
|
||||
else
|
||||
jv[jss::Fee] = std::to_string(
|
||||
env_.current()->fees().increment.drops() +
|
||||
env_.current()->fees().base.drops());
|
||||
jv[jss::Fee] = std::to_string(env_.current()->fees().increment.drops());
|
||||
submit(jv, seq, ter);
|
||||
|
||||
if (!ter || env_.ter() == tesSUCCESS)
|
||||
|
||||
@@ -180,7 +180,7 @@ AMMTest::reserve(jtx::Env& env, std::uint32_t count) const
|
||||
XRPAmount
|
||||
AMMTest::ammCrtFee(jtx::Env& env) const
|
||||
{
|
||||
return env.current()->fees().increment + env.current()->fees().base;
|
||||
return env.current()->fees().increment;
|
||||
}
|
||||
|
||||
jtx::Env
|
||||
|
||||
@@ -37,36 +37,20 @@ namespace test {
|
||||
|
||||
static char const* bobs_account_objects[] = {
|
||||
R"json({
|
||||
"Account" : "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK",
|
||||
"BookDirectory" : "50AD0A9E54D2B381288D535EB724E4275FFBF41580D28A925D038D7EA4C68000",
|
||||
"BookNode" : "0",
|
||||
"Flags" : 65536,
|
||||
"LedgerEntryType" : "Offer",
|
||||
"OwnerNode" : "0",
|
||||
"Sequence" : 4,
|
||||
"TakerGets" : {
|
||||
"currency" : "USD",
|
||||
"issuer" : "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK",
|
||||
"value" : "1"
|
||||
},
|
||||
"TakerPays" : "100000000",
|
||||
"index" : "A984D036A0E562433A8377CA57D1A1E056E58C0D04818F8DFD3A1AA3F217DD82"
|
||||
})json",
|
||||
R"json({
|
||||
"Account" : "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK",
|
||||
"BookDirectory" : "B025997A323F5C3E03DDF1334471F5984ABDE31C59D463525D038D7EA4C68000",
|
||||
"BookNode" : "0",
|
||||
"Flags" : 65536,
|
||||
"LedgerEntryType" : "Offer",
|
||||
"OwnerNode" : "0",
|
||||
"Sequence" : 5,
|
||||
"TakerGets" : {
|
||||
"currency" : "USD",
|
||||
"issuer" : "r32rQHyesiTtdWFU7UJVtff4nCR5SHCbJW",
|
||||
"value" : "1"
|
||||
},
|
||||
"TakerPays" : "100000000",
|
||||
"index" : "CAFE32332D752387B01083B60CC63069BA4A969C9730836929F841450F6A718E"
|
||||
"Account" : "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK",
|
||||
"BookDirectory" : "B025997A323F5C3E03DDF1334471F5984ABDE31C59D463525D038D7EA4C68000",
|
||||
"BookNode" : "0",
|
||||
"Flags" : 65536,
|
||||
"LedgerEntryType" : "Offer",
|
||||
"OwnerNode" : "0",
|
||||
"Sequence" : 4,
|
||||
"TakerGets" : {
|
||||
"currency" : "USD",
|
||||
"issuer" : "r32rQHyesiTtdWFU7UJVtff4nCR5SHCbJW",
|
||||
"value" : "1"
|
||||
},
|
||||
"TakerPays" : "100000000",
|
||||
"index" : "A984D036A0E562433A8377CA57D1A1E056E58C0D04818F8DFD3A1AA3F217DD82"
|
||||
})json",
|
||||
R"json({
|
||||
"Balance" : {
|
||||
@@ -111,6 +95,22 @@ static char const* bobs_account_objects[] = {
|
||||
},
|
||||
"LowNode" : "0",
|
||||
"index" : "D89BC239086183EB9458C396E643795C1134963E6550E682A190A5F021766D43"
|
||||
})json",
|
||||
R"json({
|
||||
"Account" : "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK",
|
||||
"BookDirectory" : "50AD0A9E54D2B381288D535EB724E4275FFBF41580D28A925D038D7EA4C68000",
|
||||
"BookNode" : "0",
|
||||
"Flags" : 65536,
|
||||
"LedgerEntryType" : "Offer",
|
||||
"OwnerNode" : "0",
|
||||
"Sequence" : 3,
|
||||
"TakerGets" : {
|
||||
"currency" : "USD",
|
||||
"issuer" : "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK",
|
||||
"value" : "1"
|
||||
},
|
||||
"TakerPays" : "100000000",
|
||||
"index" : "E11029302EE744401427793A4F37BCB18F698D55C96851BEC5ABBD6242CF03D7"
|
||||
})json"};
|
||||
|
||||
class AccountObjects_test : public beast::unit_test::suite
|
||||
@@ -332,7 +332,7 @@ public:
|
||||
auto& aobj = resp[jss::result][jss::account_objects][i];
|
||||
aobj.removeMember("PreviousTxnID");
|
||||
aobj.removeMember("PreviousTxnLgrSeq");
|
||||
BEAST_EXPECT(aobj == bobj[i + 2]);
|
||||
BEAST_EXPECT(aobj == bobj[i + 1]);
|
||||
}
|
||||
}
|
||||
// test stepped one-at-a-time with limit=1, resume from prev marker
|
||||
@@ -1135,15 +1135,7 @@ public:
|
||||
return types == typesOut;
|
||||
};
|
||||
// Find AMM objects
|
||||
// gw has a hook installed, so pay the hook execution fee too.
|
||||
AMM amm(
|
||||
env,
|
||||
gw,
|
||||
XRP(1'000),
|
||||
USD(1'000),
|
||||
false,
|
||||
0,
|
||||
env.current()->fees().increment.drops() + 1'000'000);
|
||||
AMM amm(env, gw, XRP(1'000), USD(1'000));
|
||||
amm.deposit(alice, USD(1));
|
||||
// AMM account has 4 objects: AMM object and 3 trustlines
|
||||
auto const lines = getAccountLines(env, amm.ammAccount());
|
||||
|
||||
@@ -117,9 +117,9 @@ public:
|
||||
BEAST_EXPECT(jroOuter[0u][jss::quality] == "100000000");
|
||||
BEAST_EXPECT(jroOuter[0u][jss::taker_gets][jss::currency] == "USD");
|
||||
BEAST_EXPECT(
|
||||
jroOuter[0u][jss::taker_gets][jss::issuer] == gw.human());
|
||||
BEAST_EXPECT(jroOuter[0u][jss::taker_gets][jss::value] == "2");
|
||||
BEAST_EXPECT(jroOuter[0u][jss::taker_pays] == "200000000");
|
||||
jroOuter[0u][jss::taker_gets][jss::issuer] == bob.human());
|
||||
BEAST_EXPECT(jroOuter[0u][jss::taker_gets][jss::value] == "1");
|
||||
BEAST_EXPECT(jroOuter[0u][jss::taker_pays] == "100000000");
|
||||
|
||||
BEAST_EXPECT(jroOuter[1u][jss::quality] == "5000000");
|
||||
BEAST_EXPECT(jroOuter[1u][jss::taker_gets][jss::currency] == "USD");
|
||||
@@ -131,9 +131,9 @@ public:
|
||||
BEAST_EXPECT(jroOuter[2u][jss::quality] == "100000000");
|
||||
BEAST_EXPECT(jroOuter[2u][jss::taker_gets][jss::currency] == "USD");
|
||||
BEAST_EXPECT(
|
||||
jroOuter[2u][jss::taker_gets][jss::issuer] == bob.human());
|
||||
BEAST_EXPECT(jroOuter[2u][jss::taker_gets][jss::value] == "1");
|
||||
BEAST_EXPECT(jroOuter[2u][jss::taker_pays] == "100000000");
|
||||
jroOuter[2u][jss::taker_gets][jss::issuer] == gw.human());
|
||||
BEAST_EXPECT(jroOuter[2u][jss::taker_gets][jss::value] == "2");
|
||||
BEAST_EXPECT(jroOuter[2u][jss::taker_pays] == "200000000");
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -163,7 +163,7 @@ public:
|
||||
void
|
||||
testNoParams(FeatureBitset features)
|
||||
{
|
||||
testcase("Default Env: config-forced, none on-ledger");
|
||||
testcase("No Params, None Enabled");
|
||||
|
||||
using namespace test::jtx;
|
||||
Env env{*this};
|
||||
@@ -178,6 +178,8 @@ public:
|
||||
{
|
||||
if (!BEAST_EXPECT(feature.isMember(jss::name)))
|
||||
return;
|
||||
// default config - so all should be disabled, and
|
||||
// supported. Some may be vetoed.
|
||||
bool expectVeto =
|
||||
(votes.at(feature[jss::name].asString()) ==
|
||||
VoteBehavior::DefaultNo);
|
||||
@@ -186,12 +188,8 @@ public:
|
||||
VoteBehavior::Obsolete);
|
||||
BEAST_EXPECTS(
|
||||
feature.isMember(jss::enabled) &&
|
||||
feature[jss::enabled].asBool(),
|
||||
!feature[jss::enabled].asBool(),
|
||||
feature[jss::name].asString() + " enabled");
|
||||
BEAST_EXPECTS(
|
||||
feature.isMember(jss::ledger_enabled) &&
|
||||
!feature[jss::ledger_enabled].asBool(),
|
||||
feature[jss::name].asString() + " ledger_enabled");
|
||||
BEAST_EXPECTS(
|
||||
feature.isMember(jss::vetoed) &&
|
||||
feature[jss::vetoed].isBool() == !expectObsolete &&
|
||||
@@ -210,7 +208,7 @@ public:
|
||||
void
|
||||
testSomeEnabled(FeatureBitset features)
|
||||
{
|
||||
testcase("Two config-forced, none on-ledger");
|
||||
testcase("No Params, Some Enabled");
|
||||
|
||||
using namespace test::jtx;
|
||||
Env env{
|
||||
@@ -230,10 +228,7 @@ public:
|
||||
(void)id.parseHex(it.key().asString().c_str());
|
||||
if (!BEAST_EXPECT((*it).isMember(jss::name)))
|
||||
return;
|
||||
bool const expectOnLedger =
|
||||
env.app().getAmendmentTable().isEnabled(id);
|
||||
bool const expectForced =
|
||||
id == featureDepositAuth || id == featureDepositPreauth;
|
||||
bool expectEnabled = env.app().getAmendmentTable().isEnabled(id);
|
||||
bool expectSupported =
|
||||
env.app().getAmendmentTable().isSupported(id);
|
||||
bool expectVeto =
|
||||
@@ -244,14 +239,9 @@ public:
|
||||
VoteBehavior::Obsolete);
|
||||
BEAST_EXPECTS(
|
||||
(*it).isMember(jss::enabled) &&
|
||||
(*it)[jss::enabled].asBool() ==
|
||||
(expectOnLedger || expectForced),
|
||||
(*it)[jss::enabled].asBool() == expectEnabled,
|
||||
(*it)[jss::name].asString() + " enabled");
|
||||
BEAST_EXPECTS(
|
||||
(*it).isMember(jss::ledger_enabled) &&
|
||||
(*it)[jss::ledger_enabled].asBool() == expectOnLedger,
|
||||
(*it)[jss::name].asString() + " ledger_enabled");
|
||||
if (expectOnLedger)
|
||||
if (expectEnabled)
|
||||
BEAST_EXPECTS(
|
||||
!(*it).isMember(jss::vetoed),
|
||||
(*it)[jss::name].asString() + " vetoed");
|
||||
@@ -370,122 +360,12 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testConfigForced(FeatureBitset features)
|
||||
{
|
||||
testcase("Config-forced features ([features] stanza)");
|
||||
|
||||
using namespace test::jtx;
|
||||
|
||||
auto const forced = featurePriceOracle;
|
||||
auto const forcedHex = to_string(forced);
|
||||
|
||||
Env env{*this, FeatureBitset(forced)};
|
||||
|
||||
auto jrr = env.rpc("server_definitions")[jss::result];
|
||||
if (!BEAST_EXPECT(jrr.isMember(jss::features)))
|
||||
return;
|
||||
|
||||
bool sawForced = false;
|
||||
for (auto it = jrr[jss::features].begin();
|
||||
it != jrr[jss::features].end();
|
||||
++it)
|
||||
{
|
||||
auto const& f = *it;
|
||||
auto const name = f[jss::name].asString();
|
||||
|
||||
if (!BEAST_EXPECTS(
|
||||
f.isMember(jss::enabled) && f.isMember(jss::ledger_enabled),
|
||||
name + " enabled/ledger_enabled"))
|
||||
return;
|
||||
|
||||
BEAST_EXPECTS(
|
||||
!f[jss::ledger_enabled].asBool(), name + " ledger_enabled");
|
||||
|
||||
if (it.key().asString() == forcedHex)
|
||||
{
|
||||
sawForced = true;
|
||||
BEAST_EXPECTS(f[jss::enabled].asBool(), name + " enabled");
|
||||
}
|
||||
else
|
||||
{
|
||||
BEAST_EXPECTS(!f[jss::enabled].asBool(), name + " enabled");
|
||||
}
|
||||
}
|
||||
BEAST_EXPECT(sawForced);
|
||||
}
|
||||
|
||||
void
|
||||
testOnLedger(FeatureBitset features)
|
||||
{
|
||||
testcase("On-ledger plus one config-forced");
|
||||
|
||||
using namespace test::jtx;
|
||||
|
||||
// Veto XahauGenesis: FRESH would otherwise enable it via pseudo-tx.
|
||||
auto const forced = featurePriceOracle;
|
||||
auto const forcedHex = to_string(forced);
|
||||
|
||||
Env env{
|
||||
*this,
|
||||
envconfig([](std::unique_ptr<Config> cfg) {
|
||||
cfg->START_UP = Config::FRESH;
|
||||
cfg->section(SECTION_VETO_AMENDMENTS)
|
||||
.append(to_string(featureXahauGenesis) + " XahauGenesis");
|
||||
return cfg;
|
||||
}),
|
||||
FeatureBitset(forced)};
|
||||
|
||||
auto jrr = env.rpc("server_definitions")[jss::result];
|
||||
if (!BEAST_EXPECT(jrr.isMember(jss::features)))
|
||||
return;
|
||||
|
||||
bool sawOnLedger = false;
|
||||
bool sawForced = false;
|
||||
for (auto it = jrr[jss::features].begin();
|
||||
it != jrr[jss::features].end();
|
||||
++it)
|
||||
{
|
||||
uint256 id;
|
||||
(void)id.parseHex(it.key().asString().c_str());
|
||||
auto const& f = *it;
|
||||
auto const name = f[jss::name].asString();
|
||||
|
||||
if (!BEAST_EXPECTS(
|
||||
f.isMember(jss::enabled) && f.isMember(jss::ledger_enabled),
|
||||
name + " enabled/ledger_enabled"))
|
||||
return;
|
||||
|
||||
bool const onLedger = env.app().getAmendmentTable().isEnabled(id);
|
||||
bool const isForced = it.key().asString() == forcedHex;
|
||||
|
||||
BEAST_EXPECTS(
|
||||
f[jss::ledger_enabled].asBool() == onLedger,
|
||||
name + " ledger_enabled");
|
||||
BEAST_EXPECTS(
|
||||
f[jss::enabled].asBool() == (onLedger || isForced),
|
||||
name + " enabled");
|
||||
|
||||
if (onLedger)
|
||||
sawOnLedger = true;
|
||||
if (isForced)
|
||||
{
|
||||
sawForced = true;
|
||||
BEAST_EXPECTS(!onLedger, name + " forced not on-ledger");
|
||||
}
|
||||
}
|
||||
BEAST_EXPECT(sawOnLedger);
|
||||
BEAST_EXPECT(sawForced);
|
||||
}
|
||||
|
||||
void
|
||||
testServerFeatures(FeatureBitset features)
|
||||
{
|
||||
testNoParams(features);
|
||||
testSomeEnabled(features);
|
||||
testWithMajorities(features);
|
||||
testConfigForced(features);
|
||||
testOnLedger(features);
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -101,14 +101,6 @@ HookAPI::sto_subfield(Bytes const& data, uint32_t field_id) const
|
||||
if (data.size() < 2)
|
||||
return Unexpected(TOO_SMALL);
|
||||
|
||||
if (hookCtx.applyCtx.view().rules().enabled(fixHookAPISType))
|
||||
{
|
||||
// validate the data
|
||||
auto const valid = sto_validate(data);
|
||||
if (!valid || !valid.value())
|
||||
return Unexpected(PARSE_ERROR);
|
||||
}
|
||||
|
||||
unsigned char* start = const_cast<unsigned char*>(data.data());
|
||||
unsigned char* upto = start;
|
||||
unsigned char* end = start + data.size();
|
||||
@@ -173,18 +165,6 @@ HookAPI::sto_subarray(Bytes const& data, uint32_t index_id) const
|
||||
unsigned char* upto = start;
|
||||
unsigned char* end = start + data.size();
|
||||
|
||||
if (hookCtx.applyCtx.view().rules().enabled(fixHookAPISType))
|
||||
{
|
||||
// check if the array has valid trailing data
|
||||
if ((*upto & 0xF0U) == 0xF0U && *(end - 1) != 0xF1U)
|
||||
return Unexpected(PARSE_ERROR);
|
||||
|
||||
// validate the array
|
||||
auto const valid = sto_validate(data);
|
||||
if (!valid || !valid.value())
|
||||
return Unexpected(PARSE_ERROR);
|
||||
}
|
||||
|
||||
// unwrap the array if it is wrapped,
|
||||
// by removing a byte from the start and end
|
||||
// why here 0xF0?
|
||||
@@ -279,13 +259,6 @@ HookAPI::sto_emplace(
|
||||
return Unexpected(TOO_SMALL);
|
||||
}
|
||||
|
||||
if (hookCtx.applyCtx.view().rules().enabled(fixHookAPISType))
|
||||
{
|
||||
auto const source_valid = sto_validate(source_object);
|
||||
if (!source_valid || !source_valid.value())
|
||||
return Unexpected(PARSE_ERROR);
|
||||
}
|
||||
|
||||
if (field_object.has_value() &&
|
||||
hookCtx.applyCtx.view().rules().enabled(fixHookAPI20251128))
|
||||
{
|
||||
@@ -306,9 +279,6 @@ HookAPI::sto_emplace(
|
||||
0);
|
||||
if (!length)
|
||||
return Unexpected(PARSE_ERROR);
|
||||
if (hookCtx.applyCtx.view().rules().enabled(fixHookAPISType) &&
|
||||
length.value() != field_object->size())
|
||||
return Unexpected(PARSE_ERROR);
|
||||
if ((type << 16) + field != field_id)
|
||||
{
|
||||
return Unexpected(PARSE_ERROR);
|
||||
@@ -3078,7 +3048,6 @@ HookAPI::get_stobject_length(
|
||||
type == STI_PATHSET && rules.enabled(featureHookAPISerializedType240))
|
||||
{
|
||||
length = 0;
|
||||
bool terminated = false;
|
||||
while (upto + length < end)
|
||||
{
|
||||
// iterate Path step
|
||||
@@ -3096,9 +3065,6 @@ HookAPI::get_stobject_length(
|
||||
if (flag & 0x20) // issuer
|
||||
length += 20;
|
||||
|
||||
if (rules.enabled(fixHookAPISType) && upto + length >= end)
|
||||
return Unexpected(pe_unexpected_end);
|
||||
|
||||
int next_flag = *(upto + length);
|
||||
if (next_flag == 0x00 || next_flag == 0xff)
|
||||
// end of Path step
|
||||
@@ -3110,68 +3076,43 @@ HookAPI::get_stobject_length(
|
||||
if (lastflag == 0xff)
|
||||
continue; // continue byte
|
||||
else if (lastflag == 0x00)
|
||||
{
|
||||
terminated = true;
|
||||
break; // end byte
|
||||
}
|
||||
else
|
||||
return Unexpected(pe_unexpected_end);
|
||||
}
|
||||
if (upto >= end)
|
||||
return Unexpected(pe_unexpected_end);
|
||||
if (rules.enabled(fixHookAPISType) && !terminated)
|
||||
return Unexpected(pe_unexpected_end);
|
||||
}
|
||||
else if (type == STI_ISSUE)
|
||||
{
|
||||
bool const fix = rules.enabled(fixHookAPISType);
|
||||
auto zero20 = std::array<char, 20>{0};
|
||||
// if first 20 byte is all zeros return 20
|
||||
// else return 40
|
||||
if (fix && end - upto < 20)
|
||||
return Unexpected(pe_unexpected_end);
|
||||
if (memcmp(upto, zero20.data(), 20) == 0)
|
||||
length = 20;
|
||||
// MPT is not supported yet
|
||||
// else if (
|
||||
// fix && end - upto >= 40 &&
|
||||
// memcmp(upto + 20, noAccount().data(), 20) == 0)
|
||||
// length = 44; // MPT: Issuer + noAccount + Sequence
|
||||
else
|
||||
length = 40;
|
||||
}
|
||||
else if (type == STI_XCHAIN_BRIDGE)
|
||||
{
|
||||
bool const fix = rules.enabled(fixHookAPISType);
|
||||
auto zero20 = std::array<char, 20>{0};
|
||||
length = 0;
|
||||
for (int i = 0; i < 2; ++i) // Locking Chain, Issuing Chain
|
||||
{
|
||||
// Door Account
|
||||
if (!fix)
|
||||
length += 21;
|
||||
else if (end - upto <= length)
|
||||
return Unexpected(pe_unexpected_end);
|
||||
else if (upto[length] == 0)
|
||||
length += 1; // default (empty) account
|
||||
else if (upto[length] == 20)
|
||||
length += 21;
|
||||
else
|
||||
return Unexpected(pe_unexpected_end);
|
||||
// Lock Chain
|
||||
length = 1; // Door Account1 prefix length
|
||||
length += 20; // Door Account1 length
|
||||
// Door Issue1
|
||||
if (memcmp(upto + length, zero20.data(), 20) == 0)
|
||||
length += 20; // only Currency
|
||||
else
|
||||
length += 40; // Currency and Issue
|
||||
|
||||
// Door Issue
|
||||
if (fix && end - upto < length + 20)
|
||||
return Unexpected(pe_unexpected_end);
|
||||
if (memcmp(upto + length, zero20.data(), 20) == 0)
|
||||
length += 20; // only Currency
|
||||
// MPT is not supported yet
|
||||
// else if (
|
||||
// fix && end - upto >= length + 40 &&
|
||||
// memcmp(upto + length + 20, noAccount().data(), 20) == 0)
|
||||
// length += 44; // MPT: Issuer + noAccount + Sequence
|
||||
else
|
||||
length += 40; // Currency and Issuer
|
||||
}
|
||||
// Issuing Chain
|
||||
length += 1; // Door Account2 prefix length
|
||||
length += 20; // Door Account2 length
|
||||
// Door Issue2
|
||||
if (memcmp(upto + length, zero20.data(), 20) == 0)
|
||||
length += 20; // only Currency
|
||||
else
|
||||
length += 40; // Currency and Issue
|
||||
}
|
||||
|
||||
if (length > -1)
|
||||
@@ -3188,10 +3129,6 @@ HookAPI::get_stobject_length(
|
||||
length,
|
||||
payload_start,
|
||||
payload_length);
|
||||
|
||||
if (rules.enabled(fixHookAPISType) && length > end - upto)
|
||||
return Unexpected(pe_unexpected_end);
|
||||
|
||||
return length + (upto - start);
|
||||
}
|
||||
|
||||
|
||||
@@ -81,8 +81,8 @@ AMMCreate::preflight(PreflightContext const& ctx)
|
||||
XRPAmount
|
||||
AMMCreate::calculateBaseFee(ReadView const& view, STTx const& tx)
|
||||
{
|
||||
// The least fee required for AMMCreate is one owner reserve.
|
||||
return Transactor::calculateBaseFee(view, tx) + view.fees().increment;
|
||||
// The fee required for AMMCreate is one owner reserve.
|
||||
return view.fees().increment;
|
||||
}
|
||||
|
||||
TER
|
||||
@@ -252,7 +252,12 @@ applyCreate(
|
||||
auto sleAMMRoot = std::make_shared<SLE>(keylet::account(*ammAccount));
|
||||
sleAMMRoot->setAccountID(sfAccount, *ammAccount);
|
||||
sleAMMRoot->setFieldAmount(sfBalance, STAmount{});
|
||||
std::uint32_t const seqno = newAccountSeqNo(ctx_.view());
|
||||
std::uint32_t const seqno{
|
||||
ctx_.view().rules().enabled(featureXahauGenesis)
|
||||
? ctx_.view().info().parentCloseTime.time_since_epoch().count()
|
||||
: ctx_.view().rules().enabled(featureDeletableAccounts)
|
||||
? ctx_.view().seq()
|
||||
: 1};
|
||||
sleAMMRoot->setFieldU32(sfSequence, seqno);
|
||||
// Ignore reserves requirement, disable the master key, allow default
|
||||
// rippling (AMM LPToken can be used in payments and offer crossing but
|
||||
|
||||
@@ -554,7 +554,8 @@ Change::activateXahauGenesis()
|
||||
{
|
||||
sle = std::make_shared<SLE>(kl);
|
||||
sle->setAccountID(sfAccount, accid);
|
||||
std::uint32_t const seqno = newAccountSeqNo(sb);
|
||||
std::uint32_t const seqno{
|
||||
sb.info().parentCloseTime.time_since_epoch().count()};
|
||||
sle->setFieldU32(sfSequence, seqno);
|
||||
}
|
||||
|
||||
|
||||
@@ -243,7 +243,8 @@ GenesisMint::doApply()
|
||||
if (created)
|
||||
{
|
||||
// Create the account.
|
||||
std::uint32_t const seqno = newAccountSeqNo(view());
|
||||
std::uint32_t const seqno{
|
||||
view().info().parentCloseTime.time_since_epoch().count()};
|
||||
|
||||
sle = std::make_shared<SLE>(k);
|
||||
sle->setAccountID(sfAccount, id);
|
||||
|
||||
@@ -1307,7 +1307,12 @@ Import::doApply()
|
||||
if (create)
|
||||
{
|
||||
// Create the account.
|
||||
std::uint32_t const seqno = newAccountSeqNo(view());
|
||||
std::uint32_t const seqno{
|
||||
view().rules().enabled(featureXahauGenesis)
|
||||
? view().info().parentCloseTime.time_since_epoch().count()
|
||||
: view().rules().enabled(featureDeletableAccounts)
|
||||
? view().seq()
|
||||
: 1};
|
||||
|
||||
sle = std::make_shared<SLE>(keylet::account(id));
|
||||
sle->setAccountID(sfAccount, id);
|
||||
|
||||
@@ -1041,7 +1041,11 @@ ValidNewAccountRoot::finalize(
|
||||
tt == ttXCHAIN_ADD_ACCOUNT_CREATE_ATTESTATION) &&
|
||||
isTesSuccess(result))
|
||||
{
|
||||
std::uint32_t const startingSeq = newAccountSeqNo(view);
|
||||
std::uint32_t const startingSeq{
|
||||
view.rules().enabled(featureXahauGenesis)
|
||||
? view.info().parentCloseTime.time_since_epoch().count()
|
||||
: view.rules().enabled(featureDeletableAccounts) ? view.seq()
|
||||
: 1};
|
||||
|
||||
if (accountSeq_ != startingSeq)
|
||||
{
|
||||
|
||||
@@ -354,7 +354,12 @@ Payment::doApply()
|
||||
|
||||
if (!sleDst)
|
||||
{
|
||||
std::uint32_t const seqno = newAccountSeqNo(view());
|
||||
std::uint32_t const seqno{
|
||||
view().rules().enabled(featureXahauGenesis)
|
||||
? view().info().parentCloseTime.time_since_epoch().count()
|
||||
: view().rules().enabled(featureDeletableAccounts)
|
||||
? view().seq()
|
||||
: 1};
|
||||
|
||||
// Create the account.
|
||||
sleDst = std::make_shared<SLE>(k);
|
||||
|
||||
@@ -18,12 +18,12 @@
|
||||
//==============================================================================
|
||||
|
||||
#include <xrpld/app/tx/detail/Remit.h>
|
||||
#include <xrpld/app/tx/detail/URIToken.h>
|
||||
#include <xrpld/ledger/View.h>
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/protocol/UTF8.h>
|
||||
#include <xrpl/protocol/st.h>
|
||||
|
||||
namespace ripple {
|
||||
@@ -201,7 +201,7 @@ Remit::preflight(PreflightContext const& ctx)
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
if (!isValidUTF8(makeSlice(uri)))
|
||||
if (!URIToken::validateUTF8(uri))
|
||||
{
|
||||
JLOG(ctx.j.warn())
|
||||
<< "Malformed transaction: Invalid UTF8 inside MintURIToken.";
|
||||
@@ -328,7 +328,11 @@ Remit::doApply()
|
||||
nativeRemit += accountReserve;
|
||||
|
||||
// Create the account.
|
||||
std::uint32_t const seqno = newAccountSeqNo(sb);
|
||||
std::uint32_t const seqno{
|
||||
sb.rules().enabled(featureXahauGenesis)
|
||||
? sb.info().parentCloseTime.time_since_epoch().count()
|
||||
: sb.rules().enabled(featureDeletableAccounts) ? sb.seq()
|
||||
: 1};
|
||||
|
||||
sleDstAcc = std::make_shared<SLE>(keylet::account(dstAccID));
|
||||
sleDstAcc->setAccountID(sfAccount, dstAccID);
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include <xrpld/app/ledger/Ledger.h>
|
||||
#include <xrpld/app/ledger/LedgerMaster.h>
|
||||
#include <xrpld/app/ledger/OpenLedger.h>
|
||||
#include <xrpld/app/tx/detail/URIToken.h>
|
||||
#include <xrpld/ledger/ApplyView.h>
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/hook/Enum.h>
|
||||
@@ -34,7 +35,6 @@
|
||||
#include <xrpl/protocol/STObject.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/protocol/UTF8.h>
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <exception>
|
||||
@@ -207,7 +207,7 @@ validateHookParams(SetHookCtx& ctx, STArray const& hookParams)
|
||||
// infer which operation the user is attempting to execute from the present and
|
||||
// absent fields
|
||||
HookSetOperation
|
||||
SetHook::inferOperation(SetHookCtx& ctx, STObject const& hookSetObj)
|
||||
SetHook::inferOperation(STObject const& hookSetObj)
|
||||
{
|
||||
uint64_t wasmByteCount = hookSetObj.isFieldPresent(sfCreateCode)
|
||||
? hookSetObj.getFieldVL(sfCreateCode).size()
|
||||
@@ -216,12 +216,7 @@ SetHook::inferOperation(SetHookCtx& ctx, STObject const& hookSetObj)
|
||||
bool hasHash = hookSetObj.isFieldPresent(sfHookHash);
|
||||
bool hasCode = hookSetObj.isFieldPresent(sfCreateCode);
|
||||
|
||||
bool invalidHookOn = ctx.rules.enabled(featureHookOnV2_1) &&
|
||||
hookSetObj.isFieldPresent(sfHookOnOutgoing) !=
|
||||
hookSetObj.isFieldPresent(sfHookOnIncoming);
|
||||
|
||||
if ((hasHash && hasCode) || invalidHookOn) // Both HookHash and CreateCode
|
||||
// or invalid HookOn: invalid
|
||||
if (hasHash && hasCode) // Both HookHash and CreateCode: invalid
|
||||
return hsoINVALID;
|
||||
else if (hasHash) // Hookhash only: install
|
||||
return hsoINSTALL;
|
||||
@@ -249,62 +244,6 @@ SetHook::inferOperation(SetHookCtx& ctx, STObject const& hookSetObj)
|
||||
: hsoUPDATE;
|
||||
}
|
||||
|
||||
bool
|
||||
validateHookOn(SetHookCtx& ctx, STObject const& hookSetObj)
|
||||
{
|
||||
if (!hookSetObj.isFieldPresent(sfHookOn))
|
||||
{
|
||||
if (!ctx.rules.enabled(featureHookOnV2) &&
|
||||
!ctx.rules.enabled(featureHookOnV2_1))
|
||||
{
|
||||
JLOG(ctx.j.trace())
|
||||
<< "HookSet(" << hook::log::HOOKON_MISSING << ")[" << HS_ACC()
|
||||
<< "]: Malformed transaction: SetHook must include "
|
||||
"sfHookOn before featureHookOnV2_1 is enabled.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!hookSetObj.isFieldPresent(sfHookOnOutgoing) ||
|
||||
!hookSetObj.isFieldPresent(sfHookOnIncoming))
|
||||
{
|
||||
JLOG(ctx.j.trace())
|
||||
<< "HookSet(" << hook::log::HOOKON_MISSING << ")[" << HS_ACC()
|
||||
<< "]: Malformed transaction: SetHook must include "
|
||||
"sfHookOnOutgoing and sfHookOnIncoming "
|
||||
"when creating a new hook without sfHookOn.";
|
||||
return false;
|
||||
}
|
||||
|
||||
auto const outgoing = hookSetObj.getFieldH256(sfHookOnOutgoing);
|
||||
auto const incoming = hookSetObj.getFieldH256(sfHookOnIncoming);
|
||||
if (outgoing == incoming)
|
||||
{
|
||||
JLOG(ctx.j.trace())
|
||||
<< "HookSet(" << hook::log::HOOKON_MISSING << ")[" << HS_ACC()
|
||||
<< "]: Malformed transaction: SetHook outgoing and "
|
||||
"incoming hookon must be different.";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (hookSetObj.isFieldPresent(sfHookOnOutgoing) ||
|
||||
hookSetObj.isFieldPresent(sfHookOnIncoming))
|
||||
{
|
||||
JLOG(ctx.j.trace())
|
||||
<< "HookSet(" << hook::log::HOOKON_MISSING << ")[" << HS_ACC()
|
||||
<< "]: Malformed transaction: SetHook must no"
|
||||
"include sfHookOnOutgoing and sfHookOnIncoming "
|
||||
"when creating a new hook with sfHookOn.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// This is a context-free validation, it does not take into account the current
|
||||
// state of the ledger returns < valid, instruction count > may throw
|
||||
// overflow_error
|
||||
@@ -315,7 +254,7 @@ SetHook::validateHookSetEntry(SetHookCtx& ctx, STObject const& hookSetObj)
|
||||
? hookSetObj.getFieldU32(sfFlags)
|
||||
: 0;
|
||||
|
||||
switch (inferOperation(ctx, hookSetObj))
|
||||
switch (inferOperation(hookSetObj))
|
||||
{
|
||||
case hsoNOOP: {
|
||||
return true;
|
||||
@@ -424,13 +363,6 @@ SetHook::validateHookSetEntry(SetHookCtx& ctx, STObject const& hookSetObj)
|
||||
// hookon may be present if the user so chooses
|
||||
// flags may be present if the user so chooses
|
||||
|
||||
if (ctx.rules.enabled(featureHookOnV2_1) &&
|
||||
(hookSetObj.isFieldPresent(sfHookOn) ||
|
||||
hookSetObj.isFieldPresent(sfHookOnOutgoing) ||
|
||||
hookSetObj.isFieldPresent(sfHookOnIncoming)) &&
|
||||
!validateHookOn(ctx, hookSetObj))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -475,13 +407,6 @@ SetHook::validateHookSetEntry(SetHookCtx& ctx, STObject const& hookSetObj)
|
||||
// hookon may be present if the user so chooses
|
||||
// flags may be present if the user so chooses
|
||||
|
||||
if (ctx.rules.enabled(featureHookOnV2_1) &&
|
||||
(hookSetObj.isFieldPresent(sfHookOn) ||
|
||||
hookSetObj.isFieldPresent(sfHookOnOutgoing) ||
|
||||
hookSetObj.isFieldPresent(sfHookOnIncoming)) &&
|
||||
!validateHookOn(ctx, hookSetObj))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -531,8 +456,56 @@ SetHook::validateHookSetEntry(SetHookCtx& ctx, STObject const& hookSetObj)
|
||||
}
|
||||
|
||||
// validate sfHookOn
|
||||
if (!validateHookOn(ctx, hookSetObj))
|
||||
return false;
|
||||
if (!hookSetObj.isFieldPresent(sfHookOn))
|
||||
{
|
||||
if (!ctx.rules.enabled(featureHookOnV2))
|
||||
{
|
||||
JLOG(ctx.j.trace())
|
||||
<< "HookSet(" << hook::log::HOOKON_MISSING << ")["
|
||||
<< HS_ACC()
|
||||
<< "]: Malformed transaction: SetHook must include "
|
||||
"sfHookOn before featureHookOnV2 is enabled.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!hookSetObj.isFieldPresent(sfHookOnOutgoing) ||
|
||||
!hookSetObj.isFieldPresent(sfHookOnIncoming))
|
||||
{
|
||||
JLOG(ctx.j.trace())
|
||||
<< "HookSet(" << hook::log::HOOKON_MISSING << ")["
|
||||
<< HS_ACC()
|
||||
<< "]: Malformed transaction: SetHook must include "
|
||||
"sfHookOnOutgoing and sfHookOnIncoming "
|
||||
"when creating a new hook without sfHookOn.";
|
||||
return false;
|
||||
}
|
||||
|
||||
auto const outgoing = hookSetObj.getFieldH256(sfHookOnOutgoing);
|
||||
auto const incoming = hookSetObj.getFieldH256(sfHookOnIncoming);
|
||||
if (outgoing == incoming)
|
||||
{
|
||||
JLOG(ctx.j.trace())
|
||||
<< "HookSet(" << hook::log::HOOKON_MISSING << ")["
|
||||
<< HS_ACC()
|
||||
<< "]: Malformed transaction: SetHook outgoing and "
|
||||
"incoming hookon must be different.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (hookSetObj.isFieldPresent(sfHookOnOutgoing) ||
|
||||
hookSetObj.isFieldPresent(sfHookOnIncoming))
|
||||
{
|
||||
JLOG(ctx.j.trace())
|
||||
<< "HookSet(" << hook::log::HOOKON_MISSING << ")["
|
||||
<< HS_ACC()
|
||||
<< "]: Malformed transaction: SetHook must no"
|
||||
"include sfHookOnOutgoing and sfHookOnIncoming "
|
||||
"when creating a new hook with sfHookOn.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// validate sfHookCanEmit
|
||||
// HookCanEmit field is an optional field for backward compatibility
|
||||
@@ -657,7 +630,7 @@ SetHook::validateHookName(Blob const& name, beast::Journal const& j)
|
||||
<< "sfHookName must be between 8 and 32 hex characters.";
|
||||
return false;
|
||||
}
|
||||
if (!isValidUTF8(makeSlice(name)))
|
||||
if (!URIToken::validateUTF8(name))
|
||||
{
|
||||
JLOG(j.trace()) << "sfHookName must be a valid UTF-8 string.";
|
||||
return false;
|
||||
@@ -1432,7 +1405,7 @@ SetHook::setHook()
|
||||
HookSetOperation op = hsoNOOP;
|
||||
|
||||
if (hookSetObj)
|
||||
op = inferOperation(ctx, hookSetObj->get());
|
||||
op = inferOperation(hookSetObj->get());
|
||||
|
||||
// these flags are not able to be passed onto the ledger object
|
||||
int newFlags = 0;
|
||||
@@ -1654,23 +1627,10 @@ SetHook::setHook()
|
||||
newHook.setFieldH256(sfHookNamespace, *newNamespace);
|
||||
}
|
||||
|
||||
if (ctx.rules.enabled(featureHookOnV2_1))
|
||||
{
|
||||
// sanity check
|
||||
if (newHookOn && (newHookOnOutgoing || newHookOnIncoming))
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
if ((!newHookOnOutgoing && newHookOnIncoming) ||
|
||||
(newHookOnOutgoing && !newHookOnIncoming))
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
// set the hookon field if it differs from definition
|
||||
if (newHookOn)
|
||||
{
|
||||
if ((!view().rules().enabled(featureHookOnV2_1) ||
|
||||
defHookOn.has_value()) &&
|
||||
*defHookOn == *newHookOn)
|
||||
if (*defHookOn == *newHookOn)
|
||||
{
|
||||
if (newHook.isFieldPresent(sfHookOn))
|
||||
newHook.makeFieldAbsent(sfHookOn);
|
||||
@@ -1705,22 +1665,6 @@ SetHook::setHook()
|
||||
sfHookOnIncoming, *newHookOnIncoming);
|
||||
}
|
||||
|
||||
if (ctx.rules.enabled(featureHookOnV2_1))
|
||||
{
|
||||
if (newHookOn)
|
||||
{
|
||||
if (newHook.isFieldPresent(sfHookOnIncoming))
|
||||
newHook.makeFieldAbsent(sfHookOnIncoming);
|
||||
if (newHook.isFieldPresent(sfHookOnOutgoing))
|
||||
newHook.makeFieldAbsent(sfHookOnOutgoing);
|
||||
}
|
||||
if (newHookOnOutgoing || newHookOnIncoming)
|
||||
{
|
||||
if (newHook.isFieldPresent(sfHookOn))
|
||||
newHook.makeFieldAbsent(sfHookOn);
|
||||
}
|
||||
}
|
||||
|
||||
// set the hookcanemit field if it differs from definition
|
||||
if (newHookCanEmit)
|
||||
{
|
||||
@@ -1899,8 +1843,7 @@ SetHook::setHook()
|
||||
newHookDef->setFieldH256(sfHookHash, *createHookHash);
|
||||
|
||||
// only HookOn or (HookOnOutgoing and HookOnIncoming)
|
||||
if ((!view().rules().enabled(featureHookOnV2) &&
|
||||
!view().rules().enabled(featureHookOnV2_1)) ||
|
||||
if (!view().rules().enabled(featureHookOnV2) ||
|
||||
(!newHookOnOutgoing && !newHookOnIncoming))
|
||||
newHookDef->setFieldH256(sfHookOn, *newHookOn);
|
||||
else
|
||||
|
||||
@@ -86,7 +86,7 @@ public:
|
||||
calculateBaseFee(ReadView const& view, STTx const& tx);
|
||||
|
||||
static HookSetOperation
|
||||
inferOperation(SetHookCtx& ctx, STObject const& hookSetObj);
|
||||
inferOperation(STObject const& hookSetObj);
|
||||
|
||||
static HookSetValidation
|
||||
validateHookSetEntry(SetHookCtx& ctx, STObject const& hookSetObj);
|
||||
|
||||
@@ -155,11 +155,7 @@ preflight1(PreflightContext const& ctx)
|
||||
!ctx.rules.enabled(featureNamedHooks))
|
||||
return temMALFORMED;
|
||||
|
||||
auto const& name = ctx.tx.getFieldVL(sfHookName);
|
||||
|
||||
if (name.size() == 0 && ctx.rules.enabled(fixHookNameValidation))
|
||||
return temMALFORMED;
|
||||
if (!SetHook::validateHookName(name, ctx.j))
|
||||
if (!SetHook::validateHookName(ctx.tx.getFieldVL(sfHookName), ctx.j))
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
#include <xrpl/protocol/STAccount.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/protocol/UTF8.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
@@ -99,7 +98,7 @@ URIToken::preflight(PreflightContext const& ctx)
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
if (!isValidUTF8(makeSlice(uri)))
|
||||
if (!validateUTF8(uri))
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Malformed transaction. URI must be a "
|
||||
"valid utf-8 string.";
|
||||
|
||||
@@ -30,6 +30,56 @@ namespace ripple {
|
||||
class URIToken : public Transactor
|
||||
{
|
||||
public:
|
||||
bool inline static validateUTF8(std::vector<uint8_t> const& u)
|
||||
{
|
||||
// this code is from
|
||||
// https://www.cl.cam.ac.uk/~mgk25/ucs/utf8_check.c
|
||||
uint8_t const* s = (uint8_t const*)u.data();
|
||||
uint8_t const* end = s + u.size();
|
||||
while (s < end)
|
||||
{
|
||||
if (*s < 0x80)
|
||||
/* 0xxxxxxx */
|
||||
s++;
|
||||
else if ((s[0] & 0xe0) == 0xc0)
|
||||
{
|
||||
/* 110XXXXx 10xxxxxx */
|
||||
if ((s[1] & 0xc0) != 0x80 ||
|
||||
(s[0] & 0xfe) == 0xc0) /* overlong? */
|
||||
return false;
|
||||
else
|
||||
s += 2;
|
||||
}
|
||||
else if ((s[0] & 0xf0) == 0xe0)
|
||||
{
|
||||
/* 1110XXXX 10Xxxxxx 10xxxxxx */
|
||||
if ((s[1] & 0xc0) != 0x80 || (s[2] & 0xc0) != 0x80 ||
|
||||
(s[0] == 0xe0 && (s[1] & 0xe0) == 0x80) || /* overlong? */
|
||||
(s[0] == 0xed && (s[1] & 0xe0) == 0xa0) || /* surrogate? */
|
||||
(s[0] == 0xef && s[1] == 0xbf &&
|
||||
(s[2] & 0xfe) == 0xbe)) /* U+FFFE or U+FFFF? */
|
||||
return false;
|
||||
else
|
||||
s += 3;
|
||||
}
|
||||
else if ((s[0] & 0xf8) == 0xf0)
|
||||
{
|
||||
/* 11110XXX 10XXxxxx 10xxxxxx 10xxxxxx */
|
||||
if ((s[1] & 0xc0) != 0x80 || (s[2] & 0xc0) != 0x80 ||
|
||||
(s[3] & 0xc0) != 0x80 ||
|
||||
(s[0] == 0xf0 && (s[1] & 0xf0) == 0x80) || /* overlong? */
|
||||
(s[0] == 0xf4 && s[1] > 0x8f) ||
|
||||
s[0] > 0xf4) /* > U+10FFFF? */
|
||||
return false;
|
||||
else
|
||||
s += 4;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
|
||||
|
||||
explicit URIToken(ApplyContext& ctx) : Transactor(ctx)
|
||||
|
||||
@@ -481,7 +481,11 @@ transferHelper(
|
||||
}
|
||||
|
||||
// Create the account.
|
||||
std::uint32_t const seqno = newAccountSeqNo(psb);
|
||||
std::uint32_t const seqno{
|
||||
psb.rules().enabled(featureXahauGenesis)
|
||||
? psb.info().parentCloseTime.time_since_epoch().count()
|
||||
: psb.rules().enabled(featureDeletableAccounts) ? psb.seq()
|
||||
: 1};
|
||||
|
||||
sleDst = std::make_shared<SLE>(dstK);
|
||||
sleDst->setAccountID(sfAccount, dst);
|
||||
|
||||
@@ -1266,24 +1266,6 @@ deleteAMMTrustLine(
|
||||
std::optional<AccountID> const& ammAccountID,
|
||||
beast::Journal j);
|
||||
|
||||
[[nodiscard]] inline std::uint32_t
|
||||
newAccountSeqNo(ReadView const& view)
|
||||
{
|
||||
auto const time = view.info().parentCloseTime.time_since_epoch().count();
|
||||
return view.rules().enabled(featureXahauGenesis)
|
||||
// TEQU:
|
||||
// When creating accounts in GenesisLedger, we previously set
|
||||
// the account Sequence to 0. However, since this conflicts with
|
||||
// the PseudoAccount requirements, we are changing it to 1.
|
||||
// There will be no impact on networks that are already running,
|
||||
// and for future networks, there won't be any impact unless you
|
||||
// create accounts via GenesisLedger.
|
||||
// This specifically addresses an issue that comes up during
|
||||
// unittests.
|
||||
? (time == 0 ? 1 : time)
|
||||
: view.rules().enabled(featureDeletableAccounts) ? view.seq() : 1;
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
@@ -22,10 +22,12 @@
|
||||
#include <xrpld/app/main/Application.h>
|
||||
#include <xrpld/app/misc/AmendmentTable.h>
|
||||
#include <xrpld/app/misc/NetworkOPs.h>
|
||||
#include <xrpld/core/Config.h>
|
||||
#include <xrpld/rpc/detail/TransactionSign.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/json/json_writer.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/RPCErr.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/protocol/digest.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
@@ -33,6 +35,14 @@
|
||||
#include <magic_enum.hpp>
|
||||
#include <sstream>
|
||||
|
||||
#define MAGIC_ENUM(x, _min, _max) \
|
||||
template <> \
|
||||
struct magic_enum::customize::enum_range<x> \
|
||||
{ \
|
||||
static constexpr int min = _min; \
|
||||
static constexpr int max = _max; \
|
||||
};
|
||||
|
||||
#define MAGIC_ENUM_16(x) \
|
||||
template <> \
|
||||
struct magic_enum::customize::enum_range<x> \
|
||||
@@ -48,6 +58,15 @@
|
||||
static constexpr bool is_flags = true; \
|
||||
};
|
||||
|
||||
MAGIC_ENUM(ripple::SerializedTypeID, -2, 10004);
|
||||
MAGIC_ENUM(ripple::LedgerEntryType, 0, 255);
|
||||
MAGIC_ENUM(ripple::TELcodes, -399, 300);
|
||||
MAGIC_ENUM(ripple::TEMcodes, -299, -200);
|
||||
MAGIC_ENUM(ripple::TEFcodes, -199, -100);
|
||||
MAGIC_ENUM(ripple::TERcodes, -99, -1);
|
||||
MAGIC_ENUM(ripple::TEScodes, 0, 1);
|
||||
MAGIC_ENUM(ripple::TECcodes, 100, 255);
|
||||
MAGIC_ENUM_16(ripple::TxType);
|
||||
MAGIC_ENUM_FLAG(ripple::UniversalFlags);
|
||||
MAGIC_ENUM_FLAG(ripple::AccountSetFlags);
|
||||
MAGIC_ENUM_FLAG(ripple::OfferCreateFlags);
|
||||
@@ -173,19 +192,24 @@ private:
|
||||
|
||||
ret[jss::TYPES]["Done"] = -1;
|
||||
std::map<int32_t, std::string> type_map{{-1, "Done"}};
|
||||
for (auto const& [rawName, typeValue] : sTypeMap)
|
||||
for (auto const& entry : magic_enum::enum_entries<SerializedTypeID>())
|
||||
{
|
||||
std::string typeName =
|
||||
translate(std::string(rawName).substr(4) /* remove STI_ */);
|
||||
ret[jss::TYPES][typeName] = typeValue;
|
||||
type_map[typeValue] = typeName;
|
||||
const auto name = entry.second;
|
||||
std::string type_name =
|
||||
translate(name.data() + 4 /* remove STI_ */);
|
||||
int32_t type_value = static_cast<int32_t>(entry.first);
|
||||
ret[jss::TYPES][type_name] = type_value;
|
||||
type_map[type_value] = type_name;
|
||||
}
|
||||
|
||||
ret[jss::LEDGER_ENTRY_TYPES] = Json::objectValue;
|
||||
ret[jss::LEDGER_ENTRY_TYPES][jss::Invalid] = -1;
|
||||
for (auto const& f : LedgerFormats::getInstance())
|
||||
for (auto const& entry : magic_enum::enum_entries<LedgerEntryType>())
|
||||
{
|
||||
ret[jss::LEDGER_ENTRY_TYPES][f.getName()] = f.getType();
|
||||
const auto name = entry.second;
|
||||
std::string type_name = translate(name.data() + 2 /* remove lt_ */);
|
||||
int32_t type_value = static_cast<int32_t>(entry.first);
|
||||
ret[jss::LEDGER_ENTRY_TYPES][type_name] = type_value;
|
||||
}
|
||||
|
||||
ret[jss::FIELDS] = Json::arrayValue;
|
||||
@@ -302,16 +326,71 @@ private:
|
||||
}
|
||||
|
||||
ret[jss::TRANSACTION_RESULTS] = Json::objectValue;
|
||||
for (auto const& [code, terInfo] : transResults())
|
||||
for (auto const& entry : magic_enum::enum_entries<TELcodes>())
|
||||
{
|
||||
ret[jss::TRANSACTION_RESULTS][terInfo.first] = code;
|
||||
const auto name = entry.second;
|
||||
ret[jss::TRANSACTION_RESULTS][STR(name)] =
|
||||
static_cast<int32_t>(entry.first);
|
||||
}
|
||||
for (auto const& entry : magic_enum::enum_entries<TEMcodes>())
|
||||
{
|
||||
const auto name = entry.second;
|
||||
ret[jss::TRANSACTION_RESULTS][STR(name)] =
|
||||
static_cast<int32_t>(entry.first);
|
||||
}
|
||||
for (auto const& entry : magic_enum::enum_entries<TEFcodes>())
|
||||
{
|
||||
const auto name = entry.second;
|
||||
ret[jss::TRANSACTION_RESULTS][STR(name)] =
|
||||
static_cast<int32_t>(entry.first);
|
||||
}
|
||||
for (auto const& entry : magic_enum::enum_entries<TERcodes>())
|
||||
{
|
||||
const auto name = entry.second;
|
||||
ret[jss::TRANSACTION_RESULTS][STR(name)] =
|
||||
static_cast<int32_t>(entry.first);
|
||||
}
|
||||
for (auto const& entry : magic_enum::enum_entries<TEScodes>())
|
||||
{
|
||||
const auto name = entry.second;
|
||||
ret[jss::TRANSACTION_RESULTS][STR(name)] =
|
||||
static_cast<int32_t>(entry.first);
|
||||
}
|
||||
for (auto const& entry : magic_enum::enum_entries<TECcodes>())
|
||||
{
|
||||
const auto name = entry.second;
|
||||
ret[jss::TRANSACTION_RESULTS][STR(name)] =
|
||||
static_cast<int32_t>(entry.first);
|
||||
}
|
||||
|
||||
auto const translate_tt = [](std::string inp) -> std::string {
|
||||
if (inp == "Amendment")
|
||||
return "EnableAmendment";
|
||||
if (inp == "Fee")
|
||||
return "SetFee";
|
||||
if (inp == "PaychanClaim")
|
||||
return "PaymentChannelClaim";
|
||||
if (inp == "PaychanCreate")
|
||||
return "PaymentChannelCreate";
|
||||
if (inp == "PaychanFund")
|
||||
return "PaymentChannelFund";
|
||||
if (inp == "RegularKeySet")
|
||||
return "SetRegularKey";
|
||||
if (inp == "HookSet")
|
||||
return "SetHook";
|
||||
if (inp == "RemarksSet")
|
||||
return "SetRemarks";
|
||||
return inp;
|
||||
};
|
||||
|
||||
ret[jss::TRANSACTION_TYPES] = Json::objectValue;
|
||||
ret[jss::TRANSACTION_TYPES][jss::Invalid] = -1;
|
||||
for (auto const& f : TxFormats::getInstance())
|
||||
for (auto const& entry : magic_enum::enum_entries<TxType>())
|
||||
{
|
||||
ret[jss::TRANSACTION_TYPES][f.getName()] = f.getType();
|
||||
const auto name = entry.second;
|
||||
std::string type_name = translate_tt(translate(name.data() + 2));
|
||||
int32_t type_value = static_cast<int32_t>(entry.first);
|
||||
ret[jss::TRANSACTION_TYPES][type_name] = type_value;
|
||||
}
|
||||
|
||||
// Transaction Flags:
|
||||
@@ -466,25 +545,6 @@ doServerDefinitions(RPC::JsonContext& context)
|
||||
features[to_string(h)][jss::majority] =
|
||||
t.time_since_epoch().count();
|
||||
|
||||
// getJson's enabled is on-ledger; [features] also apply here.
|
||||
for (auto const& name : features.getMemberNames())
|
||||
{
|
||||
Json::Value& entry = features[name];
|
||||
entry[jss::ledger_enabled] = entry[jss::enabled].asBool();
|
||||
}
|
||||
for (auto const& h : context.app.config().features)
|
||||
{
|
||||
Json::Value& entry = features[to_string(h)];
|
||||
if (!entry.isMember(jss::name))
|
||||
{
|
||||
if (auto const fname = featureToName(h); !fname.empty())
|
||||
entry[jss::name] = fname;
|
||||
}
|
||||
if (!entry.isMember(jss::ledger_enabled))
|
||||
entry[jss::ledger_enabled] = false;
|
||||
entry[jss::enabled] = true;
|
||||
}
|
||||
|
||||
lastFeatures = features;
|
||||
{
|
||||
const std::string out = Json::FastWriter().write(features);
|
||||
|
||||
Reference in new issue
Block a user