mirror of
https://github.com/XRPLF/rippled.git
synced 2026-07-10 16:50:28 +00:00
Compare commits
31 Commits
wasmi/bina
...
gregtatcam
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6cba6772e5 | ||
|
|
6b7151f7c2 | ||
|
|
8ffdfc5953 | ||
|
|
1c8fc3c806 | ||
|
|
c7e3dae68a | ||
|
|
e46cb6bd78 | ||
|
|
5c0c358ea6 | ||
|
|
6b71fb3f48 | ||
|
|
5ec090b258 | ||
|
|
35890680bf | ||
|
|
d3e22a1251 | ||
|
|
0f262a56cf | ||
|
|
392e69ae67 | ||
|
|
080830c4ae | ||
|
|
940dcef212 | ||
|
|
62bdfc6bf6 | ||
|
|
e85f010ef0 | ||
|
|
af17ab2642 | ||
|
|
70fa0d7f79 | ||
|
|
a2ab6c79ac | ||
|
|
5439264bdb | ||
|
|
62921eb913 | ||
|
|
50cacddcf8 | ||
|
|
a839b8b92a | ||
|
|
1a2c0e7d32 | ||
|
|
77df21b485 | ||
|
|
63f270f00e | ||
|
|
0a896b7957 | ||
|
|
12eb050bf8 | ||
|
|
d26d93130e | ||
|
|
a91e8ddad7 |
64
.github/scripts/strategy-matrix/generate.py
vendored
64
.github/scripts/strategy-matrix/generate.py
vendored
@@ -32,32 +32,7 @@ We will further set additional CMake arguments as follows:
|
||||
"""
|
||||
|
||||
|
||||
def build_config_name(os_entry: dict[str, str], platform: str, build_type: str) -> str:
|
||||
parts = [os_entry["distro_name"]]
|
||||
for key in ("distro_version", "compiler_name", "compiler_version"):
|
||||
if value := os_entry[key]:
|
||||
parts.append(value)
|
||||
parts.append("arm64" if "arm64" in platform else "amd64")
|
||||
parts.append(build_type.lower())
|
||||
return "-".join(parts)
|
||||
|
||||
|
||||
def generate_packaging_matrix(config: Config) -> list[dict]:
|
||||
"""Emit one entry per os entry with `package: true`. Architecture is
|
||||
hardcoded to linux/amd64 here (and the runner is hardcoded at the
|
||||
workflow level) until arm64 packaging is ready.
|
||||
"""
|
||||
return [
|
||||
{
|
||||
"artifact_name": f"xrpld-{build_config_name(os, 'linux/amd64', 'Release')}",
|
||||
"os": os,
|
||||
}
|
||||
for os in config.os
|
||||
if os.get("package", False)
|
||||
]
|
||||
|
||||
|
||||
def generate_strategy_matrix(all: bool, config: Config) -> list[dict]:
|
||||
def generate_strategy_matrix(all: bool, config: Config) -> list:
|
||||
configurations = []
|
||||
for architecture, os, build_type, cmake_args in itertools.product(
|
||||
config.architecture, config.os, config.build_type, config.cmake_args
|
||||
@@ -126,15 +101,14 @@ def generate_strategy_matrix(all: bool, config: Config) -> list[dict]:
|
||||
continue
|
||||
|
||||
# RHEL:
|
||||
# - 9 using GCC 12: Debug and Release on linux/amd64
|
||||
# (Release is required for RPM packaging).
|
||||
# - 9 using GCC 12: Debug on linux/amd64.
|
||||
# - 10 using Clang: Release on linux/amd64.
|
||||
if os["distro_name"] == "rhel":
|
||||
skip = True
|
||||
if os["distro_version"] == "9":
|
||||
if (
|
||||
f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-12"
|
||||
and build_type in ["Debug", "Release"]
|
||||
and build_type == "Debug"
|
||||
and architecture["platform"] == "linux/amd64"
|
||||
):
|
||||
skip = False
|
||||
@@ -149,8 +123,7 @@ def generate_strategy_matrix(all: bool, config: Config) -> list[dict]:
|
||||
continue
|
||||
|
||||
# Ubuntu:
|
||||
# - Jammy using GCC 12: Debug on linux/arm64, Release on
|
||||
# linux/amd64 (Release is required for DEB packaging).
|
||||
# - Jammy using GCC 12: Debug on linux/arm64.
|
||||
# - Noble using GCC 14: Release on linux/amd64.
|
||||
# - Noble using Clang 18: Debug on linux/amd64.
|
||||
# - Noble using Clang 19: Release on linux/arm64.
|
||||
@@ -163,12 +136,6 @@ def generate_strategy_matrix(all: bool, config: Config) -> list[dict]:
|
||||
and architecture["platform"] == "linux/arm64"
|
||||
):
|
||||
skip = False
|
||||
if (
|
||||
f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-12"
|
||||
and build_type == "Release"
|
||||
and architecture["platform"] == "linux/amd64"
|
||||
):
|
||||
skip = False
|
||||
elif os["distro_version"] == "noble":
|
||||
if (
|
||||
f"{os['compiler_name']}-{os['compiler_version']}" == "gcc-14"
|
||||
@@ -251,7 +218,17 @@ def generate_strategy_matrix(all: bool, config: Config) -> list[dict]:
|
||||
|
||||
# Generate a unique name for the configuration, e.g. macos-arm64-debug
|
||||
# or debian-bookworm-gcc-12-amd64-release.
|
||||
config_name = build_config_name(os, architecture["platform"], build_type)
|
||||
config_name = os["distro_name"]
|
||||
if (n := os["distro_version"]) != "":
|
||||
config_name += f"-{n}"
|
||||
if (n := os["compiler_name"]) != "":
|
||||
config_name += f"-{n}"
|
||||
if (n := os["compiler_version"]) != "":
|
||||
config_name += f"-{n}"
|
||||
config_name += (
|
||||
f"-{architecture['platform'][architecture['platform'].find('/')+1:]}"
|
||||
)
|
||||
config_name += f"-{build_type.lower()}"
|
||||
if "-Dcoverage=ON" in cmake_args:
|
||||
config_name += "-coverage"
|
||||
if "-Dunity=ON" in cmake_args:
|
||||
@@ -355,19 +332,10 @@ if __name__ == "__main__":
|
||||
required=False,
|
||||
type=Path,
|
||||
)
|
||||
parser.add_argument(
|
||||
"-p",
|
||||
"--packaging",
|
||||
help="Emit the packaging matrix (derived from the 'package' field on os entries) instead of the build/test matrix.",
|
||||
action="store_true",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
matrix = []
|
||||
if args.packaging:
|
||||
config_path = args.config if args.config else THIS_DIR / "linux.json"
|
||||
matrix += generate_packaging_matrix(read_config(config_path))
|
||||
elif args.config is None or args.config == "":
|
||||
if args.config is None or args.config == "":
|
||||
matrix += generate_strategy_matrix(
|
||||
args.all, read_config(THIS_DIR / "linux.json")
|
||||
)
|
||||
|
||||
6
.github/scripts/strategy-matrix/linux.json
vendored
6
.github/scripts/strategy-matrix/linux.json
vendored
@@ -127,8 +127,7 @@
|
||||
"distro_version": "9",
|
||||
"compiler_name": "gcc",
|
||||
"compiler_version": "12",
|
||||
"image_sha": "4c086b9",
|
||||
"package": true
|
||||
"image_sha": "4c086b9"
|
||||
},
|
||||
{
|
||||
"distro_name": "rhel",
|
||||
@@ -170,8 +169,7 @@
|
||||
"distro_version": "jammy",
|
||||
"compiler_name": "gcc",
|
||||
"compiler_version": "12",
|
||||
"image_sha": "4c086b9",
|
||||
"package": true
|
||||
"image_sha": "4c086b9"
|
||||
},
|
||||
{
|
||||
"distro_name": "ubuntu",
|
||||
|
||||
12
.github/workflows/on-pr.yml
vendored
12
.github/workflows/on-pr.yml
vendored
@@ -64,13 +64,11 @@ jobs:
|
||||
.github/workflows/reusable-build-test-config.yml
|
||||
.github/workflows/reusable-build-test.yml
|
||||
.github/workflows/reusable-clang-tidy.yml
|
||||
.github/workflows/reusable-package.yml
|
||||
.github/workflows/reusable-strategy-matrix.yml
|
||||
.github/workflows/reusable-test.yml
|
||||
.github/workflows/reusable-upload-recipe.yml
|
||||
.clang-tidy
|
||||
.codecov.yml
|
||||
cfg/**
|
||||
cmake/**
|
||||
conan/**
|
||||
external/**
|
||||
@@ -80,10 +78,6 @@ jobs:
|
||||
CMakeLists.txt
|
||||
conanfile.py
|
||||
conan.lock
|
||||
LICENSE.md
|
||||
package/**
|
||||
README.md
|
||||
|
||||
- name: Check whether to run
|
||||
# This step determines whether the rest of the workflow should
|
||||
# run. The rest of the workflow will run if this job runs AND at
|
||||
@@ -140,11 +134,6 @@ jobs:
|
||||
secrets:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
package:
|
||||
needs: [should-run, build-test]
|
||||
if: ${{ needs.should-run.outputs.go == 'true' }}
|
||||
uses: ./.github/workflows/reusable-package.yml
|
||||
|
||||
upload-recipe:
|
||||
needs:
|
||||
- should-run
|
||||
@@ -179,7 +168,6 @@ jobs:
|
||||
- check-rename
|
||||
- clang-tidy
|
||||
- build-test
|
||||
- package
|
||||
- upload-recipe
|
||||
- notify-clio
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
23
.github/workflows/on-tag.yml
vendored
23
.github/workflows/on-tag.yml
vendored
@@ -1,5 +1,5 @@
|
||||
# This workflow uploads the libxrpl recipe to the Conan remote and builds
|
||||
# release packages when a versioned tag is pushed.
|
||||
# This workflow uploads the libxrpl recipe to the Conan remote when a versioned
|
||||
# tag is pushed.
|
||||
name: Tag
|
||||
|
||||
on:
|
||||
@@ -22,22 +22,3 @@ jobs:
|
||||
secrets:
|
||||
remote_username: ${{ secrets.CONAN_REMOTE_USERNAME }}
|
||||
remote_password: ${{ secrets.CONAN_REMOTE_PASSWORD }}
|
||||
|
||||
build-test:
|
||||
if: ${{ github.repository == 'XRPLF/rippled' }}
|
||||
uses: ./.github/workflows/reusable-build-test.yml
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
os: [linux]
|
||||
with:
|
||||
ccache_enabled: false
|
||||
os: ${{ matrix.os }}
|
||||
strategy_matrix: minimal
|
||||
secrets:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
package:
|
||||
if: ${{ github.repository == 'XRPLF/rippled' }}
|
||||
needs: build-test
|
||||
uses: ./.github/workflows/reusable-package.yml
|
||||
|
||||
9
.github/workflows/on-trigger.yml
vendored
9
.github/workflows/on-trigger.yml
vendored
@@ -21,13 +21,11 @@ on:
|
||||
- ".github/workflows/reusable-build-test-config.yml"
|
||||
- ".github/workflows/reusable-build-test.yml"
|
||||
- ".github/workflows/reusable-clang-tidy.yml"
|
||||
- ".github/workflows/reusable-package.yml"
|
||||
- ".github/workflows/reusable-strategy-matrix.yml"
|
||||
- ".github/workflows/reusable-test.yml"
|
||||
- ".github/workflows/reusable-upload-recipe.yml"
|
||||
- ".clang-tidy"
|
||||
- ".codecov.yml"
|
||||
- "cfg/**"
|
||||
- "cmake/**"
|
||||
- "conan/**"
|
||||
- "external/**"
|
||||
@@ -37,9 +35,6 @@ on:
|
||||
- "CMakeLists.txt"
|
||||
- "conanfile.py"
|
||||
- "conan.lock"
|
||||
- "LICENSE.md"
|
||||
- "package/**"
|
||||
- "README.md"
|
||||
|
||||
# Run at 06:32 UTC on every day of the week from Monday through Friday. This
|
||||
# will force all dependencies to be rebuilt, which is useful to verify that
|
||||
@@ -100,7 +95,3 @@ jobs:
|
||||
secrets:
|
||||
remote_username: ${{ secrets.CONAN_REMOTE_USERNAME }}
|
||||
remote_password: ${{ secrets.CONAN_REMOTE_PASSWORD }}
|
||||
|
||||
package:
|
||||
needs: build-test
|
||||
uses: ./.github/workflows/reusable-package.yml
|
||||
|
||||
99
.github/workflows/reusable-package.yml
vendored
99
.github/workflows/reusable-package.yml
vendored
@@ -1,99 +0,0 @@
|
||||
# Build Linux packages (DEB and RPM) from pre-built binary artifacts.
|
||||
# Discovers which configurations to package from linux.json (os entries
|
||||
# with "package": true) and fans out one job per entry. Today only
|
||||
# linux/amd64 is emitted; the architecture is hardcoded both here
|
||||
# (runner) and in generate.py.
|
||||
name: Package
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
pkg_release:
|
||||
description: "Package release number. Increment when repackaging the same executable."
|
||||
required: false
|
||||
type: string
|
||||
default: "1"
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
env:
|
||||
BUILD_DIR: build
|
||||
|
||||
jobs:
|
||||
generate-matrix:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
matrix: ${{ steps.generate.outputs.matrix }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: 3.13
|
||||
|
||||
- name: Generate packaging matrix
|
||||
id: generate
|
||||
working-directory: .github/scripts/strategy-matrix
|
||||
run: |
|
||||
./generate.py --packaging --config=linux.json >> "${GITHUB_OUTPUT}"
|
||||
|
||||
generate-version:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
version: ${{ steps.version.outputs.version }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
sparse-checkout: |
|
||||
.github/actions/generate-version
|
||||
src/libxrpl/protocol/BuildInfo.cpp
|
||||
- name: Generate version
|
||||
id: version
|
||||
uses: ./.github/actions/generate-version
|
||||
|
||||
package:
|
||||
needs: [generate-matrix, generate-version]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix) }}
|
||||
name: "${{ matrix.artifact_name }}"
|
||||
permissions:
|
||||
contents: read
|
||||
runs-on: ["self-hosted", "Linux", "X64", "heavy"]
|
||||
container: ${{ format('ghcr.io/xrplf/ci/{0}-{1}:{2}-{3}-sha-{4}', matrix.os.distro_name, matrix.os.distro_version, matrix.os.compiler_name, matrix.os.compiler_version, matrix.os.image_sha) }}
|
||||
timeout-minutes: 30
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Download pre-built binary
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: ${{ matrix.artifact_name }}
|
||||
path: ${{ env.BUILD_DIR }}
|
||||
|
||||
- name: Make binary executable
|
||||
run: chmod +x "${BUILD_DIR}/xrpld"
|
||||
|
||||
- name: Build package
|
||||
env:
|
||||
PKG_VERSION: ${{ needs.generate-version.outputs.version }}
|
||||
PKG_RELEASE: ${{ inputs.pkg_release }}
|
||||
run: ./package/build_pkg.sh
|
||||
|
||||
- name: Upload package artifact
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
if: ${{ github.event.repository.visibility == 'public' }}
|
||||
with:
|
||||
name: ${{ matrix.artifact_name }}-pkg-${{ needs.generate-version.outputs.version }}
|
||||
path: |
|
||||
${{ env.BUILD_DIR }}/debbuild/*.deb
|
||||
${{ env.BUILD_DIR }}/debbuild/*.ddeb
|
||||
${{ env.BUILD_DIR }}/rpmbuild/RPMS/**/*.rpm
|
||||
if-no-files-found: error
|
||||
@@ -93,7 +93,6 @@ find_package(OpenSSL REQUIRED)
|
||||
find_package(secp256k1 REQUIRED)
|
||||
find_package(SOCI REQUIRED)
|
||||
find_package(SQLite3 REQUIRED)
|
||||
find_package(wasmi REQUIRED)
|
||||
find_package(xxHash REQUIRED)
|
||||
|
||||
target_link_libraries(
|
||||
@@ -135,7 +134,6 @@ endif()
|
||||
include(XrplCore)
|
||||
include(XrplProtocolAutogen)
|
||||
include(XrplInstall)
|
||||
include(XrplPackaging)
|
||||
include(XrplValidatorKeys)
|
||||
|
||||
if(tests)
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
# https://vl.ripple.com
|
||||
# https://unl.xrplf.org
|
||||
# http://127.0.0.1:8000
|
||||
# file:///etc/xrpld/vl.txt
|
||||
# file:///etc/opt/xrpld/vl.txt
|
||||
#
|
||||
# [validator_list_keys]
|
||||
#
|
||||
|
||||
@@ -67,7 +67,6 @@ target_link_libraries(
|
||||
Xrpl::opts
|
||||
Xrpl::syslibs
|
||||
secp256k1::secp256k1
|
||||
wasmi::wasmi
|
||||
xrpl.libpb
|
||||
xxHash::xxhash
|
||||
$<$<BOOL:${voidstar}>:antithesis-sdk-cpp>
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
#[===================================================================[
|
||||
Linux packaging support: 'package' target.
|
||||
|
||||
The packaging script (package/build_pkg.sh) installs to FHS-standard
|
||||
paths (/usr/bin, /etc/xrpld, etc.) regardless of CMAKE_INSTALL_PREFIX,
|
||||
so no prefix guard is needed here.
|
||||
#]===================================================================]
|
||||
if(NOT is_linux)
|
||||
message(STATUS "Packaging not supported on non-Linux hosts")
|
||||
return()
|
||||
endif()
|
||||
|
||||
if(NOT DEFINED pkg_release)
|
||||
set(pkg_release 1)
|
||||
endif()
|
||||
|
||||
find_program(RPMBUILD_EXECUTABLE rpmbuild)
|
||||
find_program(DPKG_BUILDPACKAGE_EXECUTABLE dpkg-buildpackage)
|
||||
|
||||
if(NOT (RPMBUILD_EXECUTABLE OR DPKG_BUILDPACKAGE_EXECUTABLE))
|
||||
message(
|
||||
STATUS
|
||||
"Neither rpmbuild nor dpkg-buildpackage found; 'package' target not available"
|
||||
)
|
||||
return()
|
||||
endif()
|
||||
|
||||
set(package_env
|
||||
SRC_DIR=${CMAKE_SOURCE_DIR}
|
||||
BUILD_DIR=${CMAKE_BINARY_DIR}
|
||||
PKG_VERSION=${xrpld_version}
|
||||
PKG_RELEASE=${pkg_release}
|
||||
)
|
||||
|
||||
add_custom_target(
|
||||
package
|
||||
COMMAND
|
||||
${CMAKE_COMMAND} -E env ${package_env}
|
||||
${CMAKE_SOURCE_DIR}/package/build_pkg.sh
|
||||
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
|
||||
DEPENDS xrpld
|
||||
COMMENT "Building Linux package (deb/rpm inferred from host tooling)"
|
||||
VERBATIM
|
||||
)
|
||||
@@ -3,8 +3,6 @@
|
||||
"requires": [
|
||||
"zlib/1.3.2#1cb806da49011867778ffb6ac7190fcb%1777558780.503",
|
||||
"xxhash/0.8.3#681d36a0a6111fc56e5e45ea182c19cc%1765850149.987",
|
||||
|
||||
"wasmi/1.0.9#1fecdab9b90c96698eb35ea99ca4f5cb%1772227278.324",
|
||||
"sqlite3/3.53.0#324ada52333108388a9a6108bfa96734%1776096494.149",
|
||||
"soci/4.0.3#fe32b9ad5eb47e79ab9e45a68f363945%1774450067.231",
|
||||
"snappy/1.1.10#968fef506ff261592ec30c574d4a7809%1765850147.878",
|
||||
|
||||
@@ -34,7 +34,6 @@ class Xrpl(ConanFile):
|
||||
"openssl/3.6.2",
|
||||
"secp256k1/0.7.1",
|
||||
"soci/4.0.3",
|
||||
"wasmi/1.0.9",
|
||||
"zlib/1.3.2",
|
||||
]
|
||||
|
||||
@@ -215,7 +214,6 @@ class Xrpl(ConanFile):
|
||||
"soci::soci",
|
||||
"secp256k1::secp256k1",
|
||||
"sqlite3::sqlite",
|
||||
"wasmi::wasmi",
|
||||
"xxhash::xxhash",
|
||||
"zlib::zlib",
|
||||
]
|
||||
|
||||
@@ -7,7 +7,6 @@ ignorePaths:
|
||||
- cmake/**
|
||||
- LICENSE.md
|
||||
- .clang-tidy
|
||||
- src/test/app/wasm_fixtures/*.c
|
||||
language: en
|
||||
allowCompoundWords: true # TODO (#6334)
|
||||
ignoreRandomStrings: true
|
||||
@@ -66,7 +65,6 @@ words:
|
||||
- Btrfs
|
||||
- Buildx
|
||||
- canonicality
|
||||
- cdylib
|
||||
- changespq
|
||||
- checkme
|
||||
- choco
|
||||
@@ -101,15 +99,12 @@ words:
|
||||
- desync
|
||||
- desynced
|
||||
- determ
|
||||
- disablerepo
|
||||
- distro
|
||||
- doxyfile
|
||||
- dxrpl
|
||||
- enabled
|
||||
- enablerepo
|
||||
- endmacro
|
||||
- exceptioned
|
||||
- EXPECT_STREQ
|
||||
- Falco
|
||||
- fcontext
|
||||
- finalizers
|
||||
@@ -167,7 +162,6 @@ words:
|
||||
- Merkle
|
||||
- Metafuncton
|
||||
- misprediction
|
||||
- missingok
|
||||
- mptbalance
|
||||
- MPTDEX
|
||||
- mptflags
|
||||
@@ -199,9 +193,7 @@ words:
|
||||
- NOLINT
|
||||
- NOLINTNEXTLINE
|
||||
- nonxrp
|
||||
- noreplace
|
||||
- noripple
|
||||
- notifempty
|
||||
- nudb
|
||||
- nullptr
|
||||
- nunl
|
||||
@@ -221,7 +213,6 @@ words:
|
||||
- preauthorize
|
||||
- preauthorizes
|
||||
- preclaim
|
||||
- preun
|
||||
- protobuf
|
||||
- protos
|
||||
- ptrs
|
||||
@@ -256,20 +247,17 @@ words:
|
||||
- sfields
|
||||
- shamap
|
||||
- shamapitem
|
||||
- shlibs
|
||||
- sidechain
|
||||
- SIGGOOD
|
||||
- sle
|
||||
- sles
|
||||
- soci
|
||||
- socidb
|
||||
- SRPMS
|
||||
- sslws
|
||||
- statsd
|
||||
- STATSDCOLLECTOR
|
||||
- stissue
|
||||
- stnum
|
||||
- stnumber
|
||||
- stobj
|
||||
- stobject
|
||||
- stpath
|
||||
@@ -292,8 +280,8 @@ words:
|
||||
- txn
|
||||
- txns
|
||||
- txs
|
||||
- ubsan
|
||||
- UBSAN
|
||||
- ubsan
|
||||
- umant
|
||||
- unacquired
|
||||
- unambiguity
|
||||
@@ -329,8 +317,8 @@ words:
|
||||
- wthread
|
||||
- xbridge
|
||||
- xchain
|
||||
- xfloat
|
||||
- ximinez
|
||||
- EXPECT_STREQ
|
||||
- XMACRO
|
||||
- xrpkuwait
|
||||
- xrpl
|
||||
|
||||
@@ -121,6 +121,15 @@ canTransfer(
|
||||
[[nodiscard]] TER
|
||||
canTrade(ReadView const& view, Asset const& asset);
|
||||
|
||||
/** Convenience to combine canTrade/Transfer. Returns tesSUCCESS if Asset is Issue.
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
canMPTTradeAndTransfer(
|
||||
ReadView const& v,
|
||||
Asset const& asset,
|
||||
AccountID const& from,
|
||||
AccountID const& to);
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Empty holding operations (MPT-specific)
|
||||
@@ -227,17 +236,4 @@ issuerFundsToSelfIssue(ReadView const& view, MPTIssue const& issue);
|
||||
void
|
||||
issuerSelfDebitHookMPT(ApplyView& view, MPTIssue const& issue, std::uint64_t amount);
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// MPT DEX
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/* Return true if a transaction is allowed for the specified MPT/account. The
|
||||
* function checks MPTokenIssuance and MPToken objects flags to determine if the
|
||||
* transaction is allowed.
|
||||
*/
|
||||
TER
|
||||
checkMPTTxAllowed(ReadView const& v, TxType tx, Asset const& asset, AccountID const& accountID);
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -54,9 +54,15 @@ enum class AuthType { StrongAuth, WeakAuth, Legacy };
|
||||
[[nodiscard]] bool
|
||||
isGlobalFrozen(ReadView const& view, Asset const& asset);
|
||||
|
||||
[[nodiscard]] TER
|
||||
checkGlobalFrozen(ReadView const& view, Asset const& asset);
|
||||
|
||||
[[nodiscard]] bool
|
||||
isIndividualFrozen(ReadView const& view, AccountID const& account, Asset const& asset);
|
||||
|
||||
[[nodiscard]] TER
|
||||
checkIndividualFrozen(ReadView const& view, AccountID const& account, Asset const& asset);
|
||||
|
||||
/**
|
||||
* isFrozen check is recursive for MPT shares in a vault, descending to
|
||||
* assets in the vault, up to maxAssetCheckDepth recursion depth. This is
|
||||
|
||||
@@ -251,12 +251,6 @@ constexpr std::uint8_t kVaultMaximumIouScale = 18;
|
||||
* another vault; counted from 0 */
|
||||
constexpr std::uint8_t kMaxAssetCheckDepth = 5;
|
||||
|
||||
/** Maximum length of a Data field in Escrow object that can be updated by WASM code. */
|
||||
constexpr std::size_t kMaxWasmDataLength = 1 * 1024; // 1KB
|
||||
|
||||
/** Maximum amount of data transfer across hostfunction<->wasm border. */
|
||||
constexpr std::size_t kWasmTransferLimit = 1 << 20; // 1MB
|
||||
|
||||
/** A ledger index. */
|
||||
using LedgerIndex = std::uint32_t;
|
||||
|
||||
|
||||
@@ -127,9 +127,7 @@ enum TEMcodes : TERUnderlyingType {
|
||||
temARRAY_TOO_LARGE,
|
||||
temBAD_TRANSFER_FEE,
|
||||
temINVALID_INNER_BATCH,
|
||||
|
||||
temBAD_MPT,
|
||||
temBAD_WASM,
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -360,7 +358,6 @@ enum TECcodes : TERUnderlyingType {
|
||||
tecLIMIT_EXCEEDED = 195,
|
||||
tecPSEUDO_ACCOUNT = 196,
|
||||
tecPRECISION_LOSS = 197,
|
||||
tecOUT_OF_GAS = 200,
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -84,7 +84,7 @@ LEDGER_ENTRY(ltNEGATIVE_UNL, 0x004e, NegativeUNL, nunl, ({
|
||||
|
||||
/** A ledger object which contains a list of NFTs
|
||||
|
||||
\sa keylet::nftpageMin, keylet::nftpageMax, keylet::nftpage
|
||||
\sa keylet::nftpage_min, keylet::nftpage_max, keylet::nftpage
|
||||
*/
|
||||
LEDGER_ENTRY(ltNFTOKEN_PAGE, 0x0050, NFTokenPage, nft_page, ({
|
||||
{sfPreviousPageMin, SoeOptional},
|
||||
@@ -112,7 +112,7 @@ LEDGER_ENTRY(ltSIGNER_LIST, 0x0053, SignerList, signer_list, ({
|
||||
|
||||
/** A ledger object which describes a ticket.
|
||||
|
||||
\sa keylet::kTicket
|
||||
\sa keylet::ticket
|
||||
*/
|
||||
LEDGER_ENTRY(ltTICKET, 0x0054, Ticket, ticket, ({
|
||||
{sfAccount, SoeRequired},
|
||||
|
||||
@@ -115,7 +115,7 @@ protected:
|
||||
beast::WrappedSink sink_;
|
||||
beast::Journal const j_;
|
||||
|
||||
AccountID const accountID_;
|
||||
AccountID const account_;
|
||||
XRPAmount preFeeBalance_{}; // Balance before fees.
|
||||
|
||||
public:
|
||||
|
||||
@@ -399,7 +399,8 @@ using InvariantChecks = std::tuple<
|
||||
ValidLoanBroker,
|
||||
ValidLoan,
|
||||
ValidVault,
|
||||
ValidMPTPayment>;
|
||||
ValidMPTPayment,
|
||||
ValidMPTTransfer>;
|
||||
|
||||
/**
|
||||
* @brief get a tuple of all invariant checks
|
||||
|
||||
@@ -56,4 +56,42 @@ public:
|
||||
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
|
||||
};
|
||||
|
||||
class ValidMPTTransfer
|
||||
{
|
||||
struct Value
|
||||
{
|
||||
std::optional<std::uint64_t> amtBefore;
|
||||
std::optional<std::uint64_t> amtAfter;
|
||||
};
|
||||
// MPTID: {holder: Value}
|
||||
hash_map<uint192, hash_map<AccountID, Value>> amount_;
|
||||
// Deleted MPToken
|
||||
// MPToken key: true if MPTAuthorized is set
|
||||
hash_map<uint256, bool> deletedAuthorized_;
|
||||
|
||||
public:
|
||||
void
|
||||
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
|
||||
|
||||
bool
|
||||
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Check whether a holder is authorized to send or receive an MPToken.
|
||||
*
|
||||
* Deleted MPToken SLEs are no longer present in the view by the time
|
||||
* finalize() runs, so their authorization state is captured during
|
||||
* visitEntry() and stored in deletedAuthorized_. For deleted MPTokens,
|
||||
* returns true if reqAuth is false or lsfMPTAuthorized was set at deletion.
|
||||
* For existing MPTokens, returns the result of requireAuth()
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
isAuthorized(
|
||||
ReadView const& view,
|
||||
MPTID const& mptid,
|
||||
AccountID const& holder,
|
||||
bool requireAuth) const;
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -21,7 +21,7 @@ class TOffer
|
||||
private:
|
||||
SLE::pointer entry_;
|
||||
Quality quality_{};
|
||||
AccountID accountID_;
|
||||
AccountID account_;
|
||||
Asset assetIn_;
|
||||
Asset assetOut_;
|
||||
|
||||
@@ -53,7 +53,7 @@ public:
|
||||
[[nodiscard]] AccountID const&
|
||||
owner() const
|
||||
{
|
||||
return accountID_;
|
||||
return account_;
|
||||
}
|
||||
|
||||
/** Returns the in and out amounts.
|
||||
@@ -122,7 +122,7 @@ public:
|
||||
isFunded() const
|
||||
{
|
||||
// Offer owner is issuer; they have unlimited funds if IOU
|
||||
return accountID_ == assetOut_.getIssuer() && assetOut_.holds<Issue>();
|
||||
return account_ == assetOut_.getIssuer() && assetOut_.holds<Issue>();
|
||||
}
|
||||
|
||||
static std::pair<std::uint32_t, std::uint32_t>
|
||||
@@ -159,7 +159,7 @@ public:
|
||||
|
||||
template <StepAmount TIn, StepAmount TOut>
|
||||
TOffer<TIn, TOut>::TOffer(SLE::pointer entry, Quality quality)
|
||||
: entry_(std::move(entry)), quality_(quality), accountID_(entry_->getAccountID(sfAccount))
|
||||
: entry_(std::move(entry)), quality_(quality), account_(entry_->getAccountID(sfAccount))
|
||||
{
|
||||
auto const tp = entry_->getFieldAmount(sfTakerPays);
|
||||
auto const tg = entry_->getFieldAmount(sfTakerGets);
|
||||
|
||||
@@ -22,7 +22,7 @@ public:
|
||||
|
||||
private:
|
||||
// Tx account owner is required to get the AMM trading fee in BookStep
|
||||
AccountID accountID_;
|
||||
AccountID account_;
|
||||
// true if payment has multiple paths
|
||||
bool multiPath_{false};
|
||||
// Is true if AMM offer is consumed during a payment engine iteration.
|
||||
@@ -31,8 +31,7 @@ private:
|
||||
std::uint16_t ammIters_{0};
|
||||
|
||||
public:
|
||||
AMMContext(AccountID const& account, bool multiPath)
|
||||
: accountID_(account), multiPath_(multiPath)
|
||||
AMMContext(AccountID const& account, bool multiPath) : account_(account), multiPath_(multiPath)
|
||||
{
|
||||
}
|
||||
~AMMContext() = default;
|
||||
@@ -81,7 +80,7 @@ public:
|
||||
[[nodiscard]] AccountID
|
||||
account() const
|
||||
{
|
||||
return accountID_;
|
||||
return account_;
|
||||
}
|
||||
|
||||
/** Strand execution may fail. Reset the flag at the start
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/Expected.h>
|
||||
#include <xrpl/tx/wasm/CodecEnvelope.h>
|
||||
|
||||
namespace xrpl::wasm {
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Public wasm codec API.
|
||||
//
|
||||
// This header is what host-function code includes. It intentionally contains
|
||||
// NO codec machinery, NO registered-codec list, and NO concrete type
|
||||
// registrations — only the two entry points. Their definitions are provided by
|
||||
// explicit instantiation in src/libxrpl/tx/wasm/codecs/CodecRegistry.cpp, so
|
||||
// including this header does not instantiate the (consteval) dispatch tables.
|
||||
//
|
||||
// A decode<T>/encode<T> for an unregistered T is a link error by design.
|
||||
//
|
||||
// To add a codec, see include/xrpl/tx/wasm/detail/codec_types.macro.
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
// Decode a value of the statically-known type T from a self-describing
|
||||
// envelope. The envelope carries its own (type_code, version), so no revision
|
||||
// context is needed here.
|
||||
template <typename T>
|
||||
[[nodiscard]] Expected<T, CodecError>
|
||||
decode(CodecEnvelope const& env);
|
||||
|
||||
// Encode `value` toward a contract that declared codec revision
|
||||
// `declaredRevision`. The wire version emitted is chosen by the revision policy
|
||||
// (wireVersionFor); if the value cannot be represented in that version the
|
||||
// result is NotRepresentable (the graceful-fail path).
|
||||
template <typename T>
|
||||
[[nodiscard]] Expected<CodecEnvelope, CodecError>
|
||||
encode(CodecVersion declaredRevision, T const& value);
|
||||
|
||||
} // namespace xrpl::wasm
|
||||
@@ -1,35 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace xrpl::wasm {
|
||||
|
||||
using CodecTypeCode = std::uint16_t;
|
||||
using CodecVersion = std::uint8_t;
|
||||
using CodecLength = std::uint32_t;
|
||||
|
||||
enum class CodecError : std::uint8_t {
|
||||
Ok = 0,
|
||||
UnknownType = 1, // no type code registered for the type
|
||||
UnsupportedVersion = 2, // The envelope's version has no registered codec
|
||||
TypeMismatch = 3, // When decoding, the codec called does not match the type of the envelope
|
||||
Truncated = 4, // payload size != fixed payload size
|
||||
NotRepresentable = 5, // when encoding, the value doesn't fit within the requested version
|
||||
TypeNotInRevision = 6, // There is no codec for the type in the requested version
|
||||
UnknownCodecVersion = 7, // The codec version is not registered
|
||||
};
|
||||
|
||||
// Defines the header of the binary payload for each parameter based through the host functions
|
||||
struct CodecEnvelopeHeader {
|
||||
CodecTypeCode type_code;
|
||||
CodecVersion version;
|
||||
CodecLength payload_size; // I would prefer a variable sized integer, will need to revisit this part.
|
||||
};
|
||||
|
||||
// Defines the binary payload for each parameter based through the host functions
|
||||
struct CodecEnvelope {
|
||||
CodecEnvelopeHeader header;
|
||||
std::uint8_t const* payload; // Unclear if this can be a pointer into the wasm payload or a vector holding a copy.
|
||||
};
|
||||
|
||||
} // namespace xrpl::wasm
|
||||
@@ -1,516 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/Expected.h>
|
||||
#include <xrpl/basics/Slice.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Asset.h>
|
||||
#include <xrpl/protocol/Keylet.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/UintTypes.h>
|
||||
#include <xrpl/tx/wasm/WasmCommon.h>
|
||||
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
namespace wasm_float {
|
||||
|
||||
std::string
|
||||
floatToString(Slice const& data);
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatFromIntImpl(int64_t x, int32_t mode);
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatFromUintImpl(uint64_t x, int32_t mode);
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatFromSTAmountImpl(STAmount const& x, int32_t mode);
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatFromSTNumberImpl(STNumber const& x, int32_t mode);
|
||||
|
||||
Expected<int64_t, HostFunctionError>
|
||||
floatToIntImpl(Slice const& x, int32_t mode);
|
||||
|
||||
Expected<FloatPair, HostFunctionError>
|
||||
floatToMantExpImpl(Slice const& x);
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatFromMantExpImpl(int64_t mantissa, int32_t exponent, int32_t mode);
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
floatCompareImpl(Slice const& x, Slice const& y);
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatAddImpl(Slice const& x, Slice const& y, int32_t mode);
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatSubtractImpl(Slice const& x, Slice const& y, int32_t mode);
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatMultiplyImpl(Slice const& x, Slice const& y, int32_t mode);
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatDivideImpl(Slice const& x, Slice const& y, int32_t mode);
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatRootImpl(Slice const& x, int32_t n, int32_t mode);
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatPowerImpl(Slice const& x, int32_t n, int32_t mode);
|
||||
|
||||
} // namespace wasm_float
|
||||
|
||||
// Intended to work only through wasm runtime. Don't call them directly, except with unit tests
|
||||
class HostFunctions
|
||||
{
|
||||
protected:
|
||||
RTOptRef rt_;
|
||||
beast::Journal j_;
|
||||
|
||||
public:
|
||||
HostFunctions(beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) : j_(j)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
setRT(WasmRuntimeWrapper& rt)
|
||||
{
|
||||
rt_ = rt;
|
||||
}
|
||||
|
||||
void
|
||||
resetRT()
|
||||
{
|
||||
rt_ = std::nullopt;
|
||||
}
|
||||
|
||||
[[nodiscard]] WasmRuntimeWrapper&
|
||||
getRT() const
|
||||
{
|
||||
if (!rt_)
|
||||
Throw<std::logic_error>("Wasm runtime not set");
|
||||
return rt_->get();
|
||||
}
|
||||
|
||||
[[nodiscard]] beast::Journal
|
||||
getJournal() const
|
||||
{
|
||||
return j_;
|
||||
}
|
||||
|
||||
// LCOV_EXCL_START
|
||||
|
||||
[[nodiscard]] virtual bool
|
||||
checkSelf() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual Expected<std::uint32_t, HostFunctionError>
|
||||
getLedgerSqn() const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Unimplemented);
|
||||
}
|
||||
|
||||
virtual Expected<std::uint32_t, HostFunctionError>
|
||||
getParentLedgerTime() const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Unimplemented);
|
||||
}
|
||||
|
||||
virtual Expected<Hash, HostFunctionError>
|
||||
getParentLedgerHash() const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Unimplemented);
|
||||
}
|
||||
|
||||
virtual Expected<uint32_t, HostFunctionError>
|
||||
getBaseFee() const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Unimplemented);
|
||||
}
|
||||
|
||||
virtual Expected<int32_t, HostFunctionError>
|
||||
isAmendmentEnabled(uint256 const& amendmentId) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Unimplemented);
|
||||
}
|
||||
|
||||
virtual Expected<int32_t, HostFunctionError>
|
||||
isAmendmentEnabled(std::string_view const& amendmentName) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Unimplemented);
|
||||
}
|
||||
|
||||
virtual Expected<int32_t, HostFunctionError>
|
||||
cacheLedgerObj(uint256 const& objId, int32_t cacheIdx)
|
||||
{
|
||||
return Unexpected(HostFunctionError::Unimplemented);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
getTxField(SField const& fname) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Unimplemented);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
getCurrentLedgerObjField(SField const& fname) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Unimplemented);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
getLedgerObjField(int32_t cacheIdx, SField const& fname) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Unimplemented);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
getTxNestedField(FieldLocator const& locator) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Unimplemented);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
getCurrentLedgerObjNestedField(FieldLocator const& locator) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Unimplemented);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
getLedgerObjNestedField(int32_t cacheIdx, FieldLocator const& locator) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Unimplemented);
|
||||
}
|
||||
|
||||
virtual Expected<int32_t, HostFunctionError>
|
||||
getTxArrayLen(SField const& fname) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Unimplemented);
|
||||
}
|
||||
|
||||
virtual Expected<int32_t, HostFunctionError>
|
||||
getCurrentLedgerObjArrayLen(SField const& fname) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Unimplemented);
|
||||
}
|
||||
|
||||
virtual Expected<int32_t, HostFunctionError>
|
||||
getLedgerObjArrayLen(int32_t cacheIdx, SField const& fname) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Unimplemented);
|
||||
}
|
||||
|
||||
virtual Expected<int32_t, HostFunctionError>
|
||||
getTxNestedArrayLen(FieldLocator const& locator) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Unimplemented);
|
||||
}
|
||||
|
||||
virtual Expected<int32_t, HostFunctionError>
|
||||
getCurrentLedgerObjNestedArrayLen(FieldLocator const& locator) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Unimplemented);
|
||||
}
|
||||
|
||||
virtual Expected<int32_t, HostFunctionError>
|
||||
getLedgerObjNestedArrayLen(int32_t cacheIdx, FieldLocator const& locator) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Unimplemented);
|
||||
}
|
||||
|
||||
virtual Expected<int32_t, HostFunctionError>
|
||||
updateData(Slice const& data)
|
||||
{
|
||||
return Unexpected(HostFunctionError::Unimplemented);
|
||||
}
|
||||
|
||||
virtual Expected<int32_t, HostFunctionError>
|
||||
checkSignature(Slice const& message, Slice const& signature, Slice const& pubkey) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Unimplemented);
|
||||
}
|
||||
|
||||
virtual Expected<Hash, HostFunctionError>
|
||||
computeSha512HalfHash(Slice const& data) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Unimplemented);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
accountKeylet(AccountID const& account) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Unimplemented);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
ammKeylet(Asset const& issue1, Asset const& issue2) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Unimplemented);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
checkKeylet(AccountID const& account, std::uint32_t seq) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Unimplemented);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
credentialKeylet(AccountID const& subject, AccountID const& issuer, Slice const& credentialType)
|
||||
const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Unimplemented);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
didKeylet(AccountID const& account) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Unimplemented);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
delegateKeylet(AccountID const& account, AccountID const& authorize) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Unimplemented);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
depositPreauthKeylet(AccountID const& account, AccountID const& authorize) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Unimplemented);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
escrowKeylet(AccountID const& account, std::uint32_t seq) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Unimplemented);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
lineKeylet(AccountID const& account1, AccountID const& account2, Currency const& currency) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Unimplemented);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
mptIssuanceKeylet(AccountID const& issuer, std::uint32_t seq) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Unimplemented);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
mptokenKeylet(MPTID const& mptid, AccountID const& holder) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Unimplemented);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
nftOfferKeylet(AccountID const& account, std::uint32_t seq) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Unimplemented);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
offerKeylet(AccountID const& account, std::uint32_t seq) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Unimplemented);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
oracleKeylet(AccountID const& account, std::uint32_t docId) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Unimplemented);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
paychanKeylet(AccountID const& account, AccountID const& destination, std::uint32_t seq) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Unimplemented);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
permissionedDomainKeylet(AccountID const& account, std::uint32_t seq) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Unimplemented);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
signersKeylet(AccountID const& account) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Unimplemented);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
ticketKeylet(AccountID const& account, std::uint32_t seq) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Unimplemented);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
vaultKeylet(AccountID const& account, std::uint32_t seq) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Unimplemented);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
getNFT(AccountID const& account, uint256 const& nftId) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Unimplemented);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
getNFTIssuer(uint256 const& nftId) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Unimplemented);
|
||||
}
|
||||
|
||||
virtual Expected<std::uint32_t, HostFunctionError>
|
||||
getNFTTaxon(uint256 const& nftId) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Unimplemented);
|
||||
}
|
||||
|
||||
virtual Expected<int32_t, HostFunctionError>
|
||||
getNFTFlags(uint256 const& nftId) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Unimplemented);
|
||||
}
|
||||
|
||||
virtual Expected<int32_t, HostFunctionError>
|
||||
getNFTTransferFee(uint256 const& nftId) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Unimplemented);
|
||||
}
|
||||
|
||||
virtual Expected<std::uint32_t, HostFunctionError>
|
||||
getNFTSerial(uint256 const& nftId) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Unimplemented);
|
||||
}
|
||||
|
||||
virtual Expected<int32_t, HostFunctionError>
|
||||
trace(std::string_view const& msg, Slice const& data, bool asHex) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Unimplemented);
|
||||
}
|
||||
|
||||
virtual Expected<int32_t, HostFunctionError>
|
||||
traceNum(std::string_view const& msg, int64_t data) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Unimplemented);
|
||||
}
|
||||
|
||||
virtual Expected<int32_t, HostFunctionError>
|
||||
traceAccount(std::string_view const& msg, AccountID const& account) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Unimplemented);
|
||||
}
|
||||
|
||||
virtual Expected<int32_t, HostFunctionError>
|
||||
traceFloat(std::string_view const& msg, Slice const& data) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Unimplemented);
|
||||
}
|
||||
|
||||
virtual Expected<int32_t, HostFunctionError>
|
||||
traceAmount(std::string_view const& msg, STAmount const& amount) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Unimplemented);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
floatFromInt(int64_t x, int32_t mode) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Unimplemented);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
floatFromUint(uint64_t x, int32_t mode) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Unimplemented);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
floatFromSTAmount(STAmount const& x, int32_t mode) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Unimplemented);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
floatFromSTNumber(STNumber const& x, int32_t mode) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Unimplemented);
|
||||
}
|
||||
|
||||
virtual Expected<int64_t, HostFunctionError>
|
||||
floatToInt(Slice const& x, int32_t mode) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Unimplemented);
|
||||
}
|
||||
|
||||
virtual Expected<FloatPair, HostFunctionError>
|
||||
floatToMantExp(Slice const& x) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Unimplemented);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
floatFromMantExp(int64_t mantissa, int32_t exponent, int32_t mode) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Unimplemented);
|
||||
}
|
||||
|
||||
virtual Expected<int32_t, HostFunctionError>
|
||||
floatCompare(Slice const& x, Slice const& y) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Unimplemented);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
floatAdd(Slice const& x, Slice const& y, int32_t mode) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Unimplemented);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
floatSubtract(Slice const& x, Slice const& y, int32_t mode) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Unimplemented);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
floatMultiply(Slice const& x, Slice const& y, int32_t mode) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Unimplemented);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
floatDivide(Slice const& x, Slice const& y, int32_t mode) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Unimplemented);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
floatRoot(Slice const& x, int32_t n, int32_t mode) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Unimplemented);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, HostFunctionError>
|
||||
floatPower(Slice const& x, int32_t n, int32_t mode) const
|
||||
{
|
||||
return Unexpected(HostFunctionError::Unimplemented);
|
||||
}
|
||||
|
||||
virtual ~HostFunctions() = default;
|
||||
// LCOV_EXCL_STOP
|
||||
};
|
||||
|
||||
using HFRef = std::reference_wrapper<HostFunctions>;
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -1,283 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/tx/ApplyContext.h>
|
||||
#include <xrpl/tx/wasm/HostFunc.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
// Intended to work only through wasm runtime. Don't call them directly, except with unit tests
|
||||
class WasmHostFunctionsImpl : public HostFunctions
|
||||
{
|
||||
ApplyContext& ctx_;
|
||||
|
||||
Keylet leKey_;
|
||||
mutable std::optional<std::shared_ptr<SLE const>> currentLedgerObj_;
|
||||
|
||||
static int constexpr maxCache = 256;
|
||||
std::array<std::shared_ptr<SLE const>, maxCache> cache_;
|
||||
|
||||
std::optional<Bytes> data_;
|
||||
|
||||
public:
|
||||
Expected<std::shared_ptr<SLE const>, HostFunctionError>
|
||||
getCurrentLedgerObj() const
|
||||
{
|
||||
if (!currentLedgerObj_)
|
||||
currentLedgerObj_ = ctx_.view().read(leKey_);
|
||||
if (*currentLedgerObj_)
|
||||
return *currentLedgerObj_;
|
||||
return Unexpected(HostFunctionError::LedgerObjNotFound);
|
||||
}
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
normalizeCacheIndex(int32_t cacheIdx) const
|
||||
{
|
||||
--cacheIdx;
|
||||
if (cacheIdx < 0 || cacheIdx >= maxCache)
|
||||
return Unexpected(HostFunctionError::SlotOutRange);
|
||||
if (!cache_[cacheIdx])
|
||||
return Unexpected(HostFunctionError::EmptySlot);
|
||||
return cacheIdx;
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
void
|
||||
log(std::string_view const& msg, F&& dataFn) const
|
||||
{
|
||||
#ifdef DEBUG_OUTPUT
|
||||
auto& j = std::cerr;
|
||||
#else
|
||||
if (!getJournal().active(beast::Severity::Trace))
|
||||
return;
|
||||
auto j = getJournal().trace();
|
||||
#endif
|
||||
j << "WasmTrace[" << toShortString(leKey_.key) << "]: " << msg << " " << dataFn();
|
||||
|
||||
#ifdef DEBUG_OUTPUT
|
||||
j << std::endl;
|
||||
#endif
|
||||
}
|
||||
|
||||
public:
|
||||
WasmHostFunctionsImpl(ApplyContext& ct, Keylet const& leKey)
|
||||
: HostFunctions(ct.journal), ctx_(ct), leKey_(leKey)
|
||||
{
|
||||
}
|
||||
|
||||
bool
|
||||
checkSelf() const override
|
||||
{
|
||||
return !currentLedgerObj_ && !data_ &&
|
||||
std::ranges::none_of(cache_, [](auto const& p) { return !!p; });
|
||||
}
|
||||
|
||||
std::optional<Bytes> const&
|
||||
getData() const
|
||||
{
|
||||
return data_;
|
||||
}
|
||||
|
||||
Expected<std::uint32_t, HostFunctionError>
|
||||
getLedgerSqn() const override;
|
||||
|
||||
Expected<std::uint32_t, HostFunctionError>
|
||||
getParentLedgerTime() const override;
|
||||
|
||||
Expected<Hash, HostFunctionError>
|
||||
getParentLedgerHash() const override;
|
||||
|
||||
Expected<std::uint32_t, HostFunctionError>
|
||||
getBaseFee() const override;
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
isAmendmentEnabled(uint256 const& amendmentId) const override;
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
isAmendmentEnabled(std::string_view const& amendmentName) const override;
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
cacheLedgerObj(uint256 const& objId, int32_t cacheIdx) override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
getTxField(SField const& fname) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
getCurrentLedgerObjField(SField const& fname) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
getLedgerObjField(int32_t cacheIdx, SField const& fname) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
getTxNestedField(FieldLocator const& locator) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
getCurrentLedgerObjNestedField(FieldLocator const& locator) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
getLedgerObjNestedField(int32_t cacheIdx, FieldLocator const& locator) const override;
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
getTxArrayLen(SField const& fname) const override;
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
getCurrentLedgerObjArrayLen(SField const& fname) const override;
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
getLedgerObjArrayLen(int32_t cacheIdx, SField const& fname) const override;
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
getTxNestedArrayLen(FieldLocator const& locator) const override;
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
getCurrentLedgerObjNestedArrayLen(FieldLocator const& locator) const override;
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
getLedgerObjNestedArrayLen(int32_t cacheIdx, FieldLocator const& locator) const override;
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
updateData(Slice const& data) override;
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
checkSignature(Slice const& message, Slice const& signature, Slice const& pubkey)
|
||||
const override;
|
||||
|
||||
Expected<Hash, HostFunctionError>
|
||||
computeSha512HalfHash(Slice const& data) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
accountKeylet(AccountID const& account) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
ammKeylet(Asset const& issue1, Asset const& issue2) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
checkKeylet(AccountID const& account, std::uint32_t seq) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
credentialKeylet(AccountID const& subject, AccountID const& issuer, Slice const& credentialType)
|
||||
const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
didKeylet(AccountID const& account) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
delegateKeylet(AccountID const& account, AccountID const& authorize) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
depositPreauthKeylet(AccountID const& account, AccountID const& authorize) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
escrowKeylet(AccountID const& account, std::uint32_t seq) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
lineKeylet(AccountID const& account1, AccountID const& account2, Currency const& currency)
|
||||
const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
mptIssuanceKeylet(AccountID const& issuer, std::uint32_t seq) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
mptokenKeylet(MPTID const& mptid, AccountID const& holder) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
nftOfferKeylet(AccountID const& account, std::uint32_t seq) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
offerKeylet(AccountID const& account, std::uint32_t seq) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
oracleKeylet(AccountID const& account, std::uint32_t docId) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
paychanKeylet(AccountID const& account, AccountID const& destination, std::uint32_t seq)
|
||||
const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
permissionedDomainKeylet(AccountID const& account, std::uint32_t seq) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
signersKeylet(AccountID const& account) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
ticketKeylet(AccountID const& account, std::uint32_t seq) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
vaultKeylet(AccountID const& account, std::uint32_t seq) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
getNFT(AccountID const& account, uint256 const& nftId) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
getNFTIssuer(uint256 const& nftId) const override;
|
||||
|
||||
Expected<std::uint32_t, HostFunctionError>
|
||||
getNFTTaxon(uint256 const& nftId) const override;
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
getNFTFlags(uint256 const& nftId) const override;
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
getNFTTransferFee(uint256 const& nftId) const override;
|
||||
|
||||
Expected<std::uint32_t, HostFunctionError>
|
||||
getNFTSerial(uint256 const& nftId) const override;
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
trace(std::string_view const& msg, Slice const& data, bool asHex) const override;
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
traceNum(std::string_view const& msg, int64_t data) const override;
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
traceAccount(std::string_view const& msg, AccountID const& account) const override;
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
traceFloat(std::string_view const& msg, Slice const& data) const override;
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
traceAmount(std::string_view const& msg, STAmount const& amount) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatFromInt(int64_t x, int32_t mode) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatFromUint(uint64_t x, int32_t mode) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatFromSTAmount(STAmount const& x, int32_t mode) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatFromSTNumber(STNumber const& x, int32_t mode) const override;
|
||||
|
||||
Expected<int64_t, HostFunctionError>
|
||||
floatToInt(Slice const& x, int32_t mode) const override;
|
||||
|
||||
Expected<FloatPair, HostFunctionError>
|
||||
floatToMantExp(Slice const& x) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatFromMantExp(int64_t mantissa, int32_t exponent, int32_t mode) const override;
|
||||
|
||||
Expected<int32_t, HostFunctionError>
|
||||
floatCompare(Slice const& x, Slice const& y) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatAdd(Slice const& x, Slice const& y, int32_t mode) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatSubtract(Slice const& x, Slice const& y, int32_t mode) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatMultiply(Slice const& x, Slice const& y, int32_t mode) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatDivide(Slice const& x, Slice const& y, int32_t mode) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatRoot(Slice const& x, int32_t n, int32_t mode) const override;
|
||||
|
||||
Expected<Bytes, HostFunctionError>
|
||||
floatPower(Slice const& x, int32_t n, int32_t mode) const override;
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -1,254 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/tx/wasm/WasmImportsHelper.h>
|
||||
|
||||
#include <wasm.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
#define WASM_CB_PARAMS_LIST void *env, wasm_val_vec_t const *params, wasm_val_vec_t *results
|
||||
#define WASM_SECONDARY_CB_PARAMS_LIST \
|
||||
HostFunctions &hf, wasm_val_vec_t const *params, wasm_val_vec_t *results
|
||||
|
||||
wasm_trap_t* HostFuncMain_wrap(WASM_CB_PARAMS_LIST);
|
||||
|
||||
using getLedgerSqn_proto = int32_t(uint8_t*, int32_t);
|
||||
wasm_trap_t* getLedgerSqn_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using getParentLedgerTime_proto = int32_t(uint8_t*, int32_t);
|
||||
wasm_trap_t* getParentLedgerTime_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using getParentLedgerHash_proto = int32_t(uint8_t*, int32_t);
|
||||
wasm_trap_t* getParentLedgerHash_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using getBaseFee_proto = int32_t(uint8_t*, int32_t);
|
||||
wasm_trap_t* getBaseFee_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using isAmendmentEnabled_proto = int32_t(uint8_t const*, int32_t);
|
||||
wasm_trap_t* isAmendmentEnabled_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using cacheLedgerObj_proto = int32_t(uint8_t const*, int32_t, int32_t);
|
||||
wasm_trap_t* cacheLedgerObj_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using getTxField_proto = int32_t(int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* getTxField_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using getCurrentLedgerObjField_proto = int32_t(int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* getCurrentLedgerObjField_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using getLedgerObjField_proto = int32_t(int32_t, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* getLedgerObjField_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using getTxNestedField_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* getTxNestedField_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using getCurrentLedgerObjNestedField_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* getCurrentLedgerObjNestedField_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using getLedgerObjNestedField_proto = int32_t(int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* getLedgerObjNestedField_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using getTxArrayLen_proto = int32_t(int32_t);
|
||||
wasm_trap_t* getTxArrayLen_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using getCurrentLedgerObjArrayLen_proto = int32_t(int32_t);
|
||||
wasm_trap_t* getCurrentLedgerObjArrayLen_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using getLedgerObjArrayLen_proto = int32_t(int32_t, int32_t);
|
||||
wasm_trap_t* getLedgerObjArrayLen_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using getTxNestedArrayLen_proto = int32_t(uint8_t const*, int32_t);
|
||||
wasm_trap_t* getTxNestedArrayLen_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using getCurrentLedgerObjNestedArrayLen_proto = int32_t(uint8_t const*, int32_t);
|
||||
wasm_trap_t* getCurrentLedgerObjNestedArrayLen_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using getLedgerObjNestedArrayLen_proto = int32_t(int32_t, uint8_t const*, int32_t);
|
||||
wasm_trap_t* getLedgerObjNestedArrayLen_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using updateData_proto = int32_t(uint8_t const*, int32_t);
|
||||
wasm_trap_t* updateData_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using checkSignature_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t const*, int32_t);
|
||||
wasm_trap_t* checkSignature_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using computeSha512HalfHash_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* computeSha512HalfHash_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using accountKeylet_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* accountKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using ammKeylet_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* ammKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using checkKeylet_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* checkKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using credentialKeylet_proto = int32_t(
|
||||
uint8_t const*,
|
||||
int32_t,
|
||||
uint8_t const*,
|
||||
int32_t,
|
||||
uint8_t const*,
|
||||
int32_t,
|
||||
uint8_t*,
|
||||
int32_t);
|
||||
wasm_trap_t* credentialKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using delegateKeylet_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* delegateKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using depositPreauthKeylet_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* depositPreauthKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using didKeylet_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* didKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using escrowKeylet_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* escrowKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using lineKeylet_proto = int32_t(
|
||||
uint8_t const*,
|
||||
int32_t,
|
||||
uint8_t const*,
|
||||
int32_t,
|
||||
uint8_t const*,
|
||||
int32_t,
|
||||
uint8_t*,
|
||||
int32_t);
|
||||
wasm_trap_t* lineKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using mptIssuanceKeylet_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* mptIssuanceKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using mptokenKeylet_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* mptokenKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using nftOfferKeylet_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* nftOfferKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using offerKeylet_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* offerKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using oracleKeylet_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* oracleKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using paychanKeylet_proto = int32_t(
|
||||
uint8_t const*,
|
||||
int32_t,
|
||||
uint8_t const*,
|
||||
int32_t,
|
||||
uint8_t const*,
|
||||
int32_t,
|
||||
uint8_t*,
|
||||
int32_t);
|
||||
wasm_trap_t* paychanKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using permissionedDomainKeylet_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* permissionedDomainKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using signersKeylet_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* signersKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using ticketKeylet_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* ticketKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using vaultKeylet_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* vaultKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using getNFT_proto = int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* getNFT_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using getNFTIssuer_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* getNFTIssuer_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using getNFTTaxon_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* getNFTTaxon_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using getNFTFlags_proto = int32_t(uint8_t const*, int32_t);
|
||||
wasm_trap_t* getNFTFlags_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using getNFTTransferFee_proto = int32_t(uint8_t const*, int32_t);
|
||||
wasm_trap_t* getNFTTransferFee_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using getNFTSerial_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* getNFTSerial_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using trace_proto = int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, int32_t);
|
||||
wasm_trap_t* trace_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using traceNum_proto = int32_t(uint8_t const*, int32_t, int64_t);
|
||||
wasm_trap_t* traceNum_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using traceAccount_proto = int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t);
|
||||
wasm_trap_t* traceAccount_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using traceFloat_proto = int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t);
|
||||
wasm_trap_t* traceFloat_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using traceAmount_proto = int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t);
|
||||
wasm_trap_t* traceAmount_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using floatFromInt_proto = int32_t(int64_t, uint8_t*, int32_t, int32_t);
|
||||
wasm_trap_t* floatFromInt_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using floatFromUint_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t, int32_t);
|
||||
wasm_trap_t* floatFromUint_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using floatFromSTAmount_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t, int32_t);
|
||||
wasm_trap_t* floatFromSTAmount_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using floatFromSTNumber_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t, int32_t);
|
||||
wasm_trap_t* floatFromSTNumber_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using floatToInt_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t, int32_t);
|
||||
wasm_trap_t* floatToInt_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using floatToMantExp_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t* floatToMantExp_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using floatFromMantExp_proto = int32_t(int64_t, int32_t, uint8_t*, int32_t, int32_t);
|
||||
wasm_trap_t* floatFromMantExp_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using floatCompare_proto = int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t);
|
||||
wasm_trap_t* floatCompare_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using floatAdd_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t, int32_t);
|
||||
wasm_trap_t* floatAdd_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using floatSubtract_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t, int32_t);
|
||||
wasm_trap_t* floatSubtract_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using floatMultiply_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t, int32_t);
|
||||
wasm_trap_t* floatMultiply_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using floatDivide_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t, int32_t);
|
||||
wasm_trap_t* floatDivide_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using floatRoot_proto = int32_t(uint8_t const*, int32_t, int32_t, uint8_t*, int32_t, int32_t);
|
||||
wasm_trap_t* floatRoot_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
using floatPower_proto = int32_t(uint8_t const*, int32_t, int32_t, uint8_t*, int32_t, int32_t);
|
||||
wasm_trap_t* floatPower_wrap(WASM_SECONDARY_CB_PARAMS_LIST);
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -1,189 +0,0 @@
|
||||
# WASM Module for Programmable Escrows
|
||||
|
||||
This module provides WebAssembly (WASM) execution capabilities for programmable
|
||||
escrows on the XRP Ledger. When an escrow is finished, the WASM code runs to
|
||||
determine whether the escrow conditions are met, enabling custom programmable
|
||||
logic for escrow release conditions.
|
||||
|
||||
For the full specification, see
|
||||
[XLS-0102: WASM VM](https://xls.xrpl.org/xls/XLS-0102-wasm-vm.html).
|
||||
|
||||
## Architecture
|
||||
|
||||
The module follows a layered architecture:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ WasmEngine (WasmVM.h) │
|
||||
│ runEscrowWasm(), preflightEscrowWasm() │
|
||||
│ Host function registration │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ WasmiEngine (WasmiVM.h) │
|
||||
│ Low-level wasmi interpreter integration │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ HostFuncWrapper │ HostFuncImpl │
|
||||
│ C-style WASM bridges │ C++ implementations │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ HostFunc (Interface) │
|
||||
│ Abstract base class for host functions │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Key Components
|
||||
|
||||
- **`WasmVM.h` / `detail/WasmVM.cpp`** - High-level facade providing:
|
||||
- `WasmEngine` singleton that wraps the underlying WASM interpreter
|
||||
- `runEscrowWasm()` - Execute WASM code for escrow finish
|
||||
- `preflightEscrowWasm()` - Validate WASM code during preflight
|
||||
- `createWasmImport()` - Register all host functions
|
||||
|
||||
- **`WasmiVM.h` / `detail/WasmiVM.cpp`** - Low-level integration with the
|
||||
[wasmi](https://github.com/wasmi-labs/wasmi) WebAssembly interpreter:
|
||||
- `WasmiEngine` - Manages WASM modules, instances, and execution
|
||||
- Memory management and gas metering
|
||||
- Function invocation and result handling
|
||||
|
||||
- **`HostFunc.h`** - Abstract `HostFunctions` base class defining the interface
|
||||
for all callable host functions. Each method returns
|
||||
`Expected<T, HostFunctionError>`.
|
||||
|
||||
- **`HostFuncImpl.h` / `detail/HostFuncImpl*.cpp`** - Concrete
|
||||
`WasmHostFunctionsImpl` class that implements host functions with access to
|
||||
`ApplyContext` for ledger state queries. Implementation split across files:
|
||||
- `HostFuncImpl.cpp` - Core utilities (updateData, checkSignature, etc.)
|
||||
- `HostFuncImplFloat.cpp` - Float/number arithmetic operations
|
||||
- `HostFuncImplGetter.cpp` - Field access (transaction, ledger objects)
|
||||
- `HostFuncImplKeylet.cpp` - Keylet construction functions
|
||||
- `HostFuncImplLedgerHeader.cpp` - Ledger header info access
|
||||
- `HostFuncImplNFT.cpp` - NFT-related queries
|
||||
- `HostFuncImplTrace.cpp` - Debugging/tracing functions
|
||||
|
||||
- **`HostFuncWrapper.h` / `detail/HostFuncWrapper.cpp`** - C-style wrapper
|
||||
functions that bridge WASM calls to C++ `HostFunctions` methods. Each host
|
||||
function has:
|
||||
- A `_proto` type alias defining the function signature
|
||||
- A `_wrap` function that extracts parameters and calls the implementation
|
||||
|
||||
- **`ParamsHelper.h`** - Utilities for WASM parameter handling:
|
||||
- `WASM_IMPORT_FUNC` / `WASM_IMPORT_FUNC2` macros for registration
|
||||
- `wasmParams()` helper for building parameter vectors
|
||||
- Type conversion between WASM and C++ types
|
||||
|
||||
## Host Functions
|
||||
|
||||
Host functions allow WASM code to interact with the XRP Ledger. They are
|
||||
organized into categories:
|
||||
|
||||
- **Ledger Information** - Access ledger sequence, timestamps, hashes, fees
|
||||
- **Transaction & Ledger Object Access** - Read fields from the transaction
|
||||
and ledger objects (including the current escrow object)
|
||||
- **Keylet Construction** - Build keylets to look up various ledger object types
|
||||
- **Cryptography** - Signature verification and hashing
|
||||
- **Float Arithmetic** - Mathematical operations for amount calculations
|
||||
- **NFT Operations** - Query NFT properties
|
||||
- **Tracing/Debugging** - Log messages for debugging
|
||||
|
||||
For the complete list of available host functions, their WASM names, and gas
|
||||
costs, see the [XLS-0102 specification](https://xls.xrpl.org/xls/XLS-0102-wasm-vm.html)
|
||||
or `detail/WasmVM.cpp` where they are registered via `WASM_IMPORT_FUNC2` macros.
|
||||
For method signatures, see `HostFunc.h`.
|
||||
|
||||
## Gas Model
|
||||
|
||||
Each host function has an associated gas cost. The gas cost is specified when
|
||||
registering the function in `detail/WasmVM.cpp`:
|
||||
|
||||
```cpp
|
||||
WASM_IMPORT_FUNC2(i, getLedgerSqn, "get_ledger_sqn", hfs, 60);
|
||||
// ^^ gas cost
|
||||
```
|
||||
|
||||
WASM execution is metered, and if the gas limit is exceeded, execution fails.
|
||||
|
||||
## Entry Point
|
||||
|
||||
The WASM module must export a function with the name defined by
|
||||
`ESCROW_FUNCTION_NAME` (currently `"finish"`). This function:
|
||||
|
||||
- Takes no parameters (or parameters passed via host function calls)
|
||||
- Returns an `int32_t`:
|
||||
- `1` (or positive): Escrow conditions are met, allow finish
|
||||
- `0` (or negative): Escrow conditions are not met, reject finish
|
||||
|
||||
## Adding a New Host Function
|
||||
|
||||
To add a new host function, follow these steps:
|
||||
|
||||
### 1. Add to HostFunc.h (Base Class)
|
||||
|
||||
Add a virtual method declaration with a default implementation that returns an
|
||||
error:
|
||||
|
||||
```cpp
|
||||
virtual Expected<ReturnType, HostFunctionError>
|
||||
myNewFunction(ParamType1 param1, ParamType2 param2)
|
||||
{
|
||||
return Unexpected(HostFunctionError::INTERNAL);
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Add to HostFuncImpl.h (Declaration)
|
||||
|
||||
Add the method override declaration in `WasmHostFunctionsImpl`:
|
||||
|
||||
```cpp
|
||||
Expected<ReturnType, HostFunctionError>
|
||||
myNewFunction(ParamType1 param1, ParamType2 param2) override;
|
||||
```
|
||||
|
||||
### 3. Implement in detail/HostFuncImpl\*.cpp
|
||||
|
||||
Add the implementation in the appropriate file:
|
||||
|
||||
```cpp
|
||||
Expected<ReturnType, HostFunctionError>
|
||||
WasmHostFunctionsImpl::myNewFunction(ParamType1 param1, ParamType2 param2)
|
||||
{
|
||||
// Implementation using ctx (ApplyContext) for ledger access
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Add Wrapper to HostFuncWrapper.h
|
||||
|
||||
Add the prototype and wrapper declaration:
|
||||
|
||||
```cpp
|
||||
using myNewFunction_proto = int32_t(uint8_t const*, int32_t, ...);
|
||||
wasm_trap_t*
|
||||
myNewFunction_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
|
||||
```
|
||||
|
||||
### 5. Implement Wrapper in detail/HostFuncWrapper.cpp
|
||||
|
||||
Implement the C-style wrapper that bridges WASM to C++:
|
||||
|
||||
```cpp
|
||||
wasm_trap_t*
|
||||
myNewFunction_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results)
|
||||
{
|
||||
// Extract parameters from params
|
||||
// Call hfs->myNewFunction(...)
|
||||
// Set results and return
|
||||
}
|
||||
```
|
||||
|
||||
### 6. Register in WasmVM.cpp
|
||||
|
||||
Add the function registration in `setCommonHostFunctions()` or
|
||||
`createWasmImport()`:
|
||||
|
||||
```cpp
|
||||
WASM_IMPORT_FUNC2(i, myNewFunction, "my_new_function", hfs, 100);
|
||||
// ^^ WASM name ^^ gas cost
|
||||
```
|
||||
|
||||
> [!IMPORTANT]
|
||||
> New host functions MUST be amendment-gated in `WasmVM.cpp`.
|
||||
> Wrap the registration in an amendment check to ensure the function is only
|
||||
> available after the corresponding amendment is enabled on the network.
|
||||
@@ -1,237 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/basics/contract.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
using Bytes = std::vector<std::uint8_t>;
|
||||
using Hash = xrpl::uint256;
|
||||
using FloatPair = std::pair<int64_t, int32_t>;
|
||||
|
||||
// Error signals that cross the wasm boundary as trap messages (the C API has no
|
||||
// trap code). WasmiEngine::call maps them to TER: hfErrInternal -> tecINTERNAL,
|
||||
// hfErrOutOfGas / wasmi's OutOfFuel -> tecOUT_OF_GAS, anything else ->
|
||||
// tecFAILED_PROCESSING.
|
||||
//
|
||||
// Matched as substrings, not by equality: the C API returns the Rust Debug form
|
||||
// of the error, e.g. `Error { kind: Message("HfInternal") }` or
|
||||
// `Error { kind: TrapCode(OutOfFuel) }`.
|
||||
std::string_view inline constexpr hfErrInternal = "HfInternal";
|
||||
std::string_view inline constexpr hfErrOutOfGas = "HfOutOfGas";
|
||||
std::string_view inline constexpr wasmiTrapOutOfFuel = "OutOfFuel";
|
||||
|
||||
enum class HostFunctionError : int32_t {
|
||||
Unimplemented = -1,
|
||||
FieldNotFound = -2,
|
||||
BufferTooSmall = -3,
|
||||
NoArray = -4,
|
||||
NotLeafField = -5,
|
||||
LocatorMalformed = -6,
|
||||
SlotOutRange = -7,
|
||||
SlotsFull = -8,
|
||||
EmptySlot = -9,
|
||||
LedgerObjNotFound = -10,
|
||||
OutOfTransferLimit = -11,
|
||||
DataFieldTooLarge = -12,
|
||||
PointerOutOfBounds = -13,
|
||||
NoMemExported = -14,
|
||||
InvalidParams = -15,
|
||||
InvalidAccount = -16,
|
||||
InvalidField = -17,
|
||||
IndexOutOfBounds = -18,
|
||||
FloatInputMalformed = -19,
|
||||
FloatComputationError = -20,
|
||||
};
|
||||
|
||||
enum class WasmTypes { WtI32, WtI64 };
|
||||
|
||||
struct Wmem
|
||||
{
|
||||
std::uint8_t* p = nullptr;
|
||||
std::size_t s = 0;
|
||||
|
||||
Wmem() = default;
|
||||
Wmem(void* ptr, std::size_t size) : p(reinterpret_cast<std::uint8_t*>(ptr)), s(size)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct WasmResult
|
||||
{
|
||||
T result;
|
||||
int64_t cost;
|
||||
};
|
||||
using EscrowResult = WasmResult<int32_t>;
|
||||
|
||||
// Engine error when wasm does not run to completion. `cost` is the gas consumed
|
||||
// when meaningful (tecOUT_OF_GAS / tecFAILED_PROCESSING; caller writes it to tx
|
||||
// metadata); std::nullopt for tecINTERNAL and malformed input (no gas reported).
|
||||
struct WasmTER
|
||||
{
|
||||
TER ter;
|
||||
std::optional<int64_t> cost;
|
||||
};
|
||||
|
||||
class FieldLocator
|
||||
{
|
||||
int32_t const* ptr_ = nullptr;
|
||||
uint32_t size_ = 0;
|
||||
std::vector<int32_t> buf_;
|
||||
|
||||
public:
|
||||
FieldLocator(std::vector<int32_t>&& buf)
|
||||
: ptr_(&buf[0]), size_(buf.size()), buf_(std::move(buf))
|
||||
{
|
||||
}
|
||||
|
||||
FieldLocator(int32_t const* ptr, uint32_t const size) : ptr_(ptr), size_(size)
|
||||
{
|
||||
}
|
||||
|
||||
FieldLocator(FieldLocator const&) = delete;
|
||||
FieldLocator&
|
||||
operator=(FieldLocator const&) = delete;
|
||||
FieldLocator(FieldLocator&&) = default;
|
||||
FieldLocator&
|
||||
operator=(FieldLocator&&) = default;
|
||||
|
||||
int32_t
|
||||
operator[](unsigned i) const
|
||||
{
|
||||
if (i >= size_)
|
||||
Throw<std::runtime_error>("index out of bounds");
|
||||
return ptr_[i];
|
||||
}
|
||||
|
||||
[[nodiscard]] uint32_t
|
||||
size() const
|
||||
{
|
||||
return size_;
|
||||
}
|
||||
|
||||
[[nodiscard]] int32_t const*
|
||||
data() const
|
||||
{
|
||||
return ptr_;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
empty() const
|
||||
{
|
||||
return size_ == 0;
|
||||
}
|
||||
};
|
||||
|
||||
class WasmRuntimeWrapper
|
||||
{
|
||||
public:
|
||||
virtual ~WasmRuntimeWrapper() = default;
|
||||
|
||||
virtual Wmem
|
||||
getMem() = 0;
|
||||
|
||||
virtual std::int64_t
|
||||
getGas() = 0;
|
||||
|
||||
virtual std::int64_t
|
||||
setGas(std::int64_t gas) = 0;
|
||||
|
||||
virtual std::int64_t
|
||||
getTransferLimit() = 0;
|
||||
|
||||
virtual std::int64_t
|
||||
setTransferLimit(std::int64_t transferLimit) = 0;
|
||||
};
|
||||
using RTOptRef = std::optional<std::reference_wrapper<WasmRuntimeWrapper>>;
|
||||
|
||||
struct WasmParam
|
||||
{
|
||||
// We are not supporting float/double
|
||||
|
||||
WasmTypes type = WasmTypes::WtI32;
|
||||
union
|
||||
{
|
||||
std::int32_t i32;
|
||||
std::int64_t i64 = 0;
|
||||
} of;
|
||||
};
|
||||
|
||||
template <class... Types>
|
||||
inline void
|
||||
wasmParamsHlp(std::vector<WasmParam>& v, std::int32_t p, Types&&... args)
|
||||
{
|
||||
v.push_back({.type = WasmTypes::WtI32, .of = {.i32 = p}});
|
||||
wasmParamsHlp(v, std::forward<Types>(args)...);
|
||||
}
|
||||
|
||||
template <class... Types>
|
||||
inline void
|
||||
wasmParamsHlp(std::vector<WasmParam>& v, std::int64_t p, Types&&... args)
|
||||
{
|
||||
v.push_back({.type = WasmTypes::WtI64, .of = {.i64 = p}});
|
||||
wasmParamsHlp(v, std::forward<Types>(args)...);
|
||||
}
|
||||
|
||||
inline void
|
||||
wasmParamsHlp(std::vector<WasmParam>& v)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
template <class... Types>
|
||||
inline std::vector<WasmParam>
|
||||
wasmParams(Types&&... args)
|
||||
{
|
||||
std::vector<WasmParam> v;
|
||||
v.reserve(sizeof...(args));
|
||||
wasmParamsHlp(v, std::forward<Types>(args)...);
|
||||
return v;
|
||||
}
|
||||
|
||||
template <typename T, size_t Size = sizeof(T)>
|
||||
constexpr T
|
||||
adjustWasmEndianessHlp(T x)
|
||||
{
|
||||
static_assert(std::is_integral_v<T>, "Only integral types");
|
||||
if constexpr (Size > 1)
|
||||
{
|
||||
using U = std::make_unsigned_t<T>;
|
||||
U u = static_cast<U>(x);
|
||||
U const low = (u & 0xFF) << ((Size - 1) << 3);
|
||||
u = adjustWasmEndianessHlp<U, Size - 1>(u >> 8);
|
||||
return static_cast<T>(low | u);
|
||||
}
|
||||
|
||||
return x;
|
||||
}
|
||||
|
||||
template <typename T, size_t Size = sizeof(T)>
|
||||
constexpr T
|
||||
adjustWasmEndianess(T x)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
static_assert(std::is_integral_v<T>, "Only integral types");
|
||||
if constexpr (std::endian::native == std::endian::big)
|
||||
{
|
||||
return adjustWasmEndianessHlp(x);
|
||||
}
|
||||
return x;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
constexpr int32_t
|
||||
hfErrorToInt(HostFunctionError e)
|
||||
{
|
||||
return static_cast<int32_t>(e);
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -1,128 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/tx/wasm/HostFunc.h>
|
||||
|
||||
#include <boost/function_types/function_arity.hpp>
|
||||
#include <boost/function_types/parameter_types.hpp>
|
||||
#include <boost/function_types/result_type.hpp>
|
||||
#include <boost/mpl/vector.hpp>
|
||||
|
||||
#include <wasm.h>
|
||||
|
||||
namespace bft = boost::function_types;
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
using wasmSecondaryCbFuncType =
|
||||
wasm_trap_t*(HostFunctions&, wasm_val_vec_t const*, wasm_val_vec_t*);
|
||||
|
||||
struct WasmImportFunc
|
||||
{
|
||||
std::string_view name;
|
||||
std::optional<WasmTypes> result;
|
||||
std::vector<WasmTypes> params;
|
||||
|
||||
wasmSecondaryCbFuncType* wrap = nullptr;
|
||||
uint32_t gas = 0;
|
||||
};
|
||||
|
||||
using WasmUserData = std::pair<HFRef, WasmImportFunc>;
|
||||
// string - import function name
|
||||
using ImportVec = std::unordered_map<std::string_view, WasmUserData>;
|
||||
|
||||
template <int N, int C, typename Mpl>
|
||||
void
|
||||
WasmImpArgs(WasmImportFunc& e)
|
||||
{
|
||||
if constexpr (N < C)
|
||||
{
|
||||
using at = typename boost::mpl::at_c<Mpl, N>::type;
|
||||
if constexpr (std::is_pointer_v<at>)
|
||||
{
|
||||
e.params.push_back(WasmTypes::WtI32);
|
||||
}
|
||||
else if constexpr (std::is_same_v<at, std::int32_t>)
|
||||
{
|
||||
e.params.push_back(WasmTypes::WtI32);
|
||||
}
|
||||
else if constexpr (std::is_same_v<at, std::int64_t>)
|
||||
{
|
||||
e.params.push_back(WasmTypes::WtI64);
|
||||
}
|
||||
else
|
||||
{
|
||||
static_assert(std::is_pointer_v<at>, "Unsupported argument type");
|
||||
}
|
||||
|
||||
return WasmImpArgs<N + 1, C, Mpl>(e);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
template <typename>
|
||||
inline constexpr bool wasmDependentFalse = false;
|
||||
|
||||
template <typename Rt>
|
||||
void
|
||||
WasmImpRet(WasmImportFunc& e)
|
||||
{
|
||||
if constexpr (std::is_pointer_v<Rt>)
|
||||
{
|
||||
e.result = WasmTypes::WtI32;
|
||||
}
|
||||
else if constexpr (std::is_same_v<Rt, std::int32_t>)
|
||||
{
|
||||
e.result = WasmTypes::WtI32;
|
||||
}
|
||||
else if constexpr (std::is_same_v<Rt, std::int64_t>)
|
||||
{
|
||||
e.result = WasmTypes::WtI64;
|
||||
}
|
||||
else if constexpr (std::is_void_v<Rt>)
|
||||
{
|
||||
e.result.reset();
|
||||
}
|
||||
else
|
||||
{
|
||||
static_assert(wasmDependentFalse<Rt>, "Unsupported return type");
|
||||
}
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
void
|
||||
WasmImpFuncHelper(WasmImportFunc& e)
|
||||
{
|
||||
using rt = typename bft::result_type<F>::type;
|
||||
using pt = typename bft::parameter_types<F>::type;
|
||||
// typename boost::mpl::at_c<mpl, N>::type
|
||||
|
||||
WasmImpRet<rt>(e);
|
||||
WasmImpArgs<0, bft::function_arity<F>::value, pt>(e);
|
||||
// WasmImpWrap(e, std::forward<F>(f));
|
||||
}
|
||||
|
||||
// imp_name - string literal, must have static lifetime
|
||||
template <typename F>
|
||||
void
|
||||
WasmImpFunc(
|
||||
ImportVec& v,
|
||||
std::string_view impName,
|
||||
wasmSecondaryCbFuncType* fWrap,
|
||||
HostFunctions& hf,
|
||||
uint32_t gas = 0)
|
||||
{
|
||||
WasmImportFunc e;
|
||||
e.name = impName;
|
||||
e.wrap = fWrap;
|
||||
e.gas = gas;
|
||||
WasmImpFuncHelper<F>(e);
|
||||
v.emplace(impName, std::make_pair(HFRef(hf), std::move(e)));
|
||||
}
|
||||
|
||||
#define WASM_IMPORT_FUNC(v, f, ...) WasmImpFunc<f##_proto>(v, #f, &f##_wrap, ##__VA_ARGS__)
|
||||
|
||||
// n - string literal name, must have static lifetime
|
||||
#define WASM_IMPORT_FUNC2(v, f, n, ...) WasmImpFunc<f##_proto>(v, n, &f##_wrap, ##__VA_ARGS__)
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -1,89 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/tx/wasm/HostFunc.h>
|
||||
#include <xrpl/tx/wasm/WasmImportsHelper.h>
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
std::string_view inline constexpr wEnv = "env";
|
||||
std::string_view inline constexpr wHostLib = "host_lib";
|
||||
std::string_view inline constexpr wMem = "memory";
|
||||
std::string_view inline constexpr wStore = "store";
|
||||
std::string_view inline constexpr wLoad = "load";
|
||||
std::string_view inline constexpr wSize = "size";
|
||||
std::string_view inline constexpr wAlloc = "allocate";
|
||||
std::string_view inline constexpr wDealloc = "deallocate";
|
||||
std::string_view inline constexpr wProcExit = "proc_exit";
|
||||
|
||||
std::string_view inline constexpr escrowFunctionName = "finish";
|
||||
|
||||
uint32_t inline constexpr maxPages = 128; // 8MB = 64KB*128
|
||||
|
||||
class WasmiEngine;
|
||||
|
||||
class WasmEngine
|
||||
{
|
||||
std::unique_ptr<WasmiEngine> const impl_;
|
||||
|
||||
WasmEngine();
|
||||
|
||||
public:
|
||||
WasmEngine(WasmEngine const&) = delete;
|
||||
WasmEngine(WasmEngine&&) = delete;
|
||||
WasmEngine&
|
||||
operator=(WasmEngine const&) = delete;
|
||||
WasmEngine&
|
||||
operator=(WasmEngine&&) = delete;
|
||||
|
||||
static WasmEngine&
|
||||
instance();
|
||||
|
||||
Expected<WasmResult<int32_t>, WasmTER>
|
||||
run(Bytes const& wasmCode,
|
||||
HostFunctions& hfs,
|
||||
int64_t gasLimit,
|
||||
std::string_view funcName = {},
|
||||
std::vector<WasmParam> const& params = {},
|
||||
ImportVec const& imports = {},
|
||||
beast::Journal j = beast::Journal{beast::Journal::getNullSink()});
|
||||
|
||||
NotTEC
|
||||
check(
|
||||
Bytes const& wasmCode,
|
||||
HostFunctions& hfs,
|
||||
std::string_view funcName,
|
||||
std::vector<WasmParam> const& params = {},
|
||||
ImportVec const& imports = {},
|
||||
beast::Journal j = beast::Journal{beast::Journal::getNullSink()});
|
||||
|
||||
// Host functions helper functionality
|
||||
void*
|
||||
newTrap(std::string const& txt = std::string());
|
||||
|
||||
[[nodiscard]] beast::Journal
|
||||
getJournal() const;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
ImportVec
|
||||
createWasmImport(HostFunctions& hfs);
|
||||
|
||||
Expected<EscrowResult, WasmTER>
|
||||
runEscrowWasm(
|
||||
Bytes const& wasmCode,
|
||||
HostFunctions& hfs,
|
||||
int64_t gasLimit,
|
||||
std::string_view funcName = escrowFunctionName,
|
||||
std::vector<WasmParam> const& params = {});
|
||||
|
||||
NotTEC
|
||||
preflightEscrowWasm(
|
||||
Bytes const& wasmCode,
|
||||
HostFunctions& hfs,
|
||||
std::string_view funcName = escrowFunctionName,
|
||||
std::vector<WasmParam> const& params = {});
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -1,446 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/tx/wasm/WasmVM.h>
|
||||
|
||||
#include <wasm.h>
|
||||
#include <wasmi.h>
|
||||
|
||||
#include <optional>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
template <class T, void (*Create)(T*, size_t), void (*Destroy)(T*)>
|
||||
class WasmVec
|
||||
{
|
||||
using TD = std::remove_pointer_t<decltype(T::data)>;
|
||||
T vec_;
|
||||
|
||||
public:
|
||||
WasmVec(size_t s = 0) : vec_ WASM_EMPTY_VEC
|
||||
{
|
||||
if (s > 0)
|
||||
Create(&vec_, s); // zeroes memory
|
||||
}
|
||||
|
||||
~WasmVec()
|
||||
{
|
||||
clear();
|
||||
}
|
||||
|
||||
WasmVec(WasmVec const&) = delete;
|
||||
WasmVec&
|
||||
operator=(WasmVec const&) = delete;
|
||||
|
||||
WasmVec(WasmVec&& other) noexcept : vec_ WASM_EMPTY_VEC
|
||||
{
|
||||
*this = std::move(other);
|
||||
}
|
||||
|
||||
WasmVec&
|
||||
operator=(WasmVec&& other) noexcept
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
clear();
|
||||
vec_ = other.vec_;
|
||||
other.vec_ = WASM_EMPTY_VEC;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
void
|
||||
clear()
|
||||
{
|
||||
Destroy(&vec_); // call destructor for every elements too
|
||||
vec_ = WASM_EMPTY_VEC;
|
||||
}
|
||||
|
||||
T
|
||||
release()
|
||||
{
|
||||
T result = vec_;
|
||||
vec_ = WASM_EMPTY_VEC;
|
||||
return result;
|
||||
}
|
||||
|
||||
T*
|
||||
get()
|
||||
{
|
||||
return &vec_;
|
||||
}
|
||||
|
||||
[[nodiscard]] T const*
|
||||
get() const
|
||||
{
|
||||
return &vec_;
|
||||
}
|
||||
|
||||
TD&
|
||||
operator[](size_t i)
|
||||
{
|
||||
if (i >= vec_.size)
|
||||
Throw<std::runtime_error>("Out of bound");
|
||||
return vec_.data[i];
|
||||
}
|
||||
|
||||
TD const&
|
||||
operator[](size_t i) const
|
||||
{
|
||||
if (i >= vec_.size)
|
||||
Throw<std::runtime_error>("Out of bound");
|
||||
return vec_.data[i];
|
||||
}
|
||||
|
||||
[[nodiscard]] size_t
|
||||
size() const
|
||||
{
|
||||
return vec_.size;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
empty() const
|
||||
{
|
||||
return vec_.size == 0u;
|
||||
}
|
||||
};
|
||||
|
||||
using WasmValtypeVec =
|
||||
WasmVec<wasm_valtype_vec_t, &wasm_valtype_vec_new_uninitialized, &wasm_valtype_vec_delete>;
|
||||
using WasmValVec = WasmVec<wasm_val_vec_t, &wasm_val_vec_new_uninitialized, &wasm_val_vec_delete>;
|
||||
using WasmExternVec =
|
||||
WasmVec<wasm_extern_vec_t, &wasm_extern_vec_new_uninitialized, &wasm_extern_vec_delete>;
|
||||
using WasmExporttypeVec = WasmVec<
|
||||
wasm_exporttype_vec_t,
|
||||
&wasm_exporttype_vec_new_uninitialized,
|
||||
&wasm_exporttype_vec_delete>;
|
||||
using WasmImporttypeVec = WasmVec<
|
||||
wasm_importtype_vec_t,
|
||||
&wasm_importtype_vec_new_uninitialized,
|
||||
&wasm_importtype_vec_delete>;
|
||||
|
||||
struct WasmiResult
|
||||
{
|
||||
WasmValVec r;
|
||||
// Set iff the call trapped. Holds the TER the trap was classified into
|
||||
// (tecINTERNAL / tecOUT_OF_GAS / tecFAILED_PROCESSING); see
|
||||
// WasmiEngine::call. std::nullopt means the call returned normally.
|
||||
std::optional<TER> ter;
|
||||
|
||||
WasmiResult(unsigned n = 0) : r(n)
|
||||
{
|
||||
}
|
||||
|
||||
WasmiResult() = delete;
|
||||
~WasmiResult() = default;
|
||||
WasmiResult(WasmiResult&& o) = default;
|
||||
WasmiResult&
|
||||
operator=(WasmiResult&& o) = default;
|
||||
};
|
||||
|
||||
using ModulePtr = std::unique_ptr<wasm_module_t, decltype(&wasm_module_delete)>;
|
||||
using InstancePtr = std::unique_ptr<wasm_instance_t, decltype(&wasm_instance_delete)>;
|
||||
using EnginePtr = std::unique_ptr<wasm_engine_t, decltype(&wasm_engine_delete)>;
|
||||
using StorePtr = std::unique_ptr<wasm_store_t, decltype(&wasm_store_delete)>;
|
||||
|
||||
using FuncInfo = std::pair<wasm_func_t const*, wasm_functype_t const*>;
|
||||
|
||||
class InstanceWrapper
|
||||
{
|
||||
wasm_store_t* store_ = nullptr;
|
||||
WasmExternVec exports_;
|
||||
mutable int memIdx_ = -1;
|
||||
InstancePtr instance_;
|
||||
beast::Journal j_ = beast::Journal(beast::Journal::getNullSink());
|
||||
std::int64_t transferLimit_ = kWasmTransferLimit;
|
||||
|
||||
private:
|
||||
static InstancePtr
|
||||
init(
|
||||
StorePtr& s,
|
||||
ModulePtr& m,
|
||||
WasmExternVec& expt,
|
||||
WasmExternVec const& imports,
|
||||
beast::Journal j);
|
||||
|
||||
public:
|
||||
InstanceWrapper() : instance_(nullptr, &wasm_instance_delete) {};
|
||||
|
||||
InstanceWrapper(InstanceWrapper const&) = delete;
|
||||
|
||||
InstanceWrapper(InstanceWrapper&& o) : instance_(nullptr, &wasm_instance_delete)
|
||||
{
|
||||
*this = std::move(o); // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
InstanceWrapper(StorePtr& s, ModulePtr& m, WasmExternVec const& imports, beast::Journal j)
|
||||
: store_(s.get()), instance_(init(s, m, exports_, imports, j)), j_(j)
|
||||
{
|
||||
}
|
||||
|
||||
InstanceWrapper&
|
||||
operator=(InstanceWrapper&& o);
|
||||
|
||||
InstanceWrapper&
|
||||
operator=(InstanceWrapper const&) = delete;
|
||||
|
||||
operator bool() const
|
||||
{
|
||||
return static_cast<bool>(instance_);
|
||||
}
|
||||
|
||||
FuncInfo
|
||||
getFunc(std::string_view funcName, WasmExporttypeVec const& exportTypes) const;
|
||||
|
||||
Wmem
|
||||
getMem() const;
|
||||
|
||||
std::int64_t
|
||||
getGas() const;
|
||||
|
||||
std::int64_t
|
||||
setGas(std::int64_t) const;
|
||||
|
||||
std::int64_t
|
||||
getTransferLimit() const;
|
||||
|
||||
std::int64_t
|
||||
setTransferLimit(std::int64_t);
|
||||
};
|
||||
|
||||
class ModuleWrapper
|
||||
{
|
||||
ModulePtr module_;
|
||||
InstanceWrapper instanceWrap_;
|
||||
WasmExporttypeVec exportTypes_;
|
||||
beast::Journal j_ = beast::Journal(beast::Journal::getNullSink());
|
||||
|
||||
public:
|
||||
// LCOV_EXCL_START
|
||||
ModuleWrapper() : module_(nullptr, &wasm_module_delete)
|
||||
{
|
||||
}
|
||||
|
||||
ModuleWrapper(ModuleWrapper&& o) : module_(nullptr, &wasm_module_delete)
|
||||
{
|
||||
*this = std::move(o);
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
ModuleWrapper&
|
||||
operator=(ModuleWrapper&& o);
|
||||
ModuleWrapper(
|
||||
StorePtr& s,
|
||||
Bytes const& wasmBin,
|
||||
bool instantiate,
|
||||
ImportVec const& imports,
|
||||
beast::Journal j);
|
||||
~ModuleWrapper() = default;
|
||||
|
||||
operator bool() const
|
||||
{
|
||||
return instanceWrap_;
|
||||
}
|
||||
|
||||
FuncInfo
|
||||
getFunc(std::string_view funcName) const
|
||||
{
|
||||
return instanceWrap_.getFunc(funcName, exportTypes_);
|
||||
}
|
||||
|
||||
wasm_functype_t*
|
||||
getFuncType(std::string_view funcName) const;
|
||||
|
||||
Wmem
|
||||
getMem() const
|
||||
{
|
||||
return instanceWrap_.getMem();
|
||||
}
|
||||
|
||||
InstanceWrapper&
|
||||
getInstance(int i = 0)
|
||||
{
|
||||
return instanceWrap_;
|
||||
}
|
||||
|
||||
InstanceWrapper const&
|
||||
getInstance(int i = 0) const
|
||||
{
|
||||
return instanceWrap_;
|
||||
}
|
||||
|
||||
int
|
||||
addInstance(StorePtr& s, WasmExternVec const& imports)
|
||||
{
|
||||
instanceWrap_ = {s, module_, imports, j_};
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::int64_t
|
||||
getGas() const
|
||||
{
|
||||
return instanceWrap_ ? instanceWrap_.getGas() : -1;
|
||||
}
|
||||
|
||||
private:
|
||||
static ModulePtr
|
||||
init(StorePtr& s, Bytes const& wasmBin, beast::Journal j);
|
||||
|
||||
WasmExternVec
|
||||
buildImports(StorePtr& s, ImportVec const& imports) const;
|
||||
};
|
||||
|
||||
class WasmiEngine
|
||||
{
|
||||
EnginePtr engine_;
|
||||
StorePtr store_;
|
||||
std::unique_ptr<ModuleWrapper> moduleWrap_;
|
||||
beast::Journal j_ = beast::Journal(beast::Journal::getNullSink());
|
||||
|
||||
std::mutex m_; // 1 instance mutex
|
||||
|
||||
public:
|
||||
WasmiEngine() : engine_(init()), store_(nullptr, &wasm_store_delete)
|
||||
{
|
||||
}
|
||||
|
||||
~WasmiEngine() = default;
|
||||
|
||||
static EnginePtr
|
||||
init();
|
||||
|
||||
Expected<WasmResult<int32_t>, WasmTER>
|
||||
run(Bytes const& wasmCode,
|
||||
HostFunctions& hfs,
|
||||
int64_t gas,
|
||||
std::string_view funcName,
|
||||
std::vector<WasmParam> const& params,
|
||||
ImportVec const& imports,
|
||||
beast::Journal j);
|
||||
|
||||
NotTEC
|
||||
check(
|
||||
Bytes const& wasmCode,
|
||||
HostFunctions& hfs,
|
||||
std::string_view funcName,
|
||||
std::vector<WasmParam> const& params,
|
||||
ImportVec const& imports,
|
||||
beast::Journal j);
|
||||
|
||||
[[nodiscard]] std::int64_t
|
||||
getGas() const
|
||||
{
|
||||
return moduleWrap_ ? moduleWrap_->getGas() : -1; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
// Host functions helper functionality
|
||||
wasm_trap_t*
|
||||
newTrap(std::string const& msg);
|
||||
|
||||
// LCOV_EXCL_START
|
||||
[[nodiscard]] beast::Journal
|
||||
getJournal() const
|
||||
{
|
||||
return j_;
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
private:
|
||||
[[nodiscard]] InstanceWrapper&
|
||||
getRT(int m = 0, int i = 0) const
|
||||
{
|
||||
if (!moduleWrap_)
|
||||
Throw<std::runtime_error>("no module");
|
||||
return moduleWrap_->getInstance(i);
|
||||
}
|
||||
|
||||
[[nodiscard]] Wmem
|
||||
getMem() const
|
||||
{
|
||||
return moduleWrap_ ? moduleWrap_->getMem() : Wmem();
|
||||
}
|
||||
|
||||
Expected<WasmResult<int32_t>, WasmTER>
|
||||
runHlp(
|
||||
Bytes const& wasmCode,
|
||||
HostFunctions& hfs,
|
||||
int64_t gas,
|
||||
std::string_view funcName,
|
||||
std::vector<WasmParam> const& params,
|
||||
ImportVec const& imports,
|
||||
beast::Journal j);
|
||||
|
||||
NotTEC
|
||||
checkHlp(
|
||||
Bytes const& wasmCode,
|
||||
HostFunctions& hfs,
|
||||
std::string_view funcName,
|
||||
std::vector<WasmParam> const& params,
|
||||
ImportVec const& imports,
|
||||
beast::Journal j);
|
||||
|
||||
int
|
||||
addModule(Bytes const& wasmCode, bool instantiate, ImportVec const& imports, int64_t gas);
|
||||
void
|
||||
clearModules();
|
||||
|
||||
// int addInstance();
|
||||
|
||||
int32_t
|
||||
runFunc(std::string_view const funcName, int32_t p);
|
||||
|
||||
int32_t
|
||||
makeModule(Bytes const& wasmCode, WasmExternVec const& imports = {});
|
||||
|
||||
[[nodiscard]] FuncInfo
|
||||
getFunc(std::string_view funcName) const
|
||||
{
|
||||
return moduleWrap_->getFunc(funcName);
|
||||
}
|
||||
|
||||
static std::vector<wasm_val_t>
|
||||
convertParams(std::vector<WasmParam> const& params);
|
||||
|
||||
static int
|
||||
compareParamTypes(wasm_valtype_vec_t const* ftp, std::vector<wasm_val_t> const& p);
|
||||
|
||||
static void
|
||||
addParam(std::vector<wasm_val_t>& in, int32_t p);
|
||||
static void
|
||||
addParam(std::vector<wasm_val_t>& in, int64_t p);
|
||||
|
||||
template <int NR, class... Types>
|
||||
inline WasmiResult
|
||||
call(std::string_view func, Types&&... args);
|
||||
|
||||
template <int NR, class... Types>
|
||||
inline WasmiResult
|
||||
call(FuncInfo const& f, Types&&... args);
|
||||
|
||||
template <int NR, class... Types>
|
||||
inline WasmiResult
|
||||
call(FuncInfo const& f, std::vector<wasm_val_t>& in);
|
||||
|
||||
template <int NR, class... Types>
|
||||
inline WasmiResult
|
||||
call(FuncInfo const& f, std::vector<wasm_val_t>& in, std::int32_t p, Types&&... args);
|
||||
|
||||
template <int NR, class... Types>
|
||||
inline WasmiResult
|
||||
call(FuncInfo const& f, std::vector<wasm_val_t>& in, std::int64_t p, Types&&... args);
|
||||
|
||||
template <int NR, class... Types>
|
||||
inline WasmiResult
|
||||
call(
|
||||
FuncInfo const& f,
|
||||
std::vector<wasm_val_t>& in,
|
||||
uint8_t const* d,
|
||||
int32_t sz,
|
||||
Types&&... args);
|
||||
|
||||
template <int NR, class... Types>
|
||||
inline WasmiResult
|
||||
call(FuncInfo const& f, std::vector<wasm_val_t>& in, Bytes const& p, Types&&... args);
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -1,145 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/Expected.h>
|
||||
#include <xrpl/tx/wasm/CodecEnvelope.h>
|
||||
#include <xrpl/tx/wasm/detail/CodecTypeCode.h>
|
||||
#include <xrpl/tx/wasm/detail/CodecTypeTrait.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
namespace xrpl::wasm::detail {
|
||||
|
||||
// Sentinel that lets the registry's X-macro expansion end with a trailing
|
||||
// comma: CodecList<Trait0, Trait1, CodecEnd>. Its `void` CodecType never
|
||||
// matches a real type, so every table builder simply skips it.
|
||||
struct CodecEnd
|
||||
{
|
||||
using CodecType = void;
|
||||
};
|
||||
|
||||
template <typename Trait, typename T>
|
||||
inline constexpr bool traitIsV = std::is_same_v<typename Trait::CodecType, T>;
|
||||
|
||||
template <typename T>
|
||||
using DecodeFn = Expected<T, CodecError> (*)(CodecEnvelope const&);
|
||||
|
||||
template <typename T>
|
||||
using EncodeFn = Expected<CodecEnvelope, CodecError> (*)(T const&);
|
||||
|
||||
// Highest registered version of T within List. Unregistered T ⇒ build error.
|
||||
template <typename T, typename List, std::size_t... I>
|
||||
consteval CodecVersion
|
||||
maxVersionImpl(std::index_sequence<I...>)
|
||||
{
|
||||
auto v = CodecVersion{};
|
||||
auto found = false;
|
||||
([&] {
|
||||
using Trait = std::tuple_element_t<I, List>;
|
||||
if constexpr (traitIsV<Trait, T>)
|
||||
{
|
||||
found = true;
|
||||
v = std::max(v, Trait::kVersion);
|
||||
}
|
||||
}(), ...);
|
||||
if (!found)
|
||||
{
|
||||
xrpl::Throw<std::runtime_error>("wasm codec: no codec registered for this type");
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
template <typename T, typename List>
|
||||
consteval CodecVersion
|
||||
maxVersion()
|
||||
{
|
||||
return maxVersionImpl<T, List>(
|
||||
std::make_index_sequence<std::tuple_size_v<List>>{});
|
||||
}
|
||||
|
||||
template <typename T, typename List, std::size_t... I>
|
||||
consteval auto
|
||||
makeDecoderTableImpl(std::index_sequence<I...>)
|
||||
{
|
||||
constexpr auto n = maxVersion<T, List>() + 1;
|
||||
auto table = std::array<DecodeFn<T>, n>{};
|
||||
([&] {
|
||||
using Trait = std::tuple_element_t<I, List>;
|
||||
if constexpr (traitIsV<Trait, T>)
|
||||
{
|
||||
table[Trait::kVersion] = &Trait::decode;
|
||||
}
|
||||
}(), ...);
|
||||
for (const auto& entry : table)
|
||||
{
|
||||
if (!entry)
|
||||
{
|
||||
xrpl::Throw<std::runtime_error>("wasm codec: decoder versions must be contiguous from 0");
|
||||
}
|
||||
}
|
||||
return table;
|
||||
}
|
||||
|
||||
template <typename T, typename List>
|
||||
inline constexpr auto decoderTableV = makeDecoderTableImpl<T, List>(
|
||||
std::make_index_sequence<std::tuple_size_v<List>>{});
|
||||
|
||||
template <typename T, typename List, std::size_t... I>
|
||||
consteval auto
|
||||
makeEncoderTableImpl(std::index_sequence<I...>)
|
||||
{
|
||||
constexpr auto n = maxVersion<T, List>() + 1;
|
||||
auto table = std::array<EncodeFn<T>, n>{};
|
||||
([&] {
|
||||
using Trait = std::tuple_element_t<I, List>;
|
||||
if constexpr (traitIsV<Trait, T>)
|
||||
{
|
||||
table[Trait::kVersion] = &Trait::encode;
|
||||
}
|
||||
}(), ...);
|
||||
for (const auto& entry : table)
|
||||
{
|
||||
if (!entry)
|
||||
{
|
||||
xrpl::Throw<std::runtime_error>("wasm codec: encoder versions must be contiguous from 0");
|
||||
}
|
||||
}
|
||||
return table;
|
||||
}
|
||||
|
||||
template <typename T, typename List>
|
||||
inline constexpr auto encoderTableV = makeEncoderTableImpl<T, List>(
|
||||
std::make_index_sequence<std::tuple_size_v<List>>{});
|
||||
|
||||
template <typename T, typename List>
|
||||
Expected<T, CodecError>
|
||||
decodeImpl(CodecEnvelope const& env)
|
||||
{
|
||||
if (env.header.type_code != codecTypeCodeV<T>)
|
||||
{
|
||||
return Unexpected{CodecError::TypeMismatch};
|
||||
}
|
||||
auto const& table = decoderTableV<T, List>;
|
||||
if (env.header.version >= table.size())
|
||||
{
|
||||
return Unexpected{CodecError::UnsupportedVersion};
|
||||
}
|
||||
return table[env.header.version](env);
|
||||
}
|
||||
|
||||
template <typename T, typename List>
|
||||
Expected<CodecEnvelope, CodecError>
|
||||
encodeAt(CodecVersion wireVersion, T const& value)
|
||||
{
|
||||
auto const& table = encoderTableV<T, List>;
|
||||
if (wireVersion >= table.size())
|
||||
{
|
||||
return Unexpected{CodecError::UnsupportedVersion};
|
||||
}
|
||||
return table[wireVersion](value);
|
||||
}
|
||||
|
||||
} // namespace xrpl::wasm::detail
|
||||
@@ -1,12 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <tuple>
|
||||
|
||||
namespace xrpl::wasm::detail {
|
||||
|
||||
template <typename... Traits>
|
||||
struct CodecList {
|
||||
using TupleType = std::tuple<Traits...>;
|
||||
};
|
||||
|
||||
} // namespace xrpl::wasm::detail
|
||||
@@ -1,18 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/Expected.h>
|
||||
#include <xrpl/tx/wasm/CodecEnvelope.h>
|
||||
|
||||
namespace xrpl::wasm::detail {
|
||||
|
||||
// Maps a contract's declared codec revision to the per-type wire version to
|
||||
// emit for `code`. This is the revision/amendment POLICY layer, kept separate
|
||||
// from both the generic dispatch machinery and the concrete value codecs.
|
||||
//
|
||||
// Defined in src/libxrpl/tx/wasm/codecs/CodecRevisions.cpp alongside the
|
||||
// concrete CodecTrait<N> revision maps. (Amendment gating via Rules is a
|
||||
// deliberate TODO there — see the .cpp.)
|
||||
Expected<CodecVersion, CodecError>
|
||||
wireVersionFor(CodecVersion declaredRevision, CodecTypeCode code);
|
||||
|
||||
} // namespace xrpl::wasm::detail
|
||||
@@ -1,18 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/tx/wasm/CodecEnvelope.h>
|
||||
|
||||
namespace xrpl::wasm::detail {
|
||||
|
||||
template <CodecVersion Version>
|
||||
struct CodecTrait;
|
||||
|
||||
// template <>
|
||||
// struct CodecTrait<0> {
|
||||
// static constexpr auto kVersion = CodecVersion{0};
|
||||
// static constexpr auto versions = std::to_array<std::pair<CodecTypeCode, CodecVersion>>({
|
||||
// {codecTypeCodeV<STNumber>, 0},
|
||||
// });
|
||||
// };
|
||||
|
||||
} // namespace xrpl::wasm::detail
|
||||
@@ -1,21 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/tx/wasm/CodecEnvelope.h>
|
||||
|
||||
namespace xrpl::wasm::detail {
|
||||
|
||||
template <typename T>
|
||||
struct CodecTypeCodeOf;
|
||||
|
||||
// Defines a type trait for registering a type code for each
|
||||
// type in the codec.
|
||||
template <typename T>
|
||||
inline constexpr CodecTypeCode codecTypeCodeV = CodecTypeCodeOf<T>::kCodecTypeCode;
|
||||
|
||||
#define XRPL_WASM_CODEC_TYPE_CODE(CppType, Code) \
|
||||
template <> \
|
||||
struct CodecTypeCodeOf<CppType> { \
|
||||
static constexpr CodecTypeCode kCodecTypeCode = Code; \
|
||||
};
|
||||
|
||||
} // namespace xrpl::wasm::detail
|
||||
@@ -1,27 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/tx/wasm/CodecEnvelope.h>
|
||||
|
||||
#include <limits>
|
||||
|
||||
namespace xrpl::wasm::detail {
|
||||
|
||||
// Defines a type trait for registering a specific type code and version
|
||||
// encoding/decoding implementation for each type in the codec.
|
||||
template <CodecTypeCode TypeCode, CodecVersion Version>
|
||||
struct CodecTypeTrait;
|
||||
|
||||
#define XRPL_WASM_CODEC_TYPE_TRAIT(CppType, Version, PayloadSize) \
|
||||
template <> \
|
||||
struct CodecTypeTrait<codecTypeCodeV<CppType>, Version> { \
|
||||
using CodecType = CppType; \
|
||||
static constexpr auto kCodecTypeCode = codecTypeCodeV<CppType>; \
|
||||
static constexpr auto kVersion = Version; \
|
||||
static constexpr auto kPayloadSize = PayloadSize; \
|
||||
static Expected<CodecType, CodecError> decode(CodecEnvelope const& env); \
|
||||
static Expected<CodecEnvelope, CodecError> encode(CodecType const& value); \
|
||||
};
|
||||
|
||||
inline constexpr auto kVariablePayloadSize = std::numeric_limits<CodecLength>::max();
|
||||
|
||||
} // namespace xrpl::wasm::detail
|
||||
@@ -1,48 +0,0 @@
|
||||
//
|
||||
// codec_revisions.macro — the SINGLE registration point for codec
|
||||
// REVISIONS (the "contract-declared codec revision -> per-type wire version"
|
||||
// pinning). Included multiple times with different macro definitions, so NO
|
||||
// include guard.
|
||||
//
|
||||
// A revision fixes, for every type, which wire version a contract that declared
|
||||
// that revision receives. It is deliberately SEPARATE from the (type, version)
|
||||
// registry: adding a codec version does not change any revision — bumping a
|
||||
// revision is its own act, tied to the amendment that enables it.
|
||||
//
|
||||
// CODEC_REVISION_BEGIN(Revision)
|
||||
// CODEC_REVISION_ENTRY(CppType, Version) // repeated, one per type
|
||||
// CODEC_REVISION_END(Revision)
|
||||
//
|
||||
// HOW TO ADD A REVISION (e.g. revision 1 that emits the widened Number v1):
|
||||
// 1. Add a block here:
|
||||
// CODEC_REVISION_BEGIN(1)
|
||||
// CODEC_REVISION_ENTRY(Number, 1) // widened
|
||||
// CODEC_REVISION_ENTRY(AccountID, 0) // unchanged
|
||||
// CODEC_REVISION_END(1)
|
||||
// 2. Gate it behind its amendment in CodecRevisions.cpp (see the
|
||||
// amendment-gating TODO in wireVersionFor).
|
||||
// Both CodecTrait<1> and the wireVersionFor switch case regenerate from this.
|
||||
//
|
||||
|
||||
#ifndef CODEC_REVISION_BEGIN
|
||||
#define CODEC_REVISION_BEGIN(Revision)
|
||||
#endif
|
||||
#ifndef CODEC_REVISION_ENTRY
|
||||
#define CODEC_REVISION_ENTRY(CppType, Version)
|
||||
#endif
|
||||
#ifndef CODEC_REVISION_END
|
||||
#define CODEC_REVISION_END(Revision)
|
||||
#endif
|
||||
|
||||
// clang-format off
|
||||
|
||||
CODEC_REVISION_BEGIN(0)
|
||||
CODEC_REVISION_ENTRY(Number, 0)
|
||||
CODEC_REVISION_ENTRY(AccountID, 0)
|
||||
CODEC_REVISION_END(0)
|
||||
|
||||
// clang-format on
|
||||
|
||||
#undef CODEC_REVISION_BEGIN
|
||||
#undef CODEC_REVISION_ENTRY
|
||||
#undef CODEC_REVISION_END
|
||||
@@ -1,58 +0,0 @@
|
||||
//
|
||||
// codec_types.macro — the SINGLE registration point for wasm codecs.
|
||||
//
|
||||
// This file is #included multiple times with different macro definitions, so
|
||||
// it has NO include guard. Every codec is declared here exactly once, on two
|
||||
// axes, and everything else (the trait tuple, the dispatch tables, the
|
||||
// explicit instantiations, the type-code map) is generated from it — so those
|
||||
// can never drift apart.
|
||||
//
|
||||
// CODEC_TYPE(CppType, TypeCode)
|
||||
// Once per C++ type. Assigns its stable 16-bit wire type code. Drives the
|
||||
// type-code map and the per-type explicit instantiations of decode/encode.
|
||||
//
|
||||
// CODEC_VERSION(CppType, Version, PayloadSize)
|
||||
// Once per (type, version). Declares a CodecTypeTrait specialization and
|
||||
// adds it to the registered-codec tuple. Versions per type MUST be
|
||||
// contiguous from 0 (enforced at compile time).
|
||||
//
|
||||
// HOW TO ADD A CODEC
|
||||
//
|
||||
// A) A new VERSION of an existing type (e.g. widen Number):
|
||||
// 1. Add a line here: CODEC_VERSION(Number, 1, 20)
|
||||
// 2. Define its bodies in the type's .cpp (codecs/NumberCodec.cpp):
|
||||
// CodecTypeTrait<codecTypeCodeV<Number>, 1>::decode/encode
|
||||
// 3. If a codec revision should emit it, add an entry in
|
||||
// codec_revisions.macro.
|
||||
// (Versions must stay contiguous from 0, or the build fails.)
|
||||
//
|
||||
// B) A brand-new TYPE (e.g. STAmount):
|
||||
// 1. CODEC_TYPE(STAmount, 0x0003) and CODEC_VERSION(STAmount, 0, N) here.
|
||||
// 2. Add its C++ header include to codecs/CodecTypes.h.
|
||||
// 3. Create codecs/STAmountCodec.cpp with the decode/encode bodies.
|
||||
// (GLOB_RECURSE picks up the new .cpp automatically — no CMake edit.)
|
||||
// 4. Add a CODEC_REVISION_ENTRY(STAmount, 0) to each relevant revision
|
||||
// block in codec_revisions.macro.
|
||||
//
|
||||
|
||||
#ifndef CODEC_TYPE
|
||||
#define CODEC_TYPE(CppType, TypeCode)
|
||||
#endif
|
||||
#ifndef CODEC_VERSION
|
||||
#define CODEC_VERSION(CppType, Version, PayloadSize)
|
||||
#endif
|
||||
|
||||
// clang-format off
|
||||
|
||||
// C++ type wire type code
|
||||
CODEC_TYPE(Number, 0x0001)
|
||||
CODEC_TYPE(AccountID, 0x0002)
|
||||
|
||||
// C++ type version payload bytes
|
||||
CODEC_VERSION(Number, 0, 12) // int64 mantissa + int32 exponent
|
||||
CODEC_VERSION(AccountID, 0, 20) // 20 raw bytes
|
||||
|
||||
// clang-format on
|
||||
|
||||
#undef CODEC_TYPE
|
||||
#undef CODEC_VERSION
|
||||
@@ -1,175 +0,0 @@
|
||||
# Linux Packaging
|
||||
|
||||
This directory contains all files needed to build RPM and Debian packages for `xrpld`.
|
||||
|
||||
## Directory layout
|
||||
|
||||
```
|
||||
package/
|
||||
build_pkg.sh Staging and build script (called by CMake targets and CI)
|
||||
rpm/
|
||||
xrpld.spec RPM spec (xrpld_version/pkg_release passed via rpmbuild --define)
|
||||
debian/ Debian control files (control, rules, install, links, conffiles, ...)
|
||||
shared/
|
||||
xrpld.service systemd unit file (used by both RPM and DEB)
|
||||
xrpld.sysusers sysusers.d config (used by both RPM and DEB)
|
||||
xrpld.tmpfiles tmpfiles.d config (used by both RPM and DEB)
|
||||
xrpld.logrotate logrotate config (installed to /etc/logrotate.d/xrpld)
|
||||
update-xrpld auto-update script (installed to /usr/libexec/xrpld/, run by update-xrpld.timer)
|
||||
```
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Packaging targets and their container images are declared in
|
||||
[`.github/scripts/strategy-matrix/linux.json`](../.github/scripts/strategy-matrix/linux.json)
|
||||
via a `"package": true` field on specific os entries. Today only
|
||||
`linux/amd64` is emitted; the architecture is hardcoded in `generate.py`
|
||||
and the workflow runner. The package format
|
||||
(deb or rpm) is inferred at build time from the container's package manager
|
||||
(`apt-get` -> deb, `dnf`/`yum` -> rpm). The image tag is composed as
|
||||
`ghcr.io/xrplf/ci/{distro}-{version}:{compiler}-{cver}-sha-{image_sha}` —
|
||||
the same scheme used by `reusable-build-test.yml`. Bump `image_sha` in
|
||||
`linux.json` and both CI and local builds pick up the new image with no
|
||||
workflow edits.
|
||||
|
||||
| Package type | Image (derived from `linux.json`) | Tool required |
|
||||
| ------------ | ---------------------------------------------------- | --------------------------------------------------------------- |
|
||||
| RPM | `ghcr.io/xrplf/ci/rhel-9:gcc-12-sha-<git_sha>` | `rpmbuild` |
|
||||
| DEB | `ghcr.io/xrplf/ci/ubuntu-jammy:gcc-12-sha-<git_sha>` | `dpkg-buildpackage`, `debhelper (>= 13)`, `dh-sequence-systemd` |
|
||||
|
||||
To print the exact image tags for the current `linux.json`:
|
||||
|
||||
```bash
|
||||
./.github/scripts/strategy-matrix/generate.py --packaging --config=.github/scripts/strategy-matrix/linux.json
|
||||
```
|
||||
|
||||
## Building packages
|
||||
|
||||
### Via CI
|
||||
|
||||
Caller workflows (`on-pr.yml`, `on-tag.yml`, `on-trigger.yml`) call
|
||||
`reusable-strategy-matrix.yml` with `mode: packaging` to generate the matrix of
|
||||
`{artifact_name, os}` entries, then fan out to
|
||||
`reusable-package.yml` per entry. That workflow downloads the pre-built `xrpld`
|
||||
binary artifact, detects the package format from the container, and calls
|
||||
`build_pkg.sh` directly — no CMake configure or build step is needed inside
|
||||
the packaging job.
|
||||
|
||||
### Locally (mirrors CI)
|
||||
|
||||
With an `xrpld` binary already built at `build/xrpld`, run the packaging step
|
||||
inside the same container CI uses. The image tag is derived from `linux.json`
|
||||
so you don't need to hardcode a SHA.
|
||||
|
||||
```bash
|
||||
# From the repo root. Pick any image flagged with `"package": true` in
|
||||
# linux.json; the package format is inferred from the container's package
|
||||
# manager. Example for the rpm-producing image:
|
||||
IMAGE=$(jq -r '
|
||||
.os | map(select(.package == true))[0] |
|
||||
"ghcr.io/xrplf/ci/\(.distro_name)-\(.distro_version):\(.compiler_name)-\(.compiler_version)-sha-\(.image_sha)"
|
||||
' .github/scripts/strategy-matrix/linux.json)
|
||||
|
||||
VERSION=2.4.0-local
|
||||
PKG_RELEASE=1
|
||||
|
||||
docker run --rm \
|
||||
-v "$(pwd):/src" \
|
||||
-w /src \
|
||||
"$IMAGE" \
|
||||
./package/build_pkg.sh --pkg-version "$VERSION" --pkg-release "$PKG_RELEASE"
|
||||
|
||||
# Output:
|
||||
# build/debbuild/*.deb (DEB + dbgsym .ddeb)
|
||||
# build/rpmbuild/RPMS/x86_64/*.rpm
|
||||
```
|
||||
|
||||
### Via CMake (host-side target)
|
||||
|
||||
If you run CMake configure on a host that has `rpmbuild` or `dpkg-buildpackage`
|
||||
installed natively, you can use the CMake target directly — no container
|
||||
needed, but the host toolchain replaces the pinned CI image:
|
||||
|
||||
```bash
|
||||
cmake \
|
||||
-Dxrpld=ON \
|
||||
-Dxrpld_version=2.4.0-local \
|
||||
-Dtests=OFF \
|
||||
..
|
||||
|
||||
cmake --build . --target package # deb on Debian/Ubuntu, rpm on RHEL
|
||||
```
|
||||
|
||||
The `cmake/XrplPackaging.cmake` module defines the target only if at least one
|
||||
of `rpmbuild` / `dpkg-buildpackage` is present; `build_pkg.sh` then infers the
|
||||
package format from the host's package manager. The packaging script installs
|
||||
to FHS-standard paths (`/usr/bin`, `/etc/xrpld`, etc.) regardless of
|
||||
`CMAKE_INSTALL_PREFIX`.
|
||||
|
||||
## How `build_pkg.sh` works
|
||||
|
||||
`build_pkg.sh` accepts long-form flags, each of which can also be set via an
|
||||
environment variable. Flags override env vars; env vars override the built-in
|
||||
defaults. Run `./package/build_pkg.sh --help` for the same table:
|
||||
|
||||
| Flag | Env var | Default | Purpose |
|
||||
| -------------------------- | ------------------- | ----------------------------- | ----------------------------------- |
|
||||
| `--src-dir DIR` | `SRC_DIR` | `$PWD` | repo root |
|
||||
| `--build-dir DIR` | `BUILD_DIR` | `$PWD/build` | directory holding pre-built `xrpld` |
|
||||
| `--pkg-version STR` | `PKG_VERSION` | parsed from `xrpld --version` | version string, e.g. `3.2.0-b1` |
|
||||
| `--pkg-release N` | `PKG_RELEASE` | `1` | package release number |
|
||||
| `--source-date-epoch SECS` | `SOURCE_DATE_EPOCH` | latest git commit ctime | reproducibility timestamp |
|
||||
|
||||
The package format (`deb` or `rpm`) is inferred from the host's package
|
||||
manager (`apt-get` -> deb, `dnf`/`yum` -> rpm). Hosts without one of those
|
||||
fail early.
|
||||
|
||||
Flags are for explicit invocation; environment variables are intended for
|
||||
CMake/systemd/CI integration. The CI workflow and the CMake `package` target
|
||||
both invoke `build_pkg.sh` with no flags, configuring it entirely via env
|
||||
(see `cmake/XrplPackaging.cmake`).
|
||||
|
||||
It resolves `SRC_DIR` and `BUILD_DIR` to absolute paths, then calls
|
||||
`stage_common()` to copy the binary, config files, and shared support files
|
||||
into the staging area, and invokes the platform build tool.
|
||||
|
||||
### RPM
|
||||
|
||||
1. Creates the standard `rpmbuild/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS}` tree inside the build directory.
|
||||
2. Copies `xrpld.spec` and all source files (binary, configs, service files) into `SOURCES/`.
|
||||
3. Runs `rpmbuild -bb --define "xrpld_version ..." --define "pkg_release ..."`. The spec uses manual `install` commands to place files.
|
||||
4. Output: `rpmbuild/RPMS/x86_64/xrpld-*.rpm`
|
||||
|
||||
### DEB
|
||||
|
||||
1. Creates a staging source tree at `debbuild/source/` inside the build directory.
|
||||
2. Stages the binary, configs, `README.md`, and `LICENSE.md`.
|
||||
3. Copies `package/debian/` control files into `debbuild/source/debian/`.
|
||||
4. Copies shared service/sysusers/tmpfiles into `debian/` where `dh_installsystemd`, `dh_installsysusers`, and `dh_installtmpfiles` pick them up automatically.
|
||||
5. Generates a minimal `debian/changelog` (pre-release versions use `~` instead of `-`).
|
||||
6. Runs `dpkg-buildpackage -b --no-sign`. `debian/rules` uses manual `install` commands.
|
||||
7. Output: `debbuild/*.deb` and `debbuild/*.ddeb` (dbgsym package)
|
||||
|
||||
## Post-build verification
|
||||
|
||||
```bash
|
||||
# DEB
|
||||
dpkg-deb -c debbuild/*.deb | grep -E 'systemd|sysusers|tmpfiles'
|
||||
lintian -I debbuild/*.deb
|
||||
|
||||
# RPM
|
||||
rpm -qlp rpmbuild/RPMS/x86_64/*.rpm
|
||||
```
|
||||
|
||||
## Reproducibility
|
||||
|
||||
The following environment variables improve build reproducibility. They are not
|
||||
set automatically by `build_pkg.sh`; set them manually if needed:
|
||||
|
||||
```bash
|
||||
export SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)
|
||||
export TZ=UTC
|
||||
export LC_ALL=C.UTF-8
|
||||
export GZIP=-n
|
||||
export DEB_BUILD_OPTIONS="noautodbgsym reproducible=+fixfilepath"
|
||||
```
|
||||
@@ -1,192 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Build an RPM or Debian package from a pre-built xrpld binary.
|
||||
#
|
||||
# Flags override env vars; env vars override defaults. Env vars are intended
|
||||
# for CMake/systemd/CI integration; flags are for explicit invocation.
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage: build_pkg.sh [options]
|
||||
|
||||
Options (each can also be set via the env var shown):
|
||||
--src-dir DIR repo root [SRC_DIR; default: $PWD]
|
||||
--build-dir DIR directory holding xrpld [BUILD_DIR; default: $PWD/build]
|
||||
--pkg-version STR version, e.g. 3.2.0-b1 [PKG_VERSION; default: parsed from xrpld --version]
|
||||
--pkg-release N package release number [PKG_RELEASE; default: 1]
|
||||
--source-date-epoch SECS reproducibility timestamp [SOURCE_DATE_EPOCH; default: latest git commit ctime]
|
||||
-h, --help show this help and exit
|
||||
EOF
|
||||
}
|
||||
|
||||
need_arg() {
|
||||
if [[ $# -lt 2 || "$2" == --* ]]; then
|
||||
echo "Missing value for $1" >&2
|
||||
exit 2
|
||||
fi
|
||||
}
|
||||
|
||||
# Seed from env. CLI parsing below overrides these directly.
|
||||
SRC_DIR="${SRC_DIR:-}"
|
||||
BUILD_DIR="${BUILD_DIR:-}"
|
||||
PKG_VERSION="${PKG_VERSION:-}"
|
||||
PKG_RELEASE="${PKG_RELEASE:-}"
|
||||
SOURCE_DATE_EPOCH="${SOURCE_DATE_EPOCH:-}"
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--src-dir) need_arg "$@"; SRC_DIR="$2"; shift 2 ;;
|
||||
--build-dir) need_arg "$@"; BUILD_DIR="$2"; shift 2 ;;
|
||||
--pkg-version) need_arg "$@"; PKG_VERSION="$2"; shift 2 ;;
|
||||
--pkg-release) need_arg "$@"; PKG_RELEASE="$2"; shift 2 ;;
|
||||
--source-date-epoch) need_arg "$@"; SOURCE_DATE_EPOCH="$2"; shift 2 ;;
|
||||
-h|--help) usage; exit 0 ;;
|
||||
*)
|
||||
echo "Unknown argument: $1" >&2
|
||||
usage >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
SRC_DIR="$(cd "${SRC_DIR:-${PWD}}" && pwd)"
|
||||
BUILD_DIR="$(cd "${BUILD_DIR:-${PWD}/build}" && pwd)"
|
||||
PKG_RELEASE="${PKG_RELEASE:-1}"
|
||||
|
||||
if [[ -z "${PKG_VERSION}" ]]; then
|
||||
PKG_VERSION="$("${BUILD_DIR}/xrpld" --version | awk 'NR==1 {print $3; exit}')"
|
||||
fi
|
||||
|
||||
if [[ -z "${PKG_VERSION}" ]]; then
|
||||
echo "PKG_VERSION is empty (not provided and could not be derived)." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
VERSION="${PKG_VERSION}"
|
||||
|
||||
if command -v apt-get >/dev/null 2>&1; then
|
||||
pkg_type=deb
|
||||
elif command -v dnf >/dev/null 2>&1 || command -v yum >/dev/null 2>&1; then
|
||||
pkg_type=rpm
|
||||
else
|
||||
echo "Cannot infer pkg_type: no apt-get, dnf, or yum on PATH." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z "${SOURCE_DATE_EPOCH}" ]]; then
|
||||
if git -C "$SRC_DIR" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
||||
SOURCE_DATE_EPOCH="$(git -C "$SRC_DIR" log -1 --format=%ct)"
|
||||
else
|
||||
SOURCE_DATE_EPOCH="$(date +%s)"
|
||||
fi
|
||||
fi
|
||||
|
||||
export SOURCE_DATE_EPOCH
|
||||
CHANGELOG_DATE="$(date -u -R -d "@$SOURCE_DATE_EPOCH")"
|
||||
|
||||
# Split VERSION at the first '-' into base and optional pre-release suffix.
|
||||
# Examples: "3.2.0" -> ("3.2.0", ""); "3.2.0-b1" -> ("3.2.0", "b1").
|
||||
VER_BASE="${VERSION%%-*}"
|
||||
VER_SUFFIX="${VERSION#*-}"
|
||||
[[ "${VER_SUFFIX}" == "${VERSION}" ]] && VER_SUFFIX=""
|
||||
|
||||
# Reject multi-segment suffixes (e.g. "beta-1", "rc1-15-gabc123"). The RPM
|
||||
# Release field forbids '-', and the convention here is single-token suffixes
|
||||
# like b1 or rc2. Fail early with a clear message rather than letting either
|
||||
# rpmbuild blow up or silently mangling dashes into dots.
|
||||
if [[ "${VER_SUFFIX}" == *-* ]]; then
|
||||
echo "build_pkg.sh: multi-segment pre-release in VERSION='${VERSION}' (suffix '${VER_SUFFIX}')." >&2
|
||||
echo "Use single-token suffixes like 3.2.0-b1 or 3.2.0-rc2." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SHARED="${SRC_DIR}/package/shared"
|
||||
DEBIAN_DIR="${SRC_DIR}/package/debian"
|
||||
|
||||
# Stage files that both packaging systems consume using the same filenames.
|
||||
stage_common() {
|
||||
local dest="$1"
|
||||
mkdir -p "${dest}"
|
||||
|
||||
cp "${BUILD_DIR}/xrpld" "${dest}/xrpld"
|
||||
cp "${SRC_DIR}/cfg/xrpld-example.cfg" "${dest}/xrpld.cfg"
|
||||
cp "${SRC_DIR}/cfg/validators-example.txt" "${dest}/validators.txt"
|
||||
cp "${SRC_DIR}/LICENSE.md" "${dest}/LICENSE.md"
|
||||
cp "${SRC_DIR}/README.md" "${dest}/README.md"
|
||||
|
||||
cp "${SHARED}/xrpld.service" "${dest}/xrpld.service"
|
||||
cp "${SHARED}/xrpld.sysusers" "${dest}/xrpld.sysusers"
|
||||
cp "${SHARED}/xrpld.tmpfiles" "${dest}/xrpld.tmpfiles"
|
||||
cp "${SHARED}/xrpld.logrotate" "${dest}/xrpld.logrotate"
|
||||
cp "${SHARED}/update-xrpld" "${dest}/update-xrpld"
|
||||
cp "${SHARED}/update-xrpld.service" "${dest}/update-xrpld.service"
|
||||
cp "${SHARED}/update-xrpld.timer" "${dest}/update-xrpld.timer"
|
||||
cp "${SHARED}/50-xrpld.preset" "${dest}/50-xrpld.preset"
|
||||
}
|
||||
|
||||
build_rpm() {
|
||||
local topdir="${BUILD_DIR}/rpmbuild"
|
||||
rm -rf "${topdir}"
|
||||
mkdir -p "${topdir}"/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS}
|
||||
|
||||
cp "${SRC_DIR}/package/rpm/xrpld.spec" "${topdir}/SPECS/xrpld.spec"
|
||||
stage_common "${topdir}/SOURCES"
|
||||
|
||||
# RPM Version can't contain '-'. A pre-release goes in Release with a
|
||||
# leading "0." so 3.2.0-b1 sorts before the final 3.2.0-<pkg_release>.
|
||||
local rpm_release="${PKG_RELEASE}"
|
||||
[[ -n "${VER_SUFFIX}" ]] && rpm_release="0.${VER_SUFFIX}.${PKG_RELEASE}"
|
||||
|
||||
set -x
|
||||
rpmbuild -bb \
|
||||
--define "_topdir ${topdir}" \
|
||||
--define "xrpld_version ${VER_BASE}" \
|
||||
--define "xrpld_release ${rpm_release}" \
|
||||
"${topdir}/SPECS/xrpld.spec"
|
||||
}
|
||||
|
||||
build_deb() {
|
||||
local staging="${BUILD_DIR}/debbuild/source"
|
||||
rm -rf "${staging}"
|
||||
mkdir -p "${staging}"
|
||||
|
||||
stage_common "${staging}"
|
||||
cp -r "${DEBIAN_DIR}" "${staging}/debian"
|
||||
|
||||
# Debhelper auto-discovers these only from debian/.
|
||||
cp "${staging}/xrpld.service" "${staging}/debian/xrpld.service"
|
||||
cp "${staging}/xrpld.sysusers" "${staging}/debian/xrpld.sysusers"
|
||||
cp "${staging}/xrpld.tmpfiles" "${staging}/debian/xrpld.tmpfiles"
|
||||
cp "${staging}/xrpld.logrotate" "${staging}/debian/xrpld.logrotate"
|
||||
cp "${staging}/update-xrpld.service" "${staging}/debian/xrpld.update-xrpld.service"
|
||||
cp "${staging}/update-xrpld.timer" "${staging}/debian/xrpld.update-xrpld.timer"
|
||||
|
||||
# Debian '~' marks a pre-release; 3.2.0~b1 sorts before 3.2.0.
|
||||
local deb_full_version="${VER_BASE}${VER_SUFFIX:+~${VER_SUFFIX}}-${PKG_RELEASE}"
|
||||
|
||||
# Derive release channel from the version suffix:
|
||||
# (none) -> stable (tagged release)
|
||||
# b0 -> develop (develop-branch build)
|
||||
# b<N>, rc<N> -> unstable (pre-release)
|
||||
local deb_distribution
|
||||
case "${VER_SUFFIX}" in
|
||||
"") deb_distribution="stable" ;;
|
||||
b0) deb_distribution="develop" ;;
|
||||
*) deb_distribution="unstable" ;;
|
||||
esac
|
||||
|
||||
cat > "${staging}/debian/changelog" <<EOF
|
||||
xrpld (${deb_full_version}) ${deb_distribution}; urgency=medium
|
||||
* Release ${VERSION}.
|
||||
|
||||
-- XRPL Foundation <contact@xrplf.org> ${CHANGELOG_DATE}
|
||||
EOF
|
||||
|
||||
chmod +x "${staging}/debian/rules"
|
||||
|
||||
set -x
|
||||
( cd "${staging}" && dpkg-buildpackage -b --no-sign -d )
|
||||
}
|
||||
|
||||
"build_${pkg_type}"
|
||||
@@ -1,23 +0,0 @@
|
||||
Source: xrpld
|
||||
Section: net
|
||||
Priority: optional
|
||||
Maintainer: XRPL Foundation <contact@xrplf.org>
|
||||
Rules-Requires-Root: no
|
||||
Build-Depends:
|
||||
debhelper-compat (= 13)
|
||||
Standards-Version: 4.7.0
|
||||
Homepage: https://github.com/XRPLF/rippled
|
||||
Vcs-Git: https://github.com/XRPLF/rippled.git
|
||||
Vcs-Browser: https://github.com/XRPLF/rippled
|
||||
|
||||
Package: xrpld
|
||||
Section: net
|
||||
Priority: optional
|
||||
Architecture: any
|
||||
Depends:
|
||||
${shlibs:Depends},
|
||||
${misc:Depends}
|
||||
Description: XRP Ledger daemon
|
||||
Reference implementation of the XRP Ledger protocol.
|
||||
Participates in the peer-to-peer network, processes transactions,
|
||||
and maintains a local ledger copy.
|
||||
@@ -1,18 +0,0 @@
|
||||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
Upstream-Name: rippled
|
||||
Source: https://github.com/XRPLF/rippled
|
||||
|
||||
Files: *
|
||||
Copyright: 2011-present, the XRP Ledger developers
|
||||
License: ISC
|
||||
Permission to use, copy, modify, and 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.
|
||||
@@ -1,27 +0,0 @@
|
||||
#!/usr/bin/make -f
|
||||
|
||||
export DH_VERBOSE = 1
|
||||
|
||||
%:
|
||||
dh $@
|
||||
|
||||
override_dh_auto_configure override_dh_auto_build override_dh_auto_test:
|
||||
@:
|
||||
|
||||
override_dh_installsystemd:
|
||||
dh_installsystemd --no-start xrpld.service
|
||||
dh_installsystemd --name=update-xrpld --no-start update-xrpld.service update-xrpld.timer
|
||||
|
||||
execute_before_dh_installtmpfiles:
|
||||
dh_installsysusers
|
||||
|
||||
override_dh_installsysusers:
|
||||
|
||||
override_dh_install:
|
||||
install -D -m 0755 xrpld debian/xrpld/usr/bin/xrpld
|
||||
install -D -m 0644 xrpld.cfg debian/xrpld/etc/xrpld/xrpld.cfg
|
||||
install -D -m 0644 validators.txt debian/xrpld/etc/xrpld/validators.txt
|
||||
install -D -m 0755 update-xrpld debian/xrpld/usr/libexec/xrpld/update-xrpld
|
||||
|
||||
override_dh_dwz:
|
||||
@:
|
||||
@@ -1 +0,0 @@
|
||||
3.0 (quilt)
|
||||
@@ -1,2 +0,0 @@
|
||||
README.md
|
||||
LICENSE.md
|
||||
@@ -1,2 +0,0 @@
|
||||
# Legacy compat symlinks (remove next major release)
|
||||
usr/bin/xrpld usr/local/bin/rippled
|
||||
@@ -1,100 +0,0 @@
|
||||
Name: xrpld
|
||||
Version: %{xrpld_version}
|
||||
Release: %{xrpld_release}%{?dist}
|
||||
Summary: XRP Ledger daemon
|
||||
|
||||
License: ISC
|
||||
URL: https://github.com/XRPLF/rippled
|
||||
|
||||
ExclusiveArch: x86_64 aarch64
|
||||
BuildRequires: systemd-rpm-macros
|
||||
|
||||
%undefine _debugsource_packages
|
||||
%debug_package
|
||||
|
||||
%build_mtime_policy clamp_to_source_date_epoch
|
||||
|
||||
%{?systemd_requires}
|
||||
%{?sysusers_requires_compat}
|
||||
|
||||
%description
|
||||
xrpld is the reference implementation of the XRP Ledger protocol. It
|
||||
participates in the peer-to-peer XRP Ledger network, processes
|
||||
transactions, and maintains the ledger database.
|
||||
|
||||
%prep
|
||||
:
|
||||
|
||||
%build
|
||||
:
|
||||
|
||||
%install
|
||||
install -Dm0755 %{_sourcedir}/xrpld %{buildroot}%{_bindir}/%{name}
|
||||
install -Dm0644 %{_sourcedir}/xrpld.cfg %{buildroot}%{_sysconfdir}/%{name}/xrpld.cfg
|
||||
install -Dm0644 %{_sourcedir}/validators.txt %{buildroot}%{_sysconfdir}/%{name}/validators.txt
|
||||
|
||||
# systemd units, sysusers, tmpfiles, preset
|
||||
install -Dm0644 %{_sourcedir}/xrpld.service %{buildroot}%{_unitdir}/xrpld.service
|
||||
install -Dm0644 %{_sourcedir}/update-xrpld.service %{buildroot}%{_unitdir}/update-xrpld.service
|
||||
install -Dm0644 %{_sourcedir}/update-xrpld.timer %{buildroot}%{_unitdir}/update-xrpld.timer
|
||||
install -Dm0644 %{_sourcedir}/xrpld.sysusers %{buildroot}%{_sysusersdir}/xrpld.conf
|
||||
install -Dm0644 %{_sourcedir}/xrpld.tmpfiles %{buildroot}%{_tmpfilesdir}/xrpld.conf
|
||||
install -Dm0644 %{_sourcedir}/50-xrpld.preset %{buildroot}%{_presetdir}/50-xrpld.preset
|
||||
|
||||
# Logrotate config
|
||||
install -Dm0644 %{_sourcedir}/xrpld.logrotate %{buildroot}%{_sysconfdir}/logrotate.d/%{name}
|
||||
|
||||
# Update helper
|
||||
install -Dm0755 %{_sourcedir}/update-xrpld %{buildroot}%{_libexecdir}/%{name}/update-xrpld
|
||||
|
||||
# Docs
|
||||
install -Dm0644 %{_sourcedir}/LICENSE.md %{buildroot}%{_docdir}/%{name}/LICENSE.md
|
||||
install -Dm0644 %{_sourcedir}/README.md %{buildroot}%{_docdir}/%{name}/README.md
|
||||
|
||||
# Legacy compatibility for pre-FHS package layouts.
|
||||
# TODO: remove after rippled fully deprecated.
|
||||
install -d %{buildroot}/usr/local/bin
|
||||
ln -s %{_bindir}/%{name} %{buildroot}/usr/local/bin/rippled
|
||||
|
||||
%pre
|
||||
%sysusers_create_package %{name} %{_sourcedir}/xrpld.sysusers
|
||||
|
||||
%post
|
||||
systemd-tmpfiles --create %{_tmpfilesdir}/xrpld.conf || :
|
||||
%systemd_post xrpld.service update-xrpld.timer
|
||||
|
||||
%preun
|
||||
%systemd_preun xrpld.service update-xrpld.timer
|
||||
|
||||
%postun
|
||||
%systemd_postun_with_restart xrpld.service
|
||||
|
||||
%files
|
||||
%license %{_docdir}/%{name}/LICENSE.md
|
||||
%doc %{_docdir}/%{name}/README.md
|
||||
|
||||
%dir %{_sysconfdir}/%{name}
|
||||
%dir %{_libexecdir}/%{name}
|
||||
|
||||
%{_bindir}/%{name}
|
||||
|
||||
%config(noreplace) %{_sysconfdir}/%{name}/xrpld.cfg
|
||||
%config(noreplace) %{_sysconfdir}/%{name}/validators.txt
|
||||
%config(noreplace) %{_sysconfdir}/logrotate.d/%{name}
|
||||
|
||||
%{_libexecdir}/%{name}/update-xrpld
|
||||
|
||||
%{_unitdir}/xrpld.service
|
||||
%{_unitdir}/update-xrpld.service
|
||||
%{_unitdir}/update-xrpld.timer
|
||||
%{_presetdir}/50-xrpld.preset
|
||||
%{_sysusersdir}/xrpld.conf
|
||||
%{_tmpfilesdir}/xrpld.conf
|
||||
|
||||
%ghost %dir /var/lib/%{name}
|
||||
%ghost %dir /var/log/%{name}
|
||||
|
||||
|
||||
# Legacy compatibility for pre-FHS package layouts.
|
||||
# TODO: remove after rippled fully deprecated.
|
||||
/usr/local/bin/rippled
|
||||
@@ -1,4 +0,0 @@
|
||||
# /usr/lib/systemd/system-preset/50-xrpld.preset
|
||||
enable xrpld.service
|
||||
# Don't enable automatic updates
|
||||
disable update-xrpld.timer
|
||||
@@ -1,152 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Optional: also write logs to a legacy file in addition to journald.
|
||||
# By default, this script logs to systemd/journald, viewable via:
|
||||
# journalctl -t update-xrpld
|
||||
#
|
||||
# Uncomment the line below if you need a flat file for compatibility with
|
||||
# external tooling, manual inspection, or environments where journald logs
|
||||
# are not persisted or easily accessible.
|
||||
#
|
||||
# Note: This duplicates all output (stdout/stderr) to both journald and the file.
|
||||
# It is generally not needed on modern systems and may cause log file growth
|
||||
# if left enabled long-term.
|
||||
#
|
||||
# Requires /var/log/xrpld/ to exist and be writable by the service (root).
|
||||
#
|
||||
# exec > >(tee -a /var/log/xrpld/update.log) 2>&1
|
||||
|
||||
PATH=/usr/sbin:/usr/bin:/sbin:/bin
|
||||
|
||||
PKG_NAME=${PKG_NAME:-xrpld}
|
||||
|
||||
log() {
|
||||
# If running under systemd/journald, let it handle timestamps.
|
||||
if [[ -n "${JOURNAL_STREAM:-}" ]]; then
|
||||
printf '%s\n' "$*"
|
||||
else
|
||||
printf '%s %s\n' "$(date -u +'%Y-%m-%dT%H:%M:%SZ')" "$*"
|
||||
fi
|
||||
}
|
||||
|
||||
require_root() {
|
||||
if [[ ${EUID:-$(id -u)} -ne 0 ]]; then
|
||||
log "RESULT: failed reason=not-root"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
get_installed_version() {
|
||||
if command -v dpkg-query >/dev/null 2>&1; then
|
||||
dpkg-query -W -f='${Version}' "$PKG_NAME" 2>/dev/null || printf 'unknown'
|
||||
elif command -v rpm >/dev/null 2>&1; then
|
||||
rpm -q --qf '%{VERSION}-%{RELEASE}' "$PKG_NAME" 2>/dev/null || printf 'unknown'
|
||||
else
|
||||
printf 'unknown'
|
||||
fi
|
||||
}
|
||||
|
||||
trap 'log "RESULT: failed reason=script-error exit_code=$?"' ERR
|
||||
|
||||
apt_can_update() {
|
||||
apt-get update -qq
|
||||
apt-get -s --only-upgrade install "$PKG_NAME" 2>/dev/null | grep -q "^Inst ${PKG_NAME}\b"
|
||||
}
|
||||
|
||||
apt_apply_update() {
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y -qq \
|
||||
-o Dpkg::Options::="--force-confdef" \
|
||||
-o Dpkg::Options::="--force-confold" \
|
||||
"$PKG_NAME"
|
||||
}
|
||||
|
||||
get_rpm_pm() {
|
||||
if command -v dnf >/dev/null 2>&1; then
|
||||
printf 'dnf\n'
|
||||
elif command -v yum >/dev/null 2>&1; then
|
||||
printf 'yum\n'
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
rpm_refresh_metadata() {
|
||||
local pm=$1
|
||||
if [[ "$pm" == "dnf" ]]; then
|
||||
dnf makecache --refresh -q >/dev/null
|
||||
else
|
||||
yum clean expire-cache -q >/dev/null
|
||||
fi
|
||||
}
|
||||
|
||||
rpm_can_update() {
|
||||
local pm=$1
|
||||
|
||||
rpm_refresh_metadata "$pm"
|
||||
local rc=0
|
||||
set +e
|
||||
"$pm" check-update -q "$PKG_NAME" >/dev/null 2>&1
|
||||
rc=$?
|
||||
set -e
|
||||
|
||||
if [[ $rc -eq 100 ]]; then
|
||||
return 0
|
||||
elif [[ $rc -eq 0 ]]; then
|
||||
return 1
|
||||
else
|
||||
log "$pm check-update failed with exit code ${rc}."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
rpm_apply_update() {
|
||||
local pm=$1
|
||||
"$pm" update -y "$PKG_NAME"
|
||||
}
|
||||
|
||||
restart_service() {
|
||||
# Preserve the operator's prior service state: if xrpld was intentionally
|
||||
# stopped before the update, don't bring it back up just because the
|
||||
# auto-update timer fired.
|
||||
if systemctl is-active --quiet "${PKG_NAME}.service"; then
|
||||
systemctl restart "${PKG_NAME}.service"
|
||||
log "${PKG_NAME} service restarted successfully."
|
||||
else
|
||||
log "${PKG_NAME} service was not running; skipping restart to preserve prior state."
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
require_root
|
||||
if command -v apt-get >/dev/null 2>&1; then
|
||||
log "Checking for ${PKG_NAME} updates via apt"
|
||||
if apt_can_update; then
|
||||
log "Update available; installing."
|
||||
apt_apply_update
|
||||
restart_service
|
||||
log "RESULT: updated ${PKG_NAME}=$(get_installed_version)"
|
||||
else
|
||||
log "RESULT: no-update ${PKG_NAME}=$(get_installed_version)"
|
||||
fi
|
||||
return
|
||||
fi
|
||||
|
||||
local rpm_pm=""
|
||||
if rpm_pm="$(get_rpm_pm)"; then
|
||||
log "Checking for ${PKG_NAME} updates via ${rpm_pm}"
|
||||
if rpm_can_update "$rpm_pm"; then
|
||||
log "Update available; installing"
|
||||
rpm_apply_update "$rpm_pm"
|
||||
restart_service
|
||||
log "RESULT: updated ${PKG_NAME}=$(get_installed_version)"
|
||||
else
|
||||
log "RESULT: no-update ${PKG_NAME}=$(get_installed_version)"
|
||||
fi
|
||||
return
|
||||
fi
|
||||
log "RESULT: failed reason=no-package-manager"
|
||||
exit 1
|
||||
}
|
||||
|
||||
main "$@"
|
||||
@@ -1,16 +0,0 @@
|
||||
[Unit]
|
||||
Description=Check for and install xrpld package updates
|
||||
Documentation=man:systemd.service(5)
|
||||
Wants=network-online.target
|
||||
After=network-online.target
|
||||
ConditionPathExists=/usr/libexec/xrpld/update-xrpld
|
||||
ConditionPathExists=/usr/bin/xrpld
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/bin/flock -n /run/lock/xrpld-update.lock /usr/libexec/xrpld/update-xrpld
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
SyslogIdentifier=update-xrpld
|
||||
TimeoutStartSec=30min
|
||||
PrivateTmp=true
|
||||
@@ -1,10 +0,0 @@
|
||||
[Unit]
|
||||
Description=Daily xrpld update check
|
||||
|
||||
[Timer]
|
||||
OnCalendar=*-*-* 00:00:00
|
||||
RandomizedDelaySec=24h
|
||||
Persistent=true
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
@@ -1,19 +0,0 @@
|
||||
/var/log/xrpld/*.log {
|
||||
daily
|
||||
minsize 200M
|
||||
rotate 7
|
||||
nocreate
|
||||
missingok
|
||||
notifempty
|
||||
compress
|
||||
compresscmd /usr/bin/gzip
|
||||
compressext .gz
|
||||
postrotate
|
||||
# Only signal the daemon if it's actually running; otherwise the RPC
|
||||
# call returns a transport error and logrotate marks the rotation as
|
||||
# failed, generating recurring errors on stopped nodes.
|
||||
if systemctl is-active --quiet xrpld; then
|
||||
/usr/bin/xrpld --conf /etc/xrpld/xrpld.cfg logrotate
|
||||
fi
|
||||
endscript
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
[Unit]
|
||||
Description=XRP Ledger Daemon
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
StartLimitIntervalSec=300
|
||||
StartLimitBurst=5
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/usr/bin/xrpld --net --silent --conf /etc/xrpld/xrpld.cfg
|
||||
Restart=always
|
||||
RestartSec=5s
|
||||
NoNewPrivileges=true
|
||||
ProtectSystem=full
|
||||
ProtectHome=true
|
||||
PrivateTmp=true
|
||||
User=xrpld
|
||||
Group=xrpld
|
||||
LimitNOFILE=65536
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -1 +0,0 @@
|
||||
u xrpld - "XRP Ledger daemon" /var/lib/xrpld /sbin/nologin
|
||||
@@ -1,2 +0,0 @@
|
||||
d /var/lib/xrpld 0750 xrpld xrpld -
|
||||
d /var/log/xrpld 0750 xrpld xrpld -
|
||||
@@ -25,7 +25,6 @@
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/protocol/TxFormats.h>
|
||||
#include <xrpl/protocol/UintTypes.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
|
||||
@@ -524,6 +523,22 @@ canTrade(ReadView const& view, Asset const& asset)
|
||||
});
|
||||
}
|
||||
|
||||
TER
|
||||
canMPTTradeAndTransfer(
|
||||
ReadView const& view,
|
||||
Asset const& asset,
|
||||
AccountID const& from,
|
||||
AccountID const& to)
|
||||
{
|
||||
if (!asset.holds<MPTIssue>())
|
||||
return tesSUCCESS;
|
||||
|
||||
if (auto const ter = canTrade(view, asset); !isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
return canTransfer(view, asset, from, to);
|
||||
}
|
||||
|
||||
TER
|
||||
lockEscrowMPT(ApplyView& view, AccountID const& sender, STAmount const& amount, beast::Journal j)
|
||||
{
|
||||
@@ -891,65 +906,4 @@ issuerSelfDebitHookMPT(ApplyView& view, MPTIssue const& issue, std::uint64_t amo
|
||||
view.issuerSelfDebitHookMPT(issue, amount, available);
|
||||
}
|
||||
|
||||
static TER
|
||||
checkMPTAllowed(ReadView const& view, TxType txType, Asset const& asset, AccountID const& accountID)
|
||||
{
|
||||
if (!asset.holds<MPTIssue>())
|
||||
return tesSUCCESS;
|
||||
|
||||
auto const& issuanceID = asset.get<MPTIssue>().getMptID();
|
||||
auto const validTx = txType == ttAMM_CREATE || txType == ttAMM_DEPOSIT ||
|
||||
txType == ttAMM_WITHDRAW || txType == ttOFFER_CREATE || txType == ttCHECK_CREATE ||
|
||||
txType == ttCHECK_CASH || txType == ttPAYMENT;
|
||||
XRPL_ASSERT(validTx, "xrpl::checkMPTAllowed : all MPT tx or DEX");
|
||||
if (!validTx)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const& issuer = asset.getIssuer();
|
||||
if (!view.exists(keylet::account(issuer)))
|
||||
return tecNO_ISSUER; // LCOV_EXCL_LINE
|
||||
|
||||
auto const issuanceKey = keylet::mptIssuance(issuanceID);
|
||||
auto const issuanceSle = view.read(issuanceKey);
|
||||
if (!issuanceSle)
|
||||
return tecOBJECT_NOT_FOUND; // LCOV_EXCL_LINE
|
||||
|
||||
auto const flags = issuanceSle->getFlags();
|
||||
|
||||
if ((flags & lsfMPTLocked) != 0u)
|
||||
return tecLOCKED; // LCOV_EXCL_LINE
|
||||
// Offer crossing and Payment
|
||||
if ((flags & lsfMPTCanTrade) == 0)
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
if (accountID != issuer)
|
||||
{
|
||||
if ((flags & lsfMPTCanTransfer) == 0)
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
auto const mptSle = view.read(keylet::mptoken(issuanceKey.key, accountID));
|
||||
// Allow to succeed since some tx create MPToken if it doesn't exist.
|
||||
// Tx's have their own check for missing MPToken.
|
||||
if (!mptSle)
|
||||
return tesSUCCESS;
|
||||
|
||||
if (mptSle->isFlag(lsfMPTLocked))
|
||||
return tecLOCKED;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
checkMPTTxAllowed(
|
||||
ReadView const& view,
|
||||
TxType txType,
|
||||
Asset const& asset,
|
||||
AccountID const& accountID)
|
||||
{
|
||||
// use isDEXAllowed for payment/offer crossing
|
||||
XRPL_ASSERT(txType != ttPAYMENT, "xrpl::checkMPTTxAllowed : not payment");
|
||||
return checkMPTAllowed(view, txType, asset, accountID);
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -183,15 +183,15 @@ trustCreate(
|
||||
bool const bSrcHigh,
|
||||
AccountID const& uSrcAccountID,
|
||||
AccountID const& uDstAccountID,
|
||||
uint256 const& uIndex, // ripple state entry
|
||||
SLE::ref sleAccount, // the account being set.
|
||||
bool const bAuth, // authorize account.
|
||||
bool const bNoRipple, // others cannot ripple through
|
||||
bool const bFreeze, // funds cannot leave
|
||||
bool bDeepFreeze, // can neither receive nor send funds
|
||||
STAmount const& saBalance, // balance of account being set.
|
||||
uint256 const& uIndex, // --> ripple state entry
|
||||
SLE::ref sleAccount, // --> the account being set.
|
||||
bool const bAuth, // --> authorize account.
|
||||
bool const bNoRipple, // --> others cannot ripple through
|
||||
bool const bFreeze, // --> funds cannot leave
|
||||
bool bDeepFreeze, // --> can neither receive nor send funds
|
||||
STAmount const& saBalance, // --> balance of account being set.
|
||||
// Issuer should be noAccount()
|
||||
STAmount const& saLimit, // limit for account being set.
|
||||
STAmount const& saLimit, // --> limit for account being set.
|
||||
// Issuer should be the account being set.
|
||||
std::uint32_t uQualityIn,
|
||||
std::uint32_t uQualityOut,
|
||||
|
||||
@@ -55,6 +55,14 @@ isGlobalFrozen(ReadView const& view, Asset const& asset)
|
||||
[&](MPTIssue const& issue) { return isGlobalFrozen(view, issue); });
|
||||
}
|
||||
|
||||
TER
|
||||
checkGlobalFrozen(ReadView const& view, Asset const& asset)
|
||||
{
|
||||
if (isGlobalFrozen(view, asset))
|
||||
return asset.holds<MPTIssue>() ? tecLOCKED : tecFROZEN;
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
bool
|
||||
isIndividualFrozen(ReadView const& view, AccountID const& account, Asset const& asset)
|
||||
{
|
||||
@@ -62,6 +70,14 @@ isIndividualFrozen(ReadView const& view, AccountID const& account, Asset const&
|
||||
[&](auto const& issue) { return isIndividualFrozen(view, account, issue); }, asset.value());
|
||||
}
|
||||
|
||||
TER
|
||||
checkIndividualFrozen(ReadView const& view, AccountID const& account, Asset const& asset)
|
||||
{
|
||||
if (isIndividualFrozen(view, account, asset))
|
||||
return asset.holds<MPTIssue>() ? tecLOCKED : tecFROZEN;
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
bool
|
||||
isFrozen(ReadView const& view, AccountID const& account, Asset const& asset, int depth)
|
||||
{
|
||||
@@ -1191,7 +1207,7 @@ directSendNoLimitMultiMPT(
|
||||
// Use uint64_t, not STAmount, to keep MaximumAmount comparisons in exact
|
||||
// integer arithmetic. STAmount implicitly converts to Number, whose
|
||||
// small-scale mantissa (~16 digits) can lose precision for values near
|
||||
// kMaxMpTokenAmount (19 digits).
|
||||
// maxMPTokenAmount (19 digits).
|
||||
std::uint64_t totalSendAmount{0};
|
||||
std::uint64_t const maximumAmount = sle->at(~sfMaximumAmount).value_or(kMaxMpTokenAmount);
|
||||
std::uint64_t const outstandingAmount = sle->getFieldU64(sfOutstandingAmount);
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace {
|
||||
//------------------------------------------------------------------------------
|
||||
// clang-format off
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
char const* const versionString = "3.3.0-b0"
|
||||
char const* const versionString = "3.2.0-b0"
|
||||
// clang-format on
|
||||
;
|
||||
|
||||
|
||||
@@ -106,7 +106,6 @@ transResults()
|
||||
MAKE_ERROR(tecLIMIT_EXCEEDED, "Limit exceeded."),
|
||||
MAKE_ERROR(tecPSEUDO_ACCOUNT, "This operation is not allowed against a pseudo-account."),
|
||||
MAKE_ERROR(tecPRECISION_LOSS, "The amounts used by the transaction cannot interact."),
|
||||
MAKE_ERROR(tecOUT_OF_GAS, "The WASM code ran out of gas during execution."),
|
||||
|
||||
MAKE_ERROR(tefALREADY, "The exact transaction was already in this ledger."),
|
||||
MAKE_ERROR(tefBAD_ADD_AUTH, "Not authorized to add account."),
|
||||
@@ -200,7 +199,6 @@ transResults()
|
||||
MAKE_ERROR(temARRAY_TOO_LARGE, "Malformed: Array is too large."),
|
||||
MAKE_ERROR(temBAD_TRANSFER_FEE, "Malformed: Transfer fee is outside valid range."),
|
||||
MAKE_ERROR(temINVALID_INNER_BATCH, "Malformed: Invalid inner batch transaction."),
|
||||
MAKE_ERROR(temBAD_WASM, "Malformed: Provided WASM code is invalid."),
|
||||
|
||||
MAKE_ERROR(terRETRY, "Retry transaction."),
|
||||
MAKE_ERROR(terFUNDS_SPENT, "DEPRECATED."),
|
||||
|
||||
@@ -262,7 +262,7 @@ Transactor::Transactor(ApplyContext& ctx)
|
||||
: ctx_(ctx)
|
||||
, sink_(ctx.journal, toShortString(ctx.tx.getTransactionID()) + " ")
|
||||
, j_(sink_)
|
||||
, accountID_(ctx.tx.getAccountID(sfAccount))
|
||||
, account_(ctx.tx.getAccountID(sfAccount))
|
||||
{
|
||||
}
|
||||
|
||||
@@ -431,7 +431,7 @@ Transactor::payFee()
|
||||
// Deduct the fee, so it's not available during the transaction.
|
||||
// Will only write the account back if the transaction succeeds.
|
||||
sle->setFieldAmount(sfBalance, sle->getFieldAmount(sfBalance) - feePaid);
|
||||
if (feePayer != accountID_)
|
||||
if (feePayer != account_)
|
||||
view().update(sle); // done in `apply()` for the account
|
||||
|
||||
// VFALCO Should we call view().rawDestroyXRP() here as well?
|
||||
@@ -544,7 +544,7 @@ Transactor::consumeSeqProxy(SLE::pointer const& sleAccount)
|
||||
sleAccount->setFieldU32(sfSequence, seqProx.value() + 1);
|
||||
return tesSUCCESS;
|
||||
}
|
||||
return ticketDelete(view(), accountID_, getTicketIndex(accountID_, seqProx), j_);
|
||||
return ticketDelete(view(), account_, getTicketIndex(account_, seqProx), j_);
|
||||
}
|
||||
|
||||
// Remove a single Ticket from the ledger.
|
||||
@@ -617,7 +617,7 @@ Transactor::ticketDelete(
|
||||
void
|
||||
Transactor::preCompute()
|
||||
{
|
||||
XRPL_ASSERT(accountID_ != beast::kZero, "xrpl::Transactor::preCompute : nonzero account");
|
||||
XRPL_ASSERT(account_ != beast::kZero, "xrpl::Transactor::preCompute : nonzero account");
|
||||
}
|
||||
|
||||
TER
|
||||
@@ -627,12 +627,12 @@ Transactor::apply()
|
||||
|
||||
// If the transactor requires a valid account and the transaction doesn't
|
||||
// list one, preflight will have already a flagged a failure.
|
||||
auto const sle = view().peek(keylet::account(accountID_));
|
||||
auto const sle = view().peek(keylet::account(account_));
|
||||
|
||||
// sle must exist except for transactions
|
||||
// that allow zero account.
|
||||
XRPL_ASSERT(
|
||||
sle != nullptr || accountID_ == beast::kZero,
|
||||
sle != nullptr || account_ == beast::kZero,
|
||||
"xrpl::Transactor::apply : non-null SLE or zero account");
|
||||
|
||||
if (sle)
|
||||
@@ -1238,7 +1238,7 @@ Transactor::operator()()
|
||||
|
||||
if (isTecClaim(result) && ((view().flags() & TapFailHard) != 0u))
|
||||
{
|
||||
// If the TapFailHard flag is set, a tec result
|
||||
// If the tapFAIL_HARD flag is set, a tec result
|
||||
// must not do anything
|
||||
ctx_.discard();
|
||||
applied = false;
|
||||
|
||||
@@ -45,7 +45,8 @@ ValidAMM::visitEntry(
|
||||
// AMM pool changed
|
||||
else if (
|
||||
(type == ltRIPPLE_STATE && after->isFlag(lsfAMMNode)) ||
|
||||
(type == ltACCOUNT_ROOT && after->isFieldPresent(sfAMMID)))
|
||||
(type == ltACCOUNT_ROOT && after->isFieldPresent(sfAMMID)) ||
|
||||
(type == ltMPTOKEN && after->isFlag(lsfMPTAMM)))
|
||||
{
|
||||
ammPoolChanged_ = true;
|
||||
}
|
||||
@@ -85,9 +86,9 @@ ValidAMM::finalizeVote(bool enforce, beast::Journal const& j) const
|
||||
{
|
||||
// LPTokens and the pool can not change on vote
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.error()) << "AMMVote invariant failed: " << lptAMMBalanceBefore_.value_or(STAmount{})
|
||||
<< " " << lptAMMBalanceAfter_.value_or(STAmount{}) << " "
|
||||
<< ammPoolChanged_;
|
||||
JLOG(j.error()) << "Invariant failed: AMMVote failed, "
|
||||
<< lptAMMBalanceBefore_.value_or(STAmount{}) << " "
|
||||
<< lptAMMBalanceAfter_.value_or(STAmount{}) << " " << ammPoolChanged_;
|
||||
if (enforce)
|
||||
return false;
|
||||
// LCOV_EXCL_STOP
|
||||
@@ -103,7 +104,7 @@ ValidAMM::finalizeBid(bool enforce, beast::Journal const& j) const
|
||||
{
|
||||
// The pool can not change on bid
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.error()) << "AMMBid invariant failed: pool changed";
|
||||
JLOG(j.error()) << "Invariant failed: AMMBid failed, pool changed";
|
||||
if (enforce)
|
||||
return false;
|
||||
// LCOV_EXCL_STOP
|
||||
@@ -114,7 +115,7 @@ ValidAMM::finalizeBid(bool enforce, beast::Journal const& j) const
|
||||
(*lptAMMBalanceAfter_ > *lptAMMBalanceBefore_ || *lptAMMBalanceAfter_ <= beast::kZero))
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.error()) << "AMMBid invariant failed: " << *lptAMMBalanceBefore_ << " "
|
||||
JLOG(j.error()) << "Invariant failed: AMMBid failed, " << *lptAMMBalanceBefore_ << " "
|
||||
<< *lptAMMBalanceAfter_;
|
||||
if (enforce)
|
||||
return false;
|
||||
@@ -134,7 +135,7 @@ ValidAMM::finalizeCreate(
|
||||
if (!ammAccount_)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.error()) << "AMMCreate invariant failed: AMM object is not created";
|
||||
JLOG(j.error()) << "Invariant failed: AMMCreate failed, AMM object is not created";
|
||||
if (enforce)
|
||||
return false;
|
||||
// LCOV_EXCL_STOP
|
||||
@@ -157,8 +158,8 @@ ValidAMM::finalizeCreate(
|
||||
if (!validBalances(amount, amount2, *lptAMMBalanceAfter_, ZeroAllowed::No) ||
|
||||
ammLPTokens(amount, amount2, lptAMMBalanceAfter_->get<Issue>()) != *lptAMMBalanceAfter_)
|
||||
{
|
||||
JLOG(j.error()) << "AMMCreate invariant failed: " << amount << " " << amount2 << " "
|
||||
<< *lptAMMBalanceAfter_;
|
||||
JLOG(j.error()) << "Invariant failed: AMMCreate failed, " << amount << " " << amount2
|
||||
<< " " << *lptAMMBalanceAfter_;
|
||||
if (enforce)
|
||||
return false;
|
||||
}
|
||||
@@ -176,7 +177,7 @@ ValidAMM::finalizeDelete(bool enforce, TER res, beast::Journal const& j) const
|
||||
// LCOV_EXCL_START
|
||||
std::string const msg = (isTesSuccess(res)) ? "AMM object is not deleted on tesSUCCESS"
|
||||
: "AMM object is changed on tecINCOMPLETE";
|
||||
JLOG(j.error()) << "AMMDelete invariant failed: " << msg;
|
||||
JLOG(j.error()) << "Invariant failed: AMMDelete failed, " << msg;
|
||||
if (enforce)
|
||||
return false;
|
||||
// LCOV_EXCL_STOP
|
||||
@@ -191,7 +192,7 @@ ValidAMM::finalizeDEX(bool enforce, beast::Journal const& j) const
|
||||
if (ammAccount_)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.error()) << "AMM swap invariant failed: AMM object changed";
|
||||
JLOG(j.error()) << "Invariant failed: AMM swap failed, AMM object changed";
|
||||
if (enforce)
|
||||
return false;
|
||||
// LCOV_EXCL_STOP
|
||||
@@ -232,10 +233,10 @@ ValidAMM::generalInvariant(
|
||||
};
|
||||
if (!nonNegativeBalances || (!strongInvariantCheck && !weakInvariantCheck()))
|
||||
{
|
||||
JLOG(j.error()) << "AMM " << tx.getTxnType()
|
||||
<< " invariant failed: " << tx.getHash(HashPrefix::TransactionId) << " "
|
||||
<< ammPoolChanged_ << " " << amount << " " << amount2 << " "
|
||||
<< poolProductMean << " " << lptAMMBalanceAfter_->getText() << " "
|
||||
JLOG(j.error()) << "Invariant failed: AMM " << tx.getTxnType() << " "
|
||||
<< tx.getHash(HashPrefix::TransactionId) << " " << ammPoolChanged_ << " "
|
||||
<< amount << " " << amount2 << " " << poolProductMean << " "
|
||||
<< lptAMMBalanceAfter_->getText() << " "
|
||||
<< ((*lptAMMBalanceAfter_ == beast::kZero)
|
||||
? Number{1}
|
||||
: ((*lptAMMBalanceAfter_ - poolProductMean) / poolProductMean));
|
||||
@@ -256,7 +257,7 @@ ValidAMM::finalizeDeposit(
|
||||
if (!ammAccount_)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.error()) << "AMMDeposit invariant failed: AMM object is deleted";
|
||||
JLOG(j.error()) << "Invariant failed: AMMDeposit failed, AMM object is deleted";
|
||||
if (enforce)
|
||||
return false;
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
@@ -15,6 +16,7 @@
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/TxFormats.h>
|
||||
#include <xrpl/protocol/UintTypes.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
#include <xrpl/tx/invariants/InvariantCheckPrivilege.h>
|
||||
|
||||
@@ -384,4 +386,141 @@ ValidMPTPayment::finalize(
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
ValidMPTTransfer::visitEntry(
|
||||
bool isDelete,
|
||||
std::shared_ptr<SLE const> const& before,
|
||||
std::shared_ptr<SLE const> const& after)
|
||||
{
|
||||
// Record the before/after MPTAmount for each (issuanceID, account) pair
|
||||
// so finalize() can determine whether a transfer actually occurred.
|
||||
auto update = [&](SLE const& sle, bool isBefore) {
|
||||
if (sle.getType() == ltMPTOKEN)
|
||||
{
|
||||
auto const issuanceID = sle[sfMPTokenIssuanceID];
|
||||
auto const account = sle[sfAccount];
|
||||
auto const amount = sle[sfMPTAmount];
|
||||
if (isBefore)
|
||||
{
|
||||
amount_[issuanceID][account].amtBefore = amount;
|
||||
}
|
||||
else
|
||||
{
|
||||
amount_[issuanceID][account].amtAfter = amount;
|
||||
}
|
||||
if (isDelete && isBefore)
|
||||
{
|
||||
deletedAuthorized_[sle.key()] = sle.isFlag(lsfMPTAuthorized);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (before)
|
||||
update(*before, true);
|
||||
|
||||
if (after)
|
||||
update(*after, false);
|
||||
}
|
||||
|
||||
bool
|
||||
ValidMPTTransfer::isAuthorized(
|
||||
ReadView const& view,
|
||||
MPTID const& mptid,
|
||||
AccountID const& holder,
|
||||
bool reqAuth) const
|
||||
{
|
||||
auto const key = keylet::mptoken(mptid, holder);
|
||||
auto const it = deletedAuthorized_.find(key.key);
|
||||
if (it != deletedAuthorized_.end())
|
||||
return !reqAuth || it->second;
|
||||
return isTesSuccess(requireAuth(view, MPTIssue{mptid}, holder));
|
||||
}
|
||||
|
||||
bool
|
||||
ValidMPTTransfer::finalize(
|
||||
STTx const& tx,
|
||||
TER const,
|
||||
XRPAmount const,
|
||||
ReadView const& view,
|
||||
beast::Journal const& j)
|
||||
{
|
||||
if (hasPrivilege(tx, OverrideFreeze))
|
||||
return true;
|
||||
|
||||
// DEX transactions (AMM[Create,Deposit,Withdraw], cross-currency payments, offer creates) are
|
||||
// subject to the MPTCanTrade flag in addition to the standard transfer rules.
|
||||
// A payment is only DEX if it is a cross-currency payment.
|
||||
auto const isDEX = [&tx] {
|
||||
auto const txnType = tx.getTxnType();
|
||||
if (txnType == ttPAYMENT)
|
||||
{
|
||||
// A payment is cross-currency (and thus DEX) only if SendMax is present
|
||||
// and its asset differs from the destination asset.
|
||||
auto const amount = tx[sfAmount];
|
||||
return tx[~sfSendMax].value_or(amount).asset() != amount.asset();
|
||||
}
|
||||
return txnType == ttAMM_CREATE || txnType == ttAMM_DEPOSIT || txnType == ttAMM_WITHDRAW ||
|
||||
txnType == ttOFFER_CREATE;
|
||||
}();
|
||||
|
||||
// Only enforce once MPTokensV2 is enabled to preserve consensus with non-V2 nodes.
|
||||
// Log invariant failure error even if MPTokensV2 is disabled.
|
||||
auto const enforce = !view.rules().enabled(featureMPTokensV2);
|
||||
|
||||
for (auto const& [mptID, values] : amount_)
|
||||
{
|
||||
std::uint16_t senders = 0;
|
||||
std::uint16_t receivers = 0;
|
||||
bool invalidTransfer = false;
|
||||
auto const sleIssuance = view.read(keylet::mptIssuance(mptID));
|
||||
if (!sleIssuance)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
auto const canTransfer = sleIssuance->isFlag(lsfMPTCanTransfer);
|
||||
auto const canTrade = sleIssuance->isFlag(lsfMPTCanTrade);
|
||||
auto const reqAuth = sleIssuance->isFlag(lsfMPTRequireAuth);
|
||||
|
||||
for (auto const& [account, value] : values)
|
||||
{
|
||||
// Classify each account as a sender or receiver based on whether their MPTAmount
|
||||
// decreased or increased. Count new MPToken holders (no amtBefore) as receivers.
|
||||
// Skip deleted MPToken holders (amtAfter is nullopt); deletion requires zero balance.
|
||||
if (value.amtAfter.has_value() && value.amtBefore.value_or(0) != *value.amtAfter)
|
||||
{
|
||||
if (!value.amtBefore.has_value() || *value.amtAfter > *value.amtBefore)
|
||||
{
|
||||
++receivers;
|
||||
}
|
||||
else
|
||||
{
|
||||
++senders;
|
||||
}
|
||||
|
||||
// Check once: if any involved account is frozen, the whole
|
||||
// issuance transfer is considered frozen. Only need to check for
|
||||
// frozen if there is a transfer of funds.
|
||||
if (!invalidTransfer &&
|
||||
(isFrozen(view, account, MPTIssue{mptID}) ||
|
||||
!isAuthorized(view, mptID, account, reqAuth)))
|
||||
{
|
||||
invalidTransfer = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// A transfer between holders has occurred (senders > 0 && receivers > 0).
|
||||
// Fail if the issuance is frozen, does not permit transfers, or — for
|
||||
// DEX transactions — does not permit trading.
|
||||
if ((invalidTransfer || !canTransfer || (isDEX && !canTrade)) && senders > 0 &&
|
||||
receivers > 0)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: invalid MPToken transfer between holders";
|
||||
return enforce;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -395,6 +395,9 @@ MPTEndpointPaymentStep::check(StrandContext const& ctx, std::shared_ptr<const SL
|
||||
TER
|
||||
MPTEndpointOfferCrossingStep::check(StrandContext const& ctx, std::shared_ptr<const SLE> const&)
|
||||
{
|
||||
// The standard checks are all we can do because any remaining checks
|
||||
// require the existence of a MPToken. Offer crossing does not
|
||||
// require a pre-existing MPToken.
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
|
||||
@@ -344,7 +344,7 @@ AccountDelete::preclaim(PreclaimContext const& ctx)
|
||||
TER
|
||||
AccountDelete::doApply()
|
||||
{
|
||||
auto src = view().peek(keylet::account(accountID_));
|
||||
auto src = view().peek(keylet::account(account_));
|
||||
XRPL_ASSERT(src, "xrpl::AccountDelete::doApply : non-null source account");
|
||||
|
||||
auto const dstID = ctx_.tx[sfDestination];
|
||||
@@ -357,12 +357,12 @@ AccountDelete::doApply()
|
||||
if (ctx_.tx.isFieldPresent(sfCredentialIDs))
|
||||
{
|
||||
if (auto err =
|
||||
verifyDepositPreauth(ctx_.tx, ctx_.view(), accountID_, dstID, dst, ctx_.journal);
|
||||
verifyDepositPreauth(ctx_.tx, ctx_.view(), account_, dstID, dst, ctx_.journal);
|
||||
!isTesSuccess(err))
|
||||
return err;
|
||||
}
|
||||
|
||||
Keylet const ownerDirKeylet{keylet::ownerDir(accountID_)};
|
||||
Keylet const ownerDirKeylet{keylet::ownerDir(account_)};
|
||||
auto const ter = cleanupOnAccountDelete(
|
||||
view(),
|
||||
ownerDirKeylet,
|
||||
@@ -371,7 +371,7 @@ AccountDelete::doApply()
|
||||
std::shared_ptr<SLE>& sleItem) -> std::pair<TER, SkipEntry> {
|
||||
if (auto deleter = nonObligationDeleter(nodeType))
|
||||
{
|
||||
TER const result{deleter(ctx_.registry, view(), accountID_, dirEntry, sleItem, j_)};
|
||||
TER const result{deleter(ctx_.registry, view(), account_, dirEntry, sleItem, j_)};
|
||||
|
||||
return {result, SkipEntry::No};
|
||||
}
|
||||
@@ -402,7 +402,7 @@ AccountDelete::doApply()
|
||||
// delete it.
|
||||
if (view().exists(ownerDirKeylet) && !view().emptyDirDelete(ownerDirKeylet))
|
||||
{
|
||||
JLOG(j_.error()) << "AccountDelete cannot delete root dir node of " << toBase58(accountID_);
|
||||
JLOG(j_.error()) << "AccountDelete cannot delete root dir node of " << toBase58(account_);
|
||||
return tecHAS_OBLIGATIONS;
|
||||
}
|
||||
|
||||
|
||||
@@ -279,7 +279,7 @@ AccountSet::preclaim(PreclaimContext const& ctx)
|
||||
TER
|
||||
AccountSet::doApply()
|
||||
{
|
||||
auto const sle = view().peek(keylet::account(accountID_));
|
||||
auto const sle = view().peek(keylet::account(account_));
|
||||
if (!sle)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
@@ -298,7 +298,7 @@ AccountSet::doApply()
|
||||
bool const bSetDisallowXRP{tx.isFlag(tfDisallowXRP) || (uSetFlag == asfDisallowXRP)};
|
||||
bool const bClearDisallowXRP{tx.isFlag(tfAllowXRP) || (uClearFlag == asfDisallowXRP)};
|
||||
|
||||
bool const sigWithMaster{[&tx, &acct = accountID_]() {
|
||||
bool const sigWithMaster{[&tx, &acct = account_]() {
|
||||
auto const spk = tx.getSigningPubKey();
|
||||
|
||||
if (publicKeyType(makeSlice(spk)))
|
||||
@@ -367,7 +367,7 @@ AccountSet::doApply()
|
||||
return tecNEED_MASTER_KEY;
|
||||
}
|
||||
|
||||
if ((!sle->isFieldPresent(sfRegularKey)) && (!view().peek(keylet::signers(accountID_))))
|
||||
if ((!sle->isFieldPresent(sfRegularKey)) && (!view().peek(keylet::signers(account_))))
|
||||
{
|
||||
// Account has no regular key or multi-signer signer list.
|
||||
return tecNO_ALTERNATIVE_KEY;
|
||||
|
||||
@@ -55,7 +55,7 @@ SetRegularKey::preflight(PreflightContext const& ctx)
|
||||
TER
|
||||
SetRegularKey::doApply()
|
||||
{
|
||||
auto const sle = view().peek(keylet::account(accountID_));
|
||||
auto const sle = view().peek(keylet::account(account_));
|
||||
if (!sle)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
@@ -69,7 +69,7 @@ SetRegularKey::doApply()
|
||||
else
|
||||
{
|
||||
// Account has disabled master key and no multi-signer signer list.
|
||||
if (sle->isFlag(lsfDisableMaster) && !view().peek(keylet::signers(accountID_)))
|
||||
if (sle->isFlag(lsfDisableMaster) && !view().peek(keylet::signers(account_)))
|
||||
return tecNO_ALTERNATIVE_KEY;
|
||||
|
||||
sle->makeFieldAbsent(sfRegularKey);
|
||||
|
||||
@@ -300,9 +300,9 @@ SignerListSet::validateQuorumAndSignerEntries(
|
||||
TER
|
||||
SignerListSet::replaceSignerList()
|
||||
{
|
||||
auto const accountKeylet = keylet::account(accountID_);
|
||||
auto const ownerDirKeylet = keylet::ownerDir(accountID_);
|
||||
auto const signerListKeylet = keylet::signers(accountID_);
|
||||
auto const accountKeylet = keylet::account(account_);
|
||||
auto const ownerDirKeylet = keylet::ownerDir(account_);
|
||||
auto const signerListKeylet = keylet::signers(account_);
|
||||
|
||||
// This may be either a create or a replace. Preemptively remove any
|
||||
// old signer list. May reduce the reserve, so this is done before
|
||||
@@ -337,9 +337,9 @@ SignerListSet::replaceSignerList()
|
||||
auto viewJ = ctx_.registry.get().getJournal("View");
|
||||
// Add the signer list to the account's directory.
|
||||
auto const page =
|
||||
ctx_.view().dirInsert(ownerDirKeylet, signerListKeylet, describeOwnerDir(accountID_));
|
||||
ctx_.view().dirInsert(ownerDirKeylet, signerListKeylet, describeOwnerDir(account_));
|
||||
|
||||
JLOG(j_.trace()) << "Create signer list for account " << toBase58(accountID_) << ": "
|
||||
JLOG(j_.trace()) << "Create signer list for account " << toBase58(account_) << ": "
|
||||
<< (page ? "success" : "failure");
|
||||
|
||||
if (!page)
|
||||
@@ -356,7 +356,7 @@ SignerListSet::replaceSignerList()
|
||||
TER
|
||||
SignerListSet::destroySignerList()
|
||||
{
|
||||
auto const accountKeylet = keylet::account(accountID_);
|
||||
auto const accountKeylet = keylet::account(account_);
|
||||
// Destroying the signer list is only allowed if either the master key
|
||||
// is enabled or there is a regular key.
|
||||
SLE::pointer const ledgerEntry = view().peek(accountKeylet);
|
||||
@@ -366,8 +366,8 @@ SignerListSet::destroySignerList()
|
||||
if ((ledgerEntry->isFlag(lsfDisableMaster)) && (!ledgerEntry->isFieldPresent(sfRegularKey)))
|
||||
return tecNO_ALTERNATIVE_KEY;
|
||||
|
||||
auto const ownerDirKeylet = keylet::ownerDir(accountID_);
|
||||
auto const signerListKeylet = keylet::signers(accountID_);
|
||||
auto const ownerDirKeylet = keylet::ownerDir(account_);
|
||||
auto const signerListKeylet = keylet::signers(account_);
|
||||
return removeSignersFromLedger(
|
||||
ctx_.registry, view(), accountKeylet, ownerDirKeylet, signerListKeylet, j_);
|
||||
}
|
||||
@@ -378,7 +378,7 @@ SignerListSet::writeSignersToSLE(SLE::pointer const& ledgerEntry, std::uint32_t
|
||||
// Assign the quorum, default SignerListID, and flags.
|
||||
if (ctx_.view().rules().enabled(fixIncludeKeyletFields))
|
||||
{
|
||||
ledgerEntry->setAccountID(sfOwner, accountID_);
|
||||
ledgerEntry->setAccountID(sfOwner, account_);
|
||||
}
|
||||
ledgerEntry->setFieldU32(sfSignerQuorum, quorum_);
|
||||
ledgerEntry->setFieldU32(sfSignerListID, kDefaultSignerListId);
|
||||
|
||||
@@ -1920,7 +1920,7 @@ XChainCommit::doApply()
|
||||
|
||||
// Support dipping into reserves to pay the fee
|
||||
TransferHelperSubmittingAccountInfo submittingAccountInfo{
|
||||
.account = accountID_,
|
||||
.account = account_,
|
||||
.preFeeBalance = preFeeBalance_,
|
||||
.postFeeBalance = (*sleAccount)[sfBalance]};
|
||||
|
||||
@@ -2197,9 +2197,7 @@ XChainCreateAccountCommit::doApply()
|
||||
|
||||
// Support dipping into reserves to pay the fee
|
||||
TransferHelperSubmittingAccountInfo submittingAccountInfo{
|
||||
.account = accountID_,
|
||||
.preFeeBalance = preFeeBalance_,
|
||||
.postFeeBalance = (*sle)[sfBalance]};
|
||||
.account = account_, .preFeeBalance = preFeeBalance_, .postFeeBalance = (*sle)[sfBalance]};
|
||||
STAmount const toTransfer = amount + reward;
|
||||
auto const thTer = transferHelper(
|
||||
psb,
|
||||
|
||||
@@ -264,9 +264,10 @@ CheckCash::preclaim(PreclaimContext const& ctx)
|
||||
return tecLOCKED;
|
||||
}
|
||||
|
||||
if (auto const err = canTrade(ctx.view, value.asset()); !isTesSuccess(err))
|
||||
if (auto const err = canTransfer(ctx.view, issue, srcId, dstId);
|
||||
!isTesSuccess(err))
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "MPT DEX is not allowed.";
|
||||
JLOG(ctx.j.warn()) << "MPT transfer is disabled.";
|
||||
return err;
|
||||
}
|
||||
|
||||
@@ -294,7 +295,7 @@ CheckCash::doApply()
|
||||
}
|
||||
|
||||
AccountID const srcId{sleCheck->getAccountID(sfAccount)};
|
||||
if (!psb.exists(keylet::account(srcId)) || !psb.exists(keylet::account(accountID_)))
|
||||
if (!psb.exists(keylet::account(srcId)) || !psb.exists(keylet::account(account_)))
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(ctx_.journal.fatal()) << "Precheck did not verify source or destination's existence.";
|
||||
@@ -315,7 +316,7 @@ CheckCash::doApply()
|
||||
auto viewJ = ctx_.registry.get().getJournal("View");
|
||||
auto const optDeliverMin = ctx_.tx[~sfDeliverMin];
|
||||
|
||||
if (srcId != accountID_)
|
||||
if (srcId != account_)
|
||||
{
|
||||
STAmount const sendMax = sleCheck->at(sfSendMax);
|
||||
|
||||
@@ -353,7 +354,7 @@ CheckCash::doApply()
|
||||
}
|
||||
|
||||
// The source account has enough XRP so make the ledger change.
|
||||
if (TER const ter{transferXRP(psb, srcId, accountID_, xrpDeliver, viewJ)};
|
||||
if (TER const ter{transferXRP(psb, srcId, account_, xrpDeliver, viewJ)};
|
||||
!isTesSuccess(ter))
|
||||
{
|
||||
// The transfer failed. Return the error code.
|
||||
@@ -383,7 +384,7 @@ CheckCash::doApply()
|
||||
// Check reserve. Return destination account SLE if enough reserve,
|
||||
// otherwise return nullptr.
|
||||
auto checkReserve = [&]() -> std::shared_ptr<SLE> {
|
||||
auto sleDst = psb.peek(keylet::account(accountID_));
|
||||
auto sleDst = psb.peek(keylet::account(account_));
|
||||
|
||||
// Can the account cover the trust line's or MPT reserve?
|
||||
if (std::uint32_t const ownerCount = {sleDst->at(sfOwnerCount)};
|
||||
@@ -405,9 +406,9 @@ CheckCash::doApply()
|
||||
[&](Issue const& issue) -> std::optional<TER> {
|
||||
// If a trust line does not exist yet create one.
|
||||
Issue const& trustLineIssue = issue;
|
||||
AccountID const truster = deliverIssuer == accountID_ ? srcId : accountID_;
|
||||
AccountID const truster = deliverIssuer == account_ ? srcId : account_;
|
||||
trustLineKey = keylet::line(truster, trustLineIssue);
|
||||
destLow = deliverIssuer > accountID_;
|
||||
destLow = deliverIssuer > account_;
|
||||
|
||||
if (!psb.exists(*trustLineKey))
|
||||
{
|
||||
@@ -433,7 +434,7 @@ CheckCash::doApply()
|
||||
psb, // payment sandbox
|
||||
destLow, // is dest low?
|
||||
deliverIssuer, // source
|
||||
accountID_, // destination
|
||||
account_, // destination
|
||||
trustLineKey->key, // ledger index
|
||||
sleDst, // Account to add to
|
||||
false, // authorize account
|
||||
@@ -441,7 +442,7 @@ CheckCash::doApply()
|
||||
false, // freeze trust line
|
||||
false, // deep freeze trust line
|
||||
initialBalance, // zero initial balance
|
||||
Issue(currency, accountID_), // limit of zero
|
||||
Issue(currency, account_), // limit of zero
|
||||
0, // quality in
|
||||
0, // quality out
|
||||
viewJ); // journal
|
||||
@@ -479,18 +480,18 @@ CheckCash::doApply()
|
||||
return std::nullopt;
|
||||
},
|
||||
[&](MPTIssue const& issue) -> std::optional<TER> {
|
||||
if (accountID_ != deliverIssuer)
|
||||
if (account_ != deliverIssuer)
|
||||
{
|
||||
auto const& mptID = issue.getMptID();
|
||||
// Create MPT if it doesn't exist
|
||||
auto const mptokenKey = keylet::mptoken(mptID, accountID_);
|
||||
auto const mptokenKey = keylet::mptoken(mptID, account_);
|
||||
if (!psb.exists(mptokenKey))
|
||||
{
|
||||
auto sleDst = checkReserve();
|
||||
if (sleDst == nullptr)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
|
||||
if (auto const err = checkCreateMPT(psb, mptID, accountID_, j_);
|
||||
if (auto const err = checkCreateMPT(psb, mptID, account_, j_);
|
||||
!isTesSuccess(err))
|
||||
{
|
||||
return err;
|
||||
@@ -518,7 +519,7 @@ CheckCash::doApply()
|
||||
psb,
|
||||
flowDeliver,
|
||||
srcId,
|
||||
accountID_,
|
||||
account_,
|
||||
STPathSet{},
|
||||
true, // default path
|
||||
static_cast<bool>(optDeliverMin), // partial payment
|
||||
@@ -556,9 +557,9 @@ CheckCash::doApply()
|
||||
|
||||
// Check was cashed. If not a self send (and it shouldn't be), remove
|
||||
// check link from destination directory.
|
||||
if (srcId != accountID_ &&
|
||||
if (srcId != account_ &&
|
||||
!psb.dirRemove(
|
||||
keylet::ownerDir(accountID_), sleCheck->at(sfDestinationNode), sleCheck->key(), true))
|
||||
keylet::ownerDir(account_), sleCheck->at(sfDestinationNode), sleCheck->key(), true))
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j_.fatal()) << "Unable to delete check from destination.";
|
||||
|
||||
@@ -111,10 +111,10 @@ CheckCreate::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
// The currency may not be globally frozen
|
||||
AccountID const& issuerId{sendMax.getIssuer()};
|
||||
if (isGlobalFrozen(ctx.view, sendMax.asset()))
|
||||
if (auto const ter = checkGlobalFrozen(ctx.view, sendMax.asset()); !isTesSuccess(ter))
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Creating a check for frozen asset";
|
||||
return sendMax.asset().holds<MPTIssue>() ? tecLOCKED : tecFROZEN;
|
||||
JLOG(ctx.j.warn()) << "Creating a check for frozen or locked asset";
|
||||
return ter;
|
||||
}
|
||||
auto const err = sendMax.asset().visit(
|
||||
[&](Issue const& issue) -> std::optional<TER> {
|
||||
@@ -153,9 +153,21 @@ CheckCreate::preclaim(PreclaimContext const& ctx)
|
||||
},
|
||||
[&](MPTIssue const& issue) -> std::optional<TER> {
|
||||
if (srcId != issuerId && isFrozen(ctx.view, srcId, issue))
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Creating a check for locked MPT.";
|
||||
return tecLOCKED;
|
||||
}
|
||||
if (dstId != issuerId && isFrozen(ctx.view, dstId, issue))
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Creating a check for locked MPT.";
|
||||
return tecLOCKED;
|
||||
}
|
||||
if (auto const ter = canTransfer(ctx.view, issue, srcId, dstId);
|
||||
!isTesSuccess(ter))
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "MPT transfer is disabled.";
|
||||
return ter;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
});
|
||||
@@ -169,13 +181,13 @@ CheckCreate::preclaim(PreclaimContext const& ctx)
|
||||
return tecEXPIRED;
|
||||
}
|
||||
|
||||
return canTrade(ctx.view, ctx.tx[sfSendMax].asset());
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
CheckCreate::doApply()
|
||||
{
|
||||
auto const sle = view().peek(keylet::account(accountID_));
|
||||
auto const sle = view().peek(keylet::account(account_));
|
||||
if (!sle)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
@@ -192,10 +204,10 @@ CheckCreate::doApply()
|
||||
// Note that we use the value from the sequence or ticket as the
|
||||
// Check sequence. For more explanation see comments in SeqProxy.h.
|
||||
std::uint32_t const seq = ctx_.tx.getSeqValue();
|
||||
Keylet const checkKeylet = keylet::check(accountID_, seq);
|
||||
Keylet const checkKeylet = keylet::check(account_, seq);
|
||||
auto sleCheck = std::make_shared<SLE>(checkKeylet);
|
||||
|
||||
sleCheck->setAccountID(sfAccount, accountID_);
|
||||
sleCheck->setAccountID(sfAccount, account_);
|
||||
AccountID const dstAccountId = ctx_.tx[sfDestination];
|
||||
sleCheck->setAccountID(sfDestination, dstAccountId);
|
||||
sleCheck->setFieldU32(sfSequence, seq);
|
||||
@@ -214,7 +226,7 @@ CheckCreate::doApply()
|
||||
auto viewJ = ctx_.registry.get().getJournal("View");
|
||||
// If it's not a self-send (and it shouldn't be), add Check to the
|
||||
// destination's owner directory.
|
||||
if (dstAccountId != accountID_)
|
||||
if (dstAccountId != account_)
|
||||
{
|
||||
auto const page = view().dirInsert(
|
||||
keylet::ownerDir(dstAccountId), checkKeylet, describeOwnerDir(dstAccountId));
|
||||
@@ -229,8 +241,8 @@ CheckCreate::doApply()
|
||||
}
|
||||
|
||||
{
|
||||
auto const page = view().dirInsert(
|
||||
keylet::ownerDir(accountID_), checkKeylet, describeOwnerDir(accountID_));
|
||||
auto const page =
|
||||
view().dirInsert(keylet::ownerDir(account_), checkKeylet, describeOwnerDir(account_));
|
||||
|
||||
JLOG(j_.trace()) << "Adding Check to owner directory " << to_string(checkKeylet.key) << ": "
|
||||
<< (page ? "success" : "failure");
|
||||
|
||||
@@ -89,7 +89,7 @@ CredentialAccept::doApply()
|
||||
AccountID const issuer{ctx_.tx[sfIssuer]};
|
||||
|
||||
// Both exist as credential object exist itself (checked in preclaim)
|
||||
auto const sleSubject = view().peek(keylet::account(accountID_));
|
||||
auto const sleSubject = view().peek(keylet::account(account_));
|
||||
auto const sleIssuer = view().peek(keylet::account(issuer));
|
||||
|
||||
if (!sleSubject || !sleIssuer)
|
||||
@@ -103,7 +103,7 @@ CredentialAccept::doApply()
|
||||
}
|
||||
|
||||
auto const credType(ctx_.tx[sfCredentialType]);
|
||||
Keylet const credentialKey = keylet::credential(accountID_, issuer, credType);
|
||||
Keylet const credentialKey = keylet::credential(account_, issuer, credType);
|
||||
auto const sleCred = view().peek(credentialKey); // Checked in preclaim()
|
||||
if (!sleCred)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
@@ -104,7 +104,7 @@ CredentialCreate::doApply()
|
||||
{
|
||||
auto const subject = ctx_.tx[sfSubject];
|
||||
auto const credType(ctx_.tx[sfCredentialType]);
|
||||
Keylet const credentialKey = keylet::credential(subject, accountID_, credType);
|
||||
Keylet const credentialKey = keylet::credential(subject, account_, credType);
|
||||
|
||||
auto const sleCred = std::make_shared<SLE>(credentialKey);
|
||||
if (!sleCred)
|
||||
@@ -126,7 +126,7 @@ CredentialCreate::doApply()
|
||||
sleCred->setFieldU32(sfExpiration, *optExp);
|
||||
}
|
||||
|
||||
auto const sleIssuer = view().peek(keylet::account(accountID_));
|
||||
auto const sleIssuer = view().peek(keylet::account(account_));
|
||||
if (!sleIssuer)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
@@ -138,15 +138,15 @@ CredentialCreate::doApply()
|
||||
}
|
||||
|
||||
sleCred->setAccountID(sfSubject, subject);
|
||||
sleCred->setAccountID(sfIssuer, accountID_);
|
||||
sleCred->setAccountID(sfIssuer, account_);
|
||||
sleCred->setFieldVL(sfCredentialType, credType);
|
||||
|
||||
if (ctx_.tx.isFieldPresent(sfURI))
|
||||
sleCred->setFieldVL(sfURI, ctx_.tx.getFieldVL(sfURI));
|
||||
|
||||
{
|
||||
auto const page = view().dirInsert(
|
||||
keylet::ownerDir(accountID_), credentialKey, describeOwnerDir(accountID_));
|
||||
auto const page =
|
||||
view().dirInsert(keylet::ownerDir(account_), credentialKey, describeOwnerDir(account_));
|
||||
JLOG(j_.trace()) << "Adding Credential to owner directory " << to_string(credentialKey.key)
|
||||
<< ": " << (page ? "success" : "failure");
|
||||
if (!page)
|
||||
@@ -156,7 +156,7 @@ CredentialCreate::doApply()
|
||||
adjustOwnerCount(view(), sleIssuer, 1, j_);
|
||||
}
|
||||
|
||||
if (subject == accountID_)
|
||||
if (subject == account_)
|
||||
{
|
||||
sleCred->setFieldU32(sfFlags, lsfAccepted);
|
||||
}
|
||||
|
||||
@@ -78,15 +78,15 @@ CredentialDelete::preclaim(PreclaimContext const& ctx)
|
||||
TER
|
||||
CredentialDelete::doApply()
|
||||
{
|
||||
auto const subject = ctx_.tx[~sfSubject].value_or(accountID_);
|
||||
auto const issuer = ctx_.tx[~sfIssuer].value_or(accountID_);
|
||||
auto const subject = ctx_.tx[~sfSubject].value_or(account_);
|
||||
auto const issuer = ctx_.tx[~sfIssuer].value_or(account_);
|
||||
|
||||
auto const credType(ctx_.tx[sfCredentialType]);
|
||||
auto const sleCred = view().peek(keylet::credential(subject, issuer, credType));
|
||||
if (!sleCred)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
if ((subject != accountID_) && (issuer != accountID_) &&
|
||||
if ((subject != account_) && (issuer != account_) &&
|
||||
!checkExpired(*sleCred, ctx_.view().header().parentCloseTime))
|
||||
{
|
||||
JLOG(j_.trace()) << "Can't delete non-expired credential.";
|
||||
|
||||
@@ -68,12 +68,12 @@ DelegateSet::preclaim(PreclaimContext const& ctx)
|
||||
TER
|
||||
DelegateSet::doApply()
|
||||
{
|
||||
auto const sleOwner = ctx_.view().peek(keylet::account(accountID_));
|
||||
auto const sleOwner = ctx_.view().peek(keylet::account(account_));
|
||||
if (!sleOwner)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const& authAccount = ctx_.tx[sfAuthorize];
|
||||
auto const delegateKey = keylet::delegate(accountID_, authAccount);
|
||||
auto const delegateKey = keylet::delegate(account_, authAccount);
|
||||
|
||||
auto sle = ctx_.view().peek(delegateKey);
|
||||
if (sle)
|
||||
@@ -101,22 +101,22 @@ DelegateSet::doApply()
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
|
||||
sle = std::make_shared<SLE>(delegateKey);
|
||||
sle->setAccountID(sfAccount, accountID_);
|
||||
sle->setAccountID(sfAccount, account_);
|
||||
sle->setAccountID(sfAuthorize, authAccount);
|
||||
|
||||
sle->setFieldArray(sfPermissions, permissions);
|
||||
|
||||
// Add to delegating account's owner directory
|
||||
auto const page = ctx_.view().dirInsert(
|
||||
keylet::ownerDir(accountID_), delegateKey, describeOwnerDir(accountID_));
|
||||
auto const page =
|
||||
ctx_.view().dirInsert(keylet::ownerDir(account_), delegateKey, describeOwnerDir(account_));
|
||||
|
||||
if (!page)
|
||||
return tecDIR_FULL; // LCOV_EXCL_LINE
|
||||
|
||||
(*sle)[sfOwnerNode] = *page;
|
||||
|
||||
// Add to authorized account's owner directory so AccountDelete can find
|
||||
// and clean up inbound delegations when the authorized account is deleted.
|
||||
// Add to authorized account's owner directory so the object can be found
|
||||
// and cleaned up when the authorized account is deleted.
|
||||
auto const destPage = ctx_.view().dirInsert(
|
||||
keylet::ownerDir(authAccount), delegateKey, describeOwnerDir(authAccount));
|
||||
|
||||
|
||||
@@ -371,7 +371,7 @@ AMMBid::doApply()
|
||||
// as we go on processing transactions.
|
||||
Sandbox sb(&ctx_.view());
|
||||
|
||||
auto const result = applyBid(ctx_, sb, accountID_, j_);
|
||||
auto const result = applyBid(ctx_, sb, account_, j_);
|
||||
if (result.second)
|
||||
sb.apply(ctx_.rawView());
|
||||
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/TxFormats.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
#include <xrpl/tx/ApplyContext.h>
|
||||
#include <xrpl/tx/Transactor.h>
|
||||
@@ -120,11 +119,16 @@ AMMCreate::preclaim(PreclaimContext const& ctx)
|
||||
}
|
||||
|
||||
// Globally or individually frozen
|
||||
if (isFrozen(ctx.view, accountID, amount.asset()) ||
|
||||
isFrozen(ctx.view, accountID, amount2.asset()))
|
||||
if (auto const ter = checkFrozen(ctx.view, accountID, amount.asset()); !isTesSuccess(ter))
|
||||
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "AMM Instance: involves frozen asset.";
|
||||
return tecFROZEN;
|
||||
JLOG(ctx.j.debug()) << "AMM Instance: involves frozen or locked asset.";
|
||||
return ter;
|
||||
}
|
||||
if (auto const ter = checkFrozen(ctx.view, accountID, amount2.asset()); !isTesSuccess(ter))
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "AMM Instance: involves frozen or locked asset.";
|
||||
return ter;
|
||||
}
|
||||
|
||||
auto noDefaultRipple = [](ReadView const& view, Asset const& asset) {
|
||||
@@ -191,10 +195,10 @@ AMMCreate::preclaim(PreclaimContext const& ctx)
|
||||
return terADDRESS_COLLISION;
|
||||
}
|
||||
|
||||
if (auto const ter = checkMPTTxAllowed(ctx.view, ttAMM_CREATE, amount.asset(), accountID);
|
||||
if (auto const ter = canMPTTradeAndTransfer(ctx.view, amount.asset(), accountID, accountID);
|
||||
!isTesSuccess(ter))
|
||||
return ter;
|
||||
if (auto const ter = checkMPTTxAllowed(ctx.view, ttAMM_CREATE, amount2.asset(), accountID);
|
||||
if (auto const ter = canMPTTradeAndTransfer(ctx.view, amount2.asset(), accountID, accountID);
|
||||
!isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
@@ -385,7 +389,7 @@ AMMCreate::doApply()
|
||||
// as we go on processing transactions.
|
||||
Sandbox sb(&ctx_.view());
|
||||
|
||||
auto const result = applyCreate(ctx_, sb, accountID_, j_);
|
||||
auto const result = applyCreate(ctx_, sb, account_, j_);
|
||||
if (result.second)
|
||||
sb.apply(ctx_.rawView());
|
||||
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/protocol/TxFormats.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
#include <xrpl/tx/Transactor.h>
|
||||
|
||||
@@ -267,12 +266,12 @@ AMMDeposit::preclaim(PreclaimContext const& ctx)
|
||||
return ter;
|
||||
}
|
||||
|
||||
if (isFrozen(ctx.view, accountID, asset))
|
||||
if (auto const ter = checkFrozen(ctx.view, accountID, asset); !isTesSuccess(ter))
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "AMM Deposit: account or currency is frozen, "
|
||||
JLOG(ctx.j.debug()) << "AMM Deposit: account or currency is frozen or locked, "
|
||||
<< to_string(accountID) << " " << to_string(asset);
|
||||
|
||||
return tecFROZEN;
|
||||
return ter;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
@@ -304,18 +303,20 @@ AMMDeposit::preclaim(PreclaimContext const& ctx)
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
// AMM account or currency frozen
|
||||
if (isFrozen(ctx.view, ammAccountID, amount->asset()))
|
||||
if (auto const ter = checkFrozen(ctx.view, ammAccountID, amount->asset());
|
||||
!isTesSuccess(ter))
|
||||
{
|
||||
JLOG(ctx.j.debug())
|
||||
<< "AMM Deposit: AMM account or currency is frozen, " << to_string(accountID);
|
||||
return tecFROZEN;
|
||||
JLOG(ctx.j.debug()) << "AMM Deposit: AMM account or currency is frozen or locked, "
|
||||
<< to_string(accountID);
|
||||
return ter;
|
||||
}
|
||||
// Account frozen
|
||||
if (isIndividualFrozen(ctx.view, accountID, amount->asset()))
|
||||
if (auto const ter = checkIndividualFrozen(ctx.view, accountID, amount->asset());
|
||||
!isTesSuccess(ter))
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "AMM Deposit: account is frozen, " << to_string(accountID)
|
||||
<< " " << to_string(amount->asset());
|
||||
return tecFROZEN;
|
||||
JLOG(ctx.j.debug()) << "AMM Deposit: account is frozen or locked, "
|
||||
<< to_string(accountID) << " " << to_string(amount->asset());
|
||||
return ter;
|
||||
}
|
||||
if (checkBalance)
|
||||
{
|
||||
@@ -368,10 +369,10 @@ AMMDeposit::preclaim(PreclaimContext const& ctx)
|
||||
}
|
||||
}
|
||||
|
||||
if (auto const ter = checkMPTTxAllowed(ctx.view, ttAMM_DEPOSIT, ctx.tx[sfAsset], accountID);
|
||||
if (auto const ter = canMPTTradeAndTransfer(ctx.view, ctx.tx[sfAsset], accountID, accountID);
|
||||
!isTesSuccess(ter))
|
||||
return ter;
|
||||
if (auto const ter = checkMPTTxAllowed(ctx.view, ttAMM_DEPOSIT, ctx.tx[sfAsset2], accountID);
|
||||
if (auto const ter = canMPTTradeAndTransfer(ctx.view, ctx.tx[sfAsset2], accountID, accountID);
|
||||
!isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
@@ -403,7 +404,7 @@ AMMDeposit::applyGuts(Sandbox& sb)
|
||||
auto const [amountBalance, amount2Balance, lptAMMBalance] = *expected;
|
||||
auto const tfee = (lptAMMBalance == beast::kZero)
|
||||
? ctx_.tx[~sfTradingFee].value_or(0)
|
||||
: getTradingFee(ctx_.view(), *ammSle, accountID_);
|
||||
: getTradingFee(ctx_.view(), *ammSle, account_);
|
||||
|
||||
auto const subTxType = ctx_.tx.getFlags() & tfDepositSubTx;
|
||||
|
||||
@@ -474,7 +475,7 @@ AMMDeposit::applyGuts(Sandbox& sb)
|
||||
// LP depositing into AMM empty state gets the auction slot
|
||||
// and the voting
|
||||
if (lptAMMBalance == beast::kZero)
|
||||
initializeFeeAuctionVote(sb, ammSle, accountID_, lptAMMBalance.asset(), tfee);
|
||||
initializeFeeAuctionVote(sb, ammSle, account_, lptAMMBalance.asset(), tfee);
|
||||
|
||||
sb.update(ammSle);
|
||||
}
|
||||
@@ -519,14 +520,14 @@ AMMDeposit::deposit(
|
||||
{
|
||||
auto const& lpIssue = lpTokensDeposit.get<Issue>();
|
||||
// Adjust the reserve if LP doesn't have LPToken trustline
|
||||
auto const sle = view.read(keylet::line(accountID_, lpIssue.account, lpIssue.currency));
|
||||
if (xrpLiquid(view, accountID_, !sle, j_) >= depositAmount)
|
||||
auto const sle = view.read(keylet::line(account_, lpIssue.account, lpIssue.currency));
|
||||
if (xrpLiquid(view, account_, !sle, j_) >= depositAmount)
|
||||
return tesSUCCESS;
|
||||
}
|
||||
else if (
|
||||
accountFunds(
|
||||
view,
|
||||
accountID_,
|
||||
account_,
|
||||
depositAmount,
|
||||
FreezeHandling::IgnoreFreeze,
|
||||
AuthHandling::IgnoreAuth,
|
||||
@@ -574,7 +575,7 @@ AMMDeposit::deposit(
|
||||
}
|
||||
|
||||
auto res = accountSend(
|
||||
view, accountID_, ammAccount, amountDepositActual, ctx_.journal, WaiveTransferFee::Yes);
|
||||
view, account_, ammAccount, amountDepositActual, ctx_.journal, WaiveTransferFee::Yes);
|
||||
if (!isTesSuccess(res))
|
||||
{
|
||||
JLOG(ctx_.journal.debug()) << "AMM Deposit: failed to deposit " << amountDepositActual;
|
||||
@@ -593,12 +594,7 @@ AMMDeposit::deposit(
|
||||
}
|
||||
|
||||
res = accountSend(
|
||||
view,
|
||||
accountID_,
|
||||
ammAccount,
|
||||
*amount2DepositActual,
|
||||
ctx_.journal,
|
||||
WaiveTransferFee::Yes);
|
||||
view, account_, ammAccount, *amount2DepositActual, ctx_.journal, WaiveTransferFee::Yes);
|
||||
if (!isTesSuccess(res))
|
||||
{
|
||||
JLOG(ctx_.journal.debug())
|
||||
@@ -608,7 +604,7 @@ AMMDeposit::deposit(
|
||||
}
|
||||
|
||||
// Deposit LP tokens
|
||||
res = accountSend(view, ammAccount, accountID_, lpTokensDepositActual, ctx_.journal);
|
||||
res = accountSend(view, ammAccount, account_, lpTokensDepositActual, ctx_.journal);
|
||||
if (!isTesSuccess(res))
|
||||
{
|
||||
JLOG(ctx_.journal.debug()) << "AMM Deposit: failed to deposit LPTokens";
|
||||
|
||||
@@ -80,14 +80,14 @@ AMMVote::preclaim(PreclaimContext const& ctx)
|
||||
}
|
||||
|
||||
static std::pair<TER, bool>
|
||||
applyVote(ApplyContext& ctx, Sandbox& sb, AccountID const& accountID, beast::Journal j)
|
||||
applyVote(ApplyContext& ctx, Sandbox& sb, AccountID const& account, beast::Journal j)
|
||||
{
|
||||
auto const feeNew = ctx.tx[sfTradingFee];
|
||||
auto ammSle = sb.peek(keylet::amm(ctx.tx[sfAsset], ctx.tx[sfAsset2]));
|
||||
if (!ammSle)
|
||||
return {tecINTERNAL, false};
|
||||
STAmount const lptAMMBalance = (*ammSle)[sfLPTokenBalance];
|
||||
auto const lpTokensNew = ammLPHolds(sb, *ammSle, accountID, ctx.journal);
|
||||
auto const lpTokensNew = ammLPHolds(sb, *ammSle, account, ctx.journal);
|
||||
std::optional<STAmount> minTokens;
|
||||
std::size_t minPos{0};
|
||||
AccountID minAccount{0};
|
||||
@@ -108,13 +108,13 @@ applyVote(ApplyContext& ctx, Sandbox& sb, AccountID const& accountID, beast::Jou
|
||||
auto lpTokens = ammLPHolds(sb, *ammSle, entryAccount, ctx.journal);
|
||||
if (lpTokens == beast::kZero)
|
||||
{
|
||||
JLOG(j.debug()) << "AMMVote::applyVote, accountID " << entryAccount << " is not LP";
|
||||
JLOG(j.debug()) << "AMMVote::applyVote, account " << entryAccount << " is not LP";
|
||||
continue;
|
||||
}
|
||||
auto feeVal = entry[sfTradingFee];
|
||||
STObject newEntry = STObject::makeInnerObject(sfVoteEntry);
|
||||
// The account already has the vote entry.
|
||||
if (entryAccount == accountID)
|
||||
if (entryAccount == account)
|
||||
{
|
||||
lpTokens = lpTokensNew;
|
||||
feeVal = feeNew;
|
||||
@@ -156,7 +156,7 @@ applyVote(ApplyContext& ctx, Sandbox& sb, AccountID const& accountID, beast::Jou
|
||||
sfVoteWeight,
|
||||
static_cast<std::int64_t>(
|
||||
Number(lpTokensNew) * kVoteWeightScaleFactor / lptAMMBalance));
|
||||
newEntry.setAccountID(sfAccount, accountID);
|
||||
newEntry.setAccountID(sfAccount, account);
|
||||
num += feeNew * lpTokensNew;
|
||||
den += lpTokensNew;
|
||||
if (minPos)
|
||||
@@ -241,7 +241,7 @@ AMMVote::doApply()
|
||||
// as we go on processing transactions.
|
||||
Sandbox sb(&ctx_.view());
|
||||
|
||||
auto const result = applyVote(ctx_, sb, accountID_, j_);
|
||||
auto const result = applyVote(ctx_, sb, account_, j_);
|
||||
if (result.second)
|
||||
sb.apply(ctx_.rawView());
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#include <xrpl/core/ServiceRegistry.h>
|
||||
#include <xrpl/ledger/Sandbox.h>
|
||||
#include <xrpl/ledger/helpers/AMMHelpers.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
@@ -26,7 +25,6 @@
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/protocol/TxFormats.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
#include <xrpl/tx/Transactor.h>
|
||||
|
||||
@@ -242,22 +240,24 @@ AMMWithdraw::preclaim(PreclaimContext const& ctx)
|
||||
return ter;
|
||||
}
|
||||
// AMM account or currency frozen
|
||||
if (isFrozen(ctx.view, ammAccountID, amount->asset()))
|
||||
if (auto const ter = checkFrozen(ctx.view, ammAccountID, amount->asset());
|
||||
!isTesSuccess(ter))
|
||||
{
|
||||
JLOG(ctx.j.debug())
|
||||
<< "AMM Withdraw: AMM account or currency is frozen, " << to_string(accountID);
|
||||
return tecFROZEN;
|
||||
JLOG(ctx.j.debug()) << "AMM Withdraw: AMM account or currency is frozen or locked, "
|
||||
<< to_string(accountID);
|
||||
return ter;
|
||||
}
|
||||
// Account frozen
|
||||
if (isIndividualFrozen(ctx.view, accountID, amount->asset()))
|
||||
if (auto const ter = checkIndividualFrozen(ctx.view, accountID, amount->asset());
|
||||
!isTesSuccess(ter))
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "AMM Withdraw: account is frozen, " << to_string(accountID)
|
||||
<< " " << to_string(amount->asset());
|
||||
return tecFROZEN;
|
||||
JLOG(ctx.j.debug()) << "AMM Withdraw: account is frozen or locked, "
|
||||
<< to_string(accountID) << " " << to_string(amount->asset());
|
||||
return ter;
|
||||
}
|
||||
|
||||
if (auto const ter =
|
||||
checkMPTTxAllowed(ctx.view, ttAMM_WITHDRAW, amount->asset(), accountID);
|
||||
canMPTTradeAndTransfer(ctx.view, amount->asset(), accountID, accountID);
|
||||
!isTesSuccess(ter))
|
||||
return ter;
|
||||
}
|
||||
@@ -329,11 +329,11 @@ AMMWithdraw::applyGuts(Sandbox& sb)
|
||||
// might not match the LP's trustline balance
|
||||
if (sb.rules().enabled(fixAMMv1_1))
|
||||
{
|
||||
if (auto const res = verifyAndAdjustLPTokenBalance(sb, lpTokens, ammSle, accountID_); !res)
|
||||
if (auto const res = verifyAndAdjustLPTokenBalance(sb, lpTokens, ammSle, account_); !res)
|
||||
return {res.error(), false};
|
||||
}
|
||||
|
||||
auto const tfee = getTradingFee(ctx_.view(), *ammSle, accountID_);
|
||||
auto const tfee = getTradingFee(ctx_.view(), *ammSle, account_);
|
||||
|
||||
auto const expected = ammHolds(
|
||||
sb,
|
||||
@@ -458,7 +458,7 @@ AMMWithdraw::withdraw(
|
||||
view,
|
||||
ammSle,
|
||||
ammAccount,
|
||||
accountID_,
|
||||
account_,
|
||||
amountBalance,
|
||||
amountWithdraw,
|
||||
amount2Withdraw,
|
||||
@@ -635,10 +635,6 @@ AMMWithdraw::withdraw(
|
||||
auto const balanceAdj = isIssue ? std::max(priorBalance, balance.xrp()) : priorBalance;
|
||||
if (balanceAdj < reserve)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
|
||||
// Update owner count.
|
||||
if (!isIssue)
|
||||
adjustOwnerCount(view, sleAccount, 1, journal);
|
||||
}
|
||||
return tesSUCCESS;
|
||||
};
|
||||
@@ -748,7 +744,7 @@ AMMWithdraw::equalWithdrawTokens(
|
||||
std::tie(ter, newLPTokenBalance, std::ignore, std::ignore) = equalWithdrawTokens(
|
||||
view,
|
||||
ammSle,
|
||||
accountID_,
|
||||
account_,
|
||||
ammAccount,
|
||||
amountBalance,
|
||||
amount2Balance,
|
||||
|
||||
@@ -55,11 +55,11 @@ OfferCancel::doApply()
|
||||
{
|
||||
auto const offerSequence = ctx_.tx[sfOfferSequence];
|
||||
|
||||
auto const sle = view().read(keylet::account(accountID_));
|
||||
auto const sle = view().read(keylet::account(account_));
|
||||
if (!sle)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
if (auto sleOffer = view().peek(keylet::offer(accountID_, offerSequence)))
|
||||
if (auto sleOffer = view().peek(keylet::offer(account_, offerSequence)))
|
||||
{
|
||||
JLOG(j_.debug()) << "Trying to cancel offer #" << offerSequence;
|
||||
return offerDelete(view(), sleOffer, ctx_.registry.get().getJournal("View"));
|
||||
|
||||
@@ -181,11 +181,15 @@ OfferCreate::preclaim(PreclaimContext const& ctx)
|
||||
|
||||
auto viewJ = ctx.registry.get().getJournal("View");
|
||||
|
||||
if (isGlobalFrozen(ctx.view, saTakerPays.asset()) ||
|
||||
isGlobalFrozen(ctx.view, saTakerGets.asset()))
|
||||
if (auto const ter = checkGlobalFrozen(ctx.view, saTakerPays.asset()); !isTesSuccess(ter))
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "Offer involves frozen asset";
|
||||
return tecFROZEN;
|
||||
return ter;
|
||||
}
|
||||
if (auto const ter = checkGlobalFrozen(ctx.view, saTakerGets.asset()); !isTesSuccess(ter))
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "Offer involves frozen asset";
|
||||
return ter;
|
||||
}
|
||||
|
||||
// Allow unfunded MPT for issuer (OutstandingAmount >= MaximumAmount)
|
||||
@@ -320,7 +324,13 @@ OfferCreate::checkAcceptAsset(
|
||||
[&](MPTIssue const& issue) -> TER {
|
||||
// WeakAuth - don't check if MPToken exists since it's created
|
||||
// if needed.
|
||||
return requireAuth(view, issue, id, AuthType::WeakAuth);
|
||||
if (auto const ter = requireAuth(view, issue, id, AuthType::WeakAuth);
|
||||
!isTesSuccess(ter))
|
||||
{
|
||||
return ter;
|
||||
}
|
||||
|
||||
return checkFrozen(view, id, issue);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -341,14 +351,14 @@ OfferCreate::flowCross(
|
||||
// below the reserve) so we check this case again.
|
||||
STAmount const inStartBalance = accountFunds(
|
||||
psb,
|
||||
accountID_,
|
||||
account_,
|
||||
takerAmount.in,
|
||||
FreezeHandling::ZeroIfFrozen,
|
||||
AuthHandling::ZeroIfUnauthorized,
|
||||
j_);
|
||||
// Allow unfunded MPT issuer
|
||||
auto const disallowUnfunded =
|
||||
!inStartBalance.holds<MPTIssue>() || inStartBalance.getIssuer() != accountID_;
|
||||
!inStartBalance.holds<MPTIssue>() || inStartBalance.getIssuer() != account_;
|
||||
if (disallowUnfunded && inStartBalance <= beast::kZero)
|
||||
{
|
||||
// The account balance can't cover even part of the offer.
|
||||
@@ -361,7 +371,7 @@ OfferCreate::flowCross(
|
||||
// offer taker. Set sendMax to allow for the gateway's cut.
|
||||
Rate gatewayXferRate{QUALITY_ONE};
|
||||
STAmount sendMax = takerAmount.in;
|
||||
if (!sendMax.native() && (accountID_ != sendMax.getIssuer()))
|
||||
if (!sendMax.native() && (account_ != sendMax.getIssuer()))
|
||||
{
|
||||
gatewayXferRate = transferRate(psb, sendMax);
|
||||
if (gatewayXferRate.value != QUALITY_ONE)
|
||||
@@ -428,8 +438,8 @@ OfferCreate::flowCross(
|
||||
auto const result = flow(
|
||||
psb,
|
||||
deliver,
|
||||
accountID_,
|
||||
accountID_,
|
||||
account_,
|
||||
account_,
|
||||
paths,
|
||||
true, // default path
|
||||
!ctx_.tx.isFlag(tfFillOrKill), // partial payment
|
||||
@@ -455,7 +465,7 @@ OfferCreate::flowCross(
|
||||
{
|
||||
STAmount const takerInBalance = accountFunds(
|
||||
psb,
|
||||
accountID_,
|
||||
account_,
|
||||
takerAmount.in,
|
||||
FreezeHandling::ZeroIfFrozen,
|
||||
AuthHandling::ZeroIfUnauthorized,
|
||||
@@ -619,7 +629,7 @@ OfferCreate::applyGuts(Sandbox& sb, Sandbox& sbCancel)
|
||||
// Process a cancellation request that's passed along with an offer.
|
||||
if (cancelSequence)
|
||||
{
|
||||
auto const sleCancel = sb.peek(keylet::offer(accountID_, *cancelSequence));
|
||||
auto const sleCancel = sb.peek(keylet::offer(account_, *cancelSequence));
|
||||
|
||||
// It's not an error to not find the offer to cancel: it might have
|
||||
// been consumed or removed. If it is found, however, it's an error
|
||||
@@ -810,7 +820,7 @@ OfferCreate::applyGuts(Sandbox& sb, Sandbox& sbCancel)
|
||||
return {tesSUCCESS, true};
|
||||
}
|
||||
|
||||
auto const sleCreator = sb.peek(keylet::account(accountID_));
|
||||
auto const sleCreator = sb.peek(keylet::account(account_));
|
||||
if (!sleCreator)
|
||||
return {tefINTERNAL, false};
|
||||
|
||||
@@ -836,11 +846,11 @@ OfferCreate::applyGuts(Sandbox& sb, Sandbox& sbCancel)
|
||||
}
|
||||
|
||||
// We need to place the remainder of the offer into its order book.
|
||||
auto const offerIndex = keylet::offer(accountID_, offerSequence);
|
||||
auto const offerIndex = keylet::offer(account_, offerSequence);
|
||||
|
||||
// Add offer to owner's directory.
|
||||
auto const ownerNode =
|
||||
sb.dirInsert(keylet::ownerDir(accountID_), offerIndex, describeOwnerDir(accountID_));
|
||||
sb.dirInsert(keylet::ownerDir(account_), offerIndex, describeOwnerDir(account_));
|
||||
|
||||
if (!ownerNode)
|
||||
{
|
||||
@@ -905,7 +915,7 @@ OfferCreate::applyGuts(Sandbox& sb, Sandbox& sbCancel)
|
||||
}
|
||||
|
||||
auto sleOffer = std::make_shared<SLE>(offerIndex);
|
||||
sleOffer->setAccountID(sfAccount, accountID_);
|
||||
sleOffer->setAccountID(sfAccount, account_);
|
||||
sleOffer->setFieldU32(sfSequence, offerSequence);
|
||||
sleOffer->setFieldH256(sfBookDirectory, dir.key);
|
||||
sleOffer->setFieldAmount(sfTakerPays, saTakerPays);
|
||||
|
||||
@@ -67,7 +67,7 @@ DIDDelete::deleteSLE(
|
||||
TER
|
||||
DIDDelete::doApply()
|
||||
{
|
||||
return deleteSLE(ctx_, keylet::did(accountID_), accountID_);
|
||||
return deleteSLE(ctx_, keylet::did(account_), account_);
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -99,7 +99,7 @@ TER
|
||||
DIDSet::doApply()
|
||||
{
|
||||
// Edit ledger object if it already exists
|
||||
Keylet const didKeylet = keylet::did(accountID_);
|
||||
Keylet const didKeylet = keylet::did(account_);
|
||||
if (auto const sleDID = ctx_.view().peek(didKeylet))
|
||||
{
|
||||
auto update = [&](auto const& sField) {
|
||||
@@ -130,7 +130,7 @@ DIDSet::doApply()
|
||||
|
||||
// Create new ledger object otherwise
|
||||
auto const sleDID = std::make_shared<SLE>(didKeylet);
|
||||
(*sleDID)[sfAccount] = accountID_;
|
||||
(*sleDID)[sfAccount] = account_;
|
||||
|
||||
auto set = [&](auto const& sField) {
|
||||
if (auto const field = ctx_.tx[~sField]; field && !field->empty())
|
||||
@@ -146,7 +146,7 @@ DIDSet::doApply()
|
||||
return tecEMPTY_DID;
|
||||
}
|
||||
|
||||
return addSLE(ctx_, sleDID, accountID_);
|
||||
return addSLE(ctx_, sleDID, account_);
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -178,13 +178,13 @@ EscrowCancel::doApply()
|
||||
return temDISABLED; // LCOV_EXCL_LINE
|
||||
|
||||
auto const issuer = amount.getIssuer();
|
||||
bool const createAsset = account == accountID_;
|
||||
bool const createAsset = account == account_;
|
||||
if (auto const ret = std::visit(
|
||||
[&]<typename T>(T const&) {
|
||||
return escrowUnlockApplyHelper<T>(
|
||||
ctx_.view(),
|
||||
kParityRate,
|
||||
ctx_.view().rules().enabled(fixCleanup3_2_0) ? sle : slep,
|
||||
slep,
|
||||
preFeeBalance_,
|
||||
amount,
|
||||
issuer,
|
||||
|
||||
@@ -417,7 +417,7 @@ EscrowCreate::doApply()
|
||||
if (ctx_.tx[~sfFinishAfter] && after(closeTime, ctx_.tx[sfFinishAfter]))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
auto const sle = ctx_.view().peek(keylet::account(accountID_));
|
||||
auto const sle = ctx_.view().peek(keylet::account(account_));
|
||||
if (!sle)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
@@ -448,10 +448,10 @@ EscrowCreate::doApply()
|
||||
|
||||
// Create escrow in ledger. Note that we use the value from the
|
||||
// sequence or ticket. For more explanation see comments in SeqProxy.h.
|
||||
Keylet const escrowKeylet = keylet::escrow(accountID_, ctx_.tx.getSeqValue());
|
||||
Keylet const escrowKeylet = keylet::escrow(account_, ctx_.tx.getSeqValue());
|
||||
auto const slep = std::make_shared<SLE>(escrowKeylet);
|
||||
(*slep)[sfAmount] = amount;
|
||||
(*slep)[sfAccount] = accountID_;
|
||||
(*slep)[sfAccount] = account_;
|
||||
(*slep)[~sfCondition] = ctx_.tx[~sfCondition];
|
||||
(*slep)[~sfSourceTag] = ctx_.tx[~sfSourceTag];
|
||||
(*slep)[sfDestination] = ctx_.tx[sfDestination];
|
||||
@@ -476,7 +476,7 @@ EscrowCreate::doApply()
|
||||
// Add escrow to sender's owner directory
|
||||
{
|
||||
auto page = ctx_.view().dirInsert(
|
||||
keylet::ownerDir(accountID_), escrowKeylet, describeOwnerDir(accountID_));
|
||||
keylet::ownerDir(account_), escrowKeylet, describeOwnerDir(account_));
|
||||
if (!page)
|
||||
return tecDIR_FULL; // LCOV_EXCL_LINE
|
||||
(*slep)[sfOwnerNode] = *page;
|
||||
@@ -484,7 +484,7 @@ EscrowCreate::doApply()
|
||||
|
||||
// If it's not a self-send, add escrow to recipient's owner directory.
|
||||
AccountID const dest = ctx_.tx[sfDestination];
|
||||
if (dest != accountID_)
|
||||
if (dest != account_)
|
||||
{
|
||||
auto page =
|
||||
ctx_.view().dirInsert(keylet::ownerDir(dest), escrowKeylet, describeOwnerDir(dest));
|
||||
@@ -497,7 +497,7 @@ EscrowCreate::doApply()
|
||||
// track the total locked balance. For MPT, this isn't necessary because the
|
||||
// locked balance is already stored directly in the MPTokenIssuance object.
|
||||
AccountID const issuer = amount.getIssuer();
|
||||
if (!isXRP(amount) && issuer != accountID_ && issuer != dest && !amount.holds<MPTIssue>())
|
||||
if (!isXRP(amount) && issuer != account_ && issuer != dest && !amount.holds<MPTIssue>())
|
||||
{
|
||||
auto page =
|
||||
ctx_.view().dirInsert(keylet::ownerDir(issuer), escrowKeylet, describeOwnerDir(issuer));
|
||||
@@ -515,7 +515,7 @@ EscrowCreate::doApply()
|
||||
{
|
||||
if (auto const ret = std::visit(
|
||||
[&]<typename T>(T const&) {
|
||||
return escrowLockApplyHelper<T>(ctx_.view(), issuer, accountID_, amount, j_);
|
||||
return escrowLockApplyHelper<T>(ctx_.view(), issuer, account_, amount, j_);
|
||||
},
|
||||
amount.asset().value());
|
||||
!isTesSuccess(ret))
|
||||
|
||||
@@ -310,8 +310,7 @@ EscrowFinish::doApply()
|
||||
if (!sled)
|
||||
return tecNO_DST;
|
||||
|
||||
if (auto err =
|
||||
verifyDepositPreauth(ctx_.tx, ctx_.view(), accountID_, destID, sled, ctx_.journal);
|
||||
if (auto err = verifyDepositPreauth(ctx_.tx, ctx_.view(), account_, destID, sled, ctx_.journal);
|
||||
!isTesSuccess(err))
|
||||
return err;
|
||||
|
||||
@@ -356,7 +355,7 @@ EscrowFinish::doApply()
|
||||
? xrpl::Rate(slep->getFieldU32(sfTransferRate))
|
||||
: kParityRate;
|
||||
auto const issuer = amount.getIssuer();
|
||||
bool const createAsset = destID == accountID_;
|
||||
bool const createAsset = destID == account_;
|
||||
if (auto const ret = std::visit(
|
||||
[&]<typename T>(T const&) {
|
||||
return escrowUnlockApplyHelper<T>(
|
||||
|
||||
@@ -121,8 +121,7 @@ LoanBrokerCoverDeposit::doApply()
|
||||
auto const brokerPseudoID = broker->at(sfAccount);
|
||||
|
||||
// Transfer assets from depositor to pseudo-account.
|
||||
if (auto ter =
|
||||
accountSend(view(), accountID_, brokerPseudoID, amount, j_, WaiveTransferFee::Yes))
|
||||
if (auto ter = accountSend(view(), account_, brokerPseudoID, amount, j_, WaiveTransferFee::Yes))
|
||||
return ter;
|
||||
|
||||
// Increase the LoanBroker's CoverAvailable by Amount
|
||||
|
||||
@@ -163,7 +163,7 @@ LoanBrokerCoverWithdraw::doApply()
|
||||
|
||||
auto const brokerID = tx[sfLoanBrokerID];
|
||||
auto const amount = tx[sfAmount];
|
||||
auto const dstAcct = tx[~sfDestination].value_or(accountID_);
|
||||
auto const dstAcct = tx[~sfDestination].value_or(account_);
|
||||
|
||||
auto broker = view().peek(keylet::loanbroker(brokerID));
|
||||
if (!broker)
|
||||
@@ -183,7 +183,7 @@ LoanBrokerCoverWithdraw::doApply()
|
||||
|
||||
associateAsset(*broker, vaultAsset);
|
||||
|
||||
return doWithdraw(view(), tx, accountID_, dstAcct, brokerPseudoID, preFeeBalance_, amount, j_);
|
||||
return doWithdraw(view(), tx, account_, dstAcct, brokerPseudoID, preFeeBalance_, amount, j_);
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -130,7 +130,7 @@ LoanBrokerDelete::doApply()
|
||||
auto const brokerPseudoID = broker->at(sfAccount);
|
||||
|
||||
if (!view().dirRemove(
|
||||
keylet::ownerDir(accountID_), broker->at(sfOwnerNode), broker->key(), false))
|
||||
keylet::ownerDir(account_), broker->at(sfOwnerNode), broker->key(), false))
|
||||
{
|
||||
return tefBAD_LEDGER; // LCOV_EXCL_LINE
|
||||
}
|
||||
@@ -143,7 +143,7 @@ LoanBrokerDelete::doApply()
|
||||
{
|
||||
auto const coverAvailable = STAmount{vaultAsset, broker->at(sfCoverAvailable)};
|
||||
if (auto const ter = accountSend(
|
||||
view(), brokerPseudoID, accountID_, coverAvailable, j_, WaiveTransferFee::Yes))
|
||||
view(), brokerPseudoID, account_, coverAvailable, j_, WaiveTransferFee::Yes))
|
||||
return ter;
|
||||
}
|
||||
|
||||
@@ -177,7 +177,7 @@ LoanBrokerDelete::doApply()
|
||||
view().erase(broker);
|
||||
|
||||
{
|
||||
auto owner = view().peek(keylet::account(accountID_));
|
||||
auto owner = view().peek(keylet::account(account_));
|
||||
if (!owner)
|
||||
return tefBAD_LEDGER; // LCOV_EXCL_LINE
|
||||
|
||||
|
||||
@@ -220,7 +220,7 @@ LoanBrokerSet::doApply()
|
||||
auto const vaultAsset = sleVault->at(sfAsset);
|
||||
auto const sequence = tx.getSeqValue();
|
||||
|
||||
auto owner = view.peek(keylet::account(accountID_));
|
||||
auto owner = view.peek(keylet::account(account_));
|
||||
if (!owner)
|
||||
{
|
||||
// This should be impossible
|
||||
@@ -229,9 +229,9 @@ LoanBrokerSet::doApply()
|
||||
return tefBAD_LEDGER;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
auto broker = std::make_shared<SLE>(keylet::loanbroker(accountID_, sequence));
|
||||
auto broker = std::make_shared<SLE>(keylet::loanbroker(account_, sequence));
|
||||
|
||||
if (auto const ter = dirLink(view, accountID_, broker))
|
||||
if (auto const ter = dirLink(view, account_, broker))
|
||||
return ter; // LCOV_EXCL_LINE
|
||||
if (auto const ter = dirLink(view, vaultPseudoID, broker, sfVaultNode))
|
||||
return ter; // LCOV_EXCL_LINE
|
||||
@@ -255,7 +255,7 @@ LoanBrokerSet::doApply()
|
||||
// Initialize data fields:
|
||||
broker->at(sfSequence) = sequence;
|
||||
broker->at(sfVaultID) = vaultID;
|
||||
broker->at(sfOwner) = accountID_;
|
||||
broker->at(sfOwner) = account_;
|
||||
broker->at(sfAccount) = pseudoId;
|
||||
// The LoanSequence indexes loans created by this broker, starting at 1
|
||||
broker->at(sfLoanSequence) = 1;
|
||||
|
||||
@@ -99,7 +99,7 @@ LoanPay::calculateBaseFee(ReadView const& view, STTx const& tx)
|
||||
|
||||
if (loanSle->at(sfPaymentRemaining) <= kLoanPaymentsPerFeeIncrement)
|
||||
{
|
||||
// If there are fewer than kLoanPaymentsPerFeeIncrement payments left to
|
||||
// If there are fewer than loanPaymentsPerFeeIncrement payments left to
|
||||
// pay, we can skip the computations.
|
||||
return normalCost;
|
||||
}
|
||||
@@ -581,13 +581,13 @@ LoanPay::doApply()
|
||||
// These three values are used to check that funds are conserved after the transfers
|
||||
auto const accountBalanceBefore = accountHolds(
|
||||
view,
|
||||
accountID_,
|
||||
account_,
|
||||
asset,
|
||||
FreezeHandling::IgnoreFreeze,
|
||||
AuthHandling::IgnoreAuth,
|
||||
j_,
|
||||
SpendableHandling::FullBalance);
|
||||
auto const vaultBalanceBefore = accountID_ == vaultPseudoAccount
|
||||
auto const vaultBalanceBefore = account_ == vaultPseudoAccount
|
||||
? STAmount{asset, 0}
|
||||
: accountHolds(
|
||||
view,
|
||||
@@ -597,16 +597,15 @@ LoanPay::doApply()
|
||||
AuthHandling::IgnoreAuth,
|
||||
j_,
|
||||
SpendableHandling::FullBalance);
|
||||
auto const brokerBalanceBefore = accountID_ == brokerPayee
|
||||
? STAmount{asset, 0}
|
||||
: accountHolds(
|
||||
view,
|
||||
brokerPayee,
|
||||
asset,
|
||||
FreezeHandling::IgnoreFreeze,
|
||||
AuthHandling::IgnoreAuth,
|
||||
j_,
|
||||
SpendableHandling::FullBalance);
|
||||
auto const brokerBalanceBefore = account_ == brokerPayee ? STAmount{asset, 0}
|
||||
: accountHolds(
|
||||
view,
|
||||
brokerPayee,
|
||||
asset,
|
||||
FreezeHandling::IgnoreFreeze,
|
||||
AuthHandling::IgnoreAuth,
|
||||
j_,
|
||||
SpendableHandling::FullBalance);
|
||||
|
||||
if (totalPaidToVaultRounded != beast::kZero)
|
||||
{
|
||||
@@ -616,7 +615,7 @@ LoanPay::doApply()
|
||||
|
||||
if (totalPaidToBroker != beast::kZero)
|
||||
{
|
||||
if (brokerPayee == accountID_)
|
||||
if (brokerPayee == account_)
|
||||
{
|
||||
// The broker may have deleted their holding. Recreate it if needed
|
||||
if (auto const ter = addEmptyHolding(
|
||||
@@ -634,7 +633,7 @@ LoanPay::doApply()
|
||||
|
||||
if (auto const ter = accountSendMulti(
|
||||
view,
|
||||
accountID_,
|
||||
account_,
|
||||
asset,
|
||||
{{vaultPseudoAccount, totalPaidToVaultRounded}, {brokerPayee, totalPaidToBroker}},
|
||||
j_,
|
||||
@@ -660,13 +659,13 @@ LoanPay::doApply()
|
||||
// Check that funds are conserved
|
||||
auto const accountBalanceAfter = accountHolds(
|
||||
view,
|
||||
accountID_,
|
||||
account_,
|
||||
asset,
|
||||
FreezeHandling::IgnoreFreeze,
|
||||
AuthHandling::IgnoreAuth,
|
||||
j_,
|
||||
SpendableHandling::FullBalance);
|
||||
auto const vaultBalanceAfter = accountID_ == vaultPseudoAccount
|
||||
auto const vaultBalanceAfter = account_ == vaultPseudoAccount
|
||||
? STAmount{asset, 0}
|
||||
: accountHolds(
|
||||
view,
|
||||
@@ -676,15 +675,15 @@ LoanPay::doApply()
|
||||
AuthHandling::IgnoreAuth,
|
||||
j_,
|
||||
SpendableHandling::FullBalance);
|
||||
auto const brokerBalanceAfter = accountID_ == brokerPayee ? STAmount{asset, 0}
|
||||
: accountHolds(
|
||||
view,
|
||||
brokerPayee,
|
||||
asset,
|
||||
FreezeHandling::IgnoreFreeze,
|
||||
AuthHandling::IgnoreAuth,
|
||||
j_,
|
||||
SpendableHandling::FullBalance);
|
||||
auto const brokerBalanceAfter = account_ == brokerPayee ? STAmount{asset, 0}
|
||||
: accountHolds(
|
||||
view,
|
||||
brokerPayee,
|
||||
asset,
|
||||
FreezeHandling::IgnoreFreeze,
|
||||
AuthHandling::IgnoreAuth,
|
||||
j_,
|
||||
SpendableHandling::FullBalance);
|
||||
auto const balanceScale = [&]() {
|
||||
// Find a reasonable scale to use for the balance comparisons.
|
||||
//
|
||||
@@ -801,7 +800,7 @@ LoanPay::doApply()
|
||||
goodRounding, "xrpl::LoanPay::doApply", "funds are conserved (with rounding)");
|
||||
|
||||
XRPL_ASSERT_PARTS(
|
||||
accountBalanceAfter < accountBalanceBefore || accountID_ == asset.getIssuer(),
|
||||
accountBalanceAfter < accountBalanceBefore || account_ == asset.getIssuer(),
|
||||
"xrpl::LoanPay::doApply",
|
||||
"account balance decreased");
|
||||
XRPL_ASSERT_PARTS(
|
||||
|
||||
@@ -388,7 +388,7 @@ LoanSet::doApply()
|
||||
Asset const vaultAsset = vaultSle->at(sfAsset);
|
||||
|
||||
auto const counterparty = tx[~sfCounterparty].value_or(brokerOwner);
|
||||
auto const borrower = counterparty == brokerOwner ? accountID_ : counterparty;
|
||||
auto const borrower = counterparty == brokerOwner ? account_ : counterparty;
|
||||
auto const borrowerSle = view.peek(keylet::account(borrower));
|
||||
if (!borrowerSle)
|
||||
{
|
||||
@@ -508,7 +508,7 @@ LoanSet::doApply()
|
||||
{
|
||||
auto const ownerCount = borrowerSle->at(sfOwnerCount);
|
||||
auto const balance =
|
||||
accountID_ == borrower ? preFeeBalance_ : borrowerSle->at(sfBalance).value().xrp();
|
||||
account_ == borrower ? preFeeBalance_ : borrowerSle->at(sfBalance).value().xrp();
|
||||
if (balance < view.fees().accountReserve(ownerCount))
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
}
|
||||
@@ -520,7 +520,7 @@ LoanSet::doApply()
|
||||
// Create a holding for the borrower if one does not already exist.
|
||||
|
||||
XRPL_ASSERT_PARTS(
|
||||
borrower == accountID_ || borrower == counterparty,
|
||||
borrower == account_ || borrower == counterparty,
|
||||
"xrpl::LoanSet::doApply",
|
||||
"borrower signed transaction");
|
||||
if (auto const ter = addEmptyHolding(
|
||||
@@ -542,7 +542,7 @@ LoanSet::doApply()
|
||||
// Create the holding if it doesn't already exist (necessary for MPTs).
|
||||
// The owner may have deleted their MPT / line at some point.
|
||||
XRPL_ASSERT_PARTS(
|
||||
brokerOwner == accountID_ || brokerOwner == counterparty,
|
||||
brokerOwner == account_ || brokerOwner == counterparty,
|
||||
"xrpl::LoanSet::doApply",
|
||||
"broker owner signed transaction");
|
||||
|
||||
|
||||
@@ -407,8 +407,8 @@ NFTokenAcceptOffer::acceptOffer(std::shared_ptr<SLE> const& offer)
|
||||
{
|
||||
bool const isSell = offer->isFlag(lsfSellNFToken);
|
||||
AccountID const owner = (*offer)[sfOwner];
|
||||
AccountID const& seller = isSell ? owner : accountID_;
|
||||
AccountID const& buyer = isSell ? accountID_ : owner;
|
||||
AccountID const& seller = isSell ? owner : account_;
|
||||
AccountID const& buyer = isSell ? account_ : owner;
|
||||
|
||||
auto const nftokenID = (*offer)[sfNFTokenID];
|
||||
|
||||
@@ -528,7 +528,7 @@ NFTokenAcceptOffer::doApply()
|
||||
// Send the broker the amount they requested.
|
||||
if (auto const cut = ctx_.tx[~sfNFTokenBrokerFee]; cut && cut.value() != beast::kZero)
|
||||
{
|
||||
if (auto const r = pay(buyer, accountID_, cut.value()); !isTesSuccess(r))
|
||||
if (auto const r = pay(buyer, account_, cut.value()); !isTesSuccess(r))
|
||||
return r;
|
||||
|
||||
amount -= cut.value();
|
||||
|
||||
@@ -222,7 +222,7 @@ NFTokenMint::preclaim(PreclaimContext const& ctx)
|
||||
TER
|
||||
NFTokenMint::doApply()
|
||||
{
|
||||
auto const issuer = ctx_.tx[~sfIssuer].value_or(accountID_);
|
||||
auto const issuer = ctx_.tx[~sfIssuer].value_or(account_);
|
||||
|
||||
auto const tokenSeq = [this, &issuer]() -> Expected<std::uint32_t, TER> {
|
||||
auto const root = view().peek(keylet::account(issuer));
|
||||
@@ -279,7 +279,7 @@ NFTokenMint::doApply()
|
||||
return (tokenSeq.error());
|
||||
|
||||
std::uint32_t const ownerCountBefore =
|
||||
view().read(keylet::account(accountID_))->getFieldU32(sfOwnerCount);
|
||||
view().read(keylet::account(account_))->getFieldU32(sfOwnerCount);
|
||||
|
||||
// Assemble the new NFToken.
|
||||
SOTemplate const* nfTokenTemplate =
|
||||
@@ -305,7 +305,7 @@ NFTokenMint::doApply()
|
||||
object.setFieldVL(sfURI, *uri);
|
||||
});
|
||||
|
||||
if (TER const ret = nft::insertToken(ctx_.view(), accountID_, std::move(newToken));
|
||||
if (TER const ret = nft::insertToken(ctx_.view(), account_, std::move(newToken));
|
||||
!isTesSuccess(ret))
|
||||
return ret;
|
||||
|
||||
@@ -333,7 +333,7 @@ NFTokenMint::doApply()
|
||||
// requiring the reserve to be met each time. The reserve is
|
||||
// only managed when a new NFT page or sell offer is added.
|
||||
if (auto const ownerCountAfter =
|
||||
view().read(keylet::account(accountID_))->getFieldU32(sfOwnerCount);
|
||||
view().read(keylet::account(account_))->getFieldU32(sfOwnerCount);
|
||||
ownerCountAfter > ownerCountBefore)
|
||||
{
|
||||
if (auto const reserve = view().fees().accountReserve(ownerCountAfter);
|
||||
|
||||
@@ -82,8 +82,8 @@ OracleDelete::deleteOracle(
|
||||
TER
|
||||
OracleDelete::doApply()
|
||||
{
|
||||
if (auto sle = ctx_.view().peek(keylet::oracle(accountID_, ctx_.tx[sfOracleDocumentID])))
|
||||
return deleteOracle(ctx_.view(), sle, accountID_, j_);
|
||||
if (auto sle = ctx_.view().peek(keylet::oracle(account_, ctx_.tx[sfOracleDocumentID])))
|
||||
return deleteOracle(ctx_.view(), sle, account_, j_);
|
||||
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
@@ -201,7 +201,7 @@ setPriceDataInnerObjTemplate(STObject& obj)
|
||||
TER
|
||||
OracleSet::doApply()
|
||||
{
|
||||
auto const oracleID = keylet::oracle(accountID_, ctx_.tx[sfOracleDocumentID]);
|
||||
auto const oracleID = keylet::oracle(account_, ctx_.tx[sfOracleDocumentID]);
|
||||
|
||||
auto populatePriceData = [](STObject& priceData, STObject const& entry) {
|
||||
setPriceDataInnerObjTemplate(priceData);
|
||||
@@ -311,7 +311,7 @@ OracleSet::doApply()
|
||||
sle->setFieldU32(sfLastUpdateTime, ctx_.tx[sfLastUpdateTime]);
|
||||
|
||||
auto page = ctx_.view().dirInsert(
|
||||
keylet::ownerDir(accountID_), sle->key(), describeOwnerDir(accountID_));
|
||||
keylet::ownerDir(account_), sle->key(), describeOwnerDir(account_));
|
||||
if (!page)
|
||||
return tecDIR_FULL; // LCOV_EXCL_LINE
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user