Compare commits

...
Author SHA1 Message Date
Nicholas Dudfield 6238cb7109 test(amendment): cover unsupported ledger jump shutdown 2026-09-07 18:49:31 +07:00
Richard Holland 0cd4f6aae4 test cases and more useful message 2026-09-07 19:40:52 +10:00
Richard Holland a4b0a90998 Merge branch 'dev' into fail-fast-amendment-blocking 2026-09-07 13:25:17 +10:00
Richard Holland 6191c1e09d Onchain manifests (#779) 2026-09-07 13:19:25 +10:00
Nicholas Dudfield 6981b5b06b refactor(app): colocate amendment receipt helpers 2026-09-02 16:35:50 +07:00
tequ f1a3a775a1 Add optional Hooks data to account_info response (#789) 2026-09-02 17:22:53 +10:00
Nicholas Dudfield d4a681430a fix(amendment): persist blocked shutdown receipt 2026-09-02 13:44:53 +07:00
Nicholas Dudfield 3760e1fb41 fix: begin unsupported amendment shutdown five minutes early 2026-09-01 13:06:13 +07:00
tequ c1dabe5576 Fix comments (#768) 2026-09-01 14:18:28 +10:00
Nicholas Dudfield 6442e0990b Merge remote-tracking branch 'origin/dev' into fail-fast-amendment-blocking 2026-09-01 09:41:41 +07:00
Niq Dudfield c6d5b345eb fix: subscription webhook delivery stalling on HTTP errors (#677) 2026-08-27 12:13:21 +10:00
Richard Holland bb244ef772 put release builds into a candidate folder to prevent auto-update scripts running before smoke tests (#761) 2026-06-21 12:12:43 +10:00
Nicholas Dudfield 1e6cda4d64 fix: add fatal log on amendment-blocked shutdown 2026-06-19 09:35:39 +07:00
Nicholas Dudfield c146f15247 fix: rethrow runtime_error if not amendment blocked 2026-06-19 09:35:39 +07:00
Nicholas Dudfield fb5081d1f4 fix: narrow catch to std::runtime_error in switchLastClosedLedger 2026-06-19 09:35:39 +07:00
Nicholas Dudfield d4bec012a2 fix: skip signalStop in standalone mode for test compatibility 2026-06-19 09:35:39 +07:00
Nicholas Dudfield f7187ba94f fix: fail fast when amendment blocked instead of zombie state
- signalStop() for graceful shutdown when unsupported amendment activates
- early shutdown ~1 minute before expected activation to avoid race
- try/catch in switchLastClosedLedger to survive unknown field crashes
  during shutdown window
- show amendment warning to all RPC users, not just admin

Fixes: #706
2026-06-19 09:35:39 +07:00
Richard Holland 639ea34377 Fixhookmap (#756) 2026-06-16 17:06:25 +10:00
tequ 089c0dc3fe Fix ammLPHolds logic to include escrowed cases (#757) 2026-06-16 15:26:29 +10:00
tequ 78167e09c0 change sfHookOn of ltHookDefinition to soeOptional for HookOnV2 (#755) 2026-06-16 13:58:53 +10:00
tequ 607a7fdf98 Change AMM to Supported::no (#758) 2026-06-16 13:56:58 +10:00
tequ c55420bcd8 Fix duplicate and incorrect fields in server_definitions (#753) 2026-05-27 07:58:13 +10:00
tequ cb91b4e88e Restore pre-reserved tem codes (#754) 2026-05-27 07:57:55 +10:00
tequ 90333b6fd0 Fix HookAPI Expected and Refactor Enum classes (#729) 2026-05-26 11:02:17 +10:00
tequ 7f9a9364b0 fix: Ensures canonical order for PriceDataSeries upon PriceOracle creation (#5485) (#744) 2026-05-25 11:34:24 +10:00
tequ 706d31f01d Update .git-blame-ignore-revs (#746) 2026-05-25 11:28:56 +10:00
tequ 083e9e4315 Generate hook extern declarations with C linkage (#750) 2026-05-25 11:27:21 +10:00
tequ b9e0c56def Guard depth 32 (#653) 2026-05-25 11:10:49 +10:00
tequ 663ed4edb8 Named Hook (#718) 2026-05-19 12:00:25 +10:00
tequ 8422758d4d chore: Improve codecov coverage reporting (#4977) (#745) 2026-05-18 12:21:47 +10:00
tequ 586c78e812 fix: Replace badCurrency() checks with isBadCurrency() for improved clarity (#742) 2026-05-18 11:57:25 +10:00
Niq Dudfield 9b50b68d39 perf(ledger): optimize catalogue loading memory usage and performance (#548) 2026-05-06 17:29:44 +10:00
tequandEd Hennis 5e8d26f67a refactor: Calculate numFeatures automatically (#5324) (#739)
Co-authored-by: Ed Hennis <[email protected]>
2026-04-30 18:17:50 +10:00
tequ a6186d7855 IOURewardClaim (#500) 2026-04-30 15:27:51 +10:00
tequ b449599408 Add --definitions CLI flag to output static server definitions without starting server (#708) 2026-04-30 12:42:42 +10:00
tequ 9d4e507675 Treat suspicious gcov hit counts as coverage warnings (#737) 2026-04-29 17:06:44 +10:00
tequ 49fd0c33b5 fixIOULockedBalanceInvariant Amendment (#732) 2026-04-29 17:05:18 +10:00
tequ 0ffb6e8c21 Replace vendored magic_enum header with Conan package dependency (#727) 2026-04-29 17:00:25 +10:00
tequ 61138058a6 Delete unused sfHookDefinition (#715) 2026-04-29 16:45:35 +10:00
tequ 8dd17973d0 Add hook header ls_flags.h and tx_flags.h (#717) 2026-04-29 16:41:52 +10:00
Fomo dd9e6053a0 fix: Expand the pathing tables and lower weight on sabbxd, also return up … (#723) 2026-04-29 14:28:00 +10:00
tequ 8c1be39f70 Fix LedgerNameSpace to ensure uniqueness (#710) 2026-04-29 12:01:57 +10:00
tequ 61d1d6e441 Add test for rejected Remit to AMM account (#698) 2026-04-29 11:20:13 +10:00
tequ 788ba43266 Refactor getFieldU16(sfTransactionType) with getTxnType() (#711) 2026-04-29 11:17:49 +10:00
tequ 55710c4baf Disallow setting a AMM account as Issuer/Destination/Inform (#709) 2026-04-29 11:00:25 +10:00
tequ 7bf5bcaf20 Add new TSH tests (#704) 2026-04-29 10:40:50 +10:00
Niq Dudfield 9f2160f42e fix: resolve switch fall-through in util_keylet unimplemented cases (#701) 2026-04-29 10:37:17 +10:00
tequ ef7a03ec10 fix owner count assertion at XahauGenesis_test (#688) 2026-04-29 10:32:43 +10:00
tequ 6eb6c41ec6 fix: use OIDC token for Codecov uploads from fork PRs (#736) 2026-04-29 09:50:47 +10:00
9651f68b2e Add coverage workflow (#661)
Co-authored-by: Bronek Kozicki <[email protected]>
Co-authored-by: Niq Dudfield <[email protected]>
2026-04-28 18:29:09 +10:00
tequ f1f44ae232 Build xahau.h from source and check in workflow https://github.com/Xahau/xahaud/pull/674 2026-04-28 18:28:59 +10:00
Alloy Networks c461dd9055 change build instructions url 2026-04-28 18:23:32 +10:00
tequ 73cf6d34cd Fix BEAST_ENHANCED_LOGGING not working and restore original behavior 2026-04-28 18:23:32 +10:00
tequ ea92477d21 Test: hint build_test_hooks.sh when hook wasm is empty in hso() 2026-04-28 18:23:32 +10:00
Nicholas Dudfield e4c7893bf0 Revert "chore: use improved levelization script with threading and argparse"
This reverts commit 5c1d7d9ae9.
2026-04-28 18:23:32 +10:00
Nicholas Dudfield ad9d6a6eb3 chore: use improved levelization script with threading and argparse 2026-04-28 18:23:32 +10:00
Nicholas Dudfield dbbffd917e chore: replace levelization shell script with python
Backport of XRPLF/rippled#6325. The python version runs ~80x faster.
2026-04-28 18:23:31 +10:00
tequ 1d7c7e5603 enable ccache direct_mode 2026-04-28 18:23:31 +10:00
tequ 52843e2321 output ccache configuration in release-builder 2026-04-28 18:23:31 +10:00
tequ 6aabbc940b fix: typo SignersListSet 2026-04-28 18:23:31 +10:00
tequ 3111ecea52 Update util_keylet fee test 2026-04-28 18:23:31 +10:00
tequ 1008508c9b Updated tests to align with the changes merged into the dev branch. 2026-04-28 18:23:31 +10:00
tequ 58e278289b Add tests for Hooks fee 2026-04-28 18:23:31 +10:00
tequ d3d24f781b Merge fixAMMClawbackRounding amendment into featureAMMClawback amendment 2026-04-28 18:23:31 +10:00
yinyiqian1 131d659032 fixAMMClawbackRounding: adjust last holder's LPToken balance (#5513)
Due to rounding, the LPTokenBalance of the last LP might not match the LP's trustline balance. This was fixed for `AMMWithdraw` in `fixAMMv1_1` by adjusting the LPTokenBalance to be the same as the trustline balance. Since `AMMClawback` is also performing a withdrawal, we need to adjust LPTokenBalance as well in `AMMClawback.`

This change includes:
1. Refactored `verifyAndAdjustLPTokenBalance` function in `AMMUtils`, which both`AMMWithdraw` and `AMMClawback` call to adjust LPTokenBalance.
2. Added the unit test `testLastHolderLPTokenBalance` to test the scenario.
3. Modify the existing unit tests for `fixAMMClawbackRounding`.
2026-04-28 18:23:31 +10:00
tequ 503dee619a Merge fixAMMv1_3 amendment into featureAMM amendment 2026-04-28 18:23:31 +10:00
Gregory Tsipenyuk 1703d96a48 fix: Add AMMv1_3 amendment (#5203)
* Add AMM bid/create/deposit/swap/withdraw/vote invariants:
  - Deposit, Withdrawal invariants: `sqrt(asset1Balance * asset2Balance) >= LPTokens`.
  - Bid: `sqrt(asset1Balance * asset2Balance) > LPTokens` and the pool balances don't change.
  - Create: `sqrt(asset1Balance * assetBalance2) == LPTokens`.
  - Swap: `asset1BalanceAfter * asset2BalanceAfter >= asset1BalanceBefore * asset2BalanceBefore`
     and `LPTokens` don't change.
  - Vote: `LPTokens` and pool balances don't change.
  - All AMM and swap transactions: amounts and tokens are greater than zero, except on withdrawal if all tokens
    are withdrawn.
* Add AMM deposit and withdraw rounding to ensure AMM invariant:
  - On deposit, tokens out are rounded downward and deposit amount is rounded upward.
  - On withdrawal, tokens in are rounded upward and withdrawal amount is rounded downward.
* Add Order Book Offer invariant to verify consumed amounts. Consumed amounts are less than the offer.
* Fix Bid validation. `AuthAccount` can't have duplicate accounts or the submitter account.
2026-04-28 18:23:31 +10:00
200 changed files with 20542 additions and 5159 deletions

No files matched your search

+37
View File
@@ -0,0 +1,37 @@
codecov:
require_ci_to_pass: true
comment:
behavior: default
layout: reach,diff,flags,tree,reach
show_carryforward_flags: false
coverage:
range: "60..80"
precision: 1
round: nearest
status:
project:
default:
target: 60%
threshold: 2%
patch:
default:
target: auto
threshold: 2%
changes: false
github_checks:
annotations: true
parsers:
cobertura:
partials_as_hits: true
handle_missing_conditions : true
slack_app: false
ignore:
- "src/test/"
- "include/xrpl/beast/test/"
- "include/xrpl/beast/unit_test/"
+21 -4
View File
@@ -1,8 +1,25 @@
# This feature requires Git >= 2.24
# To use it by default in git blame:
# git config blame.ignoreRevsFile .git-blame-ignore-revs
# Format first-party source according to .clang-format
50760c693510894ca368e90369b0cc2dabfd07f3
e2384885f5f630c8f0ffe4bf21a169b433a16858
241b9ddde9e11beb7480600fd5ed90e1ef109b21
760f16f56835663d9286bd29294d074de26a7ba6
0eebe6a5f4246fced516d52b83ec4e7f47373edd
# Reintroduce Clang-Format & Levelization
da1d20d6d5d862716125d60899b80fab5302954a
# Consolidate external libraries
da1d20d6d5d862716125d60899b80fab5302954a
# Rename .hpp to .h
0345a2645d0f5ad900f4fbbcaff96040d3a887fc
# Format formerly .hpp files
5a227dc719016e10045e17c9396ad401118044f1
# Rewrite includes
e61880699997398f5a746e6c4034edc7632661f5
# Move CMake directory (#4997)
e47b1c1b3b97c3f6d11858ee02f463596e29e7f0
# Rearrange sources (#4997)
bfafa2bb39e562901736d656806bd700c3699a2f
# Rewrite includes (#4997)
e61880699997398f5a746e6c4034edc7632661f5
# Recompute loops (#4997)
d25b5dcd568bb96c18e347d55fac10fe901a1bfb
# Reformat code with clang-format-18
02749feea88ce61c1f7eeb2d61a57d8ecf07ab11
+21 -8
View File
@@ -2,6 +2,14 @@ name: build
description: 'Builds the project with ccache integration'
inputs:
cmake-target:
description: 'CMake target to build'
required: false
default: all
cmake-args:
description: 'Additional CMake arguments'
required: false
default: null
generator:
description: 'CMake generator to use'
required: true
@@ -20,6 +28,10 @@ inputs:
description: 'C++ compiler to use'
required: false
default: ''
gcov:
description: 'Gcov to use'
required: false
default: ''
compiler-id:
description: 'Unique identifier: compiler-version-stdlib[-gccversion] (e.g. clang-14-libstdcxx-gcc11, gcc-13-libstdcxx)'
required: false
@@ -41,10 +53,11 @@ inputs:
required: false
default: 'dev'
stdlib:
description: 'C++ standard library to use'
description: 'C++ standard library to use (default = compiler default, e.g. GCC always uses libstdc++)'
required: true
type: choice
options:
- default
- libstdcxx
- libcxx
clang_gcc_toolchain:
@@ -87,11 +100,6 @@ runs:
export CCACHE_CONFIGPATH="$HOME/.config/ccache/ccache.conf"
echo "CCACHE_CONFIGPATH=$CCACHE_CONFIGPATH" >> $GITHUB_ENV
# Keep config separate from cache_dir so configs aren't swapped when CCACHE_DIR changes between steps
mkdir -p ~/.config/ccache
export CCACHE_CONFIGPATH="$HOME/.config/ccache/ccache.conf"
echo "CCACHE_CONFIGPATH=$CCACHE_CONFIGPATH" >> $GITHUB_ENV
# Configure ccache settings AFTER cache restore (prevents stale cached config)
ccache --set-config=max_size=${{ inputs.ccache_max_size }}
ccache --set-config=hash_dir=${{ inputs.ccache_hash_dir }}
@@ -122,6 +130,10 @@ runs:
export CXX="${{ inputs.cxx }}"
fi
if [ -n "${{ inputs.gcov }}" ]; then
ln -sf /usr/bin/${{ inputs.gcov }} /usr/local/bin/gcov
fi
# Create wrapper toolchain that overlays ccache on top of Conan's toolchain
# This enables ccache for the main app build without affecting Conan dependency builds
if [ "${{ inputs.ccache_enabled }}" = "true" ]; then
@@ -185,7 +197,8 @@ runs:
-DCMAKE_TOOLCHAIN_FILE:FILEPATH=${TOOLCHAIN_FILE} \
-DCMAKE_BUILD_TYPE=${{ inputs.configuration }} \
-Dtests=TRUE \
-Dxrpld=TRUE
-Dxrpld=TRUE \
${{ inputs.cmake-args }}
- name: Show ccache config before build
if: inputs.ccache_enabled == 'true'
@@ -209,7 +222,7 @@ runs:
VERBOSE_FLAG="-- -v"
fi
cmake --build . --config ${{ inputs.configuration }} --parallel $(nproc) ${VERBOSE_FLAG}
cmake --build . --config ${{ inputs.configuration }} --parallel $(nproc) --target ${{ inputs.cmake-target }} ${VERBOSE_FLAG}
- name: Show ccache statistics
if: inputs.ccache_enabled == 'true'
+107
View File
@@ -0,0 +1,107 @@
name: Check Genesis Hooks
on:
push:
pull_request:
jobs:
check-genesis-hooks:
runs-on: ubuntu-24.04
env:
CLANG_VERSION: 18
name: Verify xahau.h is in sync with genesis hooks
steps:
- name: Checkout repository
uses: actions/checkout@v6
# Install binaryen from GitHub Releases (pinned to version 100)
- name: Install binaryen (version 100)
run: |
curl -LO https://github.com/WebAssembly/binaryen/releases/download/version_100/binaryen-version_100-x86_64-linux.tar.gz
tar -xzf binaryen-version_100-x86_64-linux.tar.gz
sudo cp binaryen-version_100/bin/* /usr/local/bin/
wasm-opt --version
- name: Install clang-format
run: |
codename=$( lsb_release --codename --short )
sudo tee /etc/apt/sources.list.d/llvm.list >/dev/null <<EOF
deb http://apt.llvm.org/${codename}/ llvm-toolchain-${codename}-${CLANG_VERSION} main
deb-src http://apt.llvm.org/${codename}/ llvm-toolchain-${codename}-${CLANG_VERSION} main
EOF
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add
sudo apt-get update
sudo apt-get install clang-format-${CLANG_VERSION}
clang-format --version
# Install wasienv (WebAssembly SDK)
- name: Install wasienv
run: |
# Download install.sh
curl -o /tmp/wasienv-install.sh https://raw.githubusercontent.com/wasienv/wasienv/master/install.sh
# Replace /bin to /local/bin
sed -i 's|/bin|/local/bin|g' /tmp/wasienv-install.sh
# Execute the installed script
bash /tmp/wasienv-install.sh
# Add wasienv to PATH for subsequent steps
- name: Setup wasienv
run: |
echo "$HOME/.wasienv/bin" >> $GITHUB_PATH
wasmcc -v || true
# Build and install hook-cleaner tool
- name: Build and install hook-cleaner
run: |
git clone https://github.com/richardah/hook-cleaner-c.git /tmp/hook-cleaner
cd /tmp/hook-cleaner
make
cp hook-cleaner /usr/local/bin/
chmod +x /usr/local/bin/hook-cleaner
# Build and install guard_checker tool
- name: Build and install guard_checker
run: |
cd include/xrpl/hook
make
cp guard_checker /usr/local/bin/
chmod +x /usr/local/bin/guard_checker
# Verify all required tools are available
- name: Verify required tools
run: |
echo "Checking tool availability..."
command -v wasmcc || (echo "Error: wasmcc not found" && exit 1)
command -v wasm-opt || (echo "Error: wasm-opt not found" && exit 1)
command -v hook-cleaner || (echo "Error: hook-cleaner not found" && exit 1)
command -v guard_checker || (echo "Error: guard_checker not found" && exit 1)
command -v xxd || (echo "Error: xxd not found" && exit 1)
command -v clang-format || (echo "Error: clang-format not found" && exit 1)
echo "All tools verified successfully"
# Execute build script to regenerate xahau.h
- name: Run build_xahau_h.sh
run: |
cd hook/genesis
./build_xahau_h.sh
# Check if xahau.h has changed (fail if out of sync)
- name: Verify xahau.h is in sync
run: |
if ! git diff --exit-code include/xrpl/hook/xahau.h; then
echo ""
echo "❌ ERROR: xahau.h is out of sync with genesis hooks"
echo ""
echo "The generated xahau.h differs from the committed version."
echo "Please run the following command and commit the changes:"
echo ""
echo " cd hook/genesis && ./build_xahau_h.sh"
echo ""
echo "Diff:"
git diff include/xrpl/hook/xahau.h
exit 1
fi
echo "✅ xahau.h is in sync with genesis hooks"
+1 -1
View File
@@ -20,7 +20,7 @@ jobs:
sudo apt-get update
sudo apt-get install clang-format-${CLANG_VERSION}
- name: Format first-party sources
run: find include src -type f \( -name '*.cpp' -o -name '*.hpp' -o -name '*.h' -o -name '*.ipp' \) -not -path "src/magic/magic_enum.h" -exec clang-format-${CLANG_VERSION} -i {} +
run: find include src -type f \( -name '*.cpp' -o -name '*.hpp' -o -name '*.h' -o -name '*.ipp' \) -exec clang-format-${CLANG_VERSION} -i {} +
- name: Check for differences
id: assert
run: |
+2 -2
View File
@@ -10,7 +10,7 @@ jobs:
steps:
- uses: actions/checkout@v3
- name: Check levelization
run: Builds/levelization/levelization.sh
run: python Builds/levelization/levelization.py
- name: Check for differences
id: assert
run: |
@@ -40,7 +40,7 @@ jobs:
To fix it, you can do one of two things:
1. Download and apply the patch generated as an artifact of this
job to your repo, commit, and push.
2. Run './Builds/levelization/levelization.sh' in your repo,
2. Run 'python Builds/levelization/levelization.py' in your repo,
commit, and push.
See Builds/levelization/README.md for more info.
@@ -18,6 +18,10 @@ jobs:
generator: bash ./hook/generate_sfcodes.sh
- target: hook/tts.h
generator: ./hook/generate_tts.sh
- target: hook/ls_flags.h
generator: ./hook/generate_lsflags.sh
- target: hook/tx_flags.h
generator: ./hook/generate_txflags.sh
runs-on: ubuntu-24.04
env:
CLANG_VERSION: 18
+117 -15
View File
@@ -57,8 +57,9 @@ jobs:
"cc": "gcc-11",
"cxx": "g++-11",
"compiler_version": 11,
"stdlib": "libstdcxx",
"configuration": "Debug"
"stdlib": "default",
"configuration": "Debug",
"job_type": "build"
},
{
"compiler_id": "gcc-13-libstdcxx",
@@ -66,8 +67,20 @@ jobs:
"cc": "gcc-13",
"cxx": "g++-13",
"compiler_version": 13,
"stdlib": "libstdcxx",
"configuration": "Debug"
"stdlib": "default",
"configuration": "Debug",
"job_type": "build"
},
{
"compiler_id": "gcc-13-libstdcxx",
"compiler": "gcc",
"cc": "gcc-13",
"cxx": "g++-13",
"gcov": "gcov-13",
"compiler_version": 13,
"stdlib": "default",
"configuration": "Debug",
"job_type": "coverage"
},
{
"compiler_id": "clang-14-libstdcxx-gcc11",
@@ -77,7 +90,8 @@ jobs:
"compiler_version": 14,
"stdlib": "libstdcxx",
"clang_gcc_toolchain": 11,
"configuration": "Debug"
"configuration": "Debug",
"job_type": "build"
},
{
"compiler_id": "clang-16-libstdcxx-gcc13",
@@ -87,7 +101,8 @@ jobs:
"compiler_version": 16,
"stdlib": "libstdcxx",
"clang_gcc_toolchain": 13,
"configuration": "Debug"
"configuration": "Debug",
"job_type": "build"
},
{
"compiler_id": "clang-17-libcxx",
@@ -96,7 +111,8 @@ jobs:
"cxx": "clang++-17",
"compiler_version": 17,
"stdlib": "libcxx",
"configuration": "Debug"
"configuration": "Debug",
"job_type": "build"
},
{
# Clang 18 - testing if it's faster than Clang 17 with libc++
@@ -107,14 +123,16 @@ jobs:
"cxx": "clang++-18",
"compiler_version": 18,
"stdlib": "libcxx",
"configuration": "Debug"
"configuration": "Debug",
"job_type": "build"
}
]
# Minimal matrix for PRs and feature branches
minimal_matrix = [
full_matrix[1], # gcc-13 (middle-ground gcc)
full_matrix[2] # clang-14 (mature, stable clang)
full_matrix[2], # gcc-13 coverage
full_matrix[3] # clang-14 (mature, stable clang)
]
# Determine which matrix to use based on the target branch
@@ -189,14 +207,21 @@ jobs:
# Select the appropriate matrix
if use_full:
if force_full:
print(f"Using FULL matrix (6 configs) - forced by [ci-nix-full-matrix] tag")
print(f"Using FULL matrix (7 configs) - forced by [ci-nix-full-matrix] tag")
else:
print(f"Using FULL matrix (6 configs) - targeting main branch")
print(f"Using FULL matrix (7 configs) - targeting main branch")
matrix = full_matrix
else:
print(f"Using MINIMAL matrix (2 configs) - feature branch/PR")
print(f"Using MINIMAL matrix (3 configs) - feature branch/PR")
matrix = minimal_matrix
# Add runs_on based on job_type
for entry in matrix:
if entry.get("job_type") == "coverage":
entry["runs_on"] = '["self-hosted", "generic", 24.04]'
else:
entry["runs_on"] = '["self-hosted", "generic", 20.04]'
# Output the matrix as JSON
output = json.dumps({"include": matrix})
with open(os.environ['GITHUB_OUTPUT'], 'a') as f:
@@ -204,7 +229,10 @@ jobs:
build:
needs: matrix-setup
runs-on: [self-hosted, generic, 20.04]
runs-on: ${{ fromJSON(matrix.runs_on) }}
permissions:
id-token: write
contents: read
container:
image: ubuntu:24.04
volumes:
@@ -233,7 +261,7 @@ jobs:
apt-get install -y software-properties-common
add-apt-repository ppa:ubuntu-toolchain-r/test -y
apt-get update
apt-get install -y python3 python-is-python3 pipx
apt-get install -y git python3 python-is-python3 pipx
pipx ensurepath
apt-get install -y cmake ninja-build ${{ matrix.cc }} ${{ matrix.cxx }} ccache
apt-get install -y perl # for openssl build
@@ -304,6 +332,12 @@ jobs:
pipx install "conan>=2.0,<3"
echo "$HOME/.local/bin" >> $GITHUB_PATH
# Install gcovr for coverage jobs
if [ "${{ matrix.job_type }}" = "coverage" ]; then
pipx install "gcovr>=7,<9"
apt-get install -y curl lcov
fi
- name: Check environment
run: |
echo "PATH:"
@@ -313,6 +347,13 @@ jobs:
which ${{ matrix.cc }} && ${{ matrix.cc }} --version || echo "${{ matrix.cc }} not found"
which ${{ matrix.cxx }} && ${{ matrix.cxx }} --version || echo "${{ matrix.cxx }} not found"
which ccache && ccache --version || echo "ccache not found"
# Check gcovr for coverage jobs
if [ "${{ matrix.job_type }}" = "coverage" ]; then
which gcov && gcov --version || echo "gcov not found"
which gcovr && gcovr --version || echo "gcovr not found"
fi
echo "---- Full Environment ----"
env
@@ -340,6 +381,7 @@ jobs:
gha_cache_enabled: 'false' # Disable caching for self hosted runner
- name: Build
if: matrix.job_type == 'build'
uses: ./.github/actions/xahau-ga-build
with:
generator: Ninja
@@ -354,7 +396,27 @@ jobs:
clang_gcc_toolchain: ${{ matrix.clang_gcc_toolchain || '' }}
ccache_max_size: '100G'
- name: Build (Coverage)
if: matrix.job_type == 'coverage'
uses: ./.github/actions/xahau-ga-build
with:
generator: Ninja
configuration: ${{ matrix.configuration }}
build_dir: ${{ env.build_dir }}
cc: ${{ matrix.cc }}
cxx: ${{ matrix.cxx }}
gcov: ${{ matrix.gcov }}
compiler-id: ${{ matrix.compiler_id }}
cache_version: ${{ env.CACHE_VERSION }}
main_branch: ${{ env.MAIN_BRANCH_NAME }}
stdlib: ${{ matrix.stdlib }}
# Coverage builds are slower due to instrumentation; use fewer parallel jobs to avoid flakiness
cmake-args: '-Dcoverage=ON -Dcoverage_format=xml -Dcoverage_test_parallelism=$(($(nproc)/2)) -DCODE_COVERAGE_VERBOSE=ON -DCMAKE_CXX_FLAGS="-O0" -DCMAKE_C_FLAGS="-O0"'
cmake-target: 'coverage'
ccache_max_size: '100G'
- name: Set artifact name
if: matrix.job_type == 'build'
id: set-artifact-name
run: |
ARTIFACT_NAME="build-output-nix-${{ github.run_id }}-${{ matrix.compiler }}-${{ matrix.configuration }}"
@@ -367,6 +429,7 @@ jobs:
ls -la ${{ env.build_dir }} || echo "Build directory not found or empty"
- name: Run tests
if: matrix.job_type == 'build'
run: |
# Ensure the binary exists before trying to run
if [ -f "${{ env.build_dir }}/rippled" ]; then
@@ -375,3 +438,42 @@ jobs:
echo "Error: rippled executable not found in ${{ env.build_dir }}"
exit 1
fi
# Coverage-specific steps
- name: Move coverage report
if: matrix.job_type == 'coverage'
shell: bash
run: |
mv "${{ env.build_dir }}/coverage.xml" ./
- name: Archive coverage report
if: matrix.job_type == 'coverage'
uses: actions/upload-artifact@v4
with:
name: coverage.xml
path: coverage.xml
retention-days: 30
- name: Upload coverage report
if: matrix.job_type == 'coverage'
uses: codecov/codecov-action@v5
with:
files: coverage.xml
fail_ci_if_error: true
disable_search: true
verbose: true
plugins: noop
use_oidc: true
- name: Export server definitions
if: matrix.job_type == 'build' && matrix.compiler_id == 'gcc-13-libstdcxx'
run: |
${{ env.build_dir }}/rippled --definitions | python3 -m json.tool > server_definitions.json
- name: Upload server definitions
if: matrix.job_type == 'build' && matrix.compiler_id == 'gcc-13-libstdcxx'
uses: actions/upload-artifact@v7
with:
name: server-definitions
path: server_definitions.json
archive: false
+3
View File
@@ -53,6 +53,9 @@ Builds/levelization/results/paths.txt
Builds/levelization/results/includes/
Builds/levelization/results/includedby/
# Python
__pycache__
# Ignore tmp directory.
tmp
+3 -3
View File
@@ -50,7 +50,7 @@ that `test` code should *never* be included in `ripple` code.)
## Validation
The [levelization.sh](levelization.sh) script takes no parameters,
The [levelization.py](levelization.py) script takes no parameters,
reads no environment variables, and can be run from any directory,
as long as it is in the expected location in the rippled repo.
It can be run at any time from within a checked out repo, and will
@@ -84,7 +84,7 @@ It generates many files of [results](results):
Github Actions workflow to test that levelization loops haven't
changed. Unfortunately, if changes are detected, it can't tell if
they are improvements or not, so if you have resolved any issues or
done anything else to improve levelization, run `levelization.sh`,
done anything else to improve levelization, run `levelization.py`,
and commit the updated results.
The `loops.txt` and `ordering.txt` files relate the modules
@@ -108,7 +108,7 @@ The committed files hide the detailed values intentionally, to
prevent false alarms and merging issues, and because it's easy to
get those details locally.
1. Run `levelization.sh`
1. Run `levelization.py`
2. Grep the modules in `paths.txt`.
* For example, if a cycle is found `A ~= B`, simply `grep -w
A Builds/levelization/results/paths.txt | grep -w B`
+283
View File
@@ -0,0 +1,283 @@
#!/usr/bin/env python3
"""
Usage: levelization.py
This script takes no parameters, and can be called from any directory in the file system.
"""
import os
import re
import sys
from collections import defaultdict
from pathlib import Path
# Compile regex patterns once at module level
INCLUDE_PATTERN = re.compile(r"^\s*#include.*/.*\.h")
INCLUDE_PATH_PATTERN = re.compile(r'[<"]([^>"]+)[>"]')
def dictionary_sort_key(s):
"""
Create a sort key that mimics 'sort -d' (dictionary order).
Dictionary order only considers blanks and alphanumeric characters.
"""
return "".join(c for c in s if c.isalnum() or c.isspace())
def get_level(file_path):
"""
Extract the level from a file path (second and third directory components).
Equivalent to bash: cut -d/ -f 2,3
Examples:
src/ripple/app/main.cpp -> ripple.app
src/test/app/Import_test.cpp -> test.app
"""
parts = file_path.split("/")
if len(parts) >= 3:
level = f"{parts[1]}/{parts[2]}"
elif len(parts) >= 2:
level = f"{parts[1]}/toplevel"
else:
level = file_path
# If the "level" indicates a file, cut off the filename
if "." in level.split("/")[-1]:
# Use the "toplevel" label as a workaround for `sort`
# inconsistencies between different utility versions
level = level.rsplit("/", 1)[0] + "/toplevel"
return level.replace("/", ".")
def extract_include_level(include_line):
"""
Extract the include path from an #include directive.
Gets the first two directory components from the include path.
Equivalent to bash: cut -d/ -f 1,2
Examples:
#include <ripple/basics/base_uint.h> -> ripple.basics
#include "ripple/app/main/Application.h" -> ripple.app
"""
match = INCLUDE_PATH_PATTERN.search(include_line)
if not match:
return None
include_path = match.group(1)
parts = include_path.split("/")
if len(parts) >= 2:
include_level = f"{parts[0]}/{parts[1]}"
else:
include_level = include_path
# If the "includelevel" indicates a file, cut off the filename
if "." in include_level.split("/")[-1]:
include_level = include_level.rsplit("/", 1)[0] + "/toplevel"
return include_level.replace("/", ".")
def find_repository_directories(start_path, depth_limit=10):
"""
Find the repository root by looking for src or include folders.
Walks up the directory tree from the start path.
"""
current = start_path.resolve()
for _ in range(depth_limit):
src_path = current / "src"
include_path = current / "include"
has_src = src_path.exists()
has_include = include_path.exists()
if has_src or has_include:
dirs = []
if has_src:
dirs.append(src_path)
if has_include:
dirs.append(include_path)
return current, dirs
parent = current.parent
if parent == current:
break
current = parent
raise RuntimeError(
"Could not find repository root. "
"Expected to find a directory containing 'src' and/or 'include' folders."
)
def main():
script_dir = Path(__file__).parent.resolve()
os.chdir(script_dir)
# Clean up and create results directory.
results_dir = script_dir / "results"
if results_dir.exists():
import shutil
shutil.rmtree(results_dir)
results_dir.mkdir()
# Find the repository root.
try:
repo_root, scan_dirs = find_repository_directories(script_dir)
print(f"Found repository root: {repo_root}")
for scan_dir in scan_dirs:
print(f" Scanning: {scan_dir.relative_to(repo_root)}")
except RuntimeError as e:
print(f"Error: {e}", file=sys.stderr)
sys.exit(1)
# Find all #include directives.
print("\nScanning for raw includes...")
raw_includes = []
rawincludes_file = results_dir / "rawincludes.txt"
with open(rawincludes_file, "w", buffering=8192) as raw_f:
for dir_path in scan_dirs:
for file_path in dir_path.rglob("*"):
if not file_path.is_file():
continue
try:
rel_path_str = str(file_path.relative_to(repo_root))
with open(
file_path, "r", encoding="utf-8", errors="ignore", buffering=8192
) as f:
for line in f:
if "#include" not in line or "boost" in line:
continue
if INCLUDE_PATTERN.match(line):
line_stripped = line.strip()
entry = f"{rel_path_str}:{line_stripped}\n"
print(entry, end="")
raw_f.write(entry)
raw_includes.append((rel_path_str, line_stripped))
except Exception as e:
print(f"Error reading {file_path}: {e}", file=sys.stderr)
# Build levelization paths and count directly.
print("Build levelization paths")
path_counts = defaultdict(int)
for file_path, include_line in raw_includes:
include_level = extract_include_level(include_line)
if not include_level:
continue
level = get_level(file_path)
if level != include_level:
path_counts[(level, include_level)] += 1
# Sort and deduplicate paths.
print("Sort and deduplicate paths")
sorted_items = sorted(
path_counts.items(),
key=lambda x: (dictionary_sort_key(x[0][0]), dictionary_sort_key(x[0][1])),
)
paths_file = results_dir / "paths.txt"
with open(paths_file, "w") as f:
for (level, include_level), count in sorted_items:
line = f"{count:7} {level} {include_level}\n"
print(line.rstrip())
f.write(line)
# Split into flat-file database.
print("Split into flat-file database")
includes_dir = results_dir / "includes"
includedby_dir = results_dir / "includedby"
includes_dir.mkdir()
includedby_dir.mkdir()
includes_data = defaultdict(list)
includedby_data = defaultdict(list)
for (level, include_level), count in sorted_items:
includes_data[level].append((include_level, count))
includedby_data[include_level].append((level, count))
for level in sorted(includes_data.keys(), key=dictionary_sort_key):
with open(includes_dir / level, "w") as f:
for include_level, count in includes_data[level]:
line = f"{include_level} {count}\n"
print(line.rstrip())
f.write(line)
for include_level in sorted(includedby_data.keys(), key=dictionary_sort_key):
with open(includedby_dir / include_level, "w") as f:
for level, count in includedby_data[include_level]:
line = f"{level} {count}\n"
print(line.rstrip())
f.write(line)
# Search for loops.
print("Search for loops")
loops_file = results_dir / "loops.txt"
ordering_file = results_dir / "ordering.txt"
# Pre-load all include files into memory for fast lookup.
includes_cache = {}
includes_lookup = {}
for include_file in sorted(includes_dir.iterdir(), key=lambda p: p.name):
if not include_file.is_file():
continue
includes_cache[include_file.name] = []
includes_lookup[include_file.name] = {}
with open(include_file, "r") as f:
for line in f:
parts = line.strip().split()
if len(parts) >= 2:
name, count = parts[0], int(parts[1])
includes_cache[include_file.name].append((name, count))
includes_lookup[include_file.name][name] = count
loops_found = set()
with open(loops_file, "w", buffering=8192) as loops_f, open(
ordering_file, "w", buffering=8192
) as ordering_f:
for source in sorted(includes_cache.keys()):
for include, include_freq in includes_cache[source]:
if include not in includes_lookup:
continue
source_freq = includes_lookup[include].get(source)
if source_freq is not None:
loop_key = tuple(sorted([source, include]))
if loop_key in loops_found:
continue
loops_found.add(loop_key)
loops_f.write(f"Loop: {source} {include}\n")
diff = include_freq - source_freq
if diff > 3:
loops_f.write(f" {source} > {include}\n\n")
elif diff < -3:
loops_f.write(f" {include} > {source}\n\n")
elif source_freq == include_freq:
loops_f.write(f" {include} == {source}\n\n")
else:
loops_f.write(f" {include} ~= {source}\n\n")
else:
ordering_f.write(f"{source} > {include}\n")
# Print results.
print("\nOrdering:")
with open(ordering_file, "r") as f:
print(f.read(), end="")
print("\nLoops:")
with open(loops_file, "r") as f:
print(f.read(), end="")
if __name__ == "__main__":
main()
-130
View File
@@ -1,130 +0,0 @@
#!/bin/bash
# Usage: levelization.sh
# This script takes no parameters, reads no environment variables,
# and can be run from any directory, as long as it is in the expected
# location in the repo.
pushd $( dirname $0 )
if [ -v PS1 ]
then
# if the shell is interactive, clean up any flotsam before analyzing
git clean -ix
fi
# Ensure all sorting is ASCII-order consistently across platforms.
export LANG=C
rm -rfv results
mkdir results
includes="$( pwd )/results/rawincludes.txt"
pushd ../..
echo Raw includes:
grep -r '^[ ]*#include.*/.*\.h' include src | \
grep -v boost | tee ${includes}
popd
pushd results
oldifs=${IFS}
IFS=:
mkdir includes
mkdir includedby
echo Build levelization paths
exec 3< ${includes} # open rawincludes.txt for input
while read -r -u 3 file include
do
level=$( echo ${file} | cut -d/ -f 2,3 )
# If the "level" indicates a file, cut off the filename
if [[ "${level##*.}" != "${level}" ]]
then
# Use the "toplevel" label as a workaround for `sort`
# inconsistencies between different utility versions
level="$( dirname ${level} )/toplevel"
fi
level=$( echo ${level} | tr '/' '.' )
includelevel=$( echo ${include} | sed 's/.*["<]//; s/[">].*//' | \
cut -d/ -f 1,2 )
if [[ "${includelevel##*.}" != "${includelevel}" ]]
then
# Use the "toplevel" label as a workaround for `sort`
# inconsistencies between different utility versions
includelevel="$( dirname ${includelevel} )/toplevel"
fi
includelevel=$( echo ${includelevel} | tr '/' '.' )
if [[ "$level" != "$includelevel" ]]
then
echo $level $includelevel | tee -a paths.txt
fi
done
echo Sort and dedup paths
sort -ds paths.txt | uniq -c | tee sortedpaths.txt
mv sortedpaths.txt paths.txt
exec 3>&- #close fd 3
IFS=${oldifs}
unset oldifs
echo Split into flat-file database
exec 4<paths.txt # open paths.txt for input
while read -r -u 4 count level include
do
echo ${include} ${count} | tee -a includes/${level}
echo ${level} ${count} | tee -a includedby/${include}
done
exec 4>&- #close fd 4
loops="$( pwd )/loops.txt"
ordering="$( pwd )/ordering.txt"
pushd includes
echo Search for loops
# Redirect stdout to a file
exec 4>&1
exec 1>"${loops}"
for source in *
do
if [[ -f "$source" ]]
then
exec 5<"${source}" # open for input
while read -r -u 5 include includefreq
do
if [[ -f $include ]]
then
if grep -q -w $source $include
then
if grep -q -w "Loop: $include $source" "${loops}"
then
continue
fi
sourcefreq=$( grep -w $source $include | cut -d\ -f2 )
echo "Loop: $source $include"
# If the counts are close, indicate that the two modules are
# on the same level, though they shouldn't be
if [[ $(( $includefreq - $sourcefreq )) -gt 3 ]]
then
echo -e " $source > $include\n"
elif [[ $(( $sourcefreq - $includefreq )) -gt 3 ]]
then
echo -e " $include > $source\n"
elif [[ $sourcefreq -eq $includefreq ]]
then
echo -e " $include == $source\n"
else
echo -e " $include ~= $source\n"
fi
else
echo "$source > $include" >> "${ordering}"
fi
fi
done
exec 5>&- #close fd 5
fi
done
exec 1>&4 #close fd 1
exec 4>&- #close fd 4
cat "${ordering}"
cat "${loops}"
popd
popd
popd
+8
View File
@@ -77,6 +77,11 @@ test.ledger > xrpld.app
test.ledger > xrpld.core
test.ledger > xrpld.ledger
test.ledger > xrpl.protocol
test.net > test.toplevel
test.net > xrpl.basics
test.net > xrpld.core
test.net > xrpld.net
test.net > xrpl.json
test.nodestore > test.jtx
test.nodestore > test.toplevel
test.nodestore > test.unit_test
@@ -84,6 +89,7 @@ test.nodestore > xrpl.basics
test.nodestore > xrpld.core
test.nodestore > xrpld.nodestore
test.nodestore > xrpld.unity
test.nodestore > xrpl.protocol
test.overlay > test.jtx
test.overlay > test.toplevel
test.overlay > test.unit_test
@@ -118,6 +124,7 @@ test.rpc > xrpld.core
test.rpc > xrpld.net
test.rpc > xrpld.overlay
test.rpc > xrpld.rpc
test.rpc > xrpld.shamap
test.rpc > xrpl.hook
test.rpc > xrpl.json
test.rpc > xrpl.protocol
@@ -198,6 +205,7 @@ xrpld.rpc > xrpld.core
xrpld.rpc > xrpld.ledger
xrpld.rpc > xrpld.nodestore
xrpld.rpc > xrpld.shamap
xrpld.rpc > xrpl.hook
xrpld.rpc > xrpl.json
xrpld.rpc > xrpl.protocol
xrpld.rpc > xrpl.resource
+1
View File
@@ -122,6 +122,7 @@ endif()
find_package(nudb REQUIRED)
find_package(date REQUIRED)
find_package(xxHash REQUIRED)
find_package(magic_enum REQUIRED)
include(deps/WasmEdge)
if(TARGET nudb::core)
+1 -1
View File
@@ -12,7 +12,7 @@ The server software that powers Xahau is called `xahaud` and is available in thi
### Build from Source
* [Read the build instructions in our documentation](https://xahau.network/infrastructure/building-xahau)
* [Read the build instructions in our documentation](https://xahau.network/docs/infrastructure/build-xahaud/)
* If you encounter any issues, please [open an issue](https://github.com/xahau/xahaud/issues)
## Highlights of Xahau
+11 -2
View File
@@ -71,6 +71,7 @@ cmake .. -G Ninja \
-Dxrpld=TRUE \
-Dtests=TRUE &&
ccache -z &&
ccache -p &&
ninja -j $3 && echo "=== Re-running final link with verbose output ===" && rm -f rippled && ninja -v rippled &&
ccache -s &&
strip -s rippled &&
@@ -94,8 +95,16 @@ if [[ "$4" == "" ]]; then
echo "Non GH, local building, no Action runner magic"
else
# GH Action, runner
cp /io/release-build/xahaud /data/builds/$(date +%Y).$(date +%-m).$(date +%-d)-$(git rev-parse --abbrev-ref HEAD)+$4
cp /io/release-build/release.info /data/builds/$(date +%Y).$(date +%-m).$(date +%-d)-$(git rev-parse --abbrev-ref HEAD)+$4.releaseinfo
if [[ "$(git rev-parse --abbrev-ref HEAD)" == "release" ]]; then
echo "building on the release branch... placing it in builds/candidate"
mkdir /data/builds/candidate
cp /io/release-build/xahaud /data/builds/candidate/$(date +%Y).$(date +%-m).$(date +%-d)-$(git rev-parse --abbrev-ref HEAD)+$4
cp /io/release-build/release.info /data/builds/candidate/$(date +%Y).$(date +%-m).$(date +%-d)-$(git rev-parse --abbrev-ref HEAD)+$4.releaseinfo
else
echo "building non-release branch, placing it in builds root"
cp /io/release-build/xahaud /data/builds/$(date +%Y).$(date +%-m).$(date +%-d)-$(git rev-parse --abbrev-ref HEAD)+$4
cp /io/release-build/release.info /data/builds/$(date +%Y).$(date +%-m).$(date +%-d)-$(git rev-parse --abbrev-ref HEAD)+$4.releaseinfo
fi
echo "Published build to: http://build.xahau.tech/"
echo $(date +%Y).$(date +%-m).$(date +%-d)-$(git rev-parse --abbrev-ref HEAD)+$4
fi
+14 -16
View File
@@ -12,17 +12,16 @@ echo "-- GITHUB_REPOSITORY: $1"
echo "-- GITHUB_SHA: $2"
echo "-- GITHUB_RUN_NUMBER: $4"
umask 0000;
umask 0000
####
cd /io;
mkdir -p src/certs;
curl --silent -k https://raw.githubusercontent.com/RichardAH/rippled-release-builder/main/ca-bundle/certbundle.h -o src/certs/certbundle.h;
if [ "`grep certbundle.h src/xrpld/net/detail/RegisterSSLCerts.cpp | wc -l`" -eq "0" ]
then
cp src/xrpld/net/detail/RegisterSSLCerts.cpp src/xrpld/net/detail/RegisterSSLCerts.cpp.old
perl -i -pe "s/^{/{
cd /io
mkdir -p src/certs
curl --silent -k https://raw.githubusercontent.com/RichardAH/rippled-release-builder/main/ca-bundle/certbundle.h -o src/certs/certbundle.h
if [ "$(grep certbundle.h src/xrpld/net/detail/RegisterSSLCerts.cpp | wc -l)" -eq "0" ]; then
cp src/xrpld/net/detail/RegisterSSLCerts.cpp src/xrpld/net/detail/RegisterSSLCerts.cpp.old
perl -i -pe "s/^{/{
#ifdef EMBEDDED_CA_BUNDLE
BIO *cbio = BIO_new_mem_buf(ca_bundle.data(), ca_bundle.size());
X509_STORE *cts = SSL_CTX_get_cert_store(ctx.native_handle());
@@ -68,15 +67,14 @@ fi
source /opt/rh/gcc-toolset-11/enable
export PATH=/usr/local/bin:$PATH
export CC='/usr/lib64/ccache/gcc' &&
export CXX='/usr/lib64/ccache/g++' &&
echo "-- Build Rippled --" &&
pwd &&
export CXX='/usr/lib64/ccache/g++' &&
echo "-- Build Rippled --" &&
pwd &&
echo "MOVING TO [ build-core.sh ]"
echo "MOVING TO [ build-core.sh ]";
printenv > .env.temp;
cat .env.temp | grep '=' | sed s/\\\(^[^=]\\+=\\\)/\\1\\\"/g|sed s/\$/\\\"/g > .env;
rm .env.temp;
printenv >.env.temp
cat .env.temp | grep '=' | sed s/\\\(^[^=]\\+=\\\)/\\1\\\"/g | sed s/\$/\\\"/g >.env
rm .env.temp
echo "Persisting ENV:"
cat .env
+2 -2
View File
@@ -494,7 +494,7 @@
#
# Configure the maximum number of transactions to have in the job queue
#
# Must be a number between 100 and 1000, defaults to 250
# Must be a number between 100 and 1000, defaults to 1000
#
#
# [overlay]
@@ -593,7 +593,7 @@
# reaches or exceeds this number. After that the limit may still
# change, but will stay above the target. If consensus is not
# healthy, the limit will be clamped to this value or lower.
# Default: 50.
# Default: 1000.
#
# maximum_txn_in_ledger = <number>
#
+18 -4
View File
@@ -95,6 +95,9 @@
# - replace both functions setup_target_for_coverage_gcovr_* with a single setup_target_for_coverage_gcovr
# - add support for all gcovr output formats
#
# 2024-04-03, Bronek Kozicki
# - add support for output formats: jacoco, clover, lcov
#
# USAGE:
#
# 1. Copy this file into your cmake modules path.
@@ -256,10 +259,10 @@ endif()
# BASE_DIRECTORY "../" # Base directory for report
# # (defaults to PROJECT_SOURCE_DIR)
# FORMAT "cobertura" # Output format, one of:
# # xml cobertura sonarqube json-summary
# # json-details coveralls csv txt
# # html-single html-nested html-details
# # (xml is an alias to cobertura;
# # xml cobertura sonarqube jacoco clover
# # json-summary json-details coveralls csv
# # txt html-single html-nested html-details
# # lcov (xml is an alias to cobertura;
# # if no format is set, defaults to xml)
# EXCLUDE "src/dir1/*" "src/dir2/*" # Patterns to exclude (can be relative
# # to BASE_DIRECTORY, with CMake 3.4+)
@@ -308,6 +311,8 @@ function(setup_target_for_coverage_gcovr)
set(GCOVR_OUTPUT_FILE ${Coverage_NAME}.txt)
elseif(Coverage_FORMAT STREQUAL "csv")
set(GCOVR_OUTPUT_FILE ${Coverage_NAME}.csv)
elseif(Coverage_FORMAT STREQUAL "lcov")
set(GCOVR_OUTPUT_FILE ${Coverage_NAME}.lcov)
else()
set(GCOVR_OUTPUT_FILE ${Coverage_NAME}.xml)
endif()
@@ -320,6 +325,14 @@ function(setup_target_for_coverage_gcovr)
set(Coverage_FORMAT cobertura) # overwrite xml
elseif(Coverage_FORMAT STREQUAL "sonarqube")
list(APPEND GCOVR_ADDITIONAL_ARGS --sonarqube "${GCOVR_OUTPUT_FILE}" )
elseif(Coverage_FORMAT STREQUAL "jacoco")
list(APPEND GCOVR_ADDITIONAL_ARGS --jacoco "${GCOVR_OUTPUT_FILE}" )
list(APPEND GCOVR_ADDITIONAL_ARGS --jacoco-pretty )
elseif(Coverage_FORMAT STREQUAL "clover")
list(APPEND GCOVR_ADDITIONAL_ARGS --clover "${GCOVR_OUTPUT_FILE}" )
list(APPEND GCOVR_ADDITIONAL_ARGS --clover-pretty )
elseif(Coverage_FORMAT STREQUAL "lcov")
list(APPEND GCOVR_ADDITIONAL_ARGS --lcov "${GCOVR_OUTPUT_FILE}" )
elseif(Coverage_FORMAT STREQUAL "json-summary")
list(APPEND GCOVR_ADDITIONAL_ARGS --json-summary "${GCOVR_OUTPUT_FILE}" )
list(APPEND GCOVR_ADDITIONAL_ARGS --json-summary-pretty)
@@ -380,6 +393,7 @@ function(setup_target_for_coverage_gcovr)
${GCOVR_PATH}
--gcov-executable ${GCOV_TOOL}
--gcov-ignore-parse-errors=negative_hits.warn_once_per_file
--gcov-ignore-parse-errors=suspicious_hits.warn_once_per_file
-r ${BASEDIR}
${GCOVR_ADDITIONAL_ARGS}
${GCOVR_EXCLUDE_ARGS}
+12
View File
@@ -54,6 +54,7 @@ add_library(xrpl.imports.main INTERFACE)
target_link_libraries(xrpl.imports.main
INTERFACE
LibArchive::LibArchive
magic_enum::magic_enum
OpenSSL::Crypto
Ripple::boost
wasmedge::wasmedge
@@ -68,6 +69,17 @@ target_link_libraries(xrpl.imports.main
$<$<BOOL:${voidstar}>:antithesis-sdk-cpp>
)
# date-tz for enhanced logging (always linked, code is #ifdef guarded)
if(TARGET date::date-tz)
target_link_libraries(xrpl.imports.main INTERFACE date::date-tz)
endif()
# BEAST_ENHANCED_LOGGING: enable for Debug builds OR when explicitly requested
# Uses generator expression so it works with multi-config generators (Xcode, VS, Ninja Multi-Config)
target_compile_definitions(xrpl.imports.main INTERFACE
$<$<OR:$<CONFIG:Debug>,$<BOOL:${BEAST_ENHANCED_LOGGING}>>:BEAST_ENHANCED_LOGGING=1>
)
include(add_module)
include(target_link_modules)
+1
View File
@@ -29,6 +29,7 @@ class Xrpl(ConanFile):
'date/3.0.3',
'grpc/1.50.1',
'libarchive/3.7.6',
'magic_enum/0.9.5',
'nudb/2.0.8',
'openssl/3.6.0',
'soci/4.0.3@xahaud/stable',
+6
View File
@@ -2,6 +2,9 @@
// Generated using generate_extern.sh
#include <stdint.h>
#ifndef HOOK_EXTERN
#ifdef __cplusplus
extern "C" {
#endif
extern int32_t __attribute__((noduplicate))
_g(uint32_t guard_id, uint32_t maxiter);
@@ -336,5 +339,8 @@ prepare(
uint32_t read_ptr,
uint32_t read_len);
#ifdef __cplusplus
}
#endif
#define HOOK_EXTERN
#endif // HOOK_EXTERN
+2 -2
View File
@@ -9,7 +9,7 @@ ENUM_FILE="$SCRIPT_DIR/../include/xrpl/hook/Enum.h"
echo '// For documentation please see: https://xrpl-hooks.readme.io/reference/'
echo '// Generated using generate_error.sh'
echo '#ifndef HOOK_ERROR_CODES'
sed -n '/enum hook_return_code/,/};/p' "$ENUM_FILE" |
sed -n '/enum class hook_return_code/,/};/p' "$ENUM_FILE" |
awk '
function ltrim(s) { sub(/^[[:space:]]+/, "", s); return s }
function rtrim(s) { sub(/[[:space:]]+$/, "", s); return s }
@@ -31,7 +31,7 @@ sed -n '/enum hook_return_code/,/};/p' "$ENUM_FILE" |
{
line = $0
if (line ~ /enum[[:space:]]+hook_return_code/)
if (line ~ /enum[[:space:]]+class[[:space:]]+hook_return_code/)
next
if (line ~ /^[[:space:]]*\{/)
next
+6
View File
@@ -11,6 +11,9 @@ APPLY_HOOK="$SCRIPT_DIR/../include/xrpl/hook/hook_api.macro"
echo '// Generated using generate_extern.sh'
echo '#include <stdint.h>'
echo '#ifndef HOOK_EXTERN'
echo '#ifdef __cplusplus'
echo 'extern "C" {'
echo '#endif'
echo
awk '
function trim(s) {
@@ -46,6 +49,9 @@ APPLY_HOOK="$SCRIPT_DIR/../include/xrpl/hook/hook_api.macro"
}
' "$APPLY_HOOK"
echo '#ifdef __cplusplus'
echo '}'
echo '#endif'
echo '#define HOOK_EXTERN'
echo '#endif // HOOK_EXTERN'
} | (
+82
View File
@@ -0,0 +1,82 @@
#!/bin/bash
set -eu
SCRIPT_DIR=$(dirname "$0")
SCRIPT_DIR=$(cd "$SCRIPT_DIR" && pwd)
RIPPLED_ROOT="$SCRIPT_DIR/../include/xrpl"
LEDGER_FORMATS="$RIPPLED_ROOT/protocol/LedgerFormats.h"
echo '// Generated using generate_lsflags.sh'
echo ''
echo '#ifndef HOOKLSFLAGS_INCLUDED'
echo '#define HOOKLSFLAGS_INCLUDED 1'
echo ''
awk '
function ltrim(s) { sub(/^[[:space:]]+/, "", s); return s }
function rtrim(s) { sub(/[[:space:]]+$/, "", s); return s }
function trim(s) { return rtrim(ltrim(s)) }
function flush_group() {
if (entry_count > 0 && group != "") {
printf "enum %s {\n", group
for (i = 1; i <= entry_count; i++) {
printf " %s,\n", entries[i]
}
printf "};\n"
}
delete entries
entry_count = 0
}
/enum LedgerSpecificFlags \{/ { inside = 1; next }
inside && /^\};/ { inside = 0; flush_group(); next }
!inside { next }
# Group header comments: // ltFOO or // remarks
/^[[:space:]]*\/\/[[:space:]]*(lt[A-Z_]+|remarks)[[:space:]]*$/ {
flush_group()
line = $0
sub(/.*\/\/[[:space:]]*/, "", line)
group = trim(line)
next
}
# Skip pure comment lines (not group headers)
/^[[:space:]]*\/\// { next }
# Skip blank lines
/^[[:space:]]*$/ { next }
# Accumulate flag lines (handle multi-line values)
{
line = $0
# Strip inline comments
sub(/\/\/.*/, "", line)
line = trim(line)
if (line == "") next
if (pending != "") {
pending = pending " " line
} else {
pending = line
}
# If line ends with comma, the entry is complete
if (pending ~ /,$/) {
# Remove trailing comma
sub(/,$/, "", pending)
entries[++entry_count] = pending
pending = ""
}
}
BEGIN {
inside = 0
group = ""
pending = ""
entry_count = 0
}
' "$LEDGER_FORMATS"
echo ''
echo '#endif // HOOKLSFLAGS_INCLUDED'
+25
View File
@@ -0,0 +1,25 @@
#!/bin/bash
set -eu
SCRIPT_DIR=$(dirname "$0")
SCRIPT_DIR=$(cd "$SCRIPT_DIR" && pwd)
RIPPLED_ROOT="$SCRIPT_DIR/../include/xrpl"
TX_FLAGS="$RIPPLED_ROOT/protocol/TxFlags.h"
echo '// Generated using generate_txflags.sh'
echo '#include "ls_flags.h"'
echo '#include <stdint.h>'
echo ''
cat "$TX_FLAGS" |
awk '
/^[[:space:]]*enum / {
if (count > 0) print ""
inside = 1
count++
}
inside {
print
if (/};/) inside = 0
}
'
+203
View File
@@ -0,0 +1,203 @@
#!/bin/bash
# build_xahau_h.sh
# Builds genesis hook WASMs and updates xahau.h with hex arrays
set -euo pipefail
# Color codes for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Script directory and path constants
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
XAHAU_H="${SCRIPT_DIR}/../../include/xrpl/hook/xahau.h"
TEMP_DIR="${SCRIPT_DIR}/.temp"
# Hook file mappings (space-separated: name:file)
HOOK_FILES=(
"GovernanceHook:govern.wasm"
"RewardHook:reward.wasm"
# "MintHook:mint.wasm"
)
# Cleanup function
cleanup() {
local exit_code=$?
if [ ${exit_code} -eq 0 ] && [ -d "${TEMP_DIR}" ]; then
rm -rf "${TEMP_DIR}"
elif [ ${exit_code} -ne 0 ]; then
echo -e "${RED}Error: Script failed with exit code ${exit_code}${NC}" >&2
if [ -d "${TEMP_DIR}" ]; then
echo -e "${YELLOW}Temp files preserved at: ${TEMP_DIR}${NC}" >&2
fi
fi
exit ${exit_code}
}
trap cleanup EXIT INT TERM
# Tool verification
echo -e "${BLUE}==> Checking required tools...${NC}"
REQUIRED_TOOLS=("make" "xxd" "sed" "clang-format" "wasm-opt")
for tool in "${REQUIRED_TOOLS[@]}"; do
if ! command -v "${tool}" &> /dev/null; then
echo -e "${RED}Error: Required tool '${tool}' not found${NC}" >&2
exit 1
fi
echo -e "${GREEN} ✓ ${tool}${NC}"
done
# Verify wasm-opt version is exactly 100
WASM_OPT_VERSION=$(wasm-opt --version | grep -oE '[0-9]+' | head -1)
if [ "${WASM_OPT_VERSION}" != "100" ]; then
echo -e "${RED}Error: wasm-opt version must be 100, but found ${WASM_OPT_VERSION}${NC}" >&2
exit 1
fi
echo -e "${GREEN} ✓ wasm-opt version 100${NC}"
# Verify xahau.h exists
if [ ! -f "${XAHAU_H}" ]; then
echo -e "${RED}Error: xahau.h not found at ${XAHAU_H}${NC}" >&2
exit 1
fi
# Create temp directory
mkdir -p "${TEMP_DIR}"
# Build all WASM files
echo -e "${BLUE}==> Building WASM files with 'make all'...${NC}"
cd "${SCRIPT_DIR}"
make all
echo -e "${GREEN} Build completed successfully${NC}"
# Function to convert WASM to hex array
wasm_to_hex_array() {
local wasm_file="$1"
local indent=" "
if [ ! -f "${wasm_file}" ]; then
echo -e "${RED}Error: WASM file not found: ${wasm_file}${NC}" >&2
return 1
fi
# Convert to hex with xxd, format with sed
xxd -p -u -c 10 "${wasm_file}" | \
sed 's/../0x&U,/g' | \
sed "s/^/${indent}/g" | \
sed '$ s/,$//'
}
# Function to update hook array in xahau.h
update_hook_array() {
local hook_name="$1"
local hex_array="$2"
local temp_file="${TEMP_DIR}/xahau.h.tmp"
echo -e "${BLUE}==> Updating ${hook_name}...${NC}"
# Check if hook already exists
if grep -q "static const std::vector<uint8_t> ${hook_name} = {" "${XAHAU_H}"; then
echo -e "${YELLOW} Replacing existing ${hook_name}${NC}"
# Use awk to replace the array content
awk -v hook="${hook_name}" -v hex="${hex_array}" '
BEGIN { in_array=0 }
{
if ($0 ~ "static const std::vector<uint8_t> " hook " = {") {
print $0
print hex
in_array=1
next
}
if (in_array && $0 ~ /};/) {
print "};"
in_array=0
next
}
if (!in_array) {
print $0
}
}
' "${XAHAU_H}" > "${temp_file}"
mv "${temp_file}" "${XAHAU_H}"
else
echo -e "${YELLOW} Adding new ${hook_name}${NC}"
# Find the position before #endif and add the new hook
awk -v hook="${hook_name}" -v hex="${hex_array}" '
{
if ($0 ~ /#endif.*XAHAU_GENESIS_HOOKS/) {
print ""
print "static const std::vector<uint8_t> " hook " = {"
print hex
print "};"
print ""
print $0
} else {
print $0
}
}
' "${XAHAU_H}" > "${temp_file}"
mv "${temp_file}" "${XAHAU_H}"
fi
echo -e "${GREEN} ✓ ${hook_name} updated${NC}"
}
# Process each hook
for hook_entry in "${HOOK_FILES[@]}"; do
hook_name="${hook_entry%%:*}"
wasm_file="${SCRIPT_DIR}/${hook_entry##*:}"
echo -e "${BLUE}==> Converting ${wasm_file} to hex array...${NC}"
hex_array=$(wasm_to_hex_array "${wasm_file}")
if [ $? -ne 0 ]; then
echo -e "${RED}Error: Failed to convert ${wasm_file}${NC}" >&2
exit 1
fi
echo -e "${GREEN} Conversion successful ($(echo "${hex_array}" | wc -l) lines)${NC}"
update_hook_array "${hook_name}" "${hex_array}"
done
# Format with clang-format
echo -e "${BLUE}==> Formatting with clang-format...${NC}"
cp "${XAHAU_H}" "${TEMP_DIR}/xahau.h.before_format"
clang-format -i "${XAHAU_H}"
echo -e "${GREEN} Formatting completed${NC}"
# Verification
echo -e "${BLUE}==> Verifying changes...${NC}"
for hook_entry in "${HOOK_FILES[@]}"; do
hook_name="${hook_entry%%:*}"
if grep -q "static const std::vector<uint8_t> ${hook_name} = {" "${XAHAU_H}"; then
echo -e "${GREEN} ✓ ${hook_name} found in xahau.h${NC}"
else
echo -e "${RED} ✗ ${hook_name} NOT found in xahau.h${NC}" >&2
exit 1
fi
done
# Show summary
echo ""
echo -e "${GREEN}========================================${NC}"
echo -e "${GREEN}Successfully updated xahau.h${NC}"
echo -e "${GREEN}========================================${NC}"
echo -e "Updated hooks:"
for hook_entry in "${HOOK_FILES[@]}"; do
hook_name="${hook_entry%%:*}"
wasm_file="${SCRIPT_DIR}/${hook_entry##*:}"
size=$(wc -c < "${wasm_file}" | tr -d ' ')
echo -e " - ${hook_name}: ${size} bytes"
done
echo ""
echo -e "File location: ${XAHAU_H}"
echo ""
+46
View File
@@ -0,0 +1,46 @@
// For documentation please see: https://xrpl-hooks.readme.io/reference/
// Generated using generate_error.sh
#ifndef HOOK_ERROR_CODES
#define SUCCESS 0
#define OUT_OF_BOUNDS -1
#define INTERNAL_ERROR -2
#define TOO_BIG -3
#define TOO_SMALL -4
#define DOESNT_EXIST -5
#define NO_FREE_SLOTS -6
#define INVALID_ARGUMENT -7
#define ALREADY_SET -8
#define PREREQUISITE_NOT_MET -9
#define FEE_TOO_LARGE -10
#define EMISSION_FAILURE -11
#define TOO_MANY_NONCES -12
#define TOO_MANY_EMITTED_TXN -13
#define NOT_IMPLEMENTED -14
#define INVALID_ACCOUNT -15
#define GUARD_VIOLATION -16
#define INVALID_FIELD -17
#define PARSE_ERROR -18
#define RC_ROLLBACK -19
#define RC_ACCEPT -20
#define NO_SUCH_KEYLET -21
#define NOT_AN_ARRAY -22
#define NOT_AN_OBJECT -23
#define INVALID_FLOAT -10024
#define DIVISION_BY_ZERO -25
#define MANTISSA_OVERSIZED -26
#define MANTISSA_UNDERSIZED -27
#define EXPONENT_OVERSIZED -28
#define EXPONENT_UNDERSIZED -29
#define OVERFLOW -30
#define NOT_IOU_AMOUNT -31
#define NOT_AN_AMOUNT -32
#define CANT_RETURN_NEGATIVE -33
#define NOT_AUTHORIZED -34
#define PREVIOUS_FAILURE_PREVENTS_RETRY -35
#define TOO_MANY_PARAMS -36
#define INVALID_TXN -37
#define RESERVE_INSUFFICIENT -38
#define COMPLEX_NOT_SUPPORTED -39
#define DOES_NOT_MATCH -40
#define HOOK_ERROR_CODES
#endif //HOOK_ERROR_CODES
+352
View File
@@ -0,0 +1,352 @@
// For documentation please see: https://xrpl-hooks.readme.io/reference/
// Generated using generate_extern.sh
#include <stdint.h>
#ifndef HOOK_EXTERN
extern int32_t __attribute__((noduplicate))
_g(uint32_t guard_id, uint32_t maxiter);
extern int64_t
accept(uint32_t read_ptr, uint32_t read_len, int64_t error_code);
extern int64_t
emit(
uint32_t write_ptr,
uint32_t write_len,
uint32_t read_ptr,
uint32_t read_len);
extern int64_t
etxn_burden(void);
extern int64_t
etxn_details(uint32_t write_ptr, uint32_t write_len);
extern int64_t
etxn_fee_base(uint32_t read_ptr, uint32_t read_len);
extern int64_t
etxn_generation(void);
extern int64_t
etxn_nonce(uint32_t write_ptr, uint32_t write_len);
extern int64_t
etxn_reserve(uint32_t count);
extern int64_t
fee_base(void);
extern int64_t
float_compare(int64_t float1, int64_t float2, uint32_t mode);
extern int64_t
float_divide(int64_t float1, int64_t float2);
extern int64_t
float_exponent(int64_t float1);
extern int64_t
float_exponent_set(int64_t float1, int32_t exponent);
extern int64_t
float_int(int64_t float1, uint32_t decimal_places, uint32_t abs);
extern int64_t
float_invert(int64_t float1);
extern int64_t
float_log(int64_t float1);
extern int64_t
float_mantissa(int64_t float1);
extern int64_t
float_mantissa_set(int64_t float1, int64_t mantissa);
extern int64_t
float_mulratio(
int64_t float1,
uint32_t round_up,
uint32_t numerator,
uint32_t denominator);
extern int64_t
float_multiply(int64_t float1, int64_t float2);
extern int64_t
float_negate(int64_t float1);
extern int64_t
float_one(void);
extern int64_t
float_root(int64_t float1, uint32_t n);
extern int64_t
float_set(int32_t exponent, int64_t mantissa);
extern int64_t
float_sign(int64_t float1);
extern int64_t
float_sign_set(int64_t float1, uint32_t negative);
extern int64_t
float_sto(
uint32_t write_ptr,
uint32_t write_len,
uint32_t cread_ptr,
uint32_t cread_len,
uint32_t iread_ptr,
uint32_t iread_len,
int64_t float1,
uint32_t field_code);
extern int64_t
float_sto_set(uint32_t read_ptr, uint32_t read_len);
extern int64_t
float_sum(int64_t float1, int64_t float2);
extern int64_t
hook_account(uint32_t write_ptr, uint32_t write_len);
extern int64_t
hook_again(void);
extern int64_t
hook_hash(uint32_t write_ptr, uint32_t write_len, int32_t hook_no);
extern int64_t
hook_param(
uint32_t write_ptr,
uint32_t write_len,
uint32_t read_ptr,
uint32_t read_len);
extern int64_t
otxn_param(
uint32_t write_ptr,
uint32_t write_len,
uint32_t read_ptr,
uint32_t read_len);
extern int64_t
hook_param_set(
uint32_t read_ptr,
uint32_t read_len,
uint32_t kread_ptr,
uint32_t kread_len,
uint32_t hread_ptr,
uint32_t hread_len);
extern int64_t
hook_pos(void);
extern int64_t
hook_skip(uint32_t read_ptr, uint32_t read_len, uint32_t flags);
extern int64_t
ledger_keylet(
uint32_t write_ptr,
uint32_t write_len,
uint32_t lread_ptr,
uint32_t lread_len,
uint32_t hread_ptr,
uint32_t hread_len);
extern int64_t
ledger_last_hash(uint32_t write_ptr, uint32_t write_len);
extern int64_t
ledger_last_time(void);
extern int64_t
ledger_nonce(uint32_t write_ptr, uint32_t write_len);
extern int64_t
ledger_seq(void);
extern int64_t
meta_slot(uint32_t slot_no);
extern int64_t
otxn_burden(void);
extern int64_t
otxn_field(uint32_t write_ptr, uint32_t write_len, uint32_t field_id);
extern int64_t
otxn_field_txt(uint32_t write_ptr, uint32_t write_len, uint32_t field_id);
extern int64_t
otxn_generation(void);
extern int64_t
otxn_id(uint32_t write_ptr, uint32_t write_len, uint32_t flags);
extern int64_t
otxn_slot(uint32_t slot_no);
extern int64_t
otxn_type(void);
extern int64_t
rollback(uint32_t read_ptr, uint32_t read_len, int64_t error_code);
extern int64_t
slot(uint32_t write_ptr, uint32_t write_len, uint32_t slot);
extern int64_t
slot_clear(uint32_t slot);
extern int64_t
slot_count(uint32_t slot);
extern int64_t
slot_float(uint32_t slot_no);
extern int64_t
slot_id(uint32_t write_ptr, uint32_t write_len, uint32_t slot);
extern int64_t
slot_set(uint32_t read_ptr, uint32_t read_len, uint32_t slot);
extern int64_t
slot_size(uint32_t slot);
extern int64_t
slot_subarray(uint32_t parent_slot, uint32_t array_id, uint32_t new_slot);
extern int64_t
slot_subfield(uint32_t parent_slot, uint32_t field_id, uint32_t new_slot);
extern int64_t
slot_type(uint32_t slot_no, uint32_t flags);
extern int64_t
state(
uint32_t write_ptr,
uint32_t write_len,
uint32_t kread_ptr,
uint32_t kread_len);
extern int64_t
state_foreign(
uint32_t write_ptr,
uint32_t write_len,
uint32_t kread_ptr,
uint32_t kread_len,
uint32_t nread_ptr,
uint32_t nread_len,
uint32_t aread_ptr,
uint32_t aread_len);
extern int64_t
state_foreign_set(
uint32_t read_ptr,
uint32_t read_len,
uint32_t kread_ptr,
uint32_t kread_len,
uint32_t nread_ptr,
uint32_t nread_len,
uint32_t aread_ptr,
uint32_t aread_len);
extern int64_t
state_set(
uint32_t read_ptr,
uint32_t read_len,
uint32_t kread_ptr,
uint32_t kread_len);
extern int64_t
sto_emplace(
uint32_t write_ptr,
uint32_t write_len,
uint32_t sread_ptr,
uint32_t sread_len,
uint32_t fread_ptr,
uint32_t fread_len,
uint32_t field_id);
extern int64_t
sto_erase(
uint32_t write_ptr,
uint32_t write_len,
uint32_t read_ptr,
uint32_t read_len,
uint32_t field_id);
extern int64_t
sto_subarray(uint32_t read_ptr, uint32_t read_len, uint32_t array_id);
extern int64_t
sto_subfield(uint32_t read_ptr, uint32_t read_len, uint32_t field_id);
extern int64_t
sto_validate(uint32_t tread_ptr, uint32_t tread_len);
extern int64_t
trace(
uint32_t mread_ptr,
uint32_t mread_len,
uint32_t dread_ptr,
uint32_t dread_len,
uint32_t as_hex);
extern int64_t
trace_float(uint32_t read_ptr, uint32_t read_len, int64_t float1);
extern int64_t
trace_num(uint32_t read_ptr, uint32_t read_len, int64_t number);
extern int64_t
trace_slot(uint32_t read_ptr, uint32_t read_len, uint32_t slot);
extern int64_t
util_accid(
uint32_t write_ptr,
uint32_t write_len,
uint32_t read_ptr,
uint32_t read_len);
extern int64_t
util_keylet(
uint32_t write_ptr,
uint32_t write_len,
uint32_t keylet_type,
uint32_t a,
uint32_t b,
uint32_t c,
uint32_t d,
uint32_t e,
uint32_t f);
extern int64_t
util_raddr(
uint32_t write_ptr,
uint32_t write_len,
uint32_t read_ptr,
uint32_t read_len);
extern int64_t
util_sha512h(
uint32_t write_ptr,
uint32_t write_len,
uint32_t read_ptr,
uint32_t read_len);
extern int64_t
util_verify(
uint32_t dread_ptr,
uint32_t dread_len,
uint32_t sread_ptr,
uint32_t sread_len,
uint32_t kread_ptr,
uint32_t kread_len);
extern int64_t xpop_slot(uint32_t, uint32_t);
#define HOOK_EXTERN
#endif // HOOK_EXTERN
+50
View File
@@ -0,0 +1,50 @@
/**
* Hook API include file
*
* Note to the reader:
* This include defines two types of things: external functions and macros
* Functions are used sparingly because a non-inlining compiler may produce
* undesirable output.
*
* Find documentation here: https://xrpl-hooks.readme.io/reference/
*/
#ifndef HOOKAPI_INCLUDED
#define HOOKAPI_INCLUDED 1
#define KEYLET_HOOK 1
#define KEYLET_HOOK_STATE 2
#define KEYLET_ACCOUNT 3
#define KEYLET_AMENDMENTS 4
#define KEYLET_CHILD 5
#define KEYLET_SKIP 6
#define KEYLET_FEES 7
#define KEYLET_NEGATIVE_UNL 8
#define KEYLET_LINE 9
#define KEYLET_OFFER 10
#define KEYLET_QUALITY 11
#define KEYLET_EMITTED_DIR 12
#define KEYLET_TICKET 13
#define KEYLET_SIGNERS 14
#define KEYLET_CHECK 15
#define KEYLET_DEPOSIT_PREAUTH 16
#define KEYLET_UNCHECKED 17
#define KEYLET_OWNER_DIR 18
#define KEYLET_PAGE 19
#define KEYLET_ESCROW 20
#define KEYLET_PAYCHAN 21
#define KEYLET_EMITTED 22
#define KEYLET_NFT_OFFER 23
#define KEYLET_HOOK_DEFINITION 24
#define COMPARE_EQUAL 1U
#define COMPARE_LESS 2U
#define COMPARE_GREATER 4U
#include "error.h"
#include "extern.h"
#include "sfcodes.h"
#include "macro.h"
#include "types.h"
#endif
+671
View File
@@ -0,0 +1,671 @@
/**
* These are helper macros for writing hooks, all of them are optional as is including hookmacro.h at all
*/
#include <stdint.h>
#include "hookapi.h"
#include "sfcodes.h"
#ifndef HOOKMACROS_INCLUDED
#define HOOKMACROS_INCLUDED 1
#ifdef NDEBUG
#define DEBUG 0
#else
#define DEBUG 1
#endif
#define TRACEVAR(v) if (DEBUG) trace_num((uint32_t)(#v), (uint32_t)(sizeof(#v) - 1), (int64_t)v);
#define TRACEHEX(v) if (DEBUG) trace((uint32_t)(#v), (uint32_t)(sizeof(#v) - 1), (uint32_t)(v), (uint32_t)(sizeof(v)), 1);
#define TRACEXFL(v) if (DEBUG) trace_float((uint32_t)(#v), (uint32_t)(sizeof(#v) - 1), (int64_t)v);
#define TRACESTR(v) if (DEBUG) trace((uint32_t)(#v), (uint32_t)(sizeof(#v) - 1), (uint32_t)(v), sizeof(v), 0);
// hook developers should use this guard macro, simply GUARD(<maximum iterations>)
#define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1)
#define GUARDM(maxiter, n) _g(( (1ULL << 31U) + (__LINE__ << 16) + n), (maxiter)+1)
#define SBUF(str) (uint32_t)(str), sizeof(str)
#define REQUIRE(cond, str)\
{\
if (!(cond))\
rollback(SBUF(str), __LINE__);\
}
// make a report buffer as a c-string
// provide a name for a buffer to declare (buf)
// provide a static string
// provide an integer to print after the string
#define RBUF(buf, out_len, str, num)\
unsigned char buf[sizeof(str) + 21];\
int out_len = 0;\
{\
int i = 0;\
for (; GUARDM(sizeof(str),1),i < sizeof(str); ++i)\
(buf)[i] = str[i];\
if ((buf)[sizeof(str)-1] == 0) i--;\
if ((num) < 0) (buf)[i++] = '-';\
uint64_t unsigned_num = (uint64_t)( (num) < 0 ? (num) * -1 : (num) );\
uint64_t j = 10000000000000000000ULL;\
int start = 1;\
for (; GUARDM(20,2), unsigned_num > 0 && j > 0; j /= 10)\
{\
unsigned char digit = ( unsigned_num / j ) % 10;\
if (digit == 0 && start)\
continue;\
start = 0;\
(buf)[i++] = '0' + digit;\
}\
(buf)[i] = '\0';\
out_len = i;\
}
#define RBUF2(buff, out_len, str, num, str2, num2)\
unsigned char buff[sizeof(str) + sizeof(str2) + 42];\
int out_len = 0;\
{\
unsigned char* buf = buff;\
int i = 0;\
for (; GUARDM(sizeof(str),1),i < sizeof(str); ++i)\
(buf)[i] = str[i];\
if ((buf)[sizeof(str)-1] == 0) i--;\
if ((num) < 0) (buf)[i++] = '-';\
uint64_t unsigned_num = (uint64_t)( (num) < 0 ? (num) * -1 : (num) );\
uint64_t j = 10000000000000000000ULL;\
int start = 1;\
for (; GUARDM(20,2), unsigned_num > 0 && j > 0; j /= 10)\
{\
unsigned char digit = ( unsigned_num / j ) % 10;\
if (digit == 0 && start)\
continue;\
start = 0;\
(buf)[i++] = '0' + digit;\
}\
buf += i;\
out_len += i;\
i = 0;\
for (; GUARDM(sizeof(str2),3),i < sizeof(str2); ++i)\
(buf)[i] = str2[i];\
if ((buf)[sizeof(str2)-1] == 0) i--;\
if ((num2) < 0) (buf)[i++] = '-';\
unsigned_num = (uint64_t)( (num2) < 0 ? (num2) * -1 : (num2) );\
j = 10000000000000000000ULL;\
start = 1;\
for (; GUARDM(20,4), unsigned_num > 0 && j > 0; j /= 10)\
{\
unsigned char digit = ( unsigned_num / j ) % 10;\
if (digit == 0 && start)\
continue;\
start = 0;\
(buf)[i++] = '0' + digit;\
}\
(buf)[i] = '\0';\
out_len += i;\
}
#define CLEARBUF(b)\
{\
for (int x = 0; GUARD(sizeof(b)), x < sizeof(b); ++x)\
b[x] = 0;\
}
// returns an in64_t, negative if error, non-negative if valid drops
#define AMOUNT_TO_DROPS(amount_buffer)\
(((amount_buffer)[0] >> 7) ? -2 : (\
((((uint64_t)((amount_buffer)[0])) & 0xb00111111) << 56) +\
(((uint64_t)((amount_buffer)[1])) << 48) +\
(((uint64_t)((amount_buffer)[2])) << 40) +\
(((uint64_t)((amount_buffer)[3])) << 32) +\
(((uint64_t)((amount_buffer)[4])) << 24) +\
(((uint64_t)((amount_buffer)[5])) << 16) +\
(((uint64_t)((amount_buffer)[6])) << 8) +\
(((uint64_t)((amount_buffer)[7])))))
#define SUB_OFFSET(x) ((int32_t)(x >> 32))
#define SUB_LENGTH(x) ((int32_t)(x & 0xFFFFFFFFULL))
#define BUFFER_EQUAL_20(buf1, buf2)\
(\
*(((uint64_t*)(buf1)) + 0) == *(((uint64_t*)(buf2)) + 0) &&\
*(((uint64_t*)(buf1)) + 1) == *(((uint64_t*)(buf2)) + 1) &&\
*(((uint32_t*)(buf1)) + 4) == *(((uint32_t*)(buf2)) + 4))
#define BUFFER_EQUAL_32(buf1, buf2)\
(\
*(((uint64_t*)(buf1)) + 0) == *(((uint64_t*)(buf2)) + 0) &&\
*(((uint64_t*)(buf1)) + 1) == *(((uint64_t*)(buf2)) + 1) &&\
*(((uint64_t*)(buf1)) + 2) == *(((uint64_t*)(buf2)) + 2) &&\
*(((uint64_t*)(buf1)) + 3) == *(((uint64_t*)(buf2)) + 3))
// when using this macro buf1len may be dynamic but buf2len must be static
// provide n >= 1 to indicate how many times the macro will be hit on the line of code
// e.g. if it is in a loop that loops 10 times n = 10
#define BUFFER_EQUAL_GUARD(output, buf1, buf1len, buf2, buf2len, n)\
{\
output = ((buf1len) == (buf2len) ? 1 : 0);\
for (int x = 0; GUARDM( (buf2len) * (n), 1 ), output && x < (buf2len);\
++x)\
output = *(((uint8_t*)(buf1)) + x) == *(((uint8_t*)(buf2)) + x);\
}
#define BUFFER_SWAP(x,y)\
{\
uint8_t* z = x;\
x = y;\
y = z;\
}
#define ACCOUNT_COMPARE(compare_result, buf1, buf2)\
{\
compare_result = 0;\
for (int i = 0; GUARD(20), i < 20; ++i)\
{\
if (buf1[i] > buf2[i])\
{\
compare_result = 1;\
break;\
}\
else if (buf1[i] < buf2[i])\
{\
compare_result = -1;\
break;\
}\
}\
}
#define BUFFER_EQUAL_STR_GUARD(output, buf1, buf1len, str, n)\
BUFFER_EQUAL_GUARD(output, buf1, buf1len, str, (sizeof(str)-1), n)
#define BUFFER_EQUAL_STR(output, buf1, buf1len, str)\
BUFFER_EQUAL_GUARD(output, buf1, buf1len, str, (sizeof(str)-1), 1)
#define BUFFER_EQUAL(output, buf1, buf2, compare_len)\
BUFFER_EQUAL_GUARD(output, buf1, compare_len, buf2, compare_len, 1)
#define UINT16_TO_BUF(buf_raw, i)\
{\
unsigned char* buf = (unsigned char*)buf_raw;\
buf[0] = (((uint64_t)i) >> 8) & 0xFFUL;\
buf[1] = (((uint64_t)i) >> 0) & 0xFFUL;\
}
#define UINT16_FROM_BUF(buf)\
(((uint64_t)((buf)[0]) << 8) +\
((uint64_t)((buf)[1]) << 0))
#define UINT32_TO_BUF(buf_raw, i)\
{\
unsigned char* buf = (unsigned char*)buf_raw;\
buf[0] = (((uint64_t)i) >> 24) & 0xFFUL;\
buf[1] = (((uint64_t)i) >> 16) & 0xFFUL;\
buf[2] = (((uint64_t)i) >> 8) & 0xFFUL;\
buf[3] = (((uint64_t)i) >> 0) & 0xFFUL;\
}
#define UINT32_FROM_BUF(buf)\
(((uint64_t)((buf)[0]) << 24) +\
((uint64_t)((buf)[1]) << 16) +\
((uint64_t)((buf)[2]) << 8) +\
((uint64_t)((buf)[3]) << 0))
#define UINT64_TO_BUF(buf_raw, i)\
{\
unsigned char* buf = (unsigned char*)buf_raw;\
buf[0] = (((uint64_t)i) >> 56) & 0xFFUL;\
buf[1] = (((uint64_t)i) >> 48) & 0xFFUL;\
buf[2] = (((uint64_t)i) >> 40) & 0xFFUL;\
buf[3] = (((uint64_t)i) >> 32) & 0xFFUL;\
buf[4] = (((uint64_t)i) >> 24) & 0xFFUL;\
buf[5] = (((uint64_t)i) >> 16) & 0xFFUL;\
buf[6] = (((uint64_t)i) >> 8) & 0xFFUL;\
buf[7] = (((uint64_t)i) >> 0) & 0xFFUL;\
}
#define UINT64_FROM_BUF(buf)\
(((uint64_t)((buf)[0]) << 56) +\
((uint64_t)((buf)[1]) << 48) +\
((uint64_t)((buf)[2]) << 40) +\
((uint64_t)((buf)[3]) << 32) +\
((uint64_t)((buf)[4]) << 24) +\
((uint64_t)((buf)[5]) << 16) +\
((uint64_t)((buf)[6]) << 8) +\
((uint64_t)((buf)[7]) << 0))
#define INT64_FROM_BUF(buf)\
((((uint64_t)((buf)[0] & 0x7FU) << 56) +\
((uint64_t)((buf)[1]) << 48) +\
((uint64_t)((buf)[2]) << 40) +\
((uint64_t)((buf)[3]) << 32) +\
((uint64_t)((buf)[4]) << 24) +\
((uint64_t)((buf)[5]) << 16) +\
((uint64_t)((buf)[6]) << 8) +\
((uint64_t)((buf)[7]) << 0)) * (buf[0] & 0x80U ? -1 : 1))
#define INT64_TO_BUF(buf_raw, i)\
{\
unsigned char* buf = (unsigned char*)buf_raw;\
buf[0] = (((uint64_t)i) >> 56) & 0x7FUL;\
buf[1] = (((uint64_t)i) >> 48) & 0xFFUL;\
buf[2] = (((uint64_t)i) >> 40) & 0xFFUL;\
buf[3] = (((uint64_t)i) >> 32) & 0xFFUL;\
buf[4] = (((uint64_t)i) >> 24) & 0xFFUL;\
buf[5] = (((uint64_t)i) >> 16) & 0xFFUL;\
buf[6] = (((uint64_t)i) >> 8) & 0xFFUL;\
buf[7] = (((uint64_t)i) >> 0) & 0xFFUL;\
if (i < 0) buf[0] |= 0x80U;\
}
#define ttPAYMENT 0
#define ttESCROW_CREATE 1
#define ttESCROW_FINISH 2
#define ttACCOUNT_SET 3
#define ttESCROW_CANCEL 4
#define ttREGULAR_KEY_SET 5
#define ttOFFER_CREATE 7
#define ttOFFER_CANCEL 8
#define ttTICKET_CREATE 10
#define ttSIGNER_LIST_SET 12
#define ttPAYCHAN_CREATE 13
#define ttPAYCHAN_FUND 14
#define ttPAYCHAN_CLAIM 15
#define ttCHECK_CREATE 16
#define ttCHECK_CASH 17
#define ttCHECK_CANCEL 18
#define ttDEPOSIT_PREAUTH 19
#define ttTRUST_SET 20
#define ttACCOUNT_DELETE 21
#define ttHOOK_SET 22
#define ttNFTOKEN_MINT 25
#define ttNFTOKEN_BURN 26
#define ttNFTOKEN_CREATE_OFFER 27
#define ttNFTOKEN_CANCEL_OFFER 28
#define ttNFTOKEN_ACCEPT_OFFER 29
#define ttURITOKEN_MINT 45
#define ttURITOKEN_BURN 46
#define ttURITOKEN_BUY 47
#define ttURITOKEN_CREATE_SELL_OFFER 48
#define ttURITOKEN_CANCEL_SELL_OFFER 49
#define ttCLAIM_REWARD 98
#define ttINVOKE 99
#define ttAMENDMENT 100
#define ttFEE 101
#define ttUNL_MODIFY 102
#define ttEMIT_FAILURE 103
#define tfCANONICAL 0x80000000UL
#define atACCOUNT 1U
#define atOWNER 2U
#define atDESTINATION 3U
#define atISSUER 4U
#define atAUTHORIZE 5U
#define atUNAUTHORIZE 6U
#define atTARGET 7U
#define atREGULARKEY 8U
#define atPSEUDOCALLBACK 9U
#define amAMOUNT 1U
#define amBALANCE 2U
#define amLIMITAMOUNT 3U
#define amTAKERPAYS 4U
#define amTAKERGETS 5U
#define amLOWLIMIT 6U
#define amHIGHLIMIT 7U
#define amFEE 8U
#define amSENDMAX 9U
#define amDELIVERMIN 10U
#define amMINIMUMOFFER 16U
#define amRIPPLEESCROW 17U
#define amDELIVEREDAMOUNT 18U
/**
* RH NOTE -- PAY ATTENTION
*
* ALL 'ENCODE' MACROS INCREMENT BUF_OUT
* THIS IS TO MAKE CHAINING EASY
* BUF_OUT IS A SACRIFICIAL POINTER
*
* 'ENCODE' MACROS WITH CONSTANTS HAVE
* ALIASING TO ASSIST YOU WITH ORDER
* _TYPECODE_FIELDCODE_ENCODE_MACRO
* TO PRODUCE A SERIALIZED OBJECT
* IN CANONICAL FORMAT YOU MUST ORDER
* FIRST BY TYPE CODE THEN BY FIELD CODE
*
* ALL 'PREPARE' MACROS PRESERVE POINTERS
*
**/
#define ENCODE_TL_SIZE 49
#define ENCODE_TL(buf_out, tlamt, amount_type)\
{\
uint8_t uat = amount_type; \
buf_out[0] = 0x60U +(uat & 0x0FU ); \
for (int i = 1; GUARDM(48, 1), i < 49; ++i)\
buf_out[i] = tlamt[i-1];\
buf_out += ENCODE_TL_SIZE;\
}
#define _06_XX_ENCODE_TL(buf_out, drops, amount_type )\
ENCODE_TL(buf_out, drops, amount_type );
#define ENCODE_TL_AMOUNT(buf_out, drops )\
ENCODE_TL(buf_out, drops, amAMOUNT );
#define _06_01_ENCODE_TL_AMOUNT(buf_out, drops )\
ENCODE_TL_AMOUNT(buf_out, drops );
// Encode drops to serialization format
// consumes 9 bytes
#define ENCODE_DROPS_SIZE 9
#define ENCODE_DROPS(buf_out, drops, amount_type ) \
{\
uint8_t uat = amount_type; \
uint64_t udrops = drops; \
buf_out[0] = 0x60U +(uat & 0x0FU ); \
buf_out[1] = 0b01000000 + (( udrops >> 56 ) & 0b00111111 ); \
buf_out[2] = (udrops >> 48) & 0xFFU; \
buf_out[3] = (udrops >> 40) & 0xFFU; \
buf_out[4] = (udrops >> 32) & 0xFFU; \
buf_out[5] = (udrops >> 24) & 0xFFU; \
buf_out[6] = (udrops >> 16) & 0xFFU; \
buf_out[7] = (udrops >> 8) & 0xFFU; \
buf_out[8] = (udrops >> 0) & 0xFFU; \
buf_out += ENCODE_DROPS_SIZE; \
}
#define _06_XX_ENCODE_DROPS(buf_out, drops, amount_type )\
ENCODE_DROPS(buf_out, drops, amount_type );
#define ENCODE_DROPS_AMOUNT(buf_out, drops )\
ENCODE_DROPS(buf_out, drops, amAMOUNT );
#define _06_01_ENCODE_DROPS_AMOUNT(buf_out, drops )\
ENCODE_DROPS_AMOUNT(buf_out, drops );
#define ENCODE_DROPS_FEE(buf_out, drops )\
ENCODE_DROPS(buf_out, drops, amFEE );
#define _06_08_ENCODE_DROPS_FEE(buf_out, drops )\
ENCODE_DROPS_FEE(buf_out, drops );
#define ENCODE_TT_SIZE 3
#define ENCODE_TT(buf_out, tt )\
{\
uint8_t utt = tt;\
buf_out[0] = 0x12U;\
buf_out[1] =(utt >> 8 ) & 0xFFU;\
buf_out[2] =(utt >> 0 ) & 0xFFU;\
buf_out += ENCODE_TT_SIZE; \
}
#define _01_02_ENCODE_TT(buf_out, tt)\
ENCODE_TT(buf_out, tt);
#define ENCODE_ACCOUNT_SIZE 22
#define ENCODE_ACCOUNT(buf_out, account_id, account_type)\
{\
uint8_t uat = account_type;\
buf_out[0] = 0x80U + uat;\
buf_out[1] = 0x14U;\
*(uint64_t*)(buf_out + 2) = *(uint64_t*)(account_id + 0);\
*(uint64_t*)(buf_out + 10) = *(uint64_t*)(account_id + 8);\
*(uint32_t*)(buf_out + 18) = *(uint32_t*)(account_id + 16);\
buf_out += ENCODE_ACCOUNT_SIZE;\
}
#define _08_XX_ENCODE_ACCOUNT(buf_out, account_id, account_type)\
ENCODE_ACCOUNT(buf_out, account_id, account_type);
#define ENCODE_ACCOUNT_SRC_SIZE 22
#define ENCODE_ACCOUNT_SRC(buf_out, account_id)\
ENCODE_ACCOUNT(buf_out, account_id, atACCOUNT);
#define _08_01_ENCODE_ACCOUNT_SRC(buf_out, account_id)\
ENCODE_ACCOUNT_SRC(buf_out, account_id);
#define ENCODE_ACCOUNT_DST_SIZE 22
#define ENCODE_ACCOUNT_DST(buf_out, account_id)\
ENCODE_ACCOUNT(buf_out, account_id, atDESTINATION);
#define _08_03_ENCODE_ACCOUNT_DST(buf_out, account_id)\
ENCODE_ACCOUNT_DST(buf_out, account_id);
#define ENCODE_ACCOUNT_OWNER_SIZE 22
#define ENCODE_ACCOUNT_OWNER(buf_out, account_id) \
ENCODE_ACCOUNT(buf_out, account_id, atOWNER);
#define _08_02_ENCODE_ACCOUNT_OWNER(buf_out, account_id) \
ENCODE_ACCOUNT_OWNER(buf_out, account_id);
#define ENCODE_UINT32_COMMON_SIZE 5U
#define ENCODE_UINT32_COMMON(buf_out, i, field)\
{\
uint32_t ui = i; \
uint8_t uf = field; \
buf_out[0] = 0x20U +(uf & 0x0FU); \
buf_out[1] =(ui >> 24 ) & 0xFFU; \
buf_out[2] =(ui >> 16 ) & 0xFFU; \
buf_out[3] =(ui >> 8 ) & 0xFFU; \
buf_out[4] =(ui >> 0 ) & 0xFFU; \
buf_out += ENCODE_UINT32_COMMON_SIZE; \
}
#define _02_XX_ENCODE_UINT32_COMMON(buf_out, i, field)\
ENCODE_UINT32_COMMON(buf_out, i, field)\
#define ENCODE_UINT32_UNCOMMON_SIZE 6U
#define ENCODE_UINT32_UNCOMMON(buf_out, i, field)\
{\
uint32_t ui = i; \
uint8_t uf = field; \
buf_out[0] = 0x20U; \
buf_out[1] = uf; \
buf_out[2] =(ui >> 24 ) & 0xFFU; \
buf_out[3] =(ui >> 16 ) & 0xFFU; \
buf_out[4] =(ui >> 8 ) & 0xFFU; \
buf_out[5] =(ui >> 0 ) & 0xFFU; \
buf_out += ENCODE_UINT32_UNCOMMON_SIZE; \
}
#define _02_XX_ENCODE_UINT32_UNCOMMON(buf_out, i, field)\
ENCODE_UINT32_UNCOMMON(buf_out, i, field)\
#define ENCODE_LLS_SIZE 6U
#define ENCODE_LLS(buf_out, lls )\
ENCODE_UINT32_UNCOMMON(buf_out, lls, 0x1B );
#define _02_27_ENCODE_LLS(buf_out, lls )\
ENCODE_LLS(buf_out, lls );
#define ENCODE_FLS_SIZE 6U
#define ENCODE_FLS(buf_out, fls )\
ENCODE_UINT32_UNCOMMON(buf_out, fls, 0x1A );
#define _02_26_ENCODE_FLS(buf_out, fls )\
ENCODE_FLS(buf_out, fls );
#define ENCODE_TAG_SRC_SIZE 5
#define ENCODE_TAG_SRC(buf_out, tag )\
ENCODE_UINT32_COMMON(buf_out, tag, 0x3U );
#define _02_03_ENCODE_TAG_SRC(buf_out, tag )\
ENCODE_TAG_SRC(buf_out, tag );
#define ENCODE_TAG_DST_SIZE 5
#define ENCODE_TAG_DST(buf_out, tag )\
ENCODE_UINT32_COMMON(buf_out, tag, 0xEU );
#define _02_14_ENCODE_TAG_DST(buf_out, tag )\
ENCODE_TAG_DST(buf_out, tag );
#define ENCODE_SEQUENCE_SIZE 5
#define ENCODE_SEQUENCE(buf_out, sequence )\
ENCODE_UINT32_COMMON(buf_out, sequence, 0x4U );
#define _02_04_ENCODE_SEQUENCE(buf_out, sequence )\
ENCODE_SEQUENCE(buf_out, sequence );
#define ENCODE_FLAGS_SIZE 5
#define ENCODE_FLAGS(buf_out, tag )\
ENCODE_UINT32_COMMON(buf_out, tag, 0x2U );
#define _02_02_ENCODE_FLAGS(buf_out, tag )\
ENCODE_FLAGS(buf_out, tag );
#define ENCODE_SIGNING_PUBKEY_SIZE 35
#define ENCODE_SIGNING_PUBKEY(buf_out, pkey )\
{\
buf_out[0] = 0x73U;\
buf_out[1] = 0x21U;\
*(uint64_t*)(buf_out + 2) = *(uint64_t*)(pkey + 0);\
*(uint64_t*)(buf_out + 10) = *(uint64_t*)(pkey + 8);\
*(uint64_t*)(buf_out + 18) = *(uint64_t*)(pkey + 16);\
*(uint64_t*)(buf_out + 26) = *(uint64_t*)(pkey + 24);\
buf[34] = pkey[32];\
buf_out += ENCODE_SIGNING_PUBKEY_SIZE;\
}
#define _07_03_ENCODE_SIGNING_PUBKEY(buf_out, pkey )\
ENCODE_SIGNING_PUBKEY(buf_out, pkey );
#define ENCODE_SIGNING_PUBKEY_NULL_SIZE 2
#define ENCODE_SIGNING_PUBKEY_NULL(buf_out )\
{\
*buf_out++ = 0x73U;\
*buf_out++ = 0x00U;\
}
#define _07_03_ENCODE_SIGNING_PUBKEY_NULL(buf_out )\
ENCODE_SIGNING_PUBKEY_NULL(buf_out );
#define _0E_0E_ENCODE_HOOKOBJ(buf_out, hhash)\
{\
uint8_t* hook0 = (hhash);\
*buf_out++ = 0xEEU; /* hook obj start */ \
if (hook0 == 0) /* noop */\
{\
/* do nothing */ \
}\
else\
{\
*buf_out++ = 0x22U; /* flags = override */\
*buf_out++ = 0x00U;\
*buf_out++ = 0x00U;\
*buf_out++ = 0x00U;\
*buf_out++ = 0x01U;\
if (hook0 == 0xFFFFFFFFUL) /* delete operation */ \
{\
*buf_out++ = 0x7BU; /* empty createcode */ \
*buf_out++ = 0x00U;\
}\
else\
{\
*buf_out++ = 0x50U; /* HookHash */\
*buf_out++ = 0x1FU;\
uint64_t* d = (uint64_t*)buf_out;\
uint64_t* s = (uint64_t*)hook0;\
*d++ = *s++;\
*d++ = *s++;\
*d++ = *s++;\
*d++ = *s++;\
buf_out+=32;\
}\
}\
*buf_out++ = 0xE1U;\
}
#define PREPARE_HOOKSET(buf_out_master, maxlen, h, sizeout)\
{\
uint8_t* buf_out = (buf_out_master); \
uint8_t acc[20]; \
uint32_t cls = (uint32_t)ledger_seq(); \
hook_account(SBUF(acc)); \
_01_02_ENCODE_TT (buf_out, ttHOOK_SET ); \
_02_02_ENCODE_FLAGS (buf_out, tfCANONICAL ); \
_02_04_ENCODE_SEQUENCE (buf_out, 0 ); \
_02_26_ENCODE_FLS (buf_out, cls + 1 ); \
_02_27_ENCODE_LLS (buf_out, cls + 5 ); \
uint8_t* fee_ptr = buf_out; \
_06_08_ENCODE_DROPS_FEE (buf_out, 0 ); \
_07_03_ENCODE_SIGNING_PUBKEY_NULL (buf_out ); \
_08_01_ENCODE_ACCOUNT_SRC (buf_out, acc ); \
uint32_t remaining_size = (maxlen) - (buf_out - (buf_out_master)); \
int64_t edlen = etxn_details((uint32_t)buf_out, remaining_size); \
buf_out += edlen; \
*buf_out++ = 0xFBU; /* hook array start */ \
_0E_0E_ENCODE_HOOKOBJ (buf_out, h[0]); \
_0E_0E_ENCODE_HOOKOBJ (buf_out, h[1]); \
_0E_0E_ENCODE_HOOKOBJ (buf_out, h[2]); \
_0E_0E_ENCODE_HOOKOBJ (buf_out, h[3]); \
_0E_0E_ENCODE_HOOKOBJ (buf_out, h[4]); \
_0E_0E_ENCODE_HOOKOBJ (buf_out, h[5]); \
_0E_0E_ENCODE_HOOKOBJ (buf_out, h[6]); \
_0E_0E_ENCODE_HOOKOBJ (buf_out, h[7]); \
_0E_0E_ENCODE_HOOKOBJ (buf_out, h[8]); \
_0E_0E_ENCODE_HOOKOBJ (buf_out, h[9]); \
*buf_out++ = 0xF1U; /* hook array end */ \
sizeout = (buf_out - (buf_out_master)); \
int64_t fee = etxn_fee_base(buf_out_master, sizeout); \
_06_08_ENCODE_DROPS_FEE (fee_ptr, fee ); \
}
#ifdef HAS_CALLBACK
#define PREPARE_PAYMENT_SIMPLE_SIZE 270U
#else
#define PREPARE_PAYMENT_SIMPLE_SIZE 248U
#endif
#define PREPARE_PAYMENT_SIMPLE(buf_out_master, drops_amount_raw, to_address, dest_tag_raw, src_tag_raw)\
{\
uint8_t* buf_out = buf_out_master;\
uint8_t acc[20];\
uint64_t drops_amount = (drops_amount_raw);\
uint32_t dest_tag = (dest_tag_raw);\
uint32_t src_tag = (src_tag_raw);\
uint32_t cls = (uint32_t)ledger_seq();\
hook_account(SBUF(acc));\
_01_02_ENCODE_TT (buf_out, ttPAYMENT ); /* uint16 | size 3 */ \
_02_02_ENCODE_FLAGS (buf_out, tfCANONICAL ); /* uint32 | size 5 */ \
_02_03_ENCODE_TAG_SRC (buf_out, src_tag ); /* uint32 | size 5 */ \
_02_04_ENCODE_SEQUENCE (buf_out, 0 ); /* uint32 | size 5 */ \
_02_14_ENCODE_TAG_DST (buf_out, dest_tag ); /* uint32 | size 5 */ \
_02_26_ENCODE_FLS (buf_out, cls + 1 ); /* uint32 | size 6 */ \
_02_27_ENCODE_LLS (buf_out, cls + 5 ); /* uint32 | size 6 */ \
_06_01_ENCODE_DROPS_AMOUNT (buf_out, drops_amount ); /* amount | size 9 */ \
uint8_t* fee_ptr = buf_out;\
_06_08_ENCODE_DROPS_FEE (buf_out, 0 ); /* amount | size 9 */ \
_07_03_ENCODE_SIGNING_PUBKEY_NULL (buf_out ); /* pk | size 35 */ \
_08_01_ENCODE_ACCOUNT_SRC (buf_out, acc ); /* account | size 22 */ \
_08_03_ENCODE_ACCOUNT_DST (buf_out, to_address ); /* account | size 22 */ \
int64_t edlen = etxn_details((uint32_t)buf_out, PREPARE_PAYMENT_SIMPLE_SIZE); /* emitdet | size 1?? */ \
int64_t fee = etxn_fee_base(buf_out_master, PREPARE_PAYMENT_SIMPLE_SIZE); \
_06_08_ENCODE_DROPS_FEE (fee_ptr, fee ); \
}
#ifdef HAS_CALLBACK
#define PREPARE_PAYMENT_SIMPLE_TRUSTLINE_SIZE 309
#else
#define PREPARE_PAYMENT_SIMPLE_TRUSTLINE_SIZE 287
#endif
#define PREPARE_PAYMENT_SIMPLE_TRUSTLINE(buf_out_master, tlamt, to_address, dest_tag_raw, src_tag_raw)\
{\
uint8_t* buf_out = buf_out_master;\
uint8_t acc[20];\
uint32_t dest_tag = (dest_tag_raw);\
uint32_t src_tag = (src_tag_raw);\
uint32_t cls = (uint32_t)ledger_seq();\
hook_account(SBUF(acc));\
_01_02_ENCODE_TT (buf_out, ttPAYMENT ); /* uint16 | size 3 */ \
_02_02_ENCODE_FLAGS (buf_out, tfCANONICAL ); /* uint32 | size 5 */ \
_02_03_ENCODE_TAG_SRC (buf_out, src_tag ); /* uint32 | size 5 */ \
_02_04_ENCODE_SEQUENCE (buf_out, 0 ); /* uint32 | size 5 */ \
_02_14_ENCODE_TAG_DST (buf_out, dest_tag ); /* uint32 | size 5 */ \
_02_26_ENCODE_FLS (buf_out, cls + 1 ); /* uint32 | size 6 */ \
_02_27_ENCODE_LLS (buf_out, cls + 5 ); /* uint32 | size 6 */ \
_06_01_ENCODE_TL_AMOUNT (buf_out, tlamt ); /* amount | size 48 */ \
uint8_t* fee_ptr = buf_out;\
_06_08_ENCODE_DROPS_FEE (buf_out, 0 ); /* amount | size 9 */ \
_07_03_ENCODE_SIGNING_PUBKEY_NULL (buf_out ); /* pk | size 35 */ \
_08_01_ENCODE_ACCOUNT_SRC (buf_out, acc ); /* account | size 22 */ \
_08_03_ENCODE_ACCOUNT_DST (buf_out, to_address ); /* account | size 22 */ \
etxn_details((uint32_t)buf_out, PREPARE_PAYMENT_SIMPLE_TRUSTLINE_SIZE); /* emitdet | size 1?? */ \
int64_t fee = etxn_fee_base(buf_out_master, PREPARE_PAYMENT_SIMPLE_TRUSTLINE_SIZE); \
_06_08_ENCODE_DROPS_FEE (fee_ptr, fee ); \
}
#endif
+215
View File
@@ -0,0 +1,215 @@
// For documentation please see: https://xrpl-hooks.readme.io/reference/
// Generated using generate_sfcodes.sh
#define sfCloseResolution ((16U << 16U) + 1U)
#define sfMethod ((16U << 16U) + 2U)
#define sfTransactionResult ((16U << 16U) + 3U)
#define sfTickSize ((16U << 16U) + 16U)
#define sfUNLModifyDisabling ((16U << 16U) + 17U)
#define sfHookResult ((16U << 16U) + 18U)
#define sfLedgerEntryType ((1U << 16U) + 1U)
#define sfTransactionType ((1U << 16U) + 2U)
#define sfSignerWeight ((1U << 16U) + 3U)
#define sfTransferFee ((1U << 16U) + 4U)
#define sfVersion ((1U << 16U) + 16U)
#define sfHookStateChangeCount ((1U << 16U) + 17U)
#define sfHookEmitCount ((1U << 16U) + 18U)
#define sfHookExecutionIndex ((1U << 16U) + 19U)
#define sfHookApiVersion ((1U << 16U) + 20U)
#define sfNetworkID ((2U << 16U) + 1U)
#define sfFlags ((2U << 16U) + 2U)
#define sfSourceTag ((2U << 16U) + 3U)
#define sfSequence ((2U << 16U) + 4U)
#define sfPreviousTxnLgrSeq ((2U << 16U) + 5U)
#define sfLedgerSequence ((2U << 16U) + 6U)
#define sfCloseTime ((2U << 16U) + 7U)
#define sfParentCloseTime ((2U << 16U) + 8U)
#define sfSigningTime ((2U << 16U) + 9U)
#define sfExpiration ((2U << 16U) + 10U)
#define sfTransferRate ((2U << 16U) + 11U)
#define sfWalletSize ((2U << 16U) + 12U)
#define sfOwnerCount ((2U << 16U) + 13U)
#define sfDestinationTag ((2U << 16U) + 14U)
#define sfHighQualityIn ((2U << 16U) + 16U)
#define sfHighQualityOut ((2U << 16U) + 17U)
#define sfLowQualityIn ((2U << 16U) + 18U)
#define sfLowQualityOut ((2U << 16U) + 19U)
#define sfQualityIn ((2U << 16U) + 20U)
#define sfQualityOut ((2U << 16U) + 21U)
#define sfStampEscrow ((2U << 16U) + 22U)
#define sfBondAmount ((2U << 16U) + 23U)
#define sfLoadFee ((2U << 16U) + 24U)
#define sfOfferSequence ((2U << 16U) + 25U)
#define sfFirstLedgerSequence ((2U << 16U) + 26U)
#define sfLastLedgerSequence ((2U << 16U) + 27U)
#define sfTransactionIndex ((2U << 16U) + 28U)
#define sfOperationLimit ((2U << 16U) + 29U)
#define sfReferenceFeeUnits ((2U << 16U) + 30U)
#define sfReserveBase ((2U << 16U) + 31U)
#define sfReserveIncrement ((2U << 16U) + 32U)
#define sfSetFlag ((2U << 16U) + 33U)
#define sfClearFlag ((2U << 16U) + 34U)
#define sfSignerQuorum ((2U << 16U) + 35U)
#define sfCancelAfter ((2U << 16U) + 36U)
#define sfFinishAfter ((2U << 16U) + 37U)
#define sfSignerListID ((2U << 16U) + 38U)
#define sfSettleDelay ((2U << 16U) + 39U)
#define sfTicketCount ((2U << 16U) + 40U)
#define sfTicketSequence ((2U << 16U) + 41U)
#define sfNFTokenTaxon ((2U << 16U) + 42U)
#define sfMintedNFTokens ((2U << 16U) + 43U)
#define sfBurnedNFTokens ((2U << 16U) + 44U)
#define sfHookStateCount ((2U << 16U) + 45U)
#define sfEmitGeneration ((2U << 16U) + 46U)
#define sfLockCount ((2U << 16U) + 47U)
#define sfRewardTime ((2U << 16U) + 98U)
#define sfRewardLgrFirst ((2U << 16U) + 99U)
#define sfRewardLgrLast ((2U << 16U) + 100U)
#define sfIndexNext ((3U << 16U) + 1U)
#define sfIndexPrevious ((3U << 16U) + 2U)
#define sfBookNode ((3U << 16U) + 3U)
#define sfOwnerNode ((3U << 16U) + 4U)
#define sfBaseFee ((3U << 16U) + 5U)
#define sfExchangeRate ((3U << 16U) + 6U)
#define sfLowNode ((3U << 16U) + 7U)
#define sfHighNode ((3U << 16U) + 8U)
#define sfDestinationNode ((3U << 16U) + 9U)
#define sfCookie ((3U << 16U) + 10U)
#define sfServerVersion ((3U << 16U) + 11U)
#define sfNFTokenOfferNode ((3U << 16U) + 12U)
#define sfEmitBurden ((3U << 16U) + 13U)
#define sfHookInstructionCount ((3U << 16U) + 17U)
#define sfHookReturnCode ((3U << 16U) + 18U)
#define sfReferenceCount ((3U << 16U) + 19U)
#define sfRewardAccumulator ((3U << 16U) + 100U)
#define sfEmailHash ((4U << 16U) + 1U)
#define sfTakerPaysCurrency ((10U << 16U) + 1U)
#define sfTakerPaysIssuer ((10U << 16U) + 2U)
#define sfTakerGetsCurrency ((10U << 16U) + 3U)
#define sfTakerGetsIssuer ((10U << 16U) + 4U)
#define sfLedgerHash ((5U << 16U) + 1U)
#define sfParentHash ((5U << 16U) + 2U)
#define sfTransactionHash ((5U << 16U) + 3U)
#define sfAccountHash ((5U << 16U) + 4U)
#define sfPreviousTxnID ((5U << 16U) + 5U)
#define sfLedgerIndex ((5U << 16U) + 6U)
#define sfWalletLocator ((5U << 16U) + 7U)
#define sfRootIndex ((5U << 16U) + 8U)
#define sfAccountTxnID ((5U << 16U) + 9U)
#define sfNFTokenID ((5U << 16U) + 10U)
#define sfEmitParentTxnID ((5U << 16U) + 11U)
#define sfEmitNonce ((5U << 16U) + 12U)
#define sfEmitHookHash ((5U << 16U) + 13U)
#define sfBookDirectory ((5U << 16U) + 16U)
#define sfInvoiceID ((5U << 16U) + 17U)
#define sfNickname ((5U << 16U) + 18U)
#define sfAmendment ((5U << 16U) + 19U)
#define sfHookOn ((5U << 16U) + 20U)
#define sfDigest ((5U << 16U) + 21U)
#define sfChannel ((5U << 16U) + 22U)
#define sfConsensusHash ((5U << 16U) + 23U)
#define sfCheckID ((5U << 16U) + 24U)
#define sfValidatedHash ((5U << 16U) + 25U)
#define sfPreviousPageMin ((5U << 16U) + 26U)
#define sfNextPageMin ((5U << 16U) + 27U)
#define sfNFTokenBuyOffer ((5U << 16U) + 28U)
#define sfNFTokenSellOffer ((5U << 16U) + 29U)
#define sfHookStateKey ((5U << 16U) + 30U)
#define sfHookHash ((5U << 16U) + 31U)
#define sfHookNamespace ((5U << 16U) + 32U)
#define sfHookSetTxnID ((5U << 16U) + 33U)
#define sfOfferID ((5U << 16U) + 34U)
#define sfEscrowID ((5U << 16U) + 35U)
#define sfURITokenID ((5U << 16U) + 36U)
#define sfAmount ((6U << 16U) + 1U)
#define sfBalance ((6U << 16U) + 2U)
#define sfLimitAmount ((6U << 16U) + 3U)
#define sfTakerPays ((6U << 16U) + 4U)
#define sfTakerGets ((6U << 16U) + 5U)
#define sfLowLimit ((6U << 16U) + 6U)
#define sfHighLimit ((6U << 16U) + 7U)
#define sfFee ((6U << 16U) + 8U)
#define sfSendMax ((6U << 16U) + 9U)
#define sfDeliverMin ((6U << 16U) + 10U)
#define sfMinimumOffer ((6U << 16U) + 16U)
#define sfRippleEscrow ((6U << 16U) + 17U)
#define sfDeliveredAmount ((6U << 16U) + 18U)
#define sfNFTokenBrokerFee ((6U << 16U) + 19U)
#define sfHookCallbackFee ((6U << 16U) + 20U)
#define sfLockedBalance ((6U << 16U) + 21U)
#define sfPublicKey ((7U << 16U) + 1U)
#define sfMessageKey ((7U << 16U) + 2U)
#define sfSigningPubKey ((7U << 16U) + 3U)
#define sfTxnSignature ((7U << 16U) + 4U)
#define sfURI ((7U << 16U) + 5U)
#define sfSignature ((7U << 16U) + 6U)
#define sfDomain ((7U << 16U) + 7U)
#define sfFundCode ((7U << 16U) + 8U)
#define sfRemoveCode ((7U << 16U) + 9U)
#define sfExpireCode ((7U << 16U) + 10U)
#define sfCreateCode ((7U << 16U) + 11U)
#define sfMemoType ((7U << 16U) + 12U)
#define sfMemoData ((7U << 16U) + 13U)
#define sfMemoFormat ((7U << 16U) + 14U)
#define sfFulfillment ((7U << 16U) + 16U)
#define sfCondition ((7U << 16U) + 17U)
#define sfMasterSignature ((7U << 16U) + 18U)
#define sfUNLModifyValidator ((7U << 16U) + 19U)
#define sfValidatorToDisable ((7U << 16U) + 20U)
#define sfValidatorToReEnable ((7U << 16U) + 21U)
#define sfHookStateData ((7U << 16U) + 22U)
#define sfHookReturnString ((7U << 16U) + 23U)
#define sfHookParameterName ((7U << 16U) + 24U)
#define sfHookParameterValue ((7U << 16U) + 25U)
#define sfBlob ((7U << 16U) + 26U)
#define sfAccount ((8U << 16U) + 1U)
#define sfOwner ((8U << 16U) + 2U)
#define sfDestination ((8U << 16U) + 3U)
#define sfIssuer ((8U << 16U) + 4U)
#define sfAuthorize ((8U << 16U) + 5U)
#define sfUnauthorize ((8U << 16U) + 6U)
#define sfRegularKey ((8U << 16U) + 8U)
#define sfNFTokenMinter ((8U << 16U) + 9U)
#define sfEmitCallback ((8U << 16U) + 10U)
#define sfHookAccount ((8U << 16U) + 16U)
#define sfIndexes ((19U << 16U) + 1U)
#define sfHashes ((19U << 16U) + 2U)
#define sfAmendments ((19U << 16U) + 3U)
#define sfNFTokenOffers ((19U << 16U) + 4U)
#define sfHookNamespaces ((19U << 16U) + 5U)
#define sfPaths ((18U << 16U) + 1U)
#define sfTransactionMetaData ((14U << 16U) + 2U)
#define sfCreatedNode ((14U << 16U) + 3U)
#define sfDeletedNode ((14U << 16U) + 4U)
#define sfModifiedNode ((14U << 16U) + 5U)
#define sfPreviousFields ((14U << 16U) + 6U)
#define sfFinalFields ((14U << 16U) + 7U)
#define sfNewFields ((14U << 16U) + 8U)
#define sfTemplateEntry ((14U << 16U) + 9U)
#define sfMemo ((14U << 16U) + 10U)
#define sfSignerEntry ((14U << 16U) + 11U)
#define sfNFToken ((14U << 16U) + 12U)
#define sfEmitDetails ((14U << 16U) + 13U)
#define sfHook ((14U << 16U) + 14U)
#define sfSigner ((14U << 16U) + 16U)
#define sfMajority ((14U << 16U) + 18U)
#define sfDisabledValidator ((14U << 16U) + 19U)
#define sfEmittedTxn ((14U << 16U) + 20U)
#define sfHookExecution ((14U << 16U) + 21U)
#define sfHookDefinition ((14U << 16U) + 22U)
#define sfHookParameter ((14U << 16U) + 23U)
#define sfHookGrant ((14U << 16U) + 24U)
#define sfSigners ((15U << 16U) + 3U)
#define sfSignerEntries ((15U << 16U) + 4U)
#define sfTemplate ((15U << 16U) + 5U)
#define sfNecessary ((15U << 16U) + 6U)
#define sfSufficient ((15U << 16U) + 7U)
#define sfAffectedNodes ((15U << 16U) + 8U)
#define sfMemos ((15U << 16U) + 9U)
#define sfNFTokens ((15U << 16U) + 10U)
#define sfHooks ((15U << 16U) + 11U)
#define sfMajorities ((15U << 16U) + 16U)
#define sfDisabledValidators ((15U << 16U) + 17U)
#define sfHookExecutions ((15U << 16U) + 18U)
#define sfHookParameters ((15U << 16U) + 19U)
#define sfHookGrants ((15U << 16U) + 20U)
#define sfActiveValidators ((15U << 16U) + 95U)
+239
View File
@@ -0,0 +1,239 @@
#include <stdint.h>
// 8 byte-int = 1 bytes
#define SFL_CLOSERESOLUTION 1
#define SFL_METHOD 1
#define SFL_TRANSACTIONRESULT 1
#define SFL_TICKSIZE 1
#define SFL_UNLMODIFYDISABLING 1
#define SFL_HOOKRESULT 1
// 16 byte-int = 2 bytes
#define SFL_LEDGERENTRYTYPE 2
#define SFL_TRANSACTIONTYPE 2
#define SFL_SIGNERWEIGHT 2
#define SFL_TRANSFERFEE 2
#define SFL_VERSION 2
#define SFL_HOOKSTATECHANGECOUNT 2
#define SFL_HOOKEMITCOUNT 2
#define SFL_HOOKEXECUTIONINDEX 2
#define SFL_HOOKAPIVERSION 2
// 32 byte-int = 4 bytes
#define SFL_NETWORKID 4
#define SFL_FLAGS 4
#define SFL_SOURCETAG 4
#define SFL_SEQUENCE 4
#define SFL_PREVIOUSTXNLGRSEQ 4
#define SFL_LEDGERSEQUENCE 4
#define SFL_CLOSETIME 4
#define SFL_PARENTCLOSETIME 4
#define SFL_SIGNINGTIME 4
#define SFL_EXPIRATION 4
#define SFL_TRANSFERRATE 4
#define SFL_WALLETSIZE 4
#define SFL_OWNERCOUNT 4
#define SFL_DESTINATIONTAG 4
#define SFL_HIGHQUALITYIN 4
#define SFL_HIGHQUALITYOUT 4
#define SFL_LOWQUALITYIN 4
#define SFL_LOWQUALITYOUT 4
#define SFL_QUALITYIN 4
#define SFL_QUALITYOUT 4
#define SFL_STAMPESCROW 4
#define SFL_BONDAMOUNT 4
#define SFL_LOADFEE 4
#define SFL_OFFERSEQUENCE 4
#define SFL_FIRSTLEDGERSEQUENCE 4
#define SFL_LASTLEDGERSEQUENCE 4
#define SFL_TRANSACTIONINDEX 4
#define SFL_OPERATIONLIMIT 4
#define SFL_REFERENCEFEEUNITS 4
#define SFL_RESERVEBASE 4
#define SFL_RESERVEINCREMENT 4
#define SFL_SETFLAG 4
#define SFL_CLEARFLAG 4
#define SFL_SIGNERQUORUM 4
#define SFL_CANCELAFTER 4
#define SFL_FINISHAFTER 4
#define SFL_SIGNERLISTID 4
#define SFL_SETTLEDELAY 4
#define SFL_TICKETCOUNT 4
#define SFL_TICKETSEQUENCE 4
#define SFL_NFTOKENTAXON 4
#define SFL_MINTEDNFTOKENS 4
#define SFL_BURNEDNFTOKENS 4
#define SFL_HOOKSTATECOUNT 4
#define SFL_EMITGENERATION 4
#define SFL_LOCKCOUNT 4
#define SFL_REWARDTIME 4
#define SFL_REWARDLGRFIRST 4
#define SFL_REWARDLGRLAST 4
#define SFL_FIRSTNFTOKENSEQUENCE 4
// 64 byte-int = 8 bytes
#define SFL_INDEX_NEXT 8
#define SFL_INDEX_PREVIOUS 8
#define SFL_BOOK_NODE 8
#define SFL_OWNER_NODE 8
#define SFL_BASE_FEE 8
#define SFL_EXCHANGE_RATE 8
#define SFL_LOW_NODE 8
#define SFL_HIGH_NODE 8
#define SFL_DESTINATION_NODE 8
#define SFL_COOKIE 8
#define SFL_SERVER_VERSION 8
#define SFL_EMIT_BURDEN 8
#define SFL_NFTOKEN_OFFER_NODE 8
#define SFL_HOOK_INSTRUCTION_COUNT 8
#define SFL_HOOK_RETURN_CODE 8
#define SFL_REFERENCE_COUNT 8
#define SFL_REWARD_ACCUMULATOR 8
// 128 byte-int = 4 bytes
#define SFL_EMAIL_HASH 128
// 160 byte-int = 4 bytes
#define SFL_TAKER_PAYS_CURRENCY 160
#define SFL_TAKER_PAYS_ISSUER 160
#define SFL_TAKER_GETS_CURRENCY 160
#define SFL_TAKER_GETS_ISSUER 160
// 256 byte-int = ??? bytes
#define SFL_LEDGER_HASH 256
#define SFL_PARENT_HASH 256
#define SFL_TRANSACTION_HASH 256
#define SFL_ACCOUNT_HASH 256
#define SFL_HOOK_ON 256
#define SFL_PREVIOUS_TXN_ID 256
#define SFL_LEDGER_INDEX 256
#define SFL_WALLET_LOCATOR 256
#define SFL_ROOT_INDEX 256
#define SFL_ACCOUNT_TXN_ID 256
#define SFL_NFTOKEN_ID 256
#define SFL_EMIT_PARENT_TXN_ID 256
#define SFL_EMIT_NONCE 256
#define SFL_EMIT_HOOK_HASH 256
// 256 byte-int = ??? bytes
#define SFL_BOOK_DIRECTORY 256
#define SFL_INVOICE_ID 256
#define SFL_NICKNAME 256
#define SFL_AMENDMENT 256
#define SFL_DIGEST 256
#define SFL_CHANNEL 256
#define SFL_CONSENSUS_HASH 256
#define SFL_CHECK_ID 256
#define SFL_VALIDATED_HASH 256
#define SFL_PREVIOUS_PAGE_MIN 256
#define SFL_NEXT_PAGE_MIN 256
#define SFL_NFTOKEN_BUY_OFFER 256
#define SFL_NFTOKEN_SELL_OFFER 256
#define SFL_HOOK_STATE_KEY 256
#define SFL_HOOK_HASH 256
#define SFL_HOOK_NAMESPACE 256
#define SFL_HOOK_SET_TXN_ID 256
#define SFL_OFFER_ID 256
#define SFL_ESCROW_ID 256
#define SFL_URITOKEN_ID 256
// 20 bytes
#define SFL_AMOUNT 20
#define SFL_BALANCE 20
#define SFL_LIMIT_AMOUNT 20
#define SFL_TAKER_PAYS 20
#define SFL_TAKER_GETS 20
#define SFL_LOW_LIMIT 20
#define SFL_HIGH_LIMIT 20
#define SFL_FEE 20
#define SFL_SEND_MAX 20
#define SFL_DELIVER_MIN 20
#define SFL_LOCKED_BALANCE 20
// Unimplemented
#define SFL_AMOUNT_MINIMUM_OFFER 8
#define SFL_AMOUNT_RIPPLE_ESCROW 8
#define SFL_AMOUNT_DELIVERED_AMOUNT 8
#define SFL_AMOUNT_NFTOKEN_BROKER_FEE 8
#define SFL_AMOUNT_HOOK_CALLBACK_FEE 8
#define SFL_AMOUNT_BASE_FEE_DROPS 8
#define SFL_AMOUNT_RESERVE_BASE_DROPS 8
#define SFL_AMOUNT_RESERVE_INCREMENT_DROPS 8
// Unimplemented
#define SFL_VL_PUBLIC_KEY 64
#define SFL_VL_MESSAGE_KEY 64
#define SFL_VL_SIGNING_PUB_KEY 64
// Unimplemented
#define SFL_VL_TXN_SIGNATURE 96
// Unimplemented
#define SFL_VL_URI 256
// Unimplemented
#define SFL_VL_SIGNATURE 96
// Unimplemented
#define SFL_VL_DOMAIN 256
#define SFL_VL_FUND_CODE 256
#define SFL_VL_REMOVE_CODE 256
#define SFL_VL_EXPIRE_CODE 256
#define SFL_VL_CREATE_CODE 256
#define SFL_VL_MEMO_TYPE 256
#define SFL_VL_MEMO_DATA 256
#define SFL_VL_MEMO_FORMAT 256
#define SFL_VL_FULFILLMENT 256
#define SFL_VL_CONDITION 256
// Unimplemented
#define SFL_VL_MASTER_SIGNATURE 96
// Unimplemented
#define SFL_VL_UNL_MODIFY_VALIDATOR 256
#define SFL_VL_VALIDATOR_TO_DISABLE 256
#define SFL_VL_VALIDATOR_TO_RE_ENABLE 256
#define SFL_VL_HOOK_STATE_DATA 256
#define SFL_VL_HOOK_RETURN_STRING 256
#define SFL_VL_HOOK_PARAMETER_NAME 256
#define SFL_VL_HOOK_PARAMETER_VALUE 256
#define SFL_VL_BLOB 256
// 20 bytes
#define SFL_ACCOUNT 20
#define SFL_OWNER 20
#define SFL_DESTINATION 20
#define SFL_ISSUER 20
#define SFL_AUTHORIZE 20
#define SFL_UNAUTHORIZE 20
#define SFL_REGULAR_KEY 20
#define SFL_NFTOKEN_MINTER 20
#define SFL_EMIT_CALLBACK 20
#define SFL_HOOK_ACCOUNT 20
#define SFL_NFTOKEN_MINTER 20
// Unimplemented
#define SFL_PATHS 1
// Unimplemented
#define SFL_VECTOR256_INDEXES 32
#define SFL_VECTOR256_HASHES 32
#define SFL_VECTOR256_AMENDMENTS 32
#define SFL_VECTOR256_NFTOKEN_OFFERS 32
#define SFL_VECTOR256_HOOK_NAMESPACES 32
// Unimplemented
#define SFL_TRANSACTION_META_DATA 1
#define SFL_CREATED_NODE 1
#define SFL_DELETED_NODE 1
#define SFL_MODIFIED_NODE 1
#define SFL_PREVIOUS_FIELDS 1
#define SFL_FINAL_FIELDS 1
#define SFL_NEW_FIELDS 1
#define SFL_TEMPLATE_ENTRY 1
#define SFL_MEMO 1
#define SFL_SIGNER_ENTRY 1
#define SFL_NFTOKEN 1
#define SFL_EMIT_DETAILS 1
#define SFL_HOOK 1
#define SFL_SIGNER 1
#define SFL_MAJORITY 1
#define SFL_DISABLED_VALIDATOR 1
#define SFL_EMITTED_TXN 1
#define SFL_HOOK_EXECUTION 1
#define SFL_HOOK_DEFINITION 1
#define SFL_HOOK_PARAMETER 1
#define SFL_HOOK_GRANT 1
#define SFL_SIGNERS 1
#define SFL_SIGNER_ENTRIES 1
#define SFL_TEMPLATE 1
#define SFL_NECESSARY 1
#define SFL_SUFFICIENT 1
#define SFL_AFFECTED_NODES 1
#define SFL_MEMOS 1
#define SFL_NFTOKENS 1
#define SFL_HOOKS 1
#define SFL_MAJORITIES 1
#define SFL_DISABLED_VALIDATORS 1
#define SFL_HOOK_EXECUTIONS 1
#define SFL_HOOK_EXECUTION 1
+5 -5
View File
@@ -1,9 +1,9 @@
all: reward govern mint
accept:
wasmcc accept.c -o accept.wasm -Oz -Wl,--allow-undefined -I../
wasmcc accept.c -o accept.wasm -Oz -Wl,--allow-undefined -I./headers
hook-cleaner accept.wasm
reward:
wasmcc reward.c -o reward.wasm -Oz -Wl,--allow-undefined -I../
wasmcc reward.c -o reward.wasm -Oz -Wl,--allow-undefined -I./headers
wasm-opt reward.wasm -o reward.wasm \
--shrink-level=100000000 \
--coalesce-locals-learning \
@@ -58,7 +58,7 @@ reward:
hook-cleaner reward.wasm
guard_checker reward.wasm
govern:
wasmcc govern.c -o govern.wasm -Oz -Wl,--allow-undefined -I../
wasmcc govern.c -o govern.wasm -Oz -Wl,--allow-undefined -I./headers
wasm-opt govern.wasm -o govern.wasm \
--shrink-level=100000000 \
--coalesce-locals-learning \
@@ -113,7 +113,7 @@ govern:
hook-cleaner govern.wasm
guard_checker govern.wasm
mint:
wasmcc mint.c -o mint.wasm -Oz -Wl,--allow-undefined -I../
wasmcc mint.c -o mint.wasm -Oz -Wl,--allow-undefined -I./headers
wasm-opt mint.wasm -o mint.wasm \
--shrink-level=100000000 \
--coalesce-locals-learning \
@@ -142,5 +142,5 @@ mint:
hook-cleaner mint.wasm
guard_checker mint.wasm
nftoken:
wasmcc nftoken.c -o nftoken.wasm -Oz -Wl,--allow-undefined -I../
wasmcc nftoken.c -o nftoken.wasm -Oz -Wl,--allow-undefined -I./headers
hook-cleaner nftoken.wasm
+3
View File
@@ -49,4 +49,7 @@
#include "macro.h"
#include "tts.h"
#include "ls_flags.h"
#include "tx_flags.h"
#endif
+75
View File
@@ -0,0 +1,75 @@
// Generated using generate_lsflags.sh
#ifndef HOOKLSFLAGS_INCLUDED
#define HOOKLSFLAGS_INCLUDED 1
enum ltACCOUNT_ROOT {
lsfPasswordSpent = 0x00010000,
lsfRequireDestTag = 0x00020000,
lsfRequireAuth = 0x00040000,
lsfDisallowXRP = 0x00080000,
lsfDisableMaster = 0x00100000,
lsfNoFreeze = 0x00200000,
lsfGlobalFreeze = 0x00400000,
lsfDefaultRipple = 0x00800000,
lsfDepositAuth = 0x01000000,
lsfTshCollect = 0x02000000,
lsfDisallowIncomingNFTokenOffer = 0x04000000,
lsfDisallowIncomingCheck = 0x08000000,
lsfDisallowIncomingPayChan = 0x10000000,
lsfDisallowIncomingTrustline = 0x20000000,
lsfURITokenIssuer = 0x40000000,
lsfDisallowIncomingRemit = 0x80000000,
lsfAllowTrustLineClawback = 0x00001000,
};
enum ltOFFER {
lsfPassive = 0x00010000,
lsfSell = 0x00020000,
};
enum ltRIPPLE_STATE {
lsfLowReserve = 0x00010000,
lsfHighReserve = 0x00020000,
lsfLowAuth = 0x00040000,
lsfHighAuth = 0x00080000,
lsfLowNoRipple = 0x00100000,
lsfHighNoRipple = 0x00200000,
lsfLowFreeze = 0x00400000,
lsfHighFreeze = 0x00800000,
lsfLowDeepFreeze = 0x02000000,
lsfHighDeepFreeze = 0x04000000,
lsfAMMNode = 0x01000000,
};
enum ltSIGNER_LIST {
lsfOneOwnerCount = 0x00010000,
};
enum ltDIR_NODE {
lsfNFTokenBuyOffers = 0x00000001,
lsfNFTokenSellOffers = 0x00000002,
lsfEmittedDir = 0x00000004,
};
enum ltNFTOKEN_OFFER {
lsfSellNFToken = 0x00000001,
};
enum ltURI_TOKEN {
lsfBurnable = 0x00000001,
};
enum remarks {
lsfImmutable = 1,
};
enum ltMPTOKEN_ISSUANCE {
lsfMPTLocked = 0x00000001,
lsfMPTCanLock = 0x00000002,
lsfMPTRequireAuth = 0x00000004,
lsfMPTCanEscrow = 0x00000008,
lsfMPTCanTrade = 0x00000010,
lsfMPTCanTransfer = 0x00000020,
lsfMPTCanClawback = 0x00000040,
};
enum ltMPTOKEN {
lsfMPTAuthorized = 0x00000002,
};
enum ltCREDENTIAL {
lsfAccepted = 0x00010000,
};
#endif // HOOKLSFLAGS_INCLUDED
+7 -1
View File
@@ -152,6 +152,7 @@
#define sfEscrowID ((5U << 16U) + 35U)
#define sfURITokenID ((5U << 16U) + 36U)
#define sfDomainID ((5U << 16U) + 37U)
#define sfManifestID ((5U << 16U) + 91U)
#define sfHookOnOutgoing ((5U << 16U) + 93U)
#define sfHookOnIncoming ((5U << 16U) + 94U)
#define sfCron ((5U << 16U) + 95U)
@@ -189,6 +190,7 @@
#define sfSignatureReward ((6U << 16U) + 29U)
#define sfMinAccountCreateAmount ((6U << 16U) + 30U)
#define sfLPTokenBalance ((6U << 16U) + 31U)
#define sfTrustLineRewardAccumulator ((6U << 16U) + 99U)
#define sfPublicKey ((7U << 16U) + 1U)
#define sfMessageKey ((7U << 16U) + 2U)
#define sfSigningPubKey ((7U << 16U) + 3U)
@@ -220,6 +222,7 @@
#define sfProvider ((7U << 16U) + 30U)
#define sfMPTokenMetadata ((7U << 16U) + 31U)
#define sfCredentialType ((7U << 16U) + 32U)
#define sfHookName ((7U << 16U) + 97U)
#define sfRemarkValue ((7U << 16U) + 98U)
#define sfRemarkName ((7U << 16U) + 99U)
#define sfAccount ((8U << 16U) + 1U)
@@ -255,6 +258,7 @@
#define sfIssuingChainIssue ((24U << 16U) + 2U)
#define sfAsset ((24U << 16U) + 3U)
#define sfAsset2 ((24U << 16U) + 4U)
#define sfClaimCurrency ((24U << 16U) + 5U)
#define sfXChainBridge ((25U << 16U) + 1U)
#define sfTransactionMetaData ((14U << 16U) + 2U)
#define sfCreatedNode ((14U << 16U) + 3U)
@@ -274,7 +278,6 @@
#define sfDisabledValidator ((14U << 16U) + 19U)
#define sfEmittedTxn ((14U << 16U) + 20U)
#define sfHookExecution ((14U << 16U) + 21U)
#define sfHookDefinition ((14U << 16U) + 22U)
#define sfHookParameter ((14U << 16U) + 23U)
#define sfHookGrant ((14U << 16U) + 24U)
#define sfVoteEntry ((14U << 16U) + 25U)
@@ -286,6 +289,7 @@
#define sfXChainCreateAccountAttestationCollectionElement ((14U << 16U) + 31U)
#define sfPriceData ((14U << 16U) + 32U)
#define sfCredential ((14U << 16U) + 33U)
#define sfManifest ((14U << 16U) + 90U)
#define sfAmountEntry ((14U << 16U) + 91U)
#define sfMintURIToken ((14U << 16U) + 92U)
#define sfHookEmission ((14U << 16U) + 93U)
@@ -293,6 +297,8 @@
#define sfActiveValidator ((14U << 16U) + 95U)
#define sfGenesisMint ((14U << 16U) + 96U)
#define sfRemark ((14U << 16U) + 97U)
#define sfHighReward ((14U << 16U) + 98U)
#define sfLowReward ((14U << 16U) + 99U)
#define sfSigners ((15U << 16U) + 3U)
#define sfSignerEntries ((15U << 16U) + 4U)
#define sfTemplate ((15U << 16U) + 5U)
+1
View File
@@ -61,6 +61,7 @@
#define ttNFTOKEN_MODIFY 70
#define ttPERMISSIONED_DOMAIN_SET 71
#define ttPERMISSIONED_DOMAIN_DELETE 72
#define ttMANIFEST_SET 91
#define ttCRON 92
#define ttCRON_SET 93
#define ttREMARKS_SET 94
+117
View File
@@ -0,0 +1,117 @@
// Generated using generate_txflags.sh
#include "ls_flags.h"
#include <stdint.h>
enum UniversalFlags : uint32_t {
tfFullyCanonicalSig = 0x80000000,
};
enum AccountSetFlags : uint32_t {
tfRequireDestTag = 0x00010000,
tfOptionalDestTag = 0x00020000,
tfRequireAuth = 0x00040000,
tfOptionalAuth = 0x00080000,
tfDisallowXRP = 0x00100000,
tfAllowXRP = 0x00200000,
};
enum AccountFlags : uint32_t {
asfRequireDest = 1,
asfRequireAuth = 2,
asfDisallowXRP = 3,
asfDisableMaster = 4,
asfAccountTxnID = 5,
asfNoFreeze = 6,
asfGlobalFreeze = 7,
asfDefaultRipple = 8,
asfDepositAuth = 9,
asfAuthorizedNFTokenMinter = 10,
asfTshCollect = 11,
asfDisallowIncomingNFTokenOffer = 12,
asfDisallowIncomingCheck = 13,
asfDisallowIncomingPayChan = 14,
asfDisallowIncomingTrustline = 15,
asfDisallowIncomingRemit = 16,
asfAllowTrustLineClawback = 17,
};
enum OfferCreateFlags : uint32_t {
tfPassive = 0x00010000,
tfImmediateOrCancel = 0x00020000,
tfFillOrKill = 0x00040000,
tfSell = 0x00080000,
};
enum PaymentFlags : uint32_t {
tfNoRippleDirect = 0x00010000,
tfPartialPayment = 0x00020000,
tfLimitQuality = 0x00040000,
};
enum TrustSetFlags : uint32_t {
tfSetfAuth = 0x00010000,
tfSetNoRipple = 0x00020000,
tfClearNoRipple = 0x00040000,
tfSetFreeze = 0x00100000,
tfClearFreeze = 0x00200000,
tfSetDeepFreeze = 0x00400000,
tfClearDeepFreeze = 0x00800000
};
enum EnableAmendmentFlags : uint32_t {
tfGotMajority = 0x00010000,
tfLostMajority = 0x00020000,
tfTestSuite = 0x80000000,
};
enum PaymentChannelClaimFlags : uint32_t {
tfRenew = 0x00010000,
tfClose = 0x00020000,
};
enum NFTokenMintFlags : uint32_t {
tfBurnable = 0x00000001,
tfOnlyXRP = 0x00000002,
tfTrustLine = 0x00000004,
tfTransferable = 0x00000008,
tfMutable = 0x00000010,
tfStrongTSH = 0x00008000,
};
enum MPTokenIssuanceCreateFlags : uint32_t {
tfMPTCanLock = lsfMPTCanLock,
tfMPTRequireAuth = lsfMPTRequireAuth,
tfMPTCanEscrow = lsfMPTCanEscrow,
tfMPTCanTrade = lsfMPTCanTrade,
tfMPTCanTransfer = lsfMPTCanTransfer,
tfMPTCanClawback = lsfMPTCanClawback,
};
enum MPTokenAuthorizeFlags : uint32_t {
tfMPTUnauthorize = 0x00000001,
};
enum MPTokenIssuanceSetFlags : uint32_t {
tfMPTLock = 0x00000001,
tfMPTUnlock = 0x00000002,
};
enum NFTokenCreateOfferFlags : uint32_t {
tfSellNFToken = 0x00000001,
};
enum ClaimRewardFlags : uint32_t {
tfOptOut = 0x00000001,
};
enum CronSetFlags : uint32_t {
tfCronUnset = 0x00000001,
};
enum AMMClawbackFlags : uint32_t {
tfClawTwoAssets = 0x00000001,
};
enum BridgeModifyFlags : uint32_t {
tfClearAccountCreateAmount = 0x00010000,
};
+7 -3
View File
@@ -16,6 +16,7 @@
#define featureHooksUpdate1 "1"
#define featureHooksUpdate2 "1"
#define fix20250131 "1"
#define fixGuardDepth32 "1"
namespace hook_api {
struct Rules
{
@@ -319,7 +320,7 @@ namespace compare_mode {
enum compare_mode : uint32_t { EQUAL = 1, LESS = 2, GREATER = 4 };
}
enum hook_return_code : int64_t {
enum class hook_return_code : int64_t {
SUCCESS =
0, // return codes > 0 are reserved for hook apis to return "success"
OUT_OF_BOUNDS =
@@ -381,12 +382,12 @@ enum hook_return_code : int64_t {
INVALID_KEY = -41, // user supplied key was not valid
NOT_A_STRING = -42, // nul terminator missing from a string argument
MEM_OVERLAP = -43, // one or more specified buffers are the same memory
TOO_MANY_STATE_MODIFICATIONS = -44, // more than 5000 modified state
TOO_MANY_STATE_MODIFICATIONS = -44, // more than 256 modified state
// entires in the combined hook chains
TOO_MANY_NAMESPACES = -45
};
enum ExitType : uint8_t {
enum class ExitType : uint8_t {
UNSET = 0,
WASM_ERROR = 1,
ROLLBACK = 2,
@@ -443,6 +444,7 @@ getImportWhitelist(Rules const& rules)
enum GuardRulesVersion : uint64_t {
GuardRuleFix20250131 = 0x00000001,
GuardRuleDepth32 = 0x00000002,
};
inline uint64_t
@@ -451,6 +453,8 @@ getGuardRulesVersion(Rules const& rules)
uint64_t version = 0;
if (rules.enabled(fix20250131))
version |= GuardRuleFix20250131;
if (rules.enabled(fixGuardDepth32))
version |= GuardRuleDepth32;
return version;
}
+15 -6
View File
@@ -204,9 +204,13 @@ struct WasmBlkInf
}
// compute worst case execution time
inline uint64_t
compute_wce(const WasmBlkInf* blk, int level, bool* recursion_limit_reached)
compute_wce(
const WasmBlkInf* blk,
int level,
int max_level,
bool* recursion_limit_reached)
{
if (level > 16)
if (level > max_level)
{
*recursion_limit_reached = true;
return 0;
@@ -233,8 +237,8 @@ compute_wce(const WasmBlkInf* blk, int level, bool* recursion_limit_reached)
if (blk->children.size() > 0)
for (auto const& child : blk->children)
worst_case_execution +=
compute_wce(child, level + 1, recursion_limit_reached);
worst_case_execution += compute_wce(
child, level + 1, max_level, recursion_limit_reached);
if (parent == 0 ||
parent->iteration_bound ==
@@ -788,12 +792,17 @@ check_guard(
}
bool recursion_limit_reached = false;
uint64_t wce = compute_wce(&(*root), 0, &recursion_limit_reached);
int max_level = 16;
if (rulesVersion & hook_api::GuardRuleDepth32)
max_level = 32;
uint64_t wce =
compute_wce(&(*root), 0, max_level, &recursion_limit_reached);
if (recursion_limit_reached)
{
GUARDLOG(hook::log::NESTING_LIMIT)
<< "GuardCheck "
<< "Maximum allowable depth of blocks reached (16 levels). Flatten "
<< "Maximum allowable depth of blocks reached (" << max_level
<< " levels). Flatten "
"your loops and conditions!.\n";
return {};
}
+60 -49
View File
@@ -89,58 +89,69 @@
#define WASM_VAL_TYPE(T, b) CAT2(TYP_, T)
#define DECLARE_HOOK_FUNCTION(R, F, ...) \
R F(hook::HookContext& hookCtx, \
WasmEdge_CallingFrameContext const& frameCtx __VA_OPT__( \
COMMA __VA_ARGS__)); \
extern WasmEdge_Result WasmFunction##F( \
void* data_ptr, \
const WasmEdge_CallingFrameContext* frameCtx, \
const WasmEdge_Value* in, \
WasmEdge_Value* out); \
extern WasmEdge_ValType WasmFunctionParams##F[]; \
extern WasmEdge_ValType WasmFunctionResult##F[]; \
extern WasmEdge_FunctionTypeContext* WasmFunctionType##F; \
#define UNSIGNED_TYPE(T) std::make_unsigned_t<T>
#define DECLARE_HOOK_FUNCTION(R, F, ...) \
std::variant<UNSIGNED_TYPE(R), hook_api::hook_return_code> F( \
hook::HookContext& hookCtx, \
WasmEdge_CallingFrameContext const& frameCtx __VA_OPT__( \
COMMA __VA_ARGS__)); \
extern WasmEdge_Result WasmFunction##F( \
void* data_ptr, \
const WasmEdge_CallingFrameContext* frameCtx, \
const WasmEdge_Value* in, \
WasmEdge_Value* out); \
extern WasmEdge_ValType WasmFunctionParams##F[]; \
extern WasmEdge_ValType WasmFunctionResult##F[]; \
extern WasmEdge_FunctionTypeContext* WasmFunctionType##F; \
extern WasmEdge_String WasmFunctionName##F;
#define DEFINE_HOOK_FUNCTION(R, F, ...) \
WasmEdge_Result hook_api::WasmFunction##F( \
void* data_ptr, \
const WasmEdge_CallingFrameContext* frameCtx, \
const WasmEdge_Value* in, \
WasmEdge_Value* out) \
{ \
__VA_OPT__(int _stack = 0;) \
__VA_OPT__(FOR_VARS(VAR_ASSIGN, 2, __VA_ARGS__);) \
hook::HookContext* hookCtx = \
reinterpret_cast<hook::HookContext*>(data_ptr); \
R return_code = hook_api::F( \
*hookCtx, \
*const_cast<WasmEdge_CallingFrameContext*>(frameCtx) \
__VA_OPT__(COMMA STRIP_TYPES(__VA_ARGS__))); \
if (return_code == RC_ROLLBACK || return_code == RC_ACCEPT) \
return WasmEdge_Result_Terminate; \
out[0] = RET_ASSIGN(R, return_code); \
return WasmEdge_Result_Success; \
}; \
WasmEdge_ValType hook_api::WasmFunctionParams##F[] = { \
__VA_OPT__(FOR_VARS(WASM_VAL_TYPE, 0, __VA_ARGS__))}; \
WasmEdge_ValType hook_api::WasmFunctionResult##F[1] = { \
WASM_VAL_TYPE(R, dummy)}; \
WasmEdge_FunctionTypeContext* hook_api::WasmFunctionType##F = \
WasmEdge_FunctionTypeCreate( \
WasmFunctionParams##F, \
VA_NARGS(NULL __VA_OPT__(, __VA_ARGS__)), \
WasmFunctionResult##F, \
1); \
WasmEdge_String hook_api::WasmFunctionName##F = \
WasmEdge_StringCreateByCString(#F); \
R hook_api::F( \
hook::HookContext& hookCtx, \
WasmEdge_CallingFrameContext const& frameCtx __VA_OPT__( \
#define DEFINE_HOOK_FUNCTION(R, F, ...) \
WasmEdge_Result hook_api::WasmFunction##F( \
void* data_ptr, \
const WasmEdge_CallingFrameContext* frameCtx, \
const WasmEdge_Value* in, \
WasmEdge_Value* out) \
{ \
__VA_OPT__(int _stack = 0;) \
__VA_OPT__(FOR_VARS(VAR_ASSIGN, 2, __VA_ARGS__);) \
hook::HookContext* hookCtx = \
reinterpret_cast<hook::HookContext*>(data_ptr); \
auto const& return_code = hook_api::F( \
*hookCtx, \
*const_cast<WasmEdge_CallingFrameContext*>(frameCtx) \
__VA_OPT__(COMMA STRIP_TYPES(__VA_ARGS__))); \
if (std::holds_alternative<hook_api::hook_return_code>(return_code) && \
(std::get<hook_api::hook_return_code>(return_code) == \
RC_ROLLBACK || \
std::get<hook_api::hook_return_code>(return_code) == RC_ACCEPT)) \
return WasmEdge_Result_Terminate; \
out[0] = RET_ASSIGN( \
R, \
std::holds_alternative<UNSIGNED_TYPE(R)>(return_code) \
? std::get<UNSIGNED_TYPE(R)>(return_code) \
: R(std::get<hook_api::hook_return_code>(return_code))); \
return WasmEdge_Result_Success; \
}; \
WasmEdge_ValType hook_api::WasmFunctionParams##F[] = { \
__VA_OPT__(FOR_VARS(WASM_VAL_TYPE, 0, __VA_ARGS__))}; \
WasmEdge_ValType hook_api::WasmFunctionResult##F[1] = { \
WASM_VAL_TYPE(R, dummy)}; \
WasmEdge_FunctionTypeContext* hook_api::WasmFunctionType##F = \
WasmEdge_FunctionTypeCreate( \
WasmFunctionParams##F, \
VA_NARGS(NULL __VA_OPT__(, __VA_ARGS__)), \
WasmFunctionResult##F, \
1); \
WasmEdge_String hook_api::WasmFunctionName##F = \
WasmEdge_StringCreateByCString(#F); \
std::variant<UNSIGNED_TYPE(R), hook_api::hook_return_code> hook_api::F( \
hook::HookContext& hookCtx, \
WasmEdge_CallingFrameContext const& frameCtx __VA_OPT__( \
COMMA __VA_ARGS__))
#define HOOK_SETUP() \
using enum hook_api::hook_return_code; \
try \
{ \
[[maybe_unused]] ApplyContext& applyCtx = hookCtx.applyCtx; \
@@ -203,7 +214,7 @@
host_memory_ptr, \
guest_memory_length) \
{ \
int64_t bytes_written = 0; \
uint64_t bytes_written = 0; \
WRITE_WASM_MEMORY( \
bytes_written, \
guest_dst_ptr, \
@@ -272,7 +283,7 @@
data_ptr < (data_ptr_in)) \
return INTERNAL_ERROR; \
if (data_len == 0) \
return 0; \
return 0ULL; \
if ((write_ptr_in) == 0) \
return data_as_int64(data_ptr, data_len); \
if (data_len > (write_len_in)) \
+737 -731
View File
File diff suppressed because it is too large. Load diff
+56 -26
View File
@@ -33,35 +33,39 @@
*
* Steps required to add new features to the code:
*
* 1) In this file, increment `numFeatures` and add a uint256 declaration
* for the feature at the bottom
* 2) Add a uint256 definition for the feature to the corresponding source
* file (Feature.cpp). Use `registerFeature` to create the feature with
* the feature's name, `Supported::no`, and `VoteBehavior::DefaultNo`. This
* should be the only place the feature's name appears in code as a string.
* 3) Use the uint256 as the parameter to `view.rules.enabled()` to
* control flow into new code that this feature limits.
* 4) If the feature development is COMPLETE, and the feature is ready to be
* SUPPORTED, change the `registerFeature` parameter to Supported::yes.
* 5) When the feature is ready to be ENABLED, change the `registerFeature`
* parameter to `VoteBehavior::DefaultYes`.
* In general, any newly supported amendments (`Supported::yes`) should have
* a `VoteBehavior::DefaultNo` for at least one full release cycle. High
* priority bug fixes can be an exception to this rule of thumb.
* 1) Add the appropriate XRPL_FEATURE or XRPL_FIX macro definition for the
* feature to features.macro with the feature's name, `Supported::no`, and
* `VoteBehavior::DefaultNo`.
*
* 2) Use the generated variable name as the parameter to `view.rules.enabled()`
* to control flow into new code that this feature limits. (featureName or
* fixName)
*
* 3) If the feature development is COMPLETE, and the feature is ready to be
* SUPPORTED, change the macro parameter in features.macro to Supported::yes.
*
* 4) In general, any newly supported amendments (`Supported::yes`) should have
* a `VoteBehavior::DefaultNo` indefinitely so that external governance can
* make the decision on when to activate it. High priority bug fixes can be
* an exception to this rule. In such cases, ensure the fix has been
* clearly communicated to the community using appropriate channels,
* then change the macro parameter in features.macro to
* `VoteBehavior::DefaultYes`. The communication process is beyond
* the scope of these instructions.
*
*
* When a feature has been enabled for several years, the conditional code
* may be removed, and the feature "retired". To retire a feature:
* 1) Remove the uint256 declaration from this file.
* 2) MOVE the uint256 definition in Feature.cpp to the "retired features"
* section at the end of the file.
* 3) CHANGE the name of the variable to start with "retired".
* 4) CHANGE the parameters of the `registerFeature` call to `Supported::yes`
* and `VoteBehavior::DefaultNo`.
*
* 1) MOVE the macro definition in features.macro to the "retired features"
* section at the end of the file, and change the macro to XRPL_RETIRE.
*
* The feature must remain registered and supported indefinitely because it
* still exists in the ledger, but there is no need to vote for it because
* there's nothing to vote for. If it is removed completely from the code, any
* instances running that code will get amendment blocked. Removing the
* feature from the ledger is beyond the scope of these instructions.
* may exist in the Amendments object on ledger. There is no need to vote
* for it because there's nothing to vote for. If the feature definition is
* removed completely from the code, any instances running that code will get
* amendment blocked. Removing the feature from the ledger is beyond the scope
* of these instructions.
*
*/
@@ -76,11 +80,32 @@ allAmendments();
namespace detail {
#pragma push_macro("XRPL_FEATURE")
#undef XRPL_FEATURE
#pragma push_macro("XRPL_FIX")
#undef XRPL_FIX
#pragma push_macro("XRPL_RETIRE")
#undef XRPL_RETIRE
#define XRPL_FEATURE(name, supported, vote) +1
#define XRPL_FIX(name, supported, vote) +1
#define XRPL_RETIRE(name) +1
// This value SHOULD be equal to the number of amendments registered in
// Feature.cpp. Because it's only used to reserve storage, and determine how
// large to make the FeatureBitset, it MAY be larger. It MUST NOT be less than
// the actual number of amendments. A LogicError on startup will verify this.
static constexpr std::size_t numFeatures = 113;
static constexpr std::size_t numFeatures =
(0 +
#include <xrpl/protocol/detail/features.macro>
);
#undef XRPL_RETIRE
#pragma pop_macro("XRPL_RETIRE")
#undef XRPL_FIX
#pragma pop_macro("XRPL_FIX")
#undef XRPL_FEATURE
#pragma pop_macro("XRPL_FEATURE")
/** Amendments that this server supports and the default voting behavior.
Whether they are enabled depends on the Rules defined in the validated
@@ -320,12 +345,17 @@ foreachFeature(FeatureBitset bs, F&& f)
#undef XRPL_FEATURE
#pragma push_macro("XRPL_FIX")
#undef XRPL_FIX
#pragma push_macro("XRPL_RETIRE")
#undef XRPL_RETIRE
#define XRPL_FEATURE(name, supported, vote) extern uint256 const feature##name;
#define XRPL_FIX(name, supported, vote) extern uint256 const fix##name;
#define XRPL_RETIRE(name)
#include <xrpl/protocol/detail/features.macro>
#undef XRPL_RETIRE
#pragma pop_macro("XRPL_RETIRE")
#undef XRPL_FIX
#pragma pop_macro("XRPL_FIX")
#undef XRPL_FEATURE
+6
View File
@@ -97,6 +97,12 @@ public:
static IOUAmount
minPositiveAmount();
friend std::ostream&
operator<<(std::ostream& os, IOUAmount const& x)
{
return os << to_string(x);
}
};
inline IOUAmount::IOUAmount(beast::Zero)
+4
View File
@@ -372,6 +372,10 @@ permissionedDomain(AccountID const& account, std::uint32_t seq) noexcept;
Keylet
permissionedDomain(uint256 const& domainID) noexcept;
Keylet
manifest(PublicKey const& pk) noexcept;
} // namespace keylet
// Everything below is deprecated and should be removed in favor of keylets:
+3
View File
@@ -28,6 +28,9 @@
namespace ripple {
bool
isFeatureEnabled(uint256 const& feature);
class DigestAwareReadView;
/** Rules controlling protocol behavior. */
+3
View File
@@ -137,6 +137,7 @@ enum TEMcodes : TERUnderlyingType {
temXCHAIN_BRIDGE_NONDOOR_OWNER,
temXCHAIN_BRIDGE_BAD_MIN_ACCOUNT_CREATE_AMOUNT,
temXCHAIN_BRIDGE_BAD_REWARD_AMOUNT,
temXCHAIN_TOO_MANY_ATTESTATIONS, // RESERVED - not used
temHOOK_DATA_TOO_LARGE,
temEMPTY_DID,
@@ -193,6 +194,8 @@ enum TEFcodes : TERUnderlyingType {
tefNONDIR_EMIT,
tefIMPORT_BLACKHOLED,
tefINVALID_LEDGER_FIX_TYPE,
tefPAST_MANIFEST_SEQ,
tefREVOKED_MANIFEST,
};
//------------------------------------------------------------------------------
+1 -1
View File
@@ -132,7 +132,7 @@ constexpr std::uint32_t tfTrustSetMask =
tfClearFreeze | tfSetDeepFreeze | tfClearDeepFreeze);
// EnableAmendment flags:
enum EnableAmendmentFlags : std::uint32_t {
enum EnableAmendmentFlags : uint32_t {
tfGotMajority = 0x00010000,
tfLostMajority = 0x00020000,
tfTestSuite = 0x80000000,
+41 -11
View File
@@ -23,6 +23,9 @@
#if !defined(XRPL_FIX)
#error "undefined macro: XRPL_FIX"
#endif
#if !defined(XRPL_RETIRE)
#error "undefined macro: XRPL_RETIRE"
#endif
// clang-format off
@@ -31,6 +34,13 @@
// If you add an amendment here, then do not forget to increment `numFeatures`
// in include/xrpl/protocol/Feature.h.
XRPL_FEATURE(OnChainManifests, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (HookMap, Supported::yes, VoteBehavior::DefaultYes)
XRPL_FIX (GuardDepth32, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(NamedHooks, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(IOURewardClaim, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (IOULockedBalanceInvariant, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (ImportIssuer, Supported::yes, VoteBehavior::DefaultYes)
XRPL_FEATURE(HookAPISerializedType240, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(PermissionedDomains, Supported::no, VoteBehavior::DefaultNo)
XRPL_FEATURE(DynamicNFT, Supported::no, VoteBehavior::DefaultNo)
@@ -52,18 +62,18 @@ XRPL_FIX (FillOrKill, Supported::yes, VoteBehavior::DefaultYe
XRPL_FEATURE(DID, Supported::no, VoteBehavior::DefaultNo)
XRPL_FIX (DisallowIncomingV1, Supported::yes, VoteBehavior::DefaultYes)
XRPL_FEATURE(XChainBridge, Supported::no, VoteBehavior::DefaultNo)
XRPL_FEATURE(AMM, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(AMM, Supported::no, VoteBehavior::DefaultNo)
XRPL_FIX (ReducedOffersV1, Supported::yes, VoteBehavior::DefaultYes)
XRPL_FEATURE(HooksUpdate2, Supported::yes, VoteBehavior::DefaultNo);
XRPL_FEATURE(HookOnV2, Supported::yes, VoteBehavior::DefaultNo);
XRPL_FIX (HookAPI20251128, Supported::yes, VoteBehavior::DefaultYes);
XRPL_FIX (CronStacking, Supported::yes, VoteBehavior::DefaultYes);
XRPL_FEATURE(ExtendedHookState, Supported::yes, VoteBehavior::DefaultNo);
XRPL_FIX (InvalidTxFlags, Supported::yes, VoteBehavior::DefaultYes);
XRPL_FEATURE(Cron, Supported::yes, VoteBehavior::DefaultNo);
XRPL_FEATURE(IOUIssuerWeakTSH, Supported::yes, VoteBehavior::DefaultNo);
XRPL_FEATURE(DeepFreeze, Supported::yes, VoteBehavior::DefaultNo);
XRPL_FIX (ProvisionalDoubleThreading, Supported::yes, VoteBehavior::DefaultYes);
XRPL_FEATURE(HooksUpdate2, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(HookOnV2, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (HookAPI20251128, Supported::yes, VoteBehavior::DefaultYes)
XRPL_FIX (CronStacking, Supported::yes, VoteBehavior::DefaultYes)
XRPL_FEATURE(ExtendedHookState, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (InvalidTxFlags, Supported::yes, VoteBehavior::DefaultYes)
XRPL_FEATURE(Cron, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(IOUIssuerWeakTSH, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(DeepFreeze, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (ProvisionalDoubleThreading, Supported::yes, VoteBehavior::DefaultYes)
XRPL_FEATURE(Clawback, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (RewardClaimFlags, Supported::yes, VoteBehavior::DefaultYes)
XRPL_FEATURE(HookCanEmit, Supported::yes, VoteBehavior::DefaultNo)
@@ -147,4 +157,24 @@ XRPL_FIX (NFTokenDirV1, Supported::yes, VoteBehavior::Obsolete)
XRPL_FEATURE(NonFungibleTokensV1, Supported::yes, VoteBehavior::Obsolete)
XRPL_FEATURE(CryptoConditionsSuite, Supported::yes, VoteBehavior::Obsolete)
// The following amendments have been active for at least two years. Their
// pre-amendment code has been removed and the identifiers are deprecated.
// All known amendments and amendments that may appear in a validated
// ledger must be registered either here or above with the "active" amendments
XRPL_RETIRE(MultiSign)
XRPL_RETIRE(TrustSetAuth)
XRPL_RETIRE(FeeEscalation)
XRPL_RETIRE(PayChan)
XRPL_RETIRE(CryptoConditions)
XRPL_RETIRE(TickSize)
XRPL_RETIRE(fix1368)
XRPL_RETIRE(Escrow)
XRPL_RETIRE(fix1373)
XRPL_RETIRE(EnforceInvariants)
XRPL_RETIRE(SortedDirectories)
XRPL_RETIRE(fix1201)
XRPL_RETIRE(fix1512)
XRPL_RETIRE(fix1523)
XRPL_RETIRE(fix1528)
// clang-format on
@@ -93,7 +93,7 @@ LEDGER_ENTRY(ltCHECK, 0x0043, Check, check, ({
*/
LEDGER_ENTRY(ltHOOK_DEFINITION, 'D', HookDefinition, hook_definition, ({
{sfHookHash, soeREQUIRED},
{sfHookOn, soeREQUIRED},
{sfHookOn, soeOPTIONAL},
{sfHookOnIncoming, soeOPTIONAL},
{sfHookOnOutgoing, soeOPTIONAL},
{sfHookCanEmit, soeOPTIONAL},
@@ -262,6 +262,7 @@ LEDGER_ENTRY(ltACCOUNT_ROOT, 0x0061, AccountRoot, account, ({
{sfHookStateScale, soeOPTIONAL},
{sfCron, soeOPTIONAL},
{sfAMMID, soeOPTIONAL},
{sfManifestID, soeOPTIONAL},
}))
/** A ledger object which contains a list of object identifiers.
@@ -396,6 +397,8 @@ LEDGER_ENTRY(ltRIPPLE_STATE, 0x0072, RippleState, state, ({
{sfHighQualityOut, soeOPTIONAL},
{sfLockedBalance, soeOPTIONAL},
{sfLockCount, soeOPTIONAL},
{sfHighReward, soeOPTIONAL},
{sfLowReward, soeOPTIONAL},
}))
/** The ledger object which lists the network's fee settings.
@@ -590,6 +593,26 @@ LEDGER_ENTRY(ltDID, 0x008D, DID, did, ({
{sfPreviousTxnLgrSeq, soeREQUIRED},
}))
LEDGER_ENTRY(ltMANIFEST, 0x004D, Manifest, manifest_entry, ({
{sfAccount, soeREQUIRED},
{sfPublicKey, soeREQUIRED},
{sfSigningPubKey, soeOPTIONAL}, /* may be absent if the master is revoked */
{sfSequence, soeREQUIRED},
/* sfVersion defaults to 0 and is omitted from the signed payload when
absent. Storing it unconditionally would change the bytes and break
signature verification when the manifest is reconstructed. */
{sfVersion, soeOPTIONAL},
{sfDomain, soeOPTIONAL},
/* The signatures are mirrored so the object is a lossless copy of the
manifest and can be independently verified, and re-served to peers,
by any node reading it (see ManifestCache::applyLedger). */
{sfMasterSignature, soeREQUIRED},
{sfSignature, soeOPTIONAL}, /* absent if the master is revoked */
{sfManifestID, soeOPTIONAL}, /* pointer to the identical other copy on the other key */
{sfPreviousTxnID, soeREQUIRED},
{sfPreviousTxnLgrSeq, soeREQUIRED},
}))
#undef EXPAND
#undef LEDGER_ENTRY_DUPLICATE
+8 -1
View File
@@ -210,6 +210,7 @@ TYPED_SFIELD(sfOfferID, UINT256, 34)
TYPED_SFIELD(sfEscrowID, UINT256, 35)
TYPED_SFIELD(sfURITokenID, UINT256, 36)
TYPED_SFIELD(sfDomainID, UINT256, 37)
TYPED_SFIELD(sfManifestID, UINT256, 91)
TYPED_SFIELD(sfHookOnOutgoing, UINT256, 93)
TYPED_SFIELD(sfHookOnIncoming, UINT256, 94)
TYPED_SFIELD(sfCron, UINT256, 95)
@@ -257,6 +258,7 @@ TYPED_SFIELD(sfPrice, AMOUNT, 28)
TYPED_SFIELD(sfSignatureReward, AMOUNT, 29)
TYPED_SFIELD(sfMinAccountCreateAmount, AMOUNT, 30)
TYPED_SFIELD(sfLPTokenBalance, AMOUNT, 31)
TYPED_SFIELD(sfTrustLineRewardAccumulator,AMOUNT, 99)
// variable length (common)
TYPED_SFIELD(sfPublicKey, VL, 1)
@@ -292,6 +294,7 @@ TYPED_SFIELD(sfAssetClass, VL, 29)
TYPED_SFIELD(sfProvider, VL, 30)
TYPED_SFIELD(sfMPTokenMetadata, VL, 31)
TYPED_SFIELD(sfCredentialType, VL, 32)
TYPED_SFIELD(sfHookName, VL, 97)
TYPED_SFIELD(sfRemarkValue, VL, 98)
TYPED_SFIELD(sfRemarkName, VL, 99)
@@ -340,6 +343,7 @@ TYPED_SFIELD(sfLockingChainIssue, ISSUE, 1)
TYPED_SFIELD(sfIssuingChainIssue, ISSUE, 2)
TYPED_SFIELD(sfAsset, ISSUE, 3)
TYPED_SFIELD(sfAsset2, ISSUE, 4)
TYPED_SFIELD(sfClaimCurrency, ISSUE, 5)
// bridge
TYPED_SFIELD(sfXChainBridge, XCHAIN_BRIDGE, 1)
@@ -367,7 +371,7 @@ UNTYPED_SFIELD(sfMajority, OBJECT, 18)
UNTYPED_SFIELD(sfDisabledValidator, OBJECT, 19)
UNTYPED_SFIELD(sfEmittedTxn, OBJECT, 20)
UNTYPED_SFIELD(sfHookExecution, OBJECT, 21)
UNTYPED_SFIELD(sfHookDefinition, OBJECT, 22)
// 22 unused
UNTYPED_SFIELD(sfHookParameter, OBJECT, 23)
UNTYPED_SFIELD(sfHookGrant, OBJECT, 24)
UNTYPED_SFIELD(sfVoteEntry, OBJECT, 25)
@@ -379,6 +383,7 @@ UNTYPED_SFIELD(sfXChainClaimAttestationCollectionElement, OBJECT, 30)
UNTYPED_SFIELD(sfXChainCreateAccountAttestationCollectionElement, OBJECT, 31)
UNTYPED_SFIELD(sfPriceData, OBJECT, 32)
UNTYPED_SFIELD(sfCredential, OBJECT, 33)
UNTYPED_SFIELD(sfManifest, OBJECT, 90)
UNTYPED_SFIELD(sfAmountEntry, OBJECT, 91)
UNTYPED_SFIELD(sfMintURIToken, OBJECT, 92)
UNTYPED_SFIELD(sfHookEmission, OBJECT, 93)
@@ -386,6 +391,8 @@ UNTYPED_SFIELD(sfImportVLKey, OBJECT, 94)
UNTYPED_SFIELD(sfActiveValidator, OBJECT, 95)
UNTYPED_SFIELD(sfGenesisMint, OBJECT, 96)
UNTYPED_SFIELD(sfRemark, OBJECT, 97)
UNTYPED_SFIELD(sfHighReward, OBJECT, 98)
UNTYPED_SFIELD(sfLowReward, OBJECT, 99)
// array of objects (common)
// ARRAY/1 is reserved for end of array
@@ -500,6 +500,12 @@ TRANSACTION(ttPERMISSIONED_DOMAIN_DELETE, 72, PermissionedDomainDelete, ({
{sfDomainID, soeREQUIRED},
}))
/* sfAccount is supplied by TxFormats::commonFields; listing it here as well
makes the SOTemplate reject the format at construction. */
TRANSACTION(ttMANIFEST_SET, 91, SetManifest, ({
{sfManifest, soeREQUIRED},
}))
/* A pseudo-txn alarm signal for invoking a hook, emitted by validators after alarm set conditions are met */
TRANSACTION(ttCRON, 92, Cron, ({
{sfOwner, soeREQUIRED},
@@ -550,6 +556,7 @@ TRANSACTION(ttIMPORT, 97, Import, ({
* from a specified hook */
TRANSACTION(ttCLAIM_REWARD, 98, ClaimReward, ({
{sfIssuer, soeOPTIONAL},
{sfClaimCurrency, soeOPTIONAL},
}))
/** This transaction invokes a hook, providing arbitrary data. Essentially as a 0 drop payment. **/
+2
View File
@@ -76,6 +76,7 @@ JSS(Holder); // field.
JSS(HookApiVersion); // field
JSS(HookCanEmit); // field
JSS(HookHash); // field
JSS(HookName); // field
JSS(HookNamespace); // field
JSS(HookOn); // field
JSS(HookOnIncoming); // field
@@ -349,6 +350,7 @@ JSS(hash_mismatches); // out: catalogue
JSS(have_header); // out: InboundLedger
JSS(have_state); // out: InboundLedger
JSS(have_transactions); // out: InboundLedger
JSS(hooks); // in/out: AccountInfo
JSS(high); // out: BookChanges
JSS(highest_sequence); // out: AccountInfo
JSS(highest_ticket); // out: AccountInfo
+17 -17
View File
@@ -11,11 +11,11 @@ echo "START BUILDING (HOST)"
echo "Cleaning previously built binary"
rm -f release-build/xahaud
BUILD_CORES=$(echo "scale=0 ; `nproc` / 1.337" | bc)
BUILD_CORES=$(echo "scale=0 ; $(nproc) / 1.337" | bc)
if [[ "$GITHUB_REPOSITORY" == "" ]]; then
#Default
BUILD_CORES=${BUILD_CORES:-8}
BUILD_CORES=${BUILD_CORES:-8}
fi
# Ensure still works outside of GH Actions by setting these to /dev/null
@@ -31,21 +31,19 @@ echo "-- GITHUB_SHA: $GITHUB_SHA"
echo "-- GITHUB_RUN_NUMBER: $GITHUB_RUN_NUMBER"
echo "-- CONTAINER_NAME: $CONTAINER_NAME"
which docker 2> /dev/null 2> /dev/null
if [ "$?" -eq "1" ]
then
which docker 2>/dev/null 2>/dev/null
if [ "$?" -eq "1" ]; then
echo 'Docker not found. Install it first.'
exit 1
fi
stat .git 2> /dev/null 2> /dev/null
if [ "$?" -eq "1" ]
then
stat .git 2>/dev/null 2>/dev/null
if [ "$?" -eq "1" ]; then
echo 'Run this inside the source directory. (.git dir not found).'
exit 1
fi
STATIC_CONTAINER=$(docker ps -a | grep $CONTAINER_NAME |wc -l)
STATIC_CONTAINER=$(docker ps -a | grep $CONTAINER_NAME | wc -l)
CACHE_VOLUME_NAME="xahau-release-builder-cache"
@@ -57,13 +55,14 @@ if false; then
docker stop $CONTAINER_NAME
else
echo "No static container, build on temp container"
rm -rf release-build;
mkdir -p release-build;
rm -rf release-build
mkdir -p release-build
docker volume create $CACHE_VOLUME_NAME
# Create inline Dockerfile with environment setup for build-full.sh
DOCKERFILE_CONTENT=$(cat <<'DOCKERFILE_EOF'
DOCKERFILE_CONTENT=$(
cat <<'DOCKERFILE_EOF'
FROM ghcr.io/phusion/holy-build-box:4.0.1-amd64
ARG BUILD_CORES=8
@@ -196,6 +195,7 @@ ENV PATH=/usr/local/bin:$PATH
RUN /hbb_exe/activate-exec bash -c "ccache -M 100G && \
ccache -o cache_dir=/cache/ccache && \
ccache -o compiler_check=content && \
ccache -o direct_mode=true && \
mkdir -p ~/.conan2 /cache/conan2 /cache/conan2_download /cache/conan2_sources && \
echo 'core.cache:storage_path=/cache/conan2' > ~/.conan2/global.conf && \
echo 'core.download:download_cache=/cache/conan2_download' >> ~/.conan2/global.conf && \
@@ -217,7 +217,7 @@ RUN /hbb_exe/activate-exec bash -c "ccache -M 100G && \
ln -s ../../bin/ccache /usr/lib64/ccache/c++"
DOCKERFILE_EOF
)
)
# Build custom Docker image
IMAGE_NAME="xahaud-builder:latest"
@@ -227,14 +227,14 @@ DOCKERFILE_EOF
if [[ "$GITHUB_REPOSITORY" == "" ]]; then
# Non GH, local building
echo "Non-GH runner, local building, temp container"
docker run -i --user 0:$(id -g) --rm -v /data/builds:/data/builds -v `pwd`:/io -v "$CACHE_VOLUME_NAME":/cache --network host "$IMAGE_NAME" /hbb_exe/activate-exec bash -c "source /opt/rh/gcc-toolset-11/enable && bash -x /io/build-full.sh '$GITHUB_REPOSITORY' '$GITHUB_SHA' '$BUILD_CORES' '$GITHUB_RUN_NUMBER'"
docker run -i --user 0:$(id -g) --rm -v /data/builds:/data/builds -v $(pwd):/io -v "$CACHE_VOLUME_NAME":/cache --network host "$IMAGE_NAME" /hbb_exe/activate-exec bash -c "source /opt/rh/gcc-toolset-11/enable && bash -x /io/build-full.sh '$GITHUB_REPOSITORY' '$GITHUB_SHA' '$BUILD_CORES' '$GITHUB_RUN_NUMBER'"
else
# GH Action, runner
echo "GH Action, runner, clean & re-create create persistent container"
docker rm -f $CONTAINER_NAME
echo "echo 'Stopping container: $CONTAINER_NAME'" >> "$JOB_CLEANUP_SCRIPT"
echo "docker stop --time=15 \"$CONTAINER_NAME\" || echo 'Failed to stop container or container not running'" >> "$JOB_CLEANUP_SCRIPT"
docker run -di --user 0:$(id -g) --name $CONTAINER_NAME -v /data/builds:/data/builds -v `pwd`:/io -v "$CACHE_VOLUME_NAME":/cache --network host "$IMAGE_NAME" /hbb_exe/activate-exec bash
echo "echo 'Stopping container: $CONTAINER_NAME'" >>"$JOB_CLEANUP_SCRIPT"
echo "docker stop --time=15 \"$CONTAINER_NAME\" || echo 'Failed to stop container or container not running'" >>"$JOB_CLEANUP_SCRIPT"
docker run -di --user 0:$(id -g) --name $CONTAINER_NAME -v /data/builds:/data/builds -v $(pwd):/io -v "$CACHE_VOLUME_NAME":/cache --network host "$IMAGE_NAME" /hbb_exe/activate-exec bash
docker exec -i $CONTAINER_NAME /hbb_exe/activate-exec bash -c "source /opt/rh/gcc-toolset-11/enable && bash -x /io/build-full.sh '$GITHUB_REPOSITORY' '$GITHUB_SHA' '$BUILD_CORES' '$GITHUB_RUN_NUMBER'"
docker stop $CONTAINER_NAME
fi
+1 -1
View File
@@ -66,7 +66,7 @@ invalidAMMAsset(
Issue const& issue,
std::optional<std::pair<Issue, Issue>> const& pair)
{
if (badCurrency() == issue.currency)
if (isBadCurrency(issue.currency))
return temBAD_CURRENCY;
if (isXRP(issue) && issue.account.isNonZero())
return temBAD_ISSUER;
+8 -30
View File
@@ -250,12 +250,9 @@ FeatureCollections::registerFeature(
Feature const* i = getByName(name);
if (!i)
{
// If this check fails, and you just added a feature, increase the
// numFeatures value in Feature.h
check(
features.size() < detail::numFeatures,
"More features defined than allocated. Adjust numFeatures in "
"Feature.h.");
"More features defined than allocated.");
auto const f = sha512Half(Slice(name.data(), name.size()));
@@ -424,45 +421,26 @@ featureToName(uint256 const& f)
#undef XRPL_FEATURE
#pragma push_macro("XRPL_FIX")
#undef XRPL_FIX
#pragma push_macro("XRPL_RETIRE")
#undef XRPL_RETIRE
#define XRPL_FEATURE(name, supported, vote) \
uint256 const feature##name = registerFeature(#name, supported, vote);
#define XRPL_FIX(name, supported, vote) \
uint256 const fix##name = registerFeature("fix" #name, supported, vote);
#define XRPL_RETIRE(name) \
[[deprecated("The referenced amendment has been retired"), maybe_unused]] \
uint256 const retired##name = retireFeature(#name);
#include <xrpl/protocol/detail/features.macro>
#undef XRPL_RETIRE
#pragma pop_macro("XRPL_RETIRE")
#undef XRPL_FIX
#pragma pop_macro("XRPL_FIX")
#undef XRPL_FEATURE
#pragma pop_macro("XRPL_FEATURE")
// clang-format off
// The following amendments have been active for at least two years. Their
// pre-amendment code has been removed and the identifiers are deprecated.
// All known amendments and amendments that may appear in a validated
// ledger must be registered either here or above with the "active" amendments
[[deprecated("The referenced amendment has been retired"), maybe_unused]]
uint256 const
retiredMultiSign = retireFeature("MultiSign"),
retiredTrustSetAuth = retireFeature("TrustSetAuth"),
retiredFeeEscalation = retireFeature("FeeEscalation"),
retiredPayChan = retireFeature("PayChan"),
retiredCryptoConditions = retireFeature("CryptoConditions"),
retiredTickSize = retireFeature("TickSize"),
retiredFix1368 = retireFeature("fix1368"),
retiredEscrow = retireFeature("Escrow"),
retiredFix1373 = retireFeature("fix1373"),
retiredEnforceInvariants = retireFeature("EnforceInvariants"),
retiredSortedDirectories = retireFeature("SortedDirectories"),
retiredFix1201 = retireFeature("fix1201"),
retiredFix1512 = retireFeature("fix1512"),
retiredFix1523 = retireFeature("fix1523"),
retiredFix1528 = retireFeature("fix1528");
// clang-format on
// All of the features should now be registered, since variables in a cpp file
// are initialized from top to bottom.
//
+13 -4
View File
@@ -31,6 +31,8 @@
namespace ripple {
#define LEDGER_NAMESPACE2(value1, value2) (uint16_t(value1) << 8) | value2
/** Type-specific prefix for calculating ledger indices.
The identifier for a given object within the ledger is calculated based
@@ -80,14 +82,15 @@ enum class LedgerNameSpace : std::uint16_t {
UNL_REPORT = 'R',
CRON = 'L',
AMM = 'A',
BRIDGE = 'H',
BRIDGE = LEDGER_NAMESPACE2(0x01, 'H'),
XCHAIN_CLAIM_ID = 'Q',
XCHAIN_CREATE_ACCOUNT_CLAIM_ID = 'K',
DID = 'I',
ORACLE = 'R',
DID = LEDGER_NAMESPACE2(0x01, 'I'),
ORACLE = LEDGER_NAMESPACE2(0x01, 'R'),
MPTOKEN_ISSUANCE = '~',
MPTOKEN = 't',
CREDENTIAL = 'D',
MANIFEST = 'M',
CREDENTIAL = LEDGER_NAMESPACE2(0x01, 'D'),
PERMISSIONED_DOMAIN = 'm',
// No longer used or supported. Left here to reserve the space
@@ -668,6 +671,12 @@ permissionedDomain(uint256 const& domainID) noexcept
return {ltPERMISSIONED_DOMAIN, domainID};
}
Keylet
manifest(PublicKey const& pk) noexcept
{
return {ltMANIFEST, indexHash(LedgerNameSpace::MANIFEST, pk.slice())};
}
} // namespace keylet
} // namespace ripple
+19 -13
View File
@@ -87,19 +87,6 @@ InnerObjectFormats::InnerObjectFormats()
{sfEmittedTxnID, soeREQUIRED},
{sfEmitNonce, soeOPTIONAL}});
add(sfHookDefinition.jsonName,
sfHookDefinition.getCode(),
{{sfCreateCode, soeREQUIRED},
{sfHookNamespace, soeREQUIRED},
{sfHookParameters, soeREQUIRED},
{sfHookOn, soeOPTIONAL},
{sfHookOnIncoming, soeOPTIONAL},
{sfHookOnOutgoing, soeOPTIONAL},
{sfHookCanEmit, soeOPTIONAL},
{sfHookApiVersion, soeREQUIRED},
{sfFlags, soeREQUIRED},
{sfFee, soeREQUIRED}});
add(sfHook.jsonName,
sfHook.getCode(),
{{sfHookHash, soeOPTIONAL},
@@ -112,6 +99,7 @@ InnerObjectFormats::InnerObjectFormats()
{sfHookOnOutgoing, soeOPTIONAL},
{sfHookCanEmit, soeOPTIONAL},
{sfHookApiVersion, soeOPTIONAL},
{sfHookName, soeOPTIONAL},
{sfFlags, soeOPTIONAL}});
add(sfHookGrant.jsonName,
@@ -266,6 +254,24 @@ InnerObjectFormats::InnerObjectFormats()
{sfIssuer, soeREQUIRED},
{sfCredentialType, soeREQUIRED},
});
add(sfHighReward.jsonName,
sfHighReward.getCode(),
{
{sfRewardLgrFirst, soeREQUIRED},
{sfRewardLgrLast, soeREQUIRED},
{sfRewardTime, soeREQUIRED},
{sfTrustLineRewardAccumulator, soeREQUIRED},
});
add(sfLowReward.jsonName,
sfLowReward.getCode(),
{
{sfRewardLgrFirst, soeREQUIRED},
{sfRewardLgrLast, soeREQUIRED},
{sfRewardTime, soeREQUIRED},
{sfTrustLineRewardAccumulator, soeREQUIRED},
});
}
InnerObjectFormats const&
+1 -1
View File
@@ -111,7 +111,7 @@ issueFromJson(Json::Value const& v)
}
auto const currency = to_currency(curStr.asString());
if (currency == badCurrency() || currency == noCurrency())
if (isBadCurrency(currency) || currency == noCurrency())
{
Throw<Json::error>("issueFromJson currency must be a valid currency");
}
+8
View File
@@ -153,4 +153,12 @@ Rules::operator!=(Rules const& other) const
{
return !(*this == other);
}
bool
isFeatureEnabled(uint256 const& feature)
{
auto const& rules = getCurrentTransactionRules();
return rules && rules->enabled(feature);
}
} // namespace ripple
+1 -1
View File
@@ -103,7 +103,7 @@ currencyFromJson(SField const& name, Json::Value const& v)
}
auto const currency = to_currency(v.asString());
if (currency == badCurrency() || currency == noCurrency())
if (isBadCurrency(currency) || currency == noCurrency())
{
Throw<std::runtime_error>(
"currencyFromJson currency must be a valid currency");
+2
View File
@@ -151,6 +151,8 @@ transResults()
MAKE_ERROR(tefNONDIR_EMIT, "An emitted txn was injected into the ledger without a corresponding directory entry."),
MAKE_ERROR(tefIMPORT_BLACKHOLED, "Cannot import keying because target account is blackholed."),
MAKE_ERROR(tefINVALID_LEDGER_FIX_TYPE, "The LedgerFixType field has an invalid value."),
MAKE_ERROR(tefPAST_MANIFEST_SEQ, "The submitted manifest's sequence is not newer than the current."),
MAKE_ERROR(tefREVOKED_MANIFEST, "The submitted manifest is for a revoked master key."),
MAKE_ERROR(telLOCAL_ERROR, "Local failure."),
MAKE_ERROR(telBAD_DOMAIN, "Domain too long."),
+1
View File
@@ -48,6 +48,7 @@ TxFormats::TxFormats()
{sfFirstLedgerSequence, soeOPTIONAL},
{sfNetworkID, soeOPTIONAL},
{sfHookParameters, soeOPTIONAL},
{sfHookName, soeOPTIONAL},
};
#pragma push_macro("UNWRAP")
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+31 -15
View File
@@ -1403,7 +1403,8 @@ private:
testOffers()
{
using namespace jtx;
FeatureBitset const all{supported_amendments()};
FeatureBitset const all{
supported_amendments() | featureAMM | featureAMMClawback};
testRmFundedOffer(all);
testEnforceNoRipple(all);
testFillModes(all);
@@ -2802,8 +2803,11 @@ private:
testcase("limitQuality");
using namespace jtx;
auto const features =
supported_amendments() | featureAMM | featureAMMClawback;
{
Env env(*this);
Env env(*this, features);
fund(env, gw, {alice, bob, carol}, XRP(10'000), {USD(2'000)});
@@ -2829,11 +2833,13 @@ private:
using namespace jtx;
auto const features =
supported_amendments() | featureAMM | featureAMMClawback;
for (auto const withFix : {true, false})
{
auto const feats = withFix
? supported_amendments()
: supported_amendments() - FeatureBitset{fix1781};
auto const feats =
withFix ? features : features - FeatureBitset{fix1781};
// Payment path starting with XRP
Env env(*this, feats);
@@ -2861,7 +2867,7 @@ private:
}
{
// Payment path ending with XRP
Env env(*this);
Env env(*this, features);
// Note, if alice doesn't have default ripple, then pay fails
// with tecPATH_DRY.
fund(
@@ -2885,7 +2891,7 @@ private:
// Payment where loop is formed in the middle of the path, not
// on an endpoint
auto const JPY = gw["JPY"];
Env env(*this);
Env env(*this, features);
// Note, if alice doesn't have default ripple, then pay fails
// with tecPATH_DRY.
fund(
@@ -3113,7 +3119,10 @@ private:
using namespace jtx;
Env env(*this);
auto const features =
supported_amendments() | featureAMM | featureAMMClawback;
Env env(*this, features);
fund(env, gw, {alice, bob, carol}, XRP(10'000));
env.trust(USD(1'000), alice, bob, carol);
@@ -3828,7 +3837,8 @@ private:
testFlow()
{
using namespace jtx;
FeatureBitset const all{supported_amendments()};
FeatureBitset const all{
supported_amendments() | featureAMM | featureAMMClawback};
FeatureBitset const ownerPaysFee{featureOwnerPaysFee};
testFalseDry(all);
@@ -3844,7 +3854,8 @@ private:
testCrossingLimits()
{
using namespace jtx;
FeatureBitset const all{supported_amendments()};
FeatureBitset const all{
supported_amendments() | featureAMM | featureAMMClawback};
testStepLimit(all);
}
@@ -3852,14 +3863,16 @@ private:
testDeliverMin()
{
using namespace jtx;
FeatureBitset const all{supported_amendments()};
FeatureBitset const all{
supported_amendments() | featureAMM | featureAMMClawback};
test_convert_all_of_an_asset(all);
}
void
testDepositAuth()
{
auto const supported{jtx::supported_amendments()};
auto const supported{
jtx::supported_amendments() | featureAMM | featureAMMClawback};
testPayment(supported - featureDepositPreauth);
testPayment(supported);
testPayIOU();
@@ -3869,7 +3882,8 @@ private:
testFreeze()
{
using namespace test::jtx;
auto const sa = supported_amendments();
auto const sa =
supported_amendments() | featureAMM | featureAMMClawback;
testRippleState(sa);
testGlobalFreeze(sa);
testOffersWhenFrozen(sa);
@@ -3879,7 +3893,8 @@ private:
testMultisign()
{
using namespace jtx;
auto const all = supported_amendments();
auto const all =
supported_amendments() | featureAMM | featureAMMClawback;
testTxMultisign(
all - featureMultiSignReserve - featureExpandedSignerList);
@@ -3891,7 +3906,8 @@ private:
testPayStrand()
{
using namespace jtx;
auto const all = supported_amendments();
auto const all =
supported_amendments() | featureAMM | featureAMMClawback;
testToStrand(all);
testRIPD1373(all);
+902 -277
View File
File diff suppressed because it is too large. Load diff
+3 -3
View File
@@ -582,9 +582,9 @@ class BaseFee_test : public beast::unit_test::suite
}
void
testSignersListSet(FeatureBitset features)
testSignerListSet(FeatureBitset features)
{
testcase("signers list set w/ hook params");
testcase("signer list set w/ hook params");
using namespace test::jtx;
using namespace std::literals;
@@ -810,7 +810,7 @@ class BaseFee_test : public beast::unit_test::suite
testPaymentChannelFund(features);
testSetHook(features);
testSetRegularKey(features);
testSignersListSet(features);
testSignerListSet(features);
testTicketCreate(features);
testTrustSet(features);
testURITokenBurnFee(features);
+753 -13
View File
@@ -18,6 +18,7 @@
//==============================================================================
#include <test/jtx.h>
#include <test/jtx/AMM.h>
#include <xrpld/app/ledger/LedgerMaster.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/jss.h>
@@ -26,6 +27,14 @@ namespace ripple {
namespace test {
struct ClaimReward_test : public beast::unit_test::suite
{
private:
// helper
void static overrideFlag(Json::Value& jv)
{
jv[jss::Flags] = hsfOVERRIDE;
}
public:
bool
expectRewards(
jtx::Env const& env,
@@ -59,6 +68,52 @@ struct ClaimReward_test : public beast::unit_test::suite
return true;
}
bool
expectRewardsIOU(
jtx::Env const& env,
jtx::Account const& acct,
jtx::IOU const& iou,
std::uint32_t ledgerFirst,
std::uint32_t ledgerLast,
STAmount accumulator,
std::uint32_t time)
{
auto const sle = env.le(keylet::line(acct, iou.account, iou.currency));
BEAST_EXPECT(!!sle);
auto const& sfRewardField =
std::minmax(acct.id(), iou.account.id()).first == acct.id()
? sfLowReward
: sfHighReward;
if (!sle->isFieldPresent(sfRewardField))
return false;
auto const& reward =
static_cast<STObject const&>(sle->peekAtField(sfRewardField));
if (!reward.isFieldPresent(sfRewardLgrFirst) ||
reward.getFieldU32(sfRewardLgrFirst) != ledgerFirst)
{
return false;
}
if (!reward.isFieldPresent(sfRewardLgrLast) ||
reward.getFieldU32(sfRewardLgrLast) != ledgerLast)
{
return false;
}
if (!reward.isFieldPresent(sfTrustLineRewardAccumulator) ||
reward.getFieldAmount(sfTrustLineRewardAccumulator) != accumulator)
{
return false;
}
if (!reward.isFieldPresent(sfRewardTime) ||
reward.getFieldU32(sfRewardTime) != time)
{
return false;
}
return true;
}
bool
expectNoRewards(jtx::Env const& env, jtx::Account const& acct)
{
@@ -82,6 +137,24 @@ struct ClaimReward_test : public beast::unit_test::suite
return true;
}
bool
expectNoRewardsIOU(
jtx::Env const& env,
jtx::Account const& acct,
jtx::IOU const& iou)
{
auto const sle = env.le(keylet::line(acct, iou.account, iou.currency));
BEAST_EXPECT(!!sle);
auto const& sfRewardField =
std::minmax(acct.id(), iou.account.id()).first == acct.id()
? sfLowReward
: sfHighReward;
if (sle->isFieldPresent(sfRewardField))
return false;
return true;
}
void
testEnabled(FeatureBitset features)
{
@@ -91,7 +164,7 @@ struct ClaimReward_test : public beast::unit_test::suite
// setup env
auto const alice = Account("alice");
auto const issuer = Account("issuer");
auto const issuer = Account::master;
for (bool const withClaimReward : {false, true})
{
@@ -101,7 +174,11 @@ struct ClaimReward_test : public beast::unit_test::suite
withClaimReward ? features : features - featureBalanceRewards;
Env env{*this, amend};
env.fund(XRP(1000), alice, issuer);
env.fund(XRP(1000), alice);
env.close();
env(hook(issuer, {{hso(jtx::genesis::AcceptHook)}}, 0),
fee(XRP(1)));
env.close();
auto const txResult =
@@ -118,7 +195,10 @@ struct ClaimReward_test : public beast::unit_test::suite
.count();
// CLAIM
env(reward::claim(alice), reward::issuer(issuer), txResult);
env(reward::claim(alice),
reward::issuer(issuer),
fee(XRP(1)),
txResult);
env.close();
if (withClaimReward)
@@ -194,14 +274,19 @@ struct ClaimReward_test : public beast::unit_test::suite
Env env{*this, amend};
auto const alice = Account("alice");
auto const issuer = Account("issuer");
auto const issuer = Account::master;
env.fund(XRP(1000), alice, issuer);
env.fund(XRP(1000), alice);
env.close();
env(hook(issuer, {{hso(jtx::genesis::AcceptHook)}}, 0),
fee(XRP(1)));
env.close();
auto tx = reward::claim(alice);
env(tx,
reward::issuer(issuer),
fee(XRP(1)),
txflags(tfFullyCanonicalSig),
withFixFlags ? ter(tesSUCCESS) : ter(temINVALID_FLAG));
env.close();
@@ -220,6 +305,105 @@ struct ClaimReward_test : public beast::unit_test::suite
env(reward::claim(alice), reward::issuer(alice), ter(temMALFORMED));
env.close();
}
// featureIOURewardClaim
// temDISABLED
// featureIOURewardClaim amendment is disabled
{
test::jtx::Env env{
*this,
network::makeNetworkConfig(21337),
features - featureIOURewardClaim};
auto const alice = Account("alice");
auto const bob = Account("bob");
auto const gw = Account("gw");
env.fund(XRP(1000), alice, bob, gw);
env.close();
jtx::IOU const USD = gw["USD"];
env(reward::claim(alice),
reward::issuer(bob),
reward::claimCurrency(USD),
ter(temDISABLED));
env.close();
}
// temMALFORMED
// ClaimCurrency.account cannot be the source account.
{
test::jtx::Env env{*this, network::makeNetworkConfig(21337)};
auto const alice = Account("alice");
auto const bob = Account("bob");
env.fund(XRP(1000), alice);
env.close();
jtx::IOU const USD = alice["USD"];
env(reward::claim(alice),
reward::issuer(bob),
reward::claimCurrency(USD),
ter(temMALFORMED));
env.close();
}
// temMALFORMED
// Issuer cannot be Genesis account if ClaimCurrency is set.
{
test::jtx::Env env{*this, network::makeNetworkConfig(21337)};
auto const alice = Account("alice");
auto const gw = Account("gw");
env.fund(XRP(1000), alice, gw);
env.close();
jtx::IOU const USD = gw["USD"];
env(reward::claim(alice),
reward::issuer(Account::master),
reward::claimCurrency(USD),
ter(temBAD_ISSUER));
}
// MPT
{
// tested in testMPTInvalidInTx() at MPToken_test.cpp
}
// XAH RewardClaim: Issuer must be the Genesis account if
// featureXahauGenesis and featureIOURewardClaim are enabled.
for (bool const withIOURewardClaim : {false, true})
{
auto const amend = withIOURewardClaim
? features
: features - featureIOURewardClaim;
auto const alice = Account("alice");
auto const badIssuer = Account("gw");
auto const issuer = Account::master;
auto const USD = badIssuer["USD"];
Env env{*this, amend};
env.fund(XRP(1000), alice, badIssuer);
env.close();
env(hook(issuer, {{hso(jtx::genesis::AcceptHook)}}, 0),
fee(XRP(1)));
env.close();
env(reward::claim(alice),
reward::issuer(badIssuer),
fee(XRP(1)),
withIOURewardClaim ? ter(temBAD_ISSUER) : ter(tesSUCCESS));
env(reward::claim(alice),
reward::issuer(issuer),
fee(XRP(1)),
ter(tesSUCCESS));
}
}
void
@@ -243,6 +427,7 @@ struct ClaimReward_test : public beast::unit_test::suite
auto const alice = Account("alice");
auto const issuer = Account("issuer");
env.memoize(alice);
auto USD = issuer["USD"];
env.fund(XRP(1000), issuer);
env.close();
@@ -250,7 +435,10 @@ struct ClaimReward_test : public beast::unit_test::suite
auto tx = reward::claim(alice);
tx[jss::Sequence] = 0;
tx[jss::Fee] = 10;
env(tx, reward::issuer(issuer), ter(terNO_ACCOUNT));
env(tx,
reward::issuer(issuer),
reward::claimCurrency(USD),
ter(terNO_ACCOUNT));
env.close();
}
@@ -260,9 +448,9 @@ struct ClaimReward_test : public beast::unit_test::suite
test::jtx::Env env{*this, network::makeNetworkConfig(21337)};
auto const alice = Account("alice");
auto const issuer = Account("issuer");
auto const issuer = Account::master;
env.fund(XRP(1000), alice, issuer);
env.fund(XRP(1000), alice);
env.close();
env(reward::claim(alice),
@@ -293,13 +481,196 @@ struct ClaimReward_test : public beast::unit_test::suite
auto const issuer = Account("issuer");
env.memoize(issuer);
auto USD = issuer["USD"];
env.fund(XRP(1000), alice);
env.close();
auto tx = reward::claim(alice);
env(tx, reward::issuer(issuer), ter(tecNO_ISSUER));
env(tx,
reward::issuer(issuer),
reward::claimCurrency(USD),
ter(tecNO_ISSUER));
env.close();
}
// tecNO_PERMISSION
// issuer is an AMM account
{
test::jtx::Env env{
*this,
network::makeNetworkConfig(21337),
features | featureAMM};
auto const alice = Account("alice");
auto const issuer = Account("issuer");
auto const USD = issuer["USD"];
env.fund(XRP(1000), alice, issuer);
env.close();
AMM amm(env, issuer, XRP(100), USD(100));
BEAST_EXPECT(amm.ammExists());
env(reward::claim(alice),
reward::issuer(amm.ammAccount()),
reward::claimCurrency(USD),
ter(tecNO_PERMISSION));
env.close();
}
// tecNO_TARGET
// no claim reward hook
{
Env env{*this};
auto const alice = Account("alice");
auto const issuer = Account::master;
env.fund(XRP(1000), alice);
env.close();
// Doesn't have hook
{
env(reward::claim(alice),
reward::issuer(issuer),
ter(tecNO_TARGET));
env.close();
}
// Invalid HookOn
{
auto hookObj = hso(jtx::genesis::AcceptHook, overrideFlag);
hookObj[jss::HookOn] = to_string(UINT256_BIT[ttCLAIM_REWARD]);
env(hook(issuer, {{hookObj}}, 0), fee(XRP(1)));
env.close();
env(reward::claim(alice),
reward::issuer(issuer),
ter(tecNO_TARGET));
env.close();
}
// Invalid IncomingHookOn
{
auto hookObj = hso(jtx::genesis::AcceptHook, overrideFlag);
hookObj.removeMember(jss::HookOn);
hookObj[jss::HookOnIncoming] =
to_string(UINT256_BIT[ttCLAIM_REWARD]);
hookObj[jss::HookOnOutgoing] = to_string(uint256{});
env(hook(issuer, {{hookObj}}, 0), fee(XRP(1)));
env.close();
env(reward::claim(alice),
reward::issuer(issuer),
ter(tecNO_TARGET));
}
// Vaild HookOn
{
auto hookObj = hso(jtx::genesis::AcceptHook, overrideFlag);
hookObj[jss::HookOn] = to_string(~UINT256_BIT[ttCLAIM_REWARD]);
env(hook(issuer, {{hookObj}}, 0), fee(XRP(1)));
env.close();
env(reward::claim(alice),
reward::issuer(issuer),
fee(XRP(1)),
ter(tesSUCCESS));
}
// Vaild IncomingHookOn
{
auto hookObj = hso(jtx::genesis::AcceptHook, overrideFlag);
hookObj.removeMember(jss::HookOn);
hookObj[jss::HookOnIncoming] =
to_string(~UINT256_BIT[ttCLAIM_REWARD]);
hookObj[jss::HookOnOutgoing] = to_string(uint256{});
env(hook(issuer, {{hookObj}}, 0), fee(XRP(1)));
env.close();
env(reward::claim(alice),
reward::issuer(issuer),
fee(XRP(1)),
ter(tesSUCCESS));
}
// Invalid Hooks Array
{
auto hookObj = hso(jtx::genesis::AcceptHook, overrideFlag);
hookObj[jss::HookOn] = to_string(UINT256_BIT[ttCLAIM_REWARD]);
env(hook(
issuer,
{{
hookObj,
hookObj,
hookObj,
hookObj,
hookObj,
hookObj,
hookObj,
hookObj,
hookObj,
hookObj,
}},
0),
fee(XRP(1)));
env.close();
env(reward::claim(alice),
reward::issuer(issuer),
fee(XRP(1)),
ter(tecNO_TARGET));
}
// Vaild Hooks Array
{
auto hookObj = hso(jtx::genesis::AcceptHook, overrideFlag);
hookObj[jss::HookOn] = to_string(UINT256_BIT[ttCLAIM_REWARD]);
auto hookObj2 = hso(jtx::genesis::AcceptHook, overrideFlag);
hookObj2[jss::HookOn] = to_string(~UINT256_BIT[ttCLAIM_REWARD]);
env(hook(
issuer,
{{
hookObj,
hookObj,
hookObj,
hookObj,
hookObj,
hookObj,
hookObj,
hookObj,
hookObj,
hookObj2,
}},
0),
fee(XRP(1)));
env.close();
env(reward::claim(alice),
reward::issuer(issuer),
fee(XRP(1)),
ter(tesSUCCESS));
}
}
// tecNO_LINE
// trustline does not exist.
{
test::jtx::Env env{*this, network::makeNetworkConfig(21337)};
auto const alice = Account("alice");
auto const gw = Account("gw");
env.fund(XRP(1000), alice, gw);
env.close();
env(hook(gw, {{hso(jtx::genesis::AcceptHook)}}, 0), fee(XRP(1)));
env.close();
jtx::IOU const USD = gw["USD"];
env(reward::claim(alice),
reward::issuer(gw),
reward::claimCurrency(USD),
fee(XRP(1)),
ter(tecNO_LINE));
}
}
void
@@ -312,9 +683,17 @@ struct ClaimReward_test : public beast::unit_test::suite
test::jtx::Env env{*this, network::makeNetworkConfig(21337)};
auto const alice = Account("alice");
auto const bob = Account("bob");
auto const gw = Account("gw");
auto const issuer = Account("issuer");
env.fund(XRP(1000), alice, issuer);
env.fund(XRP(1000), alice, bob, gw, issuer);
env.close();
env(hook(issuer, {{hso(jtx::genesis::AcceptHook)}}, 0), fee(XRP(1)));
env.close();
env(hook(Account::master, {{hso(jtx::genesis::AcceptHook)}}, 0),
fee(XRP(1)));
env.close();
// test claim rewards - no opt out
@@ -329,7 +708,7 @@ struct ClaimReward_test : public beast::unit_test::suite
.count();
auto tx = reward::claim(alice);
env(tx, reward::issuer(issuer), ter(tesSUCCESS));
env(tx, reward::issuer(Account::master), fee(XRP(1)), ter(tesSUCCESS));
env.close();
BEAST_EXPECT(
@@ -342,6 +721,51 @@ struct ClaimReward_test : public beast::unit_test::suite
env.close();
BEAST_EXPECT(expectNoRewards(env, alice) == true);
// test iou claim rewards
{
// set trustline
env(trust(bob, gw["USD"](10000)));
env.close();
// opt in
auto const currentLedger = env.current()->seq();
auto const currentTime =
std::chrono::duration_cast<std::chrono::seconds>(
env.app()
.getLedgerMaster()
.getValidatedLedger()
->info()
.parentCloseTime.time_since_epoch())
.count();
auto tx = reward::claim(bob);
env(tx,
reward::issuer(issuer),
reward::claimCurrency(gw["USD"]),
fee(XRP(1)),
ter(tesSUCCESS));
env.close();
BEAST_EXPECT(
expectRewardsIOU(
env,
bob,
gw["USD"],
currentLedger,
currentLedger,
gw["USD"](0),
currentTime) == true);
// opt out
env(reward::claim(bob),
reward::claimCurrency(gw["USD"]),
txflags(tfOptOut),
ter(tesSUCCESS));
env.close();
BEAST_EXPECT(expectNoRewardsIOU(env, bob, gw["USD"]) == true);
}
}
void
@@ -352,16 +776,20 @@ struct ClaimReward_test : public beast::unit_test::suite
using namespace std::literals::chrono_literals;
Env env{*this, features};
auto const alice = Account("alice");
auto const issuer = Account("issuer");
env.fund(XRP(10000), alice, issuer);
auto const issuer = Account::master;
env.fund(XRP(10000), alice);
std::uint32_t aliceTicketSeq{env.seq(alice) + 1};
env(ticket::create(alice, 10));
std::uint32_t const aliceSeq{env.seq(alice)};
env.require(owners(alice, 10));
env(hook(issuer, {{hso(jtx::genesis::AcceptHook)}}, 0), fee(XRP(1)));
env.close();
env(reward::claim(alice),
reward::issuer(issuer),
ticket::use(aliceTicketSeq++),
fee(XRP(1)),
ter(tesSUCCESS));
env.require(tickets(alice, env.seq(alice) - aliceTicketSeq));
@@ -369,6 +797,317 @@ struct ClaimReward_test : public beast::unit_test::suite
env.require(owners(alice, 9));
}
void
testBalanceChanges(FeatureBitset features)
{
testcase("balance changes");
using namespace jtx;
using namespace std::literals::chrono_literals;
auto const getCurrentTime = [&](Env& env) {
return std::chrono::duration_cast<std::chrono::seconds>(
env.app()
.getLedgerMaster()
.getValidatedLedger()
->info()
.parentCloseTime.time_since_epoch())
.count();
};
// Native Reward Claim
{
Env env{*this, features};
auto const alice = Account("alice");
auto const gw = Account("gw");
auto const issuer = Account::master;
env.fund(XRP(10001), alice, gw);
env.close();
env(hook(issuer, {{hso(jtx::genesis::AcceptHook)}}, 0),
fee(XRP(1)));
env.close();
auto const currentTime = getCurrentTime(env);
auto const currentLedger = env.current()->seq();
env(reward::claim(alice), reward::issuer(issuer), fee(XRP(1)));
env.close();
env(fset(alice, 0));
env.close();
BEAST_EXPECT(
expectRewards(
env,
alice,
currentLedger,
currentLedger + 1,
10000, // 10000 XAH * time 1
currentTime) == true);
}
// IOU Reward Claim
for (bool const fromHighAccount : {true, false})
{
Env env{*this, features};
auto const alice = Account("alice");
auto const bob = Account("bob");
auto const issuer = Account("issuer");
auto const user = fromHighAccount ? alice : bob;
auto const gw = fromHighAccount ? bob : alice;
if (fromHighAccount)
BEAST_EXPECT(user.id() < gw.id());
else
BEAST_EXPECT(user.id() > gw.id());
env.fund(XRP(10000), user, gw, issuer);
env(fset(gw, asfDefaultRipple));
auto hookObj = hso(jtx::genesis::AcceptHook);
hookObj[jss::HookOn] = to_string(~UINT256_BIT[ttCLAIM_REWARD]);
env(hook(gw, {{hookObj}}, 0), fee(XRP(1)));
env.close();
env(trust(user, gw["USD"](1000000)), fee(XRP(1)));
env.close();
env(pay(gw, user, gw["USD"](10000)));
env.close();
auto currentTime = getCurrentTime(env);
auto currentLedger = env.current()->seq();
env(reward::claim(user),
reward::issuer(gw),
reward::claimCurrency(gw["USD"]),
fee(XRP(1)));
env.close();
env(pay(user, gw, gw["USD"](10000)));
env.close();
BEAST_EXPECT(
expectRewardsIOU(
env,
user,
gw["USD"],
currentLedger,
currentLedger + 1,
user["USD"](10000), // 10000 USD * time 1
currentTime) == true);
env(pay(gw, user, gw["USD"](1)));
env.close();
// check Balance == 0
BEAST_EXPECT(
expectRewardsIOU(
env,
user,
gw["USD"],
currentLedger,
currentLedger + 2,
user["USD"](10000), // 10000 USD * time 1 + 0 USD * time 1
currentTime) == true);
}
// Check Balance minus -> plus, plus -> minus
for (bool const fromHighAccount : {true, false})
{
Env env{*this, features};
auto const alice = Account("alice");
auto const bob = Account("bob");
auto const issuer = Account("issuer");
auto const user = fromHighAccount ? alice : bob;
auto const gw = fromHighAccount ? bob : alice;
if (fromHighAccount)
BEAST_EXPECT(user.id() < gw.id());
else
BEAST_EXPECT(user.id() > gw.id());
env.fund(XRP(10000), user, gw, issuer);
env(fset(gw, asfDefaultRipple));
env.close();
auto hookObj = hso(jtx::genesis::AcceptHook);
hookObj[jss::HookOn] = to_string(~UINT256_BIT[ttCLAIM_REWARD]);
env(hook(gw, {{hookObj}}, 0), fee(XRP(1)));
env.close();
env(trust(user, gw["USD"](1000000)));
env.close();
env(trust(gw, user["USD"](1000000)));
env(pay(gw, user, gw["USD"](10000)));
env.close();
auto currentTime = getCurrentTime(env);
auto currentLedger = env.current()->seq();
env(reward::claim(user),
reward::issuer(gw),
reward::claimCurrency(gw["USD"]),
fee(XRP(1)));
env.close();
env(pay(user, gw, gw["USD"](20000)));
env.close();
env(pay(user, gw, gw["USD"](1)));
env.close();
BEAST_EXPECT(
expectRewardsIOU(
env,
user,
gw["USD"],
currentLedger,
currentLedger + 2,
user["USD"](10000), // 10000 USD * time 1 + 0 USD * time 1
currentTime) == true);
}
// test with escrow (locked balance)
for (bool const fromHighAccount : {true, false})
{
for (bool const hasEscrow : {true, false})
{
Env env{*this, features};
auto const alice = Account("alice");
auto const bob = Account("bob");
auto const issuer = Account("issuer");
auto const user = fromHighAccount ? alice : bob;
auto const gw = fromHighAccount ? bob : alice;
if (fromHighAccount)
BEAST_EXPECT(user.id() < gw.id());
else
BEAST_EXPECT(user.id() > gw.id());
env.fund(XRP(10000), user, gw, issuer);
env(fset(gw, asfDefaultRipple));
auto hookObj = hso(jtx::genesis::AcceptHook);
hookObj[jss::HookOn] = to_string(~UINT256_BIT[ttCLAIM_REWARD]);
env(hook(gw, {{hookObj}}, 0), fee(XRP(1)));
env.close();
env(trust(user, gw["USD"](1000000)), fee(XRP(1)));
env.close();
env(pay(gw, user, gw["USD"](10000)));
env.close();
if (hasEscrow)
{
env(escrow(user, user, gw["USD"](2000)),
finish_time(env.now() + 1s),
fee(XRP(1)));
env.close();
}
auto currentTime = getCurrentTime(env);
auto currentLedger = env.current()->seq();
env(reward::claim(user),
reward::issuer(gw),
reward::claimCurrency(gw["USD"]),
fee(XRP(1)));
env.close();
env(pay(user, gw, gw["USD"](5000)));
env.close();
BEAST_EXPECT(
expectRewardsIOU(
env,
user,
gw["USD"],
currentLedger,
currentLedger + 1,
user["USD"](10000), // 10000 USD * time 1
currentTime) == true);
env(pay(gw, user, gw["USD"](1)));
env.close();
// check Balance == 0
BEAST_EXPECT(
expectRewardsIOU(
env,
user,
gw["USD"],
currentLedger,
currentLedger + 2,
user["USD"](
15000), // 10000 USD * time 1 + 5000 USD * time 1
currentTime) == true);
}
}
// STAmount overflow in reward accumulation should not cause
// transaction failure (tefEXCEPTION). The overflow should be
// gracefully skipped via try-catch.
for (bool const fromHighAccount : {true, false})
{
Env env{*this, features};
auto const alice = Account("alice");
auto const bob = Account("bob");
auto const issuer = Account("issuer");
auto const user = fromHighAccount ? alice : bob;
auto const gw = fromHighAccount ? bob : alice;
env.fund(XRP(10000), user, gw, issuer);
env(fset(gw, asfDefaultRipple));
env.close();
auto hookObj = hso(jtx::genesis::AcceptHook);
hookObj[jss::HookOn] = to_string(~UINT256_BIT[ttCLAIM_REWARD]);
env(hook(gw, {{hookObj}}, 0), fee(XRP(1)));
env.close();
// Use a near-max IOU balance at exponent 80. When
// multiply(balance, STAmount(lgrElapsed), issue) is called
// with lgrElapsed >= 2, the result exponent exceeds
// cMaxOffset(80), causing IOUAmount::normalize to throw
// std::overflow_error("value overflow").
auto const bigUSD = STAmount{
gw["USD"].issue(), std::uint64_t(5000000000000000ull), 80};
// Payment amount must be large enough to register a
// balance change given STAmount's 16-digit precision.
auto const payBackUSD = STAmount{
gw["USD"].issue(), std::uint64_t(1000000000000000ull), 80};
env(trust(user, bigUSD));
env.close();
env(pay(gw, user, bigUSD));
env.close();
// Claim IOU reward to initialize reward tracking
env(reward::claim(user),
reward::issuer(gw),
reward::claimCurrency(gw["USD"]),
fee(XRP(1)));
env.close();
// Advance ledger so lgrElapsed >= 2. With lgrElapsed=1
// the multiply result is exactly at cMaxOffset boundary
// (no overflow). With lgrElapsed >= 2, the result exponent
// exceeds cMaxOffset and triggers the overflow.
env.close();
// This payment modifies the trustline balance, triggering
// reward accumulation in Transactor. Without the try-catch
// fix, multiply() throws std::overflow_error("value overflow")
// and the transaction fails with tefEXCEPTION.
env(pay(user, gw, payBackUSD), ter(tesSUCCESS));
env.close();
}
}
void
testWithFeats(FeatureBitset features)
{
@@ -377,6 +1116,7 @@ struct ClaimReward_test : public beast::unit_test::suite
testInvalidPreclaim(features);
testValidNoHook(features);
testUsingTickets(features);
testBalanceChanges(features);
}
public:
+89
View File
@@ -18,6 +18,7 @@
//==============================================================================
#include <test/jtx.h>
#include <test/jtx/AMM.h>
#include <xrpld/app/tx/applySteps.h>
#include <xrpld/ledger/Dir.h>
#include <xrpl/protocol/Feature.h>
@@ -4316,6 +4317,93 @@ struct Escrow_test : public beast::unit_test::suite
env.close();
}
void
testIOUAMM(FeatureBitset features)
{
testcase("IOU AMM");
using namespace test::jtx;
using namespace std::chrono;
Account alice{"alice"};
Account bob{"bob"};
Account gw{"gw"};
auto const USD = gw["USD"];
// AMMCreate fails - insufficient balance
Env env(*this, features | featureAMM | featureAMMClawback);
env.fund(XRP(10000), alice, bob, gw);
env.close();
env.trust(USD(100000), alice);
env.close();
env(pay(gw, alice, USD(1000)));
env.close();
env(escrow(alice, bob, USD(1000)), finish_time(env.now() + 1s));
env.close();
// AMMCreate fails - insufficient balance
AMM ammFail(env, alice, XRP(1000), USD(1000), ter(tecUNFUNDED_AMM));
env(pay(gw, alice, USD(1000)));
env.close();
AMM ammAlice(env, alice, XRP(1000), USD(1000));
BEAST_EXPECT(ammAlice.ammExists());
// Single Asset Deposit fails - insufficient balance
ammAlice.deposit(
alice,
USD(1000),
std::nullopt,
std::nullopt,
std::nullopt,
ter(tecUNFUNDED_AMM));
// Double Asset Deposit fails - insufficient balance
ammAlice.deposit(
alice,
USD(1000),
XRP(1000),
std::nullopt,
std::nullopt,
ter(tecUNFUNDED_AMM));
auto const lptoken = ammAlice.getLPTokensBalance(alice);
// lock all LP tokens
env(escrow(alice, bob, STAmount{lptoken, ammAlice.lptIssue()}),
finish_time(env.now() + 1s));
env.close();
// Withdraw
ammAlice.withdraw(
alice, USD(1000), std::nullopt, std::nullopt, ter(tecAMM_BALANCE));
ammAlice.withdrawAll(alice, USD(1000), ter(tecAMM_BALANCE));
env(ammAlice.bid(BidArg{
.account = alice,
.bidMax = 100,
.assets = {{USD, XRP}},
}),
ter(tecAMM_INVALID_TOKENS));
ammAlice.vote(
alice,
1'000,
std::nullopt,
std::nullopt,
{{USD, XRP}},
ter(tecAMM_INVALID_TOKENS));
// Cannot escrow clawbackable tokens, so we cannot ammClawback escrowed
// tokens
// env(amm::ammClawback(gw, alice, USD, XRP, USD(100)),
// ter(tecAMM_BALANCE));
// env(amm::ammClawback(gw, alice, USD, XRP, std::nullopt),
// ter(tecAMM_BALANCE));
}
static uint256
getEscrowIndex(AccountID const& account, std::uint32_t uSequence)
{
@@ -4729,6 +4817,7 @@ struct Escrow_test : public beast::unit_test::suite
testIOUTLINSF(features);
testIOUPrecisionLoss(features);
testIOUClawback(features);
testIOUAMM(features);
}
public:
+326 -1
View File
@@ -50,6 +50,7 @@ private:
}
public:
using enum hook_api::hook_return_code;
void
test_accept(FeatureBitset features)
{
@@ -3631,8 +3632,9 @@ public:
auto const alice = Account{"alice"};
auto const bob = Account{"bob"};
auto const claire = Account{"claire"};
Env env{*this, features};
env.fund(XRP(100000), alice, bob);
env.fund(XRP(100000), alice, bob, claire);
env.close();
// Compute hook hash for the accept hook
@@ -3772,6 +3774,84 @@ public:
BEAST_EXPECT(result2.has_value());
BEAST_EXPECT(result2.value() == newData.size());
}
{
// fixHookMap: foreign state set without grant after state previous
// modified
HookStateMap stateMap;
auto hookCtx = makeStubHookContext(
applyCtx, alice.id(), bob.id(), {}, stateMap);
AccountID const aliceid = alice.id();
// Pre-populate stateMap
stateMap[alice.id()] = {
100, // availableForReserves
1, // namespaceCount
1, // hookStateScale
{}};
auto& api = hookCtx.api();
// setup a hook on alice, and on claire, no grants
env(hook(alice, {{hso(genesis::AcceptHook)}}, 0), fee(XRP(1)));
env(hook(claire, {{hso(genesis::AcceptHook)}}, 0), fee(XRP(1)));
env.close();
// First modification
auto result1 =
api.state_foreign_set(testKey, testNs, aliceid, testData);
BEAST_EXPECT(result1.has_value());
// Second modification this time using bob as hookacc (should hit
// cache)
auto hookCtx2 = makeStubHookContext(
applyCtx, claire.id(), bob.id(), {}, stateMap);
// check the state entry is carried into the second context
// does the map contain the account?
BEAST_EXPECT(
hookCtx2.result.stateMap.find(aliceid) !=
hookCtx2.result.stateMap.end());
// the name space?
BEAST_EXPECT(
std::get<3>(hookCtx2.result.stateMap[aliceid]).find(testNs) !=
std::get<3>(hookCtx2.result.stateMap[aliceid]).end());
// the key entry?
BEAST_EXPECT(
std::get<3>(hookCtx2.result.stateMap[aliceid])[testNs].find(
testKey) !=
std::get<3>(hookCtx2.result.stateMap[aliceid])[testNs].end());
// is the entry marked as modified?
BEAST_EXPECT(
std::get<3>(hookCtx2.result.stateMap[aliceid])[testNs][testKey]
.first);
auto& api2 = hookCtx2.api();
Bytes newData{0x04, 0x05};
auto result2 =
api2.state_foreign_set(testKey, testNs, aliceid, newData);
if (features[fixHookMap])
{
// new behaviour: grant is missing, cannot write
BEAST_EXPECT(!result2.has_value());
BEAST_EXPECT(result2.error() == NOT_AUTHORIZED);
BEAST_EXPECT(hookCtx2.result.foreignStateSetDisabled);
}
else
{
// old behaviour: allow this illegal write due to the entry
// being modified previously in the map
BEAST_EXPECT(result2.has_value());
BEAST_EXPECT(result2.value() == newData.size());
}
}
}
void
@@ -4513,6 +4593,249 @@ public:
BEAST_EXPECT(ok.value());
}
void
testHookFloat(FeatureBitset features)
{
// testcase("Test hook float");
using namespace jtx;
using namespace hook::hook_float;
{
// get_exponent
testcase << "get_exponent";
BEAST_EXPECT(get_exponent(-1).error() == INVALID_FLOAT);
BEAST_EXPECT(get_exponent(0).value() == 0);
// 1234567891000000 * 10^(-5)
BEAST_EXPECT(get_exponent(6270245249190730432).value() == -5);
}
{
// get_mantissa
testcase << "get_mantissa";
BEAST_EXPECT(get_mantissa(-1).error() == INVALID_FLOAT);
BEAST_EXPECT(get_mantissa(0).value() == 0);
// 1234567891000000 * 10^(-5)
BEAST_EXPECT(
get_mantissa(6270245249190730432).value() == 1234567891000000);
}
{
// is_negative
testcase << "is_negative";
// 1234567891000000 * 10^(-5)
BEAST_EXPECT(is_negative(6270245249190730432) == false);
// -1234567891000000 * 10^(-5)
BEAST_EXPECT(is_negative(1658559230763342528) == true);
}
{
// invert_sign
testcase << "invert_sign";
BEAST_EXPECT(
invert_sign(6270245249190730432) == 1658559230763342528);
BEAST_EXPECT(
invert_sign(1658559230763342528) == 6270245249190730432);
}
{
// set_sign
testcase << "set_sign";
BEAST_EXPECT(
set_sign(6270245249190730432, true) == 1658559230763342528);
BEAST_EXPECT(
set_sign(1658559230763342528, false) == 6270245249190730432);
BEAST_EXPECT(
set_sign(6270245249190730432, false) == 6270245249190730432);
BEAST_EXPECT(
set_sign(1658559230763342528, true) == 1658559230763342528);
}
{
// set_mantissa
testcase << "set_mantissa";
BEAST_EXPECT(
set_mantissa(6270245249190730432, maxMantissa + 1).error() ==
MANTISSA_OVERSIZED);
BEAST_EXPECT(
set_mantissa(6270245249190730432, minMantissa - 1).error() ==
MANTISSA_UNDERSIZED);
BEAST_EXPECT(
set_mantissa(6270245249190730432, 1234567891000000).value() ==
6270245249190730432);
BEAST_EXPECT(
set_mantissa(1658559230763342528, 1234567891000000).value() ==
1658559230763342528);
BEAST_EXPECT(
set_mantissa(6270245249190730432, 1098765432100000).value() ==
6270109446731830432);
BEAST_EXPECT(
set_mantissa(1658559230763342528, 1098765432100000).value() ==
1658423428304442528);
}
{
// set_exponent
testcase << "set_exponent";
BEAST_EXPECT(
set_exponent(6270245249190730432, maxExponent + 1).error() ==
EXPONENT_OVERSIZED);
BEAST_EXPECT(
set_exponent(6270245249190730432, minExponent - 1).error() ==
EXPONENT_UNDERSIZED);
BEAST_EXPECT(
set_exponent(6270245249190730432, 40).value() ==
7080893182117419712);
BEAST_EXPECT(
set_exponent(6270245249190730432, -40).value() ==
5639741301358860992);
}
{
// make_float
testcase << "make_float";
BEAST_EXPECT(make_float(0, -5, false).value() == 0);
// invalid mantissa
BEAST_EXPECT(
make_float(maxMantissa + 1, -5, false).error() ==
MANTISSA_OVERSIZED);
BEAST_EXPECT(
make_float(minMantissa - 1, -5, false).error() ==
MANTISSA_UNDERSIZED);
// invalid exponent
BEAST_EXPECT(
make_float(1234567891000000, maxExponent + 1, false).error() ==
EXPONENT_OVERSIZED);
BEAST_EXPECT(
make_float(1234567891000000, minExponent - 1, false).error() ==
EXPONENT_UNDERSIZED);
BEAST_EXPECT(
make_float(1234567891000000, -5, false).value() ==
6270245249190730432);
BEAST_EXPECT(
make_float(1234567891000000, -5, true).value() ==
1658559230763342528);
}
{
// normalize_xfl
testcase << "normalize_xfl";
{
// zero
uint64_t mantissa = 0;
int32_t exponent = -5;
BEAST_EXPECT(
normalize_xfl(mantissa, exponent, false).value() == 0);
BEAST_EXPECT(mantissa == 0);
BEAST_EXPECT(exponent == -5);
}
{
// scale the mantissa up into the canonical range
uint64_t mantissa = 12345;
int32_t exponent = 0;
BEAST_EXPECT(
normalize_xfl(mantissa, exponent, false).value() ==
make_float(1234500000000000ULL, -11, false).value());
BEAST_EXPECT(mantissa == 1234500000000000ULL);
BEAST_EXPECT(exponent == -11);
}
{
// scale the mantissa down into the canonical range
uint64_t mantissa = 1234567891000000000ULL;
int32_t exponent = -5;
BEAST_EXPECT(
normalize_xfl(mantissa, exponent, false).value() ==
make_float(1234567891000000ULL, -2, false).value());
BEAST_EXPECT(mantissa == 1234567891000000ULL);
BEAST_EXPECT(exponent == -2);
}
{
// explicit negative sign
uint64_t mantissa = 12345;
int32_t exponent = 0;
BEAST_EXPECT(
normalize_xfl(mantissa, exponent, true).value() ==
make_float(1234500000000000ULL, -11, true).value());
BEAST_EXPECT(mantissa == 1234500000000000ULL);
BEAST_EXPECT(exponent == -11);
}
{
// signed negative mantissa
int64_t mantissa = -12345;
int32_t exponent = 0;
BEAST_EXPECT(
normalize_xfl(mantissa, exponent).value() ==
make_float(1234500000000000ULL, -11, true).value());
BEAST_EXPECT(mantissa == -1234500000000000LL);
BEAST_EXPECT(exponent == -11);
}
{
// signed minimum is adjusted before taking the absolute value
int64_t mantissa = std::numeric_limits<int64_t>::min();
int32_t exponent = 0;
BEAST_EXPECT(
normalize_xfl(mantissa, exponent).value() ==
make_float(9223372036854775ULL, 3, true).value());
BEAST_EXPECT(mantissa == -9223372036854775LL);
BEAST_EXPECT(exponent == 3);
}
{
// one below the minimum mantissa is rounded into range
uint64_t mantissa = minMantissa - 1;
int32_t exponent = -5;
BEAST_EXPECT(
normalize_xfl(mantissa, exponent, false).value() ==
make_float(minMantissa, -5, false).value());
BEAST_EXPECT(mantissa == minMantissa);
BEAST_EXPECT(exponent == -5);
}
{
// underflow returns canonical zero
uint64_t mantissa = 1;
int32_t exponent = minExponent;
BEAST_EXPECT(
normalize_xfl(mantissa, exponent, false).value() == 0);
BEAST_EXPECT(mantissa == 0);
BEAST_EXPECT(exponent == 0);
}
{
// exponent overflow is reported
uint64_t mantissa = minMantissa;
int32_t exponent = maxExponent + 1;
BEAST_EXPECT(
normalize_xfl(mantissa, exponent, false).error() ==
XFL_OVERFLOW);
BEAST_EXPECT(mantissa == minMantissa);
BEAST_EXPECT(exponent == maxExponent + 1);
}
}
}
void
testWithFeatures(FeatureBitset features)
{
@@ -4588,6 +4911,7 @@ public:
test_state(features);
test_state_foreign(features);
test_state_foreign_set(features - fixHookMap);
test_state_foreign_set(features);
test_state_foreign_set_max(features);
test_state_set(features);
@@ -4615,6 +4939,7 @@ public:
{
using namespace test::jtx;
testWithFeatures(supported_amendments());
testHookFloat(supported_amendments());
}
};
+9 -9
View File
@@ -879,7 +879,7 @@ inline std::string ImportTCSetRegularKey::w_signers = R"json({
}
})json";
class ImportTCSignersListSet
class ImportTCSignerListSet
{
public:
static std::string w_seed_bad_fee;
@@ -891,7 +891,7 @@ public:
static std::string w_signers_empty;
};
inline std::string ImportTCSignersListSet::w_seed_bad_fee = R"json({
inline std::string ImportTCSignerListSet::w_seed_bad_fee = R"json({
"ledger": {
"acroot": "64F75A08037D9F8ED8A103893401EB2AD726E7D6AAC3EAA249005916A9354892",
"close": 743008501,
@@ -952,7 +952,7 @@ inline std::string ImportTCSignersListSet::w_seed_bad_fee = R"json({
}
}
})json";
inline std::string ImportTCSignersListSet::w_seed = R"json({
inline std::string ImportTCSignerListSet::w_seed = R"json({
"ledger": {
"acroot": "8112FF5F3FEEA34894A16CCCD64A24D552521F2E699780A587A9E6F5F5117CE5",
"close": 743008510,
@@ -993,7 +993,7 @@ inline std::string ImportTCSignersListSet::w_seed = R"json({
}
}
})json";
inline std::string ImportTCSignersListSet::w_regular_key = R"json({
inline std::string ImportTCSignerListSet::w_regular_key = R"json({
"ledger": {
"acroot": "2A25CA219781A3144C72FD5FB6EB62763214E050050DA6176624A046C51EECBD",
"close": 743015350,
@@ -1034,7 +1034,7 @@ inline std::string ImportTCSignersListSet::w_regular_key = R"json({
}
}
})json";
inline std::string ImportTCSignersListSet::w_signers = R"json({
inline std::string ImportTCSignerListSet::w_signers = R"json({
"ledger": {
"acroot": "BC35E65B52724CF258BDAC8B8E0D3B9CA0F012F5B243F6AAD1B671EDABD5188E",
"close": 745594953,
@@ -1075,7 +1075,7 @@ inline std::string ImportTCSignersListSet::w_signers = R"json({
}
}
})json";
inline std::string ImportTCSignersListSet::w_seed_empty = R"json({
inline std::string ImportTCSignerListSet::w_seed_empty = R"json({
"ledger": {
"acroot": "ECCAFDE52A6D5F1E36EB82EAA5247FF1D8ADE51FCF1ED0842850193018A510F7",
"close": 743056482,
@@ -1116,7 +1116,7 @@ inline std::string ImportTCSignersListSet::w_seed_empty = R"json({
}
}
})json";
inline std::string ImportTCSignersListSet::w_regular_key_empty = R"json({
inline std::string ImportTCSignerListSet::w_regular_key_empty = R"json({
"ledger": {
"acroot": "E222F46D5F35C79FDA3BB98973E2024EF9F6FA7B26471CC9CEF2CE033FA0E6E7",
"close": 743169800,
@@ -1157,7 +1157,7 @@ inline std::string ImportTCSignersListSet::w_regular_key_empty = R"json({
}
}
})json";
inline std::string ImportTCSignersListSet::w_signers_empty = R"json({
inline std::string ImportTCSignerListSet::w_signers_empty = R"json({
"ledger": {
"acroot": "987438A87AD998B7D7ED04A280FB5414C76E8475D621A55FB8463F15CEEEAD49",
"close": 743172592,
@@ -1261,4 +1261,4 @@ inline std::string ImportTCHalving::base_genesis = R"json({
} // namespace test
} // namespace ripple
#endif
#endif
+96 -23
View File
@@ -19,6 +19,7 @@
#include <test/app/Import_json.h>
#include <test/jtx.h>
#include <test/jtx/AMM.h>
#include <xrpld/app/ledger/LedgerMaster.h>
#include <xrpld/app/misc/AmendmentTable.h>
#include <xrpld/app/misc/HashRouter.h>
@@ -1898,7 +1899,7 @@ class Import_test : public beast::unit_test::suite
// different keys.
{
auto const xpopJson =
import::loadXpop(ImportTCSignersListSet::w_signers);
import::loadXpop(ImportTCSignerListSet::w_signers);
env(import::import(alice, xpopJson),
msig(bob, dave),
fee((3 * feeDrops) * 10),
@@ -1910,7 +1911,7 @@ class Import_test : public beast::unit_test::suite
// different keys. - empty innerSigners
{
Json::Value xpopJson =
import::loadXpop(ImportTCSignersListSet::w_signers);
import::loadXpop(ImportTCSignerListSet::w_signers);
xpopJson[jss::transaction][jss::blob] =
"12000C22000000002400000014201B0000002B201D00005359202300000002"
"6840000000001E84B073008114AE123A8556F3CF91154711376AFB0F894F83"
@@ -1927,7 +1928,7 @@ class Import_test : public beast::unit_test::suite
// different keys.
{
Json::Value xpopJson =
import::loadXpop(ImportTCSignersListSet::w_signers);
import::loadXpop(ImportTCSignerListSet::w_signers);
xpopJson[jss::transaction][jss::blob] =
"12000C22000000002400000014201B0000002B201D00005359202300000002"
"6840000000001E84B073008114AE123A8556F3CF91154711376AFB0F894F83"
@@ -1953,7 +1954,7 @@ class Import_test : public beast::unit_test::suite
// temMALFORMED - Import: inner txn signature verify failed
{
Json::Value xpopJson =
import::loadXpop(ImportTCSignersListSet::w_signers);
import::loadXpop(ImportTCSignerListSet::w_signers);
xpopJson[jss::transaction][jss::blob] =
"12000C2200000008240000001A201B000003B9201D00005359202300000000"
"6840000000001E84B073008114AE123A8556F3CF91154711376AFB0F894F83"
@@ -2635,6 +2636,78 @@ class Import_test : public beast::unit_test::suite
ter(temDISABLED));
}
// tecNO_ISSUER, tecNO_PERMISSION
// issuer not found, issuer is an AMM account
{
auto const alice = Account("alice");
auto const bob = Account("bob");
auto const issuer = Account("issuer");
auto const USD = issuer["USD"];
for (bool const withFixImportIssuer : {true, false})
{
auto const amend =
withFixImportIssuer ? features : features - fixImportIssuer;
test::jtx::Env env{
*this, network::makeNetworkVLConfig(21337, keys), amend};
env.fund(XRP(1000), alice, issuer);
env.close();
// burn 10'000 xrp
auto const master = Account("masterpassphrase");
env(noop(master), fee(10'000'000'000), ter(tesSUCCESS));
env.close();
env(import::import(
alice, import::loadXpop(ImportTCAccountSet::w_seed)),
import::issuer(bob),
fee(100'000'000),
withFixImportIssuer ? ter(tecNO_ISSUER) : ter(tesSUCCESS));
env.close();
}
for (bool const withFixImportIssuer : {true, false})
{
auto const withAMM = features | featureAMM;
auto const amend =
withFixImportIssuer ? withAMM : withAMM - fixImportIssuer;
test::jtx::Env env{
*this, network::makeNetworkVLConfig(21337, keys), amend};
env.fund(XRP(1000), alice, issuer);
env.close();
// burn 10'000 xrp
auto const master = Account("masterpassphrase");
env(noop(master), fee(10'000'000'000), ter(tesSUCCESS));
env.close();
AMM amm(env, issuer, XRP(100), USD(100));
BEAST_EXPECT(amm.ammExists());
env(import::import(
alice, import::loadXpop(ImportTCAccountSet::w_seed)),
import::issuer(amm.ammAccount()),
fee(100'000'000),
withFixImportIssuer ? ter(tecNO_PERMISSION)
: ter(tesSUCCESS));
env.close();
}
// env.enableFeature(fixImportIssuer);
// env.close();
// env(import::import(
// carol, import::loadXpop(ImportTCAccountSet::w_seed)),
// import::issuer(bob),
// ter(tecNO_ISSUER));
// env.close();
// env(import::import(
// dave, import::loadXpop(ImportTCAccountSet::w_seed)),
// import::issuer(amm.ammAccount()),
// // fee(100'000'000),
// ter(tecNO_PERMISSION));
// env.close();
}
// tefINTERNAL
// during preclaim could not parse xpop, bailing.
{
@@ -2768,7 +2841,7 @@ class Import_test : public beast::unit_test::suite
env.close();
}
// tefIMPORT_BLACKHOLED - SignersListSet (w/seed)
// tefIMPORT_BLACKHOLED - SignerListSet (w/seed)
{
test::jtx::Env env{
*this, network::makeNetworkVLConfig(21337, keys)};
@@ -2792,7 +2865,7 @@ class Import_test : public beast::unit_test::suite
// Import with Master Key
Json::Value tmpXpop =
import::loadXpop(ImportTCSignersListSet::w_seed);
import::loadXpop(ImportTCSignerListSet::w_seed);
env(import::import(alice, tmpXpop),
ter(tefIMPORT_BLACKHOLED),
fee(feeDrops * 10),
@@ -3244,7 +3317,7 @@ class Import_test : public beast::unit_test::suite
env(noop(alice), sig(bob), fee(feeDrops), ter(tefBAD_AUTH));
}
// w/ signers list -> dne
// w/ signer list -> dne
{
test::jtx::Env env{
*this, network::makeNetworkVLConfig(21337, keys)};
@@ -3975,7 +4048,7 @@ class Import_test : public beast::unit_test::suite
env(noop(alice), sig(carol), fee(feeDrops), ter(tesSUCCESS));
}
// w/ signers list -> funded (update regular key)
// w/ signer list -> funded (update regular key)
{
test::jtx::Env env{
*this, network::makeNetworkVLConfig(21337, keys)};
@@ -4049,7 +4122,7 @@ class Import_test : public beast::unit_test::suite
BEAST_EXPECT(acctSle->getAccountID(sfRegularKey) == dave.id());
env(noop(alice), sig(dave), fee(feeDrops), ter(tesSUCCESS));
// confirm signers list not set
// confirm signer list not set
auto const k = keylet::signers(alice);
BEAST_EXPECT(env.current()->read(k) == nullptr);
}
@@ -4351,9 +4424,9 @@ class Import_test : public beast::unit_test::suite
}
void
testSignersListSet(FeatureBitset features)
testSignerListSet(FeatureBitset features)
{
testcase("signers list set tx");
testcase("signer list set tx");
using namespace test::jtx;
using namespace std::literals;
@@ -4394,7 +4467,7 @@ class Import_test : public beast::unit_test::suite
// import tx
auto const xpopJson =
import::loadXpop(ImportTCSignersListSet::w_seed_bad_fee);
import::loadXpop(ImportTCSignerListSet::w_seed_bad_fee);
Json::Value tx = import::import(alice, xpopJson);
tx[jss::Sequence] = 0;
// tx[jss::Fee] = 0;
@@ -4438,7 +4511,7 @@ class Import_test : public beast::unit_test::suite
// import tx
auto const xpopJson =
import::loadXpop(ImportTCSignersListSet::w_seed);
import::loadXpop(ImportTCSignerListSet::w_seed);
Json::Value tx = import::import(alice, xpopJson);
tx[jss::Sequence] = 0;
tx[jss::Fee] = 0;
@@ -4523,7 +4596,7 @@ class Import_test : public beast::unit_test::suite
// import tx
auto const burnAmt = XRP(2);
auto const xpopJson =
import::loadXpop(ImportTCSignersListSet::w_regular_key);
import::loadXpop(ImportTCSignerListSet::w_regular_key);
Json::Value tx = import::import(alice, xpopJson);
tx[jss::Sequence] = 0;
tx[jss::Fee] = 0;
@@ -4614,7 +4687,7 @@ class Import_test : public beast::unit_test::suite
// import tx
auto const xpopJson =
import::loadXpop(ImportTCSignersListSet::w_signers);
import::loadXpop(ImportTCSignerListSet::w_signers);
Json::Value tx = import::import(alice, xpopJson);
tx[jss::Sequence] = 0;
tx[jss::Fee] = 0;
@@ -4685,7 +4758,7 @@ class Import_test : public beast::unit_test::suite
// import tx
auto const xpopJson =
import::loadXpop(ImportTCSignersListSet::w_seed);
import::loadXpop(ImportTCSignerListSet::w_seed);
env(import::import(alice, xpopJson),
fee(feeDrops * 10),
ter(tesSUCCESS));
@@ -4771,7 +4844,7 @@ class Import_test : public beast::unit_test::suite
auto const envAlice = env.balance(alice);
BEAST_EXPECT(envAlice == XRP(1000));
// set the signers list
// set the signer list
env(signers(alice, 2, {{bob, 1}, {carol, 1}}));
env(noop(alice),
msig(bob, carol),
@@ -4787,7 +4860,7 @@ class Import_test : public beast::unit_test::suite
// import tx
auto const xpopJson =
import::loadXpop(ImportTCSignersListSet::w_seed_empty);
import::loadXpop(ImportTCSignerListSet::w_seed_empty);
env(import::import(alice, xpopJson),
fee(feeDrops * 10),
ter(tesSUCCESS));
@@ -4852,7 +4925,7 @@ class Import_test : public beast::unit_test::suite
env(noop(alice), sig(bob), fee(feeDrops), ter(tesSUCCESS));
env.close();
// set the signers list
// set the signer list
env(signers(alice, 2, {{bob, 1}, {carol, 1}}));
env(noop(alice),
msig(bob, carol),
@@ -4868,7 +4941,7 @@ class Import_test : public beast::unit_test::suite
// import tx
auto const xpopJson =
import::loadXpop(ImportTCSignersListSet::w_regular_key_empty);
import::loadXpop(ImportTCSignerListSet::w_regular_key_empty);
env(import::import(alice, xpopJson),
fee(feeDrops * 10),
sig(bob),
@@ -4935,7 +5008,7 @@ class Import_test : public beast::unit_test::suite
auto const envAlice = env.balance(alice);
BEAST_EXPECT(envAlice == XRP(1000));
// set the signers list
// set the signer list
env(signers(alice, 2, {{bob, 1}, {carol, 1}}));
env(noop(alice),
msig(bob, carol),
@@ -4951,7 +5024,7 @@ class Import_test : public beast::unit_test::suite
// import tx
auto const xpopJson =
import::loadXpop(ImportTCSignersListSet::w_signers_empty);
import::loadXpop(ImportTCSignerListSet::w_signers_empty);
env(import::import(alice, xpopJson),
msig(bob, carol),
fee((3 * feeDrops) * 10),
@@ -6228,7 +6301,7 @@ public:
testAccountSetFlags(features);
testSetRegularKey(features);
testSetRegularKeyFlags(features);
testSignersListSet(features);
testSignerListSet(features);
testUsingTickets(features);
testAccountIndex(features);
testHookIssuer(features);
+25
View File
@@ -18,6 +18,7 @@
//==============================================================================
#include <test/jtx.h>
#include <test/jtx/AMM.h>
#include <xrpl/protocol/jss.h>
namespace ripple {
@@ -168,6 +169,30 @@ class Invoke_test : public beast::unit_test::suite
fee(feeDrops),
ter(tecNO_TARGET));
}
// tecNO_PERMISSION
// issuer is an AMM account
{
test::jtx::Env env{
*this,
network::makeNetworkConfig(21337),
features | featureAMM};
auto const alice = Account("alice");
auto const issuer = Account("issuer");
auto const USD = issuer["USD"];
env.fund(XRP(1000), alice, issuer);
env.close();
AMM amm(env, issuer, XRP(100), USD(100));
BEAST_EXPECT(amm.ammExists());
env(invoke::invoke(alice),
invoke::dest(amm.ammAccount()),
ter(tecNO_PERMISSION));
env.close();
}
}
void
+1 -1
View File
@@ -388,7 +388,7 @@ public:
void
run() override
{
FeatureBitset const all{jtx::supported_amendments()};
FeatureBitset const all{jtx::supported_amendments() | featureAMM};
testDirectStep(all);
testBookStep(all);
+213
View File
@@ -20,6 +20,8 @@
#include <test/jtx.h>
#include <test/jtx/Env.h>
#include <xrpld/app/ledger/LedgerMaster.h>
#include <xrpld/app/ledger/detail/PublishGap.h>
#include <xrpl/basics/RangeSet.h>
#include <xrpl/protocol/jss.h>
namespace ripple {
@@ -117,13 +119,224 @@ class LedgerMaster_test : public beast::unit_test::suite
}
}
void
testCanSkipPinnedGap()
{
// detail::canSkipPinnedGap is the lifted decision used by
// findNewLedgersToPublish to decide whether pubSeq can leap
// across a gap in the publish-target set without violating
// contiguous publication of non-pinned ledgers. In production
// this guard rarely fires (pinned ranges are old historical
// catalogue data, pubSeq is at the live tip) — it matters in
// test/standalone catalogue-load scenarios where pinned seqs
// can sit near pubSeq. The invariant is small and crisp;
// unit-testing it directly is far cheaper than reconstructing
// a believable findNewLedgersToPublish state through Env.
testcase("detail::canSkipPinnedGap");
using detail::canSkipPinnedGap;
// No gap (pubSeq is already at intervalStart) — always OK.
{
RangeSet<std::uint32_t> empty;
BEAST_EXPECT(canSkipPinnedGap(10, 10, empty));
BEAST_EXPECT(canSkipPinnedGap(10, 5, empty)); // past
}
// One-element gap, exactly pinned: can skip.
{
RangeSet<std::uint32_t> pinned;
pinned.insert(range(10u, 10u));
BEAST_EXPECT(canSkipPinnedGap(10, 11, pinned));
}
// One-element gap, NOT pinned: must halt.
{
RangeSet<std::uint32_t> empty;
BEAST_EXPECT(!canSkipPinnedGap(10, 11, empty));
}
// Multi-element gap entirely covered by pinned: can skip.
{
RangeSet<std::uint32_t> pinned;
pinned.insert(range(11u, 15u));
BEAST_EXPECT(canSkipPinnedGap(11, 16, pinned));
}
// Multi-element gap, pinned covers most but leaves one
// non-pinned seq exposed: must halt. This is the bug the
// guard exists to prevent — silently publishing seq 16
// while seq 15 (non-pinned) is unfetched.
{
RangeSet<std::uint32_t> pinned;
pinned.insert(range(11u, 14u)); // missing 15
BEAST_EXPECT(!canSkipPinnedGap(11, 16, pinned));
}
// Pinned set extends beyond the gap — only the gap window
// matters for the decision.
{
RangeSet<std::uint32_t> pinned;
pinned.insert(range(0u, 1000u));
BEAST_EXPECT(canSkipPinnedGap(50, 100, pinned));
}
// Empty pinned, with a gap: must halt.
{
RangeSet<std::uint32_t> empty;
BEAST_EXPECT(!canSkipPinnedGap(50, 100, empty));
}
// Disjoint pinned that doesn't intersect the gap: must halt.
{
RangeSet<std::uint32_t> pinned;
pinned.insert(range(200u, 300u));
BEAST_EXPECT(!canSkipPinnedGap(50, 100, pinned));
}
// Pinned partially intersects gap from the left only.
{
RangeSet<std::uint32_t> pinned;
pinned.insert(range(50u, 75u));
BEAST_EXPECT(!canSkipPinnedGap(50, 100, pinned));
}
// Pinned partially intersects gap from the right only.
{
RangeSet<std::uint32_t> pinned;
pinned.insert(range(75u, 99u));
BEAST_EXPECT(!canSkipPinnedGap(50, 100, pinned));
}
}
void
testPinUnpinSymmetry()
{
testcase("pin/unpin symmetry");
using namespace test::jtx;
Env env{*this, makeNetworkConfig(11111)};
auto const alice = Account("alice");
env.fund(XRP(1000), alice);
env.close();
auto& lm = env.app().getLedgerMaster();
// The most recently closed ledger is the one we'll pin.
auto const ledger = lm.getClosedLedger();
BEAST_EXPECT(ledger);
if (!ledger)
return;
auto const seq = ledger->info().seq;
BEAST_EXPECT(seq > 0);
// Baseline — not pinned.
BEAST_EXPECT(!lm.isPinned(seq));
// Pin via storeLedger(ledger, pin=true). This is the same path
// catalogue_load uses just before saveValidatedLedger, which is why
// a save failure must roll back via unpinLedger() to keep the
// in-memory pinned-set consistent with state.db.
lm.storeLedger(ledger, /*pin=*/true);
BEAST_EXPECT(lm.isPinned(seq));
BEAST_EXPECT(boost::icl::contains(lm.getPinnedLedgersRangeSet(), seq));
// Unpin reverses the pin (the rollback contract used by
// catalogue_load's RAII guard on save failure).
lm.unpinLedger(seq);
BEAST_EXPECT(!lm.isPinned(seq));
BEAST_EXPECT(!boost::icl::contains(lm.getPinnedLedgersRangeSet(), seq));
// Unpinning a seq that isn't pinned is a no-op (idempotent).
lm.unpinLedger(seq);
BEAST_EXPECT(!lm.isPinned(seq));
}
void
testSetPinnedRangesImmediateMerge()
{
// Regression test: when setPinnedLedgersRangeSet runs and
// mCompleteLedgers is already non-empty (LOAD/standalone startup
// already called setFullLedger), the deferred merge in
// setFullLedger never fires because the one-shot guard relies on
// a flag, not on emptiness. Without the immediate-merge branch
// in setPinnedLedgersRangeSet, pinned ranges restored from
// state.db would never appear in mCompleteLedgers and historical
// pinned ledgers would not be queryable until the next
// validation. This test asserts the immediate-merge behavior.
testcase("setPinnedLedgersRangeSet immediate merge");
using namespace test::jtx;
Env env{*this, makeNetworkConfig(11111)};
auto const alice = Account("alice");
env.fund(XRP(1000), alice);
env.close();
auto& lm = env.app().getLedgerMaster();
// Why mCompleteLedgers is non-empty here:
// env.close() above runs synchronous standalone consensus.
// That hits LedgerMaster::switchLCL, which (in standalone)
// calls setFullLedger directly on the calling thread, which
// inserts the closed ledger's seq into mCompleteLedgers.
// So by the time we get here, mCompleteLedgers has the
// genesis + funded seqs in it.
//
// This is the precondition that triggers the immediate-merge
// branch in setPinnedLedgersRangeSet (the LOAD/standalone
// startup case). On a NORMAL/NETWORK production startup,
// mCompleteLedgers would still be empty when state.db is read
// and setPinnedLedgersRangeSet is called, and the merge would
// be deferred to the first validated ledger via setFullLedger.
// We're specifically testing the *other* branch here.
auto const completeBefore = lm.getCompleteLedgersRangeSet();
BEAST_EXPECT(!completeBefore.empty());
// Pinned ranges are far outside the existing range so we can
// unambiguously detect them being merged in.
std::uint32_t const pinLo = 100000;
std::uint32_t const pinHi = 100099;
RangeSet<std::uint32_t> pinned;
pinned.insert(range(pinLo, pinHi));
// Restore pinned ranges (mirrors what SHAMapStoreImp::start does
// after reading state.db's PinnedLedgers row).
lm.setPinnedLedgersRangeSet(pinned);
// Pinned set is restored.
BEAST_EXPECT(lm.isPinned(pinLo));
BEAST_EXPECT(lm.isPinned(pinHi));
BEAST_EXPECT(lm.isPinned((pinLo + pinHi) / 2));
// Critical invariant: pinned ranges are now in mCompleteLedgers
// immediately, not deferred to the first validated ledger.
// This is what makes haveLedger() return true for pinned seqs
// after a LOAD/standalone restart, without which the historical
// pinned data is on disk but the system reports "we don't have
// it".
BEAST_EXPECT(lm.haveLedger(pinLo));
BEAST_EXPECT(lm.haveLedger(pinHi));
BEAST_EXPECT(lm.haveLedger((pinLo + pinHi) / 2));
// The pre-existing complete range is still complete (merge,
// not replace).
for (auto const& interval : completeBefore)
for (auto s = interval.lower(); s <= interval.upper(); ++s)
BEAST_EXPECT(lm.haveLedger(s));
}
public:
void
run() override
{
using namespace test::jtx;
FeatureBitset const all{supported_amendments() - featureXahauGenesis};
testCanSkipPinnedGap();
testWithFeats(all);
testPinUnpinSymmetry();
testSetPinnedRangesImmediateMerge();
}
void
+7
View File
@@ -1878,6 +1878,13 @@ class MPToken_test : public beast::unit_test::suite
[jss::Amount] = mpt.getJson(JsonOptions::none);
test(jv, sfAmounts.jsonName.c_str());
}
// ClaimReward
{
Json::Value jv = reward::claim(alice);
jv[sfClaimCurrency.jsonName][jss::mpt_issuance_id] =
to_string(issue);
test(jv, sfClaimCurrency.jsonName.c_str());
}
}
for (const auto& str : txWithAmounts)
printf("%s\n", str.c_str());
+40
View File
@@ -568,6 +568,46 @@ private:
Oracle oracle(env, {.owner = owner});
oracle.set(UpdateArg{.series = {{"XRP", "USD", 742, 2}}});
}
// Should be same order as creation
Env env(*this);
auto const baseFee =
static_cast<int>(env.current()->fees().base.drops());
auto test = [&](Env& env, DataSeries const& series) {
env.fund(XRP(1'000), owner);
Oracle oracle(
env, {.owner = owner, .series = series, .fee = baseFee});
BEAST_EXPECT(oracle.exists());
auto sle = env.le(keylet::oracle(owner, oracle.documentID()));
BEAST_EXPECT(
sle->getFieldArray(sfPriceDataSeries).size() == series.size());
auto const beforeQuoteAssetName1 =
sle->getFieldArray(sfPriceDataSeries)[0]
.getFieldCurrency(sfQuoteAsset)
.getText();
auto const beforeQuoteAssetName2 =
sle->getFieldArray(sfPriceDataSeries)[1]
.getFieldCurrency(sfQuoteAsset)
.getText();
oracle.set(UpdateArg{.series = series, .fee = baseFee});
sle = env.le(keylet::oracle(owner, oracle.documentID()));
auto const afterQuoteAssetName1 =
sle->getFieldArray(sfPriceDataSeries)[0]
.getFieldCurrency(sfQuoteAsset)
.getText();
auto const afterQuoteAssetName2 =
sle->getFieldArray(sfPriceDataSeries)[1]
.getFieldCurrency(sfQuoteAsset)
.getText();
BEAST_EXPECT(afterQuoteAssetName1 == beforeQuoteAssetName1);
BEAST_EXPECT(afterQuoteAssetName2 == beforeQuoteAssetName2);
};
test(env, {{"XRP", "USD", 742, 2}, {"XRP", "EUR", 711, 2}});
}
void
+162
View File
@@ -20,6 +20,8 @@
#include <test/jtx.h>
#include <test/jtx/envconfig.h>
#include <xrpld/app/paths/AccountCurrencies.h>
#include <xrpld/app/paths/Pathfinder.h>
#include <xrpld/app/paths/RippleLineCache.h>
#include <xrpld/core/JobQueue.h>
#include <xrpld/rpc/Context.h>
#include <xrpld/rpc/RPCHandler.h>
@@ -573,6 +575,164 @@ public:
BEAST_EXPECT(equal(sa, Account("alice")["USD"](5)));
}
Json::Value
six_path_append_request_result()
{
using namespace jtx;
Env env = pathTestEnv();
Account A1{"A1"};
Account A2{"A2"};
Account G1{"G1"};
Account G2{"G2"};
Account M1{"M1"};
Account M2{"M2"};
Account M3{"M3"};
Account M4{"M4"};
Account M5{"M5"};
Account M6{"M6"};
Account MM{"MM"};
env.fund(XRP(1000), A1, A2, G1, G2, M1, M2, M3, M4, M5, M6, MM);
env.close();
env.trust(G1["HKD"](2000), A1);
env.trust(G2["HKD"](2000), A2);
env.trust(G1["HKD"](100000), M1, M2, M3, M4, M5, M6, MM);
env.trust(G2["HKD"](100000), M1, M2, M3, M4, M5, M6, MM);
env.close();
env(pay(G1, A1, G1["HKD"](1000)));
env(pay(G1, M1, G1["HKD"](10)));
env(pay(G1, M2, G1["HKD"](10)));
env(pay(G1, M3, G1["HKD"](10)));
env(pay(G1, M4, G1["HKD"](10)));
env(pay(G1, M5, G1["HKD"](10)));
env(pay(G1, M6, G1["HKD"](10)));
env(pay(G1, MM, G1["HKD"](1000)));
env(pay(G2, M1, G2["HKD"](10)));
env(pay(G2, M2, G2["HKD"](10)));
env(pay(G2, M3, G2["HKD"](10)));
env(pay(G2, M4, G2["HKD"](10)));
env(pay(G2, M5, G2["HKD"](10)));
env(pay(G2, M6, G2["HKD"](10)));
env(pay(G2, MM, G2["HKD"](1000)));
env.close();
env(offer(MM, G1["HKD"](1000), G2["HKD"](100)));
env.close();
return find_paths_request(
env, A1, A2, A2["HKD"](60), std::nullopt, G1["HKD"].currency);
}
void
pathfind_paths_computed_never_exceeds_six()
{
testcase("pathfind paths_computed never exceeds six");
auto const result = six_path_append_request_result();
BEAST_EXPECT(result.isMember(jss::alternatives));
if (!result.isMember(jss::alternatives))
return;
BEAST_EXPECT(result[jss::alternatives].isArray());
if (!result[jss::alternatives].isArray())
return;
bool sawPathsComputed = false;
for (auto const& alt : result[jss::alternatives])
{
if (!alt.isMember(jss::paths_computed))
continue;
sawPathsComputed = true;
BEAST_EXPECT(alt[jss::paths_computed].isArray());
if (alt[jss::paths_computed].isArray())
BEAST_EXPECT(alt[jss::paths_computed].size() <= 6);
}
BEAST_EXPECT(sawPathsComputed);
}
void
pathfind_can_return_six_paths_with_append()
{
testcase("pathfind can return six paths with append");
using namespace jtx;
Env env = pathTestEnv();
Account A1{"A1"};
Account A2{"A2"};
Account G1{"G1"};
Account G2{"G2"};
Account M1{"M1"};
Account M2{"M2"};
Account M3{"M3"};
Account M4{"M4"};
Account M5{"M5"};
Account M6{"M6"};
Account MM{"MM"};
env.fund(XRP(1000), A1, A2, G1, G2, M1, M2, M3, M4, M5, M6, MM);
env.close();
env.trust(G1["HKD"](2000), A1);
env.trust(G2["HKD"](2000), A2);
env.trust(G1["HKD"](100000), M1, M2, M3, M4, M5, M6, MM);
env.trust(G2["HKD"](100000), M1, M2, M3, M4, M5, M6, MM);
env.close();
env(pay(G1, A1, G1["HKD"](1000)));
env(pay(G1, M1, G1["HKD"](10)));
env(pay(G1, M2, G1["HKD"](10)));
env(pay(G1, M3, G1["HKD"](10)));
env(pay(G1, M4, G1["HKD"](10)));
env(pay(G1, M5, G1["HKD"](10)));
env(pay(G1, M6, G1["HKD"](10)));
env(pay(G1, MM, G1["HKD"](1000)));
env(pay(G2, M1, G2["HKD"](10)));
env(pay(G2, M2, G2["HKD"](10)));
env(pay(G2, M3, G2["HKD"](10)));
env(pay(G2, M4, G2["HKD"](10)));
env(pay(G2, M5, G2["HKD"](10)));
env(pay(G2, M6, G2["HKD"](10)));
env(pay(G2, MM, G2["HKD"](1000)));
env.close();
env(offer(MM, G1["HKD"](1000), G2["HKD"](100)));
env.close();
auto cache = std::make_shared<RippleLineCache>(
env.current(), env.app().journal("RippleLineCache"));
Pathfinder pf(
cache,
A1.id(),
A2.id(),
G1["HKD"].currency,
std::nullopt,
A2["HKD"](60),
std::nullopt,
env.app());
BEAST_EXPECT(pf.findPaths(7));
pf.computePathRanks(5);
STPath fullLiquidityPath;
auto bestPaths =
pf.getBestPaths(5, fullLiquidityPath, STPathSet{}, A1.id());
BEAST_EXPECT(bestPaths.size() == 5);
BEAST_EXPECT(!fullLiquidityPath.empty());
if (!fullLiquidityPath.empty())
bestPaths.push_back(fullLiquidityPath);
BEAST_EXPECT(bestPaths.size() == 6);
}
void
issues_path_negative_issue()
{
@@ -1381,6 +1541,8 @@ public:
path_find_04();
path_find_05();
path_find_06();
pathfind_paths_computed_never_exceeds_six();
pathfind_can_return_six_paths_with_append();
}
};
+50
View File
@@ -18,6 +18,7 @@
//==============================================================================
#include <test/jtx.h>
#include <test/jtx/AMM.h>
#include <xrpld/core/ConfigSections.h>
#include <xrpld/ledger/Dir.h>
#include <xrpl/basics/chrono.h>
@@ -410,6 +411,21 @@ struct Remit_test : public beast::unit_test::suite
env.close();
}
// tecNO_PERMISSION - inform account is an AMM
{
Env env{*this, features | featureAMM};
env.fund(XRP(1000), gw, alice, bob);
env.close();
AMM amm(env, gw, XRP(100), USD(100));
BEAST_EXPECT(amm.ammExists());
auto tx = remit::remit(alice, bob);
tx[sfInform.jsonName] = to_string(amm.ammAccount());
env(tx, alice, ter(tecNO_PERMISSION));
env.close();
}
// tecNO_PERMISSION - lsfDisallowIncomingRemit
// DA: see testAllowIncoming
@@ -2875,6 +2891,39 @@ struct Remit_test : public beast::unit_test::suite
}
}
void
testDestAMM(FeatureBitset features)
{
testcase("remit to AMM Account");
using namespace test::jtx;
using namespace std::literals;
auto const alice = Account("alice");
auto const gw = Account("gw");
auto const USD = gw["USD"];
Env env{*this, features | featureAMM};
env.fund(XRP(10'000'000), alice, gw);
env.close();
env.trust(USD(100'000), alice);
env.close();
env(pay(gw, alice, USD(100'000)));
env.close();
AMM ammAlice(env, alice, XRP(10'000), USD(10'000));
env(remit::remit(alice, ammAlice.ammAccount()),
remit::amts({USD(10'000)}),
ter(tecNO_PERMISSION));
env(remit::remit(alice, ammAlice.ammAccount()),
remit::amts({XRP(10'000)}),
ter(tecNO_PERMISSION));
env(remit::remit(gw, ammAlice.ammAccount()),
remit::amts({gw["EUR"](10'000)}),
ter(tecNO_PERMISSION));
}
void
testWithFeats(FeatureBitset features)
{
@@ -2896,6 +2945,7 @@ struct Remit_test : public beast::unit_test::suite
testRippling(features);
testURIToken(features);
testOptionals(features);
testDestAMM(features);
}
public:
+201
View File
@@ -22,10 +22,14 @@
#include <xrpld/app/main/Application.h>
#include <xrpld/app/main/NodeStoreScheduler.h>
#include <xrpld/app/misc/SHAMapStore.h>
#include <xrpld/app/misc/SHAMapStoreImp.h>
#include <xrpld/app/misc/detail/OnlineDeleteRanges.h>
#include <xrpld/app/rdb/backend/SQLiteDatabase.h>
#include <xrpld/core/ConfigSections.h>
#include <xrpld/nodestore/detail/DatabaseRotatingImp.h>
#include <xrpl/beast/utility/temp_dir.h>
#include <xrpl/protocol/jss.h>
#include <boost/filesystem.hpp>
namespace ripple {
namespace test {
@@ -51,6 +55,25 @@ class SHAMapStore_test : public beast::unit_test::suite
return cfg;
}
static auto
pinnedPersistent(
std::unique_ptr<Config> cfg,
std::string const& dbPath,
std::string const& nodePath,
std::string const& pinnedPath)
{
cfg = onlineDelete(std::move(cfg));
cfg->legacy("database_path", dbPath);
cfg->overwrite(SECTION_RELATIONAL_DB, "backend", "sqlite");
auto& section = cfg->section(ConfigSection::nodeDatabase());
section.set("type", "rwdb");
section.set("path", nodePath);
section.set("pinned_type", "nudb");
section.set("pinned_path", pinnedPath);
return cfg;
}
bool
goodLedger(
jtx::Env& env,
@@ -648,13 +671,191 @@ public:
BEAST_EXPECT(dbr->getName() == "3");
}
void
testPinnedRangeRestoreRequiresPinnedData()
{
testcase("pinned range restore requires pinned data");
using namespace jtx;
beast::temp_dir tempDir;
auto const tempPath = boost::filesystem::path(tempDir.path());
auto const dbPath = (tempPath / "db").string();
auto const nodePath = (tempPath / "node").string();
auto const pinnedPath = (tempPath / "pinned").string();
boost::filesystem::create_directories(dbPath);
boost::filesystem::create_directories(nodePath);
boost::filesystem::create_directories(pinnedPath);
{
Env env(
*this,
envconfig(pinnedPersistent, dbPath, nodePath, pinnedPath));
RangeSet<std::uint32_t> ranges;
ranges.insert(range(100u, 200u));
env.app().getSHAMapStore().setPinnedRanges(ranges);
NodeStoreScheduler scheduler(env.app().getJobQueue());
auto const journal = env.app().journal("SHAMapStoreTest");
try
{
SHAMapStoreImp restoreCheck(env.app(), scheduler, journal);
restoreCheck.start();
fail(
"Expected startup failure for stale persisted pinned "
"ranges");
}
catch (std::runtime_error const& e)
{
BEAST_EXPECT(
std::string(e.what()).find(
"Persisted pinned interval 100-200") !=
std::string::npos);
}
}
}
// Helper: compare two RangeSet<uint32_t> for set-equality. The
// test cares about which sequences are in the result, not the
// particular interval representation.
static bool
sameRanges(
RangeSet<std::uint32_t> const& a,
RangeSet<std::uint32_t> const& b)
{
return a == b;
}
void
testComputeOnlineDeleteTargets()
{
// detail::computeOnlineDeleteTargets is the lifted interval
// arithmetic used by SHAMapStoreImp::clearSqlRanges to decide
// which ledger sequences to actually delete during online
// delete. The base window is [minSeq, lastRotated - 1] and
// pinned ranges are subtracted from it. The result must:
// (a) be empty when the base is empty or fully pinned,
// (b) preserve all pinned seqs (they survive rotation),
// (c) cover every non-pinned seq in the base.
// Lifting the math out keeps the contract testable without
// standing up a full SHAMapStoreImp + database fixture.
testcase("detail::computeOnlineDeleteTargets");
using detail::computeOnlineDeleteTargets;
// Empty base window (minSeq >= lastRotated): no work.
{
RangeSet<std::uint32_t> empty;
BEAST_EXPECT(computeOnlineDeleteTargets(100, 100, empty).empty());
BEAST_EXPECT(computeOnlineDeleteTargets(101, 100, empty).empty());
}
// No pins: the entire base window is the target.
{
RangeSet<std::uint32_t> empty;
RangeSet<std::uint32_t> expected;
expected.insert(range(50u, 99u)); // [50, 100-1]
BEAST_EXPECT(sameRanges(
computeOnlineDeleteTargets(50, 100, empty), expected));
}
// Pins fully cover the base window: nothing to delete. This
// is the bug the pinning feature exists to prevent — pinned
// ranges getting silently rotated away.
{
RangeSet<std::uint32_t> pinned;
pinned.insert(range(50u, 99u));
BEAST_EXPECT(computeOnlineDeleteTargets(50, 100, pinned).empty());
}
// Pins extend beyond the base window: still nothing to delete.
{
RangeSet<std::uint32_t> pinned;
pinned.insert(range(0u, 1000u));
BEAST_EXPECT(computeOnlineDeleteTargets(50, 100, pinned).empty());
}
// Pins cover only the leading section of the base. Result is
// the trailing remainder.
{
RangeSet<std::uint32_t> pinned;
pinned.insert(range(50u, 70u));
RangeSet<std::uint32_t> expected;
expected.insert(range(71u, 99u));
BEAST_EXPECT(sameRanges(
computeOnlineDeleteTargets(50, 100, pinned), expected));
}
// Pins cover only the trailing section. Result is leading.
{
RangeSet<std::uint32_t> pinned;
pinned.insert(range(80u, 99u));
RangeSet<std::uint32_t> expected;
expected.insert(range(50u, 79u));
BEAST_EXPECT(sameRanges(
computeOnlineDeleteTargets(50, 100, pinned), expected));
}
// Pins cut a hole in the middle. Result is two disjoint
// intervals — both get deleted, the hole is preserved.
{
RangeSet<std::uint32_t> pinned;
pinned.insert(range(60u, 80u));
RangeSet<std::uint32_t> expected;
expected.insert(range(50u, 59u));
expected.insert(range(81u, 99u));
BEAST_EXPECT(sameRanges(
computeOnlineDeleteTargets(50, 100, pinned), expected));
}
// Multiple disjoint pinned ranges all inside the base. Result
// is the base minus all pinned holes.
{
RangeSet<std::uint32_t> pinned;
pinned.insert(range(55u, 60u));
pinned.insert(range(70u, 75u));
pinned.insert(range(90u, 95u));
RangeSet<std::uint32_t> expected;
expected.insert(range(50u, 54u));
expected.insert(range(61u, 69u));
expected.insert(range(76u, 89u));
expected.insert(range(96u, 99u));
BEAST_EXPECT(sameRanges(
computeOnlineDeleteTargets(50, 100, pinned), expected));
}
// Pinned ranges entirely outside the base window: no effect.
{
RangeSet<std::uint32_t> pinned;
pinned.insert(range(0u, 49u));
pinned.insert(range(100u, 200u));
RangeSet<std::uint32_t> expected;
expected.insert(range(50u, 99u));
BEAST_EXPECT(sameRanges(
computeOnlineDeleteTargets(50, 100, pinned), expected));
}
// Single-sequence base window with a single-sequence pin
// matching exactly: empty result.
{
RangeSet<std::uint32_t> pinned;
pinned.insert(range(99u, 99u));
BEAST_EXPECT(computeOnlineDeleteTargets(99, 100, pinned).empty());
}
}
void
run() override
{
testComputeOnlineDeleteTargets();
testClear();
testAutomatic();
testCanDelete();
testRotate();
testPinnedRangeRestoreRequiresPinnedData();
}
};
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+283 -94
View File
@@ -20453,7 +20453,7 @@ std::map<std::string, std::vector<uint8_t>> wasm = {
0,0
)));
ASSERT(34 == (e=util_keylet(buf, 34, KEYLET_AMM,
ASSERT(INVALID_ARGUMENT == (e=util_keylet(buf, 34, KEYLET_AMM,
SBUF(asset1), SBUF(asset2),
0,0
)));
@@ -20534,9 +20534,9 @@ std::map<std::string, std::vector<uint8_t>> wasm = {
0x6CU, 0x62U, 0x61U, 0x63U, 0x6BU, 0x00U, 0x02U, 0x03U, 0x65U, 0x6EU,
0x76U, 0x06U, 0x61U, 0x63U, 0x63U, 0x65U, 0x70U, 0x74U, 0x00U, 0x02U,
0x03U, 0x02U, 0x01U, 0x03U, 0x05U, 0x03U, 0x01U, 0x00U, 0x02U, 0x06U,
0x51U, 0x0DU, 0x7FU, 0x01U, 0x41U, 0xB0U, 0xA6U, 0x04U, 0x0BU, 0x7FU,
0x00U, 0x41U, 0xB0U, 0x26U, 0x0BU, 0x7FU, 0x00U, 0x41U, 0x80U, 0x08U,
0x0BU, 0x7FU, 0x00U, 0x41U, 0xB0U, 0xA6U, 0x04U, 0x0BU, 0x7FU, 0x00U,
0x51U, 0x0DU, 0x7FU, 0x01U, 0x41U, 0xC0U, 0xA6U, 0x04U, 0x0BU, 0x7FU,
0x00U, 0x41U, 0xBEU, 0x26U, 0x0BU, 0x7FU, 0x00U, 0x41U, 0x80U, 0x08U,
0x0BU, 0x7FU, 0x00U, 0x41U, 0xC0U, 0xA6U, 0x04U, 0x0BU, 0x7FU, 0x00U,
0x41U, 0x80U, 0x08U, 0x0BU, 0x7FU, 0x00U, 0x41U, 0xF0U, 0x08U, 0x0BU,
0x7FU, 0x00U, 0x41U, 0x90U, 0x09U, 0x0BU, 0x7FU, 0x00U, 0x41U, 0xE4U,
0x08U, 0x0BU, 0x7FU, 0x00U, 0x41U, 0x80U, 0x08U, 0x0BU, 0x7FU, 0x00U,
@@ -21016,64 +21016,64 @@ std::map<std::string, std::vector<uint8_t>> wasm = {
0x1AU, 0x0BU, 0x02U, 0x40U, 0x20U, 0x01U, 0x41U, 0x22U, 0x41U, 0x1BU,
0x41U, 0xB0U, 0x89U, 0x80U, 0x80U, 0x00U, 0x41U, 0x28U, 0x41U, 0xE0U,
0x89U, 0x80U, 0x80U, 0x00U, 0x41U, 0x28U, 0x41U, 0x00U, 0x41U, 0x00U,
0x10U, 0x81U, 0x80U, 0x80U, 0x80U, 0x00U, 0x42U, 0x22U, 0x51U, 0x0DU,
0x00U, 0x41U, 0x89U, 0x9FU, 0x80U, 0x80U, 0x00U, 0x41U, 0xCDU, 0x00U,
0x10U, 0x81U, 0x80U, 0x80U, 0x80U, 0x00U, 0x42U, 0x79U, 0x51U, 0x0DU,
0x00U, 0x41U, 0x89U, 0x9FU, 0x80U, 0x80U, 0x00U, 0x41U, 0xDBU, 0x00U,
0x42U, 0xD0U, 0x04U, 0x10U, 0x82U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU,
0x0BU, 0x02U, 0x40U, 0x20U, 0x01U, 0x41U, 0x22U, 0x41U, 0x1CU, 0x41U,
0xF0U, 0x88U, 0x80U, 0x80U, 0x00U, 0x41U, 0x14U, 0x41U, 0x90U, 0x89U,
0x80U, 0x80U, 0x00U, 0x41U, 0x14U, 0x41U, 0x00U, 0x41U, 0x00U, 0x10U,
0x81U, 0x80U, 0x80U, 0x80U, 0x00U, 0x42U, 0x79U, 0x51U, 0x0DU, 0x00U,
0x41U, 0xD6U, 0x9FU, 0x80U, 0x80U, 0x00U, 0x41U, 0xD4U, 0x00U, 0x42U,
0x41U, 0xE4U, 0x9FU, 0x80U, 0x80U, 0x00U, 0x41U, 0xD4U, 0x00U, 0x42U,
0xD5U, 0x04U, 0x10U, 0x82U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU,
0x02U, 0x40U, 0x20U, 0x01U, 0x41U, 0x22U, 0x41U, 0x1DU, 0x41U, 0xF0U,
0x88U, 0x80U, 0x80U, 0x00U, 0x41U, 0x14U, 0x41U, 0x90U, 0x89U, 0x80U,
0x80U, 0x00U, 0x41U, 0x14U, 0x41U, 0x00U, 0x41U, 0x00U, 0x10U, 0x81U,
0x80U, 0x80U, 0x80U, 0x00U, 0x42U, 0x79U, 0x51U, 0x0DU, 0x00U, 0x41U,
0xAAU, 0xA0U, 0x80U, 0x80U, 0x00U, 0x41U, 0xE3U, 0x00U, 0x42U, 0xDAU,
0xB8U, 0xA0U, 0x80U, 0x80U, 0x00U, 0x41U, 0xE3U, 0x00U, 0x42U, 0xDAU,
0x04U, 0x10U, 0x82U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, 0x02U,
0x40U, 0x20U, 0x01U, 0x41U, 0x22U, 0x41U, 0x1EU, 0x41U, 0xF0U, 0x88U,
0x80U, 0x80U, 0x00U, 0x41U, 0x14U, 0x41U, 0x90U, 0x89U, 0x80U, 0x80U,
0x00U, 0x41U, 0x14U, 0x41U, 0x00U, 0x41U, 0x00U, 0x10U, 0x81U, 0x80U,
0x80U, 0x80U, 0x00U, 0x42U, 0x79U, 0x51U, 0x0DU, 0x00U, 0x41U, 0x8DU,
0x80U, 0x80U, 0x00U, 0x42U, 0x79U, 0x51U, 0x0DU, 0x00U, 0x41U, 0x9BU,
0xA1U, 0x80U, 0x80U, 0x00U, 0x41U, 0xF2U, 0x00U, 0x42U, 0xDFU, 0x04U,
0x10U, 0x82U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, 0x02U, 0x40U,
0x20U, 0x01U, 0x41U, 0x22U, 0x41U, 0x1FU, 0x41U, 0xF0U, 0x88U, 0x80U,
0x80U, 0x00U, 0x41U, 0x14U, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U,
0x41U, 0x00U, 0x10U, 0x81U, 0x80U, 0x80U, 0x80U, 0x00U, 0x42U, 0x79U,
0x51U, 0x0DU, 0x00U, 0x41U, 0xFFU, 0xA1U, 0x80U, 0x80U, 0x00U, 0x41U,
0x51U, 0x0DU, 0x00U, 0x41U, 0x8DU, 0xA2U, 0x80U, 0x80U, 0x00U, 0x41U,
0xCDU, 0x00U, 0x42U, 0xE5U, 0x04U, 0x10U, 0x82U, 0x80U, 0x80U, 0x80U,
0x00U, 0x1AU, 0x0BU, 0x02U, 0x40U, 0x20U, 0x01U, 0x41U, 0x22U, 0x41U,
0x20U, 0x41U, 0xF0U, 0x88U, 0x80U, 0x80U, 0x00U, 0x41U, 0x14U, 0x41U,
0x03U, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x10U, 0x81U, 0x80U,
0x80U, 0x80U, 0x00U, 0x42U, 0x22U, 0x51U, 0x0DU, 0x00U, 0x41U, 0xCCU,
0x80U, 0x80U, 0x00U, 0x42U, 0x22U, 0x51U, 0x0DU, 0x00U, 0x41U, 0xDAU,
0xA2U, 0x80U, 0x80U, 0x00U, 0x41U, 0xC3U, 0x00U, 0x42U, 0xEBU, 0x04U,
0x10U, 0x82U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, 0x02U, 0x40U,
0x20U, 0x01U, 0x41U, 0x22U, 0x41U, 0x21U, 0x41U, 0xF0U, 0x88U, 0x80U,
0x80U, 0x00U, 0x41U, 0x14U, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U,
0x41U, 0x00U, 0x10U, 0x81U, 0x80U, 0x80U, 0x80U, 0x00U, 0x42U, 0x79U,
0x51U, 0x0DU, 0x00U, 0x41U, 0x8FU, 0xA3U, 0x80U, 0x80U, 0x00U, 0x41U,
0x51U, 0x0DU, 0x00U, 0x41U, 0x9DU, 0xA3U, 0x80U, 0x80U, 0x00U, 0x41U,
0xDAU, 0x00U, 0x42U, 0xF1U, 0x04U, 0x10U, 0x82U, 0x80U, 0x80U, 0x80U,
0x00U, 0x1AU, 0x0BU, 0x02U, 0x40U, 0x20U, 0x01U, 0x41U, 0x22U, 0x41U,
0x22U, 0x41U, 0xF0U, 0x88U, 0x80U, 0x80U, 0x00U, 0x41U, 0x14U, 0x41U,
0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x10U, 0x81U, 0x80U,
0x80U, 0x80U, 0x00U, 0x42U, 0x79U, 0x51U, 0x0DU, 0x00U, 0x41U, 0xE9U,
0x80U, 0x80U, 0x00U, 0x42U, 0x79U, 0x51U, 0x0DU, 0x00U, 0x41U, 0xF7U,
0xA3U, 0x80U, 0x80U, 0x00U, 0x41U, 0xD1U, 0x00U, 0x42U, 0xF7U, 0x04U,
0x10U, 0x82U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, 0x02U, 0x40U,
0x20U, 0x01U, 0x41U, 0x22U, 0x41U, 0x23U, 0x41U, 0xF0U, 0x88U, 0x80U,
0x80U, 0x00U, 0x41U, 0x14U, 0x41U, 0x90U, 0x89U, 0x80U, 0x80U, 0x00U,
0x41U, 0x14U, 0x41U, 0x00U, 0x41U, 0x00U, 0x10U, 0x81U, 0x80U, 0x80U,
0x80U, 0x00U, 0x42U, 0x79U, 0x51U, 0x0DU, 0x00U, 0x41U, 0xBAU, 0xA4U,
0x80U, 0x00U, 0x42U, 0x79U, 0x51U, 0x0DU, 0x00U, 0x41U, 0xC8U, 0xA4U,
0x80U, 0x80U, 0x00U, 0x41U, 0xD8U, 0x00U, 0x42U, 0xFCU, 0x04U, 0x10U,
0x82U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, 0x02U, 0x40U, 0x20U,
0x01U, 0x41U, 0x22U, 0x41U, 0x24U, 0x41U, 0xF0U, 0x88U, 0x80U, 0x80U,
0x00U, 0x41U, 0x14U, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x41U,
0x00U, 0x10U, 0x81U, 0x80U, 0x80U, 0x80U, 0x00U, 0x42U, 0x79U, 0x51U,
0x0DU, 0x00U, 0x41U, 0x92U, 0xA5U, 0x80U, 0x80U, 0x00U, 0x41U, 0xDDU,
0x0DU, 0x00U, 0x41U, 0xA0U, 0xA5U, 0x80U, 0x80U, 0x00U, 0x41U, 0xDDU,
0x00U, 0x42U, 0x82U, 0x05U, 0x10U, 0x82U, 0x80U, 0x80U, 0x80U, 0x00U,
0x1AU, 0x0BU, 0x02U, 0x40U, 0x20U, 0x01U, 0x41U, 0x22U, 0x41U, 0x1AU,
0x41U, 0xF0U, 0x88U, 0x80U, 0x80U, 0x00U, 0x41U, 0x14U, 0x41U, 0x01U,
0x41U, 0x00U, 0x41U, 0x00U, 0x41U, 0x00U, 0x10U, 0x81U, 0x80U, 0x80U,
0x80U, 0x00U, 0x42U, 0x22U, 0x51U, 0x0DU, 0x00U, 0x41U, 0xEFU, 0xA5U,
0x80U, 0x00U, 0x42U, 0x22U, 0x51U, 0x0DU, 0x00U, 0x41U, 0xFDU, 0xA5U,
0x80U, 0x80U, 0x00U, 0x41U, 0xC1U, 0x00U, 0x42U, 0x84U, 0x05U, 0x10U,
0x82U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, 0x02U, 0x40U, 0x02U,
0x40U, 0x20U, 0x01U, 0x29U, 0x03U, 0x00U, 0x42U, 0x80U, 0x82U, 0xDDU,
@@ -21088,7 +21088,7 @@ std::map<std::string, std::vector<uint8_t>> wasm = {
0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, 0x41U, 0x00U, 0x41U, 0x00U,
0x42U, 0x00U, 0x10U, 0x83U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x20U,
0x01U, 0x41U, 0x30U, 0x6AU, 0x24U, 0x80U, 0x80U, 0x80U, 0x80U, 0x00U,
0x20U, 0x02U, 0x0BU, 0x0BU, 0xBFU, 0x1EU, 0x02U, 0x00U, 0x41U, 0x80U,
0x20U, 0x02U, 0x0BU, 0x0BU, 0xCDU, 0x1EU, 0x02U, 0x00U, 0x41U, 0x80U,
0x08U, 0x0BU, 0x88U, 0x02U, 0xC5U, 0xD0U, 0xF3U, 0x4BU, 0x0AU, 0x19U,
0x05U, 0xBCU, 0x3BU, 0x29U, 0xAAU, 0x1BU, 0xE1U, 0x39U, 0xFEU, 0x04U,
0xD6U, 0x0CU, 0x86U, 0x94U, 0xD3U, 0x95U, 0x0AU, 0x8DU, 0x80U, 0x25U,
@@ -21116,7 +21116,7 @@ std::map<std::string, std::vector<uint8_t>> wasm = {
0x45U, 0x48U, 0x52U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x3AU, 0x51U,
0x8AU, 0x22U, 0x53U, 0x81U, 0x60U, 0x84U, 0x1CU, 0x14U, 0x32U, 0xFEU,
0x6FU, 0x3EU, 0x6DU, 0x6EU, 0x76U, 0x29U, 0xFBU, 0xBAU, 0x00U, 0x41U,
0x88U, 0x0AU, 0x0BU, 0xA8U, 0x1CU, 0x75U, 0x74U, 0x69U, 0x6CU, 0x5FU,
0x88U, 0x0AU, 0x0BU, 0xB6U, 0x1CU, 0x75U, 0x74U, 0x69U, 0x6CU, 0x5FU,
0x6BU, 0x65U, 0x79U, 0x6CU, 0x65U, 0x74U, 0x28U, 0x31U, 0x30U, 0x30U,
0x30U, 0x30U, 0x30U, 0x30U, 0x2CU, 0x20U, 0x33U, 0x34U, 0x2CU, 0x20U,
0x4BU, 0x45U, 0x59U, 0x4CU, 0x45U, 0x54U, 0x5FU, 0x53U, 0x4BU, 0x49U,
@@ -21385,41 +21385,31 @@ std::map<std::string, std::vector<uint8_t>> wasm = {
0x54U, 0x45U, 0x5FU, 0x44U, 0x49U, 0x52U, 0x2CU, 0x20U, 0x53U, 0x42U,
0x55U, 0x46U, 0x28U, 0x61U, 0x29U, 0x2CU, 0x20U, 0x53U, 0x42U, 0x55U,
0x46U, 0x28U, 0x6EU, 0x73U, 0x29U, 0x2CU, 0x20U, 0x30U, 0x2CU, 0x30U,
0x20U, 0x29U, 0x29U, 0x00U, 0x33U, 0x34U, 0x20U, 0x3DU, 0x3DU, 0x20U,
0x28U, 0x65U, 0x3DU, 0x75U, 0x74U, 0x69U, 0x6CU, 0x5FU, 0x6BU, 0x65U,
0x79U, 0x6CU, 0x65U, 0x74U, 0x28U, 0x62U, 0x75U, 0x66U, 0x2CU, 0x20U,
0x33U, 0x34U, 0x2CU, 0x20U, 0x4BU, 0x45U, 0x59U, 0x4CU, 0x45U, 0x54U,
0x5FU, 0x41U, 0x4DU, 0x4DU, 0x2CU, 0x20U, 0x53U, 0x42U, 0x55U, 0x46U,
0x28U, 0x61U, 0x73U, 0x73U, 0x65U, 0x74U, 0x31U, 0x29U, 0x2CU, 0x20U,
0x53U, 0x42U, 0x55U, 0x46U, 0x28U, 0x61U, 0x73U, 0x73U, 0x65U, 0x74U,
0x32U, 0x29U, 0x2CU, 0x20U, 0x30U, 0x2CU, 0x30U, 0x20U, 0x29U, 0x29U,
0x00U, 0x49U, 0x4EU, 0x56U, 0x41U, 0x4CU, 0x49U, 0x44U, 0x5FU, 0x41U,
0x52U, 0x47U, 0x55U, 0x4DU, 0x45U, 0x4EU, 0x54U, 0x20U, 0x3DU, 0x3DU,
0x20U, 0x28U, 0x65U, 0x3DU, 0x75U, 0x74U, 0x69U, 0x6CU, 0x5FU, 0x6BU,
0x65U, 0x79U, 0x6CU, 0x65U, 0x74U, 0x28U, 0x62U, 0x75U, 0x66U, 0x2CU,
0x20U, 0x33U, 0x34U, 0x2CU, 0x20U, 0x4BU, 0x45U, 0x59U, 0x4CU, 0x45U,
0x54U, 0x5FU, 0x42U, 0x52U, 0x49U, 0x44U, 0x47U, 0x45U, 0x2CU, 0x20U,
0x53U, 0x42U, 0x55U, 0x46U, 0x28U, 0x61U, 0x29U, 0x2CU, 0x20U, 0x53U,
0x42U, 0x55U, 0x46U, 0x28U, 0x62U, 0x29U, 0x2CU, 0x20U, 0x30U, 0x2CU,
0x30U, 0x20U, 0x29U, 0x29U, 0x00U, 0x49U, 0x4EU, 0x56U, 0x41U, 0x4CU,
0x49U, 0x44U, 0x5FU, 0x41U, 0x52U, 0x47U, 0x55U, 0x4DU, 0x45U, 0x4EU,
0x54U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x28U, 0x65U, 0x3DU, 0x75U, 0x74U,
0x69U, 0x6CU, 0x5FU, 0x6BU, 0x65U, 0x79U, 0x6CU, 0x65U, 0x74U, 0x28U,
0x62U, 0x75U, 0x66U, 0x2CU, 0x20U, 0x33U, 0x34U, 0x2CU, 0x20U, 0x4BU,
0x45U, 0x59U, 0x4CU, 0x45U, 0x54U, 0x5FU, 0x58U, 0x43U, 0x48U, 0x41U,
0x49U, 0x4EU, 0x5FU, 0x4FU, 0x57U, 0x4EU, 0x45U, 0x44U, 0x5FU, 0x43U,
0x4CU, 0x41U, 0x49U, 0x4DU, 0x5FU, 0x49U, 0x44U, 0x2CU, 0x20U, 0x53U,
0x42U, 0x55U, 0x46U, 0x28U, 0x61U, 0x29U, 0x2CU, 0x20U, 0x53U, 0x42U,
0x55U, 0x46U, 0x28U, 0x62U, 0x29U, 0x2CU, 0x20U, 0x30U, 0x2CU, 0x30U,
0x20U, 0x29U, 0x29U, 0x00U, 0x49U, 0x4EU, 0x56U, 0x41U, 0x4CU, 0x49U,
0x44U, 0x5FU, 0x41U, 0x52U, 0x47U, 0x55U, 0x4DU, 0x45U, 0x4EU, 0x54U,
0x20U, 0x3DU, 0x3DU, 0x20U, 0x28U, 0x65U, 0x3DU, 0x75U, 0x74U, 0x69U,
0x6CU, 0x5FU, 0x6BU, 0x65U, 0x79U, 0x6CU, 0x65U, 0x74U, 0x28U, 0x62U,
0x75U, 0x66U, 0x2CU, 0x20U, 0x33U, 0x34U, 0x2CU, 0x20U, 0x4BU, 0x45U,
0x59U, 0x4CU, 0x45U, 0x54U, 0x5FU, 0x58U, 0x43U, 0x48U, 0x41U, 0x49U,
0x4EU, 0x5FU, 0x4FU, 0x57U, 0x4EU, 0x45U, 0x44U, 0x5FU, 0x43U, 0x52U,
0x45U, 0x41U, 0x54U, 0x45U, 0x5FU, 0x41U, 0x43U, 0x43U, 0x4FU, 0x55U,
0x4EU, 0x54U, 0x5FU, 0x43U, 0x4CU, 0x41U, 0x49U, 0x4DU, 0x5FU, 0x49U,
0x59U, 0x4CU, 0x45U, 0x54U, 0x5FU, 0x41U, 0x4DU, 0x4DU, 0x2CU, 0x20U,
0x53U, 0x42U, 0x55U, 0x46U, 0x28U, 0x61U, 0x73U, 0x73U, 0x65U, 0x74U,
0x31U, 0x29U, 0x2CU, 0x20U, 0x53U, 0x42U, 0x55U, 0x46U, 0x28U, 0x61U,
0x73U, 0x73U, 0x65U, 0x74U, 0x32U, 0x29U, 0x2CU, 0x20U, 0x30U, 0x2CU,
0x30U, 0x20U, 0x29U, 0x29U, 0x00U, 0x49U, 0x4EU, 0x56U, 0x41U, 0x4CU,
0x49U, 0x44U, 0x5FU, 0x41U, 0x52U, 0x47U, 0x55U, 0x4DU, 0x45U, 0x4EU,
0x54U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x28U, 0x65U, 0x3DU, 0x75U, 0x74U,
0x69U, 0x6CU, 0x5FU, 0x6BU, 0x65U, 0x79U, 0x6CU, 0x65U, 0x74U, 0x28U,
0x62U, 0x75U, 0x66U, 0x2CU, 0x20U, 0x33U, 0x34U, 0x2CU, 0x20U, 0x4BU,
0x45U, 0x59U, 0x4CU, 0x45U, 0x54U, 0x5FU, 0x42U, 0x52U, 0x49U, 0x44U,
0x47U, 0x45U, 0x2CU, 0x20U, 0x53U, 0x42U, 0x55U, 0x46U, 0x28U, 0x61U,
0x29U, 0x2CU, 0x20U, 0x53U, 0x42U, 0x55U, 0x46U, 0x28U, 0x62U, 0x29U,
0x2CU, 0x20U, 0x30U, 0x2CU, 0x30U, 0x20U, 0x29U, 0x29U, 0x00U, 0x49U,
0x4EU, 0x56U, 0x41U, 0x4CU, 0x49U, 0x44U, 0x5FU, 0x41U, 0x52U, 0x47U,
0x55U, 0x4DU, 0x45U, 0x4EU, 0x54U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x28U,
0x65U, 0x3DU, 0x75U, 0x74U, 0x69U, 0x6CU, 0x5FU, 0x6BU, 0x65U, 0x79U,
0x6CU, 0x65U, 0x74U, 0x28U, 0x62U, 0x75U, 0x66U, 0x2CU, 0x20U, 0x33U,
0x34U, 0x2CU, 0x20U, 0x4BU, 0x45U, 0x59U, 0x4CU, 0x45U, 0x54U, 0x5FU,
0x58U, 0x43U, 0x48U, 0x41U, 0x49U, 0x4EU, 0x5FU, 0x4FU, 0x57U, 0x4EU,
0x45U, 0x44U, 0x5FU, 0x43U, 0x4CU, 0x41U, 0x49U, 0x4DU, 0x5FU, 0x49U,
0x44U, 0x2CU, 0x20U, 0x53U, 0x42U, 0x55U, 0x46U, 0x28U, 0x61U, 0x29U,
0x2CU, 0x20U, 0x53U, 0x42U, 0x55U, 0x46U, 0x28U, 0x62U, 0x29U, 0x2CU,
0x20U, 0x30U, 0x2CU, 0x30U, 0x20U, 0x29U, 0x29U, 0x00U, 0x49U, 0x4EU,
@@ -21427,58 +21417,70 @@ std::map<std::string, std::vector<uint8_t>> wasm = {
0x4DU, 0x45U, 0x4EU, 0x54U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x28U, 0x65U,
0x3DU, 0x75U, 0x74U, 0x69U, 0x6CU, 0x5FU, 0x6BU, 0x65U, 0x79U, 0x6CU,
0x65U, 0x74U, 0x28U, 0x62U, 0x75U, 0x66U, 0x2CU, 0x20U, 0x33U, 0x34U,
0x2CU, 0x20U, 0x4BU, 0x45U, 0x59U, 0x4CU, 0x45U, 0x54U, 0x5FU, 0x44U,
0x49U, 0x44U, 0x2CU, 0x20U, 0x53U, 0x42U, 0x55U, 0x46U, 0x28U, 0x61U,
0x29U, 0x2CU, 0x20U, 0x30U, 0x2CU, 0x30U, 0x2CU, 0x20U, 0x30U, 0x2CU,
0x30U, 0x20U, 0x29U, 0x29U, 0x00U, 0x33U, 0x34U, 0x20U, 0x3DU, 0x3DU,
0x20U, 0x28U, 0x65U, 0x3DU, 0x75U, 0x74U, 0x69U, 0x6CU, 0x5FU, 0x6BU,
0x65U, 0x79U, 0x6CU, 0x65U, 0x74U, 0x28U, 0x62U, 0x75U, 0x66U, 0x2CU,
0x20U, 0x33U, 0x34U, 0x2CU, 0x20U, 0x4BU, 0x45U, 0x59U, 0x4CU, 0x45U,
0x54U, 0x5FU, 0x4FU, 0x52U, 0x41U, 0x43U, 0x4CU, 0x45U, 0x2CU, 0x20U,
0x53U, 0x42U, 0x55U, 0x46U, 0x28U, 0x61U, 0x29U, 0x2CU, 0x20U, 0x33U,
0x2CU, 0x20U, 0x30U, 0x2CU, 0x20U, 0x30U, 0x2CU, 0x30U, 0x20U, 0x29U,
0x2CU, 0x20U, 0x4BU, 0x45U, 0x59U, 0x4CU, 0x45U, 0x54U, 0x5FU, 0x58U,
0x43U, 0x48U, 0x41U, 0x49U, 0x4EU, 0x5FU, 0x4FU, 0x57U, 0x4EU, 0x45U,
0x44U, 0x5FU, 0x43U, 0x52U, 0x45U, 0x41U, 0x54U, 0x45U, 0x5FU, 0x41U,
0x43U, 0x43U, 0x4FU, 0x55U, 0x4EU, 0x54U, 0x5FU, 0x43U, 0x4CU, 0x41U,
0x49U, 0x4DU, 0x5FU, 0x49U, 0x44U, 0x2CU, 0x20U, 0x53U, 0x42U, 0x55U,
0x46U, 0x28U, 0x61U, 0x29U, 0x2CU, 0x20U, 0x53U, 0x42U, 0x55U, 0x46U,
0x28U, 0x62U, 0x29U, 0x2CU, 0x20U, 0x30U, 0x2CU, 0x30U, 0x20U, 0x29U,
0x29U, 0x00U, 0x49U, 0x4EU, 0x56U, 0x41U, 0x4CU, 0x49U, 0x44U, 0x5FU,
0x41U, 0x52U, 0x47U, 0x55U, 0x4DU, 0x45U, 0x4EU, 0x54U, 0x20U, 0x3DU,
0x3DU, 0x20U, 0x28U, 0x65U, 0x3DU, 0x75U, 0x74U, 0x69U, 0x6CU, 0x5FU,
0x6BU, 0x65U, 0x79U, 0x6CU, 0x65U, 0x74U, 0x28U, 0x62U, 0x75U, 0x66U,
0x2CU, 0x20U, 0x33U, 0x34U, 0x2CU, 0x20U, 0x4BU, 0x45U, 0x59U, 0x4CU,
0x45U, 0x54U, 0x5FU, 0x4DU, 0x50U, 0x54U, 0x4FU, 0x4BU, 0x45U, 0x4EU,
0x5FU, 0x49U, 0x53U, 0x53U, 0x55U, 0x41U, 0x4EU, 0x43U, 0x45U, 0x2CU,
0x45U, 0x54U, 0x5FU, 0x44U, 0x49U, 0x44U, 0x2CU, 0x20U, 0x53U, 0x42U,
0x55U, 0x46U, 0x28U, 0x61U, 0x29U, 0x2CU, 0x20U, 0x30U, 0x2CU, 0x30U,
0x2CU, 0x20U, 0x30U, 0x2CU, 0x30U, 0x20U, 0x29U, 0x29U, 0x00U, 0x33U,
0x34U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x28U, 0x65U, 0x3DU, 0x75U, 0x74U,
0x69U, 0x6CU, 0x5FU, 0x6BU, 0x65U, 0x79U, 0x6CU, 0x65U, 0x74U, 0x28U,
0x62U, 0x75U, 0x66U, 0x2CU, 0x20U, 0x33U, 0x34U, 0x2CU, 0x20U, 0x4BU,
0x45U, 0x59U, 0x4CU, 0x45U, 0x54U, 0x5FU, 0x4FU, 0x52U, 0x41U, 0x43U,
0x4CU, 0x45U, 0x2CU, 0x20U, 0x53U, 0x42U, 0x55U, 0x46U, 0x28U, 0x61U,
0x29U, 0x2CU, 0x20U, 0x33U, 0x2CU, 0x20U, 0x30U, 0x2CU, 0x20U, 0x30U,
0x2CU, 0x30U, 0x20U, 0x29U, 0x29U, 0x00U, 0x49U, 0x4EU, 0x56U, 0x41U,
0x4CU, 0x49U, 0x44U, 0x5FU, 0x41U, 0x52U, 0x47U, 0x55U, 0x4DU, 0x45U,
0x4EU, 0x54U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x28U, 0x65U, 0x3DU, 0x75U,
0x74U, 0x69U, 0x6CU, 0x5FU, 0x6BU, 0x65U, 0x79U, 0x6CU, 0x65U, 0x74U,
0x28U, 0x62U, 0x75U, 0x66U, 0x2CU, 0x20U, 0x33U, 0x34U, 0x2CU, 0x20U,
0x4BU, 0x45U, 0x59U, 0x4CU, 0x45U, 0x54U, 0x5FU, 0x4DU, 0x50U, 0x54U,
0x4FU, 0x4BU, 0x45U, 0x4EU, 0x5FU, 0x49U, 0x53U, 0x53U, 0x55U, 0x41U,
0x4EU, 0x43U, 0x45U, 0x2CU, 0x20U, 0x53U, 0x42U, 0x55U, 0x46U, 0x28U,
0x61U, 0x29U, 0x2CU, 0x20U, 0x30U, 0x2CU, 0x30U, 0x2CU, 0x20U, 0x30U,
0x2CU, 0x30U, 0x20U, 0x29U, 0x29U, 0x00U, 0x49U, 0x4EU, 0x56U, 0x41U,
0x4CU, 0x49U, 0x44U, 0x5FU, 0x41U, 0x52U, 0x47U, 0x55U, 0x4DU, 0x45U,
0x4EU, 0x54U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x28U, 0x65U, 0x3DU, 0x75U,
0x74U, 0x69U, 0x6CU, 0x5FU, 0x6BU, 0x65U, 0x79U, 0x6CU, 0x65U, 0x74U,
0x28U, 0x62U, 0x75U, 0x66U, 0x2CU, 0x20U, 0x33U, 0x34U, 0x2CU, 0x20U,
0x4BU, 0x45U, 0x59U, 0x4CU, 0x45U, 0x54U, 0x5FU, 0x4DU, 0x50U, 0x54U,
0x4FU, 0x4BU, 0x45U, 0x4EU, 0x2CU, 0x20U, 0x53U, 0x42U, 0x55U, 0x46U,
0x28U, 0x61U, 0x29U, 0x2CU, 0x20U, 0x30U, 0x2CU, 0x30U, 0x2CU, 0x20U,
0x30U, 0x2CU, 0x30U, 0x20U, 0x29U, 0x29U, 0x00U, 0x49U, 0x4EU, 0x56U,
0x41U, 0x4CU, 0x49U, 0x44U, 0x5FU, 0x41U, 0x52U, 0x47U, 0x55U, 0x4DU,
0x45U, 0x4EU, 0x54U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x28U, 0x65U, 0x3DU,
0x75U, 0x74U, 0x69U, 0x6CU, 0x5FU, 0x6BU, 0x65U, 0x79U, 0x6CU, 0x65U,
0x74U, 0x28U, 0x62U, 0x75U, 0x66U, 0x2CU, 0x20U, 0x33U, 0x34U, 0x2CU,
0x20U, 0x4BU, 0x45U, 0x59U, 0x4CU, 0x45U, 0x54U, 0x5FU, 0x43U, 0x52U,
0x45U, 0x44U, 0x45U, 0x4EU, 0x54U, 0x49U, 0x41U, 0x4CU, 0x2CU, 0x20U,
0x53U, 0x42U, 0x55U, 0x46U, 0x28U, 0x61U, 0x29U, 0x2CU, 0x20U, 0x53U,
0x42U, 0x55U, 0x46U, 0x28U, 0x62U, 0x29U, 0x2CU, 0x20U, 0x30U, 0x2CU,
0x30U, 0x20U, 0x29U, 0x29U, 0x00U, 0x49U, 0x4EU, 0x56U, 0x41U, 0x4CU,
0x49U, 0x44U, 0x5FU, 0x41U, 0x52U, 0x47U, 0x55U, 0x4DU, 0x45U, 0x4EU,
0x54U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x28U, 0x65U, 0x3DU, 0x75U, 0x74U,
0x69U, 0x6CU, 0x5FU, 0x6BU, 0x65U, 0x79U, 0x6CU, 0x65U, 0x74U, 0x28U,
0x62U, 0x75U, 0x66U, 0x2CU, 0x20U, 0x33U, 0x34U, 0x2CU, 0x20U, 0x4BU,
0x45U, 0x59U, 0x4CU, 0x45U, 0x54U, 0x5FU, 0x50U, 0x45U, 0x52U, 0x4DU,
0x49U, 0x53U, 0x53U, 0x49U, 0x4FU, 0x4EU, 0x45U, 0x44U, 0x5FU, 0x44U,
0x4FU, 0x4DU, 0x41U, 0x49U, 0x4EU, 0x2CU, 0x20U, 0x53U, 0x42U, 0x55U,
0x46U, 0x28U, 0x61U, 0x29U, 0x2CU, 0x20U, 0x30U, 0x2CU, 0x30U, 0x2CU,
0x20U, 0x30U, 0x2CU, 0x30U, 0x20U, 0x29U, 0x29U, 0x00U, 0x33U, 0x34U,
0x20U, 0x3DU, 0x3DU, 0x20U, 0x28U, 0x65U, 0x3DU, 0x75U, 0x74U, 0x69U,
0x6CU, 0x5FU, 0x6BU, 0x65U, 0x79U, 0x6CU, 0x65U, 0x74U, 0x28U, 0x62U,
0x75U, 0x66U, 0x2CU, 0x20U, 0x33U, 0x34U, 0x2CU, 0x20U, 0x4BU, 0x45U,
0x59U, 0x4CU, 0x45U, 0x54U, 0x5FU, 0x43U, 0x52U, 0x4FU, 0x4EU, 0x2CU,
0x20U, 0x53U, 0x42U, 0x55U, 0x46U, 0x28U, 0x61U, 0x29U, 0x2CU, 0x20U,
0x30U, 0x2CU, 0x30U, 0x2CU, 0x20U, 0x30U, 0x2CU, 0x30U, 0x20U, 0x29U,
0x29U, 0x00U, 0x49U, 0x4EU, 0x56U, 0x41U, 0x4CU, 0x49U, 0x44U, 0x5FU,
0x41U, 0x52U, 0x47U, 0x55U, 0x4DU, 0x45U, 0x4EU, 0x54U, 0x20U, 0x3DU,
0x3DU, 0x20U, 0x28U, 0x65U, 0x3DU, 0x75U, 0x74U, 0x69U, 0x6CU, 0x5FU,
0x6BU, 0x65U, 0x79U, 0x6CU, 0x65U, 0x74U, 0x28U, 0x62U, 0x75U, 0x66U,
0x2CU, 0x20U, 0x33U, 0x34U, 0x2CU, 0x20U, 0x4BU, 0x45U, 0x59U, 0x4CU,
0x45U, 0x54U, 0x5FU, 0x4DU, 0x50U, 0x54U, 0x4FU, 0x4BU, 0x45U, 0x4EU,
0x2CU, 0x20U, 0x53U, 0x42U, 0x55U, 0x46U, 0x28U, 0x61U, 0x29U, 0x2CU,
0x20U, 0x30U, 0x2CU, 0x30U, 0x2CU, 0x20U, 0x30U, 0x2CU, 0x30U, 0x20U,
0x29U, 0x29U, 0x00U, 0x49U, 0x4EU, 0x56U, 0x41U, 0x4CU, 0x49U, 0x44U,
0x5FU, 0x41U, 0x52U, 0x47U, 0x55U, 0x4DU, 0x45U, 0x4EU, 0x54U, 0x20U,
0x3DU, 0x3DU, 0x20U, 0x28U, 0x65U, 0x3DU, 0x75U, 0x74U, 0x69U, 0x6CU,
0x5FU, 0x6BU, 0x65U, 0x79U, 0x6CU, 0x65U, 0x74U, 0x28U, 0x62U, 0x75U,
0x66U, 0x2CU, 0x20U, 0x33U, 0x34U, 0x2CU, 0x20U, 0x4BU, 0x45U, 0x59U,
0x4CU, 0x45U, 0x54U, 0x5FU, 0x43U, 0x52U, 0x45U, 0x44U, 0x45U, 0x4EU,
0x54U, 0x49U, 0x41U, 0x4CU, 0x2CU, 0x20U, 0x53U, 0x42U, 0x55U, 0x46U,
0x28U, 0x61U, 0x29U, 0x2CU, 0x20U, 0x53U, 0x42U, 0x55U, 0x46U, 0x28U,
0x62U, 0x29U, 0x2CU, 0x20U, 0x30U, 0x2CU, 0x30U, 0x20U, 0x29U, 0x29U,
0x00U, 0x49U, 0x4EU, 0x56U, 0x41U, 0x4CU, 0x49U, 0x44U, 0x5FU, 0x41U,
0x52U, 0x47U, 0x55U, 0x4DU, 0x45U, 0x4EU, 0x54U, 0x20U, 0x3DU, 0x3DU,
0x20U, 0x28U, 0x65U, 0x3DU, 0x75U, 0x74U, 0x69U, 0x6CU, 0x5FU, 0x6BU,
0x65U, 0x79U, 0x6CU, 0x65U, 0x74U, 0x28U, 0x62U, 0x75U, 0x66U, 0x2CU,
0x20U, 0x33U, 0x34U, 0x2CU, 0x20U, 0x4BU, 0x45U, 0x59U, 0x4CU, 0x45U,
0x54U, 0x5FU, 0x50U, 0x45U, 0x52U, 0x4DU, 0x49U, 0x53U, 0x53U, 0x49U,
0x4FU, 0x4EU, 0x45U, 0x44U, 0x5FU, 0x44U, 0x4FU, 0x4DU, 0x41U, 0x49U,
0x4EU, 0x2CU, 0x20U, 0x53U, 0x42U, 0x55U, 0x46U, 0x28U, 0x61U, 0x29U,
0x2CU, 0x20U, 0x30U, 0x2CU, 0x30U, 0x2CU, 0x20U, 0x30U, 0x2CU, 0x30U,
0x20U, 0x29U, 0x29U, 0x00U, 0x33U, 0x34U, 0x20U, 0x3DU, 0x3DU, 0x20U,
0x28U, 0x65U, 0x3DU, 0x75U, 0x74U, 0x69U, 0x6CU, 0x5FU, 0x6BU, 0x65U,
0x79U, 0x6CU, 0x65U, 0x74U, 0x28U, 0x62U, 0x75U, 0x66U, 0x2CU, 0x20U,
0x33U, 0x34U, 0x2CU, 0x20U, 0x4BU, 0x45U, 0x59U, 0x4CU, 0x45U, 0x54U,
0x5FU, 0x43U, 0x52U, 0x4FU, 0x4EU, 0x2CU, 0x20U, 0x53U, 0x42U, 0x55U,
0x46U, 0x28U, 0x61U, 0x29U, 0x2CU, 0x20U, 0x31U, 0x2CU, 0x20U, 0x30U,
0x2CU, 0x20U, 0x30U, 0x2CU, 0x20U, 0x30U, 0x29U, 0x29U, 0x00U,
0x31U, 0x2CU, 0x20U, 0x30U, 0x2CU, 0x20U, 0x30U, 0x2CU, 0x20U, 0x30U,
0x29U, 0x29U, 0x00U,
}},
/* ==== WASM: 89 ==== */
@@ -34110,6 +34112,193 @@ std::map<std::string, std::vector<uint8_t>> wasm = {
0x0BU,
}},
/* ==== WASM: 100 ==== */
{R"[test.hook](
#include <stdint.h>
extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code);
extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code);
extern int64_t emit (uint32_t write_ptr, uint32_t write_len, uint32_t read_ptr, uint32_t read_len);
extern int64_t hook_account(uint32_t write_ptr, uint32_t write_len);
extern int64_t etxn_reserve(uint32_t);
extern int64_t etxn_fee_base (uint32_t read_ptr, uint32_t read_len);
extern int64_t etxn_details (uint32_t write_ptr, uint32_t write_len);
extern int64_t ledger_seq (void);
#define SBUF(x) (uint32_t)x,sizeof(x)
// clang-format off
uint8_t txn[229] =
{
/* size, upto, field name */
/* 3, 0, tt = Invoke */ 0x12U, 0x00U, 0x63U,
/* 5, 3, flags */ 0x22U, 0x00U, 0x00U, 0x00U, 0x00U,
/* 5, 8, sequence */ 0x24U, 0x00U, 0x00U, 0x00U, 0x00U,
/* 6, 13, firstledgersequence */ 0x20U, 0x1AU, 0x00U, 0x00U, 0x00U, 0x00U,
/* 6, 19, lastledgersequence */ 0x20U, 0x1BU, 0x00U, 0x00U, 0x00U, 0x00U,
/* 9, 25, fee */ 0x68U, 0x40U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
/* 35, 34, signingpubkey */ 0x73U, 0x21U, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
/* 22, 69, account */ 0x81U, 0x14U, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
/* 116, 91, emit details */
/* 0, 229, */
};
// clang-format on
// TX BUILDER
#define FLAGS_OUT (txn + 4U)
#define FLS_OUT (txn + 15U)
#define LLS_OUT (txn + 21U)
#define FEE_OUT (txn + 26U)
#define ACCOUNT_OUT (txn + 71U)
#define EMIT_OUT (txn + 91U)
#define FLIP_ENDIAN_32(value) \
(uint32_t)(((value & 0xFFU) << 24) | ((value & 0xFF00U) << 8) | \
((value & 0xFF0000U) >> 8) | ((value & 0xFF000000U) >> 24))
#define SET_UINT32(ptr, value) *((uint32_t *)(ptr)) = FLIP_ENDIAN_32(value);
#define SET_NATIVE_AMOUNT(ptr, amount) \
do { \
uint8_t *b = (ptr); \
*b++ = 0b01000000 + ((amount >> 56) & 0b00111111); \
*b++ = (amount >> 48) & 0xFFU; \
*b++ = (amount >> 40) & 0xFFU; \
*b++ = (amount >> 32) & 0xFFU; \
*b++ = (amount >> 24) & 0xFFU; \
*b++ = (amount >> 16) & 0xFFU; \
*b++ = (amount >> 8) & 0xFFU; \
*b++ = (amount >> 0) & 0xFFU; \
} while (0)
#define PREPARE_TXN() \
do { \
etxn_reserve(1); \
uint32_t fls = (uint32_t)ledger_seq() + 1; \
SET_UINT32(FLS_OUT, fls); \
SET_UINT32(LLS_OUT, fls + 4); \
hook_account(ACCOUNT_OUT, 20); \
etxn_details(EMIT_OUT, 138U); \
int64_t fee = etxn_fee_base(SBUF(txn)); \
SET_NATIVE_AMOUNT(FEE_OUT, fee); \
} while (0)
int64_t cbak(uint32_t r)
{
_g(1,1);
return accept(0,0,0);
}
int64_t hook(uint32_t reserved)
{
_g(1,1);
PREPARE_TXN();
uint8_t emithash[32];
int64_t emit_result = emit(SBUF(emithash), SBUF(txn));
if (emit_result < 0)
return rollback(SBUF("Emit failed."), __LINE__);
return accept(SBUF("Emit succeeded."), __LINE__);
}
)[test.hook]",
{
0x00U, 0x61U, 0x73U, 0x6DU, 0x01U, 0x00U, 0x00U, 0x00U, 0x01U, 0x25U,
0x06U, 0x60U, 0x02U, 0x7FU, 0x7FU, 0x01U, 0x7FU, 0x60U, 0x03U, 0x7FU,
0x7FU, 0x7EU, 0x01U, 0x7EU, 0x60U, 0x01U, 0x7FU, 0x01U, 0x7EU, 0x60U,
0x00U, 0x01U, 0x7EU, 0x60U, 0x02U, 0x7FU, 0x7FU, 0x01U, 0x7EU, 0x60U,
0x04U, 0x7FU, 0x7FU, 0x7FU, 0x7FU, 0x01U, 0x7EU, 0x02U, 0x8FU, 0x01U,
0x09U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x02U, 0x5FU, 0x67U, 0x00U, 0x00U,
0x03U, 0x65U, 0x6EU, 0x76U, 0x06U, 0x61U, 0x63U, 0x63U, 0x65U, 0x70U,
0x74U, 0x00U, 0x01U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x0CU, 0x65U, 0x74U,
0x78U, 0x6EU, 0x5FU, 0x72U, 0x65U, 0x73U, 0x65U, 0x72U, 0x76U, 0x65U,
0x00U, 0x02U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x0AU, 0x6CU, 0x65U, 0x64U,
0x67U, 0x65U, 0x72U, 0x5FU, 0x73U, 0x65U, 0x71U, 0x00U, 0x03U, 0x03U,
0x65U, 0x6EU, 0x76U, 0x0CU, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x5FU, 0x61U,
0x63U, 0x63U, 0x6FU, 0x75U, 0x6EU, 0x74U, 0x00U, 0x04U, 0x03U, 0x65U,
0x6EU, 0x76U, 0x0CU, 0x65U, 0x74U, 0x78U, 0x6EU, 0x5FU, 0x64U, 0x65U,
0x74U, 0x61U, 0x69U, 0x6CU, 0x73U, 0x00U, 0x04U, 0x03U, 0x65U, 0x6EU,
0x76U, 0x0DU, 0x65U, 0x74U, 0x78U, 0x6EU, 0x5FU, 0x66U, 0x65U, 0x65U,
0x5FU, 0x62U, 0x61U, 0x73U, 0x65U, 0x00U, 0x04U, 0x03U, 0x65U, 0x6EU,
0x76U, 0x04U, 0x65U, 0x6DU, 0x69U, 0x74U, 0x00U, 0x05U, 0x03U, 0x65U,
0x6EU, 0x76U, 0x08U, 0x72U, 0x6FU, 0x6CU, 0x6CU, 0x62U, 0x61U, 0x63U,
0x6BU, 0x00U, 0x01U, 0x03U, 0x03U, 0x02U, 0x02U, 0x02U, 0x05U, 0x03U,
0x01U, 0x00U, 0x02U, 0x06U, 0x27U, 0x06U, 0x7FU, 0x01U, 0x41U, 0x90U,
0x8AU, 0x04U, 0x0BU, 0x7FU, 0x00U, 0x41U, 0x82U, 0x0AU, 0x0BU, 0x7FU,
0x00U, 0x41U, 0x80U, 0x08U, 0x0BU, 0x7FU, 0x00U, 0x41U, 0x90U, 0x8AU,
0x04U, 0x0BU, 0x7FU, 0x00U, 0x41U, 0x80U, 0x08U, 0x0BU, 0x7FU, 0x00U,
0x41U, 0x80U, 0x08U, 0x0BU, 0x07U, 0x0FU, 0x02U, 0x04U, 0x63U, 0x62U,
0x61U, 0x6BU, 0x00U, 0x09U, 0x04U, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x00U,
0x0AU, 0x0AU, 0xA8U, 0x83U, 0x00U, 0x02U, 0x99U, 0x80U, 0x00U, 0x00U,
0x41U, 0x01U, 0x41U, 0x01U, 0x10U, 0x80U, 0x80U, 0x80U, 0x80U, 0x00U,
0x1AU, 0x41U, 0x00U, 0x41U, 0x00U, 0x42U, 0x00U, 0x10U, 0x81U, 0x80U,
0x80U, 0x80U, 0x00U, 0x0BU, 0x88U, 0x83U, 0x00U, 0x02U, 0x03U, 0x7FU,
0x01U, 0x7EU, 0x23U, 0x80U, 0x80U, 0x80U, 0x80U, 0x00U, 0x41U, 0x20U,
0x6BU, 0x22U, 0x01U, 0x24U, 0x80U, 0x80U, 0x80U, 0x80U, 0x00U, 0x41U,
0x01U, 0x41U, 0x01U, 0x10U, 0x80U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU,
0x41U, 0x01U, 0x10U, 0x82U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x41U,
0x00U, 0x10U, 0x83U, 0x80U, 0x80U, 0x80U, 0x00U, 0xA7U, 0x22U, 0x02U,
0x41U, 0x05U, 0x6AU, 0x22U, 0x03U, 0x41U, 0x18U, 0x74U, 0x20U, 0x03U,
0x41U, 0x08U, 0x74U, 0x41U, 0x80U, 0x80U, 0xFCU, 0x07U, 0x71U, 0x72U,
0x20U, 0x03U, 0x41U, 0x08U, 0x76U, 0x41U, 0x80U, 0xFEU, 0x03U, 0x71U,
0x20U, 0x03U, 0x41U, 0x18U, 0x76U, 0x72U, 0x72U, 0x36U, 0x02U, 0x95U,
0x88U, 0x80U, 0x80U, 0x00U, 0x41U, 0x00U, 0x20U, 0x02U, 0x41U, 0x01U,
0x6AU, 0x22U, 0x03U, 0x41U, 0x18U, 0x74U, 0x20U, 0x03U, 0x41U, 0x08U,
0x74U, 0x41U, 0x80U, 0x80U, 0xFCU, 0x07U, 0x71U, 0x72U, 0x20U, 0x03U,
0x41U, 0x08U, 0x76U, 0x41U, 0x80U, 0xFEU, 0x03U, 0x71U, 0x20U, 0x03U,
0x41U, 0x18U, 0x76U, 0x72U, 0x72U, 0x36U, 0x02U, 0x8FU, 0x88U, 0x80U,
0x80U, 0x00U, 0x41U, 0xC7U, 0x88U, 0x80U, 0x80U, 0x00U, 0x41U, 0x14U,
0x10U, 0x84U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x41U, 0xDBU, 0x88U,
0x80U, 0x80U, 0x00U, 0x41U, 0x8AU, 0x01U, 0x10U, 0x85U, 0x80U, 0x80U,
0x80U, 0x00U, 0x1AU, 0x41U, 0x00U, 0x41U, 0x80U, 0x88U, 0x80U, 0x80U,
0x00U, 0x41U, 0xE5U, 0x01U, 0x10U, 0x86U, 0x80U, 0x80U, 0x80U, 0x00U,
0x22U, 0x04U, 0x3CU, 0x00U, 0xA1U, 0x88U, 0x80U, 0x80U, 0x00U, 0x41U,
0x00U, 0x20U, 0x04U, 0x42U, 0x08U, 0x88U, 0x3CU, 0x00U, 0xA0U, 0x88U,
0x80U, 0x80U, 0x00U, 0x41U, 0x00U, 0x20U, 0x04U, 0x42U, 0x10U, 0x88U,
0x3CU, 0x00U, 0x9FU, 0x88U, 0x80U, 0x80U, 0x00U, 0x41U, 0x00U, 0x20U,
0x04U, 0x42U, 0x18U, 0x88U, 0x3CU, 0x00U, 0x9EU, 0x88U, 0x80U, 0x80U,
0x00U, 0x41U, 0x00U, 0x20U, 0x04U, 0x42U, 0x20U, 0x88U, 0x3CU, 0x00U,
0x9DU, 0x88U, 0x80U, 0x80U, 0x00U, 0x41U, 0x00U, 0x20U, 0x04U, 0x42U,
0x28U, 0x88U, 0x3CU, 0x00U, 0x9CU, 0x88U, 0x80U, 0x80U, 0x00U, 0x41U,
0x00U, 0x20U, 0x04U, 0x42U, 0x30U, 0x88U, 0x3CU, 0x00U, 0x9BU, 0x88U,
0x80U, 0x80U, 0x00U, 0x41U, 0x00U, 0x20U, 0x04U, 0x42U, 0x38U, 0x88U,
0xA7U, 0x41U, 0x3FU, 0x71U, 0x41U, 0xC0U, 0x00U, 0x72U, 0x3AU, 0x00U,
0x9AU, 0x88U, 0x80U, 0x80U, 0x00U, 0x02U, 0x40U, 0x02U, 0x40U, 0x20U,
0x01U, 0x41U, 0x20U, 0x41U, 0x80U, 0x88U, 0x80U, 0x80U, 0x00U, 0x41U,
0xE5U, 0x01U, 0x10U, 0x87U, 0x80U, 0x80U, 0x80U, 0x00U, 0x42U, 0x7FU,
0x55U, 0x0DU, 0x00U, 0x41U, 0xE5U, 0x89U, 0x80U, 0x80U, 0x00U, 0x41U,
0x0DU, 0x42U, 0xD3U, 0x00U, 0x10U, 0x88U, 0x80U, 0x80U, 0x80U, 0x00U,
0x21U, 0x04U, 0x0CU, 0x01U, 0x0BU, 0x41U, 0xF2U, 0x89U, 0x80U, 0x80U,
0x00U, 0x41U, 0x10U, 0x42U, 0xD4U, 0x00U, 0x10U, 0x81U, 0x80U, 0x80U,
0x80U, 0x00U, 0x21U, 0x04U, 0x0BU, 0x20U, 0x01U, 0x41U, 0x20U, 0x6AU,
0x24U, 0x80U, 0x80U, 0x80U, 0x80U, 0x00U, 0x20U, 0x04U, 0x0BU, 0x0BU,
0x90U, 0x02U, 0x02U, 0x00U, 0x41U, 0x80U, 0x08U, 0x0BU, 0xE5U, 0x01U,
0x12U, 0x00U, 0x63U, 0x22U, 0x00U, 0x00U, 0x00U, 0x00U, 0x24U, 0x00U,
0x00U, 0x00U, 0x00U, 0x20U, 0x1AU, 0x00U, 0x00U, 0x00U, 0x00U, 0x20U,
0x1BU, 0x00U, 0x00U, 0x00U, 0x00U, 0x68U, 0x40U, 0x00U, 0x00U, 0x00U,
0x00U, 0x00U, 0x00U, 0x00U, 0x73U, 0x21U, 0x00U, 0x00U, 0x00U, 0x00U,
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x81U,
0x14U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U,
0x41U, 0xE5U, 0x09U, 0x0BU, 0x1DU, 0x45U, 0x6DU, 0x69U, 0x74U, 0x20U,
0x66U, 0x61U, 0x69U, 0x6CU, 0x65U, 0x64U, 0x2EU, 0x00U, 0x45U, 0x6DU,
0x69U, 0x74U, 0x20U, 0x73U, 0x75U, 0x63U, 0x63U, 0x65U, 0x65U, 0x64U,
0x65U, 0x64U, 0x2EU, 0x00U,
}},
};
}
} // namespace ripple
+819
View File
@@ -0,0 +1,819 @@
//------------------------------------------------------------------------------
/*
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 <test/jtx.h>
#include <test/jtx/network.h>
#include <xrpld/app/ledger/OpenLedger.h>
#include <xrpld/app/misc/Manifest.h>
#include <xrpld/app/tx/apply.h>
#include <xrpld/app/tx/detail/SetManifest.h>
#include <xrpld/core/Config.h>
#include <xrpld/ledger/OpenView.h>
#include <xrpl/basics/StringUtilities.h>
#include <xrpl/basics/strHex.h>
#include <xrpl/json/to_string.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/HashPrefix.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/PublicKey.h>
#include <xrpl/protocol/Sign.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/protocol/jss.h>
#include <xrpl/protocol/st.h>
#include <functional>
#include <limits>
#include <utility>
#include <vector>
namespace ripple {
namespace test {
/** Tests the OnChainManifests amendment: the SetManifest transactor, the
submit RPC path that builds its transaction, and reading manifests back out
of the ledger into the manifest cache.
*/
struct SetManifest_test : public beast::unit_test::suite
{
// A manifest transaction is unsigned, so the network id is mandatory and
// the network must be one that requires it (id > 1024).
static std::unique_ptr<Config>
makeConfig()
{
return jtx::network::makeNetworkConfig(
21337, "10", "1000000", "200000");
}
/** Builds a manifest signed by `master` nominating `ephemeral`.
A sequence of UINT32_MAX makes it a revocation, which by definition
names no signing key and carries no ephemeral signature.
*/
static std::string
makeManifest(
jtx::Account const& master,
jtx::Account const& ephemeral,
std::uint32_t seq)
{
STObject st(sfGeneric);
st[sfSequence] = seq;
st[sfPublicKey] = master.pk();
if (seq != std::numeric_limits<std::uint32_t>::max())
{
st[sfSigningPubKey] = ephemeral.pk();
sign(
st,
HashPrefix::manifest,
*publicKeyType(ephemeral.pk()),
ephemeral.sk());
}
sign(
st,
HashPrefix::manifest,
*publicKeyType(master.pk()),
master.sk(),
sfMasterSignature);
Serializer s;
st.add(s);
return std::string(static_cast<char const*>(s.data()), s.size());
}
/** Submits a manifest through the submit RPC.
The transaction is unsigned -- authority comes from the manifest's own
master and ephemeral signatures -- so it cannot be submitted through
env() the way a signed transaction can.
*/
static Json::Value
submit(jtx::Env& env, std::string const& manifest)
{
Json::Value params;
params[jss::manifest] = strHex(manifest);
return env.rpc("json", "submit", to_string(params))[jss::result];
}
static std::string
engineResult(Json::Value const& result)
{
return result[jss::engine_result].asString();
}
/** A well formed STObject that is not a well formed manifest.
The manifest format requires sfPublicKey and sfMasterSignature, so this
parses as an object and then fails applyTemplate.
*/
static std::string
makeUnparseableManifest()
{
STObject st(sfGeneric);
st[sfSequence] = 1;
Serializer s;
st.add(s);
return std::string(static_cast<char const*>(s.data()), s.size());
}
/** The envelope Submit would build for `manifest`, optionally tweaked.
makeSetManifestTx() rejects any manifest the transactor would reject,
and checkValidity() rejects any envelope whose signatures do not check
out, so several of the transactor's own rejections cannot be provoked
through the submit RPC at all. Building the envelope here and applying
it straight to the open view reaches them.
*/
static std::shared_ptr<STTx const>
envelope(
jtx::Env& env,
std::string const& manifest,
AccountID const& account,
std::function<void(STObject&)> const& tweak = {})
{
auto const build = [&](XRPAmount fee, bool tweaked) {
return std::make_shared<STTx const>(
ttMANIFEST_SET, [&](STObject& obj) {
obj.setAccountID(sfAccount, account);
obj.setFieldU32(sfSequence, 0);
obj.setFieldU32(sfNetworkID, env.app().config().NETWORK_ID);
obj.setFieldAmount(sfFee, fee);
obj.setFieldVL(sfSigningPubKey, Blob{});
obj.setFieldVL(sfTxnSignature, Blob{});
SerialIter mit{makeSlice(manifest)};
obj.peekFieldObject(sfManifest).set(mit);
if (tweaked && tweak)
tweak(obj);
});
};
// The same two-pass pricing makeSetManifestTx() does: encode once with
// a placeholder purely to have something to measure, then encode at the
// ceiling checkFee() will accept.
auto const base = SetManifest::calculateBaseFee(
*env.current(), *build(XRPAmount{0}, false));
return build(mulRatio(base, 12, 10, /*roundUp*/ true), true);
}
/** A mutable copy of a ledger object, suitable for the RawView interface.
Round-tripped through the wire format rather than copy constructed.
applyTemplate() rejects an object carrying a materialised soeDEFAULT
field that holds its default value, and that is exactly what reading one
out of a view hands back: an AccountRoot arrives with sfMintedNFTokens
present and zero. Serializing omits those fields and deserializing
re-materialises them.
*/
static std::shared_ptr<SLE>
rawCopy(std::shared_ptr<SLE const> const& sle)
{
Serializer s;
sle->add(s);
SerialIter sit{s.slice()};
return std::make_shared<SLE>(sit, sle->key());
}
/** Removes a ledger object from a view, if present.
OpenView exposes only the RawView interface, so the entry has to be
copied off the read side before it can be handed back to rawErase.
*/
static void
rawErase(OpenView& view, Keylet const& kl)
{
if (auto const sle = view.read(kl))
view.rawErase(rawCopy(sle));
}
/** Applies `tx` to a throwaway copy of the open ledger and reports the TER.
`prepare` runs against the same view first, so a test can corrupt state
that no ordinary transaction could produce. Nothing is retained.
*/
TER
applyDirect(
jtx::Env& env,
std::shared_ptr<STTx const> const& tx,
std::function<void(OpenView&)> const& prepare = {})
{
TER ret = tesSUCCESS;
env.app().openLedger().modify([&](OpenView& view, beast::Journal j) {
if (prepare)
prepare(view);
ret = ripple::apply(env.app(), view, *tx, tapNONE, j).ter;
return false; // discard, corrupt or otherwise
});
return ret;
}
void
testSubmission(FeatureBitset features)
{
testcase("submission");
using namespace jtx;
Env env{*this, makeConfig(), features};
auto const master = Account("master", KeyType::ed25519);
auto const ephemeral = Account("ephemeral", KeyType::ed25519);
env.fund(XRP(1000), master);
env.close();
BEAST_EXPECT(
engineResult(submit(env, makeManifest(master, ephemeral, 1))) ==
"tesSUCCESS");
env.close();
// A manifest is written twice so it can be found from either key, and
// each copy points at the other.
auto const byMaster = env.le(keylet::manifest(master.pk()));
auto const byEphemeral = env.le(keylet::manifest(ephemeral.pk()));
if (!BEAST_EXPECT(byMaster))
return;
if (!BEAST_EXPECT(byEphemeral))
return;
BEAST_EXPECT(byMaster->getAccountID(sfAccount) == master.id());
BEAST_EXPECT(byEphemeral->getAccountID(sfAccount) == master.id());
BEAST_EXPECT(byMaster->getFieldU32(sfSequence) == 1);
BEAST_EXPECT(
byMaster->getFieldH256(sfManifestID) ==
keylet::manifest(ephemeral.pk()).key);
BEAST_EXPECT(
byEphemeral->getFieldH256(sfManifestID) ==
keylet::manifest(master.pk()).key);
// Both signatures are mirrored so the object can be verified, and
// re-served, by any node that reads it.
BEAST_EXPECT(byMaster->isFieldPresent(sfMasterSignature));
BEAST_EXPECT(byMaster->isFieldPresent(sfSignature));
// The account root points at the master key's copy.
auto const sleAcct = env.le(master);
if (!BEAST_EXPECT(sleAcct))
return;
BEAST_EXPECT(
sleAcct->getFieldH256(sfManifestID) ==
keylet::manifest(master.pk()).key);
// The account sequence must be untouched: the transaction is unsigned
// and pinned to sequence 0, so consuming a sequence would let a third
// party burn the validator's sequence numbers -- and writing seq + 1
// would reset the account to 1.
BEAST_EXPECT(sleAcct->getFieldU32(sfSequence) == env.seq(master));
}
void
testUpdate(FeatureBitset features)
{
testcase("update and stale rejection");
using namespace jtx;
Env env{*this, makeConfig(), features};
auto const master = Account("master", KeyType::ed25519);
auto const eph1 = Account("eph1", KeyType::ed25519);
auto const eph2 = Account("eph2", KeyType::ed25519);
env.fund(XRP(1000), master);
env.close();
submit(env, makeManifest(master, eph1, 1));
env.close();
// Rotating the ephemeral key erases both old copies and writes two
// new ones, so the two can never drift apart.
BEAST_EXPECT(
engineResult(submit(env, makeManifest(master, eph2, 2))) ==
"tesSUCCESS");
env.close();
BEAST_EXPECT(!env.le(keylet::manifest(eph1.pk())));
if (!BEAST_EXPECT(env.le(keylet::manifest(eph2.pk()))))
return;
BEAST_EXPECT(
env.le(keylet::manifest(master.pk()))->getFieldU32(sfSequence) ==
2);
// Replaying the manifest we just applied, and anything older, is
// rejected on sequence. This is what prevents replay: the transaction
// is unsigned, so nothing else would.
BEAST_EXPECT(
engineResult(submit(env, makeManifest(master, eph2, 2))) ==
"tefPAST_MANIFEST_SEQ");
BEAST_EXPECT(
engineResult(submit(env, makeManifest(master, eph1, 1))) ==
"tefPAST_MANIFEST_SEQ");
}
void
testRevocation(FeatureBitset features)
{
testcase("revocation");
using namespace jtx;
Env env{*this, makeConfig(), features};
auto const master = Account("master", KeyType::ed25519);
auto const ephemeral = Account("ephemeral", KeyType::ed25519);
env.fund(XRP(1000), master);
env.close();
submit(env, makeManifest(master, ephemeral, 1));
env.close();
BEAST_EXPECT(
engineResult(submit(
env,
makeManifest(
master,
ephemeral,
std::numeric_limits<std::uint32_t>::max()))) ==
"tesSUCCESS");
env.close();
// A revocation has no signing key, so only the master key's copy
// exists and it points at nothing.
auto const byMaster = env.le(keylet::manifest(master.pk()));
if (!BEAST_EXPECT(byMaster))
return;
BEAST_EXPECT(!env.le(keylet::manifest(ephemeral.pk())));
BEAST_EXPECT(!byMaster->isFieldPresent(sfManifestID));
BEAST_EXPECT(!byMaster->isFieldPresent(sfSigningPubKey));
BEAST_EXPECT(
byMaster->getFieldU32(sfSequence) ==
std::numeric_limits<std::uint32_t>::max());
// Nothing supersedes a revocation.
BEAST_EXPECT(
engineResult(submit(env, makeManifest(master, ephemeral, 2))) ==
"tefREVOKED_MANIFEST");
}
void
testRetrieval(FeatureBitset features)
{
testcase("retrieval into the manifest cache");
using namespace jtx;
Env env{*this, makeConfig(), features};
auto const master = Account("master", KeyType::ed25519);
auto const eph1 = Account("eph1", KeyType::ed25519);
auto const eph2 = Account("eph2", KeyType::ed25519);
env.fund(XRP(1000), master);
env.close();
submit(env, makeManifest(master, eph1, 1));
env.close();
auto& cache = env.app().validatorManifests();
// Nothing has fed the cache yet, so an unknown key maps to itself.
BEAST_EXPECT(cache.getMasterKey(eph1.pk()) == eph1.pk());
// Reading the ledger resolves both directions of the mapping. The
// manifest is reconstructed from the ledger object and verified, so a
// lossy round trip would fail here rather than be accepted.
BEAST_EXPECT(cache.applyLedger(*env.closed(), {master.pk()}) == 1);
BEAST_EXPECT(cache.getMasterKey(eph1.pk()) == master.pk());
BEAST_EXPECT(cache.getSigningKey(master.pk()) == eph1.pk());
BEAST_EXPECT(cache.getSequence(master.pk()) == 1);
// Applying the same ledger again is a no-op: the cache is already at
// that sequence.
BEAST_EXPECT(cache.applyLedger(*env.closed(), {master.pk()}) == 0);
// A rotation on-ledger is picked up, and the superseded ephemeral key
// stops resolving.
submit(env, makeManifest(master, eph2, 2));
env.close();
BEAST_EXPECT(cache.applyLedger(*env.closed(), {master.pk()}) == 1);
BEAST_EXPECT(cache.getSigningKey(master.pk()) == eph2.pk());
BEAST_EXPECT(cache.getMasterKey(eph1.pk()) == eph1.pk());
// A key we never ask about is never read.
BEAST_EXPECT(cache.applyLedger(*env.closed(), {eph2.pk()}) == 0);
// A revocation reaches the cache too, and is the one thing that must
// never be lost: forgetting it would mean trusting the old key again.
submit(
env,
makeManifest(
master, eph2, std::numeric_limits<std::uint32_t>::max()));
env.close();
BEAST_EXPECT(cache.applyLedger(*env.closed(), {master.pk()}) == 1);
BEAST_EXPECT(cache.revoked(master.pk()));
}
void
testSigningKeyRetrieval(FeatureBitset features)
{
testcase("retrieval by ephemeral key");
using namespace jtx;
Env env{*this, makeConfig(), features};
auto const master = Account("master", KeyType::ed25519);
auto const eph1 = Account("eph1", KeyType::ed25519);
auto const eph2 = Account("eph2", KeyType::ed25519);
auto const stranger = Account("stranger", KeyType::ed25519);
env.fund(XRP(1000), master);
env.close();
submit(env, makeManifest(master, eph1, 1));
env.close();
auto& cache = env.app().validatorManifests();
// The situation applyLedger() cannot serve: a validation arrives
// signed by eph1 and the node holds no manifest naming it, so the
// master key to probe for is exactly what is missing.
BEAST_EXPECT(cache.getMasterKey(eph1.pk()) == eph1.pk());
BEAST_EXPECT(
cache.applyLedgerSigningKey(*env.closed(), eph1.pk()) ==
master.pk());
BEAST_EXPECT(cache.getMasterKey(eph1.pk()) == master.pk());
BEAST_EXPECT(cache.getSigningKey(master.pk()) == eph1.pk());
BEAST_EXPECT(cache.getSequence(master.pk()) == 1);
// A key with no manifest on-ledger resolves to nothing and leaves the
// cache untouched.
BEAST_EXPECT(
!cache.applyLedgerSigningKey(*env.closed(), stranger.pk()));
BEAST_EXPECT(cache.getMasterKey(stranger.pk()) == stranger.pk());
// A rotation is recovered from the new ephemeral key alone, and the
// superseded key stops resolving because its object is gone.
submit(env, makeManifest(master, eph2, 2));
env.close();
BEAST_EXPECT(
cache.applyLedgerSigningKey(*env.closed(), eph2.pk()) ==
master.pk());
BEAST_EXPECT(cache.getSigningKey(master.pk()) == eph2.pk());
BEAST_EXPECT(cache.getMasterKey(eph1.pk()) == eph1.pk());
BEAST_EXPECT(!env.le(keylet::manifest(eph1.pk())));
BEAST_EXPECT(!cache.applyLedgerSigningKey(*env.closed(), eph1.pk()));
// Asking again is answered from the cache, ahead of the per-ledger
// probe bookkeeping.
BEAST_EXPECT(
cache.applyLedgerSigningKey(*env.closed(), eph2.pk()) ==
master.pk());
// A master key is not a signing key. The object at its keylet is a
// perfectly good manifest and is ingested, but it binds eph2, not the
// master key, so nothing is reported for the key asked about.
BEAST_EXPECT(!cache.applyLedgerSigningKey(*env.closed(), master.pk()));
// A revoked master publishes no ephemeral object at all, so this
// direction goes quiet. The revocation still reaches the cache by
// master key, which is what applyLedger() is for.
submit(
env,
makeManifest(
master, eph2, std::numeric_limits<std::uint32_t>::max()));
env.close();
BEAST_EXPECT(!env.le(keylet::manifest(eph2.pk())));
BEAST_EXPECT(cache.applyLedger(*env.closed(), {master.pk()}) == 1);
BEAST_EXPECT(cache.revoked(master.pk()));
}
void
testMalformed(FeatureBitset features)
{
testcase("malformed submissions");
using namespace jtx;
Env env{*this, makeConfig(), features};
auto const master = Account("master", KeyType::ed25519);
auto const other = Account("other", KeyType::ed25519);
auto const ephemeral = Account("ephemeral", KeyType::ed25519);
env.fund(XRP(1000), master, other);
env.close();
// Not a manifest at all.
BEAST_EXPECT(
submit(env, "not a manifest")[jss::error].asString() ==
"invalidManifest");
// A manifest whose ephemeral signature does not check out.
{
auto blob = makeManifest(master, ephemeral, 1);
blob[blob.size() - 1] ^= 0xFF;
BEAST_EXPECT(
submit(env, blob)[jss::error].asString() == "invalidManifest");
}
// The master key's account must exist: it pays the fee and holds the
// pointer to the manifest.
auto const unfunded = Account("unfunded", KeyType::ed25519);
BEAST_EXPECT(
engineResult(submit(env, makeManifest(unfunded, ephemeral, 1))) ==
"terNO_ACCOUNT");
// An ephemeral key already claimed by a different account would
// collide with -- and clobber -- that account's manifest object.
submit(env, makeManifest(master, ephemeral, 1));
env.close();
BEAST_EXPECT(
engineResult(submit(env, makeManifest(other, ephemeral, 1))) ==
"tecDUPLICATE");
}
void
testEnvelopeRejections(FeatureBitset features)
{
testcase("envelope rejections");
using namespace jtx;
Env env{*this, makeConfig(), features};
auto const master = Account("master", KeyType::ed25519);
auto const other = Account("other", KeyType::ed25519);
auto const ephemeral = Account("ephemeral", KeyType::ed25519);
env.fund(XRP(1000), master, other);
env.close();
auto const good = makeManifest(master, ephemeral, 1);
// Sanity check: the unmodified envelope is the one Submit builds, so
// every rejection below is attributable to the tweak and nothing else.
BEAST_EXPECT(
applyDirect(env, envelope(env, good, master.id())) == tesSUCCESS);
// Any bit but tfFullyCanonicalSig (0x80000000), which is tfUniversal
// and so permitted on every transaction.
BEAST_EXPECT(
applyDirect(
env, envelope(env, good, master.id(), [](STObject& obj) {
obj.setFieldU32(sfFlags, 0x00000001);
})) == temINVALID_FLAG);
// sfManifest parses as an object but not as a manifest.
BEAST_EXPECT(
applyDirect(
env, envelope(env, makeUnparseableManifest(), master.id())) ==
temMALFORMED);
// A manifest whose signatures do not check out. checkValidity() would
// stop this before preflight, so only a direct apply reaches the
// transactor's own verify() call.
{
auto bad = good;
bad[bad.size() - 1] ^= 0xFF;
BEAST_EXPECT(
applyDirect(env, envelope(env, bad, master.id())) ==
temMALFORMED);
}
// The envelope's account must be the manifest's master key.
BEAST_EXPECT(
applyDirect(env, envelope(env, good, other.id())) == temMALFORMED);
// Every envelope field a relayer could otherwise choose is pinned.
for (auto const& [name, tweak] : std::vector<
std::pair<char const*, std::function<void(STObject&)>>>{
{"Sequence",
[](STObject& obj) { obj.setFieldU32(sfSequence, 1); }},
{"AccountTxnID",
[](STObject& obj) {
obj.setFieldH256(sfAccountTxnID, uint256{1});
}},
{"TicketSequence",
[](STObject& obj) { obj.setFieldU32(sfTicketSequence, 1); }},
{"SigningPubKey", [&](STObject& obj) {
obj.setFieldVL(sfSigningPubKey, master.pk().slice());
}}})
{
BEAST_EXPECTS(
applyDirect(env, envelope(env, good, master.id(), tweak)) ==
temMALFORMED,
name);
}
// sfFee is the one envelope field preflight cannot bound, because the
// base fee is not in scope until preclaim. checkFee() caps it instead.
auto const priced = envelope(env, good, master.id());
auto const ceiling = priced->getFieldAmount(sfFee).xrp();
BEAST_EXPECT(
applyDirect(
env, envelope(env, good, master.id(), [&](STObject& obj) {
obj.setFieldAmount(sfFee, ceiling + XRPAmount{1});
})) == temBAD_FEE);
// At the ceiling exactly, which is what Submit sends.
BEAST_EXPECT(
applyDirect(
env, envelope(env, good, master.id(), [&](STObject& obj) {
obj.setFieldAmount(sfFee, ceiling);
})) == tesSUCCESS);
}
void
testCorruptLedger(FeatureBitset features)
{
testcase("corrupt manifest state");
using namespace jtx;
Env env{*this, makeConfig(), features};
auto const master = Account("master", KeyType::ed25519);
auto const eph1 = Account("eph1", KeyType::ed25519);
auto const eph2 = Account("eph2", KeyType::ed25519);
env.fund(XRP(1000), master);
env.close();
BEAST_EXPECT(
engineResult(submit(env, makeManifest(master, eph1, 1))) ==
"tesSUCCESS");
env.close();
auto const update =
envelope(env, makeManifest(master, eph2, 2), master.id());
// sfManifestID on the account root points at nothing.
BEAST_EXPECT(applyDirect(env, update, [&](OpenView& view) {
rawErase(view, keylet::manifest(master.pk()));
}) == tefBAD_LEDGER);
// The master copy survives but the ephemeral copy it points at is
// gone.
BEAST_EXPECT(applyDirect(env, update, [&](OpenView& view) {
rawErase(view, keylet::manifest(eph1.pk()));
}) == tefBAD_LEDGER);
// The reverse: the account root has forgotten its manifest, so the
// erase pass is skipped and the keylet is found occupied.
BEAST_EXPECT(applyDirect(env, update, [&](OpenView& view) {
auto replacement =
rawCopy(view.read(keylet::account(master.id())));
replacement->makeFieldAbsent(sfManifestID);
view.rawReplace(replacement);
}) == tefBAD_LEDGER);
// None of the above was retained, so the ledger is still intact and an
// ordinary update still works.
BEAST_EXPECT(applyDirect(env, update) == tesSUCCESS);
}
void
testGossipSelection(FeatureBitset features)
{
testcase("gossip selection");
using namespace jtx;
Env env{*this, makeConfig(), features};
std::vector<Account> masters;
for (int i = 0; i < 4; ++i)
masters.emplace_back("gm" + std::to_string(i), KeyType::ed25519);
auto const ephemeral = Account("gossipeph", KeyType::ed25519);
for (auto const& m : masters)
env.fund(XRP(1000), m);
env.close();
// Each master needs its own ephemeral key: preclaim rejects a key
// already claimed by another account.
std::vector<Account> ephs;
for (int i = 0; i < 4; ++i)
ephs.emplace_back("ge" + std::to_string(i), KeyType::ed25519);
for (int i = 0; i < 4; ++i)
{
BEAST_EXPECT(
engineResult(
submit(env, makeManifest(masters[i], ephs[i], 1))) ==
"tesSUCCESS");
env.close();
}
ManifestCache cache;
hash_set<PublicKey> all;
for (auto const& m : masters)
all.insert(m.pk());
BEAST_EXPECT(cache.applyLedger(*env.closed(), all) == 4);
// The raw form is what a republishing node needs: the exact bytes the
// master key signed, plus the sequence to compare against the ledger.
auto const raw = cache.getRawManifest(masters[0].pk());
if (BEAST_EXPECT(raw))
{
BEAST_EXPECT(raw->first == 1);
BEAST_EXPECT(!raw->second.empty());
}
BEAST_EXPECT(!cache.getRawManifest(ephemeral.pk()));
// Pinning bumps the sequence so a cached gossip message is rebuilt,
// and pinning the same set again does not.
auto const seq = cache.sequence();
cache.pin({masters[0].pk(), masters[1].pk()});
BEAST_EXPECT(cache.sequence() > seq);
auto const seq2 = cache.sequence();
cache.pin({masters[0].pk(), masters[1].pk()});
BEAST_EXPECT(cache.sequence() == seq2);
// Everything is offered: two pinned plus two under the gossip limit.
std::size_t reserved = 0;
std::vector<PublicKey> offered;
cache.for_each_gossip_manifest(
[&](std::size_t n) { reserved = n; },
[&](Manifest const& m) { offered.push_back(m.masterKey); });
BEAST_EXPECT(reserved == 4);
BEAST_EXPECT(offered.size() == 4);
BEAST_EXPECT(
hash_set<PublicKey>(offered.begin(), offered.end()) == all);
// A pinned key with no manifest is counted in the reservation but not
// offered, since the reservation is only an upper bound.
cache.pin({masters[0].pk(), ephemeral.pk()});
offered.clear();
cache.for_each_gossip_manifest(
[&](std::size_t n) { reserved = n; },
[&](Manifest const& m) { offered.push_back(m.masterKey); });
BEAST_EXPECT(reserved == 5);
BEAST_EXPECT(offered.size() == 4);
}
void
testDisabled(FeatureBitset features)
{
testcase("amendment gate");
using namespace jtx;
Env env{*this, makeConfig(), features - featureOnChainManifests};
auto const master = Account("master", KeyType::ed25519);
auto const ephemeral = Account("ephemeral", KeyType::ed25519);
env.fund(XRP(1000), master);
env.close();
// The submitted transaction carries no account signature, so with the
// amendment off it would otherwise be rejected as unsigned and never
// reach preflight's temDISABLED. The RPC therefore refuses it up
// front, and says why.
auto const result = submit(env, makeManifest(master, ephemeral, 1));
BEAST_EXPECT(result[jss::error].asString() == "notEnabled");
BEAST_EXPECT(!result.isMember(jss::engine_result));
env.close();
BEAST_EXPECT(!env.le(keylet::manifest(master.pk())));
}
public:
void
run() override
{
using namespace test::jtx;
auto const sa = supported_amendments();
testSubmission(sa);
testUpdate(sa);
testRevocation(sa);
testRetrieval(sa);
testSigningKeyRetrieval(sa);
testMalformed(sa);
testEnvelopeRejections(sa);
testCorruptLedger(sa);
testGossipSelection(sa);
testDisabled(sa);
}
};
BEAST_DEFINE_TESTSUITE(SetManifest, app, ripple);
} // namespace test
} // namespace ripple
+14 -8
View File
@@ -207,19 +207,25 @@ private:
test::jtx::Env env{*this, envconfig(), features};
auto const alice = Account("alice");
auto const issuer = Account("issuer");
env.fund(XRP(1000), alice, issuer);
auto const issuer = Account::master;
env.fund(XRP(1000), alice);
env.close();
env(hook(issuer, {{hso(jtx::genesis::AcceptHook)}}, 0), fee(XRP(1)));
env.close();
// claim reward
env(reward::claim(alice), reward::issuer(issuer), ter(tesSUCCESS));
env(reward::claim(alice),
reward::issuer(issuer),
fee(XRP(1)),
ter(tesSUCCESS));
env.close();
// verify touch
validateTouch(env, alice, {"ClaimReward", "tesSUCCESS"});
auto const tt = env.current()->rules().enabled(featureTouch)
? "ClaimReward"
: "AccountSet";
: "SetHook";
validateTouch(env, issuer, {tt, "tesSUCCESS"});
}
@@ -880,9 +886,9 @@ private:
}
void
testSignersListSet(FeatureBitset features)
testSignerListSet(FeatureBitset features)
{
testcase("signers list set");
testcase("signer list set");
using namespace test::jtx;
using namespace std::literals;
@@ -895,7 +901,7 @@ private:
env.fund(XRP(1000), alice, signer1, signer2);
env.close();
// signers list set
// signer list set
env(signers(alice, 2, {{signer1, 1}, {signer2, 1}}), ter(tesSUCCESS));
env.close();
@@ -1384,7 +1390,7 @@ private:
testPaymentChannelFund(features);
testSetHook(features);
testSetRegularKey(features);
testSignersListSet(features);
testSignerListSet(features);
testTicketCreate(features);
testTrustSet(features);
testURITokenMint(features);
+5 -7
View File
@@ -140,8 +140,7 @@ struct XahauGenesis_test : public beast::unit_test::suite
bool skipTests = false,
bool const testFlag = false,
bool const badNetID = false,
uint32_t const expectedOwnerCount =
10 /** testFlag ? 10 : 14 (default) */)
uint32_t const expectedOwnerCount = 14 /** case for testFlag=false */)
{
using namespace jtx;
@@ -250,9 +249,7 @@ struct XahauGenesis_test : public beast::unit_test::suite
genesisAccRoot->getFieldAmount(sfBalance) ==
XahauGenesis::GenesisAmount);
BEAST_EXPECT(
genesisAccRoot->getFieldU32(sfOwnerCount) == !testFlag
? expectedOwnerCount
: 14);
genesisAccRoot->getFieldU32(sfOwnerCount) == expectedOwnerCount);
// ensure the definitions are correctly set
{
@@ -595,7 +592,8 @@ struct XahauGenesis_test : public beast::unit_test::suite
false,
true,
{},
3 /* IRR,IRD,IMC */ + members.size() + tables.size());
2 /*Hook objects *2 */ + 3 /* IRR,IRD,IMC HookStates */ +
members.size());
env.close();
env.close();
@@ -2327,7 +2325,7 @@ struct XahauGenesis_test : public beast::unit_test::suite
{
BEAST_EXPECT(
root->getFieldU32(sfOwnerCount) ==
mc * 2 + 2 + paramsCount);
(mc * 2 + 2 + paramsCount));
BEAST_EXPECT(root->getFieldU32(sfFlags) & lsfDisableMaster);
BEAST_EXPECT(root->getAccountID(sfRegularKey) == noAccount());
}
+128
View File
@@ -1513,6 +1513,133 @@ r.ripple.com:51235
}
}
void
testDatabasePinnedValidation()
{
testcase("DatabasePinned config validation");
// Test 1: pinned_type=rwdb in non-standalone mode (should throw)
{
Config c;
std::string toLoad =
"[node_db]\n"
"type=NuDB\n"
"path=main\n"
"online_delete=256\n"
"pinned_type=rwdb\n"
"pinned_path=pinned\n";
c.setupControl(true, true, false); // standalone = false
try
{
c.loadFromString(toLoad);
fail("Expected exception for non-durable pinned_type");
}
catch (std::runtime_error const& e)
{
BEAST_EXPECT(
std::string(e.what()).find("durable backend") !=
std::string::npos);
pass();
}
}
// Test 2: pinned_type=none in non-standalone mode (should throw)
{
Config c;
std::string toLoad =
"[node_db]\n"
"type=NuDB\n"
"path=main\n"
"online_delete=256\n"
"pinned_type=none\n"
"pinned_path=pinned\n";
c.setupControl(true, true, false); // standalone = false
try
{
c.loadFromString(toLoad);
fail("Expected exception for non-durable pinned_type");
}
catch (std::runtime_error const& e)
{
BEAST_EXPECT(
std::string(e.what()).find("durable backend") !=
std::string::npos);
pass();
}
}
// Test 3: pinned_type=rwdb in standalone mode (should succeed
// — standalone is unrestricted)
{
Config c;
std::string toLoad =
"[node_db]\n"
"type=NuDB\n"
"path=main\n"
"online_delete=256\n"
"pinned_type=rwdb\n"
"pinned_path=pinned\n";
c.setupControl(true, true, true); // standalone = true
try
{
c.loadFromString(toLoad);
pass();
}
catch (std::runtime_error const& e)
{
fail("Should not throw in standalone mode");
}
}
// Test 4: pinned_type=NuDB in non-standalone mode (should succeed)
{
Config c;
std::string toLoad =
"[node_db]\n"
"type=NuDB\n"
"path=main\n"
"online_delete=256\n"
"pinned_type=NuDB\n"
"pinned_path=pinned\n";
c.setupControl(true, true, false); // standalone = false
try
{
c.loadFromString(toLoad);
pass();
}
catch (std::runtime_error const& e)
{
fail("Should not throw for durable pinned_type");
}
}
// Test 5: pinned_type without online_delete (should throw)
{
Config c;
std::string toLoad =
"[node_db]\n"
"type=NuDB\n"
"path=main\n"
"pinned_type=NuDB\n"
"pinned_path=pinned\n";
c.setupControl(true, true, false);
try
{
c.loadFromString(toLoad);
fail(
"Expected exception for pinned_type without "
"online_delete");
}
catch (std::runtime_error const& e)
{
BEAST_EXPECT(
std::string(e.what()).find("online_delete") !=
std::string::npos);
pass();
}
}
}
void
testOverlay()
{
@@ -1604,6 +1731,7 @@ r.ripple.com:51235
testGetters();
testAmendment();
testRWDBOnlineDelete();
testDatabasePinnedValidation();
testOverlay();
testNetworkID();
}
+10 -1
View File
@@ -125,7 +125,6 @@ class AMM
STAmount const asset1_;
STAmount const asset2_;
uint256 const ammID_;
IOUAmount const initialLPTokens_;
bool log_;
bool doClose_;
// Predict next purchase price
@@ -138,6 +137,7 @@ class AMM
std::uint32_t const fee_;
AccountID const ammAccount_;
Issue const lptIssue_;
IOUAmount const initialLPTokens_;
public:
AMM(Env& env,
@@ -194,6 +194,12 @@ public:
Issue const& issue2,
std::optional<AccountID> const& account = std::nullopt) const;
std::tuple<STAmount, STAmount, STAmount>
balances(std::optional<AccountID> const& account = std::nullopt) const
{
return balances(asset1_.get<Issue>(), asset2_.get<Issue>(), account);
}
[[nodiscard]] bool
expectLPTokens(AccountID const& account, IOUAmount const& tokens) const;
@@ -428,6 +434,9 @@ private:
[[nodiscard]] bool
expectAuctionSlot(auto&& cb) const;
IOUAmount
initialTokens();
};
namespace amm {
+16 -1
View File
@@ -33,6 +33,15 @@ class AMM;
enum class Fund { All, Acct, Gw, IOUOnly };
struct TestAMMArg
{
std::optional<std::pair<STAmount, STAmount>> pool = std::nullopt;
std::uint16_t tfee = 0;
std::optional<jtx::ter> ter = std::nullopt;
std::vector<FeatureBitset> features = {supported_amendments()};
bool noLog = false;
};
void
fund(
jtx::Env& env,
@@ -84,7 +93,13 @@ protected:
std::optional<std::pair<STAmount, STAmount>> const& pool = std::nullopt,
std::uint16_t tfee = 0,
std::optional<jtx::ter> const& ter = std::nullopt,
std::vector<FeatureBitset> const& features = {supported_amendments()});
std::vector<FeatureBitset> const& features = {
supported_amendments() | featureAMM | featureAMMClawback});
void
testAMM(
std::function<void(jtx::AMM&, jtx::Env&)>&& cb,
TestAMMArg const& arg);
};
class AMMTest : public jtx::AMMTestBase
+6
View File
@@ -646,6 +646,12 @@ public:
void
disableFeature(uint256 const feature);
bool
enabled(uint256 feature) const
{
return current()->rules().enabled(feature);
}
private:
void
fund(bool setDefaultRipple, STAmount const& amount, Account const& account);
+7 -8
View File
@@ -18,8 +18,9 @@
//==============================================================================
#include <test/jtx/AMM.h>
#include <test/jtx/Env.h>
#include <xrpld/app/misc/AMMHelpers.h>
#include <xrpld/app/misc/AMMUtils.h>
#include <xrpld/rpc/detail/RPCHelpers.h>
#include <xrpl/protocol/AMMCore.h>
@@ -30,7 +31,7 @@ namespace ripple {
namespace test {
namespace jtx {
static Number
[[maybe_unused]] static Number
number(STAmount const& a)
{
if (isXRP(a))
@@ -38,12 +39,10 @@ number(STAmount const& a)
return a;
}
static IOUAmount
initialTokens(STAmount const& asset1, STAmount const& asset2)
IOUAmount
AMM::initialTokens()
{
auto const product = number(asset1) * number(asset2);
return (IOUAmount)(product.mantissa() >= 0 ? root2(product)
: root2(-product));
return getLPTokensBalance();
}
AMM::AMM(
@@ -64,7 +63,6 @@ AMM::AMM(
, asset1_(asset1)
, asset2_(asset2)
, ammID_(keylet::amm(asset1_.issue(), asset2_.issue()).key)
, initialLPTokens_(initialTokens(asset1, asset2))
, log_(log)
, doClose_(close)
, lastPurchasePrice_(0)
@@ -77,6 +75,7 @@ AMM::AMM(
asset1_.issue().currency,
asset2_.issue().currency,
ammAccount_))
, initialLPTokens_(initialTokens())
{
}
+30 -7
View File
@@ -17,9 +17,9 @@
*/
//==============================================================================
#include <test/jtx/AMMTest.h>
#include <test/jtx/AMM.h>
#include <test/jtx/AMMTest.h>
#include <test/jtx/CaptureLogs.h>
#include <test/jtx/Env.h>
#include <test/jtx/pay.h>
#include <xrpld/rpc/RPCHandler.h>
@@ -104,15 +104,37 @@ AMMTestBase::testAMM(
std::uint16_t tfee,
std::optional<jtx::ter> const& ter,
std::vector<FeatureBitset> const& vfeatures)
{
testAMM(
std::move(cb),
TestAMMArg{
.pool = pool, .tfee = tfee, .ter = ter, .features = vfeatures});
}
void
AMMTestBase::testAMM(
std::function<void(jtx::AMM&, jtx::Env&)>&& cb,
TestAMMArg const& arg)
{
using namespace jtx;
for (auto const& features : vfeatures)
std::string logs;
for (auto const& features : arg.features)
{
Env env{*this, features};
// Env env{
// *this,
// features,
// arg.noLog ? std::make_unique<CaptureLogs>(&logs) : nullptr};
Env env(
*this,
envconfig(),
features,
nullptr,
beast::severities::kDisabled);
auto const [asset1, asset2] =
pool ? *pool : std::make_pair(XRP(10000), USD(10000));
arg.pool ? *arg.pool : std::make_pair(XRP(10000), USD(10000));
auto tofund = [&](STAmount const& a) -> STAmount {
if (a.native())
{
@@ -142,7 +164,7 @@ AMMTestBase::testAMM(
alice,
asset1,
asset2,
CreateArg{.log = false, .tfee = tfee, .err = ter});
CreateArg{.log = false, .tfee = arg.tfee, .err = arg.ter});
if (BEAST_EXPECT(
ammAlice.expectBalances(asset1, asset2, ammAlice.tokens())))
cb(ammAlice, env);
@@ -175,7 +197,8 @@ AMMTest::pathTestEnv()
cfg->PATH_SEARCH_MAX = 10;
return cfg;
}),
supported_amendments() - featureXahauGenesis - featureTouch);
(supported_amendments() | featureAMM | featureAMMClawback) -
featureXahauGenesis - featureTouch);
}
Json::Value
+4 -17
View File
@@ -65,29 +65,16 @@ hso_delete(void (*f)(Json::Value& jv))
Json::Value
hso(std::vector<uint8_t> const& wasmBytes, void (*f)(Json::Value& jv))
{
if (wasmBytes.size() == 0)
throw std::runtime_error("empty hook wasm passed to hso()");
Json::Value jv;
jv[jss::CreateCode] = strHex(wasmBytes);
{
jv[jss::HookOn] =
"0000000000000000000000000000000000000000000000000000000000000000";
jv[jss::HookNamespace] = to_string(uint256{beast::zero});
jv[jss::HookApiVersion] = Json::Value{0};
}
if (f)
f(jv);
return jv;
return hso(strHex(wasmBytes), f);
}
Json::Value
hso(std::string const& wasmHex, void (*f)(Json::Value& jv))
{
if (wasmHex.size() == 0)
throw std::runtime_error("empty hook wasm passed to hso()");
throw std::runtime_error(
"empty hook wasm passed to hso(): run "
"src/test/app/build_test_hooks.sh to generate the hook wasm");
Json::Value jv;
jv[jss::CreateCode] = wasmHex;
+1 -1
View File
@@ -44,7 +44,7 @@ import(jtx::Account const& account, Json::Value const& xpop)
void
issuer::operator()(Env& env, JTx& jt) const
{
jt.jv[sfIssuer.jsonName] = issuer_.human();
jt.jv[sfIssuer.jsonName] = to_string(issuer_);
}
Json::Value
+1 -1
View File
@@ -66,7 +66,7 @@ blob::operator()(Env& env, JTx& jt) const
void
dest::operator()(Env& env, JTx& jt) const
{
jt.jv[sfDestination.jsonName] = dest_.human();
jt.jv[sfDestination.jsonName] = to_string(dest_);
}
} // namespace invoke
+15 -2
View File
@@ -29,18 +29,31 @@ namespace reward {
// Claim a reward.
Json::Value
claim(jtx::Account const& account)
{
return claim(account.id());
}
Json::Value
claim(AccountID const& account)
{
using namespace jtx;
Json::Value jv;
jv[jss::TransactionType] = jss::ClaimReward;
jv[jss::Account] = account.human();
jv[jss::Account] = to_string(account);
return jv;
}
void
issuer::operator()(Env& env, JTx& jt) const
{
jt.jv[sfIssuer.jsonName] = issuer_.human();
jt.jv[sfIssuer.jsonName] = to_string(issuer_);
}
void
claimCurrency::operator()(Env& env, JTx& jt) const
{
jt.jv[sfClaimCurrency.jsonName] =
STIssue{sfClaimCurrency, claimCurrency_}.getJson(JsonOptions::none);
}
} // namespace reward
+1 -1
View File
@@ -63,7 +63,7 @@ countTx(std::shared_ptr<SHAMap> const& txSet)
auto tx = std::make_shared<STTx
const>(SerialIter{sit.getSlice(sit.getVLDataLength())});
if (tx->getFieldU16(sfTransactionType) == ttUNL_MODIFY)
if (tx->getTxnType() == ttUNL_MODIFY)
counter++;
}
*/
+2 -2
View File
@@ -37,10 +37,10 @@ import(jtx::Account const& account, Json::Value const& xpop);
class issuer
{
private:
jtx::Account issuer_;
AccountID issuer_;
public:
explicit issuer(jtx::Account const& issuer) : issuer_(issuer)
explicit issuer(AccountID const& issuer) : issuer_(issuer)
{
}
Loaded 100 of 200 files, more files were not shown because too many files have changed in this diff. Show more