Compare commits

..
Author SHA1 Message Date
Ed Hennis a2bc76d843 Merge remote-tracking branch 'XRPLF/develop' into ximinez/fix/validator-cache
* XRPLF/develop: (369 commits)
  refactor: Use DepositPreauthEntry everywhere (8364)
  chore: [DEPENDABOT] bump cxx from 1.0.202 to 1.0.203 in /crates in the rust-dependencies group (8564)
  chore: Fix readability-trailing-comma (8545)
  ci: [DEPENDABOT] bump the github-actions group across 1 directory with 2 updates (8562)
  chore: Fix misc-const-correctness (8546)
  chore: Update XRPLF/actions to use pip from python3.14 explicitly (8560)
  chore: Bump version to 3.4.1 and make pkg_release 2
  build: Reduce number of conan logs (8548)
  chore: Fix readability-redundant-lambda-parameter-list (8544)
  fix: Reject Batch inner txs with the wrong wrapper (fixBatchV1_2)
  ci: Update Nexus packaging URL
  fix: Assorted integer-arithmetic hardening in the payment engine and ledger helpers
  fix: Change mpt subscription msg type back to `transaction` (8539)
  chore: Update clang-tidy image to v23 (8536)
  feat: Integrate Permissioned Domain & Credential Checks for Lending Protocol (6517)
  refactor: Group binaries in subdirectories (8535)
  chore: Split multiple-in-one into individual tests (8025)
  refactor: Migrate handlers to rpc-spec (B) (8484)
  build: Use images with Clang 23 except for clang-tidy (8530)
  chore: Update pre-commit hooks and image (8528)
  ...
2026-10-12 17:12:06 -04:00
Ed Hennis 3c31a1dd01 Merge branch 'develop' into ximinez/fix/validator-cache 2026-07-14 20:13:13 -04:00
Ed Hennis bbfaf85a14 Merge remote-tracking branch 'XRPLF/develop' into ximinez/fix/validator-cache
* XRPLF/develop:
  ci: Run full matrix only on `Ready to merge` or `Full CI build` labeled PRs (7689)
  fix: Strengthen Clawback invariant checks for MPT balances (7285)
  test: Add unit tests for IP address related functions (7744)
  ci: Add Rust to Nix docker image (7571)
  docs: Add more information about pre-commit hooks and how to set them up (7802)
  test: Add JSON array size tests (7592)
  chore: Enable most readability checks (7772)
  ci: Do not run conflict checker when label is applied (7774)
  chore: Run clang_tidy_check with `pass_filenames: false` from pre-commit (7800)
  feat: Add delegate filter param for account_tx RPC (6126)
  refactor: Move `jss.h` `include` out of `Indexes.h` (7799)
  test: Add tests for check doxygen style (7795)
  style: Add pre-commit hook to check doxygen style (7794)
  style: Unify style for all Doxygen comments (7776)
  fix: Improve Number addition/subtraction rounding (7369)
  feat: XLS-68: Sponsor, 5887 continuation  (7350)
2026-07-14 15:02:07 -04:00
Ed Hennis 1c4c998ae1 Merge branch 'develop' into ximinez/fix/validator-cache 2026-07-09 19:50:05 -04:00
Ed Hennis 5d2faef541 Merge remote-tracking branch 'XRPLF/develop' into ximinez/fix/validator-cache
* XRPLF/develop: (158 commits)
  chore: Use std::ranges where possible (7634)
  ci: Use macOS 26 Tahoe with apple-clang 21 (7601)
  build: Mark sec256k1 and mpt-crypto as transitive headers (7658)
  chore: Add a script to nicely format clang-tidy output (7650)
  chore: Enable most bugprone checks (7643)
  feat: Confidential Transfer for MPT (5860)
  fix: Use trustline balance direction to validate IOU PaymentMint/PaymentBurn (7584)
  fix: Unify freeze checks for pseudo-account deposit/withdraw (7382)
  fix: Block delegate tx from being queued (7640)
  chore: Enable groups of clang-tidy checks by default (7637)
  ci: Better determine when we need to run full clang-tidy (7635)
  refactor: Retire NFTokenReserve fix (7367)
  refactor: Retire Clawback amendment (7353)
  refactor: Rename (mostly keylet) functions to more closely match the docs (7059)
  build: Switch to a new conan XRPLF remote, again (7638)
  chore: Revert "build: Switch to a new conan XRPLF remote (7622)" (7623)
  build: Switch to a new conan XRPLF remote (7622)
  build: Align xrpld RPM packaging with DEB package (7529)
  chore: Use clang-tidy v22 new features (7427)
  build: Patch nix binaries in CMake (7539)
  ...
2026-06-30 16:48:26 -04:00
Ed Hennis 5d2be06748 Merge branch 'develop' into ximinez/fix/validator-cache 2026-05-19 16:53:46 -04:00
Ed Hennis 2f5b17c2cb Merge branch 'develop' into ximinez/fix/validator-cache 2026-05-19 10:15:25 -04:00
Ed Hennis a963e4a389 Merge branch 'develop' into ximinez/fix/validator-cache 2026-05-19 05:16:02 -04:00
Ed Hennis 962f3ee744 Merge branch 'develop' into ximinez/fix/validator-cache 2026-05-15 21:43:29 -04:00
Ed Hennis 2a7499a415 Merge branch 'develop' into ximinez/fix/validator-cache 2026-05-14 20:39:19 -04:00
Ed Hennis ae8c44b606 Merge branch 'develop' into ximinez/fix/validator-cache 2026-05-13 22:32:02 -04:00
Ed Hennis 1edd6fe4bf Merge branch 'develop' into ximinez/fix/validator-cache 2026-05-13 12:03:49 -04:00
Ed Hennis 2edf310a6f Merge branch 'develop' into ximinez/fix/validator-cache 2026-05-12 19:18:57 -04:00
Ed Hennis 7af68be818 Merge branch 'develop' into ximinez/fix/validator-cache 2026-05-12 16:26:19 -04:00
Ed Hennis 19fc5c48dd Merge branch 'develop' into ximinez/fix/validator-cache 2026-05-11 13:34:22 -04:00
Ed Hennis 2f43a1901d Merge branch 'develop' into ximinez/fix/validator-cache 2026-05-07 18:10:13 -04:00
Ed Hennis 6a0440bf6d Merge branch 'develop' into ximinez/fix/validator-cache 2026-05-07 14:18:39 -04:00
Ed Hennis cf398d05aa Merge branch 'develop' into ximinez/fix/validator-cache 2026-05-07 13:28:42 -04:00
Ed Hennis 59a8e30a3e Merge branch 'develop' into ximinez/fix/validator-cache 2026-05-06 22:34:33 -04:00
Ed Hennis 8160921783 Merge branch 'develop' into ximinez/fix/validator-cache 2026-05-06 14:18:12 -04:00
Ed Hennis 6dc01196b4 Merge branch 'develop' into ximinez/fix/validator-cache 2026-05-05 16:46:54 -04:00
Ed Hennis cdbbbdd27f Merge branch 'develop' into ximinez/fix/validator-cache 2026-05-05 10:50:44 -04:00
Ed Hennis 3d30c09031 Merge branch 'develop' into ximinez/fix/validator-cache 2026-05-05 00:14:58 -04:00
Ed Hennis e59ca23487 Merge branch 'develop' into ximinez/fix/validator-cache 2026-05-01 13:59:15 -04:00
Ed Hennis 72c700c3e0 Merge branch 'develop' into ximinez/fix/validator-cache 2026-04-28 16:23:38 -04:00
Ed Hennis 4589fcbcfc Merge branch 'develop' into ximinez/fix/validator-cache 2026-04-25 14:45:54 -04:00
Ed Hennis 86d840f53d Merge branch 'develop' into ximinez/fix/validator-cache 2026-04-23 15:56:11 -04:00
Ed Hennis 741b61cdf3 Merge branch 'develop' into ximinez/fix/validator-cache 2026-04-22 23:40:28 -04:00
Ed Hennis 6bb0989c9f Merge branch 'develop' into ximinez/fix/validator-cache 2026-04-22 14:49:12 -04:00
Ed Hennis 9120506613 Merge branch 'develop' into ximinez/fix/validator-cache 2026-04-22 13:10:44 -04:00
Ed Hennis 3b3de96bd4 Merge branch 'develop' into ximinez/fix/validator-cache 2026-04-21 18:48:39 -04:00
Ed Hennis c9ab6ab25f Merge remote-tracking branch 'XRPLF/develop' into ximinez/fix/validator-cache
* XRPLF/develop:
  chore: Remove empty Taker.h (6984)
  chore: Enable clang-tidy modernize checks (6975)
  ci: Upload clang-tidy git diff (6983)
  fix: Add rounding to Vault invariants (6217) (6955)
2026-04-21 18:46:58 -04:00
Ed Hennis fb0605cfd3 Merge branch 'develop' into ximinez/fix/validator-cache 2026-04-20 17:49:47 -04:00
Ed Hennis 156553bb5e Merge branch 'develop' into ximinez/fix/validator-cache 2026-04-20 15:45:04 -04:00
Ed Hennis 781b56849b Merge branch 'develop' into ximinez/fix/validator-cache 2026-04-20 11:39:06 -04:00
Ed Hennis 278c02bebb Merge branch 'develop' into ximinez/fix/validator-cache 2026-04-17 18:11:31 -04:00
Ed Hennis 1d6fedf9a2 Merge branch 'develop' into ximinez/fix/validator-cache 2026-04-16 13:44:37 -04:00
Ed Hennis 2e8de499aa Merge branch 'develop' into ximinez/fix/validator-cache 2026-04-15 19:06:29 -04:00
Ed Hennis 0bce3639a6 Merge branch 'develop' into ximinez/fix/validator-cache 2026-04-15 14:28:55 -04:00
Ed Hennis 8f329e3bc6 Use Validator List (VL) cache files in more scenarios
- If any [validator_list_keys] are not available after all
  [validator_list_sites] have had a chance to be queried, then fall
  back to loading cache files. Currently, cache files are only used if
  no sites are defined, or the request to one of them has an error. It
  does not include cases where not enough sites are defined, or if a
  site returns an invalid VL (or something else entirely).
- Resolves #5320
2026-04-13 19:46:06 -04:00
297 changed files with 5992 additions and 43252 deletions

No files matched your search

-2
View File
@@ -103,7 +103,6 @@ words:
- dearmor
- decryptor
- dedented
- dedup
- deleteme
- demultiplexer
- deserializaton
@@ -345,7 +344,6 @@ words:
- unambiguity
- unauthorizes
- unauthorizing
- undeserializable
- unergonomic
- unfetched
- unfindable
-15
View File
@@ -36,18 +36,3 @@ updates:
rust-dependencies:
patterns:
- "*"
- package-ecosystem: uv
directory: /cmake/scripts/codegen
schedule:
interval: weekly
day: monday
time: "04:00"
timezone: Etc/GMT
commit-message:
prefix: "chore: [DEPENDABOT] "
target-branch: develop
groups:
codegen-dependencies:
patterns:
- "*"
@@ -62,7 +62,6 @@ libxrpl.tx > xrpl.protocol
libxrpl.tx > xrpl.server
libxrpl.tx > xrpl.tx
test.app > test.jtx
test.app > tests.libxrpl
test.app > test.unit_test
test.app > xrpl.basics
test.app > xrpl.config
-1
View File
@@ -8,7 +8,6 @@ on:
branches:
- "develop"
- "release/*"
- "staging/*"
paths:
- "crates/**/Cargo.toml"
- "crates/Cargo.lock"
+1 -1
View File
@@ -243,7 +243,7 @@ jobs:
- should-run
- build-test
# Only run when committing to a PR that targets a release branch.
if: ${{ github.repository == 'XRPLF/rippled' && needs.should-run.outputs.go == 'true' && github.event_name == 'pull_request' && startsWith(github.event.pull_request.base.ref, 'release/') }}
if: ${{ github.repository == 'XRPLF/rippled' && needs.should-run.outputs.go == 'true' && github.event_name == 'pull_request' && startsWith(github.event.pull_request.base.ref, 'release') }}
uses: ./.github/workflows/reusable-upload-recipe.yml
secrets:
remote_username: ${{ secrets.NEXUS_REMOTE_USERNAME }}
+6 -8
View File
@@ -1,16 +1,14 @@
# This workflow runs all workflows to build and test the code on various Linux
# flavors, as well as on MacOS and Windows, on a scheduled basis, on merge into
# the 'develop', 'release/*' or 'staging/*' branches, or when requested
# manually. Upon pushes to the develop branch, it also uploads the libxrpl
# recipe to the Conan remote.
# the 'develop' or 'release*' branches, or when requested manually. Upon pushes
# to the develop branch it also uploads the libxrpl recipe to the Conan remote.
name: Trigger
on:
push:
branches:
- "develop"
- "release/*"
- "staging/*"
- "release*"
paths:
# These paths are unique to `on-trigger.yml`.
- ".github/workflows/on-trigger.yml"
@@ -64,9 +62,9 @@ on:
workflow_dispatch:
concurrency:
# When a PR is merged into the develop branch, it will be assigned a unique
# When a PR is merged into the develop branch it will be assigned a unique
# group identifier, so execution will continue even if another PR is merged
# while it is still running. In all other cases, the group identifier is shared
# while it is still running. In all other cases the group identifier is shared
# per branch, so that any in-progress runs are cancelled when a new commit is
# pushed.
group: ${{ github.workflow }}-${{ github.event_name == 'push' && github.ref == 'refs/heads/develop' && github.sha || github.ref }}
@@ -100,7 +98,7 @@ jobs:
# However, we do not enable ccache for events targeting a release branch,
# to protect against the rare case that the output produced by ccache is
# not identical to a regular compilation.
ccache_enabled: ${{ github.repository_owner == 'XRPLF' && !startsWith(github.ref, 'refs/heads/release/') }}
ccache_enabled: ${{ github.repository_owner == 'XRPLF' && !startsWith(github.ref, 'refs/heads/release') }}
os: ${{ matrix.os }}
secrets:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
+1 -2
View File
@@ -8,8 +8,7 @@ on:
push:
branches:
- "develop"
- "release/*"
- "staging/*"
- "release*"
workflow_dispatch:
jobs:
+2 -8
View File
@@ -123,19 +123,13 @@ jobs:
- name: Fix style
if: ${{ steps.files_changed.outcome != 'success' }}
# pre-commit installs every hook's environment, even for hooks that match
# no files, and node-based hooks need libatomic, which this image lacks.
# Exit code 1 just means hooks modified files or found issues.
env:
SKIP: prettier,cspell
run: |
git add --update
pre-commit run || [ $? -eq 1 ]
pre-commit run --all-files || true
- name: Generate git diff
if: ${{ steps.files_changed.outcome != 'success' }}
run: |
git diff HEAD | tee "${DIFF_FILE}"
git diff | tee "${DIFF_FILE}"
- name: Upload clang-tidy diff output
if: ${{ github.event.repository.visibility == 'public' && steps.files_changed.outcome != 'success' }}
@@ -95,15 +95,11 @@ jobs:
apt-get update -qq
apt-get install -y "./${PACKAGE}"
# Rocky Linux and AlmaLinux pick a mirror from a mirrorlist, and now and
# then get one that serves the repository metadata at a few hundred kB/s,
# which alone outlasts the job's timeout. dnf only abandons a mirror below
# 1000 B/s for 30 s, so raise that floor to fail over to the next one fast.
- name: Install the RPM
if: ${{ inputs.package_type == 'rpm' }}
env:
PACKAGE: ${{ steps.find.outputs.package }}
run: dnf install -y --setopt=minrate=1M --setopt=timeout=10 "./${PACKAGE}"
run: dnf install -y "./${PACKAGE}"
- name: Run xrpld
run: xrpld --version
-7
View File
@@ -30,18 +30,11 @@ Version 3.5.0 is not yet released.
- `subscribe`, `unsubscribe`: Added an optional `mpt_issuances` request field, an array of MPT issuance IDs (hex strings). Subscribers receive the same `transaction` message as the `transactions` stream for each validated transaction whose metadata affects a subscribed issuance. MPT issuance subscriptions count toward the per-connection subscription limit. An empty array, a non-array value, or an invalid ID returns `invalidParams`. ([#5671](https://github.com/XRPLF/rippled/pull/5671))
- `ledger_entry`: Add full support for checks, NFT offers, payment channels, and signer lists. ([#6319](https://github.com/XRPLF/rippled/pull/6319))
- `channel_authorize`, `channel_verify`: `amount` now also accepts the transaction `Amount` JSON object for a token payment channel (`currency`, `issuer` and `value` for a trust line token; `mpt_issuance_id` and `value` for an MPT), and the claim authorization is serialized for that token. A string of drops is still used for an XRP channel. An object that names XRP, is negative or is malformed returns `channelAmtMalformed`. On the command line, the amount argument may be that JSON object as a single argument. For a token channel the signed message is the `PaymentChannelClaim` hash prefix, the channel ID and the amount serialized as an `Amount` field value without its field header; the XRP message is unchanged. ([#7935](https://github.com/XRPLF/rippled/pull/7935))
- `PaymentChannelCreate`, `PaymentChannelFund`, `PaymentChannelClaim`: `Amount` (and `Balance` on `PaymentChannelClaim`) now accept a trust line or MPT value in addition to XRP, gated by the `TokenPaychan` amendment. ([#7935](https://github.com/XRPLF/rippled/pull/7935))
- `ledger_entry`, `account_objects`: The `PayChannel` ledger entry now includes optional `TransferRate` and `IssuerNode` fields, present on a channel funded with a trust line or MPT value. ([#7935](https://github.com/XRPLF/rippled/pull/7935))
- `account_channels`: For a token payment channel, `amount` and `balance` are `Amount` JSON objects (`currency`, `issuer` and `value` for a trust line token; `mpt_issuance_id` and `value` for an MPT). An XRP channel still returns strings of drops. ([#7935](https://github.com/XRPLF/rippled/pull/7935))
### Bugfixes in 3.5.0
- `channel_authorize`: The `channel_id` field now returns an `invalidParams` error if the value is not a string. [#7582](https://github.com/XRPLF/rippled/pull/7582)
- `channel_verify`: The `channel_id` and `signature` fields now return an `invalidParams` error if the value is not a string. [#7582](https://github.com/XRPLF/rippled/pull/7582)
- `account_currencies`, `account_nfts`, `account_offers`: A request with more than one problem may now report a different one of them. An invalid `ledger_hash` or `ledger_index` is reported before an invalid or missing `account`. An invalid `limit` or `marker` is reported before `lgrNotFound` or `actNotFound`, and for `account_offers` also before `actMalformed`. A request with a single problem returns the same error as before. [#8561](https://github.com/XRPLF/rippled/pull/8561)
- `deposit_authorized`: A `credentials` entry that is all zeros (for example `"0"` or 64 `0` characters) now returns a `badCredentials` error instead of looking up a credential that can never exist.
- `account_info`, `account_lines`, `account_channels`: A request with more than one problem may now report a different one of them. An invalid `ledger_hash` or `ledger_index` is reported before a missing or invalid `account`, or `ident` for `account_info`. For `account_lines`, a `peer` that is not a string, or an invalid `limit` or `marker`, is reported before `lgrNotFound`, `actNotFound` or a malformed `account`, and an invalid `limit` or `marker` also before a malformed `peer`. For `account_channels`, a malformed `account` is reported before `lgrNotFound`, and an invalid `destination_account`, `limit` or `marker` before `lgrNotFound` or `actNotFound`. A request with a single problem returns the same error as before. [#8573](https://github.com/XRPLF/rippled/pull/8573)
### Bugfixes in 3.5.0
+26 -5
View File
@@ -2,12 +2,33 @@
| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| These instructions assume you have a C++ development environment ready with Git, Python, Conan, CMake, and a C++ compiler. For help setting one up on Linux, macOS, or Windows, [see this guide](./docs/build/environment.md).<br><br>These instructions also assume a basic familiarity with Conan and CMake. If you are unfamiliar with Conan, you can read our [crash course](./docs/build/conan.md) or the official [Getting Started][conan-getting-started] walkthrough. |
## Supported platforms
## Minimum Requirements
Building and testing are supported on Linux, macOS, and (best effort) Windows;
see [Supported development platforms](./docs/build/environment.md#supported-development-platforms).
Running `xrpld` is officially supported on Linux only;
see [Requirements](./docs/install.md#requirements).
For the hardware needed to run a node, see
[System Requirements](https://xrpl.org/system-requirements.html).
For the software needed to build xrpld, see the
[environment setup guide](./docs/build/environment.md).
## Operating Systems
### Linux
The Ubuntu Linux distribution has received the highest level of quality
assurance, testing, and support. We also support Red Hat and use Debian
internally.
Our Linux CI tooling is distro-independent and uses a Nix-based environment, so it should be possible to build on other Linux distributions as well, although we have not tested them.
### macOS
Many `xrpld` engineers use macOS for development.
The minimum supported version is macOS 15 (Sequoia).
CI testing is done in macOS 26 (Tahoe), but the build defaults `CMAKE_OSX_DEPLOYMENT_TARGET` to 15.
### Windows
Windows is used by some engineers for development only, and is not recommended
for production use.
## Steps
+1 -1
View File
@@ -42,7 +42,7 @@ if(is_gcc)
# GCC-specific fixes
add_compile_options(-Wno-unknown-pragmas -Wno-subobject-linkage)
# -Wno-subobject-linkage can be removed when we upgrade GCC version to at least 13.3
elseif(is_any_clang)
elseif(is_clang)
# Clang-specific fixes
add_compile_options(-Wno-unknown-warning-option) # Ignore unknown warning options
elseif(is_msvc)
+1 -1
View File
@@ -332,7 +332,7 @@ This ensures that configuration changes don't introduce new warnings across the
### Installing clang-tidy
The [Nix development shell](./docs/build/nix.md) provides `clang-tidy` at the [tested version](./docs/build/environment.md#tested-compiler-versions).
See the [environment setup guide](./docs/build/environment.md#clang-tidy) for how to get clang-tidy.
### Running clang-tidy locally
+10 -16
View File
@@ -34,7 +34,7 @@ so tools can filter them and protection rules can target `release/*` and `stagin
- **Branches only move forward.**
`develop`, `staging/X.Y.x`, and `release/X.Y.x` are never rewritten.
- **Cherry-picking only goes from `develop` to a staging branch**,
for fixes that must get into a release after the [code freeze](#release-candidates)
for fixes that must get into a release after the code freeze
(see [Emergency Fixes From `develop`](#emergency-fixes-from-develop)).
- **Security fixes are prepared privately and published with the release that contains them**,
so vulnerabilities are not disclosed prematurely.
@@ -49,7 +49,6 @@ develop staging/X.Y.x & release/X.Y.x
│
├── Tag: X.Y.0-b1
├── Tag: X.Y.0-bN
│ (2 weeks before rc1: freeze on large changes starts)
├── Tag: X.Y.0-rc1 ───────────────┐ (branches created)
│ │
│ (development continues) ├── Fixes
@@ -76,20 +75,16 @@ develop staging/X.Y.x & release/X.Y.x
3. **Branches:** `staging/X.Y.x` and `release/X.Y.x` are created from `develop`
at the commit tagged `X.Y.0-rc1`.
The first RC also kicks off the QE process.
### Freeze on Large Changes
From two weeks before `X.Y.0-rc1` until `X.Y.0` is [merged back](#merging-back-into-develop),
large changes (e.g., big refactors, moving or renaming many files, mass reformatting)
are not merged into `develop`.
4. **Code freeze:** No new features or unrelated changes are pulled from `develop`
into `staging/X.Y.x` or `release/X.Y.x`.
Only critical stabilization fixes go into the line.
5. **No large changes on `develop`:** Until `X.Y.0` is [merged back](#merging-back-into-develop),
large changes (e.g., big refactors, moving or renaming many files, mass reformatting)
are not merged into `develop`,
so that fixes on the line and the merge back don't run into conflicts.
### Release Candidates
After branching, the line is in code freeze:
no new features or unrelated changes are pulled from `develop`
into `staging/X.Y.x` or `release/X.Y.x`.
Only critical stabilization fixes go into the line.
1. Fixes are developed against `staging/X.Y.x`.
2. When ready, a new RC is created on `staging/X.Y.x`,
and `release/X.Y.x` is fast-forwarded to it.
@@ -111,8 +106,7 @@ one right after the other.
2. `release/X.Y.x` is fast-forwarded to `staging/X.Y.x`,
and the release is tagged on it.
3. The release is immediately [merged back into `develop`](#merging-back-into-develop).
For a major-minor release, this lifts the [freeze on large changes](#freeze-on-large-changes)
in `develop`.
For a major-minor release, this lifts the freeze on large changes in `develop`.
### Merging Back Into `develop`
@@ -133,7 +127,7 @@ After the merge, `git log develop..<tag>` must be empty.
### Emergency Fixes From `develop`
If a commit was merged to `develop`
and needs to be included in a release after the [code freeze](#release-candidates):
and needs to be included in a release after the code freeze:
1. Create a PR that cherry-picks the commit onto `staging/X.Y.x`.
2. Leave `develop` as it is, with no reverts.
-33
View File
@@ -1360,39 +1360,6 @@
# Example:
# owner_reserve = 200000 # 0.2 XRP
#
# gas_limit = <gas>
#
# The gas limit is the maximum amount of gas that can be
# consumed by a single transaction. The gas limit is used to prevent
# transactions from consuming too many resources.
#
# If this parameter is unspecified, xrpld will use an internal
# default. Don't change this without understanding the consequences.
#
# Example:
# gas_limit = 1000000 # 1 million gas
#
# bytecode_size_limit = <bytes>
#
# The bytecode size limit is the maximum size of a WASM extension in
# bytes. The size limit is used to prevent extensions from consuming
# too many resources.
#
# If this parameter is unspecified, xrpld will use an internal
# default. Don't change this without understanding the consequences.
#
# Example:
# bytecode_size_limit = 100000 # 100 kb
#
# gas_price = <micro-drops>
#
# The gas price is the conversion between WASM gas and its price in drops.
#
# If this parameter is unspecified, xrpld will use an internal
# default. Don't change this without understanding the consequences.
#
# Example:
# gas_price = 1000000 # 1 drop per gas
#-------------------------------------------------------------------------------
#
# 9. Misc Settings
+3 -11
View File
@@ -9,21 +9,13 @@ include_guard(GLOBAL)
# --------------------------------------------------------------------
# Compiler detection (C++)
# --------------------------------------------------------------------
# is_any_clang is true for both LLVM Clang and AppleClang. Their version numbers
# and feature sets differ, so use is_llvm_clang or is_apple_clang when it matters.
set(is_any_clang FALSE)
set(is_llvm_clang FALSE)
set(is_apple_clang FALSE)
set(is_clang FALSE)
set(is_gcc FALSE)
set(is_msvc FALSE)
set(is_xcode FALSE)
if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
set(is_any_clang TRUE)
set(is_llvm_clang TRUE)
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang")
set(is_any_clang TRUE)
set(is_apple_clang TRUE)
if(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") # Clang or AppleClang
set(is_clang TRUE)
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
set(is_gcc TRUE)
elseif(MSVC)
+3 -6
View File
@@ -130,7 +130,7 @@ else()
INTERFACE
-Wall
-Wdeprecated
$<$<BOOL:${is_any_clang}>:-Wno-deprecated-declarations>
$<$<BOOL:${is_clang}>:-Wno-deprecated-declarations>
$<$<BOOL:${wextra}>:-Wextra
-Wno-unused-parameter>
$<$<BOOL:${werr}>:-Werror>
@@ -177,7 +177,7 @@ else()
if(
is_nix_compiler
AND is_linux
AND is_any_clang
AND is_clang
AND CMAKE_CXX_FLAGS MATCHES "stdlib=libstdc"
)
string(
@@ -204,10 +204,7 @@ if(voidstar)
"Antithesis instrumentation requires Linux, aborting..."
)
elseif(
NOT (
is_llvm_clang
AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 16.0
)
NOT (is_clang AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 16.0)
)
message(
FATAL_ERROR
+1 -10
View File
@@ -296,16 +296,7 @@ if(xrpld)
OUT_VAR rpcspec_instantiations
VALUE_TYPE "::json::Value"
VIEW_HEADER "xrpld/rpc/detail/JsonObjectView.hpp"
HANDLERS
account_channels
account_currencies
account_info
account_lines
account_nfts
account_offers
book_changes
ledger
transaction_entry
HANDLERS book_changes ledger transaction_entry
)
target_sources(xrpld PRIVATE ${rpcspec_instantiations})
+1 -1
View File
@@ -58,7 +58,7 @@ target_link_options(common INTERFACE ${sanitizers_linker_flags})
# This module appends -fsanitize-ignorelist=<path> for Clang builds.
# The ignorelist path contains CMAKE_SOURCE_DIR, so it must be set here, rather than in the Conan profile.
# GCC does not support -fsanitize-ignorelist.
if(is_any_clang)
if(is_clang)
set(ignorelist_path
"${CMAKE_SOURCE_DIR}/sanitizers/suppressions/sanitizer-ignorelist.txt"
)
+5 -2
View File
@@ -23,8 +23,11 @@ if(NOT is_multiconfig)
endif()
endif()
if(is_llvm_clang)
if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 16.0)
if(is_clang) # both Clang and AppleClang
if(
"${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang"
AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 16.0
)
message(FATAL_ERROR "This project requires clang 16 or later")
endif()
elseif(is_gcc)
+3 -3
View File
@@ -62,11 +62,11 @@ if(unity)
set(CMAKE_UNITY_BUILD ON CACHE BOOL "Do a unity build")
endif()
if(is_llvm_clang AND is_linux)
if(is_clang AND is_linux)
option(voidstar "Enable Antithesis instrumentation." OFF)
endif()
if(is_gcc OR is_any_clang)
if(is_gcc OR is_clang)
include(ProcessorCount)
ProcessorCount(PROCESSOR_COUNT)
@@ -125,7 +125,7 @@ else()
set(use_mold OFF CACHE BOOL "mold linker, linux only" FORCE)
endif()
if(is_any_clang)
if(is_clang)
option(use_lld "enables detection of lld linker" ON)
else()
set(use_lld OFF CACHE BOOL "try lld linker, clang only" FORCE)
+2 -2
View File
@@ -7,7 +7,7 @@
pcpp>=1.30
# Parser combinator library - used to parse the macro DSL
pyparsing>=3.3.3
pyparsing>=3.0.0
# Template engine - used to generate C++ code from templates
Mako>=1.4.2
Mako>=1.2.2
+6 -6
View File
@@ -1,8 +1,8 @@
# This file was autogenerated by uv via the following command:
# uv pip compile requirements.in --generate-hashes --output-file requirements.txt
mako==1.4.3 \
--hash=sha256:723296007c870bfd6b3f0c3230dba7198096e5269297ebf5e4eff9e7ffa39d4f \
--hash=sha256:cd6537fe88d5fec315c55c2f8529bc4ce7a9a352ad7db3eeaa6a66e2dd4ec37a
mako==1.3.12 \
--hash=sha256:8f61569480282dbf557145ce441e4ba888be453c30989f879f0d652e39f53ea9 \
--hash=sha256:9f778e93289bd410bb35daadeb4fc66d95a746f0b75777b942088b7fd7af550a
# via -r requirements.in
markupsafe==3.0.3 \
--hash=sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f \
@@ -99,7 +99,7 @@ pcpp==1.30 \
--hash=sha256:05fe08292b6da57f385001c891a87f40d6aa7f46787b03e8ba326d20a3297c6e \
--hash=sha256:5af9fbce55f136d7931ae915fae03c34030a3b36c496e72d9636cedc8e2543a1
# via -r requirements.in
pyparsing==3.3.3 \
--hash=sha256:928ae7e20211f3b6f3915a72f06a0cfd29ab9d24279dd6346b6b1a7146397d36 \
--hash=sha256:ece8c00a69cf01b45d0b1dedabb469c90d8caf996d4fda40f147627a122849a4
pyparsing==3.3.2 \
--hash=sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d \
--hash=sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc
# via -r requirements.in
+1 -1
View File
@@ -3,7 +3,7 @@
"requires": [
"zlib/1.3.2#1cb806da49011867778ffb6ac7190fcb%1782392402.122708",
"xxhash/0.8.3#681d36a0a6111fc56e5e45ea182c19cc%1782392402.420688",
"xrpl-rpc-spec/0.1.25#2daa763e2d86ccc4ad0a0fa4d1983e9f%1791907455.579992",
"xrpl-rpc-spec/0.1.21#d536f87a2ae7d313452746cfa3ac4404%1790869005.122975",
"sqlite3/3.53.0#324ada52333108388a9a6108bfa96734%1782392403.185447",
"soci/4.0.3#e726491a03468795453f7c83fc924a96%1782392402.679521",
"snappy/1.1.10#968fef506ff261592ec30c574d4a7809%1782307151.633168",
+1 -1
View File
@@ -40,7 +40,7 @@ class Xrpl(ConanFile):
"nudb/2.0.9",
"openssl/3.6.3",
"soci/4.0.3",
"xrpl-rpc-spec/0.1.25",
"xrpl-rpc-spec/0.1.21",
"zlib/1.3.2",
]
+77 -32
View File
@@ -4,18 +4,6 @@ This document explains how to set one up.
[BUILD.md]: ../../BUILD.md
## Supported development platforms
- **Linux**: the [Nix development shell](./nix.md),
which provides the same toolchain CI uses on any distribution.
- **macOS** 15 (Sequoia) or newer: the [Nix development shell](./nix.md),
or the system [Apple Clang](#macos-apple-clang).
CI runs on macOS 26 (Tahoe), but the build defaults `CMAKE_OSX_DEPLOYMENT_TARGET` to 15.
- **Windows**: best effort, see [Windows](#windows).
For the platforms supported for running `xrpld`,
see [Requirements](../install.md#requirements).
## Tested compiler versions
`xrpld` is built in the **C++23** dialect by default, so your toolchain has to
@@ -47,16 +35,20 @@ compiler building. Treat support for anything outside the table as best-effort.
## Required tools
Besides a compiler, building `xrpld` requires
[Git](https://git-scm.com/downloads),
[Python](https://www.python.org/downloads/),
[Conan](https://conan.io/downloads.html),
and [CMake](https://cmake.org/download/).
Building with `-Drust=ON` additionally requires a Rust toolchain,
see [Rust](#rust).
Besides a compiler, building `xrpld` requires:
| Tool | Minimum version |
| ------------------------------------------- | --------------- |
| [Git](https://git-scm.com/downloads) | any recent |
| [Python](https://www.python.org/downloads/) | 3.11 |
| [Conan](https://conan.io/downloads.html) | 2.17 |
| [CMake](https://cmake.org/download/) | 3.16 |
On Linux and macOS, the [Nix development shell](./nix.md) provides all of them
at the versions CI uses, recorded in [`nix/check-tools/`](../../nix/check-tools).
(see below). On Windows they have to be installed manually.
Building with `-Drust=ON` additionally requires a Rust toolchain, see
[Rust](#rust). A default build does not, so it is not in the table above.
Once they are in place, verify that everything is installed and runnable with:
@@ -66,19 +58,60 @@ Once they are in place, verify that everything is installed and runnable with:
## Linux and macOS
Use the [Nix development shell](./nix.md):
it provides the compiler and every tool CI uses,
without installing anything system-wide.
The **recommended way** to get a development environment on Linux and macOS is
the Nix development shell. It provides the exact tooling used in CI — `git`,
`python`, `conan`, `cmake`, `clang-tidy`, `clang-format`, and everything else —
with a single command and without installing anything system-wide:
### macOS: Apple Clang
```bash
nix --experimental-features 'nix-command flakes' develop
```
You can also build with the system Apple Clang,
installing the other [required tools](#required-tools) any way you like
(or getting them from `nix develop .#apple-clang`).
Its version is whatever your installed Xcode (or Command Line Tools) provides,
so check that `clang --version` is at least the [tested one](#tested-compiler-versions).
If several Xcode versions are installed,
select one with `sudo xcode-select -s <path>` or the `DEVELOPER_DIR` environment variable.
On **Linux**, Nix also provides the compiler (GCC); on **macOS**, it provides
Clang. If you instead opt to use your system-wide Apple Clang (via
`nix develop .#apple-clang`), you need to manage its version yourself (see
below).
See [Using the Nix development shell](./nix.md) for installation and usage
details, including how to select a different compiler and why we recommend Nix
over a hand-maintained environment.
### macOS: managing the Apple Clang version
If you use your system-wide Apple Clang on macOS (via `nix develop .#apple-clang`),
the compiler version is whatever your installed Xcode (or Command Line Tools)
provides. The following command should return a version greater than or equal to
the [tested one](#tested-compiler-versions):
```bash
clang --version
```
If you develop other applications using Xcode, you might be consistently
updating to the newest version of Apple Clang, which will likely cause issues
building xrpld. You may want to install and pin a specific version of Xcode:
1. **Download Xcode**
- Visit [Apple Developer Downloads](https://developer.apple.com/download/more/)
- Sign in with your Apple Developer account
- Search for an Xcode version that includes the expected Apple Clang version
- Download the `.xip` file
2. **Install and configure Xcode**
```bash
# Extract the .xip file and rename for version management
# Example: Xcode_16.2.app
# Move to Applications directory
sudo mv Xcode_16.2.app /Applications/
# Set as default toolchain (persistent)
sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer
# Set as environment variable (temporary)
export DEVELOPER_DIR=/Applications/Xcode_16.2.app/Contents/Developer
```
## Windows
@@ -90,7 +123,8 @@ manually:
"x64 Native Tools Command Prompt". CI configures CMake with the
`Visual Studio 18 2026` generator.
- [Git for Windows](https://git-scm.com/download/win)
- recent releases of Python, Conan, and CMake
- Python, Conan, and CMake, at the versions listed in
[Required tools](#required-tools).
- a [Rust toolchain](https://rustup.rs) — only needed to build with
`-Drust=ON`, see [Rust](#rust)
@@ -111,5 +145,16 @@ version.
Everything else the Rust build needs on the CMake side comes from Conan along
with the rest of the dependencies, so there is nothing further to install.
## 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. The version this project uses is listed in
[Tested compiler versions](#tested-compiler-versions).
On Linux and macOS, the [Nix development shell](./nix.md) provides that exact
version out of the box — run it via `run-clang-tidy`. No separate installation
is needed.
[cpp23-support]: https://en.cppreference.com/w/cpp/compiler_support/23
[packages.nix]: ../../nix/packages.nix
+9 -2
View File
@@ -9,7 +9,9 @@ This guide explains how to use Nix to set up a reproducible development environm
- **Reproducible environment**: Everyone gets the same versions of tools and compilers
- **Matches CI**: The Linux CI runs in Docker images built from this exact Nix environment, and CI builds some macOS configurations in it as well
- **No system pollution**: Dependencies are isolated and don't affect your system packages
- **Consistent compilers**: The GCC and Clang shells use the same versions as CI
- **Quick setup**: Get started with a single command
- **Works on Linux and macOS**: Consistent experience across platforms
## Install Nix
@@ -25,8 +27,13 @@ From the root of the xrpld repository, enter the default development shell:
nix --experimental-features 'nix-command flakes' develop
```
This downloads and sets up all required development tools (CMake, Ninja, Conan, etc.)
and the compiler for your platform (see [Platform notes](#platform-notes)).
This will:
- Download and set up all required development tools (CMake, Ninja, Conan, etc.)
- Configure the appropriate compiler for your platform:
- **Linux**: GCC (provided by Nix)
- **macOS**: Clang (provided by Nix)
The first time you run this command, it will take a few minutes to download and build the environment. Subsequent runs will be much faster.
### Platform notes
+1 -1
View File
@@ -565,7 +565,7 @@ template <class Adaptor>
class Consensus
{
public:
Consensus(clock_type, Adaptor &, beast::journal);
Consensus(ClockType, Adaptor &, beast::journal);
// Kick-off the next round of consensus.
void startRound(
-13
View File
@@ -10,19 +10,6 @@ and DNF on Red Hat-based distributions such as RHEL, AlmaLinux, and Rocky Linux,
where `yum` is a symlink to `dnf`.
To build from source instead, see [BUILD.md](../BUILD.md).
## Requirements
Linux is the only operating system officially supported for running `xrpld`;
macOS and Windows builds are for development only.
Any x86-64 Linux distribution with APT or DNF and glibc 2.31 or newer should work
(see [How libc is handled](../nix/docker/README.md#how-libc-is-handled)).
CI tests the packages on Debian 11–13, Ubuntu 20.04–26.04,
RHEL (UBI) 9–10, AlmaLinux 9–10, and Rocky Linux 9–10.
For the hardware needed to run a node,
see [System Requirements](https://xrpl.org/system-requirements.html).
## Release channels
Packages are published to four channels:
-72
View File
@@ -39,31 +39,6 @@ checkedSub(std::int64_t a, std::int64_t b) noexcept
return a - b;
}
/**
* Multiply two signed 64-bit integers, returning std::nullopt when the exact
* mathematical product is not representable in std::int64_t.
*/
[[nodiscard]] constexpr std::optional<std::int64_t>
checkedMul(std::int64_t a, std::int64_t b) noexcept
{
using L = std::numeric_limits<std::int64_t>;
if (a == 0 || b == 0)
return 0;
if (a > 0)
{
if (b > 0 ? a > L::max() / b : b < L::min() / a)
return std::nullopt;
}
else if (b > 0 ? a < L::min() / b : b < L::max() / a)
{
return std::nullopt;
}
return a * b;
}
static_assert(checkedAdd(0, 0) == 0);
static_assert(checkedAdd(1, -1) == 0);
static_assert(checkedAdd(-5, 2) == -3);
@@ -106,53 +81,6 @@ static_assert(
!checkedSub(std::numeric_limits<std::int64_t>::min(), std::numeric_limits<std::int64_t>::max())
.has_value());
static_assert(checkedMul(0, std::numeric_limits<std::int64_t>::min()) == 0);
static_assert(checkedMul(std::numeric_limits<std::int64_t>::min(), 0) == 0);
static_assert(checkedMul(-1, -1) == 1);
// a > 0, b > 0
static_assert(
checkedMul(std::numeric_limits<std::int64_t>::max(), 1) ==
std::numeric_limits<std::int64_t>::max());
static_assert(
checkedMul(std::numeric_limits<std::int64_t>::max() / 2, 2) ==
std::numeric_limits<std::int64_t>::max() - 1);
static_assert(!checkedMul((std::numeric_limits<std::int64_t>::max() / 2) + 1, 2).has_value());
static_assert(!checkedMul(2, (std::numeric_limits<std::int64_t>::max() / 2) + 1).has_value());
// a > 0, b < 0
static_assert(
checkedMul(std::numeric_limits<std::int64_t>::max(), -1) ==
-std::numeric_limits<std::int64_t>::max());
static_assert(
checkedMul((std::numeric_limits<std::int64_t>::max() / 2) + 1, -2) ==
std::numeric_limits<std::int64_t>::min());
static_assert(!checkedMul((std::numeric_limits<std::int64_t>::max() / 2) + 2, -2).has_value());
static_assert(
checkedMul(1, std::numeric_limits<std::int64_t>::min()) ==
std::numeric_limits<std::int64_t>::min());
static_assert(
checkedMul(2, std::numeric_limits<std::int64_t>::min() / 2) ==
std::numeric_limits<std::int64_t>::min());
static_assert(!checkedMul(2, (std::numeric_limits<std::int64_t>::min() / 2) - 1).has_value());
// a < 0, b > 0
static_assert(
checkedMul(std::numeric_limits<std::int64_t>::min(), 1) ==
std::numeric_limits<std::int64_t>::min());
static_assert(
checkedMul(std::numeric_limits<std::int64_t>::min() / 2, 2) ==
std::numeric_limits<std::int64_t>::min());
static_assert(!checkedMul((std::numeric_limits<std::int64_t>::min() / 2) - 1, 2).has_value());
static_assert(!checkedMul(std::numeric_limits<std::int64_t>::min(), 2).has_value());
// a < 0, b < 0
static_assert(
checkedMul(-(std::numeric_limits<std::int64_t>::max() / 2), -2) ==
std::numeric_limits<std::int64_t>::max() - 1);
static_assert(!checkedMul(std::numeric_limits<std::int64_t>::min() / 2, -2).has_value());
static_assert(
checkedMul(-1, -std::numeric_limits<std::int64_t>::max()) ==
std::numeric_limits<std::int64_t>::max());
static_assert(!checkedMul(std::numeric_limits<std::int64_t>::min(), -1).has_value());
static_assert(!checkedMul(-1, std::numeric_limits<std::int64_t>::min()).has_value());
/**
* Calculate one number divided by another number in percentage.
* The result is rounded up to the next integer, and capped in the range [0,100]
+2 -6
View File
@@ -352,9 +352,9 @@ class Number final
using rep = std::int64_t;
using InternalRep = MantissaRange::rep;
bool negative_{false};
InternalRep mantissa_{0};
int exponent_{std::numeric_limits<int>::lowest()};
bool negative_{false};
public:
// The range for the exponent when normalized
@@ -650,12 +650,8 @@ private:
shiftExponent(int exponentDelta) const;
};
// The number class currently has 3 tail padding bytes. If you are adding new
// members, try to fit them there before expanding the size of the class.
static_assert(sizeof(Number) == 16);
constexpr Number::Number(bool negative, InternalRep mantissa, int exponent, Unchecked) noexcept
: mantissa_{mantissa}, exponent_{exponent}, negative_(negative)
: negative_(negative), mantissa_{mantissa}, exponent_{exponent}
{
}
+3 -34
View File
@@ -153,24 +153,6 @@ private:
Policy policy,
Callback&& replaceCallback = nullptr);
/**
* canonicalizeImpl variant for callers that already hold `mutex_`.
*
* The unnamed `scoped_lock<MutexType> const&` parameter is proof of
* ownership; this overload does not acquire the lock. Use it from other
* methods (e.g. fetchAndModify) that need to compose canonicalize with
* additional work under a single critical section, without relying on
* recursive-mutex re-entry.
*/
template <class Policy, class Callback = std::nullptr_t>
bool
canonicalizeImpl(
std::scoped_lock<MutexType> const&,
key_type const& key,
CanonicalizeClientPointerType<Policy> data,
Policy policy,
Callback&& replaceCallback = nullptr);
public:
/**
* Replace aliased objects with originals.
@@ -257,6 +239,9 @@ public:
bool
retrieve(key_type const& key, T& data);
MutexType&
peekMutex();
std::vector<key_type>
getKeys() const;
@@ -278,22 +263,6 @@ public:
fetch(key_type const& digest, Handler const& h);
// End CachedSLEs functions.
/**
* Fetch or create an entry and execute a callback while holding the lock.
*
* The entry for the given key is fetched from the cache or created if it
* doesn't exist. The callback is then invoked with a reference to the
* entry while the cache mutex is still held, allowing safe modification
* of the cached object.
*
* @param key The key to fetch or create
* @param callback Function to call with the entry under lock.
* Signature: void(T&)
*/
template <class Callback>
void
fetchAndModify(key_type const& key, Callback&& callback);
private:
SharedPointerType
initialFetch(key_type const& key, std::scoped_lock<MutexType> const& l);
+18 -67
View File
@@ -4,7 +4,6 @@
#include <xrpl/basics/Log.h> // IWYU pragma: keep
#include <xrpl/basics/TaggedCache.h>
#include <xrpl/basics/scope.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <algorithm>
@@ -336,29 +335,6 @@ template <class Policy, class Callback>
inline bool
TaggedCache<Key, T, IsKeyCache, SharedWeakUnionPointer, SharedPointerType, Hash, KeyEqual, Mutex>::
canonicalizeImpl(
key_type const& key,
CanonicalizeClientPointerType<Policy> data,
Policy policy,
Callback&& replaceCallback)
{
std::scoped_lock<MutexType> const lock(mutex_);
return canonicalizeImpl(lock, key, data, policy, std::forward<Callback>(replaceCallback));
}
template <
class Key,
class T,
bool IsKeyCache,
class SharedWeakUnionPointer,
class SharedPointerType,
class Hash,
class KeyEqual,
class Mutex>
template <class Policy, class Callback>
inline bool
TaggedCache<Key, T, IsKeyCache, SharedWeakUnionPointer, SharedPointerType, Hash, KeyEqual, Mutex>::
canonicalizeImpl(
std::scoped_lock<MutexType> const&,
key_type const& key,
CanonicalizeClientPointerType<Policy> data,
[[maybe_unused]] Policy policy,
@@ -378,6 +354,8 @@ TaggedCache<Key, T, IsKeyCache, SharedWeakUnionPointer, SharedPointerType, Hash,
// passing a const argument is a compile error.
constexpr bool replaceCached = std::is_same_v<Policy, detail::ReplaceCached>;
std::scoped_lock const lock(mutex_);
auto const cit = cache_.find(key);
if (cit == cache_.end())
@@ -594,6 +572,22 @@ TaggedCache<Key, T, IsKeyCache, SharedWeakUnionPointer, SharedPointerType, Hash,
return true;
}
template <
class Key,
class T,
bool IsKeyCache,
class SharedWeakUnionPointer,
class SharedPointerType,
class Hash,
class KeyEqual,
class Mutex>
inline auto
TaggedCache<Key, T, IsKeyCache, SharedWeakUnionPointer, SharedPointerType, Hash, KeyEqual, Mutex>::
peekMutex() -> MutexType&
{
return mutex_;
}
template <
class Key,
class T,
@@ -706,49 +700,6 @@ TaggedCache<Key, T, IsKeyCache, SharedWeakUnionPointer, SharedPointerType, Hash,
}
// End CachedSLEs functions.
template <
class Key,
class T,
bool IsKeyCache,
class SharedWeakUnionPointer,
class SharedPointerType,
class Hash,
class KeyEqual,
class Mutex>
template <class Callback>
inline void
TaggedCache<Key, T, IsKeyCache, SharedWeakUnionPointer, SharedPointerType, Hash, KeyEqual, Mutex>::
fetchAndModify(key_type const& key, Callback&& callback)
{
static_assert(
!IsKeyCache, "fetchAndModify is only supported for value caches, not key-only caches");
static_assert(
std::is_same_v<std::shared_ptr<T>, SharedPointerType> ||
std::is_same_v<intr_ptr::SharedPtr<T>, SharedPointerType>,
"fetchAndModify requires std::shared_ptr<T> or intr_ptr::SharedPtr<T>");
std::scoped_lock<MutexType> const lock(mutex_);
// Same pointer-type dispatch as insert(): the placeholder must have exactly
// SharedPointerType so canonicalizeImpl can write the cached pointer back.
SharedPointerType entry = [] {
if constexpr (std::is_same_v<std::shared_ptr<T>, SharedPointerType>)
{
return std::make_shared<T>();
}
else
{
return intr_ptr::makeShared<T>();
}
}();
canonicalizeImpl(lock, key, entry, detail::ReplaceClient{});
ALWAYS(
entry != nullptr, "xrpl::TaggedCache::fetchAndModify : entry present after canonicalize");
callback(*entry);
}
template <
class Key,
class T,
-3
View File
@@ -94,7 +94,6 @@ struct Keys
static constexpr auto kBbtOptions = "bbt_options";
static constexpr auto kBgThreads = "bg_threads";
static constexpr auto kBlockSize = "block_size";
static constexpr auto kBytecodeSizeLimit = "bytecode_size_limit";
static constexpr auto kCacheAge = "cache_age";
static constexpr auto kCacheMb = "cache_mb";
static constexpr auto kCacheSize = "cache_size";
@@ -109,8 +108,6 @@ struct Keys
static constexpr auto kFileSizeMult = "file_size_mult";
static constexpr auto kFilterBits = "filter_bits";
static constexpr auto kFilterFull = "filter_full";
static constexpr auto kGasLimit = "gas_limit";
static constexpr auto kGasPrice = "gas_price";
static constexpr auto kHardSet = "hard_set";
static constexpr auto kHighThreads = "high_threads";
static constexpr auto kHoldTime = "hold_time";
+15 -65
View File
@@ -257,42 +257,13 @@ public:
header_.validated = true;
}
/**
* Mark this ledger as accepted and attempt to make it immutable.
*
* The close-time fields are recorded first, since the ledger hash covers
* them.
*
* @param closeTime The consensus-agreed close time.
* @param closeResolution The close time resolution.
* @param correctCloseTime Whether consensus agreed on the close time. If
* false, kSLcfNoConsensusTime is recorded in closeFlags instead.
* @return What setImmutable() returned.
*/
[[nodiscard]] bool
void
setAccepted(
NetClock::time_point closeTime,
NetClock::duration closeResolution,
bool correctCloseTime);
/**
* Mark this ledger as immutable, so it can no longer be modified.
*
* A ledger built or loaded locally cannot have an invalid map, since only
* a map syncing against hashes from outside can be proven impossible (see
* SHAMap::addKnownNode), so a caller on such a path may treat a false
* return as a broken internal invariant. A caller assembling a ledger from
* peer data may not: for it, false is an outcome a peer can produce.
*
* @param rehash Whether to recompute the ledger hash from the header
* fields. The transaction and account hashes are recomputed from the
* maps too, but only the first time.
* @return false if either map is Invalid, leaving the immutable flag as it
* was and the header untouched. A map invalidated partway through can
* still leave the other one immutable, so a false return means the
* ledger must be discarded rather than retried.
*/
[[nodiscard]] bool
void
setImmutable(bool rehash = true);
bool
@@ -301,28 +272,23 @@ public:
return immutable_;
}
/**
* @return Whether both maps can still be the maps the header names. See
* SHAMap::isValid().
*/
[[nodiscard]] bool
mapsValid() const
{
return txMap_.isValid() && stateMap_.isValid();
}
/* Mark this ledger as "should be full".
/**
* Mark this ledger as "should be full", indicating that the local server
* wants all the corresponding nodes in durable storage.
*
* Const because it reflects metadata, not data this ledger shares with
* other nodes on the network.
*/
"Full" is metadata property of the ledger, it indicates
that the local server wants all the corresponding nodes
in durable storage.
This is marked `const` because it reflects metadata
and not data that is in common with other nodes on the
network.
*/
void
setFull() const
{
txMap_.setFull(header_.seq);
stateMap_.setFull(header_.seq);
txMap_.setFull();
txMap_.setLedgerSeq(header_.seq);
stateMap_.setFull();
stateMap_.setLedgerSeq(header_.seq);
}
void
@@ -452,22 +418,6 @@ private:
static std::pair<std::shared_ptr<STTx const>, std::shared_ptr<STObject const>>
deserializeTxPlusMeta(SHAMapItem const& item);
/**
* Make both maps immutable.
*
* Each map is settled on its own, so neither is left mid-sync because the
* other refused.
*
* @return Whether both maps are immutable.
*/
[[nodiscard]] bool
setMapsImmutable()
{
bool const txImmutable = txMap_.setImmutable();
bool const stateImmutable = stateMap_.setImmutable();
return txImmutable && stateImmutable;
}
bool immutable_;
// A SHAMap containing the transactions associated with this ledger.
+1 -9
View File
@@ -265,13 +265,6 @@ canWithdraw(
[[nodiscard]] TER
canWithdraw(ReadView const& view, STTx const& tx);
/**
* Pays out a withdrawal from a vault or loan broker pseudo-account.
*
* `sourceAmount` leaves `sourceAcct` and `destinationAmount` reaches `dstAcct`.
* The two differ only when the withdrawal carries a transfer fee; the
* difference is the fee and is settled through the issuer.
*/
[[nodiscard]] TER
doWithdraw(
ApplyViewContext ctx,
@@ -279,8 +272,7 @@ doWithdraw(
AccountID const& dstAcct,
AccountID const& sourceAcct,
XRPAmount priorBalance,
STAmount const& destinationAmount,
STAmount const& sourceAmount,
STAmount const& amount,
beast::Journal j);
/**
+160 -81
View File
@@ -2,6 +2,7 @@
#include <xrpl/basics/Log.h>
#include <xrpl/basics/Number.h>
#include <xrpl/basics/contract.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/ledger/ApplyView.h>
@@ -28,6 +29,7 @@
#include <expected>
#include <functional>
#include <optional>
#include <stdexcept>
#include <tuple>
#include <utility>
@@ -311,11 +313,11 @@ getAMMOfferStartWithTakerPays(
* is equal to LOB quality (in this case AMM offer quality is
* better than LOB quality) or AMM offer is equal to LOB quality
* (in this case SPQ is better than LOB quality).
* The economically coarser integral offer side is calculated first. The
* result is rounded down, which makes the offer quality better. Calculating
* takerPays first instead would round down takerGets when that is the coarser
* side, giving worse quality than LOB quality, and the offer might fail to
* generate.
* Pre-amendment code calculates takerPays first. If takerGets is the
* economically coarser integral side, it is rounded down, which results in
* worse offer quality than LOB quality, and the offer might fail to generate.
* Post-amendment code calculates the economically coarser integral offer side
* first. The result is rounded down, which makes the offer quality better.
* It might not be possible to match either SPQ or AMM offer to LOB
* quality. This generally happens at higher fees.
* @param pool AMM pool balances
@@ -329,8 +331,69 @@ changeSpotPriceQuality(
TAmounts<TIn, TOut> const& pool,
Quality const& quality,
std::uint16_t tfee,
Rules const& rules,
beast::Journal j)
{
if (!rules.enabled(fixAMMv1_1))
{
// Finds takerPays (i) and takerGets (o) such that given pool
// composition poolGets(I) and poolPays(O): (O - o) / (I + i) = quality.
// Where takerGets is calculated as the swapAssetIn (see below).
// The above equation produces the quadratic equation:
// i^2*(1-fee) + i*I*(2-fee) + I^2 - I*O/quality,
// which is solved for i, and o is found with swapAssetIn().
auto const f = feeMult(tfee); // 1 - fee
auto const& a = f;
auto const b = pool.in * (1 + f);
Number const c = pool.in * pool.in - pool.in * pool.out * quality.rate();
auto const res = b * b - 4 * a * c;
if (res < 0)
{
return std::nullopt; // LCOV_EXCL_LINE
}
if (auto const nTakerPaysPropose = (-b + root2(res)) / (2 * a); nTakerPaysPropose > 0)
{
auto const nTakerPays = [&] {
// The fee might make the AMM offer quality less than CLOB
// quality. Therefore, AMM offer has to satisfy this constraint:
// o / i >= q. Substituting o with swapAssetIn() gives: i <= O /
// q - I / (1 - fee).
auto const nTakerPaysConstraint = pool.out * quality.rate() - pool.in / f;
if (nTakerPaysPropose > nTakerPaysConstraint)
return nTakerPaysConstraint;
return nTakerPaysPropose;
}();
if (nTakerPays <= 0)
{
JLOG(j.trace()) << "changeSpotPriceQuality calc failed: " << to_string(pool.in)
<< " " << to_string(pool.out) << " " << quality << " " << tfee;
return std::nullopt;
}
auto const takerPays =
toAmount<TIn>(getAsset(pool.in), nTakerPays, Number::RoundingMode::Upward);
// should not fail
auto amounts = TAmounts<TIn, TOut>{takerPays, swapAssetIn(pool, takerPays, tfee)};
if (Quality{amounts} < quality &&
!withinRelativeDistance(Quality{amounts}, quality, Number(1, -7)))
{
JLOG(j.error()) << "changeSpotPriceQuality failed: " << to_string(pool.in) << " "
<< to_string(pool.out) << " "
<< " " << quality << " " << tfee << " " << to_string(amounts.in)
<< " " << to_string(amounts.out);
Throw<std::runtime_error>("changeSpotPriceQuality failed");
}
JLOG(j.trace()) << "changeSpotPriceQuality succeeded: " << to_string(pool.in) << " "
<< to_string(pool.out) << " "
<< " " << quality << " " << tfee << " " << to_string(amounts.in) << " "
<< to_string(amounts.out);
return amounts;
}
JLOG(j.trace()) << "changeSpotPriceQuality calc failed: " << to_string(pool.in) << " "
<< to_string(pool.out) << " " << quality << " " << tfee;
return std::nullopt;
}
auto amounts = [&] {
bool const inIntegral = getAsset(pool.in).integral();
bool const outIntegral = getAsset(pool.out).integral();
@@ -396,49 +459,57 @@ template <typename TIn, typename TOut>
TOut
swapAssetIn(TAmounts<TIn, TOut> const& pool, TIn const& assetIn, std::uint16_t tfee)
{
// set rounding to always favor the amm. Clip to zero.
// calculate:
// pool.out -
// (pool.in * pool.out) / (pool.in + assetIn * feeMult(tfee)),
// and explicitly set the rounding modes
// Favoring the amm means we should:
// minimize:
// pool.out -
// (pool.in * pool.out) / (pool.in + assetIn * feeMult(tfee)),
// maximize:
// (pool.in * pool.out) / (pool.in + assetIn * feeMult(tfee)),
// (pool.in * pool.out)
// minimize:
// (pool.in + assetIn * feeMult(tfee)),
// minimize:
// assetIn * feeMult(tfee)
// feeMult is: (1-fee), fee is tfee/100000
// minimize:
// 1-fee
// maximize:
// fee
SaveNumberRoundMode const _{Number::getround()};
if (auto const& rules = getCurrentTransactionRules(); rules && rules->enabled(fixAMMv1_1))
{
// set rounding to always favor the amm. Clip to zero.
// calculate:
// pool.out -
// (pool.in * pool.out) / (pool.in + assetIn * feeMult(tfee)),
// and explicitly set the rounding modes
// Favoring the amm means we should:
// minimize:
// pool.out -
// (pool.in * pool.out) / (pool.in + assetIn * feeMult(tfee)),
// maximize:
// (pool.in * pool.out) / (pool.in + assetIn * feeMult(tfee)),
// (pool.in * pool.out)
// minimize:
// (pool.in + assetIn * feeMult(tfee)),
// minimize:
// assetIn * feeMult(tfee)
// feeMult is: (1-fee), fee is tfee/100000
// minimize:
// 1-fee
// maximize:
// fee
SaveNumberRoundMode const _{Number::getround()};
Number::setround(Number::RoundingMode::Upward);
auto const numerator = pool.in * pool.out;
auto const fee = getFee(tfee);
Number::setround(Number::RoundingMode::Upward);
auto const numerator = pool.in * pool.out;
auto const fee = getFee(tfee);
Number::setround(Number::RoundingMode::Downward);
auto const denom = pool.in + assetIn * (1 - fee);
Number::setround(Number::RoundingMode::Downward);
auto const denom = pool.in + assetIn * (1 - fee);
if (denom.signum() <= 0)
return toAmount<TOut>(getAsset(pool.out), 0);
if (denom.signum() <= 0)
return toAmount<TOut>(getAsset(pool.out), 0);
Number::setround(Number::RoundingMode::Upward);
auto const ratio = numerator / denom;
Number::setround(Number::RoundingMode::Upward);
auto const ratio = numerator / denom;
Number::setround(Number::RoundingMode::Downward);
auto const swapOut = pool.out - ratio;
Number::setround(Number::RoundingMode::Downward);
auto const swapOut = pool.out - ratio;
if (swapOut.signum() < 0)
return toAmount<TOut>(getAsset(pool.out), 0);
if (swapOut.signum() < 0)
return toAmount<TOut>(getAsset(pool.out), 0);
return toAmount<TOut>(getAsset(pool.out), swapOut, Number::RoundingMode::Downward);
return toAmount<TOut>(getAsset(pool.out), swapOut, Number::RoundingMode::Downward);
}
return toAmount<TOut>(
getAsset(pool.out),
pool.out - (pool.in * pool.out) / (pool.in + assetIn * feeMult(tfee)),
Number::RoundingMode::Downward);
}
/**
@@ -455,49 +526,57 @@ template <typename TIn, typename TOut>
TIn
swapAssetOut(TAmounts<TIn, TOut> const& pool, TOut const& assetOut, std::uint16_t tfee)
{
// set rounding to always favor the amm. Clip to zero.
// calculate:
// ((pool.in * pool.out) / (pool.out - assetOut) - pool.in) /
// (1-tfee/100000)
// maximize:
// ((pool.in * pool.out) / (pool.out - assetOut) - pool.in)
// maximize:
// (pool.in * pool.out) / (pool.out - assetOut)
// maximize:
// (pool.in * pool.out)
// minimize
// (pool.out - assetOut)
// minimize:
// (1-tfee/100000)
// maximize:
// tfee/100000
SaveNumberRoundMode const _{Number::getround()};
Number::setround(Number::RoundingMode::Upward);
auto const numerator = pool.in * pool.out;
Number::setround(Number::RoundingMode::Downward);
auto const denom = pool.out - assetOut;
if (denom.signum() <= 0)
if (auto const& rules = getCurrentTransactionRules(); rules && rules->enabled(fixAMMv1_1))
{
return toMaxAmount<TIn>(getAsset(pool.in));
// set rounding to always favor the amm. Clip to zero.
// calculate:
// ((pool.in * pool.out) / (pool.out - assetOut) - pool.in) /
// (1-tfee/100000)
// maximize:
// ((pool.in * pool.out) / (pool.out - assetOut) - pool.in)
// maximize:
// (pool.in * pool.out) / (pool.out - assetOut)
// maximize:
// (pool.in * pool.out)
// minimize
// (pool.out - assetOut)
// minimize:
// (1-tfee/100000)
// maximize:
// tfee/100000
SaveNumberRoundMode const _{Number::getround()};
Number::setround(Number::RoundingMode::Upward);
auto const numerator = pool.in * pool.out;
Number::setround(Number::RoundingMode::Downward);
auto const denom = pool.out - assetOut;
if (denom.signum() <= 0)
{
return toMaxAmount<TIn>(getAsset(pool.in));
}
Number::setround(Number::RoundingMode::Upward);
auto const ratio = numerator / denom;
auto const numerator2 = ratio - pool.in;
auto const fee = getFee(tfee);
Number::setround(Number::RoundingMode::Downward);
auto const feeMult = 1 - fee;
Number::setround(Number::RoundingMode::Upward);
auto const swapIn = numerator2 / feeMult;
if (swapIn.signum() < 0)
return toAmount<TIn>(getAsset(pool.in), 0);
return toAmount<TIn>(getAsset(pool.in), swapIn, Number::RoundingMode::Upward);
}
Number::setround(Number::RoundingMode::Upward);
auto const ratio = numerator / denom;
auto const numerator2 = ratio - pool.in;
auto const fee = getFee(tfee);
Number::setround(Number::RoundingMode::Downward);
auto const feeMult = 1 - fee;
Number::setround(Number::RoundingMode::Upward);
auto const swapIn = numerator2 / feeMult;
if (swapIn.signum() < 0)
return toAmount<TIn>(getAsset(pool.in), 0);
return toAmount<TIn>(getAsset(pool.in), swapIn, Number::RoundingMode::Upward);
return toAmount<TIn>(
getAsset(pool.in),
((pool.in * pool.out) / (pool.out - assetOut) - pool.in) / feeMult(tfee),
Number::RoundingMode::Upward);
}
/**
+422 -64
View File
@@ -1,21 +1,31 @@
#pragma once
#include <xrpl/basics/Log.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Concepts.h>
#include <xrpl/protocol/Fees.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/Keylet.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/MPTAmount.h>
#include <xrpl/protocol/MPTIssue.h>
#include <xrpl/protocol/Rate.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/XRPAmount.h>
#include <cstdint>
#include <xrpl/protocol/UintTypes.h>
namespace xrpl {
@@ -40,22 +50,153 @@ escrowLockPreclaimHelper(
beast::Journal j);
template <>
TER
inline TER
escrowLockPreclaimHelper<Issue>(
ReadView const& view,
AccountID const& account,
AccountID const& dest,
STAmount const& amount,
beast::Journal j);
beast::Journal j)
{
auto const& issue = amount.get<Issue>();
auto const& issuer = amount.getIssuer();
// If the issuer is the same as the account, return tecNO_PERMISSION
if (issuer == account)
return tecNO_PERMISSION;
// If the lsfAllowTrustLineLocking is not enabled, return tecNO_PERMISSION
auto const sleIssuer = view.read(keylet::account(issuer));
if (!sleIssuer)
return tecNO_ISSUER;
if (!sleIssuer->isFlag(lsfAllowTrustLineLocking))
return tecNO_PERMISSION;
// If the account does not have a trustline to the issuer, return tecNO_LINE
auto const sleRippleState = view.read(keylet::trustLine(account, issuer, issue.currency));
if (!sleRippleState)
return tecNO_LINE;
STAmount const balance = (*sleRippleState)[sfBalance];
// If balance is positive, issuer must have higher address than account
if (balance > beast::kZero && issuer < account)
return tecNO_PERMISSION; // LCOV_EXCL_LINE
// If balance is negative, issuer must have lower address than account
if (balance < beast::kZero && issuer > account)
return tecNO_PERMISSION; // LCOV_EXCL_LINE
// If the issuer has requireAuth set, check if the account is authorized
if (auto const ter = requireAuth(view, issue, account); !isTesSuccess(ter))
return ter;
// If the issuer has requireAuth set, check if the destination is authorized
if (auto const ter = requireAuth(view, issue, dest); !isTesSuccess(ter))
return ter;
// If the issuer has frozen the account, return tecFROZEN
if (isFrozen(view, account, issue))
return tecFROZEN;
// If the issuer has frozen the destination, return tecFROZEN
if (isFrozen(view, dest, issue))
return tecFROZEN;
STAmount const spendableAmount =
accountHolds(view, account, issue.currency, issuer, FreezeHandling::IgnoreFreeze, j);
// If the balance is less than or equal to 0, return tecINSUFFICIENT_FUNDS
if (spendableAmount <= beast::kZero)
return tecINSUFFICIENT_FUNDS;
// If the spendable amount is less than the amount, return
// tecINSUFFICIENT_FUNDS
if (spendableAmount < amount)
return tecINSUFFICIENT_FUNDS;
// If the amount is not addable to the balance, return tecPRECISION_LOSS
if (!canAdd(spendableAmount, amount))
return tecPRECISION_LOSS;
return tesSUCCESS;
}
template <>
TER
inline TER
escrowLockPreclaimHelper<MPTIssue>(
ReadView const& view,
AccountID const& account,
AccountID const& dest,
STAmount const& amount,
beast::Journal j);
beast::Journal j)
{
AccountID const issuer = amount.getIssuer();
// If the issuer is the same as the account, return tecNO_PERMISSION
if (issuer == account)
return tecNO_PERMISSION;
// If the mpt does not exist, return tecOBJECT_NOT_FOUND
auto const issuanceKey = keylet::mptokenIssuance(amount.get<MPTIssue>().getMptID());
auto const sleIssuance = view.read(issuanceKey);
if (!sleIssuance)
return tecOBJECT_NOT_FOUND;
// If the lsfMPTCanEscrow is not enabled, return tecNO_PERMISSION
if (!sleIssuance->isFlag(lsfMPTCanEscrow))
return tecNO_PERMISSION;
// If the issuer is not the same as the issuer of the mpt, return
// tecNO_PERMISSION
if (sleIssuance->getAccountID(sfIssuer) != issuer)
return tecNO_PERMISSION; // LCOV_EXCL_LINE
// If the account does not have the mpt, return tecOBJECT_NOT_FOUND
if (!view.exists(keylet::mptoken(issuanceKey.key, account)))
return tecOBJECT_NOT_FOUND;
// If the issuer has requireAuth set, check if the account is
// authorized
auto const& mptIssue = amount.get<MPTIssue>();
if (auto const ter = requireAuth(view, mptIssue, account, AuthType::WeakAuth);
!isTesSuccess(ter))
return ter;
// If the issuer has requireAuth set, check if the destination is
// authorized
if (auto const ter = requireAuth(view, mptIssue, dest, AuthType::WeakAuth); !isTesSuccess(ter))
return ter;
// If the issuer has frozen the account, return tecLOCKED
if (isFrozen(view, account, *sleIssuance))
return tecLOCKED;
// If the issuer has frozen the destination, return tecLOCKED
if (isFrozen(view, dest, *sleIssuance))
return tecLOCKED;
// If the mpt cannot be transferred, return tecNO_AUTH
if (auto const ter = canTransfer(view, mptIssue, account, dest); !isTesSuccess(ter))
return ter;
STAmount const spendableAmount = accountHolds(
view,
account,
amount.get<MPTIssue>(),
FreezeHandling::IgnoreFreeze,
AuthHandling::IgnoreAuth,
j);
// If the balance is less than or equal to 0, return tecINSUFFICIENT_FUNDS
if (spendableAmount <= beast::kZero)
return tecINSUFFICIENT_FUNDS;
// If the spendable amount is less than the amount, return
// tecINSUFFICIENT_FUNDS
if (spendableAmount < amount)
return tecINSUFFICIENT_FUNDS;
return tesSUCCESS;
}
template <ValidIssueType T>
TER
@@ -67,22 +208,43 @@ escrowLockApplyHelper(
beast::Journal journal);
template <>
TER
inline TER
escrowLockApplyHelper<Issue>(
ApplyView& view,
AccountID const& issuer,
AccountID const& sender,
STAmount const& amount,
beast::Journal journal);
beast::Journal journal)
{
// Defensive: Issuer cannot create an escrow
if (issuer == sender)
return tecINTERNAL; // LCOV_EXCL_LINE
auto const ter =
directSendNoFee(view, sender, issuer, amount, !amount.holds<MPTIssue>(), journal);
if (!isTesSuccess(ter))
return ter; // LCOV_EXCL_LINE
return tesSUCCESS;
}
template <>
TER
inline TER
escrowLockApplyHelper<MPTIssue>(
ApplyView& view,
AccountID const& issuer,
AccountID const& sender,
STAmount const& amount,
beast::Journal journal);
beast::Journal journal)
{
// Defensive: Issuer cannot create an escrow
if (issuer == sender)
return tecINTERNAL; // LCOV_EXCL_LINE
auto const ter = lockEscrowMPT(view, sender, amount, journal);
if (!isTesSuccess(ter))
return ter; // LCOV_EXCL_LINE
return tesSUCCESS;
}
template <ValidIssueType T>
TER
@@ -93,20 +255,62 @@ escrowUnlockPreclaimHelper(
bool checkFreeze = true);
template <>
TER
inline TER
escrowUnlockPreclaimHelper<Issue>(
ReadView const& view,
AccountID const& account,
STAmount const& amount,
bool checkFreeze);
bool checkFreeze)
{
AccountID const& issuer = amount.getIssuer();
// If the issuer is the same as the account, return tesSUCCESS
if (issuer == account)
return tesSUCCESS;
// If the issuer has requireAuth set, check if the destination is authorized
if (auto const ter = requireAuth(view, amount.get<Issue>(), account); !isTesSuccess(ter))
return ter;
// If the issuer has deep frozen the destination, return tecFROZEN
if (checkFreeze &&
isDeepFrozen(view, account, amount.get<Issue>().currency, amount.getIssuer()))
return tecFROZEN;
return tesSUCCESS;
}
template <>
TER
inline TER
escrowUnlockPreclaimHelper<MPTIssue>(
ReadView const& view,
AccountID const& account,
STAmount const& amount,
bool checkFreeze);
bool checkFreeze)
{
AccountID const& issuer = amount.getIssuer();
// If the issuer is the same as the account, return tesSUCCESS
if (issuer == account)
return tesSUCCESS;
// If the mpt does not exist, return tecOBJECT_NOT_FOUND
auto const issuanceKey = keylet::mptokenIssuance(amount.get<MPTIssue>().getMptID());
auto const sleIssuance = view.read(issuanceKey);
if (!sleIssuance)
return tecOBJECT_NOT_FOUND;
// If the issuer has requireAuth set, check if the account is
// authorized
auto const& mptIssue = amount.get<MPTIssue>();
if (auto const ter = requireAuth(view, mptIssue, account, AuthType::WeakAuth);
!isTesSuccess(ter))
return ter;
// If the issuer has frozen the account, return tecLOCKED
if (checkFreeze && isFrozen(view, account, *sleIssuance))
return tecLOCKED;
return tesSUCCESS;
}
//------------------------------------------------------------------------------
@@ -125,7 +329,7 @@ escrowUnlockApplyHelper(
beast::Journal journal);
template <>
TER
inline TER
escrowUnlockApplyHelper<Issue>(
ApplyViewContext ctx,
Rate lockedRate,
@@ -136,10 +340,138 @@ escrowUnlockApplyHelper<Issue>(
AccountID const& sender,
AccountID const& receiver,
bool createAsset,
beast::Journal journal);
beast::Journal journal)
{
bool const senderIssuer = issuer == sender;
bool const receiverIssuer = issuer == receiver;
if (senderIssuer)
return tecINTERNAL; // LCOV_EXCL_LINE
if (receiverIssuer)
return tesSUCCESS;
auto const& issue = amount.get<Issue>();
Keylet const trustLineKey = keylet::trustLine(receiver, issue);
bool const recvLow = issuer > receiver;
if (!ctx.view.exists(trustLineKey) && createAsset)
{
// Can the account cover the trust line's reserve?
auto const sponsorSle = getEffectiveTxReserveSponsor(ctx, sleDest);
if (!sponsorSle)
return sponsorSle.error(); // LCOV_EXCL_LINE
if (auto const ret = checkReserve(
ctx,
sleDest,
xrpBalance,
*sponsorSle,
{.ownerCountDelta = 1},
journal,
tecNO_LINE_INSUF_RESERVE);
!isTesSuccess(ret))
{
JLOG(journal.trace()) << "Trust line does not exist. "
"Insufficient reserve to create line.";
return ret;
}
Currency const currency = issue.currency;
STAmount initialBalance(issue);
initialBalance.get<Issue>().account = noAccount();
if (TER const ter = trustCreate(
ctx.view, // payment sandbox
recvLow, // is dest low?
issuer, // source
receiver, // destination
trustLineKey.key, // ledger index
sleDest, // Account to add to
false, // authorize account
!sleDest->isFlag(lsfDefaultRipple), //
false, // freeze trust line
false, // deep freeze trust line
initialBalance, // zero initial balance
Issue(currency, receiver), // limit of zero
0, // quality in
0, // quality out
*sponsorSle, // sponsor
journal); // journal
!isTesSuccess(ter))
{
return ter; // LCOV_EXCL_LINE
}
ctx.view.update(sleDest);
}
if (!ctx.view.exists(trustLineKey) && !receiverIssuer)
return tecNO_LINE;
auto const xferRate = transferRate(ctx.view, amount);
// update if issuer rate is less than locked rate
if (xferRate < lockedRate)
lockedRate = xferRate;
// Transfer Rate only applies when:
// 1. Issuer is not involved in the transfer (senderIssuer or
// receiverIssuer)
// 2. The locked rate is different from the parity rate
// NOTE: Transfer fee in escrow works a bit differently from a normal
// payment. In escrow, the fee is deducted from the locked/sending amount,
// whereas in a normal payment, the transfer fee is taken on top of the
// sending amount.
auto finalAmt = amount;
if ((!senderIssuer && !receiverIssuer) && lockedRate != kParityRate)
{
// compute transfer fee, if any
auto const xferFee =
amount.value() - divideRound(amount, lockedRate, amount.get<Issue>(), true);
// compute balance to transfer
finalAmt = amount.value() - xferFee;
}
// validate the line limit if the account submitting txn is not the receiver
// of the funds
if (!createAsset)
{
auto const sleRippleState = ctx.view.peek(trustLineKey);
if (!sleRippleState)
return tecINTERNAL; // LCOV_EXCL_LINE
// if the issuer is the high, then we use the low limit
// otherwise we use the high limit
STAmount const lineLimit =
sleRippleState->getFieldAmount(recvLow ? sfLowLimit : sfHighLimit);
STAmount lineBalance = sleRippleState->getFieldAmount(sfBalance);
// flip the sign of the line balance if the issuer is not high
if (!recvLow)
lineBalance.negate();
// add the final amount to the line balance
lineBalance += finalAmt;
// if the transfer would exceed the line limit return tecLIMIT_EXCEEDED
if (lineLimit < lineBalance)
return tecLIMIT_EXCEEDED;
}
// if destination is not the issuer then transfer funds
if (!receiverIssuer)
{
auto const ter = directSendNoFee(ctx.view, issuer, receiver, finalAmt, true, journal);
if (!isTesSuccess(ter))
return ter; // LCOV_EXCL_LINE
}
return tesSUCCESS;
}
template <>
TER
inline TER
escrowUnlockApplyHelper<MPTIssue>(
ApplyViewContext ctx,
Rate lockedRate,
@@ -150,56 +482,82 @@ escrowUnlockApplyHelper<MPTIssue>(
AccountID const& sender,
AccountID const& receiver,
bool createAsset,
beast::Journal journal);
/**
* Smart escrow kill switches, driven by the voted FeeSettings.
*
* Zeroing `bytecodeSizeLimit` stops new uploads while leaving existing escrows
* finishable without forcing holders to wait for `CancelAfter`.
* Zeroing `gasLimit` stops both.
*/
/** @{ */
inline bool
isBytecodeUploadDisabled(Fees const& fees)
beast::Journal journal)
{
return fees.bytecodeSizeLimit == 0 || fees.gasLimit == 0;
}
bool const senderIssuer = issuer == sender;
bool const receiverIssuer = issuer == receiver;
inline bool
isBytecodeExecutionDisabled(Fees const& fees)
{
return fees.gasLimit == 0;
}
/** @} */
/**
* Owner count an escrow costs.
*
* An escrow costs one increment per started 500 bytes of bytecode, and one
* increment if it carries no bytecode.
*/
template <class T>
int32_t
calculateAdditionalReserve(T const& finishFunction)
{
static auto constexpr kBytecodeReserveIncrement = 500;
if (!finishFunction)
return 1;
auto const size = finishFunction->size();
if (size == 0)
auto const mptID = amount.get<MPTIssue>().getMptID();
auto const issuanceKey = keylet::mptokenIssuance(mptID);
auto const mptKeylet = keylet::mptoken(issuanceKey.key, receiver);
if (!ctx.view.exists(mptKeylet) && createAsset && !receiverIssuer)
{
// LCOV_EXCL_START
// Empty Bytecode is refused at create, so no escrow carries it. Ceiling
// division would answer 0, which would subtract less than the create added.
UNREACHABLE("xrpl::calculateAdditionalReserve : empty Bytecode");
return 1;
// LCOV_EXCL_STOP
auto const sponsorSle = getEffectiveTxReserveSponsor(ctx, sleDest);
if (!sponsorSle)
return sponsorSle.error(); // LCOV_EXCL_LINE
if (auto const ret = checkReserve(
ctx, sleDest, xrpBalance, *sponsorSle, {.ownerCountDelta = 1}, journal);
!isTesSuccess(ret))
return ret;
if (auto const ter = createMPToken(ctx.view, mptID, receiver, *sponsorSle, 0);
!isTesSuccess(ter))
{
return ter; // LCOV_EXCL_LINE
}
// update owner count.
increaseOwnerCount(ctx.view, sleDest, *sponsorSle, 1, journal);
}
return static_cast<int32_t>((size + kBytecodeReserveIncrement - 1) / kBytecodeReserveIncrement);
if (!ctx.view.exists(mptKeylet) && !receiverIssuer)
return tecNO_PERMISSION;
auto const xferRate = transferRate(ctx.view, amount);
// update if issuer rate is less than locked rate
if (xferRate < lockedRate)
lockedRate = xferRate;
// Transfer Rate only applies when:
// 1. Issuer is not involved in the transfer (senderIssuer or
// receiverIssuer)
// 2. The locked rate is different from the parity rate
// NOTE: Transfer fee in escrow works a bit differently from a normal
// payment. In escrow, the fee is deducted from the locked/sending amount,
// whereas in a normal payment, the transfer fee is taken on top of the
// sending amount.
auto finalAmt = amount;
if ((!senderIssuer && !receiverIssuer) && lockedRate != kParityRate)
{
if (ctx.view.rules().enabled(fixCleanup3_4_0))
{
XRPL_ASSERT(
lockedRate >= kParityRate,
"xrpl::escrowUnlockApplyHelper<MPTIssue> : lockedRate is at least parity");
// MPTs are integral, so round the delivered amount down and
// charge any fractional transfer fee to the escrowed amount.
auto const delivered =
mulRatio(amount.mpt(), kParityRate.value, lockedRate.value, false);
finalAmt = STAmount(amount.asset(), delivered.value());
}
else
{
// compute transfer fee, if any
auto const xferFee =
amount.value() - divideRound(amount, lockedRate, amount.asset(), true);
// compute balance to transfer
finalAmt = amount.value() - xferFee;
}
}
return unlockEscrowMPT(
ctx.view,
sender,
receiver,
finalAmt,
ctx.view.rules().enabled(fixTokenEscrowV1) ? amount : finalAmt,
journal);
}
} // namespace xrpl
+38 -124
View File
@@ -7,10 +7,10 @@
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/VaultHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/LedgerFormats.h> // IWYU pragma: keep
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/Rules.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STAmount.h>
@@ -31,9 +31,9 @@ namespace xrpl {
/**
* Broker cover preclaim precision guard (fixCleanup3_2_0).
*
* Prevents a silent sub-ULP no-op where a deposit, withdrawal, or clawback
* amount is so small that it rounds to zero at sfCoverAvailable's scale.
* Without this guard, both the pseudo trust-line and sfCoverAvailable would
* Prevents a "silent sub-ULP no-op" where a deposit, withdrawal, or clawback
* amount is so small that it rounds to zero at `sfCoverAvailable`'s scale.
* Without this guard, both the pseudo trust-line and `sfCoverAvailable` would
* identically absorb the rounded zero, resulting in a successful transaction
* (tesSUCCESS) where no funds actually moved.
*
@@ -44,8 +44,8 @@ namespace xrpl {
* @param j Journal for logging.
* @param logPrefix Transactor name for log diagnostics.
*
* @return tecPRECISION_LOSS if the request rounds to zero at cover scale.
* tesSUCCESS if the amendment is disabled or the request is safely supra-ULP.
* @return `tecPRECISION_LOSS` if the request rounds to zero at cover scale.
* `tesSUCCESS` if the amendment is disabled or the request is safely supra-ULP.
*/
[[nodiscard]] TER
canApplyToBrokerCover(
@@ -56,82 +56,6 @@ canApplyToBrokerCover(
beast::Journal j,
std::string_view logPrefix);
namespace detail {
/**
* Return a LoanBroker's posterior live cover exponent after applying an
* unrounded delta.
*/
[[nodiscard]] int
getPosteriorBrokerCoverScale(SLE::ConstRef vault, SLE::ConstRef broker, Number const& delta);
/**
* Round a cover outflow delta (cover withdraw, cover clawback, the
* broker-side decrease of a default's cover credit) at the LoanBroker's
* posterior live exponent. Safe to round the delta directly for an outflow:
* amount on the posterior grid of CoverAvailable always leaves CoverAvailable
* minus amount representable, since the posterior grid is exactly the grid
* CoverAvailable itself will canonicalize to after the subtraction.
*/
[[nodiscard]] STAmount
roundToPosteriorBrokerCoverScale(
SLE::ConstRef vault,
SLE::ConstRef broker,
STAmount const& delta,
Number::RoundingMode roundingMode);
} // namespace detail
/**
* Round a cover debit (cover withdraw, cover clawback, the broker-side
* decrease of a default's cover credit): amount is the non-negative
* magnitude leaving CoverAvailable. Negates so the posterior is computed for
* CoverAvailable minus amount, then negates the result back to a
* non-negative magnitude, per roundToPosteriorBrokerCoverScale's outflow
* rationale above. Magnitude in, magnitude out -- unlike the vault-side
* outflow dispatcher (clampVaultOutflow), which takes and returns a negative
* delta; callers on each side already hold the value in that side's native
* form.
*/
[[nodiscard]] STAmount
debitToPosteriorBrokerCoverScale(
SLE::ConstRef vault,
SLE::ConstRef broker,
STAmount const& amount,
Number::RoundingMode roundingMode);
/**
* Round a cover inflow (cover deposit, LoanPay's fee redirect into cover)
* into CoverAvailable by flooring the sum rather than the delta:
* credit = floor16(CoverAvailable + raw) - CoverAvailable, never finer
* than the Vault's base exponent (-Scale). See
* creditToPosteriorAvailableScale's doc for why the sum, not the delta,
* must be floored; the same reasoning applies to CoverAvailable.
*/
[[nodiscard]] STAmount
creditToPosteriorBrokerCoverScale(
SLE::ConstRef vault,
SLE::ConstRef broker,
Number const& raw,
Number::RoundingMode roundingMode);
/**
* Check whether amount is an admissible optional cover inflow.
*
* Legacy and CashBasis Vaults always succeed. A LoanBroker attached to a
* FixedPrecision Vault must remain at the Vault's base scale after applying
* the rounded amount, and its posterior CoverAvailable must stay within the
* Open zone.
*
* LoanPay's fee redirect into cover is a mandatory inflow and does not call
* this check, so it can push CoverAvailable past the Open zone limit this
* check enforces. Once that happens, every subsequent LoanBrokerCoverDeposit
* returns tecLIMIT_EXCEEDED until the owner withdraws cover back under the
* limit; LoanBrokerCoverWithdraw and LoanBrokerCoverClawback are unaffected.
*/
[[nodiscard]] TER
checkOptionalBrokerCoverInflow(SLE::ConstRef vault, SLE::ConstRef broker, STAmount const& amount);
// Lending protocol has dependencies, so capture them here.
bool
checkLendingProtocolDependencies(Rules const& rules, STTx const& tx);
@@ -140,13 +64,13 @@ checkLendingProtocolDependencies(Rules const& rules, STTx const& tx);
* The accounts and asset that LoanManage::defaultLoan's fixCleanup3_4_0
* freeze/lock exemption applies to.
*
* defaultLoan moves funds from the LoanBroker pseudo-account to the Vault
* pseudo-account via accountSend. Since neither is the vault asset's
* `defaultLoan` moves funds from the LoanBroker pseudo-account to the Vault
* pseudo-account via `accountSend`. Since neither is the vault asset's
* issuer, this is a third-party transfer that transits through the issuer in
* two hops (broker -> issuer, issuer -> vault; see
* directSendNoLimitIOU/directSendNoLimitMPT), so the exemption must cover
* `directSendNoLimitIOU`/`directSendNoLimitMPT`), so the exemption must cover
* both the issuer/broker and issuer/vault pairs, not a direct broker/vault
* pair. asset scopes it further to the vault's own currency/MPT issuance,
* pair. `asset` scopes it further to the vault's own currency/MPT issuance,
* so an unrelated one the same accounts happen to hold is still protected.
*/
struct LoanDefaultFreezeExemptAccounts
@@ -164,10 +88,10 @@ struct LoanDefaultFreezeExemptAccounts
* @param view Ledger view used to resolve the Loan -> LoanBroker -> Vault
* chain.
* @param tx The transaction under invariant review.
* @return The exempt accounts and asset if tx is a ttLOAN_MANAGE
* transaction with the tfLoanDefault flag set, fixCleanup3_4_0 is
* @return The exempt accounts and asset if `tx` is a `ttLOAN_MANAGE`
* transaction with the `tfLoanDefault` flag set, `fixCleanup3_4_0` is
* enabled, and the loan/broker/vault objects it references can all be
* resolved; std::nullopt otherwise.
* resolved; `std::nullopt` otherwise.
*/
[[nodiscard]] std::optional<LoanDefaultFreezeExemptAccounts>
getLoanDefaultFreezeExemptAccounts(ReadView const& view, STTx const& tx);
@@ -330,38 +254,28 @@ adjustImpreciseNumber(
value = 0;
}
/**
* Apply a signed delta to a LoanBroker's DebtTotal: on FixedPrecision Vaults,
* add the exact delta (DebtTotal moves by exactly the same amount as the
* Vault's AssetsDeployed, never re-rounded); on Legacy/CashBasis Vaults,
* round through adjustImpreciseNumber at vaultScale. Shared by LoanSet
* (origination) and LoanPay (payment, where delta is negative).
*
* @param brokerSle The LoanBroker whose DebtTotal is adjusted.
* @param vaultSle The LoanBroker's Vault.
* @param delta The signed change to DebtTotal.
* @param vaultScale The Vault scale the caller captured before changing
* AssetsTotal. Legacy/CashBasis round DebtTotal at that scale, not at
* the scale after this transaction's AssetsTotal change. Unused on
* FixedPrecision Vaults.
*/
void
adjustBrokerDebtTotal(
SLE::Ref brokerSle,
SLE::ConstRef vaultSle,
Number const& delta,
int vaultScale);
inline int
getAssetsTotalScale(SLE::ConstRef vaultSle)
{
if (!vaultSle)
return Number::kMinExponent - 1; // LCOV_EXCL_LINE
return scale(vaultSle->at(sfAssetsTotal), vaultSle->at(sfAsset));
}
/**
* Minimum required broker cover, rounded up.
*
* DebtTotal is a broker-level aggregate, never rounded at an individual
* loan's scale. Legacy and CashBasis Vaults round at the live AssetsTotal
* exponent. FixedPrecision Vaults round at the Vault's base exponent
* (-Scale, or 0 for integral assets).
*/
Number
minimumBrokerCover(Number const& debtTotal, TenthBips32 coverRateMinimum, SLE::ConstRef vaultSle);
// Compute the minimum required broker cover, rounded consistently.
// DebtTotal is a broker-level aggregate maintained at vault scale, so the
// rounding must also use vault scale — never an individual loan's scale.
inline Number
minimumBrokerCover(Number const& debtTotal, TenthBips32 coverRateMinimum, SLE::ConstRef vaultSle)
{
XRPL_ASSERT(
vaultSle && vaultSle->getType() == ltVAULT, "xrpl::minimumBrokerCover : valid Vault sle");
NumberRoundModeGuard const mg(Number::RoundingMode::Upward);
return roundToAsset(
vaultSle->at(sfAsset),
tenthBipsOfValue(debtTotal, coverRateMinimum),
getAssetsTotalScale(vaultSle));
}
TER
checkLoanGuards(
@@ -465,10 +379,10 @@ loanPaymentDeltas(LoanPaymentParts const& parts);
} // namespace cash_basis
// Public dispatchers: pick cash_basis:: if the Vault's LEVersion
// (VaultHelpers::getVaultVersion) is CashBasis or later (CashBasis or
// FixedPrecision), else instant_recognition::. These are the only entry
// points transactors call.
// Public dispatchers: pick cash_basis:: if featureLendingProtocolV1_1 is
// enabled AND the Vault's LEVersion (VaultHelpers::getVaultVersion) is
// VaultVersion::CashBasis, else instant_recognition::. These are the only entry points
// transactors call.
AccountingDeltas
loanOriginationDeltas(
SLE::ConstRef vaultSle,
+4 -25
View File
@@ -312,22 +312,12 @@ checkCreateMPT(
//
//------------------------------------------------------------------------------
/**
* MaximumAmount doesn't exceed 2**63-1
*/
// MaximumAmount doesn't exceed 2**63-1
std::int64_t
maxMPTAmount(SLE const& sleIssuance);
/**
* std::nullopt if the issuance doesn't exist.
*/
std::optional<std::int64_t>
maxMPTAmount(ReadView const& view, MPTID const& mptID);
/**
* OutstandingAmount may overflow and available amount might be negative.
* But available amount is always <= |MaximumAmount - OutstandingAmount|.
*/
// OutstandingAmount may overflow and available amount might be negative.
// But available amount is always <= |MaximumAmount - OutstandingAmount|.
std::int64_t
availableMPTAmount(SLE const& sleIssuance);
@@ -341,12 +331,7 @@ availableMPTAmount(ReadView const& view, MPTID const& mptID);
* directSendNoFee transactions that bypass the payment engine.
* 2. **accountSend & Payment Engine (Overflow: Yes):** A temporary overflow
* check when `OutstandingAmount > UINT64_MAX`. This higher threshold is used
* for `accountSend` and payments processed via the payment engine, whose
* reverse pass may issue before it redeems.
*
* In both cases a single `sendAmount > MaximumAmount` is rejected. Such a send
* can never settle: redemptions within the transaction are bounded by the
* outstanding amount, so the final OutstandingAmount would exceed the cap.
* for `accountSend` and payments processed via the payment engine.
*/
bool
isMPTOverflow(
@@ -368,12 +353,6 @@ issuerFundsToSelfIssue(ReadView const& view, MPTIssue const& issue);
/**
* Facilitate tracking of MPT sold by an issuer owning MPT sell offer.
* See ApplyView::issuerSelfDebitHookMPT().
*
* `amount` may be 0. BookStep removes an offer whose fee-adjusted funded
* output is 0, but other clips (for example MaximumAmount via limitStepIn)
* can still consume a zero slice of a funded remainder. The hook must
* accept 0: first call still snapshots origBalance; a zero debit adds
* nothing to selfDebit.
*/
void
issuerSelfDebitHookMPT(ApplyView& view, MPTIssue const& issue, std::uint64_t amount);
@@ -3,98 +3,27 @@
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Concepts.h>
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/MPTIssue.h>
#include <xrpl/protocol/Rules.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/XRPAmount.h>
#include <cstdint>
#include <optional>
namespace xrpl {
/**
* Check that a payment channel transaction may carry a token amount.
*
* Token channels rely on fixCleanup3_2_0 and fixTokenEscrowV1, so both must be
* enabled alongside featureTokenPaychan.
*
* @param rules The current ledger rules used to check amendment status.
* @return temBAD_AMOUNT if featureTokenPaychan is not enabled; temDISABLED if
* fixCleanup3_2_0 or fixTokenEscrowV1 is not enabled; tesSUCCESS otherwise.
*/
NotTEC
checkTokenPayChanRules(Rules const& rules);
/**
* Validate the token amount of a PaymentChannelCreate or PaymentChannelFund
* transaction during preflight.
*
* @param amount The channel or funding amount from the transaction.
* @return tesSUCCESS if the amount is valid; temBAD_AMOUNT or temBAD_CURRENCY
* otherwise.
*/
template <ValidIssueType T>
NotTEC
payChanAmountPreflightHelper(STAmount const& amount);
template <>
NotTEC
payChanAmountPreflightHelper<Issue>(STAmount const& amount);
template <>
NotTEC
payChanAmountPreflightHelper<MPTIssue>(STAmount const& amount);
/**
* Check that locking @p amount debits the source's trust line by exactly
* @p amount.
*
* @param view The ledger view holding the source's trust line.
* @param account The channel source whose balance is debited.
* @param amount The amount to lock in the channel.
* @param j Journal passed to the balance lookup.
* @return tecPRECISION_LOSS if @p amount is an IOU and the source's balance
* less @p amount is not exactly representable; tesSUCCESS otherwise,
* including for XRP and MPT amounts.
*/
TER
payChanLockPrecisionHelper(
ReadView const& view,
AccountID const& account,
STAmount const& amount,
beast::Journal j);
/**
* Close a payment channel and return its remaining funds to the channel owner.
*
* @param slep The SLE for the PayChannel object to close.
* @param ctx The apply view context (view and transaction) in which ledger
* state modifications are made.
* @param key The ledger key identifying the PayChannel entry.
* @param txAccount The account submitting the transaction that closes the
* channel.
* @param submitterPreFeeBalance The submitter's XRP balance before the
* transaction fee; the source's balance when the source closes its own
* channel and its trust line must be re-created for the refund.
* @param j Journal used for fatal-level diagnostic messages.
* @return tesSUCCESS on success; tefBAD_LEDGER if a directory removal
* fails; tefINTERNAL if the source account SLE cannot be found.
* @param slep The SLE for the PayChannel object to close.
* @param view The apply view in which ledger state modifications are made.
* @param key The ledger key identifying the PayChannel entry.
* @param j Journal used for fatal-level diagnostic messages.
* @return tesSUCCESS on success; tefBAD_LEDGER if a directory removal
* fails; tefINTERNAL if the source account SLE cannot be found.
*/
TER
closeChannel(
SLE::Ref slep,
ApplyViewContext ctx,
UInt256 const& key,
AccountID const& txAccount,
XRPAmount submitterPreFeeBalance,
beast::Journal j);
closeChannel(SLE::Ref slep, ApplyView& view, UInt256 const& key, beast::Journal j);
/**
* Add two uint32_t values with saturation at UINT32_MAX.
+21 -108
View File
@@ -1,17 +1,10 @@
#pragma once
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STObject.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/TxFormats.h>
#include <cstdint>
#include <optional>
namespace xrpl::proposal {
@@ -38,21 +31,30 @@ proposalOwnerCount(STObject const& proposedTx)
}
/**
* Whether the proposed transaction is independently submittable through the
* ordinary multi-sign path: not a proposal transaction itself, not a
* pseudo-transaction, not itself flagged as someone else's inner batch
* transaction, and — if it is a Batch — every one of its own inner
* transactions carries a TransactionType and is neither a proposal, a
* pseudo-transaction, nor a nested Batch. A Batch inner transaction cannot
* lack a TransactionType, be pseudo, or be a nested Batch (STTx construction
* rejects a typeless inner and an inner Batch, and preflight0 rejects the
* pseudo/tfInnerBatchTxn combination), but those guards live outside this
* feature, so they are checked again here rather than relied upon.
* Whether the proposed transaction is itself a proposal transaction, which
* would nest one proposal inside another.
*
* TODO: cover ttTRANSACTION_PROPOSAL_SIGN once that transaction exists.
* TODO: cover ttTRANSACTION_PROPOSAL_SIGN and ttTRANSACTION_PROPOSAL_CANCEL
* once those transactions exist.
*/
inline bool
isProposalTx(STObject const& proposedTx)
{
return proposedTx.getFieldU16(sfTransactionType) == ttTRANSACTION_PROPOSAL_CREATE;
}
/**
* Whether the proposed transaction is independently submittable through the
* ordinary multi-sign path: not a nested proposal, not a pseudo-transaction,
* not itself flagged as someone else's inner batch transaction, and — if it
* is a Batch — none of its own inner transactions is a nested proposal or a
* pseudo-transaction either. A Batch inner transaction cannot itself be
* pseudo (preflight0 rejects the pseudo/tfInnerBatchTxn combination
* generically), but that guard lives outside this feature, so it is checked
* again here rather than relied upon.
*/
bool
isValidProposalTxnType(STObject const& proposedTx);
isValidProposal(STObject const& proposedTx);
/**
* Whether the proposed transaction carries any signature field.
@@ -82,93 +84,4 @@ hasEmptySigningPubKey(STObject const& proposedTx)
proposedTx.getFieldVL(sfSigningPubKey).empty();
}
/**
* Whether the proposal is terminal (On-Chain Cosigner spec §4.5). A terminal proposal
* stops accepting signatures and anyone may delete it. An already signed
* payload can still execute after Expiration, because the Ticket reservation
* compares payloads only; a passed LastLedgerSequence does stop it.
*
* A proposal is terminal when either:
* - its Expiration has passed (the parent ledger closed at or after it), or
* - the proposed transaction carries a LastLedgerSequence and the current
* ledger sequence is strictly greater than it. This mirrors the ordinary
* transactor's tefMAX_LEDGER rule: at view.seq() == LastLedgerSequence the
* payload can still be submitted in the current ledger, so the proposal is
* still live.
*
* @param view The ledger the deciding transaction is being applied to.
* @param expiration The proposal's Expiration field.
* @param proposedTx The proposal's ProposedTransaction field.
*/
bool
isTerminal(
ReadView const& view,
std::optional<std::uint32_t> expiration,
STObject const& proposedTx);
/**
* Delete a TransactionProposal ledger entry.
*
* Removes the entry from its Owner's directory, releases the reserve the
* proposal holds against the Owner, and erases the entry. Shared by every
* deletion path the spec defines (On-Chain Cosigner spec §4.5): automatic cleanup when the
* proposed transaction's TicketSequence is consumed, and the
* TransactionProposalCancel / TransactionProposalSign cleanup paths once
* those transactions exist.
*
* The release goes through decreaseOwnerCountForObject, so it follows the
* sfSponsor an entry created with reserve sponsorship carries: the reserve is
* returned to whoever was holding it, the Owner or that sponsor.
*
* @param view The apply view for making changes
* @param sleProposal The TransactionProposal ledger entry to delete
* @param j Journal for logging
* @return tesSUCCESS, tecINTERNAL for an invalid entry, or tefBAD_LEDGER if
* the ledger contradicts the entry
*/
TER
deleteProposal(ApplyView& view, SLE::pointer const& sleProposal, beast::Journal j);
/**
* Whether tx carries the same payload as a proposal's stored
* ProposedTransaction (On-Chain Cosigner spec §4.2.1).
*
* The payload is the signing payload: the fields the collected signatures
* commit to. Compared is every field except those flagged kNotSigning
* (TxnSignature, Signers, BatchSigners, CounterpartySignature,
* SponsorSignature, Signature, MasterSignature) and SigningPubKey, which is
* stored empty and filled only if the target signs with its own key. So the
* completed transaction matches no matter which mix of collected or
* off-ledger signatures it carries, and any change to a non-signature field
* results in a false-return value.
*
* Signature (PaymentChannelClaim and the XChain attestation transactions)
* is deliberately not compared: it is outside the account's own TxnSignature
* even for an ordinary submission, so a proposal cannot bind it more tightly
* than the signatures it collects do, and those transactors still validate a
* substituted value exactly as they would for an ordinary submission.
* MasterSignature appears in no transaction format.
*
* @param proposedTx The proposal's ProposedTransaction field.
* @param tx The transaction to compare, typically an STTx.
*/
bool
payloadMatches(STObject const& proposedTx, STObject const& tx);
/**
* Whether tx may consume its Ticket.
*
* While a proposal keyed to the Ticket exists, the target's Ticket is
* reserved: only the proposal's own proposed transaction may spend it
* (On-Chain Cosigner spec §4.2.1), so that unrelated target-account activity cannot
* invalidate the proposal while signatures are being collected.
* TransactionProposalCancel is not allowed through: it deletes the
* reservation without consuming the ticket.
*
* @param view The ledger containing the Ticket and any reservation.
* @param tx The transaction attempting to consume that ticket.
*/
bool
canConsumeTicket(ReadView const& view, STTx const& tx);
} // namespace xrpl::proposal
@@ -316,20 +316,6 @@ transferRate(ReadView const& view, Asset const& asset);
[[nodiscard]] Rate
transferRate(ReadView const& view, STAmount const& amount);
/**
* Returns the amount a sender must part with for `amount` to reach the
* receiver once the issuer takes the transfer fee. Rounds up.
*/
[[nodiscard]] STAmount
addTransferFee(STAmount const& amount, Rate const& rate);
/**
* Returns the amount a receiver gets when `amount` leaves the sender and the
* issuer takes the transfer fee. Rounds down.
*/
[[nodiscard]] STAmount
subtractTransferFee(STAmount const& amount, Rate const& rate);
//------------------------------------------------------------------------------
//
// Holding operations (Asset-based dispatchers)
+29 -354
View File
@@ -1,12 +1,10 @@
#pragma once
#include <xrpl/basics/Number.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/Rules.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/TER.h>
@@ -14,239 +12,11 @@
#include <cstdint>
#include <expected>
#include <optional>
#include <string_view>
namespace xrpl {
class STTx;
/**
* @brief Return the Vault's current assets total.
*
* FixedPrecision Vaults return AssetsAvailable plus AssetsDeployed, with no
* rounding to the asset's 16-digit precision: the sum is exact at Number's
* active 19-digit mantissa width (the large mantissa range, enabled by
* featureSingleAssetVault or featureLendingProtocol), rounds Downward regardless of the
* caller's ambient mode beyond that, and is not rounded to match the cached
* sfAssetsTotal field. Legacy and CashBasis Vaults return the stored
* AssetsTotal.
*
* @param vault The vault SLE.
* @return The current assets total.
*/
[[nodiscard]] Number
getAssetsTotal(SLE::ConstRef vault);
/**
* @brief A change to a FixedPrecision Vault's balances.
*/
struct VaultBalanceChange
{
/**
* Signed change to AssetsAvailable. Must be the exact amount moved on the
* Vault pseudo-account in the same transaction. Absent means no cash
* moved; adjustVaultBalances then uses zero of the Vault's own asset.
*/
std::optional<STAmount> cash = std::nullopt;
/**
* Signed change to AssetsDeployed: open Loan principal, exact.
*/
Number deployed = 0;
/**
* Signed change to scheduled, unpaid interest (YieldUnrealized), exact.
*/
Number yield = 0;
/**
* Signed change to impaired principal (LossUnrealized), exact.
*/
Number loss = 0;
};
/**
* @brief Apply a balance change to a FixedPrecision Vault. The only writer of
* AssetsAvailable, AssetsDeployed, AssetsTotal, YieldUnrealized and
* LossUnrealized on these Vaults after creation.
*
* @param vault The vault SLE. Must be FixedPrecision.
* @param change The balance change to apply.
* @param j Journal for logging the fatal (tefBAD_LEDGER) case.
*
* @return tesSUCCESS; tecLIMIT_EXCEEDED if LossUnrealized would exceed
* AssetsDeployed; tefBAD_LEDGER if AssetsAvailable, AssetsDeployed,
* LossUnrealized or YieldUnrealized would become negative.
*/
[[nodiscard]] TER
adjustVaultBalances(SLE::Ref vault, VaultBalanceChange const& change, beast::Journal j);
/**
* @brief Apply a signed cash movement to a Vault of any version. FixedPrecision
* Vaults go through adjustVaultBalances; Legacy and CashBasis Vaults add the
* amount to both AssetsTotal and AssetsAvailable.
*
* @param vault The vault SLE.
* @param delta Signed change to AssetsAvailable; the exact amount moved on the
* Vault pseudo-account.
* @param j Journal passed through to adjustVaultBalances.
*
* @return tesSUCCESS, or the adjustVaultBalances error for FixedPrecision Vaults.
*/
[[nodiscard]] TER
adjustVaultCash(SLE::Ref vault, STAmount const& delta, beast::Journal j);
/**
* Return the Vault's current live exponent.
*
* Legacy and CashBasis Vaults use the exponent of AssetsTotal. FixedPrecision
* Vaults use max(base exponent, exponent of AssetsTotal): the grid is never
* finer than -Scale and coarsens once AssetsTotal outgrows 16 digits there.
*/
[[nodiscard]] int
getVaultScale(SLE::ConstRef vault);
/**
* Return the Vault's base exponent.
*
* Legacy and CashBasis Vaults use their current live exponent. FixedPrecision
* Vaults use -Scale, or 0 for integral assets.
*/
[[nodiscard]] int
getVaultBaseScale(SLE::ConstRef vault);
/**
* Round an amount at the Vault's posterior live exponent.
*/
[[nodiscard]] STAmount
roundToPosteriorVaultScale(
SLE::ConstRef vault,
STAmount const& amount,
Number::RoundingMode roundingMode);
namespace detail {
/**
* Return the Vault's posterior live exponent after applying an unrounded delta.
*/
[[nodiscard]] int
getPosteriorVaultScale(SLE::ConstRef vault, STAmount const& delta);
/**
* Shared grid-floor core of getVaultScale / getPosteriorVaultScale /
* getPosteriorBrokerCoverScale: baseScale when reference is zero, otherwise
* max(baseScale, scale(reference, asset)), rounded Downward regardless of
* the caller's ambient rounding mode.
*/
[[nodiscard]] int
liveScale(Number const& reference, Asset const& asset, int baseScale);
/**
* Shared core of getPosteriorVaultScale and getPosteriorBrokerCoverScale:
* the exponent reference + delta would have under version's live-scale rule
* (floored at baseScale for FixedPrecision, unbounded for Legacy/CashBasis).
*
* @param version The Vault's LEVersion.
* @param asset The Vault's underlying asset.
* @param baseScale The Vault's base exponent; unused outside the
* FixedPrecision case.
* @param reference The balance the delta is applied to (AssetsAvailable,
* AssetsTotal, or a LoanBroker's CoverAvailable).
* @param delta The unrounded change to reference.
*/
[[nodiscard]] int
posteriorAssetScale(
VaultVersion version,
Asset const& asset,
int baseScale,
Number const& reference,
Number const& delta);
/**
* Shared core of creditToPosteriorAvailableScale and
* creditToPosteriorBrokerCoverScale: floors reference + raw at the sum's
* own live scale (never finer than baseScale) and returns the difference
* from reference, so the delta applied is exactly what moves reference onto
* the floored sum. See creditToPosteriorAvailableScale's doc for why the
* sum, not raw alone, must be floored. raw is the exact amount; it is not
* rounded on its own before being added to reference. The sum and its scale
* are computed once, in roundingMode, so a Downward credit is always
* between zero and raw.
*/
[[nodiscard]] STAmount
creditToPosteriorScale(
Asset const& asset,
Number const& reference,
int baseScale,
Number const& raw,
Number::RoundingMode roundingMode);
} // namespace detail
/**
* Round a FixedPrecision cash inflow (LoanPay's credit, LoanManage's
* default cover credit) into AssetsAvailable, never AssetsTotal, which is
* a derived cache, by flooring the sum rather than the delta:
* credit equals floor16(AssetsAvailable + raw) minus AssetsAvailable, never
* finer than the Vault's base exponent (-Scale).
*
* Flooring only the delta at its own posterior grid is not enough: the sum
* can still need a 17th digit when AssetsAvailable is off the coarser grid
* right after crossing a power of ten. Example at Scale 6: AssetsAvailable
* equals 9999999999.999999 (16 digits already) and a credit of 0.000011
* floors to 0.00001 on its own (finer) grid, but 9999999999.999999 plus
* 0.00001 equals 10000000000.000009, which still needs 17 digits. Flooring
* the sum instead, floor16(9999999999.999999 + 0.000011) minus
* 9999999999.999999, is exact by construction, since STAmount's own
* 16-digit canonical form of the sum is what gets subtracted from.
*
* raw is taken as an exact Number: rounding it to 16 digits before adding
* it to AssetsAvailable could drop a tail that moves the floored sum.
*/
[[nodiscard]] STAmount
creditToPosteriorAvailableScale(
SLE::ConstRef vault,
Number const& raw,
Number::RoundingMode roundingMode);
/**
* Open-zone capacity ceiling: 9 * 10^(15 + baseScale).
*
* Defined only for FixedPrecision Vaults, where this is 9 * 10^(15 - P).
*/
[[nodiscard]] Number
getVaultOpenLimit(SLE::ConstRef vault);
/**
* Check whether amount is an admissible optional inflow.
*
* Legacy and CashBasis Vaults always succeed. FixedPrecision Vaults must
* remain at their base scale after applying the rounded amount, and the
* posterior capacity (AssetsTotal + YieldUnrealized + rounded amount) must
* stay within the Open zone.
*
* The amount is rounded toward zero at the posterior live exponent.
*/
[[nodiscard]] TER
checkOptionalVaultInflow(SLE::ConstRef vault, STAmount const& amount);
/**
* Open-zone capacity after adding roundedAmount: AssetsTotal + YieldUnrealized
* + roundedAmount, computed TowardsZero regardless of the caller's ambient
* rounding mode. Defined only for FixedPrecision Vaults.
*
* Shared by checkOptionalVaultInflow and LoanSet's loan-origination capacity
* check, which pair this formula with different scale checks (posterior vs.
* current), so only the capacity formula itself -- not the whole check -- is
* shared here.
*
* @param vault The vault SLE. Must be FixedPrecision.
* @param roundedAmount The amount to add, already rounded to the relevant
* posterior or base scale by the caller.
*/
[[nodiscard]] Number
vaultOpenZoneCapacity(SLE::ConstRef vault, Number const& roundedAmount);
/**
* From the perspective of a vault, return the number of shares to give
* depositor when they offer a fixed amount of assets. Note, since shares are
@@ -276,93 +46,31 @@ assetsToSharesDeposit(SLE::ConstRef vault, SLE::ConstRef issuance, STAmount cons
sharesToAssetsDeposit(SLE::ConstRef vault, SLE::ConstRef issuance, STAmount const& shares);
/**
* Adjusts a requested asset change (delta) to match the decimal scale of the
* updated total vault assets. This ensures sfAssetsTotal, sfAssetsAvailable,
* Adjusts a requested asset change (`delta`) to match the decimal scale of the
* updated total vault assets. This ensures `sfAssetsTotal`, `sfAssetsAvailable`,
* and the actual asset transfer change by the exact same representable amount.
*
* Rounding strategy:
* - Legacy/CashBasis debits (withdrawals): rounds down the magnitude of delta
* on the new scale to prevent paying out more than requested.
* - Legacy/CashBasis credits: floors the resulting total asset balance and
* returns the difference from the current total.
* - FixedPrecision credits only (VaultDeposit): rounds the delta toward zero
* at the scale getAssetsTotal (AssetsAvailable + AssetsDeployed, derived,
* not the stored AssetsTotal cache) would have after applying delta.
* FixedPrecision outflows (VaultWithdraw, VaultClawback) never call this;
* they use clampVaultOutflow instead.
* - Debits (withdrawals): Rounds down `|delta|` on the new scale to prevent
* paying out more than requested.
* - Credits (deposits): Floors the resulting total asset balance and returns the
* difference from the current total. This prevents crediting the vault with
* more assets than the user deposited.
*
* Key rules:
* - The returned magnitude never exceeds the magnitude of delta.
* - Returns tecPRECISION_LOSS if the change is smaller than 1 ULP of the target scale
* - The returned magnitude never exceeds `|delta|`.
* - Returns `tecPRECISION_LOSS` if the change is smaller than 1 ULP of the target scale
* (prevents share operations when totals cannot change).
* - For integer assets (XRP, MPT), rounding is a no-op.
*
* @param vault The vault ledger entry.
* @param delta The requested signed change to sfAssetsTotal.
* @return The rounded, positive magnitude, or tecPRECISION_LOSS if the
* @return The rounded, positive magnitude, or `tecPRECISION_LOSS` if the
* change is below representable precision.
*/
[[nodiscard]] std::expected<STAmount, TER>
clampToAssetsTotalScale(SLE::ConstRef vault, STAmount const& delta);
/**
* Outflow clamp dispatcher for VaultWithdraw and VaultClawback: rounds a
* negative delta to the scale appropriate for the Vault's version -- for
* FixedPrecision, at AssetsAvailable's own posterior scale (the balance the
* cash actually leaves); for Legacy/CashBasis, clampToAssetsTotalScale --
* so both call sites share one dispatch point. Same tecPRECISION_LOSS and
* integral-asset and magnitude-never-exceeds-delta rules as
* clampToAssetsTotalScale.
*
* @param vault The vault ledger entry.
* @param delta The requested signed outflow (negative).
* @return The rounded, positive magnitude, or tecPRECISION_LOSS if the
* change is below representable precision.
*/
[[nodiscard]] std::expected<STAmount, TER>
clampVaultOutflow(SLE::ConstRef vault, STAmount const& delta);
/**
* Returns the Vault's base exponent for asset at scale, before a Vault
* object exists to read it from: 0 for an integral asset, -scale otherwise.
* Used by VaultCreate::preclaim, which only has the proposed Scale field,
* not yet a Vault SLE; getVaultBaseScale is the post-creation equivalent.
*
* @param asset The (prospective) Vault's underlying asset.
* @param scale The (prospective) Vault's sfScale value, ignored for
* integral assets.
*/
[[nodiscard]] int
vaultBaseScale(Asset const& asset, std::uint8_t scale);
/**
* Returns true iff value is exactly representable both as an STAmount of
* asset and on the base grid at baseScale (no precision lost rounding
* TowardsZero at baseScale). Returns false, rather than throwing, for a value
* out of range for the asset. Shared representability check for
* VaultCreate::preclaim, VaultSet::preclaim and LoanBrokerSet::preclaim's
* AssetsMaximum / DebtMaximum / cover-rate field checks.
*
* @param asset The vault's underlying asset.
* @param value The value to check.
* @param baseScale The vault's base exponent (see vaultBaseScale /
* getVaultBaseScale).
*/
[[nodiscard]] bool
isOnVaultBaseGrid(Asset const& asset, Number const& value, int baseScale);
/**
* Checks that a requested AssetsMaximum is exactly representable on the
* Vault's base grid, otherwise associateAsset would silently round the cap
* the owner asked for. Used by VaultSet::preclaim on an existing Vault.
*
* @param vault The vault ledger entry.
* @param amount The requested AssetsMaximum.
* @return tesSUCCESS, or tecPRECISION_LOSS if amount is not representable.
*/
[[nodiscard]] TER
checkAssetsMaximum(SLE::ConstRef vault, Number const& amount);
/**
* Controls whether to truncate shares instead of rounding.
*/
@@ -390,24 +98,21 @@ enum class WaiveUnrealizedLoss : bool { No = false, Yes = true };
assetsTotalForWithdrawal(SLE::ConstRef vault, WaiveUnrealizedLoss waive);
/**
* Reject a cash outflow that is dust (see debitIsNonZeroDust) or whose check
* overflows Number, which is easy to hit with a large Scale.
* Returns true if debiting `amount` from `total` (the current value of a
* vault's sfAssetsTotal or sfAssetsAvailable) would canonicalize to the
* same STAmount value. This happens when `amount` is non-zero but too small
* to change the stored total at STAmount's precision. Shares would still
* move, so the ValidVault invariant would fail after apply; callers use
* this to reject the transaction upfront instead.
*
* @param vault The vault SLE.
* @param amount The amount to debit.
* @param j Journal for the debug log.
* @param name Transaction name used as the log prefix.
*
* @return tesSUCCESS; tecPRECISION_LOSS if amount is dust; tecPATH_DRY if the
* check overflows. Overflow means the transaction cannot apply, but
* ledger state is still consistent.
* @param asset The vault's underlying asset, used to canonicalize both
* sides the same way the ledger will when the field is stored.
* @param total The field's current value.
* @param amount The amount to debit. Zero always returns false; that case
* is rejected separately.
*/
[[nodiscard]] TER
checkDebitNotDust(
SLE::ConstRef vault,
Number const& amount,
beast::Journal j,
std::string_view name);
[[nodiscard]] bool
debitIsNonZeroDust(Asset const& asset, Number const& total, Number const& amount);
/**
* From the perspective of a vault, return the number of shares to demand from
@@ -453,7 +158,7 @@ sharesToAssetsWithdraw(
WaiveUnrealizedLoss waive = WaiveUnrealizedLoss::No);
/**
* Returns true iff account holds all of the vault's outstanding shares,
* Returns true iff `account` holds all of the vault's outstanding shares —
* i.e. is the sole remaining shareholder. Returns false if the account
* holds no shares or fewer than the total outstanding.
*
@@ -466,13 +171,11 @@ sharesToAssetsWithdraw(
isSoleShareholder(ReadView const& view, AccountID const& account, SLE::ConstRef issuance);
/**
* Resolves a Vault's LEVersion.
*
* LEVersion is the single point every accounting and rounding helper
* should call to decide which protocol a Vault follows. It is written
* at VaultCreate and is not updated afterwards, so a Vault created
* under an older amendment keeps that behaviour after later amendments
* activate. Absent sfLEVersion resolves to VaultVersion::Legacy.
* Resolves a Vault's LEVersion, the single point every accounting touch
* point should call to determine which recognition model (instant interest
* recognition vs. cash-basis) a Vault uses. Vaults created before featureLendingProtocolV1_1
* activated never have sfLEVersion set, which resolves here to
* VaultVersion::Legacy.
*
* @param vault The vault SLE.
*
@@ -482,34 +185,6 @@ isSoleShareholder(ReadView const& view, AccountID const& account, SLE::ConstRef
[[nodiscard]] VaultVersion
getVaultVersion(SLE::ConstRef vault);
/**
* Decodes an already-extracted sfLEVersion value with the same range check as
* getVaultVersion. Usable from contexts, such as an invariant's ledger-entry
* snapshot, that keep the field value but not the owning SLE.
*
* @param leVersion The value of sfLEVersion, or nullopt if absent.
*
* @return The decoded LEVersion, or VaultVersion::Legacy if absent.
*/
[[nodiscard]] VaultVersion
decodeVaultVersion(std::optional<std::uint8_t> leVersion);
/**
* Resolves which LEVersion a newly-created Vault should get under the
* currently active amendments. This is the only place that decides Vault
* version policy; VaultCreate calls it instead of checking amendments
* directly.
*
* @param rules The active ledger rules.
*
* @return VaultVersion::FixedPrecision once featureLendingProtocolV1_1,
* featureLendingProtocolV1_2 and fixCleanup3_4_0 are all enabled;
* VaultVersion::CashBasis
* once featureLendingProtocolV1_1 is enabled; VaultVersion::Legacy otherwise.
*/
[[nodiscard]] VaultVersion
getVaultDefaultVersion(Rules const& rules);
/**
* Resolves the VaultKind of a vault SLE. Returns VaultKind::ClosedEnded when
* sfVaultKind is present and equal to that value; anything else (including an
+5 -10
View File
@@ -348,19 +348,16 @@ isAuditorMirrorCurrent(SLE const& issuance, SLE const& mptoken);
areMirrorsCurrent(SLE const& issuance, SLE const& mptoken);
/**
* @brief Set the holder's issuer mirror epoch, and the issuer mirror key, to match the issuance's
* current key.
* @brief Set the holder's issuer mirror epoch to match the issuance's current issuer key epoch.
*
* Call this after writing the issuer mirror ciphertext under the issuance's
* currently registered issuer key, so that the mirror reads as current afterwards.
* Also records sfIssuerMirrorEncryptionKey as the issuance's current issuer key
* (both fields stay absent at epoch 0, where the original issuer key applies).
*
* @param issuance The MPTokenIssuance ledger object.
* @param mptoken The holder's MPToken ledger entry to update.
*/
void
setIssuerMirrorCurrent(SLE const& issuance, SLE& mptoken);
setIssuerMirrorEpoch(SLE const& issuance, SLE& mptoken);
/**
* @brief Set the holder's auditor mirror epoch to match the issuance's current auditor key epoch.
@@ -375,18 +372,16 @@ void
setAuditorMirrorEpoch(SLE const& issuance, SLE& mptoken);
/**
* @brief Set the holder's MPToken mirror epochs, and the issuer mirror key, to match the issuance's
* current keys.
* @brief Set the holder's MPToken mirror epochs to match the issuance's current key epochs.
*
* Call this after writing mirror ciphertexts under the issuance's currently
* registered keys, so that the mirrors read as current afterwards. Also records
* sfIssuerMirrorEncryptionKey via setIssuerMirrorCurrent.
* registered keys, so that the mirrors read as current afterwards.
*
* @param issuance The MPTokenIssuance ledger object.
* @param mptoken The holder's MPToken ledger entry to update.
*/
void
setMirrorsCurrent(SLE const& issuance, SLE& mptoken);
setMirrorEpochs(SLE const& issuance, SLE& mptoken);
/**
* @brief Verifies revealed amount encryptions for all recipients.
+1 -54
View File
@@ -10,25 +10,6 @@ namespace xrpl {
// This was the reference fee units used in the old fee calculation.
inline constexpr std::uint32_t kFeeUnitsDeprecated = 10;
// Number of micro-drops in one drop.
inline constexpr std::uint32_t microDropsPerDrop{1'000'000};
/**
* Hard protocol bounds on the Feature Extension fee settings. A voted value
* can never fall outside these, so `preflight`, which has no view, may bound
* against them. Gas and bytecode limits are capped; gas price has a floor
* (zero would make WASM execution effectively free).
*/
inline constexpr std::uint32_t kMaxGasLimit{2'000'000};
inline constexpr std::uint32_t kMaxBytecodeSizeLimit{200'000};
inline constexpr std::uint32_t kMinGasPrice{1};
// The following default values of fee settings will seed into FeeSettings
// and write to the ledger on featureSmartEscrow activation.
inline constexpr std::uint32_t kDefaultGasLimit{1'000'000};
inline constexpr std::uint32_t kDefaultBytecodeSizeLimit{100'000};
inline constexpr std::uint32_t kDefaultGasPrice{1'000'000};
/**
* Reflects the fee settings for a particular ledger.
*
@@ -52,47 +33,13 @@ struct Fees
*/
XRPAmount increment{0};
/**
* @brief Gas limit for Feature Extensions (instructions).
*
* This and the one below default to the protocol values rather than 0,
* because an explicit 0 is the voted kill switch.
*/
std::uint32_t gasLimit{kDefaultGasLimit};
/**
* @brief Bytecode size limit for Feature Extensions (bytes).
*/
std::uint32_t bytecodeSizeLimit{kDefaultBytecodeSizeLimit};
/**
* @brief Price of WASM gas (micro-drops).
*/
std::uint32_t gasPrice{kDefaultGasPrice};
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}
{
}
Fees(
XRPAmount base,
XRPAmount reserve,
XRPAmount increment,
std::uint32_t gasLimit,
std::uint32_t bytecodeSizeLimit,
std::uint32_t gasPrice)
: base{base}
, reserve{reserve}
, increment{increment}
, gasLimit{gasLimit}
, bytecodeSizeLimit{bytecodeSizeLimit}
, gasPrice{gasPrice}
: base(base), reserve(reserve), increment(increment)
{
}
-25
View File
@@ -2,17 +2,11 @@
#include <xrpl/basics/base_uint.h>
#include <xrpl/protocol/HashPrefix.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/Serializer.h>
#include <xrpl/protocol/XRPAmount.h>
namespace xrpl {
/**
* Serialize the PaymentChannelClaim authorization message:
* HashPrefix::PaymentChannelClaim, the channel key and the drops as a
* 64-bit integer.
*/
inline void
serializePayChanAuthorization(Serializer& msg, UInt256 const& key, XRPAmount const& amt)
{
@@ -21,23 +15,4 @@ serializePayChanAuthorization(Serializer& msg, UInt256 const& key, XRPAmount con
msg.add64(amt.drops());
}
/**
* Serialize the PaymentChannelClaim authorization message for any asset: a
* native amount uses the XRP layout; a token amount follows
* HashPrefix::PaymentChannelClaim and the channel key with STAmount::add, the
* Amount field value without its field header.
*/
inline void
serializePayChanAuthorization(Serializer& msg, UInt256 const& key, STAmount const& amt)
{
if (amt.native())
{
serializePayChanAuthorization(msg, key, amt.xrp());
return;
}
msg.add32(HashPrefix::PaymentChannelClaim);
msg.addBitString(key);
amt.add(msg);
}
} // namespace xrpl
+8 -35
View File
@@ -311,46 +311,24 @@ constexpr std::uint8_t kVaultStrategyFirstComeFirstServe = 1;
* Default IOU scale factor for a Vault
*/
constexpr std::uint8_t kVaultDefaultIouScale = 6;
/**
* Maximum Scale for a Vault created before featureLendingProtocolV1_2.
* Chosen so 1 IOU can always convert to shares:
* 10^19 > maxMPTokenAmount (2^64-1) > 10^18.
* Maximum scale factor for a Vault. The number is chosen to ensure that
* 1 IOU can be always converted to shares.
* 10^19 > maxMPTokenAmount (2^64-1) > 10^18
*/
constexpr std::uint8_t kLegacyVaultMaximumIouScale = 18;
constexpr std::uint8_t kVaultMaximumIouScale = 18;
/**
* Maximum Scale for a Vault created under featureLendingProtocolV1_2.
*/
constexpr std::uint8_t kFixedPrecisionVaultMaximumIouScale = 10;
/**
* Vault ledger-entry schema versions, persisted as sfLEVersion.
*
* LEVersion records which protocol a Vault was created under so later
* amendments can change the rules for new Vaults without rewriting
* existing ones. VaultCreate writes it from the then-active lending
* amendments; later transactions do not update it. A Vault created
* under an older amendment keeps that amendment's behaviour for its
* lifetime, even after a newer lending amendment activates.
*
* Absent sfLEVersion is implicit Legacy (version 0): instant interest
* recognition and a dynamic AssetsTotal scale. CashBasis (V1.1) uses
* cash-basis recognition on the same dynamic scale. FixedPrecision
* (V1.2) keeps cash-basis recognition and adds the lifetime base grid.
* Vault ledger-entry schema versions. Assigned to newly created
* Vaults once featureLendingProtocolV1_1 is enabled. Vaults created before
* activation are left without LEVersion (implicit legacy version 0,
* instant interest recognition).
*/
enum class VaultVersion : uint8_t {
Legacy = 0,
CashBasis,
FixedPrecision,
};
// Code compares VaultVersion values with < and >= (e.g. "CashBasis or later"),
// so each later version must stay numerically larger than the one before.
static_assert(
VaultVersion::Legacy < VaultVersion::CashBasis &&
VaultVersion::CashBasis < VaultVersion::FixedPrecision);
/**
* Vault kind. Distinguishes closed-ended vaults from the default open-ended
* kind. Persisted as sfVaultKind (UINT8); absent means OpenEnded.
@@ -398,11 +376,6 @@ constexpr std::uint32_t kMaxInvestmentPeriod = std::chrono::seconds{std::chrono:
*/
constexpr std::uint8_t kMaxAssetCheckDepth = 5;
/**
* Maximum length of a Data field in Escrow object that can be updated by WASM code.
*/
constexpr std::size_t kMaxWasmDataLength = 1 * 1024; // 1KB
/**
* A ledger index.
*/
-58
View File
@@ -696,16 +696,6 @@ divRound(STAmount const& v1, STAmount const& v2, Asset const& asset, bool roundU
STAmount
divRoundStrict(STAmount const& v1, STAmount const& v2, Asset const& asset, bool roundUp);
/**
* Returns amt * num / den, rounded in the requested direction.
*
* Dispatches to the XRPAmount, IOUAmount or MPTAmount overload. XRP and MPT
* results are exact; an IOU result is rounded to the 16-digit mantissa in the
* requested direction. Only Upward and Downward are supported.
*/
[[nodiscard]] STAmount
mulRatio(STAmount const& amt, std::uint32_t num, std::uint32_t den, Number::RoundingMode rounding);
// Someone is offering X for Y, what is the rate?
// Rate: smaller is better, the taker wants the most out: in/out
// VFALCO TODO Return a Quality object
@@ -776,21 +766,6 @@ roundToAsset(
return roundToScale(ret, scale);
}
/**
* Check whether an arbitrary precision Number can be converted to an STAmount
* of a given Asset without exceeding the asset's range.
*
* Use this before `roundToAsset` or `STAmount{asset, value}` on unchecked
* input, since those throw for out-of-range values, e.g. XRP above
* `STAmount::kMaxNativeN`.
*
* @param asset The relevant asset
* @param value The value to be checked
* @return true if the value is in range for the asset; false otherwise.
*/
[[nodiscard]] bool
isRepresentable(Asset const& asset, Number const& value);
//------------------------------------------------------------------------------
inline bool
@@ -805,39 +780,6 @@ canAdd(STAmount const& amt1, STAmount const& amt2);
bool
canSubtract(STAmount const& amt1, STAmount const& amt2);
/**
* Determine whether `amt1 + amt2` equals the mathematical sum.
*
* XRP and MPT sums are integer additions, so this is canAdd(). An IOU sum is
* rounded to the mantissa width and is exact only if subtracting each
* operand from it gives back the other.
*
* @param amt1 The first addend.
* @param amt2 The second addend.
* @return true if the sum is exact; false if it rounds, exceeds the largest
* amount the asset can represent, or the amounts are not comparable.
* Never throws.
*/
bool
isExactSum(STAmount const& amt1, STAmount const& amt2);
/**
* Determine whether `amt1 - amt2` equals the mathematical difference.
*
* XRP and MPT differences are integer subtractions, so this is canSubtract().
* An IOU difference is rounded to the mantissa width and is exact only if
* adding the subtrahend back gives the minuend and subtracting the difference
* from the minuend gives back the subtrahend.
*
* @param amt1 The minuend.
* @param amt2 The subtrahend.
* @return true if the difference is exact; false if it rounds, underflows,
* exceeds the largest amount the asset can represent, or the amounts are
* not comparable. Never throws.
*/
bool
isExactDifference(STAmount const& amt1, STAmount const& amt2);
/**
* Get the scale of a Number for a given asset.
*
+1 -4
View File
@@ -133,6 +133,7 @@ enum TEMcodes : TERUnderlyingType {
temBAD_MPT,
temBAD_CIPHERTEXT,
temINVALID_BYTECODE,
temTEMP_DISABLED,
};
//------------------------------------------------------------------------------
@@ -183,8 +184,6 @@ enum TEFcodes : TERUnderlyingType {
tefBAD_PATH_COUNT,
tefNO_BYTECODE,
tefBYTECODE_NOT_INCLUDED,
tefTEMP_DISABLED,
tefEXCEEDS_LIMIT,
};
//------------------------------------------------------------------------------
@@ -232,8 +231,6 @@ enum TERcodes : TERUnderlyingType {
terNO_DELEGATE_PERMISSION, // Delegate does not have permission
terLOCKED, // MPT is locked
terNO_PERMISSION, // No permission but retry
terTICKET_RESERVED, // Ticket is reserved and cannot be consumed
// until the reservation is released
};
//------------------------------------------------------------------------------
+20 -13
View File
@@ -1,6 +1,5 @@
#pragma once
#include <xrpl/basics/MathUtilities.h>
#include <xrpl/basics/Number.h>
#include <xrpl/basics/contract.h>
#include <xrpl/beast/utility/Zero.h>
@@ -70,13 +69,7 @@ public:
constexpr XRPAmount
operator*(value_type const& rhs) const
{
if (auto const result = checkedMul(drops_, rhs))
return XRPAmount{*result};
// Not reached in constant evaluation: an overflowing constant
// expression fails to compile instead.
XRPAmount product{*this};
product *= rhs;
return product;
return XRPAmount{drops_ * rhs};
}
friend constexpr XRPAmount
@@ -89,23 +82,37 @@ public:
XRPAmount&
operator+=(XRPAmount const& other)
{
return *this += other.drops();
drops_ += other.drops();
return *this;
}
XRPAmount&
operator-=(XRPAmount const& other)
{
return *this -= other.drops();
drops_ -= other.drops();
return *this;
}
XRPAmount&
operator+=(value_type const& rhs);
operator+=(value_type const& rhs)
{
drops_ += rhs;
return *this;
}
XRPAmount&
operator-=(value_type const& rhs);
operator-=(value_type const& rhs)
{
drops_ -= rhs;
return *this;
}
XRPAmount&
operator*=(value_type const& rhs);
operator*=(value_type const& rhs)
{
drops_ *= rhs;
return *this;
}
XRPAmount
operator-() const
+1 -3
View File
@@ -17,13 +17,11 @@
XRPL_FEATURE(Cosign, Supported::No, VoteBehavior::DefaultNo)
XRPL_FEATURE(SmartEscrow, Supported::No, VoteBehavior::DefaultNo)
// Requires LendingProtocolV1_1. New vaults take FixedPrecision plus cash-basis.
XRPL_FEATURE(LendingProtocolV1_2, Supported::No, VoteBehavior::DefaultNo)
XRPL_FIX (Cleanup3_5_0, Supported::Yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(ConfidentialMPTKeyRotation, Supported::No, VoteBehavior::DefaultNo)
XRPL_FIX (BatchV1_2, Supported::Yes, VoteBehavior::DefaultYes)
XRPL_FIX (Cleanup3_4_0, Supported::Yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(TokenPaychan, Supported::Yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(Sponsor, Supported::Yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(BatchV1_1, Supported::Yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(LendingProtocolV1_1, Supported::Yes, VoteBehavior::DefaultNo)
@@ -62,6 +60,7 @@ XRPL_FIX (InnerObjTemplate2, Supported::Yes, VoteBehavior::DefaultNo
XRPL_FIX (EnforceNFTokenTrustline, Supported::Yes, VoteBehavior::DefaultNo)
XRPL_FIX (ReducedOffersV2, Supported::Yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(NFTokenMintOffer, Supported::Yes, VoteBehavior::DefaultNo)
XRPL_FIX (AMMv1_1, Supported::Yes, VoteBehavior::DefaultNo)
XRPL_FIX (PreviousTxnID, Supported::Yes, VoteBehavior::DefaultNo)
XRPL_FIX (XChainRewardRounding, Supported::Yes, VoteBehavior::DefaultNo)
XRPL_FIX (EmptyDID, Supported::Yes, VoteBehavior::DefaultNo)
@@ -107,7 +106,6 @@ XRPL_RETIRE_FIX(1623)
XRPL_RETIRE_FIX(1781)
XRPL_RETIRE_FIX(AmendmentMajorityCalc)
XRPL_RETIRE_FIX(AMMOverflowOffer)
XRPL_RETIRE_FIX(AMMv1_1)
XRPL_RETIRE_FIX(CheckThreading)
XRPL_RETIRE_FIX(DisallowIncomingV1)
XRPL_RETIRE_FIX(InnerObjTemplate)
@@ -376,8 +376,6 @@ LEDGER_ENTRY(ltPAYCHAN, 0x0078, PayChannel, payment_channel, ({
{sfPreviousTxnID, SoeRequired},
{sfPreviousTxnLgrSeq, SoeRequired},
{sfDestinationNode, SoeOptional},
{sfTransferRate, SoeOptional},
{sfIssuerNode, SoeOptional},
}))
/** The ledger object which tracks the AMM.
@@ -419,7 +417,6 @@ LEDGER_ENTRY(ltMPTOKEN_ISSUANCE, 0x007e, MPTokenIssuance, mpt_issuance, ({
{sfAuditorEncryptionKey, SoeOptional},
{sfIssuerKeyEpoch, SoeOptional},
{sfAuditorKeyEpoch, SoeOptional},
{sfInitialIssuerEncryptionKey, SoeOptional},
{sfConfidentialOutstandingAmount, SoeDefault},
}))
@@ -441,7 +438,6 @@ LEDGER_ENTRY(ltMPTOKEN, 0x007f, MPToken, mptoken, ({
{sfAuditorEncryptedBalance, SoeOptional},
{sfIssuerKeyMirrorEpoch, SoeOptional},
{sfAuditorKeyMirrorEpoch, SoeOptional},
{sfIssuerMirrorEncryptionKey, SoeOptional},
{sfHolderEncryptionKey, SoeOptional},
{sfRecoveryKey, SoeOptional},
}))
@@ -518,8 +514,6 @@ LEDGER_ENTRY(ltVAULT, 0x0084, Vault, vault, ({
{sfAssetsAvailable, SoeDefault},
{sfAssetsMaximum, SoeDefault},
{sfLossUnrealized, SoeDefault},
{sfYieldUnrealized, SoeDefault},
{sfAssetsDeployed, SoeDefault},
{sfShareMPTID, SoeRequired},
{sfWithdrawalPolicy, SoeRequired},
{sfScale, SoeDefault},
@@ -240,8 +240,6 @@ TYPED_SFIELD(sfPrincipalRequested, NUMBER, 14)
TYPED_SFIELD(sfTotalValueOutstanding, NUMBER, 15, SField::kSmdNeedsAsset | SField::kSmdDefault)
TYPED_SFIELD(sfPeriodicPayment, NUMBER, 16)
TYPED_SFIELD(sfManagementFeeOutstanding, NUMBER, 17, SField::kSmdNeedsAsset | SField::kSmdDefault)
TYPED_SFIELD(sfYieldUnrealized, NUMBER, 18, SField::kSmdNeedsAsset | SField::kSmdDefault)
TYPED_SFIELD(sfAssetsDeployed, NUMBER, 19, SField::kSmdNeedsAsset | SField::kSmdDefault)
// 32-bit signed (common)
TYPED_SFIELD(sfLoanScale, INT32, 1)
@@ -333,8 +331,6 @@ TYPED_SFIELD(sfAmountCommitment, VL, 45)
TYPED_SFIELD(sfBalanceCommitment, VL, 46)
TYPED_SFIELD(sfBytecode, VL, 47)
TYPED_SFIELD(sfRecoveryKey, VL, 48)
TYPED_SFIELD(sfInitialIssuerEncryptionKey, VL, 49)
TYPED_SFIELD(sfIssuerMirrorEncryptionKey, VL, 50)
// account (common)
TYPED_SFIELD(sfAccount, ACCOUNT, 1)
@@ -182,7 +182,7 @@ TRANSACTION(ttSIGNER_LIST_SET, 12, SignerListSet,
#endif
TRANSACTION(ttPAYCHAN_CREATE, 13, PaymentChannelCreate, ({.delegable = Delegation::Delegable}), ({
{sfDestination, SoeRequired},
{sfAmount, SoeRequired, SoeMptSupported},
{sfAmount, SoeRequired},
{sfSettleDelay, SoeRequired},
{sfPublicKey, SoeRequired},
{sfCancelAfter, SoeOptional},
@@ -195,7 +195,7 @@ TRANSACTION(ttPAYCHAN_CREATE, 13, PaymentChannelCreate, ({.delegable = Delegatio
#endif
TRANSACTION(ttPAYCHAN_FUND, 14, PaymentChannelFund, ({.delegable = Delegation::Delegable}), ({
{sfChannel, SoeRequired},
{sfAmount, SoeRequired, SoeMptSupported},
{sfAmount, SoeRequired},
{sfExpiration, SoeOptional},
}))
@@ -205,8 +205,8 @@ TRANSACTION(ttPAYCHAN_FUND, 14, PaymentChannelFund, ({.delegable = Delegation::D
#endif
TRANSACTION(ttPAYCHAN_CLAIM, 15, PaymentChannelClaim, ({.delegable = Delegation::Delegable}), ({
{sfChannel, SoeRequired},
{sfAmount, SoeOptional, SoeMptSupported},
{sfBalance, SoeOptional, SoeMptSupported},
{sfAmount, SoeOptional},
{sfBalance, SoeOptional},
{sfSignature, SoeOptional},
{sfPublicKey, SoeOptional},
{sfCredentialIDs, SoeOptional},
@@ -1175,20 +1175,6 @@ TRANSACTION(ttTRANSACTION_PROPOSAL_CREATE, 95, TransactionProposalCreate,
{sfExpiration, SoeRequired},
}))
// ttTRANSACTION_PROPOSAL_SIGN arrives in a later PR and takes the next unassigned type.
/** This transaction deletes a TransactionProposal, releasing its owner's
reserve. While the proposal is live only its Owner or its target account
may cancel; once it is terminal anyone may (On-Chain Cosigner spec §7). */
#if TRANSACTION_INCLUDE
# include <xrpl/tx/transactors/proposal/TransactionProposalCancel.h>
#endif
TRANSACTION(ttTRANSACTION_PROPOSAL_CANCEL, 94, TransactionProposalCancel,
({.amendment = featureCosign}),
({
{sfProposalID, SoeRequired},
}))
/** This system-generated transaction type is used to update the status of the various amendments.
For details, see: https://xrpl.org/amendments.html
-3
View File
@@ -252,9 +252,6 @@ JSS(expected_date); // out: any (warnings)
JSS(expected_date_UTC); // out: any (warnings)
JSS(expected_ledger_size); // out: TxQ
JSS(expiration); // out: AccountOffers, AccountChannels, ValidatorList, amm_info
JSS(gas_limit); // out: NetworkOPs
JSS(bytecode_size_limit); // out: NetworkOPs
JSS(gas_price); // out: NetworkOPs
JSS(fail_hard); // in: Sign, Submit
JSS(failed); // out: InboundLedger
JSS(feature); // in: Feature
@@ -316,30 +316,6 @@ public:
return this->sle_->isFieldPresent(sfAuditorKeyMirrorEpoch);
}
/**
* @brief Get sfIssuerMirrorEncryptionKey (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_VL::type::value_type>
getIssuerMirrorEncryptionKey() const
{
if (hasIssuerMirrorEncryptionKey())
return this->sle_->at(sfIssuerMirrorEncryptionKey);
return std::nullopt;
}
/**
* @brief Check if sfIssuerMirrorEncryptionKey is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasIssuerMirrorEncryptionKey() const
{
return this->sle_->isFieldPresent(sfIssuerMirrorEncryptionKey);
}
/**
* @brief Get sfHolderEncryptionKey (SoeOptional)
* @return The field value, or std::nullopt if not present.
@@ -589,17 +565,6 @@ public:
return *this;
}
/**
* @brief Set sfIssuerMirrorEncryptionKey (SoeOptional)
* @return Reference to this builder for method chaining.
*/
MPTokenBuilder&
setIssuerMirrorEncryptionKey(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfIssuerMirrorEncryptionKey] = value;
return *this;
}
/**
* @brief Set sfHolderEncryptionKey (SoeOptional)
* @return Reference to this builder for method chaining.
@@ -399,30 +399,6 @@ public:
return this->sle_->isFieldPresent(sfAuditorKeyEpoch);
}
/**
* @brief Get sfInitialIssuerEncryptionKey (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_VL::type::value_type>
getInitialIssuerEncryptionKey() const
{
if (hasInitialIssuerEncryptionKey())
return this->sle_->at(sfInitialIssuerEncryptionKey);
return std::nullopt;
}
/**
* @brief Check if sfInitialIssuerEncryptionKey is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasInitialIssuerEncryptionKey() const
{
return this->sle_->isFieldPresent(sfInitialIssuerEncryptionKey);
}
/**
* @brief Get sfConfidentialOutstandingAmount (SoeDefault)
* @return The field value, or std::nullopt if not present.
@@ -694,17 +670,6 @@ public:
return *this;
}
/**
* @brief Set sfInitialIssuerEncryptionKey (SoeOptional)
* @return Reference to this builder for method chaining.
*/
MPTokenIssuanceBuilder&
setInitialIssuerEncryptionKey(std::decay_t<typename SF_VL::type::value_type> const& value)
{
object_[sfInitialIssuerEncryptionKey] = value;
return *this;
}
/**
* @brief Set sfConfidentialOutstandingAmount (SoeDefault)
* @return Reference to this builder for method chaining.
@@ -287,54 +287,6 @@ public:
{
return this->sle_->isFieldPresent(sfDestinationNode);
}
/**
* @brief Get sfTransferRate (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT32::type::value_type>
getTransferRate() const
{
if (hasTransferRate())
return this->sle_->at(sfTransferRate);
return std::nullopt;
}
/**
* @brief Check if sfTransferRate is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasTransferRate() const
{
return this->sle_->isFieldPresent(sfTransferRate);
}
/**
* @brief Get sfIssuerNode (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_UINT64::type::value_type>
getIssuerNode() const
{
if (hasIssuerNode())
return this->sle_->at(sfIssuerNode);
return std::nullopt;
}
/**
* @brief Check if sfIssuerNode is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasIssuerNode() const
{
return this->sle_->isFieldPresent(sfIssuerNode);
}
};
/**
@@ -556,28 +508,6 @@ public:
return *this;
}
/**
* @brief Set sfTransferRate (SoeOptional)
* @return Reference to this builder for method chaining.
*/
PayChannelBuilder&
setTransferRate(std::decay_t<typename SF_UINT32::type::value_type> const& value)
{
object_[sfTransferRate] = value;
return *this;
}
/**
* @brief Set sfIssuerNode (SoeOptional)
* @return Reference to this builder for method chaining.
*/
PayChannelBuilder&
setIssuerNode(std::decay_t<typename SF_UINT64::type::value_type> const& value)
{
object_[sfIssuerNode] = value;
return *this;
}
/**
* @brief Build and return the completed PayChannel wrapper.
* @param index The ledger entry index.
@@ -242,54 +242,6 @@ public:
return this->sle_->isFieldPresent(sfLossUnrealized);
}
/**
* @brief Get sfYieldUnrealized (SoeDefault)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_NUMBER::type::value_type>
getYieldUnrealized() const
{
if (hasYieldUnrealized())
return this->sle_->at(sfYieldUnrealized);
return std::nullopt;
}
/**
* @brief Check if sfYieldUnrealized is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasYieldUnrealized() const
{
return this->sle_->isFieldPresent(sfYieldUnrealized);
}
/**
* @brief Get sfAssetsDeployed (SoeDefault)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_NUMBER::type::value_type>
getAssetsDeployed() const
{
if (hasAssetsDeployed())
return this->sle_->at(sfAssetsDeployed);
return std::nullopt;
}
/**
* @brief Check if sfAssetsDeployed is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasAssetsDeployed() const
{
return this->sle_->isFieldPresent(sfAssetsDeployed);
}
/**
* @brief Get sfShareMPTID (SoeRequired)
* @return The field value.
@@ -619,28 +571,6 @@ public:
return *this;
}
/**
* @brief Set sfYieldUnrealized (SoeDefault)
* @return Reference to this builder for method chaining.
*/
VaultBuilder&
setYieldUnrealized(std::decay_t<typename SF_NUMBER::type::value_type> const& value)
{
object_[sfYieldUnrealized] = value;
return *this;
}
/**
* @brief Set sfAssetsDeployed (SoeDefault)
* @return Reference to this builder for method chaining.
*/
VaultBuilder&
setAssetsDeployed(std::decay_t<typename SF_NUMBER::type::value_type> const& value)
{
object_[sfAssetsDeployed] = value;
return *this;
}
/**
* @brief Set sfShareMPTID (SoeRequired)
* @return Reference to this builder for method chaining.
@@ -60,7 +60,6 @@ public:
/**
* @brief Get sfAmount (SoeOptional)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
@@ -87,7 +86,6 @@ public:
/**
* @brief Get sfBalance (SoeOptional)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
@@ -248,7 +246,6 @@ public:
/**
* @brief Set sfAmount (SoeOptional)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return Reference to this builder for method chaining.
*/
PaymentChannelClaimBuilder&
@@ -260,7 +257,6 @@ public:
/**
* @brief Set sfBalance (SoeOptional)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return Reference to this builder for method chaining.
*/
PaymentChannelClaimBuilder&
@@ -60,7 +60,6 @@ public:
/**
* @brief Get sfAmount (SoeRequired)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return The field value.
*/
[[nodiscard]]
@@ -208,7 +207,6 @@ public:
/**
* @brief Set sfAmount (SoeRequired)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return Reference to this builder for method chaining.
*/
PaymentChannelCreateBuilder&
@@ -60,7 +60,6 @@ public:
/**
* @brief Get sfAmount (SoeRequired)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return The field value.
*/
[[nodiscard]]
@@ -156,7 +155,6 @@ public:
/**
* @brief Set sfAmount (SoeRequired)
* @note This field supports MPT (Multi-Purpose Token) amounts.
* @return Reference to this builder for method chaining.
*/
PaymentChannelFundBuilder&
@@ -1,131 +0,0 @@
// This file is auto-generated. Do not edit.
#pragma once
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/STParsedJSON.h>
#include <xrpl/protocol/jss.h>
#include <xrpl/protocol_autogen/TransactionBase.h>
#include <xrpl/protocol_autogen/TransactionBuilderBase.h>
#include <xrpl/json/json_value.h>
#include <stdexcept>
#include <optional>
namespace xrpl::transactions {
class TransactionProposalCancelBuilder;
/**
* @brief Transaction: TransactionProposalCancel
*
* Type: ttTRANSACTION_PROPOSAL_CANCEL (94)
* Delegable: Delegation::NotDelegable
* Amendment: featureCosign
* Privileges: Privilege::NoPriv
*
* Immutable wrapper around STTx providing type-safe field access.
* Use TransactionProposalCancelBuilder to construct new transactions.
*/
class TransactionProposalCancel : public TransactionBase
{
public:
static constexpr xrpl::TxType txType = ttTRANSACTION_PROPOSAL_CANCEL;
/**
* @brief Construct a TransactionProposalCancel transaction wrapper from an existing STTx object.
* @throws std::runtime_error if the transaction type doesn't match.
*/
explicit TransactionProposalCancel(std::shared_ptr<STTx const> tx)
: TransactionBase(std::move(tx))
{
// Verify transaction type
if (tx_->getTxnType() != txType)
{
throw std::runtime_error("Invalid transaction type for TransactionProposalCancel");
}
}
// Transaction-specific field getters
/**
* @brief Get sfProposalID (SoeRequired)
* @return The field value.
*/
[[nodiscard]]
SF_UINT256::type::value_type
getProposalID() const
{
return this->tx_->at(sfProposalID);
}
};
/**
* @brief Builder for TransactionProposalCancel transactions.
*
* Provides a fluent interface for constructing transactions with method chaining.
* Uses STObject internally for flexible transaction construction.
* Inherits common field setters from TransactionBuilderBase.
*/
class TransactionProposalCancelBuilder : public TransactionBuilderBase<TransactionProposalCancelBuilder>
{
public:
/**
* @brief Construct a new TransactionProposalCancelBuilder with required fields.
* @param account The account initiating the transaction.
* @param proposalID The sfProposalID field value.
* @param sequence Optional sequence number for the transaction.
* @param fee Optional fee for the transaction.
*/
TransactionProposalCancelBuilder(SF_ACCOUNT::type::value_type account,
std::decay_t<typename SF_UINT256::type::value_type> const& proposalID, std::optional<SF_UINT32::type::value_type> sequence = std::nullopt,
std::optional<SF_AMOUNT::type::value_type> fee = std::nullopt
)
: TransactionBuilderBase<TransactionProposalCancelBuilder>(ttTRANSACTION_PROPOSAL_CANCEL, account, sequence, fee)
{
setProposalID(proposalID);
}
/**
* @brief Construct a TransactionProposalCancelBuilder from an existing STTx object.
* @param tx The existing transaction to copy from.
* @throws std::runtime_error if the transaction type doesn't match.
*/
TransactionProposalCancelBuilder(std::shared_ptr<STTx const> tx)
{
if (tx->getTxnType() != ttTRANSACTION_PROPOSAL_CANCEL)
{
throw std::runtime_error("Invalid transaction type for TransactionProposalCancelBuilder");
}
object_ = *tx;
}
/**
* @brief Transaction-specific field setters
*/
/**
* @brief Set sfProposalID (SoeRequired)
* @return Reference to this builder for method chaining.
*/
TransactionProposalCancelBuilder&
setProposalID(std::decay_t<typename SF_UINT256::type::value_type> const& value)
{
object_[sfProposalID] = value;
return *this;
}
/**
* @brief Build and return the TransactionProposalCancel wrapper.
* @param publicKey The public key for signing.
* @param secretKey The secret key for signing.
* @return The constructed transaction wrapper.
*/
TransactionProposalCancel
build(PublicKey const& publicKey, SecretKey const& secretKey)
{
sign(publicKey, secretKey);
return TransactionProposalCancel{std::make_shared<STTx>(std::move(object_))};
}
};
} // namespace xrpl::transactions
+28 -250
View File
@@ -2,7 +2,6 @@
#include <xrpl/basics/Blob.h>
#include <xrpl/basics/IntrusivePointer.h>
#include <xrpl/basics/Log.h>
#include <xrpl/basics/SHAMapHash.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
@@ -18,7 +17,6 @@
#include <xrpl/shamap/SHAMapNodeID.h>
#include <xrpl/shamap/SHAMapTreeNode.h>
#include <atomic>
#include <condition_variable>
#include <cstddef>
#include <cstdint>
@@ -42,7 +40,7 @@ class SHAMapSyncFilter;
/**
* Describes the current state of a given SHAMap
*/
enum class SHAMapState : std::uint8_t {
enum class SHAMapState {
/**
* The map is in flux and objects can be added and removed.
*
@@ -122,42 +120,16 @@ private:
*/
std::uint32_t cowid_ = 1;
// ledgerSeq_, state_ and full_ are touched on the nodestore fetch path and on a
// getMissingNodes() walk, neither of which may block, so pin them lock-free.
static_assert(std::atomic<std::uint32_t>::is_always_lock_free);
static_assert(std::atomic<SHAMapState>::is_always_lock_free);
static_assert(std::atomic<bool>::is_always_lock_free);
/**
* The sequence of the ledger that this map references, if any.
*
* Written when a map's ledger sequence is established (Ledger::setFull(),
* InboundLedger) while a nodestore reader thread reads it. Relaxed either
* way: it serves as a lookup hint for a nodestore keyed by hash.
*/
std::atomic<std::uint32_t> ledgerSeq_ = 0;
std::uint32_t ledgerSeq_ = 0;
SHAMapTreeNodePtr root_;
/**
* The map's state.
*
* A getMissingNodes() walk writes it, through setInvalid() and
* clearSynching(), while whatever drives the acquisition reads it.
* The walk runs with the acquisition's lock released, so this is atomic
* rather than guarded.
*/
mutable std::atomic<SHAMapState> state_;
mutable SHAMapState state_;
SHAMapType const type_;
bool backed_ = true; // Map is backed by the database
/**
* Map is believed complete in database.
*
* Atomic because finishFetch() clears it on any nodestore reader thread,
* several of which can run at once.
*/
mutable std::atomic<bool> full_ = false;
bool backed_ = true; // Map is backed by the database
mutable bool full_ = false; // Map is believed complete in database
public:
/**
@@ -180,14 +152,7 @@ public:
SHAMap&
operator=(SHAMap const&) = delete;
/**
* Take a snapshot of the given map.
*
* @param other The map to snapshot. An Invalid source yields an Invalid
* snapshot.
* @param isMutable Whether the snapshot may be modified. Ignored when other
* is Invalid.
*/
// Take a snapshot of the given map:
SHAMap(SHAMap const& other, bool isMutable);
// build new map
@@ -225,33 +190,17 @@ public:
//--------------------------------------------------------------------------
/**
* Return a new map that is a snapshot of this one.
*
* Handles copy on write for mutable snapshots. An invalid map yields an
* invalid snapshot.
*
* @param isMutable Whether the snapshot may be modified.
* @return The snapshot.
*/
// Returns a new map that's a snapshot of this one.
// Handles copy on write for mutable snapshots.
std::shared_ptr<SHAMap>
snapShot(bool isMutable) const;
/**
* Mark this map as "should be full": the local server wants every node
* it references in durable storage, so a nodestore miss is a gap to report.
*
* Publishes the ledger sequence before the flag. The sequence store is
* relaxed and the flag store is release, and finishFetch() reads the
* sequence only after acquiring the flag, so the one miss that withdraws
* the flag always reports the sequence stored here. Keeping both stores in
* one place is what makes that order impossible to get wrong.
*
* @param ledgerSeq Sequence of the ledger this map belongs to, which the
* gap report names.
*/
/* Mark this SHAMap as "should be full", indicating
that the local server wants all the corresponding nodes
in durable storage.
*/
void
setFull(std::uint32_t ledgerSeq);
setFull();
void
setLedgerSeq(std::uint32_t lseq);
@@ -347,13 +296,9 @@ public:
* concurrency, to discover nodes referenced in the
* SHAMap but not available locally.
*
* Only a leaf may occupy a position at or beyond kLeafDepth. A map that
* breaks that is marked Invalid and the traversal is abandoned, so callers
* ask isValid() to tell an empty result from a satisfied map.
*
* @param maxNodes The maximum number of found nodes to return
* @param filter The filter to use when retrieving nodes
* @return The nodes known to be missing, or empty if the map is Invalid
* @param return The nodes known to be missing
*/
std::vector<std::pair<SHAMapNodeID, UInt256>>
getMissingNodes(int maxNodes, SHAMapSyncFilter const* filter);
@@ -396,9 +341,6 @@ public:
* This function is used when receiving the root node of a SHAMap from a peer during ledger
* synchronization. The node must already have been deserialized.
*
* A root offered under a hash the map does not hold names another tree and
* is reported as invalid data. A root matching that hash is a duplicate.
*
* @param hash The expected hash of the root node.
* @param rootNode A deserialized root node to add.
* @param filter Optional sync filter to track received nodes.
@@ -418,10 +360,6 @@ public:
* is inserted at the position specified by nodeID. The node must already have been
* deserialized.
*
* A node that no valid tree can hold makes the map Invalid, which is
* terminal: the root hash committed to an impossible shape, so no peer
* can satisfy it.
*
* @param nodeID The position in the tree where this node belongs.
* @param treeNode A deserialized tree node to add.
* @param filter Optional sync filter to track received nodes.
@@ -437,57 +375,16 @@ public:
SHAMapTreeNodePtr treeNode,
SHAMapSyncFilter const* filter);
/**
* Mark this map as immutable, so it can no longer be modified.
*
* @return false if the map is Invalid and was left unchanged, true
* otherwise.
*/
[[nodiscard]] bool
// status functions
void
setImmutable();
/**
* @return Whether the map is being synced against a hash it was given, so
* its hash is fixed while nodes may still be added.
*/
[[nodiscard]] bool
bool
isSynching() const;
/**
* @return Whether the map is settled, with its hash and nodes fixed.
*/
[[nodiscard]] bool
isImmutable() const;
/**
* Mark this map as syncing, fixing its hash while still allowing missing
* nodes to be added.
*
* The map must be freshly constructed. The body asserts that the map is not
* Invalid, so a caller that ignores the precondition stops a build with
* assertions enabled.
*/
void
setSynching();
/**
* Mark this map as no longer syncing, so it can be modified again.
*
* Moves only a Synching map. An Immutable map stays settled, a Modifying
* map has nothing to clear, and Invalid is terminal.
*/
void
clearSynching();
/**
* Whether the map can still be the map it claims to be.
*
* A map that is merely missing nodes is valid, and stays valid until
* something proves the tree it is syncing against cannot exist.
*
* @return Whether the map has not been proven impossible.
*/
[[nodiscard]] bool
bool
isValid() const;
// caution: otherMap must be accessed only by this function
@@ -627,51 +524,6 @@ private:
using DeltaRef =
std::pair<boost::intrusive_ptr<SHAMapItem const>, boost::intrusive_ptr<SHAMapItem const>>;
/**
* The sequence of the ledger this map references, read atomically.
*
* @return The sequence, or zero if the map references no ledger.
*/
[[nodiscard]] std::uint32_t
ledgerSeq() const;
/**
* The current state, read atomically.
*
* Orders state_ alone. The tree's nodes carry no ordering guarantees.
*
* @return The state as of the call, which a concurrent walk may already
* have moved past.
*/
[[nodiscard]] SHAMapState
state() const;
/**
* Record that the map is provably not the one it claims to be.
*
* Private because only the map itself can prove that, from a node that
* contradicts the hashes it is syncing against. Cannot fail, since
* Invalid outranks every other state; see trySetState().
*/
void
setInvalid();
/**
* Move the map to a new state, atomically.
*
* With clearSynching(), the only writer of state_ past construction, so
* the order between the states lives in one place: Invalid outranks all
* of them and is always stored, while every other transition is refused
* once the map is Invalid, which is what makes that verdict terminal.
* clearSynching() is narrower and moves only Synching.
*
* @param desired The state to move to.
* @return false if the map is Invalid and the requested state is not,
* leaving it unchanged; true otherwise.
*/
bool
trySetState(SHAMapState desired);
// tree node cache operations
SHAMapTreeNodePtr
cacheLookup(SHAMapHash const& hash) const;
@@ -887,120 +739,46 @@ private:
};
inline void
SHAMap::setFull(std::uint32_t ledgerSeq)
SHAMap::setFull()
{
ledgerSeq_.store(ledgerSeq, std::memory_order_relaxed);
full_.store(true, std::memory_order_release);
full_ = true;
}
inline void
SHAMap::setLedgerSeq(std::uint32_t lseq)
{
ledgerSeq_.store(lseq, std::memory_order_relaxed);
ledgerSeq_ = lseq;
}
inline std::uint32_t
SHAMap::ledgerSeq() const
{
return ledgerSeq_.load(std::memory_order_relaxed);
}
inline SHAMapState
SHAMap::state() const
{
return state_.load(std::memory_order_acquire);
}
inline bool
SHAMap::trySetState(SHAMapState desired)
{
// Invalid is stored outright: a walk reaching that verdict has to win against a thread
// settling the map.
if (desired == SHAMapState::Invalid)
{
state_.store(SHAMapState::Invalid, std::memory_order_release);
return true;
}
// A failed exchange both reports the state and refreshes expected, so no load is needed
// ahead of the loop.
auto expected = SHAMapState::Modifying;
while (expected != SHAMapState::Invalid)
{
if (state_.compare_exchange_weak(
expected, desired, std::memory_order_acq_rel, std::memory_order_acquire))
{
return true;
}
}
return false;
}
inline bool
inline void
SHAMap::setImmutable()
{
SOMETIMES(!isValid(), "xrpl::SHAMap::setImmutable : map is invalid");
return trySetState(SHAMapState::Immutable);
XRPL_ASSERT(state_ != SHAMapState::Invalid, "xrpl::SHAMap::setImmutable : state is valid");
state_ = SHAMapState::Immutable;
}
inline bool
SHAMap::isSynching() const
{
return state() == SHAMapState::Synching;
}
inline bool
SHAMap::isImmutable() const
{
return state() == SHAMapState::Immutable;
return state_ == SHAMapState::Synching;
}
inline void
SHAMap::setSynching()
{
// Guarded, so this is not a way out of Invalid.
if (!trySetState(SHAMapState::Synching))
{
// Only ever called on a freshly constructed map.
// LCOV_EXCL_START
UNREACHABLE("xrpl::SHAMap::setSynching : map is invalid");
// LCOV_EXCL_STOP
}
state_ = SHAMapState::Synching;
}
inline void
SHAMap::clearSynching()
{
// Only Synching moves. A walk can end after the ledger settled, and then the map stays
// Immutable; a Modifying map has nothing to clear; an invalid map stays invalid. Only that
// last refusal is logged, since a concurrent walk is what produces it.
auto expected = SHAMapState::Synching;
if (state_.compare_exchange_strong(
expected, SHAMapState::Modifying, std::memory_order_acq_rel, std::memory_order_acquire))
{
return;
}
bool const invalid = expected == SHAMapState::Invalid;
SOMETIMES(invalid, "xrpl::SHAMap::clearSynching : map is invalid");
if (invalid)
{
JLOG(journal_.warn()) << "Refused to clear synching on an invalid map, root hash "
<< root_->getHash();
}
state_ = SHAMapState::Modifying;
}
inline bool
SHAMap::isValid() const
{
return state() != SHAMapState::Invalid;
}
inline void
SHAMap::setInvalid()
{
// Through trySetState() like every other transition, so state_ has one writer funnel.
trySetState(SHAMapState::Invalid);
return state_ != SHAMapState::Invalid;
}
inline void
+37 -126
View File
@@ -14,112 +14,35 @@ private:
public:
SHAMapAddNode();
/**
* Record one node that was rejected.
*/
void
incInvalid();
/**
* Record one node that produced a good result.
*/
void
incUseful();
/**
* Record one node that was not needed: the map already held it, or the map
* or its acquisition was no longer taking nodes. isGood() counts it on the
* accepted side.
*/
void
incDuplicate();
/**
* @return How many nodes produced a good result.
*/
[[nodiscard]] int
getGood() const;
/**
* @return How many nodes this code rejected, which is not how many the
* batch was given: a batch that stops at its first bad node counts
* one, and a batch that carries on counts each.
*/
[[nodiscard]] int
getBad() const;
/**
* @return How many nodes were not needed, whether already held or offered
* to a map no longer taking nodes, tallied apart from the good and
* bad counts.
*/
[[nodiscard]] int
getDuplicate() const;
/**
* Whether nodes that produced a good result or were not needed outnumber
* the ones rejected.
*
* @return Whether the batch was good.
*/
[[nodiscard]] bool
isGood() const;
/**
* @return Whether at least one node in the batch was rejected.
*/
[[nodiscard]] bool
isInvalid() const;
/**
* @return Whether at least one node in the batch produced a good result.
*/
[[nodiscard]] bool
isUseful() const;
/**
* @return A verdict recording one node that was not needed.
*/
static SHAMapAddNode
duplicate();
/**
* @return A verdict recording one useful node.
*/
static SHAMapAddNode
useful();
/**
* @return A verdict recording one invalid node.
*/
static SHAMapAddNode
invalid();
/**
* Clear every count back to zero.
*/
void
reset();
/**
* Render the tally as a log line.
*
* @return The tally, e.g. "good:2 bad:1 dupe:1", or "no nodes processed" if
* every count is zero.
*/
[[nodiscard]] int
getGood() const;
[[nodiscard]] bool
isGood() const;
[[nodiscard]] bool
isInvalid() const;
[[nodiscard]] bool
isUseful() const;
[[nodiscard]] std::string
get() const;
/**
* Add another verdict's counts into this one.
*
* @param n The verdict to add.
* @return This verdict, updated.
*/
SHAMapAddNode&
operator+=(SHAMapAddNode const& n);
static SHAMapAddNode
duplicate();
static SHAMapAddNode
useful();
static SHAMapAddNode
invalid();
private:
SHAMapAddNode(int good, int bad, int duplicate);
};
@@ -151,30 +74,18 @@ SHAMapAddNode::incDuplicate()
++duplicate_;
}
inline void
SHAMapAddNode::reset()
{
good_ = bad_ = duplicate_ = 0;
}
inline int
SHAMapAddNode::getGood() const
{
return good_;
}
inline int
SHAMapAddNode::getBad() const
{
return bad_;
}
inline int
SHAMapAddNode::getDuplicate() const
{
return duplicate_;
}
inline bool
SHAMapAddNode::isGood() const
{
return (good_ + duplicate_) > bad_;
}
inline bool
SHAMapAddNode::isInvalid() const
{
@@ -187,6 +98,22 @@ SHAMapAddNode::isUseful() const
return good_ > 0;
}
inline SHAMapAddNode&
SHAMapAddNode::operator+=(SHAMapAddNode const& n)
{
good_ += n.good_;
bad_ += n.bad_;
duplicate_ += n.duplicate_;
return *this;
}
inline bool
SHAMapAddNode::isGood() const
{
return (good_ + duplicate_) > bad_;
}
inline SHAMapAddNode
SHAMapAddNode::duplicate()
{
@@ -205,12 +132,6 @@ SHAMapAddNode::invalid()
return SHAMapAddNode(0, 1, 0);
}
inline void
SHAMapAddNode::reset()
{
good_ = bad_ = duplicate_ = 0;
}
inline std::string
SHAMapAddNode::get() const
{
@@ -239,14 +160,4 @@ SHAMapAddNode::get() const
return ret;
}
inline SHAMapAddNode&
SHAMapAddNode::operator+=(SHAMapAddNode const& n)
{
good_ += n.good_;
bad_ += n.bad_;
duplicate_ += n.duplicate_;
return *this;
}
} // namespace xrpl
+3 -15
View File
@@ -31,19 +31,7 @@ private:
*/
TaggedPointer hashesAndChildren_;
// Pin that wrapping fullBelowGen_ in an atomic keeps the packed layout, and that the load
// isFullBelow() does once per node of every walk stays lock-free.
static_assert(std::atomic<std::uint32_t>::is_always_lock_free);
static_assert(sizeof(std::atomic<std::uint32_t>) == sizeof(std::uint32_t));
static_assert(alignof(std::atomic<std::uint32_t>) == alignof(std::uint32_t));
/**
* Written from more than one thread, since canonicalization shares a node
* between maps and a walk can run with the acquisition lock released (see
* SHAMap::state_). Relaxed both ways, since a generation is only compared
* for equality and the children it vouches for are published through lock_.
*/
std::atomic<std::uint32_t> fullBelowGen_ = 0;
std::uint32_t fullBelowGen_ = 0;
std::uint16_t isBranch_ = 0;
/**
@@ -216,13 +204,13 @@ SHAMapInnerNode::getBranchCount() const
inline bool
SHAMapInnerNode::isFullBelow(std::uint32_t generation) const
{
return fullBelowGen_.load(std::memory_order_relaxed) == generation;
return fullBelowGen_ == generation;
}
inline void
SHAMapInnerNode::setFullBelowGen(std::uint32_t gen)
{
fullBelowGen_.store(gen, std::memory_order_relaxed);
fullBelowGen_ = gen;
}
} // namespace xrpl
-16
View File
@@ -375,22 +375,6 @@ public:
/////////////////////////////////////////////////////
// Interface used by AccountDelete
/**
* This method removes one Ticket ledger-object from the ledger. AccountDelete uses it to
* clean up Ticket objects en masse.
*
* This method reduces the TicketCount and the OwnerCount of the Owner AccountRoot object. If
* a TransactionProposal reserved this Ticket, that proposal is deleted as well: once the
* Ticket is gone, its proposed transaction can never be applied (tefNO_TICKET).
*
* @param view the apply view for making changes.
* @param account the account which owns the Ticket object.
* @param ticketIndex ticketIndex is used to construct the ledger-entry ID of Ticket object.
* @param j the journal reference used to log messages.
*
* @return the final TER after all invariant checks. `tefBAD_LEDGER` is returned if Ticket,
* Owner AccountRoot is not found in the apply-view ledger.
*/
static TER
ticketDelete(
ApplyView& view,
@@ -1,52 +0,0 @@
#pragma once
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/protocol/Keylet.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/XRPAmount.h>
#include <cstdint>
#include <set>
#include <vector>
namespace xrpl {
class ReadView;
/**
* @brief Invariants for TransactionProposal ledger entries.
*
* - Only transaction types implementing proposal lifecycle operations may
* create or modify proposals, and a failed one leaves no proposal effect.
* - A proposal is deleted only by TransactionProposalCancel or together with
* the Ticket it is keyed to, and a deleted Ticket takes the proposal keyed
* to it along.
* - Collected signer arrays remain sorted, unique, and bounded.
*/
class ValidTransactionProposal
{
// The Ticket each deleted proposal was keyed to; a proposal may go with
// its Ticket, or by Cancel, and no other way.
std::vector<uint256> ticketsOfDeletedProposals_;
// The proposal each deleted Ticket was keyed to, whether or not one
// existed; none may survive its Ticket.
std::vector<Keylet> proposalsOfDeletedTickets_;
std::set<uint256> deletedTickets_;
std::uint32_t created_ = 0;
std::uint32_t modified_ = 0;
std::uint32_t deleted_ = 0;
bool invalidSignerArrays_ = false;
public:
void
visitEntry(bool isDelete, SLE::ConstRef before, SLE::ConstRef after);
[[nodiscard]] bool
finalize(STTx const& tx, TER result, XRPAmount, ReadView const& view, beast::Journal const& j)
const;
};
} // namespace xrpl
@@ -1,15 +1,12 @@
#pragma once
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/invariants/AMMInvariant.h>
#include <xrpl/tx/invariants/CosignerInvariant.h>
#include <xrpl/tx/invariants/DirectoryInvariant.h>
#include <xrpl/tx/invariants/FreezeInvariant.h>
#include <xrpl/tx/invariants/LoanBrokerInvariant.h>
@@ -24,7 +21,6 @@
#include <boost/multiprecision/cpp_int.hpp>
#include <cstdint>
#include <map>
#include <set>
#include <string>
#include <tuple>
@@ -367,27 +363,11 @@ public:
* unique to pseudo-accounts. Check that all pseudo-accounts are following the
* rules, and that only pseudo-accounts look like pseudo-accounts.
*
* Post-fixCleanup3_5_0, a pseudo-account also owns only entries that belong
* to its own AMM, Vault or LoanBroker:
* - AMM: its AMM entry, its holdings of the two pool assets, and the trust
* lines of its LP token,
* - Vault: its share MPTokenIssuance, its holding of the vault asset, and the
* LoanBrokers operating on this vault,
* - LoanBroker: its Loans and its holding of the vault asset (the cover).
*
* A pseudo-account cannot sign transactions, so it could never accept or
* remove any other object linked into its owner directory. Such an object
* would pin the directory and block deletion of the owning object.
*/
class ValidPseudoAccounts
{
std::vector<std::string> errors_;
// Entries newly linked into owner directories, keyed by the directory
// owner. Whether an owner is a pseudo-account is decided in finalize,
// where the view is available.
std::map<AccountID, std::vector<uint256>> ownerDirAdditions_;
public:
void
visitEntry(bool, SLE::ConstRef, SLE::ConstRef);
@@ -484,7 +464,6 @@ using InvariantChecks = std::tuple<
ValidAmounts,
ValidMPTTransfer,
ObjectHasPseudoAccount,
ValidTransactionProposal,
SponsorshipOwnerCountsMatch,
SponsorshipAccountCountMatchesField>;
@@ -27,8 +27,6 @@ class ValidMPTIssuance
std::uint32_t mptokensCreated_ = 0;
std::uint32_t mptokensDeleted_ = 0;
// holder of the last MPToken created
std::optional<AccountID> mptokenCreatedHolder_;
// non-MPT transactions may attempt to create
// MPToken by an issuer
bool mptCreatedByIssuer_ = false;
+1 -46
View File
@@ -8,7 +8,6 @@
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/MPTIssue.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/Rules.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STTx.h>
@@ -30,7 +29,7 @@ namespace xrpl {
* - vault must have MPTokenIssuance for shares
* - vault without shares outstanding must have no shares
* - loss unrealized does not exceed the difference between assets total and
* assets available (Legacy/CashBasis), or AssetsDeployed (FixedPrecision)
* assets available
* - assets available do not exceed assets total
* - vault deposit increases assets and share issuance, and adds to:
* total assets, assets available, shares outstanding
@@ -47,14 +46,6 @@ namespace xrpl {
* - vault withdrawal may not succeed when the vault phase is Investment
* - closed-ended loan origination (ttLOAN_SET) may only succeed when the
* vault phase is Investment
* - FixedPrecision only: AssetsDeployed is non-negative and exactly
* representable at the asset's precision; AssetsDeployed changes only for
* ttLOAN_SET, ttLOAN_PAY and ttLOAN_MANAGE; AssetsDeployed is zero right
* after ttVAULT_CREATE and whenever shares outstanding return to zero; the
* stored AssetsTotal cache equals AssetsAvailable + AssetsDeployed, rounded
* Downward to the asset's precision
* - FixedPrecision only: YieldUnrealized is non-negative; YieldUnrealized
* changes only for ttLOAN_SET, ttLOAN_PAY and ttLOAN_MANAGE
*
* Immutability of VaultKind, SubscriptionDate and RedemptionDate is enforced
* by NoModifiedUnmodifiableFields (see InvariantCheck.cpp). From
@@ -77,9 +68,6 @@ class ValidVault
Number assetsAvailable = 0;
Number assetsMaximum = 0;
Number lossUnrealized = 0;
Number assetsDeployed = 0;
Number yieldUnrealized = 0;
VaultVersion version = VaultVersion::Legacy;
std::optional<std::uint8_t> vaultKind;
std::optional<std::uint32_t> subscriptionDate;
std::optional<std::uint32_t> redemptionDate;
@@ -229,39 +217,6 @@ private:
[[nodiscard]] bool
finalizeLoanSet(ReadView const& view, beast::Journal const& j) const;
/**
* @brief Check that a vault's AssetsTotal and AssetsAvailable deltas add
* up against the real transfer, for deposit/withdraw/clawback.
*
* Shared body of the ttVAULT_DEPOSIT, ttVAULT_WITHDRAW and
* ttVAULT_CLAWBACK "assets outstanding"/"assets available" add-up checks,
* which differ only in the rounded/exact deltas plugged in and the verb
* used in the log message.
*
* @param afterVault Snapshot of the vault after the transaction.
* @param beforeVault Snapshot of the vault before the transaction.
* @param vaultAsset The vault's underlying asset.
* @param fix340Enabled Whether fixCleanup3_4_0 is enabled.
* @param minScale The posterior scale computed by computeVaultMinScale.
* @param roundedVaultDelta The real transfer's delta, rounded to minScale.
* @param exactVaultDelta The real transfer's exact (unrounded) delta.
* @param verb The transaction name used in the log message ("deposit",
* "withdrawal" or "clawback").
* @param j Journal for logging.
* @return false if either add-up check fails.
*/
[[nodiscard]] static bool
checkTotalsAddUp(
Vault const& afterVault,
Vault const& beforeVault,
Asset const& vaultAsset,
bool fix340Enabled,
std::int32_t minScale,
Number const& roundedVaultDelta,
Number const& exactVaultDelta,
char const* verb,
beast::Journal const& j);
public:
// Compute the coarsest scale required to represent all numbers
[[nodiscard]] static std::int32_t
@@ -20,14 +20,11 @@ public:
{
}
static bool
checkExtraFeatures(PreflightContext const& ctx);
static TxConsequences
makeTxConsequences(PreflightContext const& ctx);
static XRPAmount
calculateBaseFee(ReadView const& view, STTx const& tx);
static bool
checkExtraFeatures(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
@@ -5,7 +5,6 @@
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/XRPAmount.h>
@@ -46,29 +45,9 @@ public:
SLE::Ref loanSle,
SLE::Ref brokerSle,
SLE::Ref vaultSle,
Asset const& vaultAsset,
beast::Journal j);
/**
* @brief Compute the First-Loss Capital cover amount for a defaulted Loan on a FixedPrecision
* Vault, capped at the LoanBroker's CoverAvailable and floored so that AssetsAvailable + cover
* is exactly on its posterior grid.
*
* The LoanBroker absorbs the rounding. When CoverAvailable - cover needs finer digits than the
* broker's posterior grid, it rounds to nearest, as the broker's trust line does, so
* CoverAvailable can move by up to half a posterior ulp more or less than the cover. When the
* two grids differ, no cover is exact on both sides; the Vault side is kept exact.
*
* The one exception: when AssetsAvailable has digits finer than the 16-digit cover can carry,
* no cover cancels them, and the balance writer rounds AssetsAvailable + cover to nearest,
* by at most half a posterior ulp. VaultDeposit and LoanPay credits have the same limit.
* @param loanSle The Loan being defaulted.
* @param brokerSle The Loan's LoanBroker.
* @param vaultSle The LoanBroker's Vault.
* @return The cover amount, in the Vault's Asset.
*/
static STAmount
calculateDefaultCover(SLE::Ref loanSle, SLE::Ref brokerSle, SLE::Ref vaultSle);
/**
* Helper function that might be needed by other transactors
*/
@@ -23,9 +23,6 @@ public:
static TxConsequences
makeTxConsequences(PreflightContext const& ctx);
static bool
checkExtraFeatures(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
@@ -23,15 +23,9 @@ public:
static TxConsequences
makeTxConsequences(PreflightContext const& ctx);
static bool
checkExtraFeatures(PreflightContext const& ctx);
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
@@ -1,44 +0,0 @@
#pragma once
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/ApplyContext.h>
#include <xrpl/tx/Transactor.h>
namespace xrpl {
class TransactionProposalCancel : public Transactor
{
public:
static constexpr auto kConsequencesFactory = ConsequencesFactoryType::Normal;
explicit TransactionProposalCancel(ApplyContext& ctx) : Transactor(ctx)
{
}
static NotTEC
preflight(PreflightContext const& ctx);
static TER
preclaim(PreclaimContext const& ctx);
TER
doApply() override;
void
visitInvariantEntry(bool isDelete, SLE::ConstRef before, SLE::ConstRef after) override;
[[nodiscard]] bool
finalizeInvariants(
STTx const& tx,
TER result,
XRPAmount fee,
ReadView const& view,
beast::Journal const& j) override;
};
} // namespace xrpl
@@ -52,13 +52,6 @@ private:
TER
applyFee();
/**
* Write the default Feature Extension fee settings into FeeSettings on
* `featureSmartEscrow` activation.
*/
void
initializeVMFees();
TER
applyUNLModify();
};
+14 -92
View File
@@ -200,23 +200,11 @@ Ledger::Ledger(
sle->at(sfReserveIncrement) = *f;
sle->at(sfReferenceFeeUnits) = kFeeUnitsDeprecated;
}
if (std::ranges::find(amendments, featureSmartEscrow) != amendments.end())
{
sle->at(sfGasLimit) = fees.gasLimit;
sle->at(sfBytecodeSizeLimit) = fees.bytecodeSizeLimit;
sle->at(sfGasPrice) = fees.gasPrice;
}
rawInsert(sle);
}
stateMap_.flushDirty(NodeObjectType::AccountNode);
// Built locally. See Ledger::setImmutable().
if (!setImmutable())
{
// LCOV_EXCL_START
logicError("Ledger::Ledger(CreateGenesisT, ...): genesis ledger map is invalid");
// LCOV_EXCL_STOP
}
setImmutable();
}
Ledger::Ledger(
@@ -227,7 +215,7 @@ Ledger::Ledger(
Fees const& fees,
Family& family,
beast::Journal j)
: immutable_(false)
: immutable_(true)
, txMap_(SHAMapType::TRANSACTION, info.txHash, family)
, stateMap_(SHAMapType::STATE, info.accountHash, family)
, fees_(fees)
@@ -250,20 +238,8 @@ Ledger::Ledger(
JLOG(j.warn()) << "Don't have state data root for ledger" << header_.seq;
}
// Loaded locally. See Ledger::setImmutable().
if (setMapsImmutable())
{
immutable_ = true;
}
else
{
// LCOV_EXCL_START
JLOG(j.error()) << "Invalid map for ledger " << header_.seq;
UNREACHABLE("xrpl::Ledger::Ledger(LedgerHeader const&, ...) : map is invalid");
// Treat it as a damaged ledger: the code below recomputes the hash and re-acquires.
loaded = false;
// LCOV_EXCL_STOP
}
txMap_.setImmutable();
stateMap_.setImmutable();
if (!setup())
loaded = false;
@@ -334,50 +310,27 @@ Ledger::Ledger(
setup();
}
bool
void
Ledger::setImmutable(bool rehash)
{
// A map found structurally invalid during sync must never be made immutable: isValid() tests
// only for Invalid, and an immutable ledger is treated as persistable. Asked before anything is
// written, so a refusal leaves the header exactly as it was rather than half relabeled.
if (!mapsValid())
return false;
// Read here but written to the header below, once the maps are settled: getHash() can unshare a
// dirty tree, so it has to run while the map is still mutable, while a write to the header must
// wait until both maps have made it. Skipped once the ledger is immutable, since its maps can
// no longer change.
bool const deriveMapHashes = !immutable_ && rehash;
UInt256 const txHash = deriveMapHashes ? txMap_.getHash().asUInt256() : UInt256{};
UInt256 const accountHash = deriveMapHashes ? stateMap_.getHash().asUInt256() : UInt256{};
// Both were valid at the check above, but a concurrent walk can invalidate one in between (see
// SHAMap::state_), so the result is checked. setInvalid() outranks Immutable and can land
// after both maps have been settled, so this narrows the window it guards.
bool const bothImmutable = setMapsImmutable();
SOMETIMES(!bothImmutable, "xrpl::Ledger::setImmutable : map invalidated while going immutable");
if (!bothImmutable)
return false;
// Written only now, so losing the race above leaves the header describing what the ledger was
// built from rather than a map that has since been abandoned. Forced rather than conditional,
// since this is the only place the hash transitions to valid.
if (deriveMapHashes)
// Force update, since this is the only
// place the hash transitions to valid
if (!immutable_ && rehash)
{
header_.txHash = txHash;
header_.accountHash = accountHash;
header_.txHash = txMap_.getHash().asUInt256();
header_.accountHash = stateMap_.getHash().asUInt256();
}
if (rehash)
header_.hash = calculateLedgerHash(header_);
// Set last, so isImmutable() reports only a ledger whose maps are both immutable.
immutable_ = true;
txMap_.setImmutable();
stateMap_.setImmutable();
setup();
return true;
}
bool
void
Ledger::setAccepted(
NetClock::time_point closeTime,
NetClock::duration closeResolution,
@@ -389,17 +342,7 @@ Ledger::setAccepted(
header_.closeTime = closeTime;
header_.closeTimeResolution = closeResolution;
header_.closeFlags = correctCloseTime ? 0 : kSLcfNoConsensusTime;
// Built locally. See Ledger::setImmutable().
if (!setImmutable())
{
// LCOV_EXCL_START
JLOG(j_.error()) << "Invalid map for accepted ledger " << header_.seq;
UNREACHABLE("xrpl::Ledger::setAccepted : map is invalid");
return false;
// LCOV_EXCL_STOP
}
return true;
setImmutable();
}
bool
@@ -623,7 +566,6 @@ Ledger::setup()
{
bool oldFees = false;
bool newFees = false;
bool extensionFees = false;
{
auto const baseFee = sle->at(~sfBaseFee);
auto const reserveBase = sle->at(~sfReserveBase);
@@ -658,20 +600,6 @@ Ledger::setup()
assign(fees_.increment, reserveIncrementXRP);
newFees = baseFeeXRP || reserveBaseXRP || reserveIncrementXRP;
}
{
auto const gasLimit = sle->at(~sfGasLimit);
auto const bytecodeSizeLimit = sle->at(~sfBytecodeSizeLimit);
auto const gasPrice = sle->at(~sfGasPrice);
// An absent field resolves to the protocol constant.
if (rules_.enabled(featureSmartEscrow))
{
fees_.gasLimit = gasLimit.value_or(kDefaultGasLimit);
fees_.bytecodeSizeLimit = bytecodeSizeLimit.value_or(kDefaultBytecodeSizeLimit);
fees_.gasPrice = gasPrice.value_or(kDefaultGasPrice);
}
extensionFees = gasLimit || bytecodeSizeLimit || gasPrice;
}
if (oldFees && newFees)
{
// Should be all of one or the other, but not both
@@ -682,12 +610,6 @@ Ledger::setup()
// Can't populate the new fees before the amendment is enabled
ret = false;
}
if (!rules_.enabled(featureSmartEscrow) && extensionFees)
{
// Can't populate the extension fees before the amendment is
// enabled
ret = false;
}
}
}
catch (SHAMapMissingNode const&)
+2
View File
@@ -435,6 +435,8 @@ PaymentSandbox::issuerSelfDebitHookMPT(
std::uint64_t amount,
std::int64_t origBalance)
{
XRPL_ASSERT(amount > 0, "PaymentSandbox::issuerSelfDebitHookMPT: amount must be > 0");
tab_.issuerSelfDebitMPT(issue, amount, origBalance);
}
+7 -29
View File
@@ -511,14 +511,9 @@ doWithdraw(
AccountID const& dstAcct,
AccountID const& sourceAcct,
XRPAmount priorBalance,
STAmount const& destinationAmount,
STAmount const& sourceAmount,
STAmount const& amount,
beast::Journal j)
{
XRPL_ASSERT(
destinationAmount.asset() == sourceAmount.asset(),
"xrpl::doWithdraw : delivered and source amounts use the same asset");
auto const dstSle = ctx.view.read(keylet::account(dstAcct));
// Create a trust line or MPToken for a self-destination only when there
@@ -528,10 +523,9 @@ doWithdraw(
// create+delete MPTokens in the same transaction.
if (dstAcct == senderAcct)
{
if (destinationAmount > beast::kZero || !ctx.view.rules().enabled(fixCleanup3_4_0))
if (amount > beast::kZero || !ctx.view.rules().enabled(fixCleanup3_4_0))
{
if (auto const ter =
addEmptyHolding(ctx, senderAcct, priorBalance, destinationAmount.asset(), j);
if (auto const ter = addEmptyHolding(ctx, senderAcct, priorBalance, amount.asset(), j);
!isTesSuccess(ter) && ter != tecDUPLICATE)
return ter;
}
@@ -546,33 +540,17 @@ doWithdraw(
if (accountHolds(
ctx.view,
sourceAcct,
destinationAmount.asset(),
amount.asset(),
FreezeHandling::IgnoreFreeze,
AuthHandling::IgnoreAuth,
j) < sourceAmount)
j) < amount)
{
// LCOV_EXCL_START
JLOG(j.error()) << "doWithdraw: source account holds fewer assets than the withdrawal.";
JLOG(j.error()) << "doWithdraw: negative balance of broker cover assets.";
return tefINTERNAL;
// LCOV_EXCL_STOP
}
// A transfer fee applies. Redeem the gross amount with the issuer, then
// issue the net amount to the destination.
if (sourceAmount != destinationAmount)
{
auto const issuer = destinationAmount.getIssuer();
XRPL_ASSERT(
!destinationAmount.native() && sourceAcct != issuer && dstAcct != issuer,
"xrpl::doWithdraw : transfer fee applies between token holders");
if (auto const ter = directSendNoFee(ctx.view, sourceAcct, issuer, sourceAmount, false, j);
!isTesSuccess(ter))
return ter; // LCOV_EXCL_LINE
return directSendNoFee(ctx.view, issuer, dstAcct, destinationAmount, false, j);
}
// A reserve sponsor only covers tx.Account's own objects, so resolve the
// sponsor against the destination. accountSend can auto-create a holding
// for dstAcct; keying on the destination ensures a third-party destination's
@@ -584,7 +562,7 @@ doWithdraw(
// Move the funds directly from the broker's pseudo-account to the
// dstAcct
return accountSend(
ctx.view, sourceAcct, dstAcct, destinationAmount, j, *sponsorSle, WaiveTransferFee::Yes);
ctx.view, sourceAcct, dstAcct, amount, j, *sponsorSle, WaiveTransferFee::Yes);
}
TER
+34 -4
View File
@@ -213,19 +213,49 @@ adjustAmountsByLPTokens(
if (lpTokensActual < lpTokens)
{
bool const ammRoundingEnabled = [&] {
if (auto const& rules = getCurrentTransactionRules();
rules && rules->enabled(fixAMMv1_1))
return true;
return false;
}();
// Equal trade
if (amount2)
{
Number const fr = lpTokensActual / lpTokens;
auto const amountActual = toSTAmount(amount.asset(), fr * amount);
auto const amount2Actual = toSTAmount(amount2->asset(), fr * *amount2);
if (!ammRoundingEnabled)
{
return std::make_tuple(
amountActual < amount ? amountActual : amount,
amount2Actual < amount2 ? amount2Actual : amount2,
lpTokensActual);
}
return std::make_tuple(amountActual, amount2Actual, lpTokensActual);
}
// Single trade
auto const amountActual = isDeposit == IsDeposit::Yes
? ammAssetIn(amountBalance, lptAMMBalance, lpTokensActual, tfee)
: ammAssetOut(amountBalance, lptAMMBalance, lpTokensActual, tfee);
auto const amountActual = [&] {
if (isDeposit == IsDeposit::Yes)
{
return ammAssetIn(amountBalance, lptAMMBalance, lpTokensActual, tfee);
}
if (!ammRoundingEnabled)
{
return ammAssetOut(amountBalance, lptAMMBalance, lpTokens, tfee);
}
return ammAssetOut(amountBalance, lptAMMBalance, lpTokensActual, tfee);
}();
if (!ammRoundingEnabled)
{
return amountActual < amount
? std::make_tuple(amountActual, std::nullopt, lpTokensActual)
: std::make_tuple(amount, std::nullopt, lpTokensActual);
}
return std::make_tuple(amountActual, std::nullopt, lpTokensActual);
}
@@ -950,7 +980,7 @@ verifyAndAdjustLPTokenBalance(
}
else
{
return std::unexpected<TER>(tecAMM_INVALID_TOKENS); // LCOV_EXCL_LINE
return std::unexpected<TER>(tecAMM_INVALID_TOKENS);
}
}
return true;
@@ -1,510 +0,0 @@
#include <xrpl/ledger/helpers/EscrowHelpers.h>
#include <xrpl/basics/Log.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/Keylet.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/MPTAmount.h>
#include <xrpl/protocol/MPTIssue.h>
#include <xrpl/protocol/Rate.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/UintTypes.h>
#include <xrpl/protocol/XRPAmount.h>
namespace xrpl {
template <>
TER
escrowLockPreclaimHelper<Issue>(
ReadView const& view,
AccountID const& account,
AccountID const& dest,
STAmount const& amount,
beast::Journal j)
{
auto const& issue = amount.get<Issue>();
auto const& issuer = amount.getIssuer();
// If the issuer is the same as the account, return tecNO_PERMISSION
if (issuer == account)
return tecNO_PERMISSION;
// If the lsfAllowTrustLineLocking is not enabled, return tecNO_PERMISSION
auto const sleIssuer = view.read(keylet::account(issuer));
if (!sleIssuer)
return tecNO_ISSUER;
if (!sleIssuer->isFlag(lsfAllowTrustLineLocking))
return tecNO_PERMISSION;
// If the account does not have a trustline to the issuer, return tecNO_LINE
auto const sleRippleState = view.read(keylet::trustLine(account, issuer, issue.currency));
if (!sleRippleState)
return tecNO_LINE;
STAmount const balance = (*sleRippleState)[sfBalance];
// If balance is positive, issuer must have higher address than account
if (balance > beast::kZero && issuer < account)
return tecNO_PERMISSION; // LCOV_EXCL_LINE
// If balance is negative, issuer must have lower address than account
if (balance < beast::kZero && issuer > account)
return tecNO_PERMISSION; // LCOV_EXCL_LINE
// If the issuer has requireAuth set, check if the account is authorized
if (auto const ter = requireAuth(view, issue, account); !isTesSuccess(ter))
return ter;
// If the issuer has requireAuth set, check if the destination is authorized
if (auto const ter = requireAuth(view, issue, dest); !isTesSuccess(ter))
return ter;
// If the issuer has frozen the account, return tecFROZEN
if (isFrozen(view, account, issue))
return tecFROZEN;
// If the issuer has frozen the destination, return tecFROZEN
if (isFrozen(view, dest, issue))
return tecFROZEN;
STAmount const spendableAmount =
accountHolds(view, account, issue.currency, issuer, FreezeHandling::IgnoreFreeze, j);
// If the balance is less than or equal to 0, return tecINSUFFICIENT_FUNDS
if (spendableAmount <= beast::kZero)
return tecINSUFFICIENT_FUNDS;
// If the spendable amount is less than the amount, return
// tecINSUFFICIENT_FUNDS
if (spendableAmount < amount)
return tecINSUFFICIENT_FUNDS;
// If the amount is not addable to the balance, return tecPRECISION_LOSS
if (!canAdd(spendableAmount, amount))
return tecPRECISION_LOSS;
return tesSUCCESS;
}
template <>
TER
escrowLockPreclaimHelper<MPTIssue>(
ReadView const& view,
AccountID const& account,
AccountID const& dest,
STAmount const& amount,
beast::Journal j)
{
AccountID const issuer = amount.getIssuer();
// If the issuer is the same as the account, return tecNO_PERMISSION
if (issuer == account)
return tecNO_PERMISSION;
// If the mpt does not exist, return tecOBJECT_NOT_FOUND
auto const issuanceKey = keylet::mptokenIssuance(amount.get<MPTIssue>().getMptID());
auto const sleIssuance = view.read(issuanceKey);
if (!sleIssuance)
return tecOBJECT_NOT_FOUND;
// If the lsfMPTCanEscrow is not enabled, return tecNO_PERMISSION
if (!sleIssuance->isFlag(lsfMPTCanEscrow))
return tecNO_PERMISSION;
// If the issuer is not the same as the issuer of the mpt, return
// tecNO_PERMISSION
if (sleIssuance->getAccountID(sfIssuer) != issuer)
return tecNO_PERMISSION; // LCOV_EXCL_LINE
// If the account does not have the mpt, return tecOBJECT_NOT_FOUND
if (!view.exists(keylet::mptoken(issuanceKey.key, account)))
return tecOBJECT_NOT_FOUND;
// If the issuer has requireAuth set, check if the account is
// authorized
auto const& mptIssue = amount.get<MPTIssue>();
if (auto const ter = requireAuth(view, mptIssue, account, AuthType::WeakAuth);
!isTesSuccess(ter))
return ter;
// If the issuer has requireAuth set, check if the destination is
// authorized
if (auto const ter = requireAuth(view, mptIssue, dest, AuthType::WeakAuth); !isTesSuccess(ter))
return ter;
// If the issuer has frozen the account, return tecLOCKED
if (isFrozen(view, account, *sleIssuance))
return tecLOCKED;
// If the issuer has frozen the destination, return tecLOCKED
if (isFrozen(view, dest, *sleIssuance))
return tecLOCKED;
// If the mpt cannot be transferred, return tecNO_AUTH
if (auto const ter = canTransfer(view, mptIssue, account, dest); !isTesSuccess(ter))
return ter;
STAmount const spendableAmount = accountHolds(
view,
account,
amount.get<MPTIssue>(),
FreezeHandling::IgnoreFreeze,
AuthHandling::IgnoreAuth,
j);
// If the balance is less than or equal to 0, return tecINSUFFICIENT_FUNDS
if (spendableAmount <= beast::kZero)
return tecINSUFFICIENT_FUNDS;
// If the spendable amount is less than the amount, return
// tecINSUFFICIENT_FUNDS
if (spendableAmount < amount)
return tecINSUFFICIENT_FUNDS;
return tesSUCCESS;
}
template <>
TER
escrowLockApplyHelper<Issue>(
ApplyView& view,
AccountID const& issuer,
AccountID const& sender,
STAmount const& amount,
beast::Journal journal)
{
// Defensive: Issuer cannot create an escrow
if (issuer == sender)
return tecINTERNAL; // LCOV_EXCL_LINE
auto const ter =
directSendNoFee(view, sender, issuer, amount, !amount.holds<MPTIssue>(), journal);
if (!isTesSuccess(ter))
return ter; // LCOV_EXCL_LINE
return tesSUCCESS;
}
template <>
TER
escrowLockApplyHelper<MPTIssue>(
ApplyView& view,
AccountID const& issuer,
AccountID const& sender,
STAmount const& amount,
beast::Journal journal)
{
// Defensive: Issuer cannot create an escrow
if (issuer == sender)
return tecINTERNAL; // LCOV_EXCL_LINE
auto const ter = lockEscrowMPT(view, sender, amount, journal);
if (!isTesSuccess(ter))
return ter; // LCOV_EXCL_LINE
return tesSUCCESS;
}
template <>
TER
escrowUnlockPreclaimHelper<Issue>(
ReadView const& view,
AccountID const& account,
STAmount const& amount,
bool checkFreeze)
{
AccountID const& issuer = amount.getIssuer();
// If the issuer is the same as the account, return tesSUCCESS
if (issuer == account)
return tesSUCCESS;
// If the issuer has requireAuth set, check if the destination is authorized
if (auto const ter = requireAuth(view, amount.get<Issue>(), account); !isTesSuccess(ter))
return ter;
// If the issuer has deep frozen the destination, return tecFROZEN
if (checkFreeze &&
isDeepFrozen(view, account, amount.get<Issue>().currency, amount.getIssuer()))
return tecFROZEN;
return tesSUCCESS;
}
template <>
TER
escrowUnlockPreclaimHelper<MPTIssue>(
ReadView const& view,
AccountID const& account,
STAmount const& amount,
bool checkFreeze)
{
AccountID const& issuer = amount.getIssuer();
// If the issuer is the same as the account, return tesSUCCESS
if (issuer == account)
return tesSUCCESS;
// If the mpt does not exist, return tecOBJECT_NOT_FOUND
auto const issuanceKey = keylet::mptokenIssuance(amount.get<MPTIssue>().getMptID());
auto const sleIssuance = view.read(issuanceKey);
if (!sleIssuance)
return tecOBJECT_NOT_FOUND;
// If the issuer has requireAuth set, check if the account is
// authorized
auto const& mptIssue = amount.get<MPTIssue>();
if (auto const ter = requireAuth(view, mptIssue, account, AuthType::WeakAuth);
!isTesSuccess(ter))
return ter;
// If the issuer has frozen the account, return tecLOCKED
if (checkFreeze && isFrozen(view, account, *sleIssuance))
return tecLOCKED;
return tesSUCCESS;
}
template <>
TER
escrowUnlockApplyHelper<Issue>(
ApplyViewContext ctx,
Rate lockedRate,
SLE::Ref sleDest,
XRPAmount xrpBalance,
STAmount const& amount,
AccountID const& issuer,
AccountID const& sender,
AccountID const& receiver,
bool createAsset,
beast::Journal journal)
{
bool const senderIssuer = issuer == sender;
bool const receiverIssuer = issuer == receiver;
if (senderIssuer)
return tecINTERNAL; // LCOV_EXCL_LINE
if (receiverIssuer)
return tesSUCCESS;
auto const& issue = amount.get<Issue>();
Keylet const trustLineKey = keylet::trustLine(receiver, issue);
bool const recvLow = issuer > receiver;
if (!ctx.view.exists(trustLineKey) && createAsset)
{
// Can the account cover the trust line's reserve?
auto const sponsorSle = getEffectiveTxReserveSponsor(ctx, sleDest);
if (!sponsorSle)
return sponsorSle.error(); // LCOV_EXCL_LINE
if (auto const ret = checkReserve(
ctx,
sleDest,
xrpBalance,
*sponsorSle,
{.ownerCountDelta = 1},
journal,
tecNO_LINE_INSUF_RESERVE);
!isTesSuccess(ret))
{
JLOG(journal.trace()) << "Trust line does not exist. "
"Insufficient reserve to create line.";
return ret;
}
Currency const currency = issue.currency;
STAmount initialBalance(issue);
initialBalance.get<Issue>().account = noAccount();
if (TER const ter = trustCreate(
ctx.view, // payment sandbox
recvLow, // is dest low?
issuer, // source
receiver, // destination
trustLineKey.key, // ledger index
sleDest, // Account to add to
false, // authorize account
!sleDest->isFlag(lsfDefaultRipple), //
false, // freeze trust line
false, // deep freeze trust line
initialBalance, // zero initial balance
Issue(currency, receiver), // limit of zero
0, // quality in
0, // quality out
*sponsorSle, // sponsor
journal); // journal
!isTesSuccess(ter))
{
return ter; // LCOV_EXCL_LINE
}
ctx.view.update(sleDest);
}
if (!ctx.view.exists(trustLineKey) && !receiverIssuer)
return tecNO_LINE;
auto const xferRate = transferRate(ctx.view, amount);
// update if issuer rate is less than locked rate
if (xferRate < lockedRate)
lockedRate = xferRate;
// Transfer Rate only applies when:
// 1. Issuer is not involved in the transfer (senderIssuer or
// receiverIssuer)
// 2. The locked rate is different from the parity rate
// NOTE: Transfer fee in escrow works a bit differently from a normal
// payment. In escrow, the fee is deducted from the locked/sending amount,
// whereas in a normal payment, the transfer fee is taken on top of the
// sending amount.
auto finalAmt = amount;
if ((!senderIssuer && !receiverIssuer) && lockedRate != kParityRate)
{
// compute transfer fee, if any
auto const xferFee =
amount.value() - divideRound(amount, lockedRate, amount.get<Issue>(), true);
// compute balance to transfer
finalAmt = amount.value() - xferFee;
}
// validate the line limit if the account submitting txn is not the receiver
// of the funds
if (!createAsset)
{
auto const sleRippleState = ctx.view.peek(trustLineKey);
if (!sleRippleState)
return tecINTERNAL; // LCOV_EXCL_LINE
// if the issuer is the high, then we use the low limit
// otherwise we use the high limit
STAmount const lineLimit =
sleRippleState->getFieldAmount(recvLow ? sfLowLimit : sfHighLimit);
STAmount lineBalance = sleRippleState->getFieldAmount(sfBalance);
// flip the sign of the line balance if the issuer is not high
if (!recvLow)
lineBalance.negate();
// add the final amount to the line balance
lineBalance += finalAmt;
// if the transfer would exceed the line limit return tecLIMIT_EXCEEDED
if (lineLimit < lineBalance)
return tecLIMIT_EXCEEDED;
}
// if destination is not the issuer then transfer funds
if (!receiverIssuer)
{
auto const ter = directSendNoFee(ctx.view, issuer, receiver, finalAmt, true, journal);
if (!isTesSuccess(ter))
return ter; // LCOV_EXCL_LINE
}
return tesSUCCESS;
}
template <>
TER
escrowUnlockApplyHelper<MPTIssue>(
ApplyViewContext ctx,
Rate lockedRate,
SLE::Ref sleDest,
XRPAmount xrpBalance,
STAmount const& amount,
AccountID const& issuer,
AccountID const& sender,
AccountID const& receiver,
bool createAsset,
beast::Journal journal)
{
bool const senderIssuer = issuer == sender;
bool const receiverIssuer = issuer == receiver;
auto const mptID = amount.get<MPTIssue>().getMptID();
auto const issuanceKey = keylet::mptokenIssuance(mptID);
auto const mptKeylet = keylet::mptoken(issuanceKey.key, receiver);
if (!ctx.view.exists(mptKeylet) && createAsset && !receiverIssuer)
{
auto const sponsorSle = getEffectiveTxReserveSponsor(ctx, sleDest);
if (!sponsorSle)
return sponsorSle.error(); // LCOV_EXCL_LINE
if (auto const ret = checkReserve(
ctx, sleDest, xrpBalance, *sponsorSle, {.ownerCountDelta = 1}, journal);
!isTesSuccess(ret))
return ret;
if (auto const ter = createMPToken(ctx.view, mptID, receiver, *sponsorSle, 0);
!isTesSuccess(ter))
{
return ter; // LCOV_EXCL_LINE
}
// update owner count.
increaseOwnerCount(ctx.view, sleDest, *sponsorSle, 1, journal);
}
if (!ctx.view.exists(mptKeylet) && !receiverIssuer)
return tecNO_PERMISSION;
auto const xferRate = transferRate(ctx.view, amount);
// update if issuer rate is less than locked rate
if (xferRate < lockedRate)
lockedRate = xferRate;
// Transfer Rate only applies when:
// 1. Issuer is not involved in the transfer (senderIssuer or
// receiverIssuer)
// 2. The locked rate is different from the parity rate
// NOTE: Transfer fee in escrow works a bit differently from a normal
// payment. In escrow, the fee is deducted from the locked/sending amount,
// whereas in a normal payment, the transfer fee is taken on top of the
// sending amount.
auto finalAmt = amount;
if ((!senderIssuer && !receiverIssuer) && lockedRate != kParityRate)
{
if (ctx.view.rules().enabled(fixCleanup3_4_0))
{
XRPL_ASSERT(
lockedRate >= kParityRate,
"xrpl::escrowUnlockApplyHelper<MPTIssue> : lockedRate is at least parity");
// MPTs are integral, so round the delivered amount down and
// charge any fractional transfer fee to the escrowed amount.
auto const delivered =
mulRatio(amount.mpt(), kParityRate.value, lockedRate.value, false);
finalAmt = STAmount(amount.asset(), delivered.value());
}
else
{
// compute transfer fee, if any
auto const xferFee =
amount.value() - divideRound(amount, lockedRate, amount.asset(), true);
// compute balance to transfer
finalAmt = amount.value() - xferFee;
}
}
return unlockEscrowMPT(
ctx.view,
sender,
receiver,
finalAmt,
ctx.view.rules().enabled(fixTokenEscrowV1) ? amount : finalAmt,
journal);
}
} // namespace xrpl
+5 -159
View File
@@ -66,161 +66,6 @@ canApplyToBrokerCover(
return tesSUCCESS;
}
namespace detail {
[[nodiscard]] int
getPosteriorBrokerCoverScale(SLE::ConstRef vault, SLE::ConstRef broker, Number const& delta)
{
XRPL_ASSERT(
vault && vault->getType() == ltVAULT,
"xrpl::detail::getPosteriorBrokerCoverScale : valid Vault sle");
XRPL_ASSERT(
broker && broker->getType() == ltLOAN_BROKER,
"xrpl::detail::getPosteriorBrokerCoverScale : valid LoanBroker sle");
return posteriorAssetScale(
getVaultVersion(vault),
vault->at(sfAsset),
getVaultBaseScale(vault),
broker->at(sfCoverAvailable),
delta);
}
[[nodiscard]] STAmount
roundToPosteriorBrokerCoverScale(
SLE::ConstRef vault,
SLE::ConstRef broker,
STAmount const& delta,
Number::RoundingMode roundingMode)
{
XRPL_ASSERT(
vault && vault->getType() == ltVAULT,
"xrpl::detail::roundToPosteriorBrokerCoverScale : valid Vault sle");
XRPL_ASSERT(
broker && broker->getType() == ltLOAN_BROKER,
"xrpl::detail::roundToPosteriorBrokerCoverScale : valid LoanBroker sle");
XRPL_ASSERT(
delta.asset() == vault->at(sfAsset),
"xrpl::detail::roundToPosteriorBrokerCoverScale : delta and Vault asset match");
if (delta.integral())
return delta;
return roundToScale(delta, getPosteriorBrokerCoverScale(vault, broker, delta), roundingMode);
}
} // namespace detail
[[nodiscard]] STAmount
creditToPosteriorBrokerCoverScale(
SLE::ConstRef vault,
SLE::ConstRef broker,
Number const& raw,
Number::RoundingMode roundingMode)
{
XRPL_ASSERT(
vault && vault->getType() == ltVAULT,
"xrpl::creditToPosteriorBrokerCoverScale : valid Vault sle");
XRPL_ASSERT(
broker && broker->getType() == ltLOAN_BROKER,
"xrpl::creditToPosteriorBrokerCoverScale : valid LoanBroker sle");
Asset const asset = vault->at(sfAsset);
if (asset.integral())
{
NumberRoundModeGuard const rg(roundingMode);
return STAmount{asset, raw};
}
Number const reference = broker->at(sfCoverAvailable);
return detail::creditToPosteriorScale(
asset, reference, getVaultBaseScale(vault), raw, roundingMode);
}
[[nodiscard]] STAmount
debitToPosteriorBrokerCoverScale(
SLE::ConstRef vault,
SLE::ConstRef broker,
STAmount const& amount,
Number::RoundingMode roundingMode)
{
XRPL_ASSERT(
vault && vault->getType() == ltVAULT,
"xrpl::debitToPosteriorBrokerCoverScale : valid Vault sle");
XRPL_ASSERT(
broker && broker->getType() == ltLOAN_BROKER,
"xrpl::debitToPosteriorBrokerCoverScale : valid LoanBroker sle");
XRPL_ASSERT(
amount.asset() == vault->at(sfAsset),
"xrpl::debitToPosteriorBrokerCoverScale : amount and Vault asset match");
XRPL_ASSERT(!amount.negative(), "xrpl::debitToPosteriorBrokerCoverScale : non-negative amount");
return -detail::roundToPosteriorBrokerCoverScale(vault, broker, -amount, roundingMode);
}
[[nodiscard]] TER
checkOptionalBrokerCoverInflow(SLE::ConstRef vault, SLE::ConstRef broker, STAmount const& amount)
{
XRPL_ASSERT(
vault && vault->getType() == ltVAULT,
"xrpl::checkOptionalBrokerCoverInflow : valid Vault sle");
XRPL_ASSERT(
broker && broker->getType() == ltLOAN_BROKER,
"xrpl::checkOptionalBrokerCoverInflow : valid LoanBroker sle");
XRPL_ASSERT(
amount.asset() == vault->at(sfAsset),
"xrpl::checkOptionalBrokerCoverInflow : amount and Vault asset match");
XRPL_ASSERT(!amount.negative(), "xrpl::checkOptionalBrokerCoverInflow : non-negative amount");
if (getVaultVersion(vault) != VaultVersion::FixedPrecision)
return tesSUCCESS;
// amount is the effective credit (already floored on the posterior
// CoverAvailable grid by creditToPosteriorBrokerCoverScale); rounding it
// again as a standalone delta could hide a crossing.
STAmount const& rounded = amount;
int const baseScale = getVaultBaseScale(vault);
// Keep this explicit even though the Open-limit capacity check below rejects
// every coarsening transition too. The protocol defines both conditions
// independently.
if (detail::getPosteriorBrokerCoverScale(vault, broker, rounded) != baseScale)
return tecLIMIT_EXCEEDED;
Number const posterior = [&] {
NumberRoundModeGuard const rg(Number::RoundingMode::TowardsZero);
return broker->at(sfCoverAvailable) + rounded;
}();
if (posterior > getVaultOpenLimit(vault))
return tecLIMIT_EXCEEDED;
return tesSUCCESS;
}
void
adjustBrokerDebtTotal(
SLE::Ref brokerSle,
SLE::ConstRef vaultSle,
Number const& delta,
int vaultScale)
{
// On FixedPrecision Vaults, LoanSet adds principal to DebtTotal exactly, LoanPay and default
// subtract exact amounts. Thus DebtTotal must not be rounded coarser than the base scale.
if (getVaultVersion(vaultSle) == VaultVersion::FixedPrecision)
{
brokerSle->at(sfDebtTotal) += delta;
}
else
{
adjustImpreciseNumber(brokerSle->at(sfDebtTotal), delta, vaultSle->at(sfAsset), vaultScale);
}
}
Number
minimumBrokerCover(Number const& debtTotal, TenthBips32 coverRateMinimum, SLE::ConstRef vaultSle)
{
XRPL_ASSERT(
vaultSle && vaultSle->getType() == ltVAULT, "xrpl::minimumBrokerCover : valid Vault sle");
NumberRoundModeGuard const mg(Number::RoundingMode::Upward);
return roundToAsset(
vaultSle->at(sfAsset),
tenthBipsOfValue(debtTotal, coverRateMinimum),
getVaultBaseScale(vaultSle));
}
bool
checkLendingProtocolDependencies(Rules const& rules, STTx const& tx)
{
@@ -405,13 +250,14 @@ loanPaymentDeltas(LoanPaymentParts const& parts)
namespace {
// Cash-basis accounting applies to Vaults created under
// featureLendingProtocolV1_1 or a later version. Vaults created before
// activation keep instant interest recognition forever.
// Cash-basis accounting applies only when featureLendingProtocolV1_1 is
// enabled AND the specific Vault was created under it (LEVersion ==
// VaultVersion::CashBasis). Vaults created before activation keep instant
// interest recognition forever, even after the amendment later turns on.
bool
cashBasisEnabled(SLE::ConstRef vaultSle)
{
return getVaultVersion(vaultSle) >= VaultVersion::CashBasis;
return getVaultVersion(vaultSle) == VaultVersion::CashBasis;
}
} // namespace
@@ -1047,14 +1047,6 @@ maxMPTAmount(SLE const& sleIssuance)
return sleIssuance[~sfMaximumAmount].value_or(kMaxMpTokenAmount);
}
std::optional<std::int64_t>
maxMPTAmount(ReadView const& view, MPTID const& mptID)
{
if (auto const sle = view.read(keylet::mptokenIssuance(mptID)))
return maxMPTAmount(*sle);
return std::nullopt;
}
std::int64_t
availableMPTAmount(SLE const& sleIssuance)
{
@@ -3,26 +3,16 @@
#include <xrpl/basics/Log.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/EscrowHelpers.h>
#include <xrpl/ledger/helpers/TokenHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Concepts.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/MPTIssue.h>
#include <xrpl/protocol/Rate.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/UintTypes.h>
#include <algorithm>
#include <cstdint>
@@ -31,76 +21,9 @@
namespace xrpl {
NotTEC
checkTokenPayChanRules(Rules const& rules)
{
if (!rules.enabled(featureTokenPaychan))
return temBAD_AMOUNT;
if (!rules.enabled(fixCleanup3_2_0) || !rules.enabled(fixTokenEscrowV1))
return temDISABLED;
return tesSUCCESS;
}
template <>
NotTEC
payChanAmountPreflightHelper<Issue>(STAmount const& amount)
{
if (amount.native() || amount <= beast::kZero)
return temBAD_AMOUNT;
if (badCurrency() == amount.get<Issue>().currency)
return temBAD_CURRENCY;
return tesSUCCESS;
}
template <>
NotTEC
payChanAmountPreflightHelper<MPTIssue>(STAmount const& amount)
{
if (amount.native() || amount <= beast::kZero)
return temBAD_AMOUNT;
return tesSUCCESS;
}
TER
payChanLockPrecisionHelper(
ReadView const& view,
AccountID const& account,
STAmount const& amount,
beast::Journal j)
closeChannel(SLE::Ref slep, ApplyView& view, UInt256 const& key, beast::Journal j)
{
if (!amount.holds<Issue>())
return tesSUCCESS;
// The trust line debit rounds to the mantissa, so require an exact
// difference rather than canAdd's relative tolerance.
STAmount const spendable = accountHolds(
view,
account,
amount.get<Issue>().currency,
amount.getIssuer(),
FreezeHandling::IgnoreFreeze,
j);
if (!isExactDifference(spendable, amount))
return tecPRECISION_LOSS;
return tesSUCCESS;
}
TER
closeChannel(
SLE::Ref slep,
ApplyViewContext ctx,
UInt256 const& key,
AccountID const& txAccount,
XRPAmount submitterPreFeeBalance,
beast::Journal j)
{
ApplyView& view = ctx.view;
AccountID const src = (*slep)[sfAccount];
// Remove PayChan from owner directory
{
@@ -115,9 +38,9 @@ closeChannel(
}
// Remove PayChan from recipient's owner directory, if present.
AccountID const dst = (*slep)[sfDestination];
if (auto const page = (*slep)[~sfDestinationNode])
{
auto const dst = (*slep)[sfDestination];
if (!view.dirRemove(keylet::ownerDir(dst), *page, key, true))
{
// LCOV_EXCL_START
@@ -127,69 +50,15 @@ closeChannel(
}
}
// Transfer amount back to owner, decrement owner count
auto const sle = view.peek(keylet::account(src));
if (!sle)
return tefINTERNAL; // LCOV_EXCL_LINE
// Release the channel's reserve first: a token refund can re-create a
// holding the owner deleted while the channel was open.
decreaseOwnerCountForObject(view, sle, slep, 1, j);
XRPL_ASSERT(
(*slep)[sfAmount] >= (*slep)[sfBalance], "xrpl::closeChannel : minimum channel amount");
XRPL_ASSERT(
isExactDifference((*slep)[sfAmount], (*slep)[sfBalance]),
"xrpl::closeChannel : exact refund");
auto const reqDelta = (*slep)[sfAmount] - (*slep)[sfBalance];
auto const& issuer = reqDelta.getIssuer();
// Only update the balance if there is a positive delta.
if (reqDelta > beast::kZero)
{
if (isXRP(reqDelta))
{
(*sle)[sfBalance] = (*sle)[sfBalance] + reqDelta;
}
else
{
if (auto const ret = reqDelta.asset().visit([&]<typename T>(T const&) {
return escrowUnlockPreclaimHelper<T>(view, src, reqDelta, false);
});
!isTesSuccess(ret))
return ret;
bool const createAsset = src == txAccount;
if (auto const ret = reqDelta.asset().visit([&]<typename T>(T const&) {
return escrowUnlockApplyHelper<T>(
ctx,
kParityRate,
sle,
submitterPreFeeBalance,
reqDelta,
issuer,
src,
src,
createAsset,
j);
});
!isTesSuccess(ret))
return ret;
}
}
// Remove PayChan from issuer's owner directory, if present.
if (auto const optPage = (*slep)[~sfIssuerNode])
{
if (!view.dirRemove(keylet::ownerDir(issuer), *optPage, key, true))
{
// LCOV_EXCL_START
JLOG(j.fatal()) << "Could not remove paychan from issuer owner directory";
return tefBAD_LEDGER;
// LCOV_EXCL_STOP
}
}
(*sle)[sfBalance] = (*sle)[sfBalance] + (*slep)[sfAmount] - (*slep)[sfBalance];
decreaseOwnerCountForObject(view, sle, slep, 1, j);
view.update(sle);
// Remove PayChan from ledger
+5 -160
View File
@@ -1,54 +1,18 @@
#include <xrpl/ledger/helpers/ProposalHelpers.h>
#include <xrpl/basics/Log.h>
#include <xrpl/basics/Slice.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/View.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Keylet.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STArray.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STObject.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/Serializer.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/protocol/TxFormats.h>
#include <algorithm>
#include <array>
#include <cstdint>
#include <optional>
namespace xrpl::proposal {
namespace {
// Proposals do not nest, so a proposed transaction may carry none of these
// types, neither as its own payload nor as a Batch inner transaction.
constexpr auto kProposalTxTypes = std::to_array<TxType>({
ttTRANSACTION_PROPOSAL_CREATE,
ttTRANSACTION_PROPOSAL_CANCEL,
});
} // namespace
bool
isValidProposalTxnType(STObject const& proposedTx)
isValidProposal(STObject const& proposedTx)
{
if (!proposedTx.isFieldPresent(sfTransactionType))
return false;
auto const type = proposedTx.getFieldU16(sfTransactionType);
if (std::ranges::find(kProposalTxTypes, type) != kProposalTxTypes.end())
if (isProposalTx(proposedTx))
return false;
if (isPseudoTx(proposedTx))
@@ -58,24 +22,13 @@ isValidProposalTxnType(STObject const& proposedTx)
(proposedTx.getFieldU32(sfFlags) & tfInnerBatchTxn) != 0u)
return false;
if (type == ttBATCH && proposedTx.isFieldPresent(sfRawTransactions))
if (proposedTx.getFieldU16(sfTransactionType) == ttBATCH &&
proposedTx.isFieldPresent(sfRawTransactions))
{
STArray const& innerTxns = proposedTx.getFieldArray(sfRawTransactions);
for (STObject const& inner : innerTxns)
{
// The only production caller reaches this walk through STTx
// construction, which rejects a typeless inner first, so this is
// re-checked rather than relied upon.
if (!inner.isFieldPresent(sfTransactionType))
return false;
auto const innerType = inner.getFieldU16(sfTransactionType);
// A nested Batch is rejected alongside nested proposals and
// pseudo-transactions: STTx construction forbids inner Batches, so
// a proposal must not be able to store one either.
if (std::ranges::find(kProposalTxTypes, innerType) != kProposalTxTypes.end() ||
innerType == ttBATCH || isPseudoTx(inner))
if (isProposalTx(inner) || isPseudoTx(inner))
return false;
}
}
@@ -83,112 +36,4 @@ isValidProposalTxnType(STObject const& proposedTx)
return true;
}
bool
isTerminal(
ReadView const& view,
std::optional<std::uint32_t> expiration,
STObject const& proposedTx)
{
if (hasExpired(view, expiration))
return true;
// Mirror the ordinary transactor's tefMAX_LEDGER rule: view.seq() > bound.
// At view.seq() == bound the payload can still be submitted, so the
// proposal is still live (On-Chain Cosigner spec §4.5).
return proposedTx.isFieldPresent(sfLastLedgerSequence) &&
view.seq() > proposedTx.getFieldU32(sfLastLedgerSequence);
}
TER
deleteProposal(ApplyView& view, SLE::pointer const& sleProposal, beast::Journal j)
{
if (!sleProposal || sleProposal->getType() != ltTRANSACTION_PROPOSAL ||
!view.exists(Keylet{ltTRANSACTION_PROPOSAL, sleProposal->key()}))
{
// LCOV_EXCL_START
JLOG(j.fatal()) << "Invalid TransactionProposal deletion.";
return tecINTERNAL;
// LCOV_EXCL_STOP
}
AccountID const owner = sleProposal->getAccountID(sfOwner);
std::uint64_t const page{(*sleProposal)[sfOwnerNode]};
if (!view.dirRemove(keylet::ownerDir(owner), page, sleProposal->key(), true))
{
// LCOV_EXCL_START
JLOG(j.fatal()) << "Unable to delete TransactionProposal from owner.";
return tefBAD_LEDGER;
// LCOV_EXCL_STOP
}
auto const sleOwner = view.peek(keylet::account(owner));
if (!sleOwner)
{
// LCOV_EXCL_START
JLOG(j.fatal()) << "Could not find TransactionProposal owner account root.";
return tefBAD_LEDGER;
// LCOV_EXCL_STOP
}
// Release the reserve against the Owner or, if the entry carries a
// reserve sponsor, against that sponsor.
decreaseOwnerCountForObject(
view,
sleOwner,
sleProposal,
proposalOwnerCount(sleProposal->getFieldObject(sfProposedTransaction)),
j);
view.erase(sleProposal);
return tesSUCCESS;
}
bool
payloadMatches(STObject const& proposedTx, STObject const& tx)
{
XRPL_ASSERT(
proposedTx.isFieldPresent(sfTransactionType) && tx.isFieldPresent(sfTransactionType),
"xrpl::proposal::payloadMatches : transaction-shaped inputs");
// The signing-payload serialization omits every kNotSigning field: the
// signature containers a proposal lets evolve (TxnSignature, Signers,
// BatchSigners, CounterpartySignature, SponsorSignature) plus Signature
// and MasterSignature, which the account's own signature does not cover
// either (see the header). What remains is the field set fixed at
// creation — except SigningPubKey, which signing payloads include but
// which is also mutable here (stored empty, filled if the target signs
// with its own key). Neutralize it on both sides before comparing.
auto const payloadBytes = [](STObject const& obj) {
STObject copy{obj};
copy.setFieldVL(sfSigningPubKey, Slice{});
Serializer s;
copy.addWithoutSigningFields(s);
return s.getData();
};
return payloadBytes(proposedTx) == payloadBytes(tx);
}
bool
canConsumeTicket(ReadView const& view, STTx const& tx)
{
auto const seqProx = tx.getSeqProxy();
if (!seqProx.isTicket())
{
// The caller only reaches here for a Ticket-based transaction.
// LCOV_EXCL_START
UNREACHABLE("xrpl::proposal::canConsumeTicket : tx spends a Sequence");
return false;
// LCOV_EXCL_STOP
}
auto const sleProposal =
view.read(keylet::txProposal(tx.getAccountID(sfAccount), seqProx.value()));
if (!sleProposal)
return true;
return payloadMatches(sleProposal->getFieldObject(sfProposedTransaction), tx);
}
} // namespace xrpl::proposal
@@ -4,7 +4,6 @@
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/EscrowHelpers.h>
#include <xrpl/ledger/helpers/OracleHelpers.h>
#include <xrpl/ledger/helpers/ProposalHelpers.h>
#include <xrpl/protocol/AccountID.h>
@@ -320,8 +319,6 @@ getLedgerEntryOwnerCount(SLE const& sle)
UNREACHABLE("AccountRoots are not supported by object sponsorship.");
return 0;
// LCOV_EXCL_STOP
case ltESCROW:
return calculateAdditionalReserve(sle[~sfBytecode]);
default:
return 1;
}
+17 -24
View File
@@ -2,7 +2,6 @@
#include <xrpl/basics/Log.h>
#include <xrpl/basics/MathUtilities.h>
#include <xrpl/basics/Number.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/beast/utility/instrumentation.h>
@@ -552,22 +551,6 @@ transferRate(ReadView const& view, STAmount const& amount)
return transferRate(view, amount.asset());
}
STAmount
addTransferFee(STAmount const& amount, Rate const& rate)
{
if (rate == kParityRate)
return amount;
return mulRatio(amount, rate.value, QUALITY_ONE, Number::RoundingMode::Upward);
}
STAmount
subtractTransferFee(STAmount const& amount, Rate const& rate)
{
if (rate == kParityRate)
return amount;
return mulRatio(amount, QUALITY_ONE, rate.value, Number::RoundingMode::Downward);
}
//------------------------------------------------------------------------------
//
// Holding operations
@@ -990,11 +973,19 @@ accountSendIOU(
SLE::Ref sponsorSle,
WaiveTransferFee waiveFee)
{
if (saAmount < beast::kZero || saAmount.holds<MPTIssue>())
if (view.rules().enabled(fixAMMv1_1))
{
if (saAmount < beast::kZero || saAmount.holds<MPTIssue>())
{
return tecINTERNAL; // LCOV_EXCL_LINE
}
}
else
{
// LCOV_EXCL_START
JLOG(j.fatal()) << "accountSendIOU: negative amount or invalid asset";
return tecINTERNAL;
XRPL_ASSERT(
saAmount >= beast::kZero && !saAmount.holds<MPTIssue>(),
"xrpl::accountSendIOU : minimum amount and not MPT");
// LCOV_EXCL_STOP
}
@@ -1364,8 +1355,9 @@ directSendNoLimitMPT(
{
// Number math loses precision on large MPT amounts, which can
// overcharge the sender. MPTs are integral, so compute the cost
// exactly, matching the payment engine.
saActual = addTransferFee(saAmount, rate);
// exactly and round it up, matching the payment engine.
auto const cost = mulRatio(saAmount.mpt(), rate.value, QUALITY_ONE, true);
saActual = STAmount(saAmount.asset(), cost.value());
}
else
{
@@ -1490,8 +1482,9 @@ directSendNoLimitMultiMPT(
{
// Number math loses precision on large MPT amounts, which can
// overcharge the sender. MPTs are integral, so compute the
// cost exactly, matching the payment engine.
actualSend = addTransferFee(amount, rate);
// cost exactly and round it up, matching the payment engine.
auto const cost = mulRatio(amount.mpt(), rate.value, QUALITY_ONE, true);
actualSend = STAmount(amount.asset(), cost.value());
}
else
{
+55 -573
View File
@@ -1,16 +1,12 @@
#include <xrpl/ledger/helpers/VaultHelpers.h>
#include <xrpl/basics/Log.h>
#include <xrpl/basics/Number.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/CredentialHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h> // IWYU pragma: keep
#include <xrpl/protocol/Protocol.h>
@@ -21,480 +17,13 @@
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <algorithm>
#include <cstdint>
#include <expected>
#include <optional>
#include <stdexcept>
#include <string_view>
#include <utility>
namespace xrpl {
namespace {
[[nodiscard]] int
fixedBaseScale(SLE::ConstRef vault)
{
XRPL_ASSERT(vault && vault->getType() == ltVAULT, "xrpl::fixedBaseScale : valid Vault sle");
Asset const asset = vault->at(sfAsset);
// sfScale is never written for an integral asset (VaultCreate only sets
// it when scale != 0, and integral assets always get scale 0), so it
// must not be read here; vaultBaseScale ignores the scale argument for
// an integral asset anyway.
if (asset.integral())
return 0;
return vaultBaseScale(asset, vault->at(sfScale));
}
// Thin, vault-specific wrapper over posteriorAssetScale: used by
// getPosteriorVaultScale and the cash outflow rounding below.
[[nodiscard]] int
posteriorScale(SLE::ConstRef vault, Number const& reference, Number const& delta)
{
return detail::posteriorAssetScale(
getVaultVersion(vault), vault->at(sfAsset), fixedBaseScale(vault), reference, delta);
}
// FixedPrecision-only counterpart to clampToAssetsTotalScale, for cash
// outflows (VaultWithdraw, VaultClawback): rounds the magnitude of delta
// toward zero at AssetsAvailable's own posterior scale, the balance the cash
// actually leaves, instead of the derived AssetsTotal cache. Same
// tecPRECISION_LOSS and integral-asset and magnitude-never-exceeds-delta
// rules as clampToAssetsTotalScale. Not part of the public interface; reached
// only through clampVaultOutflow.
//
// For an outflow it is safe to round the delta directly, unlike the credit
// case: amount on the posterior grid of AssetsAvailable always leaves
// AssetsAvailable minus amount representable, since the posterior grid is
// exactly the grid AssetsAvailable itself will canonicalize to after the
// subtraction.
[[nodiscard]] std::expected<STAmount, TER>
clampToAvailableScale(SLE::ConstRef vault, STAmount const& delta)
{
XRPL_ASSERT(
delta.asset() == vault->at(sfAsset),
"xrpl::clampToAvailableScale : delta and vault asset match");
XRPL_ASSERT(
getVaultVersion(vault) == VaultVersion::FixedPrecision,
"xrpl::clampToAvailableScale : FixedPrecision Vault");
XRPL_ASSERT(delta.negative(), "xrpl::clampToAvailableScale : outflow delta is negative");
Asset const asset = vault->at(sfAsset);
STAmount magnitude = delta.negative() ? -delta : delta;
if (asset.integral())
return magnitude;
STAmount const rounded = roundToScale(
delta,
posteriorScale(vault, vault->at(sfAssetsAvailable), delta),
Number::RoundingMode::TowardsZero);
STAmount actualDelta = rounded.negative() ? -rounded : rounded;
XRPL_ASSERT(
abs(actualDelta) <= abs(delta),
"xrpl::clampToAvailableScale : actual delta smaller or equal to calculated delta");
// Reject changes below scale precision (1 ULP) to prevent share balance changes
// without corresponding asset movements.
if (actualDelta <= beast::kZero)
return std::unexpected(tecPRECISION_LOSS);
return actualDelta;
}
[[nodiscard]] VaultKind
decodeVaultKind(std::optional<std::uint8_t> vaultKind)
{
if (vaultKind && *vaultKind == std::to_underlying(VaultKind::ClosedEnded))
return VaultKind::ClosedEnded;
return VaultKind::OpenEnded;
}
// Write cached AssetsTotal from getAssetsTotal, as the exact total rounded
// Downward to the Vault asset's 16-digit STAmount representation. Below
// coarsening this equals the exact total; above it, the cache is a
// deliberate floor of the exact value. Internal to adjustVaultBalances,
// the only writer of AssetsAvailable, AssetsDeployed, AssetsTotal,
// YieldUnrealized and LossUnrealized on a FixedPrecision Vault after
// creation; not part of the public interface.
void
syncAssetsTotal(SLE::Ref vault)
{
XRPL_ASSERT(vault && vault->getType() == ltVAULT, "xrpl::syncAssetsTotal : valid Vault sle");
Asset const asset = vault->at(sfAsset);
// adjustVaultBalances writes AssetsAvailable as an already-canonical
// STAmount before calling this, so the associateAsset pass a transactor
// runs afterwards is always a no-op. If this fires, AssetsAvailable was
// written some other way.
XRPL_ASSERT(
(STAmount{asset, vault->at(sfAssetsAvailable)} == vault->at(sfAssetsAvailable)),
"xrpl::syncAssetsTotal : AssetsAvailable is a 16-digit STAmount value");
NumberRoundModeGuard const rg(Number::RoundingMode::Downward);
vault->at(sfAssetsTotal) = STAmount{asset, getAssetsTotal(vault)};
}
[[nodiscard]] bool
debitIsNonZeroDust(SLE::ConstRef vault, Number const& amount)
{
if (amount == 0)
return false;
auto const total = getVaultVersion(vault) == VaultVersion::FixedPrecision
? vault->at(sfAssetsAvailable)
: getAssetsTotal(vault);
auto const asset = vault->at(sfAsset);
return STAmount{asset, total - amount} == STAmount{asset, total};
}
} // namespace
namespace detail {
[[nodiscard]] int
liveScale(Number const& reference, Asset const& asset, int baseScale)
{
if (reference == beast::kZero)
return baseScale;
// Round Downward, not the ambient mode: the live exponent must match the
// exponent syncAssetsTotal actually stores (also Downward), regardless of
// what rounding mode the caller's own arithmetic is using.
NumberRoundModeGuard const rg(Number::RoundingMode::Downward);
return std::max(baseScale, scale(reference, asset));
}
[[nodiscard]] int
posteriorAssetScale(
VaultVersion version,
Asset const& asset,
int baseScale,
Number const& reference,
Number const& delta)
{
// ToNearest for the sum and for scale()'s canonicalization of it, as the
// Legacy/CashBasis clamp always did; liveScale sets its own mode.
NumberRoundModeGuard const rg(Number::RoundingMode::ToNearest);
Number const posterior = reference + delta;
switch (version)
{
case VaultVersion::Legacy:
case VaultVersion::CashBasis:
return scale(posterior, asset);
case VaultVersion::FixedPrecision:
return liveScale(posterior, asset, baseScale);
}
// LCOV_EXCL_START
UNREACHABLE("xrpl::detail::posteriorAssetScale : valid VaultVersion");
return Number::kMinExponent - 1;
// LCOV_EXCL_STOP
}
[[nodiscard]] STAmount
creditToPosteriorScale(
Asset const& asset,
Number const& reference,
int baseScale,
Number const& raw,
Number::RoundingMode roundingMode)
{
// Floor the SUM (reference + raw), not just raw. See
// creditToPosteriorAvailableScale's doc: a delta floored on its own grid
// can still leave a 17-digit sum once the reference has crossed a power
// of ten.
//
// Both reference + raw and flooredSum - reference can carry more digits
// than Number holds (the sum is on the posterior grid, the reference on
// the finer one). Compute both under roundingMode, not the caller's
// ambient mode, so the credit this returns never exceeds raw.
//
// Take the grid from this same sum. A sum just under a power of ten that
// rounds up to it in another mode would pick the coarser grid, and
// flooring the unrounded sum on it would land below reference.
NumberRoundModeGuard const rg(roundingMode);
Number const sum = reference + raw;
int const atScale = liveScale(sum, asset, baseScale);
Number const flooredSum = roundToAsset(asset, sum, atScale, roundingMode);
STAmount const credit{asset, flooredSum - reference};
XRPL_ASSERT_PARTS(
roundingMode != Number::RoundingMode::Downward ||
(credit >= beast::kZero && Number(credit) <= raw),
"xrpl::detail::creditToPosteriorScale",
"credit is between zero and raw");
return credit;
}
[[nodiscard]] int
getPosteriorVaultScale(SLE::ConstRef vault, STAmount const& delta)
{
XRPL_ASSERT(
vault && vault->getType() == ltVAULT,
"xrpl::detail::getPosteriorVaultScale : valid Vault sle");
XRPL_ASSERT(
delta.asset() == vault->at(sfAsset),
"xrpl::detail::getPosteriorVaultScale : delta and Vault asset match");
return posteriorScale(vault, getAssetsTotal(vault), delta);
}
} // namespace detail
[[nodiscard]] Number
getAssetsTotal(SLE::ConstRef vault)
{
XRPL_ASSERT(vault && vault->getType() == ltVAULT, "xrpl::getAssetsTotal : valid Vault sle");
if (getVaultVersion(vault) != VaultVersion::FixedPrecision)
return vault->at(sfAssetsTotal);
// AssetsAvailable is at -Scale (or coarser) and AssetsDeployed is always at
// -Scale, so the sum is exact while it fits 19 digits. Fix the mode so a
// sum that does not fit rounds the same way for every caller.
NumberRoundModeGuard const rg(Number::RoundingMode::Downward);
return vault->at(sfAssetsAvailable) + vault->at(sfAssetsDeployed);
}
[[nodiscard]] TER
adjustVaultBalances(SLE::Ref vault, VaultBalanceChange const& change, beast::Journal j)
{
XRPL_ASSERT(
vault && vault->getType() == ltVAULT, "xrpl::adjustVaultBalances : valid Vault sle");
XRPL_ASSERT(
getVaultVersion(vault) == VaultVersion::FixedPrecision,
"xrpl::adjustVaultBalances : FixedPrecision Vault");
Asset const asset = vault->at(sfAsset);
STAmount const cash = change.cash.value_or(STAmount{asset, 0});
XRPL_ASSERT(cash.asset() == asset, "xrpl::adjustVaultBalances : cash and Vault asset match");
// This is the mode the ledger's own transfers (trust line / MPT balance
// updates) normalize in; computing AA' with any other ambient mode would
// not reproduce what the real transfer just did to the backing balance.
XRPL_ASSERT(
Number::getround() == Number::RoundingMode::ToNearest,
"xrpl::adjustVaultBalances : ambient rounding mode is ToNearest");
// AA' is the exact trust-line/MPT arithmetic the ledger uses for the
// transfer itself: a Number sum re-canonicalized to the asset's 16-digit
// STAmount precision. MPT and XRP sums are already integral, so this is
// a no-op rounding for them.
//
// AssetsDeployed, LossUnrealized and YieldUnrealized move no cash, so they stay
// exact Number sums; the asserts below check each fits 16 digits, rather
// than letting an STAmount conversion round it silently.
STAmount const availableAfter{asset, Number(vault->at(sfAssetsAvailable)) + Number(cash)};
Number const assetsDeployedAfter = Number(vault->at(sfAssetsDeployed)) + change.deployed;
Number const lossUnrealizedAfter = Number(vault->at(sfLossUnrealized)) + change.loss;
// LoanSet, LoanPay and default each move YieldUnrealized by the change in
// the Loan's own TotalValueOutstanding - PrincipalOutstanding -
// ManagementFeeOutstanding, so it always equals that sum over the
// Vault's Loans and cannot go negative.
Number const yieldUnrealizedAfter = Number(vault->at(sfYieldUnrealized)) + change.yield;
if (availableAfter < beast::kZero || assetsDeployedAfter < beast::kZero ||
lossUnrealizedAfter < beast::kZero || yieldUnrealizedAfter < beast::kZero)
{
// LCOV_EXCL_START
JLOG(j.fatal()) << "adjustVaultBalances: a balance would become negative."
<< " AssetsAvailable: " << Number(availableAfter)
<< ", AssetsDeployed: " << assetsDeployedAfter
<< ", LossUnrealized: " << lossUnrealizedAfter
<< ", YieldUnrealized: " << yieldUnrealizedAfter;
return tefBAD_LEDGER;
// LCOV_EXCL_STOP
}
// The impairment rule: a Loan can only be impaired for as much as it
// still owes. Reachable only through LoanManage's impair.
if (lossUnrealizedAfter > assetsDeployedAfter)
return tecLIMIT_EXCEEDED;
// Every caller derives principal from a Loan's own PrincipalOutstanding so the new total must
// stay representable at the asset's own 16-digit precision too.
XRPL_ASSERT(
(STAmount{asset, assetsDeployedAfter} == assetsDeployedAfter),
"xrpl::adjustVaultBalances : AssetsDeployed is a 16-digit STAmount value");
XRPL_ASSERT(
(STAmount{asset, lossUnrealizedAfter} == lossUnrealizedAfter),
"xrpl::adjustVaultBalances : LossUnrealized is a 16-digit STAmount value");
XRPL_ASSERT(
(STAmount{asset, yieldUnrealizedAfter} == yieldUnrealizedAfter),
"xrpl::adjustVaultBalances : YieldUnrealized is a 16-digit STAmount value");
vault->at(sfAssetsAvailable) = availableAfter;
vault->at(sfAssetsDeployed) = assetsDeployedAfter;
vault->at(sfLossUnrealized) = lossUnrealizedAfter;
vault->at(sfYieldUnrealized) = yieldUnrealizedAfter;
syncAssetsTotal(vault);
return tesSUCCESS;
}
[[nodiscard]] TER
adjustVaultCash(SLE::Ref vault, STAmount const& delta, beast::Journal j)
{
if (getVaultVersion(vault) == VaultVersion::FixedPrecision)
return adjustVaultBalances(vault, {.cash = delta}, j);
vault->at(sfAssetsTotal) += delta;
vault->at(sfAssetsAvailable) += delta;
return tesSUCCESS;
}
[[nodiscard]] int
getVaultScale(SLE::ConstRef vault)
{
XRPL_ASSERT(vault && vault->getType() == ltVAULT, "xrpl::getVaultScale : valid Vault sle");
Number const assetsTotal = getAssetsTotal(vault);
switch (getVaultVersion(vault))
{
case VaultVersion::Legacy:
case VaultVersion::CashBasis:
return scale(assetsTotal, vault->at(sfAsset));
case VaultVersion::FixedPrecision:
return detail::liveScale(assetsTotal, vault->at(sfAsset), fixedBaseScale(vault));
}
// LCOV_EXCL_START
UNREACHABLE("xrpl::getVaultScale : valid VaultVersion");
return Number::kMinExponent - 1;
// LCOV_EXCL_STOP
}
[[nodiscard]] int
getVaultBaseScale(SLE::ConstRef vault)
{
XRPL_ASSERT(vault && vault->getType() == ltVAULT, "xrpl::getVaultBaseScale : valid Vault sle");
switch (getVaultVersion(vault))
{
case VaultVersion::Legacy:
case VaultVersion::CashBasis:
return getVaultScale(vault);
case VaultVersion::FixedPrecision:
return fixedBaseScale(vault);
}
// LCOV_EXCL_START
UNREACHABLE("xrpl::getVaultBaseScale : valid VaultVersion");
return Number::kMinExponent - 1;
// LCOV_EXCL_STOP
}
[[nodiscard]] STAmount
roundToPosteriorVaultScale(
SLE::ConstRef vault,
STAmount const& amount,
Number::RoundingMode roundingMode)
{
XRPL_ASSERT(
vault && vault->getType() == ltVAULT, "xrpl::roundToPosteriorVaultScale : valid Vault sle");
XRPL_ASSERT(
amount.asset() == vault->at(sfAsset),
"xrpl::roundToPosteriorVaultScale : amount and Vault asset match");
if (amount.integral())
return amount;
return roundToScale(amount, detail::getPosteriorVaultScale(vault, amount), roundingMode);
}
[[nodiscard]] STAmount
creditToPosteriorAvailableScale(
SLE::ConstRef vault,
Number const& raw,
Number::RoundingMode roundingMode)
{
XRPL_ASSERT(
vault && vault->getType() == ltVAULT,
"xrpl::creditToPosteriorAvailableScale : valid Vault sle");
Asset const asset = vault->at(sfAsset);
if (asset.integral())
{
NumberRoundModeGuard const rg(roundingMode);
return STAmount{asset, raw};
}
Number const reference = vault->at(sfAssetsAvailable);
return detail::creditToPosteriorScale(
asset, reference, fixedBaseScale(vault), raw, roundingMode);
}
[[nodiscard]] Number
getVaultOpenLimit(SLE::ConstRef vault)
{
XRPL_ASSERT(vault && vault->getType() == ltVAULT, "xrpl::getVaultOpenLimit : valid Vault sle");
XRPL_ASSERT(
getVaultVersion(vault) == VaultVersion::FixedPrecision,
"xrpl::getVaultOpenLimit : FixedPrecision Vault");
return Number{9, 15 + getVaultBaseScale(vault)};
}
[[nodiscard]] TER
checkOptionalVaultInflow(SLE::ConstRef vault, STAmount const& amount)
{
XRPL_ASSERT(
vault && vault->getType() == ltVAULT, "xrpl::checkOptionalVaultInflow : valid Vault sle");
XRPL_ASSERT(
amount.asset() == vault->at(sfAsset),
"xrpl::checkOptionalVaultInflow : amount and Vault asset match");
XRPL_ASSERT(!amount.negative(), "xrpl::checkOptionalVaultInflow : non-negative amount");
if (amount.negative())
return tefINTERNAL; // LCOV_EXCL_LINE
if (getVaultVersion(vault) != VaultVersion::FixedPrecision)
return tesSUCCESS;
STAmount const rounded =
roundToPosteriorVaultScale(vault, amount, Number::RoundingMode::TowardsZero);
int const baseScale = getVaultBaseScale(vault);
// Keep this explicit even though a non-negative YieldUnrealized makes the
// Open-zone capacity ceiling reject every coarsening transition too. The
// protocol defines both conditions independently.
if (detail::getPosteriorVaultScale(vault, rounded) != baseScale)
return tecLIMIT_EXCEEDED;
if (vaultOpenZoneCapacity(vault, rounded) > getVaultOpenLimit(vault))
return tecLIMIT_EXCEEDED;
return tesSUCCESS;
}
[[nodiscard]] Number
vaultOpenZoneCapacity(SLE::ConstRef vault, Number const& roundedAmount)
{
XRPL_ASSERT(
vault && vault->getType() == ltVAULT, "xrpl::vaultOpenZoneCapacity : valid Vault sle");
XRPL_ASSERT(
getVaultVersion(vault) == VaultVersion::FixedPrecision,
"xrpl::vaultOpenZoneCapacity : FixedPrecision Vault");
NumberRoundModeGuard const rg(Number::RoundingMode::TowardsZero);
return getAssetsTotal(vault) + vault->at(sfYieldUnrealized) + roundedAmount;
}
[[nodiscard]] TER
checkDebitNotDust(
SLE::ConstRef vault,
Number const& amount,
beast::Journal j,
std::string_view name)
{
try
{
if (debitIsNonZeroDust(vault, amount))
{
JLOG(j.debug()) << name << ": amount too small to change stored vault balance";
return tecPRECISION_LOSS;
}
}
// LCOV_EXCL_START
catch (std::overflow_error const&)
{
// It's easy to hit this exception from Number with large enough Scale
// so we avoid spamming the log and only use debug here.
JLOG(j.debug()) //
<< name << ": overflow error with"
<< " scale=" << (int)vault->at(sfScale) //
<< ", assetsTotal=" << vault->at(sfAssetsTotal) << ", amount=" << amount;
return tecPATH_DRY;
}
// LCOV_EXCL_STOP
return tesSUCCESS;
}
[[nodiscard]] std::optional<STAmount>
assetsToSharesDeposit(SLE::ConstRef vault, SLE::ConstRef issuance, STAmount const& assets)
{
@@ -505,7 +34,7 @@ assetsToSharesDeposit(SLE::ConstRef vault, SLE::ConstRef issuance, STAmount cons
if (assets.negative() || assets.asset() != vault->at(sfAsset))
return std::nullopt; // LCOV_EXCL_LINE
Number const assetTotal = getAssetsTotal(vault);
Number const assetTotal = vault->at(sfAssetsTotal);
STAmount shares{vault->at(sfShareMPTID)};
if (assetTotal == 0)
{
@@ -529,7 +58,7 @@ sharesToAssetsDeposit(SLE::ConstRef vault, SLE::ConstRef issuance, STAmount cons
if (shares.negative() || shares.asset() != vault->at(sfShareMPTID))
return std::nullopt; // LCOV_EXCL_LINE
Number const assetTotal = getAssetsTotal(vault);
Number const assetTotal = vault->at(sfAssetsTotal);
STAmount assets{vault->at(sfAsset)};
if (assetTotal == 0)
{
@@ -556,52 +85,37 @@ clampToAssetsTotalScale(SLE::ConstRef vault, STAmount const& delta)
{
return magnitude;
}
Number const assetsTotal = vault->at(sfAssetsTotal);
// Calculate the scale after applying the delta using ToNearest rounding.
// This aligns the delta with scale checks used by vault invariants.
int const postScale = [&] {
NumberRoundModeGuard const rg(Number::RoundingMode::ToNearest);
return scale(assetsTotal + delta, asset);
}();
STAmount actualDelta;
if (getVaultVersion(vault) == VaultVersion::FixedPrecision)
if (delta.negative())
{
// FixedPrecision only reaches this branch for credits (VaultDeposit);
// outflows go through clampVaultOutflow -> clampToAvailableScale,
// which rounds at AssetsAvailable's own posterior scale instead.
XRPL_ASSERT(
!delta.negative(), "xrpl::clampToAssetsTotalScale : FixedPrecision credit only");
STAmount const rounded =
roundToPosteriorVaultScale(vault, delta, Number::RoundingMode::TowardsZero);
actualDelta = rounded.negative() ? -rounded : rounded;
// For withdrawals (debits), floor the magnitude to the target scale
// to ensure exact grid alignment without paying out extra assets.
actualDelta = roundToScale(magnitude, postScale, Number::RoundingMode::Downward);
}
else
{
Number const assetsTotal = getAssetsTotal(vault);
// Calculate the scale after applying the delta using ToNearest rounding.
// This aligns the delta with scale checks used by vault invariants.
int const postScale = [&] {
NumberRoundModeGuard const rg(Number::RoundingMode::ToNearest);
return scale(assetsTotal + delta, asset);
// For deposits (credits), derive actualDelta from the floored posterior total.
// This prevents grid alignment issues from crediting the vault more than deposited.
//
// Sum using Downward rounding so intermediate precision doesn't round up
// and exceed the original requested amount.
Number const posterior = [&] {
NumberRoundModeGuard const rg(Number::RoundingMode::Downward);
return assetsTotal + magnitude;
}();
if (delta.negative())
{
// For withdrawals (debits), floor the magnitude to the target scale
// to ensure exact grid alignment without paying out extra assets.
actualDelta = roundToScale(magnitude, postScale, Number::RoundingMode::Downward);
}
else
{
// For deposits (credits), derive actualDelta from the floored posterior total.
// This prevents grid alignment issues from crediting the vault more than deposited.
//
// Sum using Downward rounding so intermediate precision doesn't round up
// and exceed the original requested amount.
Number const posterior = [&] {
NumberRoundModeGuard const rg(Number::RoundingMode::Downward);
return assetsTotal + magnitude;
}();
Number const roundedPosterior =
roundToAsset(asset, posterior, postScale, Number::RoundingMode::Downward);
actualDelta = STAmount{asset, roundedPosterior - assetsTotal};
}
Number const roundedPosterior =
roundToAsset(asset, posterior, postScale, Number::RoundingMode::Downward);
actualDelta = STAmount{asset, roundedPosterior - assetsTotal};
}
XRPL_ASSERT(
@@ -616,49 +130,23 @@ clampToAssetsTotalScale(SLE::ConstRef vault, STAmount const& delta)
return actualDelta;
}
[[nodiscard]] std::expected<STAmount, TER>
clampVaultOutflow(SLE::ConstRef vault, STAmount const& delta)
{
XRPL_ASSERT(delta.negative(), "xrpl::clampVaultOutflow : outflow delta is negative");
return getVaultVersion(vault) == VaultVersion::FixedPrecision
? clampToAvailableScale(vault, delta)
: clampToAssetsTotalScale(vault, delta);
}
[[nodiscard]] int
vaultBaseScale(Asset const& asset, std::uint8_t scale)
{
if (asset.integral())
return 0;
return -static_cast<int>(scale);
}
[[nodiscard]] bool
isOnVaultBaseGrid(Asset const& asset, Number const& value, int baseScale)
{
// isRepresentable first: building an STAmount from an out-of-range value
// throws, and this checks user-supplied values in preclaim.
return isRepresentable(asset, value) && STAmount{asset, value} == value &&
roundToAsset(asset, value, baseScale, Number::RoundingMode::TowardsZero) == value;
}
[[nodiscard]] TER
checkAssetsMaximum(SLE::ConstRef vault, Number const& amount)
{
return isOnVaultBaseGrid(vault->at(sfAsset), amount, getVaultBaseScale(vault))
? TER{tesSUCCESS}
: TER{tecPRECISION_LOSS};
}
[[nodiscard]] Number
assetsTotalForWithdrawal(SLE::ConstRef vault, WaiveUnrealizedLoss waive)
{
Number assetTotal = getAssetsTotal(vault);
Number assetTotal = vault->at(sfAssetsTotal);
if (waive == WaiveUnrealizedLoss::No)
assetTotal -= vault->at(sfLossUnrealized);
return assetTotal;
}
[[nodiscard]] bool
debitIsNonZeroDust(Asset const& asset, Number const& total, Number const& amount)
{
if (amount == 0)
return false;
return STAmount{asset, total - amount} == STAmount{asset, total};
}
[[nodiscard]] std::optional<STAmount>
assetsToSharesWithdraw(
SLE::ConstRef vault,
@@ -729,42 +217,36 @@ isSoleShareholder(ReadView const& view, AccountID const& account, SLE::ConstRef
return sleToken->getFieldU64(sfMPTAmount) == outstanding;
}
[[nodiscard]] VaultVersion
decodeVaultVersion(std::optional<std::uint8_t> leVersion)
{
if (!leVersion)
return VaultVersion::Legacy;
if (*leVersion > std::to_underlying(VaultVersion::FixedPrecision))
{
// LCOV_EXCL_START
UNREACHABLE("xrpl::decodeVaultVersion : invalid vault version");
return VaultVersion::Legacy;
// LCOV_EXCL_STOP
}
return static_cast<VaultVersion>(*leVersion);
}
[[nodiscard]] VaultVersion
getVaultVersion(SLE::ConstRef vault)
{
XRPL_ASSERT(vault && vault->getType() == ltVAULT, "xrpl::getVaultVersion : valid Vault sle");
return decodeVaultVersion(vault->at(~sfLEVersion));
if (!vault->isFieldPresent(sfLEVersion))
return VaultVersion::Legacy;
auto const version = vault->at(sfLEVersion);
if (version > std::to_underlying(VaultVersion::CashBasis))
{
// LCOV_EXCL_START
UNREACHABLE("xrpl::getVaultVersion : invalid vault version");
return VaultVersion::Legacy;
// LCOV_EXCL_STOP
}
return static_cast<VaultVersion>(version);
}
[[nodiscard]] VaultVersion
getVaultDefaultVersion(Rules const& rules)
namespace {
[[nodiscard]] VaultKind
decodeVaultKind(std::optional<std::uint8_t> vaultKind)
{
// FixedPrecision requires featureLendingProtocolV1_1 (closed-ended Vaults and
// cash-basis accounting) and fixCleanup3_4_0, so a FixedPrecision Vault always
// sees both enabled. fixCleanup3_2_0 is already enabled on the network.
if (rules.enabled(featureLendingProtocolV1_1) && rules.enabled(featureLendingProtocolV1_2) &&
rules.enabled(fixCleanup3_4_0))
return VaultVersion::FixedPrecision;
if (rules.enabled(featureLendingProtocolV1_1))
return VaultVersion::CashBasis;
return VaultVersion::Legacy;
if (vaultKind && *vaultKind == std::to_underlying(VaultKind::ClosedEnded))
return VaultKind::ClosedEnded;
return VaultKind::OpenEnded;
}
} // namespace
[[nodiscard]] VaultKind
getVaultKind(SLE::ConstRef vault)
{
+5 -12
View File
@@ -435,25 +435,18 @@ areMirrorsCurrent(SLE const& issuance, SLE const& mptoken)
}
void
setIssuerMirrorCurrent(SLE const& issuance, SLE& mptoken)
setIssuerMirrorEpoch(SLE const& issuance, SLE& mptoken)
{
XRPL_ASSERT(
issuance.getType() == ltMPTOKEN_ISSUANCE,
"xrpl::setIssuerMirrorCurrent : issuance MPTokenIssuance object");
"xrpl::setIssuerMirrorEpoch : issuance MPTokenIssuance object");
XRPL_ASSERT(
mptoken.getType() == ltMPTOKEN, "xrpl::setIssuerMirrorCurrent : mptoken MPToken object");
mptoken.getType() == ltMPTOKEN, "xrpl::setIssuerMirrorEpoch : mptoken MPToken object");
// Unlike the auditor mirror, the issuer mirror is not optional: every
// confidential MPToken carries one, so there is no existence check here.
//
// Record the key and epoch under which the mirror is now encrypted. Both fields remain absent
// at epoch 0: such mirrors use the original issuer key. The original key is stored in
// sfInitialIssuerEncryptionKey after the first rotation.
if (auto const epoch = issuance[~sfIssuerKeyEpoch].value_or(0); epoch != 0)
{
mptoken[sfIssuerKeyMirrorEpoch] = epoch;
mptoken[sfIssuerMirrorEncryptionKey] = issuance[sfIssuerEncryptionKey];
}
}
void
@@ -473,9 +466,9 @@ setAuditorMirrorEpoch(SLE const& issuance, SLE& mptoken)
}
void
setMirrorsCurrent(SLE const& issuance, SLE& mptoken)
setMirrorEpochs(SLE const& issuance, SLE& mptoken)
{
setIssuerMirrorCurrent(issuance, mptoken);
setIssuerMirrorEpoch(issuance, mptoken);
setAuditorMirrorEpoch(issuance, mptoken);
}
-21
View File
@@ -1,24 +1,10 @@
#include <xrpl/protocol/MPTAmount.h>
#include <xrpl/basics/MathUtilities.h>
#include <xrpl/basics/contract.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Rules.h>
#include <stdexcept>
namespace xrpl {
MPTAmount&
MPTAmount::operator+=(MPTAmount const& other)
{
if (auto const result = checkedAdd(value_, other.value()))
{
value_ = *result;
return *this;
}
if (isFeatureEnabled(fixCleanup3_5_0, /*resultIfNoRules*/ true))
Throw<std::overflow_error>("MPTAmount::operator+= overflow");
value_ += other.value();
return *this;
}
@@ -26,13 +12,6 @@ MPTAmount::operator+=(MPTAmount const& other)
MPTAmount&
MPTAmount::operator-=(MPTAmount const& other)
{
if (auto const result = checkedSub(value_, other.value()))
{
value_ = *result;
return *this;
}
if (isFeatureEnabled(fixCleanup3_5_0, /*resultIfNoRules*/ true))
Throw<std::overflow_error>("MPTAmount::operator-= overflow");
value_ -= other.value();
return *this;
}
+1 -38
View File
@@ -3,14 +3,10 @@
#include <xrpl/beast/utility/Zero.h> // IWYU pragma: keep
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Rules.h>
#include <xrpl/protocol/STAmount.h>
#include <cstdint>
#include <limits> // IWYU pragma: keep
#include <optional>
#include <stdexcept>
namespace xrpl {
@@ -85,46 +81,13 @@ Quality::ceilInStrict(Amounts const& amount, STAmount const& limit, bool roundUp
return ceilInImpl<divRoundStrict>(amount, limit, roundUp, *this);
}
// Returns std::nullopt if a native result can't be represented, instead of
// throwing. Pathfinding runs without transaction
// rules, so default to the fixed behavior there to match transactions once the
// amendment is enabled.
template <STAmount (*MulRoundFunc)(STAmount const&, STAmount const&, Asset const&, bool)>
static std::optional<STAmount>
tryMulRound(STAmount const& v1, STAmount const& v2, Asset const& asset, bool roundUp)
{
try
{
return MulRoundFunc(v1, v2, asset, roundUp);
}
catch (std::overflow_error const&)
{
// std::overflow_error is a std::runtime_error, so it must be caught
// first to keep the handler below from swallowing it. It means the
// computation itself overflowed, not that the result is out of range.
// Callers handle it, e.g. BookStep removes the offer.
throw;
}
catch (std::runtime_error const&)
{
if (!asset.native() || !isFeatureEnabled(fixCleanup3_5_0, /*resultIfNoRules*/ true))
throw;
return std::nullopt;
}
}
template <STAmount (*MulRoundFunc)(STAmount const&, STAmount const&, Asset const&, bool)>
static Amounts
ceilOutImpl(Amounts const& amount, STAmount const& limit, bool roundUp, Quality const& quality)
{
if (amount.out > limit)
{
// An unrepresentable XRP product is larger than amount.in, so the
// clamp below would return amount.in anyway.
Amounts result(
tryMulRound<MulRoundFunc>(limit, quality.rate(), amount.in.asset(), roundUp)
.value_or(amount.in),
limit);
Amounts result(MulRoundFunc(limit, quality.rate(), amount.in.asset(), roundUp), limit);
// Clamp in
if (result.in > amount.in)
result.in = amount.in;
+55 -159
View File
@@ -1,7 +1,6 @@
#include <xrpl/protocol/STAmount.h>
#include <xrpl/basics/Log.h>
#include <xrpl/basics/MathUtilities.h>
#include <xrpl/basics/Number.h>
#include <xrpl/basics/contract.h>
#include <xrpl/basics/safe_cast.h>
@@ -385,11 +384,10 @@ operator+(STAmount const& v1, STAmount const& v2)
return {v1.getFName(), v1.asset(), v2.mantissa(), v2.exponent(), v2.negative()};
}
// XRPAmount and MPTAmount check for int64 overflow under fixCleanup3_5_0.
if (v1.native())
return {v1.getFName(), (v1.xrp() + v2.xrp()).drops()};
return {v1.getFName(), getSNValue(v1) + getSNValue(v2)};
if (v1.holds<MPTIssue>())
return {v1.asset_, (v1.mpt() + v2.mpt()).value()};
return {v1.asset_, v1.mpt().value() + v2.mpt().value()};
auto x = v1;
x = v1.iou() + v2.iou();
@@ -445,22 +443,6 @@ getRate(STAmount const& offerOut, STAmount const& offerIn)
}
}
bool
isRepresentable(Asset const& asset, Number const& value)
{
try
{
// Out-of-range values throw std::overflow_error from Number or
// IOUAmount, or std::runtime_error from STAmount::canonicalize.
[[maybe_unused]] STAmount const amount{asset, value};
return true;
}
catch (std::runtime_error const&)
{
return false;
}
}
/**
* @brief Safely checks if two STAmount values can be added without overflow,
* underflow, or precision loss.
@@ -492,7 +474,16 @@ canAdd(STAmount const& a, STAmount const& b)
// XRP case (overflow & underflow check)
if (isXRP(a) && isXRP(b))
return checkedAdd(a.xrp().drops(), b.xrp().drops()).has_value();
{
XRPAmount const aVal = a.xrp();
XRPAmount const bVal = b.xrp();
return !(
(bVal > XRPAmount{0} &&
aVal > XRPAmount{std::numeric_limits<XRPAmount::value_type>::max()} - bVal) ||
(bVal < XRPAmount{0} &&
aVal < XRPAmount{std::numeric_limits<XRPAmount::value_type>::min()} - bVal));
}
// IOU case (precision check)
auto const ret = std::visit(
@@ -509,7 +500,15 @@ canAdd(STAmount const& a, STAmount const& b)
// MPT (overflow & underflow check)
if constexpr (kIsMptissueV<TIss1> && kIsMptissueV<TIss2>)
return checkedAdd(a.mpt().value(), b.mpt().value()).has_value();
{
MPTAmount const aVal = a.mpt();
MPTAmount const bVal = b.mpt();
return !(
(bVal > MPTAmount{0} &&
aVal > MPTAmount{std::numeric_limits<MPTAmount::value_type>::max()} - bVal) ||
(bVal < MPTAmount{0} &&
aVal < MPTAmount{std::numeric_limits<MPTAmount::value_type>::min()} - bVal));
}
return std::nullopt;
},
a.asset().value(),
@@ -522,64 +521,6 @@ canAdd(STAmount const& a, STAmount const& b)
// LCOV_EXCL_STOP
}
/**
* Integral types defer to canAdd(). For IOU amounts the rounding error of the
* sum is a multiple of the finer operand's unit in the last place, so
* subtracting the coarser operand back from the sum returns the finer one
* only when that error is zero. Both differences are checked because either
* operand may be the finer. IOUAmount throws std::overflow_error when a sum
* or difference exceeds the largest IOU value; the exact result is then not
* representable, so the sum is not exact.
*/
bool
isExactSum(STAmount const& a, STAmount const& b)
{
if (!areComparable(a, b))
return false;
if (a.integral())
return canAdd(a, b);
try
{
STAmount const sum = a + b;
return sum - a == b && sum - b == a;
}
catch (std::overflow_error const&)
{
return false;
}
}
/**
* Integral types defer to canSubtract(). For IOU amounts each check misses
* the case where one operand is dust against the other. With a dust
* subtrahend, a - b rounds to a and adding b back rounds to a again, so only
* a - diff != b shows the loss. With a dust minuend, a - b rounds to -b and
* a - diff rounds to b, so only diff + b != a shows it. Both are checked.
* IOUAmount throws std::overflow_error when a step rounds past the largest
* IOU value; the difference is then not exact.
*/
bool
isExactDifference(STAmount const& a, STAmount const& b)
{
if (!areComparable(a, b))
return false;
if (a.integral())
return canSubtract(a, b);
try
{
STAmount const diff = a - b;
return diff + b == a && a - diff == b;
}
catch (std::overflow_error const&)
{
return false;
}
}
/**
* @brief Determines if it is safe to subtract one STAmount from another.
*
@@ -618,7 +559,11 @@ canSubtract(STAmount const& a, STAmount const& b)
return false;
// Check for overflow
return checkedSub(aVal.drops(), bVal.drops()).has_value();
if (bVal < XRPAmount{0} &&
aVal > XRPAmount{std::numeric_limits<XRPAmount::value_type>::max()} + bVal)
return false;
return true;
}
// IOU case (no underflow)
@@ -641,7 +586,10 @@ canSubtract(STAmount const& a, STAmount const& b)
return false;
// Overflow check
return checkedSub(aVal.value(), bVal.value()).has_value();
if (bVal < MPTAmount{0} &&
aVal > MPTAmount{std::numeric_limits<MPTAmount::value_type>::max()} + bVal)
return false;
return true;
}
return std::nullopt;
},
@@ -1283,19 +1231,6 @@ divide(STAmount const& num, STAmount const& den, Asset const& asset)
if (num == beast::kZero)
return {asset};
// The legacy path below assumes 16-digit mantissas. An MPT mantissa is
// the raw 63-bit value and is never scaled down, so an MPT numerator can
// overflow the quotient and an MPT denominator shortens it, moving the +5
// nudge into a significant digit. Use Number arithmetic under
// MPTokensV2 whenever an MPT is involved, as mulRound/divRound do. Pin
// the rounding mode so the result does not depend on the caller's.
if (isFeatureEnabled(featureMPTokensV2, /*resultIfNoRules*/ true) &&
(asset.holds<MPTIssue>() || num.holds<MPTIssue>() || den.holds<MPTIssue>()))
{
NumberRoundModeGuard const rg(Number::RoundingMode::ToNearest);
return STAmount{asset, Number{num} / Number{den}};
}
std::uint64_t numVal = num.mantissa();
std::uint64_t denVal = den.mantissa();
int numOffset = num.exponent();
@@ -1325,21 +1260,11 @@ divide(STAmount const& num, STAmount const& den, Asset const& asset)
// numerator by 10^17 (the product is in the range of
// 10^32 to 10^33) followed by a division, so the result
// is in the range of 10^16 to 10^15.
std::uint64_t quotient = muldiv(numVal, kTenTO17, denVal);
int offset = numOffset - denOffset - 17;
// An integral (XRP) numerator is not scaled down to 16 digits, so the
// quotient can land in [2^63, 2^64). The STAmount constructor reads
// that as a negative IOU mantissa, and the +5 can wrap. Drop a digit
// first; the result is canonicalized to 16 digits anyway.
if (quotient > static_cast<std::uint64_t>(std::numeric_limits<std::int64_t>::max()) - 5 &&
isFeatureEnabled(featureMPTokensV2, /*resultIfNoRules*/ true))
{
quotient /= 10;
++offset;
}
return STAmount(asset, quotient + 5, offset, num.negative() != den.negative());
return STAmount(
asset,
muldiv(numVal, kTenTO17, denVal) + 5,
numOffset - denOffset - 17,
num.negative() != den.negative());
}
STAmount
@@ -1545,28 +1470,28 @@ roundNumberResult(
// and for materializing the final integral amount.
NumberRoundModeGuard const finalRound(roundMode(resultNegative, roundUp));
auto result = STAmount{asset, number};
// An integral result can't underflow: when roundUp is set, roundMode()
// above selects Upward, and materializing a Number into an integral
// STAmount honors that mode (Number::operator rep()), so any positive
// value rounds up to at least 1.
[[maybe_unused]] bool const nonzeroPositiveRoundUp =
roundUp && !resultNegative && number != beast::kZero;
ALWAYS(
!nonzeroPositiveRoundUp || !asset.integral() || result != beast::kZero,
"xrpl::roundNumberResult : positive rounded-up integral result is representable");
!nonzeroPositiveRoundUp || result != beast::kZero,
"xrpl::roundNumberResult : positive rounded-up MPT result is representable");
if (roundUp && !resultNegative && !result)
{
// Preserve existing mulRound/divRound behavior for a positive result
// too small to represent in the target asset. An IOU result with an
// MPT operand (e.g. 1 MPT / 1e95) below the smallest IOU is flushed to
// zero by IOUAmount normalization regardless of the rounding mode, so
// snap it up to the smallest positive IOU. The only zero case for an
// integral result is a zero operand, which the mulRound/divRound
// callers handle before reaching this function.
// Intended to preserve existing mulRound/divRound behavior for a
// positive result too small to represent in the target asset.
//
// Unreachable in practice: when roundUp is set, roundMode() above
// selects Upward, and materializing a Number into an STAmount honors
// that mode (Number::operator rep()), so any positive value rounds up
// to at least the smallest representable unit. Hence, a positive result
// is never !result here; the only zero case is a zero operand, which
// the mulRound/divRound callers handle before reaching this function.
// LCOV_EXCL_START
if (asset.integral())
return STAmount{asset, 1}; // LCOV_EXCL_LINE
return STAmount{asset, 1};
return STAmount{asset, STAmount::kMinValue, STAmount::kMinOffset, false};
// LCOV_EXCL_STOP
}
return result;
@@ -1615,19 +1540,11 @@ mulRoundImpl(STAmount const& v1, STAmount const& v2, Asset const& asset, bool ro
bool const resultNegative = v1.negative() != v2.negative();
// The legacy path below assumes 16-digit mantissas. An MPT mantissa is the
// raw 63-bit balance, so any MPT operand (not just an MPT result) can
// overflow it: a large MPT amount times an IOU-shaped rate into an XRP or
// IOU result is the offer-crossing remainder and partial-fill case. Use
// Number arithmetic under MPTokensV2 whenever an MPT is involved.
//
// Outside a transaction (no current rules, e.g. pathfinding) default to
// the Number path: an MPT operand can only reach here through MPT
// offers or AMMs, which require MPTokensV2, so this keeps RPC quotes on
// the same arithmetic as the transaction they describe.
if (isFeatureEnabled(featureMPTokensV2, /*resultIfNoRules*/ true) &&
(asset.holds<MPTIssue>() || v1.holds<MPTIssue>() || v2.holds<MPTIssue>()))
if (asset.holds<MPTIssue>() && isFeatureEnabled(featureMPTokensV2, false))
{
// MPT DEX can combine 63-bit MPT amounts with IOU-shaped transfer
// rates. Use Number arithmetic under MPTokensV2 so the rounded
// operation is not limited by the legacy uint64_t scaled mantissa.
Number result;
{
NumberRoundModeGuard const operationRound(roundMode(resultNegative, roundUp));
@@ -1725,12 +1642,9 @@ divRoundImpl(STAmount const& num, STAmount const& den, Asset const& asset, bool
bool const resultNegative = (num.negative() != den.negative());
// See mulRoundImpl: any MPT operand, not just an MPT result, and the
// Number path when there are no current rules.
// fixCleanup3_5_0: the legacy path below overflows on large MPT amounts.
auto const enabled = isFeatureEnabled(featureMPTokensV2, /*resultIfNoRules*/ true) ||
isFeatureEnabled(fixCleanup3_5_0, /*resultIfNoRules*/ true);
if ((asset.holds<MPTIssue>() || num.holds<MPTIssue>() || den.holds<MPTIssue>()) && enabled)
if (asset.holds<MPTIssue>() &&
(isFeatureEnabled(featureMPTokensV2, true) || isFeatureEnabled(fixCleanup3_5_0, true)))
{
// Match the multiply path above: Number performs the rounded
// operation, then STAmount materializes the final MPT amount using the
@@ -1821,24 +1735,6 @@ divRoundStrict(STAmount const& num, STAmount const& den, Asset const& asset, boo
return divRoundImpl<NumberRoundModeGuard>(num, den, asset, roundUp);
}
STAmount
mulRatio(STAmount const& amt, std::uint32_t num, std::uint32_t den, Number::RoundingMode rounding)
{
XRPL_ASSERT(
rounding == Number::RoundingMode::Upward || rounding == Number::RoundingMode::Downward,
"xrpl::mulRatio(STAmount) : rounding is Upward or Downward");
bool const roundUp = rounding == Number::RoundingMode::Upward;
return amt.asset().visit(
[&](Issue const& issue) -> STAmount {
if (amt.native())
return mulRatio(amt.xrp(), num, den, roundUp);
return {mulRatio(amt.iou(), num, den, roundUp), issue};
},
[&](MPTIssue const& issue) -> STAmount {
return {mulRatio(amt.mpt(), num, den, roundUp), issue};
});
}
[[nodiscard]] bool
STAmount::isZeroAtScale(int scale) const
{
+1 -3
View File
@@ -137,8 +137,6 @@ transResults()
MAKE_ERROR(tefBAD_PATH_COUNT, "Malformed: Too many paths."),
MAKE_ERROR(tefNO_BYTECODE, "There is no WASM code to run, but a WASM-specific field was included."),
MAKE_ERROR(tefBYTECODE_NOT_INCLUDED, "WASM code requires a field that was not included."),
MAKE_ERROR(tefTEMP_DISABLED, "The transaction requires logic that is currently temporarily disabled."),
MAKE_ERROR(tefEXCEEDS_LIMIT, "The transaction exceeds a limit set by fee voting."),
MAKE_ERROR(telLOCAL_ERROR, "Local failure."),
MAKE_ERROR(telBAD_DOMAIN, "Domain too long."),
@@ -211,6 +209,7 @@ transResults()
MAKE_ERROR(temINVALID_INNER_BATCH, "Malformed: Invalid inner batch transaction."),
MAKE_ERROR(temBAD_CIPHERTEXT, "Malformed: Invalid ciphertext."),
MAKE_ERROR(temINVALID_BYTECODE, "Malformed: Provided byte code is invalid."),
MAKE_ERROR(temTEMP_DISABLED, "The transaction requires logic that is currently temporarily disabled."),
MAKE_ERROR(terRETRY, "Retry transaction."),
MAKE_ERROR(terFUNDS_SPENT, "DEPRECATED."),
@@ -229,7 +228,6 @@ transResults()
MAKE_ERROR(terNO_DELEGATE_PERMISSION, "Delegated account lacks permission to perform this transaction."),
MAKE_ERROR(terLOCKED, "Fund is locked."),
MAKE_ERROR(terNO_PERMISSION, "No permission to perform requested operation."),
MAKE_ERROR(terTICKET_RESERVED, "Ticket is reserved."),
MAKE_ERROR(tesSUCCESS, "The transaction was applied. Only final in a validated ledger."),
};
-54
View File
@@ -1,54 +0,0 @@
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/basics/MathUtilities.h>
#include <xrpl/basics/contract.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Rules.h>
#include <stdexcept>
namespace xrpl {
XRPAmount&
XRPAmount::operator+=(value_type const& rhs)
{
if (auto const result = checkedAdd(drops_, rhs))
{
drops_ = *result;
return *this;
}
if (isFeatureEnabled(fixCleanup3_5_0, /*resultIfNoRules*/ true))
Throw<std::overflow_error>("XRPAmount::operator+= overflow");
drops_ += rhs;
return *this;
}
XRPAmount&
XRPAmount::operator-=(value_type const& rhs)
{
if (auto const result = checkedSub(drops_, rhs))
{
drops_ = *result;
return *this;
}
if (isFeatureEnabled(fixCleanup3_5_0, /*resultIfNoRules*/ true))
Throw<std::overflow_error>("XRPAmount::operator-= overflow");
drops_ -= rhs;
return *this;
}
XRPAmount&
XRPAmount::operator*=(value_type const& rhs)
{
if (auto const result = checkedMul(drops_, rhs))
{
drops_ = *result;
return *this;
}
if (isFeatureEnabled(fixCleanup3_5_0, /*resultIfNoRules*/ true))
Throw<std::overflow_error>("XRPAmount::operator*= overflow");
drops_ *= rhs;
return *this;
}
} // namespace xrpl
+15 -32
View File
@@ -26,7 +26,6 @@
#include <boost/smart_ptr/intrusive_ptr.hpp>
#include <atomic>
#include <cstdint>
#include <exception>
#include <functional>
@@ -78,23 +77,14 @@ SHAMap::SHAMap(SHAMap const& other, bool isMutable)
: f_(other.f_)
, journal_(other.f_.journal())
, cowid_(other.cowid_ + 1)
, ledgerSeq_(other.ledgerSeq())
, ledgerSeq_(other.ledgerSeq_)
, root_(other.root_)
, state_(isMutable ? SHAMapState::Modifying : SHAMapState::Immutable)
, type_(other.type_)
, backed_(other.backed_)
{
// A snapshot shares the source's root, so Invalid carries over. Read once into a local, since
// the source's state can change while this runs.
auto const otherState = other.state();
auto const ownState = [&] {
if (otherState == SHAMapState::Invalid)
return SHAMapState::Invalid;
return isMutable ? SHAMapState::Modifying : SHAMapState::Immutable;
}();
state_.store(ownState, std::memory_order_release);
// If either map may change, they cannot share nodes
if ((ownState != SHAMapState::Immutable) || (otherState != SHAMapState::Immutable))
if ((state_ != SHAMapState::Immutable) || (other.state_ != SHAMapState::Immutable))
{
unshare();
}
@@ -115,7 +105,7 @@ SHAMap::dirtyUp(NodePathStack& stack, UInt256 const& target, SHAMapTreeNodePtr c
// child can be an inner node or a leaf
XRPL_ASSERT(
(state() != SHAMapState::Synching) && (state() != SHAMapState::Immutable),
(state_ != SHAMapState::Synching) && (state_ != SHAMapState::Immutable),
"xrpl::SHAMap::dirtyUp : valid state");
XRPL_ASSERT(child && (child->cowid() == cowid_), "xrpl::SHAMap::dirtyUp : valid child input");
@@ -180,7 +170,7 @@ SHAMapTreeNodePtr
SHAMap::fetchNodeFromDB(SHAMapHash const& hash) const
{
XRPL_ASSERT(backed_, "xrpl::SHAMap::fetchNodeFromDB : is backed");
auto const obj = f_.db().fetchNodeObject(hash.asUInt256(), ledgerSeq());
auto const obj = f_.db().fetchNodeObject(hash.asUInt256(), ledgerSeq_);
return finishFetch(hash, obj);
}
@@ -193,17 +183,10 @@ SHAMap::finishFetch(SHAMapHash const& hash, std::shared_ptr<NodeObject> const& o
{
if (!object)
{
// A missing node disproves full_, so withdraw it and report the gap. The exchange
// makes exactly one reader report it, and its acquire pairs with the release store in
// setFull(). It needs no release: the only other reader of full_ is this same path,
// and a reader that loses the exchange does nothing with what the winner did. The load
// ahead of it is relaxed, since all it decides is whether to try the exchange: a stale
// true costs one exchange that loses, and a stale false means another reader won, or
// setFull() is not visible yet and the gap goes unreported.
if (full_.load(std::memory_order_relaxed) &&
full_.exchange(false, std::memory_order_acquire))
if (full_)
{
f_.missingNodeAcquireBySeq(ledgerSeq(), hash.asUInt256());
full_ = false;
f_.missingNodeAcquireBySeq(ledgerSeq_, hash.asUInt256());
}
return {};
}
@@ -236,7 +219,7 @@ SHAMap::checkFilter(SHAMapHash const& hash, SHAMapSyncFilter const* filter) cons
auto node = SHAMapTreeNode::makeFromPrefix(makeSlice(*nodeData), hash);
if (node)
{
filter->gotNode(true, hash, ledgerSeq(), std::move(*nodeData), node->getType());
filter->gotNode(true, hash, ledgerSeq_, std::move(*nodeData), node->getType());
if (backed_)
canonicalize(hash, node);
}
@@ -416,7 +399,7 @@ SHAMap::descendAsync(
{
f_.db().asyncFetch(
hash.asUInt256(),
ledgerSeq(),
ledgerSeq_,
[this, hash, cb{std::move(callback)}](std::shared_ptr<NodeObject> const& object) {
auto const node = finishFetch(hash, object);
cb(node, hash);
@@ -441,7 +424,7 @@ SHAMap::unshareNode(intr_ptr::SharedPtr<Node> node, SHAMapNodeID const& nodeID)
if (node->cowid() != cowid_)
{
// have a CoW
XRPL_ASSERT(state() != SHAMapState::Immutable, "xrpl::SHAMap::unshareNode : not immutable");
XRPL_ASSERT(state_ != SHAMapState::Immutable, "xrpl::SHAMap::unshareNode : not immutable");
node = intr_ptr::staticPointerCast<Node>(node->clone(cowid_));
if (nodeID.isRoot())
root_ = node;
@@ -654,7 +637,7 @@ bool
SHAMap::delItem(UInt256 const& id)
{
// delete the item with this ID
XRPL_ASSERT(state() != SHAMapState::Immutable, "xrpl::SHAMap::delItem : not immutable");
XRPL_ASSERT(state_ != SHAMapState::Immutable, "xrpl::SHAMap::delItem : not immutable");
NodePathStack stack;
walkTowardsKey(id, &stack);
@@ -736,7 +719,7 @@ SHAMap::delItem(UInt256 const& id)
bool
SHAMap::addGiveItem(SHAMapNodeType type, boost::intrusive_ptr<SHAMapItem const> item)
{
XRPL_ASSERT(state() != SHAMapState::Immutable, "xrpl::SHAMap::addGiveItem : not immutable");
XRPL_ASSERT(state_ != SHAMapState::Immutable, "xrpl::SHAMap::addGiveItem : not immutable");
XRPL_ASSERT(type != SHAMapNodeType::TnInner, "xrpl::SHAMap::addGiveItem : valid type input");
// add the specified item, does not update
@@ -827,7 +810,7 @@ SHAMap::updateGiveItem(SHAMapNodeType type, boost::intrusive_ptr<SHAMapItem cons
// can't change the tag but can change the hash
UInt256 const tag = item->key();
XRPL_ASSERT(state() != SHAMapState::Immutable, "xrpl::SHAMap::updateGiveItem : not immutable");
XRPL_ASSERT(state_ != SHAMapState::Immutable, "xrpl::SHAMap::updateGiveItem : not immutable");
NodePathStack stack;
walkTowardsKey(tag, &stack);
@@ -918,7 +901,7 @@ SHAMap::writeNode(NodeObjectType t, SHAMapTreeNodePtr node) const
Serializer s;
node->serializeWithPrefix(s);
f_.db().store(t, std::move(s.modData()), node->getHash().asUInt256(), ledgerSeq());
f_.db().store(t, std::move(s.modData()), node->getHash().asUInt256(), ledgerSeq_);
return node;
}
+1 -4
View File
@@ -16,7 +16,6 @@
#include <xrpl/shamap/detail/TaggedPointer.h>
#include <xrpl/shamap/detail/TaggedPointer.ipp>
#include <atomic>
#include <cstddef>
#include <cstdint>
#include <mutex>
@@ -78,9 +77,7 @@ SHAMapInnerNode::clone(std::uint32_t cowid) const
auto p = intr_ptr::makeShared<SHAMapInnerNode>(cowid, branchCount);
p->hash_ = hash_;
p->isBranch_ = isBranch_;
// Relaxed, as everywhere. p is not reachable by another thread until this returns.
p->fullBelowGen_.store(
fullBelowGen_.load(std::memory_order_relaxed), std::memory_order_relaxed);
p->fullBelowGen_ = fullBelowGen_;
SHAMapHash* cloneHashes = nullptr;
SHAMapHash* thisHashes = nullptr;
SHAMapTreeNodePtr* cloneChildren = nullptr;
+40 -112
View File
@@ -31,25 +31,6 @@
namespace xrpl {
namespace {
/**
* Whether a depth is at or past the deepest an inner node may occupy.
*
* Nibbles run out at SHAMap::kLeafDepth, so only a leaf may sit there. True for
* every deeper position too, which lies past the end of a key.
*
* @param depth The depth to judge.
* @return Whether an inner node at that depth makes the map impossible.
*/
[[nodiscard]] bool
isLeafDepth(unsigned int depth)
{
return depth >= SHAMap::kLeafDepth;
}
} // namespace
void
SHAMap::visitLeaves(
std::function<void(boost::intrusive_ptr<SHAMapItem const> const& item)> const& leafFunction)
@@ -162,10 +143,13 @@ SHAMap::visitDifferences(
if (!function(*node))
return;
// Only a leaf belongs at kLeafDepth. The node is still reported, since the wire form
// carries no depth and the recipient hooks blobs by hash. Its children are skipped, since
// getChildNodeID has no answer past kLeafDepth.
if (isLeafDepth(nodeID.getDepth()))
// Nibbles run out at kLeafDepth, so only a leaf belongs there. A well-formed map never
// holds an inner node at that depth: addKnownNode marks the map invalid rather than hooking
// one in, and fetch-pack data is hash-verified against a validated root, so reaching this
// means a defect or a corrupt store, not something a peer can provoke. Report the node
// anyway - the wire form carries no depth, and the recipient hooks blobs in by hash - but
// skip the children rather than letting getChildNodeID throw on them.
if (nodeID.getDepth() >= kLeafDepth)
{
// LCOV_EXCL_START
UNREACHABLE("xrpl::SHAMap::visitDifferences : inner node at leaf depth");
@@ -223,12 +207,7 @@ SHAMap::gmnProcessNodes(MissingNodes& mn, MissingNodes::StackEntry& se)
// we already know this child node is missing
fullBelow = false;
}
// The depth test precedes the cache lookup for the same reason it does in addKnownNode():
// the cache is keyed by node hash and shared across maps, and a hash covers a node's
// children but not its depth. Skipping the shortcut forgoes an optimization only.
else if (
!backed_ || isLeafDepth(nodeID.getDepth() + 1) ||
!f_.getFullBelowCache()->touchIfExists(childHash.asUInt256()))
else if (!backed_ || !f_.getFullBelowCache()->touchIfExists(childHash.asUInt256()))
{
bool pending = false;
auto d = descendAsync(
@@ -259,17 +238,6 @@ SHAMap::gmnProcessNodes(MissingNodes& mn, MissingNodes::StackEntry& se)
if (--mn.max <= 0)
return;
}
else if (d->isInner() && isLeafDepth(nodeID.getDepth() + 1))
{
// Only a leaf belongs that deep (see isLeafDepth and SHAMap::addKnownNode). A node
// resolved locally reaches the walk without passing through addKnownNode(), so the
// walk reaches this verdict itself. Ordered ahead of the full-below test below,
// which canonicalization shares across maps.
JLOG(journal_.warn()) << "Inner node at branch " << branch << " below " << nodeID
<< " makes the map invalid";
setInvalid();
return;
}
else if (d->isInner() && !safeDowncast<SHAMapInnerNode*>(d)->isFullBelow(mn.generation))
{
mn.stack.push(se);
@@ -333,8 +301,6 @@ SHAMap::gmnProcessDeferredReads(MissingNodes& mn)
}
else if ((mn.max > 0) && mn.missingHashes.insert(nodeHash).second)
{
// getChildNodeID() is safe here: gmnProcessNodes refuses to descend into an inner node
// at kLeafDepth, so a deferred parent sits at most one level above it.
mn.missingNodes.emplace_back(parentID.getChildNodeID(branch), nodeHash.asUInt256());
--mn.max;
}
@@ -345,20 +311,17 @@ SHAMap::gmnProcessDeferredReads(MissingNodes& mn)
mn.deferred = 0;
}
/**
* Get a list of node IDs and hashes for nodes that are part of this SHAMap
* but not available locally. The filter can hold alternate sources of
* nodes that are not permanently stored locally
*/
std::vector<std::pair<SHAMapNodeID, UInt256>>
SHAMap::getMissingNodes(int max, SHAMapSyncFilter const* filter)
{
XRPL_ASSERT(root_->getHash().isNonZero(), "xrpl::SHAMap::getMissingNodes : nonzero root hash");
XRPL_ASSERT(max > 0, "xrpl::SHAMap::getMissingNodes : valid max input");
if (!isValid())
{
// The root node's own hash, since getHash() unshares the tree on a zero hash.
JLOG(journal_.warn()) << "getMissingNodes called on an invalid map, root hash "
<< root_->getHash() << " seq " << ledgerSeq();
return {};
}
MissingNodes mn(
max,
filter,
@@ -391,11 +354,6 @@ SHAMap::getMissingNodes(int max, SHAMapSyncFilter const* filter)
{
gmnProcessNodes(mn, pos);
// The walk just invalidated the map. The loop stops descending here but falls through
// to the drain below, since every posted read must be drained while `mn` is alive.
if (!isValid())
break;
if (mn.max <= 0)
break;
@@ -425,11 +383,6 @@ SHAMap::getMissingNodes(int max, SHAMapSyncFilter const* filter)
if (mn.deferred != 0)
gmnProcessDeferredReads(mn);
// Reads are drained, so the map can be abandoned. What was collected belongs to a tree
// that cannot exist.
if (!isValid())
return {};
if (mn.max <= 0)
return std::move(mn.missingNodes);
@@ -463,11 +416,6 @@ SHAMap::getMissingNodes(int max, SHAMapSyncFilter const* filter)
} while (node != nullptr);
// addKnownNode() on another thread can write the verdict after the loop's own test, so the map
// is judged once more before the result is returned.
if (!isValid())
return {}; // LCOV_EXCL_LINE: only that other thread reaches this, so no test does
if (mn.missingNodes.empty())
clearSynching();
@@ -579,19 +527,11 @@ SHAMap::addRootNode(
XRPL_ASSERT(cowid_ >= 1, "xrpl::SHAMap::addRootNode : valid cowid");
XRPL_ASSERT(rootNode, "xrpl::SHAMap::addRootNode : non-null root node");
// A map syncs against one hash and installs a root once, so a root already held is a duplicate
// only once it hashes to the hash asked for.
// we already have a root_ node
if (root_->getHash().isNonZero())
{
JLOG(journal_.trace()) << "Got root node, already have one";
if (root_->getHash() != hash)
{
JLOG(journal_.warn()) << "Root node offered under hash " << hash
<< ", but the map holds " << root_->getHash();
return SHAMapAddNode::invalid();
}
XRPL_ASSERT(root_->getHash() == hash, "xrpl::SHAMap::addRootNode : valid hash");
return SHAMapAddNode::duplicate();
}
@@ -615,7 +555,7 @@ SHAMap::addRootNode(
Serializer s;
root_->serializeWithPrefix(s);
filter->gotNode(
false, root_->getHash(), ledgerSeq(), std::move(s.modData()), root_->getType());
false, root_->getHash(), ledgerSeq_, std::move(s.modData()), root_->getType());
}
return SHAMapAddNode::useful();
@@ -658,13 +598,7 @@ SHAMap::addKnownNode(
}
auto childHash = inner->getChildHash(branch);
// Depth before the cache: the cache is keyed by node hash and shared across the family, and
// a hash covers a node's children but not its depth, so the same subtree can be cached as
// complete at one depth and reached at another. Skipping the shortcut only forgoes an
// optimization.
if (!isLeafDepth(currNodeID.getDepth() + 1) &&
f_.getFullBelowCache()->touchIfExists(childHash.asUInt256()))
if (f_.getFullBelowCache()->touchIfExists(childHash.asUInt256()))
{
return SHAMapAddNode::duplicate();
}
@@ -682,32 +616,25 @@ SHAMap::addKnownNode(
return SHAMapAddNode::invalid();
}
// Only leaves may sit at kLeafDepth (see isLeafDepth), so an inner node there makes the map
// impossible. The node is reported as bad data.
//
// Every node from the root down hash-verified to get here, so it is the requested root hash
// itself that commits to a shape no valid tree can have. The verdict belongs to that hash
// rather than to our copy of the tree.
bool const badDepth = treeNode->isInner() && isLeafDepth(currNodeID.getDepth());
SOMETIMES(badDepth, "xrpl::SHAMap::addKnownNode : map is invalid");
if (badDepth)
// Inner nodes must be at a level strictly less than 64
// but leaf nodes (while notionally at level 64) can be
// at any depth up to and including 64:
if ((currNodeID.getDepth() > kLeafDepth) ||
(treeNode->isInner() && currNodeID.getDepth() == kLeafDepth))
{
JLOG(journal_.warn()) << "Node " << nodeID << " makes the map invalid at "
<< currNodeID;
setInvalid();
return SHAMapAddNode::invalid();
// Map is provably invalid
state_ = SHAMapState::Invalid;
return SHAMapAddNode::useful();
}
// The data hashes to the child at currNodeID but claims to belong at nodeID, so it is not
// the node that was asked for. Only the label is wrong, so the map stays sound and the node
// is still obtainable from another sender.
bool const badPosition = (currNodeID != nodeID);
SOMETIMES(badPosition, "xrpl::SHAMap::addKnownNode : node ID does not match its position");
if (badPosition)
if (currNodeID != nodeID)
{
JLOG(journal_.warn()) << "Unable to hook node " << nodeID << ", stuck at "
<< currNodeID;
return SHAMapAddNode::invalid();
// Either this node is broken or we didn't request it (yet)
JLOG(journal_.warn()) << "unable to hook node " << nodeID;
JLOG(journal_.info()) << " stuck at " << currNodeID;
JLOG(journal_.info()) << "got depth=" << nodeID.getDepth()
<< ", walked to= " << currNodeID.getDepth();
return SHAMapAddNode::useful();
}
if (backed_)
@@ -720,7 +647,7 @@ SHAMap::addKnownNode(
Serializer s;
treeNode->serializeWithPrefix(s);
filter->gotNode(
false, childHash, ledgerSeq(), std::move(s.modData()), treeNode->getType());
false, childHash, ledgerSeq_, std::move(s.modData()), treeNode->getType());
}
return SHAMapAddNode::useful();
@@ -839,7 +766,7 @@ SHAMap::hasLeafNode(UInt256 const& tag, SHAMapHash const& targetNodeHash) const
// Same kLeafDepth hazard as in visitDifferences above. That guard bounds the caller's own
// traversal, not the map queried here, and the loop below descends from this map's root
// independently, so this check is what keeps a malformed map from reaching getChildNodeID.
if (isLeafDepth(nodeID.getDepth()))
if (nodeID.getDepth() >= kLeafDepth)
{
// LCOV_EXCL_START
UNREACHABLE("xrpl::SHAMap::hasLeafNode : inner node at leaf depth");
@@ -922,8 +849,8 @@ SHAMap::verifyProofPath(UInt256 const& rootHash, UInt256 const& key, std::vector
// there. These nodes come off the wire, so a peer can still claim an inner one;
// reject it rather than passing this depth to selectBranch.
SOMETIMES(
isLeafDepth(depth), "xrpl::SHAMap::verifyProofPath : inner at leaf depth");
if (isLeafDepth(depth))
depth >= kLeafDepth, "xrpl::SHAMap::verifyProofPath : inner at leaf depth");
if (depth >= kLeafDepth)
return false;
auto const nodeId = SHAMapNodeID::createID(depth, key);
@@ -932,9 +859,10 @@ SHAMap::verifyProofPath(UInt256 const& rootHash, UInt256 const& key, std::vector
}
else
{
// The hash chain up to rootHash proves this leaf sits where the path claims. Any
// leaf whose subtree hashes the same at every level above satisfies that chain, so
// the terminal leaf's own key is what ties the proof to `key`.
// The hash chain up to rootHash only proves this leaf sits where the path claims,
// not that it is the leaf for `key`: a peer could substitute any other leaf whose
// subtree hashes to the same value at every level above it. Checking the terminal
// leaf's own key is what ties the proof to `key` specifically.
if (leafKey(*node) != key)
return false;
+2 -35
View File
@@ -16,7 +16,6 @@
#include <xrpl/ledger/helpers/DelegateHelpers.h>
#include <xrpl/ledger/helpers/NFTokenHelpers.h>
#include <xrpl/ledger/helpers/OfferHelpers.h>
#include <xrpl/ledger/helpers/ProposalHelpers.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/protocol/AccountID.h>
@@ -328,9 +327,10 @@ Transactor::preflight2(PreflightContext const& ctx)
auto const sigValid = checkValidity(ctx.registry.get().getHashRouter(), ctx.tx, ctx.rules);
if (sigValid.first == Validity::SigBad)
{
{ // LCOV_EXCL_START
JLOG(ctx.j.debug()) << "preflight2: bad signature. " << sigValid.second;
return temINVALID;
// LCOV_EXCL_STOP
}
// Do not add any checks after this point that are relevant for
@@ -755,19 +755,6 @@ Transactor::checkSeqProxy(ReadView const& view, STTx const& tx, beast::Journal j
<< "a_seq=" << aSeq << " t_seq=" << tSeqProx;
return tefNO_TICKET;
}
// While a TransactionProposal keyed to this Ticket exists, the
// Ticket is reserved: only the proposal's own proposed transaction
// may consume it, so unrelated activity cannot invalidate the
// proposal while signatures are being collected (On-Chain Cosigner spec §4.2.1).
// Cancelling the proposal frees the Ticket, so this is a retry, not
// a final failure. No proposal can exist before the Cosign amendment,
// so the gate only makes that dependency explicit.
if (view.rules().enabled(featureCosign) && !proposal::canConsumeTicket(view, tx))
{
JLOG(j.trace()) << "applyTransaction: ticket " << tSeqProx << " is reserved";
return terTICKET_RESERVED;
}
}
return tesSUCCESS;
@@ -879,28 +866,8 @@ Transactor::ticketDelete(
// Update the Ticket owner's reserve.
decreaseOwnerCountForObject(view, sleAccount, sleTicket, 1, j);
std::uint32_t const ticketSeq{(*sleTicket)[sfTicketSequence]};
// Remove Ticket from ledger.
view.erase(sleTicket);
// Once the Ticket is gone, a TransactionProposal keyed to it can never
// execute: its proposed transaction would fail with tefNO_TICKET. Clean
// up the stale proposal and release its Owner's reserve (On-Chain Cosigner spec §4.5).
// The reservation check in checkSeqProxy means this is reached only by
// the proposal's own proposed transaction executing (or failing with a
// claimed-fee tec), or by AccountDelete sweeping the target account's
// Tickets. No proposal can exist before the Cosign amendment, so the gate
// only makes that dependency explicit.
if (view.rules().enabled(featureCosign))
{
if (auto const sleProposal = view.peek(keylet::txProposal(account, ticketSeq)))
{
if (TER const ter = proposal::deleteProposal(view, sleProposal, j); !isTesSuccess(ter))
return ter; // LCOV_EXCL_LINE
}
}
return tesSUCCESS;
}
Loaded 100 of 297 files, more files were not shown because too many files have changed in this diff. Show more