Compare commits

..

32 Commits

Author SHA1 Message Date
Mayukha Vadari
3bc460951c add makeNew functions 2026-03-23 11:07:31 -07:00
Mayukha Vadari
d1dae53097 fix clang-tidy 2026-03-23 10:54:06 -07:00
Mayukha Vadari
733dd51720 fix includes 2026-03-23 10:54:06 -07:00
Mayukha Vadari
9d67db1843 fix rebase issues 2026-03-23 10:54:06 -07:00
Mayukha Vadari
29bb59698f fix pre-commit issues 2026-03-23 10:54:06 -07:00
Mayukha Vadari
3b2dcefb81 fix more stuff 2026-03-23 10:54:06 -07:00
Mayukha Vadari
4e92ec2daf fix issues 2026-03-23 10:54:06 -07:00
Mayukha Vadari
6afcf51dee rename MPToken -> MPTokenIssuance 2026-03-23 10:54:06 -07:00
Mayukha Vadari
7e62ecb63b more usage of IOUToken 2026-03-23 10:54:06 -07:00
Mayukha Vadari
ad2c359f21 more usage of MPToken 2026-03-23 10:54:06 -07:00
Mayukha Vadari
96d4a69a37 add some more helper functions 2026-03-23 10:54:06 -07:00
Mayukha Vadari
14fbdd5a1c implement most of the token stuff 2026-03-23 10:54:06 -07:00
Mayukha Vadari
3c5e87888e establish architecture for creating a new object 2026-03-23 10:53:45 -07:00
Mayukha Vadari
8e218a6285 cleanup 2026-03-23 09:59:13 -07:00
Mayukha Vadari
8a1adc4ec9 fix pre-commit issues 2026-03-23 09:29:38 -07:00
Mayukha Vadari
c9db124015 add readme 2026-03-23 09:23:50 -07:00
Mayukha Vadari
7056b43c5e WrappedSLEBase -> SLEBase 2026-03-23 09:23:43 -07:00
Mayukha Vadari
ee370fefee fix issues 2026-03-23 09:23:02 -07:00
Mayukha Vadari
e68d74cda4 fix account_ 2026-03-23 09:23:01 -07:00
Mayukha Vadari
89a3a6c379 more fixes 2026-03-23 09:22:59 -07:00
Mayukha Vadari
f3a6d17b5b fix long tail issues 2026-03-23 09:21:44 -07:00
Mayukha Vadari
45531b1eaf fix more AccountRoot stuff 2026-03-23 09:21:44 -07:00
Mayukha Vadari
fa8de2b47f change . to -> 2026-03-23 09:21:44 -07:00
Mayukha Vadari
6fcda8802e clean up comments 2026-03-23 09:21:44 -07:00
Mayukha Vadari
0cf42b150c Add insert/update/erase to WritableSLE 2026-03-23 09:21:44 -07:00
Mayukha Vadari
74b010a905 split up read only and write 2026-03-23 09:21:44 -07:00
Mayukha Vadari
265deccabe minor improvements 2026-03-23 09:21:44 -07:00
Mayukha Vadari
ea1146d413 fix remaining build issues 2026-03-23 09:21:44 -07:00
Mayukha Vadari
5fa9bb53a8 fix more build issues 2026-03-23 09:21:44 -07:00
Mayukha Vadari
d9bf263543 fix all the build issues 2026-03-23 09:21:42 -07:00
Mayukha Vadari
1f73da466c First cut of WrappedAccountRoot 2026-03-23 09:21:17 -07:00
Mayukha Vadari
e9eb5e53c9 add WrappedSLEBase 2026-03-23 09:21:15 -07:00
487 changed files with 5704 additions and 5437 deletions

View File

@@ -1,6 +1,4 @@
---
# This entire group of checks was applied to all cpp files but not all header files.
# ---
Checks: "-*,
bugprone-argument-comment,
bugprone-assert-side-effect,
@@ -10,26 +8,26 @@ Checks: "-*,
bugprone-chained-comparison,
bugprone-compare-pointer-to-member-virtual-function,
bugprone-copy-constructor-init,
# bugprone-crtp-constructor-accessibility, # has issues
bugprone-crtp-constructor-accessibility,
bugprone-dangling-handle,
bugprone-dynamic-static-initializers,
# bugprone-empty-catch, # has issues
bugprone-empty-catch,
bugprone-fold-init-type,
# bugprone-forward-declaration-namespace, # has issues
# bugprone-inaccurate-erase,
# bugprone-inc-dec-in-conditions,
# bugprone-incorrect-enable-if,
# bugprone-incorrect-roundings,
# bugprone-infinite-loop,
# bugprone-integer-division,
bugprone-forward-declaration-namespace,
bugprone-inaccurate-erase,
bugprone-inc-dec-in-conditions,
bugprone-incorrect-enable-if,
bugprone-incorrect-roundings,
bugprone-infinite-loop,
bugprone-integer-division,
bugprone-lambda-function-name,
# bugprone-macro-parentheses, # has issues
bugprone-macro-parentheses,
bugprone-macro-repeated-side-effects,
bugprone-misplaced-operator-in-strlen-in-alloc,
bugprone-misplaced-pointer-arithmetic-in-alloc,
bugprone-misplaced-widening-cast,
bugprone-move-forwarding-reference,
# bugprone-multi-level-implicit-pointer-conversion, # has issues
bugprone-multi-level-implicit-pointer-conversion,
bugprone-multiple-new-in-one-expression,
bugprone-multiple-statement-macro,
bugprone-no-escape,
@@ -39,13 +37,13 @@ Checks: "-*,
bugprone-pointer-arithmetic-on-polymorphic-object,
bugprone-posix-return,
bugprone-redundant-branch-condition,
# bugprone-reserved-identifier, # has issues
# bugprone-return-const-ref-from-parameter, # has issues
bugprone-reserved-identifier,
bugprone-return-const-ref-from-parameter,
bugprone-shared-ptr-array-mismatch,
bugprone-signal-handler,
bugprone-signed-char-misuse,
bugprone-sizeof-container,
# bugprone-sizeof-expression, # has issues
bugprone-sizeof-expression,
bugprone-spuriously-wake-up-functions,
bugprone-standalone-empty,
bugprone-string-constructor,
@@ -62,7 +60,7 @@ Checks: "-*,
bugprone-suspicious-string-compare,
bugprone-suspicious-stringview-data-usage,
bugprone-swapped-arguments,
# bugprone-switch-missing-default-case, # has issues
bugprone-switch-missing-default-case,
bugprone-terminating-continue,
bugprone-throw-keyword-missing,
bugprone-too-small-loop-variable,
@@ -73,75 +71,70 @@ Checks: "-*,
bugprone-unhandled-self-assignment,
bugprone-unique-ptr-array-mismatch,
bugprone-unsafe-functions,
# bugprone-use-after-move, # has issues
bugprone-use-after-move,
bugprone-unused-raii,
bugprone-unused-return-value,
bugprone-unused-local-non-trivial-variable,
bugprone-virtual-near-miss,
# cppcoreguidelines-init-variables, # has issues
# cppcoreguidelines-misleading-capture-default-by-value, # has issues
cppcoreguidelines-init-variables,
cppcoreguidelines-misleading-capture-default-by-value,
cppcoreguidelines-no-suspend-with-lock,
# cppcoreguidelines-pro-type-member-init, # has issues
cppcoreguidelines-pro-type-member-init,
cppcoreguidelines-pro-type-static-cast-downcast,
# cppcoreguidelines-rvalue-reference-param-not-moved, # has issues
# cppcoreguidelines-use-default-member-init, # has issues
# cppcoreguidelines-virtual-class-destructor, # has issues
cppcoreguidelines-rvalue-reference-param-not-moved,
cppcoreguidelines-use-default-member-init,
cppcoreguidelines-virtual-class-destructor,
hicpp-ignored-remove-result,
# misc-definitions-in-headers, # has issues
misc-definitions-in-headers,
misc-header-include-cycle,
misc-misplaced-const,
misc-static-assert,
# misc-throw-by-value-catch-by-reference, # has issues
misc-throw-by-value-catch-by-reference,
misc-unused-alias-decls,
misc-unused-using-decls,
modernize-deprecated-headers,
modernize-make-shared,
modernize-make-unique,
performance-faster-string-find,
performance-for-range-copy,
performance-implicit-conversion-in-loop,
performance-inefficient-vector-operation,
performance-move-const-arg,
performance-move-constructor-init,
performance-no-automatic-move,
performance-trivially-destructible,
# readability-avoid-nested-conditional-operator, # has issues
# readability-avoid-return-with-void-value, # has issues
# readability-braces-around-statements, # has issues
# readability-const-return-type, # has issues
# readability-container-contains, # has issues
# readability-container-size-empty, # has issues
# readability-convert-member-functions-to-static, # has issues
readability-avoid-nested-conditional-operator,
readability-avoid-return-with-void-value,
readability-braces-around-statements,
readability-const-return-type,
readability-container-contains,
readability-container-size-empty,
readability-duplicate-include,
# readability-else-after-return, # has issues
# readability-enum-initial-value, # has issues
# readability-implicit-bool-conversion, # has issues
# readability-make-member-function-const, # has issues
# readability-math-missing-parentheses, # has issues
readability-else-after-return,
readability-enum-initial-value,
readability-make-member-function-const,
readability-misleading-indentation,
readability-non-const-parameter,
# readability-redundant-casting, # has issues
# readability-redundant-declaration, # has issues
# readability-redundant-inline-specifier, # has issues
# readability-redundant-member-init, # has issues
readability-redundant-casting,
readability-redundant-declaration,
readability-redundant-inline-specifier,
readability-redundant-member-init,
readability-redundant-string-init,
readability-reference-to-constructed-temporary,
# readability-simplify-boolean-expr, # has issues
# readability-static-definition-in-anonymous-namespace, # has issues
# readability-suspicious-call-argument, # has issues
readability-static-definition-in-anonymous-namespace,
readability-use-std-min-max
"
# ---
# other checks that have issues that need to be resolved:
# checks that have some issues that need to be resolved:
#
# llvm-namespace-comment,
# misc-const-correctness,
# misc-include-cleaner,
# misc-redundant-expression,
#
# readability-inconsistent-declaration-parameter-name, # in this codebase this check will break a lot of arg names
# readability-static-accessed-through-instance, # this check is probably unnecessary. it makes the code less readable
# readability-identifier-naming, # https://github.com/XRPLF/rippled/pull/6571
# readability-convert-member-functions-to-static,
# readability-implicit-bool-conversion,
# readability-inconsistent-declaration-parameter-name,
# readability-identifier-naming,
# readability-math-missing-parentheses,
# readability-simplify-boolean-expr,
# readability-suspicious-call-argument,
# readability-static-accessed-through-instance,
#
# modernize-concat-nested-namespaces,
# modernize-pass-by-value,
@@ -155,6 +148,12 @@ Checks: "-*,
# modernize-use-starts-ends-with,
# modernize-use-std-numbers,
# modernize-use-using,
#
# performance-faster-string-find,
# performance-for-range-copy,
# performance-inefficient-vector-operation,
# performance-move-const-arg,
# performance-no-automatic-move,
# ---
#
CheckOptions:
@@ -196,6 +195,5 @@ CheckOptions:
bugprone-unused-return-value.CheckedReturnTypes: ::std::error_code;::std::error_condition;::std::errc
# misc-include-cleaner.IgnoreHeaders: '.*/(detail|impl)/.*;.*(expected|unexpected).*;.*ranges_lower_bound\.h;time.h;stdlib.h;__chrono/.*;fmt/chrono.h;boost/uuid/uuid_hash.hpp'
#
HeaderFilterRegex: '^.*/(test|xrpl|xrpld)/.*\.(h|hpp)$'
ExcludeHeaderFilterRegex: '^.*/protocol_autogen/.*\.(h|hpp)$'
# HeaderFilterRegex: '^.*/(src|tests)/.*\.(h|hpp)$'
WarningsAsErrors: "*"

View File

@@ -21,7 +21,6 @@ libxrpl.protocol > xrpl.json
libxrpl.protocol > xrpl.protocol
libxrpl.protocol_autogen > xrpl.protocol_autogen
libxrpl.rdb > xrpl.basics
libxrpl.rdb > xrpl.core
libxrpl.rdb > xrpl.rdb
libxrpl.resource > xrpl.basics
libxrpl.resource > xrpl.json
@@ -91,7 +90,6 @@ test.core > xrpl.server
test.csf > xrpl.basics
test.csf > xrpld.consensus
test.csf > xrpl.json
test.csf > xrpl.ledger
test.csf > xrpl.protocol
test.json > test.jtx
test.json > xrpl.json
@@ -110,6 +108,7 @@ test.jtx > xrpl.tx
test.ledger > test.jtx
test.ledger > test.toplevel
test.ledger > xrpl.basics
test.ledger > xrpld.app
test.ledger > xrpld.core
test.ledger > xrpl.ledger
test.ledger > xrpl.protocol
@@ -126,7 +125,6 @@ test.overlay > xrpl.basics
test.overlay > xrpld.app
test.overlay > xrpld.overlay
test.overlay > xrpld.peerfinder
test.overlay > xrpl.ledger
test.overlay > xrpl.nodestore
test.overlay > xrpl.protocol
test.overlay > xrpl.shamap
@@ -185,6 +183,7 @@ xrpl.conditions > xrpl.basics
xrpl.conditions > xrpl.protocol
xrpl.core > xrpl.basics
xrpl.core > xrpl.json
xrpl.core > xrpl.ledger
xrpl.core > xrpl.protocol
xrpl.json > xrpl.basics
xrpl.ledger > xrpl.basics
@@ -235,7 +234,6 @@ xrpld.app > xrpl.shamap
xrpld.app > xrpl.tx
xrpld.consensus > xrpl.basics
xrpld.consensus > xrpl.json
xrpld.consensus > xrpl.ledger
xrpld.consensus > xrpl.protocol
xrpld.core > xrpl.basics
xrpld.core > xrpl.core

View File

@@ -1,25 +0,0 @@
name: Label PRs with merge conflicts
on:
# So that PRs touching the same files as the push are updated.
push:
# So that the `dirtyLabel` is removed if conflicts are resolved.
# We recommend `pull_request_target` so that github secrets are available.
# In `pull_request` we wouldn't be able to change labels of fork PRs.
pull_request_target:
types: [synchronize]
permissions:
pull-requests: write
jobs:
main:
runs-on: ubuntu-latest
steps:
- name: Check if PRs are dirty
uses: eps1lon/actions-label-merge-conflict@1df065ebe6e3310545d4f4c4e862e43bdca146f0 # v3.0.3
with:
dirtyLabel: "PR: has conflicts"
repoToken: "${{ secrets.GITHUB_TOKEN }}"
commentOnDirty: "This PR has conflicts, please resolve them in order for the PR to be reviewed."
commentOnClean: "All conflicts have been resolved. Assigned reviewers can now start or resume their review."

View File

@@ -6,6 +6,7 @@ on:
push:
branches:
- "develop"
- "release*"
paths:
- ".github/workflows/publish-docs.yml"
- "*.md"
@@ -81,13 +82,13 @@ jobs:
cmake --build . --target docs --parallel ${BUILD_NPROC}
- name: Create documentation artifact
if: ${{ github.event.repository.visibility == 'public' && github.event_name == 'push' }}
if: ${{ github.event_name == 'push' }}
uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b # v4.0.0
with:
path: ${{ env.BUILD_DIR }}/docs/html
deploy:
if: ${{ github.event.repository.visibility == 'public' && github.event_name == 'push' }}
if: ${{ github.event_name == 'push' }}
needs: build
runs-on: ubuntu-latest
permissions:
@@ -99,4 +100,4 @@ jobs:
steps:
- name: Deploy to GitHub Pages
id: deploy
uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0
uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4.0.5

View File

@@ -199,7 +199,7 @@ jobs:
fi
- name: Upload the binary (Linux)
if: ${{ github.event.repository.visibility == 'public' && runner.os == 'Linux' }}
if: ${{ github.repository == 'XRPLF/rippled' && runner.os == 'Linux' }}
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: xrpld-${{ inputs.config_name }}
@@ -298,7 +298,7 @@ jobs:
- name: Upload coverage report
if: ${{ github.repository == 'XRPLF/rippled' && !inputs.build_only && env.COVERAGE_ENABLED == 'true' }}
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0
uses: codecov/codecov-action@1af58845a975a7985b0beb0cbe6fbbb71a41dbad # v5.5.3
with:
disable_search: true
disable_telem: true

View File

@@ -83,7 +83,7 @@ jobs:
run-clang-tidy -j ${{ steps.nproc.outputs.nproc }} -p "${BUILD_DIR}" ${TARGETS} 2>&1 | tee clang-tidy-output.txt
- name: Upload clang-tidy output
if: ${{ github.event.repository.visibility == 'public' && steps.run_clang_tidy.outcome != 'success' }}
if: steps.run_clang_tidy.outcome != 'success'
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: clang-tidy-results

View File

@@ -51,5 +51,5 @@ jobs:
if: ${{ always() && !cancelled() && (!inputs.check_only_changed || needs.determine-files.outputs.any_cpp_changed == 'true' || needs.determine-files.outputs.clang_tidy_config_changed == 'true') }}
uses: ./.github/workflows/reusable-clang-tidy-files.yml
with:
files: ${{ (needs.determine-files.outputs.clang_tidy_config_changed != 'true' && inputs.check_only_changed) && needs.determine-files.outputs.all_changed_files || '' }}
files: ${{ needs.determine-files.outputs.clang_tidy_config_changed == 'true' && '' || (inputs.check_only_changed && needs.determine-files.outputs.all_changed_files || '') }}
create_issue_on_failure: ${{ inputs.create_issue_on_failure }}

2
.gitignore vendored
View File

@@ -71,8 +71,6 @@ DerivedData
/.zed/
# AI tools.
/.agent
/.agents
/.augment
/.claude
/CLAUDE.md

View File

@@ -125,9 +125,9 @@ 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.
The recipes in Conan Center occasionally need to be patched for compatibility
with the latest version of `xrpld`. We maintain a fork of the Conan Center
[here](https://github.com/XRPLF/conan-center-index/) containing the patches.
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
@@ -137,11 +137,19 @@ can do this by running:
conan remote add --index 0 xrplf https://conan.ripplex.io
```
Alternatively, you can pull our recipes from the repository and export them locally:
Alternatively, you can pull the patched recipes into the repository and use them
locally:
```bash
# Extract the version number from the lockfile.
function extract_version {
version=$(cat conan.lock | sed -nE "s@.+${1}/(.+)#.+@\1@p" | head -n1)
echo ${version}
}
# Define which recipes to export.
recipes=('abseil' 'ed25519' 'grpc' 'm4' 'mpt-crypto' 'nudb' 'openssl' 'secp256k1' 'snappy' 'soci' 'wasm-xrplf' 'wasmi')
recipes=('ed25519' 'grpc' 'nudb' 'openssl' 'secp256k1' 'snappy' 'soci')
folders=('all' 'all' 'all' '3.x.x' 'all' 'all' 'all')
# Selectively check out the recipes from our CCI fork.
cd external
@@ -150,19 +158,29 @@ 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}
for ((index = 1; index <= ${#recipes[@]}; index++)); do
recipe=${recipes[index]}
folder=${folders[index]}
echo "Checking out recipe '${recipe}' from folder '${folder}'..."
git sparse-checkout add recipes/${recipe}/${folder}
done
git fetch origin master
git checkout master
cd ../..
./export_all.sh
cd ../../
# Export the recipes into the local cache.
for ((index = 1; index <= ${#recipes[@]}; index++)); do
recipe=${recipes[index]}
folder=${folders[index]}
version=$(extract_version ${recipe})
echo "Exporting '${recipe}/${version}' from '${recipe}/${folder}'..."
conan export --version $(extract_version ${recipe}) \
external/conan-center-index/recipes/${recipe}/${folder}
done
```
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
patch, 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.
@@ -171,8 +189,6 @@ the new recipe will be automatically pulled from the official Conan Center.
> 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
@@ -188,14 +204,39 @@ Possible values are ['5.0', '5.1', '6.0', '6.1', '7.0', '7.3', '8.0', '8.1',
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)
you need to amend the list of compiler versions in
`$(conan config home)/settings.yml`, by appending the required version number(s)
to the `version` array specific for your compiler. For example:
```yaml
compiler:
apple-clang:
version: ["17.0"]
apple-clang:
version:
[
"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",
"17",
"17.0",
]
```
#### Multiple compilers

View File

@@ -259,10 +259,6 @@ There is a Continuous Integration job that runs clang-tidy on pull requests. The
This ensures that configuration changes don't introduce new warnings across the codebase.
### Installing clang-tidy
See the [environment setup guide](./docs/build/environment.md#clang-tidy) for platform-specific installation instructions.
### Running clang-tidy locally
Before running clang-tidy, you must build the project to generate required files (particularly protobuf headers). Refer to [`BUILD.md`](./BUILD.md) for build instructions.
@@ -270,15 +266,10 @@ Before running clang-tidy, you must build the project to generate required files
Then run clang-tidy on your local changes:
```
run-clang-tidy -p build src include tests
run-clang-tidy -p build src tests
```
This will check all source files in the `src`, `include` and `tests` directories using the compile commands from your `build` directory.
If you wish to automatically fix whatever clang-tidy finds _and_ is capable of fixing, add `-fix` to the above command:
```
run-clang-tidy -p build -fix src include tests
```
This will check all source files in the `src` and `tests` directories using the compile commands from your `build` directory.
## Contracts and instrumentation

View File

@@ -1,7 +1,7 @@
ISC License
Copyright (c) 2011, Arthur Britto, David Schwartz, Jed McCaleb, Vinnie Falco, Bob Way, Eric Lombrozo, Nikolaos D. Bougalis, Howard Hinnant.
Copyright (c) 2012-present, the XRP Ledger developers.
Copyright (c) 2012-2025, the XRP Ledger developers.
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above

View File

@@ -140,28 +140,6 @@ function(setup_protocol_autogen)
)
endif()
# Check pip index URL configuration
execute_process(
COMMAND ${VENV_PIP} config get global.index-url
OUTPUT_VARIABLE PIP_INDEX_URL
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET
)
# Default PyPI URL
set(DEFAULT_PIP_INDEX "https://pypi.org/simple")
# Show warning if using non-default index
if(PIP_INDEX_URL AND NOT PIP_INDEX_URL STREQUAL "")
if(NOT PIP_INDEX_URL STREQUAL DEFAULT_PIP_INDEX)
message(
WARNING
"Private pip index URL detected: ${PIP_INDEX_URL}\n"
"You may need to connect to VPN to access this URL."
)
endif()
endif()
message(STATUS "Installing Python dependencies...")
execute_process(
COMMAND ${VENV_PIP} install --upgrade pip

View File

@@ -1,16 +1,16 @@
{
"version": "0.5",
"requires": [
"zlib/1.3.1#cac0f6daea041b0ccf42934163defb20%1774439233.809",
"zlib/1.3.1#b8bc2603263cf7eccbd6e17e66b0ed76%1765850150.075",
"xxhash/0.8.3#681d36a0a6111fc56e5e45ea182c19cc%1765850149.987",
"sqlite3/3.51.0#66aa11eabd0e34954c5c1c061ad44abe%1763899256.358",
"soci/4.0.3#fe32b9ad5eb47e79ab9e45a68f363945%1774450067.231",
"sqlite3/3.49.1#8631739a4c9b93bd3d6b753bac548a63%1765850149.926",
"soci/4.0.3#a9f8d773cd33e356b5879a4b0564f287%1765850149.46",
"snappy/1.1.10#968fef506ff261592ec30c574d4a7809%1765850147.878",
"secp256k1/0.7.1#481881709eb0bdd0185a12b912bbe8ad%1770910500.329",
"secp256k1/0.7.1#3a61e95e220062ef32c48d019e9c81f7%1770306721.686",
"rocksdb/10.5.1#4a197eca381a3e5ae8adf8cffa5aacd0%1765850186.86",
"re2/20251105#8579cfd0bda4daf0683f9e3898f964b4%1774398111.888",
"protobuf/6.33.5#d96d52ba5baaaa532f47bda866ad87a5%1773224203.27",
"openssl/3.6.1#e6399de266349245a4542fc5f6c71552%1774458290.139",
"re2/20230301#ca3b241baec15bd31ea9187150e0b333%1765850148.103",
"protobuf/6.32.1#f481fd276fc23a33b85a3ed1e898b693%1765850161.038",
"openssl/3.5.5#05a4ac5b7323f7a329b2db1391d9941f%1769599205.414",
"nudb/2.0.9#0432758a24204da08fee953ec9ea03cb%1769436073.32",
"lz4/1.10.0#59fc63cac7f10fbe8e05c7e62c2f3504%1765850143.914",
"libiconv/1.17#1e65319e945f2d31941a9d28cc13c058%1765842973.492",
@@ -18,26 +18,27 @@
"libarchive/3.8.1#ffee18995c706e02bf96e7a2f7042e0d%1765850144.736",
"jemalloc/5.3.0#e951da9cf599e956cebc117880d2d9f8%1729241615.244",
"gtest/1.17.0#5224b3b3ff3b4ce1133cbdd27d53ee7d%1768312129.152",
"grpc/1.78.1#b1a9e74b145cc471bed4dc64dc6eb2c1%1772623605.068",
"grpc/1.72.0#f244a57bff01e708c55a1100b12e1589%1765850193.734",
"ed25519/2015.03#ae761bdc52730a843f0809bdf6c1b1f6%1765850143.772",
"date/3.0.4#862e11e80030356b53c2c38599ceb32b%1765850143.772",
"c-ares/1.34.6#545240bb1c40e2cacd4362d6b8967650%1774439234.681",
"c-ares/1.34.5#5581c2b62a608b40bb85d965ab3ec7c8%1765850144.336",
"bzip2/1.0.8#c470882369c2d95c5c77e970c0c7e321%1765850143.837",
"boost/1.90.0#d5e8defe7355494953be18524a7f135b%1769454080.269",
"abseil/20250127.0#bb0baf1f362bc4a725a24eddd419b8f7%1774365460.196"
"abseil/20250127.0#99262a368bd01c0ccca8790dfced9719%1766517936.993"
],
"build_requires": [
"zlib/1.3.1#cac0f6daea041b0ccf42934163defb20%1774439233.809",
"strawberryperl/5.32.1.1#8d114504d172cfea8ea1662d09b6333e%1774447376.964",
"protobuf/6.33.5#d96d52ba5baaaa532f47bda866ad87a5%1773224203.27",
"zlib/1.3.1#b8bc2603263cf7eccbd6e17e66b0ed76%1765850150.075",
"strawberryperl/5.32.1.1#707032463aa0620fa17ec0d887f5fe41%1765850165.196",
"protobuf/6.32.1#f481fd276fc23a33b85a3ed1e898b693%1765850161.038",
"nasm/2.16.01#31e26f2ee3c4346ecd347911bd126904%1765850144.707",
"msys2/cci.latest#d22fe7b2808f5fd34d0a7923ace9c54f%1770657326.649",
"m4/1.4.19#5d7a4994e5875d76faf7acf3ed056036%1774365463.87",
"cmake/4.3.0#b939a42e98f593fb34d3a8c5cc860359%1774439249.183",
"b2/5.4.2#ffd6084a119587e70f11cd45d1a386e2%1774439233.447",
"msys2/cci.latest#eea83308ad7e9023f7318c60d5a9e6cb%1770199879.083",
"m4/1.4.19#70dc8bbb33e981d119d2acc0175cf381%1763158052.846",
"cmake/4.2.0#ae0a44f44a1ef9ab68fd4b3e9a1f8671%1765850153.937",
"cmake/3.31.10#313d16a1aa16bbdb2ca0792467214b76%1765850153.479",
"b2/5.3.3#107c15377719889654eb9a162a673975%1765850144.355",
"automake/1.16.5#b91b7c384c3deaa9d535be02da14d04f%1755524470.56",
"autoconf/2.71#51077f068e61700d65bb05541ea1e4b0%1731054366.86",
"abseil/20250127.0#bb0baf1f362bc4a725a24eddd419b8f7%1774365460.196"
"abseil/20250127.0#99262a368bd01c0ccca8790dfced9719%1766517936.993"
],
"python_requires": [],
"overrides": {
@@ -45,14 +46,14 @@
null,
"boost/1.90.0"
],
"protobuf/[>=5.27.0 <7]": [
"protobuf/6.33.5"
"protobuf/5.27.0": [
"protobuf/6.32.1"
],
"lz4/1.9.4": [
"lz4/1.10.0"
],
"sqlite3/[>=3.44 <4]": [
"sqlite3/3.51.0"
"sqlite3/3.44.2": [
"sqlite3/3.49.1"
],
"boost/1.83.0": [
"boost/1.90.0"

View File

@@ -1,9 +1,10 @@
import os
import re
import os
from conan.tools.cmake import CMake, CMakeToolchain, cmake_layout
from conan import ConanFile
from conan import __version__ as conan_version
class Xrpl(ConanFile):
@@ -29,10 +30,10 @@ class Xrpl(ConanFile):
requires = [
"ed25519/2015.03",
"grpc/1.78.1",
"grpc/1.72.0",
"libarchive/3.8.1",
"nudb/2.0.9",
"openssl/3.6.1",
"openssl/3.5.5",
"secp256k1/0.7.1",
"soci/4.0.3",
"zlib/1.3.1",
@@ -43,7 +44,7 @@ class Xrpl(ConanFile):
]
tool_requires = [
"protobuf/6.33.5",
"protobuf/6.32.1",
]
default_options = {
@@ -136,16 +137,20 @@ class Xrpl(ConanFile):
self.default_options["fPIC"] = False
def requirements(self):
self.requires("boost/1.90.0", force=True, transitive_headers=True)
self.requires("date/3.0.4", transitive_headers=True)
# Conan 2 requires transitive headers to be specified
transitive_headers_opt = (
{"transitive_headers": True} if conan_version.split(".")[0] == "2" else {}
)
self.requires("boost/1.90.0", force=True, **transitive_headers_opt)
self.requires("date/3.0.4", **transitive_headers_opt)
self.requires("lz4/1.10.0", force=True)
self.requires("protobuf/6.33.5", force=True)
self.requires("sqlite3/3.51.0", force=True)
self.requires("protobuf/6.32.1", force=True)
self.requires("sqlite3/3.49.1", force=True)
if self.options.jemalloc:
self.requires("jemalloc/5.3.0")
if self.options.rocksdb:
self.requires("rocksdb/10.5.1")
self.requires("xxhash/0.8.3", transitive_headers=True)
self.requires("xxhash/0.8.3", **transitive_headers_opt)
exports_sources = (
"CMakeLists.txt",

View File

@@ -297,7 +297,6 @@ words:
- venv
- vfalco
- vinnie
- wasmi
- wextra
- wptr
- writeme

View File

@@ -109,32 +109,3 @@ Install CMake with Homebrew too:
```
brew install cmake
```
## 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.
### 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.

View File

@@ -15,7 +15,7 @@
#define ALWAYS_OR_UNREACHABLE(cond, message) assert((message) && (cond))
#define SOMETIMES(cond, message, ...)
#define REACHABLE(message, ...)
#define UNREACHABLE(message, ...) assert((message) && false) // NOLINT(misc-static-assert)
#define UNREACHABLE(message, ...) assert((message) && false)
#endif
#define XRPL_ASSERT ALWAYS_OR_UNREACHABLE

View File

@@ -70,24 +70,14 @@ JobQueue::Coro::resume()
running_ = true;
}
{
std::lock_guard lk(jq_.m_mutex);
std::lock_guard lock(jq_.m_mutex);
--jq_.nSuspend_;
}
auto saved = detail::getLocalValues().release();
detail::getLocalValues().reset(&lvs_);
std::lock_guard lock(mutex_);
// A late resume() can arrive after the coroutine has already completed.
// This is an expected (if rare) outcome of the race condition documented
// in JobQueue.h:354-377 where post() schedules a resume job before the
// coroutine yields — the mutex serializes access, but by the time this
// resume() acquires the lock the coroutine may have already run to
// completion. Calling operator() on a completed boost::coroutine2 is
// undefined behavior, so we must check and skip invoking the coroutine
// body if it has already completed.
if (coro_)
{
coro_();
}
XRPL_ASSERT(static_cast<bool>(coro_), "xrpl::JobQueue::Coro::resume : is runnable");
coro_();
detail::getLocalValues().release();
detail::getLocalValues().reset(saved);
std::lock_guard lk(mutex_run_);

View File

@@ -99,8 +99,8 @@ public:
Effects:
The coroutine continues execution from where it last left off
using this same thread.
If the coroutine has already completed, returns immediately
(handles the documented post-before-yield race condition).
Undefined behavior if called after the coroutine has completed
with a return (as opposed to a yield()).
Undefined behavior if resume() or post() called consecutively
without a corresponding yield.
*/
@@ -316,7 +316,7 @@ private:
// Returns the limit of running jobs for the given job type.
// For jobs with no limit, we return the largest int. Hopefully that
// will be enough.
static int
int
getJobLimit(JobType type);
};
@@ -357,10 +357,8 @@ private:
If the post() job were to be executed before yield(), undefined behavior
would occur. The lock ensures that coro_ is not called again until we exit
the coroutine. At which point a scheduled resume() job waiting on the lock
would gain entry. resume() checks if the coroutine has already completed
(coro_ converts to false) and, if so, skips invoking operator() since
calling operator() on a completed boost::coroutine2 pull_type is undefined
behavior.
would gain entry, harmlessly call coro_ and immediately return as we have
already completed the coroutine.
The race condition occurs as follows:

View File

@@ -3,6 +3,7 @@
#include <xrpl/basics/Blob.h>
#include <xrpl/basics/SHAMapHash.h>
#include <xrpl/basics/TaggedCache.h>
#include <xrpl/ledger/CachedSLEs.h>
#include <boost/asio.hpp>
@@ -22,20 +23,6 @@ class PerfLog;
// This is temporary until we migrate all code to use ServiceRegistry.
class Application;
template <
class Key,
class T,
bool IsKeyCache,
class SharedWeakUnionPointer,
class SharedPointerType,
class Hash,
class KeyEqual,
class Mutex>
class TaggedCache;
class STLedgerEntry;
using SLE = STLedgerEntry;
using CachedSLEs = TaggedCache<uint256, SLE const>;
// Forward declarations
class AcceptedLedger;
class AmendmentTable;
@@ -58,7 +45,7 @@ class NetworkIDService;
class OpenLedger;
class OrderBookDB;
class Overlay;
class PathRequestManager;
class PathRequests;
class PeerReservationTable;
class PendingSaves;
class RelationalDatabase;
@@ -102,7 +89,7 @@ public:
getNodeFamily() = 0;
virtual TimeKeeper&
getTimeKeeper() = 0;
timeKeeper() = 0;
virtual JobQueue&
getJobQueue() = 0;
@@ -111,7 +98,7 @@ public:
getTempNodeCache() = 0;
virtual CachedSLEs&
getCachedSLEs() = 0;
cachedSLEs() = 0;
virtual NetworkIDService&
getNetworkIDService() = 0;
@@ -133,26 +120,26 @@ public:
getValidations() = 0;
virtual ValidatorList&
getValidators() = 0;
validators() = 0;
virtual ValidatorSite&
getValidatorSites() = 0;
validatorSites() = 0;
virtual ManifestCache&
getValidatorManifests() = 0;
validatorManifests() = 0;
virtual ManifestCache&
getPublisherManifests() = 0;
publisherManifests() = 0;
// Network services
virtual Overlay&
getOverlay() = 0;
overlay() = 0;
virtual Cluster&
getCluster() = 0;
cluster() = 0;
virtual PeerReservationTable&
getPeerReservations() = 0;
peerReservations() = 0;
virtual Resource::Manager&
getResourceManager() = 0;
@@ -187,13 +174,13 @@ public:
getLedgerReplayer() = 0;
virtual PendingSaves&
getPendingSaves() = 0;
pendingSaves() = 0;
virtual OpenLedger&
getOpenLedger() = 0;
openLedger() = 0;
virtual OpenLedger const&
getOpenLedger() const = 0;
openLedger() const = 0;
// Transaction and operation services
virtual NetworkOPs&
@@ -208,8 +195,8 @@ public:
virtual TxQ&
getTxQ() = 0;
virtual PathRequestManager&
getPathRequestManager() = 0;
virtual PathRequests&
getPathRequests() = 0;
// Server services
virtual ServerHandler&
@@ -223,16 +210,16 @@ public:
isStopping() const = 0;
virtual beast::Journal
getJournal(std::string const& name) = 0;
journal(std::string const& name) = 0;
virtual boost::asio::io_context&
getIOContext() = 0;
virtual Logs&
getLogs() = 0;
logs() = 0;
virtual std::optional<uint256> const&
getTrapTxID() const = 0;
trapTxID() const = 0;
/** Retrieve the "wallet database" */
virtual DatabaseCon&
@@ -241,7 +228,7 @@ public:
// Temporary: Get the underlying Application for functions that haven't
// been migrated yet. This should be removed once all code is migrated.
virtual Application&
getApp() = 0;
app() = 0;
};
} // namespace xrpl

View File

@@ -5,7 +5,7 @@
namespace xrpl {
enum class StartUpType { Fresh, Normal, Load, LoadFile, Replay, Network };
enum class StartUpType { FRESH, NORMAL, LOAD, LOAD_FILE, REPLAY, NETWORK };
inline std::ostream&
operator<<(std::ostream& os, StartUpType const& type)

View File

@@ -76,33 +76,16 @@ public:
@return true if a book from this issue to XRP exists
*/
virtual bool
isBookToXRP(Issue const& issue, std::optional<Domain> const& domain = std::nullopt) = 0;
isBookToXRP(Issue const& issue, std::optional<Domain> domain = std::nullopt) = 0;
/**
* Process a transaction for order book tracking.
* @param ledger The ledger the transaction was applied to
* @param alTx The transaction to process
* @param jvObj The JSON object of the transaction
*/
virtual void
processTxn(
std::shared_ptr<ReadView const> const& ledger,
AcceptedLedgerTx const& alTx,
MultiApiJson const& jvObj) = 0;
/**
* Get the book listeners for a book.
* @param book The book to get the listeners for
* @return The book listeners for the book
*/
virtual BookListeners::pointer
getBookListeners(Book const&) = 0;
/**
* Create a new book listeners for a book.
* @param book The book to create the listeners for
* @return The new book listeners for the book
*/
virtual BookListeners::pointer
makeBookListeners(Book const&) = 0;
};

View File

@@ -2,19 +2,28 @@
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/OpenView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/helpers/OfferHelpers.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/ledger/helpers/VaultHelpers.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/MPTIssue.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/Rate.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/STObject.h>
#include <xrpl/protocol/Serializer.h>
#include <xrpl/protocol/TER.h>
#include <cstdint>
#include <functional>
#include <initializer_list>
#include <map>
#include <memory>
#include <optional>
#include <set>
#include <utility>
namespace xrpl {
@@ -157,7 +166,7 @@ canWithdraw(
ReadView const& view,
AccountID const& from,
AccountID const& to,
SLE::const_ref toSle,
AccountRoot const& toWrapped,
STAmount const& amount,
bool hasDestinationTag);

View File

@@ -2,8 +2,10 @@
#include <xrpl/basics/Expected.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/SLEBase.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Rate.h>
#include <xrpl/protocol/STLedgerEntry.h>
@@ -15,40 +17,128 @@
namespace xrpl {
/** Check if the issuer has the global freeze flag set.
@param issuer The account to check
@return true if the account has global freeze set
*/
[[nodiscard]] bool
isGlobalFrozen(ReadView const& view, AccountID const& issuer);
// Calculate liquid XRP balance for an account.
// This function may be used to calculate the amount of XRP that
// the holder is able to freely spend. It subtracts reserve requirements.
//
// ownerCountAdj adjusts the owner count in case the caller calculates
// before ledger entries are added or removed. Positive to add, negative
// to subtract.
//
// @param ownerCountAdj positive to add to count, negative to reduce count.
[[nodiscard]] XRPAmount
xrpLiquid(ReadView const& view, AccountID const& id, std::int32_t ownerCountAdj, beast::Journal j);
/** Adjust the owner count up or down. */
void
adjustOwnerCount(
ApplyView& view,
std::shared_ptr<SLE> const& 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
* is represented as 1,010,000,000.
* @param issuer The IOU issuer
/**
* Read-only wrapper for AccountRoot ledger entries.
*
* Provides read-only access to account data.
*/
[[nodiscard]] Rate
transferRate(ReadView const& view, AccountID const& issuer);
class AccountRoot : public ReadOnlySLE
{
protected:
AccountID const id_;
public:
AccountRoot(AccountID const& id, ReadView const& view)
: ReadOnlySLE(view.read(keylet::account(id)), view), id_(id)
{
}
AccountID const&
id() const
{
return id_;
}
/** Check if the issuer has the global freeze flag set.
@return true if the account has global freeze set
*/
[[nodiscard]] bool
isGlobalFrozen() const;
/** Returns IOU issuer transfer fee as Rate. Rate specifies
* the fee as fractions of 1 billion. For example, 1% transfer rate
* is represented as 1,010,000,000.
*/
[[nodiscard]] Rate
transferRate() const;
// Calculate liquid XRP balance for an account.
// This function may be used to calculate the amount of XRP that
// the holder is able to freely spend. It subtracts reserve requirements.
//
// ownerCountAdj adjusts the owner count in case the caller calculates
// before ledger entries are added or removed. Positive to add, negative
// to subtract.
//
// @param ownerCountAdj positive to add to count, negative to reduce count.
[[nodiscard]] XRPAmount
xrpLiquid(std::int32_t ownerCountAdj, beast::Journal j) const;
/** Checks the destination and tag.
- Checks that the SLE is not null.
- If the SLE requires a destination tag, checks that there is a tag.
*/
[[nodiscard]] TER
checkDestinationAndTag(bool hasDestinationTag) const;
/** Returns true if and only if sleAcct is a pseudo-account or specific
pseudo-accounts in pseudoFieldFilter.
Returns false if sleAcct is:
- NOT a pseudo-account OR
- NOT a ltACCOUNT_ROOT OR
- null pointer
*/
[[nodiscard]] bool
isPseudoAccount(std::set<SField const*> const& pseudoFieldFilter = {}) const;
[[nodiscard]] bool
operator==(AccountRoot const& other) const
{
return id_ == other.id_;
}
[[nodiscard]] bool
operator==(AccountID const& other) const
{
return id_ == other;
}
};
/**
* Writable wrapper for AccountRoot ledger entries.
*
* Provides read-write access to account data.
* Inherits from AccountRoot to reuse read-only methods,
* and adds write capabilities.
*/
class WritableAccountRoot : public AccountRoot, public WritableSLE
{
public:
WritableAccountRoot(AccountID const& id, ApplyView& view)
: AccountRoot(id, view), WritableSLE(keylet::account(id), view)
{
}
/** Create a WritableAccountRoot backed by a brand-new SLE
* (not yet inserted into the view).
*/
[[nodiscard]] static WritableAccountRoot
makeNew(AccountID const& id, ApplyView& view)
{
return WritableAccountRoot(id, view, std::make_shared<SLE>(keylet::account(id)));
}
private:
// This is a private constructor only used by `makeNew`
WritableAccountRoot(AccountID const& id, ApplyView& view, std::shared_ptr<SLE> sle)
: AccountRoot(id, view), WritableSLE(std::move(sle), view)
{
insert();
}
public:
// Resolve ambiguity: use writable operator-> for non-const, read-only for const
using WritableSLE::operator->;
using AccountRoot::operator->;
using WritableSLE::operator*;
using AccountRoot::operator*;
/** Adjust the owner count up or down. */
void
adjustOwnerCount(std::int32_t amount, beast::Journal j);
};
/** Generate a pseudo-account address from a pseudo owner key.
@param pseudoOwnerKey The key to generate the address from
@@ -87,7 +177,10 @@ isPseudoAccount(
AccountID const& accountId,
std::set<SField const*> const& pseudoFieldFilter = {})
{
return isPseudoAccount(view.read(keylet::account(accountId)), pseudoFieldFilter);
AccountRoot const acct(accountId, view);
if (!acct)
return false;
return acct.isPseudoAccount(pseudoFieldFilter);
}
/**
@@ -101,12 +194,4 @@ isPseudoAccount(
[[nodiscard]] Expected<std::shared_ptr<SLE>, TER>
createPseudoAccount(ApplyView& view, uint256 const& pseudoOwnerKey, SField const& ownerField);
/** Checks the destination and tag.
- Checks that the SLE is not null.
- If the SLE requires a destination tag, checks that there is a tag.
*/
[[nodiscard]] TER
checkDestinationAndTag(SLE::const_ref toSle, bool hasDestinationTag);
} // namespace xrpl

View File

@@ -5,6 +5,7 @@
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/STArray.h>
#include <xrpl/protocol/STTx.h>
@@ -73,8 +74,7 @@ verifyDepositPreauth(
STTx const& tx,
ApplyView& view,
AccountID const& src,
AccountID const& dst,
std::shared_ptr<SLE const> const& sleDst,
AccountRoot const& dst,
beast::Journal j);
} // namespace xrpl

View File

@@ -14,126 +14,238 @@
namespace xrpl {
//------------------------------------------------------------------------------
//
// Freeze checking (MPT-specific)
//
//------------------------------------------------------------------------------
class MPTokenIssuance : public virtual TokenBase
{
public:
MPTokenIssuance(ReadView const& view, MPTIssue const& mptIssue)
: ReadOnlySLE(view.read(keylet::mptIssuance(mptIssue.getMptID())), view)
, TokenBase(view, view.read(keylet::mptIssuance(mptIssue.getMptID())))
, mptID_(mptIssue.getMptID())
, mptIssue_(mptIssue)
{
}
[[nodiscard]] bool
isGlobalFrozen(ReadView const& view, MPTIssue const& mptIssue);
MPTokenIssuance(ReadView const& view, MPTID const& mptID)
: ReadOnlySLE(view.read(keylet::mptIssuance(mptID)), view)
, TokenBase(view, view.read(keylet::mptIssuance(mptID)))
, mptID_(mptID)
, mptIssue_(MPTIssue(mptID_))
{
}
[[nodiscard]] bool
isIndividualFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue);
MPTID const&
getMptID() const
{
return mptID_;
}
[[nodiscard]] bool
isFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue, int depth = 0);
MPTIssue const&
getMptIssue() const
{
return mptIssue_;
}
[[nodiscard]] bool
isAnyFrozen(
ReadView const& view,
std::initializer_list<AccountID> const& accounts,
MPTIssue const& mptIssue,
int depth = 0);
AccountID const&
getIssuer() const
{
return mptIssue_.getIssuer();
}
//------------------------------------------------------------------------------
//
// Transfer rate (MPT-specific)
//
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
//
// Freeze checking (MPT-specific)
//
//------------------------------------------------------------------------------
/** Returns MPT transfer fee as Rate. Rate specifies
* the fee as fractions of 1 billion. For example, 1% transfer rate
* is represented as 1,010,000,000.
* @param issuanceID MPTokenIssuanceID of MPTTokenIssuance object
*/
[[nodiscard]] Rate
transferRate(ReadView const& view, MPTID const& issuanceID);
[[nodiscard]] bool
isGlobalFrozen() const override;
//------------------------------------------------------------------------------
//
// Holding checks (MPT-specific)
//
//------------------------------------------------------------------------------
[[nodiscard]] bool
isIndividualFrozen(AccountID const& account) const override;
[[nodiscard]] TER
canAddHolding(ReadView const& view, MPTIssue const& mptIssue);
[[nodiscard]] bool
isFrozen(AccountID const& account, int depth = 0) const override;
//------------------------------------------------------------------------------
//
// Authorization (MPT-specific)
//
//------------------------------------------------------------------------------
[[nodiscard]] TER
checkFrozen(AccountID const& account) const override;
[[nodiscard]] TER
authorizeMPToken(
ApplyView& view,
XRPAmount const& priorBalance,
MPTID const& mptIssuanceID,
AccountID const& account,
beast::Journal journal,
std::uint32_t flags = 0,
std::optional<AccountID> holderID = std::nullopt);
[[nodiscard]] bool
isAnyFrozen(std::initializer_list<AccountID> const& accounts, int depth = 0) const override;
/** Check if the account lacks required authorization for MPT.
*
* requireAuth check is recursive for MPT shares in a vault, descending to
* assets in the vault, up to maxAssetCheckDepth recursion depth. This is
* purely defensive, as we currently do not allow such vaults to be created.
*/
[[nodiscard]] TER
requireAuth(
ReadView const& view,
MPTIssue const& mptIssue,
AccountID const& account,
AuthType authType = AuthType::Legacy,
int depth = 0);
[[nodiscard]] bool
isDeepFrozen(AccountID const& account, int depth = 0) const override;
/** Enforce account has MPToken to match its authorization.
*
* Called from doApply - it will check for expired (and delete if found any)
* credentials matching DomainID set in MPTokenIssuance. Must be called if
* requireAuth(...MPTIssue...) returned tesSUCCESS or tecEXPIRED in preclaim.
*/
[[nodiscard]] TER
enforceMPTokenAuthorization(
ApplyView& view,
MPTID const& mptIssuanceID,
AccountID const& account,
XRPAmount const& priorBalance,
beast::Journal j);
[[nodiscard]] TER
checkDeepFrozen(AccountID const& account) const override;
/** Check if the destination account is allowed
* to receive MPT. Return tecNO_AUTH if it doesn't
* and tesSUCCESS otherwise.
*/
[[nodiscard]] TER
canTransfer(
ReadView const& view,
MPTIssue const& mptIssue,
AccountID const& from,
AccountID const& to);
//------------------------------------------------------------------------------
//
// Transfer rate (MPT-specific)
//
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
//
// Empty holding operations (MPT-specific)
//
//------------------------------------------------------------------------------
/** Returns MPT transfer fee as Rate. Rate specifies
* the fee as fractions of 1 billion. For example, 1% transfer rate
* is represented as 1,010,000,000.
* @param issuanceID MPTokenIssuanceID of MPTTokenIssuance object
*/
[[nodiscard]] Rate
transferRate() const override;
[[nodiscard]] TER
addEmptyHolding(
ApplyView& view,
AccountID const& accountID,
XRPAmount priorBalance,
MPTIssue const& mptIssue,
beast::Journal journal);
//------------------------------------------------------------------------------
//
// Holding checks (MPT-specific)
//
//------------------------------------------------------------------------------
[[nodiscard]] TER
removeEmptyHolding(
ApplyView& view,
AccountID const& accountID,
MPTIssue const& mptIssue,
beast::Journal journal);
[[nodiscard]] TER
canAddHolding() const override;
/** Check if the account lacks required authorization for MPT.
*
* requireAuth check is recursive for MPT shares in a vault, descending to
* assets in the vault, up to maxAssetCheckDepth recursion depth. This is
* purely defensive, as we currently do not allow such vaults to be created.
*/
[[nodiscard]] TER
requireAuth(AccountID const& account, AuthType authType = AuthType::Legacy, int depth = 0)
const override;
/** Check if the destination account is allowed
* to receive MPT. Return tecNO_AUTH if it doesn't
* and tesSUCCESS otherwise.
*/
[[nodiscard]] TER
canTransfer(AccountID const& from, AccountID const& to) const override;
//------------------------------------------------------------------------------
//
// Token capability checks (MPT-specific)
//
//------------------------------------------------------------------------------
[[nodiscard]] bool
canClawback() const override;
[[nodiscard]] bool
requiresAuth() const override;
STAmount
accountHolds(
AccountID const& account,
FreezeHandling zeroIfFrozen,
beast::Journal j,
SpendableHandling includeFullBalance = shSIMPLE_BALANCE) const override;
[[nodiscard]] STAmount
accountHolds(
AccountID const& account,
FreezeHandling zeroIfFrozen,
AuthHandling zeroIfUnauthorized,
beast::Journal j,
SpendableHandling includeFullBalance = shSIMPLE_BALANCE) const override;
protected:
MPTID const mptID_;
MPTIssue const mptIssue_;
};
class WritableMPTokenIssuance : public virtual WritableTokenBase, public virtual MPTokenIssuance
{
public:
WritableMPTokenIssuance(ApplyView& view, MPTIssue const& mptIssue)
: ReadOnlySLE(view.peek(keylet::mptIssuance(mptIssue.getMptID())), view)
, TokenBase(view, view.peek(keylet::mptIssuance(mptIssue.getMptID())))
, WritableSLE(view.peek(keylet::mptIssuance(mptIssue.getMptID())), view)
, WritableTokenBase(view, view.peek(keylet::mptIssuance(mptIssue.getMptID())))
, MPTokenIssuance(view, mptIssue)
{
}
WritableMPTokenIssuance(ApplyView& view, MPTID const& mptID)
: ReadOnlySLE(view.peek(keylet::mptIssuance(mptID)), view)
, TokenBase(view, view.peek(keylet::mptIssuance(mptID)))
, WritableSLE(view.peek(keylet::mptIssuance(mptID)), view)
, WritableTokenBase(view, view.peek(keylet::mptIssuance(mptID)))
, MPTokenIssuance(view, mptID)
{
}
// Resolve ambiguity: use writable operator-> for non-const, read-only for const
using WritableSLE::operator->;
using MPTokenIssuance::operator->;
using WritableSLE::operator*;
using MPTokenIssuance::operator*;
//------------------------------------------------------------------------------
//
// Authorization (MPT-specific)
//
//------------------------------------------------------------------------------
[[nodiscard]] TER
authorizeMPToken(
XRPAmount const& priorBalance,
AccountID const& account,
beast::Journal journal,
std::uint32_t flags = 0,
std::optional<AccountID> holderID = std::nullopt);
/** Enforce account has MPToken to match its authorization.
*
* Called from doApply - it will check for expired (and delete if found any)
* credentials matching DomainID set in MPTokenIssuance. Must be called if
* requireAuth(...MPTIssue...) returned tesSUCCESS or tecEXPIRED in preclaim.
*/
[[nodiscard]] TER
enforceMPTokenAuthorization(
AccountID const& account,
XRPAmount const& priorBalance,
beast::Journal j);
//------------------------------------------------------------------------------
//
// Empty holding operations (MPT-specific)
//
//------------------------------------------------------------------------------
[[nodiscard]] TER
addEmptyHolding(AccountID const& accountID, XRPAmount priorBalance, beast::Journal journal)
override;
[[nodiscard]] TER
removeEmptyHolding(AccountID const& accountID, beast::Journal journal) override;
/** Create a WritableMPTokenIssuance backed by a brand-new SLE
* (not yet inserted into the view).
*/
[[nodiscard]] static WritableMPTokenIssuance
makeNew(MPTID const& mptID, ApplyView& view)
{
return WritableMPTokenIssuance(
mptID, std::make_shared<SLE>(keylet::mptIssuance(mptID)), view);
}
[[nodiscard]] static WritableMPTokenIssuance
makeNew(std::uint32_t const seq, AccountID const& issuer, ApplyView& view)
{
auto const mptID = makeMptID(seq, issuer);
return WritableMPTokenIssuance(
mptID, std::make_shared<SLE>(keylet::mptIssuance(mptID)), view);
}
private:
// This is a private constructor only used by `makeNew`
WritableMPTokenIssuance(MPTID const& mptID, std::shared_ptr<SLE> sle, ApplyView& view)
: ReadOnlySLE(sle, view)
, TokenBase(view, sle)
, WritableSLE(sle, view)
, WritableTokenBase(view, sle)
, MPTokenIssuance(view, mptID)
{
insert();
}
};
//------------------------------------------------------------------------------
//

View File

@@ -0,0 +1,39 @@
# Ledger Entry Helpers (`entries/`)
## Overview
This folder contains helper classes and free functions for working with **Serialized Ledger Entries (SLEs)**. Its centerpiece is `SLEBase.h`, which defines two base classes — `ReadOnlySLE` and `WritableSLE` — that provide a type-safe, context-aware wrapper around the raw `std::shared_ptr<SLE>` used throughout the rest of the codebase.
## The Problem: Untyped SLE Access
Historically, ledger entries are passed around as bare `std::shared_ptr<SLE>` (or `std::shared_ptr<SLE const>`). This has several drawbacks:
1. **No compile-time entry-type safety.** Any code holding an `std::shared_ptr<SLE>` can read or write _any_ field on _any_ ledger entry type. Nothing prevents you from calling `sle->getFieldU32(sfOwnerCount)` on an Offer SLE, even though Offers don't have that field.
2. **No read/write distinction.** A function that only needs to _read_ an entry still receives a mutable `std::shared_ptr<SLE>`, making it easy to accidentally mutate state. Conversely, a function that _must_ write has no way to express that requirement in its signature.
3. **No association with the view.** The SLE and the `ReadView` / `ApplyView` it came from travel as separate arguments, so callers must manually keep them in sync and remember to call `view.update(sle)` after mutations.
## The Solution: `SLEBase.h`
`SLEBase.h` introduces two base classes that pair an SLE with its view context and enforce read/write semantics at compile time.
**`ReadOnlySLE`** bundles a `std::shared_ptr<SLE const>` with the `ReadView` it was read from. It provides existence checks and const-only access to the underlying entry. Derived classes add domain-specific read-only accessors (e.g. `AccountRoot::isGlobalFrozen()`).
**`WritableSLE`** bundles a mutable `std::shared_ptr<SLE>` with the `ApplyView` used for writes. It provides helpers to insert, update, and erase the entry in the view, keeping the SLE and its view in sync automatically.
### Dual-Inheritance Pattern
Concrete writable wrappers inherit from _both_ the read-only wrapper and `WritableSLE`:
```
WritableAccountRoot
├── AccountRoot (extends ReadOnlySLE) — read-only domain methods
└── WritableSLE — write capabilities
```
This lets a writable wrapper reuse all read-only domain logic from its parent while gaining mutation and persistence operations from `WritableSLE`.
## Migration Status
This migration is still in progress and is still in the early stages. New code should prefer the wrapper style where possible; existing free functions will be migrated incrementally.

View File

@@ -3,6 +3,7 @@
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/IOUAmount.h>
#include <xrpl/protocol/Issue.h>
@@ -18,6 +19,191 @@
namespace xrpl {
class IOUToken : public virtual TokenBase
{
public:
IOUToken(ReadView const& view, Issue const& issue)
: ReadOnlySLE(view.read(keylet::account(issue.getIssuer())), view)
, TokenBase(view, view.read(keylet::account(issue.getIssuer())))
, issue_(issue)
, issuer_(issue.getIssuer())
, issuerAccount_(issuer_, view)
, currency_(issue.currency)
{
}
IOUToken(ReadView const& view, AccountID const& issuer, Currency const& currency)
: IOUToken(view, Issue{currency, issuer})
{
}
[[nodiscard]] AccountID const&
getIssuer() const
{
return issuer_;
}
[[nodiscard]] Currency const&
getCurrency() const
{
return currency_;
}
[[nodiscard]] bool
isGlobalFrozen() const override
{
return issuerAccount_.isGlobalFrozen();
}
[[nodiscard]] bool
isIndividualFrozen(AccountID const& account) const override;
[[nodiscard]] bool
isFrozen(AccountID const& account, int depth = 0) const override;
[[nodiscard]] TER
checkFrozen(AccountID const& account) const override;
[[nodiscard]] bool
isAnyFrozen(std::initializer_list<AccountID> const& accounts, int depth = 0) const override;
[[nodiscard]] bool
isDeepFrozen(AccountID const& account, int depth = 0) const override;
[[nodiscard]] TER
checkDeepFrozen(AccountID const& account) const override;
[[nodiscard]] Rate
transferRate() const override;
STAmount
accountHolds(
AccountID const& account,
FreezeHandling zeroIfFrozen,
beast::Journal j,
SpendableHandling includeFullBalance = shSIMPLE_BALANCE) const override;
[[nodiscard]] STAmount
accountHolds(
AccountID const& account,
FreezeHandling zeroIfFrozen,
AuthHandling zeroIfUnauthorized,
beast::Journal j,
SpendableHandling includeFullBalance = shSIMPLE_BALANCE) const override;
/** Returns the funds available for account to pay for an amount.
*
* If the account is the issuer of the currency, it can always
* afford to pay (returns saDefault as-is). Otherwise, returns
* the result of accountHolds.
*/
[[nodiscard]] STAmount
accountFunds(
AccountID const& id,
STAmount const& saDefault,
FreezeHandling freezeHandling,
beast::Journal j) const;
[[nodiscard]] TER
canAddHolding() const override;
//------------------------------------------------------------------------------
//
// Authorization and transfer checks (Asset-based dispatchers)
//
//------------------------------------------------------------------------------
[[nodiscard]] TER
requireAuth(AccountID const& account, AuthType authType = AuthType::Legacy, int depth = 0)
const override;
[[nodiscard]] TER
canTransfer(AccountID const& from, AccountID const& to) const override;
//------------------------------------------------------------------------------
//
// Token capability checks (IOU-specific)
//
//------------------------------------------------------------------------------
[[nodiscard]] bool
canClawback() const override;
[[nodiscard]] bool
requiresAuth() const override;
protected:
Issue const issue_;
AccountID const issuer_;
AccountRoot const issuerAccount_;
Currency const currency_;
};
class WritableIOUToken : public virtual WritableTokenBase, public virtual IOUToken
{
public:
WritableIOUToken(ApplyView& view, Issue const& issue)
: ReadOnlySLE(view.peek(keylet::account(issue.getIssuer())), view)
, TokenBase(view, view.peek(keylet::account(issue.getIssuer())))
, WritableSLE(view.peek(keylet::account(issue.getIssuer())), view)
, WritableTokenBase(view, view.peek(keylet::account(issue.getIssuer())))
, IOUToken(view, issue)
{
}
WritableIOUToken(ApplyView& view, AccountID const& issuer, Currency const& currency)
: WritableIOUToken(view, Issue{currency, issuer})
{
}
// Resolve ambiguity: use writable operator-> for non-const, read-only for const
using WritableSLE::operator->;
using IOUToken::operator->;
using WritableSLE::operator*;
using IOUToken::operator*;
//------------------------------------------------------------------------------
//
// Holding management (WritableTokenBase interface)
//
//------------------------------------------------------------------------------
[[nodiscard]] TER
addEmptyHolding(AccountID const& accountID, XRPAmount priorBalance, beast::Journal journal)
override;
[[nodiscard]] TER
removeEmptyHolding(AccountID const& accountID, beast::Journal journal) override;
/** Create a WritableIOUToken backed by a brand-new SLE
* (not yet inserted into the view).
*/
[[nodiscard]] static WritableIOUToken
makeNew(AccountID const& id, Currency const& currency, ApplyView& view)
{
return makeNew(Issue{currency, id}, view);
}
[[nodiscard]] static WritableIOUToken
makeNew(Issue const& issue, ApplyView& view)
{
return WritableIOUToken(
issue, view, std::make_shared<SLE>(keylet::account(issue.getIssuer())));
}
private:
// This is a private constructor only used by `makeNew`
WritableIOUToken(Issue const& issue, ApplyView& view, std::shared_ptr<SLE> sle)
: ReadOnlySLE(sle, view)
, TokenBase(view, sle)
, WritableSLE(sle, view)
, WritableTokenBase(view, sle)
, IOUToken(view, issue)
{
insert();
}
};
//------------------------------------------------------------------------------
//
// Credit functions (from Credit.h)
@@ -58,69 +244,6 @@ creditBalance(
Currency const& currency);
/** @} */
//------------------------------------------------------------------------------
//
// Freeze checking (IOU-specific)
//
//------------------------------------------------------------------------------
[[nodiscard]] bool
isIndividualFrozen(
ReadView const& view,
AccountID const& account,
Currency const& currency,
AccountID const& issuer);
[[nodiscard]] inline bool
isIndividualFrozen(ReadView const& view, AccountID const& account, Issue const& issue)
{
return isIndividualFrozen(view, account, issue.currency, issue.account);
}
[[nodiscard]] bool
isFrozen(
ReadView const& view,
AccountID const& account,
Currency const& currency,
AccountID const& issuer);
[[nodiscard]] inline bool
isFrozen(ReadView const& view, AccountID const& account, Issue const& issue)
{
return isFrozen(view, account, issue.currency, issue.account);
}
// Overload with depth parameter for uniformity with MPTIssue version.
// The depth parameter is ignored for IOUs since they don't have vault recursion.
[[nodiscard]] inline bool
isFrozen(ReadView const& view, AccountID const& account, Issue const& issue, int /*depth*/)
{
return isFrozen(view, account, issue);
}
[[nodiscard]] bool
isDeepFrozen(
ReadView const& view,
AccountID const& account,
Currency const& currency,
AccountID const& issuer);
[[nodiscard]] inline bool
isDeepFrozen(
ReadView const& view,
AccountID const& account,
Issue const& issue,
int = 0 /*ignored*/)
{
return isDeepFrozen(view, account, issue.currency, issue.account);
}
[[nodiscard]] inline TER
checkDeepFrozen(ReadView const& view, AccountID const& account, Issue const& issue)
{
return isDeepFrozen(view, account, issue) ? (TER)tecFROZEN : (TER)tesSUCCESS;
}
//------------------------------------------------------------------------------
//
// Trust line operations
@@ -137,16 +260,16 @@ trustCreate(
bool const bSrcHigh,
AccountID const& uSrcAccountID,
AccountID const& uDstAccountID,
uint256 const& uIndex, // --> ripple state entry
SLE::ref sleAccount, // --> the account being set.
bool const bAuth, // --> authorize account.
bool const bNoRipple, // --> others cannot ripple through
bool const bFreeze, // --> funds cannot leave
bool bDeepFreeze, // --> can neither receive nor send funds
STAmount const& saBalance, // --> balance of account being set.
// Issuer should be noAccount()
STAmount const& saLimit, // --> limit for account being set.
// Issuer should be the account being set.
uint256 const& uIndex, // ripple state entry
WritableAccountRoot& wrappedAcct, // the account being set.
bool const bAuth, // authorize account.
bool const bNoRipple, // others cannot ripple through
bool const bFreeze, // funds cannot leave
bool bDeepFreeze, // can neither receive nor send funds
STAmount const& saBalance, // balance of account being set.
// Issuer should be noAccount()
STAmount const& saLimit, // limit for account being set.
// Issuer should be the account being set.
std::uint32_t uQualityIn,
std::uint32_t uQualityOut,
beast::Journal j);
@@ -181,43 +304,6 @@ redeemIOU(
Issue const& issue,
beast::Journal j);
//------------------------------------------------------------------------------
//
// Authorization and transfer checks (IOU-specific)
//
//------------------------------------------------------------------------------
/** Check if the account lacks required authorization.
*
* Return tecNO_AUTH or tecNO_LINE if it does
* and tesSUCCESS otherwise.
*
* If StrongAuth then return tecNO_LINE if the RippleState doesn't exist. Return
* tecNO_AUTH if lsfRequireAuth is set on the issuer's AccountRoot, and the
* RippleState does exist, and the RippleState is not authorized.
*
* If WeakAuth then return tecNO_AUTH if lsfRequireAuth is set, and the
* RippleState exists, and is not authorized. Return tecNO_LINE if
* lsfRequireAuth is set and the RippleState doesn't exist. Consequently, if
* WeakAuth and lsfRequireAuth is *not* set, this function will return
* tesSUCCESS even if RippleState does *not* exist.
*
* The default "Legacy" auth type is equivalent to WeakAuth.
*/
[[nodiscard]] TER
requireAuth(
ReadView const& view,
Issue const& issue,
AccountID const& account,
AuthType authType = AuthType::Legacy);
/** Check if the destination account is allowed
* to receive IOU. Return terNO_RIPPLE if rippling is
* disabled on both sides and tesSUCCESS otherwise.
*/
[[nodiscard]] TER
canTransfer(ReadView const& view, Issue const& issue, AccountID const& from, AccountID const& to);
//------------------------------------------------------------------------------
//
// Empty holding operations (IOU-specific)

View File

@@ -0,0 +1,200 @@
#pragma once
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <memory>
#include <stdexcept>
namespace xrpl {
/**
* Read-only base class for all ledger entry view classes.
*
* Provides common functionality for existence checking and raw SLE read access.
* Supports read-only (ReadView) contexts.
*
* Derived classes should provide domain-specific accessors that hide
* implementation details of the underlying ledger entry format.
*/
class ReadOnlySLE
{
public:
virtual ~ReadOnlySLE() = default;
// Copy/move constructors are fine (reference can be initialized from another)
ReadOnlySLE(ReadOnlySLE const&) = default;
ReadOnlySLE(ReadOnlySLE&&) = default;
// Assignment operators are deleted (cannot rebind reference members)
ReadOnlySLE&
operator=(ReadOnlySLE const&) = delete;
ReadOnlySLE&
operator=(ReadOnlySLE&&) = delete;
/** Returns true if the ledger entry exists */
bool
exists() const
{
return sle_ != nullptr;
}
/** Explicit conversion to bool for convenient existence checking */
explicit
operator bool() const
{
return exists();
}
/** Returns the underlying SLE for read access (always available) */
std::shared_ptr<SLE const> const&
sle() const
{
return sle_;
}
/** Returns the read view (always available) */
ReadView const&
readView() const
{
return readView_;
}
STLedgerEntry const*
operator->() const
{
XRPL_ASSERT(exists(), "xrpl::ReadOnlySLE::operator-> : exists");
return sle_.get();
}
STLedgerEntry const&
operator*() const
{
XRPL_ASSERT(exists(), "xrpl::ReadOnlySLE::operator* : exists");
return *sle_;
}
protected:
// Default constructor is deleted (cannot leave reference uninitialized)
ReadOnlySLE() = delete;
/** Constructor for read-only context (ReadView) */
explicit ReadOnlySLE(std::shared_ptr<SLE const> sle, ReadView const& view)
: sle_(std::move(sle)), readView_(view)
{
}
std::shared_ptr<SLE const> sle_; // Always valid (const view)
ReadView const& readView_; // Always valid
};
/**
* Writable base class for all ledger entry view classes.
*
* Extends ReadOnlySLE with write access capabilities.
* Supports read-write (ApplyView) contexts.
*
* Derived classes should provide domain-specific accessors that hide
* implementation details of the underlying ledger entry format.
*/
class WritableSLE
{
public:
virtual ~WritableSLE() = default;
// Copy/move constructors are fine (reference can be initialized from another)
WritableSLE(WritableSLE const&) = default;
WritableSLE(WritableSLE&&) = default;
// Assignment operators are deleted (cannot rebind reference members)
WritableSLE&
operator=(WritableSLE const&) = delete;
WritableSLE&
operator=(WritableSLE&&) = delete;
/** Returns a mutable SLE for write operations */
std::shared_ptr<SLE> const&
mutableSle() const
{
return mutableSle_;
}
/** Returns true if this wrapper supports write operations */
bool
canModify() const
{
return mutableSle_ != nullptr;
}
/** Returns the apply view for write operations */
ApplyView&
applyView() const
{
return applyView_;
}
STLedgerEntry*
operator->()
{
XRPL_ASSERT(canModify(), "xrpl::WritableSLE::operator-> : can modify");
return mutableSle_.get();
}
STLedgerEntry&
operator*()
{
XRPL_ASSERT(canModify(), "xrpl::WritableSLE::operator* : can modify");
return *mutableSle_;
}
void
insert()
{
XRPL_ASSERT(canModify(), "xrpl::WritableSLE::insert : can modify");
applyView_.insert(mutableSle_);
}
void
erase()
{
XRPL_ASSERT(canModify(), "xrpl::WritableSLE::erase : can modify");
applyView_.erase(mutableSle_);
}
void
update()
{
XRPL_ASSERT(canModify(), "xrpl::WritableSLE::update : can modify");
applyView_.update(mutableSle_);
}
void
newSLE()
{
XRPL_ASSERT(!canModify(), "xrpl::WritableSLE::newSLE : mutableSle_ is not null");
mutableSle_ = std::make_shared<SLE>(key_);
}
protected:
// Default constructor is deleted (cannot leave reference uninitialized)
WritableSLE() = delete;
/** Constructor for read-write context (ApplyView) */
explicit WritableSLE(std::shared_ptr<SLE> sle, ApplyView& view)
: applyView_(view)
, key_(sle ? Keylet(sle->getType(), sle->key()) : Keylet(ltANY, uint256{}))
, mutableSle_(std::move(sle))
{
}
/** Constructor for read-write context (ApplyView) */
explicit WritableSLE(Keylet const& key, ApplyView& view)
: applyView_(view), key_(key), mutableSle_(applyView_.peek(key))
{
}
ApplyView& applyView_; // ApplyView for write contexts (first for init order)
Keylet const key_;
std::shared_ptr<SLE> mutableSle_; // Mutable SLE for write contexts
};
} // namespace xrpl

View File

@@ -3,6 +3,7 @@
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/SLEBase.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/MPTIssue.h>
#include <xrpl/protocol/Rate.h>
@@ -10,6 +11,7 @@
#include <xrpl/protocol/TER.h>
#include <initializer_list>
#include <memory>
#include <vector>
namespace xrpl {
@@ -48,82 +50,156 @@ enum class AuthType { StrongAuth, WeakAuth, Legacy };
//
//------------------------------------------------------------------------------
[[nodiscard]] bool
isGlobalFrozen(ReadView const& view, Asset const& asset);
class TokenBase : public virtual ReadOnlySLE
{
public:
[[nodiscard]] virtual bool
isGlobalFrozen() const = 0;
[[nodiscard]] bool
isIndividualFrozen(ReadView const& view, AccountID const& account, Asset const& asset);
[[nodiscard]] virtual bool
isIndividualFrozen(AccountID const& account) const = 0;
/**
* isFrozen check is recursive for MPT shares in a vault, descending to
* assets in the vault, up to maxAssetCheckDepth recursion depth. This is
* purely defensive, as we currently do not allow such vaults to be created.
*/
[[nodiscard]] bool
isFrozen(ReadView const& view, AccountID const& account, Asset const& asset, int depth = 0);
/**
* isFrozen check is recursive for MPT shares in a vault, descending to
* assets in the vault, up to maxAssetCheckDepth recursion depth. This is
* purely defensive, as we currently do not allow such vaults to be created.
*/
[[nodiscard]] virtual bool
isFrozen(AccountID const& account, int depth = 0) const;
[[nodiscard]] TER
checkFrozen(ReadView const& view, AccountID const& account, Issue const& issue);
[[nodiscard]] virtual TER
checkFrozen(AccountID const& account) const = 0;
[[nodiscard]] TER
checkFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue);
[[nodiscard]] virtual bool
isAnyFrozen(std::initializer_list<AccountID> const& accounts, int depth = 0) const = 0;
[[nodiscard]] TER
checkFrozen(ReadView const& view, AccountID const& account, Asset const& asset);
/**
* isFrozen check is recursive for MPT shares in a vault, descending to
* assets in the vault, up to maxAssetCheckDepth recursion depth. This is
* purely defensive, as we currently do not allow such vaults to be created.
*/
[[nodiscard]] virtual bool
isDeepFrozen(AccountID const& account, int depth = 0) const = 0;
[[nodiscard]] bool
isAnyFrozen(
ReadView const& view,
std::initializer_list<AccountID> const& accounts,
Issue const& issue);
[[nodiscard]] virtual TER
checkDeepFrozen(AccountID const& account) const = 0;
[[nodiscard]] bool
isAnyFrozen(
ReadView const& view,
std::initializer_list<AccountID> const& accounts,
Asset const& asset,
int depth = 0);
/** Returns the transfer fee as Rate based on the type of token
* @param view The ledger view
* @param amount The amount to transfer
*/
[[nodiscard]] virtual Rate
transferRate() const = 0;
//------------------------------------------------------------------------------
//
// Account balance functions (Asset-based dispatchers)
//
//------------------------------------------------------------------------------
[[nodiscard]] bool
isDeepFrozen(
ReadView const& view,
AccountID const& account,
MPTIssue const& mptIssue,
int depth = 0);
// Returns the amount an account can spend.
//
// If shSIMPLE_BALANCE is specified, this is the amount the account can spend
// without going into debt.
//
// If shFULL_BALANCE is specified, this is the amount the account can spend
// total. Specifically:
// * The account can go into debt if using a trust line, and the other side has
// a non-zero limit.
// * If the account is the asset issuer the limit is defined by the asset /
// issuance.
//
// <-- saAmount: amount of currency held by account. May be negative.
virtual STAmount
accountHolds(
AccountID const& account,
FreezeHandling zeroIfFrozen,
beast::Journal j,
SpendableHandling includeFullBalance = shSIMPLE_BALANCE) const = 0;
/**
* isFrozen check is recursive for MPT shares in a vault, descending to
* assets in the vault, up to maxAssetCheckDepth recursion depth. This is
* purely defensive, as we currently do not allow such vaults to be created.
*/
[[nodiscard]] bool
isDeepFrozen(ReadView const& view, AccountID const& account, Asset const& asset, int depth = 0);
[[nodiscard]] virtual STAmount
accountHolds(
AccountID const& account,
FreezeHandling zeroIfFrozen,
AuthHandling zeroIfUnauthorized,
beast::Journal j,
SpendableHandling includeFullBalance = shSIMPLE_BALANCE) const = 0;
[[nodiscard]] TER
checkDeepFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue);
[[nodiscard]] virtual TER
canAddHolding() const = 0;
[[nodiscard]] TER
checkDeepFrozen(ReadView const& view, AccountID const& account, Asset const& asset);
//------------------------------------------------------------------------------
//
// Authorization and transfer checks (Asset-based dispatchers)
//
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
//
// Account balance functions (Asset-based dispatchers)
//
//------------------------------------------------------------------------------
[[nodiscard]] virtual TER
requireAuth(AccountID const& account, AuthType authType = AuthType::Legacy, int depth = 0)
const = 0;
// Returns the amount an account can spend.
//
// If shSIMPLE_BALANCE is specified, this is the amount the account can spend
// without going into debt.
//
// If shFULL_BALANCE is specified, this is the amount the account can spend
// total. Specifically:
// * The account can go into debt if using a trust line, and the other side has
// a non-zero limit.
// * If the account is the asset issuer the limit is defined by the asset /
// issuance.
//
// <-- saAmount: amount of currency held by account. May be negative.
[[nodiscard]] virtual TER
canTransfer(AccountID const& from, AccountID const& to) const = 0;
//------------------------------------------------------------------------------
//
// Token capability checks (Asset-based dispatchers)
//
//------------------------------------------------------------------------------
/** Check if the token issuer has enabled clawback capability.
* For IOUs, checks lsfAllowTrustLineClawback on issuer's AccountRoot.
* For MPTs, checks lsfMPTCanClawback on the issuance.
*/
[[nodiscard]] virtual bool
canClawback() const = 0;
/** Check if the token requires authorization for holders.
* For IOUs, checks lsfRequireAuth on issuer's AccountRoot.
* For MPTs, checks lsfMPTRequireAuth on the issuance.
*/
[[nodiscard]] virtual bool
requiresAuth() const = 0;
protected:
TokenBase(ReadView const& view, std::shared_ptr<SLE const> sle) : ReadOnlySLE(sle, view)
{
}
};
class WritableTokenBase : public virtual TokenBase, public virtual WritableSLE
{
public:
//------------------------------------------------------------------------------
//
// Holding operations (Asset-based dispatchers)
//
//------------------------------------------------------------------------------
[[nodiscard]] virtual TER
addEmptyHolding(AccountID const& accountID, XRPAmount priorBalance, beast::Journal journal) = 0;
[[nodiscard]] virtual TER
removeEmptyHolding(AccountID const& accountID, beast::Journal journal) = 0;
protected:
WritableTokenBase(ApplyView& view, std::shared_ptr<SLE> sle)
: TokenBase(view, sle), WritableSLE(sle, view)
{
}
};
std::unique_ptr<TokenBase>
makeTokenBase(ReadView const& view, Asset const& asset);
std::unique_ptr<WritableTokenBase>
makeWritableTokenBase(ApplyView& view, Asset const& asset);
// Helper function to get transfer rate from an STAmount
[[nodiscard]] Rate
transferRate(ReadView const& view, STAmount const& amount);
// Returns the amount the specified account can spend.
// Supports both IOU and MPT via Currency/AccountID parameters.
[[nodiscard]] STAmount
accountHolds(
ReadView const& view,
@@ -134,25 +210,8 @@ accountHolds(
beast::Journal j,
SpendableHandling includeFullBalance = shSIMPLE_BALANCE);
[[nodiscard]] STAmount
accountHolds(
ReadView const& view,
AccountID const& account,
Issue const& issue,
FreezeHandling zeroIfFrozen,
beast::Journal j,
SpendableHandling includeFullBalance = shSIMPLE_BALANCE);
[[nodiscard]] STAmount
accountHolds(
ReadView const& view,
AccountID const& account,
MPTIssue const& mptIssue,
FreezeHandling zeroIfFrozen,
AuthHandling zeroIfUnauthorized,
beast::Journal j,
SpendableHandling includeFullBalance = shSIMPLE_BALANCE);
// Returns the amount the specified account can spend for a given Asset.
// Dispatches to appropriate token wrapper based on Asset type.
[[nodiscard]] STAmount
accountHolds(
ReadView const& view,
@@ -163,66 +222,6 @@ accountHolds(
beast::Journal j,
SpendableHandling includeFullBalance = shSIMPLE_BALANCE);
// Returns the amount an account can spend of the currency type saDefault, or
// returns saDefault if this account is the issuer of the currency in
// question. Should be used in favor of accountHolds when questioning how much
// an account can spend while also allowing currency issuers to spend
// unlimited amounts of their own currency (since they can always issue more).
[[nodiscard]] STAmount
accountFunds(
ReadView const& view,
AccountID const& id,
STAmount const& saDefault,
FreezeHandling freezeHandling,
beast::Journal j);
/** Returns the transfer fee as Rate based on the type of token
* @param view The ledger view
* @param amount The amount to transfer
*/
[[nodiscard]] Rate
transferRate(ReadView const& view, STAmount const& amount);
//------------------------------------------------------------------------------
//
// Holding operations (Asset-based dispatchers)
//
//------------------------------------------------------------------------------
[[nodiscard]] TER
canAddHolding(ReadView const& view, Asset const& asset);
[[nodiscard]] TER
addEmptyHolding(
ApplyView& view,
AccountID const& accountID,
XRPAmount priorBalance,
Asset const& asset,
beast::Journal journal);
[[nodiscard]] TER
removeEmptyHolding(
ApplyView& view,
AccountID const& accountID,
Asset const& asset,
beast::Journal journal);
//------------------------------------------------------------------------------
//
// Authorization and transfer checks (Asset-based dispatchers)
//
//------------------------------------------------------------------------------
[[nodiscard]] TER
requireAuth(
ReadView const& view,
Asset const& asset,
AccountID const& account,
AuthType authType = AuthType::Legacy);
[[nodiscard]] TER
canTransfer(ReadView const& view, Asset const& asset, AccountID const& from, AccountID const& to);
//------------------------------------------------------------------------------
//
// Money Transfers (Asset-based dispatchers)
@@ -232,7 +231,7 @@ canTransfer(ReadView const& view, Asset const& asset, AccountID const& from, Acc
// Direct send w/o fees:
// - Redeeming IOUs and/or sending sender's own IOUs.
// - Create trust line of needed.
// --> bCheckIssuer : normally require issuer to be involved.
// bCheckIssuer : normally require issuer to be involved.
// [[nodiscard]] // nodiscard commented out so DirectStep.cpp compiles.
/** Calls static rippleCreditIOU if saAmount represents Issue.

View File

@@ -1,5 +1,6 @@
#pragma once
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STLedgerEntry.h>
@@ -21,7 +22,7 @@ namespace xrpl {
[[nodiscard]] std::optional<STAmount>
assetsToSharesDeposit(
std::shared_ptr<SLE const> const& vault,
std::shared_ptr<SLE const> const& issuance,
MPTokenIssuance const& issuance,
STAmount const& assets);
/** From the perspective of a vault, return the number of assets to take from
@@ -37,7 +38,7 @@ assetsToSharesDeposit(
[[nodiscard]] std::optional<STAmount>
sharesToAssetsDeposit(
std::shared_ptr<SLE const> const& vault,
std::shared_ptr<SLE const> const& issuance,
MPTokenIssuance const& issuance,
STAmount const& shares);
/** Controls whether to truncate shares instead of rounding. */
@@ -58,7 +59,7 @@ enum class TruncateShares : bool { no = false, yes = true };
[[nodiscard]] std::optional<STAmount>
assetsToSharesWithdraw(
std::shared_ptr<SLE const> const& vault,
std::shared_ptr<SLE const> const& issuance,
MPTokenIssuance const& issuance,
STAmount const& assets,
TruncateShares truncate = TruncateShares::no);
@@ -75,7 +76,7 @@ assetsToSharesWithdraw(
[[nodiscard]] std::optional<STAmount>
sharesToAssetsWithdraw(
std::shared_ptr<SLE const> const& vault,
std::shared_ptr<SLE const> const& issuance,
MPTokenIssuance const& issuance,
STAmount const& shares);
} // namespace xrpl

View File

@@ -4,10 +4,6 @@
namespace xrpl {
// Deprecated constant for backwards compatibility with pre-XRPFees amendment.
// This was the reference fee units used in the old fee calculation.
inline constexpr std::uint32_t FEE_UNITS_DEPRECATED = 10;
/** Reflects the fee settings for a particular ledger.
The fees are always the same for any transactions applied
@@ -15,25 +11,15 @@ inline constexpr std::uint32_t FEE_UNITS_DEPRECATED = 10;
*/
struct Fees
{
/** @brief Cost of a reference transaction in drops. */
XRPAmount base{0};
/** @brief Minimum XRP an account must hold to exist on the ledger. */
XRPAmount reserve{0};
/** @brief Additional XRP reserve required per owned ledger object. */
XRPAmount increment{0};
XRPAmount base{0}; // Reference tx cost (drops)
XRPAmount reserve{0}; // Reserve base (drops)
XRPAmount increment{0}; // Reserve increment (drops)
explicit Fees() = default;
Fees(Fees const&) = default;
Fees&
operator=(Fees const&) = default;
Fees(XRPAmount base_, XRPAmount reserve_, XRPAmount increment_)
: 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

View File

@@ -287,7 +287,7 @@ credential(uint256 const& key) noexcept
}
Keylet
mptIssuance(std::uint32_t seq, AccountID const& issuer) noexcept;
mptIssuance(std::uint32_t const seq, AccountID const& issuer) noexcept;
Keylet
mptIssuance(MPTID const& issuanceID) noexcept;

View File

@@ -72,8 +72,4 @@ deserializeHeader(Slice data, bool hasHash = false);
LedgerHeader
deserializePrefixedHeader(Slice data, bool hasHash = false);
/** Calculate the hash of a ledger header. */
uint256
calculateLedgerHash(LedgerHeader const& info);
} // namespace xrpl

View File

@@ -72,12 +72,12 @@ public:
isDelegable(std::uint32_t const& permissionValue, Rules const& rules) const;
// for tx level permission, permission value is equal to tx type plus one
static uint32_t
txToPermissionType(TxType const& type);
uint32_t
txToPermissionType(TxType const& type) const;
// tx type value is permission value minus one
static TxType
permissionToTxType(uint32_t const& value);
TxType
permissionToTxType(uint32_t const& value) const;
};
} // namespace xrpl

View File

@@ -69,6 +69,9 @@ public:
std::vector<uint256>::iterator
insert(std::vector<uint256>::const_iterator pos, uint256 const& value);
std::vector<uint256>::iterator
insert(std::vector<uint256>::const_iterator pos, uint256&& value);
void
push_back(uint256 const& v);
@@ -181,6 +184,12 @@ STVector256::insert(std::vector<uint256>::const_iterator pos, uint256 const& val
return mValue.insert(pos, value);
}
inline std::vector<uint256>::iterator
STVector256::insert(std::vector<uint256>::const_iterator pos, uint256&& value)
{
return mValue.insert(pos, std::move(value));
}
inline void
STVector256::push_back(uint256 const& v)
{

View File

@@ -336,7 +336,7 @@ public:
static_assert(N > 0, "");
}
[[nodiscard]] bool
std::size_t
empty() const noexcept
{
return remain_ == 0;

View File

@@ -2,6 +2,6 @@
namespace xrpl {
enum class TxSearched { All, Some, Unknown };
enum class TxSearched { all, some, unknown };
}

View File

@@ -16,7 +16,6 @@
// Add new amendments to the top of this list.
// Keep it sorted in reverse chronological order.
XRPL_FIX (Security3_1_3, Supported::no, VoteBehavior::DefaultNo)
XRPL_FIX (PermissionedDomainInvariant, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (ExpiredNFTokenOfferRemoval, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (BatchInnerSigs, Supported::no, VoteBehavior::DefaultNo)

View File

@@ -1,7 +1,6 @@
#pragma once
#include <xrpl/core/PerfLog.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/core/StartUpType.h>
#include <xrpl/rdb/DBInit.h>
#include <xrpl/rdb/SociDB.h>
@@ -70,7 +69,7 @@ public:
{
explicit Setup() = default;
StartUpType startUp = StartUpType::Normal;
StartUpType startUp = StartUpType::NORMAL;
bool standAlone = false;
boost::filesystem::path dataDir;
// Indicates whether or not to return the `globalPragma`
@@ -95,7 +94,7 @@ public:
struct CheckpointerSetup
{
JobQueue* jobQueue;
std::reference_wrapper<ServiceRegistry> registry;
Logs* logs;
};
template <std::size_t N, std::size_t M>
@@ -107,8 +106,9 @@ public:
beast::Journal journal)
// Use temporary files or regular DB files?
: DatabaseCon(
setup.standAlone && setup.startUp != StartUpType::Load &&
setup.startUp != StartUpType::LoadFile && setup.startUp != StartUpType::Replay
setup.standAlone && setup.startUp != StartUpType::LOAD &&
setup.startUp != StartUpType::LOAD_FILE &&
setup.startUp != StartUpType::REPLAY
? ""
: (setup.dataDir / dbName),
setup.commonPragma(),
@@ -129,7 +129,7 @@ public:
beast::Journal journal)
: DatabaseCon(setup, dbName, pragma, initSQL, journal)
{
setupCheckpointing(checkpointerSetup.jobQueue, checkpointerSetup.registry.get());
setupCheckpointing(checkpointerSetup.jobQueue, *checkpointerSetup.logs);
}
template <std::size_t N, std::size_t M>
@@ -154,7 +154,7 @@ public:
beast::Journal journal)
: DatabaseCon(dataDir, dbName, pragma, initSQL, journal)
{
setupCheckpointing(checkpointerSetup.jobQueue, checkpointerSetup.registry.get());
setupCheckpointing(checkpointerSetup.jobQueue, *checkpointerSetup.logs);
}
~DatabaseCon();
@@ -177,7 +177,7 @@ public:
private:
void
setupCheckpointing(JobQueue*, ServiceRegistry&);
setupCheckpointing(JobQueue*, Logs&);
template <std::size_t N, std::size_t M>
DatabaseCon(

View File

@@ -49,9 +49,8 @@ public:
struct AccountTxOptions
{
AccountID const& account;
/// Ledger sequence range to search. A value of 0 for min or max
/// means unbounded in that direction (no constraint applied).
LedgerRange ledgerRange;
std::uint32_t minLedger;
std::uint32_t maxLedger;
std::uint32_t offset;
std::uint32_t limit;
bool bUnlimited;
@@ -60,7 +59,8 @@ public:
struct AccountTxPageOptions
{
AccountID const& account;
LedgerRange ledgerRange;
std::uint32_t minLedger;
std::uint32_t maxLedger;
std::optional<AccountTxMarker> marker;
std::uint32_t limit;
bool bAdmin;
@@ -247,7 +247,7 @@ public:
* @return Struct CountMinMax which contains the minimum sequence,
* maximum sequence and number of ledgers.
*/
virtual CountMinMax
virtual struct CountMinMax
getLedgerCountMinMax() = 0;
/**
@@ -405,10 +405,10 @@ public:
* @param id Hash of the transaction.
* @param range Range of ledgers to check, if present.
* @param ec Default error code value.
* @return Transaction and its metadata if found, otherwise TxSearched::All
* @return Transaction and its metadata if found, otherwise TxSearched::all
* if a range is provided and all ledgers from the range are present
* in the database, TxSearched::Some if a range is provided and not
* all ledgers are present, TxSearched::Unknown if the range is not
* in the database, TxSearched::some if a range is provided and not
* all ledgers are present, TxSearched::unknown if the range is not
* provided or a deserializing error occurred. In the last case the
* error code is returned via the ec parameter, in other cases the
* default error code is not changed.
@@ -455,10 +455,9 @@ public:
closeTransactionDB() = 0;
};
template <typename T, typename C>
template <class T, class C>
T
rangeCheckedCast(C c)
requires(std::is_arithmetic_v<T> && std::is_arithmetic_v<C> && std::convertible_to<C, T>)
{
if ((c > std::numeric_limits<T>::max()) || (!std::numeric_limits<T>::is_signed && c < 0) ||
(std::numeric_limits<T>::is_signed && std::numeric_limits<C>::is_signed &&

View File

@@ -13,8 +13,8 @@
#pragma clang diagnostic ignored "-Wdeprecated"
#endif
#include <xrpl/basics/Log.h>
#include <xrpl/core/JobQueue.h>
#include <xrpl/core/ServiceRegistry.h>
#define SOCI_USE_BOOST
#include <soci/soci.h>
@@ -111,7 +111,7 @@ public:
and so must outlive them both.
*/
std::shared_ptr<Checkpointer>
makeCheckpointer(std::uintptr_t id, std::weak_ptr<soci::session>, JobQueue&, ServiceRegistry&);
makeCheckpointer(std::uintptr_t id, std::weak_ptr<soci::session>, JobQueue&, Logs&);
} // namespace xrpl

View File

@@ -520,7 +520,7 @@ private:
// getMissingNodes helper functions
void
gmn_ProcessNodes(MissingNodes&, MissingNodes::StackEntry& node);
static void
void
gmn_ProcessDeferredReads(MissingNodes&);
// fetch from DB helper function

View File

@@ -37,7 +37,7 @@ public:
XRPL_ASSERT((flags & tapBATCH) == 0, "Batch apply flag should not be set");
}
std::reference_wrapper<ServiceRegistry> registry;
ServiceRegistry& registry;
STTx const& tx;
TER const preclaimResult;
XRPAmount const baseFee;
@@ -111,7 +111,7 @@ public:
checkInvariants(TER const result, XRPAmount const fee);
private:
static TER
TER
failInvariantCheck(TER const result);
template <std::size_t... Is>

View File

@@ -2,6 +2,7 @@
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/utility/WrappedSink.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/protocol/Permissions.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/ApplyContext.h>
@@ -13,7 +14,7 @@ namespace xrpl {
struct PreflightContext
{
public:
std::reference_wrapper<ServiceRegistry> registry;
ServiceRegistry& registry;
STTx const& tx;
Rules const rules;
ApplyFlags flags;
@@ -56,7 +57,7 @@ public:
struct PreclaimContext
{
public:
std::reference_wrapper<ServiceRegistry> registry;
ServiceRegistry& registry;
ReadView const& view;
TER preflightResult;
ApplyFlags flags;
@@ -113,7 +114,8 @@ protected:
beast::WrappedSink sink_;
beast::Journal const j_;
AccountID const account_;
AccountID const accountID_;
WritableAccountRoot account_;
XRPAmount preFeeBalance_{}; // Balance before fees.
virtual ~Transactor() = default;

View File

@@ -48,7 +48,7 @@ private:
bool
isValidEntry(std::shared_ptr<SLE const> const& before, std::shared_ptr<SLE const> const& after);
static STAmount
STAmount
calculateBalanceChange(
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after,
@@ -63,7 +63,7 @@ private:
std::shared_ptr<SLE const>
findIssuer(AccountID const& issuerID, ReadView const& view);
static bool
bool
validateIssuerChanges(
std::shared_ptr<SLE const> const& issuer,
IssuerChanges const& changes,
@@ -71,7 +71,7 @@ private:
beast::Journal const& j,
bool enforce);
static bool
bool
validateFrozenState(
BalanceChange const& change,
bool high,

View File

@@ -7,7 +7,6 @@
#include <xrpl/protocol/TER.h>
#include <xrpl/tx/invariants/AMMInvariant.h>
#include <xrpl/tx/invariants/FreezeInvariant.h>
#include <xrpl/tx/invariants/LoanBrokerInvariant.h>
#include <xrpl/tx/invariants/LoanInvariant.h>
#include <xrpl/tx/invariants/MPTInvariant.h>
#include <xrpl/tx/invariants/NFTInvariant.h>
@@ -112,7 +111,7 @@ public:
void
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
static bool
bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
};

View File

@@ -1,55 +0,0 @@
#pragma once
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <map>
#include <vector>
namespace xrpl {
/**
* @brief Invariants: Loan brokers are internally consistent
*
* 1. If `LoanBroker.OwnerCount = 0` the `DirectoryNode` will have at most one
* node (the root), which will only hold entries for `RippleState` or
* `MPToken` objects.
*
*/
class ValidLoanBroker
{
// Not all of these elements will necessarily be populated. Remaining items
// will be looked up as needed.
struct BrokerInfo
{
SLE::const_pointer brokerBefore = nullptr;
// After is used for most of the checks, except
// those that check changed values.
SLE::const_pointer brokerAfter = nullptr;
};
// Collect all the LoanBrokers found directly or indirectly through
// pseudo-accounts. Key is the brokerID / index. It will be used to find the
// LoanBroker object if brokerBefore and brokerAfter are nullptr
std::map<uint256, BrokerInfo> brokers_;
// Collect all the modified trust lines. Their high and low accounts will be
// loaded to look for LoanBroker pseudo-accounts.
std::vector<SLE::const_pointer> lines_;
// Collect all the modified MPTokens. Their accounts will be loaded to look
// for LoanBroker pseudo-accounts.
std::vector<SLE::const_pointer> mpts_;
static bool
goodZeroDirectory(ReadView const& view, SLE::const_ref dir, beast::Journal const& j);
public:
void
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
};
} // namespace xrpl

View File

@@ -1,14 +1,57 @@
#pragma once
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <map>
#include <vector>
namespace xrpl {
/**
* @brief Invariants: Loan brokers are internally consistent
*
* 1. If `LoanBroker.OwnerCount = 0` the `DirectoryNode` will have at most one
* node (the root), which will only hold entries for `RippleState` or
* `MPToken` objects.
*
*/
class ValidLoanBroker
{
// Not all of these elements will necessarily be populated. Remaining items
// will be looked up as needed.
struct BrokerInfo
{
SLE::const_pointer brokerBefore = nullptr;
// After is used for most of the checks, except
// those that check changed values.
SLE::const_pointer brokerAfter = nullptr;
};
// Collect all the LoanBrokers found directly or indirectly through
// pseudo-accounts. Key is the brokerID / index. It will be used to find the
// LoanBroker object if brokerBefore and brokerAfter are nullptr
std::map<uint256, BrokerInfo> brokers_;
// Collect all the modified trust lines. Their high and low accounts will be
// loaded to look for LoanBroker pseudo-accounts.
std::vector<SLE::const_pointer> lines_;
// Collect all the modified MPTokens. Their accounts will be loaded to look
// for LoanBroker pseudo-accounts.
std::vector<SLE::const_pointer> mpts_;
bool
goodZeroDirectory(ReadView const& view, SLE::const_ref dir, beast::Journal const& j) const;
public:
void
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
};
/**
* @brief Invariants: Loans are internally consistent
*

View File

@@ -1,6 +1,6 @@
#pragma once
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/basics/Log.h>
#include <xrpl/ledger/PaymentSandbox.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/TER.h>
@@ -92,7 +92,7 @@ public:
STPathSet const& spsPaths,
std::optional<uint256> const& domainID,
ServiceRegistry& registry,
Logs& l,
Input const* const pInputs = nullptr);
// The view we are currently working on

View File

@@ -2,7 +2,6 @@
#include <xrpl/basics/Log.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/OfferHelpers.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/IOUAmount.h>

View File

@@ -203,7 +203,7 @@ getAMMOfferStartWithTakerGets(
// Try to reduce the offer size to improve the quality.
// The quality might still not match the targetQuality for a tiny offer.
if (auto amounts = getAmounts(*nTakerGets); Quality{amounts} < targetQuality)
if (auto const amounts = getAmounts(*nTakerGets); Quality{amounts} < targetQuality)
return getAmounts(detail::reduceOffer(amounts.out));
else
return amounts;
@@ -270,7 +270,7 @@ getAMMOfferStartWithTakerPays(
// Try to reduce the offer size to improve the quality.
// The quality might still not match the targetQuality for a tiny offer.
if (auto amounts = getAmounts(*nTakerPays); Quality{amounts} < targetQuality)
if (auto const amounts = getAmounts(*nTakerPays); Quality{amounts} < targetQuality)
return getAmounts(detail::reduceOffer(amounts.in));
else
return amounts;
@@ -335,7 +335,8 @@ changeSpotPriceQuality(
}
auto const takerPays = toAmount<TIn>(getIssue(pool.in), nTakerPays, Number::upward);
// should not fail
if (auto amounts = TAmounts<TIn, TOut>{takerPays, swapAssetIn(pool, takerPays, tfee)};
if (auto const amounts =
TAmounts<TIn, TOut>{takerPays, swapAssetIn(pool, takerPays, tfee)};
Quality{amounts} < quality &&
!withinRelativeDistance(Quality{amounts}, quality, Number(1, -7)))
{
@@ -361,7 +362,7 @@ changeSpotPriceQuality(
// Generate the offer starting with XRP side. Return seated offer amounts
// if the offer can be generated, otherwise nullopt.
auto amounts = [&]() {
auto const amounts = [&]() {
if (isXRP(getIssue(pool.out)))
return getAMMOfferStartWithTakerGets(pool, quality, tfee);
return getAMMOfferStartWithTakerPays(pool, quality, tfee);

View File

@@ -3,7 +3,6 @@
#include <xrpl/basics/Expected.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/TER.h>

View File

@@ -1,7 +1,6 @@
#pragma once
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
#include <xrpl/tx/Transactor.h>
namespace xrpl {

View File

@@ -475,4 +475,15 @@ loanMakePayment(
LoanPaymentType const paymentType,
beast::Journal j);
// LoanBroker-specific `adjustOwnerCount` function (temporary, while the Wrapped classes are WIP)
// Assert will check the type, so that we ensure it's not used by anything else
// Order of parameters is different from the old `adjustOwnerCount` function to avoid anything
// accidentally calling this with the wrong type.
void
adjustOwnerCount(
std::shared_ptr<SLE> const& sle,
ApplyView& view,
std::int32_t amount,
beast::Journal j);
} // namespace xrpl

View File

@@ -1,5 +1,6 @@
#pragma once
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/tx/Transactor.h>
namespace xrpl {
@@ -26,7 +27,7 @@ private:
Expected<std::pair<STAmount, STAmount>, TER>
assetsToClawback(
std::shared_ptr<SLE> const& vault,
std::shared_ptr<SLE const> const& sleShareIssuance,
MPTokenIssuance const& shareIssuance,
AccountID const& holder,
STAmount const& clawbackAmount);
};

View File

@@ -45,7 +45,7 @@ getFileContents(
return {};
}
std::string result{
std::string const result{
std::istreambuf_iterator<char>{fileStream}, std::istreambuf_iterator<char>{}};
if (fileStream.bad())

View File

@@ -981,7 +981,7 @@ root(Number f, unsigned d)
auto ex = [e = e, di = di]() // Euclidean remainder of e/d
{
int k = (e >= 0 ? e : e - (di - 1)) / di;
int k2 = e - (k * di);
int k2 = e - k * di;
if (k2 == 0)
return 0;
return di - k2;
@@ -998,7 +998,7 @@ root(Number f, unsigned d)
}
// Quadratic least squares curve fit of f^(1/d) in the range [0, 1]
auto const D = (((6 * di + 11) * di + 6) * di) + 1;
auto const D = ((6 * di + 11) * di + 6) * di + 1;
auto const a0 = 3 * di * ((2 * di - 3) * di + 1);
auto const a1 = 24 * di * (2 * di - 1);
auto const a2 = -30 * (di - 1) * di;

View File

@@ -169,7 +169,7 @@ public:
XRPL_ASSERT(m_stopped == true, "xrpl::ResolverAsioImpl::start : stopped");
XRPL_ASSERT(m_stop_called == false, "xrpl::ResolverAsioImpl::start : not stopping");
if (m_stopped.exchange(false))
if (m_stopped.exchange(false) == true)
{
{
std::lock_guard lk{m_mut};
@@ -182,7 +182,7 @@ public:
void
stop_async() override
{
if (!m_stop_called.exchange(true))
if (m_stop_called.exchange(true) == false)
{
boost::asio::dispatch(
m_io_context,
@@ -229,7 +229,7 @@ public:
{
XRPL_ASSERT(m_stop_called == true, "xrpl::ResolverAsioImpl::do_stop : stopping");
if (!m_stopped.exchange(true))
if (m_stopped.exchange(true) == false)
{
m_work.clear();
m_resolver.cancel();
@@ -271,7 +271,7 @@ public:
m_strand, std::bind(&ResolverAsioImpl::do_work, this, CompletionCounter(this))));
}
static HostAndPort
HostAndPort
parseName(std::string const& str)
{
// first attempt to parse as an endpoint (IP addr + port).
@@ -319,7 +319,7 @@ public:
void
do_work(CompletionCounter)
{
if (m_stop_called)
if (m_stop_called == true)
return;
// We don't have any work to do at this time
@@ -367,7 +367,7 @@ public:
{
XRPL_ASSERT(!names.empty(), "xrpl::ResolverAsioImpl::do_resolve : names non-empty");
if (!m_stop_called)
if (m_stop_called == false)
{
m_work.emplace_back(names, handler);

View File

@@ -24,7 +24,7 @@ sqlBlobLiteral(Blob const& blob)
{
std::string j;
j.reserve((blob.size() * 2) + 3);
j.reserve(blob.size() * 2 + 3);
j.push_back('X');
j.push_back('\'');
boost::algorithm::hex(blob.begin(), blob.end(), std::back_inserter(j));

View File

@@ -107,7 +107,7 @@ encode(void* dest, void const* src, std::size_t len)
char const* in = static_cast<char const*>(src);
auto const tab = base64::get_alphabet();
for (auto n = len / 3; n != 0u; --n)
for (auto n = len / 3; n--;)
{
*out++ = tab[(in[0] & 0xfc) >> 2];
*out++ = tab[((in[0] & 0x03) << 4) + ((in[1] & 0xf0) >> 4)];
@@ -162,7 +162,7 @@ decode(void* dest, char const* src, std::size_t len)
auto const inverse = base64::get_inverse();
while (((len--) != 0u) && *in != '=')
while (len-- && *in != '=')
{
auto const v = inverse[*in];
if (v == -1)
@@ -181,7 +181,7 @@ decode(void* dest, char const* src, std::size_t len)
}
}
if (i != 0)
if (i)
{
c3[0] = (c4[0] << 2) + ((c4[1] & 0x30) >> 4);
c3[1] = ((c4[1] & 0xf) << 4) + ((c4[2] & 0x3c) >> 2);

View File

@@ -253,7 +253,7 @@ initAuthenticated(
// VFALCO Replace fopen() with RAII
FILE* f = fopen(chain_file.c_str(), "r");
if (f == nullptr)
if (!f)
{
LogicError(
"Problem opening SSL chain file" +

View File

@@ -352,7 +352,7 @@ public:
}
}
static void
void
log(std::vector<boost::asio::const_buffer> const& buffers)
{
(void)buffers;

View File

@@ -10,7 +10,7 @@ bool
is_private(AddressV6 const& addr)
{
return (
((addr.to_bytes()[0] & 0xfd) != 0) || // TODO fc00::/8 too ?
(addr.to_bytes()[0] & 0xfd) || // TODO fc00::/8 too ?
(addr.is_v4_mapped() &&
is_private(boost::asio::ip::make_address_v4(boost::asio::ip::v4_mapped, addr))));
}

View File

@@ -57,7 +57,7 @@ Endpoint::to_string() const
if (port() != 0 && address().is_v6())
s += '[';
s += address().to_string();
if (port() != 0u)
if (port())
{
if (address().is_v6())
s += ']';
@@ -111,7 +111,7 @@ operator>>(std::istream& is, Endpoint& endpoint)
// so we continue to honor that here by assuming we are at the end
// of the address portion if we hit a space (or the separator
// we were expecting to see)
if ((isspace(static_cast<unsigned char>(i)) != 0) || ((readTo != 0) && i == readTo))
if (isspace(static_cast<unsigned char>(i)) || (readTo && i == readTo))
break;
if ((i == '.') || (i >= '0' && i <= ':') || (i >= 'a' && i <= 'f') ||
@@ -121,13 +121,13 @@ operator>>(std::istream& is, Endpoint& endpoint)
// don't exceed a reasonable length...
if (addrStr.size() == INET6_ADDRSTRLEN ||
((readTo != 0) && readTo == ':' && addrStr.size() > 15))
(readTo && readTo == ':' && addrStr.size() > 15))
{
is.setstate(std::ios_base::failbit);
return is;
}
if ((readTo == 0) && (i == '.' || i == ':'))
if (!readTo && (i == '.' || i == ':'))
{
// if we see a dot first, must be IPv4
// otherwise must be non-bracketed IPv6
@@ -145,7 +145,7 @@ operator>>(std::istream& is, Endpoint& endpoint)
if (readTo == ']' && is.rdbuf()->in_avail() > 0)
{
is.get(i);
if ((isspace(static_cast<unsigned char>(i)) == 0) && i != ':')
if (!(isspace(static_cast<unsigned char>(i)) || i == ':'))
{
is.unget();
is.setstate(std::ios_base::failbit);

View File

@@ -84,7 +84,8 @@ JobQueue::addRefCountedJob(JobType type, std::string const& name, JobFunction co
JobType const type(job.getType());
XRPL_ASSERT(type != jtINVALID, "xrpl::JobQueue::addRefCountedJob : has valid job type");
XRPL_ASSERT(m_jobSet.contains(job), "xrpl::JobQueue::addRefCountedJob : job found");
XRPL_ASSERT(
m_jobSet.find(job) != m_jobSet.end(), "xrpl::JobQueue::addRefCountedJob : job found");
perfLog_.jobQueue(type);
JobTypeData& data(getJobTypeData(type));

View File

@@ -141,7 +141,7 @@ LoadMonitor::isOver()
update();
if (mLatencyEvents == 0)
return false;
return 0;
return isOverTarget(
mLatencyMSAvg / (mLatencyEvents * 4), mLatencyMSPeak / (mLatencyEvents * 4));

View File

@@ -47,7 +47,7 @@ Workers::setNumberOfThreads(int numberOfThreads)
if (m_numberOfThreads == numberOfThreads)
return;
if (perfLog_ != nullptr)
if (perfLog_)
perfLog_->resizeJobs(numberOfThreads);
if (numberOfThreads > m_numberOfThreads)

View File

@@ -210,8 +210,8 @@ RFC1751::extract(char const* s, int start, int length)
int const shiftR = 24 - (length + (start % 8));
cl = s[start / 8]; // get components
cc = (shiftR < 16) ? s[(start / 8) + 1] : 0;
cr = (shiftR < 8) ? s[(start / 8) + 2] : 0;
cc = (shiftR < 16) ? s[start / 8 + 1] : 0;
cr = (shiftR < 8) ? s[start / 8 + 2] : 0;
x = ((long)(cl << 8 | cc) << 8 | cr); // Put bits together
x = x >> shiftR; // Right justify number
@@ -265,13 +265,13 @@ RFC1751::insert(char* s, int x, int start, int length)
if (shift + length > 16)
{
s[start / 8] |= cl;
s[(start / 8) + 1] |= cc;
s[(start / 8) + 2] |= cr;
s[start / 8 + 1] |= cc;
s[start / 8 + 2] |= cr;
}
else if (shift + length > 8)
{
s[start / 8] |= cc;
s[(start / 8) + 1] |= cr;
s[start / 8 + 1] |= cr;
}
else
{
@@ -284,7 +284,7 @@ RFC1751::standard(std::string& strWord)
{
for (auto& letter : strWord)
{
if (islower(static_cast<unsigned char>(letter)) != 0)
if (islower(static_cast<unsigned char>(letter)))
{
letter = toupper(static_cast<unsigned char>(letter));
}
@@ -312,10 +312,10 @@ RFC1751::wsrch(std::string const& strWord, int iMin, int iMax)
while (iResult < 0 && iMin != iMax)
{
// Have a range to search.
int iMid = iMin + ((iMax - iMin) / 2);
int iMid = iMin + (iMax - iMin) / 2;
int iDir = strWord.compare(s_dictionary[iMid]);
if (iDir == 0)
if (!iDir)
{
iResult = iMid; // Found it.
}

View File

@@ -152,7 +152,7 @@ public:
#ifndef NDEBUG
// Make sure we haven't already seen this tag.
auto& tags = stack_.top().tags;
check(!tags.contains(tag), "Already seen tag " + tag);
check(tags.find(tag) == tags.end(), "Already seen tag " + tag);
tags.insert(tag);
#endif

View File

@@ -296,7 +296,7 @@ Reader::match(Location pattern, int patternLength)
int index = patternLength;
while ((index--) != 0)
while (index--)
{
if (current_[index] != pattern[index])
return false;
@@ -362,7 +362,7 @@ Reader::readNumber()
while (current_ != end_)
{
if (std::isdigit(static_cast<unsigned char>(*current_)) == 0)
if (!std::isdigit(static_cast<unsigned char>(*current_)))
{
auto ret =
std::find(std::begin(extended_tokens), std::end(extended_tokens), *current_);
@@ -913,7 +913,7 @@ Reader::getFormattedErrorMessages() const
formattedMessage += "* " + getLocationLineAndColumn(error.token_.start_) + "\n";
formattedMessage += " " + error.message_ + "\n";
if (error.extra_ != nullptr)
if (error.extra_)
formattedMessage += "See " + getLocationLineAndColumn(error.extra_) + " for detail.\n";
}

View File

@@ -40,10 +40,10 @@ public:
// return 0;
if (length == unknown)
length = (value != nullptr) ? (unsigned int)strlen(value) : 0;
length = value ? (unsigned int)strlen(value) : 0;
char* newString = static_cast<char*>(malloc(length + 1));
if (value != nullptr)
if (value)
memcpy(newString, value, length);
newString[length] = 0;
return newString;
@@ -52,7 +52,7 @@ public:
void
releaseStringValue(char* value) override
{
if (value != nullptr)
if (value)
free(value);
}
};
@@ -108,14 +108,14 @@ Value::CZString::CZString(CZString const& other)
Value::CZString::~CZString()
{
if ((cstr_ != nullptr) && index_ == duplicate)
if (cstr_ && index_ == duplicate)
valueAllocator()->releaseMemberName(const_cast<char*>(cstr_));
}
bool
Value::CZString::operator<(CZString const& other) const
{
if ((cstr_ != nullptr) && (other.cstr_ != nullptr))
if (cstr_ && other.cstr_)
return strcmp(cstr_, other.cstr_) < 0;
return index_ < other.index_;
@@ -124,7 +124,7 @@ Value::CZString::operator<(CZString const& other) const
bool
Value::CZString::operator==(CZString const& other) const
{
if ((cstr_ != nullptr) && (other.cstr_ != nullptr))
if (cstr_ && other.cstr_)
return strcmp(cstr_, other.cstr_) == 0;
return index_ == other.index_;
@@ -251,7 +251,7 @@ Value::Value(Value const& other) : type_(other.type_)
break;
case stringValue:
if (other.value_.string_ != nullptr)
if (other.value_.string_)
{
value_.string_ = valueAllocator()->duplicateStringValue(other.value_.string_);
allocated_ = true;
@@ -294,7 +294,7 @@ Value::~Value()
case arrayValue:
case objectValue:
if (value_.map_ != nullptr)
if (value_.map_)
delete value_.map_;
break;
@@ -392,11 +392,11 @@ operator<(Value const& x, Value const& y)
return x.value_.real_ < y.value_.real_;
case booleanValue:
return static_cast<int>(x.value_.bool_) < static_cast<int>(y.value_.bool_);
return x.value_.bool_ < y.value_.bool_;
case stringValue:
return (x.value_.string_ == 0 && (y.value_.string_ != nullptr)) ||
((y.value_.string_ != nullptr) && (x.value_.string_ != nullptr) &&
return (x.value_.string_ == 0 && y.value_.string_) ||
(y.value_.string_ && x.value_.string_ &&
strcmp(x.value_.string_, y.value_.string_) < 0);
case arrayValue:
@@ -413,7 +413,7 @@ operator<(Value const& x, Value const& y)
// LCOV_EXCL_STOP
}
return false; // unreachable
return 0; // unreachable
}
bool
@@ -422,9 +422,9 @@ operator==(Value const& x, Value const& y)
if (x.type_ != y.type_)
{
if (x.type_ == intValue && y.type_ == uintValue)
return integerCmp(x.value_.int_, y.value_.uint_) == 0;
return !integerCmp(x.value_.int_, y.value_.uint_);
if (x.type_ == uintValue && y.type_ == intValue)
return integerCmp(y.value_.int_, x.value_.uint_) == 0;
return !integerCmp(y.value_.int_, x.value_.uint_);
return false;
}
@@ -447,8 +447,8 @@ operator==(Value const& x, Value const& y)
case stringValue:
return x.value_.string_ == y.value_.string_ ||
((y.value_.string_ != nullptr) && (x.value_.string_ != nullptr) &&
(strcmp(x.value_.string_, y.value_.string_) == 0));
(y.value_.string_ && x.value_.string_ &&
!strcmp(x.value_.string_, y.value_.string_));
case arrayValue:
case objectValue:
@@ -461,7 +461,7 @@ operator==(Value const& x, Value const& y)
// LCOV_EXCL_STOP
}
return false; // unreachable
return 0; // unreachable
}
char const*
@@ -480,7 +480,7 @@ Value::asString() const
return "";
case stringValue:
return (value_.string_ != nullptr) ? value_.string_ : "";
return value_.string_ ? value_.string_ : "";
case booleanValue:
return value_.bool_ ? "true" : "false";
@@ -525,7 +525,7 @@ Value::asInt() const
case realValue:
JSON_ASSERT_MESSAGE(
(value_.real_ >= minInt && value_.real_ <= maxInt),
value_.real_ >= minInt && value_.real_ <= maxInt,
"Real out of signed integer range");
return Int(value_.real_);
@@ -533,7 +533,7 @@ Value::asInt() const
return value_.bool_ ? 1 : 0;
case stringValue: {
char const* const str{(value_.string_ != nullptr) ? value_.string_ : ""};
char const* const str{value_.string_ ? value_.string_ : ""};
return beast::lexicalCastThrow<int>(str);
}
@@ -584,7 +584,7 @@ Value::asAbsUInt() const
return value_.bool_ ? 1 : 0;
case stringValue: {
char const* const str{(value_.string_ != nullptr) ? value_.string_ : ""};
char const* const str{value_.string_ ? value_.string_ : ""};
auto const temp = beast::lexicalCastThrow<std::int64_t>(str);
if (temp < 0)
{
@@ -626,15 +626,14 @@ Value::asUInt() const
case realValue:
JSON_ASSERT_MESSAGE(
(value_.real_ >= 0 && value_.real_ <= maxUInt),
"Real out of unsigned integer range");
value_.real_ >= 0 && value_.real_ <= maxUInt, "Real out of unsigned integer range");
return UInt(value_.real_);
case booleanValue:
return value_.bool_ ? 1 : 0;
case stringValue: {
char const* const str{(value_.string_ != nullptr) ? value_.string_ : ""};
char const* const str{value_.string_ ? value_.string_ : ""};
return beast::lexicalCastThrow<unsigned int>(str);
}
@@ -704,7 +703,7 @@ Value::asBool() const
return value_.bool_;
case stringValue:
return (value_.string_ != nullptr) && value_.string_[0] != 0;
return value_.string_ && value_.string_[0] != 0;
case arrayValue:
case objectValue:
@@ -746,13 +745,13 @@ Value::isConvertibleTo(ValueType other) const
other == realValue || other == stringValue || other == booleanValue;
case booleanValue:
return (other == nullValue && !value_.bool_) || other == intValue ||
return (other == nullValue && value_.bool_ == false) || other == intValue ||
other == uintValue || other == realValue || other == stringValue ||
other == booleanValue;
case stringValue:
return other == stringValue ||
(other == nullValue && ((value_.string_ == nullptr) || value_.string_[0] == 0));
(other == nullValue && (!value_.string_ || value_.string_[0] == 0));
case arrayValue:
return other == arrayValue || (other == nullValue && value_.map_->empty());
@@ -814,10 +813,10 @@ operator bool() const
if (isString())
{
auto s = asCString();
return (s != nullptr) && (s[0] != 0);
return s && s[0];
}
return !(isArray() || isObject()) || (size() != 0u);
return !(isArray() || isObject()) || size();
}
void
@@ -1140,7 +1139,7 @@ Value::begin() const
{
case arrayValue:
case objectValue:
if (value_.map_ != nullptr)
if (value_.map_)
return const_iterator(value_.map_->begin());
break;
@@ -1158,7 +1157,7 @@ Value::end() const
{
case arrayValue:
case objectValue:
if (value_.map_ != nullptr)
if (value_.map_)
return const_iterator(value_.map_->end());
break;
@@ -1176,7 +1175,7 @@ Value::begin()
{
case arrayValue:
case objectValue:
if (value_.map_ != nullptr)
if (value_.map_)
return iterator(value_.map_->begin());
break;
default:
@@ -1193,7 +1192,7 @@ Value::end()
{
case arrayValue:
case objectValue:
if (value_.map_ != nullptr)
if (value_.map_)
return iterator(value_.map_->end());
break;
default:

View File

@@ -89,7 +89,7 @@ ValueIteratorBase::key() const
{
Value::CZString const czString = (*current_).first;
if (czString.c_str() != nullptr)
if (czString.c_str())
{
if (czString.isStaticString())
return Value(StaticString(czString.c_str()));
@@ -105,7 +105,7 @@ ValueIteratorBase::index() const
{
Value::CZString const czString = (*current_).first;
if (czString.c_str() == nullptr)
if (!czString.c_str())
return czString.index();
return Value::UInt(-1);
@@ -115,7 +115,7 @@ char const*
ValueIteratorBase::memberName() const
{
char const* name = (*current_).first.c_str();
return (name != nullptr) ? name : "";
return name ? name : "";
}
// //////////////////////////////////////////////////////////////////

View File

@@ -23,7 +23,7 @@ isControlCharacter(char ch)
static bool
containsControlCharacter(char const* str)
{
while (*str != 0)
while (*str)
{
if (isControlCharacter(*(str++)))
return true;
@@ -106,7 +106,7 @@ valueToQuotedString(char const* value)
// We have to walk value and escape any special characters.
// Appending to std::string is not efficient, but this should be rare.
// (Note: forward slashes are *not* rare, but I am not escaping them.)
unsigned maxsize = (strlen(value) * 2) + 3; // all-escaped+quotes+NULL
unsigned maxsize = strlen(value) * 2 + 3; // all-escaped+quotes+NULL
std::string result;
result.reserve(maxsize); // to avoid lots of mallocs
result += "\"";
@@ -416,7 +416,7 @@ StyledWriter::isMultilineArray(Value const& value)
{
childValues_.reserve(size);
addChildValues_ = true;
int lineLength = 4 + ((size - 1) * 2); // '[ ' + ', '*n + ' ]'
int lineLength = 4 + (size - 1) * 2; // '[ ' + ', '*n + ' ]'
for (int index = 0; index < size; ++index)
{
@@ -651,7 +651,7 @@ StyledStreamWriter::isMultilineArray(Value const& value)
{
childValues_.reserve(size);
addChildValues_ = true;
int lineLength = 4 + ((size - 1) * 2); // '[ ' + ', '*n + ' ]'
int lineLength = 4 + (size - 1) * 2; // '[ ' + ', '*n + ' ]'
for (int index = 0; index < size; ++index)
{

View File

@@ -2,7 +2,7 @@
#include <xrpl/basics/StringUtilities.h>
#include <xrpl/ledger/AcceptedLedgerTx.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/UintTypes.h>
#include <xrpl/protocol/jss.h>
@@ -45,12 +45,10 @@ AcceptedLedgerTx::AcceptedLedgerTx(
// If the offer create is not self funded then add the owner balance
if (account != amount.issue().account)
{
auto const ownerFunds = accountFunds(
*ledger,
account,
amount,
fhIGNORE_FREEZE,
beast::Journal{beast::Journal::getNullSink()});
auto const ownerFunds =
makeTokenBase(*ledger, amount.asset())
->accountHolds(
account, fhIGNORE_FREEZE, beast::Journal{beast::Journal::getNullSink()});
mJson[jss::transaction][jss::owner_funds] = ownerFunds.getText();
}
}

View File

@@ -275,7 +275,9 @@ ApplyStateTable::exists(ReadView const& base, Keylet const& k) const
case Action::modify:
break;
}
return k.check(*sle);
if (!k.check(*sle))
return false;
return true;
}
auto

View File

@@ -36,7 +36,7 @@ findPreviousPage(ApplyView& view, Keylet const& directory, SLE::ref start)
auto node = start;
if (page != 0u)
if (page)
{
node = view.peek(keylet::page(directory, page));
if (!node)

View File

@@ -1,6 +1,5 @@
#include <xrpl/ledger/BookDirs.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/protocol/Indexes.h>
namespace xrpl {

View File

@@ -164,7 +164,7 @@ PaymentSandbox::balanceHook(
auto delta = amount.zeroed();
auto lastBal = amount;
auto minBal = amount;
for (auto curSB = this; curSB != nullptr; curSB = curSB->ps_)
for (auto curSB = this; curSB; curSB = curSB->ps_)
{
if (auto adj = curSB->tab_.adjustments(account, issuer, currency))
{
@@ -198,7 +198,7 @@ std::uint32_t
PaymentSandbox::ownerCountHook(AccountID const& account, std::uint32_t count) const
{
std::uint32_t result = count;
for (auto curSB = this; curSB != nullptr; curSB = curSB->ps_)
for (auto curSB = this; curSB; curSB = curSB->ps_)
{
if (auto adj = curSB->tab_.ownerCount(account))
result = std::max(result, *adj);

View File

@@ -4,10 +4,8 @@
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/CredentialHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
@@ -52,13 +50,13 @@ isVaultPseudoAccountFrozen(
if (depth >= maxAssetCheckDepth)
return true; // LCOV_EXCL_LINE
auto const mptIssuance = view.read(keylet::mptIssuance(mptShare.getMptID()));
if (mptIssuance == nullptr)
auto mptIssuance = MPTokenIssuance(view, mptShare);
if (!mptIssuance.exists())
return false; // zero MPToken won't block deletion of MPTokenIssuance
auto const issuer = mptIssuance->getAccountID(sfIssuer);
auto const mptIssuer = view.read(keylet::account(issuer));
if (mptIssuer == nullptr)
auto const mptIssuer = AccountRoot(issuer, view);
if (!mptIssuer.exists())
{
// LCOV_EXCL_START
UNREACHABLE("xrpl::isVaultPseudoAccountFrozen : null MPToken issuer");
@@ -77,7 +75,9 @@ isVaultPseudoAccountFrozen(
// LCOV_EXCL_STOP
}
return isAnyFrozen(view, {issuer, account}, vault->at(sfAsset), depth + 1);
auto const vaultAsset = vault->at(sfAsset);
auto const token = makeTokenBase(view, vaultAsset);
return token->isAnyFrozen({issuer, account}, depth + 1);
}
bool
@@ -87,8 +87,9 @@ isLPTokenFrozen(
Issue const& asset,
Issue const& asset2)
{
return isFrozen(view, account, asset.currency, asset.account) ||
isFrozen(view, account, asset2.currency, asset2.account);
auto assetToken = IOUToken(view, asset);
auto asset2Token = IOUToken(view, asset2);
return assetToken.isFrozen(account) || asset2Token.isFrozen(account);
}
bool
@@ -357,17 +358,17 @@ canWithdraw(
ReadView const& view,
AccountID const& from,
AccountID const& to,
SLE::const_ref toSle,
AccountRoot const& toWrapped,
STAmount const& amount,
bool hasDestinationTag)
{
if (auto const ret = checkDestinationAndTag(toSle, hasDestinationTag))
if (auto const ret = toWrapped.checkDestinationAndTag(hasDestinationTag))
return ret;
if (from == to)
return tesSUCCESS;
if (toSle->isFlag(lsfDepositAuth))
if (toWrapped->isFlag(lsfDepositAuth))
{
if (!view.exists(keylet::depositPreauth(to, from)))
return tecNO_PERMISSION;
@@ -384,9 +385,9 @@ canWithdraw(
STAmount const& amount,
bool hasDestinationTag)
{
auto const toSle = view.read(keylet::account(to));
auto const toWrapped = AccountRoot(to, view);
return canWithdraw(view, from, to, toSle, amount, hasDestinationTag);
return canWithdraw(view, from, to, toWrapped, amount, hasDestinationTag);
}
[[nodiscard]] TER
@@ -409,31 +410,28 @@ doWithdraw(
STAmount const& amount,
beast::Journal j)
{
auto const asset = makeWritableTokenBase(view, amount.asset());
// Create trust line or MPToken for the receiving account
if (dstAcct == senderAcct)
{
if (auto const ter = addEmptyHolding(view, senderAcct, priorBalance, amount.asset(), j);
if (auto const ter = asset->addEmptyHolding(senderAcct, priorBalance, j);
!isTesSuccess(ter) && ter != tecDUPLICATE)
return ter;
}
else
{
auto dstSle = view.read(keylet::account(dstAcct));
if (auto err = verifyDepositPreauth(tx, view, senderAcct, dstAcct, dstSle, j))
auto dst = AccountRoot(dstAcct, view);
if (auto err = verifyDepositPreauth(tx, view, senderAcct, dst, j))
return err;
}
// Sanity check
if (accountHolds(
view,
sourceAcct,
amount.asset(),
FreezeHandling::fhIGNORE_FREEZE,
AuthHandling::ahIGNORE_AUTH,
j) < amount)
if (asset->accountHolds(
sourceAcct, FreezeHandling::fhIGNORE_FREEZE, AuthHandling::ahIGNORE_AUTH, j) < amount)
{
// LCOV_EXCL_START
JLOG(j.error()) << "doWithdraw: negative balance of broker cover assets.";
JLOG(j.error()) << "LoanBrokerCoverWithdraw: negative balance of "
"broker cover assets.";
return tefINTERNAL;
// LCOV_EXCL_STOP
}

View File

@@ -12,13 +12,11 @@
namespace xrpl {
bool
isGlobalFrozen(ReadView const& view, AccountID const& issuer)
AccountRoot::isGlobalFrozen() const
{
if (isXRP(issuer))
if (!exists())
return false;
if (auto const sle = view.read(keylet::account(issuer)))
return sle->isFlag(lsfGlobalFreeze);
return false;
return sle_->isFlag(lsfGlobalFreeze);
}
// An owner count cannot be negative. If adjustment would cause a negative
@@ -64,27 +62,26 @@ confineOwnerCount(
}
XRPAmount
xrpLiquid(ReadView const& view, AccountID const& id, std::int32_t ownerCountAdj, beast::Journal j)
AccountRoot::xrpLiquid(std::int32_t ownerCountAdj, beast::Journal j) const
{
auto const sle = view.read(keylet::account(id));
if (sle == nullptr)
if (!exists())
return beast::zero;
// Return balance minus reserve
std::uint32_t const ownerCount =
confineOwnerCount(view.ownerCountHook(id, sle->getFieldU32(sfOwnerCount)), ownerCountAdj);
std::uint32_t const ownerCount = confineOwnerCount(
readView_.ownerCountHook(id_, sle_->getFieldU32(sfOwnerCount)), ownerCountAdj);
// Pseudo-accounts have no reserve requirement
auto const reserve =
isPseudoAccount(sle) ? XRPAmount{0} : view.fees().accountReserve(ownerCount);
isPseudoAccount() ? XRPAmount{0} : readView_.fees().accountReserve(ownerCount);
auto const fullBalance = sle->getFieldAmount(sfBalance);
auto const fullBalance = sle_->getFieldAmount(sfBalance);
auto const balance = view.balanceHook(id, xrpAccount(), fullBalance);
auto const balance = readView_.balanceHook(id_, xrpAccount(), fullBalance);
STAmount const amount = (balance < reserve) ? STAmount{0} : balance - reserve;
JLOG(j.trace()) << "accountHolds:" << " account=" << to_string(id)
JLOG(j.trace()) << "accountHolds:" << " account=" << to_string(id_)
<< " amount=" << amount.getFullText()
<< " fullBalance=" << fullBalance.getFullText()
<< " balance=" << balance.getFullText() << " reserve=" << reserve
@@ -94,32 +91,25 @@ xrpLiquid(ReadView const& view, AccountID const& id, std::int32_t ownerCountAdj,
}
Rate
transferRate(ReadView const& view, AccountID const& issuer)
AccountRoot::transferRate() const
{
auto const sle = view.read(keylet::account(issuer));
if (sle && sle->isFieldPresent(sfTransferRate))
return Rate{sle->getFieldU32(sfTransferRate)};
if (sle_ && sle_->isFieldPresent(sfTransferRate))
return Rate{sle_->getFieldU32(sfTransferRate)};
return parityRate;
}
void
adjustOwnerCount(
ApplyView& view,
std::shared_ptr<SLE> const& sle,
std::int32_t amount,
beast::Journal j)
WritableAccountRoot::adjustOwnerCount(std::int32_t amount, beast::Journal j)
{
if (!sle)
return;
XRPL_ASSERT(canModify(), "xrpl::adjustOwnerCount : can modify");
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 current{mutableSle_->getFieldU32(sfOwnerCount)};
AccountID const id = (*mutableSle_)[sfAccount];
std::uint32_t const adjusted = confineOwnerCount(current, amount, id, j);
view.adjustOwnerCountHook(id, current, adjusted);
sle->at(sfOwnerCount) = adjusted;
view.update(sle);
applyView_.adjustOwnerCountHook(id_, current, adjusted);
mutableSle_->at(sfOwnerCount) = adjusted;
update();
}
AccountID
@@ -171,6 +161,21 @@ getPseudoAccountFields()
return pseudoFields;
}
[[nodiscard]] bool
AccountRoot::isPseudoAccount(std::set<SField const*> const& pseudoFieldFilter) const
{
auto const& fields = getPseudoAccountFields();
// Intentionally use defensive coding here because it's cheap and makes the
// semantics of true return value clean.
return sle_ && sle_->getType() == ltACCOUNT_ROOT &&
std::count_if(
fields.begin(), fields.end(), [this, &pseudoFieldFilter](SField const* sf) -> bool {
return sle_->isFieldPresent(*sf) &&
(pseudoFieldFilter.empty() || pseudoFieldFilter.contains(sf));
}) > 0;
}
[[nodiscard]] bool
isPseudoAccount(
std::shared_ptr<SLE const> sleAcct,
@@ -231,14 +236,14 @@ createPseudoAccount(ApplyView& view, uint256 const& pseudoOwnerKey, SField const
}
[[nodiscard]] TER
checkDestinationAndTag(SLE::const_ref toSle, bool hasDestinationTag)
AccountRoot::checkDestinationAndTag(bool hasDestinationTag) const
{
if (toSle == nullptr)
if (sle_ == nullptr)
return tecNO_DST;
// The tag is basically account-specific information we don't
// understand, but we can require someone to fill it in.
if (toSle->isFlag(lsfRequireDestTag) && !hasDestinationTag)
if (sle_->isFlag(lsfRequireDestTag) && !hasDestinationTag)
return tecDST_TAG_NEEDED; // Cannot send without a tag
return tesSUCCESS;

View File

@@ -51,8 +51,8 @@ deleteSLE(ApplyView& view, std::shared_ptr<SLE> const& sleCredential, beast::Jou
auto delSLE = [&view, &sleCredential, j](
AccountID const& account, SField const& node, bool isOwner) -> TER {
auto const sleAccount = view.peek(keylet::account(account));
if (!sleAccount)
WritableAccountRoot wrappedAccount(account, view);
if (!wrappedAccount)
{
// LCOV_EXCL_START
JLOG(j.fatal()) << "Internal error: can't retrieve Owner account.";
@@ -71,14 +71,14 @@ deleteSLE(ApplyView& view, std::shared_ptr<SLE> const& sleCredential, beast::Jou
}
if (isOwner)
adjustOwnerCount(view, sleAccount, -1, j);
wrappedAccount.adjustOwnerCount(-1, j);
return tesSUCCESS;
};
auto const issuer = sleCredential->getAccountID(sfIssuer);
auto const subject = sleCredential->getAccountID(sfSubject);
bool const accepted = (sleCredential->getFlags() & lsfAccepted) != 0u;
bool const accepted = sleCredential->getFlags() & lsfAccepted;
auto err = delSLE(issuer, sfIssuerNode, !accepted || (subject == issuer));
if (!isTesSuccess(err))
@@ -147,7 +147,7 @@ valid(STTx const& tx, ReadView const& view, AccountID const& src, beast::Journal
return tecBAD_CREDENTIALS;
}
if ((sleCred->getFlags() & lsfAccepted) == 0u)
if (!(sleCred->getFlags() & lsfAccepted))
{
JLOG(j.trace()) << "Credential isn't accepted. Cred: " << h;
return tecBAD_CREDENTIALS;
@@ -188,7 +188,7 @@ validDomain(ReadView const& view, uint256 domainID, AccountID const& subject)
foundExpired = true;
continue;
}
if ((sleCredential->getFlags() & lsfAccepted) != 0u)
if (sleCredential->getFlags() & lsfAccepted)
{
return tesSUCCESS;
}
@@ -309,7 +309,7 @@ verifyValidDomain(ApplyView& view, AccountID const& account, uint256 domainID, b
if (!sleCredential)
continue; // expired, i.e. deleted in credentials::removeExpired
if ((sleCredential->getFlags() & lsfAccepted) != 0u)
if (sleCredential->getFlags() & lsfAccepted)
return tesSUCCESS;
}
@@ -321,8 +321,7 @@ verifyDepositPreauth(
STTx const& tx,
ApplyView& view,
AccountID const& src,
AccountID const& dst,
std::shared_ptr<SLE const> const& sleDst,
AccountRoot const& dst,
beast::Journal j)
{
// If depositPreauth is enabled, then an account that requires
@@ -336,15 +335,15 @@ verifyDepositPreauth(
if (credentialsPresent && credentials::removeExpired(view, tx.getFieldV256(sfCredentialIDs), j))
return tecEXPIRED;
if (sleDst && ((sleDst->getFlags() & lsfDepositAuth) != 0u))
if (dst.exists() && (dst->getFlags() & lsfDepositAuth))
{
if (src != dst)
{
if (!view.exists(keylet::depositPreauth(dst, src)))
if (!view.exists(keylet::depositPreauth(dst.id(), src)))
{
return !credentialsPresent ? tecNO_PERMISSION
: credentials::authorizedDepositPreauth(
view, tx.getFieldV256(sfCredentialIDs), dst);
view, tx.getFieldV256(sfCredentialIDs), dst.id());
}
}
}

View File

@@ -69,7 +69,7 @@ forEachItem(
for (auto const& key : sle->getFieldV256(sfIndexes))
f(view.read(keylet::child(key)));
auto const next = sle->getFieldU64(sfIndexNext);
if (next == 0u)
if (!next)
return;
pos = keylet::page(root, next);
}

View File

@@ -1,7 +1,6 @@
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
//
#include <xrpl/basics/Log.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/CredentialHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
@@ -13,77 +12,103 @@
namespace xrpl {
// Forward declarations for functions that remain in View.h/cpp
bool
isGlobalFrozen(ReadView const& view, MPTIssue const& mptIssue)
isVaultPseudoAccountFrozen(
ReadView const& view,
AccountID const& account,
MPTIssue const& mptShare,
int depth);
[[nodiscard]] TER
dirLink(
ApplyView& view,
AccountID const& owner,
std::shared_ptr<SLE>& object,
SF_UINT64 const& node = sfOwnerNode);
bool
MPTokenIssuance::isGlobalFrozen() const
{
if (auto const sle = view.read(keylet::mptIssuance(mptIssue.getMptID())))
if (sle_)
return sle_->isFlag(lsfMPTLocked);
return false;
}
bool
MPTokenIssuance::isIndividualFrozen(AccountID const& account) const
{
if (auto const sle = readView_.read(keylet::mptoken(mptID_, account)))
return sle->isFlag(lsfMPTLocked);
return false;
}
bool
isIndividualFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue)
MPTokenIssuance::isFrozen(AccountID const& account, int depth) const
{
if (auto const sle = view.read(keylet::mptoken(mptIssue.getMptID(), account)))
return sle->isFlag(lsfMPTLocked);
return false;
}
bool
isFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue, int depth)
{
return isGlobalFrozen(view, mptIssue) || isIndividualFrozen(view, account, mptIssue) ||
isVaultPseudoAccountFrozen(view, account, mptIssue, depth);
return isGlobalFrozen() || isIndividualFrozen(account) ||
isVaultPseudoAccountFrozen(readView_, account, mptIssue_, depth);
}
[[nodiscard]] bool
isAnyFrozen(
ReadView const& view,
std::initializer_list<AccountID> const& accounts,
MPTIssue const& mptIssue,
int depth)
MPTokenIssuance::isAnyFrozen(std::initializer_list<AccountID> const& accounts, int depth) const
{
if (isGlobalFrozen(view, mptIssue))
if (isGlobalFrozen())
return true;
for (auto const& account : accounts)
{
if (isIndividualFrozen(view, account, mptIssue))
if (isIndividualFrozen(account))
return true;
}
for (auto const& account : accounts)
{
if (isVaultPseudoAccountFrozen(view, account, mptIssue, depth))
if (isVaultPseudoAccountFrozen(readView_, account, mptIssue_, depth))
return true;
}
return false;
}
TER
MPTokenIssuance::checkFrozen(AccountID const& account) const
{
return isFrozen(account) ? TER{tecLOCKED} : TER{tesSUCCESS};
}
bool
MPTokenIssuance::isDeepFrozen(AccountID const& account, int depth) const
{
return isFrozen(account, depth);
}
TER
MPTokenIssuance::checkDeepFrozen(AccountID const& account) const
{
return isDeepFrozen(account) ? TER{tecLOCKED} : TER{tesSUCCESS};
}
Rate
transferRate(ReadView const& view, MPTID const& issuanceID)
MPTokenIssuance::transferRate() const
{
// fee is 0-50,000 (0-50%), rate is 1,000,000,000-2,000,000,000
// For example, if transfer fee is 50% then 10,000 * 50,000 = 500,000
// which represents 50% of 1,000,000,000
if (auto const sle = view.read(keylet::mptIssuance(issuanceID));
sle && sle->isFieldPresent(sfTransferFee))
return Rate{1'000'000'000u + (10'000 * sle->getFieldU16(sfTransferFee))};
if (sle_ && sle_->isFieldPresent(sfTransferFee))
return Rate{1'000'000'000u + 10'000 * sle_->getFieldU16(sfTransferFee)};
return parityRate;
}
[[nodiscard]] TER
canAddHolding(ReadView const& view, MPTIssue const& mptIssue)
MPTokenIssuance::canAddHolding() const
{
auto mptID = mptIssue.getMptID();
auto issuance = view.read(keylet::mptIssuance(mptID));
if (!issuance)
if (!sle_)
{
return tecOBJECT_NOT_FOUND;
}
if (!issuance->isFlag(lsfMPTCanTransfer))
if (!sle_->isFlag(lsfMPTCanTransfer))
{
return tecNO_AUTH;
}
@@ -92,39 +117,33 @@ canAddHolding(ReadView const& view, MPTIssue const& mptIssue)
}
[[nodiscard]] TER
addEmptyHolding(
ApplyView& view,
WritableMPTokenIssuance::addEmptyHolding(
AccountID const& accountID,
XRPAmount priorBalance,
MPTIssue const& mptIssue,
beast::Journal journal)
{
auto const& mptID = mptIssue.getMptID();
auto const mpt = view.peek(keylet::mptIssuance(mptID));
if (!mpt)
if (!mutableSle_)
return tefINTERNAL; // LCOV_EXCL_LINE
if (mpt->isFlag(lsfMPTLocked))
if (mutableSle_->isFlag(lsfMPTLocked))
return tefINTERNAL; // LCOV_EXCL_LINE
if (view.peek(keylet::mptoken(mptID, accountID)))
if (applyView_.peek(keylet::mptoken(mptID_, accountID)))
return tecDUPLICATE;
if (accountID == mptIssue.getIssuer())
if (accountID == mptIssue_.getIssuer())
return tesSUCCESS;
return authorizeMPToken(view, priorBalance, mptID, accountID, journal);
return authorizeMPToken(priorBalance, accountID, journal);
}
[[nodiscard]] TER
authorizeMPToken(
ApplyView& view,
WritableMPTokenIssuance::authorizeMPToken(
XRPAmount const& priorBalance,
MPTID const& mptIssuanceID,
AccountID const& account,
beast::Journal journal,
std::uint32_t flags,
std::optional<AccountID> holderID)
{
auto const sleAcct = view.peek(keylet::account(account));
if (!sleAcct)
WritableAccountRoot wrappedAcct(account, applyView_);
if (!wrappedAcct)
return tecINTERNAL; // LCOV_EXCL_LINE
// If the account that submitted the tx is a holder
@@ -135,20 +154,20 @@ authorizeMPToken(
// When a holder wants to unauthorize/delete a MPT, the ledger must
// - delete mptokenKey from owner directory
// - delete the MPToken
if ((flags & tfMPTUnauthorize) != 0)
if (flags & tfMPTUnauthorize)
{
auto const mptokenKey = keylet::mptoken(mptIssuanceID, account);
auto const sleMpt = view.peek(mptokenKey);
auto const mptokenKey = keylet::mptoken(mptID_, account);
auto const sleMpt = applyView_.peek(mptokenKey);
if (!sleMpt || (*sleMpt)[sfMPTAmount] != 0)
return tecINTERNAL; // LCOV_EXCL_LINE
if (!view.dirRemove(
if (!applyView_.dirRemove(
keylet::ownerDir(account), (*sleMpt)[sfOwnerNode], sleMpt->key(), false))
return tecINTERNAL; // LCOV_EXCL_LINE
adjustOwnerCount(view, sleAcct, -1, journal);
wrappedAcct.adjustOwnerCount(-1, journal);
view.erase(sleMpt);
applyView_.erase(sleMpt);
return tesSUCCESS;
}
@@ -161,52 +180,50 @@ authorizeMPToken(
// 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.
std::uint32_t const uOwnerCount = sleAcct->getFieldU32(sfOwnerCount);
std::uint32_t const uOwnerCount = wrappedAcct->getFieldU32(sfOwnerCount);
XRPAmount const reserveCreate(
(uOwnerCount < 2) ? XRPAmount(beast::zero)
: view.fees().accountReserve(uOwnerCount + 1));
: applyView_.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));
if (!mpt || mpt->getAccountID(sfIssuer) == account)
if (!mutableSle_ || mutableSle_->getAccountID(sfIssuer) == account)
{
// LCOV_EXCL_START
UNREACHABLE("xrpl::authorizeMPToken : invalid issuance or issuers token");
if (view.rules().enabled(featureLendingProtocol))
if (applyView_.rules().enabled(featureLendingProtocol))
return tecINTERNAL;
// LCOV_EXCL_STOP
}
auto const mptokenKey = keylet::mptoken(mptIssuanceID, account);
auto const mptokenKey = keylet::mptoken(mptID_, account);
auto mptoken = std::make_shared<SLE>(mptokenKey);
if (auto ter = dirLink(view, account, mptoken))
if (auto ter = dirLink(applyView_, account, mptoken))
return ter; // LCOV_EXCL_LINE
(*mptoken)[sfAccount] = account;
(*mptoken)[sfMPTokenIssuanceID] = mptIssuanceID;
(*mptoken)[sfMPTokenIssuanceID] = mptID_;
(*mptoken)[sfFlags] = 0;
view.insert(mptoken);
applyView_.insert(mptoken);
// Update owner count.
adjustOwnerCount(view, sleAcct, 1, journal);
wrappedAcct.adjustOwnerCount(1, journal);
return tesSUCCESS;
}
auto const sleMptIssuance = view.read(keylet::mptIssuance(mptIssuanceID));
if (!sleMptIssuance)
if (!mutableSle_)
return tecINTERNAL; // LCOV_EXCL_LINE
// If the account that submitted this tx is the issuer of the MPT
// Note: `account_` is issuer's account
// `holderID` is holder's account
if (account != (*sleMptIssuance)[sfIssuer])
if (account != (*mutableSle_)[sfIssuer])
return tecINTERNAL; // LCOV_EXCL_LINE
auto const sleMpt = view.peek(keylet::mptoken(mptIssuanceID, *holderID));
auto const sleMpt = applyView_.peek(keylet::mptoken(mptID_, *holderID));
if (!sleMpt)
return tecINTERNAL; // LCOV_EXCL_LINE
@@ -215,7 +232,7 @@ authorizeMPToken(
// Issuer wants to unauthorize the holder, unset lsfMPTAuthorized on
// their MPToken
if ((flags & tfMPTUnauthorize) != 0)
if (flags & tfMPTUnauthorize)
{
flagsOut &= ~lsfMPTAuthorized;
}
@@ -229,23 +246,18 @@ authorizeMPToken(
if (flagsIn != flagsOut)
sleMpt->setFieldU32(sfFlags, flagsOut);
view.update(sleMpt);
applyView_.update(sleMpt);
return tesSUCCESS;
}
[[nodiscard]] TER
removeEmptyHolding(
ApplyView& view,
AccountID const& accountID,
MPTIssue const& mptIssue,
beast::Journal journal)
WritableMPTokenIssuance::removeEmptyHolding(AccountID const& accountID, beast::Journal journal)
{
// If the account is the issuer, then no token should exist. MPTs do not
// have the legacy ability to create such a situation, but check anyway. If
// a token does exist, it will get deleted. If not, return success.
bool const accountIsIssuer = accountID == mptIssue.getIssuer();
auto const& mptID = mptIssue.getMptID();
auto const mptoken = view.peek(keylet::mptoken(mptID, accountID));
bool const accountIsIssuer = accountID == mptIssue_.getIssuer();
auto const mptoken = applyView_.peek(keylet::mptoken(mptID_, accountID));
if (!mptoken)
return accountIsIssuer ? (TER)tesSUCCESS : (TER)tecOBJECT_NOT_FOUND;
// Unlike a trust line, if the account is the issuer, and the token has a
@@ -256,9 +268,7 @@ removeEmptyHolding(
return tecHAS_OBLIGATIONS;
return authorizeMPToken(
view,
{}, // priorBalance
mptID,
accountID,
journal,
tfMPTUnauthorize // flags
@@ -266,25 +276,18 @@ removeEmptyHolding(
}
[[nodiscard]] TER
requireAuth(
ReadView const& view,
MPTIssue const& mptIssue,
AccountID const& account,
AuthType authType,
int depth)
MPTokenIssuance::requireAuth(AccountID const& account, AuthType authType, int depth) const
{
auto const mptID = keylet::mptIssuance(mptIssue.getMptID());
auto const sleIssuance = view.read(mptID);
if (!sleIssuance)
if (!sle_)
return tecOBJECT_NOT_FOUND;
auto const mptIssuer = sleIssuance->getAccountID(sfIssuer);
auto const mptIssuer = AccountRoot(sle_->getAccountID(sfIssuer), readView_);
// issuer is always "authorized"
if (mptIssuer == account) // Issuer won't have MPToken
return tesSUCCESS;
bool const featureSAVEnabled = view.rules().enabled(featureSingleAssetVault);
bool const featureSAVEnabled = readView_.rules().enabled(featureSingleAssetVault);
if (featureSAVEnabled)
{
@@ -292,36 +295,24 @@ requireAuth(
return tecINTERNAL; // LCOV_EXCL_LINE
// requireAuth is recursive if the issuer is a vault pseudo-account
auto const sleIssuer = view.read(keylet::account(mptIssuer));
if (!sleIssuer)
if (!mptIssuer.exists())
return tefINTERNAL; // LCOV_EXCL_LINE
if (sleIssuer->isFieldPresent(sfVaultID))
if (mptIssuer->isFieldPresent(sfVaultID))
{
auto const sleVault = view.read(keylet::vault(sleIssuer->getFieldH256(sfVaultID)));
auto const sleVault = readView_.read(keylet::vault(mptIssuer->getFieldH256(sfVaultID)));
if (!sleVault)
return tefINTERNAL; // LCOV_EXCL_LINE
auto const asset = sleVault->at(sfAsset);
if (auto const err = std::visit(
[&]<ValidIssueType TIss>(TIss const& issue) {
if constexpr (std::is_same_v<TIss, Issue>)
{
return requireAuth(view, issue, account, authType);
}
else
{
return requireAuth(view, issue, account, authType, depth + 1);
}
},
asset.value());
if (auto const err =
makeTokenBase(readView_, asset)->requireAuth(account, authType, depth + 1);
!isTesSuccess(err))
return err;
}
}
auto const mptokenID = keylet::mptoken(mptID.key, account);
auto const sleToken = view.read(mptokenID);
auto const sleToken = readView_.read(keylet::mptoken(mptID_, account));
// if account has no MPToken, fail
if (!sleToken && (authType == AuthType::StrongAuth || authType == AuthType::Legacy))
@@ -329,14 +320,14 @@ requireAuth(
// Note, this check is not amendment-gated because DomainID will be always
// empty **unless** writing to it has been enabled by an amendment
auto const maybeDomainID = sleIssuance->at(~sfDomainID);
auto const maybeDomainID = sle_->at(~sfDomainID);
if (maybeDomainID)
{
XRPL_ASSERT(
sleIssuance->getFieldU32(sfFlags) & lsfMPTRequireAuth,
sle_->getFieldU32(sfFlags) & lsfMPTRequireAuth,
"xrpl::requireAuth : issuance requires authorization");
// ter = tefINTERNAL | tecOBJECT_NOT_FOUND | tecNO_AUTH | tecEXPIRED
auto const ter = credentials::validDomain(view, *maybeDomainID, account);
auto const ter = credentials::validDomain(readView_, *maybeDomainID, account);
if (isTesSuccess(ter))
{
return ter; // Note: sleToken might be null
@@ -352,47 +343,43 @@ requireAuth(
if (featureSAVEnabled)
{
// Implicitly authorize Vault and LoanBroker pseudo-accounts
if (isPseudoAccount(view, account, {&sfVaultID, &sfLoanBrokerID}))
if (isPseudoAccount(readView_, account, {&sfVaultID, &sfLoanBrokerID}))
return tesSUCCESS;
}
// mptoken must be authorized if issuance enabled requireAuth
if (sleIssuance->isFlag(lsfMPTRequireAuth) &&
(!sleToken || !sleToken->isFlag(lsfMPTAuthorized)))
if (sle_->isFlag(lsfMPTRequireAuth) && (!sleToken || !sleToken->isFlag(lsfMPTAuthorized)))
return tecNO_AUTH;
return tesSUCCESS; // Note: sleToken might be null
}
[[nodiscard]] TER
enforceMPTokenAuthorization(
ApplyView& view,
MPTID const& mptIssuanceID,
WritableMPTokenIssuance::enforceMPTokenAuthorization(
AccountID const& account,
XRPAmount const& priorBalance, // for MPToken authorization
beast::Journal j)
{
auto const sleIssuance = view.read(keylet::mptIssuance(mptIssuanceID));
if (!sleIssuance)
if (!mutableSle_)
return tefINTERNAL; // LCOV_EXCL_LINE
XRPL_ASSERT(
sleIssuance->isFlag(lsfMPTRequireAuth),
mutableSle_->isFlag(lsfMPTRequireAuth),
"xrpl::enforceMPTokenAuthorization : authorization required");
if (account == sleIssuance->at(sfIssuer))
if (account == mutableSle_->at(sfIssuer))
return tefINTERNAL; // LCOV_EXCL_LINE
auto const keylet = keylet::mptoken(mptIssuanceID, account);
auto const sleToken = view.read(keylet); // NOTE: might be null
auto const maybeDomainID = sleIssuance->at(~sfDomainID);
auto const keylet = keylet::mptoken(mptID_, account);
auto const sleToken = readView_.read(keylet); // NOTE: might be null
auto const maybeDomainID = mutableSle_->at(~sfDomainID);
bool expired = false;
bool const authorizedByDomain = [&]() -> bool {
// NOTE: defensive here, should be checked in preclaim
if (!maybeDomainID.has_value())
if (!maybeDomainID)
return false; // LCOV_EXCL_LINE
auto const ter = verifyValidDomain(view, account, *maybeDomainID, j);
auto const ter = verifyValidDomain(applyView(), account, *maybeDomainID, j);
if (isTesSuccess(ter))
return true;
if (ter == tecEXPIRED)
@@ -411,7 +398,7 @@ enforceMPTokenAuthorization(
// Either way, return tecNO_AUTH and there is nothing else to do
return expired ? tecEXPIRED : tecNO_AUTH;
}
if (!authorizedByDomain && maybeDomainID.has_value())
if (!authorizedByDomain && maybeDomainID)
{
// Found an MPToken but the account is not authorized and we expect
// it to have been authorized by the domain. This could be because the
@@ -423,7 +410,7 @@ enforceMPTokenAuthorization(
// We found an MPToken, but sfDomainID is not set, so this is a classic
// MPToken which requires authorization by the token issuer.
XRPL_ASSERT(
sleToken != nullptr && !maybeDomainID.has_value(),
sleToken != nullptr && !maybeDomainID,
"xrpl::enforceMPTokenAuthorization : found MPToken");
if (sleToken->isFlag(lsfMPTAuthorized))
return tesSUCCESS;
@@ -434,9 +421,7 @@ enforceMPTokenAuthorization(
{
// Found an MPToken, authorized by the domain. Ignore authorization flag
// lsfMPTAuthorized because it is meaningless. Return tesSUCCESS
XRPL_ASSERT(
maybeDomainID.has_value(),
"xrpl::enforceMPTokenAuthorization : found MPToken for domain");
XRPL_ASSERT(maybeDomainID, "xrpl::enforceMPTokenAuthorization : found MPToken for domain");
return tesSUCCESS;
}
if (authorizedByDomain)
@@ -444,13 +429,11 @@ enforceMPTokenAuthorization(
// Could not find MPToken but there should be one because we are
// authorized by domain. Proceed to create it, then return tesSUCCESS
XRPL_ASSERT(
maybeDomainID.has_value() && sleToken == nullptr,
maybeDomainID && sleToken == nullptr,
"xrpl::enforceMPTokenAuthorization : new MPToken for domain");
if (auto const err = authorizeMPToken(
view,
priorBalance, // priorBalance
mptIssuanceID, // mptIssuanceID
account, // account
priorBalance, // priorBalance
account, // account
j);
!isTesSuccess(err))
return err;
@@ -465,25 +448,41 @@ enforceMPTokenAuthorization(
}
TER
canTransfer(
ReadView const& view,
MPTIssue const& mptIssue,
AccountID const& from,
AccountID const& to)
MPTokenIssuance::canTransfer(AccountID const& from, AccountID const& to) const
{
auto const mptID = keylet::mptIssuance(mptIssue.getMptID());
auto const sleIssuance = view.read(mptID);
if (!sleIssuance)
if (!sle_)
return tecOBJECT_NOT_FOUND;
if (!sleIssuance->isFlag(lsfMPTCanTransfer))
if (!(sle_->getFieldU32(sfFlags) & lsfMPTCanTransfer))
{
if (from != (*sleIssuance)[sfIssuer] && to != (*sleIssuance)[sfIssuer])
if (from != (*sle_)[sfIssuer] && to != (*sle_)[sfIssuer])
return TER{tecNO_AUTH};
}
return tesSUCCESS;
}
//------------------------------------------------------------------------------
//
// Token capability checks (MPT-specific)
//
//------------------------------------------------------------------------------
bool
MPTokenIssuance::canClawback() const
{
if (!sle_)
return false;
return sle_->isFlag(lsfMPTCanClawback);
}
bool
MPTokenIssuance::requiresAuth() const
{
if (!sle_)
return false;
return sle_->isFlag(lsfMPTRequireAuth);
}
TER
rippleLockEscrowMPT(
ApplyView& view,
@@ -492,16 +491,15 @@ rippleLockEscrowMPT(
beast::Journal j)
{
auto const mptIssue = amount.get<MPTIssue>();
auto const mptID = keylet::mptIssuance(mptIssue.getMptID());
auto sleIssuance = view.peek(mptID);
if (!sleIssuance)
auto mptIssuance = WritableMPTokenIssuance(view, mptIssue);
if (!mptIssuance.exists())
{ // LCOV_EXCL_START
JLOG(j.error()) << "rippleLockEscrowMPT: MPT issuance not found for "
<< mptIssue.getMptID();
return tecOBJECT_NOT_FOUND;
} // LCOV_EXCL_STOP
if (amount.getIssuer() == sender)
if (mptIssuance.getIssuer() == sender)
{ // LCOV_EXCL_START
JLOG(j.error()) << "rippleLockEscrowMPT: sender is the issuer, cannot lock MPTs.";
return tecINTERNAL;
@@ -510,7 +508,7 @@ rippleLockEscrowMPT(
// 1. Decrease the MPT Holder MPTAmount
// 2. Increase the MPT Holder EscrowedAmount
{
auto const mptokenID = keylet::mptoken(mptID.key, sender);
auto const mptokenID = keylet::mptoken(mptIssuance.getMptID(), sender);
auto sle = view.peek(mptokenID);
if (!sle)
{ // LCOV_EXCL_START
@@ -556,7 +554,7 @@ rippleLockEscrowMPT(
// 1. Increase the Issuance EscrowedAmount
// 2. DO NOT change the Issuance OutstandingAmount
{
uint64_t const issuanceEscrowed = (*sleIssuance)[~sfLockedAmount].value_or(0);
uint64_t const issuanceEscrowed = (*mptIssuance)[~sfLockedAmount].value_or(0);
auto const pay = amount.mpt().value();
// Overflow check for addition
@@ -568,16 +566,16 @@ rippleLockEscrowMPT(
return tecINTERNAL;
} // LCOV_EXCL_STOP
if (sleIssuance->isFieldPresent(sfLockedAmount))
if (mptIssuance->isFieldPresent(sfLockedAmount))
{
(*sleIssuance)[sfLockedAmount] += pay;
(*mptIssuance)[sfLockedAmount] += pay;
}
else
{
sleIssuance->setFieldU64(sfLockedAmount, pay);
mptIssuance->setFieldU64(sfLockedAmount, pay);
}
view.update(sleIssuance);
mptIssuance.update();
}
return tesSUCCESS;
}
@@ -599,9 +597,8 @@ rippleUnlockEscrowMPT(
auto const& issuer = netAmount.getIssuer();
auto const& mptIssue = netAmount.get<MPTIssue>();
auto const mptID = keylet::mptIssuance(mptIssue.getMptID());
auto sleIssuance = view.peek(mptID);
if (!sleIssuance)
auto mptIssuance = WritableMPTokenIssuance(view, mptIssue);
if (!mptIssuance)
{ // LCOV_EXCL_START
JLOG(j.error()) << "rippleUnlockEscrowMPT: MPT issuance not found for "
<< mptIssue.getMptID();
@@ -610,14 +607,14 @@ rippleUnlockEscrowMPT(
// Decrease the Issuance EscrowedAmount
{
if (!sleIssuance->isFieldPresent(sfLockedAmount))
if (!mptIssuance->isFieldPresent(sfLockedAmount))
{ // LCOV_EXCL_START
JLOG(j.error()) << "rippleUnlockEscrowMPT: no locked amount in issuance for "
<< mptIssue.getMptID();
return tecINTERNAL;
} // LCOV_EXCL_STOP
auto const locked = sleIssuance->getFieldU64(sfLockedAmount);
auto const locked = mptIssuance->getFieldU64(sfLockedAmount);
auto const redeem = grossAmount.mpt().value();
// Underflow check for subtraction
@@ -631,19 +628,19 @@ rippleUnlockEscrowMPT(
auto const newLocked = locked - redeem;
if (newLocked == 0)
{
sleIssuance->makeFieldAbsent(sfLockedAmount);
mptIssuance->makeFieldAbsent(sfLockedAmount);
}
else
{
sleIssuance->setFieldU64(sfLockedAmount, newLocked);
mptIssuance->setFieldU64(sfLockedAmount, newLocked);
}
view.update(sleIssuance);
mptIssuance.update();
}
if (issuer != receiver)
{
// Increase the MPT Holder MPTAmount
auto const mptokenID = keylet::mptoken(mptID.key, receiver);
auto const mptokenID = keylet::mptoken(mptIssue.getMptID(), receiver);
auto sle = view.peek(mptokenID);
if (!sle)
{ // LCOV_EXCL_START
@@ -668,7 +665,7 @@ rippleUnlockEscrowMPT(
else
{
// Decrease the Issuance OutstandingAmount
auto const outstanding = sleIssuance->getFieldU64(sfOutstandingAmount);
auto const outstanding = mptIssuance->getFieldU64(sfOutstandingAmount);
auto const redeem = netAmount.mpt().value();
// Underflow check for subtraction
@@ -679,8 +676,8 @@ rippleUnlockEscrowMPT(
return tecINTERNAL;
} // LCOV_EXCL_STOP
sleIssuance->setFieldU64(sfOutstandingAmount, outstanding - redeem);
view.update(sleIssuance);
mptIssuance->setFieldU64(sfOutstandingAmount, outstanding - redeem);
mptIssuance.update();
}
if (issuer == sender)
@@ -690,7 +687,7 @@ rippleUnlockEscrowMPT(
return tecINTERNAL;
} // LCOV_EXCL_STOP
// Decrease the MPT Holder EscrowedAmount
auto const mptokenID = keylet::mptoken(mptID.key, sender);
auto const mptokenID = keylet::mptoken(mptIssue.getMptID(), sender);
auto sle = view.peek(mptokenID);
if (!sle)
{ // LCOV_EXCL_START
@@ -734,7 +731,7 @@ rippleUnlockEscrowMPT(
auto const diff = grossAmount.mpt().value() - netAmount.mpt().value();
if (diff != 0)
{
auto const outstanding = sleIssuance->getFieldU64(sfOutstandingAmount);
auto const outstanding = mptIssuance->getFieldU64(sfOutstandingAmount);
// Underflow check for subtraction
if (!canSubtract(STAmount(mptIssue, outstanding), STAmount(mptIssue, diff)))
{ // LCOV_EXCL_START
@@ -743,10 +740,80 @@ rippleUnlockEscrowMPT(
return tecINTERNAL;
} // LCOV_EXCL_STOP
sleIssuance->setFieldU64(sfOutstandingAmount, outstanding - diff);
view.update(sleIssuance);
mptIssuance->setFieldU64(sfOutstandingAmount, outstanding - diff);
mptIssuance.update();
}
return tesSUCCESS;
}
STAmount
MPTokenIssuance::accountHolds(
AccountID const& account,
FreezeHandling zeroIfFrozen,
beast::Journal j,
SpendableHandling includeFullBalance) const
{
return accountHolds(account, zeroIfFrozen, ahIGNORE_AUTH, j, includeFullBalance);
}
STAmount
MPTokenIssuance::accountHolds(
AccountID const& account,
FreezeHandling zeroIfFrozen,
AuthHandling zeroIfUnauthorized,
beast::Journal j,
SpendableHandling includeFullBalance) const
{
bool const returnSpendable = (includeFullBalance == shFULL_BALANCE);
if (returnSpendable && account == mptIssue_.getIssuer())
{
// if the account is the issuer, and the issuance exists, their limit is
// the issuance limit minus the outstanding value
if (!sle_)
{
return STAmount{mptIssue_};
}
return STAmount{
mptIssue_,
sle_->at(~sfMaximumAmount).value_or(maxMPTokenAmount) - sle_->at(sfOutstandingAmount)};
}
STAmount amount;
auto const sleMpt = readView_.read(keylet::mptoken(mptID_, account));
if (!sleMpt)
{
amount.clear(mptIssue_);
}
else if (zeroIfFrozen == fhZERO_IF_FROZEN && isFrozen(account))
{
amount.clear(mptIssue_);
}
else
{
amount = STAmount{mptIssue_, sleMpt->getFieldU64(sfMPTAmount)};
// Only if auth check is needed, as it needs to do an additional read
// operation. Note featureSingleAssetVault will affect error codes.
if (zeroIfUnauthorized == ahZERO_IF_UNAUTHORIZED &&
readView_.rules().enabled(featureSingleAssetVault))
{
if (auto const err = requireAuth(account, AuthType::StrongAuth); !isTesSuccess(err))
amount.clear(mptIssue_);
}
else if (zeroIfUnauthorized == ahZERO_IF_UNAUTHORIZED)
{
// if auth is enabled on the issuance and mpt is not authorized,
// clear amount
if (sle_ && sle_->isFlag(lsfMPTRequireAuth) && !sleMpt->isFlag(lsfMPTAuthorized))
amount.clear(mptIssue_);
}
}
return amount;
}
} // namespace xrpl

View File

@@ -48,7 +48,8 @@ offerDelete(ApplyView& view, std::shared_ptr<SLE> const& sle, beast::Journal j)
}
}
adjustOwnerCount(view, view.peek(keylet::account(owner)), -1, j);
WritableAccountRoot wrappedOwner(owner, view);
wrappedOwner.adjustOwnerCount(-1, j);
view.erase(sle);

View File

@@ -3,6 +3,7 @@
#include <xrpl/basics/Log.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/DirectoryHelpers.h>
#include <xrpl/protocol/AmountConversions.h>
@@ -21,14 +22,14 @@ namespace xrpl {
STAmount
creditLimit(
ReadView const& view,
ReadView const& readView_,
AccountID const& account,
AccountID const& issuer,
Currency const& currency)
{
STAmount result(Issue{currency, account});
auto sleRippleState = view.read(keylet::line(account, issuer, currency));
auto sleRippleState = readView_.read(keylet::line(account, issuer, currency));
if (sleRippleState)
{
@@ -49,14 +50,14 @@ creditLimit2(ReadView const& v, AccountID const& acc, AccountID const& iss, Curr
STAmount
creditBalance(
ReadView const& view,
ReadView const& readView_,
AccountID const& account,
AccountID const& issuer,
Currency const& currency)
{
STAmount result(Issue{currency, account});
auto sleRippleState = view.read(keylet::line(account, issuer, currency));
auto sleRippleState = readView_.read(keylet::line(account, issuer, currency));
if (sleRippleState)
{
@@ -78,19 +79,15 @@ creditBalance(
//------------------------------------------------------------------------------
bool
isIndividualFrozen(
ReadView const& view,
AccountID const& account,
Currency const& currency,
AccountID const& issuer)
IOUToken::isIndividualFrozen(AccountID const& account) const
{
if (isXRP(currency))
if (isXRP(currency_))
return false;
if (issuer != account)
if (issuer_ != account)
{
// Check if the issuer froze the line
auto const sle = view.read(keylet::line(account, issuer, currency));
if (sle && sle->isFlag((issuer > account) ? lsfHighFreeze : lsfLowFreeze))
auto const sle = readView_.read(keylet::line(account, issuer_, currency_));
if (sle && sle->isFlag((issuer_ > account) ? lsfHighFreeze : lsfLowFreeze))
return true;
}
return false;
@@ -99,45 +96,38 @@ isIndividualFrozen(
// Can the specified account spend the specified currency issued by
// the specified issuer or does the freeze flag prohibit it?
bool
isFrozen(
ReadView const& view,
AccountID const& account,
Currency const& currency,
AccountID const& issuer)
IOUToken::isFrozen(AccountID const& account, int depth) const
{
if (isXRP(currency))
// NOTE: depth is ignored here because it's only relevant for MPTs
if (isXRP(currency_))
return false;
auto sle = view.read(keylet::account(issuer));
if (sle && sle->isFlag(lsfGlobalFreeze))
if (issuerAccount_.exists() && issuerAccount_->isFlag(lsfGlobalFreeze))
return true;
if (issuer != account)
if (issuer_ != account)
{
// Check if the issuer froze the line
sle = view.read(keylet::line(account, issuer, currency));
if (sle && sle->isFlag((issuer > account) ? lsfHighFreeze : lsfLowFreeze))
auto const sleLine = readView_.read(keylet::line(account, issuer_, currency_));
if (sleLine && sleLine->isFlag((issuer_ > account) ? lsfHighFreeze : lsfLowFreeze))
return true;
}
return false;
}
bool
isDeepFrozen(
ReadView const& view,
AccountID const& account,
Currency const& currency,
AccountID const& issuer)
IOUToken::isDeepFrozen(AccountID const& account, int depth) const
{
if (isXRP(currency))
// NOTE: depth is ignored here because it's only relevant for MPTs
if (isXRP(currency_))
{
return false;
}
if (issuer == account)
if (issuer_ == account)
{
return false;
}
auto const sle = view.read(keylet::line(account, issuer, currency));
auto const sle = readView_.read(keylet::line(account, issuer_, currency_));
if (!sle)
{
return false;
@@ -146,6 +136,166 @@ isDeepFrozen(
return sle->isFlag(lsfHighDeepFreeze) || sle->isFlag(lsfLowDeepFreeze);
}
TER
IOUToken::checkFrozen(AccountID const& account) const
{
return isFrozen(account) ? TER{tecFROZEN} : TER{tesSUCCESS};
}
TER
IOUToken::checkDeepFrozen(AccountID const& account) const
{
return isDeepFrozen(account) ? TER{tecFROZEN} : TER{tesSUCCESS};
}
bool
IOUToken::isAnyFrozen(std::initializer_list<AccountID> const& accounts, int depth) const
{
// NOTE: depth is ignored here because it's only relevant for MPTs
if (isGlobalFrozen())
return true;
for (auto const& account : accounts)
{
if (isFrozen(account, depth))
return true;
}
return false;
}
STAmount
IOUToken::accountFunds(
AccountID const& id,
STAmount const& saDefault,
FreezeHandling freezeHandling,
beast::Journal j) const
{
if (!saDefault.native() && saDefault.getIssuer() == id)
return saDefault;
return accountHolds(id, freezeHandling, j);
}
STAmount
IOUToken::accountHolds(
AccountID const& account,
FreezeHandling zeroIfFrozen,
beast::Journal j,
SpendableHandling includeFullBalance) const
{
return accountHolds(account, zeroIfFrozen, ahIGNORE_AUTH, j, includeFullBalance);
}
STAmount
IOUToken::accountHolds(
AccountID const& account,
FreezeHandling zeroIfFrozen,
AuthHandling zeroIfUnauthorized,
beast::Journal j,
SpendableHandling includeFullBalance) const
{
if (isXRP(currency_))
{
AccountRoot accountRoot(account, readView_);
return {accountRoot.xrpLiquid(0, j)};
}
bool const returnSpendable = (includeFullBalance == shFULL_BALANCE);
if (returnSpendable && account == issuer_)
{
// If the account is the issuer, then their limit is effectively
// infinite
return STAmount{issue_, STAmount::cMaxValue, STAmount::cMaxOffset};
}
// IOU: Return balance on trust line modulo freeze
// Check if line exists and is usable (mirrors old getLineIfUsable)
SLE::const_pointer sle = readView_.read(keylet::line(account, issuer_, currency_));
if (sle && zeroIfFrozen == fhZERO_IF_FROZEN)
{
if (isFrozen(account) || isDeepFrozen(account))
{
sle = nullptr;
}
// when fixFrozenLPTokenTransfer is enabled, if currency is lptoken,
// we need to check if the associated assets have been frozen
if (sle && readView_.rules().enabled(fixFrozenLPTokenTransfer))
{
auto const sleIssuer = readView_.read(keylet::account(issuer_));
if (!sleIssuer)
{
sle = nullptr; // LCOV_EXCL_LINE
}
else if (sleIssuer->isFieldPresent(sfAMMID))
{
auto const sleAmm = readView_.read(keylet::amm((*sleIssuer)[sfAMMID]));
if (!sleAmm ||
isLPTokenFrozen(
readView_,
account,
(*sleAmm)[sfAsset].get<Issue>(),
(*sleAmm)[sfAsset2].get<Issue>()))
{
sle = nullptr;
}
}
}
}
// Extract balance (mirrors old getTrustLineBalance)
STAmount amount;
if (sle)
{
amount = sle->getFieldAmount(sfBalance);
bool const accountHigh = account > issuer_;
auto const& oppositeField = accountHigh ? sfLowLimit : sfHighLimit;
if (accountHigh)
{
// Put balance in account terms.
amount.negate();
}
if (returnSpendable)
{
amount += sle->getFieldAmount(oppositeField);
}
amount.setIssuer(issuer_);
}
else
{
amount.clear(Issue{currency_, issuer_});
}
JLOG(j.trace()) << "IOUToken::accountHolds:" << " account=" << to_string(account)
<< " amount=" << amount.getFullText();
return readView_.balanceHook(account, issuer_, amount);
}
TER
IOUToken::canAddHolding() const
{
if (isXRP(issue_))
return tesSUCCESS;
if (!issuerAccount_.exists())
return terNO_ACCOUNT;
if (!issuerAccount_->isFlag(lsfDefaultRipple))
return terNO_RIPPLE;
return tesSUCCESS;
}
Rate
IOUToken::transferRate() const
{
return issuerAccount_.transferRate();
}
//------------------------------------------------------------------------------
//
// Trust line operations
@@ -158,16 +308,16 @@ trustCreate(
bool const bSrcHigh,
AccountID const& uSrcAccountID,
AccountID const& uDstAccountID,
uint256 const& uIndex, // --> ripple state entry
SLE::ref sleAccount, // --> the account being set.
bool const bAuth, // --> authorize account.
bool const bNoRipple, // --> others cannot ripple through
bool const bFreeze, // --> funds cannot leave
bool bDeepFreeze, // --> can neither receive nor send funds
STAmount const& saBalance, // --> balance of account being set.
// Issuer should be noAccount()
STAmount const& saLimit, // --> limit for account being set.
// Issuer should be the account being set.
uint256 const& uIndex, // ripple state entry
WritableAccountRoot& wrappedAcct, // the account being set.
bool const bAuth, // authorize account.
bool const bNoRipple, // others cannot ripple through
bool const bFreeze, // funds cannot leave
bool bDeepFreeze, // can neither receive nor send funds
STAmount const& saBalance, // balance of account being set.
// Issuer should be noAccount()
STAmount const& saLimit, // limit for account being set.
// Issuer should be the account being set.
std::uint32_t uQualityIn,
std::uint32_t uQualityOut,
beast::Journal j)
@@ -204,15 +354,15 @@ trustCreate(
bool const bSetDst = saLimit.getIssuer() == uDstAccountID;
bool const bSetHigh = bSrcHigh ^ bSetDst;
XRPL_ASSERT(sleAccount, "xrpl::trustCreate : non-null SLE");
if (!sleAccount)
XRPL_ASSERT(wrappedAcct, "xrpl::trustCreate : non-null SLE");
if (!wrappedAcct)
return tefINTERNAL; // LCOV_EXCL_LINE
XRPL_ASSERT(
sleAccount->getAccountID(sfAccount) == (bSetHigh ? uHighAccountID : uLowAccountID),
wrappedAcct->getAccountID(sfAccount) == (bSetHigh ? uHighAccountID : uLowAccountID),
"xrpl::trustCreate : matching account ID");
auto const slePeer = view.peek(keylet::account(bSetHigh ? uLowAccountID : uHighAccountID));
if (!slePeer)
auto const peer = AccountRoot(bSetHigh ? uLowAccountID : uHighAccountID, view);
if (!peer.exists())
return tecNO_TARGET;
// Remember deletion hints.
@@ -224,10 +374,10 @@ trustCreate(
bSetHigh ? sfLowLimit : sfHighLimit,
STAmount(Issue{saBalance.getCurrency(), bSetDst ? uSrcAccountID : uDstAccountID}));
if (uQualityIn != 0u)
if (uQualityIn)
sleRippleState->setFieldU32(bSetHigh ? sfHighQualityIn : sfLowQualityIn, uQualityIn);
if (uQualityOut != 0u)
if (uQualityOut)
sleRippleState->setFieldU32(bSetHigh ? sfHighQualityOut : sfLowQualityOut, uQualityOut);
std::uint32_t uFlags = bSetHigh ? lsfHighReserve : lsfLowReserve;
@@ -249,14 +399,14 @@ trustCreate(
uFlags |= (bSetHigh ? lsfHighDeepFreeze : lsfLowDeepFreeze);
}
if ((slePeer->getFlags() & lsfDefaultRipple) == 0)
if ((peer->getFlags() & lsfDefaultRipple) == 0)
{
// The other side's default is no rippling
uFlags |= (bSetHigh ? lsfLowNoRipple : lsfHighNoRipple);
}
sleRippleState->setFieldU32(sfFlags, uFlags);
adjustOwnerCount(view, sleAccount, 1, j);
wrappedAcct.adjustOwnerCount(1, j);
// ONLY: Create ripple balance.
sleRippleState->setFieldAmount(sfBalance, bSetHigh ? -saBalance : saBalance);
@@ -318,8 +468,8 @@ updateTrustLine(
return false;
std::uint32_t const flags(state->getFieldU32(sfFlags));
auto sle = view.peek(keylet::account(sender));
if (!sle)
WritableAccountRoot wrappedAcct(sender, view);
if (!wrappedAcct)
return false;
// YYY Could skip this if rippling in reverse.
@@ -327,28 +477,28 @@ updateTrustLine(
// Sender balance was positive.
&& after <= beast::zero
// Sender is zero or negative.
&& ((flags & (!bSenderHigh ? lsfLowReserve : lsfHighReserve)) != 0u)
&& (flags & (!bSenderHigh ? lsfLowReserve : lsfHighReserve))
// Sender reserve is set.
&& static_cast<bool>(flags & (!bSenderHigh ? lsfLowNoRipple : lsfHighNoRipple)) !=
static_cast<bool>(sle->getFlags() & lsfDefaultRipple) &&
((flags & (!bSenderHigh ? lsfLowFreeze : lsfHighFreeze)) == 0u) &&
static_cast<bool>(wrappedAcct->getFlags() & lsfDefaultRipple) &&
!(flags & (!bSenderHigh ? lsfLowFreeze : lsfHighFreeze)) &&
!state->getFieldAmount(!bSenderHigh ? sfLowLimit : sfHighLimit)
// Sender trust limit is 0.
&& (state->getFieldU32(!bSenderHigh ? sfLowQualityIn : sfHighQualityIn) == 0u)
&& !state->getFieldU32(!bSenderHigh ? sfLowQualityIn : sfHighQualityIn)
// Sender quality in is 0.
&& (state->getFieldU32(!bSenderHigh ? sfLowQualityOut : sfHighQualityOut) == 0u))
&& !state->getFieldU32(!bSenderHigh ? sfLowQualityOut : sfHighQualityOut))
// Sender quality out is 0.
{
// VFALCO Where is the line being deleted?
// Clear the reserve of the sender, possibly delete the line!
adjustOwnerCount(view, sle, -1, j);
wrappedAcct.adjustOwnerCount(-1, j);
// Clear reserve flag.
state->setFieldU32(sfFlags, flags & (!bSenderHigh ? ~lsfLowReserve : ~lsfHighReserve));
// Balance is zero, receiver reserve is clear.
if (!after // Balance is zero.
&& ((flags & (bSenderHigh ? lsfLowReserve : lsfHighReserve)) == 0u))
&& !(flags & (bSenderHigh ? lsfLowReserve : lsfHighReserve)))
return true;
}
return false;
@@ -424,7 +574,7 @@ issueIOU(
final_balance.setIssuer(noAccount());
auto const receiverAccount = view.peek(keylet::account(account));
WritableAccountRoot receiverAccount(account, view);
if (!receiverAccount)
return tefINTERNAL; // LCOV_EXCL_LINE
@@ -450,7 +600,7 @@ issueIOU(
TER
redeemIOU(
ApplyView& view,
ApplyView& applyView,
AccountID const& account,
STAmount const& amount,
Issue const& issue,
@@ -470,7 +620,7 @@ redeemIOU(
bool bSenderHigh = account > issue.account;
if (auto state = view.peek(keylet::line(account, issue.account, issue.currency)))
if (auto state = applyView.peek(keylet::line(account, issue.account, issue.currency)))
{
STAmount final_balance = state->getFieldAmount(sfBalance);
@@ -481,10 +631,10 @@ redeemIOU(
final_balance -= amount;
auto const must_delete =
updateTrustLine(view, state, bSenderHigh, account, start_balance, final_balance, j);
auto const must_delete = updateTrustLine(
applyView, state, bSenderHigh, account, start_balance, final_balance, j);
view.creditHook(account, issue.account, amount, start_balance);
applyView.creditHook(account, issue.account, amount, start_balance);
if (bSenderHigh)
final_balance.negate();
@@ -497,14 +647,14 @@ redeemIOU(
if (must_delete)
{
return trustDelete(
view,
applyView,
state,
bSenderHigh ? issue.account : account,
bSenderHigh ? account : issue.account,
j);
}
view.update(state);
applyView.update(state);
return tesSUCCESS;
}
@@ -526,25 +676,24 @@ redeemIOU(
//------------------------------------------------------------------------------
TER
requireAuth(ReadView const& view, Issue const& issue, AccountID const& account, AuthType authType)
IOUToken::requireAuth(AccountID const& account, AuthType authType, int depth) const
{
if (isXRP(issue) || issue.account == account)
// NOTE: depth is ignored here because it's only relevant for MPTs
if (isXRP(issue_) || issuer_ == account)
return tesSUCCESS;
auto const trustLine = view.read(keylet::line(account, issue.account, issue.currency));
auto const trustLine = readView_.read(keylet::line(account, issuer_, issue_.currency));
// If account has no line, and this is a strong check, fail
if (!trustLine && authType == AuthType::StrongAuth)
return tecNO_LINE;
// If this is a weak or legacy check, or if the account has a line, fail if
// auth is required and not set on the line
if (auto const issuerAccount = view.read(keylet::account(issue.account));
issuerAccount && (((*issuerAccount)[sfFlags] & lsfRequireAuth) != 0u))
if (issuerAccount_.exists() && (*issuerAccount_)[sfFlags] & lsfRequireAuth)
{
if (trustLine)
{
return (((*trustLine)[sfFlags] &
((account > issue.account) ? lsfLowAuth : lsfHighAuth)) != 0u)
return ((*trustLine)[sfFlags] & ((account > issuer_) ? lsfLowAuth : lsfHighAuth))
? tesSUCCESS
: TER{tecNO_AUTH};
}
@@ -555,28 +704,26 @@ requireAuth(ReadView const& view, Issue const& issue, AccountID const& account,
}
TER
canTransfer(ReadView const& view, Issue const& issue, AccountID const& from, AccountID const& to)
IOUToken::canTransfer(AccountID const& from, AccountID const& to) const
{
if (issue.native())
if (issue_.native())
return tesSUCCESS;
auto const& issuerId = issue.getIssuer();
if (issuerId == from || issuerId == to)
if (issuer_ == from || issuer_ == to)
return tesSUCCESS;
auto const sleIssuer = view.read(keylet::account(issuerId));
if (sleIssuer == nullptr)
if (!issuerAccount_.exists())
return tefINTERNAL; // LCOV_EXCL_LINE
auto const isRippleDisabled = [&](AccountID account) -> bool {
// Line might not exist, but some transfers can create it. If this
// is the case, just check the default ripple on the issuer account.
auto const line = view.read(keylet::line(account, issue));
auto const line = readView_.read(keylet::line(account, issue_));
if (line)
{
bool const issuerHigh = issuerId > account;
bool const issuerHigh = issuer_ > account;
return line->isFlag(issuerHigh ? lsfHighNoRipple : lsfLowNoRipple);
}
return !sleIssuer->isFlag(lsfDefaultRipple);
return issuerAccount_->isFlag(lsfDefaultRipple) == false;
};
// Fail if rippling disabled on both trust lines
@@ -586,6 +733,29 @@ canTransfer(ReadView const& view, Issue const& issue, AccountID const& from, Acc
return tesSUCCESS;
}
//------------------------------------------------------------------------------
//
// Token capability checks (IOU-specific)
//
//------------------------------------------------------------------------------
bool
IOUToken::canClawback() const
{
if (!issuerAccount_.exists())
return false;
return issuerAccount_->isFlag(lsfAllowTrustLineClawback) &&
!issuerAccount_->isFlag(lsfNoFreeze);
}
bool
IOUToken::requiresAuth() const
{
if (!issuerAccount_.exists())
return false;
return issuerAccount_->isFlag(lsfRequireAuth);
}
//------------------------------------------------------------------------------
//
// Empty holding operations (IOU-specific)
@@ -593,73 +763,65 @@ canTransfer(ReadView const& view, Issue const& issue, AccountID const& from, Acc
//------------------------------------------------------------------------------
TER
addEmptyHolding(
ApplyView& view,
WritableIOUToken::addEmptyHolding(
AccountID const& accountID,
XRPAmount priorBalance,
Issue const& issue,
beast::Journal journal)
{
// Every account can hold XRP. An issuer can issue directly.
if (issue.native() || accountID == issue.getIssuer())
if (issue_.native() || accountID == issuer_)
return tesSUCCESS;
auto const& issuerId = issue.getIssuer();
auto const& currency = issue.currency;
if (isGlobalFrozen(view, issuerId))
if (issuerAccount_.isGlobalFrozen())
return tecFROZEN; // LCOV_EXCL_LINE
auto const& srcId = issuerId;
auto const& srcId = issuer_;
auto const& dstId = accountID;
auto const high = srcId > dstId;
auto const index = keylet::line(srcId, dstId, currency);
auto const sleSrc = view.peek(keylet::account(srcId));
auto const sleDst = view.peek(keylet::account(dstId));
if (!sleDst || !sleSrc)
auto const index = keylet::line(srcId, dstId, currency_);
WritableAccountRoot wrappedSrc(srcId, applyView_);
WritableAccountRoot wrappedDst(dstId, applyView_);
if (!wrappedDst || !wrappedSrc)
return tefINTERNAL; // LCOV_EXCL_LINE
if (!sleSrc->isFlag(lsfDefaultRipple))
if (!wrappedSrc->isFlag(lsfDefaultRipple))
return tecINTERNAL; // LCOV_EXCL_LINE
// If the line already exists, don't create it again.
if (view.read(index))
if (applyView_.read(index))
return tecDUPLICATE;
// Can the account cover the trust line reserve ?
std::uint32_t const ownerCount = sleDst->at(sfOwnerCount);
if (priorBalance < view.fees().accountReserve(ownerCount + 1))
std::uint32_t const ownerCount = wrappedDst->at(sfOwnerCount);
if (priorBalance < readView_.fees().accountReserve(ownerCount + 1))
return tecNO_LINE_INSUF_RESERVE;
return trustCreate(
view,
applyView_,
high,
srcId,
dstId,
index.key,
sleDst,
wrappedDst,
/*bAuth=*/false,
/*bNoRipple=*/true,
/*bFreeze=*/false,
/*deepFreeze*/ false,
/*saBalance=*/STAmount{Issue{currency, noAccount()}},
/*saLimit=*/STAmount{Issue{currency, dstId}},
/*saBalance=*/STAmount{Issue{currency_, noAccount()}},
/*saLimit=*/STAmount{Issue{currency_, dstId}},
/*uQualityIn=*/0,
/*uQualityOut=*/0,
journal);
}
TER
removeEmptyHolding(
ApplyView& view,
AccountID const& accountID,
Issue const& issue,
beast::Journal journal)
WritableIOUToken::removeEmptyHolding(AccountID const& accountID, beast::Journal journal)
{
if (issue.native())
if (issue_.native())
{
auto const sle = view.read(keylet::account(accountID));
if (!sle)
auto const account = AccountRoot(accountID, applyView_);
if (!account.exists())
return tecINTERNAL; // LCOV_EXCL_LINE
auto const balance = sle->getFieldAmount(sfBalance);
auto const balance = account->getFieldAmount(sfBalance);
if (balance.xrp() != 0)
return tecHAS_OBLIGATIONS;
@@ -669,8 +831,8 @@ removeEmptyHolding(
// `asset` is an IOU.
// If the account is the issuer, then no line should exist. Check anyway.
// If a line does exist, it will get deleted. If not, return success.
bool const accountIsIssuer = accountID == issue.account;
auto const line = view.peek(keylet::line(accountID, issue));
bool const accountIsIssuer = accountID == issue_.account;
auto const line = applyView_.peek(keylet::line(accountID, issue_));
if (!line)
return accountIsIssuer ? (TER)tesSUCCESS : (TER)tecOBJECT_NOT_FOUND;
if (!accountIsIssuer && line->at(sfBalance)->iou() != beast::zero)
@@ -680,11 +842,11 @@ removeEmptyHolding(
if (line->isFlag(lsfLowReserve))
{
// Clear reserve for low account.
auto sleLowAccount = view.peek(keylet::account(line->at(sfLowLimit)->getIssuer()));
if (!sleLowAccount)
WritableAccountRoot wrappedLow(line->at(sfLowLimit)->getIssuer(), applyView_);
if (!wrappedLow)
return tecINTERNAL; // LCOV_EXCL_LINE
adjustOwnerCount(view, sleLowAccount, -1, journal);
wrappedLow.adjustOwnerCount(-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.
@@ -694,11 +856,11 @@ removeEmptyHolding(
if (line->isFlag(lsfHighReserve))
{
// Clear reserve for high account.
auto sleHighAccount = view.peek(keylet::account(line->at(sfHighLimit)->getIssuer()));
if (!sleHighAccount)
WritableAccountRoot wrappedHigh(line->at(sfHighLimit)->getIssuer(), applyView_);
if (!wrappedHigh)
return tecINTERNAL; // LCOV_EXCL_LINE
adjustOwnerCount(view, sleHighAccount, -1, journal);
wrappedHigh.adjustOwnerCount(-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.
@@ -706,7 +868,11 @@ removeEmptyHolding(
}
return trustDelete(
view, line, line->at(sfLowLimit)->getIssuer(), line->at(sfHighLimit)->getIssuer(), journal);
applyView_,
line,
line->at(sfLowLimit)->getIssuer(),
line->at(sfHighLimit)->getIssuer(),
journal);
}
TER
@@ -722,13 +888,13 @@ deleteAMMTrustLine(
auto const& [low, high] = std::minmax(
sleState->getFieldAmount(sfLowLimit).getIssuer(),
sleState->getFieldAmount(sfHighLimit).getIssuer());
auto sleLow = view.peek(keylet::account(low));
auto sleHigh = view.peek(keylet::account(high));
if (!sleLow || !sleHigh)
WritableAccountRoot wrappedLow(low, view);
WritableAccountRoot wrappedHigh(high, view);
if (!wrappedLow || !wrappedHigh)
return tecINTERNAL; // LCOV_EXCL_LINE
bool const ammLow = sleLow->isFieldPresent(sfAMMID);
bool const ammHigh = sleHigh->isFieldPresent(sfAMMID);
bool const ammLow = wrappedLow->isFieldPresent(sfAMMID);
bool const ammHigh = wrappedHigh->isFieldPresent(sfAMMID);
// can't both be AMM
if (ammLow && ammHigh)
@@ -749,10 +915,11 @@ deleteAMMTrustLine(
}
auto const uFlags = !ammLow ? lsfLowReserve : lsfHighReserve;
if ((sleState->getFlags() & uFlags) == 0u)
if (!(sleState->getFlags() & uFlags))
return tecINTERNAL; // LCOV_EXCL_LINE
adjustOwnerCount(view, !ammLow ? sleLow : sleHigh, -1, j);
WritableAccountRoot wrappedHolder = !ammLow ? wrappedLow : wrappedHigh;
wrappedHolder.adjustOwnerCount(-1, j);
return tesSUCCESS;
}

View File

@@ -26,124 +26,62 @@ isLPTokenFrozen(
Issue const& asset,
Issue const& asset2);
//------------------------------------------------------------------------------
//
// Freeze checking (Asset-based)
//
//------------------------------------------------------------------------------
class IOUToken;
class MPTokenIssuance;
class WritableIOUToken;
class WritableMPTokenIssuance;
bool
isGlobalFrozen(ReadView const& view, Asset const& asset)
std::unique_ptr<TokenBase>
makeTokenBase(ReadView const& view, Asset const& asset)
{
return std::visit(
[&]<ValidIssueType TIss>(TIss const& issue) {
if constexpr (std::is_same_v<TIss, Issue>)
[&]<typename T>(T const& issue) -> std::unique_ptr<TokenBase> {
if constexpr (std::is_same_v<T, Issue>)
{
return isGlobalFrozen(view, issue.getIssuer());
return std::make_unique<IOUToken>(view, issue);
}
else
{
return isGlobalFrozen(view, issue);
return std::make_unique<MPTokenIssuance>(view, issue);
}
},
asset.value());
}
bool
isIndividualFrozen(ReadView const& view, AccountID const& account, Asset const& asset)
std::unique_ptr<WritableTokenBase>
makeWritableTokenBase(ApplyView& view, Asset const& asset)
{
return std::visit(
[&](auto const& issue) { return isIndividualFrozen(view, account, issue); }, asset.value());
}
bool
isFrozen(ReadView const& view, AccountID const& account, Asset const& asset, int depth)
{
return std::visit(
[&](auto const& issue) { return isFrozen(view, account, issue, depth); }, asset.value());
}
TER
checkFrozen(ReadView const& view, AccountID const& account, Issue const& issue)
{
return isFrozen(view, account, issue) ? (TER)tecFROZEN : (TER)tesSUCCESS;
}
TER
checkFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue)
{
return isFrozen(view, account, mptIssue) ? (TER)tecLOCKED : (TER)tesSUCCESS;
}
TER
checkFrozen(ReadView const& view, AccountID const& account, Asset const& asset)
{
return std::visit(
[&](auto const& issue) { return checkFrozen(view, account, issue); }, asset.value());
}
bool
isAnyFrozen(
ReadView const& view,
std::initializer_list<AccountID> const& accounts,
Issue const& issue)
{
for (auto const& account : accounts)
{
if (isFrozen(view, account, issue.currency, issue.account))
return true;
}
return false;
}
bool
isAnyFrozen(
ReadView const& view,
std::initializer_list<AccountID> const& accounts,
Asset const& asset,
int depth)
{
return std::visit(
[&]<ValidIssueType TIss>(TIss const& issue) {
if constexpr (std::is_same_v<TIss, Issue>)
[&]<typename T>(T const& issue) -> std::unique_ptr<WritableTokenBase> {
if constexpr (std::is_same_v<T, Issue>)
{
return isAnyFrozen(view, accounts, issue);
return std::make_unique<WritableIOUToken>(view, issue);
}
else
{
return isAnyFrozen(view, accounts, issue, depth);
return std::make_unique<WritableMPTokenIssuance>(view, issue);
}
},
asset.value());
}
bool
isDeepFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue, int depth)
{
// Unlike IOUs, frozen / locked MPTs are not allowed to send or receive
// funds, so checking "deep frozen" is the same as checking "frozen".
return isFrozen(view, account, mptIssue, depth);
}
//------------------------------------------------------------------------------
//
// TokenBase implementation
//
//------------------------------------------------------------------------------
bool
isDeepFrozen(ReadView const& view, AccountID const& account, Asset const& asset, int depth)
TokenBase::isFrozen(AccountID const& account, int depth) const
{
return std::visit(
[&](auto const& issue) { return isDeepFrozen(view, account, issue, depth); },
asset.value());
// Default implementation - subclasses should override
return isGlobalFrozen() || isIndividualFrozen(account);
}
TER
checkDeepFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue)
Rate
transferRate(ReadView const& view, STAmount const& amount)
{
return isDeepFrozen(view, account, mptIssue) ? (TER)tecLOCKED : (TER)tesSUCCESS;
}
TER
checkDeepFrozen(ReadView const& view, AccountID const& account, Asset const& asset)
{
return std::visit(
[&](auto const& issue) { return checkDeepFrozen(view, account, issue); }, asset.value());
return makeTokenBase(view, amount.asset())->transferRate();
}
//------------------------------------------------------------------------------
@@ -161,7 +99,8 @@ getLineIfUsable(
FreezeHandling zeroIfFrozen,
beast::Journal j)
{
auto sle = view.read(keylet::line(account, issuer, currency));
IOUToken token(view, issuer, currency);
auto const sle = view.read(keylet::line(account, issuer, currency));
if (!sle)
{
@@ -170,8 +109,7 @@ getLineIfUsable(
if (zeroIfFrozen == fhZERO_IF_FROZEN)
{
if (isFrozen(view, account, currency, issuer) ||
isDeepFrozen(view, account, currency, issuer))
if (token.isFrozen(account) || token.isDeepFrozen(account))
{
return nullptr;
}
@@ -180,14 +118,14 @@ getLineIfUsable(
// we need to check if the associated assets have been frozen
if (view.rules().enabled(fixFrozenLPTokenTransfer))
{
auto const sleIssuer = view.read(keylet::account(issuer));
if (!sleIssuer)
auto const issuerRoot = AccountRoot(issuer, view);
if (!issuerRoot.exists())
{
return nullptr; // LCOV_EXCL_LINE
}
if (sleIssuer->isFieldPresent(sfAMMID))
if (issuerRoot->isFieldPresent(sfAMMID))
{
auto const sleAmm = view.read(keylet::amm((*sleIssuer)[sfAMMID]));
auto const sleAmm = view.read(keylet::amm((*issuerRoot)[sfAMMID]));
if (!sleAmm ||
isLPTokenFrozen(
@@ -256,7 +194,8 @@ accountHolds(
STAmount amount;
if (isXRP(currency))
{
return {xrpLiquid(view, account, 0, j)};
AccountRoot accountRoot(account, view);
return {accountRoot.xrpLiquid(0, j)};
}
bool const returnSpendable = (includeFullBalance == shFULL_BALANCE);
@@ -275,87 +214,6 @@ accountHolds(
}
STAmount
accountHolds(
ReadView const& view,
AccountID const& account,
Issue const& issue,
FreezeHandling zeroIfFrozen,
beast::Journal j,
SpendableHandling includeFullBalance)
{
return accountHolds(
view, account, issue.currency, issue.account, zeroIfFrozen, j, includeFullBalance);
}
STAmount
accountHolds(
ReadView const& view,
AccountID const& account,
MPTIssue const& mptIssue,
FreezeHandling zeroIfFrozen,
AuthHandling zeroIfUnauthorized,
beast::Journal j,
SpendableHandling includeFullBalance)
{
bool const returnSpendable = (includeFullBalance == shFULL_BALANCE);
if (returnSpendable && account == mptIssue.getIssuer())
{
// if the account is the issuer, and the issuance exists, their limit is
// the issuance limit minus the outstanding value
auto const issuance = view.read(keylet::mptIssuance(mptIssue.getMptID()));
if (!issuance)
{
return STAmount{mptIssue};
}
return STAmount{
mptIssue,
issuance->at(~sfMaximumAmount).value_or(maxMPTokenAmount) -
issuance->at(sfOutstandingAmount)};
}
STAmount amount;
auto const sleMpt = view.read(keylet::mptoken(mptIssue.getMptID(), account));
if (!sleMpt)
{
amount.clear(mptIssue);
}
else if (zeroIfFrozen == fhZERO_IF_FROZEN && isFrozen(view, account, mptIssue))
{
amount.clear(mptIssue);
}
else
{
amount = STAmount{mptIssue, sleMpt->getFieldU64(sfMPTAmount)};
// Only if auth check is needed, as it needs to do an additional read
// operation. Note featureSingleAssetVault will affect error codes.
if (zeroIfUnauthorized == ahZERO_IF_UNAUTHORIZED &&
view.rules().enabled(featureSingleAssetVault))
{
if (auto const err = requireAuth(view, mptIssue, account, AuthType::StrongAuth);
!isTesSuccess(err))
amount.clear(mptIssue);
}
else if (zeroIfUnauthorized == ahZERO_IF_UNAUTHORIZED)
{
auto const sleIssuance = view.read(keylet::mptIssuance(mptIssue.getMptID()));
// if auth is enabled on the issuance and mpt is not authorized,
// clear amount
if (sleIssuance && sleIssuance->isFlag(lsfMPTRequireAuth) &&
!sleMpt->isFlag(lsfMPTAuthorized))
amount.clear(mptIssue);
}
}
return amount;
}
[[nodiscard]] STAmount
accountHolds(
ReadView const& view,
AccountID const& account,
@@ -365,51 +223,8 @@ accountHolds(
beast::Journal j,
SpendableHandling includeFullBalance)
{
return std::visit(
[&]<ValidIssueType TIss>(TIss const& value) {
if constexpr (std::is_same_v<TIss, Issue>)
{
return accountHolds(view, account, value, zeroIfFrozen, j, includeFullBalance);
}
else if constexpr (std::is_same_v<TIss, MPTIssue>)
{
return accountHolds(
view, account, value, zeroIfFrozen, zeroIfUnauthorized, j, includeFullBalance);
}
},
asset.value());
}
STAmount
accountFunds(
ReadView const& view,
AccountID const& id,
STAmount const& saDefault,
FreezeHandling freezeHandling,
beast::Journal j)
{
if (!saDefault.native() && saDefault.getIssuer() == id)
return saDefault;
return accountHolds(
view, id, saDefault.getCurrency(), saDefault.getIssuer(), freezeHandling, j);
}
Rate
transferRate(ReadView const& view, STAmount const& amount)
{
return std::visit(
[&]<ValidIssueType TIss>(TIss const& issue) {
if constexpr (std::is_same_v<TIss, Issue>)
{
return transferRate(view, issue.getIssuer());
}
else
{
return transferRate(view, issue.getMptID());
}
},
amount.asset().value());
auto token = makeTokenBase(view, asset);
return token->accountHolds(account, zeroIfFrozen, zeroIfUnauthorized, j, includeFullBalance);
}
//------------------------------------------------------------------------------
@@ -425,12 +240,13 @@ canAddHolding(ReadView const& view, Issue const& issue)
{
return tesSUCCESS; // No special checks for XRP
}
auto const issuer = AccountRoot(issue.getIssuer(), view);
auto const issuer = view.read(keylet::account(issue.getIssuer()));
if (!issuer)
if (!issuer.exists())
{
return terNO_ACCOUNT;
}
if (!issuer->isFlag(lsfDefaultRipple))
{
return terNO_RIPPLE;
@@ -442,9 +258,7 @@ canAddHolding(ReadView const& view, Issue const& issue)
[[nodiscard]] TER
canAddHolding(ReadView const& view, Asset const& asset)
{
return std::visit(
[&]<ValidIssueType TIss>(TIss const& issue) -> TER { return canAddHolding(view, issue); },
asset.value());
return makeTokenBase(view, asset)->canAddHolding();
}
TER
@@ -455,11 +269,7 @@ addEmptyHolding(
Asset const& asset,
beast::Journal journal)
{
return std::visit(
[&]<ValidIssueType TIss>(TIss const& issue) -> TER {
return addEmptyHolding(view, accountID, priorBalance, issue, journal);
},
asset.value());
return makeWritableTokenBase(view, asset)->addEmptyHolding(accountID, priorBalance, journal);
}
TER
@@ -469,37 +279,7 @@ removeEmptyHolding(
Asset const& asset,
beast::Journal journal)
{
return std::visit(
[&]<ValidIssueType TIss>(TIss const& issue) -> TER {
return removeEmptyHolding(view, accountID, issue, journal);
},
asset.value());
}
//------------------------------------------------------------------------------
//
// Authorization and transfer checks
//
//------------------------------------------------------------------------------
TER
requireAuth(ReadView const& view, Asset const& asset, AccountID const& account, AuthType authType)
{
return std::visit(
[&]<ValidIssueType TIss>(TIss const& issue_) {
return requireAuth(view, issue_, account, authType);
},
asset.value());
}
TER
canTransfer(ReadView const& view, Asset const& asset, AccountID const& from, AccountID const& to)
{
return std::visit(
[&]<ValidIssueType TIss>(TIss const& issue) -> TER {
return canTransfer(view, issue, from, to);
},
asset.value());
return makeWritableTokenBase(view, asset)->removeEmptyHolding(accountID, journal);
}
//------------------------------------------------------------------------------
@@ -511,7 +291,7 @@ canTransfer(ReadView const& view, Asset const& asset, AccountID const& from, Acc
// Direct send w/o fees:
// - Redeeming IOUs and/or sending sender's own IOUs.
// - Create trust line if needed.
// --> bCheckIssuer : normally require issuer to be involved.
// bCheckIssuer : normally require issuer to be involved.
static TER
rippleCreditIOU(
ApplyView& view,
@@ -570,22 +350,21 @@ rippleCreditIOU(
// Sender balance was positive.
&& saBalance <= beast::zero
// Sender is zero or negative.
&& ((uFlags & (!bSenderHigh ? lsfLowReserve : lsfHighReserve)) != 0u)
&& (uFlags & (!bSenderHigh ? lsfLowReserve : lsfHighReserve))
// Sender reserve is set.
&& static_cast<bool>(uFlags & (!bSenderHigh ? lsfLowNoRipple : lsfHighNoRipple)) !=
static_cast<bool>(
view.read(keylet::account(uSenderID))->getFlags() & lsfDefaultRipple) &&
((uFlags & (!bSenderHigh ? lsfLowFreeze : lsfHighFreeze)) == 0u) &&
static_cast<bool>(AccountRoot(uSenderID, view)->getFlags() & lsfDefaultRipple) &&
!(uFlags & (!bSenderHigh ? lsfLowFreeze : lsfHighFreeze)) &&
!sleRippleState->getFieldAmount(!bSenderHigh ? sfLowLimit : sfHighLimit)
// Sender trust limit is 0.
&& (sleRippleState->getFieldU32(!bSenderHigh ? sfLowQualityIn : sfHighQualityIn) == 0u)
&& !sleRippleState->getFieldU32(!bSenderHigh ? sfLowQualityIn : sfHighQualityIn)
// Sender quality in is 0.
&&
(sleRippleState->getFieldU32(!bSenderHigh ? sfLowQualityOut : sfHighQualityOut) == 0u))
&& !sleRippleState->getFieldU32(!bSenderHigh ? sfLowQualityOut : sfHighQualityOut))
// Sender quality out is 0.
{
// Clear the reserve of the sender, possibly delete the line!
adjustOwnerCount(view, view.peek(keylet::account(uSenderID)), -1, j);
WritableAccountRoot wrappedSender(uSenderID, view);
wrappedSender.adjustOwnerCount(-1, j);
// Clear reserve flag.
sleRippleState->setFieldU32(
@@ -593,7 +372,7 @@ rippleCreditIOU(
// Balance is zero, receiver reserve is clear.
bDelete = !saBalance // Balance is zero.
&& ((uFlags & (bSenderHigh ? lsfLowReserve : lsfHighReserve)) == 0u);
&& !(uFlags & (bSenderHigh ? lsfLowReserve : lsfHighReserve));
// Receiver reserve is clear.
}
@@ -628,11 +407,11 @@ rippleCreditIOU(
<< to_string(uSenderID) << " -> " << to_string(uReceiverID) << " : "
<< saAmount.getFullText();
auto const sleAccount = view.peek(keylet::account(uReceiverID));
if (!sleAccount)
WritableAccountRoot wrappedAccount(uReceiverID, view);
if (!wrappedAccount)
return tefINTERNAL; // LCOV_EXCL_LINE
bool const noRipple = (sleAccount->getFlags() & lsfDefaultRipple) == 0;
bool const noRipple = (wrappedAccount->getFlags() & lsfDefaultRipple) == 0;
return trustCreate(
view,
@@ -640,7 +419,7 @@ rippleCreditIOU(
uSenderID,
uReceiverID,
index.key,
sleAccount,
wrappedAccount,
false,
noRipple,
false,
@@ -653,7 +432,7 @@ rippleCreditIOU(
}
// Send regardless of limits.
// --> saAmount: Amount/currency/issuer to deliver to receiver.
// saAmount: Amount/currency/issuer to deliver to receiver.
// <-- saActual: Amount actually cost. Sender pays fees.
static TER
rippleSendIOU(
@@ -686,8 +465,10 @@ rippleSendIOU(
// Calculate the amount to transfer accounting
// for any transfer fees if the fee is not waived:
saActual = (waiveFee == WaiveTransferFee::Yes) ? saAmount
: multiply(saAmount, transferRate(view, issuer));
WritableAccountRoot wrappedIssuer(issuer, view);
saActual = (waiveFee == WaiveTransferFee::Yes)
? saAmount
: multiply(saAmount, wrappedIssuer.transferRate());
JLOG(j.debug()) << "rippleSendIOU> " << to_string(uSenderID) << " - > "
<< to_string(uReceiverID) << " : deliver=" << saAmount.getFullText()
@@ -702,7 +483,7 @@ rippleSendIOU(
}
// Send regardless of limits.
// --> receivers: Amount/currency/issuer to deliver to receivers.
// receivers: Amount/currency/issuer to deliver to receivers.
// <-- saActual: Amount actually cost to sender. Sender pays fees.
static TER
rippleSendMultiIOU(
@@ -752,9 +533,10 @@ rippleSendMultiIOU(
// Calculate the amount to transfer accounting
// for any transfer fees if the fee is not waived:
WritableAccountRoot wrappedIssuer(issuer, view);
STAmount actualSend = (waiveFee == WaiveTransferFee::Yes)
? amount
: multiply(amount, transferRate(view, issuer));
: multiply(amount, wrappedIssuer.transferRate());
actual += actualSend;
takeFromSender += actualSend;
@@ -1030,19 +812,18 @@ rippleCreditMPT(
beast::Journal j)
{
// Do not check MPT authorization here - it must have been checked earlier
auto const mptID = keylet::mptIssuance(saAmount.get<MPTIssue>().getMptID());
WritableMPTokenIssuance mptIssuance(view, saAmount.get<MPTIssue>().getMptID());
auto const& issuer = saAmount.getIssuer();
auto sleIssuance = view.peek(mptID);
if (!sleIssuance)
if (!mptIssuance.exists())
return tecOBJECT_NOT_FOUND;
if (uSenderID == issuer)
{
(*sleIssuance)[sfOutstandingAmount] += saAmount.mpt().value();
view.update(sleIssuance);
(*mptIssuance)[sfOutstandingAmount] += saAmount.mpt().value();
mptIssuance.update();
}
else
{
auto const mptokenID = keylet::mptoken(mptID.key, uSenderID);
auto const mptokenID = keylet::mptoken(mptIssuance.getMptID(), uSenderID);
if (auto sle = view.peek(mptokenID))
{
auto const amt = sle->getFieldU64(sfMPTAmount);
@@ -1060,12 +841,12 @@ rippleCreditMPT(
if (uReceiverID == issuer)
{
auto const outstanding = sleIssuance->getFieldU64(sfOutstandingAmount);
auto const outstanding = mptIssuance->getFieldU64(sfOutstandingAmount);
auto const redeem = saAmount.mpt().value();
if (outstanding >= redeem)
{
sleIssuance->setFieldU64(sfOutstandingAmount, outstanding - redeem);
view.update(sleIssuance);
mptIssuance->setFieldU64(sfOutstandingAmount, outstanding - redeem);
mptIssuance.update();
}
else
{
@@ -1074,7 +855,7 @@ rippleCreditMPT(
}
else
{
auto const mptokenID = keylet::mptoken(mptID.key, uReceiverID);
auto const mptokenID = keylet::mptoken(mptIssuance.getMptID(), uReceiverID);
if (auto sle = view.peek(mptokenID))
{
(*sle)[sfMPTAmount] += saAmount.mpt().value();
@@ -1103,9 +884,8 @@ rippleSendMPT(
// Safe to get MPT since rippleSendMPT is only called by accountSendMPT
auto const& issuer = saAmount.getIssuer();
auto const sle = view.read(keylet::mptIssuance(saAmount.get<MPTIssue>().getMptID()));
if (!sle)
WritableMPTokenIssuance mptIssuance(view, saAmount.get<MPTIssue>().getMptID());
if (!mptIssuance.exists())
return tecOBJECT_NOT_FOUND;
if (uSenderID == issuer || uReceiverID == issuer)
@@ -1115,9 +895,9 @@ rippleSendMPT(
if (uSenderID == issuer)
{
auto const sendAmount = saAmount.mpt().value();
auto const maximumAmount = sle->at(~sfMaximumAmount).value_or(maxMPTokenAmount);
auto const maximumAmount = mptIssuance->at(~sfMaximumAmount).value_or(maxMPTokenAmount);
if (sendAmount > maximumAmount ||
sle->getFieldU64(sfOutstandingAmount) > maximumAmount - sendAmount)
mptIssuance->getFieldU64(sfOutstandingAmount) > maximumAmount - sendAmount)
return tecPATH_DRY;
}
@@ -1130,9 +910,8 @@ rippleSendMPT(
}
// Sending 3rd party MPTs: transit.
saActual = (waiveFee == WaiveTransferFee::Yes)
? saAmount
: multiply(saAmount, transferRate(view, saAmount.get<MPTIssue>().getMptID()));
saActual = (waiveFee == WaiveTransferFee::Yes) ? saAmount
: multiply(saAmount, mptIssuance.transferRate());
JLOG(j.debug()) << "rippleSendMPT> " << to_string(uSenderID) << " - > "
<< to_string(uReceiverID) << " : deliver=" << saAmount.getFullText()
@@ -1158,9 +937,8 @@ rippleSendMultiMPT(
// Safe to get MPT since rippleSendMultiMPT is only called by
// accountSendMultiMPT
auto const& issuer = mptIssue.getIssuer();
auto const sle = view.read(keylet::mptIssuance(mptIssue.getMptID()));
if (!sle)
auto mptIssuance = MPTokenIssuance(view, mptIssue);
if (!mptIssuance.exists())
return tecOBJECT_NOT_FOUND;
// These may diverge
@@ -1194,9 +972,10 @@ rippleSendMultiMPT(
"xrpl::rippleSendMultiMPT",
"sender == issuer, takeFromSender == zero");
auto const sendAmount = amount.mpt().value();
auto const maximumAmount = sle->at(~sfMaximumAmount).value_or(maxMPTokenAmount);
auto const maximumAmount =
mptIssuance->at(~sfMaximumAmount).value_or(maxMPTokenAmount);
if (sendAmount > maximumAmount ||
sle->getFieldU64(sfOutstandingAmount) > maximumAmount - sendAmount)
mptIssuance->getFieldU64(sfOutstandingAmount) > maximumAmount - sendAmount)
return tecPATH_DRY;
}
@@ -1213,7 +992,7 @@ rippleSendMultiMPT(
// Sending 3rd party MPTs: transit.
STAmount actualSend = (waiveFee == WaiveTransferFee::Yes)
? amount
: multiply(amount, transferRate(view, amount.get<MPTIssue>().getMptID()));
: multiply(amount, mptIssuance.transferRate());
actual += actualSend;
takeFromSender += actualSend;
@@ -1362,15 +1141,15 @@ transferXRP(
XRPL_ASSERT(from != to, "xrpl::transferXRP : sender is not receiver");
XRPL_ASSERT(amount.native(), "xrpl::transferXRP : amount is XRP");
SLE::pointer const sender = view.peek(keylet::account(from));
SLE::pointer const receiver = view.peek(keylet::account(to));
if (!sender || !receiver)
WritableAccountRoot acctSender(from, view);
WritableAccountRoot acctReceiver(to, view);
if (!acctSender || !acctReceiver)
return tefINTERNAL; // LCOV_EXCL_LINE
JLOG(j.trace()) << "transferXRP: " << to_string(from) << " -> " << to_string(to)
<< ") : " << amount.getFullText();
if (sender->getFieldAmount(sfBalance) < amount)
if (acctSender->getFieldAmount(sfBalance) < amount)
{
// VFALCO Its unfortunate we have to keep
// mutating these TER everywhere
@@ -1381,11 +1160,11 @@ transferXRP(
}
// Decrement XRP balance.
sender->setFieldAmount(sfBalance, sender->getFieldAmount(sfBalance) - amount);
view.update(sender);
acctSender->setFieldAmount(sfBalance, acctSender->getFieldAmount(sfBalance) - amount);
acctSender.update();
receiver->setFieldAmount(sfBalance, receiver->getFieldAmount(sfBalance) + amount);
view.update(receiver);
acctReceiver->setFieldAmount(sfBalance, acctReceiver->getFieldAmount(sfBalance) + amount);
acctReceiver.update();
return tesSUCCESS;
}

View File

@@ -9,7 +9,7 @@ namespace xrpl {
[[nodiscard]] std::optional<STAmount>
assetsToSharesDeposit(
std::shared_ptr<SLE const> const& vault,
std::shared_ptr<SLE const> const& issuance,
MPTokenIssuance const& issuance,
STAmount const& assets)
{
XRPL_ASSERT(!assets.negative(), "xrpl::assetsToSharesDeposit : non-negative assets");
@@ -36,7 +36,7 @@ assetsToSharesDeposit(
[[nodiscard]] std::optional<STAmount>
sharesToAssetsDeposit(
std::shared_ptr<SLE const> const& vault,
std::shared_ptr<SLE const> const& issuance,
MPTokenIssuance const& issuance,
STAmount const& shares)
{
XRPL_ASSERT(!shares.negative(), "xrpl::sharesToAssetsDeposit : non-negative shares");
@@ -62,7 +62,7 @@ sharesToAssetsDeposit(
[[nodiscard]] std::optional<STAmount>
assetsToSharesWithdraw(
std::shared_ptr<SLE const> const& vault,
std::shared_ptr<SLE const> const& issuance,
MPTokenIssuance const& issuance,
STAmount const& assets,
TruncateShares truncate)
{
@@ -89,10 +89,10 @@ assetsToSharesWithdraw(
[[nodiscard]] std::optional<STAmount>
sharesToAssetsWithdraw(
std::shared_ptr<SLE const> const& vault,
std::shared_ptr<SLE const> const& issuance,
MPTokenIssuance const& issuance,
STAmount const& shares)
{
XRPL_ASSERT(!shares.negative(), "xrpl::sharesToAssetsWithdraw : non-negative shares");
XRPL_ASSERT(!shares.negative(), "xrpl::sharesToAssetsDeposit : non-negative shares");
XRPL_ASSERT(
shares.asset() == vault->at(sfShareMPTID),
"xrpl::sharesToAssetsWithdraw : shares and vault match");

View File

@@ -59,7 +59,6 @@ public:
//--------------------------------------------------------------------------
void
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
makeGet(std::string const& strPath, boost::asio::streambuf& sb, std::string const& strHost)
{
std::ostream osRequest(&sb);

View File

@@ -55,7 +55,7 @@ ManagerImp::make_Backend(
missing_backend();
auto factory{find(type)};
if (factory == nullptr)
if (!factory)
{
missing_backend();
}

View File

@@ -213,7 +213,7 @@ public:
rocksdb::DB* db = nullptr;
m_options.create_if_missing = createIfMissing;
rocksdb::Status status = rocksdb::DB::Open(m_options, m_name, &db);
if (!status.ok() || (db == nullptr))
if (!status.ok() || !db)
{
Throw<std::runtime_error>(
std::string("Unable to open/create RocksDB: ") + status.ToString());

View File

@@ -113,7 +113,7 @@ encodeSoftwareVersion(std::string_view versionStr)
{
std::uint8_t x = 0;
for (auto const& id : v.preReleaseIdentifiers)
for (auto id : v.preReleaseIdentifiers)
{
auto parsePreRelease = [](std::string_view identifier,
std::string_view prefix,
@@ -140,7 +140,7 @@ encodeSoftwareVersion(std::string_view versionStr)
if (x == 0)
x = parsePreRelease(id, "b", 0x40, 0, 63);
if ((x & 0xC0) != 0)
if (x & 0xC0)
{
c |= static_cast<std::uint64_t>(x) << 16;
break;

View File

@@ -205,7 +205,9 @@ make_error(error_code_i code, std::string const& message)
bool
contains_error(Json::Value const& json)
{
return json.isObject() && json.isMember(jss::error);
if (json.isObject() && json.isMember(jss::error))
return true;
return false;
}
int

View File

@@ -209,7 +209,7 @@ FeatureCollections::getRegisteredFeature(std::string const& name) const
XRPL_ASSERT(
readOnly.load(), "xrpl::FeatureCollections::getRegisteredFeature : startup completed");
Feature const* feature = getByName(name);
if (feature != nullptr)
if (feature)
return feature->feature;
return std::nullopt;
}
@@ -229,7 +229,7 @@ FeatureCollections::registerFeature(std::string const& name, Supported support,
support == Supported::yes || vote == VoteBehavior::DefaultNo,
"Invalid feature parameters. Must be supported to be up-voted.");
Feature const* i = getByName(name);
if (i == nullptr)
if (!i)
{
check(features.size() < detail::numFeatures, "More features defined than allocated.");
@@ -283,7 +283,7 @@ FeatureCollections::featureToBitsetIndex(uint256 const& f) const
readOnly.load(), "xrpl::FeatureCollections::featureToBitsetIndex : startup completed");
Feature const* feature = getByFeature(f);
if (feature == nullptr)
if (!feature)
LogicError("Invalid Feature ID");
return getIndex(*feature);
@@ -303,7 +303,7 @@ FeatureCollections::featureToName(uint256 const& f) const
{
XRPL_ASSERT(readOnly.load(), "xrpl::FeatureCollections::featureToName : startup completed");
Feature const* feature = getByFeature(f);
return (feature != nullptr) ? feature->name : to_string(f);
return feature ? feature->name : to_string(f);
}
FeatureCollections featureCollections;

View File

@@ -186,7 +186,7 @@ mulRatio(IOUAmount const& amt, std::uint32_t num, std::uint32_t den, bool roundU
{
using namespace boost::multiprecision;
if (den == 0u)
if (!den)
Throw<std::runtime_error>("division by zero");
// A vector with the value 10^index for indexes from 0 to 29

View File

@@ -471,7 +471,7 @@ oracle(AccountID const& account, std::uint32_t const& documentID) noexcept
}
Keylet
mptIssuance(std::uint32_t seq, AccountID const& issuer) noexcept
mptIssuance(std::uint32_t const seq, AccountID const& issuer) noexcept
{
return mptIssuance(makeMptID(seq, issuer));
}

View File

@@ -172,7 +172,7 @@ SOTemplate const*
InnerObjectFormats::findSOTemplateBySField(SField const& sField) const
{
auto itemPtr = findByType(sField.getCode());
if (itemPtr != nullptr)
if (itemPtr)
return &(itemPtr->getSOTemplate());
return nullptr;

View File

@@ -1,9 +1,7 @@
#include <xrpl/basics/Slice.h>
#include <xrpl/basics/chrono.h>
#include <xrpl/protocol/HashPrefix.h>
#include <xrpl/protocol/LedgerHeader.h>
#include <xrpl/protocol/Serializer.h>
#include <xrpl/protocol/digest.h>
namespace xrpl {
@@ -53,21 +51,4 @@ deserializePrefixedHeader(Slice data, bool hasHash)
return deserializeHeader(data + 4, hasHash);
}
uint256
calculateLedgerHash(LedgerHeader const& info)
{
// VFALCO This has to match addRaw in View.h.
return sha512Half(
HashPrefix::ledgerMaster,
std::uint32_t(info.seq),
std::uint64_t(info.drops.drops()),
info.parentHash,
info.txHash,
info.accountHash,
std::uint32_t(info.parentCloseTime.time_since_epoch().count()),
std::uint32_t(info.closeTime.time_since_epoch().count()),
std::uint8_t(info.closeTimeResolution.count()),
std::uint8_t(info.closeFlags));
}
} // namespace xrpl

View File

@@ -24,7 +24,7 @@ canHaveNFTokenOfferID(
return false;
TxType const tt = serializedTx->getTxnType();
if ((tt != ttNFTOKEN_MINT || !serializedTx->isFieldPresent(sfAmount)) &&
if (!(tt == ttNFTOKEN_MINT && serializedTx->isFieldPresent(sfAmount)) &&
tt != ttNFTOKEN_CREATE_OFFER)
return false;

View File

@@ -176,13 +176,13 @@ Permission::isDelegable(std::uint32_t const& permissionValue, Rules const& rules
}
uint32_t
Permission::txToPermissionType(TxType const& type)
Permission::txToPermissionType(TxType const& type) const
{
return static_cast<uint32_t>(type) + 1;
}
TxType
Permission::permissionToTxType(uint32_t const& value)
Permission::permissionToTxType(uint32_t const& value) const
{
return static_cast<TxType>(value - 1);
}

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