mirror of
https://github.com/XRPLF/rippled.git
synced 2026-06-03 00:36:48 +00:00
Merge branch 'ximinez/number-fix-maxrepcusp' into ximinez/number-division-accuracy
This commit is contained in:
2
.github/scripts/rename/binary.sh
vendored
2
.github/scripts/rename/binary.sh
vendored
@@ -6,7 +6,7 @@ set -e
|
||||
# On MacOS, ensure that GNU sed is installed and available as `gsed`.
|
||||
SED_COMMAND=sed
|
||||
if [[ "${OSTYPE}" == 'darwin'* ]]; then
|
||||
if ! command -v gsed &> /dev/null; then
|
||||
if ! command -v gsed &>/dev/null; then
|
||||
echo "Error: gsed is not installed. Please install it using 'brew install gnu-sed'."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
12
.github/scripts/rename/cmake.sh
vendored
12
.github/scripts/rename/cmake.sh
vendored
@@ -8,12 +8,12 @@ set -e
|
||||
SED_COMMAND=sed
|
||||
HEAD_COMMAND=head
|
||||
if [[ "${OSTYPE}" == 'darwin'* ]]; then
|
||||
if ! command -v gsed &> /dev/null; then
|
||||
if ! command -v gsed &>/dev/null; then
|
||||
echo "Error: gsed is not installed. Please install it using 'brew install gnu-sed'."
|
||||
exit 1
|
||||
fi
|
||||
SED_COMMAND=gsed
|
||||
if ! command -v ghead &> /dev/null; then
|
||||
if ! command -v ghead &>/dev/null; then
|
||||
echo "Error: ghead is not installed. Please install it using 'brew install coreutils'."
|
||||
exit 1
|
||||
fi
|
||||
@@ -74,10 +74,10 @@ if grep -q '"xrpld"' cmake/XrplCore.cmake; then
|
||||
# The script has been rerun, so just restore the name of the binary.
|
||||
${SED_COMMAND} -i 's/"xrpld"/"rippled"/' cmake/XrplCore.cmake
|
||||
elif ! grep -q '"rippled"' cmake/XrplCore.cmake; then
|
||||
${HEAD_COMMAND} -n -1 cmake/XrplCore.cmake > cmake.tmp
|
||||
echo ' # For the time being, we will keep the name of the binary as it was.' >> cmake.tmp
|
||||
echo ' set_target_properties(xrpld PROPERTIES OUTPUT_NAME "rippled")' >> cmake.tmp
|
||||
tail -1 cmake/XrplCore.cmake >> cmake.tmp
|
||||
${HEAD_COMMAND} -n -1 cmake/XrplCore.cmake >cmake.tmp
|
||||
echo ' # For the time being, we will keep the name of the binary as it was.' >>cmake.tmp
|
||||
echo ' set_target_properties(xrpld PROPERTIES OUTPUT_NAME "rippled")' >>cmake.tmp
|
||||
tail -1 cmake/XrplCore.cmake >>cmake.tmp
|
||||
mv cmake.tmp cmake/XrplCore.cmake
|
||||
fi
|
||||
|
||||
|
||||
2
.github/scripts/rename/config.sh
vendored
2
.github/scripts/rename/config.sh
vendored
@@ -6,7 +6,7 @@ set -e
|
||||
# On MacOS, ensure that GNU sed is installed and available as `gsed`.
|
||||
SED_COMMAND=sed
|
||||
if [[ "${OSTYPE}" == 'darwin'* ]]; then
|
||||
if ! command -v gsed &> /dev/null; then
|
||||
if ! command -v gsed &>/dev/null; then
|
||||
echo "Error: gsed is not installed. Please install it using 'brew install gnu-sed'."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
24
.github/scripts/rename/copyright.sh
vendored
24
.github/scripts/rename/copyright.sh
vendored
@@ -6,7 +6,7 @@ set -e
|
||||
# On MacOS, ensure that GNU sed is installed and available as `gsed`.
|
||||
SED_COMMAND=sed
|
||||
if [[ "${OSTYPE}" == 'darwin'* ]]; then
|
||||
if ! command -v gsed &> /dev/null; then
|
||||
if ! command -v gsed &>/dev/null; then
|
||||
echo "Error: gsed is not installed. Please install it using 'brew install gnu-sed'."
|
||||
exit 1
|
||||
fi
|
||||
@@ -62,37 +62,37 @@ done
|
||||
# restoring the verbiage that is already present in LICENSE.md. Ensure that if
|
||||
# the script is run multiple times, duplicate notices are not added.
|
||||
if ! grep -q 'Raw Material Software' include/xrpl/beast/core/CurrentThreadName.h; then
|
||||
echo -e "// Portions of this file are from JUCE (http://www.juce.com).\n// Copyright (c) 2013 - Raw Material Software Ltd.\n// Please visit http://www.juce.com\n\n$(cat include/xrpl/beast/core/CurrentThreadName.h)" > include/xrpl/beast/core/CurrentThreadName.h
|
||||
echo -e "// Portions of this file are from JUCE (http://www.juce.com).\n// Copyright (c) 2013 - Raw Material Software Ltd.\n// Please visit http://www.juce.com\n\n$(cat include/xrpl/beast/core/CurrentThreadName.h)" >include/xrpl/beast/core/CurrentThreadName.h
|
||||
fi
|
||||
if ! grep -q 'Dev Null' src/test/app/NetworkID_test.cpp; then
|
||||
echo -e "// Copyright (c) 2020 Dev Null Productions\n\n$(cat src/test/app/NetworkID_test.cpp)" > src/test/app/NetworkID_test.cpp
|
||||
echo -e "// Copyright (c) 2020 Dev Null Productions\n\n$(cat src/test/app/NetworkID_test.cpp)" >src/test/app/NetworkID_test.cpp
|
||||
fi
|
||||
if ! grep -q 'Dev Null' src/test/app/tx/apply_test.cpp; then
|
||||
echo -e "// Copyright (c) 2020 Dev Null Productions\n\n$(cat src/test/app/tx/apply_test.cpp)" > src/test/app/tx/apply_test.cpp
|
||||
echo -e "// Copyright (c) 2020 Dev Null Productions\n\n$(cat src/test/app/tx/apply_test.cpp)" >src/test/app/tx/apply_test.cpp
|
||||
fi
|
||||
if ! grep -q 'Dev Null' src/test/rpc/ManifestRPC_test.cpp; then
|
||||
echo -e "// Copyright (c) 2020 Dev Null Productions\n\n$(cat src/test/rpc/ManifestRPC_test.cpp)" > src/test/rpc/ManifestRPC_test.cpp
|
||||
echo -e "// Copyright (c) 2020 Dev Null Productions\n\n$(cat src/test/rpc/ManifestRPC_test.cpp)" >src/test/rpc/ManifestRPC_test.cpp
|
||||
fi
|
||||
if ! grep -q 'Dev Null' src/test/rpc/ValidatorInfo_test.cpp; then
|
||||
echo -e "// Copyright (c) 2020 Dev Null Productions\n\n$(cat src/test/rpc/ValidatorInfo_test.cpp)" > src/test/rpc/ValidatorInfo_test.cpp
|
||||
echo -e "// Copyright (c) 2020 Dev Null Productions\n\n$(cat src/test/rpc/ValidatorInfo_test.cpp)" >src/test/rpc/ValidatorInfo_test.cpp
|
||||
fi
|
||||
if ! grep -q 'Dev Null' src/xrpld/rpc/handlers/server_info/Manifest.cpp; then
|
||||
echo -e "// Copyright (c) 2019 Dev Null Productions\n\n$(cat src/xrpld/rpc/handlers/server_info/Manifest.cpp)" > src/xrpld/rpc/handlers/server_info/Manifest.cpp
|
||||
echo -e "// Copyright (c) 2019 Dev Null Productions\n\n$(cat src/xrpld/rpc/handlers/server_info/Manifest.cpp)" >src/xrpld/rpc/handlers/server_info/Manifest.cpp
|
||||
fi
|
||||
if ! grep -q 'Dev Null' src/xrpld/rpc/handlers/admin/status/ValidatorInfo.cpp; then
|
||||
echo -e "// Copyright (c) 2019 Dev Null Productions\n\n$(cat src/xrpld/rpc/handlers/admin/status/ValidatorInfo.cpp)" > src/xrpld/rpc/handlers/admin/status/ValidatorInfo.cpp
|
||||
echo -e "// Copyright (c) 2019 Dev Null Productions\n\n$(cat src/xrpld/rpc/handlers/admin/status/ValidatorInfo.cpp)" >src/xrpld/rpc/handlers/admin/status/ValidatorInfo.cpp
|
||||
fi
|
||||
if ! grep -q 'Bougalis' include/xrpl/basics/SlabAllocator.h; then
|
||||
echo -e "// Copyright (c) 2022, Nikolaos D. Bougalis <nikb@bougalis.net>\n\n$(cat include/xrpl/basics/SlabAllocator.h)" > include/xrpl/basics/SlabAllocator.h # cspell: ignore Nikolaos Bougalis nikb
|
||||
echo -e "// Copyright (c) 2022, Nikolaos D. Bougalis <nikb@bougalis.net>\n\n$(cat include/xrpl/basics/SlabAllocator.h)" >include/xrpl/basics/SlabAllocator.h # cspell: ignore Nikolaos Bougalis nikb
|
||||
fi
|
||||
if ! grep -q 'Bougalis' include/xrpl/basics/spinlock.h; then
|
||||
echo -e "// Copyright (c) 2022, Nikolaos D. Bougalis <nikb@bougalis.net>\n\n$(cat include/xrpl/basics/spinlock.h)" > include/xrpl/basics/spinlock.h # cspell: ignore Nikolaos Bougalis nikb
|
||||
echo -e "// Copyright (c) 2022, Nikolaos D. Bougalis <nikb@bougalis.net>\n\n$(cat include/xrpl/basics/spinlock.h)" >include/xrpl/basics/spinlock.h # cspell: ignore Nikolaos Bougalis nikb
|
||||
fi
|
||||
if ! grep -q 'Bougalis' include/xrpl/basics/tagged_integer.h; then
|
||||
echo -e "// Copyright (c) 2014, Nikolaos D. Bougalis <nikb@bougalis.net>\n\n$(cat include/xrpl/basics/tagged_integer.h)" > include/xrpl/basics/tagged_integer.h # cspell: ignore Nikolaos Bougalis nikb
|
||||
echo -e "// Copyright (c) 2014, Nikolaos D. Bougalis <nikb@bougalis.net>\n\n$(cat include/xrpl/basics/tagged_integer.h)" >include/xrpl/basics/tagged_integer.h # cspell: ignore Nikolaos Bougalis nikb
|
||||
fi
|
||||
if ! grep -q 'Ritchford' include/xrpl/beast/utility/Zero.h; then
|
||||
echo -e "// Copyright (c) 2014, Tom Ritchford <tom@swirly.com>\n\n$(cat include/xrpl/beast/utility/Zero.h)" > include/xrpl/beast/utility/Zero.h # cspell: ignore Ritchford
|
||||
echo -e "// Copyright (c) 2014, Tom Ritchford <tom@swirly.com>\n\n$(cat include/xrpl/beast/utility/Zero.h)" >include/xrpl/beast/utility/Zero.h # cspell: ignore Ritchford
|
||||
fi
|
||||
|
||||
# Restore newlines and tabs in string literals in the affected file.
|
||||
|
||||
2
.github/scripts/rename/definitions.sh
vendored
2
.github/scripts/rename/definitions.sh
vendored
@@ -6,7 +6,7 @@ set -e
|
||||
# On MacOS, ensure that GNU sed is installed and available as `gsed`.
|
||||
SED_COMMAND=sed
|
||||
if [[ "${OSTYPE}" == 'darwin'* ]]; then
|
||||
if ! command -v gsed &> /dev/null; then
|
||||
if ! command -v gsed &>/dev/null; then
|
||||
echo "Error: gsed is not installed. Please install it using 'brew install gnu-sed'."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
2
.github/scripts/rename/docs.sh
vendored
2
.github/scripts/rename/docs.sh
vendored
@@ -6,7 +6,7 @@ set -e
|
||||
# On MacOS, ensure that GNU sed is installed and available as `gsed`.
|
||||
SED_COMMAND=sed
|
||||
if [[ "${OSTYPE}" == 'darwin'* ]]; then
|
||||
if ! command -v gsed &> /dev/null; then
|
||||
if ! command -v gsed &>/dev/null; then
|
||||
echo "Error: gsed is not installed. Please install it using 'brew install gnu-sed'."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
2
.github/scripts/rename/namespace.sh
vendored
2
.github/scripts/rename/namespace.sh
vendored
@@ -6,7 +6,7 @@ set -e
|
||||
# On MacOS, ensure that GNU sed is installed and available as `gsed`.
|
||||
SED_COMMAND=sed
|
||||
if [[ "${OSTYPE}" == 'darwin'* ]]; then
|
||||
if ! command -v gsed &> /dev/null; then
|
||||
if ! command -v gsed &>/dev/null; then
|
||||
echo "Error: gsed is not installed. Please install it using 'brew install gnu-sed'."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -37,37 +37,37 @@ repos:
|
||||
exclude: ^include/xrpl/protocol_autogen/(transactions|ledger_entries)/
|
||||
|
||||
- repo: https://github.com/pre-commit/mirrors-clang-format
|
||||
rev: cd481d7b0bfb5c7b3090c21846317f9a8262e891 # frozen: v22.1.0
|
||||
rev: dd18dad857d6133e90bbe478f4f2f22ec0030269 # frozen: v22.1.5
|
||||
hooks:
|
||||
- id: clang-format
|
||||
args: [--style=file]
|
||||
"types_or": [c++, c, proto]
|
||||
exclude: ^include/xrpl/protocol_autogen/(transactions|ledger_entries)/
|
||||
|
||||
- repo: https://github.com/BlankSpruce/gersemi
|
||||
rev: 0.26.0
|
||||
- repo: https://github.com/BlankSpruce/gersemi-pre-commit
|
||||
rev: faadd6a9d852369ca94f4d15b2404c967ba8cb01 # frozen: 0.27.6
|
||||
hooks:
|
||||
- id: gersemi
|
||||
|
||||
- repo: https://github.com/rbubley/mirrors-prettier
|
||||
rev: c2bc67fe8f8f549cc489e00ba8b45aa18ee713b1 # frozen: v3.8.1
|
||||
rev: 515f543f5718ebfd6ce22e16708bb32c68ff96e1 # frozen: v3.8.3
|
||||
hooks:
|
||||
- id: prettier
|
||||
args: [--end-of-line=auto]
|
||||
|
||||
- repo: https://github.com/psf/black-pre-commit-mirror
|
||||
rev: ea488cebbfd88a5f50b8bd95d5c829d0bb76feb8 # frozen: 26.1.0
|
||||
rev: 4160603246a6b365d4a2af661c6d71b0a0f50478 # frozen: 26.5.1
|
||||
hooks:
|
||||
- id: black
|
||||
|
||||
- repo: https://github.com/openstack/bashate
|
||||
rev: 5798d24d571676fc407e81df574c1ef57b520f23 # frozen: 2.1.1
|
||||
- repo: https://github.com/scop/pre-commit-shfmt
|
||||
rev: 05c1426671b9237fb5e1444dd63aa5731bec0dfb # frozen: v3.13.1-1
|
||||
hooks:
|
||||
- id: bashate
|
||||
args: ["--ignore=E006"]
|
||||
- id: shfmt
|
||||
args: [--write, --indent=4, --case-indent=true]
|
||||
|
||||
- repo: https://github.com/streetsidesoftware/cspell-cli
|
||||
rev: a42085ade523f591dca134379a595e7859986445 # frozen: v9.7.0
|
||||
rev: 4643f154907327ee0a2c7038f0296e0dd77d9776 # frozen: v10.0.0
|
||||
hooks:
|
||||
- id: cspell # Spell check changed files
|
||||
exclude: |
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [[ $# -ne 1 || "$1" == "--help" || "$1" == "-h" ]]; then
|
||||
name=$( basename $0 )
|
||||
cat <<- USAGE
|
||||
name=$(basename $0)
|
||||
cat <<-USAGE
|
||||
Usage: $name <username>
|
||||
|
||||
Where <username> is the Github username of the upstream repo. e.g. XRPLF
|
||||
@@ -14,7 +14,7 @@ fi
|
||||
shift
|
||||
user="$1"
|
||||
# Get the origin URL. Expect it be an SSH-style URL
|
||||
origin=$( git remote get-url origin )
|
||||
origin=$(git remote get-url origin)
|
||||
if [[ "${origin}" == "" ]]; then
|
||||
echo Invalid origin remote >&2
|
||||
exit 1
|
||||
@@ -22,11 +22,11 @@ fi
|
||||
# echo "Origin: ${origin}"
|
||||
# Parse the origin
|
||||
ifs_orig="${IFS}"
|
||||
IFS=':' read remote originpath <<< "${origin}"
|
||||
IFS=':' read remote originpath <<<"${origin}"
|
||||
# echo "Remote: ${remote}, Originpath: ${originpath}"
|
||||
IFS='@' read sshuser server <<< "${remote}"
|
||||
IFS='@' read sshuser server <<<"${remote}"
|
||||
# echo "SSHUser: ${sshuser}, Server: ${server}"
|
||||
IFS='/' read originuser repo <<< "${originpath}"
|
||||
IFS='/' read originuser repo <<<"${originpath}"
|
||||
# echo "Originuser: ${originuser}, Repo: ${repo}"
|
||||
if [[ "${sshuser}" == "" || "${server}" == "" || "${originuser}" == "" || "${repo}" == "" ]]; then
|
||||
echo "Can't parse origin URL: ${origin}" >&2
|
||||
@@ -35,9 +35,9 @@ fi
|
||||
upstream="https://${server}/${user}/${repo}"
|
||||
upstreampush="${remote}:${user}/${repo}"
|
||||
upstreamgroup="upstream upstream-push"
|
||||
current=$( git remote get-url upstream 2>/dev/null )
|
||||
currentpush=$( git remote get-url upstream-push 2>/dev/null )
|
||||
currentgroup=$( git config remotes.upstreams )
|
||||
current=$(git remote get-url upstream 2>/dev/null)
|
||||
currentpush=$(git remote get-url upstream-push 2>/dev/null)
|
||||
currentgroup=$(git config remotes.upstreams)
|
||||
if [[ "${current}" == "${upstream}" ]]; then
|
||||
echo "Upstream already set up correctly. Skip"
|
||||
elif [[ -n "${current}" && "${current}" != "${upstream}" && "${current}" != "${upstreampush}" ]]; then
|
||||
@@ -45,9 +45,9 @@ elif [[ -n "${current}" && "${current}" != "${upstream}" && "${current}" != "${u
|
||||
else
|
||||
if [[ "${current}" == "${upstreampush}" ]]; then
|
||||
echo "Upstream set to dangerous push URL. Update."
|
||||
_run git remote rename upstream upstream-push || \
|
||||
_run git remote remove upstream
|
||||
currentpush=$( git remote get-url upstream-push 2>/dev/null )
|
||||
_run git remote rename upstream upstream-push ||
|
||||
_run git remote remove upstream
|
||||
currentpush=$(git remote get-url upstream-push 2>/dev/null)
|
||||
fi
|
||||
_run git remote add upstream "${upstream}"
|
||||
fi
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [[ $# -lt 3 || "$1" == "--help" || "$1" = "-h" ]]; then
|
||||
name=$( basename $0 )
|
||||
cat <<- USAGE
|
||||
name=$(basename $0)
|
||||
cat <<-USAGE
|
||||
Usage: $name workbranch base/branch user/branch [user/branch [...]]
|
||||
|
||||
* workbranch will be created locally from base/branch
|
||||
@@ -16,7 +16,7 @@ fi
|
||||
work="$1"
|
||||
shift
|
||||
|
||||
branches=( $( echo "${@}" | sed "s/:/\//" ) )
|
||||
branches=($(echo "${@}" | sed "s/:/\//"))
|
||||
base="${branches[0]}"
|
||||
unset branches[0]
|
||||
|
||||
@@ -24,10 +24,10 @@ set -e
|
||||
|
||||
users=()
|
||||
for b in "${branches[@]}"; do
|
||||
users+=( $( echo $b | cut -d/ -f1 ) )
|
||||
users+=($(echo $b | cut -d/ -f1))
|
||||
done
|
||||
|
||||
users=( $( printf '%s\n' "${users[@]}" | sort -u ) )
|
||||
users=($(printf '%s\n' "${users[@]}" | sort -u))
|
||||
|
||||
git fetch --multiple upstreams "${users[@]}"
|
||||
git checkout -B "$work" --no-track "$base"
|
||||
@@ -40,7 +40,7 @@ done
|
||||
# Make sure the commits look right
|
||||
git log --show-signature "$base..HEAD"
|
||||
|
||||
parts=( $( echo $base | sed "s/\// /" ) )
|
||||
parts=($(echo $base | sed "s/\// /"))
|
||||
repo="${parts[0]}"
|
||||
b="${parts[1]}"
|
||||
push=$repo
|
||||
@@ -50,7 +50,7 @@ fi
|
||||
if [[ "$repo" == "upstream" ]]; then
|
||||
repo="upstreams"
|
||||
fi
|
||||
cat << PUSH
|
||||
cat <<PUSH
|
||||
|
||||
-------------------------------------------------------------------
|
||||
This script will not push. Verify everything is correct, then push
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [[ $# -ne 3 || "$1" == "--help" || "$1" = "-h" ]]; then
|
||||
name=$( basename $0 )
|
||||
cat <<- USAGE
|
||||
name=$(basename $0)
|
||||
cat <<-USAGE
|
||||
Usage: $name workbranch base/branch version
|
||||
|
||||
* workbranch will be created locally from base/branch. If it exists,
|
||||
@@ -16,7 +16,7 @@ fi
|
||||
work="$1"
|
||||
shift
|
||||
|
||||
base=$( echo "$1" | sed "s/:/\//" )
|
||||
base=$(echo "$1" | sed "s/:/\//")
|
||||
shift
|
||||
|
||||
version=$1
|
||||
@@ -28,16 +28,16 @@ git fetch upstreams
|
||||
|
||||
git checkout -B "${work}" --no-track "${base}"
|
||||
|
||||
push=$( git rev-parse --abbrev-ref --symbolic-full-name '@{push}' \
|
||||
2>/dev/null ) || true
|
||||
push=$(git rev-parse --abbrev-ref --symbolic-full-name '@{push}' \
|
||||
2>/dev/null) || true
|
||||
if [[ "${push}" != "" ]]; then
|
||||
echo "Warning: ${push} may already exist."
|
||||
fi
|
||||
|
||||
build=$( find -name BuildInfo.cpp )
|
||||
sed 's/\(^.*versionString =\).*$/\1 "'${version}'"/' ${build} > version.cpp && \
|
||||
diff "${build}" version.cpp && exit 1 || \
|
||||
mv -vi version.cpp ${build}
|
||||
build=$(find -name BuildInfo.cpp)
|
||||
sed 's/\(^.*versionString =\).*$/\1 "'${version}'"/' ${build} >version.cpp &&
|
||||
diff "${build}" version.cpp && exit 1 ||
|
||||
mv -vi version.cpp ${build}
|
||||
|
||||
git diff
|
||||
|
||||
@@ -47,7 +47,7 @@ git commit -S -m "Set version to ${version}"
|
||||
|
||||
git log --oneline --first-parent ${base}^..
|
||||
|
||||
cat << PUSH
|
||||
cat <<PUSH
|
||||
|
||||
-------------------------------------------------------------------
|
||||
This script will not push. Verify everything is correct, then push
|
||||
|
||||
13
cmake/scripts/codegen/requirements.in
Normal file
13
cmake/scripts/codegen/requirements.in
Normal file
@@ -0,0 +1,13 @@
|
||||
# Python dependencies for XRP Ledger code generation scripts
|
||||
#
|
||||
# These packages are required to run the code generation scripts that
|
||||
# parse macro files and generate C++ wrapper classes.
|
||||
|
||||
# C preprocessor for Python - used to preprocess macro files
|
||||
pcpp>=1.30
|
||||
|
||||
# Parser combinator library - used to parse the macro DSL
|
||||
pyparsing>=3.0.0
|
||||
|
||||
# Template engine - used to generate C++ code from templates
|
||||
Mako>=1.2.2
|
||||
@@ -1,13 +1,105 @@
|
||||
# Python dependencies for XRP Ledger code generation scripts
|
||||
#
|
||||
# These packages are required to run the code generation scripts that
|
||||
# parse macro files and generate C++ wrapper classes.
|
||||
|
||||
# C preprocessor for Python - used to preprocess macro files
|
||||
pcpp>=1.30
|
||||
|
||||
# Parser combinator library - used to parse the macro DSL
|
||||
pyparsing>=3.0.0
|
||||
|
||||
# Template engine - used to generate C++ code from templates
|
||||
Mako>=1.2.2
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv pip compile requirements.in --generate-hashes --output-file requirements.txt
|
||||
mako==1.3.12 \
|
||||
--hash=sha256:8f61569480282dbf557145ce441e4ba888be453c30989f879f0d652e39f53ea9 \
|
||||
--hash=sha256:9f778e93289bd410bb35daadeb4fc66d95a746f0b75777b942088b7fd7af550a
|
||||
# via -r requirements.in
|
||||
markupsafe==3.0.3 \
|
||||
--hash=sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f \
|
||||
--hash=sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a \
|
||||
--hash=sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf \
|
||||
--hash=sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19 \
|
||||
--hash=sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf \
|
||||
--hash=sha256:0f4b68347f8c5eab4a13419215bdfd7f8c9b19f2b25520968adfad23eb0ce60c \
|
||||
--hash=sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175 \
|
||||
--hash=sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219 \
|
||||
--hash=sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb \
|
||||
--hash=sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6 \
|
||||
--hash=sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab \
|
||||
--hash=sha256:15d939a21d546304880945ca1ecb8a039db6b4dc49b2c5a400387cdae6a62e26 \
|
||||
--hash=sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1 \
|
||||
--hash=sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce \
|
||||
--hash=sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218 \
|
||||
--hash=sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634 \
|
||||
--hash=sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695 \
|
||||
--hash=sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad \
|
||||
--hash=sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73 \
|
||||
--hash=sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c \
|
||||
--hash=sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe \
|
||||
--hash=sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa \
|
||||
--hash=sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559 \
|
||||
--hash=sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa \
|
||||
--hash=sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37 \
|
||||
--hash=sha256:3537e01efc9d4dccdf77221fb1cb3b8e1a38d5428920e0657ce299b20324d758 \
|
||||
--hash=sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f \
|
||||
--hash=sha256:38664109c14ffc9e7437e86b4dceb442b0096dfe3541d7864d9cbe1da4cf36c8 \
|
||||
--hash=sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d \
|
||||
--hash=sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c \
|
||||
--hash=sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97 \
|
||||
--hash=sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a \
|
||||
--hash=sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19 \
|
||||
--hash=sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9 \
|
||||
--hash=sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9 \
|
||||
--hash=sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc \
|
||||
--hash=sha256:591ae9f2a647529ca990bc681daebdd52c8791ff06c2bfa05b65163e28102ef2 \
|
||||
--hash=sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4 \
|
||||
--hash=sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354 \
|
||||
--hash=sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50 \
|
||||
--hash=sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698 \
|
||||
--hash=sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9 \
|
||||
--hash=sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b \
|
||||
--hash=sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc \
|
||||
--hash=sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115 \
|
||||
--hash=sha256:7c3fb7d25180895632e5d3148dbdc29ea38ccb7fd210aa27acbd1201a1902c6e \
|
||||
--hash=sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485 \
|
||||
--hash=sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f \
|
||||
--hash=sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12 \
|
||||
--hash=sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025 \
|
||||
--hash=sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009 \
|
||||
--hash=sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d \
|
||||
--hash=sha256:949b8d66bc381ee8b007cd945914c721d9aba8e27f71959d750a46f7c282b20b \
|
||||
--hash=sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a \
|
||||
--hash=sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5 \
|
||||
--hash=sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f \
|
||||
--hash=sha256:a320721ab5a1aba0a233739394eb907f8c8da5c98c9181d1161e77a0c8e36f2d \
|
||||
--hash=sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1 \
|
||||
--hash=sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287 \
|
||||
--hash=sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6 \
|
||||
--hash=sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f \
|
||||
--hash=sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581 \
|
||||
--hash=sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed \
|
||||
--hash=sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b \
|
||||
--hash=sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c \
|
||||
--hash=sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026 \
|
||||
--hash=sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8 \
|
||||
--hash=sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676 \
|
||||
--hash=sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6 \
|
||||
--hash=sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e \
|
||||
--hash=sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d \
|
||||
--hash=sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d \
|
||||
--hash=sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01 \
|
||||
--hash=sha256:df2449253ef108a379b8b5d6b43f4b1a8e81a061d6537becd5582fba5f9196d7 \
|
||||
--hash=sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419 \
|
||||
--hash=sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795 \
|
||||
--hash=sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1 \
|
||||
--hash=sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5 \
|
||||
--hash=sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d \
|
||||
--hash=sha256:e8fc20152abba6b83724d7ff268c249fa196d8259ff481f3b1476383f8f24e42 \
|
||||
--hash=sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe \
|
||||
--hash=sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda \
|
||||
--hash=sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e \
|
||||
--hash=sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737 \
|
||||
--hash=sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523 \
|
||||
--hash=sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591 \
|
||||
--hash=sha256:f71a396b3bf33ecaa1626c255855702aca4d3d9fea5e051b41ac59a9c1c41edc \
|
||||
--hash=sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a \
|
||||
--hash=sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50
|
||||
# via mako
|
||||
pcpp==1.30 \
|
||||
--hash=sha256:05fe08292b6da57f385001c891a87f40d6aa7f46787b03e8ba326d20a3297c6e \
|
||||
--hash=sha256:5af9fbce55f136d7931ae915fae03c34030a3b36c496e72d9636cedc8e2543a1
|
||||
# via -r requirements.in
|
||||
pyparsing==3.3.2 \
|
||||
--hash=sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d \
|
||||
--hash=sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc
|
||||
# via -r requirements.in
|
||||
|
||||
@@ -256,6 +256,7 @@ words:
|
||||
- sfields
|
||||
- shamap
|
||||
- shamapitem
|
||||
- shfmt
|
||||
- shlibs
|
||||
- sidechain
|
||||
- SIGGOOD
|
||||
|
||||
@@ -9,9 +9,12 @@ set -eo pipefail
|
||||
cpp_files_dir="${1:?usage: $0 <cpp_files_dir>}"
|
||||
|
||||
case "$(uname -m)" in
|
||||
x86_64) loader=/lib64/ld-linux-x86-64.so.2 ;;
|
||||
x86_64) loader=/lib64/ld-linux-x86-64.so.2 ;;
|
||||
aarch64) loader=/lib/ld-linux-aarch64.so.1 ;;
|
||||
*) echo "Unsupported arch: $(uname -m)" >&2; exit 1 ;;
|
||||
*)
|
||||
echo "Unsupported arch: $(uname -m)" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
declare -A sanitize=(
|
||||
@@ -35,8 +38,11 @@ for compiler in g++ clang++; do
|
||||
echo "=== Run ${name}-${compiler} ==="
|
||||
output=$("$bin" 2>&1) || true
|
||||
echo "$output"
|
||||
echo "$output" | grep -q "${expect[$name]}" \
|
||||
|| { echo "expected '${expect[$name]}' from $bin"; exit 1; }
|
||||
echo "$output" | grep -q "${expect[$name]}" ||
|
||||
{
|
||||
echo "expected '${expect[$name]}' from $bin"
|
||||
exit 1
|
||||
}
|
||||
rm -f "$bin"
|
||||
done
|
||||
done
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
|
||||
@@ -43,6 +45,14 @@ sharesToAssetsDeposit(
|
||||
/** Controls whether to truncate shares instead of rounding. */
|
||||
enum class TruncateShares : bool { No = false, Yes = true };
|
||||
|
||||
/** Controls whether the withdraw conversion helpers
|
||||
(assetsToSharesWithdraw and sharesToAssetsWithdraw) subtract
|
||||
sfLossUnrealized from sfAssetsTotal before computing the exchange rate.
|
||||
The default (No) applies the standard discounted rate; Yes is used when
|
||||
the redeemer is the sole remaining shareholder.
|
||||
*/
|
||||
enum class WaiveUnrealizedLoss : bool { No = false, Yes = true };
|
||||
|
||||
/** From the perspective of a vault, return the number of shares to demand from
|
||||
the depositor when they ask to withdraw a fixed amount of assets. Since
|
||||
shares are MPT this number is integral, and it will be rounded to nearest
|
||||
@@ -52,6 +62,8 @@ enum class TruncateShares : bool { No = false, Yes = true };
|
||||
@param issuance The MPTokenIssuance SLE for the vault's shares.
|
||||
@param assets The amount of assets to convert.
|
||||
@param truncate Whether to truncate instead of rounding.
|
||||
@param waive Whether to waive the unrealized-loss discount when computing
|
||||
the exchange rate.
|
||||
|
||||
@return The number of shares, or nullopt on error.
|
||||
*/
|
||||
@@ -60,7 +72,8 @@ assetsToSharesWithdraw(
|
||||
std::shared_ptr<SLE const> const& vault,
|
||||
std::shared_ptr<SLE const> const& issuance,
|
||||
STAmount const& assets,
|
||||
TruncateShares truncate = TruncateShares::No);
|
||||
TruncateShares truncate = TruncateShares::No,
|
||||
WaiveUnrealizedLoss waive = WaiveUnrealizedLoss::No);
|
||||
|
||||
/** From the perspective of a vault, return the number of assets to give the
|
||||
depositor when they redeem a fixed amount of shares. Note, since shares are
|
||||
@@ -69,6 +82,8 @@ assetsToSharesWithdraw(
|
||||
@param vault The vault SLE.
|
||||
@param issuance The MPTokenIssuance SLE for the vault's shares.
|
||||
@param shares The amount of shares to convert.
|
||||
@param waive Whether to waive (i.e. not subtract) the vault's unrealized
|
||||
loss when computing the exchange rate.
|
||||
|
||||
@return The number of assets, or nullopt on error.
|
||||
*/
|
||||
@@ -76,6 +91,22 @@ assetsToSharesWithdraw(
|
||||
sharesToAssetsWithdraw(
|
||||
std::shared_ptr<SLE const> const& vault,
|
||||
std::shared_ptr<SLE const> const& issuance,
|
||||
STAmount const& shares);
|
||||
STAmount const& shares,
|
||||
WaiveUnrealizedLoss waive = WaiveUnrealizedLoss::No);
|
||||
|
||||
/** Returns true iff `account` holds all of the vault's outstanding shares —
|
||||
i.e. is the sole remaining shareholder. Returns false if the account
|
||||
holds no shares or fewer than the total outstanding.
|
||||
|
||||
@param view The ledger view.
|
||||
@param account The candidate sole shareholder.
|
||||
@param issuance The MPTokenIssuance SLE for the vault's shares; provides
|
||||
both the share MPTID and the outstanding-amount total.
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
isSoleShareholder(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
std::shared_ptr<SLE const> const& issuance);
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -36,12 +36,35 @@ SOURCE_DATE_EPOCH="${SOURCE_DATE_EPOCH:-}"
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--src-dir) need_arg "$@"; SRC_DIR="$2"; shift 2 ;;
|
||||
--build-dir) need_arg "$@"; BUILD_DIR="$2"; shift 2 ;;
|
||||
--pkg-version) need_arg "$@"; PKG_VERSION="$2"; shift 2 ;;
|
||||
--pkg-release) need_arg "$@"; PKG_RELEASE="$2"; shift 2 ;;
|
||||
--source-date-epoch) need_arg "$@"; SOURCE_DATE_EPOCH="$2"; shift 2 ;;
|
||||
-h|--help) usage; exit 0 ;;
|
||||
--src-dir)
|
||||
need_arg "$@"
|
||||
SRC_DIR="$2"
|
||||
shift 2
|
||||
;;
|
||||
--build-dir)
|
||||
need_arg "$@"
|
||||
BUILD_DIR="$2"
|
||||
shift 2
|
||||
;;
|
||||
--pkg-version)
|
||||
need_arg "$@"
|
||||
PKG_VERSION="$2"
|
||||
shift 2
|
||||
;;
|
||||
--pkg-release)
|
||||
need_arg "$@"
|
||||
PKG_RELEASE="$2"
|
||||
shift 2
|
||||
;;
|
||||
--source-date-epoch)
|
||||
need_arg "$@"
|
||||
SOURCE_DATE_EPOCH="$2"
|
||||
shift 2
|
||||
;;
|
||||
-h | --help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Unknown argument: $1" >&2
|
||||
usage >&2
|
||||
@@ -109,20 +132,20 @@ stage_common() {
|
||||
local dest="$1"
|
||||
mkdir -p "${dest}"
|
||||
|
||||
cp "${BUILD_DIR}/xrpld" "${dest}/xrpld"
|
||||
cp "${SRC_DIR}/cfg/xrpld-example.cfg" "${dest}/xrpld.cfg"
|
||||
cp "${SRC_DIR}/cfg/validators-example.txt" "${dest}/validators.txt"
|
||||
cp "${SRC_DIR}/LICENSE.md" "${dest}/LICENSE.md"
|
||||
cp "${SRC_DIR}/README.md" "${dest}/README.md"
|
||||
cp "${BUILD_DIR}/xrpld" "${dest}/xrpld"
|
||||
cp "${SRC_DIR}/cfg/xrpld-example.cfg" "${dest}/xrpld.cfg"
|
||||
cp "${SRC_DIR}/cfg/validators-example.txt" "${dest}/validators.txt"
|
||||
cp "${SRC_DIR}/LICENSE.md" "${dest}/LICENSE.md"
|
||||
cp "${SRC_DIR}/README.md" "${dest}/README.md"
|
||||
|
||||
cp "${SHARED}/xrpld.service" "${dest}/xrpld.service"
|
||||
cp "${SHARED}/xrpld.sysusers" "${dest}/xrpld.sysusers"
|
||||
cp "${SHARED}/xrpld.tmpfiles" "${dest}/xrpld.tmpfiles"
|
||||
cp "${SHARED}/xrpld.logrotate" "${dest}/xrpld.logrotate"
|
||||
cp "${SHARED}/update-xrpld" "${dest}/update-xrpld"
|
||||
cp "${SHARED}/update-xrpld.service" "${dest}/update-xrpld.service"
|
||||
cp "${SHARED}/update-xrpld.timer" "${dest}/update-xrpld.timer"
|
||||
cp "${SHARED}/50-xrpld.preset" "${dest}/50-xrpld.preset"
|
||||
cp "${SHARED}/xrpld.service" "${dest}/xrpld.service"
|
||||
cp "${SHARED}/xrpld.sysusers" "${dest}/xrpld.sysusers"
|
||||
cp "${SHARED}/xrpld.tmpfiles" "${dest}/xrpld.tmpfiles"
|
||||
cp "${SHARED}/xrpld.logrotate" "${dest}/xrpld.logrotate"
|
||||
cp "${SHARED}/update-xrpld" "${dest}/update-xrpld"
|
||||
cp "${SHARED}/update-xrpld.service" "${dest}/update-xrpld.service"
|
||||
cp "${SHARED}/update-xrpld.timer" "${dest}/update-xrpld.timer"
|
||||
cp "${SHARED}/50-xrpld.preset" "${dest}/50-xrpld.preset"
|
||||
}
|
||||
|
||||
build_rpm() {
|
||||
@@ -159,12 +182,12 @@ build_deb() {
|
||||
cp -r "${DEBIAN_DIR}" "${staging}/debian"
|
||||
|
||||
# Debhelper auto-discovers these only from debian/.
|
||||
cp "${staging}/xrpld.service" "${staging}/debian/xrpld.service"
|
||||
cp "${staging}/xrpld.sysusers" "${staging}/debian/xrpld.sysusers"
|
||||
cp "${staging}/xrpld.tmpfiles" "${staging}/debian/xrpld.tmpfiles"
|
||||
cp "${staging}/xrpld.logrotate" "${staging}/debian/xrpld.logrotate"
|
||||
cp "${staging}/xrpld.service" "${staging}/debian/xrpld.service"
|
||||
cp "${staging}/xrpld.sysusers" "${staging}/debian/xrpld.sysusers"
|
||||
cp "${staging}/xrpld.tmpfiles" "${staging}/debian/xrpld.tmpfiles"
|
||||
cp "${staging}/xrpld.logrotate" "${staging}/debian/xrpld.logrotate"
|
||||
cp "${staging}/update-xrpld.service" "${staging}/debian/xrpld.update-xrpld.service"
|
||||
cp "${staging}/update-xrpld.timer" "${staging}/debian/xrpld.update-xrpld.timer"
|
||||
cp "${staging}/update-xrpld.timer" "${staging}/debian/xrpld.update-xrpld.timer"
|
||||
|
||||
# Debian '~' marks a pre-release; 3.2.0~b1 sorts before 3.2.0.
|
||||
local deb_full_version="${VER_BASE}${VER_SUFFIX:+~${VER_SUFFIX}}-${PKG_RELEASE}"
|
||||
@@ -175,12 +198,12 @@ build_deb() {
|
||||
# b<N>, rc<N> -> unstable (pre-release)
|
||||
local deb_distribution
|
||||
case "${VER_SUFFIX}" in
|
||||
"") deb_distribution="stable" ;;
|
||||
b0) deb_distribution="develop" ;;
|
||||
*) deb_distribution="unstable" ;;
|
||||
"") deb_distribution="stable" ;;
|
||||
b0) deb_distribution="develop" ;;
|
||||
*) deb_distribution="unstable" ;;
|
||||
esac
|
||||
|
||||
cat > "${staging}/debian/changelog" <<EOF
|
||||
cat >"${staging}/debian/changelog" <<EOF
|
||||
xrpld (${deb_full_version}) ${deb_distribution}; urgency=medium
|
||||
* Release ${VERSION}.
|
||||
|
||||
@@ -190,7 +213,7 @@ EOF
|
||||
chmod +x "${staging}/debian/rules"
|
||||
|
||||
set -x
|
||||
( cd "${staging}" && dpkg-buildpackage -b --no-sign -d )
|
||||
(cd "${staging}" && dpkg-buildpackage -b --no-sign -d)
|
||||
}
|
||||
|
||||
"build_${pkg_type}"
|
||||
|
||||
@@ -22,7 +22,7 @@ PATH=/usr/sbin:/usr/bin:/sbin:/bin
|
||||
PKG_NAME=${PKG_NAME:-xrpld}
|
||||
|
||||
log() {
|
||||
# If running under systemd/journald, let it handle timestamps.
|
||||
# If running under systemd/journald, let it handle timestamps.
|
||||
if [[ -n "${JOURNAL_STREAM:-}" ]]; then
|
||||
printf '%s\n' "$*"
|
||||
else
|
||||
@@ -33,7 +33,7 @@ log() {
|
||||
require_root() {
|
||||
if [[ ${EUID:-$(id -u)} -ne 0 ]]; then
|
||||
log "RESULT: failed reason=not-root"
|
||||
exit 1
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
@@ -2,11 +2,16 @@
|
||||
|
||||
#include <xrpl/basics/Number.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/STNumber.h> // IWYU pragma: keep
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
@@ -70,7 +75,8 @@ assetsToSharesWithdraw(
|
||||
std::shared_ptr<SLE const> const& vault,
|
||||
std::shared_ptr<SLE const> const& issuance,
|
||||
STAmount const& assets,
|
||||
TruncateShares truncate)
|
||||
TruncateShares truncate,
|
||||
WaiveUnrealizedLoss waive)
|
||||
{
|
||||
XRPL_ASSERT(!assets.negative(), "xrpl::assetsToSharesWithdraw : non-negative assets");
|
||||
XRPL_ASSERT(
|
||||
@@ -80,7 +86,8 @@ assetsToSharesWithdraw(
|
||||
return std::nullopt; // LCOV_EXCL_LINE
|
||||
|
||||
Number assetTotal = vault->at(sfAssetsTotal);
|
||||
assetTotal -= vault->at(sfLossUnrealized);
|
||||
if (waive == WaiveUnrealizedLoss::No)
|
||||
assetTotal -= vault->at(sfLossUnrealized);
|
||||
STAmount shares{vault->at(sfShareMPTID)};
|
||||
if (assetTotal == 0)
|
||||
return shares;
|
||||
@@ -96,7 +103,8 @@ assetsToSharesWithdraw(
|
||||
sharesToAssetsWithdraw(
|
||||
std::shared_ptr<SLE const> const& vault,
|
||||
std::shared_ptr<SLE const> const& issuance,
|
||||
STAmount const& shares)
|
||||
STAmount const& shares,
|
||||
WaiveUnrealizedLoss waive)
|
||||
{
|
||||
XRPL_ASSERT(!shares.negative(), "xrpl::sharesToAssetsWithdraw : non-negative shares");
|
||||
XRPL_ASSERT(
|
||||
@@ -106,7 +114,8 @@ sharesToAssetsWithdraw(
|
||||
return std::nullopt; // LCOV_EXCL_LINE
|
||||
|
||||
Number assetTotal = vault->at(sfAssetsTotal);
|
||||
assetTotal -= vault->at(sfLossUnrealized);
|
||||
if (waive == WaiveUnrealizedLoss::No)
|
||||
assetTotal -= vault->at(sfLossUnrealized);
|
||||
STAmount assets{vault->at(sfAsset)};
|
||||
if (assetTotal == 0)
|
||||
return assets;
|
||||
@@ -115,4 +124,24 @@ sharesToAssetsWithdraw(
|
||||
return assets;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
isSoleShareholder(ReadView const& view, AccountID const& account, SLE::const_ref issuance)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
issuance && issuance->getType() == ltMPTOKEN_ISSUANCE,
|
||||
"xrpl::isSoleShareholder : valid issuance SLE");
|
||||
|
||||
std::uint64_t const outstanding = issuance->at(sfOutstandingAmount);
|
||||
if (outstanding == 0)
|
||||
return false;
|
||||
|
||||
auto const shareMPTID =
|
||||
makeMptID(issuance->getFieldU32(sfSequence), issuance->getAccountID(sfIssuer));
|
||||
auto const sleToken = view.read(keylet::mptoken(shareMPTID, account));
|
||||
if (!sleToken)
|
||||
return false; // LCOV_EXCL_LINE
|
||||
|
||||
return sleToken->getFieldU64(sfMPTAmount) == outstanding;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -4,12 +4,14 @@
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/beast/utility/Zero.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/VaultHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/MPTIssue.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
@@ -26,6 +28,18 @@
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
static WaiveUnrealizedLoss
|
||||
shouldWaiveWithdrawal(ReadView const& view, AccountID const& account, SLE::const_ref issuance)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
issuance && issuance->getType() == ltMPTOKEN_ISSUANCE,
|
||||
"xrpl::shouldWaiveWithdrawal : valid issuance sle");
|
||||
|
||||
return view.rules().enabled(fixCleanup3_2_0) && isSoleShareholder(view, account, issuance)
|
||||
? WaiveUnrealizedLoss::Yes
|
||||
: WaiveUnrealizedLoss::No;
|
||||
}
|
||||
|
||||
NotTEC
|
||||
VaultWithdraw::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
@@ -102,9 +116,14 @@ VaultWithdraw::preclaim(PreclaimContext const& ctx)
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
// When the user is the sole shareholder they own both the available and future value.
|
||||
// We waive the unrealized-loss subtraction in this case to avoid user withdrawing all of
|
||||
// their shares but keeping future value in the vault.
|
||||
auto const waiveUnrealizedLoss = shouldWaiveWithdrawal(ctx.view, account, sleIssuance);
|
||||
try
|
||||
{
|
||||
auto const maybeAssets = sharesToAssetsWithdraw(vault, sleIssuance, amount);
|
||||
auto const maybeAssets =
|
||||
sharesToAssetsWithdraw(vault, sleIssuance, amount, waiveUnrealizedLoss);
|
||||
if (!maybeAssets)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
@@ -182,13 +201,19 @@ VaultWithdraw::doApply()
|
||||
MPTIssue const share{mptIssuanceID};
|
||||
STAmount sharesRedeemed = {share};
|
||||
STAmount assetsWithdrawn;
|
||||
|
||||
// When the user is the sole shareholder they own both the available and future value.
|
||||
// We waive the unrealized-loss subtraction in this case to avoid user withdrawing all of their
|
||||
// shares but keeping future value in the vault.
|
||||
auto const waiveUnrealizedLoss = shouldWaiveWithdrawal(view(), accountID_, sleIssuance);
|
||||
try
|
||||
{
|
||||
if (amount.asset() == vaultAsset)
|
||||
{
|
||||
// Fixed assets, variable shares.
|
||||
{
|
||||
auto const maybeShares = assetsToSharesWithdraw(vault, sleIssuance, amount);
|
||||
auto const maybeShares = assetsToSharesWithdraw(
|
||||
vault, sleIssuance, amount, TruncateShares::No, waiveUnrealizedLoss);
|
||||
if (!maybeShares)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
sharesRedeemed = *maybeShares;
|
||||
@@ -196,7 +221,8 @@ VaultWithdraw::doApply()
|
||||
|
||||
if (sharesRedeemed == beast::kZero)
|
||||
return tecPRECISION_LOSS;
|
||||
auto const maybeAssets = sharesToAssetsWithdraw(vault, sleIssuance, sharesRedeemed);
|
||||
auto const maybeAssets =
|
||||
sharesToAssetsWithdraw(vault, sleIssuance, sharesRedeemed, waiveUnrealizedLoss);
|
||||
if (!maybeAssets)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
assetsWithdrawn = *maybeAssets;
|
||||
@@ -205,7 +231,8 @@ VaultWithdraw::doApply()
|
||||
{
|
||||
// Fixed shares, variable assets.
|
||||
sharesRedeemed = amount;
|
||||
auto const maybeAssets = sharesToAssetsWithdraw(vault, sleIssuance, sharesRedeemed);
|
||||
auto const maybeAssets =
|
||||
sharesToAssetsWithdraw(vault, sleIssuance, sharesRedeemed, waiveUnrealizedLoss);
|
||||
if (!maybeAssets)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
assetsWithdrawn = *maybeAssets;
|
||||
@@ -238,22 +265,64 @@ VaultWithdraw::doApply()
|
||||
|
||||
auto assetsAvailable = vault->at(sfAssetsAvailable);
|
||||
auto assetsTotal = vault->at(sfAssetsTotal);
|
||||
[[maybe_unused]] auto const lossUnrealized = vault->at(sfLossUnrealized);
|
||||
auto const lossUnrealized = vault->at(sfLossUnrealized);
|
||||
XRPL_ASSERT(
|
||||
lossUnrealized <= (assetsTotal - assetsAvailable),
|
||||
"xrpl::VaultWithdraw::doApply : loss and assets do balance");
|
||||
|
||||
// The vault must have enough assets on hand. The vault may hold assets
|
||||
// that it has already pledged. That is why we look at AssetAvailable
|
||||
// instead of the pseudo-account balance.
|
||||
// The vault must have enough assets on hand.
|
||||
if (*assetsAvailable < assetsWithdrawn)
|
||||
{
|
||||
JLOG(j_.debug()) << "VaultWithdraw: vault doesn't hold enough assets";
|
||||
return tecINSUFFICIENT_FUNDS;
|
||||
}
|
||||
|
||||
assetsTotal -= assetsWithdrawn;
|
||||
assetsAvailable -= assetsWithdrawn;
|
||||
// Post-fixCleanup3_2_0 "final withdrawal" rule:
|
||||
// a transaction that would burn every outstanding share is only permitted when the vault is in
|
||||
// a clean state — no outstanding receivables and no unrealized loss. Otherwise the resulting
|
||||
// (shares == 0, assetsTotal > 0) state would violate the zero-sized-vault invariant.
|
||||
//
|
||||
// When the rule applies, the payout is the remaining sfAssetsAvailable; in a clean vault
|
||||
// the helper result should already equal that value, and any mismatch is a rounding artifact
|
||||
// worth logging.
|
||||
bool const isFinalWithdrawal =
|
||||
sharesRedeemed == STAmount{share, sleIssuance->at(sfOutstandingAmount)};
|
||||
if (view().rules().enabled(fixCleanup3_2_0) && isFinalWithdrawal)
|
||||
{
|
||||
// Unreachable: a final withdrawal with lossUnrealized > 0 has
|
||||
// assetsWithdrawn == assetsTotal > assetsAvailable, which the
|
||||
// insufficient-funds guard above already rejected.
|
||||
if (*lossUnrealized != beast::kZero)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE(
|
||||
"xrpl::VaultWithdraw::doApply : final withdrawal with non-zero unrealized loss");
|
||||
JLOG(j_.fatal())
|
||||
<< "VaultWithdraw: " //
|
||||
"Cannot burn all outstanding shares while unrealized loss is non-zero";
|
||||
return tefINTERNAL;
|
||||
// LCOV_EXCL_END
|
||||
}
|
||||
|
||||
STAmount const allAvailable{vaultAsset, *assetsAvailable};
|
||||
if (assetsWithdrawn != allAvailable)
|
||||
{
|
||||
JLOG(j_.error()) //
|
||||
<< "VaultWithdraw: final withdrawal share-value mismatch;"
|
||||
<< " computed=" << assetsWithdrawn.getText()
|
||||
<< " assetsAvailable=" << allAvailable.getText();
|
||||
}
|
||||
assetsWithdrawn = allAvailable;
|
||||
|
||||
// Do not let dust accumulate in the Vault.
|
||||
assetsTotal = 0;
|
||||
assetsAvailable = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
assetsTotal -= assetsWithdrawn;
|
||||
assetsAvailable -= assetsWithdrawn;
|
||||
}
|
||||
view().update(vault);
|
||||
|
||||
auto const& vaultAccount = vault->at(sfAccount);
|
||||
|
||||
@@ -6457,6 +6457,604 @@ class Vault_test : public beast::unit_test::Suite
|
||||
runTest(amendments);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Helpers and tests: sole-shareholder / stuck-depositor (XLS-0065 +
|
||||
// fixCleanup3_2_0). The vault-level withdraw behavior is tested here;
|
||||
// the loan-protocol setup is incidental.
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
FeatureBitset const all_{test::jtx::testableAmendments()};
|
||||
std::string const iouCurrency_{"IOU"};
|
||||
|
||||
// design doc:
|
||||
// AssetsAvailable ≈ 3,333.50
|
||||
// AssetsTotal ≈ 6,666.50 (3,333.50 cash + 3,333 receivable)
|
||||
// LossUnrealized = 3,333
|
||||
// OutstandingShares = sharesLender (5e9 at IOU scale 1e6)
|
||||
struct StuckDepositorFixture
|
||||
{
|
||||
test::jtx::Account issuer{"issuer"};
|
||||
test::jtx::Account lender{"lender"};
|
||||
test::jtx::Account bob{"bob"};
|
||||
test::jtx::Account borrower{"borrower"};
|
||||
std::optional<PrettyAsset> asset;
|
||||
std::optional<Keylet> vaultKeylet;
|
||||
uint256 brokerID;
|
||||
std::optional<Keylet> loanKeylet;
|
||||
MPTID shareAsset;
|
||||
std::uint64_t sharesLender = 0;
|
||||
};
|
||||
|
||||
static constexpr std::int64_t kStuckFunding = 1'000'000;
|
||||
static constexpr std::int64_t kStuckDepositorIOU = 1'000'000;
|
||||
static constexpr std::int64_t kStuckBorrowerIOU = 100'000;
|
||||
static constexpr std::int64_t kStuckDeposit = 5'000;
|
||||
static constexpr std::int64_t kStuckPrincipal = 3'333;
|
||||
static constexpr std::uint32_t kStuckPayInterval = 600;
|
||||
static constexpr std::uint32_t kStuckPayTotal = 2;
|
||||
|
||||
[[nodiscard]] StuckDepositorFixture
|
||||
setupStuckDepositor(test::jtx::Env& env)
|
||||
{
|
||||
using namespace test::jtx;
|
||||
|
||||
StuckDepositorFixture f;
|
||||
f.asset = f.issuer[iouCurrency_];
|
||||
|
||||
env.fund(XRP(kStuckFunding), f.issuer, f.lender, f.bob, f.borrower);
|
||||
env.close();
|
||||
|
||||
env(trust(f.lender, (*f.asset)(10'000'000)));
|
||||
env(trust(f.bob, (*f.asset)(10'000'000)));
|
||||
env(trust(f.borrower, (*f.asset)(10'000'000)));
|
||||
env.close();
|
||||
|
||||
env(pay(f.issuer, f.lender, (*f.asset)(kStuckDepositorIOU)));
|
||||
env(pay(f.issuer, f.bob, (*f.asset)(kStuckDepositorIOU)));
|
||||
env(pay(f.issuer, f.borrower, (*f.asset)(kStuckBorrowerIOU)));
|
||||
env.close();
|
||||
|
||||
// Vault: Lender creates and seeds it; Bob matches the deposit for a
|
||||
// clean 50/50 split.
|
||||
Vault const v{env};
|
||||
auto [createTx, vaultKeylet] = v.create({.owner = f.lender, .asset = *f.asset});
|
||||
env(createTx);
|
||||
env.close();
|
||||
if (!BEAST_EXPECT(env.le(vaultKeylet)))
|
||||
return f;
|
||||
f.vaultKeylet = vaultKeylet;
|
||||
|
||||
env(v.deposit({
|
||||
.depositor = f.lender,
|
||||
.id = vaultKeylet.key,
|
||||
.amount = (*f.asset)(kStuckDeposit),
|
||||
}),
|
||||
Ter(tesSUCCESS));
|
||||
env(v.deposit({
|
||||
.depositor = f.bob,
|
||||
.id = vaultKeylet.key,
|
||||
.amount = (*f.asset)(kStuckDeposit),
|
||||
}),
|
||||
Ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
// Loan broker: no cover, no management fee, debt cap 10x principal.
|
||||
f.brokerID = keylet::loanbroker(f.lender.id(), env.seq(f.lender)).key;
|
||||
{
|
||||
using namespace loanBroker;
|
||||
env(set(f.lender, vaultKeylet.key),
|
||||
kDebtMaximum((*f.asset)(kStuckPrincipal * 10).value()));
|
||||
env.close();
|
||||
}
|
||||
|
||||
// Loan: 3,333 USD principal, impaired immediately.
|
||||
auto const sleBroker = env.le(keylet::loanbroker(f.brokerID));
|
||||
if (!BEAST_EXPECT(sleBroker))
|
||||
return f;
|
||||
f.loanKeylet = keylet::loan(f.brokerID, sleBroker->at(sfLoanSequence));
|
||||
|
||||
{
|
||||
using namespace loan;
|
||||
env(set(f.borrower, f.brokerID, kStuckPrincipal),
|
||||
Sig(sfCounterpartySignature, f.lender),
|
||||
kPaymentTotal(kStuckPayTotal),
|
||||
kPaymentInterval(kStuckPayInterval),
|
||||
Fee(env.current()->fees().base * 2),
|
||||
Ter(tesSUCCESS));
|
||||
env.close();
|
||||
env(manage(f.lender, f.loanKeylet->key, tfLoanImpair), Ter(tesSUCCESS));
|
||||
env.close();
|
||||
}
|
||||
|
||||
auto const vaultSle = env.le(vaultKeylet);
|
||||
if (!BEAST_EXPECT(vaultSle))
|
||||
return f;
|
||||
BEAST_EXPECT(vaultSle->at(sfLossUnrealized) == (*f.asset)(kStuckPrincipal).value());
|
||||
|
||||
f.shareAsset = vaultSle->at(sfShareMPTID);
|
||||
|
||||
auto const tokenBob = env.le(keylet::mptoken(f.shareAsset, f.bob.id()));
|
||||
if (!BEAST_EXPECT(tokenBob))
|
||||
return f;
|
||||
std::uint64_t const sharesBob = tokenBob->getFieldU64(sfMPTAmount);
|
||||
|
||||
// Bob (non-sole) exits at the discounted rate. Always succeeds.
|
||||
STAmount const bobShareAmt{MPTIssue{f.shareAsset}, Number(sharesBob)};
|
||||
env(v.withdraw({
|
||||
.depositor = f.bob,
|
||||
.id = vaultKeylet.key,
|
||||
.amount = bobShareAmt,
|
||||
}),
|
||||
Ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
auto const tokenLender = env.le(keylet::mptoken(f.shareAsset, f.lender.id()));
|
||||
if (!BEAST_EXPECT(tokenLender))
|
||||
return f;
|
||||
f.sharesLender = tokenLender->getFieldU64(sfMPTAmount);
|
||||
|
||||
auto const sleIssuance = env.le(keylet::mptIssuance(f.shareAsset));
|
||||
if (!BEAST_EXPECT(sleIssuance))
|
||||
return f;
|
||||
BEAST_EXPECT(sleIssuance->getFieldU64(sfOutstandingAmount) == f.sharesLender);
|
||||
|
||||
auto const vaultAfterBob = env.le(vaultKeylet);
|
||||
if (!BEAST_EXPECT(vaultAfterBob))
|
||||
return f;
|
||||
// After Bob's exit: loss is unchanged (3,333 receivable), and the
|
||||
// gap between assetsTotal and assetsAvailable equals exactly that
|
||||
// receivable.
|
||||
BEAST_EXPECT(vaultAfterBob->at(sfLossUnrealized) == (*f.asset)(kStuckPrincipal).value());
|
||||
BEAST_EXPECT(
|
||||
vaultAfterBob->at(sfAssetsTotal) - vaultAfterBob->at(sfAssetsAvailable) ==
|
||||
vaultAfterBob->at(sfLossUnrealized));
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
// Reproduces the worked example from the XLS-0065 design doc. The sole
|
||||
// remaining shareholder asks (via fixed-asset input) for the vault's
|
||||
// entire AssetsAvailable. Pre-fix this fails with the zero-sized-vault
|
||||
// invariant violation. Post-fix the full-price exchange rate burns
|
||||
// only a portion of the shares, the depositor receives all of
|
||||
// AssetsAvailable, and the residual shares remain backed by the
|
||||
// impaired-loan receivable.
|
||||
void
|
||||
testWithdrawSoleShareholderFixedAssetExit(FeatureBitset features)
|
||||
{
|
||||
using namespace test::jtx;
|
||||
|
||||
bool const withFix = features[fixCleanup3_2_0];
|
||||
testcase(
|
||||
std::string{"Vault withdraw: sole shareholder exits via "
|
||||
"fixed-asset amount with impaired loan"} +
|
||||
(withFix ? " (fixCleanup3_2_0)" : " (pre-fix)"));
|
||||
|
||||
Env env(*this, features);
|
||||
auto const f = setupStuckDepositor(env);
|
||||
if (!f.vaultKeylet || !f.asset || f.sharesLender == 0)
|
||||
{
|
||||
BEAST_EXPECT(false);
|
||||
return;
|
||||
}
|
||||
Keylet const& vaultKey = *f.vaultKeylet;
|
||||
PrettyAsset const& asset = *f.asset;
|
||||
|
||||
auto const vaultBefore = env.le(vaultKey);
|
||||
if (!BEAST_EXPECT(vaultBefore))
|
||||
return;
|
||||
Number const availableBefore = vaultBefore->at(sfAssetsAvailable);
|
||||
Number const totalBefore = vaultBefore->at(sfAssetsTotal);
|
||||
Number const lossBefore = vaultBefore->at(sfLossUnrealized);
|
||||
|
||||
STAmount const lenderBalanceBefore = env.balance(f.lender, asset);
|
||||
|
||||
// The requested amount differs between feature regimes because
|
||||
// the two regimes are testing different behaviors:
|
||||
//
|
||||
// - Pre-fix: request the full AssetsAvailable (3,333.50). Under
|
||||
// the discounted formula this would burn every outstanding
|
||||
// share, hitting the zero-sized-vault invariant. The
|
||||
// transaction is rejected with tecINVARIANT_FAILED — the
|
||||
// stuck-depositor bug.
|
||||
//
|
||||
// - Post-fix: request a strictly smaller amount (1,000 USD).
|
||||
// The full-price formula burns only ~30% of the outstanding
|
||||
// shares; the vault retains the rest, backed by the impaired
|
||||
// receivable. Requesting *exactly* AssetsAvailable post-fix
|
||||
// would currently fail with tecINSUFFICIENT_FUNDS due to the
|
||||
// round-to-nearest used by assetsToSharesWithdraw (the
|
||||
// recomputed payout can overshoot the request by a few ULPs).
|
||||
// The "force payout to AssetsAvailable" branch in doApply
|
||||
// only triggers when every share is burned, which is covered
|
||||
// by the loan-repayment test.
|
||||
STAmount const requestAssets =
|
||||
withFix ? asset(1000).value() : STAmount{asset.raw(), availableBefore};
|
||||
Vault const v{env};
|
||||
env(v.withdraw({
|
||||
.depositor = f.lender,
|
||||
.id = vaultKey.key,
|
||||
.amount = requestAssets,
|
||||
}),
|
||||
Ter(withFix ? TER{tesSUCCESS} : TER{tecINVARIANT_FAILED}));
|
||||
env.close();
|
||||
|
||||
auto const vaultAfter = env.le(vaultKey);
|
||||
if (!BEAST_EXPECT(vaultAfter))
|
||||
return;
|
||||
auto const issuanceAfter = env.le(keylet::mptIssuance(f.shareAsset));
|
||||
if (!BEAST_EXPECT(issuanceAfter))
|
||||
return;
|
||||
|
||||
std::uint64_t const sharesAfter = issuanceAfter->getFieldU64(sfOutstandingAmount);
|
||||
Number const availableAfter = vaultAfter->at(sfAssetsAvailable);
|
||||
Number const totalAfter = vaultAfter->at(sfAssetsTotal);
|
||||
Number const lossAfter = vaultAfter->at(sfLossUnrealized);
|
||||
|
||||
if (!withFix)
|
||||
{
|
||||
// Pre-fix: rejected — vault state unchanged.
|
||||
BEAST_EXPECT(sharesAfter == f.sharesLender);
|
||||
BEAST_EXPECT(availableAfter == availableBefore);
|
||||
BEAST_EXPECT(totalAfter == totalBefore);
|
||||
BEAST_EXPECT(lossAfter == lossBefore);
|
||||
return;
|
||||
}
|
||||
|
||||
// Post-fix exact-value derivation (fixture: sharesLender=5e9,
|
||||
// totalBefore=6666.5, request=1000):
|
||||
// sharesRedeemed = round(sharesLender * request / totalBefore)
|
||||
// = round(750,018,750.469) = 750,018,750
|
||||
// received = totalBefore * sharesRedeemed / sharesLender
|
||||
// = 999.999999375 (slightly under 1,000 due to
|
||||
// integer-share rounding)
|
||||
constexpr std::uint64_t kExpectedSharesRedeemed = 750'018'750;
|
||||
Number const expectedReceived =
|
||||
totalBefore * Number(kExpectedSharesRedeemed) / Number(f.sharesLender);
|
||||
|
||||
BEAST_EXPECT(sharesAfter == f.sharesLender - kExpectedSharesRedeemed);
|
||||
|
||||
// LossUnrealized is unchanged: the loan-protocol side is untouched.
|
||||
BEAST_EXPECT(lossAfter == lossBefore);
|
||||
|
||||
// The entire (total - available) gap is the impaired receivable,
|
||||
// i.e. equal to lossUnrealized.
|
||||
BEAST_EXPECT(totalAfter - availableAfter == lossAfter);
|
||||
|
||||
STAmount const lenderBalanceAfter = env.balance(f.lender, asset);
|
||||
Number const received{lenderBalanceAfter - lenderBalanceBefore};
|
||||
BEAST_EXPECT(received == expectedReceived);
|
||||
|
||||
// Conservation: assets removed from the vault equal what the
|
||||
// depositor received.
|
||||
BEAST_EXPECT(totalBefore - totalAfter == received);
|
||||
BEAST_EXPECT(availableBefore - availableAfter == received);
|
||||
}
|
||||
|
||||
// Sole shareholder attempts to burn ALL outstanding shares via
|
||||
// fixed-shares input while the vault still holds an impaired
|
||||
// receivable. Pre-fix this fails with the zero-sized-vault invariant
|
||||
// violation. Post-fix the full-price rate causes assetsWithdrawn to
|
||||
// equal assetsTotal, which exceeds assetsAvailable, so the transaction
|
||||
// is rejected with tecINSUFFICIENT_FUNDS.
|
||||
void
|
||||
testWithdrawSoleShareholderFullSharesRejected(FeatureBitset features)
|
||||
{
|
||||
using namespace test::jtx;
|
||||
|
||||
bool const withFix = features[fixCleanup3_2_0];
|
||||
testcase(
|
||||
std::string{"Vault withdraw: sole shareholder full-shares "
|
||||
"burn is rejected while loss outstanding"} +
|
||||
(withFix ? " (fixCleanup3_2_0)" : " (pre-fix)"));
|
||||
|
||||
Env env(*this, features);
|
||||
auto const f = setupStuckDepositor(env);
|
||||
if (!f.vaultKeylet || f.sharesLender == 0)
|
||||
{
|
||||
BEAST_EXPECT(false);
|
||||
return;
|
||||
}
|
||||
Keylet const& vaultKey = *f.vaultKeylet;
|
||||
|
||||
auto const vaultBefore = env.le(vaultKey);
|
||||
if (!BEAST_EXPECT(vaultBefore))
|
||||
return;
|
||||
Number const availableBefore = vaultBefore->at(sfAssetsAvailable);
|
||||
Number const totalBefore = vaultBefore->at(sfAssetsTotal);
|
||||
Number const lossBefore = vaultBefore->at(sfLossUnrealized);
|
||||
|
||||
// Fixed-shares input: ask for ALL outstanding shares.
|
||||
STAmount const shareAmt{MPTIssue{f.shareAsset}, Number(f.sharesLender)};
|
||||
Vault const v{env};
|
||||
env(v.withdraw({
|
||||
.depositor = f.lender,
|
||||
.id = vaultKey.key,
|
||||
.amount = shareAmt,
|
||||
}),
|
||||
Ter(withFix ? TER{tecINSUFFICIENT_FUNDS} : TER{tecINVARIANT_FAILED}));
|
||||
env.close();
|
||||
|
||||
// Either way the transaction was rejected; vault state unchanged.
|
||||
auto const vaultAfter = env.le(vaultKey);
|
||||
if (!BEAST_EXPECT(vaultAfter))
|
||||
return;
|
||||
auto const issuanceAfter = env.le(keylet::mptIssuance(f.shareAsset));
|
||||
if (!BEAST_EXPECT(issuanceAfter))
|
||||
return;
|
||||
BEAST_EXPECT(issuanceAfter->getFieldU64(sfOutstandingAmount) == f.sharesLender);
|
||||
BEAST_EXPECT(vaultAfter->at(sfAssetsAvailable) == availableBefore);
|
||||
BEAST_EXPECT(vaultAfter->at(sfAssetsTotal) == totalBefore);
|
||||
BEAST_EXPECT(vaultAfter->at(sfLossUnrealized) == lossBefore);
|
||||
}
|
||||
|
||||
// Post-fix end-to-end resolution: after the sole-shareholder partial
|
||||
// exit, the loan is repaid in full. With unrealized loss cleared and
|
||||
// all assets back as cash, the depositor can burn all remaining
|
||||
// shares and fully exit the vault. The final withdrawal hits the
|
||||
// "force payout to assetsAvailable" branch in doApply.
|
||||
void
|
||||
testWithdrawSoleShareholderLoanRepaymentExit()
|
||||
{
|
||||
using namespace test::jtx;
|
||||
using namespace loan;
|
||||
|
||||
testcase(
|
||||
"Vault withdraw: sole shareholder fully exits after impaired "
|
||||
"loan is repaid (fixCleanup3_2_0)");
|
||||
|
||||
Env env(*this, all_ | fixCleanup3_2_0);
|
||||
auto const f = setupStuckDepositor(env);
|
||||
if (!f.vaultKeylet || !f.asset || !f.loanKeylet || f.sharesLender == 0)
|
||||
{
|
||||
BEAST_EXPECT(false);
|
||||
return;
|
||||
}
|
||||
Keylet const& vaultKey = *f.vaultKeylet;
|
||||
Keylet const& loanKey = *f.loanKeylet;
|
||||
PrettyAsset const& asset = *f.asset;
|
||||
|
||||
Vault const v{env};
|
||||
|
||||
// Sole-shareholder partial exit (see comment in
|
||||
// testWithdrawSoleShareholderFixedAssetExit for why we request
|
||||
// less than full AssetsAvailable).
|
||||
{
|
||||
STAmount const requestAssets = asset(1000).value();
|
||||
env(v.withdraw({
|
||||
.depositor = f.lender,
|
||||
.id = vaultKey.key,
|
||||
.amount = requestAssets,
|
||||
}),
|
||||
Ter(tesSUCCESS));
|
||||
env.close();
|
||||
}
|
||||
|
||||
// Confirm the "dormant-but-alive" state from the design doc. The
|
||||
// partial exit burned exactly 750,018,750 shares (see derivation
|
||||
// in testWithdrawSoleShareholderFixedAssetExit).
|
||||
auto const tokenAfterExit = env.le(keylet::mptoken(f.shareAsset, f.lender.id()));
|
||||
if (!BEAST_EXPECT(tokenAfterExit))
|
||||
return;
|
||||
std::uint64_t const retainedShares = tokenAfterExit->getFieldU64(sfMPTAmount);
|
||||
BEAST_EXPECT(retainedShares == f.sharesLender - 750'018'750);
|
||||
|
||||
// Borrower repays the loan in full (pays more than the outstanding
|
||||
// total; the loan transactor caps the receivable).
|
||||
env(pay(f.borrower, loanKey.key, asset(kStuckPrincipal * 2)), Ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
auto const vaultAfterRepay = env.le(vaultKey);
|
||||
if (!BEAST_EXPECT(vaultAfterRepay))
|
||||
return;
|
||||
// Repayment converts the 3,333 receivable back to cash; assetsTotal
|
||||
// is unchanged but assetsAvailable jumps by exactly the same amount,
|
||||
// and lossUnrealized clears to zero.
|
||||
BEAST_EXPECT(vaultAfterRepay->at(sfLossUnrealized) == beast::kZero);
|
||||
BEAST_EXPECT(vaultAfterRepay->at(sfAssetsAvailable) == vaultAfterRepay->at(sfAssetsTotal));
|
||||
|
||||
STAmount const lenderBalanceBeforeFinal = env.balance(f.lender, asset);
|
||||
Number const availableBeforeFinal = vaultAfterRepay->at(sfAssetsAvailable);
|
||||
|
||||
// Burn all remaining shares — the clean-state preconditions of
|
||||
// the "final withdrawal" guard are now satisfied.
|
||||
STAmount const allShares{MPTIssue{f.shareAsset}, Number(retainedShares)};
|
||||
env(v.withdraw({
|
||||
.depositor = f.lender,
|
||||
.id = vaultKey.key,
|
||||
.amount = allShares,
|
||||
}),
|
||||
Ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
auto const vaultFinal = env.le(vaultKey);
|
||||
if (!BEAST_EXPECT(vaultFinal))
|
||||
return;
|
||||
auto const issuanceFinal = env.le(keylet::mptIssuance(f.shareAsset));
|
||||
if (!BEAST_EXPECT(issuanceFinal))
|
||||
return;
|
||||
|
||||
// Zero-sized vault invariant satisfied: 0 shares, 0 assets.
|
||||
BEAST_EXPECT(issuanceFinal->getFieldU64(sfOutstandingAmount) == 0);
|
||||
BEAST_EXPECT(vaultFinal->at(sfAssetsTotal) == beast::kZero);
|
||||
BEAST_EXPECT(vaultFinal->at(sfAssetsAvailable) == beast::kZero);
|
||||
BEAST_EXPECT(vaultFinal->at(sfLossUnrealized) == beast::kZero);
|
||||
|
||||
// The final payout equals exactly the AssetsAvailable that
|
||||
// existed before the call (the "force payout" branch).
|
||||
STAmount const lenderBalanceAfter = env.balance(f.lender, asset);
|
||||
Number const finalReceived{lenderBalanceAfter - lenderBalanceBeforeFinal};
|
||||
BEAST_EXPECT(finalReceived == availableBeforeFinal);
|
||||
}
|
||||
|
||||
// Clean-state regression: with no impaired loan, a sole shareholder
|
||||
// burning all their shares fully empties the vault under both the
|
||||
// pre-fix and post-fix code paths. Confirms the new logic doesn't
|
||||
// break the existing happy-path close-out.
|
||||
void
|
||||
testWithdrawSoleShareholderCleanVaultUnaffected(FeatureBitset features)
|
||||
{
|
||||
using namespace test::jtx;
|
||||
|
||||
bool const withFix = features[fixCleanup3_2_0];
|
||||
testcase(
|
||||
std::string{"Vault withdraw: sole shareholder clean-state "
|
||||
"close-out unchanged"} +
|
||||
(withFix ? " (fixCleanup3_2_0)" : " (pre-fix)"));
|
||||
|
||||
Env env(*this, features);
|
||||
|
||||
Account const issuer{"issuer"};
|
||||
Account const lender{"lender"};
|
||||
|
||||
env.fund(XRP(kStuckFunding), issuer, lender);
|
||||
env.close();
|
||||
|
||||
PrettyAsset const asset = issuer[iouCurrency_];
|
||||
env(trust(lender, asset(10'000'000)));
|
||||
env.close();
|
||||
env(pay(issuer, lender, asset(kStuckDepositorIOU)));
|
||||
env.close();
|
||||
|
||||
// Sole shareholder of a clean vault — no loan broker needed.
|
||||
Vault const v{env};
|
||||
auto [createTx, vaultKeylet] = v.create({.owner = lender, .asset = asset});
|
||||
env(createTx);
|
||||
env.close();
|
||||
|
||||
env(v.deposit({
|
||||
.depositor = lender,
|
||||
.id = vaultKeylet.key,
|
||||
.amount = asset(kStuckDeposit),
|
||||
}),
|
||||
Ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
auto const vaultBefore = env.le(vaultKeylet);
|
||||
if (!BEAST_EXPECT(vaultBefore))
|
||||
return;
|
||||
auto const shareAsset = vaultBefore->at(sfShareMPTID);
|
||||
auto const tokenLender = env.le(keylet::mptoken(shareAsset, lender.id()));
|
||||
if (!BEAST_EXPECT(tokenLender))
|
||||
return;
|
||||
std::uint64_t const sharesLender = tokenLender->getFieldU64(sfMPTAmount);
|
||||
|
||||
// Sole shareholder, no loans, no loss. Burn everything.
|
||||
STAmount const allShares{MPTIssue{shareAsset}, Number(sharesLender)};
|
||||
env(v.withdraw({
|
||||
.depositor = lender,
|
||||
.id = vaultKeylet.key,
|
||||
.amount = allShares,
|
||||
}),
|
||||
Ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
auto const vaultFinal = env.le(vaultKeylet);
|
||||
if (!BEAST_EXPECT(vaultFinal))
|
||||
return;
|
||||
auto const issuanceFinal = env.le(keylet::mptIssuance(shareAsset));
|
||||
if (!BEAST_EXPECT(issuanceFinal))
|
||||
return;
|
||||
BEAST_EXPECT(issuanceFinal->getFieldU64(sfOutstandingAmount) == 0);
|
||||
BEAST_EXPECT(vaultFinal->at(sfAssetsTotal) == beast::kZero);
|
||||
BEAST_EXPECT(vaultFinal->at(sfAssetsAvailable) == beast::kZero);
|
||||
BEAST_EXPECT(vaultFinal->at(sfLossUnrealized) == beast::kZero);
|
||||
|
||||
// (Pre-fix path takes the regular code path; post-fix path enters
|
||||
// the new final-withdrawal guard, which forces payout to exactly
|
||||
// assetsAvailable. Either way the result is identical for a clean
|
||||
// vault.)
|
||||
(void)withFix;
|
||||
}
|
||||
|
||||
// Sole shareholder in an impaired vault redeems a *partial* count of
|
||||
// shares via fixed-shares input. Pre-fix the discounted formula is
|
||||
// used; post-fix the full-price formula is used (waiveUnrealizedLoss
|
||||
// = Yes). The relative payout therefore differs, and post-fix the
|
||||
// depositor recovers proportionally more of the residual cash for
|
||||
// the shares burned. In both cases the vault is left in a valid
|
||||
// (non-empty) state.
|
||||
void
|
||||
testWithdrawSoleShareholderPartialFixedSharesUsesFullPrice()
|
||||
{
|
||||
using namespace test::jtx;
|
||||
|
||||
testcase(
|
||||
"Vault withdraw: sole-shareholder partial fixed-shares uses "
|
||||
"full-price rate (fixCleanup3_2_0)");
|
||||
|
||||
Env env(*this, all_ | fixCleanup3_2_0);
|
||||
auto const f = setupStuckDepositor(env);
|
||||
if (!f.vaultKeylet || !f.asset || f.sharesLender == 0)
|
||||
{
|
||||
BEAST_EXPECT(false);
|
||||
return;
|
||||
}
|
||||
Keylet const& vaultKey = *f.vaultKeylet;
|
||||
PrettyAsset const& asset = *f.asset;
|
||||
|
||||
auto const vaultBefore = env.le(vaultKey);
|
||||
if (!BEAST_EXPECT(vaultBefore))
|
||||
return;
|
||||
Number const totalBefore = vaultBefore->at(sfAssetsTotal);
|
||||
Number const availableBefore = vaultBefore->at(sfAssetsAvailable);
|
||||
Number const lossBefore = vaultBefore->at(sfLossUnrealized);
|
||||
|
||||
// Burn exactly half of the outstanding shares.
|
||||
std::uint64_t const halfShares = f.sharesLender / 2;
|
||||
STAmount const halfAmt{MPTIssue{f.shareAsset}, Number(halfShares)};
|
||||
|
||||
STAmount const lenderBalanceBefore = env.balance(f.lender, asset);
|
||||
|
||||
Vault const v{env};
|
||||
env(v.withdraw({
|
||||
.depositor = f.lender,
|
||||
.id = vaultKey.key,
|
||||
.amount = halfAmt,
|
||||
}),
|
||||
Ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
// Expected payout under the full-price formula:
|
||||
// assets = totalBefore * halfShares / sharesLender
|
||||
// which (with halfShares == sharesLender/2) is roughly
|
||||
// totalBefore / 2.
|
||||
STAmount const lenderBalanceAfter = env.balance(f.lender, asset);
|
||||
Number const received{lenderBalanceAfter - lenderBalanceBefore};
|
||||
Number const expected = totalBefore * Number(halfShares) / Number(f.sharesLender);
|
||||
BEAST_EXPECT(received == expected);
|
||||
|
||||
// The full-price payout exceeds the discounted formula by exactly
|
||||
// lossBefore * halfShares / sharesLender — that's the whole point
|
||||
// of the waive.
|
||||
Number const discounted =
|
||||
(totalBefore - lossBefore) * Number(halfShares) / Number(f.sharesLender);
|
||||
Number const expectedDelta = lossBefore * Number(halfShares) / Number(f.sharesLender);
|
||||
BEAST_EXPECT(received - discounted == expectedDelta);
|
||||
|
||||
auto const vaultAfter = env.le(vaultKey);
|
||||
if (!BEAST_EXPECT(vaultAfter))
|
||||
return;
|
||||
auto const issuanceAfter = env.le(keylet::mptIssuance(f.shareAsset));
|
||||
if (!BEAST_EXPECT(issuanceAfter))
|
||||
return;
|
||||
|
||||
// Vault remains valid: half the shares remain, lossUnrealized
|
||||
// is untouched, and the entire (total - available) gap is still
|
||||
// the impaired receivable.
|
||||
BEAST_EXPECT(
|
||||
issuanceAfter->getFieldU64(sfOutstandingAmount) == f.sharesLender - halfShares);
|
||||
BEAST_EXPECT(vaultAfter->at(sfAssetsTotal) == totalBefore - received);
|
||||
BEAST_EXPECT(vaultAfter->at(sfLossUnrealized) == lossBefore);
|
||||
BEAST_EXPECT(
|
||||
vaultAfter->at(sfAssetsTotal) - vaultAfter->at(sfAssetsAvailable) ==
|
||||
vaultAfter->at(sfLossUnrealized));
|
||||
|
||||
// Conservation: vault delta matches the depositor's gain.
|
||||
BEAST_EXPECT(totalBefore - vaultAfter->at(sfAssetsTotal) == received);
|
||||
BEAST_EXPECT(availableBefore - vaultAfter->at(sfAssetsAvailable) == received);
|
||||
}
|
||||
|
||||
// Bug: DeltaInfo::makeDelta uses max(scale(after), scale(before)) for the
|
||||
// sfAssetsTotal and sfAssetsAvailable deltas, and visitEntry applies the
|
||||
// same max() for the vault pseudo-account RippleState. When
|
||||
@@ -7449,6 +8047,16 @@ public:
|
||||
testAssetsMaximum();
|
||||
testBug6LimitBypassWithShares();
|
||||
testRemoveEmptyHoldingLockedAmount();
|
||||
|
||||
testWithdrawSoleShareholderFixedAssetExit(all_ - fixCleanup3_2_0);
|
||||
testWithdrawSoleShareholderFixedAssetExit(all_);
|
||||
testWithdrawSoleShareholderFullSharesRejected(all_ - fixCleanup3_2_0);
|
||||
testWithdrawSoleShareholderFullSharesRejected(all_);
|
||||
testWithdrawSoleShareholderCleanVaultUnaffected(all_ - fixCleanup3_2_0);
|
||||
testWithdrawSoleShareholderCleanVaultUnaffected(all_);
|
||||
testWithdrawSoleShareholderPartialFixedSharesUsesFullPrice();
|
||||
testWithdrawSoleShareholderLoanRepaymentExit();
|
||||
|
||||
testReferenceHolding();
|
||||
testHoldingDeletionBlocked();
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <boost/multiprecision/number.hpp>
|
||||
|
||||
#include <array>
|
||||
#include <cctype>
|
||||
#include <cstdint>
|
||||
#include <iomanip>
|
||||
#include <limits>
|
||||
|
||||
Reference in New Issue
Block a user