mirror of
https://github.com/XRPLF/rippled.git
synced 2026-06-25 11:36:53 +00:00
Compare commits
1 Commits
a1q123456/
...
dangell7/s
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc8a689a20 |
2
.github/actions/build-deps/action.yml
vendored
2
.github/actions/build-deps/action.yml
vendored
@@ -30,4 +30,4 @@ runs:
|
||||
--options:host '&:tests=True' \
|
||||
--options:host '&:xrpld=True' \
|
||||
--settings:all build_type=${{ inputs.build_type }} \
|
||||
..
|
||||
--format=json ..
|
||||
|
||||
4
.github/scripts/strategy-matrix/generate.py
vendored
4
.github/scripts/strategy-matrix/generate.py
vendored
@@ -45,7 +45,7 @@ def generate_strategy_matrix(all: bool, config: Config) -> list:
|
||||
# Only generate a subset of configurations in PRs.
|
||||
if not all:
|
||||
# Debian:
|
||||
# - Bookworm using GCC 13: Release and Unity on linux/amd64, set
|
||||
# - Bookworm using GCC 13: Release and Unity on linux/arm64, set
|
||||
# the reference fee to 500.
|
||||
# - Bookworm using GCC 15: Debug and no Unity on linux/amd64, enable
|
||||
# code coverage (which will be done below).
|
||||
@@ -57,7 +57,7 @@ def generate_strategy_matrix(all: bool, config: Config) -> list:
|
||||
if os['distro_name'] == 'debian':
|
||||
skip = True
|
||||
if os['distro_version'] == 'bookworm':
|
||||
if f'{os['compiler_name']}-{os['compiler_version']}' == 'gcc-13' and build_type == 'Release' and '-Dunity=ON' in cmake_args and architecture['platform'] == 'linux/amd64':
|
||||
if f'{os['compiler_name']}-{os['compiler_version']}' == 'gcc-13' and build_type == 'Release' and '-Dunity=ON' in cmake_args and architecture['platform'] == 'linux/arm64':
|
||||
cmake_args = f'-DUNIT_TEST_REFERENCE_FEE=500 {cmake_args}'
|
||||
skip = False
|
||||
if f'{os['compiler_name']}-{os['compiler_version']}' == 'gcc-15' and build_type == 'Debug' and '-Dunity=OFF' in cmake_args and architecture['platform'] == 'linux/amd64':
|
||||
|
||||
44
.github/workflows/check-format.yml
vendored
Normal file
44
.github/workflows/check-format.yml
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
# This workflow checks if the code is properly formatted.
|
||||
name: Check format
|
||||
|
||||
# This workflow can only be triggered by other workflows.
|
||||
on: workflow_call
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}-format
|
||||
cancel-in-progress: true
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
pre-commit:
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/xrplf/ci/tools-rippled-pre-commit
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/.github/actions/prepare-runner@638e0dc11ea230f91bd26622fb542116bb5254d5
|
||||
- name: Format code
|
||||
run: pre-commit run --show-diff-on-failure --color=always --all-files
|
||||
- name: Check for differences
|
||||
env:
|
||||
MESSAGE: |
|
||||
One or more files did not conform to the formatting. Maybe you did
|
||||
not run 'pre-commit' before committing, or your version of
|
||||
'clang-format' or 'prettier' has an incompatibility with the ones
|
||||
used here (see the "Check configuration" step above).
|
||||
|
||||
Run 'pre-commit run --all-files' in your repo, and then commit and
|
||||
push the changes.
|
||||
run: |
|
||||
DIFF=$(git status --porcelain)
|
||||
if [ -n "${DIFF}" ]; then
|
||||
# Print the files that changed to give the contributor a hint about
|
||||
# what to expect when running pre-commit on their own machine.
|
||||
git status
|
||||
echo "${MESSAGE}"
|
||||
exit 1
|
||||
fi
|
||||
11
.github/workflows/on-pr.yml
vendored
11
.github/workflows/on-pr.yml
vendored
@@ -50,17 +50,18 @@ jobs:
|
||||
files: |
|
||||
# These paths are unique to `on-pr.yml`.
|
||||
.github/scripts/levelization/**
|
||||
.github/workflows/check-format.yml
|
||||
.github/workflows/check-levelization.yml
|
||||
.github/workflows/notify-clio.yml
|
||||
.github/workflows/on-pr.yml
|
||||
.clang-format
|
||||
.pre-commit-config.yaml
|
||||
|
||||
# Keep the paths below in sync with those in `on-trigger.yml`.
|
||||
.github/actions/build-deps/**
|
||||
.github/actions/build-test/**
|
||||
.github/actions/setup-conan/**
|
||||
.github/scripts/strategy-matrix/**
|
||||
.github/workflows/build-test.yml
|
||||
.github/workflows/reusable-strategy-matrix.yml
|
||||
.codecov.yml
|
||||
cmake/**
|
||||
conan/**
|
||||
@@ -90,6 +91,11 @@ jobs:
|
||||
outputs:
|
||||
go: ${{ steps.go.outputs.go == 'true' }}
|
||||
|
||||
check-format:
|
||||
needs: should-run
|
||||
if: needs.should-run.outputs.go == 'true'
|
||||
uses: ./.github/workflows/check-format.yml
|
||||
|
||||
check-levelization:
|
||||
needs: should-run
|
||||
if: needs.should-run.outputs.go == 'true'
|
||||
@@ -122,6 +128,7 @@ jobs:
|
||||
if: failure() || cancelled()
|
||||
needs:
|
||||
- build-test
|
||||
- check-format
|
||||
- check-levelization
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
2
.github/workflows/on-trigger.yml
vendored
2
.github/workflows/on-trigger.yml
vendored
@@ -21,10 +21,8 @@ on:
|
||||
# Keep the paths below in sync with those in `on-pr.yml`.
|
||||
- ".github/actions/build-deps/**"
|
||||
- ".github/actions/build-test/**"
|
||||
- ".github/actions/setup-conan/**"
|
||||
- ".github/scripts/strategy-matrix/**"
|
||||
- ".github/workflows/build-test.yml"
|
||||
- ".github/workflows/reusable-strategy-matrix.yml"
|
||||
- ".codecov.yml"
|
||||
- "cmake/**"
|
||||
- "conan/**"
|
||||
|
||||
14
.github/workflows/pre-commit.yml
vendored
14
.github/workflows/pre-commit.yml
vendored
@@ -1,14 +0,0 @@
|
||||
name: Run pre-commit hooks
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches: [develop, release, master]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
run-hooks:
|
||||
uses: XRPLF/actions/.github/workflows/pre-commit.yml@af1b0f0d764cda2e5435f5ac97b240d4bd4d95d3
|
||||
with:
|
||||
runs_on: ubuntu-latest
|
||||
container: '{ "image": "ghcr.io/xrplf/ci/tools-rippled-pre-commit" }'
|
||||
1
.github/workflows/upload-conan-deps.yml
vendored
1
.github/workflows/upload-conan-deps.yml
vendored
@@ -28,7 +28,6 @@ on:
|
||||
- .github/workflows/reusable-strategy-matrix.yml
|
||||
|
||||
- .github/actions/build-deps/action.yml
|
||||
- .github/actions/setup-conan/action.yml
|
||||
- ".github/scripts/strategy-matrix/**"
|
||||
|
||||
- conanfile.py
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -111,6 +111,3 @@ bld.rippled/
|
||||
|
||||
# Suggested in-tree build directory
|
||||
/.build*/
|
||||
|
||||
# Rust
|
||||
external/*/target
|
||||
|
||||
21
BUILD.md
21
BUILD.md
@@ -132,7 +132,7 @@ higher index than the default Conan Center remote, so it is consulted first. You
|
||||
can do this by running:
|
||||
|
||||
```bash
|
||||
conan remote add --index 0 xrplf https://conan.ripplex.io
|
||||
conan remote add --index 0 xrplf "https://conan.ripplex.io"
|
||||
```
|
||||
|
||||
Alternatively, you can pull the patched recipes into the repository and use them
|
||||
@@ -479,24 +479,12 @@ It is implicitly used when running `conan` commands, you don't need to specify i
|
||||
|
||||
You have to update this file every time you add a new dependency or change a revision or version of an existing dependency.
|
||||
|
||||
> [!NOTE]
|
||||
> Conan uses local cache by default when creating a lockfile.
|
||||
>
|
||||
> To ensure, that lockfile creation works the same way on all developer machines, you should clear the local cache before creating a new lockfile.
|
||||
|
||||
To create a new lockfile, run the following commands in the repository root:
|
||||
To do that, run the following command in the repository root:
|
||||
|
||||
```bash
|
||||
conan remove '*' --confirm
|
||||
rm conan.lock
|
||||
# This ensure that xrplf remote is the first to be consulted
|
||||
conan remote add --force --index 0 xrplf https://conan.ripplex.io
|
||||
conan lock create . -o '&:jemalloc=True' -o '&:rocksdb=True'
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> If some dependencies are exclusive for some OS, you may need to run the last command for them adding `--profile:all <PROFILE>`.
|
||||
|
||||
## Coverage report
|
||||
|
||||
The coverage report is intended for developers using compilers GCC
|
||||
@@ -598,11 +586,6 @@ After any updates or changes to dependencies, you may need to do the following:
|
||||
4. [Regenerate lockfile](#conan-lockfile).
|
||||
5. Re-run [conan install](#build-and-test).
|
||||
|
||||
#### ERROR: Package not resolved
|
||||
|
||||
If you're seeing an error like `ERROR: Package 'snappy/1.1.10' not resolved: Unable to find 'snappy/1.1.10#968fef506ff261592ec30c574d4a7809%1756234314.246' in remotes.`,
|
||||
please add `xrplf` remote or re-run `conan export` for [patched recipes](#patched-recipes).
|
||||
|
||||
### `protobuf/port_def.inc` file not found
|
||||
|
||||
If `cmake --build .` results in an error due to a missing a protobuf file, then
|
||||
|
||||
@@ -96,7 +96,6 @@ set(SECP256K1_BUILD_EXHAUSTIVE_TESTS FALSE)
|
||||
set(SECP256K1_BUILD_CTIME_TESTS FALSE)
|
||||
set(SECP256K1_BUILD_EXAMPLES FALSE)
|
||||
add_subdirectory(external/secp256k1)
|
||||
add_subdirectory(external/rust-test)
|
||||
add_library(secp256k1::secp256k1 ALIAS secp256k1)
|
||||
add_subdirectory(external/ed25519-donna)
|
||||
add_subdirectory(external/antithesis-sdk)
|
||||
@@ -130,7 +129,6 @@ target_link_libraries(ripple_libs INTERFACE
|
||||
secp256k1::secp256k1
|
||||
soci::soci
|
||||
SQLite::SQLite3
|
||||
RustTest
|
||||
)
|
||||
|
||||
# Work around changes to Conan recipe for now.
|
||||
|
||||
15
external/rust-test/CMakeLists.txt
vendored
15
external/rust-test/CMakeLists.txt
vendored
@@ -1,15 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.15)
|
||||
project(RustTest)
|
||||
|
||||
set(CMAKE_INSTALL_CMAKEDIR "lib/cmake/RustTest")
|
||||
|
||||
execute_process(COMMAND cargo run --bin install -- --release WORKING_DIRECTORY ${PROJECT_SOURCE_DIR})
|
||||
|
||||
set(INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/target/install/release/include/")
|
||||
|
||||
add_library(RustTest STATIC IMPORTED GLOBAL)
|
||||
|
||||
set_target_properties(RustTest PROPERTIES
|
||||
IMPORTED_LOCATION "${CMAKE_CURRENT_SOURCE_DIR}/target/install/release/lib/RustTest.a"
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_CURRENT_SOURCE_DIR}/target/install/release/include/"
|
||||
)
|
||||
568
external/rust-test/Cargo.lock
generated
vendored
568
external/rust-test/Cargo.lock
generated
vendored
@@ -1,568 +0,0 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "RustTest"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cbindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
"anstyle-query",
|
||||
"anstyle-wincon",
|
||||
"colorchoice",
|
||||
"is_terminal_polyfill",
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-query"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9"
|
||||
dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "3.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"once_cell_polyfill",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
|
||||
|
||||
[[package]]
|
||||
name = "cbindgen"
|
||||
version = "0.29.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "975982cdb7ad6a142be15bdf84aea7ec6a9e5d4d797c004d43185b24cfe4e684"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"heck",
|
||||
"indexmap",
|
||||
"log",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"syn",
|
||||
"tempfile",
|
||||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.41"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be92d32e80243a54711e5d7ce823c35c41c9d929dc4ab58e1276f625841aadf9"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.41"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "707eab41e9622f9139419d573eca0900137718000c517d47da73045f54331c3d"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"strsim",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675"
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.174"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell_polyfill"
|
||||
version = "1.70.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.95"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "r-efi"
|
||||
version = "5.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "1.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.219"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.219"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.141"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.104"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"getrandom",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.8.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.22.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_write",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_write"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.14.2+wasi-0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
|
||||
dependencies = [
|
||||
"wit-bindgen-rt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.59.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.60.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
|
||||
dependencies = [
|
||||
"windows-targets 0.53.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.52.6",
|
||||
"windows_aarch64_msvc 0.52.6",
|
||||
"windows_i686_gnu 0.52.6",
|
||||
"windows_i686_gnullvm 0.52.6",
|
||||
"windows_i686_msvc 0.52.6",
|
||||
"windows_x86_64_gnu 0.52.6",
|
||||
"windows_x86_64_gnullvm 0.52.6",
|
||||
"windows_x86_64_msvc 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.53.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.53.0",
|
||||
"windows_aarch64_msvc 0.53.0",
|
||||
"windows_i686_gnu 0.53.0",
|
||||
"windows_i686_gnullvm 0.53.0",
|
||||
"windows_i686_msvc 0.53.0",
|
||||
"windows_x86_64_gnu 0.53.0",
|
||||
"windows_x86_64_gnullvm 0.53.0",
|
||||
"windows_x86_64_msvc 0.53.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.7.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rt"
|
||||
version = "0.39.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
13
external/rust-test/Cargo.toml
vendored
13
external/rust-test/Cargo.toml
vendored
@@ -1,13 +0,0 @@
|
||||
[package]
|
||||
name = "RustTest"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[lib]
|
||||
name = "test"
|
||||
crate-type = ["staticlib"]
|
||||
|
||||
[build-dependencies]
|
||||
cbindgen = "0.29"
|
||||
|
||||
[dependencies]
|
||||
15
external/rust-test/build.rs
vendored
15
external/rust-test/build.rs
vendored
@@ -1,15 +0,0 @@
|
||||
use std::{env};
|
||||
use std::{path::PathBuf};
|
||||
|
||||
fn main() {
|
||||
let crate_name = env::var("CARGO_PKG_NAME").unwrap();
|
||||
let header_name = format!("{}.h", crate_name);
|
||||
|
||||
let crate_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
|
||||
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
|
||||
let header_path = out_dir.join(header_name);
|
||||
|
||||
cbindgen::generate(&crate_dir)
|
||||
.expect("Unable to generate bindings")
|
||||
.write_to_file(header_path);
|
||||
}
|
||||
54
external/rust-test/src/bin/install.rs
vendored
54
external/rust-test/src/bin/install.rs
vendored
@@ -1,54 +0,0 @@
|
||||
use std::path::PathBuf;
|
||||
use std::process::{Command};
|
||||
use std::{env, fs};
|
||||
|
||||
fn main() {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
// Pass 'build' for the first argument and pass all other
|
||||
// parameters to the cargo command
|
||||
Command::new("cargo")
|
||||
.arg("build")
|
||||
.args(args.iter().skip(1))
|
||||
.status()
|
||||
.expect("failed to run cargo");
|
||||
|
||||
// Determine the build config
|
||||
let build_config = args.iter()
|
||||
.map(|arg| arg.to_lowercase())
|
||||
.filter(|arg| arg == "--release" || arg == "--debug")
|
||||
.next()
|
||||
.unwrap_or("--debug".to_string());
|
||||
|
||||
let config = if build_config == "--debug" { "debug" } else { "release" };
|
||||
|
||||
let crate_name = env::var("CARGO_PKG_NAME").unwrap();
|
||||
let lib_name = format!("{}.a", crate_name);
|
||||
let header_name = format!("{}.h", crate_name);
|
||||
|
||||
let crate_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
|
||||
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
|
||||
let header_out_path = out_dir.join(&header_name);
|
||||
let binary_dir = crate_dir.join(format!("target/{config}"));
|
||||
let lib_path = fs::read_dir(binary_dir)
|
||||
.expect("Failed to find lib files")
|
||||
.filter_map(Result::ok)
|
||||
.map(|entry| entry.path())
|
||||
.find(|path| path.extension().and_then(|s| s.to_str()) == Some("a"))
|
||||
.expect("Can't find a lib file");
|
||||
|
||||
let install_base_dir = crate_dir.join("target").join("install").join(config);
|
||||
let lib_install_dir = install_base_dir.join("lib");
|
||||
let include_install_dir = install_base_dir.join("include");
|
||||
|
||||
let include_install_path = include_install_dir.join(&header_name);
|
||||
let lib_install_path = lib_install_dir.join(&lib_name);
|
||||
|
||||
let _ = std::fs::create_dir_all(&lib_install_dir);
|
||||
let _ = std::fs::create_dir_all(&include_install_dir);
|
||||
|
||||
fs::copy(header_out_path, &include_install_path).expect("Failed to install the header file");
|
||||
fs::copy(lib_path, &lib_install_path)
|
||||
.expect("Failed to install the lib file");
|
||||
|
||||
println!("Installed to {}", install_base_dir.display());
|
||||
}
|
||||
4
external/rust-test/src/lib.rs
vendored
4
external/rust-test/src/lib.rs
vendored
@@ -1,4 +0,0 @@
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn add(a: i32, b: i32) -> i32 {
|
||||
a + b
|
||||
}
|
||||
@@ -349,6 +349,19 @@ permissionedDomain(AccountID const& account, std::uint32_t seq) noexcept;
|
||||
|
||||
Keylet
|
||||
permissionedDomain(uint256 const& domainID) noexcept;
|
||||
|
||||
Keylet
|
||||
subscription(
|
||||
AccountID const& account,
|
||||
AccountID const& dest,
|
||||
std::uint32_t const& seq) noexcept;
|
||||
|
||||
inline Keylet
|
||||
subscription(uint256 const& key) noexcept
|
||||
{
|
||||
return {ltSUBSCRIPTION, key};
|
||||
}
|
||||
|
||||
} // namespace keylet
|
||||
|
||||
// Everything below is deprecated and should be removed in favor of keylets:
|
||||
|
||||
@@ -62,7 +62,6 @@ enum LedgerEntryType : std::uint16_t
|
||||
|
||||
#undef LEDGER_ENTRY
|
||||
#pragma pop_macro("LEDGER_ENTRY")
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
/** A special type, matching any ledger entry type.
|
||||
|
||||
|
||||
@@ -20,8 +20,6 @@
|
||||
#ifndef RIPPLE_PROTOCOL_PERMISSION_H_INCLUDED
|
||||
#define RIPPLE_PROTOCOL_PERMISSION_H_INCLUDED
|
||||
|
||||
#include <xrpl/protocol/Rules.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/TxFormats.h>
|
||||
|
||||
#include <optional>
|
||||
@@ -55,8 +53,6 @@ class Permission
|
||||
private:
|
||||
Permission();
|
||||
|
||||
std::unordered_map<std::uint16_t, uint256> txFeatureMap_;
|
||||
|
||||
std::unordered_map<std::uint16_t, Delegation> delegatableTx_;
|
||||
|
||||
std::unordered_map<std::string, GranularPermissionType>
|
||||
@@ -84,8 +80,7 @@ public:
|
||||
getGranularTxType(GranularPermissionType const& gpType) const;
|
||||
|
||||
bool
|
||||
isDelegatable(std::uint32_t const& permissionValue, Rules const& rules)
|
||||
const;
|
||||
isDelegatable(std::uint32_t const& permissionValue) const;
|
||||
|
||||
// for tx level permission, permission value is equal to tx type plus one
|
||||
uint32_t
|
||||
|
||||
@@ -59,7 +59,7 @@ enum TxType : std::uint16_t
|
||||
#pragma push_macro("TRANSACTION")
|
||||
#undef TRANSACTION
|
||||
|
||||
#define TRANSACTION(tag, value, ...) tag = value,
|
||||
#define TRANSACTION(tag, value, name, delegatable, fields) tag = value,
|
||||
|
||||
#include <xrpl/protocol/detail/transactions.macro>
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
// If you add an amendment here, then do not forget to increment `numFeatures`
|
||||
// in include/xrpl/protocol/Feature.h.
|
||||
|
||||
XRPL_FIX (DelegateV1_1, Supported::no, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(Subscription, Supported::no, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (PriceOracleOrder, Supported::no, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (MPTDeliveredAmount, Supported::no, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (AMMClawbackRounding, Supported::yes, VoteBehavior::DefaultNo)
|
||||
|
||||
@@ -504,5 +504,25 @@ LEDGER_ENTRY(ltVAULT, 0x0084, Vault, vault, ({
|
||||
// no PermissionedDomainID ever (use MPTIssuance.sfDomainID)
|
||||
}))
|
||||
|
||||
/** A ledger object representing a subscription.
|
||||
|
||||
\sa keylet::mptoken
|
||||
*/
|
||||
LEDGER_ENTRY(ltSUBSCRIPTION, 0x0085, Subscription, subscription, ({
|
||||
{sfPreviousTxnID, soeREQUIRED},
|
||||
{sfPreviousTxnLgrSeq, soeREQUIRED},
|
||||
{sfSequence, soeREQUIRED},
|
||||
{sfOwnerNode, soeREQUIRED},
|
||||
{sfAccount, soeREQUIRED},
|
||||
{sfDestination, soeREQUIRED},
|
||||
{sfDestinationTag, soeOPTIONAL},
|
||||
{sfAmount, soeREQUIRED},
|
||||
{sfBalance, soeREQUIRED},
|
||||
{sfFrequency, soeREQUIRED},
|
||||
{sfNextClaimTime, soeREQUIRED},
|
||||
{sfExpiration, soeOPTIONAL},
|
||||
{sfDestinationNode, soeREQUIRED},
|
||||
}))
|
||||
|
||||
#undef EXPAND
|
||||
#undef LEDGER_ENTRY_DUPLICATE
|
||||
|
||||
@@ -114,6 +114,9 @@ TYPED_SFIELD(sfVoteWeight, UINT32, 48)
|
||||
TYPED_SFIELD(sfFirstNFTokenSequence, UINT32, 50)
|
||||
TYPED_SFIELD(sfOracleDocumentID, UINT32, 51)
|
||||
TYPED_SFIELD(sfPermissionValue, UINT32, 52)
|
||||
TYPED_SFIELD(sfFrequency, UINT32, 53)
|
||||
TYPED_SFIELD(sfStartTime, UINT32, 54)
|
||||
TYPED_SFIELD(sfNextClaimTime, UINT32, 55)
|
||||
|
||||
// 64-bit integers (common)
|
||||
TYPED_SFIELD(sfIndexNext, UINT64, 1)
|
||||
@@ -197,6 +200,7 @@ TYPED_SFIELD(sfHookSetTxnID, UINT256, 33)
|
||||
TYPED_SFIELD(sfDomainID, UINT256, 34)
|
||||
TYPED_SFIELD(sfVaultID, UINT256, 35)
|
||||
TYPED_SFIELD(sfParentBatchID, UINT256, 36)
|
||||
TYPED_SFIELD(sfSubscriptionID, UINT256, 37)
|
||||
|
||||
// number (common)
|
||||
TYPED_SFIELD(sfNumber, NUMBER, 1)
|
||||
|
||||
@@ -22,17 +22,14 @@
|
||||
#endif
|
||||
|
||||
/**
|
||||
* TRANSACTION(tag, value, name, delegatable, amendments, fields)
|
||||
* TRANSACTION(tag, value, name, delegatable, fields)
|
||||
*
|
||||
* You must define a transactor class in the `ripple` namespace named `name`,
|
||||
* and include its header in `src/xrpld/app/tx/detail/applySteps.cpp`.
|
||||
*/
|
||||
|
||||
/** This transaction type executes a payment. */
|
||||
TRANSACTION(ttPAYMENT, 0, Payment,
|
||||
Delegation::delegatable,
|
||||
uint256{},
|
||||
({
|
||||
TRANSACTION(ttPAYMENT, 0, Payment, Delegation::delegatable, ({
|
||||
{sfDestination, soeREQUIRED},
|
||||
{sfAmount, soeREQUIRED, soeMPTSupported},
|
||||
{sfSendMax, soeOPTIONAL, soeMPTSupported},
|
||||
@@ -45,10 +42,7 @@ TRANSACTION(ttPAYMENT, 0, Payment,
|
||||
}))
|
||||
|
||||
/** This transaction type creates an escrow object. */
|
||||
TRANSACTION(ttESCROW_CREATE, 1, EscrowCreate,
|
||||
Delegation::delegatable,
|
||||
uint256{},
|
||||
({
|
||||
TRANSACTION(ttESCROW_CREATE, 1, EscrowCreate, Delegation::delegatable, ({
|
||||
{sfDestination, soeREQUIRED},
|
||||
{sfAmount, soeREQUIRED, soeMPTSupported},
|
||||
{sfCondition, soeOPTIONAL},
|
||||
@@ -58,10 +52,7 @@ TRANSACTION(ttESCROW_CREATE, 1, EscrowCreate,
|
||||
}))
|
||||
|
||||
/** This transaction type completes an existing escrow. */
|
||||
TRANSACTION(ttESCROW_FINISH, 2, EscrowFinish,
|
||||
Delegation::delegatable,
|
||||
uint256{},
|
||||
({
|
||||
TRANSACTION(ttESCROW_FINISH, 2, EscrowFinish, Delegation::delegatable, ({
|
||||
{sfOwner, soeREQUIRED},
|
||||
{sfOfferSequence, soeREQUIRED},
|
||||
{sfFulfillment, soeOPTIONAL},
|
||||
@@ -71,10 +62,7 @@ TRANSACTION(ttESCROW_FINISH, 2, EscrowFinish,
|
||||
|
||||
|
||||
/** This transaction type adjusts various account settings. */
|
||||
TRANSACTION(ttACCOUNT_SET, 3, AccountSet,
|
||||
Delegation::notDelegatable,
|
||||
uint256{},
|
||||
({
|
||||
TRANSACTION(ttACCOUNT_SET, 3, AccountSet, Delegation::notDelegatable, ({
|
||||
{sfEmailHash, soeOPTIONAL},
|
||||
{sfWalletLocator, soeOPTIONAL},
|
||||
{sfWalletSize, soeOPTIONAL},
|
||||
@@ -88,29 +76,20 @@ TRANSACTION(ttACCOUNT_SET, 3, AccountSet,
|
||||
}))
|
||||
|
||||
/** This transaction type cancels an existing escrow. */
|
||||
TRANSACTION(ttESCROW_CANCEL, 4, EscrowCancel,
|
||||
Delegation::delegatable,
|
||||
uint256{},
|
||||
({
|
||||
TRANSACTION(ttESCROW_CANCEL, 4, EscrowCancel, Delegation::delegatable, ({
|
||||
{sfOwner, soeREQUIRED},
|
||||
{sfOfferSequence, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** This transaction type sets or clears an account's "regular key". */
|
||||
TRANSACTION(ttREGULAR_KEY_SET, 5, SetRegularKey,
|
||||
Delegation::notDelegatable,
|
||||
uint256{},
|
||||
({
|
||||
TRANSACTION(ttREGULAR_KEY_SET, 5, SetRegularKey, Delegation::notDelegatable, ({
|
||||
{sfRegularKey, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
// 6 deprecated
|
||||
|
||||
/** This transaction type creates an offer to trade one asset for another. */
|
||||
TRANSACTION(ttOFFER_CREATE, 7, OfferCreate,
|
||||
Delegation::delegatable,
|
||||
uint256{},
|
||||
({
|
||||
TRANSACTION(ttOFFER_CREATE, 7, OfferCreate, Delegation::delegatable, ({
|
||||
{sfTakerPays, soeREQUIRED},
|
||||
{sfTakerGets, soeREQUIRED},
|
||||
{sfExpiration, soeOPTIONAL},
|
||||
@@ -119,20 +98,14 @@ TRANSACTION(ttOFFER_CREATE, 7, OfferCreate,
|
||||
}))
|
||||
|
||||
/** This transaction type cancels existing offers to trade one asset for another. */
|
||||
TRANSACTION(ttOFFER_CANCEL, 8, OfferCancel,
|
||||
Delegation::delegatable,
|
||||
uint256{},
|
||||
({
|
||||
TRANSACTION(ttOFFER_CANCEL, 8, OfferCancel, Delegation::delegatable, ({
|
||||
{sfOfferSequence, soeREQUIRED},
|
||||
}))
|
||||
|
||||
// 9 deprecated
|
||||
|
||||
/** This transaction type creates a new set of tickets. */
|
||||
TRANSACTION(ttTICKET_CREATE, 10, TicketCreate,
|
||||
Delegation::delegatable,
|
||||
featureTicketBatch,
|
||||
({
|
||||
TRANSACTION(ttTICKET_CREATE, 10, TicketCreate, Delegation::delegatable, ({
|
||||
{sfTicketCount, soeREQUIRED},
|
||||
}))
|
||||
|
||||
@@ -141,19 +114,13 @@ TRANSACTION(ttTICKET_CREATE, 10, TicketCreate,
|
||||
/** This transaction type modifies the signer list associated with an account. */
|
||||
// The SignerEntries are optional because a SignerList is deleted by
|
||||
// setting the SignerQuorum to zero and omitting SignerEntries.
|
||||
TRANSACTION(ttSIGNER_LIST_SET, 12, SignerListSet,
|
||||
Delegation::notDelegatable,
|
||||
uint256{},
|
||||
({
|
||||
TRANSACTION(ttSIGNER_LIST_SET, 12, SignerListSet, Delegation::notDelegatable, ({
|
||||
{sfSignerQuorum, soeREQUIRED},
|
||||
{sfSignerEntries, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** This transaction type creates a new unidirectional XRP payment channel. */
|
||||
TRANSACTION(ttPAYCHAN_CREATE, 13, PaymentChannelCreate,
|
||||
Delegation::delegatable,
|
||||
uint256{},
|
||||
({
|
||||
TRANSACTION(ttPAYCHAN_CREATE, 13, PaymentChannelCreate, Delegation::delegatable, ({
|
||||
{sfDestination, soeREQUIRED},
|
||||
{sfAmount, soeREQUIRED},
|
||||
{sfSettleDelay, soeREQUIRED},
|
||||
@@ -163,20 +130,14 @@ TRANSACTION(ttPAYCHAN_CREATE, 13, PaymentChannelCreate,
|
||||
}))
|
||||
|
||||
/** This transaction type funds an existing unidirectional XRP payment channel. */
|
||||
TRANSACTION(ttPAYCHAN_FUND, 14, PaymentChannelFund,
|
||||
Delegation::delegatable,
|
||||
uint256{},
|
||||
({
|
||||
TRANSACTION(ttPAYCHAN_FUND, 14, PaymentChannelFund, Delegation::delegatable, ({
|
||||
{sfChannel, soeREQUIRED},
|
||||
{sfAmount, soeREQUIRED},
|
||||
{sfExpiration, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** This transaction type submits a claim against an existing unidirectional payment channel. */
|
||||
TRANSACTION(ttPAYCHAN_CLAIM, 15, PaymentChannelClaim,
|
||||
Delegation::delegatable,
|
||||
uint256{},
|
||||
({
|
||||
TRANSACTION(ttPAYCHAN_CLAIM, 15, PaymentChannelClaim, Delegation::delegatable, ({
|
||||
{sfChannel, soeREQUIRED},
|
||||
{sfAmount, soeOPTIONAL},
|
||||
{sfBalance, soeOPTIONAL},
|
||||
@@ -186,10 +147,7 @@ TRANSACTION(ttPAYCHAN_CLAIM, 15, PaymentChannelClaim,
|
||||
}))
|
||||
|
||||
/** This transaction type creates a new check. */
|
||||
TRANSACTION(ttCHECK_CREATE, 16, CheckCreate,
|
||||
Delegation::delegatable,
|
||||
featureChecks,
|
||||
({
|
||||
TRANSACTION(ttCHECK_CREATE, 16, CheckCreate, Delegation::delegatable, ({
|
||||
{sfDestination, soeREQUIRED},
|
||||
{sfSendMax, soeREQUIRED},
|
||||
{sfExpiration, soeOPTIONAL},
|
||||
@@ -198,28 +156,19 @@ TRANSACTION(ttCHECK_CREATE, 16, CheckCreate,
|
||||
}))
|
||||
|
||||
/** This transaction type cashes an existing check. */
|
||||
TRANSACTION(ttCHECK_CASH, 17, CheckCash,
|
||||
Delegation::delegatable,
|
||||
featureChecks,
|
||||
({
|
||||
TRANSACTION(ttCHECK_CASH, 17, CheckCash, Delegation::delegatable, ({
|
||||
{sfCheckID, soeREQUIRED},
|
||||
{sfAmount, soeOPTIONAL},
|
||||
{sfDeliverMin, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** This transaction type cancels an existing check. */
|
||||
TRANSACTION(ttCHECK_CANCEL, 18, CheckCancel,
|
||||
Delegation::delegatable,
|
||||
featureChecks,
|
||||
({
|
||||
TRANSACTION(ttCHECK_CANCEL, 18, CheckCancel, Delegation::delegatable, ({
|
||||
{sfCheckID, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** This transaction type grants or revokes authorization to transfer funds. */
|
||||
TRANSACTION(ttDEPOSIT_PREAUTH, 19, DepositPreauth,
|
||||
Delegation::delegatable,
|
||||
featureDepositPreauth,
|
||||
({
|
||||
TRANSACTION(ttDEPOSIT_PREAUTH, 19, DepositPreauth, Delegation::delegatable, ({
|
||||
{sfAuthorize, soeOPTIONAL},
|
||||
{sfUnauthorize, soeOPTIONAL},
|
||||
{sfAuthorizeCredentials, soeOPTIONAL},
|
||||
@@ -227,20 +176,14 @@ TRANSACTION(ttDEPOSIT_PREAUTH, 19, DepositPreauth,
|
||||
}))
|
||||
|
||||
/** This transaction type modifies a trustline between two accounts. */
|
||||
TRANSACTION(ttTRUST_SET, 20, TrustSet,
|
||||
Delegation::delegatable,
|
||||
uint256{},
|
||||
({
|
||||
TRANSACTION(ttTRUST_SET, 20, TrustSet, Delegation::delegatable, ({
|
||||
{sfLimitAmount, soeOPTIONAL},
|
||||
{sfQualityIn, soeOPTIONAL},
|
||||
{sfQualityOut, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** This transaction type deletes an existing account. */
|
||||
TRANSACTION(ttACCOUNT_DELETE, 21, AccountDelete,
|
||||
Delegation::notDelegatable,
|
||||
uint256{},
|
||||
({
|
||||
TRANSACTION(ttACCOUNT_DELETE, 21, AccountDelete, Delegation::notDelegatable, ({
|
||||
{sfDestination, soeREQUIRED},
|
||||
{sfDestinationTag, soeOPTIONAL},
|
||||
{sfCredentialIDs, soeOPTIONAL},
|
||||
@@ -249,10 +192,7 @@ TRANSACTION(ttACCOUNT_DELETE, 21, AccountDelete,
|
||||
// 22 reserved
|
||||
|
||||
/** This transaction mints a new NFT. */
|
||||
TRANSACTION(ttNFTOKEN_MINT, 25, NFTokenMint,
|
||||
Delegation::delegatable,
|
||||
featureNonFungibleTokensV1,
|
||||
({
|
||||
TRANSACTION(ttNFTOKEN_MINT, 25, NFTokenMint, Delegation::delegatable, ({
|
||||
{sfNFTokenTaxon, soeREQUIRED},
|
||||
{sfTransferFee, soeOPTIONAL},
|
||||
{sfIssuer, soeOPTIONAL},
|
||||
@@ -263,19 +203,13 @@ TRANSACTION(ttNFTOKEN_MINT, 25, NFTokenMint,
|
||||
}))
|
||||
|
||||
/** This transaction burns (i.e. destroys) an existing NFT. */
|
||||
TRANSACTION(ttNFTOKEN_BURN, 26, NFTokenBurn,
|
||||
Delegation::delegatable,
|
||||
featureNonFungibleTokensV1,
|
||||
({
|
||||
TRANSACTION(ttNFTOKEN_BURN, 26, NFTokenBurn, Delegation::delegatable, ({
|
||||
{sfNFTokenID, soeREQUIRED},
|
||||
{sfOwner, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** This transaction creates a new offer to buy or sell an NFT. */
|
||||
TRANSACTION(ttNFTOKEN_CREATE_OFFER, 27, NFTokenCreateOffer,
|
||||
Delegation::delegatable,
|
||||
featureNonFungibleTokensV1,
|
||||
({
|
||||
TRANSACTION(ttNFTOKEN_CREATE_OFFER, 27, NFTokenCreateOffer, Delegation::delegatable, ({
|
||||
{sfNFTokenID, soeREQUIRED},
|
||||
{sfAmount, soeREQUIRED},
|
||||
{sfDestination, soeOPTIONAL},
|
||||
@@ -284,37 +218,25 @@ TRANSACTION(ttNFTOKEN_CREATE_OFFER, 27, NFTokenCreateOffer,
|
||||
}))
|
||||
|
||||
/** This transaction cancels an existing offer to buy or sell an existing NFT. */
|
||||
TRANSACTION(ttNFTOKEN_CANCEL_OFFER, 28, NFTokenCancelOffer,
|
||||
Delegation::delegatable,
|
||||
featureNonFungibleTokensV1,
|
||||
({
|
||||
TRANSACTION(ttNFTOKEN_CANCEL_OFFER, 28, NFTokenCancelOffer, Delegation::delegatable, ({
|
||||
{sfNFTokenOffers, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** This transaction accepts an existing offer to buy or sell an existing NFT. */
|
||||
TRANSACTION(ttNFTOKEN_ACCEPT_OFFER, 29, NFTokenAcceptOffer,
|
||||
Delegation::delegatable,
|
||||
featureNonFungibleTokensV1,
|
||||
({
|
||||
TRANSACTION(ttNFTOKEN_ACCEPT_OFFER, 29, NFTokenAcceptOffer, Delegation::delegatable, ({
|
||||
{sfNFTokenBuyOffer, soeOPTIONAL},
|
||||
{sfNFTokenSellOffer, soeOPTIONAL},
|
||||
{sfNFTokenBrokerFee, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** This transaction claws back issued tokens. */
|
||||
TRANSACTION(ttCLAWBACK, 30, Clawback,
|
||||
Delegation::delegatable,
|
||||
featureClawback,
|
||||
({
|
||||
TRANSACTION(ttCLAWBACK, 30, Clawback, Delegation::delegatable, ({
|
||||
{sfAmount, soeREQUIRED, soeMPTSupported},
|
||||
{sfHolder, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** This transaction claws back tokens from an AMM pool. */
|
||||
TRANSACTION(ttAMM_CLAWBACK, 31, AMMClawback,
|
||||
Delegation::delegatable,
|
||||
featureAMMClawback,
|
||||
({
|
||||
TRANSACTION(ttAMM_CLAWBACK, 31, AMMClawback, Delegation::delegatable, ({
|
||||
{sfHolder, soeREQUIRED},
|
||||
{sfAsset, soeREQUIRED},
|
||||
{sfAsset2, soeREQUIRED},
|
||||
@@ -322,20 +244,14 @@ TRANSACTION(ttAMM_CLAWBACK, 31, AMMClawback,
|
||||
}))
|
||||
|
||||
/** This transaction type creates an AMM instance */
|
||||
TRANSACTION(ttAMM_CREATE, 35, AMMCreate,
|
||||
Delegation::delegatable,
|
||||
featureAMM,
|
||||
({
|
||||
TRANSACTION(ttAMM_CREATE, 35, AMMCreate, Delegation::delegatable, ({
|
||||
{sfAmount, soeREQUIRED},
|
||||
{sfAmount2, soeREQUIRED},
|
||||
{sfTradingFee, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** This transaction type deposits into an AMM instance */
|
||||
TRANSACTION(ttAMM_DEPOSIT, 36, AMMDeposit,
|
||||
Delegation::delegatable,
|
||||
featureAMM,
|
||||
({
|
||||
TRANSACTION(ttAMM_DEPOSIT, 36, AMMDeposit, Delegation::delegatable, ({
|
||||
{sfAsset, soeREQUIRED},
|
||||
{sfAsset2, soeREQUIRED},
|
||||
{sfAmount, soeOPTIONAL},
|
||||
@@ -346,10 +262,7 @@ TRANSACTION(ttAMM_DEPOSIT, 36, AMMDeposit,
|
||||
}))
|
||||
|
||||
/** This transaction type withdraws from an AMM instance */
|
||||
TRANSACTION(ttAMM_WITHDRAW, 37, AMMWithdraw,
|
||||
Delegation::delegatable,
|
||||
featureAMM,
|
||||
({
|
||||
TRANSACTION(ttAMM_WITHDRAW, 37, AMMWithdraw, Delegation::delegatable, ({
|
||||
{sfAsset, soeREQUIRED},
|
||||
{sfAsset2, soeREQUIRED},
|
||||
{sfAmount, soeOPTIONAL},
|
||||
@@ -359,20 +272,14 @@ TRANSACTION(ttAMM_WITHDRAW, 37, AMMWithdraw,
|
||||
}))
|
||||
|
||||
/** This transaction type votes for the trading fee */
|
||||
TRANSACTION(ttAMM_VOTE, 38, AMMVote,
|
||||
Delegation::delegatable,
|
||||
featureAMM,
|
||||
({
|
||||
TRANSACTION(ttAMM_VOTE, 38, AMMVote, Delegation::delegatable, ({
|
||||
{sfAsset, soeREQUIRED},
|
||||
{sfAsset2, soeREQUIRED},
|
||||
{sfTradingFee, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** This transaction type bids for the auction slot */
|
||||
TRANSACTION(ttAMM_BID, 39, AMMBid,
|
||||
Delegation::delegatable,
|
||||
featureAMM,
|
||||
({
|
||||
TRANSACTION(ttAMM_BID, 39, AMMBid, Delegation::delegatable, ({
|
||||
{sfAsset, soeREQUIRED},
|
||||
{sfAsset2, soeREQUIRED},
|
||||
{sfBidMin, soeOPTIONAL},
|
||||
@@ -381,29 +288,20 @@ TRANSACTION(ttAMM_BID, 39, AMMBid,
|
||||
}))
|
||||
|
||||
/** This transaction type deletes AMM in the empty state */
|
||||
TRANSACTION(ttAMM_DELETE, 40, AMMDelete,
|
||||
Delegation::delegatable,
|
||||
featureAMM,
|
||||
({
|
||||
TRANSACTION(ttAMM_DELETE, 40, AMMDelete, Delegation::delegatable, ({
|
||||
{sfAsset, soeREQUIRED},
|
||||
{sfAsset2, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** This transactions creates a crosschain sequence number */
|
||||
TRANSACTION(ttXCHAIN_CREATE_CLAIM_ID, 41, XChainCreateClaimID,
|
||||
Delegation::delegatable,
|
||||
featureXChainBridge,
|
||||
({
|
||||
TRANSACTION(ttXCHAIN_CREATE_CLAIM_ID, 41, XChainCreateClaimID, Delegation::delegatable, ({
|
||||
{sfXChainBridge, soeREQUIRED},
|
||||
{sfSignatureReward, soeREQUIRED},
|
||||
{sfOtherChainSource, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** This transactions initiates a crosschain transaction */
|
||||
TRANSACTION(ttXCHAIN_COMMIT, 42, XChainCommit,
|
||||
Delegation::delegatable,
|
||||
featureXChainBridge,
|
||||
({
|
||||
TRANSACTION(ttXCHAIN_COMMIT, 42, XChainCommit, Delegation::delegatable, ({
|
||||
{sfXChainBridge, soeREQUIRED},
|
||||
{sfXChainClaimID, soeREQUIRED},
|
||||
{sfAmount, soeREQUIRED},
|
||||
@@ -411,10 +309,7 @@ TRANSACTION(ttXCHAIN_COMMIT, 42, XChainCommit,
|
||||
}))
|
||||
|
||||
/** This transaction completes a crosschain transaction */
|
||||
TRANSACTION(ttXCHAIN_CLAIM, 43, XChainClaim,
|
||||
Delegation::delegatable,
|
||||
featureXChainBridge,
|
||||
({
|
||||
TRANSACTION(ttXCHAIN_CLAIM, 43, XChainClaim, Delegation::delegatable, ({
|
||||
{sfXChainBridge, soeREQUIRED},
|
||||
{sfXChainClaimID, soeREQUIRED},
|
||||
{sfDestination, soeREQUIRED},
|
||||
@@ -423,10 +318,7 @@ TRANSACTION(ttXCHAIN_CLAIM, 43, XChainClaim,
|
||||
}))
|
||||
|
||||
/** This transaction initiates a crosschain account create transaction */
|
||||
TRANSACTION(ttXCHAIN_ACCOUNT_CREATE_COMMIT, 44, XChainAccountCreateCommit,
|
||||
Delegation::delegatable,
|
||||
featureXChainBridge,
|
||||
({
|
||||
TRANSACTION(ttXCHAIN_ACCOUNT_CREATE_COMMIT, 44, XChainAccountCreateCommit, Delegation::delegatable, ({
|
||||
{sfXChainBridge, soeREQUIRED},
|
||||
{sfDestination, soeREQUIRED},
|
||||
{sfAmount, soeREQUIRED},
|
||||
@@ -434,10 +326,7 @@ TRANSACTION(ttXCHAIN_ACCOUNT_CREATE_COMMIT, 44, XChainAccountCreateCommit,
|
||||
}))
|
||||
|
||||
/** This transaction adds an attestation to a claim */
|
||||
TRANSACTION(ttXCHAIN_ADD_CLAIM_ATTESTATION, 45, XChainAddClaimAttestation,
|
||||
Delegation::delegatable,
|
||||
featureXChainBridge,
|
||||
({
|
||||
TRANSACTION(ttXCHAIN_ADD_CLAIM_ATTESTATION, 45, XChainAddClaimAttestation, Delegation::delegatable, ({
|
||||
{sfXChainBridge, soeREQUIRED},
|
||||
|
||||
{sfAttestationSignerAccount, soeREQUIRED},
|
||||
@@ -453,10 +342,7 @@ TRANSACTION(ttXCHAIN_ADD_CLAIM_ATTESTATION, 45, XChainAddClaimAttestation,
|
||||
}))
|
||||
|
||||
/** This transaction adds an attestation to an account */
|
||||
TRANSACTION(ttXCHAIN_ADD_ACCOUNT_CREATE_ATTESTATION, 46, XChainAddAccountCreateAttestation,
|
||||
Delegation::delegatable,
|
||||
featureXChainBridge,
|
||||
({
|
||||
TRANSACTION(ttXCHAIN_ADD_ACCOUNT_CREATE_ATTESTATION, 46, XChainAddAccountCreateAttestation, Delegation::delegatable, ({
|
||||
{sfXChainBridge, soeREQUIRED},
|
||||
|
||||
{sfAttestationSignerAccount, soeREQUIRED},
|
||||
@@ -473,46 +359,31 @@ TRANSACTION(ttXCHAIN_ADD_ACCOUNT_CREATE_ATTESTATION, 46, XChainAddAccountCreateA
|
||||
}))
|
||||
|
||||
/** This transaction modifies a sidechain */
|
||||
TRANSACTION(ttXCHAIN_MODIFY_BRIDGE, 47, XChainModifyBridge,
|
||||
Delegation::delegatable,
|
||||
featureXChainBridge,
|
||||
({
|
||||
TRANSACTION(ttXCHAIN_MODIFY_BRIDGE, 47, XChainModifyBridge, Delegation::delegatable, ({
|
||||
{sfXChainBridge, soeREQUIRED},
|
||||
{sfSignatureReward, soeOPTIONAL},
|
||||
{sfMinAccountCreateAmount, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** This transactions creates a sidechain */
|
||||
TRANSACTION(ttXCHAIN_CREATE_BRIDGE, 48, XChainCreateBridge,
|
||||
Delegation::delegatable,
|
||||
featureXChainBridge,
|
||||
({
|
||||
TRANSACTION(ttXCHAIN_CREATE_BRIDGE, 48, XChainCreateBridge, Delegation::delegatable, ({
|
||||
{sfXChainBridge, soeREQUIRED},
|
||||
{sfSignatureReward, soeREQUIRED},
|
||||
{sfMinAccountCreateAmount, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** This transaction type creates or updates a DID */
|
||||
TRANSACTION(ttDID_SET, 49, DIDSet,
|
||||
Delegation::delegatable,
|
||||
featureDID,
|
||||
({
|
||||
TRANSACTION(ttDID_SET, 49, DIDSet, Delegation::delegatable, ({
|
||||
{sfDIDDocument, soeOPTIONAL},
|
||||
{sfURI, soeOPTIONAL},
|
||||
{sfData, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** This transaction type deletes a DID */
|
||||
TRANSACTION(ttDID_DELETE, 50, DIDDelete,
|
||||
Delegation::delegatable,
|
||||
featureDID,
|
||||
({}))
|
||||
TRANSACTION(ttDID_DELETE, 50, DIDDelete, Delegation::delegatable, ({}))
|
||||
|
||||
/** This transaction type creates an Oracle instance */
|
||||
TRANSACTION(ttORACLE_SET, 51, OracleSet,
|
||||
Delegation::delegatable,
|
||||
featurePriceOracle,
|
||||
({
|
||||
TRANSACTION(ttORACLE_SET, 51, OracleSet, Delegation::delegatable, ({
|
||||
{sfOracleDocumentID, soeREQUIRED},
|
||||
{sfProvider, soeOPTIONAL},
|
||||
{sfURI, soeOPTIONAL},
|
||||
@@ -522,27 +393,18 @@ TRANSACTION(ttORACLE_SET, 51, OracleSet,
|
||||
}))
|
||||
|
||||
/** This transaction type deletes an Oracle instance */
|
||||
TRANSACTION(ttORACLE_DELETE, 52, OracleDelete,
|
||||
Delegation::delegatable,
|
||||
featurePriceOracle,
|
||||
({
|
||||
TRANSACTION(ttORACLE_DELETE, 52, OracleDelete, Delegation::delegatable, ({
|
||||
{sfOracleDocumentID, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** This transaction type fixes a problem in the ledger state */
|
||||
TRANSACTION(ttLEDGER_STATE_FIX, 53, LedgerStateFix,
|
||||
Delegation::delegatable,
|
||||
fixNFTokenPageLinks,
|
||||
({
|
||||
TRANSACTION(ttLEDGER_STATE_FIX, 53, LedgerStateFix, Delegation::delegatable, ({
|
||||
{sfLedgerFixType, soeREQUIRED},
|
||||
{sfOwner, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** This transaction type creates a MPTokensIssuance instance */
|
||||
TRANSACTION(ttMPTOKEN_ISSUANCE_CREATE, 54, MPTokenIssuanceCreate,
|
||||
Delegation::delegatable,
|
||||
featureMPTokensV1,
|
||||
({
|
||||
TRANSACTION(ttMPTOKEN_ISSUANCE_CREATE, 54, MPTokenIssuanceCreate, Delegation::delegatable, ({
|
||||
{sfAssetScale, soeOPTIONAL},
|
||||
{sfTransferFee, soeOPTIONAL},
|
||||
{sfMaximumAmount, soeOPTIONAL},
|
||||
@@ -551,37 +413,25 @@ TRANSACTION(ttMPTOKEN_ISSUANCE_CREATE, 54, MPTokenIssuanceCreate,
|
||||
}))
|
||||
|
||||
/** This transaction type destroys a MPTokensIssuance instance */
|
||||
TRANSACTION(ttMPTOKEN_ISSUANCE_DESTROY, 55, MPTokenIssuanceDestroy,
|
||||
Delegation::delegatable,
|
||||
featureMPTokensV1,
|
||||
({
|
||||
TRANSACTION(ttMPTOKEN_ISSUANCE_DESTROY, 55, MPTokenIssuanceDestroy, Delegation::delegatable, ({
|
||||
{sfMPTokenIssuanceID, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** This transaction type sets flags on a MPTokensIssuance or MPToken instance */
|
||||
TRANSACTION(ttMPTOKEN_ISSUANCE_SET, 56, MPTokenIssuanceSet,
|
||||
Delegation::delegatable,
|
||||
featureMPTokensV1,
|
||||
({
|
||||
TRANSACTION(ttMPTOKEN_ISSUANCE_SET, 56, MPTokenIssuanceSet, Delegation::delegatable, ({
|
||||
{sfMPTokenIssuanceID, soeREQUIRED},
|
||||
{sfHolder, soeOPTIONAL},
|
||||
{sfDomainID, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** This transaction type authorizes a MPToken instance */
|
||||
TRANSACTION(ttMPTOKEN_AUTHORIZE, 57, MPTokenAuthorize,
|
||||
Delegation::delegatable,
|
||||
featureMPTokensV1,
|
||||
({
|
||||
TRANSACTION(ttMPTOKEN_AUTHORIZE, 57, MPTokenAuthorize, Delegation::delegatable, ({
|
||||
{sfMPTokenIssuanceID, soeREQUIRED},
|
||||
{sfHolder, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** This transaction type create an Credential instance */
|
||||
TRANSACTION(ttCREDENTIAL_CREATE, 58, CredentialCreate,
|
||||
Delegation::delegatable,
|
||||
featureCredentials,
|
||||
({
|
||||
TRANSACTION(ttCREDENTIAL_CREATE, 58, CredentialCreate, Delegation::delegatable, ({
|
||||
{sfSubject, soeREQUIRED},
|
||||
{sfCredentialType, soeREQUIRED},
|
||||
{sfExpiration, soeOPTIONAL},
|
||||
@@ -589,65 +439,44 @@ TRANSACTION(ttCREDENTIAL_CREATE, 58, CredentialCreate,
|
||||
}))
|
||||
|
||||
/** This transaction type accept an Credential object */
|
||||
TRANSACTION(ttCREDENTIAL_ACCEPT, 59, CredentialAccept,
|
||||
Delegation::delegatable,
|
||||
featureCredentials,
|
||||
({
|
||||
TRANSACTION(ttCREDENTIAL_ACCEPT, 59, CredentialAccept, Delegation::delegatable, ({
|
||||
{sfIssuer, soeREQUIRED},
|
||||
{sfCredentialType, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** This transaction type delete an Credential object */
|
||||
TRANSACTION(ttCREDENTIAL_DELETE, 60, CredentialDelete,
|
||||
Delegation::delegatable,
|
||||
featureCredentials,
|
||||
({
|
||||
TRANSACTION(ttCREDENTIAL_DELETE, 60, CredentialDelete, Delegation::delegatable, ({
|
||||
{sfSubject, soeOPTIONAL},
|
||||
{sfIssuer, soeOPTIONAL},
|
||||
{sfCredentialType, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** This transaction type modify a NFToken */
|
||||
TRANSACTION(ttNFTOKEN_MODIFY, 61, NFTokenModify,
|
||||
Delegation::delegatable,
|
||||
featureDynamicNFT,
|
||||
({
|
||||
TRANSACTION(ttNFTOKEN_MODIFY, 61, NFTokenModify, Delegation::delegatable, ({
|
||||
{sfNFTokenID, soeREQUIRED},
|
||||
{sfOwner, soeOPTIONAL},
|
||||
{sfURI, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** This transaction type creates or modifies a Permissioned Domain */
|
||||
TRANSACTION(ttPERMISSIONED_DOMAIN_SET, 62, PermissionedDomainSet,
|
||||
Delegation::delegatable,
|
||||
featurePermissionedDomains,
|
||||
({
|
||||
TRANSACTION(ttPERMISSIONED_DOMAIN_SET, 62, PermissionedDomainSet, Delegation::delegatable, ({
|
||||
{sfDomainID, soeOPTIONAL},
|
||||
{sfAcceptedCredentials, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** This transaction type deletes a Permissioned Domain */
|
||||
TRANSACTION(ttPERMISSIONED_DOMAIN_DELETE, 63, PermissionedDomainDelete,
|
||||
Delegation::delegatable,
|
||||
featurePermissionedDomains,
|
||||
({
|
||||
TRANSACTION(ttPERMISSIONED_DOMAIN_DELETE, 63, PermissionedDomainDelete, Delegation::delegatable, ({
|
||||
{sfDomainID, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** This transaction type delegates authorized account specified permissions */
|
||||
TRANSACTION(ttDELEGATE_SET, 64, DelegateSet,
|
||||
Delegation::notDelegatable,
|
||||
featurePermissionDelegation,
|
||||
({
|
||||
TRANSACTION(ttDELEGATE_SET, 64, DelegateSet, Delegation::notDelegatable, ({
|
||||
{sfAuthorize, soeREQUIRED},
|
||||
{sfPermissions, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** This transaction creates a single asset vault. */
|
||||
TRANSACTION(ttVAULT_CREATE, 65, VaultCreate,
|
||||
Delegation::delegatable,
|
||||
featureSingleAssetVault,
|
||||
({
|
||||
TRANSACTION(ttVAULT_CREATE, 65, VaultCreate, Delegation::delegatable, ({
|
||||
{sfAsset, soeREQUIRED, soeMPTSupported},
|
||||
{sfAssetsMaximum, soeOPTIONAL},
|
||||
{sfMPTokenMetadata, soeOPTIONAL},
|
||||
@@ -658,10 +487,7 @@ TRANSACTION(ttVAULT_CREATE, 65, VaultCreate,
|
||||
}))
|
||||
|
||||
/** This transaction updates a single asset vault. */
|
||||
TRANSACTION(ttVAULT_SET, 66, VaultSet,
|
||||
Delegation::delegatable,
|
||||
featureSingleAssetVault,
|
||||
({
|
||||
TRANSACTION(ttVAULT_SET, 66, VaultSet, Delegation::delegatable, ({
|
||||
{sfVaultID, soeREQUIRED},
|
||||
{sfAssetsMaximum, soeOPTIONAL},
|
||||
{sfDomainID, soeOPTIONAL},
|
||||
@@ -669,27 +495,18 @@ TRANSACTION(ttVAULT_SET, 66, VaultSet,
|
||||
}))
|
||||
|
||||
/** This transaction deletes a single asset vault. */
|
||||
TRANSACTION(ttVAULT_DELETE, 67, VaultDelete,
|
||||
Delegation::delegatable,
|
||||
featureSingleAssetVault,
|
||||
({
|
||||
TRANSACTION(ttVAULT_DELETE, 67, VaultDelete, Delegation::delegatable, ({
|
||||
{sfVaultID, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** This transaction trades assets for shares with a vault. */
|
||||
TRANSACTION(ttVAULT_DEPOSIT, 68, VaultDeposit,
|
||||
Delegation::delegatable,
|
||||
featureSingleAssetVault,
|
||||
({
|
||||
TRANSACTION(ttVAULT_DEPOSIT, 68, VaultDeposit, Delegation::delegatable, ({
|
||||
{sfVaultID, soeREQUIRED},
|
||||
{sfAmount, soeREQUIRED, soeMPTSupported},
|
||||
}))
|
||||
|
||||
/** This transaction trades shares for assets with a vault. */
|
||||
TRANSACTION(ttVAULT_WITHDRAW, 69, VaultWithdraw,
|
||||
Delegation::delegatable,
|
||||
featureSingleAssetVault,
|
||||
({
|
||||
TRANSACTION(ttVAULT_WITHDRAW, 69, VaultWithdraw, Delegation::delegatable, ({
|
||||
{sfVaultID, soeREQUIRED},
|
||||
{sfAmount, soeREQUIRED, soeMPTSupported},
|
||||
{sfDestination, soeOPTIONAL},
|
||||
@@ -697,32 +514,45 @@ TRANSACTION(ttVAULT_WITHDRAW, 69, VaultWithdraw,
|
||||
}))
|
||||
|
||||
/** This transaction claws back tokens from a vault. */
|
||||
TRANSACTION(ttVAULT_CLAWBACK, 70, VaultClawback,
|
||||
Delegation::delegatable,
|
||||
featureSingleAssetVault,
|
||||
({
|
||||
TRANSACTION(ttVAULT_CLAWBACK, 70, VaultClawback, Delegation::delegatable, ({
|
||||
{sfVaultID, soeREQUIRED},
|
||||
{sfHolder, soeREQUIRED},
|
||||
{sfAmount, soeOPTIONAL, soeMPTSupported},
|
||||
}))
|
||||
|
||||
/** This transaction type batches together transactions. */
|
||||
TRANSACTION(ttBATCH, 71, Batch,
|
||||
Delegation::notDelegatable,
|
||||
featureBatch,
|
||||
({
|
||||
TRANSACTION(ttBATCH, 71, Batch, Delegation::notDelegatable, ({
|
||||
{sfRawTransactions, soeREQUIRED},
|
||||
{sfBatchSigners, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** This transaction type batches together transactions. */
|
||||
TRANSACTION(ttSUBSCRIPTION_SET, 72, SubscriptionSet, Delegation::delegatable, ({
|
||||
{sfDestination, soeOPTIONAL},
|
||||
{sfAmount, soeREQUIRED, soeMPTSupported},
|
||||
{sfFrequency, soeOPTIONAL},
|
||||
{sfStartTime, soeOPTIONAL},
|
||||
{sfExpiration, soeOPTIONAL},
|
||||
{sfDestinationTag, soeOPTIONAL},
|
||||
{sfSubscriptionID, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** This transaction type batches together transactions. */
|
||||
TRANSACTION(ttSUBSCRIPTION_CANCEL, 73, SubscriptionCancel, Delegation::delegatable, ({
|
||||
{sfSubscriptionID, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** This transaction type batches together transactions. */
|
||||
TRANSACTION(ttSUBSCRIPTION_CLAIM, 74, SubscriptionClaim, Delegation::delegatable, ({
|
||||
{sfAmount, soeREQUIRED, soeMPTSupported},
|
||||
{sfSubscriptionID, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** This system-generated transaction type is used to update the status of the various amendments.
|
||||
|
||||
For details, see: https://xrpl.org/amendments.html
|
||||
*/
|
||||
TRANSACTION(ttAMENDMENT, 100, EnableAmendment,
|
||||
Delegation::notDelegatable,
|
||||
uint256{},
|
||||
({
|
||||
TRANSACTION(ttAMENDMENT, 100, EnableAmendment, Delegation::notDelegatable, ({
|
||||
{sfLedgerSequence, soeREQUIRED},
|
||||
{sfAmendment, soeREQUIRED},
|
||||
}))
|
||||
@@ -730,10 +560,7 @@ TRANSACTION(ttAMENDMENT, 100, EnableAmendment,
|
||||
/** This system-generated transaction type is used to update the network's fee settings.
|
||||
For details, see: https://xrpl.org/fee-voting.html
|
||||
*/
|
||||
TRANSACTION(ttFEE, 101, SetFee,
|
||||
Delegation::notDelegatable,
|
||||
uint256{},
|
||||
({
|
||||
TRANSACTION(ttFEE, 101, SetFee, Delegation::notDelegatable, ({
|
||||
{sfLedgerSequence, soeOPTIONAL},
|
||||
// Old version uses raw numbers
|
||||
{sfBaseFee, soeOPTIONAL},
|
||||
@@ -750,10 +577,7 @@ TRANSACTION(ttFEE, 101, SetFee,
|
||||
|
||||
For details, see: https://xrpl.org/negative-unl.html
|
||||
*/
|
||||
TRANSACTION(ttUNL_MODIFY, 102, UNLModify,
|
||||
Delegation::notDelegatable,
|
||||
uint256{},
|
||||
({
|
||||
TRANSACTION(ttUNL_MODIFY, 102, UNLModify, Delegation::notDelegatable, ({
|
||||
{sfUNLModifyDisabling, soeREQUIRED},
|
||||
{sfLedgerSequence, soeREQUIRED},
|
||||
{sfUNLModifyValidator, soeREQUIRED},
|
||||
|
||||
@@ -99,6 +99,7 @@ JSS(Signer); // field.
|
||||
JSS(Signers); // field.
|
||||
JSS(SigningPubKey); // field.
|
||||
JSS(Subject); // in: Credential transactions
|
||||
JSS(SubscriptionID); // in: Subscription transactions
|
||||
JSS(TakerGets); // field.
|
||||
JSS(TakerPays); // field.
|
||||
JSS(TradingFee); // in/out: AMM trading fee
|
||||
@@ -283,6 +284,7 @@ JSS(fee_mult_max); // in: TransactionSign
|
||||
JSS(fee_ref); // out: NetworkOPs, DEPRECATED
|
||||
JSS(fetch_pack); // out: NetworkOPs
|
||||
JSS(FIELDS); // out: RPC server_definitions
|
||||
JSS(Frequency); // in: Subscription transactions
|
||||
// matches definitions.json format
|
||||
JSS(first); // out: rpc/Version
|
||||
JSS(finished);
|
||||
@@ -710,7 +712,7 @@ JSS(write_load); // out: GetCounts
|
||||
#pragma push_macro("TRANSACTION")
|
||||
#undef TRANSACTION
|
||||
|
||||
#define TRANSACTION(tag, value, name, ...) JSS(name);
|
||||
#define TRANSACTION(tag, value, name, delegatable, fields) JSS(name);
|
||||
|
||||
#include <xrpl/protocol/detail/transactions.macro>
|
||||
|
||||
|
||||
@@ -96,6 +96,7 @@ enum class LedgerNameSpace : std::uint16_t {
|
||||
PERMISSIONED_DOMAIN = 'm',
|
||||
DELEGATE = 'E',
|
||||
VAULT = 'V',
|
||||
SUBSCRIPTION = 'U',
|
||||
|
||||
// No longer used or supported. Left here to reserve the space
|
||||
// to avoid accidental reuse.
|
||||
@@ -580,6 +581,17 @@ permissionedDomain(uint256 const& domainID) noexcept
|
||||
return {ltPERMISSIONED_DOMAIN, domainID};
|
||||
}
|
||||
|
||||
Keylet
|
||||
subscription(
|
||||
AccountID const& account,
|
||||
AccountID const& dest,
|
||||
std::uint32_t const& seq) noexcept
|
||||
{
|
||||
return {
|
||||
ltSUBSCRIPTION,
|
||||
indexHash(LedgerNameSpace::SUBSCRIPTION, account, dest, seq)};
|
||||
}
|
||||
|
||||
} // namespace keylet
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
//==============================================================================
|
||||
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Permissions.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
|
||||
@@ -26,24 +25,11 @@ namespace ripple {
|
||||
|
||||
Permission::Permission()
|
||||
{
|
||||
txFeatureMap_ = {
|
||||
#pragma push_macro("TRANSACTION")
|
||||
#undef TRANSACTION
|
||||
|
||||
#define TRANSACTION(tag, value, name, delegatable, amendment, ...) \
|
||||
{value, amendment},
|
||||
|
||||
#include <xrpl/protocol/detail/transactions.macro>
|
||||
|
||||
#undef TRANSACTION
|
||||
#pragma pop_macro("TRANSACTION")
|
||||
};
|
||||
|
||||
delegatableTx_ = {
|
||||
#pragma push_macro("TRANSACTION")
|
||||
#undef TRANSACTION
|
||||
|
||||
#define TRANSACTION(tag, value, name, delegatable, ...) {value, delegatable},
|
||||
#define TRANSACTION(tag, value, name, delegatable, fields) {value, delegatable},
|
||||
|
||||
#include <xrpl/protocol/detail/transactions.macro>
|
||||
|
||||
@@ -132,9 +118,7 @@ Permission::getGranularTxType(GranularPermissionType const& gpType) const
|
||||
}
|
||||
|
||||
bool
|
||||
Permission::isDelegatable(
|
||||
std::uint32_t const& permissionValue,
|
||||
Rules const& rules) const
|
||||
Permission::isDelegatable(std::uint32_t const& permissionValue) const
|
||||
{
|
||||
auto const granularPermission =
|
||||
getGranularName(static_cast<GranularPermissionType>(permissionValue));
|
||||
@@ -142,27 +126,7 @@ Permission::isDelegatable(
|
||||
// granular permissions are always allowed to be delegated
|
||||
return true;
|
||||
|
||||
auto const txType = permissionToTxType(permissionValue);
|
||||
auto const it = delegatableTx_.find(txType);
|
||||
|
||||
if (rules.enabled(fixDelegateV1_1))
|
||||
{
|
||||
if (it == delegatableTx_.end())
|
||||
return false;
|
||||
|
||||
auto const txFeaturesIt = txFeatureMap_.find(txType);
|
||||
XRPL_ASSERT(
|
||||
txFeaturesIt != txFeatureMap_.end(),
|
||||
"ripple::Permissions::isDelegatable : tx exists in txFeatureMap_");
|
||||
|
||||
// fixDelegateV1_1: Delegation is only allowed if the required amendment
|
||||
// for the transaction is enabled. For transactions that do not require
|
||||
// an amendment, delegation is always allowed.
|
||||
if (txFeaturesIt->second != uint256{} &&
|
||||
!rules.enabled(txFeaturesIt->second))
|
||||
return false;
|
||||
}
|
||||
|
||||
auto const it = delegatableTx_.find(permissionValue - 1);
|
||||
if (it != delegatableTx_.end() && it->second == Delegation::notDelegatable)
|
||||
return false;
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ TxFormats::TxFormats()
|
||||
#undef TRANSACTION
|
||||
|
||||
#define UNWRAP(...) __VA_ARGS__
|
||||
#define TRANSACTION(tag, value, name, delegatable, amendment, fields) \
|
||||
#define TRANSACTION(tag, value, name, delegatable, fields) \
|
||||
add(jss::name, tag, UNWRAP fields, commonFields);
|
||||
|
||||
#include <xrpl/protocol/detail/transactions.macro>
|
||||
|
||||
@@ -2442,7 +2442,8 @@ class AMMClawback_test : public beast::unit_test::suite
|
||||
void
|
||||
run() override
|
||||
{
|
||||
FeatureBitset const all = jtx::testable_amendments();
|
||||
FeatureBitset const all{
|
||||
jtx::testable_amendments() | fixAMMClawbackRounding};
|
||||
|
||||
testInvalidRequest();
|
||||
testFeatureDisabled(all - featureAMMClawback);
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
//==============================================================================
|
||||
|
||||
#include <test/jtx.h>
|
||||
#include <test/jtx/CaptureLogs.h>
|
||||
#include <test/jtx/delegate.h>
|
||||
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
@@ -140,12 +139,12 @@ class Delegate_test : public beast::unit_test::suite
|
||||
}
|
||||
|
||||
void
|
||||
testInvalidRequest(FeatureBitset features)
|
||||
testInvalidRequest()
|
||||
{
|
||||
testcase("test invalid DelegateSet");
|
||||
using namespace jtx;
|
||||
|
||||
Env env(*this, features);
|
||||
Env env(*this);
|
||||
Account gw{"gateway"};
|
||||
Account alice{"alice"};
|
||||
Account bob{"bob"};
|
||||
@@ -217,17 +216,22 @@ class Delegate_test : public beast::unit_test::suite
|
||||
}
|
||||
|
||||
// non-delegatable transaction
|
||||
auto const res = features[fixDelegateV1_1] ? ter(temMALFORMED)
|
||||
: ter(tecNO_PERMISSION);
|
||||
{
|
||||
env(delegate::set(gw, alice, {"SetRegularKey"}), res);
|
||||
env(delegate::set(gw, alice, {"AccountSet"}), res);
|
||||
env(delegate::set(gw, alice, {"SignerListSet"}), res);
|
||||
env(delegate::set(gw, alice, {"DelegateSet"}), res);
|
||||
env(delegate::set(gw, alice, {"EnableAmendment"}), res);
|
||||
env(delegate::set(gw, alice, {"UNLModify"}), res);
|
||||
env(delegate::set(gw, alice, {"SetFee"}), res);
|
||||
env(delegate::set(gw, alice, {"Batch"}), res);
|
||||
env(delegate::set(gw, alice, {"SetRegularKey"}),
|
||||
ter(tecNO_PERMISSION));
|
||||
env(delegate::set(gw, alice, {"AccountSet"}),
|
||||
ter(tecNO_PERMISSION));
|
||||
env(delegate::set(gw, alice, {"SignerListSet"}),
|
||||
ter(tecNO_PERMISSION));
|
||||
env(delegate::set(gw, alice, {"DelegateSet"}),
|
||||
ter(tecNO_PERMISSION));
|
||||
env(delegate::set(gw, alice, {"SetRegularKey"}),
|
||||
ter(tecNO_PERMISSION));
|
||||
env(delegate::set(gw, alice, {"EnableAmendment"}),
|
||||
ter(tecNO_PERMISSION));
|
||||
env(delegate::set(gw, alice, {"UNLModify"}), ter(tecNO_PERMISSION));
|
||||
env(delegate::set(gw, alice, {"SetFee"}), ter(tecNO_PERMISSION));
|
||||
env(delegate::set(gw, alice, {"Batch"}), ter(tecNO_PERMISSION));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -532,7 +536,7 @@ class Delegate_test : public beast::unit_test::suite
|
||||
}
|
||||
|
||||
void
|
||||
testPaymentGranular(FeatureBitset features)
|
||||
testPaymentGranular()
|
||||
{
|
||||
testcase("test payment granular");
|
||||
using namespace jtx;
|
||||
@@ -702,158 +706,6 @@ class Delegate_test : public beast::unit_test::suite
|
||||
env.require(balance(alice, USD(50)));
|
||||
BEAST_EXPECT(env.balance(bob, USD) == USD(0));
|
||||
}
|
||||
|
||||
// disallow cross currency payment with only PaymentBurn/PaymentMint
|
||||
// permission
|
||||
{
|
||||
Env env(*this, features);
|
||||
Account const alice{"alice"};
|
||||
Account const bob{"bob"};
|
||||
Account const gw{"gateway"};
|
||||
Account const carol{"carol"};
|
||||
auto const USD = gw["USD"];
|
||||
|
||||
env.fund(XRP(10000), alice, bob, carol, gw);
|
||||
env.close();
|
||||
env.trust(USD(50000), alice);
|
||||
env.trust(USD(50000), bob);
|
||||
env.trust(USD(50000), carol);
|
||||
env(pay(gw, alice, USD(10000)));
|
||||
env(pay(gw, bob, USD(10000)));
|
||||
env(pay(gw, carol, USD(10000)));
|
||||
env.close();
|
||||
|
||||
auto const result = features[fixDelegateV1_1]
|
||||
? static_cast<TER>(tecNO_DELEGATE_PERMISSION)
|
||||
: static_cast<TER>(tesSUCCESS);
|
||||
auto const offerCount = features[fixDelegateV1_1] ? 1 : 0;
|
||||
|
||||
// PaymentMint
|
||||
{
|
||||
env(offer(carol, XRP(100), USD(501)));
|
||||
BEAST_EXPECT(expectOffers(env, carol, 1));
|
||||
env(delegate::set(gw, bob, {"PaymentMint"}));
|
||||
env.close();
|
||||
|
||||
// post-amendment: fixDelegateV1_1
|
||||
// bob can not send cross currency payment on behalf of the gw,
|
||||
// even with PaymentMint permission and gw being the issuer.
|
||||
env(pay(gw, alice, USD(5000)),
|
||||
path(~USD),
|
||||
sendmax(XRP(1001)),
|
||||
txflags(tfPartialPayment),
|
||||
delegate::as(bob),
|
||||
ter(result));
|
||||
BEAST_EXPECT(expectOffers(env, carol, offerCount));
|
||||
|
||||
// succeed with direct payment
|
||||
env(pay(gw, alice, USD(100)), delegate::as(bob));
|
||||
env.close();
|
||||
}
|
||||
|
||||
// PaymentBurn
|
||||
{
|
||||
env(offer(bob, XRP(100), USD(501)));
|
||||
BEAST_EXPECT(expectOffers(env, bob, 1));
|
||||
env(delegate::set(alice, bob, {"PaymentBurn"}));
|
||||
env.close();
|
||||
|
||||
// post-amendment: fixDelegateV1_1
|
||||
// bob can not send cross currency payment on behalf of alice,
|
||||
// even with PaymentBurn permission and gw being the issuer.
|
||||
env(pay(alice, gw, USD(5000)),
|
||||
path(~USD),
|
||||
sendmax(XRP(1001)),
|
||||
txflags(tfPartialPayment),
|
||||
delegate::as(bob),
|
||||
ter(result));
|
||||
BEAST_EXPECT(expectOffers(env, bob, offerCount));
|
||||
|
||||
// succeed with direct payment
|
||||
env(pay(alice, gw, USD(100)), delegate::as(bob));
|
||||
env.close();
|
||||
}
|
||||
}
|
||||
|
||||
// PaymentMint and PaymentBurn for MPT
|
||||
{
|
||||
std::string logs;
|
||||
Env env(*this, features, std::make_unique<CaptureLogs>(&logs));
|
||||
Account const alice{"alice"};
|
||||
Account const bob{"bob"};
|
||||
Account const gw{"gateway"};
|
||||
|
||||
MPTTester mpt(env, gw, {.holders = {alice, bob}});
|
||||
mpt.create(
|
||||
{.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer});
|
||||
|
||||
mpt.authorize({.account = alice});
|
||||
mpt.authorize({.account = bob});
|
||||
|
||||
auto const MPT = mpt["MPT"];
|
||||
env(pay(gw, alice, MPT(500)));
|
||||
env(pay(gw, bob, MPT(500)));
|
||||
env.close();
|
||||
auto aliceMPT = env.balance(alice, MPT);
|
||||
auto bobMPT = env.balance(bob, MPT);
|
||||
|
||||
// PaymentMint
|
||||
{
|
||||
env(delegate::set(gw, bob, {"PaymentMint"}));
|
||||
env.close();
|
||||
|
||||
if (!features[fixDelegateV1_1])
|
||||
{
|
||||
// pre-amendment: PaymentMint is not supported for MPT
|
||||
env(pay(gw, alice, MPT(50)),
|
||||
delegate::as(bob),
|
||||
ter(tefEXCEPTION));
|
||||
}
|
||||
else
|
||||
{
|
||||
env(pay(gw, alice, MPT(50)), delegate::as(bob));
|
||||
BEAST_EXPECT(env.balance(alice, MPT) == aliceMPT + MPT(50));
|
||||
BEAST_EXPECT(env.balance(bob, MPT) == bobMPT);
|
||||
aliceMPT = env.balance(alice, MPT);
|
||||
}
|
||||
}
|
||||
|
||||
// PaymentBurn
|
||||
{
|
||||
env(delegate::set(alice, bob, {"PaymentBurn"}));
|
||||
env.close();
|
||||
|
||||
if (!features[fixDelegateV1_1])
|
||||
{
|
||||
// pre-amendment: PaymentBurn is not supported for MPT
|
||||
env(pay(alice, gw, MPT(50)),
|
||||
delegate::as(bob),
|
||||
ter(tefEXCEPTION));
|
||||
}
|
||||
else
|
||||
{
|
||||
env(pay(alice, gw, MPT(50)), delegate::as(bob));
|
||||
BEAST_EXPECT(env.balance(alice, MPT) == aliceMPT - MPT(50));
|
||||
BEAST_EXPECT(env.balance(bob, MPT) == bobMPT);
|
||||
aliceMPT = env.balance(alice, MPT);
|
||||
}
|
||||
}
|
||||
|
||||
// Payment transaction for MPT is allowed for both pre and post
|
||||
// amendment
|
||||
{
|
||||
env(delegate::set(
|
||||
alice, bob, {"PaymentBurn", "PaymentMint", "Payment"}));
|
||||
env.close();
|
||||
env(pay(alice, gw, MPT(50)), delegate::as(bob));
|
||||
BEAST_EXPECT(env.balance(alice, MPT) == aliceMPT - MPT(50));
|
||||
BEAST_EXPECT(env.balance(bob, MPT) == bobMPT);
|
||||
aliceMPT = env.balance(alice, MPT);
|
||||
env(pay(alice, bob, MPT(100)), delegate::as(bob));
|
||||
BEAST_EXPECT(env.balance(alice, MPT) == aliceMPT - MPT(100));
|
||||
BEAST_EXPECT(env.balance(bob, MPT) == bobMPT + MPT(100));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@@ -1624,216 +1476,18 @@ class Delegate_test : public beast::unit_test::suite
|
||||
BEAST_EXPECT(env.balance(edward) == edwardBalance);
|
||||
}
|
||||
|
||||
void
|
||||
testPermissionValue(FeatureBitset features)
|
||||
{
|
||||
testcase("test permission value");
|
||||
using namespace jtx;
|
||||
|
||||
Env env(*this, features);
|
||||
|
||||
Account alice{"alice"};
|
||||
Account bob{"bob"};
|
||||
env.fund(XRP(100000), alice, bob);
|
||||
env.close();
|
||||
|
||||
auto buildRequest = [&](auto value) -> Json::Value {
|
||||
Json::Value jv;
|
||||
jv[jss::TransactionType] = jss::DelegateSet;
|
||||
jv[jss::Account] = alice.human();
|
||||
jv[sfAuthorize.jsonName] = bob.human();
|
||||
|
||||
Json::Value permissionsJson(Json::arrayValue);
|
||||
Json::Value permissionValue;
|
||||
permissionValue[sfPermissionValue.jsonName] = value;
|
||||
Json::Value permissionObj;
|
||||
permissionObj[sfPermission.jsonName] = permissionValue;
|
||||
permissionsJson.append(permissionObj);
|
||||
jv[sfPermissions.jsonName] = permissionsJson;
|
||||
|
||||
return jv;
|
||||
};
|
||||
|
||||
// invalid permission value.
|
||||
// neither granular permission nor transaction level permission
|
||||
for (auto value : {0, 100000, 54321})
|
||||
{
|
||||
auto jv = buildRequest(value);
|
||||
if (!features[fixDelegateV1_1])
|
||||
env(jv);
|
||||
else
|
||||
env(jv, ter(temMALFORMED));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testTxReqireFeatures(FeatureBitset features)
|
||||
{
|
||||
testcase("test delegate disabled tx");
|
||||
using namespace jtx;
|
||||
|
||||
// map of tx and required feature.
|
||||
// non-delegatable tx are not included.
|
||||
// NFTokenMint, NFTokenBurn, NFTokenCreateOffer, NFTokenCancelOffer,
|
||||
// NFTokenAcceptOffer are not included, they are tested separately.
|
||||
std::unordered_map<std::string, uint256> txRequiredFeatures{
|
||||
{"TicketCreate", featureTicketBatch},
|
||||
{"CheckCreate", featureChecks},
|
||||
{"CheckCash", featureChecks},
|
||||
{"CheckCancel", featureChecks},
|
||||
{"DepositPreauth", featureDepositPreauth},
|
||||
{"Clawback", featureClawback},
|
||||
{"AMMClawback", featureAMMClawback},
|
||||
{"AMMCreate", featureAMM},
|
||||
{"AMMDeposit", featureAMM},
|
||||
{"AMMWithdraw", featureAMM},
|
||||
{"AMMVote", featureAMM},
|
||||
{"AMMBid", featureAMM},
|
||||
{"AMMDelete", featureAMM},
|
||||
{"XChainCreateClaimID", featureXChainBridge},
|
||||
{"XChainCommit", featureXChainBridge},
|
||||
{"XChainClaim", featureXChainBridge},
|
||||
{"XChainAccountCreateCommit", featureXChainBridge},
|
||||
{"XChainAddClaimAttestation", featureXChainBridge},
|
||||
{"XChainAddAccountCreateAttestation", featureXChainBridge},
|
||||
{"XChainModifyBridge", featureXChainBridge},
|
||||
{"XChainCreateBridge", featureXChainBridge},
|
||||
{"DIDSet", featureDID},
|
||||
{"DIDDelete", featureDID},
|
||||
{"OracleSet", featurePriceOracle},
|
||||
{"OracleDelete", featurePriceOracle},
|
||||
{"LedgerStateFix", fixNFTokenPageLinks},
|
||||
{"MPTokenIssuanceCreate", featureMPTokensV1},
|
||||
{"MPTokenIssuanceDestroy", featureMPTokensV1},
|
||||
{"MPTokenIssuanceSet", featureMPTokensV1},
|
||||
{"MPTokenAuthorize", featureMPTokensV1},
|
||||
{"CredentialCreate", featureCredentials},
|
||||
{"CredentialAccept", featureCredentials},
|
||||
{"CredentialDelete", featureCredentials},
|
||||
{"NFTokenModify", featureDynamicNFT},
|
||||
{"PermissionedDomainSet", featurePermissionedDomains},
|
||||
{"PermissionedDomainDelete", featurePermissionedDomains},
|
||||
{"VaultCreate", featureSingleAssetVault},
|
||||
{"VaultSet", featureSingleAssetVault},
|
||||
{"VaultDelete", featureSingleAssetVault},
|
||||
{"VaultDeposit", featureSingleAssetVault},
|
||||
{"VaultWithdraw", featureSingleAssetVault},
|
||||
{"VaultClawback", featureSingleAssetVault}};
|
||||
|
||||
// fixDelegateV1_1 post-amendment: can not delegate tx if any
|
||||
// required feature disabled.
|
||||
{
|
||||
auto txAmendmentDisabled = [&](FeatureBitset features,
|
||||
std::string const& tx) {
|
||||
BEAST_EXPECT(txRequiredFeatures.contains(tx));
|
||||
|
||||
Env env(*this, features - txRequiredFeatures[tx]);
|
||||
|
||||
Account const alice{"alice"};
|
||||
Account const bob{"bob"};
|
||||
env.fund(XRP(100000), alice, bob);
|
||||
env.close();
|
||||
|
||||
if (!features[fixDelegateV1_1])
|
||||
env(delegate::set(alice, bob, {tx}));
|
||||
else
|
||||
env(delegate::set(alice, bob, {tx}), ter(temMALFORMED));
|
||||
};
|
||||
|
||||
for (auto const& tx : txRequiredFeatures)
|
||||
txAmendmentDisabled(features, tx.first);
|
||||
}
|
||||
|
||||
// if all the required features in txRequiredFeatures are enabled, will
|
||||
// succeed
|
||||
{
|
||||
auto txAmendmentEnabled = [&](std::string const& tx) {
|
||||
Env env(*this, features);
|
||||
|
||||
Account const alice{"alice"};
|
||||
Account const bob{"bob"};
|
||||
env.fund(XRP(100000), alice, bob);
|
||||
env.close();
|
||||
|
||||
env(delegate::set(alice, bob, {tx}));
|
||||
};
|
||||
|
||||
for (auto const& tx : txRequiredFeatures)
|
||||
txAmendmentEnabled(tx.first);
|
||||
}
|
||||
|
||||
// NFTokenMint, NFTokenBurn, NFTokenCreateOffer, NFTokenCancelOffer, and
|
||||
// NFTokenAcceptOffer are tested separately. Since
|
||||
// featureNonFungibleTokensV1_1 includes the functionality of
|
||||
// featureNonFungibleTokensV1, fixNFTokenNegOffer, and fixNFTokenDirV1,
|
||||
// both featureNonFungibleTokensV1_1 and featureNonFungibleTokensV1 need
|
||||
// to be disabled to block these transactions from being delegated.
|
||||
{
|
||||
Env env(
|
||||
*this,
|
||||
features - featureNonFungibleTokensV1 -
|
||||
featureNonFungibleTokensV1_1);
|
||||
|
||||
Account const alice{"alice"};
|
||||
Account const bob{"bob"};
|
||||
env.fund(XRP(100000), alice, bob);
|
||||
env.close();
|
||||
|
||||
for (auto const tx :
|
||||
{"NFTokenMint",
|
||||
"NFTokenBurn",
|
||||
"NFTokenCreateOffer",
|
||||
"NFTokenCancelOffer",
|
||||
"NFTokenAcceptOffer"})
|
||||
{
|
||||
if (!features[fixDelegateV1_1])
|
||||
env(delegate::set(alice, bob, {tx}));
|
||||
else
|
||||
env(delegate::set(alice, bob, {tx}), ter(temMALFORMED));
|
||||
}
|
||||
}
|
||||
|
||||
// NFTokenMint, NFTokenBurn, NFTokenCreateOffer, NFTokenCancelOffer, and
|
||||
// NFTokenAcceptOffer are allowed to be delegated if either
|
||||
// featureNonFungibleTokensV1 or featureNonFungibleTokensV1_1 is
|
||||
// enabled.
|
||||
{
|
||||
for (auto const feature :
|
||||
{featureNonFungibleTokensV1, featureNonFungibleTokensV1_1})
|
||||
{
|
||||
Env env(*this, features - feature);
|
||||
Account const alice{"alice"};
|
||||
Account const bob{"bob"};
|
||||
env.fund(XRP(100000), alice, bob);
|
||||
env.close();
|
||||
|
||||
for (auto const tx :
|
||||
{"NFTokenMint",
|
||||
"NFTokenBurn",
|
||||
"NFTokenCreateOffer",
|
||||
"NFTokenCancelOffer",
|
||||
"NFTokenAcceptOffer"})
|
||||
env(delegate::set(alice, bob, {tx}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
FeatureBitset const all = jtx::testable_amendments();
|
||||
|
||||
testFeatureDisabled();
|
||||
testDelegateSet();
|
||||
testInvalidRequest(all);
|
||||
testInvalidRequest(all - fixDelegateV1_1);
|
||||
testInvalidRequest();
|
||||
testReserve();
|
||||
testFee();
|
||||
testSequence();
|
||||
testAccountDelete();
|
||||
testDelegateTransaction();
|
||||
testPaymentGranular(all);
|
||||
testPaymentGranular(all - fixDelegateV1_1);
|
||||
testPaymentGranular();
|
||||
testTrustSetGranular();
|
||||
testAccountSetGranular();
|
||||
testMPTokenIssuanceSetGranular();
|
||||
@@ -1841,10 +1495,6 @@ class Delegate_test : public beast::unit_test::suite
|
||||
testSingleSignBadSecret();
|
||||
testMultiSign();
|
||||
testMultiSignQuorumNotMet();
|
||||
testPermissionValue(all);
|
||||
testPermissionValue(all - fixDelegateV1_1);
|
||||
testTxReqireFeatures(all);
|
||||
testTxReqireFeatures(all - fixDelegateV1_1);
|
||||
}
|
||||
};
|
||||
BEAST_DEFINE_TESTSUITE(Delegate, app, ripple);
|
||||
|
||||
3124
src/test/app/Subscription_test.cpp
Normal file
3124
src/test/app/Subscription_test.cpp
Normal file
File diff suppressed because it is too large
Load Diff
@@ -67,6 +67,7 @@
|
||||
#include <test/jtx/sendmax.h>
|
||||
#include <test/jtx/seq.h>
|
||||
#include <test/jtx/sig.h>
|
||||
#include <test/jtx/subscription.h>
|
||||
#include <test/jtx/tag.h>
|
||||
#include <test/jtx/tags.h>
|
||||
#include <test/jtx/ter.h>
|
||||
|
||||
107
src/test/jtx/impl/subscription.cpp
Normal file
107
src/test/jtx/impl/subscription.cpp
Normal file
@@ -0,0 +1,107 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2024 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <test/jtx/subscription.h>
|
||||
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
namespace jtx {
|
||||
|
||||
/** Subscription operations. */
|
||||
namespace subscription {
|
||||
|
||||
void
|
||||
start_time::operator()(Env& env, JTx& jt) const
|
||||
{
|
||||
jt.jv[sfStartTime.jsonName] = value_.time_since_epoch().count();
|
||||
}
|
||||
|
||||
Json::Value
|
||||
create(
|
||||
jtx::Account const& account,
|
||||
jtx::Account const& destination,
|
||||
STAmount const& amount,
|
||||
NetClock::duration const& frequency,
|
||||
std::optional<NetClock::time_point> const& expiration)
|
||||
{
|
||||
Json::Value jv;
|
||||
jv[jss::TransactionType] = jss::SubscriptionSet;
|
||||
jv[jss::Account] = to_string(account.id());
|
||||
jv[jss::Destination] = to_string(destination.id());
|
||||
jv[jss::Amount] = amount.getJson(JsonOptions::none);
|
||||
jv[jss::Frequency] = frequency.count();
|
||||
jv[jss::Flags] = tfFullyCanonicalSig;
|
||||
if (expiration)
|
||||
jv[sfExpiration.jsonName] = expiration->time_since_epoch().count();
|
||||
return jv;
|
||||
}
|
||||
|
||||
Json::Value
|
||||
update(
|
||||
jtx::Account const& account,
|
||||
uint256 const& subscriptionId,
|
||||
STAmount const& amount,
|
||||
std::optional<NetClock::time_point> const& expiration)
|
||||
{
|
||||
Json::Value jv;
|
||||
jv[jss::TransactionType] = jss::SubscriptionSet;
|
||||
jv[jss::Account] = to_string(account.id());
|
||||
jv[jss::SubscriptionID] = to_string(subscriptionId);
|
||||
jv[jss::Amount] = amount.getJson(JsonOptions::none);
|
||||
jv[jss::Flags] = tfFullyCanonicalSig;
|
||||
if (expiration)
|
||||
jv[sfExpiration.jsonName] = expiration->time_since_epoch().count();
|
||||
return jv;
|
||||
}
|
||||
|
||||
Json::Value
|
||||
cancel(jtx::Account const& account, uint256 const& subscriptionId)
|
||||
{
|
||||
Json::Value jv;
|
||||
jv[jss::TransactionType] = jss::SubscriptionCancel;
|
||||
jv[jss::Account] = to_string(account.id());
|
||||
jv[jss::SubscriptionID] = to_string(subscriptionId);
|
||||
jv[jss::Flags] = tfFullyCanonicalSig;
|
||||
return jv;
|
||||
}
|
||||
|
||||
Json::Value
|
||||
claim(
|
||||
jtx::Account const& account,
|
||||
uint256 const& subscriptionId,
|
||||
STAmount const& amount)
|
||||
{
|
||||
Json::Value jv;
|
||||
jv[jss::TransactionType] = jss::SubscriptionClaim;
|
||||
jv[jss::Account] = to_string(account.id());
|
||||
jv[jss::SubscriptionID] = to_string(subscriptionId);
|
||||
jv[jss::Amount] = amount.getJson(JsonOptions::none);
|
||||
jv[jss::Flags] = tfFullyCanonicalSig;
|
||||
return jv;
|
||||
}
|
||||
|
||||
} // namespace subscription
|
||||
|
||||
} // namespace jtx
|
||||
|
||||
} // namespace test
|
||||
} // namespace ripple
|
||||
79
src/test/jtx/subscription.h
Normal file
79
src/test/jtx/subscription.h
Normal file
@@ -0,0 +1,79 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2019 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_TEST_JTX_SUBSCRIPTION_H_INCLUDED
|
||||
#define RIPPLE_TEST_JTX_SUBSCRIPTION_H_INCLUDED
|
||||
|
||||
#include <test/jtx/Account.h>
|
||||
#include <test/jtx/Env.h>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
namespace jtx {
|
||||
|
||||
/** Subscription operations. */
|
||||
namespace subscription {
|
||||
|
||||
Json::Value
|
||||
create(
|
||||
jtx::Account const& account,
|
||||
jtx::Account const& destination,
|
||||
STAmount const& amount,
|
||||
NetClock::duration const& frequency,
|
||||
std::optional<NetClock::time_point> const& expiration = std::nullopt);
|
||||
|
||||
Json::Value
|
||||
update(
|
||||
jtx::Account const& account,
|
||||
uint256 const& subscriptionId,
|
||||
STAmount const& amount,
|
||||
std::optional<NetClock::time_point> const& expiration = std::nullopt);
|
||||
|
||||
Json::Value
|
||||
cancel(jtx::Account const& account, uint256 const& subscriptionId);
|
||||
|
||||
Json::Value
|
||||
claim(
|
||||
jtx::Account const& account,
|
||||
uint256 const& subscriptionId,
|
||||
STAmount const& amount);
|
||||
|
||||
/** Set the "StartTime" time tag on a JTx */
|
||||
class start_time
|
||||
{
|
||||
private:
|
||||
NetClock::time_point value_;
|
||||
|
||||
public:
|
||||
explicit start_time(NetClock::time_point const& value) : value_(value)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
operator()(Env&, JTx& jtx) const;
|
||||
};
|
||||
|
||||
} // namespace subscription
|
||||
|
||||
} // namespace jtx
|
||||
|
||||
} // namespace test
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
@@ -131,32 +131,6 @@ class Simulate_test : public beast::unit_test::suite
|
||||
std::to_string(env.current()->txCount()));
|
||||
}
|
||||
|
||||
void
|
||||
testTxJsonMetadataField(
|
||||
jtx::Env& env,
|
||||
Json::Value const& tx,
|
||||
std::function<void(
|
||||
Json::Value const&,
|
||||
Json::Value const&,
|
||||
Json::Value const&)> const& validate,
|
||||
Json::Value const& expectedMetadataKey,
|
||||
bool testSerialized = true)
|
||||
{
|
||||
env.close();
|
||||
|
||||
Json::Value params;
|
||||
params[jss::tx_json] = tx;
|
||||
validate(
|
||||
env.rpc("json", "simulate", to_string(params)),
|
||||
tx,
|
||||
expectedMetadataKey);
|
||||
validate(env.rpc("simulate", to_string(tx)), tx, expectedMetadataKey);
|
||||
|
||||
BEAST_EXPECTS(
|
||||
env.current()->txCount() == 0,
|
||||
std::to_string(env.current()->txCount()));
|
||||
}
|
||||
|
||||
Json::Value
|
||||
getJsonMetadata(Json::Value txResult) const
|
||||
{
|
||||
@@ -1212,83 +1186,6 @@ class Simulate_test : public beast::unit_test::suite
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testSuccessfulTransactionAdditionalMetadata()
|
||||
{
|
||||
testcase("Successful transaction with additional metadata");
|
||||
|
||||
using namespace jtx;
|
||||
Env env{*this, envconfig([&](std::unique_ptr<Config> cfg) {
|
||||
cfg->NETWORK_ID = 1025;
|
||||
return cfg;
|
||||
})};
|
||||
|
||||
Account const alice("alice");
|
||||
|
||||
env.fund(XRP(10000), alice);
|
||||
env.close();
|
||||
|
||||
{
|
||||
auto validateOutput = [&](Json::Value const& resp,
|
||||
Json::Value const& tx,
|
||||
Json::Value const& expectedMetadataKey) {
|
||||
auto result = resp[jss::result];
|
||||
|
||||
BEAST_EXPECT(result[jss::engine_result] == "tesSUCCESS");
|
||||
BEAST_EXPECT(result[jss::engine_result_code] == 0);
|
||||
BEAST_EXPECT(
|
||||
result[jss::engine_result_message] ==
|
||||
"The simulated transaction would have been applied.");
|
||||
|
||||
if (BEAST_EXPECT(
|
||||
result.isMember(jss::meta) ||
|
||||
result.isMember(jss::meta_blob)))
|
||||
{
|
||||
Json::Value const metadata = getJsonMetadata(result);
|
||||
|
||||
BEAST_EXPECT(metadata[sfTransactionIndex.jsonName] == 0);
|
||||
BEAST_EXPECT(
|
||||
metadata[sfTransactionResult.jsonName] == "tesSUCCESS");
|
||||
BEAST_EXPECT(
|
||||
metadata.isMember(expectedMetadataKey.asString()));
|
||||
}
|
||||
};
|
||||
|
||||
{
|
||||
Json::Value tx;
|
||||
tx[jss::Account] = env.master.human();
|
||||
tx[jss::TransactionType] = jss::Payment;
|
||||
tx[sfDestination] = alice.human();
|
||||
tx[sfAmount] = "100";
|
||||
|
||||
// test delivered amount
|
||||
testTxJsonMetadataField(
|
||||
env, tx, validateOutput, jss::delivered_amount);
|
||||
}
|
||||
|
||||
{
|
||||
Json::Value tx;
|
||||
tx[jss::Account] = env.master.human();
|
||||
tx[jss::TransactionType] = jss::NFTokenMint;
|
||||
tx[sfNFTokenTaxon] = 1;
|
||||
|
||||
// test nft synthetic
|
||||
testTxJsonMetadataField(
|
||||
env, tx, validateOutput, jss::nftoken_id);
|
||||
}
|
||||
|
||||
{
|
||||
Json::Value tx;
|
||||
tx[jss::Account] = env.master.human();
|
||||
tx[jss::TransactionType] = jss::MPTokenIssuanceCreate;
|
||||
|
||||
// test mpt issuance id
|
||||
testTxJsonMetadataField(
|
||||
env, tx, validateOutput, jss::mpt_issuance_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
void
|
||||
run() override
|
||||
@@ -1305,7 +1202,6 @@ public:
|
||||
testMultisignedBadPubKey();
|
||||
testDeleteExpiredCredentials();
|
||||
testSuccessfulTransactionNetworkID();
|
||||
testSuccessfulTransactionAdditionalMetadata();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
284
src/xrpld/app/misc/SubscriptionHelpers.h
Normal file
284
src/xrpld/app/misc/SubscriptionHelpers.h
Normal file
@@ -0,0 +1,284 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2025 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_APP_MISC_SUBSCRIPTIONHELPERS_H_INCLUDED
|
||||
#define RIPPLE_APP_MISC_SUBSCRIPTIONHELPERS_H_INCLUDED
|
||||
|
||||
#include <xrpld/app/ledger/Ledger.h>
|
||||
#include <xrpld/app/paths/Flow.h>
|
||||
#include <xrpld/app/tx/detail/MPTokenAuthorize.h>
|
||||
#include <xrpld/ledger/ApplyView.h>
|
||||
#include <xrpld/ledger/ReadView.h>
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/basics/scope.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/STAccount.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
template <ValidIssueType T>
|
||||
static TER
|
||||
canTransferTokenHelper(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
AccountID const& dest,
|
||||
STAmount const& amount,
|
||||
beast::Journal const& j);
|
||||
|
||||
template <>
|
||||
TER
|
||||
canTransferTokenHelper<Issue>(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
AccountID const& dest,
|
||||
STAmount const& amount,
|
||||
beast::Journal const& j)
|
||||
{
|
||||
AccountID issuer = amount.getIssuer();
|
||||
if (issuer == account)
|
||||
{
|
||||
JLOG(j.trace())
|
||||
<< "canTransferTokenHelper: Issuer is the same as the account.";
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
// If the issuer does not exist, return tecNO_ISSUER
|
||||
auto const sleIssuer = view.read(keylet::account(issuer));
|
||||
if (!sleIssuer)
|
||||
{
|
||||
JLOG(j.trace()) << "canTransferTokenHelper: Issuer does not exist.";
|
||||
return tecNO_ISSUER;
|
||||
}
|
||||
|
||||
// If the account does not have a trustline to the issuer, return tecNO_LINE
|
||||
auto const sleRippleState =
|
||||
view.read(keylet::line(account, issuer, amount.getCurrency()));
|
||||
if (!sleRippleState)
|
||||
{
|
||||
JLOG(j.trace()) << "canTransferTokenHelper: Trust line does not exist.";
|
||||
return tecNO_LINE;
|
||||
}
|
||||
|
||||
STAmount const balance = (*sleRippleState)[sfBalance];
|
||||
|
||||
// If balance is positive, issuer must have higher address than account
|
||||
if (balance > beast::zero && issuer < account)
|
||||
{
|
||||
JLOG(j.trace()) << "canTransferTokenHelper: Invalid trust line state.";
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
|
||||
// If balance is negative, issuer must have lower address than account
|
||||
if (balance < beast::zero && issuer > account)
|
||||
{
|
||||
JLOG(j.trace()) << "canTransferTokenHelper: Invalid trust line state.";
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
|
||||
// If the issuer has requireAuth set, check if the account is authorized
|
||||
if (auto const ter = requireAuth(view, amount.issue(), account);
|
||||
ter != tesSUCCESS)
|
||||
{
|
||||
JLOG(j.trace()) << "canTransferTokenHelper: Account is not authorized";
|
||||
return ter;
|
||||
}
|
||||
|
||||
// If the issuer has requireAuth set, check if the destination is authorized
|
||||
if (auto const ter = requireAuth(view, amount.issue(), dest);
|
||||
ter != tesSUCCESS)
|
||||
{
|
||||
JLOG(j.trace())
|
||||
<< "canTransferTokenHelper: Destination is not authorized.";
|
||||
return ter;
|
||||
}
|
||||
|
||||
// If the issuer has frozen the account, return tecFROZEN
|
||||
if (isFrozen(view, account, amount.issue()) ||
|
||||
isDeepFrozen(
|
||||
view, account, amount.issue().currency, amount.issue().account))
|
||||
{
|
||||
JLOG(j.trace()) << "canTransferTokenHelper: Account is frozen.";
|
||||
return tecFROZEN;
|
||||
}
|
||||
|
||||
// If the issuer has frozen the destination, return tecFROZEN
|
||||
if (isFrozen(view, dest, amount.issue()) ||
|
||||
isDeepFrozen(
|
||||
view, dest, amount.issue().currency, amount.issue().account))
|
||||
{
|
||||
JLOG(j.trace()) << "canTransferTokenHelper: Destination is frozen.";
|
||||
return tecFROZEN;
|
||||
}
|
||||
|
||||
STAmount const spendableAmount = accountHolds(
|
||||
view, account, amount.getCurrency(), issuer, fhIGNORE_FREEZE, j);
|
||||
|
||||
// If the balance is less than or equal to 0, return
|
||||
// tecINSUFFICIENT_FUNDS
|
||||
if (spendableAmount <= beast::zero)
|
||||
{
|
||||
JLOG(j.trace()) << "canTransferTokenHelper: Spendable amount is less "
|
||||
"than or equal to 0.";
|
||||
return tecINSUFFICIENT_FUNDS;
|
||||
}
|
||||
|
||||
// If the spendable amount is less than the amount, return
|
||||
// tecINSUFFICIENT_FUNDS
|
||||
if (spendableAmount < amount)
|
||||
{
|
||||
JLOG(j.trace()) << "canTransferTokenHelper: Spendable amount is less "
|
||||
"than the 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
|
||||
canTransferTokenHelper<MPTIssue>(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
AccountID const& dest,
|
||||
STAmount const& amount,
|
||||
beast::Journal const& j)
|
||||
{
|
||||
AccountID issuer = amount.getIssuer();
|
||||
if (issuer == account)
|
||||
{
|
||||
JLOG(j.trace())
|
||||
<< "canTransferTokenHelper: Issuer is the same as the account.";
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
// If the mpt does not exist, return tecOBJECT_NOT_FOUND
|
||||
auto const issuanceKey =
|
||||
keylet::mptIssuance(amount.get<MPTIssue>().getMptID());
|
||||
auto const sleIssuance = view.read(issuanceKey);
|
||||
if (!sleIssuance)
|
||||
{
|
||||
JLOG(j.trace())
|
||||
<< "canTransferTokenHelper: MPT issuance does not exist.";
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
}
|
||||
|
||||
// If the issuer is not the same as the issuer of the mpt, return
|
||||
// tecNO_PERMISSION
|
||||
if (sleIssuance->getAccountID(sfIssuer) != issuer)
|
||||
{
|
||||
JLOG(j.trace()) << "canTransferTokenHelper: Issuer is not the same as "
|
||||
"the issuer of the MPT.";
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
|
||||
// If the account does not have the mpt, return tecOBJECT_NOT_FOUND
|
||||
if (!view.exists(keylet::mptoken(issuanceKey.key, account)))
|
||||
{
|
||||
JLOG(j.trace())
|
||||
<< "canTransferTokenHelper: Account does not have the MPT.";
|
||||
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);
|
||||
ter != tesSUCCESS)
|
||||
{
|
||||
JLOG(j.trace()) << "canTransferTokenHelper: Account is not authorized.";
|
||||
return ter;
|
||||
}
|
||||
|
||||
// If the issuer has requireAuth set, check if the destination is
|
||||
// authorized
|
||||
if (auto const ter = requireAuth(view, mptIssue, dest, AuthType::WeakAuth);
|
||||
ter != tesSUCCESS)
|
||||
{
|
||||
JLOG(j.trace())
|
||||
<< "canTransferTokenHelper: Destination is not authorized.";
|
||||
return ter;
|
||||
}
|
||||
|
||||
// If the issuer has locked the account, return tecLOCKED
|
||||
if (isFrozen(view, account, mptIssue))
|
||||
{
|
||||
JLOG(j.trace()) << "canTransferTokenHelper: Account is locked.";
|
||||
return tecLOCKED;
|
||||
}
|
||||
|
||||
// If the issuer has locked the destination, return tecLOCKED
|
||||
if (isFrozen(view, dest, mptIssue))
|
||||
{
|
||||
JLOG(j.trace()) << "canTransferTokenHelper: Destination is locked.";
|
||||
return tecLOCKED;
|
||||
}
|
||||
|
||||
// If the mpt cannot be transferred, return tecNO_AUTH
|
||||
if (auto const ter = canTransfer(view, mptIssue, account, dest);
|
||||
ter != tesSUCCESS)
|
||||
{
|
||||
JLOG(j.trace()) << "canTransferTokenHelper: MPT cannot be transferred.";
|
||||
return ter;
|
||||
}
|
||||
|
||||
STAmount const spendableAmount = accountHolds(
|
||||
view,
|
||||
account,
|
||||
amount.get<MPTIssue>(),
|
||||
fhIGNORE_FREEZE,
|
||||
ahIGNORE_AUTH,
|
||||
j);
|
||||
|
||||
// If the balance is less than or equal to 0, return
|
||||
// tecINSUFFICIENT_FUNDS
|
||||
if (spendableAmount <= beast::zero)
|
||||
{
|
||||
JLOG(j.trace()) << "canTransferTokenHelper: Spendable amount is less "
|
||||
"than or equal to 0.";
|
||||
return tecINSUFFICIENT_FUNDS;
|
||||
}
|
||||
|
||||
// If the spendable amount is less than the amount, return
|
||||
// tecINSUFFICIENT_FUNDS
|
||||
if (spendableAmount < amount)
|
||||
{
|
||||
JLOG(j.trace()) << "canTransferTokenHelper: Spendable amount is less "
|
||||
"than the 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;
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
@@ -23,6 +23,7 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/protocol/st.h>
|
||||
|
||||
namespace ripple {
|
||||
@@ -50,11 +51,6 @@ DelegateSet::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
if (!permissionSet.insert(permission[sfPermissionValue]).second)
|
||||
return temMALFORMED;
|
||||
|
||||
if (ctx.rules.enabled(fixDelegateV1_1) &&
|
||||
!Permission::getInstance().isDelegatable(
|
||||
permission[sfPermissionValue], ctx.rules))
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
return preflight2(ctx);
|
||||
@@ -72,21 +68,9 @@ DelegateSet::preclaim(PreclaimContext const& ctx)
|
||||
auto const& permissions = ctx.tx.getFieldArray(sfPermissions);
|
||||
for (auto const& permission : permissions)
|
||||
{
|
||||
if (!ctx.view.rules().enabled(fixDelegateV1_1) &&
|
||||
!Permission::getInstance().isDelegatable(
|
||||
permission[sfPermissionValue], ctx.view.rules()))
|
||||
{
|
||||
// Before fixDelegateV1_1:
|
||||
// - The check was performed during preclaim.
|
||||
// - Transactions from amendments not yet enabled could still be
|
||||
// delegated.
|
||||
//
|
||||
// After fixDelegateV1_1:
|
||||
// - The check is performed during preflight.
|
||||
// - Transactions from amendments not yet enabled can no longer be
|
||||
// delegated.
|
||||
auto const permissionValue = permission[sfPermissionValue];
|
||||
if (!Permission::getInstance().isDelegatable(permissionValue))
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
|
||||
@@ -543,6 +543,7 @@ LedgerEntryTypesMatch::visitEntry(
|
||||
case ltCREDENTIAL:
|
||||
case ltPERMISSIONED_DOMAIN:
|
||||
case ltVAULT:
|
||||
case ltSUBSCRIPTION:
|
||||
break;
|
||||
default:
|
||||
invalidTypeAdded_ = true;
|
||||
@@ -1511,6 +1512,9 @@ ValidMPTIssuance::finalize(
|
||||
if (tx.getTxnType() == ttESCROW_FINISH)
|
||||
return true;
|
||||
|
||||
if (tx.getTxnType() == ttSUBSCRIPTION_CLAIM)
|
||||
return true;
|
||||
|
||||
if ((tx.getTxnType() == ttVAULT_CLAWBACK ||
|
||||
tx.getTxnType() == ttVAULT_WITHDRAW) &&
|
||||
mptokensDeleted_ == 1 && mptokensCreated_ == 0 &&
|
||||
|
||||
@@ -265,33 +265,8 @@ Payment::checkPermission(ReadView const& view, STTx const& tx)
|
||||
loadGranularPermission(sle, ttPAYMENT, granularPermissions);
|
||||
|
||||
auto const& dstAmount = tx.getFieldAmount(sfAmount);
|
||||
// post-amendment: disallow cross currency payments for PaymentMint and
|
||||
// PaymentBurn
|
||||
if (view.rules().enabled(fixDelegateV1_1))
|
||||
{
|
||||
auto const& amountAsset = dstAmount.asset();
|
||||
if (tx.isFieldPresent(sfSendMax) &&
|
||||
tx[sfSendMax].asset() != amountAsset)
|
||||
return tecNO_DELEGATE_PERMISSION;
|
||||
|
||||
if (granularPermissions.contains(PaymentMint) && !isXRP(amountAsset) &&
|
||||
amountAsset.getIssuer() == tx[sfAccount])
|
||||
return tesSUCCESS;
|
||||
|
||||
if (granularPermissions.contains(PaymentBurn) && !isXRP(amountAsset) &&
|
||||
amountAsset.getIssuer() == tx[sfDestination])
|
||||
return tesSUCCESS;
|
||||
|
||||
return tecNO_DELEGATE_PERMISSION;
|
||||
}
|
||||
|
||||
// Calling dstAmount.issue() in the next line would throw if it holds MPT.
|
||||
// That exception would be caught in preclaim and returned as tefEXCEPTION.
|
||||
// This check is just a cleaner, more explicit way to get the same result.
|
||||
if (dstAmount.holds<MPTIssue>())
|
||||
return tefEXCEPTION;
|
||||
|
||||
auto const& amountIssue = dstAmount.issue();
|
||||
|
||||
if (granularPermissions.contains(PaymentMint) && !isXRP(amountIssue) &&
|
||||
amountIssue.account == tx[sfAccount])
|
||||
return tesSUCCESS;
|
||||
|
||||
106
src/xrpld/app/tx/detail/SubscriptionCancel.cpp
Normal file
106
src/xrpld/app/tx/detail/SubscriptionCancel.cpp
Normal file
@@ -0,0 +1,106 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2025 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <xrpld/app/ledger/Ledger.h>
|
||||
#include <xrpld/app/paths/Flow.h>
|
||||
#include <xrpld/app/tx/detail/SubscriptionCancel.h>
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/basics/scope.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/STAccount.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
NotTEC
|
||||
SubscriptionCancel::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
if (!ctx.rules.enabled(featureSubscription))
|
||||
return temDISABLED;
|
||||
|
||||
if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
|
||||
return ret;
|
||||
|
||||
if (ctx.tx.getFlags() & tfUniversalMask)
|
||||
return temINVALID_FLAG;
|
||||
|
||||
return preflight2(ctx);
|
||||
}
|
||||
|
||||
TER
|
||||
SubscriptionCancel::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
auto const sleSub = ctx.view.read(
|
||||
keylet::subscription(ctx.tx.getFieldH256(sfSubscriptionID)));
|
||||
if (!sleSub)
|
||||
{
|
||||
JLOG(ctx.j.debug())
|
||||
<< "SubscriptionCancel: Subscription does not exist.";
|
||||
return tecNO_ENTRY;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
SubscriptionCancel::doApply()
|
||||
{
|
||||
Sandbox sb(&ctx_.view());
|
||||
|
||||
auto const sleSub =
|
||||
sb.peek(keylet::subscription(ctx_.tx.getFieldH256(sfSubscriptionID)));
|
||||
if (!sleSub)
|
||||
{
|
||||
JLOG(ctx_.journal.debug())
|
||||
<< "SubscriptionCancel: Subscription does not exist.";
|
||||
return tecINTERNAL;
|
||||
}
|
||||
|
||||
AccountID const account{sleSub->getAccountID(sfAccount)};
|
||||
AccountID const dstAcct{sleSub->getAccountID(sfDestination)};
|
||||
auto viewJ = ctx_.app.journal("View");
|
||||
|
||||
std::uint64_t const ownerPage{(*sleSub)[sfOwnerNode]};
|
||||
if (!sb.dirRemove(
|
||||
keylet::ownerDir(account), ownerPage, sleSub->key(), true))
|
||||
{
|
||||
JLOG(j_.fatal()) << "Unable to delete subscription from source.";
|
||||
return tefBAD_LEDGER;
|
||||
}
|
||||
|
||||
std::uint64_t const destPage{(*sleSub)[sfDestinationNode]};
|
||||
if (!sb.dirRemove(keylet::ownerDir(dstAcct), destPage, sleSub->key(), true))
|
||||
{
|
||||
JLOG(j_.fatal()) << "Unable to delete subscription from destination.";
|
||||
return tefBAD_LEDGER;
|
||||
}
|
||||
|
||||
auto const sleSrc = sb.peek(keylet::account(account));
|
||||
sb.erase(sleSub);
|
||||
|
||||
adjustOwnerCount(sb, sleSrc, -1, viewJ);
|
||||
|
||||
sb.apply(ctx_.rawView());
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
48
src/xrpld/app/tx/detail/SubscriptionCancel.h
Normal file
48
src/xrpld/app/tx/detail/SubscriptionCancel.h
Normal file
@@ -0,0 +1,48 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2024 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_TX_SUBSCRIPTIONCANCEL_H_INCLUDED
|
||||
#define RIPPLE_TX_SUBSCRIPTIONCANCEL_H_INCLUDED
|
||||
|
||||
#include <xrpld/app/tx/detail/Transactor.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
class SubscriptionCancel : public Transactor
|
||||
{
|
||||
public:
|
||||
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
|
||||
|
||||
explicit SubscriptionCancel(ApplyContext& ctx) : Transactor(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
};
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
#endif // RIPPLE_TX_SUBSCRIPTIONCANCEL_H_INCLUDED
|
||||
426
src/xrpld/app/tx/detail/SubscriptionClaim.cpp
Normal file
426
src/xrpld/app/tx/detail/SubscriptionClaim.cpp
Normal file
@@ -0,0 +1,426 @@
|
||||
#include <xrpld/app/ledger/Ledger.h>
|
||||
#include <xrpld/app/misc/SubscriptionHelpers.h>
|
||||
#include <xrpld/app/paths/Flow.h>
|
||||
#include <xrpld/app/tx/detail/MPTokenAuthorize.h>
|
||||
#include <xrpld/app/tx/detail/SubscriptionClaim.h>
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/basics/scope.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/STAccount.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
NotTEC
|
||||
SubscriptionClaim::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
if (!ctx.rules.enabled(featureSubscription))
|
||||
return temDISABLED;
|
||||
|
||||
if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
|
||||
return ret;
|
||||
|
||||
if (ctx.tx.getFlags() & tfUniversalMask)
|
||||
return temINVALID_FLAG;
|
||||
|
||||
return preflight2(ctx);
|
||||
}
|
||||
|
||||
TER
|
||||
SubscriptionClaim::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
auto const sleSub = ctx.view.read(
|
||||
keylet::subscription(ctx.tx.getFieldH256(sfSubscriptionID)));
|
||||
if (!sleSub)
|
||||
{
|
||||
JLOG(ctx.j.trace())
|
||||
<< "SubscriptionClaim: Subscription does not exist.";
|
||||
return tecNO_ENTRY;
|
||||
}
|
||||
|
||||
// Only claim a subscription with this account as the destination.
|
||||
AccountID const dest = sleSub->getAccountID(sfDestination);
|
||||
if (ctx.tx[sfAccount] != dest)
|
||||
{
|
||||
JLOG(ctx.j.trace()) << "SubscriptionClaim: Cashing a subscription with "
|
||||
"wrong Destination.";
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
AccountID const account = sleSub->getAccountID(sfAccount);
|
||||
if (account == dest)
|
||||
{
|
||||
JLOG(ctx.j.trace()) << "SubscriptionClaim: Malformed transaction: "
|
||||
"Cashing subscription to self.";
|
||||
return tecINTERNAL;
|
||||
}
|
||||
{
|
||||
auto const sleSrc = ctx.view.read(keylet::account(account));
|
||||
auto const sleDst = ctx.view.read(keylet::account(dest));
|
||||
if (!sleSrc || !sleDst)
|
||||
{
|
||||
JLOG(ctx.j.trace())
|
||||
<< "SubscriptionClaim: source or destination not in ledger";
|
||||
return tecNO_ENTRY;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
STAmount const amount = ctx.tx.getFieldAmount(sfAmount);
|
||||
STAmount const sleAmount = sleSub->getFieldAmount(sfAmount);
|
||||
if (amount.asset() != sleAmount.asset())
|
||||
{
|
||||
JLOG(ctx.j.trace()) << "SubscriptionClaim: Subscription claim does "
|
||||
"not match subscription currency.";
|
||||
return tecWRONG_ASSET;
|
||||
}
|
||||
|
||||
if (amount > sleAmount)
|
||||
{
|
||||
JLOG(ctx.j.trace()) << "SubscriptionClaim: Claim amount exceeds "
|
||||
"subscription amount.";
|
||||
return temBAD_AMOUNT;
|
||||
}
|
||||
|
||||
// Time/period context
|
||||
std::uint32_t const currentTime =
|
||||
ctx.view.info().parentCloseTime.time_since_epoch().count();
|
||||
std::uint32_t const nextClaimTime =
|
||||
sleSub->getFieldU32(sfNextClaimTime);
|
||||
std::uint32_t const frequency = sleSub->getFieldU32(sfFrequency);
|
||||
|
||||
// Determine effective available balance:
|
||||
// - If we have crossed into a later period AND the previous period had
|
||||
// a partial
|
||||
// balance remaining (carryover not allowed), then the effective
|
||||
// period rolls forward once and its balance resets to sleAmount.
|
||||
// - Otherwise we operate on the period at nextClaimTime with its stored
|
||||
// balance.
|
||||
STAmount balance = sleSub->getFieldAmount(sfBalance);
|
||||
bool const arrears = currentTime >= nextClaimTime + frequency;
|
||||
if (arrears && balance != sleAmount)
|
||||
{
|
||||
// We will effectively operate on (nextClaimTime + frequency) with a
|
||||
// full balance.
|
||||
balance = sleAmount;
|
||||
}
|
||||
|
||||
if (amount > balance)
|
||||
{
|
||||
JLOG(ctx.j.trace())
|
||||
<< "SubscriptionClaim: Claim amount exceeds remaining "
|
||||
"balance for this period.";
|
||||
return tecINSUFFICIENT_FUNDS;
|
||||
}
|
||||
|
||||
if (isXRP(amount))
|
||||
{
|
||||
if (xrpLiquid(ctx.view, account, 0, ctx.j) < amount)
|
||||
return tecINSUFFICIENT_FUNDS;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (auto const ret = std::visit(
|
||||
[&]<typename T>(T const&) {
|
||||
return canTransferTokenHelper<T>(
|
||||
ctx.view, account, dest, amount, ctx.j);
|
||||
},
|
||||
amount.asset().value());
|
||||
!isTesSuccess(ret))
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
// Must be at or past the start of the effective period.
|
||||
if (!hasExpired(ctx.view, sleSub->getFieldU32(sfNextClaimTime)))
|
||||
{
|
||||
JLOG(ctx.j.trace()) << "SubscriptionClaim: The subscription has not "
|
||||
"reached the next claim time.";
|
||||
return tecTOO_SOON;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
template <ValidIssueType T>
|
||||
static TER
|
||||
doTransferTokenHelper(
|
||||
ApplyView& view,
|
||||
std::shared_ptr<SLE> const& sleDest,
|
||||
STAmount const& xrpBalance,
|
||||
STAmount const& amount,
|
||||
AccountID const& issuer,
|
||||
AccountID const& sender,
|
||||
AccountID const& receiver,
|
||||
bool createAsset,
|
||||
beast::Journal journal);
|
||||
|
||||
template <>
|
||||
TER
|
||||
doTransferTokenHelper<Issue>(
|
||||
ApplyView& view,
|
||||
std::shared_ptr<SLE> const& sleDest,
|
||||
STAmount const& xrpBalance,
|
||||
STAmount const& amount,
|
||||
AccountID const& issuer,
|
||||
AccountID const& sender,
|
||||
AccountID const& receiver,
|
||||
bool createAsset,
|
||||
beast::Journal journal)
|
||||
{
|
||||
Keylet const trustLineKey = keylet::line(receiver, amount.issue());
|
||||
bool const recvLow = issuer > receiver;
|
||||
|
||||
// Review Note: We could remove this and just say to use batch to auth the
|
||||
// token first
|
||||
if (!view.exists(trustLineKey) && createAsset && issuer != receiver)
|
||||
{
|
||||
// Can the account cover the trust line's reserve?
|
||||
if (std::uint32_t const ownerCount = {sleDest->at(sfOwnerCount)};
|
||||
xrpBalance < view.fees().accountReserve(ownerCount + 1))
|
||||
{
|
||||
JLOG(journal.trace())
|
||||
<< "doTransferTokenHelper: Trust line does not exist. "
|
||||
"Insufficent reserve to create line.";
|
||||
|
||||
return tecNO_LINE_INSUF_RESERVE;
|
||||
}
|
||||
|
||||
Currency const currency = amount.getCurrency();
|
||||
STAmount initialBalance(amount.issue());
|
||||
initialBalance.setIssuer(noAccount());
|
||||
|
||||
// clang-format off
|
||||
if (TER const ter = trustCreate(
|
||||
view, // payment sandbox
|
||||
recvLow, // is dest low?
|
||||
issuer, // source
|
||||
receiver, // destination
|
||||
trustLineKey.key, // ledger index
|
||||
sleDest, // Account to add to
|
||||
false, // authorize account
|
||||
(sleDest->getFlags() & lsfDefaultRipple) == 0,
|
||||
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
|
||||
journal); // journal
|
||||
!isTesSuccess(ter))
|
||||
{
|
||||
JLOG(journal.trace()) << "doTransferTokenHelper: Failed to create trust line: " << transToken(ter);
|
||||
return ter;
|
||||
}
|
||||
// clang-format on
|
||||
|
||||
view.update(sleDest);
|
||||
}
|
||||
|
||||
if (!view.exists(trustLineKey) && issuer != receiver)
|
||||
return tecNO_LINE;
|
||||
|
||||
auto const ter = accountSend(
|
||||
view, sender, receiver, amount, journal, WaiveTransferFee::No);
|
||||
if (ter != tesSUCCESS)
|
||||
{
|
||||
JLOG(journal.trace()) << "doTransferTokenHelper: Failed to send token: "
|
||||
<< transToken(ter);
|
||||
return ter; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
template <>
|
||||
TER
|
||||
doTransferTokenHelper<MPTIssue>(
|
||||
ApplyView& view,
|
||||
std::shared_ptr<SLE> const& sleDest,
|
||||
STAmount const& xrpBalance,
|
||||
STAmount const& amount,
|
||||
AccountID const& issuer,
|
||||
AccountID const& sender,
|
||||
AccountID const& receiver,
|
||||
bool createAsset,
|
||||
beast::Journal journal)
|
||||
{
|
||||
auto const mptID = amount.get<MPTIssue>().getMptID();
|
||||
auto const issuanceKey = keylet::mptIssuance(mptID);
|
||||
if (!view.exists(keylet::mptoken(issuanceKey.key, receiver)) && createAsset)
|
||||
{
|
||||
if (std::uint32_t const ownerCount = {sleDest->at(sfOwnerCount)};
|
||||
xrpBalance < view.fees().accountReserve(ownerCount + 1))
|
||||
{
|
||||
JLOG(journal.trace())
|
||||
<< "doTransferTokenHelper: MPT does not exist. "
|
||||
"Insufficent reserve to create MPT.";
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
}
|
||||
|
||||
if (auto const ter =
|
||||
MPTokenAuthorize::createMPToken(view, mptID, receiver, 0);
|
||||
!isTesSuccess(ter))
|
||||
{
|
||||
JLOG(journal.trace())
|
||||
<< "doTransferTokenHelper: Failed to create MPT: "
|
||||
<< transToken(ter);
|
||||
return ter;
|
||||
}
|
||||
|
||||
// Update owner count.
|
||||
adjustOwnerCount(view, sleDest, 1, journal);
|
||||
}
|
||||
|
||||
if (!view.exists(keylet::mptoken(issuanceKey.key, receiver)))
|
||||
{
|
||||
JLOG(journal.trace()) << "doTransferTokenHelper: MPT does not exist.";
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
|
||||
auto const ter = accountSend(
|
||||
view, sender, receiver, amount, journal, WaiveTransferFee::No);
|
||||
if (ter != tesSUCCESS)
|
||||
{
|
||||
JLOG(journal.trace())
|
||||
<< "doTransferTokenHelper: Failed to send MPT: " << transToken(ter);
|
||||
return ter; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
SubscriptionClaim::doApply()
|
||||
{
|
||||
PaymentSandbox psb(&ctx_.view());
|
||||
auto viewJ = ctx_.app.journal("View");
|
||||
|
||||
auto sleSub =
|
||||
psb.peek(keylet::subscription(ctx_.tx.getFieldH256(sfSubscriptionID)));
|
||||
if (!sleSub)
|
||||
{
|
||||
JLOG(j_.trace()) << "SubscriptionClaim: Subscription does not exist.";
|
||||
return tecINTERNAL;
|
||||
}
|
||||
|
||||
AccountID const account = sleSub->getAccountID(sfAccount);
|
||||
if (!psb.exists(keylet::account(account)))
|
||||
{
|
||||
JLOG(j_.trace()) << "SubscriptionClaim: Account does not exist.";
|
||||
return tecINTERNAL;
|
||||
}
|
||||
|
||||
AccountID const dest = sleSub->getAccountID(sfDestination);
|
||||
if (!psb.exists(keylet::account(dest)))
|
||||
{
|
||||
JLOG(j_.trace()) << "SubscriptionClaim: Account does not exist.";
|
||||
return tecINTERNAL;
|
||||
}
|
||||
|
||||
if (dest != ctx_.tx.getAccountID(sfAccount))
|
||||
{
|
||||
JLOG(j_.trace()) << "SubscriptionClaim: Account is not the "
|
||||
"destination of the subscription.";
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
|
||||
STAmount const sleAmount = sleSub->getFieldAmount(sfAmount);
|
||||
STAmount const deliverAmount = ctx_.tx.getFieldAmount(sfAmount);
|
||||
|
||||
// Pull current period info
|
||||
std::uint32_t const currentTime =
|
||||
psb.info().parentCloseTime.time_since_epoch().count();
|
||||
std::uint32_t nextClaimTime = sleSub->getFieldU32(sfNextClaimTime);
|
||||
std::uint32_t const frequency = sleSub->getFieldU32(sfFrequency);
|
||||
|
||||
STAmount availableBalance = sleSub->getFieldAmount(sfBalance);
|
||||
bool const arrears = currentTime >= nextClaimTime + frequency;
|
||||
|
||||
// If we crossed into a later period and the previous period was partially
|
||||
// used, forfeit the leftover and roll forward exactly one period; reset the
|
||||
// balance.
|
||||
if (arrears && availableBalance != sleAmount)
|
||||
{
|
||||
nextClaimTime += frequency;
|
||||
availableBalance = sleAmount;
|
||||
|
||||
// Reflect the rollover immediately in the SLE so subsequent logic is
|
||||
// consistent.
|
||||
sleSub->setFieldU32(sfNextClaimTime, nextClaimTime);
|
||||
sleSub->setFieldAmount(sfBalance, availableBalance);
|
||||
}
|
||||
|
||||
// Enforce available balance for the effective period.
|
||||
if (deliverAmount > availableBalance)
|
||||
{
|
||||
JLOG(j_.trace()) << "SubscriptionClaim: Claim amount exceeds remaining "
|
||||
<< "balance for this period.";
|
||||
return tecINTERNAL;
|
||||
}
|
||||
|
||||
// Perform the transfer
|
||||
if (isXRP(deliverAmount))
|
||||
{
|
||||
if (TER const ter{
|
||||
transferXRP(psb, account, dest, deliverAmount, viewJ)};
|
||||
ter != tesSUCCESS)
|
||||
{
|
||||
return ter;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (auto const ret = std::visit(
|
||||
[&]<typename T>(T const&) {
|
||||
return doTransferTokenHelper<T>(
|
||||
psb,
|
||||
psb.peek(keylet::account(dest)),
|
||||
mPriorBalance,
|
||||
deliverAmount,
|
||||
deliverAmount.getIssuer(),
|
||||
account,
|
||||
dest,
|
||||
true, // create asset
|
||||
viewJ);
|
||||
},
|
||||
deliverAmount.asset().value());
|
||||
!isTesSuccess(ret))
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Update balance and period pointer
|
||||
STAmount const newBalance = availableBalance - deliverAmount;
|
||||
|
||||
if (newBalance == sleAmount.zeroed())
|
||||
{
|
||||
// Full period claimed: advance exactly one period and reset next period
|
||||
// balance.
|
||||
nextClaimTime += frequency;
|
||||
sleSub->setFieldU32(sfNextClaimTime, nextClaimTime);
|
||||
sleSub->setFieldAmount(sfBalance, sleAmount);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Partial claim within the same effective period.
|
||||
sleSub->setFieldAmount(sfBalance, newBalance);
|
||||
// Do not advance nextClaimTime; if we had a rollover-forfeit above,
|
||||
// we already moved nextClaimTime forward exactly once.
|
||||
}
|
||||
|
||||
psb.update(sleSub);
|
||||
|
||||
if (sleSub->isFieldPresent(sfExpiration) &&
|
||||
psb.info().parentCloseTime.time_since_epoch().count() >=
|
||||
sleSub->getFieldU32(sfExpiration))
|
||||
{
|
||||
psb.erase(sleSub);
|
||||
}
|
||||
|
||||
psb.apply(ctx_.rawView());
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
48
src/xrpld/app/tx/detail/SubscriptionClaim.h
Normal file
48
src/xrpld/app/tx/detail/SubscriptionClaim.h
Normal file
@@ -0,0 +1,48 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2024 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_TX_SUBSCRIPTIONCLAIM_H_INCLUDED
|
||||
#define RIPPLE_TX_SUBSCRIPTIONCLAIM_H_INCLUDED
|
||||
|
||||
#include <xrpld/app/tx/detail/Transactor.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
class SubscriptionClaim : public Transactor
|
||||
{
|
||||
public:
|
||||
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
|
||||
|
||||
explicit SubscriptionClaim(ApplyContext& ctx) : Transactor(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
};
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
#endif // RIPPLE_TX_SUBSCRIPTIONCLAIM_H_INCLUDED
|
||||
337
src/xrpld/app/tx/detail/SubscriptionSet.cpp
Normal file
337
src/xrpld/app/tx/detail/SubscriptionSet.cpp
Normal file
@@ -0,0 +1,337 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2025 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <xrpld/app/ledger/Ledger.h>
|
||||
#include <xrpld/app/misc/SubscriptionHelpers.h>
|
||||
#include <xrpld/app/paths/Flow.h>
|
||||
#include <xrpld/app/tx/detail/SubscriptionSet.h>
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/basics/scope.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/STAccount.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
template <ValidIssueType T>
|
||||
static NotTEC
|
||||
setPreflightHelper(PreflightContext const& ctx);
|
||||
|
||||
template <>
|
||||
NotTEC
|
||||
setPreflightHelper<Issue>(PreflightContext const& ctx)
|
||||
{
|
||||
STAmount const amount = ctx.tx[sfAmount];
|
||||
if (amount.native() || amount <= beast::zero)
|
||||
return temBAD_AMOUNT;
|
||||
|
||||
if (badCurrency() == amount.getCurrency())
|
||||
return temBAD_CURRENCY;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
template <>
|
||||
NotTEC
|
||||
setPreflightHelper<MPTIssue>(PreflightContext const& ctx)
|
||||
{
|
||||
if (!ctx.rules.enabled(featureMPTokensV1))
|
||||
return temDISABLED;
|
||||
|
||||
auto const amount = ctx.tx[sfAmount];
|
||||
if (amount.native() || amount.mpt() > MPTAmount{maxMPTokenAmount} ||
|
||||
amount <= beast::zero)
|
||||
return temBAD_AMOUNT;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
NotTEC
|
||||
SubscriptionSet::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
if (!ctx.rules.enabled(featureSubscription))
|
||||
return temDISABLED;
|
||||
|
||||
if (ctx.tx.getFlags() & tfUniversalMask)
|
||||
return temINVALID_FLAG;
|
||||
|
||||
if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
|
||||
return ret;
|
||||
|
||||
if (ctx.tx.isFieldPresent(sfSubscriptionID))
|
||||
{
|
||||
// update
|
||||
if (!ctx.tx.isFieldPresent(sfAmount))
|
||||
{
|
||||
JLOG(ctx.j.trace())
|
||||
<< "SubscriptionSet: Malformed transaction: SubscriptionID "
|
||||
"is present, but Amount is not.";
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
if (ctx.tx.isFieldPresent(sfDestination) ||
|
||||
ctx.tx.isFieldPresent(sfFrequency) ||
|
||||
ctx.tx.isFieldPresent(sfStartTime))
|
||||
{
|
||||
JLOG(ctx.j.trace())
|
||||
<< "SubscriptionSet: Malformed transaction: SubscriptionID "
|
||||
"is present, but optional fields are also present.";
|
||||
return temMALFORMED;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// create
|
||||
if (!ctx.tx.isFieldPresent(sfDestination) ||
|
||||
!ctx.tx.isFieldPresent(sfAmount) ||
|
||||
!ctx.tx.isFieldPresent(sfFrequency))
|
||||
{
|
||||
JLOG(ctx.j.trace())
|
||||
<< "SubscriptionSet: Malformed transaction: SubscriptionID "
|
||||
"is not present, and required fields are not present.";
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
if (ctx.tx.getAccountID(sfDestination) ==
|
||||
ctx.tx.getAccountID(sfAccount))
|
||||
{
|
||||
JLOG(ctx.j.trace())
|
||||
<< "SubscriptionSet: Malformed transaction: Account "
|
||||
"is the same as the destination.";
|
||||
return temDST_IS_SRC;
|
||||
}
|
||||
}
|
||||
|
||||
STAmount const amount = ctx.tx.getFieldAmount(sfAmount);
|
||||
if (amount.native())
|
||||
{
|
||||
if (!isLegalNet(amount) || amount <= beast::zero)
|
||||
{
|
||||
JLOG(ctx.j.trace())
|
||||
<< "SubscriptionSet: Malformed transaction: bad amount: "
|
||||
<< amount.getFullText();
|
||||
return temBAD_AMOUNT;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (auto const ret = std::visit(
|
||||
[&]<typename T>(T const&) {
|
||||
return setPreflightHelper<T>(ctx);
|
||||
},
|
||||
amount.asset().value());
|
||||
!isTesSuccess(ret))
|
||||
return ret;
|
||||
}
|
||||
|
||||
return preflight2(ctx);
|
||||
}
|
||||
|
||||
TER
|
||||
SubscriptionSet::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
STAmount const amount = ctx.tx.getFieldAmount(sfAmount);
|
||||
AccountID const account = ctx.tx.getAccountID(sfAccount);
|
||||
AccountID const dest = ctx.tx.getAccountID(sfDestination);
|
||||
if (ctx.tx.isFieldPresent(sfSubscriptionID))
|
||||
{
|
||||
// update
|
||||
auto sle = ctx.view.read(
|
||||
keylet::subscription(ctx.tx.getFieldH256(sfSubscriptionID)));
|
||||
if (!sle)
|
||||
{
|
||||
JLOG(ctx.j.trace())
|
||||
<< "SubscriptionSet: Subscription does not exist.";
|
||||
return tecNO_ENTRY;
|
||||
}
|
||||
|
||||
if (sle->getAccountID(sfAccount) != ctx.tx.getAccountID(sfAccount))
|
||||
{
|
||||
JLOG(ctx.j.trace()) << "SubscriptionSet: Account is not the "
|
||||
"owner of the subscription.";
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// create
|
||||
auto const sleDest =
|
||||
ctx.view.read(keylet::account(ctx.tx.getAccountID(sfDestination)));
|
||||
if (!sleDest)
|
||||
{
|
||||
JLOG(ctx.j.trace())
|
||||
<< "SubscriptionSet: Destination account does not exist.";
|
||||
return tecNO_DST;
|
||||
}
|
||||
|
||||
auto const flags = sleDest->getFlags();
|
||||
if ((flags & lsfRequireDestTag) && !ctx.tx[~sfDestinationTag])
|
||||
return tecDST_TAG_NEEDED;
|
||||
|
||||
if (ctx.tx.getFieldU32(sfFrequency) <= 0)
|
||||
{
|
||||
JLOG(ctx.j.trace())
|
||||
<< "SubscriptionSet: The frequency is less than or equal to 0.";
|
||||
return temMALFORMED;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isXRP(amount))
|
||||
{
|
||||
if (auto const ret = std::visit(
|
||||
[&]<typename T>(T const&) {
|
||||
return canTransferTokenHelper<T>(
|
||||
ctx.view, account, dest, amount, ctx.j);
|
||||
},
|
||||
amount.asset().value());
|
||||
!isTesSuccess(ret))
|
||||
return ret;
|
||||
}
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
SubscriptionSet::doApply()
|
||||
{
|
||||
Sandbox sb(&ctx_.view());
|
||||
|
||||
AccountID const account = ctx_.tx.getAccountID(sfAccount);
|
||||
auto const sleAccount = sb.peek(keylet::account(account));
|
||||
if (!sleAccount)
|
||||
{
|
||||
JLOG(ctx_.journal.trace())
|
||||
<< "SubscriptionSet: Account does not exist.";
|
||||
return tecINTERNAL;
|
||||
}
|
||||
|
||||
if (ctx_.tx.isFieldPresent(sfSubscriptionID))
|
||||
{
|
||||
// update
|
||||
auto sle = sb.peek(
|
||||
keylet::subscription(ctx_.tx.getFieldH256(sfSubscriptionID)));
|
||||
sle->setFieldAmount(sfAmount, ctx_.tx.getFieldAmount(sfAmount));
|
||||
if (ctx_.tx.isFieldPresent(sfExpiration))
|
||||
{
|
||||
auto const currentTime =
|
||||
sb.info().parentCloseTime.time_since_epoch().count();
|
||||
auto const expiration = ctx_.tx.getFieldU32(sfExpiration);
|
||||
|
||||
if (expiration < currentTime)
|
||||
{
|
||||
JLOG(ctx_.journal.trace())
|
||||
<< "SubscriptionSet: The expiration time is in the past.";
|
||||
return temBAD_EXPIRATION;
|
||||
}
|
||||
|
||||
sle->setFieldU32(sfExpiration, ctx_.tx.getFieldU32(sfExpiration));
|
||||
}
|
||||
|
||||
sb.update(sle);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto const currentTime =
|
||||
sb.info().parentCloseTime.time_since_epoch().count();
|
||||
auto startTime = currentTime;
|
||||
auto nextClaimTime = currentTime;
|
||||
|
||||
// create
|
||||
{
|
||||
auto const balance = STAmount((*sleAccount)[sfBalance]).xrp();
|
||||
auto const reserve =
|
||||
sb.fees().accountReserve((*sleAccount)[sfOwnerCount] + 1);
|
||||
if (balance < reserve)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
}
|
||||
|
||||
AccountID const dest = ctx_.tx.getAccountID(sfDestination);
|
||||
Keylet const subKeylet =
|
||||
keylet::subscription(account, dest, ctx_.tx.getSeqProxy().value());
|
||||
auto sle = std::make_shared<SLE>(subKeylet);
|
||||
sle->setAccountID(sfAccount, account);
|
||||
sle->setAccountID(sfDestination, dest);
|
||||
if (ctx_.tx.isFieldPresent(sfDestinationTag))
|
||||
sle->setFieldU32(
|
||||
sfDestinationTag, ctx_.tx.getFieldU32(sfDestinationTag));
|
||||
sle->setFieldAmount(sfAmount, ctx_.tx.getFieldAmount(sfAmount));
|
||||
sle->setFieldAmount(sfBalance, ctx_.tx.getFieldAmount(sfAmount));
|
||||
sle->setFieldU32(sfFrequency, ctx_.tx.getFieldU32(sfFrequency));
|
||||
if (ctx_.tx.isFieldPresent(sfStartTime))
|
||||
{
|
||||
startTime = ctx_.tx.getFieldU32(sfStartTime);
|
||||
nextClaimTime = startTime;
|
||||
if (startTime < currentTime)
|
||||
{
|
||||
JLOG(ctx_.journal.trace())
|
||||
<< "SubscriptionSet: The start time is in the past.";
|
||||
return temMALFORMED;
|
||||
}
|
||||
}
|
||||
|
||||
sle->setFieldU32(sfNextClaimTime, nextClaimTime);
|
||||
if (ctx_.tx.isFieldPresent(sfExpiration))
|
||||
{
|
||||
auto const expiration = ctx_.tx.getFieldU32(sfExpiration);
|
||||
|
||||
if (expiration < currentTime)
|
||||
{
|
||||
JLOG(ctx_.journal.trace())
|
||||
<< "SubscriptionSet: The expiration time is in the past.";
|
||||
return temBAD_EXPIRATION;
|
||||
}
|
||||
|
||||
if (expiration < nextClaimTime)
|
||||
{
|
||||
JLOG(ctx_.journal.trace())
|
||||
<< "SubscriptionSet: The expiration time is "
|
||||
"less than the next claim time.";
|
||||
return temBAD_EXPIRATION;
|
||||
}
|
||||
sle->setFieldU32(sfExpiration, expiration);
|
||||
}
|
||||
|
||||
{
|
||||
auto page = sb.dirInsert(
|
||||
keylet::ownerDir(account),
|
||||
subKeylet,
|
||||
describeOwnerDir(account));
|
||||
if (!page)
|
||||
return tecDIR_FULL;
|
||||
(*sle)[sfOwnerNode] = *page;
|
||||
}
|
||||
|
||||
{
|
||||
auto page = sb.dirInsert(
|
||||
keylet::ownerDir(dest), subKeylet, describeOwnerDir(dest));
|
||||
if (!page)
|
||||
return tecDIR_FULL;
|
||||
(*sle)[sfDestinationNode] = *page;
|
||||
}
|
||||
|
||||
adjustOwnerCount(sb, sleAccount, 1, ctx_.journal);
|
||||
sb.insert(sle);
|
||||
}
|
||||
sb.apply(ctx_.rawView());
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
48
src/xrpld/app/tx/detail/SubscriptionSet.h
Normal file
48
src/xrpld/app/tx/detail/SubscriptionSet.h
Normal file
@@ -0,0 +1,48 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2024 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_TX_SUBSCRIPTIONSET_H_INCLUDED
|
||||
#define RIPPLE_TX_SUBSCRIPTIONSET_H_INCLUDED
|
||||
|
||||
#include <xrpld/app/tx/detail/Transactor.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
class SubscriptionSet : public Transactor
|
||||
{
|
||||
public:
|
||||
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
|
||||
|
||||
explicit SubscriptionSet(ApplyContext& ctx) : Transactor(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
};
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
#endif // RIPPLE_TX_SUBSCRIPTIONSET_H_INCLUDED
|
||||
@@ -62,6 +62,9 @@
|
||||
#include <xrpld/app/tx/detail/SetRegularKey.h>
|
||||
#include <xrpld/app/tx/detail/SetSignerList.h>
|
||||
#include <xrpld/app/tx/detail/SetTrust.h>
|
||||
#include <xrpld/app/tx/detail/SubscriptionCancel.h>
|
||||
#include <xrpld/app/tx/detail/SubscriptionClaim.h>
|
||||
#include <xrpld/app/tx/detail/SubscriptionSet.h>
|
||||
#include <xrpld/app/tx/detail/VaultClawback.h>
|
||||
#include <xrpld/app/tx/detail/VaultCreate.h>
|
||||
#include <xrpld/app/tx/detail/VaultDelete.h>
|
||||
@@ -97,8 +100,8 @@ with_txn_type(TxType txnType, F&& f)
|
||||
#pragma push_macro("TRANSACTION")
|
||||
#undef TRANSACTION
|
||||
|
||||
#define TRANSACTION(tag, value, name, ...) \
|
||||
case tag: \
|
||||
#define TRANSACTION(tag, value, name, delegatable, fields) \
|
||||
case tag: \
|
||||
return f.template operator()<name>();
|
||||
|
||||
#include <xrpl/protocol/detail/transactions.macro>
|
||||
|
||||
@@ -39,96 +39,53 @@ namespace RPC {
|
||||
// The Concise Transaction ID provides a way to identify a transaction
|
||||
// that includes which network the transaction was submitted to.
|
||||
|
||||
/**
|
||||
* @brief Encodes ledger sequence, transaction index, and network ID into a CTID
|
||||
* string.
|
||||
*
|
||||
* @param ledgerSeq Ledger sequence number (max 0x0FFF'FFFF).
|
||||
* @param txnIndex Transaction index within the ledger (max 0xFFFF).
|
||||
* @param networkID Network identifier (max 0xFFFF).
|
||||
* @return Optional CTID string in uppercase hexadecimal, or std::nullopt if
|
||||
* inputs are out of range.
|
||||
*/
|
||||
inline std::optional<std::string>
|
||||
encodeCTID(uint32_t ledgerSeq, uint32_t txnIndex, uint32_t networkID) noexcept
|
||||
{
|
||||
constexpr uint32_t maxLedgerSeq = 0x0FFF'FFFF;
|
||||
constexpr uint32_t maxTxnIndex = 0xFFFF;
|
||||
constexpr uint32_t maxNetworkID = 0xFFFF;
|
||||
|
||||
if (ledgerSeq > maxLedgerSeq || txnIndex > maxTxnIndex ||
|
||||
networkID > maxNetworkID)
|
||||
return std::nullopt;
|
||||
if (ledgerSeq > 0x0FFF'FFFF || txnIndex > 0xFFFF || networkID > 0xFFFF)
|
||||
return {};
|
||||
|
||||
uint64_t ctidValue =
|
||||
((0xC000'0000ULL + static_cast<uint64_t>(ledgerSeq)) << 32) |
|
||||
((static_cast<uint64_t>(txnIndex) << 16) | networkID);
|
||||
((0xC000'0000ULL + static_cast<uint64_t>(ledgerSeq)) << 32) +
|
||||
(static_cast<uint64_t>(txnIndex) << 16) + networkID;
|
||||
|
||||
std::stringstream buffer;
|
||||
buffer << std::hex << std::uppercase << std::setfill('0') << std::setw(16)
|
||||
<< ctidValue;
|
||||
return buffer.str();
|
||||
return {buffer.str()};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Decodes a CTID string or integer into its component parts.
|
||||
*
|
||||
* @tparam T Type of the CTID input (string, string_view, char*, integral).
|
||||
* @param ctid CTID value to decode.
|
||||
* @return Optional tuple of (ledgerSeq, txnIndex, networkID), or std::nullopt
|
||||
* if invalid.
|
||||
*/
|
||||
template <typename T>
|
||||
inline std::optional<std::tuple<uint32_t, uint16_t, uint16_t>>
|
||||
decodeCTID(T const ctid) noexcept
|
||||
{
|
||||
uint64_t ctidValue = 0;
|
||||
|
||||
uint64_t ctidValue{0};
|
||||
if constexpr (
|
||||
std::is_same_v<T, std::string> || std::is_same_v<T, std::string_view> ||
|
||||
std::is_same_v<T, char*> || std::is_same_v<T, char const*>)
|
||||
std::is_same_v<T, std::string> || std::is_same_v<T, char*> ||
|
||||
std::is_same_v<T, char const*> || std::is_same_v<T, std::string_view>)
|
||||
{
|
||||
std::string const ctidString(ctid);
|
||||
|
||||
if (ctidString.size() != 16)
|
||||
return std::nullopt;
|
||||
if (ctidString.length() != 16)
|
||||
return {};
|
||||
|
||||
static boost::regex const hexRegex("^[0-9A-Fa-f]{16}$");
|
||||
if (!boost::regex_match(ctidString, hexRegex))
|
||||
return std::nullopt;
|
||||
if (!boost::regex_match(ctidString, boost::regex("^[0-9A-Fa-f]+$")))
|
||||
return {};
|
||||
|
||||
try
|
||||
{
|
||||
ctidValue = std::stoull(ctidString, nullptr, 16);
|
||||
}
|
||||
// LCOV_EXCL_START
|
||||
catch (...)
|
||||
{
|
||||
// should be impossible to hit given the length/regex check
|
||||
return std::nullopt;
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
ctidValue = std::stoull(ctidString, nullptr, 16);
|
||||
}
|
||||
else if constexpr (std::is_integral_v<T>)
|
||||
{
|
||||
ctidValue = static_cast<uint64_t>(ctid);
|
||||
}
|
||||
ctidValue = ctid;
|
||||
else
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
return {};
|
||||
|
||||
// Validate CTID prefix.
|
||||
constexpr uint64_t ctidPrefixMask = 0xF000'0000'0000'0000ULL;
|
||||
constexpr uint64_t ctidPrefix = 0xC000'0000'0000'0000ULL;
|
||||
if ((ctidValue & ctidPrefixMask) != ctidPrefix)
|
||||
return std::nullopt;
|
||||
if ((ctidValue & 0xF000'0000'0000'0000ULL) != 0xC000'0000'0000'0000ULL)
|
||||
return {};
|
||||
|
||||
uint32_t ledgerSeq = static_cast<uint32_t>((ctidValue >> 32) & 0x0FFF'FFFF);
|
||||
uint16_t txnIndex = static_cast<uint16_t>((ctidValue >> 16) & 0xFFFF);
|
||||
uint16_t networkID = static_cast<uint16_t>(ctidValue & 0xFFFF);
|
||||
|
||||
return std::make_tuple(ledgerSeq, txnIndex, networkID);
|
||||
uint32_t ledger_seq = (ctidValue >> 32) & 0xFFFF'FFFUL;
|
||||
uint16_t txn_index = (ctidValue >> 16) & 0xFFFFU;
|
||||
uint16_t network_id = ctidValue & 0xFFFFU;
|
||||
return {{ledger_seq, txn_index, network_id}};
|
||||
}
|
||||
|
||||
} // namespace RPC
|
||||
|
||||
@@ -681,6 +681,32 @@ parseXChainOwnedCreateAccountClaimID(
|
||||
return keylet.key;
|
||||
}
|
||||
|
||||
static Expected<uint256, Json::Value>
|
||||
parseSubscription(Json::Value const& params, Json::StaticString const fieldName)
|
||||
{
|
||||
if (!params.isObject())
|
||||
{
|
||||
return parseObjectID(params, fieldName);
|
||||
}
|
||||
|
||||
auto const account = LedgerEntryHelpers::requiredAccountID(
|
||||
params, jss::account, "malformedAccount");
|
||||
if (!account)
|
||||
return Unexpected(account.error());
|
||||
|
||||
auto const destination = LedgerEntryHelpers::requiredAccountID(
|
||||
params, jss::destination, "malformedDestination");
|
||||
if (!destination)
|
||||
return Unexpected(destination.error());
|
||||
|
||||
auto const seq = LedgerEntryHelpers::requiredUInt32(
|
||||
params, jss::seq, "malformedRequest");
|
||||
if (!seq)
|
||||
return Unexpected(seq.error());
|
||||
|
||||
return keylet::subscription(*account, *destination, *seq).key;
|
||||
}
|
||||
|
||||
using FunctionType = Expected<uint256, Json::Value> (*)(
|
||||
Json::Value const&,
|
||||
Json::StaticString const);
|
||||
|
||||
@@ -24,13 +24,10 @@
|
||||
#include <xrpld/app/misc/TxQ.h>
|
||||
#include <xrpld/app/tx/apply.h>
|
||||
#include <xrpld/rpc/Context.h>
|
||||
#include <xrpld/rpc/DeliveredAmount.h>
|
||||
#include <xrpld/rpc/GRPCHandlers.h>
|
||||
#include <xrpld/rpc/MPTokenIssuanceID.h>
|
||||
#include <xrpld/rpc/detail/TransactionSign.h>
|
||||
|
||||
#include <xrpl/protocol/ErrorCodes.h>
|
||||
#include <xrpl/protocol/NFTSyntheticSerializer.h>
|
||||
#include <xrpl/protocol/RPCErr.h>
|
||||
#include <xrpl/protocol/STParsedJSON.h>
|
||||
#include <xrpl/resource/Fees.h>
|
||||
@@ -275,17 +272,6 @@ simulateTxn(RPC::JsonContext& context, std::shared_ptr<Transaction> transaction)
|
||||
else
|
||||
{
|
||||
jvResult[jss::meta] = result.metadata->getJson(JsonOptions::none);
|
||||
RPC::insertDeliveredAmount(
|
||||
jvResult[jss::meta],
|
||||
view,
|
||||
transaction->getSTransaction(),
|
||||
*result.metadata);
|
||||
RPC::insertNFTSyntheticInJson(
|
||||
jvResult, transaction->getSTransaction(), *result.metadata);
|
||||
RPC::insertMPTokenIssuanceID(
|
||||
jvResult[jss::meta],
|
||||
transaction->getSTransaction(),
|
||||
*result.metadata);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user