Compare commits

..

2 Commits

Author SHA1 Message Date
Ayaz Salikhov
7b9d55326d build: Add zip to Nix images (#7551) 2026-06-16 17:35:33 +00:00
Ayaz Salikhov
0364e4dc41 docs: Rewrite build environment docs (#7533)
Co-authored-by: Ed Hennis <ed@ripple.com>
2026-06-16 13:24:12 +00:00
176 changed files with 1276 additions and 12972 deletions

View File

@@ -1,5 +1,5 @@
{
"image_tag": "sha-63ffdc3",
"image_tag": "sha-fe4c8ae",
"configs": {
"ubuntu": [
{
@@ -68,7 +68,7 @@
"compiler": ["gcc"],
"build_type": ["Release"],
"arch": ["amd64"],
"image": "ghcr.io/xrplf/xrpld/packaging-debian:sha-63ffdc3"
"image": "ghcr.io/xrplf/xrpld/packaging-debian:sha-577d745"
}
],
@@ -77,7 +77,7 @@
"compiler": ["gcc"],
"build_type": ["Release"],
"arch": ["amd64"],
"image": "ghcr.io/xrplf/xrpld/packaging-rhel:sha-63ffdc3"
"image": "ghcr.io/xrplf/xrpld/packaging-rhel:sha-577d745"
}
]
}

View File

@@ -9,12 +9,20 @@ on:
- "flake.nix"
- "flake.lock"
- "nix/**"
- "!nix/docker/README.md"
- "!nix/devshell.nix"
- "bin/check-tools.sh"
- "bin/install-sanitizer-libs.sh"
pull_request:
paths:
- ".github/workflows/build-nix-images.yml"
- "flake.nix"
- "flake.lock"
- "nix/**"
- "!nix/docker/README.md"
- "!nix/devshell.nix"
- "bin/check-tools.sh"
- "bin/install-sanitizer-libs.sh"
workflow_dispatch:
concurrency:

View File

@@ -41,7 +41,7 @@ env:
jobs:
build:
runs-on: ubuntu-latest
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-63ffdc3
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-fe4c8ae
steps:
- name: Checkout repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3

View File

@@ -121,6 +121,11 @@ jobs:
if: ${{ inputs.ccache_enabled && runner.debug == '1' }}
run: echo "CCACHE_LOGFILE=${{ runner.temp }}/ccache.log" >>"${GITHUB_ENV}"
- name: Check tools
env:
CHECK_TOOLS_SKIP_CLONE: "1"
run: ./bin/check-tools.sh
- name: Print build environment
uses: XRPLF/actions/print-build-env@59dec886e4afb05a1724443af08baccbc045b574

View File

@@ -36,7 +36,7 @@ jobs:
needs: [determine-files]
if: ${{ always() && !cancelled() && (!inputs.check_only_changed || needs.determine-files.outputs.cpp_changed_files != '' || needs.determine-files.outputs.clang_tidy_config_changed == 'true') }}
runs-on: ["self-hosted", "Linux", "X64", "heavy"]
container: "ghcr.io/xrplf/xrpld/nix-debian:sha-63ffdc3"
container: "ghcr.io/xrplf/xrpld/nix-debian:sha-fe4c8ae"
permissions:
contents: read
issues: write

View File

@@ -40,7 +40,7 @@ defaults:
jobs:
upload:
runs-on: ubuntu-latest
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-63ffdc3
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-fe4c8ae
steps:
- name: Checkout repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3

391
BUILD.md
View File

@@ -1,26 +1,57 @@
| :warning: **WARNING** :warning: |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| These instructions assume you have a C++ development environment ready with Git, Python, Conan, CMake, and a C++ compiler. For help setting one up on Linux, macOS, or Windows, [see this guide](./docs/build/environment.md). |
| :warning: **WARNING** :warning: |
| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| These instructions assume you have a C++ development environment ready with Git, Python, Conan, CMake, and a C++ compiler. For help setting one up on Linux, macOS, or Windows, [see this guide](./docs/build/environment.md).<br><br>These instructions also assume a basic familiarity with Conan and CMake. If you are unfamiliar with Conan, you can read our [crash course](./docs/build/conan.md) or the official [Getting Started][conan-getting-started] walkthrough. |
> These instructions also assume a basic familiarity with Conan and CMake.
> If you are unfamiliar with Conan, you can read our
> [crash course](./docs/build/conan.md) or the official [Getting Started][3]
> walkthrough.
## Minimum Requirements
## Branches
See [System Requirements](https://xrpl.org/system-requirements.html).
For a stable release, choose the `master` branch or one of the [tagged
releases](https://github.com/XRPLF/rippled/releases).
Building xrpld generally requires Git, Python, Conan, CMake, and a C++
compiler.
- [Python](https://www.python.org/downloads/)
- [Conan](https://conan.io/downloads.html)
- [CMake](https://cmake.org/download/)
You can verify that the required tools are installed and runnable with:
```bash
git checkout master
./bin/check-tools.sh
```
For the latest release candidate, choose the `release` branch.
`xrpld` is written in the C++23 dialect. The [tested compiler versions][cpp23-support] are:
```bash
git checkout release
```
| Compiler | Version |
| ----------- | --------------- |
| GCC | 15.2 |
| Clang | 22 |
| Apple Clang | 17 |
| MSVC | 19.44[^windows] |
## Operating Systems
Please see the [environment setup guide](./docs/build/environment.md) for detailed instructions for all platforms.
### Linux
The Ubuntu Linux distribution has received the highest level of quality
assurance, testing, and support. We also support Red Hat and use Debian
internally.
Our Linux CI tooling is distro-independent and uses a Nix-based environment, so it should be possible to build on other Linux distributions as well, although we have not tested them.
### macOS
Many `xrpld` engineers use macOS for development.
### Windows
Windows is used by some engineers for development only.
[^windows]: Windows is not recommended for production use.
## Steps
### Branches
For the latest set of untested features, or to contribute, choose the `develop`
branch.
@@ -29,55 +60,15 @@ branch.
git checkout develop
```
## Minimum Requirements
For a release candidate, choose the relevant release branch, e.g.
`release/3.2.x`.
See [System Requirements](https://xrpl.org/system-requirements.html).
```bash
git checkout release/3.2.x
```
Building xrpld generally requires git, Python, Conan, CMake, and a C++
compiler. Some guidance on setting up such a [C++ development environment can be
found here](./docs/build/environment.md).
- [Python 3.11](https://www.python.org/downloads/), or higher
- [Conan 2.17](https://conan.io/downloads.html)[^1], or higher
- [CMake 3.22](https://cmake.org/download/), or higher
[^1]:
It is possible to build with Conan 1.60+, but the instructions are
significantly different, which is why we are not recommending it.
`xrpld` is written in the C++23 dialect and includes the `<concepts>` header.
The [tested compiler versions][2] are:
| Compiler | Version |
| ----------- | --------- |
| GCC | 15 |
| Clang | 22 |
| Apple Clang | 17 |
| MSVC | 19.44[^3] |
### Linux
The Ubuntu Linux distribution has received the highest level of quality
assurance, testing, and support. We also support Red Hat and use Debian
internally.
Here are [sample instructions for setting up a C++ development environment on
Linux](./docs/build/environment.md#linux).
### Mac
Many xrpld engineers use macOS for development.
Here are [sample instructions for setting up a C++ development environment on
macOS](./docs/build/environment.md#macos).
### Windows
Windows is used by some engineers for development only.
[^3]: Windows is not recommended for production use.
## Steps
For a stable release, choose one of the [tagged
releases](https://github.com/XRPLF/rippled/releases).
### Set Up Conan
@@ -86,18 +77,11 @@ Conan, CMake, and a C++ compiler, you may need to set up your Conan profile.
These instructions assume a basic familiarity with Conan and CMake. If you are
unfamiliar with Conan, then please read [this crash course](./docs/build/conan.md) or the official
[Getting Started][3] walkthrough.
[Getting Started][conan-getting-started] walkthrough.
#### Conan lockfile
#### Profiles
To achieve reproducible dependencies, we use a [Conan lockfile](https://docs.conan.io/2/tutorial/versioning/lockfiles.html),
which has to be updated every time dependencies change.
Please see the [instructions on how to regenerate the lockfile](conan/lockfile/README.md).
#### Default profile
We recommend that you import the provided `conan/profiles/default` profile:
We recommend that you install our Conan profiles:
```bash
conan config install conan/profiles/ -tf $(conan config home)/profiles/
@@ -109,222 +93,15 @@ You can check your Conan profile by running:
conan profile show
```
#### Custom profile
If the default profile is not suitable for your environment, you can create a custom profile and pass it to Conan.
More information on customizing Conan can be found in the [Advanced Conan configuration](./docs/build/advanced_conan.md).
If the default profile does not work for you and you do not yet have a Conan
profile, you can create one by running:
#### Add xrplf remote
Run the following command to add the `xrplf` remote, which hosts some of our dependencies:
```bash
conan profile detect
```
You may need to make changes to the profile to suit your environment. You can
refer to the provided `conan/profiles/default` profile for inspiration, and you
may also need to apply the required [tweaks](#conan-profile-tweaks) to this
default profile.
### Patched recipes
Occasionally, we need patched recipes or recipes not present in Conan Center.
We maintain a fork of the Conan Center Index
[here](https://github.com/XRPLF/conan-center-index/) containing the modified and newly added recipes.
To ensure our patched recipes are used, you must add our Conan remote at a
higher index than the default Conan Center remote, so it is consulted first. You
can do this by running:
```bash
conan remote add --index 0 xrplf https://conan.ripplex.io
```
Alternatively, you can pull our recipes from the repository and export them locally:
```bash
# Define which recipes to export.
recipes=('abseil' 'ed25519' 'mpt-crypto' 'openssl' 'secp256k1' 'snappy' 'soci' 'wasm-xrplf' 'wasmi')
# Selectively check out the recipes from our CCI fork.
cd external
mkdir -p conan-center-index
cd conan-center-index
git init
git remote add origin git@github.com:XRPLF/conan-center-index.git
git sparse-checkout init
for recipe in "${recipes[@]}"; do
echo "Checking out recipe '${recipe}'..."
git sparse-checkout add recipes/${recipe}
done
git fetch origin master
git checkout master
./export_all.sh
cd ../../
```
In the case we switch to a newer version of a dependency that still requires a
patch or add a new dependency, it will be necessary for you to pull in the changes and re-export the
updated dependencies with the newer version. However, if we switch to a newer
version that no longer requires a patch, no action is required on your part, as
the new recipe will be automatically pulled from the official Conan Center.
> [!NOTE]
> You might need to add `--lockfile=""` to your `conan install` command
> to avoid automatic use of the existing `conan.lock` file when you run
> `conan export` manually on your machine
>
> This is not recommended though, as you might end up using different revisions of recipes.
### Conan profile tweaks
#### Missing compiler version
If you see an error similar to the following after running `conan profile show`:
```text
ERROR: Invalid setting '17' is not a valid 'settings.compiler.version' value.
Possible values are ['5.0', '5.1', '6.0', '6.1', '7.0', '7.3', '8.0', '8.1',
'9.0', '9.1', '10.0', '11.0', '12.0', '13', '13.0', '13.1', '14', '14.0', '15',
'15.0', '16', '16.0']
Read "http://docs.conan.io/2/knowledge/faq.html#error-invalid-setting"
```
you need to add your compiler to the list of compiler versions in
`$(conan config home)/settings_user.yml`, by adding the required version number(s)
to the `version` array specific for your compiler. For example:
```yaml
compiler:
apple-clang:
version: ["17.0"]
```
#### Multiple compilers
If you have multiple compilers installed, make sure to select the one to use in
your default Conan configuration **before** running `conan profile detect`, by
setting the `CC` and `CXX` environment variables.
For example, if you are running MacOS and have [homebrew
LLVM@18](https://formulae.brew.sh/formula/llvm@18), and want to use it as a
compiler in the new Conan profile:
```bash
export CC=$(brew --prefix llvm@18)/bin/clang
export CXX=$(brew --prefix llvm@18)/bin/clang++
conan profile detect
```
You should also explicitly set the path to the compiler in the profile file,
which helps to avoid errors when `CC` and/or `CXX` are set and disagree with the
selected Conan profile. For example:
```text
[conf]
tools.build:compiler_executables={'c':'/usr/bin/gcc','cpp':'/usr/bin/g++'}
```
#### Multiple profiles
You can manage multiple Conan profiles in the directory
`$(conan config home)/profiles`, for example renaming `default` to a different
name and then creating a new `default` profile for a different compiler.
#### Select language
The default profile created by Conan will typically select different C++ dialect
than C++23 used by this project. You should set `23` in the profile line
starting with `compiler.cppstd=`. For example:
```bash
sed -i.bak -e 's|^compiler\.cppstd=.*$|compiler.cppstd=23|' $(conan config home)/profiles/default
```
#### Select standard library in Linux
**Linux** developers will commonly have a default Conan [profile][] that
compiles with GCC and links with libstdc++. If you are linking with libstdc++
(see profile setting `compiler.libcxx`), then you will need to choose the
`libstdc++11` ABI:
```bash
sed -i.bak -e 's|^compiler\.libcxx=.*$|compiler.libcxx=libstdc++11|' $(conan config home)/profiles/default
```
#### Select architecture and runtime in Windows
**Windows** developers may need to use the x64 native build tools. An easy way
to do that is to run the shortcut "x64 Native Tools Command Prompt" for the
version of Visual Studio that you have installed.
Windows developers must also build `xrpld` and its dependencies for the x64
architecture:
```bash
sed -i.bak -e 's|^arch=.*$|arch=x86_64|' $(conan config home)/profiles/default
```
**Windows** developers also must select static runtime:
```bash
sed -i.bak -e 's|^compiler\.runtime=.*$|compiler.runtime=static|' $(conan config home)/profiles/default
```
#### Clang workaround for grpc
If your compiler is clang, version 19 or later, or apple-clang, version 17 or
later, you may encounter a compilation error while building the `grpc`
dependency:
```text
In file included from .../lib/promise/try_seq.h:26:
.../lib/promise/detail/basic_seq.h:499:38: error: a template argument list is expected after a name prefixed by the template keyword [-Wmissing-template-arg-list-after-template-kw]
499 | Traits::template CallSeqFactory(f_, *cur_, std::move(arg)));
| ^
```
The workaround for this error is to add two lines to profile:
```text
[conf]
tools.build:cxxflags=['-Wno-missing-template-arg-list-after-template-kw']
```
#### Workaround for gcc 12
If your compiler is gcc, version 12, and you have enabled `werr` option, you may
encounter a compilation error such as:
```text
/usr/include/c++/12/bits/char_traits.h:435:56: error: 'void* __builtin_memcpy(void*, const void*, long unsigned int)' accessing 9223372036854775810 or more bytes at offsets [2, 9223372036854775807] and 1 may overlap up to 9223372036854775813 bytes at offset -3 [-Werror=restrict]
435 | return static_cast<char_type*>(__builtin_memcpy(__s1, __s2, __n));
| ~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~
cc1plus: all warnings being treated as errors
```
The workaround for this error is to add two lines to your profile:
```text
[conf]
tools.build:cxxflags=['-Wno-restrict']
```
#### Workaround for clang 16
If your compiler is clang, version 16, you may encounter compilation error such
as:
```text
In file included from .../boost/beast/websocket/stream.hpp:2857:
.../boost/beast/websocket/impl/read.hpp:695:17: error: call to 'async_teardown' is ambiguous
async_teardown(impl.role, impl.stream(),
^~~~~~~~~~~~~~
```
The workaround for this error is to add two lines to your profile:
```text
[conf]
tools.build:cxxflags=['-DBOOST_ASIO_DISABLE_CONCEPTS']
conan remote add --index 0 --force xrplf https://conan.ripplex.io
```
### Set Up Ccache
@@ -333,14 +110,7 @@ To speed up repeated compilations, we recommend that you install
[ccache](https://ccache.dev), a tool that wraps your compiler so that it can
cache build objects locally.
#### Linux
You can install it using the package manager, e.g. `sudo apt install ccache`
(Ubuntu) or `sudo dnf install ccache` (RHEL).
#### macOS
You can install it using Homebrew, i.e. `brew install ccache`.
On Linux and macOS, `ccache` is included in the [Nix development shell](./docs/build/nix.md).
#### Windows
@@ -549,7 +319,7 @@ See [Sanitizers docs](./docs/build/sanitizers.md) for more details.
| Option | Default Value | Description |
| ---------- | ------------- | -------------------------------------------------------------- |
| `assert` | OFF | Enable assertions. |
| `assert` | OFF | Force enabling assertions. |
| `coverage` | OFF | Prepare the coverage report. |
| `tests` | OFF | Build tests. |
| `unity` | OFF | Configure a unity build. |
@@ -557,7 +327,7 @@ See [Sanitizers docs](./docs/build/sanitizers.md) for more details.
| `werr` | OFF | Treat compilation warnings as errors |
| `wextra` | OFF | Enable additional compilation warnings |
[Unity builds][5] may be faster for the first build (at the cost of much more
[Unity builds][unity-build] may be faster for the first build (at the cost of much more
memory) since they concatenate sources into fewer translation units. Non-unity
builds may be faster for incremental builds, and can be helpful for detecting
`#include` omissions.
@@ -583,14 +353,14 @@ After any updates or changes to dependencies, you may need to do the following:
conan remove '*'
```
3. Re-run [conan export](#patched-recipes) if needed.
4. [Regenerate lockfile](#conan-lockfile).
3. Re-run [conan export](./docs/build/advanced_conan.md#patched-recipes) if needed.
4. [Regenerate lockfile](./docs/build/advanced_conan.md#conan-lockfile).
5. Re-run [conan install](#build-and-test).
#### ERROR: Package not resolved
If you're seeing an error like `ERROR: Package 'snappy/1.1.10' not resolved: Unable to find 'snappy/1.1.10#968fef506ff261592ec30c574d4a7809%1756234314.246' in remotes.`,
please add `xrplf` remote or re-run `conan export` for [patched recipes](#patched-recipes).
please [add `xrplf` remote](#add-xrplf-remote) or re-run `conan export` for [patched recipes](./docs/build/advanced_conan.md#patched-recipes).
### `protobuf/port_def.inc` file not found
@@ -610,28 +380,9 @@ For example, if you want to build Debug:
1. For conan install, pass `--settings build_type=Debug`
2. For cmake, pass `-DCMAKE_BUILD_TYPE=Debug`
## Add a Dependency
If you want to experiment with a new package, follow these steps:
1. Search for the package on [Conan Center](https://conan.io/center/).
2. Modify [`conanfile.py`](./conanfile.py):
- Add a version of the package to the `requires` property.
- Change any default options for the package by adding them to the
`default_options` property (with syntax `'$package:$option': $value`).
3. Modify [`CMakeLists.txt`](./CMakeLists.txt):
- Add a call to `find_package($package REQUIRED)`.
- Link a library from the package to the target `xrpl_libs`
(search for the existing call to `target_link_libraries(xrpl_libs INTERFACE ...)`).
4. Start coding! Don't forget to include whatever headers you need from the package.
[1]: https://github.com/conan-io/conan-center-index/issues/13168
[2]: https://en.cppreference.com/w/cpp/compiler_support/20
[3]: https://docs.conan.io/en/latest/getting_started.html
[5]: https://en.wikipedia.org/wiki/Unity_build
[6]: https://github.com/boostorg/beast/issues/2648
[7]: https://github.com/boostorg/beast/issues/2661
[cpp23-support]: https://en.cppreference.com/w/cpp/compiler_support/23
[conan-getting-started]: https://docs.conan.io/en/latest/getting_started.html
[unity-build]: https://en.wikipedia.org/wiki/Unity_build
[gcovr]: https://gcovr.com/en/stable/getting-started.html
[python-pip]: https://packaging.python.org/en/latest/guides/installing-using-pip-and-virtual-environments/
[build_type]: https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html
[profile]: https://docs.conan.io/en/latest/reference/profiles.html

View File

@@ -14,9 +14,9 @@ The following branches exist in the main project repository:
- `develop`: The latest set of unreleased features, and the most common
starting point for contributions.
- `release`: The latest beta release or release candidate.
- `master`: The latest stable release.
- `gh-pages`: The documentation for this project, built by Doxygen.
- `release/*` (e.g. `release/3.2.x`): Release branches, one per release line,
holding the latest release candidate, or stable release for that line.
Stable releases are published as [tagged releases](https://github.com/XRPLF/rippled/releases).
The tip of each branch must be signed. In order for GitHub to sign a
squashed commit that it builds from your pull request, GitHub must know
@@ -130,11 +130,9 @@ tl;dr
## Pull requests
In general, pull requests use `develop` as the base branch.
The exceptions are
- Fixes and improvements to a release candidate use `release` as the
base.
- Hotfixes use `master` as the base.
The exceptions are fixes, improvements, and hotfixes for an existing release,
which use that release's branch (e.g. `release/3.2.x`) as the base.
If your changes are not quite ready, but you want to make it easily available
for preliminary examination or review, you can create a "Draft" pull request.
@@ -216,7 +214,7 @@ coherent rather than a set of _thou shalt not_ commandments.
## Formatting
All code must conform to `clang-format` version 21,
All code must conform to `clang-format` version 22,
according to the settings in [`.clang-format`](./.clang-format),
unless the result would be unreasonably difficult to read or maintain.
To demarcate lines that should be left as-is, surround them with comments like
@@ -261,7 +259,7 @@ This ensures that configuration changes don't introduce new warnings across the
### Installing clang-tidy
See the [environment setup guide](./docs/build/environment.md#clang-tidy) for platform-specific installation instructions.
See the [environment setup guide](./docs/build/environment.md#clang-tidy) for how to get clang-tidy.
### Running clang-tidy locally

158
bin/check-tools.sh Executable file
View File

@@ -0,0 +1,158 @@
#!/usr/bin/env bash
#
# check-tools.sh — verify the xrpld development tooling is present and runnable.
#
# Works on Linux, macOS, and Windows (Git Bash / MSYS). For every expected tool
# it runs a version probe, collecting anything that is missing or fails to run,
# and prints a summary at the end (exiting non-zero if anything is missing).
#
# The tool set is platform-aware:
# - Linux: the full Nix CI environment (see nix/packages.nix, nix/ci-env.nix),
# with GCC, Clang and the sanitizer/coverage tooling. This script is
# run during the Nix Docker image build (nix/docker/Dockerfile), so
# the Linux list is kept in sync with that environment.
# - macOS: the same tooling, minus GCC/g++/gcov/mold
# - Windows: the core build tools only (CMake, Conan, Git, Python).
# MSVC is expected to be provided separately and is not checked here.
#
# Some tools (clang-format, doxygen, gcovr, gh, git-cliff, gpg, pre-commit,
# run-clang-tidy) are present in our Linux CI images and in local development
# setups, but not in the macOS CI environment. They are checked everywhere
# except when running in CI on macOS.
#
# Environment variables:
# CI if set, skip the tools above when on macOS.
# CHECK_TOOLS_SKIP_CLONE if set, skip the git-over-HTTPS connectivity check.
set -uo pipefail
missing=()
checked=0
# check <name> [probe-command...]
# Runs the probe (default: "<name> --version") quietly. Records <name> as
# missing if the command is not found or exits non-zero.
check() {
local name="$1"
shift
local -a probe=("$@")
if [ "${#probe[@]}" -eq 0 ]; then
probe=("${name}" --version)
fi
echo "Checking ${name}..."
checked=$((checked + 1))
if "${probe[@]}" | head -n 1; then
printf ' [ ok ] %s\n' "${name}"
else
printf ' [MISS] %s\n' "${name}"
missing+=("${name}")
fi
}
case "$(uname -s)" in
Linux*) os=linux ;;
Darwin*) os=macos ;;
MINGW* | MSYS* | CYGWIN*) os=windows ;;
*)
echo "Unknown OS: $(uname -s)" >&2
exit 1
;;
esac
echo "Detected OS: ${os} ($(uname -s) $(uname -m))"
echo
echo "Core build tools:"
check cmake
check conan
check git
if [ "${os}" = "windows" ]; then
check python python --version
else
check python3
fi
# The full development toolchain. Available from Nix on Linux and macOS; on
# Windows these are typically not installed, so they are skipped.
if [ "${os}" = "linux" ] || [ "${os}" = "macos" ]; then
echo
echo "Development tooling:"
check ccache
check clang
check clang++
check ClangBuildAnalyzer
check curl
check file
check less
check make
check netstat which netstat
check ninja
check perl
check pkg-config
check vim
# These tools are present in our Linux CI images and in local development
# setups, but not in the macOS CI environment. So check them everywhere
# except when running in CI on macOS.
if [ "${os}" = "linux" ] || [ -z "${CI:-}" ]; then
check clang-format
check doxygen
check gcovr
check gh
check git-cliff
check gpg
# pre-commit, or its alternative implementation prek
check pre-commit sh -c 'pre-commit --version || prek --version'
check run-clang-tidy run-clang-tidy --help
fi
fi
# GCC is the default compiler on Linux. macOS uses the system Apple Clang
# instead, so GCC/g++/gcov are not expected there.
if [ "${os}" = "linux" ]; then
echo
echo "GCC toolchain:"
check gcc
check g++
check gcov
echo
echo "Mold:"
check mold
fi
if [ "${os}" = "windows" ]; then
echo
echo "Note: on Windows the C++ compiler is MSVC, which is provided"
echo " separately (e.g. via Visual Studio) and is not checked here."
fi
# A simple test to verify that git can clone a repository over HTTPS
# (i.e. the CA bundle is wired up). Clone to a temp dir and clean up.
if [ -n "${CHECK_TOOLS_SKIP_CLONE:-}" ]; then
echo
echo "Skipping git-over-HTTPS check (CHECK_TOOLS_SKIP_CLONE is set)."
else
echo
echo "Connectivity check:"
checked=$((checked + 1))
tmp_clone="$(mktemp -d)"
if git clone --depth 1 https://github.com/XRPLF/actions.git "${tmp_clone}/actions" >/dev/null 2>&1; then
printf ' [ ok ] git clone over HTTPS\n'
else
printf ' [MISS] git clone over HTTPS\n'
missing+=("git-https-clone")
fi
rm -rf "${tmp_clone}"
fi
echo
if [ "${#missing[@]}" -eq 0 ]; then
echo "All ${checked} checked tools are present and runnable."
else
echo "Missing or non-functional tools (${#missing[@]} of ${checked}):" >&2
for tool in "${missing[@]}"; do
echo " - ${tool}" >&2
done
exit 1
fi

View File

@@ -109,6 +109,7 @@ words:
- enabled
- enablerepo
- endmacro
- envrc
- exceptioned
- EXPECT_STREQ
- Falco
@@ -270,8 +271,6 @@ words:
- sles
- soci
- socidb
- sponsee
- sponsees
- SRPMS
- sslws
- statsd

193
docs/build/advanced_conan.md vendored Normal file
View File

@@ -0,0 +1,193 @@
# Advanced Conan configuration
This document provides advanced instructions for setting up and configuring Conan for `xrpld` development: custom profiles, the lockfile, patched recipes, and profile tweaks.
## Custom profile
If the default profile does not work for you and you do not yet have a Conan
profile, you can create one by running:
```bash
conan profile detect
```
You may need to make changes to the profile to suit your environment. You can
refer to the provided `conan/profiles/default` profile for inspiration, and you
may also need to apply the required [tweaks](#conan-profile-tweaks) to this
default profile.
## Conan lockfile
To achieve reproducible dependencies, we use a [Conan lockfile](https://docs.conan.io/2/tutorial/versioning/lockfiles.html),
which has to be updated every time dependencies change.
Please see the [instructions on how to regenerate the lockfile](../../conan/lockfile/README.md).
## Patched recipes
Occasionally, we need patched recipes or recipes not present in Conan Center.
We maintain a fork of the Conan Center Index
[here](https://github.com/XRPLF/conan-center-index/) containing the modified and newly added recipes.
To ensure our patched recipes are used, you must add our Conan remote at a
higher index than the default Conan Center remote, so it is consulted first. You
can do this by running:
```bash
conan remote add --index 0 --force xrplf https://conan.ripplex.io
```
Alternatively, you can pull our recipes from the repository and export them locally:
```bash
# Define which recipes to export.
recipes=('abseil' 'ed25519' 'mpt-crypto' 'openssl' 'secp256k1' 'snappy' 'soci' 'wasm-xrplf' 'wasmi')
# Selectively check out the recipes from our CCI fork.
cd external
mkdir -p conan-center-index
cd conan-center-index
git init
git remote add origin git@github.com:XRPLF/conan-center-index.git
git sparse-checkout init
for recipe in "${recipes[@]}"; do
echo "Checking out recipe '${recipe}'..."
git sparse-checkout add recipes/${recipe}
done
git fetch origin master
git checkout master
./export_all.sh
cd ../../
```
In the case we switch to a newer version of a dependency that still requires a
patch or add a new dependency, it will be necessary for you to pull in the changes and re-export the
updated dependencies with the newer version. However, if we switch to a newer
version that no longer requires a patch, no action is required on your part, as
the new recipe will be automatically pulled from the official Conan Center.
> [!NOTE]
> You might need to add `--lockfile=""` to your `conan install` command
> to avoid automatic use of the existing `conan.lock` file when you run
> `conan export` manually on your machine
>
> This is not recommended though, as you might end up using different revisions of recipes.
## Conan profile tweaks
### Missing compiler version
If you see an error similar to the following after running `conan profile show`:
```text
ERROR: Invalid setting '17' is not a valid 'settings.compiler.version' value.
Possible values are ['5.0', '5.1', '6.0', '6.1', '7.0', '7.3', '8.0', '8.1',
'9.0', '9.1', '10.0', '11.0', '12.0', '13', '13.0', '13.1', '14', '14.0', '15',
'15.0', '16', '16.0']
Read "http://docs.conan.io/2/knowledge/faq.html#error-invalid-setting"
```
you need to create `$(conan config home)/settings_user.yml` file if it doesn't exist and add the required version number(s)
to the `version` array specific for your compiler. For example:
```yaml
compiler:
apple-clang:
version: ["17.0"]
```
### Multiple compilers
If you have multiple compilers installed, make sure to select the one to use in
your default Conan configuration **before** running `conan profile detect`, by
setting the `CC` and `CXX` environment variables.
For example, if you are running MacOS and have [homebrew
LLVM@18](https://formulae.brew.sh/formula/llvm@18), and want to use it as a
compiler in the new Conan profile:
```bash
export CC=$(brew --prefix llvm@18)/bin/clang
export CXX=$(brew --prefix llvm@18)/bin/clang++
conan profile detect
```
You should also explicitly set the path to the compiler in the profile file,
which helps to avoid errors when `CC` and/or `CXX` are set and disagree with the
selected Conan profile. For example:
```text
[conf]
tools.build:compiler_executables={'c':'/usr/bin/gcc','cpp':'/usr/bin/g++'}
```
### Multiple profiles
You can manage multiple Conan profiles in the directory
`$(conan config home)/profiles`, for example renaming `default` to a different
name and then creating a new `default` profile for a different compiler.
### Select language
The default profile created by Conan will typically select different C++ dialect
than C++23 used by this project. You should set `23` in the profile line
starting with `compiler.cppstd=`. For example:
```bash
sed -i.bak -e 's|^compiler\.cppstd=.*$|compiler.cppstd=23|' $(conan config home)/profiles/default
```
### Select standard library in Linux
**Linux** developers will commonly have a default Conan [profile][] that
compiles with GCC and links with libstdc++. If you are linking with libstdc++
(see profile setting `compiler.libcxx`), then you will need to choose the
`libstdc++11` ABI:
```bash
sed -i.bak -e 's|^compiler\.libcxx=.*$|compiler.libcxx=libstdc++11|' $(conan config home)/profiles/default
```
### Select architecture and runtime in Windows
**Windows** developers may need to use the x64 native build tools. An easy way
to do that is to run the shortcut "x64 Native Tools Command Prompt" for the
version of Visual Studio that you have installed.
Windows developers must also build `xrpld` and its dependencies for the x64
architecture:
```bash
sed -i.bak -e 's|^arch=.*$|arch=x86_64|' $(conan config home)/profiles/default
```
**Windows** developers also must select static runtime:
```bash
sed -i.bak -e 's|^compiler\.runtime=.*$|compiler.runtime=static|' $(conan config home)/profiles/default
```
## Add a Dependency
If you want to experiment with a new package, follow these steps:
1. Search for the package on [Conan Center](https://conan.io/center/).
2. Modify [`conanfile.py`](../../conanfile.py):
- Add a version of the package to the `requires` property.
- Change any default options for the package by adding them to the
`default_options` property (with syntax `'$package:$option': $value`).
3. Regenerate the [Conan lockfile](../../conan/lockfile/README.md) so the new
dependency is captured:
```bash
./conan/lockfile/regenerate.sh
```
4. Modify [`CMakeLists.txt`](../../CMakeLists.txt):
- Add a call to `find_package($package REQUIRED)`.
- Link a library from the package to the target `xrpl_libs`
(search for the existing call to `target_link_libraries(xrpl_libs INTERFACE ...)`).
5. Start coding! Don't forget to include whatever headers you need from the package.
[profile]: https://docs.conan.io/2/reference/config_files/profiles.html

2
docs/build/conan.md vendored
View File

@@ -115,7 +115,7 @@ By default, Conan will use the profile named "default".
[find_package]: https://cmake.org/cmake/help/latest/command/find_package.html
[pcf]: https://cmake.org/cmake/help/latest/manual/cmake-packages.7.html#package-configuration-file
[prefix_path]: https://cmake.org/cmake/help/latest/variable/CMAKE_PREFIX_PATH.html
[profile]: https://docs.conan.io/en/latest/reference/profiles.html
[profile]: https://docs.conan.io/2/reference/config_files/profiles.html
[pvf]: https://cmake.org/cmake/help/latest/manual/cmake-packages.7.html#package-version-file
[runtime]: https://cmake.org/cmake/help/latest/variable/CMAKE_MSVC_RUNTIME_LIBRARY.html
[search]: https://cmake.org/cmake/help/latest/command/find_package.html#search-procedure

View File

@@ -1,69 +1,73 @@
Our [build instructions][BUILD.md] assume you have a C++ development
environment complete with Git, Python, Conan, CMake, and a C++ compiler.
This document exists to help readers set one up on any of the Big Three
platforms: Linux, macOS, or Windows.
As an alternative to system packages, the Nix development shell can be used to provide a development environment. See [using nix development shell](./nix.md) for more details.
This document explains how to set one up.
[BUILD.md]: ../../BUILD.md
## Linux
## Tested compiler versions
Package ecosystems vary across Linux distributions,
so there is no one set of instructions that will work for every Linux user.
The instructions below are written for Debian 12 (Bookworm).
`xrpld` is built in the **C++23** dialect by default.
Make sure your toolchain is recent enough — the compiler versions currently tested in CI are:
```
export GCC_RELEASE=12
sudo apt update
sudo apt install --yes gcc-${GCC_RELEASE} g++-${GCC_RELEASE} python3-pip \
python-is-python3 python3-venv python3-dev curl wget ca-certificates \
git build-essential cmake ninja-build libc6-dev
sudo pip install --break-system-packages conan
| Compiler | Version |
| ----------- | ------- |
| GCC | 15.2 |
| Clang | 22 |
| Apple Clang | 17 |
| MSVC | 19.44 |
sudo update-alternatives --install /usr/bin/cc cc /usr/bin/gcc-${GCC_RELEASE} 999
sudo update-alternatives --install \
/usr/bin/gcc gcc /usr/bin/gcc-${GCC_RELEASE} 100 \
--slave /usr/bin/g++ g++ /usr/bin/g++-${GCC_RELEASE} \
--slave /usr/bin/gcc-ar gcc-ar /usr/bin/gcc-ar-${GCC_RELEASE} \
--slave /usr/bin/gcc-nm gcc-nm /usr/bin/gcc-nm-${GCC_RELEASE} \
--slave /usr/bin/gcc-ranlib gcc-ranlib /usr/bin/gcc-ranlib-${GCC_RELEASE} \
--slave /usr/bin/gcov gcov /usr/bin/gcov-${GCC_RELEASE} \
--slave /usr/bin/gcov-tool gcov-tool /usr/bin/gcov-tool-${GCC_RELEASE} \
--slave /usr/bin/gcov-dump gcov-dump /usr/bin/gcov-dump-${GCC_RELEASE} \
--slave /usr/bin/lto-dump lto-dump /usr/bin/lto-dump-${GCC_RELEASE}
sudo update-alternatives --auto cc
sudo update-alternatives --auto gcc
LLVM tools (`clang-tidy` and `clang-format`) are also pinned to version 22.
Older compilers may fail to build the latest `develop` code: the codebase now
relies on C++23 features and has been adjusted for `clang-tidy`.
If the latest code doesn't build for you, update your build toolchain first.
## Linux and macOS
The **recommended way** to get a development environment on Linux and macOS is
the Nix development shell. It provides the exact tooling used in CI — `git`,
`python`, `conan`, `cmake`, `clang-tidy`, `clang-format`, and everything else —
with a single command and without installing anything system-wide:
```bash
nix --experimental-features 'nix-command flakes' develop
```
If you use different Linux distribution, hope the instruction above can guide
you in the right direction. We try to maintain compatibility with all recent
compiler releases, so if you use a rolling distribution like e.g. Arch or CentOS
then there is a chance that everything will "just work".
On **Linux**, Nix also provides the compiler (GCC). On **macOS**, the shell uses
your **system-wide Apple Clang** as the compiler, so you still need to manage
its version (see below).
## macOS
See [Using the Nix development shell](./nix.md) for installation and usage
details, including how to select a different compiler.
Open a Terminal and enter the below command to bring up a dialog to install
the command line developer tools.
Once it is finished, this command should return a version greater than the
minimum required (see [BUILD.md][]).
> [!NOTE]
> Using Nix is not mandatory. Any custom environment (Homebrew packages or
> anything else) will continue to work, but then it is up to you to keep it in
> sync with the environment used in CI. Nix unifies the development environment
> for everyone and synchronizes updates, which is why we recommend it.
```
### macOS: managing the Apple Clang version
Because the Nix shell uses the system-wide Apple Clang on macOS, the compiler
version is whatever your installed Xcode (or Command Line Tools) provides. The
following command should return a version greater than or equal to the
[minimum required](#tested-compiler-versions):
```bash
clang --version
```
### Install Xcode Specific Version (Optional)
If you develop other applications using XCode you might be consistently updating to the newest version of Apple Clang.
This will likely cause issues building xrpld. You may want to install a specific version of Xcode:
If you develop other applications using Xcode, you might be consistently
updating to the newest version of Apple Clang, which will likely cause issues
building xrpld. You may want to install and pin a specific version of Xcode:
1. **Download Xcode**
- Visit [Apple Developer Downloads](https://developer.apple.com/download/more/)
- Sign in with your Apple Developer account
- Search for an Xcode version that includes **Apple Clang (Expected Version)**
- Search for an Xcode version that includes the expected Apple Clang version
- Download the `.xip` file
2. **Install and Configure Xcode**
2. **Install and configure Xcode**
```bash
# Extract the .xip file and rename for version management
@@ -79,62 +83,28 @@ This will likely cause issues building xrpld. You may want to install a specific
export DEVELOPER_DIR=/Applications/Xcode_16.2.app/Contents/Developer
```
The command line developer tools should include Git too:
## Windows
```
git --version
```
Nix is not available on Windows, so the required tools have to be installed
manually:
Install [Homebrew][],
use it to install [pyenv][],
use it to install Python,
and use it to install Conan:
- [Visual Studio 2022](https://visualstudio.microsoft.com/) with the
**"Desktop development with C++"** workload — this provides MSVC and the
"x64 Native Tools Command Prompt".
- [Git for Windows](https://git-scm.com/download/win)
- [Python 3.11](https://www.python.org/downloads/), or higher
- [Conan 2.17](https://conan.io/downloads.html), or higher
- [CMake 3.22](https://cmake.org/download/), or higher
[Homebrew]: https://brew.sh/
[pyenv]: https://github.com/pyenv/pyenv
```
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
brew update
brew install xz
brew install pyenv
pyenv install 3.11
pyenv global 3.11
eval "$(pyenv init -)"
pip install 'conan'
```
Install CMake with Homebrew too:
```
brew install cmake
```
> [!NOTE]
> Windows is used for development only and is not recommended for production.
## Clang-tidy
Clang-tidy is required to run static analysis checks locally (see [CONTRIBUTING.md](../../CONTRIBUTING.md)).
It is not required to build the project. Currently this project uses clang-tidy version 21.
`clang-tidy` is required to run static analysis checks locally (see
[CONTRIBUTING.md](../../CONTRIBUTING.md)). It is not required to build the
project. This project currently uses `clang-tidy` version 22.
### Linux
LLVM 21 is not available in the default Debian 12 (Bookworm) repositories.
Install it using the official LLVM apt installer:
```
wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
sudo ./llvm.sh 21
sudo apt install --yes clang-tidy-21
```
Then use `run-clang-tidy-21` when running clang-tidy locally.
### macOS
Install LLVM 21 via Homebrew:
```
brew install llvm@21
```
Then use `run-clang-tidy` from the LLVM 21 Homebrew prefix when running clang-tidy locally.
On Linux and macOS, the [Nix development shell](./nix.md) provides `clang-tidy`
22 out of the box — run it via `run-clang-tidy`. No separate installation is
needed.

45
docs/build/nix.md vendored
View File

@@ -2,9 +2,12 @@
This guide explains how to use Nix to set up a reproducible development environment for xrpld. Using Nix eliminates the need to manually install utilities and ensures consistent tooling across different machines.
**The Nix development shell is the recommended way to develop xrpld.** It unifies the development environment for everyone and synchronizes updates: the same tooling and compiler versions are used both here and in CI. Any custom environment (Homebrew packages or anything else) will continue to work, but then it is up to you to keep it in sync with the environment used in CI.
## Benefits of Using Nix
- **Reproducible environment**: Everyone gets the same versions of tools and compilers
- **Matches CI**: The Linux CI runs in Docker images built from this exact Nix environment
- **No system pollution**: Dependencies are isolated and don't affect your system packages
- **Multiple compiler versions**: Easily switch between different GCC and Clang versions
- **Quick setup**: Get started with a single command
@@ -28,11 +31,22 @@ This will:
- Download and set up all required development tools (CMake, Ninja, Conan, etc.)
- Configure the appropriate compiler for your platform:
- **macOS**: Apple Clang (default system compiler)
- **Linux**: GCC 15
- **Linux**: GCC 15.2 (provided by Nix)
- **macOS**: Apple Clang (your system compiler)
The first time you run this command, it will take a few minutes to download and build the environment. Subsequent runs will be much faster.
### Platform notes
- **Linux**: `nix develop` gives you a shell with all the tooling necessary to
develop xrpld and with GCC 15.2 (also provided by Nix). There are no caveats.
- **macOS**: `nix develop` gives you a full environment too. The compiler is
your system-wide Apple Clang, while every other tool — including Conan — is
provided by Nix. Conan has no binary in the Nix cache for macOS, so it is
built from source the first time you enter the shell, which makes the initial
setup slower (this is handled automatically; see
[`nix/devshell.nix`](../../nix/devshell.nix)).
> [!TIP]
> To avoid typing `--experimental-features 'nix-command flakes'` every time, you can permanently enable flakes by creating `~/.config/nix/nix.conf`:
>
@@ -51,7 +65,7 @@ The first time you run this command, it will take a few minutes to download and
A compiler can be chosen by providing its name with the `.#` prefix, e.g. `nix develop .#gcc15`.
Use `nix flake show` to see all the available development shells.
Use `nix develop .#no_compiler` to use the compiler from your system.
Use `nix develop .#no-compiler` to use the compiler from your system.
### Example Usage
@@ -68,12 +82,28 @@ nix develop
### Using a different shell
`nix develop` opens bash by default. If you want to use another shell this could be done by adding `-c` flag. For example:
`nix develop` opens bash by default. To use another shell, pass it with the `-c` flag — this works with any shell, e.g. `zsh` or `fish`:
```bash
# Use zsh
nix develop -c zsh
# Use fish
nix develop -c fish
# Use your login shell
nix develop -c "$SHELL"
```
> [!WARNING]
> Your shell's interactive startup files (e.g. `config.fish`, `.zshrc`) may prepend other directories — most commonly Homebrew — to `$PATH`, which can shadow the tools provided by the Nix shell. After entering, verify that tools resolve into the Nix store:
>
> ```bash
> command -v cmake # should print a /nix/store/... path
> ```
>
> If it doesn't, either adjust your shell configuration so it doesn't override `$PATH`, or use [direnv](#automatic-activation-with-direnv) (below), which loads the environment _after_ your shell config and so takes precedence regardless of the shell you use.
## Building xrpld with Nix
Once inside the Nix development shell, follow the standard [build instructions](../../BUILD.md#steps). The Nix shell provides all necessary tools (CMake, Ninja, Conan, etc.).
@@ -82,6 +112,8 @@ Once inside the Nix development shell, follow the standard [build instructions](
[direnv](https://direnv.net/) or [nix-direnv](https://github.com/nix-community/nix-direnv) can automatically activate the Nix development shell when you enter the repository directory.
This is also the most robust way to use the environment from **any shell** (bash, zsh, fish, …): direnv stays in your current shell and loads the environment _after_ your shell's startup files have run, so the Nix-provided tools take precedence over anything your shell configuration adds to `$PATH`. To use it, install direnv for your shell, then add an `.envrc` containing `use flake` at the repository root and run `direnv allow`.
## Conan and Prebuilt Packages
Please note that there is no guarantee that binaries from conan cache will work when using nix. If you encounter any errors, please use `--build '*'` to force conan to compile everything from source:
@@ -93,3 +125,8 @@ conan install .. --output-folder . --build '*' --settings build_type=Release
## Updating `flake.lock` file
To update `flake.lock` to the latest revision use `nix flake update` command.
## Troubleshooting
See [Troubleshooting Nix problems](./nix_troubleshooting.md) for common issues,
such as `nix develop` failing inside Git worktrees.

61
docs/build/nix_troubleshooting.md vendored Normal file
View File

@@ -0,0 +1,61 @@
# Troubleshooting Nix problems
Common issues encountered when using the [Nix development shell](./nix.md), and
how to resolve them.
## Git worktrees
If `nix develop` fails with an error like:
```
error:
… while fetching the input 'git+file:///path/to/rippled'
error: opening Git repository "/path/to/rippled": unsupported extension name extensions.relativeworktrees (libgit2 error code = 6)
```
then your Nix is linked against a libgit2 older than **1.9.4**. Git 2.48+ writes
the `extensions.relativeWorktrees` config entry when a worktree is created with
relative paths (`git worktree add --relative-paths`, or with
`worktree.useRelativePaths=true`), and older libgit2 versions refuse to open a
repository that uses it. Nix uses libgit2 to read the flake, so evaluation
fails.
> [!IMPORTANT]
> This entry is written to the **shared** repository config, so once any
> relative worktree exists, `nix develop` fails in the main checkout too — not
> just inside the worktree.
### Workarounds
These work today, with any Nix version:
- bypass libgit2 with a `path:` flakeref: `nix develop "path:$PWD"`
(note: this copies the working tree to the store and ignores `.gitignore`); or
- create worktrees with absolute paths (omit `--relative-paths`); or
- clear the extension if you don't need relative worktrees:
`git config --unset extensions.relativeWorktrees`.
### Permanent fix
The fix is in [libgit2 1.9.4](https://github.com/libgit2/libgit2/releases/tag/v1.9.4),
so the real solution is a Nix that links against libgit2 `1.9.4` or newer. Check
which version yours links against:
```bash
nix-store -qR "$(readlink -f "$(command -v nix)")" | grep libgit2
```
> [!WARNING]
> `nix upgrade-nix` does **not** help yet. It installs the build from the
> official [`nix-fallback-paths`](https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/installer/tools/nix-fallback-paths.nix),
> which is still linked against libgit2 `1.9.2` — there is no new upstream Nix
> release with the fix. (On some systems that build is even the exact store path
> you already have, making the upgrade a no-op.)
nixpkgs has already rebuilt Nix against the fixed libgit2 (e.g. `nix-2.34.7+1`),
so the cleanest path is to reinstall Nix using your usual installation method
once it picks up that rebuild, then re-run the `grep libgit2` check above to
confirm it reports `1.9.4` or newer.
Until then, prefer the workarounds above.

View File

@@ -33,96 +33,9 @@ isGlobalFrozen(ReadView const& view, AccountID const& issuer);
[[nodiscard]] XRPAmount
xrpLiquid(ReadView const& view, AccountID const& id, std::int32_t ownerCountAdj, beast::Journal j);
/** Returns the account reserve, in drops.
Actual owner count can be adjusted by delta in ownerCountAdj
The reserve is calculated as
(ownerCount + "sponsoring object count" - "sponsored object count" + additionalOwnerCount) *
increment + (1 if not sponsored account + sponsoringAccountCount) * "reserve base"
*/
[[nodiscard]] XRPAmount
accountReserve(
ReadView const& view,
SLE::const_ref sle,
beast::Journal j,
std::int32_t ownerCountAdj = 0,
std::int32_t reserveCountAdj = 0);
[[nodiscard]] inline XRPAmount
accountReserve(
ReadView const& view,
AccountID const& id,
beast::Journal j,
std::int32_t ownerCountAdj = 0,
std::int32_t reserveCountAdj = 0)
{
return accountReserve(view, view.read(keylet::account(id)), j, ownerCountAdj, reserveCountAdj);
}
XRPAmount
baseAccountReserve(ReadView const& view, std::int32_t ownerCount);
[[nodiscard]] TER
checkInsufficientReserve(
ReadView const& view,
STTx const& tx,
SLE::const_ref accSle,
STAmount const& accBalance,
SLE::const_ref sponsorSle,
std::int32_t ownerCountDelta,
std::int32_t reserveCountDelta = 0,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()});
std::uint32_t
ownerCount(
ReadView const& view,
SLE::const_ref sle,
beast::Journal j,
std::int32_t ownerCountAdj = 0);
/** Adjust the owner count up or down. */
void
adjustOwnerCount(
ApplyView& view,
SLE::ref accountSle,
SLE::ref sponsorSle,
std::int32_t amount,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()});
inline void
adjustOwnerCount(
ApplyView& view,
AccountID const& account,
std::optional<AccountID> const& sponsor,
std::int32_t amount,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
{
adjustOwnerCount(
view,
view.peek(keylet::account(account)),
sponsor ? view.peek(keylet::account(*sponsor)) : SLE::pointer(),
amount,
j);
}
void
adjustOwnerCountObj(
ApplyView& view,
SLE::ref accountSle,
SLE::ref objectSle,
std::int32_t amount,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()});
inline void
adjustOwnerCountObj(
ApplyView& view,
AccountID const& account,
SLE::ref objectSle,
std::int32_t amount,
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
{
SLE::ref accountSle = view.peek(keylet::account(account));
adjustOwnerCountObj(view, accountSle, objectSle, amount, j);
}
adjustOwnerCount(ApplyView& view, SLE::ref sle, std::int32_t amount, beast::Journal j);
/** Returns IOU issuer transfer fee as Rate. Rate specifies
* the fee as fractions of 1 billion. For example, 1% transfer rate
@@ -158,7 +71,7 @@ getPseudoAccountFields();
- null pointer
*/
[[nodiscard]] bool
isPseudoAccount(SLE::const_ref sleAcct, std::set<SField const*> const& pseudoFieldFilter = {});
isPseudoAccount(SLE::const_pointer sleAcct, std::set<SField const*> const& pseudoFieldFilter = {});
/** Convenience overload that reads the account from the view. */
[[nodiscard]] inline bool

View File

@@ -6,7 +6,6 @@
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/MPTAmount.h>
@@ -18,7 +17,6 @@ template <ValidIssueType T>
TER
escrowUnlockApplyHelper(
ApplyView& view,
STTx const& tx,
Rate lockedRate,
SLE::ref sleDest,
STAmount const& xrpBalance,
@@ -33,7 +31,6 @@ template <>
inline TER
escrowUnlockApplyHelper<Issue>(
ApplyView& view,
STTx const& tx,
Rate lockedRate,
SLE::ref sleDest,
STAmount const& xrpBalance,
@@ -59,13 +56,8 @@ escrowUnlockApplyHelper<Issue>(
if (!view.exists(trustLineKey) && createAsset)
{
// Can the account cover the trust line's reserve?
auto const sponsorSle = getTxReserveSponsor(view, tx);
if (!sponsorSle)
return sponsorSle.error(); // LCOV_EXCL_LINE
if (auto const ret =
checkInsufficientReserve(view, tx, sleDest, xrpBalance, *sponsorSle, 1, 0, journal);
!isTesSuccess(ret))
if (std::uint32_t const ownerCount = {sleDest->at(sfOwnerCount)};
xrpBalance < view.fees().accountReserve(ownerCount + 1))
{
JLOG(journal.trace()) << "Trust line does not exist. "
"Insufficient reserve to create line.";
@@ -92,7 +84,6 @@ escrowUnlockApplyHelper<Issue>(
Issue(currency, receiver), // limit of zero
0, // quality in
0, // quality out
*sponsorSle, // sponsor
journal); // journal
!isTesSuccess(ter))
{
@@ -170,7 +161,6 @@ template <>
inline TER
escrowUnlockApplyHelper<MPTIssue>(
ApplyView& view,
STTx const& tx,
Rate lockedRate,
SLE::ref sleDest,
STAmount const& xrpBalance,
@@ -186,31 +176,24 @@ escrowUnlockApplyHelper<MPTIssue>(
auto const mptID = amount.get<MPTIssue>().getMptID();
auto const issuanceKey = keylet::mptIssuance(mptID);
auto const mptKeylet = keylet::mptoken(issuanceKey.key, receiver);
if (!view.exists(mptKeylet) && createAsset && !receiverIssuer)
if (!view.exists(keylet::mptoken(issuanceKey.key, receiver)) && createAsset && !receiverIssuer)
{
auto const sponsorSle = getTxReserveSponsor(view, tx);
if (!sponsorSle)
return sponsorSle.error(); // LCOV_EXCL_LINE
if (std::uint32_t const ownerCount = {sleDest->at(sfOwnerCount)};
xrpBalance < view.fees().accountReserve(ownerCount + 1))
{
return tecINSUFFICIENT_RESERVE;
}
if (auto const ret =
checkInsufficientReserve(view, tx, sleDest, xrpBalance, *sponsorSle, 1, 0, journal);
!isTesSuccess(ret))
return ret;
if (auto const ter = createMPToken(view, mptID, receiver, *sponsorSle, 0);
!isTesSuccess(ter))
if (auto const ter = createMPToken(view, mptID, receiver, 0); !isTesSuccess(ter))
{
return ter; // LCOV_EXCL_LINE
}
// update owner count.
adjustOwnerCount(view, sleDest, *sponsorSle, 1, journal);
auto mptSle = view.peek(mptKeylet);
addSponsorToLedgerEntry(mptSle, *sponsorSle);
adjustOwnerCount(view, sleDest, 1, journal);
}
if (!view.exists(mptKeylet) && !receiverIssuer)
if (!view.exists(keylet::mptoken(issuanceKey.key, receiver)) && !receiverIssuer)
return tecNO_PERMISSION;
auto const xferRate = transferRate(view, amount);

View File

@@ -72,7 +72,6 @@ canAddHolding(ReadView const& view, MPTIssue const& mptIssue);
[[nodiscard]] TER
authorizeMPToken(
ApplyView& view,
STTx const& tx,
XRPAmount const& priorBalance,
MPTID const& mptIssuanceID,
AccountID const& account,
@@ -104,7 +103,6 @@ requireAuth(
[[nodiscard]] TER
enforceMPTokenAuthorization(
ApplyView& view,
STTx const& tx,
MPTID const& mptIssuanceID,
AccountID const& account,
XRPAmount const& priorBalance,
@@ -191,7 +189,6 @@ canMPTTradeAndTransfer(
[[nodiscard]] TER
addEmptyHolding(
ApplyView& view,
STTx const& tx,
AccountID const& accountID,
XRPAmount priorBalance,
MPTIssue const& mptIssue,
@@ -200,7 +197,6 @@ addEmptyHolding(
[[nodiscard]] TER
removeEmptyHolding(
ApplyView& view,
STTx const& tx,
AccountID const& accountID,
MPTIssue const& mptIssue,
beast::Journal journal);
@@ -232,7 +228,6 @@ createMPToken(
ApplyView& view,
MPTID const& mptIssuanceID,
AccountID const& account,
SLE::ref sponsorSle,
std::uint32_t const flags);
TER
@@ -240,7 +235,6 @@ checkCreateMPT(
xrpl::ApplyView& view,
xrpl::MPTIssue const& mptIssue,
xrpl::AccountID const& holder,
SLE::ref sponsorSle,
beast::Journal j);
//------------------------------------------------------------------------------

View File

@@ -39,7 +39,7 @@ findTokenAndPage(ApplyView& view, AccountID const& owner, uint256 const& nftoken
/** Insert the token in the owner's token directory. */
TER
insertToken(ApplyView& view, STTx const& tx, AccountID owner, SLE::ref sponsorSle, STObject&& nft);
insertToken(ApplyView& view, AccountID owner, STObject&& nft);
/** Remove the token from the owner's token directory. */
TER
@@ -107,7 +107,6 @@ tokenOfferCreatePreclaim(
TER
tokenOfferCreateApply(
ApplyView& view,
STTx const& tx,
AccountID const& acctID,
STAmount const& amount,
std::optional<AccountID> const& dest,

View File

@@ -149,7 +149,6 @@ trustCreate(
// Issuer should be the account being set.
std::uint32_t uQualityIn,
std::uint32_t uQualityOut,
SLE::ref sponsorSle,
beast::Journal j);
[[nodiscard]] TER
@@ -230,7 +229,6 @@ canTransfer(ReadView const& view, Issue const& issue, AccountID const& from, Acc
[[nodiscard]] TER
addEmptyHolding(
ApplyView& view,
STTx const& tx,
AccountID const& accountID,
XRPAmount priorBalance,
Issue const& issue,

View File

@@ -1,137 +0,0 @@
#pragma once
#include <xrpl/basics/Log.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/View.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TxFlags.h>
#include <expected>
namespace xrpl {
inline bool
isReserveSponsored(STTx const& tx)
{
return (tx.getFieldU32(sfSponsorFlags) & spfSponsorReserve) != 0u;
}
inline bool
isSponsorReserveCoSigning(STTx const& tx)
{
if (!tx.isFieldPresent(sfSponsorSignature))
return false;
return isReserveSponsored(tx);
}
inline std::optional<AccountID>
getTxReserveSponsorAccountID(STTx const& tx)
{
if (tx.isFieldPresent(sfSponsor) && isReserveSponsored(tx))
{
return tx.getAccountID(sfSponsor);
}
return {};
}
inline std::expected<SLE::pointer, TER>
getTxReserveSponsor(ApplyView& view, STTx const& tx)
{
auto const sponsorID = getTxReserveSponsorAccountID(tx);
if (sponsorID)
{
auto sle = view.peek(keylet::account(*sponsorID));
// already checked in Transactor::checkSponsor
if (!sle)
return std::unexpected(tecINTERNAL);
return sle;
}
return SLE::pointer();
}
inline std::expected<SLE::const_pointer, TER>
getTxReserveSponsor(ReadView const& view, STTx const& tx)
{
auto const sponsorID = getTxReserveSponsorAccountID(tx);
if (sponsorID)
{
auto sle = view.read(keylet::account(*sponsorID));
// already checked in Transactor::checkSponsor
if (!sle)
return std::unexpected(tecINTERNAL);
return sle;
}
return SLE::pointer();
}
inline std::optional<AccountID>
getLedgerEntryReserveSponsorAccountID(SLE::const_ref sle, SF_ACCOUNT const& field = sfSponsor)
{
if (sle->isFieldPresent(field))
return sle->getAccountID(field);
return {};
}
inline SLE::pointer
getLedgerEntryReserveSponsor(
ApplyView& view,
SLE::const_ref sle,
SF_ACCOUNT const& field = sfSponsor)
{
auto const sponsorID = getLedgerEntryReserveSponsorAccountID(sle, field);
if (sponsorID)
return view.peek(keylet::account(*sponsorID));
return {};
}
inline SLE::const_pointer
getLedgerEntryReserveSponsor(
ReadView const& view,
SLE::const_ref sle,
SF_ACCOUNT const& field = sfSponsor)
{
auto const sponsorID = getLedgerEntryReserveSponsorAccountID(sle, field);
if (sponsorID)
return view.read(keylet::account(*sponsorID));
return {};
}
inline void
addSponsorToLedgerEntry(
SLE::ref sle,
SLE::const_ref sponsorSle,
SF_ACCOUNT const& field = sfSponsor)
{
XRPL_ASSERT(
(sle->getType() == ltRIPPLE_STATE && (field == sfHighSponsor || field == sfLowSponsor)) ||
(sle->getType() != ltRIPPLE_STATE && field == sfSponsor),
"addSponsorToLedgerEntry : Invalid field to the LedgerEntry");
if (sponsorSle)
sle->setAccountID(field, sponsorSle->getAccountID(sfAccount));
}
inline void
removeSponsorFromLedgerEntry(SLE::ref sle, SF_ACCOUNT const& field = sfSponsor)
{
XRPL_ASSERT(
(sle->getType() == ltRIPPLE_STATE && (field == sfHighSponsor || field == sfLowSponsor)) ||
(sle->getType() != ltRIPPLE_STATE && field == sfSponsor),
"removeSponsorFromLedgerEntry : Invalid field to the LedgerEntry");
if (sle->isFieldPresent(field))
sle->makeFieldAbsent(field);
}
// namespace sponsor
// {
// // Accessing the ledger to check if provided sponsor is valid.
// [[nodiscard]] TER
// valid(ReadView const& view, STTx const& tx, beast::Journal j)
// {
// }
// }
} // namespace xrpl

View File

@@ -231,7 +231,6 @@ canAddHolding(ReadView const& view, Asset const& asset);
[[nodiscard]] TER
addEmptyHolding(
ApplyView& view,
STTx const& tx,
AccountID const& accountID,
XRPAmount priorBalance,
Asset const& asset,
@@ -240,7 +239,6 @@ addEmptyHolding(
[[nodiscard]] TER
removeEmptyHolding(
ApplyView& view,
STTx const& tx,
AccountID const& accountID,
Asset const& asset,
beast::Journal journal);
@@ -301,7 +299,6 @@ accountSend(
AccountID const& to,
STAmount const& saAmount,
beast::Journal j,
SLE::ref sponsorSle = {},
WaiveTransferFee waiveFee = WaiveTransferFee::No,
AllowMPTOverflow allowOverflow = AllowMPTOverflow::No);
@@ -319,7 +316,6 @@ accountSendMulti(
Asset const& asset,
MultiplePaymentDestinations const& receivers,
beast::Journal j,
SLE::ref sponsorSle,
WaiveTransferFee waiveFee = WaiveTransferFee::No);
[[nodiscard]] TER

View File

@@ -33,6 +33,17 @@ struct Fees
: base(base), reserve(reserve), increment(increment)
{
}
/** Returns the account reserve given the owner count, in drops.
The reserve is calculated as the reserve base plus
the reserve increment times the number of increments.
*/
[[nodiscard]] XRPAmount
accountReserve(std::size_t ownerCount) const
{
return reserve + ownerCount * increment;
}
};
} // namespace xrpl

View File

@@ -151,10 +151,6 @@ static TicketT const kTicket{};
Keylet
signers(AccountID const& account) noexcept;
/** A Sponsorship */
Keylet
sponsor(AccountID const& sponsor, AccountID const& sponsee) noexcept;
/** A Check */
/** @{ */
Keylet

View File

@@ -203,11 +203,7 @@ enum LedgerEntryType : std::uint16_t {
LEDGER_OBJECT(Loan, \
LSF_FLAG(lsfLoanDefault, 0x00010000) \
LSF_FLAG(lsfLoanImpaired, 0x00020000) \
LSF_FLAG(lsfLoanOverpayment, 0x00040000)) /* True, loan allows overpayments */ \
\
LEDGER_OBJECT(Sponsorship, \
LSF_FLAG(lsfSponsorshipRequireSignForFee, 0x00010000) \
LSF_FLAG(lsfSponsorshipRequireSignForReserve, 0x00020000))
LSF_FLAG(lsfLoanOverpayment, 0x00040000)) /* True, loan allows overpayments */
// clang-format on

View File

@@ -220,7 +220,6 @@ enum TERcodes : TERUnderlyingType {
// create a pseudo-account
terNO_DELEGATE_PERMISSION, // Delegate does not have permission
terLOCKED, // MPT is locked
terNO_SPONSORSHIP, // No sponsorship found
};
//------------------------------------------------------------------------------
@@ -359,7 +358,6 @@ enum TECcodes : TERUnderlyingType {
tecLIMIT_EXCEEDED = 195,
tecPSEUDO_ACCOUNT = 196,
tecPRECISION_LOSS = 197,
tecNO_SPONSOR_PERMISSION = 198,
};
//------------------------------------------------------------------------------

View File

@@ -102,8 +102,7 @@ inline constexpr FlagValue tfUniversalMask = ~tfUniversal;
TRANSACTION(Payment, \
TF_FLAG(tfNoRippleDirect, 0x00010000) \
TF_FLAG(tfPartialPayment, 0x00020000) \
TF_FLAG(tfLimitQuality, 0x00040000) \
TF_FLAG(tfSponsorCreatedAccount, 0x00080000), \
TF_FLAG(tfLimitQuality, 0x00040000), \
MASK_ADJ(0)) \
\
TRANSACTION(TrustSet, \
@@ -215,20 +214,6 @@ inline constexpr FlagValue tfUniversalMask = ~tfUniversal;
TF_FLAG(tfLoanDefault, 0x00010000) \
TF_FLAG(tfLoanImpair, 0x00020000) \
TF_FLAG(tfLoanUnimpair, 0x00040000), \
MASK_ADJ(0)) \
\
TRANSACTION(SponsorshipSet, \
TF_FLAG(tfSponsorshipSetRequireSignForFee, 0x00010000) \
TF_FLAG(tfSponsorshipClearRequireSignForFee, 0x00020000) \
TF_FLAG(tfSponsorshipSetRequireSignForReserve, 0x00040000) \
TF_FLAG(tfSponsorshipClearRequireSignForReserve, 0x00080000) \
TF_FLAG(tfDeleteObject, 0x00100000), \
MASK_ADJ(0)) \
\
TRANSACTION(SponsorshipTransfer, \
TF_FLAG(tfSponsorshipEnd, 0x00000001) \
TF_FLAG(tfSponsorshipCreate, 0x00000002) \
TF_FLAG(tfSponsorshipReassign, 0x00000004), \
MASK_ADJ(0))
// clang-format on
@@ -353,9 +338,6 @@ getAllTxFlags()
inline constexpr FlagValue tfMPTPaymentMask = ~(tfUniversal | tfPartialPayment);
inline constexpr FlagValue tfTrustSetPermissionMask =
~(tfUniversal | tfSetfAuth | tfSetFreeze | tfClearFreeze);
inline constexpr FlagValue tfSponsorshipSetPermissionMask =
~(tfUniversal | tfSponsorshipSetRequireSignForFee | tfSponsorshipSetRequireSignForReserve |
tfSponsorshipClearRequireSignForFee | tfSponsorshipClearRequireSignForReserve);
// MPTokenIssuanceCreate MutableFlags:
// Indicating specific fields or flags may be changed after issuance.
@@ -463,35 +445,6 @@ getAsfFlagMap()
#pragma pop_macro("ACCOUNTSET_FLAG_TO_MAP")
#pragma pop_macro("ACCOUNTSET_FLAGS")
#pragma push_macro("SPONSOR_FLAGS")
#pragma push_macro("SPONSOR_FLAG_TO_VALUE")
#pragma push_macro("SPONSOR_FLAG_TO_MAP")
// Sponsor Flag values
#define SPONSOR_FLAGS(SPF_FLAG) \
SPF_FLAG(spfSponsorFee, 1) \
SPF_FLAG(spfSponsorReserve, 2)
#define SPONSOR_FLAG_TO_VALUE(name, value) inline constexpr FlagValue name = value;
#define SPONSOR_FLAG_TO_MAP(name, value) {#name, value},
SPONSOR_FLAGS(SPONSOR_FLAG_TO_VALUE)
inline std::map<std::string, FlagValue> const&
getspfFlagMap()
{
static std::map<std::string, FlagValue> const flags = {SPONSOR_FLAGS(SPONSOR_FLAG_TO_MAP)};
return flags;
}
#undef SPONSOR_FLAG_TO_VALUE
#undef SPONSOR_FLAG_TO_MAP
#undef SPONSOR_FLAGS
#pragma pop_macro("SPONSOR_FLAG_TO_VALUE")
#pragma pop_macro("SPONSOR_FLAG_TO_MAP")
#pragma pop_macro("SPONSOR_FLAGS")
} // namespace xrpl
// NOLINTEND(readability-identifier-naming)

View File

@@ -15,7 +15,6 @@
// Add new amendments to the top of this list.
// Keep it sorted in reverse chronological order.
XRPL_FEATURE(Sponsor, Supported::Yes, VoteBehavior::DefaultNo)
XRPL_FIX (Cleanup3_3_0, Supported::Yes, VoteBehavior::DefaultNo)
XRPL_FIX (Cleanup3_2_0, Supported::Yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(MPTokensV2, Supported::No, VoteBehavior::DefaultNo)

View File

@@ -150,9 +150,6 @@ LEDGER_ENTRY(ltACCOUNT_ROOT, 0x0061, AccountRoot, account, ({
{sfAMMID, SoeOptional}, // pseudo-account designator
{sfVaultID, SoeOptional}, // pseudo-account designator
{sfLoanBrokerID, SoeOptional}, // pseudo-account designator
{sfSponsoredOwnerCount, SoeDefault},
{sfSponsoringOwnerCount, SoeDefault},
{sfSponsoringAccountCount,SoeDefault},
}))
/** A ledger object which contains a list of object identifiers.
@@ -289,8 +286,6 @@ LEDGER_ENTRY(ltRIPPLE_STATE, 0x0072, RippleState, state, ({
{sfHighNode, SoeOptional},
{sfHighQualityIn, SoeOptional},
{sfHighQualityOut, SoeOptional},
{sfHighSponsor, SoeOptional},
{sfLowSponsor, SoeOptional},
}))
/** The ledger object which lists the network's fee settings.
@@ -612,20 +607,5 @@ LEDGER_ENTRY(ltLOAN, 0x0089, Loan, loan, ({
{sfLoanScale, SoeDefault},
}))
/** A ledger object representing a sponsorship.
\sa keylet::sponsor
*/
LEDGER_ENTRY(ltSPONSORSHIP, 0x0090, Sponsorship, sponsorship, ({
{sfPreviousTxnID, SoeRequired},
{sfPreviousTxnLgrSeq, SoeRequired},
{sfOwner, SoeRequired},
{sfSponsee, SoeRequired},
{sfFeeAmount, SoeOptional},
{sfMaxFee, SoeOptional},
{sfReserveCount, SoeDefault},
{sfOwnerNode, SoeRequired},
{sfSponseeNode, SoeRequired},
}))
#undef EXPAND
#undef LEDGER_ENTRY_DUPLICATE

View File

@@ -47,9 +47,3 @@ PERMISSION(MPTokenIssuanceLock, ttMPTOKEN_ISSUANCE_SET, 65547)
/** This permission grants the delegated account the ability to unlock MPToken. */
PERMISSION(MPTokenIssuanceUnlock, ttMPTOKEN_ISSUANCE_SET, 65548)
/** This permission grants the delegated account the ability to set SponsorFee. */
PERMISSION(SponsorFee, ttSPONSORSHIP_SET, 65549)
/** This permission grants the delegated account the ability to set SponsorReserve. */
PERMISSION(SponsorReserve, ttSPONSORSHIP_SET, 65550)

View File

@@ -113,11 +113,6 @@ TYPED_SFIELD(sfInterestRate, UINT32, 65) // 1/10 basis points (bi
TYPED_SFIELD(sfLateInterestRate, UINT32, 66) // 1/10 basis points (bips)
TYPED_SFIELD(sfCloseInterestRate, UINT32, 67) // 1/10 basis points (bips)
TYPED_SFIELD(sfOverpaymentInterestRate, UINT32, 68) // 1/10 basis points (bips)
TYPED_SFIELD(sfSponsoredOwnerCount, UINT32, 69)
TYPED_SFIELD(sfSponsoringOwnerCount, UINT32, 70)
TYPED_SFIELD(sfSponsoringAccountCount, UINT32, 71)
TYPED_SFIELD(sfReserveCount, UINT32, 72)
TYPED_SFIELD(sfSponsorFlags, UINT32, 73)
// 64-bit integers (common)
TYPED_SFIELD(sfIndexNext, UINT64, 1)
@@ -151,7 +146,6 @@ TYPED_SFIELD(sfSubjectNode, UINT64, 28)
TYPED_SFIELD(sfLockedAmount, UINT64, 29, SField::kSmdBaseTen|SField::kSmdDefault)
TYPED_SFIELD(sfVaultNode, UINT64, 30)
TYPED_SFIELD(sfLoanBrokerNode, UINT64, 31)
TYPED_SFIELD(sfSponseeNode, UINT64, 32)
// 128-bit
TYPED_SFIELD(sfEmailHash, UINT128, 1)
@@ -212,7 +206,6 @@ TYPED_SFIELD(sfLoanBrokerID, UINT256, 37,
SField::kSmdPseudoAccount | SField::kSmdDefault)
TYPED_SFIELD(sfLoanID, UINT256, 38)
TYPED_SFIELD(sfReferenceHolding, UINT256, 39)
TYPED_SFIELD(sfObjectID, UINT256, 40)
// number (common)
TYPED_SFIELD(sfNumber, NUMBER, 1)
@@ -272,8 +265,6 @@ TYPED_SFIELD(sfPrice, AMOUNT, 28)
TYPED_SFIELD(sfSignatureReward, AMOUNT, 29)
TYPED_SFIELD(sfMinAccountCreateAmount, AMOUNT, 30)
TYPED_SFIELD(sfLPTokenBalance, AMOUNT, 31)
TYPED_SFIELD(sfFeeAmount, AMOUNT, 32)
TYPED_SFIELD(sfMaxFee, AMOUNT, 33)
// variable length (common)
TYPED_SFIELD(sfPublicKey, VL, 1)
@@ -334,11 +325,6 @@ TYPED_SFIELD(sfIssuingChainDoor, ACCOUNT, 23)
TYPED_SFIELD(sfSubject, ACCOUNT, 24)
TYPED_SFIELD(sfBorrower, ACCOUNT, 25)
TYPED_SFIELD(sfCounterparty, ACCOUNT, 26)
TYPED_SFIELD(sfSponsor, ACCOUNT, 27)
TYPED_SFIELD(sfHighSponsor, ACCOUNT, 28)
TYPED_SFIELD(sfLowSponsor, ACCOUNT, 29)
TYPED_SFIELD(sfCounterpartySponsor, ACCOUNT, 30)
TYPED_SFIELD(sfSponsee, ACCOUNT, 31)
// vector of 256-bit
TYPED_SFIELD(sfIndexes, VECTOR256, 1, SField::kSmdNever)
@@ -403,7 +389,6 @@ UNTYPED_SFIELD(sfRawTransaction, OBJECT, 34)
UNTYPED_SFIELD(sfBatchSigner, OBJECT, 35)
UNTYPED_SFIELD(sfBook, OBJECT, 36)
UNTYPED_SFIELD(sfCounterpartySignature, OBJECT, 37, SField::kSmdDefault, SField::kNotSigning)
UNTYPED_SFIELD(sfSponsorSignature, OBJECT, 38, SField::kSmdDefault, SField::kNotSigning)
// array of objects (common)
// ARRAY/1 is reserved for end of array

View File

@@ -1077,35 +1077,6 @@ TRANSACTION(ttLOAN_PAY, 84, LoanPay,
{sfAmount, SoeRequired, SoeMptSupported},
}))
/** This transaction transfer sponsorship */
#if TRANSACTION_INCLUDE
# include <xrpl/tx/transactors/sponsor/SponsorshipTransfer.h>
#endif
TRANSACTION(ttSPONSORSHIP_TRANSFER, 85, SponsorshipTransfer,
Delegation::Delegable,
featureSponsor,
NoPriv,
({
{sfObjectID, SoeOptional},
{sfSponsee, SoeOptional},
}))
/** This transaction create sponsorship object */
#if TRANSACTION_INCLUDE
# include <xrpl/tx/transactors/sponsor/SponsorshipSet.h>
#endif
TRANSACTION(ttSPONSORSHIP_SET, 86, SponsorshipSet,
Delegation::Delegable,
featureSponsor,
NoPriv,
({
{sfCounterpartySponsor, SoeOptional},
{sfSponsee, SoeOptional},
{sfFeeAmount, SoeOptional},
{sfMaxFee, SoeOptional},
{sfReserveCount, SoeOptional},
}))
/** This system-generated transaction type is used to update the status of the various amendments.
For details, see: https://xrpl.org/amendments.html

View File

@@ -552,9 +552,6 @@ JSS(source_account); // in: PathRequest, RipplePathFind
JSS(source_amount); // in: PathRequest, RipplePathFind
JSS(source_currencies); // in: PathRequest, RipplePathFind
JSS(source_tag); // out: AccountChannels
JSS(sponsee); // in: LedgerEntry
JSS(sponsor); // in: LedgerEntry
JSS(sponsored); // in: AccountObjects
JSS(stand_alone); // out: NetworkOPs
JSS(standard_deviation); // out: get_aggregate_price
JSS(start); // in: TxHistory

View File

@@ -518,78 +518,6 @@ public:
{
return this->sle_->isFieldPresent(sfLoanBrokerID);
}
/**
* @brief Get sfSponsoredOwnerCount (SoeDefault)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT32::type::value_type>
getSponsoredOwnerCount() const
{
if (hasSponsoredOwnerCount())
return this->sle_->at(sfSponsoredOwnerCount);
return std::nullopt;
}
/**
* @brief Check if sfSponsoredOwnerCount is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasSponsoredOwnerCount() const
{
return this->sle_->isFieldPresent(sfSponsoredOwnerCount);
}
/**
* @brief Get sfSponsoringOwnerCount (SoeDefault)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT32::type::value_type>
getSponsoringOwnerCount() const
{
if (hasSponsoringOwnerCount())
return this->sle_->at(sfSponsoringOwnerCount);
return std::nullopt;
}
/**
* @brief Check if sfSponsoringOwnerCount is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasSponsoringOwnerCount() const
{
return this->sle_->isFieldPresent(sfSponsoringOwnerCount);
}
/**
* @brief Get sfSponsoringAccountCount (SoeDefault)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT32::type::value_type>
getSponsoringAccountCount() const
{
if (hasSponsoringAccountCount())
return this->sle_->at(sfSponsoringAccountCount);
return std::nullopt;
}
/**
* @brief Check if sfSponsoringAccountCount is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasSponsoringAccountCount() const
{
return this->sle_->isFieldPresent(sfSponsoringAccountCount);
}
};
/**
@@ -891,39 +819,6 @@ public:
return *this;
}
/**
* @brief Set sfSponsoredOwnerCount (SoeDefault)
* @return Reference to this builder for method chaining.
*/
AccountRootBuilder&
setSponsoredOwnerCount(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfSponsoredOwnerCount] = value;
return *this;
}
/**
* @brief Set sfSponsoringOwnerCount (SoeDefault)
* @return Reference to this builder for method chaining.
*/
AccountRootBuilder&
setSponsoringOwnerCount(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfSponsoringOwnerCount] = value;
return *this;
}
/**
* @brief Set sfSponsoringAccountCount (SoeDefault)
* @return Reference to this builder for method chaining.
*/
AccountRootBuilder&
setSponsoringAccountCount(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfSponsoringAccountCount] = value;
return *this;
}
/**
* @brief Build and return the completed AccountRoot wrapper.
* @param index The ledger entry index.

View File

@@ -243,54 +243,6 @@ public:
{
return this->sle_->isFieldPresent(sfHighQualityOut);
}
/**
* @brief Get sfHighSponsor (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_ACCOUNT::type::value_type>
getHighSponsor() const
{
if (hasHighSponsor())
return this->sle_->at(sfHighSponsor);
return std::nullopt;
}
/**
* @brief Check if sfHighSponsor is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasHighSponsor() const
{
return this->sle_->isFieldPresent(sfHighSponsor);
}
/**
* @brief Get sfLowSponsor (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_ACCOUNT::type::value_type>
getLowSponsor() const
{
if (hasLowSponsor())
return this->sle_->at(sfLowSponsor);
return std::nullopt;
}
/**
* @brief Check if sfLowSponsor is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasLowSponsor() const
{
return this->sle_->isFieldPresent(sfLowSponsor);
}
};
/**
@@ -458,28 +410,6 @@ public:
return *this;
}
/**
* @brief Set sfHighSponsor (SoeOptional)
* @return Reference to this builder for method chaining.
*/
RippleStateBuilder&
setHighSponsor(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
{
object_[sfHighSponsor] = value;
return *this;
}
/**
* @brief Set sfLowSponsor (SoeOptional)
* @return Reference to this builder for method chaining.
*/
RippleStateBuilder&
setLowSponsor(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
{
object_[sfLowSponsor] = value;
return *this;
}
/**
* @brief Build and return the completed RippleState wrapper.
* @param index The ledger entry index.

View File

@@ -1,344 +0,0 @@
// This file is auto-generated. Do not edit.
#pragma once
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STParsedJSON.h>
#include <xrpl/protocol/jss.h>
#include <xrpl/protocol_autogen/LedgerEntryBase.h>
#include <xrpl/protocol_autogen/LedgerEntryBuilderBase.h>
#include <xrpl/json/json_value.h>
#include <stdexcept>
#include <optional>
namespace xrpl::ledger_entries {
class SponsorshipBuilder;
/**
* @brief Ledger Entry: Sponsorship
*
* Type: ltSPONSORSHIP (0x0090)
* RPC Name: sponsorship
*
* Immutable wrapper around SLE providing type-safe field access.
* Use SponsorshipBuilder to construct new ledger entries.
*/
class Sponsorship : public LedgerEntryBase
{
public:
static constexpr LedgerEntryType entryType = ltSPONSORSHIP;
/**
* @brief Construct a Sponsorship ledger entry wrapper from an existing SLE object.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
explicit Sponsorship(SLE::const_pointer sle)
: LedgerEntryBase(std::move(sle))
{
// Verify ledger entry type
if (sle_->getType() != entryType)
{
throw std::runtime_error("Invalid ledger entry type for Sponsorship");
}
}
// Ledger entry-specific field getters
/**
* @brief Get sfPreviousTxnID (SoeRequired)
* @return The field value.
*/
[[nodiscard]]
SF_UINT256::type::value_type
getPreviousTxnID() const
{
return this->sle_->at(sfPreviousTxnID);
}
/**
* @brief Get sfPreviousTxnLgrSeq (SoeRequired)
* @return The field value.
*/
[[nodiscard]]
SF_UINT32::type::value_type
getPreviousTxnLgrSeq() const
{
return this->sle_->at(sfPreviousTxnLgrSeq);
}
/**
* @brief Get sfOwner (SoeRequired)
* @return The field value.
*/
[[nodiscard]]
SF_ACCOUNT::type::value_type
getOwner() const
{
return this->sle_->at(sfOwner);
}
/**
* @brief Get sfSponsee (SoeRequired)
* @return The field value.
*/
[[nodiscard]]
SF_ACCOUNT::type::value_type
getSponsee() const
{
return this->sle_->at(sfSponsee);
}
/**
* @brief Get sfFeeAmount (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_AMOUNT::type::value_type>
getFeeAmount() const
{
if (hasFeeAmount())
return this->sle_->at(sfFeeAmount);
return std::nullopt;
}
/**
* @brief Check if sfFeeAmount is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasFeeAmount() const
{
return this->sle_->isFieldPresent(sfFeeAmount);
}
/**
* @brief Get sfMaxFee (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_AMOUNT::type::value_type>
getMaxFee() const
{
if (hasMaxFee())
return this->sle_->at(sfMaxFee);
return std::nullopt;
}
/**
* @brief Check if sfMaxFee is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasMaxFee() const
{
return this->sle_->isFieldPresent(sfMaxFee);
}
/**
* @brief Get sfReserveCount (SoeDefault)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT32::type::value_type>
getReserveCount() const
{
if (hasReserveCount())
return this->sle_->at(sfReserveCount);
return std::nullopt;
}
/**
* @brief Check if sfReserveCount is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasReserveCount() const
{
return this->sle_->isFieldPresent(sfReserveCount);
}
/**
* @brief Get sfOwnerNode (SoeRequired)
* @return The field value.
*/
[[nodiscard]]
SF_UINT64::type::value_type
getOwnerNode() const
{
return this->sle_->at(sfOwnerNode);
}
/**
* @brief Get sfSponseeNode (SoeRequired)
* @return The field value.
*/
[[nodiscard]]
SF_UINT64::type::value_type
getSponseeNode() const
{
return this->sle_->at(sfSponseeNode);
}
};
/**
* @brief Builder for Sponsorship ledger entries.
*
* Provides a fluent interface for constructing ledger entries with method chaining.
* Uses STObject internally for flexible ledger entry construction.
* Inherits common field setters from LedgerEntryBuilderBase.
*/
class SponsorshipBuilder : public LedgerEntryBuilderBase<SponsorshipBuilder>
{
public:
/**
* @brief Construct a new SponsorshipBuilder with required fields.
* @param previousTxnID The sfPreviousTxnID field value.
* @param previousTxnLgrSeq The sfPreviousTxnLgrSeq field value.
* @param owner The sfOwner field value.
* @param sponsee The sfSponsee field value.
* @param ownerNode The sfOwnerNode field value.
* @param sponseeNode The sfSponseeNode field value.
*/
SponsorshipBuilder(std::decay_t<typename SF_UINT256::type::value_type> const& previousTxnID,std::decay_t<typename SF_UINT32::type::value_type> const& previousTxnLgrSeq,std::decay_t<typename SF_ACCOUNT::type::value_type> const& owner,std::decay_t<typename SF_ACCOUNT::type::value_type> const& sponsee,std::decay_t<typename SF_UINT64::type::value_type> const& ownerNode,std::decay_t<typename SF_UINT64::type::value_type> const& sponseeNode)
: LedgerEntryBuilderBase<SponsorshipBuilder>(ltSPONSORSHIP)
{
setPreviousTxnID(previousTxnID);
setPreviousTxnLgrSeq(previousTxnLgrSeq);
setOwner(owner);
setSponsee(sponsee);
setOwnerNode(ownerNode);
setSponseeNode(sponseeNode);
}
/**
* @brief Construct a SponsorshipBuilder from an existing SLE object.
* @param sle The existing ledger entry to copy from.
* @throws std::runtime_error if the ledger entry type doesn't match.
*/
SponsorshipBuilder(SLE::const_pointer sle)
{
if (sle->at(sfLedgerEntryType) != ltSPONSORSHIP)
{
throw std::runtime_error("Invalid ledger entry type for Sponsorship");
}
object_ = *sle;
}
/** @brief Ledger entry-specific field setters */
/**
* @brief Set sfPreviousTxnID (SoeRequired)
* @return Reference to this builder for method chaining.
*/
SponsorshipBuilder&
setPreviousTxnID(std::decay_t<typename SF_UINT256::type::value_type> const& value)
{
object_[sfPreviousTxnID] = value;
return *this;
}
/**
* @brief Set sfPreviousTxnLgrSeq (SoeRequired)
* @return Reference to this builder for method chaining.
*/
SponsorshipBuilder&
setPreviousTxnLgrSeq(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfPreviousTxnLgrSeq] = value;
return *this;
}
/**
* @brief Set sfOwner (SoeRequired)
* @return Reference to this builder for method chaining.
*/
SponsorshipBuilder&
setOwner(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
{
object_[sfOwner] = value;
return *this;
}
/**
* @brief Set sfSponsee (SoeRequired)
* @return Reference to this builder for method chaining.
*/
SponsorshipBuilder&
setSponsee(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
{
object_[sfSponsee] = value;
return *this;
}
/**
* @brief Set sfFeeAmount (SoeOptional)
* @return Reference to this builder for method chaining.
*/
SponsorshipBuilder&
setFeeAmount(std::decay_t<typename SF_AMOUNT::type::value_type> const& value)
{
object_[sfFeeAmount] = value;
return *this;
}
/**
* @brief Set sfMaxFee (SoeOptional)
* @return Reference to this builder for method chaining.
*/
SponsorshipBuilder&
setMaxFee(std::decay_t<typename SF_AMOUNT::type::value_type> const& value)
{
object_[sfMaxFee] = value;
return *this;
}
/**
* @brief Set sfReserveCount (SoeDefault)
* @return Reference to this builder for method chaining.
*/
SponsorshipBuilder&
setReserveCount(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfReserveCount] = value;
return *this;
}
/**
* @brief Set sfOwnerNode (SoeRequired)
* @return Reference to this builder for method chaining.
*/
SponsorshipBuilder&
setOwnerNode(std::decay_t<typename SF_UINT64::type::value_type> const& value)
{
object_[sfOwnerNode] = value;
return *this;
}
/**
* @brief Set sfSponseeNode (SoeRequired)
* @return Reference to this builder for method chaining.
*/
SponsorshipBuilder&
setSponseeNode(std::decay_t<typename SF_UINT64::type::value_type> const& value)
{
object_[sfSponseeNode] = value;
return *this;
}
/**
* @brief Build and return the completed Sponsorship wrapper.
* @param index The ledger entry index.
* @return The constructed ledger entry wrapper.
*/
Sponsorship
build(uint256 const& index)
{
return Sponsorship{std::make_shared<SLE>(std::move(object_), index)};
}
};
} // namespace xrpl::ledger_entries

View File

@@ -1,290 +0,0 @@
// This file is auto-generated. Do not edit.
#pragma once
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/STParsedJSON.h>
#include <xrpl/protocol/jss.h>
#include <xrpl/protocol_autogen/TransactionBase.h>
#include <xrpl/protocol_autogen/TransactionBuilderBase.h>
#include <xrpl/json/json_value.h>
#include <stdexcept>
#include <optional>
namespace xrpl::transactions {
class SponsorshipSetBuilder;
/**
* @brief Transaction: SponsorshipSet
*
* Type: ttSPONSORSHIP_SET (86)
* Delegable: Delegation::Delegable
* Amendment: featureSponsor
* Privileges: NoPriv
*
* Immutable wrapper around STTx providing type-safe field access.
* Use SponsorshipSetBuilder to construct new transactions.
*/
class SponsorshipSet : public TransactionBase
{
public:
static constexpr xrpl::TxType txType = ttSPONSORSHIP_SET;
/**
* @brief Construct a SponsorshipSet transaction wrapper from an existing STTx object.
* @throws std::runtime_error if the transaction type doesn't match.
*/
explicit SponsorshipSet(std::shared_ptr<STTx const> tx)
: TransactionBase(std::move(tx))
{
// Verify transaction type
if (tx_->getTxnType() != txType)
{
throw std::runtime_error("Invalid transaction type for SponsorshipSet");
}
}
// Transaction-specific field getters
/**
* @brief Get sfCounterpartySponsor (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_ACCOUNT::type::value_type>
getCounterpartySponsor() const
{
if (hasCounterpartySponsor())
{
return this->tx_->at(sfCounterpartySponsor);
}
return std::nullopt;
}
/**
* @brief Check if sfCounterpartySponsor is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasCounterpartySponsor() const
{
return this->tx_->isFieldPresent(sfCounterpartySponsor);
}
/**
* @brief Get sfSponsee (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_ACCOUNT::type::value_type>
getSponsee() const
{
if (hasSponsee())
{
return this->tx_->at(sfSponsee);
}
return std::nullopt;
}
/**
* @brief Check if sfSponsee is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasSponsee() const
{
return this->tx_->isFieldPresent(sfSponsee);
}
/**
* @brief Get sfFeeAmount (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_AMOUNT::type::value_type>
getFeeAmount() const
{
if (hasFeeAmount())
{
return this->tx_->at(sfFeeAmount);
}
return std::nullopt;
}
/**
* @brief Check if sfFeeAmount is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasFeeAmount() const
{
return this->tx_->isFieldPresent(sfFeeAmount);
}
/**
* @brief Get sfMaxFee (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_AMOUNT::type::value_type>
getMaxFee() const
{
if (hasMaxFee())
{
return this->tx_->at(sfMaxFee);
}
return std::nullopt;
}
/**
* @brief Check if sfMaxFee is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasMaxFee() const
{
return this->tx_->isFieldPresent(sfMaxFee);
}
/**
* @brief Get sfReserveCount (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT32::type::value_type>
getReserveCount() const
{
if (hasReserveCount())
{
return this->tx_->at(sfReserveCount);
}
return std::nullopt;
}
/**
* @brief Check if sfReserveCount is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasReserveCount() const
{
return this->tx_->isFieldPresent(sfReserveCount);
}
};
/**
* @brief Builder for SponsorshipSet transactions.
*
* Provides a fluent interface for constructing transactions with method chaining.
* Uses STObject internally for flexible transaction construction.
* Inherits common field setters from TransactionBuilderBase.
*/
class SponsorshipSetBuilder : public TransactionBuilderBase<SponsorshipSetBuilder>
{
public:
/**
* @brief Construct a new SponsorshipSetBuilder with required fields.
* @param account The account initiating the transaction.
* @param sequence Optional sequence number for the transaction.
* @param fee Optional fee for the transaction.
*/
SponsorshipSetBuilder(SF_ACCOUNT::type::value_type account,
std::optional<SF_UINT32::type::value_type> sequence = std::nullopt,
std::optional<SF_AMOUNT::type::value_type> fee = std::nullopt
)
: TransactionBuilderBase<SponsorshipSetBuilder>(ttSPONSORSHIP_SET, account, sequence, fee)
{
}
/**
* @brief Construct a SponsorshipSetBuilder from an existing STTx object.
* @param tx The existing transaction to copy from.
* @throws std::runtime_error if the transaction type doesn't match.
*/
SponsorshipSetBuilder(std::shared_ptr<STTx const> tx)
{
if (tx->getTxnType() != ttSPONSORSHIP_SET)
{
throw std::runtime_error("Invalid transaction type for SponsorshipSetBuilder");
}
object_ = *tx;
}
/** @brief Transaction-specific field setters */
/**
* @brief Set sfCounterpartySponsor (SoeOptional)
* @return Reference to this builder for method chaining.
*/
SponsorshipSetBuilder&
setCounterpartySponsor(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
{
object_[sfCounterpartySponsor] = value;
return *this;
}
/**
* @brief Set sfSponsee (SoeOptional)
* @return Reference to this builder for method chaining.
*/
SponsorshipSetBuilder&
setSponsee(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
{
object_[sfSponsee] = value;
return *this;
}
/**
* @brief Set sfFeeAmount (SoeOptional)
* @return Reference to this builder for method chaining.
*/
SponsorshipSetBuilder&
setFeeAmount(std::decay_t<typename SF_AMOUNT::type::value_type> const& value)
{
object_[sfFeeAmount] = value;
return *this;
}
/**
* @brief Set sfMaxFee (SoeOptional)
* @return Reference to this builder for method chaining.
*/
SponsorshipSetBuilder&
setMaxFee(std::decay_t<typename SF_AMOUNT::type::value_type> const& value)
{
object_[sfMaxFee] = value;
return *this;
}
/**
* @brief Set sfReserveCount (SoeOptional)
* @return Reference to this builder for method chaining.
*/
SponsorshipSetBuilder&
setReserveCount(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfReserveCount] = value;
return *this;
}
/**
* @brief Build and return the SponsorshipSet wrapper.
* @param publicKey The public key for signing.
* @param secretKey The secret key for signing.
* @return The constructed transaction wrapper.
*/
SponsorshipSet
build(PublicKey const& publicKey, SecretKey const& secretKey)
{
sign(publicKey, secretKey);
return SponsorshipSet{std::make_shared<STTx>(std::move(object_))};
}
};
} // namespace xrpl::transactions

View File

@@ -1,179 +0,0 @@
// This file is auto-generated. Do not edit.
#pragma once
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/STParsedJSON.h>
#include <xrpl/protocol/jss.h>
#include <xrpl/protocol_autogen/TransactionBase.h>
#include <xrpl/protocol_autogen/TransactionBuilderBase.h>
#include <xrpl/json/json_value.h>
#include <stdexcept>
#include <optional>
namespace xrpl::transactions {
class SponsorshipTransferBuilder;
/**
* @brief Transaction: SponsorshipTransfer
*
* Type: ttSPONSORSHIP_TRANSFER (85)
* Delegable: Delegation::Delegable
* Amendment: featureSponsor
* Privileges: NoPriv
*
* Immutable wrapper around STTx providing type-safe field access.
* Use SponsorshipTransferBuilder to construct new transactions.
*/
class SponsorshipTransfer : public TransactionBase
{
public:
static constexpr xrpl::TxType txType = ttSPONSORSHIP_TRANSFER;
/**
* @brief Construct a SponsorshipTransfer transaction wrapper from an existing STTx object.
* @throws std::runtime_error if the transaction type doesn't match.
*/
explicit SponsorshipTransfer(std::shared_ptr<STTx const> tx)
: TransactionBase(std::move(tx))
{
// Verify transaction type
if (tx_->getTxnType() != txType)
{
throw std::runtime_error("Invalid transaction type for SponsorshipTransfer");
}
}
// Transaction-specific field getters
/**
* @brief Get sfObjectID (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT256::type::value_type>
getObjectID() const
{
if (hasObjectID())
{
return this->tx_->at(sfObjectID);
}
return std::nullopt;
}
/**
* @brief Check if sfObjectID is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasObjectID() const
{
return this->tx_->isFieldPresent(sfObjectID);
}
/**
* @brief Get sfSponsee (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_ACCOUNT::type::value_type>
getSponsee() const
{
if (hasSponsee())
{
return this->tx_->at(sfSponsee);
}
return std::nullopt;
}
/**
* @brief Check if sfSponsee is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasSponsee() const
{
return this->tx_->isFieldPresent(sfSponsee);
}
};
/**
* @brief Builder for SponsorshipTransfer transactions.
*
* Provides a fluent interface for constructing transactions with method chaining.
* Uses STObject internally for flexible transaction construction.
* Inherits common field setters from TransactionBuilderBase.
*/
class SponsorshipTransferBuilder : public TransactionBuilderBase<SponsorshipTransferBuilder>
{
public:
/**
* @brief Construct a new SponsorshipTransferBuilder with required fields.
* @param account The account initiating the transaction.
* @param sequence Optional sequence number for the transaction.
* @param fee Optional fee for the transaction.
*/
SponsorshipTransferBuilder(SF_ACCOUNT::type::value_type account,
std::optional<SF_UINT32::type::value_type> sequence = std::nullopt,
std::optional<SF_AMOUNT::type::value_type> fee = std::nullopt
)
: TransactionBuilderBase<SponsorshipTransferBuilder>(ttSPONSORSHIP_TRANSFER, account, sequence, fee)
{
}
/**
* @brief Construct a SponsorshipTransferBuilder from an existing STTx object.
* @param tx The existing transaction to copy from.
* @throws std::runtime_error if the transaction type doesn't match.
*/
SponsorshipTransferBuilder(std::shared_ptr<STTx const> tx)
{
if (tx->getTxnType() != ttSPONSORSHIP_TRANSFER)
{
throw std::runtime_error("Invalid transaction type for SponsorshipTransferBuilder");
}
object_ = *tx;
}
/** @brief Transaction-specific field setters */
/**
* @brief Set sfObjectID (SoeOptional)
* @return Reference to this builder for method chaining.
*/
SponsorshipTransferBuilder&
setObjectID(std::decay_t<typename SF_UINT256::type::value_type> const& value)
{
object_[sfObjectID] = value;
return *this;
}
/**
* @brief Set sfSponsee (SoeOptional)
* @return Reference to this builder for method chaining.
*/
SponsorshipTransferBuilder&
setSponsee(std::decay_t<typename SF_ACCOUNT::type::value_type> const& value)
{
object_[sfSponsee] = value;
return *this;
}
/**
* @brief Build and return the SponsorshipTransfer wrapper.
* @param publicKey The public key for signing.
* @param secretKey The secret key for signing.
* @return The constructed transaction wrapper.
*/
SponsorshipTransfer
build(PublicKey const& publicKey, SecretKey const& secretKey)
{
sign(publicKey, secretKey);
return SponsorshipTransfer{std::make_shared<STTx>(std::move(object_))};
}
};
} // namespace xrpl::transactions

View File

@@ -2,7 +2,6 @@
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/utility/WrappedSink.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/protocol/Permissions.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/ApplyContext.h>
@@ -109,20 +108,6 @@ struct PreflightResult;
// Needed for preflight specialization
class Change;
enum class FeePayerType {
Account,
Delegate,
SponsorCoSigned,
SponsorPreFunded,
};
struct FeePayer
{
Keylet entry;
SF_AMOUNT const& balanceField;
FeePayerType type{FeePayerType::Account};
};
class Transactor
{
protected:
@@ -239,9 +224,6 @@ public:
static NotTEC
checkPermission(ReadView const& view, STTx const& tx);
static NotTEC
checkSponsor(ReadView const& view, STTx const& tx);
/////////////////////////////////////////////////////
// Interface used by AccountDelete
@@ -374,9 +356,6 @@ private:
std::pair<TER, XRPAmount>
reset(XRPAmount fee);
static FeePayer
getFeePayer(ReadView const& view, STTx const& tx);
TER
consumeSeqProxy(SLE::pointer const& sleAccount);
TER

View File

@@ -14,7 +14,6 @@
#include <xrpl/tx/invariants/NFTInvariant.h>
#include <xrpl/tx/invariants/PermissionedDEXInvariant.h>
#include <xrpl/tx/invariants/PermissionedDomainInvariant.h>
#include <xrpl/tx/invariants/SponsorshipInvariant.h>
#include <xrpl/tx/invariants/VaultInvariant.h>
#include <cstdint>
@@ -416,9 +415,7 @@ using InvariantChecks = std::tuple<
ValidVault,
ValidMPTPayment,
ValidAmounts,
ValidMPTTransfer,
SponsorshipOwnerCountsMatch,
SponsorshipAccountCountMatchesField>;
ValidMPTTransfer>;
/**
* @brief get a tuple of all invariant checks

View File

@@ -1,56 +0,0 @@
#pragma once
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <cstdint>
namespace xrpl {
/**
* @brief Invariant: Sponsored owner counts are balanced.
*
* The following check is made for every transaction:
* - The sum of all per-account deltas of `sfSponsoredOwnerCount` equals
* the sum of all per-account deltas of `sfSponsoringOwnerCount`.
* - Account OwnerCount must be greater than or equal to SponsoredOwnerCount.
*/
class SponsorshipOwnerCountsMatch
{
std::int64_t deltaSponsoredOwnerCount_ = 0;
std::int64_t deltaSponsoringOwnerCount_ = 0;
std::int64_t deltaSponsoredObjectOwnerCount_ = 0;
std::uint64_t invalidOwnerCountLessThanSponsoredOwnerCount_ = 0;
public:
void
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
[[nodiscard]] bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&) const;
};
/**
* @brief Invariant: Sponsoring account relationships tracked consistently.
*
* The following check is made for every transaction:
* - The net delta of `sfSponsoringAccountCount` across all accounts equals
* the net delta of the count of ltACCOUNT_ROOT entries having
* `sfSponsor` present (presence transitions only: add/remove).
*/
class SponsorshipAccountCountMatchesField
{
std::int64_t deltaSponsoringAccountCount_ = 0;
std::int64_t deltaSponsorFieldPresence_ = 0;
public:
void
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
[[nodiscard]] bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&) const;
};
} // namespace xrpl

View File

@@ -104,10 +104,7 @@ public:
send(Args&&... args)
{
return accountSend(
std::forward<Args>(args)...,
SLE::pointer(),
WaiveTransferFee::Yes,
AllowMPTOverflow::Yes);
std::forward<Args>(args)..., WaiveTransferFee::Yes, AllowMPTOverflow::Yes);
}
[[nodiscard]] bool

View File

@@ -224,8 +224,7 @@ template <typename... Args>
TER
TOffer<TIn, TOut>::send(Args&&... args)
{
return accountSend(
std::forward<Args>(args)..., SLE::pointer(), WaiveTransferFee::No, AllowMPTOverflow::Yes);
return accountSend(std::forward<Args>(args)..., WaiveTransferFee::No, AllowMPTOverflow::Yes);
}
template <StepAmount TIn, StepAmount TOut>

View File

@@ -100,7 +100,6 @@ public:
static std::tuple<TER, STAmount, STAmount, std::optional<STAmount>>
equalWithdrawTokens(
Sandbox& view,
STTx const& tx,
SLE const& ammSle,
AccountID const account,
AccountID const& ammAccount,
@@ -135,7 +134,6 @@ public:
static std::tuple<TER, STAmount, STAmount, std::optional<STAmount>>
withdraw(
Sandbox& view,
STTx const& tx,
SLE const& ammSle,
AccountID const& ammAccount,
AccountID const& account,
@@ -179,7 +177,6 @@ private:
std::pair<TER, STAmount>
withdraw(
Sandbox& view,
STTx const& tx,
SLE const& ammSle,
AccountID const& ammAccount,
STAmount const& amountBalance,
@@ -205,7 +202,6 @@ private:
std::pair<TER, STAmount>
equalWithdrawTokens(
Sandbox& view,
STTx const& tx,
SLE const& ammSle,
AccountID const& ammAccount,
STAmount const& amountBalance,
@@ -231,7 +227,6 @@ private:
std::pair<TER, STAmount>
equalWithdrawLimit(
Sandbox& view,
STTx const& tx,
SLE const& ammSle,
AccountID const& ammAccount,
STAmount const& amountBalance,
@@ -254,7 +249,6 @@ private:
std::pair<TER, STAmount>
singleWithdraw(
Sandbox& view,
STTx const& tx,
SLE const& ammSle,
AccountID const& ammAccount,
STAmount const& amountBalance,
@@ -276,7 +270,6 @@ private:
std::pair<TER, STAmount>
singleWithdrawTokens(
Sandbox& view,
STTx const& tx,
SLE const& ammSle,
AccountID const& ammAccount,
STAmount const& amountBalance,
@@ -299,7 +292,6 @@ private:
std::pair<TER, STAmount>
singleWithdrawEPrice(
Sandbox& view,
STTx const& tx,
SLE const& ammSle,
AccountID const& ammAccount,
STAmount const& amountBalance,

View File

@@ -22,12 +22,6 @@ public:
{
}
static uint32_t
calculateOracleReserve(std::size_t count)
{
return count > 5 ? 2 : 1;
}
static NotTEC
preflight(PreflightContext const& ctx);

View File

@@ -1,46 +0,0 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class SponsorshipSet : public Transactor
{
public:
static constexpr auto kConsequencesFactory = ConsequencesFactoryType::Normal;
explicit SponsorshipSet(ApplyContext& ctx) : Transactor(ctx)
{
}
static std::uint32_t
getFlagsMask(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static NotTEC
checkPermission(ReadView const& view, STTx const& tx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
};
} // namespace xrpl

View File

@@ -1,43 +0,0 @@
#pragma once
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class SponsorshipTransfer : public Transactor
{
public:
static constexpr auto kConsequencesFactory = ConsequencesFactoryType::Normal;
explicit SponsorshipTransfer(ApplyContext& ctx) : Transactor(ctx)
{
}
static std::uint32_t
getFlagsMask(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
void
visitInvariantEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
};
} // namespace xrpl

View File

@@ -63,7 +63,7 @@ public:
beast::Journal const& j) override;
static std::expected<MPTID, TER>
create(ApplyView& view, STTx const& tx, beast::Journal journal, MPTCreateArgs const& args);
create(ApplyView& view, beast::Journal journal, MPTCreateArgs const& args);
};
} // namespace xrpl

View File

@@ -71,7 +71,7 @@ if [ ! -e "${target}" ]; then
fi
EOF
COPY nix/docker/check-tools.sh /tmp/check-tools.sh
COPY bin/check-tools.sh /tmp/check-tools.sh
RUN /tmp/check-tools.sh
# Sanity-check that the g++/clang++ are able to build binaries, including sanitizer-instrumented ones.
@@ -93,7 +93,7 @@ RUN if echo "${BASE_IMAGE}" | grep -qiE 'nixos'; then \
SHELL ["/bin/bash", "-e", "-o", "pipefail", "-c"]
# Sanity-check that the built binaries run correctly in the vanilla base image, with the necessary sanitizer runtime libraries installed.
COPY nix/docker/install-sanitizer-libs.sh /tmp/install-sanitizer-libs.sh
COPY bin/install-sanitizer-libs.sh /tmp/install-sanitizer-libs.sh
COPY nix/docker/test_files/run-test-binaries.sh /tmp/run-test-binaries.sh
COPY --from=final /tmp/bins /tmp/bins

90
nix/docker/README.md Normal file
View File

@@ -0,0 +1,90 @@
# Nix CI Docker images
This directory builds the Docker images used by xrpld's Linux CI. Each image
bundles the **exact same toolchain that the Nix development shell provides**
(see [`docs/build/nix.md`](../../docs/build/nix.md)), so what runs in CI matches
what developers get locally from `nix develop`.
The toolchain (CMake, Ninja, Conan, GCC, Clang, clang-tidy, the
sanitizer/coverage tools, …) is defined in [`nix/packages.nix`](../packages.nix)
and assembled for CI by [`nix/ci-env.nix`](../ci-env.nix). The Docker build
turns that Nix environment into an ordinary container image layered on top of a
conventional base image (Ubuntu, Debian, RHEL, or `nixos/nix`).
## Images
The images are built by the [`build-nix-images.yml`](../../.github/workflows/build-nix-images.yml)
workflow and pushed to `ghcr.io/xrplf/xrpld/nix-<distro>`. The `<distro>` is
selected through the `BASE_IMAGE` build argument; the base images are the
**oldest supported version** of each distribution we target:
| Image | `BASE_IMAGE` | Notes |
| ------------ | -------------------------------------------- | -------------------------------------------------- |
| `nix-nixos` | `nixos/nix:latest` | Build/lint only; binaries are not run (see below). |
| `nix-ubuntu` | `ubuntu:20.04` | Oldest supported Ubuntu (glibc 2.31). |
| `nix-debian` | `debian:bookworm` | |
| `nix-rhel` | `registry.access.redhat.com/ubi9/ubi:latest` | |
All images carry the full toolchain on `PATH` (via `/nix/ci-env/bin`) plus the
CA bundle shipped in the Nix environment, so HTTPS clients (git, curl, Conan)
work without `ca-certificates` being installed in the base image.
## Build stages
[`Dockerfile`](./Dockerfile) is a multi-stage build:
1. **`builder`** — On a `nixos/nix` builder, evaluate the flake and build the
CI environment (`nix/ci-env.nix`). The resulting Nix store closure (the
complete set of store paths the toolchain depends on) is copied into a
staging directory.
2. **`final`** — Start from `BASE_IMAGE`, copy in the Nix store closure and the
`ci-env` symlink tree, and wire up `PATH` and the CA bundle. It then:
- installs the dynamic linker if the base image lacks one (see
[How libc is handled](#how-libc-is-handled)),
- runs [`bin/check-tools.sh`](../../bin/check-tools.sh) to verify every
expected tool is present and runnable, and
- compiles the C++ test programs in
[`test_files/`](./test_files) with both `g++` and `clang++`, and sanitizers.
3. **`tester`** — Start again from a clean `BASE_IMAGE` (no Nix toolchain),
install only the sanitizer runtime libraries
([`install-sanitizer-libs.sh`](./install-sanitizer-libs.sh)), and run the
binaries compiled in `final`. This proves the binaries built with the Nix
toolchain actually run on a vanilla base image. On `nixos/nix` this step is
skipped (the binaries are patched for a conventional FHS loader).
4. **Output** — The final image is gated on the tester succeeding: it copies a
sentinel file out of `tester`, so a failed test run fails the whole build.
## How libc is handled
The goal is for binaries built in these images to run on the **oldest supported
base image** (Ubuntu 20.04, glibc 2.31) and newer — without the developer's Nix
toolchain being present at runtime. Two pieces make that work:
- **Compilers linked against an old glibc.** The Nix CI environment does not use
nixpkgs' current glibc. Instead it pins a 2020 nixpkgs snapshot whose primary
glibc is **2.31** (matching Ubuntu 20.04), via the `nixpkgs-custom-glibc`
flake input. GCC, Clang, binutils and compiler-rt are all rebuilt/wrapped
against this custom glibc (see [`nix/ci-env.nix`](../ci-env.nix)). As a result
the libraries they emit (`libstdc++`, `libgcc_s`, the sanitizer runtimes)
reference only symbols available in glibc 2.31.
- **An expected dynamic linker in the image.**
Binaries built in Nix environments reference a dynamic linker from Nix store paths, which won't be present in the base image. However,
[`loader-path.sh`](./loader-path.sh) reports the expected loader path for the
current architecture, so we can patch the binaries to use the correct loader.
The build then verifies all of this end to end: the test programs in
`test_files/` (a regular binary plus ASan/TSan/UBSan variants) are compiled in
`final`, their `PT_INTERP` is patched to the target loader, and they are run in
the clean `tester` stage to confirm each emits the expected sanitizer
diagnostic on a stock base image.
## Files
| File | Purpose |
| ----------------------------------------------------------------------- | ----------------------------------------------------------------------------- |
| [`./Dockerfile`](./Dockerfile) | Multi-stage build described above. |
| [`./loader-path.sh`](./loader-path.sh) | Print the dynamic-linker (`PT_INTERP`) path for the current architecture. |
| [`./test_files/`](./test_files) | C++ sources and scripts to compile and run the sanitizer smoke tests. |
| [`/bin/check-tools.sh`](../../bin/check-tools.sh) | Verify every expected tools are present and runnable. |
| [`/bin/install-sanitizer-libs.sh`](../../bin/install-sanitizer-libs.sh) | Install `libasan`/`libtsan`/`libubsan` runtimes on the supported base images. |

View File

@@ -1,39 +0,0 @@
#!/bin/bash
# Verify that every tool expected in the Nix CI env is present and runnable.
set -euo pipefail
ccache --version
clang --version
clang++ --version
clang-format --version
ClangBuildAnalyzer --version
cmake --version
conan --version
curl --version
doxygen --version
file --version
g++ --version
gcc --version
gcov --version
gcovr --version
gh --version
git --version
git-cliff --version
gpg --version
less --version
make --version
mold --version
netstat --version
ninja --version
perl --version
pkg-config --version
pre-commit --version
python3 --version
run-clang-tidy --help
vim --version
# A simple test to verify that git can clone a repository over HTTPS
# (i.e. the CA bundle is wired up). Clone to a temp dir and clean up.
tmp_clone="$(mktemp -d)"
git clone --depth 1 https://github.com/XRPLF/actions.git "${tmp_clone}/actions"
rm -rf "${tmp_clone}"

View File

@@ -33,5 +33,6 @@ in
python3
runClangTidy
vim
zip
];
}

View File

@@ -14,7 +14,6 @@
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Asset.h>
@@ -444,7 +443,7 @@ doWithdraw(
// Create trust line or MPToken for the receiving account
if (dstAcct == senderAcct)
{
if (auto const ter = addEmptyHolding(view, tx, senderAcct, priorBalance, amount.asset(), j);
if (auto const ter = addEmptyHolding(view, senderAcct, priorBalance, amount.asset(), j);
!isTesSuccess(ter) && ter != tecDUPLICATE)
return ter;
}
@@ -470,13 +469,9 @@ doWithdraw(
// LCOV_EXCL_STOP
}
auto const sponsorSle = getTxReserveSponsor(view, tx);
if (!sponsorSle)
return sponsorSle.error(); // LCOV_EXCL_LINE
// Move the funds directly from the broker's pseudo-account to the
// dstAcct
return accountSend(view, sourceAcct, dstAcct, amount, j, *sponsorSle, WaiveTransferFee::Yes);
return accountSend(view, sourceAcct, dstAcct, amount, j, WaiveTransferFee::Yes);
}
TER

View File

@@ -8,7 +8,6 @@
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
@@ -16,7 +15,6 @@
#include <xrpl/protocol/Rate.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/protocol/digest.h>
@@ -85,109 +83,6 @@ confineOwnerCount(
return adjusted;
}
static std::uint32_t
ownerCountHlp(
ReadView const& view,
SLE::const_ref sle,
std::int32_t adjustment,
bool reportConfine,
beast::Journal j)
{
AccountID const id = sle->getAccountID(sfAccount);
std::uint32_t const savedCount = sle->at(sfOwnerCount);
std::uint32_t const hookedCount = view.ownerCountHook(id, savedCount);
std::uint32_t const sponsoredCount = sle->at(sfSponsoredOwnerCount);
std::uint32_t const sponsoringCount = sle->at(sfSponsoringOwnerCount);
if (hookedCount < sponsoredCount)
{
Throw<std::logic_error>(
"xrpl::ownerCountHlp : OwnerCount must be greater than or equal to "
"SponsoredOwnerCount");
}
std::int64_t deltaCount =
static_cast<std::int64_t>(adjustment) - sponsoredCount + sponsoringCount;
if (deltaCount > std::numeric_limits<std::int32_t>::max())
{
deltaCount = std::numeric_limits<std::int32_t>::max();
JLOG(j.fatal()) << "Account " << id << " delta count exceeds max, "
<< "adjustment: " << adjustment << ", sponsoredCount: " << sponsoredCount
<< ", sponsoringOwnerCount: " << sponsoringCount;
}
else if (deltaCount < std::numeric_limits<std::int32_t>::min())
{
deltaCount = std::numeric_limits<std::int32_t>::min();
JLOG(j.fatal()) << "Account " << id << " delta count exceeds min, "
<< "adjustment: " << adjustment << ", sponsoredCount: " << sponsoredCount
<< ", sponsoringCount: " << sponsoringCount;
}
std::uint32_t const confinedCount = reportConfine
? confineOwnerCount(hookedCount, deltaCount, id, j)
: confineOwnerCount(hookedCount, deltaCount);
return confinedCount;
}
static std::uint32_t
reserveCountHlp(SLE::const_ref sle, std::int32_t adjustment, beast::Journal j)
{
bool const isSponsored = sle->isFieldPresent(sfSponsor);
std::uint32_t const sponsoringCount = sle->getFieldU32(sfSponsoringAccountCount);
std::uint32_t const reserveCount = (isSponsored ? 0 : 1) + sponsoringCount;
std::uint32_t adjusted{reserveCount + adjustment};
if (adjustment > 0)
{
// Overflow is well defined on unsigned
if (adjusted < reserveCount)
{
JLOG(j.fatal()) << "Reserve count exceeds max!";
adjusted = std::numeric_limits<std::uint32_t>::max();
}
}
else
{
// Underflow is well defined on unsigned
if (adjusted > reserveCount)
{
JLOG(j.fatal()) << "Reserve count set below 0!";
adjusted = 0;
}
}
return adjusted;
}
static inline XRPAmount
baseReserveHlp(ReadView const& view, std::uint32_t ownerCount, std::uint32_t reserveCount)
{
auto const& fees = view.fees();
return (fees.reserve * reserveCount) + (fees.increment * ownerCount);
}
static XRPAmount
reserveHlp(
ReadView const& view,
SLE::const_ref sle,
std::uint32_t ownerCount,
std::uint32_t reserveCount)
{
// Pseudo-accounts have no reserve requirement
if (isPseudoAccount(sle))
return XRPAmount(0);
auto const reserve = baseReserveHlp(view, ownerCount, reserveCount);
return reserve;
}
std::uint32_t
ownerCount(ReadView const& view, SLE::const_ref sle, beast::Journal j, std::int32_t adjustment)
{
return ownerCountHlp(view, sle, adjustment, true, j);
}
XRPAmount
xrpLiquid(ReadView const& view, AccountID const& id, std::int32_t ownerCountAdj, beast::Journal j)
{
@@ -195,9 +90,13 @@ xrpLiquid(ReadView const& view, AccountID const& id, std::int32_t ownerCountAdj,
if (sle == nullptr)
return beast::kZero;
std::uint32_t const ownerCount = ownerCountHlp(view, sle, ownerCountAdj, false, j);
std::uint32_t const reserveCount = reserveCountHlp(sle, 0, j);
auto const reserve = reserveHlp(view, sle, ownerCount, reserveCount);
// Return balance minus reserve
std::uint32_t const ownerCount =
confineOwnerCount(view.ownerCountHook(id, sle->getFieldU32(sfOwnerCount)), ownerCountAdj);
// Pseudo-accounts have no reserve requirement
auto const reserve =
isPseudoAccount(sle) ? XRPAmount{0} : view.fees().accountReserve(ownerCount);
auto const fullBalance = sle->getFieldAmount(sfBalance);
@@ -225,158 +124,20 @@ transferRate(ReadView const& view, AccountID const& issuer)
return kParityRate;
}
static void
adjustOwnerCountHlp(
ApplyView& view,
SLE::ref sle,
SF_UINT32 const& sfield,
AccountID const& accID,
std::int32_t adjustment,
beast::Journal j,
bool callHook = true)
{
std::uint32_t const current = sle->at(sfield);
std::uint32_t const adjusted = confineOwnerCount(current, adjustment, accID, j);
if (callHook)
view.adjustOwnerCountHook(accID, current, adjusted);
sle->at(sfield) = adjusted;
view.update(sle);
}
void
adjustOwnerCount(
ApplyView& view,
SLE::ref accountSle,
SLE::ref sponsorSle,
std::int32_t adjustment,
beast::Journal j)
{
if (!accountSle)
Throw<std::runtime_error>("xrpl::adjustOwnerCount : valid account sle");
auto const sleType = accountSle->getType();
bool const validType = sponsorSle ? sleType == ltACCOUNT_ROOT
: sleType == ltLOAN_BROKER || sleType == ltACCOUNT_ROOT;
if (!validType)
Throw<std::logic_error>("xrpl::adjustOwnerCount : valid account sle type");
XRPL_ASSERT(adjustment, "xrpl::adjustOwnerCount : nonzero adjustment input");
if (adjustment == 0)
return;
auto const accountID = accountSle->getAccountID(sfAccount);
if (sponsorSle)
{
if (sponsorSle->getType() != ltACCOUNT_ROOT)
Throw<std::logic_error>("xrpl::adjustOwnerCount : valid sponsor sle type");
auto const sponsorID = sponsorSle->getAccountID(sfAccount);
adjustOwnerCountHlp(view, accountSle, sfSponsoredOwnerCount, accountID, adjustment, j);
adjustOwnerCountHlp(view, sponsorSle, sfSponsoringOwnerCount, sponsorID, adjustment, j);
auto sponsorObjSle = view.peek(keylet::sponsor(sponsorID, accountID));
if (sponsorObjSle && adjustment > 0)
{
// update the pre-funded ReserveCount on Sponsorship ledger object
// Reserve count moves opposite to adjustment: +adjustment => consume reserve (-),
adjustOwnerCountHlp(
view, sponsorObjSle, sfReserveCount, sponsorID, -adjustment, j, false);
}
}
adjustOwnerCountHlp(view, accountSle, sfOwnerCount, accountID, adjustment, j);
}
void
adjustOwnerCountObj(
ApplyView& view,
SLE::ref accountSle,
SLE::ref objectSle,
std::int32_t amount,
beast::Journal j)
{
if (!objectSle)
Throw<std::runtime_error>("xrpl::adjustOwnerCount : valid object sle");
if (objectSle->getType() == ltACCOUNT_ROOT)
Throw<std::logic_error>("xrpl::adjustOwnerCount : valid object sle type");
SLE::ref sponsorSle = getLedgerEntryReserveSponsor(view, objectSle);
adjustOwnerCount(view, accountSle, sponsorSle, amount, j);
}
XRPAmount
accountReserve(
ReadView const& view,
SLE::const_ref sle,
beast::Journal j,
std::int32_t ownerCountAdj,
std::int32_t reserveCountAdj)
adjustOwnerCount(ApplyView& view, SLE::ref sle, std::int32_t amount, beast::Journal j)
{
if (!sle)
Throw<std::runtime_error>("xrpl::accountReserve : valid sle");
if (sle->getType() != ltACCOUNT_ROOT)
Throw<std::logic_error>("xrpl::accountReserve : valid sle type");
std::uint32_t const ownerCount = ownerCountHlp(view, sle, ownerCountAdj, true, j);
std::uint32_t const reserveCount = reserveCountHlp(sle, reserveCountAdj, j);
return reserveHlp(view, sle, ownerCount, reserveCount);
return;
XRPL_ASSERT(amount, "xrpl::adjustOwnerCount : nonzero amount input");
std::uint32_t const current{sle->getFieldU32(sfOwnerCount)};
AccountID const id = (*sle)[sfAccount];
std::uint32_t const adjusted = confineOwnerCount(current, amount, id, j);
view.adjustOwnerCountHook(id, current, adjusted);
sle->at(sfOwnerCount) = adjusted;
view.update(sle);
}
XRPAmount
baseAccountReserve(ReadView const& view, std::int32_t ownerCount)
{
auto const reserve = baseReserveHlp(view, ownerCount, 1);
return reserve;
}
TER
checkInsufficientReserve(
ReadView const& view,
STTx const& tx,
SLE::const_ref accSle,
STAmount const& accBalance,
SLE::const_ref sponsorSle,
std::int32_t ownerCountDelta,
std::int32_t reserveCountDelta,
beast::Journal j)
{
if (sponsorSle)
{
auto const isCoSigning = isSponsorReserveCoSigning(tx);
auto const sle = view.read(
keylet::sponsor(sponsorSle->getAccountID(sfAccount), accSle->getAccountID(sfAccount)));
// prefunded sponsor should have a sponsorship entry
if (!isCoSigning && !sle)
return tecINTERNAL; // LCOV_EXCL_LINE
if (sle)
{
auto const ownerCountAllowed = sle->getFieldU32(sfReserveCount);
if (ownerCountAllowed < ownerCountDelta)
return tecINSUFFICIENT_RESERVE;
}
auto const sponsorBalance = sponsorSle->getFieldAmount(sfBalance);
STAmount const sponsorReserve =
accountReserve(view, sponsorSle, j, ownerCountDelta, reserveCountDelta);
if (sponsorBalance < sponsorReserve)
return tecINSUFFICIENT_RESERVE;
}
else
{
STAmount const reserve =
accountReserve(view, accSle, j, ownerCountDelta, reserveCountDelta);
if (accBalance < reserve)
return tecINSUFFICIENT_RESERVE;
}
return tesSUCCESS;
}
// ----------------------------------------------------
AccountID
pseudoAccountAddress(ReadView const& view, uint256 const& pseudoOwnerKey)
{
@@ -427,7 +188,7 @@ getPseudoAccountFields()
}
[[nodiscard]] bool
isPseudoAccount(SLE::const_ref sleAcct, std::set<SField const*> const& pseudoFieldFilter)
isPseudoAccount(SLE::const_pointer sleAcct, std::set<SField const*> const& pseudoFieldFilter)
{
auto const& fields = getPseudoAccountFields();

View File

@@ -97,7 +97,7 @@ deleteSLE(ApplyView& view, SLE::ref sleCredential, beast::Journal j)
}
if (isOwner)
adjustOwnerCountObj(view, sleAccount, sleCredential, -1, j);
adjustOwnerCount(view, sleAccount, -1, j);
return tesSUCCESS;
};

View File

@@ -3,6 +3,7 @@
#include <xrpl/basics/Log.h>
#include <xrpl/basics/contract.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
@@ -10,7 +11,6 @@
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/CredentialHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Feature.h>
@@ -23,7 +23,6 @@
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/protocol/UintTypes.h>
@@ -127,7 +126,6 @@ canAddHolding(ReadView const& view, MPTIssue const& mptIssue)
[[nodiscard]] TER
addEmptyHolding(
ApplyView& view,
STTx const& tx,
AccountID const& accountID,
XRPAmount priorBalance,
MPTIssue const& mptIssue,
@@ -144,13 +142,12 @@ addEmptyHolding(
if (accountID == mptIssue.getIssuer())
return tesSUCCESS;
return authorizeMPToken(view, tx, priorBalance, mptID, accountID, journal);
return authorizeMPToken(view, priorBalance, mptID, accountID, journal);
}
[[nodiscard]] TER
authorizeMPToken(
ApplyView& view,
STTx const& tx,
XRPAmount const& priorBalance,
MPTID const& mptIssuanceID,
AccountID const& account,
@@ -183,7 +180,7 @@ authorizeMPToken(
keylet::ownerDir(account), (*sleMpt)[sfOwnerNode], sleMpt->key(), false))
return tecINTERNAL; // LCOV_EXCL_LINE
adjustOwnerCountObj(view, sleAcct, sleMpt, -1, journal);
adjustOwnerCount(view, sleAcct, -1, journal);
view.erase(sleMpt);
return tesSUCCESS;
@@ -193,27 +190,18 @@ authorizeMPToken(
// - add the new mptokenKey to the owner directory
// - create the MPToken object for the holder
auto const sponsorSle = getTxReserveSponsor(view, tx);
if (!sponsorSle)
return sponsorSle.error(); // LCOV_EXCL_LINE
auto const isSponsoredAndPreFunded = *sponsorSle && !isSponsorReserveCoSigning(tx);
// The reserve that is required to create the MPToken. Note
// that although the reserve increases with every item
// an account owns, in the case of MPTokens we only
// *enforce* a reserve if the user owns more than two
// items. This is similar to the reserve requirements of trust lines.
// If PreFunded Sponsor, it must be checked whether sufficient
// ReserveCount exists.
if (ownerCount(view, *sponsorSle ? *sponsorSle : sleAcct, journal) >= 2 ||
isSponsoredAndPreFunded)
{
if (auto const ret = checkInsufficientReserve(
view, tx, sleAcct, priorBalance, *sponsorSle, 1, 0, journal);
!isTesSuccess(ret))
return ret;
}
std::uint32_t const uOwnerCount = sleAcct->getFieldU32(sfOwnerCount);
XRPAmount const reserveCreate(
(uOwnerCount < 2) ? XRPAmount(beast::kZero)
: view.fees().accountReserve(uOwnerCount + 1));
if (priorBalance < reserveCreate)
return tecINSUFFICIENT_RESERVE;
// Defensive check before we attempt to create MPToken for the issuer
auto const mpt = view.read(keylet::mptIssuance(mptIssuanceID));
@@ -237,8 +225,7 @@ authorizeMPToken(
view.insert(mptoken);
// Update owner count.
adjustOwnerCount(view, sleAcct, *sponsorSle, 1, journal);
addSponsorToLedgerEntry(mptoken, *sponsorSle);
adjustOwnerCount(view, sleAcct, 1, journal);
return tesSUCCESS;
}
@@ -283,7 +270,6 @@ authorizeMPToken(
[[nodiscard]] TER
removeEmptyHolding(
ApplyView& view,
STTx const& tx,
AccountID const& accountID,
MPTIssue const& mptIssue,
beast::Journal journal)
@@ -306,7 +292,6 @@ removeEmptyHolding(
return authorizeMPToken(
view,
tx,
{}, // priorBalance
mptID,
accountID,
@@ -416,7 +401,6 @@ requireAuth(
[[nodiscard]] TER
enforceMPTokenAuthorization(
ApplyView& view,
STTx const& tx,
MPTID const& mptIssuanceID,
AccountID const& account,
XRPAmount const& priorBalance, // for MPToken authorization
@@ -498,7 +482,6 @@ enforceMPTokenAuthorization(
"xrpl::enforceMPTokenAuthorization : new MPToken for domain");
if (auto const err = authorizeMPToken(
view,
tx,
priorBalance, // priorBalance
mptIssuanceID, // mptIssuanceID
account, // account
@@ -913,7 +896,6 @@ createMPToken(
ApplyView& view,
MPTID const& mptIssuanceID,
AccountID const& account,
SLE::ref sponsorSle,
std::uint32_t const flags)
{
auto const mptokenKey = keylet::mptoken(mptIssuanceID, account);
@@ -930,9 +912,6 @@ createMPToken(
(*mptoken)[sfFlags] = flags;
(*mptoken)[sfOwnerNode] = *ownerNode;
if (sponsorSle)
addSponsorToLedgerEntry(mptoken, sponsorSle);
view.insert(mptoken);
return tesSUCCESS;
@@ -943,7 +922,6 @@ checkCreateMPT(
xrpl::ApplyView& view,
xrpl::MPTIssue const& mptIssue,
xrpl::AccountID const& holder,
SLE::ref sponsorSle,
beast::Journal j)
{
if (mptIssue.getIssuer() == holder)
@@ -953,7 +931,7 @@ checkCreateMPT(
auto const mptokenID = keylet::mptoken(mptIssuanceID.key, holder);
if (!view.exists(mptokenID))
{
if (auto const err = createMPToken(view, mptIssue.getMptID(), holder, sponsorSle, 0);
if (auto const err = createMPToken(view, mptIssue.getMptID(), holder, 0);
!isTesSuccess(err))
{
return err;
@@ -963,8 +941,7 @@ checkCreateMPT(
{
return tecINTERNAL;
}
adjustOwnerCount(view, sleAcct, sponsorSle, 1, j);
adjustOwnerCount(view, sleAcct, 1, j);
}
return tesSUCCESS;
}

View File

@@ -4,14 +4,12 @@
#include <xrpl/basics/Slice.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/contract.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Feature.h>
@@ -23,7 +21,6 @@
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STArray.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/SeqProxy.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/TxFlags.h>
@@ -35,7 +32,6 @@
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <expected>
#include <functional>
#include <iterator>
#include <memory>
@@ -71,15 +67,12 @@ locatePage(ApplyView& view, AccountID const& owner, uint256 const& id)
Keylet(ltNFTOKEN_PAGE, view.succ(first.key, last.key.next()).value_or(last.key)));
}
static std::expected<SLE::pointer, TER>
static SLE::pointer
getPageForToken(
ApplyView& view,
STTx const& tx,
AccountID const& owner,
SLE::ref sponsorSle,
uint256 const& id,
std::function<TER(ApplyView&, STTx const&, SLE::ref, AccountID const&, SLE::ref)> const&
createCallback)
std::function<void(ApplyView&, AccountID const&)> const& createCallback)
{
auto const base = keylet::nftpageMin(owner);
auto const first = keylet::nftpage(base, id);
@@ -98,9 +91,7 @@ getPageForToken(
cp = std::make_shared<SLE>(last);
cp->setFieldArray(sfNFTokens, arr);
view.insert(cp);
if (auto const ret = createCallback(view, tx, cp, owner, sponsorSle); !isTesSuccess(ret))
return std::unexpected(ret);
createCallback(view, owner);
return cp;
}
@@ -213,8 +204,7 @@ getPageForToken(
cp->setFieldH256(sfPreviousPageMin, np->key());
view.update(cp);
if (auto const ret = createCallback(view, tx, np, owner, sponsorSle); ret != tesSUCCESS)
return std::unexpected(ret);
createCallback(view, owner);
return (first.key < np->key()) ? np : cp;
}
@@ -270,55 +260,37 @@ changeTokenURI(
/** Insert the token in the owner's token directory. */
TER
insertToken(ApplyView& view, STTx const& tx, AccountID owner, SLE::ref sponsorSle, STObject&& nft)
insertToken(ApplyView& view, AccountID owner, STObject&& nft)
{
XRPL_ASSERT(nft.isFieldPresent(sfNFTokenID), "xrpl::nft::insertToken : has NFT token");
// First, we need to locate the page the NFT belongs to, creating it
// if necessary. This operation may fail if it is impossible to insert
// the NFT.
auto createCallback = [](ApplyView& view,
STTx const& tx,
std::shared_ptr<SLE> const& newPage,
AccountID const& owner,
SLE::ref sponsorSle) -> TER {
if (isReserveSponsored(tx))
{
auto const ownerSle = view.read(keylet::account(owner));
auto const ownerBalance = ownerSle->getFieldAmount(sfBalance);
if (auto const ret =
checkInsufficientReserve(view, tx, ownerSle, ownerBalance, sponsorSle, 1);
!isTesSuccess(ret))
return ret;
}
SLE::pointer const page =
getPageForToken(view, owner, nft[sfNFTokenID], [](ApplyView& view, AccountID const& owner) {
adjustOwnerCount(
view,
view.peek(keylet::account(owner)),
1,
beast::Journal{beast::Journal::getNullSink()});
});
adjustOwnerCount(view, view.peek(keylet::account(owner)), sponsorSle, 1);
addSponsorToLedgerEntry(newPage, sponsorSle);
return tesSUCCESS;
};
auto const page =
getPageForToken(view, tx, owner, sponsorSle, nft[sfNFTokenID], createCallback);
if (!page.has_value())
return page.error();
if (!(*page))
if (!page)
return tecNO_SUITABLE_NFTOKEN_PAGE;
{
auto arr = (*page)->getFieldArray(sfNFTokens);
auto arr = page->getFieldArray(sfNFTokens);
arr.pushBack(std::move(nft));
arr.sort([](STObject const& o1, STObject const& o2) {
return compareTokens(o1.getFieldH256(sfNFTokenID), o2.getFieldH256(sfNFTokenID));
});
(*page)->setFieldArray(sfNFTokens, arr);
page->setFieldArray(sfNFTokens, arr);
}
view.update((*page));
view.update(page);
return tesSUCCESS;
}
@@ -439,11 +411,22 @@ removeToken(ApplyView& view, AccountID const& owner, uint256 const& nftokenID, S
curr->setFieldArray(sfNFTokens, arr);
view.update(curr);
int cnt = 0;
if (prev && mergePages(view, prev, curr))
adjustOwnerCountObj(view, owner, prev, -1);
cnt--;
if (next && mergePages(view, curr, next))
adjustOwnerCountObj(view, owner, curr, -1);
cnt--;
if (cnt != 0)
{
adjustOwnerCount(
view,
view.peek(keylet::account(owner)),
cnt,
beast::Journal{beast::Journal::getNullSink()});
}
return tesSUCCESS;
}
@@ -477,7 +460,11 @@ removeToken(ApplyView& view, AccountID const& owner, uint256 const& nftokenID, S
curr->makeFieldAbsent(sfPreviousPageMin);
}
adjustOwnerCountObj(view, owner, prev, -1);
adjustOwnerCount(
view,
view.peek(keylet::account(owner)),
-1,
beast::Journal{beast::Journal::getNullSink()});
view.update(curr);
view.erase(prev);
@@ -513,10 +500,10 @@ removeToken(ApplyView& view, AccountID const& owner, uint256 const& nftokenID, S
view.update(next);
}
adjustOwnerCountObj(view, owner, curr, -1);
view.erase(curr);
int cnt = 1;
// Since we're here, try to consolidate the previous and current pages
// of the page we removed (if any) into one. mergePages() _should_
// always return false. Since tokens are burned one at a time, there
@@ -530,9 +517,13 @@ removeToken(ApplyView& view, AccountID const& owner, uint256 const& nftokenID, S
view,
view.peek(Keylet(ltNFTOKEN_PAGE, prev->key())),
view.peek(Keylet(ltNFTOKEN_PAGE, next->key()))))
{
adjustOwnerCountObj(view, owner, prev, -1);
}
cnt++;
adjustOwnerCount(
view,
view.peek(keylet::account(owner)),
-1 * cnt,
beast::Journal{beast::Journal::getNullSink()});
return tesSUCCESS;
}
@@ -648,7 +639,8 @@ deleteTokenOffer(ApplyView& view, SLE::ref offer)
false))
return false;
adjustOwnerCountObj(view, owner, offer, -1);
adjustOwnerCount(
view, view.peek(keylet::account(owner)), -1, beast::Journal{beast::Journal::getNullSink()});
view.erase(offer);
return true;
@@ -753,7 +745,7 @@ repairNFTokenDirectoryLinks(ApplyView& view, AccountID const& owner)
{
Throw<std::runtime_error>(
"NFTokenPage directory for " + to_string(owner) +
" cannot be repaired. std::unexpected link problem.");
" cannot be repaired. Unexpected link problem.");
}
newPrev->at(sfNextPageMin) = nextPage->key();
view.update(newPrev);
@@ -927,7 +919,6 @@ tokenOfferCreatePreclaim(
TER
tokenOfferCreateApply(
ApplyView& view,
STTx const& tx,
AccountID const& acctID,
STAmount const& amount,
std::optional<AccountID> const& dest,
@@ -939,14 +930,9 @@ tokenOfferCreateApply(
std::uint32_t txFlags)
{
Keylet const acctKeylet = keylet::account(acctID);
auto const acct = view.read(acctKeylet);
auto const sponsorSle = getTxReserveSponsor(view, tx);
if (!sponsorSle)
return sponsorSle.error(); // LCOV_EXCL_LINE
if (auto const ret =
checkInsufficientReserve(view, tx, acct, priorBalance, *sponsorSle, 1, 0, j);
!isTesSuccess(ret))
return ret;
if (auto const acct = view.read(acctKeylet);
priorBalance < view.fees().accountReserve((*acct)[sfOwnerCount] + 1))
return tecINSUFFICIENT_RESERVE;
auto const offerID = keylet::nftoffer(acctID, seqProxy.value());
@@ -993,13 +979,11 @@ tokenOfferCreateApply(
if (dest)
(*offer)[sfDestination] = *dest;
addSponsorToLedgerEntry(offer, *sponsorSle);
view.insert(offer);
}
// Update owner count.
adjustOwnerCount(view, view.peek(acctKeylet), *sponsorSle, 1, j);
adjustOwnerCount(view, view.peek(acctKeylet), 1, j);
return tesSUCCESS;
}

View File

@@ -55,7 +55,7 @@ offerDelete(ApplyView& view, SLE::ref sle, beast::Journal j)
}
}
adjustOwnerCountObj(view, owner, sle, -1, j);
adjustOwnerCount(view, view.peek(keylet::account(owner)), -1, j);
view.erase(sle);

View File

@@ -51,7 +51,7 @@ closeChannel(SLE::ref slep, ApplyView& view, uint256 const& key, beast::Journal
XRPL_ASSERT(
(*slep)[sfAmount] >= (*slep)[sfBalance], "xrpl::closeChannel : minimum channel amount");
(*sle)[sfBalance] = (*sle)[sfBalance] + (*slep)[sfAmount] - (*slep)[sfBalance];
adjustOwnerCountObj(view, sle, slep, -1, j);
adjustOwnerCount(view, sle, -1, j);
view.update(sle);
// Remove PayChan from ledger

View File

@@ -9,7 +9,6 @@
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/AmountConversions.h>
@@ -22,7 +21,6 @@
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/UintTypes.h>
#include <xrpl/protocol/XRPAmount.h>
@@ -31,7 +29,6 @@
#include <cstdint>
#include <memory>
#include <optional>
#include <utility>
namespace xrpl {
@@ -198,7 +195,6 @@ trustCreate(
// Issuer should be the account being set.
std::uint32_t uQualityIn,
std::uint32_t uQualityOut,
SLE::ref sponsorSle,
beast::Journal j)
{
JLOG(j.trace()) << "trustCreate: " << to_string(uSrcAccountID) << ", "
@@ -285,9 +281,7 @@ trustCreate(
}
sleRippleState->setFieldU32(sfFlags, uFlags);
adjustOwnerCount(view, sleAccount, sponsorSle, 1, j);
addSponsorToLedgerEntry(sleRippleState, sponsorSle, bSetHigh ? sfHighSponsor : sfLowSponsor);
adjustOwnerCount(view, sleAccount, 1, j);
// ONLY: Create ripple balance.
sleRippleState->setFieldAmount(sfBalance, bSetHigh ? -saBalance : saBalance);
@@ -323,9 +317,6 @@ trustDelete(
return tefBAD_LEDGER; // LCOV_EXCL_LINE
}
removeSponsorFromLedgerEntry(sleRippleState, sfHighSponsor);
removeSponsorFromLedgerEntry(sleRippleState, sfLowSponsor);
JLOG(j.trace()) << "trustDelete: Deleting ripple line: state";
view.erase(sleRippleState);
@@ -378,15 +369,11 @@ updateTrustLine(
{
// VFALCO Where is the line being deleted?
// Clear the reserve of the sender, possibly delete the line!
auto const currentSponsor =
getLedgerEntryReserveSponsor(view, state, !bSenderHigh ? sfLowSponsor : sfHighSponsor);
adjustOwnerCount(view, sle, currentSponsor, -1, j);
adjustOwnerCount(view, sle, -1, j);
// Clear reserve flag.
state->clearFlag(senderReserveFlag);
removeSponsorFromLedgerEntry(state, !bSenderHigh ? sfLowSponsor : sfHighSponsor);
// Balance is zero, receiver reserve is clear.
if (!after && !state->isFlag(receiverReserveFlag))
return true;
@@ -485,7 +472,6 @@ issueIOU(
limit,
0,
0,
{},
j);
}
@@ -635,7 +621,6 @@ canTransfer(ReadView const& view, Issue const& issue, AccountID const& from, Acc
TER
addEmptyHolding(
ApplyView& view,
STTx const& tx,
AccountID const& accountID,
XRPAmount priorBalance,
Issue const& issue,
@@ -664,19 +649,9 @@ addEmptyHolding(
if (view.read(index))
return tecDUPLICATE;
SLE::pointer sponsorSle;
if (!isPseudoAccount(sleDst))
{
auto sle = getTxReserveSponsor(view, tx);
if (!sle)
return sle.error(); // LCOV_EXCL_LINE
sponsorSle = std::move(*sle);
}
// Can the account cover the trust line reserve ?
if (auto const ret =
checkInsufficientReserve(view, tx, sleDst, priorBalance, sponsorSle, 1, 0, journal);
!isTesSuccess(ret))
std::uint32_t const ownerCount = sleDst->at(sfOwnerCount);
if (priorBalance < view.fees().accountReserve(ownerCount + 1))
return tecNO_LINE_INSUF_RESERVE;
return trustCreate(
@@ -694,7 +669,6 @@ addEmptyHolding(
/*saLimit=*/STAmount{Issue{currency, dstId}},
/*uQualityIn=*/0,
/*uQualityOut=*/0,
sponsorSle,
journal);
}
@@ -736,14 +710,11 @@ removeEmptyHolding(
if (!sleLowAccount)
return tecINTERNAL; // LCOV_EXCL_LINE
auto const currentLowSponsor = getLedgerEntryReserveSponsor(view, line, sfLowSponsor);
adjustOwnerCount(view, sleLowAccount, currentLowSponsor, -1, journal);
adjustOwnerCount(view, sleLowAccount, -1, journal);
// It's not really necessary to clear the reserve flag, since the line
// is about to be deleted, but this will make the metadata reflect an
// accurate state at the time of deletion.
line->clearFlag(lsfLowReserve);
removeSponsorFromLedgerEntry(line, sfLowSponsor);
}
if (line->isFlag(lsfHighReserve))
@@ -753,14 +724,11 @@ removeEmptyHolding(
if (!sleHighAccount)
return tecINTERNAL; // LCOV_EXCL_LINE
auto const currentHighSponsor = getLedgerEntryReserveSponsor(view, line, sfHighSponsor);
adjustOwnerCount(view, sleHighAccount, currentHighSponsor, -1, journal);
adjustOwnerCount(view, sleHighAccount, -1, journal);
// It's not really necessary to clear the reserve flag, since the line
// is about to be deleted, but this will make the metadata reflect an
// accurate state at the time of deletion.
line->clearFlag(lsfHighReserve);
removeSponsorFromLedgerEntry(line, sfHighSponsor);
}
return trustDelete(
@@ -800,9 +768,6 @@ deleteAMMTrustLine(
if (ammAccountID && (low != *ammAccountID && high != *ammAccountID))
return terNO_AMM;
auto const sponsorSle =
getLedgerEntryReserveSponsor(view, sleState, !ammLow ? sfLowSponsor : sfHighSponsor);
if (auto const ter = trustDelete(view, sleState, low, high, j); !isTesSuccess(ter))
{
JLOG(j.error()) << "deleteAMMTrustLine: failed to delete the trustline.";
@@ -813,7 +778,7 @@ deleteAMMTrustLine(
if (!sleState->isFlag(uFlags))
return tecINTERNAL; // LCOV_EXCL_LINE
adjustOwnerCount(view, !ammLow ? sleLow : sleHigh, sponsorSle, -1, j);
adjustOwnerCount(view, !ammLow ? sleLow : sleHigh, -1, j);
return tesSUCCESS;
}

View File

@@ -6,11 +6,9 @@
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/Concepts.h>
@@ -24,7 +22,6 @@
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/UintTypes.h>
#include <xrpl/protocol/XRPAmount.h>
@@ -38,6 +35,12 @@
namespace xrpl {
// Forward declaration for function that remains in View.h/cpp
bool
isLPTokenFrozen(
ReadView const& view,
AccountID const& account,
Asset const& asset,
Asset const& asset2);
//------------------------------------------------------------------------------
//
@@ -475,7 +478,6 @@ canAddHolding(ReadView const& view, Asset const& asset)
TER
addEmptyHolding(
ApplyView& view,
STTx const& tx,
AccountID const& accountID,
XRPAmount priorBalance,
Asset const& asset,
@@ -483,7 +485,7 @@ addEmptyHolding(
{
return std::visit(
[&]<ValidIssueType TIss>(TIss const& issue) -> TER {
return addEmptyHolding(view, tx, accountID, priorBalance, issue, journal);
return addEmptyHolding(view, accountID, priorBalance, issue, journal);
},
asset.value());
}
@@ -491,21 +493,13 @@ addEmptyHolding(
TER
removeEmptyHolding(
ApplyView& view,
STTx const& tx,
AccountID const& accountID,
Asset const& asset,
beast::Journal journal)
{
return std::visit(
[&]<ValidIssueType TIss>(TIss const& issue) -> TER {
if constexpr (std::is_same_v<TIss, Issue>)
{
return removeEmptyHolding(view, accountID, issue, journal);
}
else
{
return removeEmptyHolding(view, tx, accountID, issue, journal);
}
return removeEmptyHolding(view, accountID, issue, journal);
},
asset.value());
}
@@ -559,7 +553,6 @@ directSendNoFeeIOU(
AccountID const& uReceiverID,
STAmount const& saAmount,
bool bCheckIssuer,
SLE::ref sponsorSle,
beast::Journal j)
{
AccountID const& issuer = saAmount.getIssuer();
@@ -630,12 +623,7 @@ directSendNoFeeIOU(
// Sender quality out is 0.
{
// Clear the reserve of the sender, possibly delete the line!
auto const currentSponsor = getLedgerEntryReserveSponsor(
view, sleRippleState, !bSenderHigh ? sfLowSponsor : sfHighSponsor);
adjustOwnerCount(view, view.peek(keylet::account(uSenderID)), currentSponsor, -1, j);
removeSponsorFromLedgerEntry(
sleRippleState, !bSenderHigh ? sfLowSponsor : sfHighSponsor);
adjustOwnerCount(view, view.peek(keylet::account(uSenderID)), -1, j);
// Clear reserve flag.
sleRippleState->clearFlag(senderReserveFlag);
@@ -698,7 +686,6 @@ directSendNoFeeIOU(
saReceiverLimit,
0,
0,
sponsorSle,
j);
}
@@ -713,7 +700,6 @@ directSendNoLimitIOU(
STAmount const& saAmount,
STAmount& saActual,
beast::Journal j,
SLE::ref sponsorSle,
WaiveTransferFee waiveFee)
{
auto const& issuer = saAmount.getIssuer();
@@ -726,8 +712,7 @@ directSendNoLimitIOU(
if (uSenderID == issuer || uReceiverID == issuer || issuer == noAccount())
{
// Direct send: redeeming IOUs and/or sending own IOUs.
auto const ter =
directSendNoFeeIOU(view, uSenderID, uReceiverID, saAmount, false, sponsorSle, j);
auto const ter = directSendNoFeeIOU(view, uSenderID, uReceiverID, saAmount, false, j);
if (!isTesSuccess(ter))
return ter;
saActual = saAmount;
@@ -745,12 +730,10 @@ directSendNoLimitIOU(
<< to_string(uReceiverID) << " : deliver=" << saAmount.getFullText()
<< " cost=" << saActual.getFullText();
TER terResult = directSendNoFeeIOU(view, issuer, uReceiverID, saAmount, true, sponsorSle, j);
TER terResult = directSendNoFeeIOU(view, issuer, uReceiverID, saAmount, true, j);
if (tesSUCCESS == terResult)
{
terResult = directSendNoFeeIOU(view, uSenderID, issuer, saActual, true, sponsorSle, j);
}
terResult = directSendNoFeeIOU(view, uSenderID, issuer, saActual, true, j);
return terResult;
}
@@ -766,7 +749,6 @@ directSendNoLimitMultiIOU(
MultiplePaymentDestinations const& receivers,
STAmount& actual,
beast::Journal j,
SLE::ref sponsorSle,
WaiveTransferFee waiveFee)
{
auto const& issuer = issue.getIssuer();
@@ -794,8 +776,7 @@ directSendNoLimitMultiIOU(
if (senderID == issuer || receiverID == issuer || issuer == noAccount())
{
// Direct send: redeeming IOUs and/or sending own IOUs.
if (auto const ter =
directSendNoFeeIOU(view, senderID, receiverID, amount, false, sponsorSle, j))
if (auto const ter = directSendNoFeeIOU(view, senderID, receiverID, amount, false, j))
return ter;
actual += amount;
// Do not add amount to takeFromSender, because directSendNoFeeIOU took
@@ -818,15 +799,14 @@ directSendNoLimitMultiIOU(
<< to_string(receiverID) << " : deliver=" << amount.getFullText()
<< " cost=" << actual.getFullText();
if (TER const terResult =
directSendNoFeeIOU(view, issuer, receiverID, amount, true, sponsorSle, j))
if (TER const terResult = directSendNoFeeIOU(view, issuer, receiverID, amount, true, j))
return terResult;
}
if (senderID != issuer && takeFromSender)
{
if (TER const terResult =
directSendNoFeeIOU(view, senderID, issuer, takeFromSender, true, sponsorSle, j))
directSendNoFeeIOU(view, senderID, issuer, takeFromSender, true, j))
return terResult;
}
@@ -840,7 +820,6 @@ accountSendIOU(
AccountID const& uReceiverID,
STAmount const& saAmount,
beast::Journal j,
SLE::ref sponsorSle,
WaiveTransferFee waiveFee)
{
if (view.rules().enabled(fixAMMv1_1))
@@ -872,8 +851,7 @@ accountSendIOU(
JLOG(j.trace()) << "accountSendIOU: " << to_string(uSenderID) << " -> "
<< to_string(uReceiverID) << " : " << saAmount.getFullText();
return directSendNoLimitIOU(
view, uSenderID, uReceiverID, saAmount, saActual, j, sponsorSle, waiveFee);
return directSendNoLimitIOU(view, uSenderID, uReceiverID, saAmount, saActual, j, waiveFee);
}
/* XRP send which does not check reserve and can do pure adjustment.
@@ -959,7 +937,6 @@ accountSendMultiIOU(
Issue const& issue,
MultiplePaymentDestinations const& receivers,
beast::Journal j,
SLE::ref sponsorSle,
WaiveTransferFee waiveFee)
{
XRPL_ASSERT_PARTS(
@@ -971,8 +948,7 @@ accountSendMultiIOU(
JLOG(j.trace()) << "accountSendMultiIOU: " << to_string(senderID) << " sending "
<< receivers.size() << " IOUs";
return directSendNoLimitMultiIOU(
view, senderID, issue, receivers, actual, j, sponsorSle, waiveFee);
return directSendNoLimitMultiIOU(view, senderID, issue, receivers, actual, j, waiveFee);
}
/* XRP send which does not check reserve and can do pure adjustment.
@@ -1404,7 +1380,7 @@ directSendNoFee(
{
return saAmount.asset().visit(
[&](Issue const&) {
return directSendNoFeeIOU(view, uSenderID, uReceiverID, saAmount, bCheckIssuer, {}, j);
return directSendNoFeeIOU(view, uSenderID, uReceiverID, saAmount, bCheckIssuer, j);
},
[&](MPTIssue const&) {
XRPL_ASSERT(!bCheckIssuer, "xrpl::directSendNoFee : not checking issuer");
@@ -1419,13 +1395,12 @@ accountSend(
AccountID const& uReceiverID,
STAmount const& saAmount,
beast::Journal j,
SLE::ref sponsorSle,
WaiveTransferFee waiveFee,
AllowMPTOverflow allowOverflow)
{
return saAmount.asset().visit(
[&](Issue const&) {
return accountSendIOU(view, uSenderID, uReceiverID, saAmount, j, sponsorSle, waiveFee);
return accountSendIOU(view, uSenderID, uReceiverID, saAmount, j, waiveFee);
},
[&](MPTIssue const&) {
return accountSendMPT(
@@ -1440,14 +1415,13 @@ accountSendMulti(
Asset const& asset,
MultiplePaymentDestinations const& receivers,
beast::Journal j,
SLE::ref sponsorSle,
WaiveTransferFee waiveFee)
{
XRPL_ASSERT_PARTS(
receivers.size() > 1, "xrpl::accountSendMulti", "multiple recipients provided");
return asset.visit(
[&](Issue const& issue) {
return accountSendMultiIOU(view, senderID, issue, receivers, j, sponsorSle, waiveFee);
return accountSendMultiIOU(view, senderID, issue, receivers, j, waiveFee);
},
[&](MPTIssue const& issue) {
return accountSendMultiMPT(view, senderID, issue, receivers, j, waiveFee);

View File

@@ -84,7 +84,6 @@ enum class LedgerNameSpace : std::uint16_t {
Vault = 'V',
LoanBroker = 'l', // lower-case L
Loan = 'L',
Sponsorship = '>',
// No longer used or supported. Left here to reserve the space to avoid accidental reuse.
Contract [[deprecated]] = 'c',
@@ -319,12 +318,6 @@ signers(AccountID const& account) noexcept
return signers(account, 0);
}
Keylet
sponsor(AccountID const& sponsor, AccountID const& sponsee) noexcept
{
return {ltSPONSORSHIP, indexHash(LedgerNameSpace::Sponsorship, sponsor, sponsee)};
}
Keylet
check(AccountID const& id, std::uint32_t seq) noexcept
{

View File

@@ -160,14 +160,6 @@ InnerObjectFormats::InnerObjectFormats()
{sfTxnSignature, SoeOptional},
{sfSigners, SoeOptional},
});
add(sfSponsorSignature.jsonName.cStr(),
sfSponsorSignature.getCode(),
{
{sfSigningPubKey, SoeOptional},
{sfTxnSignature, SoeOptional},
{sfSigners, SoeOptional},
});
}
InnerObjectFormats const&

View File

@@ -15,7 +15,6 @@ LedgerFormats::getCommonFields()
{sfLedgerIndex, SoeOptional},
{sfLedgerEntryType, SoeRequired},
{sfFlags, SoeRequired},
{sfSponsor, SoeOptional},
};
return kCommonFields;
}

View File

@@ -279,14 +279,6 @@ STTx::checkSign(Rules const& rules) const
if (auto const ret = checkSign(rules, counterSig); !ret)
return std::unexpected("Counterparty: " + ret.error());
}
if (isFieldPresent(sfSponsorSignature))
{
auto const sponsorSignatureObj = getFieldObject(sfSponsorSignature);
if (auto const ret = checkSign(rules, sponsorSignatureObj); !ret)
return std::unexpected("Sponsor: " + ret.error());
}
return {};
}

View File

@@ -106,7 +106,6 @@ transResults()
MAKE_ERROR(tecLIMIT_EXCEEDED, "Limit exceeded."),
MAKE_ERROR(tecPSEUDO_ACCOUNT, "This operation is not allowed against a pseudo-account."),
MAKE_ERROR(tecPRECISION_LOSS, "The amounts used by the transaction cannot interact."),
MAKE_ERROR(tecNO_SPONSOR_PERMISSION, "Sponsor has not authorized this transaction."),
MAKE_ERROR(tefALREADY, "The exact transaction was already in this ledger."),
MAKE_ERROR(tefBAD_ADD_AUTH, "Not authorized to add account."),
@@ -217,7 +216,6 @@ transResults()
MAKE_ERROR(terADDRESS_COLLISION, "Failed to allocate an unique account address."),
MAKE_ERROR(terNO_DELEGATE_PERMISSION, "Delegated account lacks permission to perform this transaction."),
MAKE_ERROR(terLOCKED, "Fund is locked."),
MAKE_ERROR(terNO_SPONSORSHIP, "No sponsorship found."),
MAKE_ERROR(tesSUCCESS, "The transaction was applied. Only final in a validated ledger."),
};

View File

@@ -30,9 +30,6 @@ TxFormats::getCommonFields()
{sfSigners, SoeOptional}, // submit_multisigned
{sfNetworkID, SoeOptional},
{sfDelegate, SoeOptional},
{sfSponsor, SoeOptional},
{sfSponsorFlags, SoeOptional},
{sfSponsorSignature, SoeOptional},
};
return kCommonFields;
}

View File

@@ -18,7 +18,6 @@
#include <xrpl/ledger/helpers/NFTokenHelpers.h>
#include <xrpl/ledger/helpers/OfferHelpers.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
@@ -42,7 +41,6 @@
#include <xrpl/tx/apply.h>
#include <xrpl/tx/applySteps.h>
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <exception>
@@ -166,62 +164,6 @@ preflightCheckSimulateKeys(ApplyFlags flags, STObject const& sigObject, beast::J
} // namespace detail
static NotTEC
preflight1Sponsor(PreflightContext const& ctx, AccountID const& id)
{
bool const hasSponsor = ctx.tx.isFieldPresent(sfSponsor);
bool const hasSponsorFlags = ctx.tx.isFieldPresent(sfSponsorFlags);
bool const hasSponsorSig = ctx.tx.isFieldPresent(sfSponsorSignature);
if ((hasSponsor || hasSponsorFlags || hasSponsorSig) && !ctx.rules.enabled(featureSponsor))
return temDISABLED;
if (hasSponsorFlags &&
((ctx.tx.getFieldU32(sfSponsorFlags) & ~(spfSponsorFee | spfSponsorReserve)) != 0u))
{
JLOG(ctx.j.debug()) << "preflight1: invalid sponsor flags";
return temINVALID_FLAG;
}
if (!hasSponsor)
{
if (hasSponsorFlags)
{
JLOG(ctx.j.debug()) << "preflight1: sponsor flags without sponsor definition";
return temINVALID_FLAG;
}
if (hasSponsorSig)
{
JLOG(ctx.j.debug()) << "preflight1: sponsor signature without sponsor definition";
return temMALFORMED;
}
}
else if (hasSponsorFlags)
{
auto const sponsorFlags = ctx.tx.getFieldU32(sfSponsorFlags);
if (((sponsorFlags & ~(spfSponsorFee | spfSponsorReserve)) != 0u) || sponsorFlags == 0)
{
JLOG(ctx.j.debug()) << "preflight1: invalid sponsor flags";
return temINVALID_FLAG;
}
}
else
{
JLOG(ctx.j.debug()) << "preflight1: no sponsor flags";
return temINVALID_FLAG;
}
if (hasSponsor && ctx.tx.getAccountID(sfSponsor) == id)
{
JLOG(ctx.j.debug()) << "preflight1: Sponsor account cannot be the "
"same as the transaction originator";
return temMALFORMED;
}
return tesSUCCESS;
}
/** Performs early sanity checks on the account and fee fields */
NotTEC
Transactor::preflight1(PreflightContext const& ctx, std::uint32_t flagMask)
@@ -273,9 +215,6 @@ Transactor::preflight1(PreflightContext const& ctx, std::uint32_t flagMask)
!ctx.rules.enabled(featureBatch),
"Inner batch transaction must have a parent batch ID.");
if (auto const ter = preflight1Sponsor(ctx, id); !isTesSuccess(ter))
return ter;
return tesSUCCESS;
}
@@ -371,40 +310,6 @@ Transactor::checkPermission(ReadView const& view, STTx const& tx)
return checkTxPermission(sle, tx);
}
NotTEC
Transactor::checkSponsor(ReadView const& view, STTx const& tx)
{
if (!tx.isFieldPresent(sfSponsor))
return tesSUCCESS;
if (auto const sponsorSle = getTxReserveSponsor(view, tx); !sponsorSle)
return terNO_ACCOUNT;
auto const hasSponsorSignature = tx.isFieldPresent(sfSponsorSignature);
if (hasSponsorSignature)
return tesSUCCESS;
auto const sponsorshipSle =
view.read(keylet::sponsor(tx.getAccountID(sfSponsor), tx.getAccountID(sfAccount)));
// sponsorship object missing for pre-funded tx
if (!sponsorshipSle)
return terNO_SPONSORSHIP;
auto const sponsorFlags = tx.getFieldU32(sfSponsorFlags);
if (((sponsorFlags & spfSponsorFee) != 0u) &&
sponsorshipSle->isFlag(lsfSponsorshipRequireSignForFee))
return terNO_SPONSORSHIP;
if (((sponsorFlags & spfSponsorReserve) != 0u) &&
sponsorshipSle->isFlag(lsfSponsorshipRequireSignForReserve))
return terNO_SPONSORSHIP;
return tesSUCCESS;
}
XRPAmount
Transactor::calculateBaseFee(ReadView const& view, STTx const& tx)
{
@@ -413,7 +318,6 @@ Transactor::calculateBaseFee(ReadView const& view, STTx const& tx)
// The computation has two parts:
// * The base fee, which is the same for most transactions.
// * The additional cost of each multisignature on the transaction.
// * The additional cost of each multisignature on the sponsor.
XRPAmount const baseFee = view.fees().base;
// Each signer adds one more baseFee to the minimum required fee
@@ -421,15 +325,7 @@ Transactor::calculateBaseFee(ReadView const& view, STTx const& tx)
std::size_t const signerCount =
tx.isFieldPresent(sfSigners) ? tx.getFieldArray(sfSigners).size() : 0;
std::size_t sponsorSignerCount = 0;
if (tx.isFieldPresent(sfSponsorSignature))
{
auto const sponsorObj = tx.getFieldObject(sfSponsorSignature);
sponsorSignerCount +=
sponsorObj.isFieldPresent(sfSigners) ? sponsorObj.getFieldArray(sfSigners).size() : 0;
}
return baseFee + ((signerCount + sponsorSignerCount) * baseFee);
return baseFee + (signerCount * baseFee);
}
// Returns the fee in fee units, not scaled for load.
@@ -499,51 +395,12 @@ Transactor::checkFee(PreclaimContext const& ctx, XRPAmount baseFee)
if (feePaid == beast::kZero)
return tesSUCCESS;
auto const feePayer = getFeePayer(ctx.view, ctx.tx);
auto const payerSle = ctx.view.read(feePayer.entry);
if (!payerSle)
{
if (feePayer.type == FeePayerType::SponsorPreFunded)
{
// Sanity check: already checked in checkSponsor
return tefINTERNAL; // LCOV_EXCL_LINE
}
auto const id = ctx.tx.getFeePayer();
auto const sle = ctx.view.read(keylet::account(id));
if (!sle)
return terNO_ACCOUNT;
}
XRPAmount maxSpendable = beast::kZero;
if (feePayer.type == FeePayerType::SponsorPreFunded)
{
if (payerSle->getType() != ltSPONSORSHIP)
return tefINTERNAL; // LCOV_EXCL_LINE
if (payerSle->isFieldPresent(feePayer.balanceField))
maxSpendable = payerSle->getFieldAmount(feePayer.balanceField).xrp();
if (payerSle->isFieldPresent(sfMaxFee))
{
auto const cap = payerSle->getFieldAmount(sfMaxFee).xrp();
maxSpendable = std::min(maxSpendable, cap);
}
}
else
{
if (payerSle->getType() != ltACCOUNT_ROOT)
return tefINTERNAL; // LCOV_EXCL_LINE
if (feePayer.type == FeePayerType::SponsorCoSigned)
{
STAmount const sponsorReserve = accountReserve(ctx.view, payerSle, ctx.j);
maxSpendable = payerSle->getFieldAmount(sfBalance).xrp() - sponsorReserve.xrp();
}
else
{
maxSpendable = payerSle->getFieldAmount(feePayer.balanceField).xrp();
}
}
auto const balance = (*sle)[sfBalance].xrp();
// NOTE: Because preclaim evaluates against a static readview, it
// does not reflect fee deductions from other transactions paid by
@@ -552,12 +409,12 @@ Transactor::checkFee(PreclaimContext const& ctx, XRPAmount baseFee)
// transactions, this check may pass optimistically.
// The fee shortfall will be handled by the Transactor::reset mechanism,
// which caps the fee to the remaining actual balance.
if (maxSpendable < feePaid)
if (balance < feePaid)
{
JLOG(ctx.j.trace()) << "Insufficient balance:" << " balance=" << to_string(maxSpendable)
JLOG(ctx.j.trace()) << "Insufficient balance:" << " balance=" << to_string(balance)
<< " paid=" << to_string(feePaid);
if ((maxSpendable > beast::kZero) && !ctx.view.open())
if ((balance > beast::kZero) && !ctx.view.open())
{
// Closed ledger, non-zero balance, less than fee
return tecINSUFF_FEE;
@@ -574,27 +431,16 @@ Transactor::payFee()
{
auto const feePaid = ctx_.tx[sfFee].xrp();
auto const feePayer = getFeePayer(view(), ctx_.tx);
auto const sle = view().peek(feePayer.entry);
JLOG(j_.trace()) << "Fee payer: " + to_string(feePayer.entry.key);
auto const feePayer = ctx_.tx.getFeePayer();
auto const sle = view().peek(keylet::account(feePayer));
if (!sle)
return tefINTERNAL; // LCOV_EXCL_LINE
auto const feeAmountAfter = sle->getFieldAmount(feePayer.balanceField) - feePaid;
if (feeAmountAfter == beast::kZero && feePayer.balanceField == sfFeeAmount)
{
// Because ltSponsorship.sfFeeAmount is soeOptional
sle->makeFieldAbsent(feePayer.balanceField);
}
else
{
sle->setFieldAmount(feePayer.balanceField, feeAmountAfter);
}
view().update(sle);
// Deduct the fee, so it's not available during the transaction.
// Will only write the account back if the transaction succeeds.
sle->setFieldAmount(sfBalance, sle->getFieldAmount(sfBalance) - feePaid);
if (feePayer != accountID_)
view().update(sle); // done in `apply()` for the account
// VFALCO Should we call view().rawDestroyXRP() here as well?
return tesSUCCESS;
@@ -768,7 +614,7 @@ Transactor::ticketDelete(
}
// Update the Ticket owner's reserve.
adjustOwnerCountObj(view, sleAccount, sleTicket, -1, j);
adjustOwnerCount(view, sleAccount, -1, j);
// Remove Ticket from ledger.
view.erase(sleTicket);
@@ -859,22 +705,6 @@ Transactor::checkSign(
return tesSUCCESS;
}
if (sigObject.isFieldPresent(sfSponsorSignature))
{
// Co-signed sponsorship
// Sanity check: already checked in preflight1
if (!sigObject.isFieldPresent(sfSponsor))
return tefINTERNAL; // LCOV_EXCL_LINE
auto const sponsorAccountID = sigObject.getAccountID(sfSponsor);
auto const sponsorSignature = sigObject.getFieldObject(sfSponsorSignature);
if (auto const ret =
checkSign(view, flags, std::nullopt, sponsorAccountID, sponsorSignature, j);
!isTesSuccess(ret))
return ret;
}
// If the pk is empty and not simulate or simulate and signers,
// then we must be multi-signing.
if (sigObject.isFieldPresent(sfSigners))
@@ -1259,19 +1089,11 @@ Transactor::reset(XRPAmount fee)
if (!txnAcct)
return {tefINTERNAL, beast::kZero};
auto const feePayer = getFeePayer(view(), ctx_.tx);
auto const payerSle = view().peek(feePayer.entry);
auto const payerSle = view().peek(keylet::account(ctx_.tx.getFeePayer()));
if (!payerSle)
return {tefINTERNAL, beast::kZero}; // LCOV_EXCL_LINE
auto const balance = payerSle->getFieldAmount(feePayer.balanceField).xrp();
if (feePayer.type == FeePayerType::SponsorPreFunded && payerSle->isFieldPresent(sfMaxFee))
{
auto const cap = payerSle->getFieldAmount(sfMaxFee).xrp();
fee = std::min(fee, cap);
}
auto const balance = payerSle->getFieldAmount(sfBalance).xrp();
// balance should have already been checked in checkFee / preFlight.
XRPL_ASSERT(
@@ -1290,17 +1112,7 @@ Transactor::reset(XRPAmount fee)
// If for some reason we are unable to consume the ticket or sequence
// then the ledger is corrupted. Rather than make things worse we
// reject the transaction.
auto const feeAmountAfter = balance - fee;
if (feeAmountAfter == beast::kZero && feePayer.balanceField == sfFeeAmount)
{
// Because ltSponsorship.sfFeeAmount is soeOptional
payerSle->makeFieldAbsent(feePayer.balanceField);
}
else
{
payerSle->setFieldAmount(feePayer.balanceField, feeAmountAfter);
}
payerSle->setFieldAmount(sfBalance, balance - fee);
TER const ter{consumeSeqProxy(txnAcct)};
XRPL_ASSERT(isTesSuccess(ter), "xrpl::Transactor::reset : result is tesSUCCESS");
@@ -1314,40 +1126,6 @@ Transactor::reset(XRPAmount fee)
return {ter, fee};
}
FeePayer
Transactor::getFeePayer(ReadView const& view, STTx const& tx)
{
if (tx.isFieldPresent(sfSponsor) && ((tx.getFieldU32(sfSponsorFlags) & spfSponsorFee) != 0u))
{
auto const sponsorAccountID = tx.getAccountID(sfSponsor);
auto const sponseeAccountID = tx.getAccountID(sfAccount);
auto const hasSponsorSignature = tx.isFieldPresent(sfSponsorSignature);
auto const sponsorshipKeylet = keylet::sponsor(sponsorAccountID, sponseeAccountID);
// if pre-funded sponsorship exists, prefer it
if (hasSponsorSignature && !view.exists(sponsorshipKeylet))
{
// co-signed
return FeePayer{
.entry = keylet::account(sponsorAccountID),
.balanceField = sfBalance,
.type = FeePayerType::SponsorCoSigned};
}
// pre funded
return FeePayer{
.entry = sponsorshipKeylet,
.balanceField = sfFeeAmount,
.type = FeePayerType::SponsorPreFunded};
}
auto const payerAccountKeylet = keylet::account(tx.getFeePayer());
auto const payerType =
tx.isFieldPresent(sfDelegate) ? FeePayerType::Delegate : FeePayerType::Account;
return FeePayer{.entry = payerAccountKeylet, .balanceField = sfBalance, .type = payerType};
}
// The sole purpose of this function is to provide a convenient, named
// location to set a breakpoint, to be used when replaying transactions.
void

View File

@@ -181,9 +181,6 @@ invokePreclaim(PreclaimContext const& ctx)
if (NotTEC const result = T::checkPriorTxAndLastLedger(ctx))
return result;
if (NotTEC const result = T::checkSponsor(ctx.view, ctx.tx))
return result;
if (NotTEC const result = T::checkPermission(ctx.view, ctx.tx))
return result;

View File

@@ -130,10 +130,6 @@ XRPNotCreated::visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref a
if (isXRP((*before)[sfAmount]))
drops_ -= (*before)[sfAmount].xrp().drops();
break;
case ltSPONSORSHIP:
if (before->isFieldPresent(sfFeeAmount))
drops_ -= (*before)[sfFeeAmount].xrp().drops();
break;
default:
break;
}
@@ -154,10 +150,6 @@ XRPNotCreated::visitEntry(bool isDelete, SLE::const_ref before, SLE::const_ref a
if (!isDelete && isXRP((*after)[sfAmount]))
drops_ += (*after)[sfAmount].xrp().drops();
break;
case ltSPONSORSHIP:
if (!isDelete && after->isFieldPresent(sfFeeAmount))
drops_ += (*after)[sfFeeAmount].xrp().drops();
break;
default:
break;
}
@@ -511,20 +503,6 @@ AccountRootsDeletedClean::finalize(
if (enforce)
return false;
}
// An account should not be deleted with sponsorship fields
if (after->isFieldPresent(sfSponsoredOwnerCount) ||
after->isFieldPresent(sfSponsoringOwnerCount) ||
after->isFieldPresent(sfSponsoringAccountCount) || after->isFieldPresent(sfSponsor))
{
JLOG(j.fatal()) << "Invariant failed: account deletion left "
"behind a sponsorship field";
XRPL_ASSERT(
enforce,
"xrpl::AccountRootsDeletedClean::finalize : "
"deleted account has no sponsorship fields");
if (enforce)
return false;
}
// Simple types
for (auto const& [keyletfunc, _1, _2] : kDirectAccountKeylets)
{
@@ -891,10 +869,8 @@ ValidPseudoAccounts::visitEntry(bool isDelete, SLE::const_ref before, SLE::const
// 1. Exactly one of the pseudo-account fields is set.
// 2. The sequence number is not changed.
// 3. The lsfDisableMaster, lsfDefaultRipple, and lsfDepositAuth
// flags are set.
// flags are set.
// 4. The RegularKey is not set.
// 5. The SponsoredOwnerCount, SponsoringOwnerCount, SponsoringAccountCount, Sponsor
// fields are not set.
{
std::vector<SField const*> const& fields = getPseudoAccountFields();
@@ -921,12 +897,6 @@ ValidPseudoAccounts::visitEntry(bool isDelete, SLE::const_ref before, SLE::const
{
errors_.emplace_back("pseudo-account has a regular key");
}
if (after->isFieldPresent(sfSponsoredOwnerCount) ||
after->isFieldPresent(sfSponsoringOwnerCount) || after->isFieldPresent(sfSponsor) ||
after->isFieldPresent(sfSponsoringAccountCount))
{
errors_.emplace_back("pseudo-account has a sponsorship field");
}
}
}
}

View File

@@ -150,18 +150,6 @@ ValidPermissionedDomain::finalize(
}
return true;
}
case ttSPONSORSHIP_TRANSFER: {
if (sleStatus_.empty())
return true;
if (sleStatus_[0].isDelete)
{
JLOG(j.fatal()) << "Invariant failed: domain object "
"deleted by SponsorshipTransfer";
return false;
}
return check(sleStatus_[0], j);
}
default: {
if (!sleStatus_.empty())
{

View File

@@ -1,174 +0,0 @@
#include <xrpl/tx/invariants/SponsorshipInvariant.h>
//
#include <xrpl/basics/Log.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STArray.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/transactors/oracle/OracleSet.h>
#include <cstdint>
#include <memory>
namespace xrpl {
// Add new sponsorship-related invariants implementations
void
SponsorshipOwnerCountsMatch::visitEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after)
{
auto getSponsored = [](std::shared_ptr<SLE const> const& sle) -> std::uint32_t {
if (sle && sle->getType() == ltACCOUNT_ROOT)
return sle->getFieldU32(sfSponsoredOwnerCount);
return 0;
};
auto getSponsoring = [](std::shared_ptr<SLE const> const& sle) -> std::uint32_t {
if (sle && sle->getType() == ltACCOUNT_ROOT)
return sle->getFieldU32(sfSponsoringOwnerCount);
return 0;
};
auto getOwnerCount = [](std::shared_ptr<SLE const> const& sle) -> std::uint32_t {
if (sle && sle->getType() == ltACCOUNT_ROOT)
return sle->getFieldU32(sfOwnerCount);
return 0;
};
auto getSponsoredObjectOwnerCount =
[&](std::shared_ptr<SLE const> const& sle) -> std::uint32_t {
if (!sle)
return 0;
switch (sle->getType())
{
case ltACCOUNT_ROOT: {
return 0;
}
case ltRIPPLE_STATE: {
uint32_t ownerCount = 0;
if (sle->isFieldPresent(sfHighSponsor))
ownerCount++;
if (sle->isFieldPresent(sfLowSponsor))
ownerCount++;
return ownerCount;
}
case ltORACLE: {
if (!sle->isFieldPresent(sfSponsor))
return 0;
auto const priceDataSeries = sle->getFieldArray(sfPriceDataSeries);
return OracleSet::calculateOracleReserve(priceDataSeries.size());
}
case ltVAULT: {
if (!sle->isFieldPresent(sfSponsor))
return 0;
return 2;
}
default: {
if (sle->isFieldPresent(sfSponsor))
return 1;
return 0;
}
}
};
std::int64_t const beforeSponsored = getSponsored(before);
std::int64_t const afterSponsored = getSponsored(after);
std::int64_t const beforeSponsoring = getSponsoring(before);
std::int64_t const afterSponsoring = getSponsoring(after);
std::int64_t const beforeSponsoredObjectOwnerCount = getSponsoredObjectOwnerCount(before);
std::int64_t const afterSponsoredObjectOwnerCount =
isDelete ? 0 : getSponsoredObjectOwnerCount(after);
deltaSponsoredOwnerCount_ += (afterSponsored - beforeSponsored);
deltaSponsoringOwnerCount_ += (afterSponsoring - beforeSponsoring);
deltaSponsoredObjectOwnerCount_ +=
(afterSponsoredObjectOwnerCount - beforeSponsoredObjectOwnerCount);
if (getOwnerCount(after) < getSponsored(after))
invalidOwnerCountLessThanSponsoredOwnerCount_ += 1;
}
bool
SponsorshipOwnerCountsMatch::finalize(
STTx const&,
TER const,
XRPAmount const,
ReadView const&,
beast::Journal const& j) const
{
if (deltaSponsoredOwnerCount_ != deltaSponsoringOwnerCount_)
{
JLOG(j.fatal()) << "Invariant failed: SponsoredOwnerCount does not "
"equal SponsoringOwnerCount delta.";
return false;
}
if (deltaSponsoredObjectOwnerCount_ != deltaSponsoredOwnerCount_)
{
JLOG(j.fatal()) << "Invariant failed: SponsoredObjectOwnerCount does not "
"equal SponsoredOwnerCount delta.";
return false;
}
if (invalidOwnerCountLessThanSponsoredOwnerCount_ > 0)
{
JLOG(j.fatal())
<< "Invariant failed: OwnerCount must be greater than or equal to SponsoredOwnerCount.";
return false;
}
return true;
}
void
SponsorshipAccountCountMatchesField::visitEntry(
bool,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after)
{
auto getSponsoringAccountCount = [](std::shared_ptr<SLE const> const& sle) -> std::uint32_t {
if (sle && sle->getType() == ltACCOUNT_ROOT)
return sle->getFieldU32(sfSponsoringAccountCount);
return 0;
};
auto hasSponsorField = [](std::shared_ptr<SLE const> const& sle) -> bool {
return sle && sle->getType() == ltACCOUNT_ROOT && sle->isFieldPresent(sfSponsor);
};
std::int64_t const beforeCount = getSponsoringAccountCount(before);
std::int64_t const afterCount = getSponsoringAccountCount(after);
deltaSponsoringAccountCount_ += (afterCount - beforeCount);
int const beforePresent = hasSponsorField(before) ? 1 : 0;
int const afterPresent = hasSponsorField(after) ? 1 : 0;
deltaSponsorFieldPresence_ += (afterPresent - beforePresent);
}
bool
SponsorshipAccountCountMatchesField::finalize(
STTx const&,
TER const,
XRPAmount const,
ReadView const&,
beast::Journal const& j) const
{
if (deltaSponsoringAccountCount_ != deltaSponsorFieldPresence_)
{
JLOG(j.fatal()) << "Invariant failed: Net delta of SponsoringAccountCount does not "
"match net delta of sfSponsor presence.";
return false;
}
return true;
}
} // namespace xrpl

View File

@@ -731,7 +731,7 @@ BookStep<TIn, TOut, TDerived>::forEachOffer(
// Create MPToken for the offer's owner. No need to check
// for the reserve since the offer is removed if it is consumed.
// Therefore, the owner count remains the same.
if (auto const err = checkCreateMPT(sb, assetIn.get<MPTIssue>(), owner, {}, j_);
if (auto const err = checkCreateMPT(sb, assetIn.get<MPTIssue>(), owner, j_);
!isTesSuccess(err))
{
return true;

View File

@@ -410,8 +410,7 @@ MPTEndpointOfferCrossingStep::checkCreateMPT(ApplyView& view, xrpl::DebtDirectio
// for the reserve since the offer doesn't go on the books
// if crossed. Insufficient reserve is allowed if the offer
// crossed. See CreateOffer::applyGuts() for reserve check.
if (auto const err = xrpl::checkCreateMPT(view, mptIssue_, dst_, {}, j_);
!isTesSuccess(err))
if (auto const err = xrpl::checkCreateMPT(view, mptIssue_, dst_, j_); !isTesSuccess(err))
{
JLOG(j_.trace()) << "MPTEndpointStep::checkCreateMPT: failed create MPT";
resetCache(srcDebtDir);

View File

@@ -1,391 +0,0 @@
#include <xrpl/tx/transactors/sponsor/SponsorshipSet.h>
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/DelegateHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/Permissions.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/protocol/TxFormats.h>
#include <xrpl/tx/Transactor.h>
#include <cstdint>
#include <memory>
#include <unordered_set>
namespace xrpl {
std::uint32_t
SponsorshipSet::getFlagsMask(PreflightContext const& ctx)
{
return tfSponsorshipSetMask;
}
NotTEC
SponsorshipSet::preflight(PreflightContext const& ctx)
{
if (ctx.tx.isFlag(tfSponsorshipSetRequireSignForFee) &&
ctx.tx.isFlag(tfSponsorshipClearRequireSignForFee))
return temINVALID_FLAG;
if (ctx.tx.isFlag(tfSponsorshipSetRequireSignForReserve) &&
ctx.tx.isFlag(tfSponsorshipClearRequireSignForReserve))
return temINVALID_FLAG;
auto const account = ctx.tx.getAccountID(sfAccount);
bool const hasSponsor = ctx.tx.isFieldPresent(sfCounterpartySponsor);
bool const hasSponsee = ctx.tx.isFieldPresent(sfSponsee);
// The transaction must specify either Sponsor or Sponsee, but not both.
if (hasSponsor == hasSponsee)
return temMALFORMED;
auto const sponsorAccountID = ctx.tx[~sfCounterpartySponsor].value_or(account);
auto const sponseeAccountID = ctx.tx[~sfSponsee].value_or(account);
if (sponsorAccountID == sponseeAccountID)
return temMALFORMED;
if (ctx.tx.isFlag(tfDeleteObject))
{
// can not combine with any modification flags when deleting
constexpr std::uint32_t kModifyFlags = tfSponsorshipSetRequireSignForFee |
tfSponsorshipSetRequireSignForReserve | tfSponsorshipClearRequireSignForFee |
tfSponsorshipClearRequireSignForReserve;
if ((ctx.tx.getFlags() & kModifyFlags) != 0u)
return temINVALID_FLAG;
// can not include these fields when deleting
if (ctx.tx.isFieldPresent(sfFeeAmount) || ctx.tx.isFieldPresent(sfReserveCount) ||
ctx.tx.isFieldPresent(sfMaxFee))
return temMALFORMED;
}
else
{
// although both Sponsor and Sponsee can delete,
// only the Sponsor can create or update sponsorship.
if (account != sponsorAccountID)
return temMALFORMED;
// Check FeeAmount and MaxFee
auto const checkOptionalAmountField = [&](SField const& field) -> NotTEC {
if (!ctx.tx.isFieldPresent(field))
return tesSUCCESS;
auto const amount = ctx.tx.getFieldAmount(field);
if (!isXRP(amount))
return temBAD_AMOUNT;
if (amount.xrp() < XRPAmount{0})
return temBAD_AMOUNT;
return tesSUCCESS;
};
if (auto const ret = checkOptionalAmountField(sfFeeAmount); !isTesSuccess(ret))
return ret;
if (auto const ret = checkOptionalAmountField(sfMaxFee); !isTesSuccess(ret))
return ret;
}
return tesSUCCESS;
}
NotTEC
SponsorshipSet::checkPermission(ReadView const& view, STTx const& tx)
{
auto const delegate = tx[~sfDelegate];
if (!delegate)
return tesSUCCESS;
auto const delegateKey = keylet::delegate(tx[sfAccount], *delegate);
auto const sle = view.read(delegateKey);
if (!sle)
return terNO_DELEGATE_PERMISSION;
if (checkTxPermission(sle, tx) == tesSUCCESS)
return tesSUCCESS;
auto const txFlags = tx.getFlags();
// this is added in case more flags will be added for SponsorshipSet
// in the future. Currently unreachable.
if ((txFlags & tfSponsorshipSetPermissionMask) != 0u)
return terNO_DELEGATE_PERMISSION;
std::unordered_set<GranularPermissionType> granularPermissions;
loadGranularPermission(sle, ttSPONSORSHIP_SET, granularPermissions);
auto const sponsoringFee = tx.isFieldPresent(sfFeeAmount) || tx.isFieldPresent(sfMaxFee) ||
((txFlags & (tfSponsorshipSetRequireSignForFee | tfSponsorshipClearRequireSignForFee)) !=
0u);
auto const sponsoringReserve = tx.isFieldPresent(sfReserveCount) ||
((txFlags &
(tfSponsorshipSetRequireSignForReserve | tfSponsorshipClearRequireSignForReserve)) != 0u);
if (sponsoringFee && !granularPermissions.contains(SponsorFee))
return terNO_DELEGATE_PERMISSION;
if (sponsoringReserve && !granularPermissions.contains(SponsorReserve))
return terNO_DELEGATE_PERMISSION;
return tesSUCCESS;
}
TER
SponsorshipSet::preclaim(PreclaimContext const& ctx)
{
auto const sponsorAccountID = ctx.tx[~sfCounterpartySponsor].value_or(ctx.tx[sfAccount]);
auto const sponseeAccountID = ctx.tx[~sfSponsee].value_or(ctx.tx[sfAccount]);
if (sponseeAccountID == sponsorAccountID)
return tecINTERNAL; // LCOV_EXCL_LINE
// check Sponsor
auto const sponsorAccSle = ctx.view.read(keylet::account(sponsorAccountID));
if (!sponsorAccSle)
return tecNO_DST;
// check Sponsee
auto const sponseeSle = ctx.view.read(keylet::account(sponseeAccountID));
if (!sponseeSle)
return tecNO_DST;
// Pseudo accounts cannot be sponsors or sponsees
if (isPseudoAccount(sponsorAccSle) || isPseudoAccount(sponseeSle))
return tecNO_PERMISSION;
// check if object exists
auto const sponsorObjSle = ctx.view.read(keylet::sponsor(sponsorAccountID, sponseeAccountID));
if (ctx.tx.isFlag(tfDeleteObject) && !sponsorObjSle)
return tecNO_ENTRY;
return tesSUCCESS;
}
TER
SponsorshipSet::doApply()
{
auto const sponsorAccountID = ctx_.tx[~sfCounterpartySponsor].value_or(accountID_);
auto const sponseeAccountID = ctx_.tx[~sfSponsee].value_or(accountID_);
if (sponseeAccountID == sponsorAccountID)
return tecINTERNAL; // LCOV_EXCL_LINE
auto const sponsorAccSle = ctx_.view().peek(keylet::account(sponsorAccountID));
if (!sponsorAccSle)
return tecINTERNAL; // LCOV_EXCL_LINE
if (!ctx_.view().exists(keylet::account(sponseeAccountID)))
return tecINTERNAL; // LCOV_EXCL_LINE
auto const sponsorKeylet = keylet::sponsor(sponsorAccountID, sponseeAccountID);
auto const sponsorObjSle = ctx_.view().peek(sponsorKeylet);
if (ctx_.tx.isFlag(tfDeleteObject))
{
// Delete
if (!sponsorObjSle)
return tecINTERNAL; // LCOV_EXCL_LINE
adjustOwnerCountObj(ctx_.view(), sponsorAccSle, sponsorObjSle, -1, ctx_.journal);
ctx_.view().dirRemove(
keylet::ownerDir(sponsorAccountID),
(*sponsorObjSle)[sfOwnerNode],
sponsorObjSle->key(),
false);
ctx_.view().dirRemove(
keylet::ownerDir(sponseeAccountID),
(*sponsorObjSle)[sfSponseeNode],
sponsorObjSle->key(),
false);
// transfer feeAmount from ledger entry
if (sponsorObjSle->isFieldPresent(sfFeeAmount))
{
auto const feeAmount = sponsorObjSle->getFieldAmount(sfFeeAmount);
(*sponsorAccSle)[sfBalance] += feeAmount;
}
ctx_.view().erase(sponsorObjSle);
return tesSUCCESS;
}
auto const feeAmount = ctx_.tx[~sfFeeAmount];
auto const maxFee = ctx_.tx[~sfMaxFee];
auto const reserveCount = ctx_.tx[~sfReserveCount];
auto reserveSponsorAccSle = getTxReserveSponsor(view(), ctx_.tx);
if (!reserveSponsorAccSle)
return reserveSponsorAccSle.error(); // LCOV_EXCL_LINE
if (!sponsorObjSle)
{
// Create
auto newSle = std::make_shared<SLE>(sponsorKeylet);
(*newSle)[sfOwner] = sponsorAccountID;
(*newSle)[sfSponsee] = sponseeAccountID;
if (feeAmount && (*feeAmount).xrp() > (*sponsorAccSle)[sfBalance])
return tecUNFUNDED;
if (feeAmount && *feeAmount > XRPAmount(0))
{
(*sponsorAccSle)[sfBalance] -= *feeAmount;
(*newSle)[sfFeeAmount] = *feeAmount;
}
if (auto const ret = checkInsufficientReserve(
ctx_.view(),
ctx_.tx,
sponsorAccSle,
STAmount{(*sponsorAccSle)[sfBalance]}.xrp(),
*reserveSponsorAccSle,
1,
0,
ctx_.journal);
!isTesSuccess(ret))
return tecUNFUNDED;
if (maxFee && *maxFee > XRPAmount(0))
(*newSle)[sfMaxFee] = *maxFee;
if (reserveCount && *reserveCount > 0)
(*newSle)[sfReserveCount] = *reserveCount;
auto flags = 0;
if (ctx_.tx.isFlag(tfSponsorshipSetRequireSignForFee))
flags |= lsfSponsorshipRequireSignForFee;
if (ctx_.tx.isFlag(tfSponsorshipSetRequireSignForReserve))
flags |= lsfSponsorshipRequireSignForReserve;
(*newSle)[sfFlags] = flags;
auto const sponsorPage = view().dirInsert(
keylet::ownerDir(sponsorAccountID), sponsorKeylet, describeOwnerDir(sponsorAccountID));
if (!sponsorPage)
return tecDIR_FULL; // LCOV_EXCL_LINE
(*newSle)[sfOwnerNode] = *sponsorPage;
auto const sponseePage = view().dirInsert(
keylet::ownerDir(sponseeAccountID), sponsorKeylet, describeOwnerDir(sponseeAccountID));
if (!sponseePage)
return tecDIR_FULL; // LCOV_EXCL_LINE
(*newSle)[sfSponseeNode] = *sponseePage;
// NOLINTNEXTLINE(readability-suspicious-call-argument)
adjustOwnerCount(view(), sponsorAccSle, *reserveSponsorAccSle, 1, ctx_.journal);
addSponsorToLedgerEntry(newSle, *reserveSponsorAccSle);
ctx_.view().insert(newSle);
return tesSUCCESS;
}
// Update
if (feeAmount)
{
auto const currentFeeAmount = (*sponsorObjSle)[~sfFeeAmount].valueOr(XRPAmount(0));
auto feeAmountDelta = XRPAmount(*feeAmount - currentFeeAmount);
if (feeAmountDelta > beast::kZero && feeAmountDelta > (*sponsorAccSle)[sfBalance])
return tecUNFUNDED;
// transfer feeAmount to ledger entry
if (feeAmountDelta != beast::kZero)
{
(*sponsorAccSle)[sfBalance] -= feeAmountDelta;
if (*feeAmount == XRPAmount(0))
{
(*sponsorObjSle).makeFieldAbsent(sfFeeAmount);
}
else
{
(*sponsorObjSle).setFieldAmount(sfFeeAmount, *feeAmount);
}
if (auto const ret = checkInsufficientReserve(
ctx_.view(),
ctx_.tx,
sponsorAccSle,
STAmount{(*sponsorAccSle)[sfBalance]}.xrp(),
*reserveSponsorAccSle,
0,
0,
ctx_.journal);
!isTesSuccess(ret))
return tecUNFUNDED;
}
}
if (maxFee)
{
if (*maxFee == XRPAmount(0))
{
(*sponsorObjSle).makeFieldAbsent(sfMaxFee);
}
else
{
(*sponsorObjSle)[sfMaxFee] = *maxFee;
}
}
if (reserveCount)
sponsorObjSle->at(sfReserveCount) = *reserveCount;
// update Flags
auto flags = sponsorObjSle->getFieldU32(sfFlags);
if (ctx_.tx.isFlag(tfSponsorshipSetRequireSignForFee))
flags |= lsfSponsorshipRequireSignForFee;
if (ctx_.tx.isFlag(tfSponsorshipClearRequireSignForFee))
flags &= ~lsfSponsorshipRequireSignForFee;
if (ctx_.tx.isFlag(tfSponsorshipSetRequireSignForReserve))
flags |= lsfSponsorshipRequireSignForReserve;
if (ctx_.tx.isFlag(tfSponsorshipClearRequireSignForReserve))
flags &= ~lsfSponsorshipRequireSignForReserve;
if (flags != (*sponsorObjSle)[sfFlags])
(*sponsorObjSle)[sfFlags] = flags;
view().update(sponsorObjSle);
return tesSUCCESS;
}
void
SponsorshipSet::visitInvariantEntry(
bool,
std::shared_ptr<SLE const> const&,
std::shared_ptr<SLE const> const&)
{
}
bool
SponsorshipSet::finalizeInvariants(
STTx const&,
TER,
XRPAmount,
ReadView const&,
beast::Journal const&)
{
return true;
}
} // namespace xrpl

View File

@@ -1,608 +0,0 @@
#include <xrpl/tx/transactors/sponsor/SponsorshipTransfer.h>
#include <xrpl/basics/Log.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/Transactor.h>
#include <xrpl/tx/transactors/oracle/OracleSet.h>
#include <bit>
#include <cstdint>
#include <memory>
#include <optional>
namespace xrpl {
std::uint32_t
SponsorshipTransfer::getFlagsMask(PreflightContext const& ctx)
{
return tfSponsorshipTransferMask;
}
NotTEC
SponsorshipTransfer::preflight(PreflightContext const& ctx)
{
auto const flags = ctx.tx.getFlags();
auto const flagsSet = flags & ~(tfSponsorshipTransferMask | tfUniversal);
if (std::popcount(flagsSet) != 1)
{
JLOG(ctx.j.debug()) << "preflight: Only one SponsorshipTransfer flag can be set per tx.";
return temINVALID_FLAG;
}
if (ctx.tx.isFlag(tfSponsorshipCreate))
{
if (!isReserveSponsored(ctx.tx))
{
JLOG(ctx.j.debug())
<< "preflight: spfSponsorReserve should be set when creating sponsorship";
return temINVALID_FLAG;
}
if (ctx.tx.isFieldPresent(sfSponsee))
{
JLOG(ctx.j.debug())
<< "preflight: sfSponsee should be available only when ending sponsorship";
return temMALFORMED;
}
}
if (ctx.tx.isFlag(tfSponsorshipReassign))
{
if (!isReserveSponsored(ctx.tx))
{
JLOG(ctx.j.debug())
<< "preflight: spfSponsorReserve should be set when reassigning sponsorship";
return temINVALID_FLAG;
}
if (ctx.tx.isFieldPresent(sfSponsee))
{
JLOG(ctx.j.debug())
<< "preflight: sfSponsee should not be set when reassigning sponsorship";
return temMALFORMED;
}
}
if (ctx.tx.isFlag(tfSponsorshipEnd))
{
if (isReserveSponsored(ctx.tx))
{
JLOG(ctx.j.debug())
<< "preflight: spfSponsorReserve should not be set when ending sponsorship";
return temINVALID_FLAG;
}
if (ctx.tx.isFieldPresent(sfSponsee))
{
if (ctx.tx.getAccountID(sfSponsee) == ctx.tx.getAccountID(sfAccount))
{
JLOG(ctx.j.debug()) << "preflight: sfSponsee should not be the same as the account";
return temMALFORMED;
}
}
}
// When an account sponsoring, sfSponsorSignature must be provided
auto const newSponsor = getTxReserveSponsorAccountID(ctx.tx);
bool const isObjectSponsor = ctx.tx.isFieldPresent(sfObjectID);
// both sfSponsor and sfObjectID are provided
bool const isNewAccountSponsor = newSponsor && !isObjectSponsor;
if (isNewAccountSponsor && !ctx.tx.isFieldPresent(sfSponsorSignature))
{
JLOG(ctx.j.debug()) << "preflight: sponsoring an account needs co-signing sponsor";
return temMALFORMED;
}
return tesSUCCESS;
}
template <typename T>
inline std::optional<AccountID>
getLedgerEntryOwner(ReadView const& view, T const& sle, AccountID const& account)
{
switch (sle->getType())
{
case ltNFTOKEN_OFFER:
case ltORACLE:
case ltPERMISSIONED_DOMAIN:
case ltVAULT:
case ltLOAN_BROKER:
return sle->getAccountID(sfOwner);
case ltCHECK:
case ltDID:
case ltTICKET:
case ltOFFER:
case ltXCHAIN_OWNED_CLAIM_ID:
case ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID:
case ltESCROW:
case ltPAYCHAN:
case ltMPTOKEN:
case ltDELEGATE:
case ltBRIDGE:
case ltDEPOSIT_PREAUTH:
return sle->getAccountID(sfAccount);
case ltMPTOKEN_ISSUANCE:
return sle->getAccountID(sfIssuer);
case ltLOAN:
return sle->getAccountID(sfBorrower);
case ltSIGNER_LIST: {
auto const signerList = view.read(keylet::signers(account));
if (!signerList)
return std::nullopt;
if (signerList->key() == sle->key())
return account;
return std::nullopt;
}
case ltCREDENTIAL: {
if (sle->isFlag(lsfAccepted))
return sle->getAccountID(sfSubject);
return sle->getAccountID(sfIssuer);
}
case ltNFTOKEN_PAGE: {
// the upper 20 bytes of the index of ltNFTokenPage are the Owner's
// AccountID
uint256 const& key = sle->key();
return AccountID::fromVoid(key.data());
}
case ltRIPPLE_STATE: {
if (sle->isFlag(lsfHighReserve))
{
auto const highAccount = sle->getFieldAmount(sfHighLimit).getIssuer();
if (highAccount == account)
return highAccount;
}
if (sle->isFlag(lsfLowReserve))
{
auto const lowAccount = sle->getFieldAmount(sfLowLimit).getIssuer();
if (lowAccount == account)
return lowAccount;
}
return std::nullopt;
}
case ltACCOUNT_ROOT: {
// AccountRoot is not supported for object sponsorship
return std::nullopt;
}
case ltNEGATIVE_UNL:
case ltDIR_NODE:
case ltAMENDMENTS:
case ltLEDGER_HASHES:
case ltFEE_SETTINGS:
case ltAMM:
return std::nullopt;
default:
return std::nullopt;
};
}
template <typename T>
inline std::uint32_t
getLedgerEntryOwnerCount(T const& sle)
{
switch (sle->getType())
{
case ltORACLE: {
return OracleSet::calculateOracleReserve(sle->getFieldArray(sfPriceDataSeries).size());
}
default:
return 1;
}
};
template <typename T>
inline SF_ACCOUNT const&
getLedgerEntrySponsorField(T const& sle, AccountID const& owner)
{
switch (sle->getType())
{
case ltRIPPLE_STATE: {
if (sle->isFlag(lsfHighReserve))
{
auto const highAccount = sle->getFieldAmount(sfHighLimit).getIssuer();
if (highAccount == owner)
return sfHighSponsor;
}
if (sle->isFlag(lsfLowReserve))
{
auto const lowAccount = sle->getFieldAmount(sfLowLimit).getIssuer();
if (lowAccount == owner)
return sfLowSponsor;
}
// LCOV_EXCL_START
UNREACHABLE("Should not happen. Owner should be checked before calling this function.");
// LCOV_EXCL_STOP
}
default:
return sfSponsor;
}
};
TER
SponsorshipTransfer::preclaim(PreclaimContext const& ctx)
{
auto const index = ctx.tx[~sfObjectID];
auto const newSponsorSle = getTxReserveSponsor(ctx.view, ctx.tx);
if (!newSponsorSle)
return newSponsorSle.error(); // LCOV_EXCL_LINE
bool const isObjectSponsor = index != std::nullopt;
auto const account = ctx.tx[sfAccount];
auto const sponseeAccountID = ctx.tx[~sfSponsee].value_or(account);
auto const sponseeSle = ctx.view.read(keylet::account(sponseeAccountID));
if (!sponseeSle)
return tecINTERNAL; // LCOV_EXCL_LINE
if (isObjectSponsor)
{
auto const sle = ctx.view.read(keylet::unchecked(*index));
if (!sle)
return tecNO_ENTRY;
auto const ownerCountDelta = getLedgerEntryOwnerCount(sle);
auto const owner = getLedgerEntryOwner(ctx.view, sle, sponseeAccountID);
if (!owner || owner != sponseeAccountID)
return tecNO_PERMISSION;
auto const& sponsorField = getLedgerEntrySponsorField(sle, *owner);
if (ctx.tx.isFlag(tfSponsorshipCreate))
{
if (!*newSponsorSle)
return tecNO_PERMISSION;
// check object is not sponsored yet
if (sle->isFieldPresent(sponsorField))
return tecNO_PERMISSION;
}
else if (ctx.tx.isFlag(tfSponsorshipReassign))
{
if (!*newSponsorSle)
return tecNO_PERMISSION;
// check object is already ctx.sponsored
if (!sle->isFieldPresent(sponsorField))
return tecNO_PERMISSION;
}
else if (ctx.tx.isFlag(tfSponsorshipEnd))
{
if (*newSponsorSle)
return tecNO_PERMISSION;
// check object is sponsored
if (!sle->isFieldPresent(sponsorField))
return tecNO_PERMISSION;
// only the sponsor or sponsee can end sponsorship
auto const sponsor = sle->getAccountID(sponsorField);
if (account != sponsor && account != sponseeAccountID)
return tecNO_PERMISSION;
}
// check new sponsor have sufficient balance
// NOLINTNEXTLINE(readability-suspicious-call-argument)
if (auto const ter = checkInsufficientReserve(
ctx.view,
ctx.tx,
sponseeSle,
sponseeSle->getFieldAmount(sfBalance),
*newSponsorSle,
ownerCountDelta,
0,
ctx.j);
!isTesSuccess(ter))
return ter;
}
else
{
if (ctx.tx.isFlag(tfSponsorshipCreate))
{
if (!*newSponsorSle)
return tecNO_PERMISSION;
// check account is not sponsored yet
if (sponseeSle->isFieldPresent(sfSponsor))
return tecNO_PERMISSION;
}
else if (ctx.tx.isFlag(tfSponsorshipReassign))
{
if (!*newSponsorSle)
return tecNO_PERMISSION;
// check account is already sponsored
if (!sponseeSle->isFieldPresent(sfSponsor))
return tecNO_PERMISSION;
}
else if (ctx.tx.isFlag(tfSponsorshipEnd))
{
if (*newSponsorSle)
return tecNO_PERMISSION;
// check account is sponsored
if (!sponseeSle->isFieldPresent(sfSponsor))
return tecNO_PERMISSION;
// only the sponsor or sponsee can end sponsorship
auto const sponsor = sponseeSle->getAccountID(sfSponsor);
if (account != sponsor && account != sponseeAccountID)
return tecNO_PERMISSION;
}
// check account have sufficient balance
// In the case of removing an account sponsor, accSle should have no sfSponsor set
// (AccountReserve = 0). However, by setting accountCountDelta = 1 here, we are able to
// calculate the actual required Account Reserve.
// NOLINTNEXTLINE(readability-suspicious-call-argument)
if (auto const ter = checkInsufficientReserve(
ctx.view,
ctx.tx,
sponseeSle,
sponseeSle->getFieldAmount(sfBalance),
*newSponsorSle,
0,
1,
ctx.j);
!isTesSuccess(ter))
return ter;
}
return tesSUCCESS;
}
TER
reduceReserveCount(
ApplyView& view,
AccountID const& account,
AccountID const& sponsor,
int32_t delta)
{
if (delta == 0)
return tesSUCCESS;
if (delta > 0)
return tefINTERNAL; // LCOV_EXCL_LINE
auto const sponsorKeylet = keylet::sponsor(sponsor, account);
auto const sponsorSle = view.peek(sponsorKeylet);
if (!sponsorSle)
return tefINTERNAL; // LCOV_EXCL_LINE
auto const reserveCount = sponsorSle->getFieldU32(sfReserveCount);
int32_t const afterReserveCount = reserveCount + delta;
if (afterReserveCount < 0)
{
// already checked in preclaim()
return tefINTERNAL; // LCOV_EXCL_LINE
}
sponsorSle->at(sfReserveCount) = static_cast<unsigned>(afterReserveCount);
view.update(sponsorSle);
return tesSUCCESS;
}
TER
SponsorshipTransfer::doApply()
{
auto const& tx = ctx_.tx;
auto const index = tx[~sfObjectID];
bool const isObjectSponsor = index != std::nullopt;
auto const sponseeAccountID = tx[~sfSponsee].value_or(accountID_);
auto const sponseeSle = view().peek(keylet::account(sponseeAccountID));
if (!sponseeSle)
return tefINTERNAL; // LCOV_EXCL_LINE
auto const setSponsorFieldU32 = [](auto const& sle, auto const& field, auto const& delta) {
int32_t const newValue = static_cast<int32_t>(sle->getFieldU32(field)) + delta;
if (newValue < 0)
{
UNREACHABLE("xrpl::SponsorshipTransfer::doApply : Invalid sponsor field value");
return;
}
sle->at(field) = static_cast<std::uint32_t>(newValue);
};
if (isObjectSponsor)
{
auto const hasSignature = tx.isFieldPresent(sfSponsorSignature);
// transfer object sponsor
auto const objSle = view().peek(keylet::unchecked(*index));
if (!objSle)
return tefINTERNAL; // LCOV_EXCL_LINE
auto const ownerAccountID = getLedgerEntryOwner(view(), objSle, sponseeAccountID);
if (!ownerAccountID)
return tefINTERNAL; // LCOV_EXCL_LINE
auto const ownerSle = view().peek(keylet::account(*ownerAccountID));
if (!ownerSle)
return tefINTERNAL; // LCOV_EXCL_LINE
auto const ownerCountDelta = getLedgerEntryOwnerCount(objSle);
auto const& sponsorField = getLedgerEntrySponsorField(objSle, *ownerAccountID);
if (ctx_.tx.isFlag(tfSponsorshipCreate))
{
auto const newSponsorAccountID = tx.getAccountID(sfSponsor);
XRPL_ASSERT(!!newSponsorAccountID, "New sponsor is required when creating sponsorship");
// update owner's sponsored count
setSponsorFieldU32(ownerSle, sfSponsoredOwnerCount, ownerCountDelta);
view().update(ownerSle);
// increment new sponsor's sponsoring count
auto const newSponsorSle = view().peek(keylet::account(newSponsorAccountID));
if (!newSponsorSle)
return tefINTERNAL; // LCOV_EXCL_LINE
setSponsorFieldU32(newSponsorSle, sfSponsoringOwnerCount, ownerCountDelta);
view().update(newSponsorSle);
// set new sponsor to object
objSle->setAccountID(sponsorField, newSponsorAccountID);
view().update(objSle);
if (!hasSignature)
{
// use ReserveCount for pre-funded sponsoring
if (auto const ter = reduceReserveCount(
view(), sponseeAccountID, newSponsorAccountID, -ownerCountDelta);
!isTesSuccess(ter))
return ter;
}
}
else if (ctx_.tx.isFlag(tfSponsorshipReassign))
{
auto const newSponsorAccountID = tx.getAccountID(sfSponsor);
XRPL_ASSERT(
!!newSponsorAccountID, "New sponsor is required when reassigning sponsorship");
auto const oldSponsorAccountID = objSle->getAccountID(sponsorField);
XRPL_ASSERT(
!!oldSponsorAccountID, "Old sponsor is required when reassigning sponsorship");
// decrement old sponsor's sponsoring count
auto const oldSponsorSle = view().peek(keylet::account(oldSponsorAccountID));
if (!oldSponsorSle)
return tefINTERNAL; // LCOV_EXCL_LINE
setSponsorFieldU32(oldSponsorSle, sfSponsoringOwnerCount, -ownerCountDelta);
view().update(oldSponsorSle);
// increment new sponsor's sponsoring count
auto const newSponsorSle = view().peek(keylet::account(newSponsorAccountID));
if (!newSponsorSle)
return tefINTERNAL; // LCOV_EXCL_LINE
setSponsorFieldU32(newSponsorSle, sfSponsoringOwnerCount, ownerCountDelta);
view().update(newSponsorSle);
// set new sponsor to object
objSle->setAccountID(sponsorField, newSponsorAccountID);
view().update(objSle);
if (!hasSignature)
{
// use ReserveCount for pre-funded sponsoring
if (auto const ter = reduceReserveCount(
view(), sponseeAccountID, newSponsorAccountID, -ownerCountDelta);
!isTesSuccess(ter))
return ter;
}
}
else if (ctx_.tx.isFlag(tfSponsorshipEnd))
{
auto const oldSponsorAccountID = objSle->getAccountID(sponsorField);
XRPL_ASSERT(!!oldSponsorAccountID, "Old sponsor is required when ending sponsorship");
auto const oldSponsorSle = view().peek(keylet::account(oldSponsorAccountID));
if (!oldSponsorSle)
return tefINTERNAL; // LCOV_EXCL_LINE
// decrement sponsored count
setSponsorFieldU32(sponseeSle, sfSponsoredOwnerCount, -ownerCountDelta);
view().update(sponseeSle);
// decrement old sponsoring count
setSponsorFieldU32(oldSponsorSle, sfSponsoringOwnerCount, -ownerCountDelta);
view().update(oldSponsorSle);
// remove sponsor from object
objSle->makeFieldAbsent(sponsorField);
view().update(objSle);
}
}
else
{
if (ctx_.tx.isFlag(tfSponsorshipCreate))
{
// create account sponsor
// increment new sponsoring count
auto const newSponsorAccountID = tx.getAccountID(sfSponsor);
auto const newSponsorSle = view().peek(keylet::account(newSponsorAccountID));
if (!newSponsorSle)
return tefINTERNAL; // LCOV_EXCL_LINE
setSponsorFieldU32(newSponsorSle, sfSponsoringAccountCount, 1);
view().update(newSponsorSle);
// set new sponsor to account
sponseeSle->setAccountID(sfSponsor, newSponsorAccountID);
view().update(sponseeSle);
}
else if (ctx_.tx.isFlag(tfSponsorshipReassign))
{
// reassign account sponsor
// increment new sponsoring count
auto const newSponsorAccountID = tx.getAccountID(sfSponsor);
auto const newSponsorSle = view().peek(keylet::account(newSponsorAccountID));
if (!newSponsorSle)
return tefINTERNAL; // LCOV_EXCL_LINE
setSponsorFieldU32(newSponsorSle, sfSponsoringAccountCount, 1);
view().update(newSponsorSle);
// decrement old sponsoring count
auto const oldSponsor = sponseeSle->getAccountID(sfSponsor);
auto const oldSponsorSle = view().peek(keylet::account(oldSponsor));
if (!oldSponsorSle)
return tefINTERNAL; // LCOV_EXCL_LINE
setSponsorFieldU32(oldSponsorSle, sfSponsoringAccountCount, -1);
view().update(oldSponsorSle);
// set new sponsor to account
sponseeSle->setAccountID(sfSponsor, newSponsorAccountID);
view().update(sponseeSle);
}
else if (ctx_.tx.isFlag(tfSponsorshipEnd))
{
// dissolve account sponsor
auto const oldSponsorAccountID = sponseeSle->getAccountID(sfSponsor);
sponseeSle->makeFieldAbsent(sfSponsor);
view().update(sponseeSle);
// decrement account sponsoring count
auto const oldSponsorSle = view().peek(keylet::account(oldSponsorAccountID));
if (!oldSponsorSle)
return tefINTERNAL; // LCOV_EXCL_LINE
setSponsorFieldU32(oldSponsorSle, sfSponsoringAccountCount, -1);
view().update(oldSponsorSle);
}
}
return tesSUCCESS;
}
void
SponsorshipTransfer::visitInvariantEntry(
bool,
std::shared_ptr<SLE const> const&,
std::shared_ptr<SLE const> const&)
{
}
bool
SponsorshipTransfer::finalizeInvariants(
STTx const&,
TER,
XRPAmount,
ReadView const&,
beast::Journal const&)
{
return true;
}
} // namespace xrpl

View File

@@ -35,6 +35,7 @@
#include <utility>
namespace xrpl {
bool
AccountDelete::checkExtraFeatures(PreflightContext const& ctx)
{
@@ -267,15 +268,6 @@ AccountDelete::preclaim(PreclaimContext const& ctx)
if (cp)
return tecHAS_OBLIGATIONS;
if (sleAccount->isFieldPresent(sfSponsor))
{
if (dst != sleAccount->getAccountID(sfSponsor))
return tecNO_SPONSOR_PERMISSION;
}
if (sleAccount->isFieldPresent(sfSponsoringOwnerCount) ||
sleAccount->isFieldPresent(sfSponsoringAccountCount))
return tecHAS_OBLIGATIONS;
// We don't allow an account to be deleted if its sequence number
// is within 256 of the current ledger. This prevents replay of old
// transactions if this account is resurrected after it is deleted.
@@ -396,41 +388,12 @@ AccountDelete::doApply()
if (!isTesSuccess(ter))
return ter;
if (src->isFieldPresent(sfSponsoredOwnerCount))
return tefINTERNAL; // LCOV_EXCL_LINE
// Transfer any XRP remaining after the fee is paid to the destination:
auto const remainingBalance = src->getFieldAmount(sfBalance).xrp();
(*dst)[sfBalance] = (*dst)[sfBalance] + remainingBalance;
(*src)[sfBalance] = (*src)[sfBalance] - remainingBalance;
ctx_.deliver(remainingBalance);
if (src->isFieldPresent(sfSponsor))
{
auto const sponsorAccountID = src->getAccountID(sfSponsor);
auto sponsorSle = view().peek(keylet::account(sponsorAccountID));
if (!sponsorSle || !sponsorSle->isFieldPresent(sfSponsoringAccountCount))
return tefINTERNAL; // LCOV_EXCL_LINE
auto const sponsoringAccountCount = sponsorSle->getFieldU32(sfSponsoringAccountCount);
if (sponsoringAccountCount == 0)
{
// sanity check
// Since sfSponsoringAccountCount is set to soeDEFAULT, the field will not be
// present with a value of 0.
return tefINTERNAL; // LCOV_EXCL_LINE
}
sponsorSle->at(sfSponsoringAccountCount) = sponsoringAccountCount - 1;
view().update(sponsorSle);
// Following line might look redundant, but without it, sfSponsor
// would end up remaining in after-ltAccountRoot during the
// InvariantCheck.
src->makeFieldAbsent(sfSponsor);
}
XRPL_ASSERT(
(*src)[sfBalance] == XRPAmount(0), "xrpl::AccountDelete::doApply : source balance is zero");

View File

@@ -8,7 +8,6 @@
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
@@ -216,8 +215,8 @@ removeSignersFromLedger(
// LCOV_EXCL_STOP
}
adjustOwnerCountObj(
view, view.peek(accountKeylet), signers, removeFromOwnerCount, registry.getJournal("View"));
adjustOwnerCount(
view, view.peek(accountKeylet), removeFromOwnerCount, registry.getJournal("View"));
view.erase(signers);
@@ -316,26 +315,19 @@ SignerListSet::replaceSignerList()
if (!sle)
return tefINTERNAL; // LCOV_EXCL_LINE
// Compute new reserve. Verify the account has funds to meet the reserve.
std::uint32_t const oldOwnerCount{(*sle)[sfOwnerCount]};
static constexpr int kAddedOwnerCount = 1;
std::uint32_t const flags{lsfOneOwnerCount};
XRPAmount const newReserve{view().fees().accountReserve(oldOwnerCount + kAddedOwnerCount)};
// We check the reserve against the starting balance because we want to
// allow dipping into the reserve to pay fees. This behavior is consistent
// with TicketCreate.
auto const sponsorSle = getTxReserveSponsor(view(), ctx_.tx);
if (!sponsorSle)
return sponsorSle.error(); // LCOV_EXCL_LINE
if (auto const ret = checkInsufficientReserve(
ctx_.view(),
ctx_.tx,
sle,
preFeeBalance_,
*sponsorSle,
kAddedOwnerCount,
0,
ctx_.journal);
!isTesSuccess(ret))
return ret;
if (preFeeBalance_ < newReserve)
return tecINSUFFICIENT_RESERVE;
// Everything's ducky. Add the ltSIGNER_LIST to the ledger.
auto signerList = std::make_shared<SLE>(signerListKeylet);
@@ -357,8 +349,7 @@ SignerListSet::replaceSignerList()
// If we succeeded, the new entry counts against the
// creator's reserve.
adjustOwnerCount(view(), sle, *sponsorSle, kAddedOwnerCount, viewJ);
addSponsorToLedgerEntry(signerList, *sponsorSle);
adjustOwnerCount(view(), sle, kAddedOwnerCount, viewJ);
return tesSUCCESS;
}

View File

@@ -12,7 +12,6 @@
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
@@ -437,7 +436,8 @@ transferHelper(
return tecINTERNAL; // LCOV_EXCL_LINE
{
auto const reserve = accountReserve(psb, sleSrc, j, 0, 0);
auto const ownerCount = sleSrc->getFieldU32(sfOwnerCount);
auto const reserve = psb.fees().accountReserve(ownerCount);
auto const availableBalance = [&]() -> STAmount {
STAmount curBal = (*sleSrc)[sfBalance];
@@ -726,10 +726,10 @@ finalizeClaimHelper(
return result;
}
adjustOwnerCountObj(outerSb, sleOwner, sleClaimID, -1, j);
// Remove the claim id from the ledger
outerSb.erase(sleClaimID);
adjustOwnerCount(outerSb, sleOwner, -1, j);
}
}
@@ -948,7 +948,6 @@ TER
applyCreateAccountAttestations(
ApplyView& view,
RawView& rawView,
STTx const& tx,
TIter attBegin,
TIter attEnd,
AccountID const& doorAccount,
@@ -1030,11 +1029,10 @@ applyCreateAccountAttestations(
// Check reserve
auto const balance = (*sleDoor)[sfBalance];
// Don't sponsor door account objects in transactions not sent by the door account
// itself
if (auto const ret = checkInsufficientReserve(psb, tx, sleDoor, balance, {}, 1, 0, j);
!isTesSuccess(ret))
return std::unexpected(ret); // tecINSUFFICIENT_RESERVE
auto const reserve = psb.fees().accountReserve((*sleDoor)[sfOwnerCount] + 1);
if (balance < reserve)
return std::unexpected(tecINSUFFICIENT_RESERVE);
}
std::vector<Attestations::AttestationCreateAccount> atts;
@@ -1139,9 +1137,8 @@ applyCreateAccountAttestations(
if (!sleDoor)
return tecINTERNAL; // LCOV_EXCL_LINE
// Don't sponsor door account objects in transactions not sent by the door account
// itself
adjustOwnerCount(psb, sleDoor, {}, 1, j);
// Reserve was already checked
adjustOwnerCount(psb, sleDoor, 1, j);
psb.insert(createdSleClaimID);
psb.update(sleDoor);
}
@@ -1314,7 +1311,6 @@ attestationDoApply(ApplyContext& ctx)
return applyCreateAccountAttestations(
ctx.view(),
ctx.rawView(),
ctx.tx,
&*att,
&*att + 1,
thisDoor,
@@ -1440,13 +1436,10 @@ XChainCreateBridge::preclaim(PreclaimContext const& ctx)
return terNO_ACCOUNT;
auto const balance = (*sleAcc)[sfBalance];
auto const sponsorSle = getTxReserveSponsor(ctx.view, ctx.tx);
if (!sponsorSle)
return sponsorSle.error(); // LCOV_EXCL_LINE
if (auto const ret = checkInsufficientReserve(
ctx.view, ctx.tx, sleAcc, balance, *sponsorSle, 1, 0, ctx.j);
!isTesSuccess(ret))
return ret;
auto const reserve = ctx.view.fees().accountReserve((*sleAcc)[sfOwnerCount] + 1);
if (balance < reserve)
return tecINSUFFICIENT_RESERVE;
}
return tesSUCCESS;
@@ -1488,11 +1481,7 @@ XChainCreateBridge::doApply()
(*sleBridge)[sfOwnerNode] = *page;
}
auto const sponsorSle = getTxReserveSponsor(view(), ctx_.tx);
if (!sponsorSle)
return sponsorSle.error(); // LCOV_EXCL_LINE
adjustOwnerCount(ctx_.view(), sleAcct, *sponsorSle, 1, ctx_.journal);
addSponsorToLedgerEntry(sleBridge, *sponsorSle);
adjustOwnerCount(ctx_.view(), sleAcct, 1, ctx_.journal);
ctx_.view().insert(sleBridge);
ctx_.view().update(sleAcct);
@@ -1995,13 +1984,10 @@ XChainCreateClaimID::preclaim(PreclaimContext const& ctx)
return terNO_ACCOUNT;
auto const balance = (*sleAcc)[sfBalance];
auto const sponsorSle = getTxReserveSponsor(ctx.view, ctx.tx);
if (!sponsorSle)
return sponsorSle.error(); // LCOV_EXCL_LINE
if (auto const ret = checkInsufficientReserve(
ctx.view, ctx.tx, sleAcc, balance, *sponsorSle, 1, 0, ctx.j);
!isTesSuccess(ret))
return ret;
auto const reserve = ctx.view.fees().accountReserve((*sleAcc)[sfOwnerCount] + 1);
if (balance < reserve)
return tecINSUFFICIENT_RESERVE;
}
return tesSUCCESS;
@@ -2057,11 +2043,7 @@ XChainCreateClaimID::doApply()
(*sleClaimID)[sfOwnerNode] = *page;
}
auto const sponsorSle = getTxReserveSponsor(view(), ctx_.tx);
if (!sponsorSle)
return sponsorSle.error(); // LCOV_EXCL_LINE
adjustOwnerCount(ctx_.view(), sleAcct, *sponsorSle, 1, ctx_.journal);
addSponsorToLedgerEntry(sleClaimID, *sponsorSle);
adjustOwnerCount(ctx_.view(), sleAcct, 1, ctx_.journal);
ctx_.view().insert(sleClaimID);
ctx_.view().update(sleBridge);

View File

@@ -91,7 +91,8 @@ CheckCancel::doApply()
}
// If we succeeded, update the check owner's reserve.
adjustOwnerCountObj(view(), srcId, sleCheck, -1, viewJ);
auto const sleSrc = view().peek(keylet::account(srcId));
adjustOwnerCount(view(), sleSrc, -1, viewJ);
// Remove check from ledger.
view().erase(sleCheck);

View File

@@ -8,7 +8,6 @@
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Asset.h>
@@ -31,7 +30,7 @@
#include <xrpl/tx/paths/detail/Steps.h>
#include <algorithm>
#include <memory>
#include <cstdint>
#include <optional>
namespace xrpl {
@@ -176,7 +175,7 @@ CheckCash::preclaim(PreclaimContext const& ctx)
// once the check is cashed, since the check's reserve will no
// longer be required. So, if we're dealing in XRP, we add one
// reserve's worth to the available funds.
if (value.native() && !sleCheck->isFieldPresent(sfSponsor))
if (value.native())
availableFunds += XRPAmount{ctx.view.fees().increment};
if (value > availableFunds)
@@ -308,8 +307,6 @@ CheckCash::doApply()
// LCOV_EXCL_STOP
}
auto const sponsorCheckSle = getLedgerEntryReserveSponsor(psb, sleCheck);
// Preclaim already checked that source has at least the requested
// funds.
//
@@ -338,7 +335,7 @@ CheckCash::doApply()
// from src's directory, we allow them to send that additional
// incremental reserve amount in the transfer. Hence the -1
// argument.
STAmount const srcLiquid{xrpLiquid(psb, srcId, sponsorCheckSle ? 0 : -1, viewJ)};
STAmount const srcLiquid{xrpLiquid(psb, srcId, -1, viewJ)};
// Now, how much do they need in order to be successful?
STAmount const xrpDeliver{
@@ -388,19 +385,14 @@ CheckCash::doApply()
STAmount const flowDeliver{
optDeliverMin ? maxDeliverMin() : ctx_.tx.getFieldAmount(sfAmount)};
auto const sponsorSle = getTxReserveSponsor(psb, ctx_.tx);
if (!sponsorSle)
return sponsorSle.error(); // LCOV_EXCL_LINE
// Check reserve. Return destination account SLE if enough reserve,
// otherwise return nullptr.
auto checkReserve = [&]() -> SLE::pointer {
auto sleDst = psb.peek(keylet::account(accountID_));
// Can the account cover the trust line's or MPT reserve?
if (auto const ret = checkInsufficientReserve(
psb, ctx_.tx, sleDst, preFeeBalance_, *sponsorSle, 1, 0, j_);
!isTesSuccess(ret))
if (std::uint32_t const ownerCount = {sleDst->at(sfOwnerCount)};
preFeeBalance_ < psb.fees().accountReserve(ownerCount + 1))
{
JLOG(j_.trace()) << "Trust line does not exist. "
"Insufficient reserve to create line.";
@@ -457,7 +449,6 @@ CheckCash::doApply()
Issue(currency, accountID_), // limit of zero
0, // quality in
0, // quality out
*sponsorSle, // sponsor
viewJ); // journal
!isTesSuccess(ter))
{
@@ -504,8 +495,7 @@ CheckCash::doApply()
if (sleDst == nullptr)
return tecINSUFFICIENT_RESERVE;
if (auto const err =
checkCreateMPT(psb, mptID, accountID_, *sponsorSle, j_);
if (auto const err = checkCreateMPT(psb, mptID, accountID_, j_);
!isTesSuccess(err))
{
return err;
@@ -591,8 +581,7 @@ CheckCash::doApply()
}
// If we succeeded, update the check owner's reserve.
adjustOwnerCount(psb, psb.peek(keylet::account(srcId)), sponsorCheckSle, -1, viewJ);
adjustOwnerCount(psb, psb.peek(keylet::account(srcId)), -1, viewJ);
// Remove check from ledger.
psb.erase(sleCheck);

View File

@@ -7,7 +7,6 @@
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Asset.h>
@@ -195,13 +194,13 @@ CheckCreate::doApply()
// A check counts against the reserve of the issuing account, but we
// check the starting balance because we want to allow dipping into the
// reserve to pay fees.
auto const sponsorSle = getTxReserveSponsor(view(), ctx_.tx);
if (!sponsorSle)
return sponsorSle.error(); // LCOV_EXCL_LINE
if (auto const ret = checkInsufficientReserve(
view(), ctx_.tx, sle, preFeeBalance_, *sponsorSle, 1, 0, ctx_.journal);
!isTesSuccess(ret))
return ret;
{
STAmount const reserve{view().fees().accountReserve(sle->getFieldU32(sfOwnerCount) + 1)};
if (preFeeBalance_ < reserve)
return tecINSUFFICIENT_RESERVE;
}
// Note that we use the value from the sequence or ticket as the
// Check sequence. For more explanation see comments in SeqProxy.h.
std::uint32_t const seq = ctx_.tx.getSeqValue();
@@ -254,9 +253,7 @@ CheckCreate::doApply()
sleCheck->setFieldU64(sfOwnerNode, *page);
}
// If we succeeded, the new entry counts against the creator's reserve.
adjustOwnerCount(view(), sle, *sponsorSle, 1, viewJ);
addSponsorToLedgerEntry(sleCheck, *sponsorSle);
adjustOwnerCount(view(), sle, 1, viewJ);
return tesSUCCESS;
}

View File

@@ -4,7 +4,6 @@
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/CredentialHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
@@ -94,13 +93,12 @@ CredentialAccept::doApply()
if (!sleSubject || !sleIssuer)
return tefINTERNAL; // LCOV_EXCL_LINE
auto const sponsorSle = getTxReserveSponsor(view(), ctx_.tx);
if (!sponsorSle)
return sponsorSle.error(); // LCOV_EXCL_LINE
if (auto const ret = checkInsufficientReserve(
view(), ctx_.tx, sleSubject, preFeeBalance_, *sponsorSle, 1, 0, ctx_.journal);
!isTesSuccess(ret))
return ret;
{
STAmount const reserve{
view().fees().accountReserve(sleSubject->getFieldU32(sfOwnerCount) + 1)};
if (preFeeBalance_ < reserve)
return tecINSUFFICIENT_RESERVE;
}
auto const credType(ctx_.tx[sfCredentialType]);
Keylet const credentialKey = keylet::credential(accountID_, issuer, credType);
@@ -119,10 +117,8 @@ CredentialAccept::doApply()
sleCred->setFieldU32(sfFlags, lsfAccepted);
view().update(sleCred);
adjustOwnerCountObj(view(), sleIssuer, sleCred, -1, j_);
removeSponsorFromLedgerEntry(sleCred);
adjustOwnerCount(view(), sleSubject, *sponsorSle, 1, j_);
addSponsorToLedgerEntry(sleCred, *sponsorSle);
adjustOwnerCount(view(), sleIssuer, -1, j_);
adjustOwnerCount(view(), sleSubject, 1, j_);
return tesSUCCESS;
}

View File

@@ -7,7 +7,6 @@
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/CredentialHelpers.h> // IWYU pragma: keep
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Keylet.h>
@@ -131,13 +130,12 @@ CredentialCreate::doApply()
if (!sleIssuer)
return tefINTERNAL; // LCOV_EXCL_LINE
auto const sponsorSle = getTxReserveSponsor(view(), ctx_.tx);
if (!sponsorSle)
return sponsorSle.error(); // LCOV_EXCL_LINE
if (auto const ret = checkInsufficientReserve(
view(), ctx_.tx, sleIssuer, preFeeBalance_, *sponsorSle, 1, 0, ctx_.journal);
!isTesSuccess(ret))
return ret;
{
STAmount const reserve{
view().fees().accountReserve(sleIssuer->getFieldU32(sfOwnerCount) + 1)};
if (preFeeBalance_ < reserve)
return tecINSUFFICIENT_RESERVE;
}
sleCred->setAccountID(sfSubject, subject);
sleCred->setAccountID(sfIssuer, accountID_);
@@ -155,8 +153,7 @@ CredentialCreate::doApply()
return tecDIR_FULL;
sleCred->setFieldU64(sfIssuerNode, *page);
adjustOwnerCount(view(), sleIssuer, *sponsorSle, 1, j_);
addSponsorToLedgerEntry(sleCred, *sponsorSle);
adjustOwnerCount(view(), sleIssuer, 1, j_);
}
if (subject == accountID_)

View File

@@ -5,7 +5,6 @@
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/SField.h>
@@ -95,13 +94,11 @@ DelegateSet::doApply()
if (permissions.empty())
return tecINTERNAL; // LCOV_EXCL_LINE
auto const sponsorSle = getTxReserveSponsor(view(), ctx_.tx);
if (!sponsorSle)
return sponsorSle.error(); // LCOV_EXCL_LINE
if (auto const ret = checkInsufficientReserve(
view(), ctx_.tx, sleOwner, preFeeBalance_, *sponsorSle, 1, 0, ctx_.journal);
!isTesSuccess(ret))
return ret;
STAmount const reserve{
ctx_.view().fees().accountReserve(sleOwner->getFieldU32(sfOwnerCount) + 1)};
if (preFeeBalance_ < reserve)
return tecINSUFFICIENT_RESERVE;
sle = std::make_shared<SLE>(delegateKey);
sle->setAccountID(sfAccount, accountID_);
@@ -129,8 +126,7 @@ DelegateSet::doApply()
(*sle)[sfDestinationNode] = *destPage;
ctx_.view().insert(sle);
adjustOwnerCount(ctx_.view(), sleOwner, *sponsorSle, 1, ctx_.journal);
addSponsorToLedgerEntry(sle, *sponsorSle);
adjustOwnerCount(ctx_.view(), sleOwner, 1, ctx_.journal);
return tesSUCCESS;
}
@@ -170,7 +166,7 @@ DelegateSet::deleteDelegate(ApplyView& view, SLE::ref sle, beast::Journal j)
if (!sleOwner)
return tecINTERNAL; // LCOV_EXCL_LINE
adjustOwnerCountObj(view, sleOwner, sle, -1, j);
adjustOwnerCount(view, sleOwner, -1, j);
view.erase(sle);

View File

@@ -225,7 +225,6 @@ AMMClawback::applyGuts(Sandbox& sb)
std::tie(result, newLPTokenBalance, amountWithdraw, amount2Withdraw) =
AMMWithdraw::equalWithdrawTokens(
sb,
ctx_.tx,
*ammSle,
holder,
ammAccount,
@@ -313,7 +312,6 @@ AMMClawback::equalWithdrawMatchingOneAmount(
// tfee is actually not used, so pass tfee as 0.
return AMMWithdraw::equalWithdrawTokens(
sb,
ctx_.tx,
ammSle,
holder,
ammAccount,
@@ -347,7 +345,6 @@ AMMClawback::equalWithdrawMatchingOneAmount(
return AMMWithdraw::withdraw(
sb,
ctx_.tx,
ammSle,
ammAccount,
holder,
@@ -368,7 +365,6 @@ AMMClawback::equalWithdrawMatchingOneAmount(
// tfee is actually not used, so pass tfee as 0.
return AMMWithdraw::withdraw(
sb,
ctx_.tx,
ammSle,
ammAccount,
holder,

View File

@@ -10,7 +10,6 @@
#include <xrpl/ledger/helpers/AMMHelpers.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AMMCore.h>
#include <xrpl/protocol/AccountID.h>
@@ -148,43 +147,15 @@ AMMCreate::preclaim(PreclaimContext const& ctx)
return terNO_RIPPLE;
}
if (ctx.view.rules().enabled(featureSponsor))
// Check the reserve for LPToken trustline
STAmount const xrpBalance = xrpLiquid(ctx.view, accountID, 1, ctx.j);
// Insufficient reserve
if (xrpBalance <= beast::kZero)
{
auto const sponsorSle = getTxReserveSponsor(ctx.view, ctx.tx);
if (!sponsorSle)
return sponsorSle.error(); // LCOV_EXCL_LINE
// Check the reserve for LPToken trustline
// Insufficient reserve
auto const accountSle = ctx.view.read(keylet::account(accountID));
if (auto const ret = checkInsufficientReserve(
ctx.view,
ctx.tx,
accountSle,
accountSle->getFieldAmount(sfBalance),
*sponsorSle,
1,
0,
ctx.j);
!isTesSuccess(ret))
{
JLOG(ctx.j.debug()) << "AMM Instance: insufficient reserves";
return tecINSUF_RESERVE_LINE;
}
}
else
{
STAmount const xrpBalance = xrpLiquid(ctx.view, accountID, 1, ctx.j);
// Insufficient reserve
if (xrpBalance <= beast::kZero)
{
JLOG(ctx.j.debug()) << "AMM Instance: insufficient reserves";
return tecINSUF_RESERVE_LINE;
}
JLOG(ctx.j.debug()) << "AMM Instance: insufficient reserves";
return tecINSUF_RESERVE_LINE;
}
auto const ownerCountAdj = isReserveSponsored(ctx.tx) ? 0 : 1;
STAmount const xrpBalance = xrpLiquid(ctx.view, accountID, ownerCountAdj, ctx.j);
auto insufficientBalance = [&](STAmount const& amount) {
if (isXRP(amount))
return xrpBalance < amount;
@@ -323,11 +294,7 @@ applyCreate(ApplyContext& ctx, Sandbox& sb, AccountID const& account, beast::Jou
sb.insert(ammSle);
// Send LPT to LP.
auto const sponsorSle = getTxReserveSponsor(sb, ctx.tx);
if (!sponsorSle)
return {sponsorSle.error(), false}; // LCOV_EXCL_LINE
auto res = accountSend(sb, accountId, account, lpTokens, ctx.journal, *sponsorSle);
auto res = accountSend(sb, accountId, account, lpTokens, ctx.journal);
if (!isTesSuccess(res))
{
JLOG(j.debug()) << "AMM Instance: failed to send LPT " << lpTokens;
@@ -348,30 +315,17 @@ applyCreate(ApplyContext& ctx, Sandbox& sb, AccountID const& account, beast::Jou
return err;
}
if (auto const err = createMPToken(sb, mptID, accountId, {}, flags);
!isTesSuccess(err))
if (auto const err = createMPToken(sb, mptID, accountId, flags); !isTesSuccess(err))
return err;
// Don't adjust AMM owner count.
// It's irrelevant for pseudo-account like AMM.
return accountSend(
sb,
account,
accountId,
amount,
ctx.journal,
{}, // don't sponsor for AMM Trustline
WaiveTransferFee::Yes);
sb, account, accountId, amount, ctx.journal, WaiveTransferFee::Yes);
},
// Set AMM flag on AMM trustline
[&](Issue const& issue) -> TER {
if (auto const res = accountSend(
sb,
account,
accountId,
amount,
ctx.journal,
{}, // don't sponsor for AMM Trustline
WaiveTransferFee::Yes))
sb, account, accountId, amount, ctx.journal, WaiveTransferFee::Yes))
return res;
// Set AMM flag on AMM trustline
if (!isXRP(amount))

View File

@@ -8,7 +8,6 @@
#include <xrpl/ledger/helpers/AMMHelpers.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AMMCore.h>
#include <xrpl/protocol/AccountID.h>
@@ -235,33 +234,11 @@ AMMDeposit::preclaim(PreclaimContext const& ctx)
// Adjust the reserve if LP doesn't have LPToken trustline
auto const sle =
ctx.view.read(keylet::line(accountID, lpIssue.account, lpIssue.currency));
auto const sponsorSle = getTxReserveSponsor(ctx.view, ctx.tx);
if (!sponsorSle)
return sponsorSle.error(); // LCOV_EXCL_LINE
auto const accountSle = ctx.view.read(keylet::account(accountID));
auto const reserveAdj = (*sponsorSle || sle) ? 0 : 1;
if (xrpLiquid(ctx.view, accountID, reserveAdj, ctx.j) < deposit)
{
if (sle)
return tecUNFUNDED_AMM;
return tecINSUF_RESERVE_LINE;
}
if (auto const ret = checkInsufficientReserve(
ctx.view,
ctx.tx,
accountSle,
accountSle->getFieldAmount(sfBalance) - deposit,
*sponsorSle,
1,
!sle,
ctx.j);
*sponsorSle && !isTesSuccess(ret))
return tecINSUF_RESERVE_LINE;
return tesSUCCESS;
if (xrpLiquid(ctx.view, accountID, !sle, ctx.j) >= deposit)
return TER(tesSUCCESS);
if (sle)
return tecUNFUNDED_AMM;
return tecINSUF_RESERVE_LINE;
}
return accountFunds(
ctx.view,
@@ -382,37 +359,12 @@ AMMDeposit::preclaim(PreclaimContext const& ctx)
// We checked above but need to check again if depositing IOU only.
if (ammLPHolds(ctx.view, *ammSle, accountID, ctx.j) == beast::kZero)
{
if (ctx.view.rules().enabled(featureSponsor))
STAmount const xrpBalance = xrpLiquid(ctx.view, accountID, 1, ctx.j);
// Insufficient reserve
if (xrpBalance <= beast::kZero)
{
auto const accountSle = ctx.view.read(keylet::account(accountID));
auto const sponsorSle = getTxReserveSponsor(ctx.view, ctx.tx);
if (!sponsorSle)
return sponsorSle.error(); // LCOV_EXCL_LINE
// Insufficient reserve
if (auto const ret = checkInsufficientReserve(
ctx.view,
ctx.tx,
accountSle,
accountSle->getFieldAmount(sfBalance),
*sponsorSle,
1,
0,
ctx.j);
!isTesSuccess(ret))
{
JLOG(ctx.j.debug()) << "AMM Instance: insufficient reserves";
return tecINSUF_RESERVE_LINE;
}
}
else
{
STAmount const xrpBalance = xrpLiquid(ctx.view, accountID, 1, ctx.j);
// Insufficient reserve
if (xrpBalance <= beast::kZero)
{
JLOG(ctx.j.debug()) << "AMM Instance: insufficient reserves";
return tecINSUF_RESERVE_LINE;
}
JLOG(ctx.j.debug()) << "AMM Instance: insufficient reserves";
return tecINSUF_RESERVE_LINE;
}
}
@@ -558,10 +510,6 @@ AMMDeposit::deposit(
std::optional<STAmount> const& lpTokensDepositMin,
std::uint16_t tfee)
{
auto const sponsorSle = getTxReserveSponsor(view, ctx_.tx);
if (!sponsorSle)
return {sponsorSle.error(), STAmount{}}; // LCOV_EXCL_LINE
// Check account has sufficient funds.
// Return true if it does, false otherwise.
auto checkBalance = [&](auto const& depositAmount) -> TER {
@@ -571,10 +519,8 @@ AMMDeposit::deposit(
{
auto const& lpIssue = lpTokensDeposit.get<Issue>();
// Adjust the reserve if LP doesn't have LPToken trustline
auto const trustlineExists =
view.exists(keylet::line(accountID_, lpIssue.account, lpIssue.currency));
auto const reserveAdj = (*sponsorSle || trustlineExists) ? 0 : 1;
if (xrpLiquid(view, accountID_, reserveAdj, j_) >= depositAmount)
auto const sle = view.read(keylet::line(accountID_, lpIssue.account, lpIssue.currency));
if (xrpLiquid(view, accountID_, !sle, j_) >= depositAmount)
return tesSUCCESS;
}
else if (
@@ -628,13 +574,7 @@ AMMDeposit::deposit(
}
auto res = accountSend(
view,
accountID_,
ammAccount,
amountDepositActual,
ctx_.journal,
{}, // don't sponsor for AMM Trustline
WaiveTransferFee::Yes);
view, accountID_, ammAccount, amountDepositActual, ctx_.journal, WaiveTransferFee::Yes);
if (!isTesSuccess(res))
{
JLOG(ctx_.journal.debug()) << "AMM Deposit: failed to deposit " << amountDepositActual;
@@ -658,7 +598,6 @@ AMMDeposit::deposit(
ammAccount,
*amount2DepositActual,
ctx_.journal,
{}, // don't sponsor for AMM Trustline
WaiveTransferFee::Yes);
if (!isTesSuccess(res))
{
@@ -669,8 +608,7 @@ AMMDeposit::deposit(
}
// Deposit LP tokens
res =
accountSend(view, ammAccount, accountID_, lpTokensDepositActual, ctx_.journal, *sponsorSle);
res = accountSend(view, ammAccount, accountID_, lpTokensDepositActual, ctx_.journal);
if (!isTesSuccess(res))
{
JLOG(ctx_.journal.debug()) << "AMM Deposit: failed to deposit LPTokens";

View File

@@ -7,10 +7,8 @@
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/Sandbox.h>
#include <xrpl/ledger/helpers/AMMHelpers.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AMMCore.h>
#include <xrpl/protocol/AccountID.h>
@@ -354,7 +352,6 @@ AMMWithdraw::applyGuts(Sandbox& sb)
{
return equalWithdrawLimit(
sb,
ctx_.tx,
*ammSle,
ammAccountID,
amountBalance,
@@ -368,7 +365,6 @@ AMMWithdraw::applyGuts(Sandbox& sb)
{
return singleWithdrawTokens(
sb,
ctx_.tx,
*ammSle,
ammAccountID,
amountBalance,
@@ -380,26 +376,17 @@ AMMWithdraw::applyGuts(Sandbox& sb)
if (subTxType & tfLimitLPToken)
{
return singleWithdrawEPrice(
sb,
ctx_.tx,
*ammSle,
ammAccountID,
amountBalance,
lptAMMBalance,
*amount,
*ePrice,
tfee);
sb, *ammSle, ammAccountID, amountBalance, lptAMMBalance, *amount, *ePrice, tfee);
}
if (subTxType & tfSingleAsset)
{
return singleWithdraw(
sb, ctx_.tx, *ammSle, ammAccountID, amountBalance, lptAMMBalance, *amount, tfee);
sb, *ammSle, ammAccountID, amountBalance, lptAMMBalance, *amount, tfee);
}
if (subTxType & tfLPToken || subTxType & tfWithdrawAll)
{
return equalWithdrawTokens(
sb,
ctx_.tx,
*ammSle,
ammAccountID,
amountBalance,
@@ -450,7 +437,6 @@ AMMWithdraw::doApply()
std::pair<TER, STAmount>
AMMWithdraw::withdraw(
Sandbox& view,
STTx const& tx,
SLE const& ammSle,
AccountID const& ammAccount,
STAmount const& amountBalance,
@@ -464,7 +450,6 @@ AMMWithdraw::withdraw(
STAmount newLPTokenBalance;
std::tie(ter, newLPTokenBalance, std::ignore, std::ignore) = withdraw(
view,
tx,
ammSle,
ammAccount,
accountID_,
@@ -485,7 +470,6 @@ AMMWithdraw::withdraw(
std::tuple<TER, STAmount, STAmount, std::optional<STAmount>>
AMMWithdraw::withdraw(
Sandbox& view,
STTx const& tx,
SLE const& ammSle,
AccountID const& ammAccount,
AccountID const& account,
@@ -609,17 +593,6 @@ AMMWithdraw::withdraw(
}
}
// this is also called from AMMClawback, but only AMMWithdraw does sponsor
// the new trustline
SLE::pointer sponsorSle;
if (tx[sfAccount] == account)
{
auto sle = getTxReserveSponsor(view, tx);
if (!sle)
return {sle.error(), STAmount{}, STAmount{}, STAmount{}}; // LCOV_EXCL_LINE
sponsorSle = std::move(*sle);
}
// Check the reserve in case a trustline or MPT has to be created
bool const enabledFixAmMv12 = view.rules().enabled(fixAMMv1_2);
// If seated after a call to sufficientReserve() then MPToken must be
@@ -645,25 +618,17 @@ AMMWithdraw::withdraw(
auto sleAccount = view.peek(keylet::account(account));
if (!sleAccount)
return tecINTERNAL; // LCOV_EXCL_LINE
STAmount const balance = (*sleAccount)[sfBalance];
std::uint32_t const ownerCount = sleAccount->at(sfOwnerCount);
auto const balance = (*sleAccount)[sfBalance]->xrp();
std::uint32_t const count =
ownerCount(view, sponsorSle ? sponsorSle : sleAccount, journal);
// See also TrustSet::doApply() and MPTokenAuthorize::authorize()
if (count >= 2)
{
if (auto const ret = checkInsufficientReserve(
view,
tx,
sleAccount,
std::max(priorBalance, balance),
sponsorSle,
1,
0,
journal);
!isTesSuccess(ret))
return ret;
}
XRPAmount const reserve(
(ownerCount < 2) ? XRPAmount(beast::kZero)
: view.fees().accountReserve(ownerCount + 1));
auto const balanceAdj = isIssue ? std::max(priorBalance, balance.xrp()) : priorBalance;
if (balanceAdj < reserve)
return tecINSUFFICIENT_RESERVE;
}
return tesSUCCESS;
};
@@ -678,7 +643,7 @@ AMMWithdraw::withdraw(
!isTesSuccess(err))
return err;
if (auto const err = checkCreateMPT(view, mptIssue, account, sponsorSle, journal);
if (auto const err = checkCreateMPT(view, mptIssue, account, journal);
!isTesSuccess(err))
{
return err;
@@ -695,13 +660,7 @@ AMMWithdraw::withdraw(
// Withdraw amountWithdraw
auto res = accountSend(
view,
ammAccount,
account,
amountWithdrawActual,
journal,
sponsorSle,
WaiveTransferFee::Yes);
view, ammAccount, account, amountWithdrawActual, journal, WaiveTransferFee::Yes);
if (!isTesSuccess(res))
{
// LCOV_EXCL_START
@@ -720,13 +679,7 @@ AMMWithdraw::withdraw(
return {res, STAmount{}, STAmount{}, STAmount{}};
res = accountSend(
view,
ammAccount,
account,
*amount2WithdrawActual,
journal,
sponsorSle,
WaiveTransferFee::Yes);
view, ammAccount, account, *amount2WithdrawActual, journal, WaiveTransferFee::Yes);
if (!isTesSuccess(res))
{
// LCOV_EXCL_START
@@ -771,7 +724,6 @@ adjustLPTokensIn(
std::pair<TER, STAmount>
AMMWithdraw::equalWithdrawTokens(
Sandbox& view,
STTx const& tx,
SLE const& ammSle,
AccountID const& ammAccount,
STAmount const& amountBalance,
@@ -785,7 +737,6 @@ AMMWithdraw::equalWithdrawTokens(
STAmount newLPTokenBalance;
std::tie(ter, newLPTokenBalance, std::ignore, std::ignore) = equalWithdrawTokens(
view,
tx,
ammSle,
accountID_,
ammAccount,
@@ -837,7 +788,6 @@ AMMWithdraw::deleteAMMAccountIfEmpty(
std::tuple<TER, STAmount, STAmount, std::optional<STAmount>>
AMMWithdraw::equalWithdrawTokens(
Sandbox& view,
STTx const& tx,
SLE const& ammSle,
AccountID const account,
AccountID const& ammAccount,
@@ -860,7 +810,6 @@ AMMWithdraw::equalWithdrawTokens(
{
return withdraw(
view,
tx,
ammSle,
ammAccount,
account,
@@ -896,7 +845,6 @@ AMMWithdraw::equalWithdrawTokens(
return withdraw(
view,
tx,
ammSle,
ammAccount,
account,
@@ -949,7 +897,6 @@ AMMWithdraw::equalWithdrawTokens(
std::pair<TER, STAmount>
AMMWithdraw::equalWithdrawLimit(
Sandbox& view,
STTx const& tx,
SLE const& ammSle,
AccountID const& ammAccount,
STAmount const& amountBalance,
@@ -970,7 +917,6 @@ AMMWithdraw::equalWithdrawLimit(
{
return withdraw(
view,
tx,
ammSle,
ammAccount,
amountBalance,
@@ -1003,7 +949,6 @@ AMMWithdraw::equalWithdrawLimit(
}
return withdraw(
view,
tx,
ammSle,
ammAccount,
amountBalance,
@@ -1022,7 +967,6 @@ AMMWithdraw::equalWithdrawLimit(
std::pair<TER, STAmount>
AMMWithdraw::singleWithdraw(
Sandbox& view,
STTx const& tx,
SLE const& ammSle,
AccountID const& ammAccount,
STAmount const& amountBalance,
@@ -1051,7 +995,6 @@ AMMWithdraw::singleWithdraw(
return {tecAMM_INVALID_TOKENS, STAmount{}}; // LCOV_EXCL_LINE
return withdraw(
view,
tx,
ammSle,
ammAccount,
amountBalance,
@@ -1075,7 +1018,6 @@ AMMWithdraw::singleWithdraw(
std::pair<TER, STAmount>
AMMWithdraw::singleWithdrawTokens(
Sandbox& view,
STTx const& tx,
SLE const& ammSle,
AccountID const& ammAccount,
STAmount const& amountBalance,
@@ -1094,7 +1036,6 @@ AMMWithdraw::singleWithdrawTokens(
{
return withdraw(
view,
tx,
ammSle,
ammAccount,
amountBalance,
@@ -1130,7 +1071,6 @@ AMMWithdraw::singleWithdrawTokens(
std::pair<TER, STAmount>
AMMWithdraw::singleWithdrawEPrice(
Sandbox& view,
STTx const& tx,
SLE const& ammSle,
AccountID const& ammAccount,
STAmount const& amountBalance,
@@ -1178,7 +1118,6 @@ AMMWithdraw::singleWithdrawEPrice(
{
return withdraw(
view,
tx,
ammSle,
ammAccount,
amountBalance,

View File

@@ -14,7 +14,6 @@
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/helpers/OfferHelpers.h>
#include <xrpl/ledger/helpers/PermissionedDEXHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Asset.h>
@@ -832,26 +831,25 @@ OfferCreate::applyGuts(Sandbox& sb, Sandbox& sbCancel)
if (!sleCreator)
return {tefINTERNAL, false};
auto const sponsorSle = getTxReserveSponsor(sb, ctx_.tx);
if (!sponsorSle)
return {sponsorSle.error(), false}; // LCOV_EXCL_LINE
if (auto const ret = checkInsufficientReserve(
sb, ctx_.tx, sleCreator, preFeeBalance_, *sponsorSle, 1, 0, j_);
!isTesSuccess(ret))
{
// If we are here, the signing account had an insufficient reserve
// *prior* to our processing. If something actually crossed, then
// we allow this; otherwise, we just claim a fee.
if (!crossed)
result = tecINSUF_RESERVE_OFFER;
XRPAmount const reserve =
sb.fees().accountReserve(sleCreator->getFieldU32(sfOwnerCount) + 1);
if (!isTesSuccess(result))
if (preFeeBalance_ < reserve)
{
JLOG(j_.debug()) << "final result: " << transToken(result);
}
// If we are here, the signing account had an insufficient reserve
// *prior* to our processing. If something actually crossed, then
// we allow this; otherwise, we just claim a fee.
if (!crossed)
result = tecINSUF_RESERVE_OFFER;
return {result, true};
if (!isTesSuccess(result))
{
JLOG(j_.debug()) << "final result: " << transToken(result);
}
return {result, true};
}
}
// We need to place the remainder of the offer into its order book.
@@ -870,7 +868,7 @@ OfferCreate::applyGuts(Sandbox& sb, Sandbox& sbCancel)
}
// Update owner count.
adjustOwnerCount(sb, sleCreator, *sponsorSle, 1, viewJ);
adjustOwnerCount(sb, sleCreator, 1, viewJ);
JLOG(j_.trace()) << "adding to book: " << to_string(saTakerPays.asset()) << " : "
<< to_string(saTakerGets.asset())
@@ -939,7 +937,6 @@ OfferCreate::applyGuts(Sandbox& sb, Sandbox& sbCancel)
sleOffer->setFlag(lsfSell);
if (domainID)
sleOffer->setFieldH256(sfDomainID, *domainID);
addSponsorToLedgerEntry(sleOffer, *sponsorSle);
// if it's a hybrid offer, set hybrid flag, and create an open dir
if (bHybrid)

View File

@@ -50,7 +50,8 @@ DIDDelete::deleteSLE(ApplyView& view, SLE::pointer sle, AccountID const owner, b
if (!sleOwner)
return tecINTERNAL; // LCOV_EXCL_LINE
adjustOwnerCountObj(view, sleOwner, sle, -1, j);
adjustOwnerCount(view, sleOwner, -1, j);
view.update(sleOwner);
// Remove object from ledger
view.erase(sle);

View File

@@ -5,7 +5,6 @@
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
@@ -71,14 +70,13 @@ addSLE(ApplyContext& ctx, SLE::ref sle, AccountID const& owner)
return tefINTERNAL; // LCOV_EXCL_LINE
// Check reserve availability for new object creation
auto const sponsorSle = getTxReserveSponsor(ctx.view(), ctx.tx);
if (!sponsorSle)
return sponsorSle.error(); // LCOV_EXCL_LINE
auto const balance = STAmount((*sleAccount)[sfBalance]).xrp();
if (auto const ret = checkInsufficientReserve(
ctx.view(), ctx.tx, sleAccount, balance, *sponsorSle, 1, 0, ctx.journal);
!isTesSuccess(ret))
return ret;
{
auto const balance = STAmount((*sleAccount)[sfBalance]).xrp();
auto const reserve = ctx.view().fees().accountReserve((*sleAccount)[sfOwnerCount] + 1);
if (balance < reserve)
return tecINSUFFICIENT_RESERVE;
}
// Add ledger object to ledger
ctx.view().insert(sle);
@@ -91,8 +89,7 @@ addSLE(ApplyContext& ctx, SLE::ref sle, AccountID const& owner)
return tecDIR_FULL; // LCOV_EXCL_LINE
(*sle)[sfOwnerNode] = *page;
}
adjustOwnerCount(ctx.view(), sleAccount, *sponsorSle, 1, ctx.journal);
addSponsorToLedgerEntry(sle, *sponsorSle);
adjustOwnerCount(ctx.view(), sleAccount, 1, ctx.journal);
ctx.view().update(sleAccount);
return tesSUCCESS;

View File

@@ -182,7 +182,6 @@ EscrowCancel::doApply()
[&]<typename T>(T const&) {
return escrowUnlockApplyHelper<T>(
ctx_.view(),
ctx_.tx,
kParityRate,
ctx_.view().rules().enabled(fixCleanup3_2_0) ? sle : slep,
preFeeBalance_,
@@ -210,7 +209,8 @@ EscrowCancel::doApply()
}
}
adjustOwnerCountObj(ctx_.view(), sle, slep, -1, ctx_.journal);
adjustOwnerCount(ctx_.view(), sle, -1, ctx_.journal);
ctx_.view().update(sle);
// Remove escrow from ledger
ctx_.view().erase(slep);

View File

@@ -11,7 +11,6 @@
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Concepts.h>
@@ -435,21 +434,16 @@ EscrowCreate::doApply()
// Check reserve and funds availability
STAmount const amount{ctx_.tx[sfAmount]};
auto const reserve = ctx_.view().fees().accountReserve((*sle)[sfOwnerCount] + 1);
auto const balance = sle->getFieldAmount(sfBalance).xrp();
auto const sponsorSle = getTxReserveSponsor(view(), ctx_.tx);
if (!sponsorSle)
return sponsorSle.error(); // LCOV_EXCL_LINE
if (auto const ret =
checkInsufficientReserve(ctx_.view(), ctx_.tx, sle, balance, *sponsorSle, 1, 0, j_);
!isTesSuccess(ret))
return ret;
if (balance < reserve)
return tecINSUFFICIENT_RESERVE;
// Check reserve and funds availability
if (isXRP(amount))
{
if (auto const ret = checkInsufficientReserve(
ctx_.view(), ctx_.tx, sle, balance - STAmount(amount).xrp(), {}, 1, 0, j_);
!isTesSuccess(ret))
if (balance < reserve + STAmount(amount).xrp())
return tecUNFUNDED;
}
@@ -541,8 +535,7 @@ EscrowCreate::doApply()
}
// increment owner count
adjustOwnerCount(ctx_.view(), sle, *sponsorSle, 1, ctx_.journal);
addSponsorToLedgerEntry(slep, *sponsorSle);
adjustOwnerCount(ctx_.view(), sle, 1, ctx_.journal);
ctx_.view().update(sle);
return tesSUCCESS;
}

View File

@@ -14,7 +14,6 @@
#include <xrpl/ledger/helpers/EscrowHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Concepts.h>
@@ -223,11 +222,6 @@ EscrowFinish::preclaim(PreclaimContext const& ctx)
return ret;
}
}
auto const sponsorSle = getTxReserveSponsor(ctx.view, ctx.tx);
if (!sponsorSle)
return sponsorSle.error();
return tesSUCCESS;
}
@@ -366,7 +360,6 @@ EscrowFinish::doApply()
[&]<typename T>(T const&) {
return escrowUnlockApplyHelper<T>(
ctx_.view(),
ctx_.tx,
lockedRate,
sled,
preFeeBalance_,
@@ -397,7 +390,9 @@ EscrowFinish::doApply()
ctx_.view().update(sled);
// Adjust source owner count
adjustOwnerCountObj(ctx_.view(), account, slep, -1, ctx_.journal);
auto const sle = ctx_.view().peek(keylet::account(account));
adjustOwnerCount(ctx_.view(), sle, -1, ctx_.journal);
ctx_.view().update(sle);
// Remove escrow from ledger
ctx_.view().erase(slep);

View File

@@ -372,7 +372,7 @@ LoanBrokerCoverClawback::doApply()
associateAsset(*sleBroker, vaultAsset);
// Transfer assets from pseudo-account to depositor.
return accountSend(view(), brokerPseudoID, account, clawAmount, j_, {}, WaiveTransferFee::Yes);
return accountSend(view(), brokerPseudoID, account, clawAmount, j_, WaiveTransferFee::Yes);
}
void

View File

@@ -166,7 +166,7 @@ LoanBrokerCoverDeposit::doApply()
// Transfer assets from depositor to pseudo-account.
if (auto ter =
accountSend(view(), accountID_, brokerPseudoID, amount, j_, {}, WaiveTransferFee::Yes))
accountSend(view(), accountID_, brokerPseudoID, amount, j_, WaiveTransferFee::Yes))
return ter;
// Increase the LoanBroker's CoverAvailable by Amount

View File

@@ -155,11 +155,11 @@ LoanBrokerDelete::doApply()
{
auto const coverAvailable = STAmount{vaultAsset, broker->at(sfCoverAvailable)};
if (auto const ter = accountSend(
view(), brokerPseudoID, accountID_, coverAvailable, j_, {}, WaiveTransferFee::Yes))
view(), brokerPseudoID, accountID_, coverAvailable, j_, WaiveTransferFee::Yes))
return ter;
}
if (auto ter = removeEmptyHolding(view(), tx, brokerPseudoID, vaultAsset, j_))
if (auto ter = removeEmptyHolding(view(), brokerPseudoID, vaultAsset, j_))
return ter;
auto brokerPseudoSLE = view().peek(keylet::account(brokerPseudoID));
@@ -184,6 +184,10 @@ LoanBrokerDelete::doApply()
return tecHAS_OBLIGATIONS; // LCOV_EXCL_LINE
}
view().erase(brokerPseudoSLE);
view().erase(broker);
{
auto owner = view().peek(keylet::account(accountID_));
if (!owner)
@@ -191,15 +195,9 @@ LoanBrokerDelete::doApply()
// Decreases the owner count by two: one for the LoanBroker object, and
// one for the pseudo-account.
// LoanBroker object can be sponsored
adjustOwnerCountObj(view(), owner, broker, -1, j_);
// pseudo-account cannot be sponsored
adjustOwnerCount(view(), owner, {}, -1, j_);
adjustOwnerCount(view(), owner, -2, j_);
}
view().erase(brokerPseudoSLE);
view().erase(broker);
associateAsset(*broker, vaultAsset);
return tesSUCCESS;

View File

@@ -7,7 +7,6 @@
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/LendingHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/Indexes.h>
@@ -237,29 +236,12 @@ LoanBrokerSet::doApply()
if (auto const ter = dirLink(view, vaultPseudoID, broker, sfVaultNode))
return ter; // LCOV_EXCL_LINE
auto const sponsorSle = getTxReserveSponsor(view, tx);
if (!sponsorSle)
return sponsorSle.error(); // LCOV_EXCL_LINE
if (auto const ret = checkInsufficientReserve(
view, tx, owner, preFeeBalance_, {}, *sponsorSle ? 1 : 2, 0, j_);
!isTesSuccess(ret))
return ret;
if (*sponsorSle)
{
if (auto const ret = checkInsufficientReserve(
view, tx, owner, preFeeBalance_, *sponsorSle, 1, 0, j_);
!isTesSuccess(ret))
return ret;
}
// Increases the owner count by two: one for the LoanBroker object, and
// one for the pseudo-account.
// Pseudo-account cannot be sponsored
adjustOwnerCount(view, owner, {}, 1, j_);
// LoanBroker object can be sponsored
adjustOwnerCount(view, owner, *sponsorSle, 1, j_);
adjustOwnerCount(view, owner, 2, j_);
auto const ownerCount = owner->at(sfOwnerCount);
if (preFeeBalance_ < view.fees().accountReserve(ownerCount))
return tecINSUFFICIENT_RESERVE;
auto maybePseudo = createPseudoAccount(view, broker->key(), sfLoanBrokerID);
if (!maybePseudo)
@@ -267,8 +249,7 @@ LoanBrokerSet::doApply()
auto& pseudo = *maybePseudo;
auto pseudoId = pseudo->at(sfAccount);
if (auto ter =
addEmptyHolding(view, tx, pseudoId, preFeeBalance_, sleVault->at(sfAsset), j_))
if (auto ter = addEmptyHolding(view, pseudoId, preFeeBalance_, sleVault->at(sfAsset), j_))
return ter;
// Initialize data fields:
@@ -289,8 +270,6 @@ LoanBrokerSet::doApply()
if (auto const coverLiq = tx[~sfCoverRateLiquidation])
broker->at(sfCoverRateLiquidation) = *coverLiq;
addSponsorToLedgerEntry(broker, *sponsorSle);
view.insert(broker);
associateAsset(*broker, vaultAsset);

Some files were not shown because too many files have changed in this diff Show More