mirror of
https://github.com/XRPLF/rippled.git
synced 2026-07-10 16:50:28 +00:00
Compare commits
103 Commits
wasmi/bina
...
mvadari/re
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1eab60b9cb | ||
|
|
ac794f695d | ||
|
|
d38f45701f | ||
|
|
b557403ab6 | ||
|
|
8c80a6aad5 | ||
|
|
9b1406ad46 | ||
|
|
1306d35e4d | ||
|
|
144835ca5d | ||
|
|
554c0baa0a | ||
|
|
7661483c4c | ||
|
|
6cb5842a05 | ||
|
|
bcd26397fb | ||
|
|
ffaf51043d | ||
|
|
cedebd5a86 | ||
|
|
700cd6a2b1 | ||
|
|
3d2e0788cc | ||
|
|
d7b6c8e098 | ||
|
|
d10bb71eea | ||
|
|
5dbbd9ba8d | ||
|
|
fb37cc2f42 | ||
|
|
fabf328485 | ||
|
|
dc2430ea86 | ||
|
|
0de37c2cad | ||
|
|
3d9eba73e8 | ||
|
|
dae0a53c20 | ||
|
|
ea3ca12894 | ||
|
|
7b27096aac | ||
|
|
c00491baa1 | ||
|
|
d616aff4e2 | ||
|
|
be9913f16f | ||
|
|
b99a7666ab | ||
|
|
683ff84ee0 | ||
|
|
9c9281e5d9 | ||
|
|
2d6450904d | ||
|
|
8269964c76 | ||
|
|
945b4f77cd | ||
|
|
03863d91ad | ||
|
|
737d128cca | ||
|
|
97629e7477 | ||
|
|
b0ca6adf7a | ||
|
|
e2238b13de | ||
|
|
27e9d0d695 | ||
|
|
b3829e5e61 | ||
|
|
50968111da | ||
|
|
fa77338bdc | ||
|
|
ffd96e354f | ||
|
|
e9287812ef | ||
|
|
819d3fcdfc | ||
|
|
dcdc5e1b52 | ||
|
|
c0895c6e2e | ||
|
|
5b0b1ff1f6 | ||
|
|
e9c4ed6a74 | ||
|
|
deaa23f4e5 | ||
|
|
638477c824 | ||
|
|
9b42e178a2 | ||
|
|
f41c02d486 | ||
|
|
a423813098 | ||
|
|
56c173a097 | ||
|
|
7900fa9ead | ||
|
|
0ffb3e2227 | ||
|
|
add3d7e68d | ||
|
|
c24432f43a | ||
|
|
1ccd84e43a | ||
|
|
1cc7424934 | ||
|
|
b5562cc81e | ||
|
|
e6369c0faa | ||
|
|
43caa7d47a | ||
|
|
e0f487bb2e | ||
|
|
a8987cf271 | ||
|
|
4157e3684c | ||
|
|
659f455335 | ||
|
|
3bc460951c | ||
|
|
d1dae53097 | ||
|
|
733dd51720 | ||
|
|
9d67db1843 | ||
|
|
29bb59698f | ||
|
|
3b2dcefb81 | ||
|
|
4e92ec2daf | ||
|
|
6afcf51dee | ||
|
|
7e62ecb63b | ||
|
|
ad2c359f21 | ||
|
|
96d4a69a37 | ||
|
|
14fbdd5a1c | ||
|
|
3c5e87888e | ||
|
|
8e218a6285 | ||
|
|
8a1adc4ec9 | ||
|
|
c9db124015 | ||
|
|
7056b43c5e | ||
|
|
ee370fefee | ||
|
|
e68d74cda4 | ||
|
|
89a3a6c379 | ||
|
|
f3a6d17b5b | ||
|
|
45531b1eaf | ||
|
|
fa8de2b47f | ||
|
|
6fcda8802e | ||
|
|
0cf42b150c | ||
|
|
74b010a905 | ||
|
|
265deccabe | ||
|
|
ea1146d413 | ||
|
|
5fa9bb53a8 | ||
|
|
d9bf263543 | ||
|
|
1f73da466c | ||
|
|
e9eb5e53c9 |
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
|
||||
|
||||
@@ -19,6 +19,11 @@
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
// Forward declarations for SLE wrappers
|
||||
template <typename ViewT>
|
||||
class AccountRoot;
|
||||
using RAccountRoot = AccountRoot<ReadView>;
|
||||
|
||||
enum class SkipEntry : bool { No = false, Yes };
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -157,7 +162,7 @@ canWithdraw(
|
||||
ReadView const& view,
|
||||
AccountID const& from,
|
||||
AccountID const& to,
|
||||
SLE::const_ref toSle,
|
||||
RAccountRoot const& toWrapped,
|
||||
STAmount const& amount,
|
||||
bool hasDestinationTag);
|
||||
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
|
||||
#include <xrpl/basics/Expected.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/helpers/SLEBase.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Rate.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
@@ -15,46 +17,160 @@
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/** Check if the issuer has the global freeze flag set.
|
||||
@param issuer The account to check
|
||||
@return true if the account has global freeze set
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
isGlobalFrozen(ReadView const& view, AccountID const& issuer);
|
||||
|
||||
// Calculate liquid XRP balance for an account.
|
||||
// This function may be used to calculate the amount of XRP that
|
||||
// the holder is able to freely spend. It subtracts reserve requirements.
|
||||
//
|
||||
// ownerCountAdj adjusts the owner count in case the caller calculates
|
||||
// before ledger entries are added or removed. Positive to add, negative
|
||||
// to subtract.
|
||||
//
|
||||
// @param ownerCountAdj positive to add to count, negative to reduce count.
|
||||
[[nodiscard]] XRPAmount
|
||||
xrpLiquid(ReadView const& view, AccountID const& id, std::int32_t ownerCountAdj, beast::Journal j);
|
||||
|
||||
/** Adjust the owner count up or down. */
|
||||
void
|
||||
adjustOwnerCount(
|
||||
ApplyView& view,
|
||||
std::shared_ptr<SLE> const& sle,
|
||||
std::int32_t amount,
|
||||
beast::Journal j);
|
||||
|
||||
/** Returns IOU issuer transfer fee as Rate. Rate specifies
|
||||
* the fee as fractions of 1 billion. For example, 1% transfer rate
|
||||
* is represented as 1,010,000,000.
|
||||
* @param issuer The IOU issuer
|
||||
/**
|
||||
* View-parameterized wrapper for AccountRoot ledger entries.
|
||||
*
|
||||
* AccountRoot<ReadView> — read-only access to account data
|
||||
* AccountRoot<ApplyView> — read-write access, with insert/update/erase
|
||||
* and domain-specific write methods
|
||||
*/
|
||||
[[nodiscard]] Rate
|
||||
transferRate(ReadView const& view, AccountID const& issuer);
|
||||
template <typename ViewT>
|
||||
class AccountRoot : public SLEBase<ViewT>
|
||||
{
|
||||
static constexpr bool kIsWritable = SLEBase<ViewT>::kIsWritable;
|
||||
|
||||
AccountID const id_;
|
||||
|
||||
public:
|
||||
/** Constructor for read-only context */
|
||||
AccountRoot(
|
||||
AccountID const& id,
|
||||
ReadView const& view,
|
||||
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
|
||||
requires(!kIsWritable)
|
||||
: SLEBase<ViewT>(view.read(keylet::account(id)), view, j), id_(id)
|
||||
{
|
||||
}
|
||||
|
||||
/** Constructor for writable context */
|
||||
AccountRoot(
|
||||
AccountID const& id,
|
||||
ApplyView& view,
|
||||
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
|
||||
requires kIsWritable
|
||||
: SLEBase<ViewT>(keylet::account(id), view, j), id_(id)
|
||||
{
|
||||
}
|
||||
|
||||
/** Converting constructor: writable → read-only. */
|
||||
template <WritableView OtherViewT>
|
||||
AccountRoot(AccountRoot<OtherViewT> const& other)
|
||||
requires(!kIsWritable)
|
||||
: SLEBase<ViewT>(other), id_(other.id())
|
||||
{
|
||||
}
|
||||
|
||||
/** Create an AccountRoot backed by a brand-new SLE.
|
||||
*/
|
||||
[[nodiscard]] static AccountRoot
|
||||
makeNew(
|
||||
AccountID const& id,
|
||||
ApplyView& view,
|
||||
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
|
||||
requires kIsWritable
|
||||
{
|
||||
return AccountRoot(id, view, j, std::make_shared<SLE>(keylet::account(id)));
|
||||
}
|
||||
|
||||
[[nodiscard]] AccountID const&
|
||||
id() const
|
||||
{
|
||||
return id_;
|
||||
}
|
||||
|
||||
// --- Read-only domain methods (available on both specializations) ---
|
||||
|
||||
/** Check if the issuer has the global freeze flag set.
|
||||
@return true if the account has global freeze set
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
isGlobalFrozen() const;
|
||||
|
||||
/** Returns IOU issuer transfer fee as Rate. Rate specifies
|
||||
* the fee as fractions of 1 billion. For example, 1% transfer rate
|
||||
* is represented as 1,010,000,000.
|
||||
*/
|
||||
[[nodiscard]] Rate
|
||||
transferRate() const;
|
||||
|
||||
// Calculate liquid XRP balance for an account.
|
||||
// This function may be used to calculate the amount of XRP that
|
||||
// the holder is able to freely spend. It subtracts reserve requirements.
|
||||
//
|
||||
// ownerCountAdj adjusts the owner count in case the caller calculates
|
||||
// before ledger entries are added or removed. Positive to add, negative
|
||||
// to subtract.
|
||||
//
|
||||
// @param ownerCountAdj positive to add to count, negative to reduce count.
|
||||
[[nodiscard]] XRPAmount
|
||||
xrpLiquid(std::int32_t ownerCountAdj) const;
|
||||
|
||||
/** Checks the destination and tag.
|
||||
|
||||
- Checks that the SLE is not null.
|
||||
- If the SLE requires a destination tag, checks that there is a tag.
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
checkDestinationAndTag(bool hasDestinationTag) const;
|
||||
|
||||
/** Returns true if and only if sleAcct is a pseudo-account or specific
|
||||
pseudo-accounts in pseudoFieldFilter.
|
||||
|
||||
Returns false if sleAcct is:
|
||||
- NOT a pseudo-account OR
|
||||
- NOT a ltACCOUNT_ROOT OR
|
||||
- null pointer
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
isPseudoAccount(std::set<SField const*> const& pseudoFieldFilter = {}) const;
|
||||
|
||||
[[nodiscard]] bool
|
||||
operator==(AccountRoot const& other) const
|
||||
{
|
||||
return id_ == other.id_;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
operator==(AccountID const& other) const
|
||||
{
|
||||
return id_ == other;
|
||||
}
|
||||
|
||||
// --- Write-only domain methods (compile-time gated) ---
|
||||
|
||||
/** Adjust the owner count up or down. */
|
||||
void
|
||||
adjustOwnerCount(std::int32_t amount)
|
||||
requires kIsWritable;
|
||||
|
||||
private:
|
||||
// Private constructor only used by `makeNew`
|
||||
AccountRoot(AccountID const& id, ApplyView& view, beast::Journal j, std::shared_ptr<SLE> sle)
|
||||
requires kIsWritable
|
||||
: SLEBase<ViewT>(std::move(sle), view, j), id_(id)
|
||||
{
|
||||
this->insert();
|
||||
}
|
||||
};
|
||||
|
||||
// CTAD deduction guide — bare AccountRoot(id, view) always deduces read-only.
|
||||
// For writable access, use WAccountRoot(id, applyView) explicitly.
|
||||
AccountRoot(AccountID const&, ReadView const&) -> AccountRoot<ReadView>;
|
||||
AccountRoot(AccountID const&, ReadView const&, beast::Journal) -> AccountRoot<ReadView>;
|
||||
|
||||
// Backward-compatible aliases
|
||||
using RAccountRoot = AccountRoot<ReadView>;
|
||||
using WAccountRoot = AccountRoot<ApplyView>;
|
||||
|
||||
// Explicit instantiation declarations (definitions in .cpp)
|
||||
extern template class AccountRoot<ReadView>;
|
||||
extern template class AccountRoot<ApplyView>;
|
||||
|
||||
/** Generate a pseudo-account address from a pseudo owner key.
|
||||
@param pseudoOwnerKey The key to generate the address from
|
||||
@return The generated account ID
|
||||
*/
|
||||
AccountID
|
||||
[[nodiscard]] AccountID
|
||||
pseudoAccountAddress(ReadView const& view, uint256 const& pseudoOwnerKey);
|
||||
|
||||
/** Returns the list of fields that define an ACCOUNT_ROOT as a pseudo-account
|
||||
@@ -67,29 +183,6 @@ pseudoAccountAddress(ReadView const& view, uint256 const& pseudoOwnerKey);
|
||||
[[nodiscard]] std::vector<SField const*> const&
|
||||
getPseudoAccountFields();
|
||||
|
||||
/** Returns true if and only if sleAcct is a pseudo-account or specific
|
||||
pseudo-accounts in pseudoFieldFilter.
|
||||
|
||||
Returns false if sleAcct is:
|
||||
- NOT a pseudo-account OR
|
||||
- NOT a ltACCOUNT_ROOT OR
|
||||
- null pointer
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
isPseudoAccount(
|
||||
std::shared_ptr<SLE const> sleAcct,
|
||||
std::set<SField const*> const& pseudoFieldFilter = {});
|
||||
|
||||
/** Convenience overload that reads the account from the view. */
|
||||
[[nodiscard]] inline bool
|
||||
isPseudoAccount(
|
||||
ReadView const& view,
|
||||
AccountID const& accountId,
|
||||
std::set<SField const*> const& pseudoFieldFilter = {})
|
||||
{
|
||||
return isPseudoAccount(view.read(keylet::account(accountId)), pseudoFieldFilter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create pseudo-account, storing pseudoOwnerKey into ownerField.
|
||||
*
|
||||
@@ -101,12 +194,4 @@ isPseudoAccount(
|
||||
[[nodiscard]] Expected<std::shared_ptr<SLE>, TER>
|
||||
createPseudoAccount(ApplyView& view, uint256 const& pseudoOwnerKey, SField const& ownerField);
|
||||
|
||||
/** Checks the destination and tag.
|
||||
|
||||
- Checks that the SLE is not null.
|
||||
- If the SLE requires a destination tag, checks that there is a tag.
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
checkDestinationAndTag(SLE::const_ref toSle, bool hasDestinationTag);
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/STArray.h>
|
||||
#include <xrpl/protocol/STTx.h>
|
||||
@@ -69,8 +70,7 @@ verifyDepositPreauth(
|
||||
STTx const& tx,
|
||||
ApplyView& view,
|
||||
AccountID const& src,
|
||||
AccountID const& dst,
|
||||
std::shared_ptr<SLE const> const& sleDst,
|
||||
RAccountRoot const& dst,
|
||||
beast::Journal j);
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
#include <xrpl/protocol/MPTAmount.h>
|
||||
#include <xrpl/protocol/Rate.h>
|
||||
|
||||
#include <variant>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
template <ValidIssueType T>
|
||||
@@ -18,7 +20,7 @@ TER
|
||||
escrowUnlockApplyHelper(
|
||||
ApplyView& view,
|
||||
Rate lockedRate,
|
||||
std::shared_ptr<SLE> const& sleDest,
|
||||
std::variant<std::shared_ptr<SLE>, WAccountRoot> dest,
|
||||
STAmount const& xrpBalance,
|
||||
STAmount const& amount,
|
||||
AccountID const& issuer,
|
||||
@@ -32,7 +34,7 @@ inline TER
|
||||
escrowUnlockApplyHelper<Issue>(
|
||||
ApplyView& view,
|
||||
Rate lockedRate,
|
||||
std::shared_ptr<SLE> const& sleDest,
|
||||
std::variant<std::shared_ptr<SLE>, WAccountRoot> dest,
|
||||
STAmount const& xrpBalance,
|
||||
STAmount const& amount,
|
||||
AccountID const& issuer,
|
||||
@@ -55,8 +57,14 @@ escrowUnlockApplyHelper<Issue>(
|
||||
|
||||
if (!view.exists(trustLineKey) && createAsset)
|
||||
{
|
||||
// For backwards compatibility: if dest is not WAccountRoot, return error
|
||||
if (!std::holds_alternative<WAccountRoot>(dest))
|
||||
return tefEXCEPTION;
|
||||
|
||||
auto& wrappedDest = std::get<WAccountRoot>(dest);
|
||||
|
||||
// Can the account cover the trust line's reserve?
|
||||
if (std::uint32_t const ownerCount = {sleDest->at(sfOwnerCount)};
|
||||
if (std::uint32_t const ownerCount = {wrappedDest->at(sfOwnerCount)};
|
||||
xrpBalance < view.fees().accountReserve(ownerCount + 1))
|
||||
{
|
||||
JLOG(journal.trace()) << "Trust line does not exist. "
|
||||
@@ -69,28 +77,28 @@ escrowUnlockApplyHelper<Issue>(
|
||||
STAmount initialBalance(issue);
|
||||
initialBalance.get<Issue>().account = noAccount();
|
||||
|
||||
if (TER const ter = trustCreate(
|
||||
view, // payment sandbox
|
||||
recvLow, // is dest low?
|
||||
issuer, // source
|
||||
receiver, // destination
|
||||
trustLineKey.key, // ledger index
|
||||
sleDest, // Account to add to
|
||||
false, // authorize account
|
||||
!sleDest->isFlag(lsfDefaultRipple), //
|
||||
false, // freeze trust line
|
||||
false, // deep freeze trust line
|
||||
initialBalance, // zero initial balance
|
||||
Issue(currency, receiver), // limit of zero
|
||||
0, // quality in
|
||||
0, // quality out
|
||||
journal); // journal
|
||||
if (TER const ter = WIOUIssuance(view, issue, journal)
|
||||
.trustCreate(
|
||||
recvLow, // is dest low?
|
||||
issuer, // source
|
||||
receiver, // destination
|
||||
trustLineKey.key, // ledger index
|
||||
wrappedDest, // Account to add to
|
||||
false, // authorize account
|
||||
!wrappedDest->isFlag(lsfDefaultRipple),
|
||||
false, // freeze trust line
|
||||
false, // deep freeze trust line
|
||||
initialBalance, // zero initial balance
|
||||
Issue(currency, receiver), // limit of zero
|
||||
0, // quality in
|
||||
0, // quality out
|
||||
journal); // journal
|
||||
!isTesSuccess(ter))
|
||||
{
|
||||
return ter; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
view.update(sleDest);
|
||||
wrappedDest.update();
|
||||
}
|
||||
|
||||
if (!view.exists(trustLineKey) && !receiverIssuer)
|
||||
@@ -162,7 +170,7 @@ inline TER
|
||||
escrowUnlockApplyHelper<MPTIssue>(
|
||||
ApplyView& view,
|
||||
Rate lockedRate,
|
||||
std::shared_ptr<SLE> const& sleDest,
|
||||
std::variant<std::shared_ptr<SLE>, WAccountRoot> dest,
|
||||
STAmount const& xrpBalance,
|
||||
STAmount const& amount,
|
||||
AccountID const& issuer,
|
||||
@@ -178,19 +186,26 @@ escrowUnlockApplyHelper<MPTIssue>(
|
||||
auto const issuanceKey = keylet::mptIssuance(mptID);
|
||||
if (!view.exists(keylet::mptoken(issuanceKey.key, receiver)) && createAsset && !receiverIssuer)
|
||||
{
|
||||
if (std::uint32_t const ownerCount = {sleDest->at(sfOwnerCount)};
|
||||
// For backwards compatibility: if dest is not WAccountRoot, return error
|
||||
if (!std::holds_alternative<WAccountRoot>(dest))
|
||||
return tefEXCEPTION;
|
||||
|
||||
auto& wrappedDest = std::get<WAccountRoot>(dest);
|
||||
|
||||
if (std::uint32_t const ownerCount = {wrappedDest->at(sfOwnerCount)};
|
||||
xrpBalance < view.fees().accountReserve(ownerCount + 1))
|
||||
{
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
}
|
||||
|
||||
if (auto const ter = createMPToken(view, mptID, receiver, 0); !isTesSuccess(ter))
|
||||
if (auto const ter = WMPTokenIssuance(view, mptID, journal).createMPToken(receiver, 0);
|
||||
!isTesSuccess(ter))
|
||||
{
|
||||
return ter; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
// update owner count.
|
||||
adjustOwnerCount(view, sleDest, 1, journal);
|
||||
wrappedDest.adjustOwnerCount(1);
|
||||
}
|
||||
|
||||
if (!view.exists(keylet::mptoken(issuanceKey.key, receiver)) && !receiverIssuer)
|
||||
@@ -218,13 +233,13 @@ escrowUnlockApplyHelper<MPTIssue>(
|
||||
// compute balance to transfer
|
||||
finalAmt = amount.value() - xferFee;
|
||||
}
|
||||
return unlockEscrowMPT(
|
||||
view,
|
||||
sender,
|
||||
receiver,
|
||||
finalAmt,
|
||||
view.rules().enabled(fixTokenEscrowV1) ? amount : finalAmt,
|
||||
journal);
|
||||
return WMPTokenIssuance(view, amount.get<MPTIssue>(), journal)
|
||||
.unlockEscrow(
|
||||
sender,
|
||||
receiver,
|
||||
finalAmt,
|
||||
view.rules().enabled(fixTokenEscrowV1) ? amount : finalAmt,
|
||||
journal);
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -487,4 +487,15 @@ loanMakePayment(
|
||||
LoanPaymentType const paymentType,
|
||||
beast::Journal j);
|
||||
|
||||
// LoanBroker-specific `adjustOwnerCount` function (temporary, while the Wrapped classes are WIP)
|
||||
// Assert will check the type, so that we ensure it's not used by anything else
|
||||
// Order of parameters is different from the old `adjustOwnerCount` function to avoid anything
|
||||
// accidentally calling this with the wrong type.
|
||||
void
|
||||
adjustOwnerCount(
|
||||
std::shared_ptr<SLE> const& sle,
|
||||
ApplyView& view,
|
||||
std::int32_t amount,
|
||||
beast::Journal j);
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -3,117 +3,297 @@
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/helpers/SLEBase.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/MPTIssue.h>
|
||||
#include <xrpl/protocol/Rate.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
|
||||
#include <initializer_list>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Freeze checking (MPT-specific)
|
||||
// MPTokenIssuance ledger entry wrapper (view-parameterized)
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
[[nodiscard]] bool
|
||||
isGlobalFrozen(ReadView const& view, MPTIssue const& mptIssue);
|
||||
|
||||
[[nodiscard]] bool
|
||||
isIndividualFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue);
|
||||
|
||||
[[nodiscard]] bool
|
||||
isFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue, int depth = 0);
|
||||
|
||||
[[nodiscard]] bool
|
||||
isAnyFrozen(
|
||||
ReadView const& view,
|
||||
std::initializer_list<AccountID> const& accounts,
|
||||
MPTIssue const& mptIssue,
|
||||
int depth = 0);
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Transfer rate (MPT-specific)
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** Returns MPT transfer fee as Rate. Rate specifies
|
||||
* the fee as fractions of 1 billion. For example, 1% transfer rate
|
||||
* is represented as 1,010,000,000.
|
||||
* @param issuanceID MPTokenIssuanceID of MPTTokenIssuance object
|
||||
*/
|
||||
[[nodiscard]] Rate
|
||||
transferRate(ReadView const& view, MPTID const& issuanceID);
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Holding checks (MPT-specific)
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
[[nodiscard]] TER
|
||||
canAddHolding(ReadView const& view, MPTIssue const& mptIssue);
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Authorization (MPT-specific)
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
[[nodiscard]] TER
|
||||
authorizeMPToken(
|
||||
ApplyView& view,
|
||||
XRPAmount const& priorBalance,
|
||||
MPTID const& mptIssuanceID,
|
||||
AccountID const& account,
|
||||
beast::Journal journal,
|
||||
std::uint32_t flags = 0,
|
||||
std::optional<AccountID> holderID = std::nullopt);
|
||||
|
||||
/** Check if the account lacks required authorization for MPT.
|
||||
/**
|
||||
* View-parameterized wrapper for MPTokenIssuance ledger entries.
|
||||
*
|
||||
* requireAuth check is recursive for MPT shares in a vault, descending to
|
||||
* assets in the vault, up to maxAssetCheckDepth recursion depth. This is
|
||||
* purely defensive, as we currently do not allow such vaults to be created.
|
||||
* WeakAuth intentionally allows missing MPTokens under MPToken V2.
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
requireAuth(
|
||||
ReadView const& view,
|
||||
MPTIssue const& mptIssue,
|
||||
AccountID const& account,
|
||||
AuthType authType = AuthType::Legacy,
|
||||
int depth = 0);
|
||||
|
||||
/** Enforce account has MPToken to match its authorization.
|
||||
* MPTokenIssuance<ReadView> — read-only access to issuance data
|
||||
* MPTokenIssuance<ApplyView> — read-write access, with insert/update/erase
|
||||
* and domain-specific write methods
|
||||
*
|
||||
* Called from doApply - it will check for expired (and delete if found any)
|
||||
* credentials matching DomainID set in MPTokenIssuance. Must be called if
|
||||
* requireAuth(...MPTIssue...) returned tesSUCCESS or tecEXPIRED in preclaim.
|
||||
* Carries the MPTID and MPTIssue alongside the SLE so callers don't need to
|
||||
* thread them through every method call.
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
enforceMPTokenAuthorization(
|
||||
ApplyView& view,
|
||||
MPTID const& mptIssuanceID,
|
||||
AccountID const& account,
|
||||
XRPAmount const& priorBalance,
|
||||
beast::Journal j);
|
||||
template <typename ViewT>
|
||||
class MPTokenIssuance : public SLEBase<ViewT>, public TokenBase<ViewT>
|
||||
{
|
||||
static constexpr bool kIsWritable = SLEBase<ViewT>::kIsWritable;
|
||||
|
||||
/** Check if the destination account is allowed
|
||||
* to receive MPT. Return tecNO_AUTH if it doesn't
|
||||
* and tesSUCCESS otherwise.
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
canTransfer(
|
||||
ReadView const& view,
|
||||
MPTIssue const& mptIssue,
|
||||
AccountID const& from,
|
||||
AccountID const& to);
|
||||
MPTID const mptID_;
|
||||
MPTIssue const mptIssue_;
|
||||
|
||||
public:
|
||||
/** Constructor for read-only context (MPTIssue) */
|
||||
MPTokenIssuance(
|
||||
ReadView const& view,
|
||||
MPTIssue const& mptIssue,
|
||||
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
|
||||
requires(!kIsWritable)
|
||||
: SLEBase<ViewT>(view.read(keylet::mptIssuance(mptIssue.getMptID())), view, j)
|
||||
, mptID_(mptIssue.getMptID())
|
||||
, mptIssue_(mptIssue)
|
||||
{
|
||||
}
|
||||
|
||||
/** Constructor for read-only context (MPTID) */
|
||||
MPTokenIssuance(
|
||||
ReadView const& view,
|
||||
MPTID const& mptID,
|
||||
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
|
||||
requires(!kIsWritable)
|
||||
: SLEBase<ViewT>(view.read(keylet::mptIssuance(mptID)), view, j)
|
||||
, mptID_(mptID)
|
||||
, mptIssue_(MPTIssue(mptID))
|
||||
{
|
||||
}
|
||||
|
||||
/** Constructor for writable context (MPTIssue) */
|
||||
MPTokenIssuance(
|
||||
ApplyView& view,
|
||||
MPTIssue const& mptIssue,
|
||||
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
|
||||
requires kIsWritable
|
||||
: SLEBase<ViewT>(keylet::mptIssuance(mptIssue.getMptID()), view, j)
|
||||
, mptID_(mptIssue.getMptID())
|
||||
, mptIssue_(mptIssue)
|
||||
{
|
||||
}
|
||||
|
||||
/** Constructor for writable context (MPTID) */
|
||||
MPTokenIssuance(
|
||||
ApplyView& view,
|
||||
MPTID const& mptID,
|
||||
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
|
||||
requires kIsWritable
|
||||
: SLEBase<ViewT>(keylet::mptIssuance(mptID), view, j)
|
||||
, mptID_(mptID)
|
||||
, mptIssue_(MPTIssue(mptID))
|
||||
{
|
||||
}
|
||||
|
||||
/** Converting constructor: writable → read-only. */
|
||||
template <WritableView OtherViewT>
|
||||
MPTokenIssuance(MPTokenIssuance<OtherViewT> const& other)
|
||||
requires(!kIsWritable)
|
||||
: SLEBase<ViewT>(other), mptID_(other.getMptID()), mptIssue_(other.getMptIssue())
|
||||
{
|
||||
}
|
||||
|
||||
/** Create an MPTokenIssuance backed by a brand-new SLE (already inserted into
|
||||
* the view).
|
||||
*/
|
||||
[[nodiscard]] static MPTokenIssuance
|
||||
makeNew(
|
||||
MPTID const& mptID,
|
||||
ApplyView& view,
|
||||
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
|
||||
requires kIsWritable
|
||||
{
|
||||
return MPTokenIssuance(mptID, view, j, std::make_shared<SLE>(keylet::mptIssuance(mptID)));
|
||||
}
|
||||
|
||||
[[nodiscard]] static MPTokenIssuance
|
||||
makeNew(
|
||||
std::uint32_t const seq,
|
||||
AccountID const& issuer,
|
||||
ApplyView& view,
|
||||
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
|
||||
requires kIsWritable
|
||||
{
|
||||
auto const mptID = makeMptID(seq, issuer);
|
||||
return MPTokenIssuance(mptID, view, j, std::make_shared<SLE>(keylet::mptIssuance(mptID)));
|
||||
}
|
||||
|
||||
// --- Inline accessors ---
|
||||
|
||||
[[nodiscard]] MPTID const&
|
||||
getMptID() const
|
||||
{
|
||||
return mptID_;
|
||||
}
|
||||
|
||||
[[nodiscard]] MPTIssue const&
|
||||
getMptIssue() const
|
||||
{
|
||||
return mptIssue_;
|
||||
}
|
||||
|
||||
[[nodiscard]] AccountID const&
|
||||
getIssuer() const
|
||||
{
|
||||
return mptIssue_.getIssuer();
|
||||
}
|
||||
|
||||
// --- Read-only domain methods (available on both specializations) ---
|
||||
|
||||
[[nodiscard]] bool
|
||||
isGlobalFrozen() const override;
|
||||
|
||||
[[nodiscard]] bool
|
||||
isIndividualFrozen(AccountID const& account) const override;
|
||||
|
||||
/**
|
||||
* isFrozen check is recursive for MPT shares in a vault, descending to
|
||||
* assets in the vault, up to kMaxAssetCheckDepth recursion depth. This is
|
||||
* purely defensive, as we currently do not allow such vaults to be created.
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
isFrozen(AccountID const& account, int depth = 0) const override;
|
||||
|
||||
[[nodiscard]] bool
|
||||
isAnyFrozen(std::initializer_list<AccountID> const& accounts, int depth = 0) const;
|
||||
|
||||
/** Returns MPT transfer fee as Rate. */
|
||||
[[nodiscard]] Rate
|
||||
transferRate() const override;
|
||||
|
||||
[[nodiscard]] TER
|
||||
canAddHolding() const;
|
||||
|
||||
/** Check if the account lacks required authorization for MPT. */
|
||||
[[nodiscard]] TER
|
||||
requireAuth(AccountID const& account, AuthType authType = AuthType::Legacy, int depth = 0)
|
||||
const override;
|
||||
|
||||
/** Check if `from` is allowed to send MPT to `to`. */
|
||||
[[nodiscard]] TER
|
||||
canTransfer(AccountID const& from, AccountID const& to) const override;
|
||||
|
||||
/** Check if the token requires authorization for holders. */
|
||||
[[nodiscard]] bool
|
||||
requiresAuth() const override;
|
||||
|
||||
/** Check if the token issuer has enabled clawback capability. */
|
||||
[[nodiscard]] bool
|
||||
canClawback() const override;
|
||||
|
||||
// --- Write-only domain methods (compile-time gated) ---
|
||||
|
||||
[[nodiscard]] TER
|
||||
addEmptyHolding(AccountID const& accountID, XRPAmount priorBalance, beast::Journal journal)
|
||||
requires kIsWritable;
|
||||
|
||||
[[nodiscard]] TER
|
||||
removeEmptyHolding(AccountID const& accountID, beast::Journal journal)
|
||||
requires kIsWritable;
|
||||
|
||||
[[nodiscard]] TER
|
||||
authorizeMPToken(
|
||||
XRPAmount const& priorBalance,
|
||||
AccountID const& account,
|
||||
beast::Journal journal,
|
||||
std::uint32_t flags = 0,
|
||||
std::optional<AccountID> holderID = std::nullopt)
|
||||
requires kIsWritable;
|
||||
|
||||
/** Enforce account has MPToken to match its authorization.
|
||||
*
|
||||
* Called from doApply - it will check for expired (and delete if found any)
|
||||
* credentials matching DomainID set in MPTokenIssuance. Must be called if
|
||||
* requireAuth(...) returned tesSUCCESS or tecEXPIRED in preclaim.
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
enforceMPTokenAuthorization(
|
||||
AccountID const& account,
|
||||
XRPAmount const& priorBalance,
|
||||
beast::Journal j)
|
||||
requires kIsWritable;
|
||||
|
||||
// --- Amount accessors ---
|
||||
|
||||
/** Maximum amount allowed for this issuance (sfMaximumAmount or default). */
|
||||
[[nodiscard]] std::int64_t
|
||||
maxAmount() const;
|
||||
|
||||
/** Available amount = maxAmount - OutstandingAmount.
|
||||
* Throws if the issuance SLE does not exist.
|
||||
*/
|
||||
[[nodiscard]] std::int64_t
|
||||
availableAmount() const;
|
||||
|
||||
/** Funds available for the issuer to sell in an issuer-owned offer. */
|
||||
[[nodiscard]] STAmount
|
||||
issuerFundsToSelfIssue() const;
|
||||
|
||||
// --- Holder MPToken management (writable) ---
|
||||
|
||||
/** Create a holder-side MPToken for `account` under this issuance. */
|
||||
[[nodiscard]] TER
|
||||
createMPToken(AccountID const& account, std::uint32_t flags)
|
||||
requires kIsWritable;
|
||||
|
||||
/** Ensure `holder` has an MPToken under this issuance, creating one if needed. */
|
||||
[[nodiscard]] TER
|
||||
checkCreateMPT(AccountID const& holder, beast::Journal j)
|
||||
requires kIsWritable;
|
||||
|
||||
/** Notify the view that the issuer self-debited `amount`. */
|
||||
void
|
||||
issuerSelfDebitHook(std::uint64_t amount)
|
||||
requires kIsWritable;
|
||||
|
||||
/** Lock MPT for escrow from `sender`. The amount must match this issuance. */
|
||||
[[nodiscard]] TER
|
||||
lockEscrow(AccountID const& sender, STAmount const& amount, beast::Journal j)
|
||||
requires kIsWritable;
|
||||
|
||||
/** Unlock previously-escrowed MPT, transferring `netAmount` to `receiver`,
|
||||
* with `grossAmount - netAmount` accounted as a transfer fee against
|
||||
* OutstandingAmount.
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
unlockEscrow(
|
||||
AccountID const& sender,
|
||||
AccountID const& receiver,
|
||||
STAmount const& netAmount,
|
||||
STAmount const& grossAmount,
|
||||
beast::Journal j)
|
||||
requires kIsWritable;
|
||||
|
||||
private:
|
||||
// Private constructor only used by `makeNew`.
|
||||
MPTokenIssuance(MPTID const& mptID, ApplyView& view, beast::Journal j, std::shared_ptr<SLE> sle)
|
||||
requires kIsWritable
|
||||
: SLEBase<ViewT>(std::move(sle), view, j), mptID_(mptID), mptIssue_(MPTIssue(mptID))
|
||||
{
|
||||
this->insert();
|
||||
}
|
||||
};
|
||||
|
||||
// CTAD deduction guides — bare MPTokenIssuance(view, ...) always deduces read-only.
|
||||
// For writable access, use WMPTokenIssuance(view, ...) explicitly.
|
||||
MPTokenIssuance(ReadView const&, MPTIssue const&) -> MPTokenIssuance<ReadView>;
|
||||
MPTokenIssuance(ReadView const&, MPTIssue const&, beast::Journal) -> MPTokenIssuance<ReadView>;
|
||||
MPTokenIssuance(ReadView const&, MPTID const&) -> MPTokenIssuance<ReadView>;
|
||||
MPTokenIssuance(ReadView const&, MPTID const&, beast::Journal) -> MPTokenIssuance<ReadView>;
|
||||
|
||||
// Backward-compatible aliases
|
||||
using RMPTokenIssuance = MPTokenIssuance<ReadView>;
|
||||
using WMPTokenIssuance = MPTokenIssuance<ApplyView>;
|
||||
|
||||
// Explicit instantiation declarations (definitions in .cpp)
|
||||
extern template class MPTokenIssuance<ReadView>;
|
||||
extern template class MPTokenIssuance<ApplyView>;
|
||||
|
||||
/** Check if Asset can be traded on DEX. return tecNO_PERMISSION
|
||||
* if it doesn't and tesSUCCESS otherwise.
|
||||
@@ -121,63 +301,6 @@ canTransfer(
|
||||
[[nodiscard]] TER
|
||||
canTrade(ReadView const& view, Asset const& asset);
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Empty holding operations (MPT-specific)
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
[[nodiscard]] TER
|
||||
addEmptyHolding(
|
||||
ApplyView& view,
|
||||
AccountID const& accountID,
|
||||
XRPAmount priorBalance,
|
||||
MPTIssue const& mptIssue,
|
||||
beast::Journal journal);
|
||||
|
||||
[[nodiscard]] TER
|
||||
removeEmptyHolding(
|
||||
ApplyView& view,
|
||||
AccountID const& accountID,
|
||||
MPTIssue const& mptIssue,
|
||||
beast::Journal journal);
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Escrow operations (MPT-specific)
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
TER
|
||||
lockEscrowMPT(
|
||||
ApplyView& view,
|
||||
AccountID const& uGrantorID,
|
||||
STAmount const& saAmount,
|
||||
beast::Journal j);
|
||||
|
||||
TER
|
||||
unlockEscrowMPT(
|
||||
ApplyView& view,
|
||||
AccountID const& uGrantorID,
|
||||
AccountID const& uGranteeID,
|
||||
STAmount const& netAmount,
|
||||
STAmount const& grossAmount,
|
||||
beast::Journal j);
|
||||
|
||||
TER
|
||||
createMPToken(
|
||||
ApplyView& view,
|
||||
MPTID const& mptIssuanceID,
|
||||
AccountID const& account,
|
||||
std::uint32_t const flags);
|
||||
|
||||
TER
|
||||
checkCreateMPT(
|
||||
xrpl::ApplyView& view,
|
||||
xrpl::MPTIssue const& mptIssue,
|
||||
xrpl::AccountID const& holder,
|
||||
beast::Journal j);
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// MPT Overflow related
|
||||
@@ -193,9 +316,6 @@ maxMPTAmount(SLE const& sleIssuance);
|
||||
std::int64_t
|
||||
availableMPTAmount(SLE const& sleIssuance);
|
||||
|
||||
std::int64_t
|
||||
availableMPTAmount(ReadView const& view, MPTID const& mptID);
|
||||
|
||||
/** Checks for two types of OutstandingAmount overflow during a send operation.
|
||||
* 1. **Direct directSendNoFee (Overflow: No):** A true overflow check when
|
||||
* `OutstandingAmount > MaximumAmount`. This threshold is used for direct
|
||||
@@ -211,21 +331,15 @@ isMPTOverflow(
|
||||
std::int64_t maximumAmount,
|
||||
AllowMPTOverflow allowOverflow);
|
||||
|
||||
/**
|
||||
* Determine funds available for an issuer to sell in an issuer owned offer.
|
||||
* Issuing step, which could be either MPTEndPointStep last step or BookStep's
|
||||
* TakerPays may overflow OutstandingAmount. Redeeming step, in BookStep's
|
||||
* TakerGets redeems the offer's owner funds, essentially balancing out
|
||||
* the overflow, unless the offer's owner is the issuer.
|
||||
/** Delete AMMs MPToken. The passed `sle` must be obtained from a prior
|
||||
* call to view.peek().
|
||||
*/
|
||||
[[nodiscard]] STAmount
|
||||
issuerFundsToSelfIssue(ReadView const& view, MPTIssue const& issue);
|
||||
|
||||
/** Facilitate tracking of MPT sold by an issuer owning MPT sell offer.
|
||||
* See ApplyView::issuerSelfDebitHookMPT().
|
||||
*/
|
||||
void
|
||||
issuerSelfDebitHookMPT(ApplyView& view, MPTIssue const& issue, std::uint64_t amount);
|
||||
[[nodiscard]] TER
|
||||
deleteAMMMPToken(
|
||||
ApplyView& view,
|
||||
std::shared_ptr<SLE> sleMPT,
|
||||
AccountID const& ammAccountID,
|
||||
beast::Journal j);
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
|
||||
53
include/xrpl/ledger/helpers/README.md
Normal file
53
include/xrpl/ledger/helpers/README.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# Ledger Entry Helpers (`helpers/`)
|
||||
|
||||
## Overview
|
||||
|
||||
This folder contains helper classes and free functions for working with **Serialized Ledger Entries (SLEs)**. Its centerpiece is `SLEBase.h`, which defines the template class `SLEBase<ViewT>` — a type-safe, context-aware wrapper around the raw `std::shared_ptr<SLE>` used throughout the rest of the codebase.
|
||||
|
||||
## The Problem: Untyped SLE Access
|
||||
|
||||
Historically, ledger entries are passed around as bare `std::shared_ptr<SLE>` (or `std::shared_ptr<SLE const>`). This has several drawbacks:
|
||||
|
||||
1. **No compile-time entry-type safety.** Any code holding an `std::shared_ptr<SLE>` can read or write _any_ field on _any_ ledger entry type. Nothing prevents you from calling `sle->getFieldU32(sfOwnerCount)` on an Offer SLE, even though Offers don't have that field.
|
||||
|
||||
2. **No read/write distinction.** A function that only needs to _read_ an entry still receives a mutable `std::shared_ptr<SLE>`, making it easy to accidentally mutate state. Conversely, a function that _must_ write has no way to express that requirement in its signature.
|
||||
|
||||
3. **No association with the view.** The SLE and the `ReadView` / `ApplyView` it came from travel as separate arguments, so callers must manually keep them in sync and remember to call `view.update(sle)` after mutations.
|
||||
|
||||
## The Solution: `SLEBase.h`
|
||||
|
||||
`SLEBase.h` introduces a single template class `SLEBase<ViewT>` that pairs an SLE with its view context and enforces read/write semantics at compile time via `requires` clauses.
|
||||
|
||||
**`SLEBase<ReadView>`** holds a `std::shared_ptr<SLE const>` and a `ReadView const&`. Write-only members are excluded at compile time.
|
||||
holds a mutable `std::shared_ptr<SLE>`, an `ApplyView&`, and a `Keylet`. It exposes `insert()`, `update()`, `erase()`, and `newSLE()` to keep the SLE and its view in sync automatically.
|
||||
|
||||
A converting constructor allows implicit conversion from `SLEBase<ApplyView>` to `SLEBase<ReadView>`, so functions taking a read-only wrapper can accept a writable one without a cast.
|
||||
|
||||
### Template Pattern
|
||||
|
||||
Each entry type is a single template class parameterized on the view type:
|
||||
|
||||
```
|
||||
AccountRoot<ReadView> — read-only: RAccountRoot
|
||||
AccountRoot<ApplyView> — writable: WAccountRoot
|
||||
```
|
||||
|
||||
Both specializations share all domain read methods. Write methods on `WAccountRoot` are gated with `requires is_writable` so they are unavailable on `RAccountRoot` at compile time. The `R`/`W` prefix aliases (`RAccountRoot`, `WAccountRoot`) are provided for convenience and backward compatibility.
|
||||
|
||||
## Files in This Directory
|
||||
|
||||
| File | Description |
|
||||
| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `SLEBase.h` | Template base class `SLEBase<ViewT>` for read-only and writable SLE wrappers |
|
||||
| `AccountRootHelpers.h` | `AccountRoot<ViewT>` wrapper (`RAccountRoot`, `WAccountRoot`) and free functions for pseudo-accounts |
|
||||
| `CredentialHelpers.h` | Free functions for Credential ledger entries |
|
||||
| `DirectoryHelpers.h` | Free functions for directory traversal (`dirFirst`, `dirNext`, `forEachItem`, etc.) |
|
||||
| `MPTokenHelpers.h` | Free functions for MPToken ledger entries and MPTokenIssuance<ViewT> wrapper (`RMPTokenIssuance`, `WMPTokenIssuance`) functions for MPTokenIssuance objects |
|
||||
| `OfferHelpers.h` | Free function `offerDelete` for removing Offer entries |
|
||||
| `RippleStateHelpers.h` | Free functions for RippleState (trust line) entries: credit, freeze, issuance, authorization |
|
||||
| `TokenHelpers.h` | Shared token helpers (freeze/auth checks used by both IOU and MPT paths) |
|
||||
| `VaultHelpers.h` | Free functions for Vault ledger entries |
|
||||
|
||||
## Migration Status
|
||||
|
||||
This migration is still in progress. New code should prefer the wrapper style where possible; existing free functions will be migrated incrementally.
|
||||
@@ -3,12 +3,14 @@
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/IOUAmount.h>
|
||||
#include <xrpl/protocol/Issue.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/UintTypes.h>
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
@@ -20,137 +22,218 @@ namespace xrpl {
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Credit functions (from Credit.h)
|
||||
// IOUIssuance ledger entry wrapper (view-parameterized)
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** Calculate the maximum amount of IOUs that an account can hold
|
||||
@param view the ledger to check against.
|
||||
@param account the account of interest.
|
||||
@param issuer the issuer of the IOU.
|
||||
@param currency the IOU to check.
|
||||
@return The maximum amount that can be held.
|
||||
*/
|
||||
/** @{ */
|
||||
STAmount
|
||||
creditLimit(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
AccountID const& issuer,
|
||||
Currency const& currency);
|
||||
/**
|
||||
* View-parameterized wrapper for an IOU issuance.
|
||||
*
|
||||
* Because IOUs have no dedicated ledger entry — issuance properties live on
|
||||
* the issuer's AccountRoot — IOUIssuance<V> inherits from AccountRoot<V> and
|
||||
* adds the currency context plus IOU-specific accessors (RequireAuth /
|
||||
* AllowTrustLineClawback flags, freeze checks against the holder's trust
|
||||
* line, etc.).
|
||||
*
|
||||
* IOUIssuance<ReadView> — read-only
|
||||
* IOUIssuance<ApplyView> — read-write
|
||||
*/
|
||||
template <typename ViewT>
|
||||
class IOUIssuance : public AccountRoot<ViewT>, public TokenBase<ViewT>
|
||||
{
|
||||
static constexpr bool kIsWritable = SLEBase<ViewT>::kIsWritable;
|
||||
|
||||
IOUAmount
|
||||
creditLimit2(ReadView const& v, AccountID const& acc, AccountID const& iss, Currency const& cur);
|
||||
/** @} */
|
||||
Currency const currency_;
|
||||
|
||||
/** Returns the amount of IOUs issued by issuer that are held by an account
|
||||
@param view the ledger to check against.
|
||||
@param account the account of interest.
|
||||
@param issuer the issuer of the IOU.
|
||||
@param currency the IOU to check.
|
||||
*/
|
||||
/** @{ */
|
||||
STAmount
|
||||
creditBalance(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
AccountID const& issuer,
|
||||
Currency const& currency);
|
||||
/** @} */
|
||||
public:
|
||||
/** Constructor for read-only context (Issue). */
|
||||
IOUIssuance(
|
||||
ReadView const& view,
|
||||
Issue const& issue,
|
||||
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
|
||||
requires(!kIsWritable)
|
||||
: AccountRoot<ViewT>(issue.getIssuer(), view, j), currency_(issue.currency)
|
||||
{
|
||||
}
|
||||
|
||||
/** Constructor for read-only context (issuer + currency). */
|
||||
IOUIssuance(
|
||||
ReadView const& view,
|
||||
AccountID const& issuer,
|
||||
Currency const& currency,
|
||||
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
|
||||
requires(!kIsWritable)
|
||||
: AccountRoot<ViewT>(issuer, view, j), currency_(currency)
|
||||
{
|
||||
}
|
||||
|
||||
/** Constructor for writable context (Issue). */
|
||||
IOUIssuance(
|
||||
ApplyView& view,
|
||||
Issue const& issue,
|
||||
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
|
||||
requires kIsWritable
|
||||
: AccountRoot<ViewT>(issue.getIssuer(), view, j), currency_(issue.currency)
|
||||
{
|
||||
}
|
||||
|
||||
/** Constructor for writable context (issuer + currency). */
|
||||
IOUIssuance(
|
||||
ApplyView& view,
|
||||
AccountID const& issuer,
|
||||
Currency const& currency,
|
||||
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
|
||||
requires kIsWritable
|
||||
: AccountRoot<ViewT>(issuer, view, j), currency_(currency)
|
||||
{
|
||||
}
|
||||
|
||||
/** Converting constructor: writable → read-only. */
|
||||
template <WritableView OtherViewT>
|
||||
IOUIssuance(IOUIssuance<OtherViewT> const& other)
|
||||
requires(!kIsWritable)
|
||||
: AccountRoot<ViewT>(other), currency_(other.getCurrency())
|
||||
{
|
||||
}
|
||||
|
||||
[[nodiscard]] Currency const&
|
||||
getCurrency() const
|
||||
{
|
||||
return currency_;
|
||||
}
|
||||
|
||||
[[nodiscard]] Issue
|
||||
getIssue() const
|
||||
{
|
||||
return Issue{currency_, this->id()};
|
||||
}
|
||||
|
||||
// --- IOU-specific domain methods ---
|
||||
|
||||
/** Returns IOU issuer transfer fee as Rate (delegates to AccountRoot). */
|
||||
[[nodiscard]] Rate
|
||||
transferRate() const override
|
||||
{
|
||||
return AccountRoot<ViewT>::transferRate();
|
||||
}
|
||||
|
||||
/** Check if the issuer is globally frozen (delegates to AccountRoot). */
|
||||
[[nodiscard]] bool
|
||||
isGlobalFrozen() const override
|
||||
{
|
||||
return AccountRoot<ViewT>::isGlobalFrozen();
|
||||
}
|
||||
|
||||
/** Check if the issuer requires holder authorization (lsfRequireAuth). */
|
||||
[[nodiscard]] bool
|
||||
requiresAuth() const override;
|
||||
|
||||
/** Check if the issuer has enabled clawback (lsfAllowTrustLineClawback). */
|
||||
[[nodiscard]] bool
|
||||
canClawback() const override;
|
||||
|
||||
/** True if the trust line between `account` and the issuer has the
|
||||
* issuer-side freeze flag set.
|
||||
*/
|
||||
[[nodiscard]] bool
|
||||
isIndividualFrozen(AccountID const& account) const override;
|
||||
|
||||
/** True if the issuance is globally frozen or the trust line is frozen.
|
||||
* `depth` is ignored for IOUs (no vault recursion). */
|
||||
[[nodiscard]] bool
|
||||
isFrozen(AccountID const& account, int depth = 0) const override;
|
||||
|
||||
/** True if the trust line has the issuer-side deep-freeze flag set. */
|
||||
[[nodiscard]] bool
|
||||
isDeepFrozen(AccountID const& account) const;
|
||||
|
||||
/** Check if `account` is allowed to hold this IOU.
|
||||
* `depth` is ignored for IOUs (no vault recursion). */
|
||||
[[nodiscard]] TER
|
||||
requireAuth(AccountID const& account, AuthType authType = AuthType::Legacy, int depth = 0)
|
||||
const override;
|
||||
|
||||
/** Check if `from` is allowed to send this IOU to `to`. */
|
||||
[[nodiscard]] TER
|
||||
canTransfer(AccountID const& from, AccountID const& to) const override;
|
||||
|
||||
// --- Credit / balance ---
|
||||
|
||||
/** Maximum amount of this IOU that `account` can hold. */
|
||||
[[nodiscard]] STAmount
|
||||
creditLimit(AccountID const& account) const;
|
||||
|
||||
[[nodiscard]] IOUAmount
|
||||
creditLimit2(AccountID const& account) const;
|
||||
|
||||
/** Amount of this IOU held by `account`. */
|
||||
[[nodiscard]] STAmount
|
||||
creditBalance(AccountID const& account) const;
|
||||
|
||||
// --- Trust line operations (writable) ---
|
||||
|
||||
/** Create a trust line for this IOU's currency. */
|
||||
[[nodiscard]] TER
|
||||
trustCreate(
|
||||
bool const bSrcHigh,
|
||||
AccountID const& uSrcAccountID,
|
||||
AccountID const& uDstAccountID,
|
||||
uint256 const& uIndex,
|
||||
WAccountRoot& wrappedAcct,
|
||||
bool const bAuth,
|
||||
bool const bNoRipple,
|
||||
bool const bFreeze,
|
||||
bool bDeepFreeze,
|
||||
STAmount const& saBalance,
|
||||
STAmount const& saLimit,
|
||||
std::uint32_t uQualityIn,
|
||||
std::uint32_t uQualityOut,
|
||||
beast::Journal j)
|
||||
requires kIsWritable;
|
||||
|
||||
/** Add an empty trust line for `accountID`. */
|
||||
[[nodiscard]] TER
|
||||
addEmptyHolding(AccountID const& accountID, XRPAmount priorBalance, beast::Journal journal)
|
||||
requires kIsWritable;
|
||||
|
||||
/** Remove an empty trust line for `accountID`. */
|
||||
[[nodiscard]] TER
|
||||
removeEmptyHolding(AccountID const& accountID, beast::Journal journal)
|
||||
requires kIsWritable;
|
||||
|
||||
/** Issue `amount` of this IOU from the issuer to `account`. */
|
||||
[[nodiscard]] TER
|
||||
issue(AccountID const& account, STAmount const& amount, beast::Journal j)
|
||||
requires kIsWritable;
|
||||
|
||||
/** Redeem `amount` of this IOU from `account` back to the issuer. */
|
||||
[[nodiscard]] TER
|
||||
redeem(AccountID const& account, STAmount const& amount, beast::Journal j)
|
||||
requires kIsWritable;
|
||||
};
|
||||
|
||||
// CTAD deduction guides — bare IOUIssuance(view, ...) always deduces read-only.
|
||||
// For writable access, use WIOUIssuance(view, ...) explicitly.
|
||||
IOUIssuance(ReadView const&, Issue const&) -> IOUIssuance<ReadView>;
|
||||
IOUIssuance(ReadView const&, Issue const&, beast::Journal) -> IOUIssuance<ReadView>;
|
||||
IOUIssuance(ReadView const&, AccountID const&, Currency const&) -> IOUIssuance<ReadView>;
|
||||
IOUIssuance(ReadView const&, AccountID const&, Currency const&, beast::Journal)
|
||||
-> IOUIssuance<ReadView>;
|
||||
|
||||
// Backward-compatible aliases
|
||||
using RIOUIssuance = IOUIssuance<ReadView>;
|
||||
using WIOUIssuance = IOUIssuance<ApplyView>;
|
||||
|
||||
// Explicit instantiation declarations (definitions in .cpp)
|
||||
extern template class IOUIssuance<ReadView>;
|
||||
extern template class IOUIssuance<ApplyView>;
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Freeze checking (IOU-specific)
|
||||
// Trust line operations (SLE-level, no issuance context)
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
[[nodiscard]] bool
|
||||
isIndividualFrozen(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
Currency const& currency,
|
||||
AccountID const& issuer);
|
||||
|
||||
[[nodiscard]] inline bool
|
||||
isIndividualFrozen(ReadView const& view, AccountID const& account, Issue const& issue)
|
||||
{
|
||||
return isIndividualFrozen(view, account, issue.currency, issue.account);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
isFrozen(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
Currency const& currency,
|
||||
AccountID const& issuer);
|
||||
|
||||
[[nodiscard]] inline bool
|
||||
isFrozen(ReadView const& view, AccountID const& account, Issue const& issue)
|
||||
{
|
||||
return isFrozen(view, account, issue.currency, issue.account);
|
||||
}
|
||||
|
||||
// Overload with depth parameter for uniformity with MPTIssue version.
|
||||
// The depth parameter is ignored for IOUs since they don't have vault recursion.
|
||||
[[nodiscard]] inline bool
|
||||
isFrozen(ReadView const& view, AccountID const& account, Issue const& issue, int /*depth*/)
|
||||
{
|
||||
return isFrozen(view, account, issue);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
isDeepFrozen(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
Currency const& currency,
|
||||
AccountID const& issuer);
|
||||
|
||||
[[nodiscard]] inline bool
|
||||
isDeepFrozen(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
Issue const& issue,
|
||||
int = 0 /*ignored*/)
|
||||
{
|
||||
return isDeepFrozen(view, account, issue.currency, issue.account);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline TER
|
||||
checkDeepFrozen(ReadView const& view, AccountID const& account, Issue const& issue)
|
||||
{
|
||||
return isDeepFrozen(view, account, issue) ? (TER)tecFROZEN : (TER)tesSUCCESS;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Trust line operations
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** Create a trust line
|
||||
|
||||
This can set an initial balance.
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
trustCreate(
|
||||
ApplyView& view,
|
||||
bool const bSrcHigh,
|
||||
AccountID const& uSrcAccountID,
|
||||
AccountID const& uDstAccountID,
|
||||
uint256 const& uIndex, // --> ripple state entry
|
||||
SLE::ref sleAccount, // --> the account being set.
|
||||
bool const bAuth, // --> authorize account.
|
||||
bool const bNoRipple, // --> others cannot ripple through
|
||||
bool const bFreeze, // --> funds cannot leave
|
||||
bool bDeepFreeze, // --> can neither receive nor send funds
|
||||
STAmount const& saBalance, // --> balance of account being set.
|
||||
// Issuer should be noAccount()
|
||||
STAmount const& saLimit, // --> limit for account being set.
|
||||
// Issuer should be the account being set.
|
||||
std::uint32_t uQualityIn,
|
||||
std::uint32_t uQualityOut,
|
||||
beast::Journal j);
|
||||
|
||||
[[nodiscard]] TER
|
||||
trustDelete(
|
||||
ApplyView& view,
|
||||
@@ -159,88 +242,6 @@ trustDelete(
|
||||
AccountID const& uHighAccountID,
|
||||
beast::Journal j);
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// IOU issuance/redemption
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
[[nodiscard]] TER
|
||||
issueIOU(
|
||||
ApplyView& view,
|
||||
AccountID const& account,
|
||||
STAmount const& amount,
|
||||
Issue const& issue,
|
||||
beast::Journal j);
|
||||
|
||||
[[nodiscard]] TER
|
||||
redeemIOU(
|
||||
ApplyView& view,
|
||||
AccountID const& account,
|
||||
STAmount const& amount,
|
||||
Issue const& issue,
|
||||
beast::Journal j);
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Authorization and transfer checks (IOU-specific)
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** Check if the account lacks required authorization.
|
||||
*
|
||||
* Return tecNO_AUTH or tecNO_LINE if it does
|
||||
* and tesSUCCESS otherwise.
|
||||
*
|
||||
* If StrongAuth then return tecNO_LINE if the RippleState doesn't exist. Return
|
||||
* tecNO_AUTH if lsfRequireAuth is set on the issuer's AccountRoot, and the
|
||||
* RippleState does exist, and the RippleState is not authorized.
|
||||
*
|
||||
* If WeakAuth then return tecNO_AUTH if lsfRequireAuth is set, and the
|
||||
* RippleState exists, and is not authorized. Return tecNO_LINE if
|
||||
* lsfRequireAuth is set and the RippleState doesn't exist. Consequently, if
|
||||
* WeakAuth and lsfRequireAuth is *not* set, this function will return
|
||||
* tesSUCCESS even if RippleState does *not* exist.
|
||||
*
|
||||
* The default "Legacy" auth type is equivalent to WeakAuth.
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
requireAuth(
|
||||
ReadView const& view,
|
||||
Issue const& issue,
|
||||
AccountID const& account,
|
||||
AuthType authType = AuthType::Legacy);
|
||||
|
||||
/** Check if the destination account is allowed
|
||||
* to receive IOU. Return terNO_RIPPLE if rippling is
|
||||
* disabled on both sides and tesSUCCESS otherwise.
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
canTransfer(ReadView const& view, Issue const& issue, AccountID const& from, AccountID const& to);
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Empty holding operations (IOU-specific)
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/// Any transactors that call addEmptyHolding() in doApply must call
|
||||
/// canAddHolding() in preflight with the same View and Asset
|
||||
[[nodiscard]] TER
|
||||
addEmptyHolding(
|
||||
ApplyView& view,
|
||||
AccountID const& accountID,
|
||||
XRPAmount priorBalance,
|
||||
Issue const& issue,
|
||||
beast::Journal journal);
|
||||
|
||||
[[nodiscard]] TER
|
||||
removeEmptyHolding(
|
||||
ApplyView& view,
|
||||
AccountID const& accountID,
|
||||
Issue const& issue,
|
||||
beast::Journal journal);
|
||||
|
||||
/** Delete trustline to AMM. The passed `sle` must be obtained from a prior
|
||||
* call to view.peek(). Fail if neither side of the trustline is AMM or
|
||||
* if ammAccountID is seated and is not one of the trustline's side.
|
||||
@@ -252,14 +253,4 @@ deleteAMMTrustLine(
|
||||
std::optional<AccountID> const& ammAccountID,
|
||||
beast::Journal j);
|
||||
|
||||
/** Delete AMMs MPToken. The passed `sle` must be obtained from a prior
|
||||
* call to view.peek().
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
deleteAMMMPToken(
|
||||
ApplyView& view,
|
||||
std::shared_ptr<SLE> sleMPT,
|
||||
AccountID const& ammAccountID,
|
||||
beast::Journal j);
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
240
include/xrpl/ledger/helpers/SLEBase.h
Normal file
240
include/xrpl/ledger/helpers/SLEBase.h
Normal file
@@ -0,0 +1,240 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
|
||||
#include <concepts>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
// Concept to distinguish read-only vs writable view types
|
||||
template <typename V>
|
||||
concept WritableView = std::derived_from<V, ApplyView>;
|
||||
|
||||
/**
|
||||
* View-parameterized base class for all ledger entry wrappers.
|
||||
*
|
||||
* SLEBase<ReadView> — read-only: holds shared_ptr<SLE const> + ReadView const&
|
||||
* SLEBase<ApplyView> — writable: holds shared_ptr<SLE> + ApplyView& + Keylet,
|
||||
* plus insert/update/erase operations
|
||||
*
|
||||
* Write-only members are gated by `requires` clauses, providing compile-time
|
||||
* guarantees that read-only wrappers cannot mutate state.
|
||||
*
|
||||
* Derived classes should provide domain-specific accessors that hide
|
||||
* implementation details of the underlying ledger entry format.
|
||||
*/
|
||||
template <typename ViewT>
|
||||
class SLEBase
|
||||
{
|
||||
public:
|
||||
static constexpr bool kIsWritable = WritableView<ViewT>;
|
||||
|
||||
// SLE pointer type: mutable for writable views, const for read-only
|
||||
using sle_ptr_type = std::conditional_t<kIsWritable, std::shared_ptr<SLE>, SLE::const_pointer>;
|
||||
|
||||
// View reference type: ApplyView& for writable, ReadView const& for read-only
|
||||
using view_ref_type = std::conditional_t<kIsWritable, ApplyView&, ReadView const&>;
|
||||
|
||||
virtual ~SLEBase() = default;
|
||||
|
||||
SLEBase(SLEBase const&) = default;
|
||||
SLEBase(SLEBase&&) = default;
|
||||
SLEBase&
|
||||
operator=(SLEBase const&) = delete;
|
||||
SLEBase&
|
||||
operator=(SLEBase&&) = delete;
|
||||
SLEBase() = delete;
|
||||
|
||||
// --- Common interface (always available) ---
|
||||
|
||||
/** Returns true if the ledger entry exists */
|
||||
[[nodiscard]] bool
|
||||
exists() const
|
||||
{
|
||||
return sle_ != nullptr;
|
||||
}
|
||||
|
||||
/** Explicit conversion to bool for convenient existence checking */
|
||||
explicit
|
||||
operator bool() const
|
||||
{
|
||||
return exists();
|
||||
}
|
||||
|
||||
/** Returns the underlying SLE for read access */
|
||||
[[nodiscard]] SLE::const_pointer
|
||||
sle() const
|
||||
{
|
||||
return sle_;
|
||||
}
|
||||
|
||||
/** Returns the read view (always available; ApplyView inherits ReadView) */
|
||||
[[nodiscard]] ReadView const&
|
||||
readView() const
|
||||
{
|
||||
return view_;
|
||||
}
|
||||
|
||||
/** Const dereference operators (always available) */
|
||||
STLedgerEntry const*
|
||||
operator->() const
|
||||
{
|
||||
XRPL_ASSERT(exists(), "xrpl::SLEBase::operator-> : exists");
|
||||
return sle_.get();
|
||||
}
|
||||
|
||||
STLedgerEntry const&
|
||||
operator*() const
|
||||
{
|
||||
XRPL_ASSERT(exists(), "xrpl::SLEBase::operator* : exists");
|
||||
return *sle_;
|
||||
}
|
||||
|
||||
// --- Writable interface (compile-time gated) ---
|
||||
|
||||
/** Returns a mutable SLE for write operations */
|
||||
[[nodiscard]] sle_ptr_type const&
|
||||
mutableSle() const
|
||||
requires kIsWritable
|
||||
{
|
||||
return sle_;
|
||||
}
|
||||
|
||||
/** Returns true if this wrapper supports write operations */
|
||||
[[nodiscard]] bool
|
||||
canModify() const
|
||||
requires kIsWritable
|
||||
{
|
||||
return sle_ != nullptr;
|
||||
}
|
||||
|
||||
/** Returns the apply view for write operations */
|
||||
[[nodiscard]] ApplyView&
|
||||
applyView() const
|
||||
requires kIsWritable
|
||||
{
|
||||
return view_;
|
||||
}
|
||||
|
||||
/** Mutable dereference operators */
|
||||
STLedgerEntry*
|
||||
operator->()
|
||||
requires kIsWritable
|
||||
{
|
||||
XRPL_ASSERT(canModify(), "xrpl::SLEBase::operator-> : can modify");
|
||||
return sle_.get();
|
||||
}
|
||||
|
||||
STLedgerEntry&
|
||||
operator*()
|
||||
requires kIsWritable
|
||||
{
|
||||
XRPL_ASSERT(canModify(), "xrpl::SLEBase::operator* : can modify");
|
||||
return *sle_;
|
||||
}
|
||||
|
||||
void
|
||||
insert()
|
||||
requires kIsWritable
|
||||
{
|
||||
XRPL_ASSERT(canModify(), "xrpl::SLEBase::insert : can modify");
|
||||
view_.insert(sle_);
|
||||
}
|
||||
|
||||
void
|
||||
erase()
|
||||
requires kIsWritable
|
||||
{
|
||||
XRPL_ASSERT(canModify(), "xrpl::SLEBase::erase : can modify");
|
||||
view_.erase(sle_);
|
||||
}
|
||||
|
||||
void
|
||||
update()
|
||||
requires kIsWritable
|
||||
{
|
||||
XRPL_ASSERT(canModify(), "xrpl::SLEBase::update : can modify");
|
||||
view_.update(sle_);
|
||||
}
|
||||
|
||||
void
|
||||
newSLE()
|
||||
requires kIsWritable
|
||||
{
|
||||
XRPL_ASSERT(!canModify(), "xrpl::SLEBase::newSLE : no existing SLE");
|
||||
sle_ = std::make_shared<SLE>(key_);
|
||||
}
|
||||
|
||||
[[nodiscard]] beast::Journal
|
||||
journal() const
|
||||
{
|
||||
return j_;
|
||||
}
|
||||
|
||||
protected:
|
||||
/** Constructor for read-only context */
|
||||
explicit SLEBase(
|
||||
SLE::const_pointer sle,
|
||||
ReadView const& view,
|
||||
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
|
||||
requires(!kIsWritable)
|
||||
: view_(view), sle_(std::move(sle)), j_(j)
|
||||
{
|
||||
}
|
||||
|
||||
/** Converting constructor: writable → read-only.
|
||||
* Enables implicit conversion from SLEBase<ApplyView> to
|
||||
* SLEBase<ReadView>, so functions taking ReadOnlySLE const& can
|
||||
* accept WritableSLE.
|
||||
*/
|
||||
template <WritableView OtherViewT>
|
||||
SLEBase(SLEBase<OtherViewT> const& other)
|
||||
requires(!kIsWritable)
|
||||
: view_(other.readView()), sle_(other.sle()), j_(other.journal())
|
||||
{
|
||||
}
|
||||
|
||||
/** Constructor for writable context (from existing SLE) */
|
||||
explicit SLEBase(
|
||||
std::shared_ptr<SLE> sle,
|
||||
ApplyView& view,
|
||||
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
|
||||
requires kIsWritable
|
||||
: view_(view)
|
||||
, key_(sle ? Keylet(sle->getType(), sle->key()) : Keylet(ltANY, uint256{}))
|
||||
, sle_(std::move(sle))
|
||||
, j_(j)
|
||||
{
|
||||
}
|
||||
|
||||
/** Constructor for writable context (peek from view by keylet) */
|
||||
explicit SLEBase(
|
||||
Keylet const& key,
|
||||
ApplyView& view,
|
||||
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
|
||||
requires kIsWritable
|
||||
: view_(view), key_(key), sle_(view_.peek(key)), j_(j)
|
||||
{
|
||||
}
|
||||
|
||||
view_ref_type view_;
|
||||
|
||||
// Keylet is only meaningful for writable views, but we conditionally
|
||||
// include it to avoid wasting space in read-only wrappers.
|
||||
struct Empty
|
||||
{
|
||||
};
|
||||
[[no_unique_address]]
|
||||
std::conditional_t<kIsWritable, Keylet, Empty> key_{};
|
||||
|
||||
sle_ptr_type sle_;
|
||||
beast::Journal j_;
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -45,6 +45,50 @@ enum class AllowMPTOverflow : bool { No = false, Yes };
|
||||
*/
|
||||
enum class AuthType { StrongAuth, WeakAuth, Legacy };
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// TokenBase<ViewT>: abstract interface shared by MPTokenIssuance and IOUIssuance.
|
||||
//
|
||||
// This is a pure-interface base (no data members), used as a second base class
|
||||
// alongside SLEBase<V> (for MPT) or AccountRoot<V> (for IOU) so callers can hold
|
||||
// a polymorphic reference and dispatch without knowing the token kind. Methods
|
||||
// whose semantics differ slightly between IOU and MPT are unified here with the
|
||||
// MPT signature (e.g. `int depth`); IOU implementations ignore depth.
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
template <typename ViewT>
|
||||
class TokenBase
|
||||
{
|
||||
public:
|
||||
virtual ~TokenBase() = default;
|
||||
|
||||
[[nodiscard]] virtual bool
|
||||
isGlobalFrozen() const = 0;
|
||||
|
||||
[[nodiscard]] virtual bool
|
||||
isIndividualFrozen(AccountID const& account) const = 0;
|
||||
|
||||
[[nodiscard]] virtual bool
|
||||
isFrozen(AccountID const& account, int depth = 0) const = 0;
|
||||
|
||||
[[nodiscard]] virtual Rate
|
||||
transferRate() const = 0;
|
||||
|
||||
[[nodiscard]] virtual TER
|
||||
requireAuth(AccountID const& account, AuthType authType = AuthType::Legacy, int depth = 0)
|
||||
const = 0;
|
||||
|
||||
[[nodiscard]] virtual TER
|
||||
canTransfer(AccountID const& from, AccountID const& to) const = 0;
|
||||
|
||||
[[nodiscard]] virtual bool
|
||||
canClawback() const = 0;
|
||||
|
||||
[[nodiscard]] virtual bool
|
||||
requiresAuth() const = 0;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Freeze checking (Asset-based dispatchers)
|
||||
@@ -116,10 +160,10 @@ checkDeepFrozen(ReadView const& view, AccountID const& account, Asset const& ass
|
||||
|
||||
// Returns the amount an account can spend.
|
||||
//
|
||||
// If shSIMPLE_BALANCE is specified, this is the amount the account can spend
|
||||
// If SpendableHandling::SimpleBalance is specified, this is the amount the account can spend
|
||||
// without going into debt.
|
||||
//
|
||||
// If shFULL_BALANCE is specified, this is the amount the account can spend
|
||||
// If SpendableHandling::FullBalance is specified, this is the amount the account can spend
|
||||
// total. Specifically:
|
||||
// * The account can go into debt if using a trust line, and the other side has
|
||||
// a non-zero limit.
|
||||
@@ -245,7 +289,7 @@ canTransfer(ReadView const& view, Asset const& asset, AccountID const& from, Acc
|
||||
// Direct send w/o fees:
|
||||
// - Redeeming IOUs and/or sending sender's own IOUs.
|
||||
// - Create trust line of needed.
|
||||
// --> bCheckIssuer : normally require issuer to be involved.
|
||||
// bCheckIssuer : normally require issuer to be involved.
|
||||
// [[nodiscard]] // nodiscard commented out so DirectStep.cpp compiles.
|
||||
|
||||
/** Calls static directSendNoFeeIOU if saAmount represents Issue.
|
||||
|
||||
@@ -287,7 +287,7 @@ credential(uint256 const& key) noexcept
|
||||
}
|
||||
|
||||
Keylet
|
||||
mptIssuance(std::uint32_t seq, AccountID const& issuer) noexcept;
|
||||
mptIssuance(std::uint32_t const seq, AccountID const& issuer) noexcept;
|
||||
|
||||
Keylet
|
||||
mptIssuance(MPTID const& issuanceID) noexcept;
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -112,7 +112,7 @@ LEDGER_ENTRY(ltSIGNER_LIST, 0x0053, SignerList, signer_list, ({
|
||||
|
||||
/** A ledger object which describes a ticket.
|
||||
|
||||
\sa keylet::kTicket
|
||||
\sa keylet::kTICKET
|
||||
*/
|
||||
LEDGER_ENTRY(ltTICKET, 0x0054, Ticket, ticket, ({
|
||||
{sfAccount, SoeRequired},
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/beast/utility/WrappedSink.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/protocol/Permissions.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
#include <xrpl/tx/ApplyContext.h>
|
||||
@@ -116,6 +117,7 @@ protected:
|
||||
beast::Journal const j_;
|
||||
|
||||
AccountID const accountID_;
|
||||
WAccountRoot account_;
|
||||
XRPAmount preFeeBalance_{}; // Balance before fees.
|
||||
|
||||
public:
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/UintTypes.h>
|
||||
|
||||
@@ -19,9 +20,10 @@ checkFreeze(
|
||||
XRPL_ASSERT(src != dst, "xrpl::checkFreeze : unequal input accounts");
|
||||
|
||||
// check freeze
|
||||
if (auto sle = view.read(keylet::account(dst)))
|
||||
AccountRoot const acctDst(dst, view);
|
||||
if (acctDst)
|
||||
{
|
||||
if (sle->isFlag(lsfGlobalFreeze))
|
||||
if (acctDst->isFlag(lsfGlobalFreeze))
|
||||
{
|
||||
return terNO_LINE;
|
||||
}
|
||||
@@ -43,10 +45,9 @@ checkFreeze(
|
||||
|
||||
if (view.rules().enabled(fixFrozenLPTokenTransfer))
|
||||
{
|
||||
if (auto const sleDst = view.read(keylet::account(dst));
|
||||
sleDst && sleDst->isFieldPresent(sfAMMID))
|
||||
if (AccountRoot const acctDst2(dst, view); acctDst2 && acctDst2->isFieldPresent(sfAMMID))
|
||||
{
|
||||
auto const sleAmm = view.read(keylet::amm((*sleDst)[sfAMMID]));
|
||||
auto const sleAmm = view.read(keylet::amm(acctDst2->at(sfAMMID)));
|
||||
if (!sleAmm)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
|
||||
@@ -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 -
|
||||
@@ -70,8 +70,8 @@ isVaultPseudoAccountFrozen(
|
||||
return false; // zero MPToken won't block deletion of MPTokenIssuance
|
||||
|
||||
auto const issuer = mptIssuance->getAccountID(sfIssuer);
|
||||
auto const mptIssuer = view.read(keylet::account(issuer));
|
||||
if (mptIssuer == nullptr)
|
||||
auto const mptIssuer = AccountRoot(issuer, view);
|
||||
if (!mptIssuer.exists())
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE("xrpl::isVaultPseudoAccountFrozen : null MPToken issuer");
|
||||
@@ -348,11 +348,11 @@ withdrawToDestExceedsLimit(
|
||||
|
||||
return amount.asset().visit(
|
||||
[&](Issue const& issue) -> TER {
|
||||
auto const& currency = issue.currency;
|
||||
auto const owed = creditBalance(view, to, issuer, currency);
|
||||
auto const iou = IOUIssuance(view, issue);
|
||||
auto const owed = iou.creditBalance(to);
|
||||
if (owed <= beast::kZero)
|
||||
{
|
||||
auto const limit = creditLimit(view, to, issuer, currency);
|
||||
auto const limit = iou.creditLimit(to);
|
||||
if (-owed >= limit || amount > (limit + owed))
|
||||
return tecNO_LINE;
|
||||
}
|
||||
@@ -366,17 +366,17 @@ canWithdraw(
|
||||
ReadView const& view,
|
||||
AccountID const& from,
|
||||
AccountID const& to,
|
||||
SLE::const_ref toSle,
|
||||
RAccountRoot const& toWrapped,
|
||||
STAmount const& amount,
|
||||
bool hasDestinationTag)
|
||||
{
|
||||
if (auto const ret = checkDestinationAndTag(toSle, hasDestinationTag))
|
||||
if (auto const ret = toWrapped.checkDestinationAndTag(hasDestinationTag))
|
||||
return ret;
|
||||
|
||||
if (from == to)
|
||||
return tesSUCCESS;
|
||||
|
||||
if (toSle->isFlag(lsfDepositAuth))
|
||||
if (toWrapped->isFlag(lsfDepositAuth))
|
||||
{
|
||||
if (!view.exists(keylet::depositPreauth(to, from)))
|
||||
return tecNO_PERMISSION;
|
||||
@@ -393,9 +393,9 @@ canWithdraw(
|
||||
STAmount const& amount,
|
||||
bool hasDestinationTag)
|
||||
{
|
||||
auto const toSle = view.read(keylet::account(to));
|
||||
auto const toWrapped = AccountRoot(to, view);
|
||||
|
||||
return canWithdraw(view, from, to, toSle, amount, hasDestinationTag);
|
||||
return canWithdraw(view, from, to, toWrapped, amount, hasDestinationTag);
|
||||
}
|
||||
|
||||
[[nodiscard]] TER
|
||||
@@ -427,8 +427,8 @@ doWithdraw(
|
||||
}
|
||||
else
|
||||
{
|
||||
auto dstSle = view.read(keylet::account(dstAcct));
|
||||
if (auto err = verifyDepositPreauth(tx, view, senderAcct, dstAcct, dstSle, j))
|
||||
auto dst = AccountRoot(dstAcct, view, j);
|
||||
if (auto err = verifyDepositPreauth(tx, view, senderAcct, dst, j))
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/Sandbox.h>
|
||||
#include <xrpl/ledger/View.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>
|
||||
#include <xrpl/protocol/AMMCore.h>
|
||||
@@ -527,7 +529,7 @@ ammLPHolds(
|
||||
<< " lpAccount=" << to_string(lpAccount)
|
||||
<< " amount=" << amount.getFullText();
|
||||
}
|
||||
else if (isFrozen(view, lpAccount, currency, ammAccount))
|
||||
else if (IOUIssuance(view, ammAccount, currency).isFrozen(lpAccount))
|
||||
{
|
||||
amount.clear(Issue{currency, ammAccount});
|
||||
JLOG(j.trace()) << "ammLPHolds: frozen currency "
|
||||
@@ -599,20 +601,20 @@ ammAccountHolds(ReadView const& view, AccountID const& ammAccountID, Asset const
|
||||
return asset.visit(
|
||||
[&](MPTIssue const& issue) {
|
||||
if (auto const sle = view.read(keylet::mptoken(issue, ammAccountID));
|
||||
sle && !isFrozen(view, ammAccountID, issue))
|
||||
sle && !MPTokenIssuance(view, issue).isFrozen(ammAccountID))
|
||||
return STAmount{issue, (*sle)[sfMPTAmount]};
|
||||
return STAmount{asset};
|
||||
},
|
||||
[&](Issue const& issue) {
|
||||
if (isXRP(issue))
|
||||
{
|
||||
if (auto const sle = view.read(keylet::account(ammAccountID)))
|
||||
return (*sle)[sfBalance];
|
||||
if (AccountRoot const acct(ammAccountID, view); acct)
|
||||
return (*acct)[sfBalance];
|
||||
}
|
||||
else if (
|
||||
auto const sle =
|
||||
view.read(keylet::line(ammAccountID, issue.account, issue.currency));
|
||||
sle && !isFrozen(view, ammAccountID, issue.currency, issue.account))
|
||||
sle && !IOUIssuance(view, issue).isFrozen(ammAccountID))
|
||||
{
|
||||
STAmount amount = (*sle)[sfBalance];
|
||||
if (ammAccountID > issue.account)
|
||||
@@ -722,8 +724,8 @@ deleteAMMAccount(Sandbox& sb, Asset const& asset, Asset const& asset2, beast::Jo
|
||||
}
|
||||
|
||||
auto const ammAccountID = (*ammSle)[sfAccount];
|
||||
auto sleAMMRoot = sb.peek(keylet::account(ammAccountID));
|
||||
if (!sleAMMRoot)
|
||||
WAccountRoot ammRoot(ammAccountID, sb, j);
|
||||
if (!ammRoot)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.error()) << "deleteAMMAccount: AMM account does not exist "
|
||||
@@ -760,7 +762,7 @@ deleteAMMAccount(Sandbox& sb, Asset const& asset, Asset const& asset2, beast::Jo
|
||||
}
|
||||
|
||||
sb.erase(ammSle);
|
||||
sb.erase(sleAMMRoot);
|
||||
ammRoot.erase();
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
@@ -31,14 +31,13 @@
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
template <typename ViewT>
|
||||
bool
|
||||
isGlobalFrozen(ReadView const& view, AccountID const& issuer)
|
||||
AccountRoot<ViewT>::isGlobalFrozen() const
|
||||
{
|
||||
if (isXRP(issuer))
|
||||
if (!this->exists())
|
||||
return false;
|
||||
if (auto const sle = view.read(keylet::account(issuer)))
|
||||
return sle->isFlag(lsfGlobalFreeze);
|
||||
return false;
|
||||
return this->sle_->isFlag(lsfGlobalFreeze);
|
||||
}
|
||||
|
||||
// An owner count cannot be negative. If adjustment would cause a negative
|
||||
@@ -83,66 +82,62 @@ confineOwnerCount(
|
||||
return adjusted;
|
||||
}
|
||||
|
||||
template <typename ViewT>
|
||||
XRPAmount
|
||||
xrpLiquid(ReadView const& view, AccountID const& id, std::int32_t ownerCountAdj, beast::Journal j)
|
||||
AccountRoot<ViewT>::xrpLiquid(std::int32_t ownerCountAdj) const
|
||||
{
|
||||
auto const sle = view.read(keylet::account(id));
|
||||
if (sle == nullptr)
|
||||
if (!this->exists())
|
||||
return beast::kZero;
|
||||
|
||||
// Return balance minus reserve
|
||||
std::uint32_t const ownerCount =
|
||||
confineOwnerCount(view.ownerCountHook(id, sle->getFieldU32(sfOwnerCount)), ownerCountAdj);
|
||||
std::uint32_t const ownerCount = confineOwnerCount(
|
||||
this->readView().ownerCountHook(id_, this->sle_->getFieldU32(sfOwnerCount)), ownerCountAdj);
|
||||
|
||||
// Pseudo-accounts have no reserve requirement
|
||||
auto const reserve =
|
||||
isPseudoAccount(sle) ? XRPAmount{0} : view.fees().accountReserve(ownerCount);
|
||||
this->isPseudoAccount() ? XRPAmount{0} : this->readView().fees().accountReserve(ownerCount);
|
||||
|
||||
auto const fullBalance = sle->getFieldAmount(sfBalance);
|
||||
auto const fullBalance = this->sle_->getFieldAmount(sfBalance);
|
||||
|
||||
auto const balance = view.balanceHookIOU(id, xrpAccount(), fullBalance);
|
||||
auto const balance = this->readView().balanceHookIOU(id_, xrpAccount(), fullBalance);
|
||||
|
||||
STAmount const amount = (balance < reserve) ? STAmount{0} : balance - reserve;
|
||||
|
||||
JLOG(j.trace()) << "accountHolds:" << " account=" << to_string(id)
|
||||
<< " amount=" << amount.getFullText()
|
||||
<< " fullBalance=" << fullBalance.getFullText()
|
||||
<< " balance=" << balance.getFullText() << " reserve=" << reserve
|
||||
<< " ownerCount=" << ownerCount << " ownerCountAdj=" << ownerCountAdj;
|
||||
JLOG(this->j_.trace()) << "accountHolds:" << " account=" << to_string(id_)
|
||||
<< " amount=" << amount.getFullText()
|
||||
<< " fullBalance=" << fullBalance.getFullText()
|
||||
<< " balance=" << balance.getFullText() << " reserve=" << reserve
|
||||
<< " ownerCount=" << ownerCount << " ownerCountAdj=" << ownerCountAdj;
|
||||
|
||||
return amount.xrp();
|
||||
}
|
||||
|
||||
template <typename ViewT>
|
||||
Rate
|
||||
transferRate(ReadView const& view, AccountID const& issuer)
|
||||
AccountRoot<ViewT>::transferRate() const
|
||||
{
|
||||
auto const sle = view.read(keylet::account(issuer));
|
||||
|
||||
if (sle && sle->isFieldPresent(sfTransferRate))
|
||||
return Rate{sle->getFieldU32(sfTransferRate)};
|
||||
if (this->sle_ && this->sle_->isFieldPresent(sfTransferRate))
|
||||
return Rate{this->sle_->getFieldU32(sfTransferRate)};
|
||||
|
||||
return kParityRate;
|
||||
}
|
||||
|
||||
template <typename ViewT>
|
||||
void
|
||||
adjustOwnerCount(
|
||||
ApplyView& view,
|
||||
std::shared_ptr<SLE> const& sle,
|
||||
std::int32_t amount,
|
||||
beast::Journal j)
|
||||
AccountRoot<ViewT>::adjustOwnerCount(std::int32_t amount)
|
||||
requires kIsWritable
|
||||
{
|
||||
if (!sle)
|
||||
return;
|
||||
XRPL_ASSERT(this->canModify(), "xrpl::adjustOwnerCount : can modify");
|
||||
XRPL_ASSERT(amount, "xrpl::adjustOwnerCount : nonzero amount input");
|
||||
std::uint32_t const current{sle->getFieldU32(sfOwnerCount)};
|
||||
AccountID const id = (*sle)[sfAccount];
|
||||
std::uint32_t const adjusted = confineOwnerCount(current, amount, id, j);
|
||||
view.adjustOwnerCountHook(id, current, adjusted);
|
||||
sle->at(sfOwnerCount) = adjusted;
|
||||
view.update(sle);
|
||||
std::uint32_t const current{this->sle_->getFieldU32(sfOwnerCount)};
|
||||
AccountID const id = (*this->sle_)[sfAccount];
|
||||
std::uint32_t const adjusted = confineOwnerCount(current, amount, id, this->j_);
|
||||
this->applyView().adjustOwnerCountHook(id_, current, adjusted);
|
||||
this->sle_->at(sfOwnerCount) = adjusted;
|
||||
this->update();
|
||||
}
|
||||
|
||||
AccountID
|
||||
[[nodiscard]] AccountID
|
||||
pseudoAccountAddress(ReadView const& view, uint256 const& pseudoOwnerKey)
|
||||
{
|
||||
// This number must not be changed without an amendment
|
||||
@@ -191,19 +186,18 @@ getPseudoAccountFields()
|
||||
return kPseudoFields;
|
||||
}
|
||||
|
||||
template <typename ViewT>
|
||||
[[nodiscard]] bool
|
||||
isPseudoAccount(
|
||||
std::shared_ptr<SLE const> sleAcct,
|
||||
std::set<SField const*> const& pseudoFieldFilter)
|
||||
AccountRoot<ViewT>::isPseudoAccount(std::set<SField const*> const& pseudoFieldFilter) const
|
||||
{
|
||||
auto const& fields = getPseudoAccountFields();
|
||||
|
||||
// Intentionally use defensive coding here because it's cheap and makes the
|
||||
// semantics of true return value clean.
|
||||
return sleAcct && sleAcct->getType() == ltACCOUNT_ROOT &&
|
||||
return this->sle_ && this->sle_->getType() == ltACCOUNT_ROOT &&
|
||||
std::count_if(
|
||||
fields.begin(), fields.end(), [&sleAcct, &pseudoFieldFilter](SField const* sf) -> bool {
|
||||
return sleAcct->isFieldPresent(*sf) &&
|
||||
fields.begin(), fields.end(), [this, &pseudoFieldFilter](SField const* sf) -> bool {
|
||||
return this->sle_->isFieldPresent(*sf) &&
|
||||
(pseudoFieldFilter.empty() || pseudoFieldFilter.contains(sf));
|
||||
}) > 0;
|
||||
}
|
||||
@@ -250,18 +244,23 @@ createPseudoAccount(ApplyView& view, uint256 const& pseudoOwnerKey, SField const
|
||||
return account;
|
||||
}
|
||||
|
||||
template <typename ViewT>
|
||||
[[nodiscard]] TER
|
||||
checkDestinationAndTag(SLE::const_ref toSle, bool hasDestinationTag)
|
||||
AccountRoot<ViewT>::checkDestinationAndTag(bool hasDestinationTag) const
|
||||
{
|
||||
if (toSle == nullptr)
|
||||
if (this->sle_ == nullptr)
|
||||
return tecNO_DST;
|
||||
|
||||
// The tag is basically account-specific information we don't
|
||||
// understand, but we can require someone to fill it in.
|
||||
if (toSle->isFlag(lsfRequireDestTag) && !hasDestinationTag)
|
||||
if (this->sle_->isFlag(lsfRequireDestTag) && !hasDestinationTag)
|
||||
return tecDST_TAG_NEEDED; // Cannot send without a tag
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
// Explicit template instantiations
|
||||
template class AccountRoot<ReadView>;
|
||||
template class AccountRoot<ApplyView>;
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -78,8 +78,8 @@ deleteSLE(ApplyView& view, std::shared_ptr<SLE> const& sleCredential, beast::Jou
|
||||
|
||||
auto delSLE = [&view, &sleCredential, j](
|
||||
AccountID const& account, SField const& node, bool isOwner) -> TER {
|
||||
auto const sleAccount = view.peek(keylet::account(account));
|
||||
if (!sleAccount)
|
||||
WAccountRoot wrappedAccount(account, view, j);
|
||||
if (!wrappedAccount)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.fatal()) << "Internal error: can't retrieve Owner account.";
|
||||
@@ -98,7 +98,7 @@ deleteSLE(ApplyView& view, std::shared_ptr<SLE> const& sleCredential, beast::Jou
|
||||
}
|
||||
|
||||
if (isOwner)
|
||||
adjustOwnerCount(view, sleAccount, -1, j);
|
||||
wrappedAccount.adjustOwnerCount(-1);
|
||||
|
||||
return tesSUCCESS;
|
||||
};
|
||||
@@ -351,8 +351,7 @@ verifyDepositPreauth(
|
||||
STTx const& tx,
|
||||
ApplyView& view,
|
||||
AccountID const& src,
|
||||
AccountID const& dst,
|
||||
std::shared_ptr<SLE const> const& sleDst,
|
||||
RAccountRoot const& dst,
|
||||
beast::Journal j)
|
||||
{
|
||||
// If depositPreauth is enabled, then an account that requires
|
||||
@@ -373,15 +372,15 @@ verifyDepositPreauth(
|
||||
return tecEXPIRED;
|
||||
}
|
||||
|
||||
if (sleDst && sleDst->isFlag(lsfDepositAuth))
|
||||
if (dst.exists() && ((dst->getFlags() & lsfDepositAuth) != 0u))
|
||||
{
|
||||
if (src != dst)
|
||||
{
|
||||
if (!view.exists(keylet::depositPreauth(dst, src)))
|
||||
if (!view.exists(keylet::depositPreauth(dst.id(), src)))
|
||||
{
|
||||
return !credentialsPresent ? tecNO_PERMISSION
|
||||
: credentials::authorizedDepositPreauth(
|
||||
view, tx.getFieldV256(sfCredentialIDs), dst);
|
||||
view, tx.getFieldV256(sfCredentialIDs), dst.id());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Asset.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
@@ -24,6 +25,9 @@
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
|
||||
namespace xrpl {
|
||||
@@ -2050,4 +2054,68 @@ loanMakePayment(
|
||||
"xrpl::loanMakePayment : fee paid is valid");
|
||||
return totalParts;
|
||||
}
|
||||
|
||||
// An owner count cannot be negative. If adjustment would cause a negative
|
||||
// owner count, clamp the owner count at 0. Similarly for overflow. This
|
||||
// adjustment allows the ownerCount to be adjusted up or down in multiple steps.
|
||||
// If id != std::nullopt, then do error reporting.
|
||||
//
|
||||
// Returns adjusted owner count.
|
||||
static std::uint32_t
|
||||
confineOwnerCount(
|
||||
std::uint32_t current,
|
||||
std::int32_t adjustment,
|
||||
std::optional<AccountID> const& id = std::nullopt,
|
||||
beast::Journal j = beast::Journal{beast::Journal::getNullSink()})
|
||||
{
|
||||
std::uint32_t adjusted{current + adjustment};
|
||||
if (adjustment > 0)
|
||||
{
|
||||
// Overflow is well defined on unsigned
|
||||
if (adjusted < current)
|
||||
{
|
||||
if (id)
|
||||
{
|
||||
JLOG(j.fatal()) << "Account " << *id << " owner count exceeds max!";
|
||||
}
|
||||
adjusted = std::numeric_limits<std::uint32_t>::max();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Underflow is well defined on unsigned
|
||||
if (adjusted > current)
|
||||
{
|
||||
if (id)
|
||||
{
|
||||
JLOG(j.fatal()) << "Account " << *id << " owner count set below 0!";
|
||||
}
|
||||
adjusted = 0;
|
||||
XRPL_ASSERT(!id, "xrpl::confineOwnerCount : id is not set");
|
||||
}
|
||||
}
|
||||
return adjusted;
|
||||
}
|
||||
|
||||
void
|
||||
adjustOwnerCount(
|
||||
std::shared_ptr<SLE> const& sle,
|
||||
ApplyView& view,
|
||||
std::int32_t amount,
|
||||
beast::Journal j)
|
||||
{
|
||||
if (!sle)
|
||||
return;
|
||||
// This function is only used for LoanBrokers, so assert that
|
||||
// AccountRoot should use WAccountRoot.adjustOwnerCount instead
|
||||
XRPL_ASSERT(sle->getType() == ltLOAN_BROKER, "xrpl::adjustOwnerCount : sle is loan broker");
|
||||
XRPL_ASSERT(amount, "xrpl::adjustOwnerCount : nonzero amount input");
|
||||
std::uint32_t const current{sle->getFieldU32(sfOwnerCount)};
|
||||
AccountID const id = (*sle)[sfAccount];
|
||||
std::uint32_t const adjusted = confineOwnerCount(current, amount, id, j);
|
||||
view.adjustOwnerCountHook(id, current, adjusted);
|
||||
sle->at(sfOwnerCount) = adjusted;
|
||||
view.update(sle);
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/CredentialHelpers.h>
|
||||
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
|
||||
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
@@ -38,64 +39,65 @@
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
template <typename ViewT>
|
||||
bool
|
||||
isGlobalFrozen(ReadView const& view, MPTIssue const& mptIssue)
|
||||
MPTokenIssuance<ViewT>::isGlobalFrozen() const
|
||||
{
|
||||
if (auto const sle = view.read(keylet::mptIssuance(mptIssue.getMptID())))
|
||||
if (this->exists())
|
||||
return this->sle_->isFlag(lsfMPTLocked);
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename ViewT>
|
||||
bool
|
||||
MPTokenIssuance<ViewT>::isIndividualFrozen(AccountID const& account) const
|
||||
{
|
||||
if (auto const sle = this->readView().read(keylet::mptoken(mptID_, account)))
|
||||
return sle->isFlag(lsfMPTLocked);
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename ViewT>
|
||||
bool
|
||||
isIndividualFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue)
|
||||
MPTokenIssuance<ViewT>::isFrozen(AccountID const& account, int depth) const
|
||||
{
|
||||
if (auto const sle = view.read(keylet::mptoken(mptIssue.getMptID(), account)))
|
||||
return sle->isFlag(lsfMPTLocked);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
isFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue, int depth)
|
||||
{
|
||||
return isGlobalFrozen(view, mptIssue) || isIndividualFrozen(view, account, mptIssue) ||
|
||||
isVaultPseudoAccountFrozen(view, account, mptIssue, depth);
|
||||
return isGlobalFrozen() || isIndividualFrozen(account) ||
|
||||
isVaultPseudoAccountFrozen(this->readView(), account, mptIssue_, depth);
|
||||
}
|
||||
|
||||
template <typename ViewT>
|
||||
[[nodiscard]] bool
|
||||
isAnyFrozen(
|
||||
ReadView const& view,
|
||||
std::initializer_list<AccountID> const& accounts,
|
||||
MPTIssue const& mptIssue,
|
||||
int depth)
|
||||
MPTokenIssuance<ViewT>::isAnyFrozen(std::initializer_list<AccountID> const& accounts, int depth)
|
||||
const
|
||||
{
|
||||
if (isGlobalFrozen(view, mptIssue))
|
||||
if (isGlobalFrozen())
|
||||
return true;
|
||||
|
||||
for (auto const& account : accounts)
|
||||
{
|
||||
if (isIndividualFrozen(view, account, mptIssue))
|
||||
if (isIndividualFrozen(account))
|
||||
return true;
|
||||
}
|
||||
|
||||
for (auto const& account : accounts)
|
||||
{
|
||||
if (isVaultPseudoAccountFrozen(view, account, mptIssue, depth))
|
||||
if (isVaultPseudoAccountFrozen(this->readView(), account, mptIssue_, depth))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename ViewT>
|
||||
Rate
|
||||
transferRate(ReadView const& view, MPTID const& issuanceID)
|
||||
MPTokenIssuance<ViewT>::transferRate() const
|
||||
{
|
||||
// fee is 0-50,000 (0-50%), rate is 1,000,000,000-2,000,000,000
|
||||
// For example, if transfer fee is 50% then 10,000 * 50,000 = 500,000
|
||||
// which represents 50% of 1,000,000,000
|
||||
if (auto const sle = view.read(keylet::mptIssuance(issuanceID));
|
||||
sle && sle->isFieldPresent(sfTransferFee))
|
||||
if (this->exists() && this->sle_->isFieldPresent(sfTransferFee))
|
||||
{
|
||||
auto const fee = sle->getFieldU16(sfTransferFee);
|
||||
auto const fee = this->sle_->getFieldU16(sfTransferFee);
|
||||
XRPL_ASSERT(fee <= kMaxTransferFee, "xrpl::transferRate : fee is too large");
|
||||
return Rate{1'000'000'000u + (10'000 * fee)};
|
||||
}
|
||||
@@ -103,16 +105,15 @@ transferRate(ReadView const& view, MPTID const& issuanceID)
|
||||
return kParityRate;
|
||||
}
|
||||
|
||||
template <typename ViewT>
|
||||
[[nodiscard]] TER
|
||||
canAddHolding(ReadView const& view, MPTIssue const& mptIssue)
|
||||
MPTokenIssuance<ViewT>::canAddHolding() const
|
||||
{
|
||||
auto mptID = mptIssue.getMptID();
|
||||
auto issuance = view.read(keylet::mptIssuance(mptID));
|
||||
if (!issuance)
|
||||
if (!this->exists())
|
||||
{
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
}
|
||||
if (!issuance->isFlag(lsfMPTCanTransfer))
|
||||
if (!this->sle_->isFlag(lsfMPTCanTransfer))
|
||||
{
|
||||
return tecNO_AUTH;
|
||||
}
|
||||
@@ -120,40 +121,39 @@ canAddHolding(ReadView const& view, MPTIssue const& mptIssue)
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
template <typename ViewT>
|
||||
[[nodiscard]] TER
|
||||
addEmptyHolding(
|
||||
ApplyView& view,
|
||||
MPTokenIssuance<ViewT>::addEmptyHolding(
|
||||
AccountID const& accountID,
|
||||
XRPAmount priorBalance,
|
||||
MPTIssue const& mptIssue,
|
||||
beast::Journal journal)
|
||||
requires kIsWritable
|
||||
{
|
||||
auto const& mptID = mptIssue.getMptID();
|
||||
auto const mpt = view.peek(keylet::mptIssuance(mptID));
|
||||
if (!mpt)
|
||||
if (!this->canModify())
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
if (mpt->isFlag(lsfMPTLocked))
|
||||
if (this->sle_->isFlag(lsfMPTLocked))
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
if (view.peek(keylet::mptoken(mptID, accountID)))
|
||||
if (this->applyView().peek(keylet::mptoken(mptID_, accountID)))
|
||||
return tecDUPLICATE;
|
||||
if (accountID == mptIssue.getIssuer())
|
||||
if (accountID == getIssuer())
|
||||
return tesSUCCESS;
|
||||
|
||||
return authorizeMPToken(view, priorBalance, mptID, accountID, journal);
|
||||
return authorizeMPToken(priorBalance, accountID, journal);
|
||||
}
|
||||
|
||||
template <typename ViewT>
|
||||
[[nodiscard]] TER
|
||||
authorizeMPToken(
|
||||
ApplyView& view,
|
||||
MPTokenIssuance<ViewT>::authorizeMPToken(
|
||||
XRPAmount const& priorBalance,
|
||||
MPTID const& mptIssuanceID,
|
||||
AccountID const& account,
|
||||
beast::Journal journal,
|
||||
std::uint32_t flags,
|
||||
std::optional<AccountID> holderID)
|
||||
requires kIsWritable
|
||||
{
|
||||
auto const sleAcct = view.peek(keylet::account(account));
|
||||
if (!sleAcct)
|
||||
ApplyView& view = this->applyView();
|
||||
WAccountRoot wrappedAcct(account, view, journal);
|
||||
if (!wrappedAcct)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
// If the account that submitted the tx is a holder
|
||||
@@ -166,7 +166,7 @@ authorizeMPToken(
|
||||
// - delete the MPToken
|
||||
if ((flags & tfMPTUnauthorize) != 0u)
|
||||
{
|
||||
auto const mptokenKey = keylet::mptoken(mptIssuanceID, account);
|
||||
auto const mptokenKey = keylet::mptoken(mptID_, account);
|
||||
auto const sleMpt = view.peek(mptokenKey);
|
||||
if (!sleMpt || (*sleMpt)[sfMPTAmount] != 0 ||
|
||||
(view.rules().enabled(fixCleanup3_1_3) &&
|
||||
@@ -177,7 +177,7 @@ authorizeMPToken(
|
||||
keylet::ownerDir(account), (*sleMpt)[sfOwnerNode], sleMpt->key(), false))
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
adjustOwnerCount(view, sleAcct, -1, journal);
|
||||
wrappedAcct.adjustOwnerCount(-1);
|
||||
|
||||
view.erase(sleMpt);
|
||||
return tesSUCCESS;
|
||||
@@ -192,7 +192,7 @@ authorizeMPToken(
|
||||
// an account owns, in the case of MPTokens we only
|
||||
// *enforce* a reserve if the user owns more than two
|
||||
// items. This is similar to the reserve requirements of trust lines.
|
||||
std::uint32_t const uOwnerCount = sleAcct->getFieldU32(sfOwnerCount);
|
||||
std::uint32_t const uOwnerCount = wrappedAcct->getFieldU32(sfOwnerCount);
|
||||
XRPAmount const reserveCreate(
|
||||
(uOwnerCount < 2) ? XRPAmount(beast::kZero)
|
||||
: view.fees().accountReserve(uOwnerCount + 1));
|
||||
@@ -201,8 +201,7 @@ authorizeMPToken(
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
|
||||
// Defensive check before we attempt to create MPToken for the issuer
|
||||
auto const mpt = view.read(keylet::mptIssuance(mptIssuanceID));
|
||||
if (!mpt || mpt->getAccountID(sfIssuer) == account)
|
||||
if (!this->sle_ || this->sle_->getAccountID(sfIssuer) == account)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE("xrpl::authorizeMPToken : invalid issuance or issuers token");
|
||||
@@ -211,33 +210,32 @@ authorizeMPToken(
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
auto const mptokenKey = keylet::mptoken(mptIssuanceID, account);
|
||||
auto const mptokenKey = keylet::mptoken(mptID_, account);
|
||||
auto mptoken = std::make_shared<SLE>(mptokenKey);
|
||||
if (auto ter = dirLink(view, account, mptoken))
|
||||
return ter; // LCOV_EXCL_LINE
|
||||
|
||||
(*mptoken)[sfAccount] = account;
|
||||
(*mptoken)[sfMPTokenIssuanceID] = mptIssuanceID;
|
||||
(*mptoken)[sfMPTokenIssuanceID] = mptID_;
|
||||
(*mptoken)[sfFlags] = 0;
|
||||
view.insert(mptoken);
|
||||
|
||||
// Update owner count.
|
||||
adjustOwnerCount(view, sleAcct, 1, journal);
|
||||
wrappedAcct.adjustOwnerCount(1);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
auto const sleMptIssuance = view.read(keylet::mptIssuance(mptIssuanceID));
|
||||
if (!sleMptIssuance)
|
||||
if (!this->sle_)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
// If the account that submitted this tx is the issuer of the MPT
|
||||
// Note: `account_` is issuer's account
|
||||
// `holderID` is holder's account
|
||||
if (account != (*sleMptIssuance)[sfIssuer])
|
||||
if (account != (*this->sle_)[sfIssuer])
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const sleMpt = view.peek(keylet::mptoken(mptIssuanceID, *holderID));
|
||||
auto const sleMpt = view.peek(keylet::mptoken(mptID_, *holderID));
|
||||
if (!sleMpt)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
@@ -264,19 +262,17 @@ authorizeMPToken(
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
template <typename ViewT>
|
||||
[[nodiscard]] TER
|
||||
removeEmptyHolding(
|
||||
ApplyView& view,
|
||||
AccountID const& accountID,
|
||||
MPTIssue const& mptIssue,
|
||||
beast::Journal journal)
|
||||
MPTokenIssuance<ViewT>::removeEmptyHolding(AccountID const& accountID, beast::Journal journal)
|
||||
requires kIsWritable
|
||||
{
|
||||
ApplyView& view = this->applyView();
|
||||
// If the account is the issuer, then no token should exist. MPTs do not
|
||||
// have the legacy ability to create such a situation, but check anyway. If
|
||||
// a token does exist, it will get deleted. If not, return success.
|
||||
bool const accountIsIssuer = accountID == mptIssue.getIssuer();
|
||||
auto const& mptID = mptIssue.getMptID();
|
||||
auto const mptoken = view.peek(keylet::mptoken(mptID, accountID));
|
||||
bool const accountIsIssuer = accountID == getIssuer();
|
||||
auto const mptoken = view.peek(keylet::mptoken(mptID_, accountID));
|
||||
if (!mptoken)
|
||||
return accountIsIssuer ? (TER)tesSUCCESS : (TER)tecOBJECT_NOT_FOUND;
|
||||
// Unlike a trust line, if the account is the issuer, and the token has a
|
||||
@@ -288,35 +284,27 @@ removeEmptyHolding(
|
||||
return tecHAS_OBLIGATIONS;
|
||||
|
||||
return authorizeMPToken(
|
||||
view,
|
||||
{}, // priorBalance
|
||||
mptID,
|
||||
accountID,
|
||||
journal,
|
||||
tfMPTUnauthorize // flags
|
||||
);
|
||||
}
|
||||
|
||||
template <typename ViewT>
|
||||
[[nodiscard]] TER
|
||||
requireAuth(
|
||||
ReadView const& view,
|
||||
MPTIssue const& mptIssue,
|
||||
AccountID const& account,
|
||||
AuthType authType,
|
||||
int depth)
|
||||
MPTokenIssuance<ViewT>::requireAuth(AccountID const& account, AuthType authType, int depth) const
|
||||
{
|
||||
auto const mptID = keylet::mptIssuance(mptIssue.getMptID());
|
||||
auto const sleIssuance = view.read(mptID);
|
||||
if (!sleIssuance)
|
||||
if (!this->exists())
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
auto const mptIssuer = sleIssuance->getAccountID(sfIssuer);
|
||||
auto const mptIssuer = AccountRoot(this->sle_->getAccountID(sfIssuer), this->readView());
|
||||
|
||||
// issuer is always "authorized"
|
||||
if (mptIssuer == account) // Issuer won't have MPToken
|
||||
return tesSUCCESS;
|
||||
|
||||
bool const featureSAVEnabled = view.rules().enabled(featureSingleAssetVault);
|
||||
bool const featureSAVEnabled = this->readView().rules().enabled(featureSingleAssetVault);
|
||||
|
||||
if (featureSAVEnabled)
|
||||
{
|
||||
@@ -324,29 +312,32 @@ requireAuth(
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
// requireAuth is recursive if the issuer is a vault pseudo-account
|
||||
auto const sleIssuer = view.read(keylet::account(mptIssuer));
|
||||
if (!sleIssuer)
|
||||
if (!mptIssuer.exists())
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
if (sleIssuer->isFieldPresent(sfVaultID))
|
||||
if (mptIssuer->isFieldPresent(sfVaultID))
|
||||
{
|
||||
auto const sleVault = view.read(keylet::vault(sleIssuer->getFieldH256(sfVaultID)));
|
||||
auto const sleVault =
|
||||
this->readView().read(keylet::vault(mptIssuer->getFieldH256(sfVaultID)));
|
||||
if (!sleVault)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const asset = sleVault->at(sfAsset);
|
||||
if (auto const err = asset.visit(
|
||||
[&](Issue const& issue) { return requireAuth(view, issue, account, authType); },
|
||||
[&](Issue const& issue) {
|
||||
return IOUIssuance(this->readView(), issue).requireAuth(account, authType);
|
||||
},
|
||||
[&](MPTIssue const& issue) {
|
||||
return requireAuth(view, issue, account, authType, depth + 1);
|
||||
return MPTokenIssuance<ReadView>(this->readView(), issue)
|
||||
.requireAuth(account, authType, depth + 1);
|
||||
});
|
||||
!isTesSuccess(err))
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
auto const mptokenID = keylet::mptoken(mptID.key, account);
|
||||
auto const sleToken = view.read(mptokenID);
|
||||
auto const mptokenID = keylet::mptoken(mptID_, account);
|
||||
auto const sleToken = this->readView().read(mptokenID);
|
||||
|
||||
// if account has no MPToken, fail
|
||||
if (!sleToken && (authType == AuthType::StrongAuth || authType == AuthType::Legacy))
|
||||
@@ -354,14 +345,14 @@ requireAuth(
|
||||
|
||||
// Note, this check is not amendment-gated because DomainID will be always
|
||||
// empty **unless** writing to it has been enabled by an amendment
|
||||
auto const maybeDomainID = sleIssuance->at(~sfDomainID);
|
||||
auto const maybeDomainID = this->sle_->at(~sfDomainID);
|
||||
if (maybeDomainID)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
sleIssuance->isFlag(lsfMPTRequireAuth),
|
||||
this->sle_->isFlag(lsfMPTRequireAuth),
|
||||
"xrpl::requireAuth : issuance requires authorization");
|
||||
// ter = tefINTERNAL | tecOBJECT_NOT_FOUND | tecNO_AUTH | tecEXPIRED
|
||||
auto const ter = credentials::validDomain(view, *maybeDomainID, account);
|
||||
auto const ter = credentials::validDomain(this->readView(), *maybeDomainID, account);
|
||||
if (isTesSuccess(ter))
|
||||
{
|
||||
return ter; // Note: sleToken might be null
|
||||
@@ -377,47 +368,46 @@ requireAuth(
|
||||
if (featureSAVEnabled)
|
||||
{
|
||||
// Implicitly authorize Vault and LoanBroker pseudo-accounts
|
||||
if (isPseudoAccount(view, account, {&sfVaultID, &sfLoanBrokerID}))
|
||||
AccountRoot const accountRoot(account, this->readView());
|
||||
if (accountRoot.isPseudoAccount({&sfVaultID, &sfLoanBrokerID}))
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
// mptoken must be authorized if issuance enabled requireAuth
|
||||
if (sleIssuance->isFlag(lsfMPTRequireAuth) &&
|
||||
(!sleToken || !sleToken->isFlag(lsfMPTAuthorized)))
|
||||
if (this->sle_->isFlag(lsfMPTRequireAuth) && (!sleToken || !sleToken->isFlag(lsfMPTAuthorized)))
|
||||
return tecNO_AUTH;
|
||||
|
||||
return tesSUCCESS; // Note: sleToken might be null
|
||||
}
|
||||
|
||||
template <typename ViewT>
|
||||
[[nodiscard]] TER
|
||||
enforceMPTokenAuthorization(
|
||||
ApplyView& view,
|
||||
MPTID const& mptIssuanceID,
|
||||
MPTokenIssuance<ViewT>::enforceMPTokenAuthorization(
|
||||
AccountID const& account,
|
||||
XRPAmount const& priorBalance, // for MPToken authorization
|
||||
beast::Journal j)
|
||||
requires kIsWritable
|
||||
{
|
||||
auto const sleIssuance = view.read(keylet::mptIssuance(mptIssuanceID));
|
||||
if (!sleIssuance)
|
||||
if (!this->canModify())
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
XRPL_ASSERT(
|
||||
sleIssuance->isFlag(lsfMPTRequireAuth),
|
||||
this->sle_->isFlag(lsfMPTRequireAuth),
|
||||
"xrpl::enforceMPTokenAuthorization : authorization required");
|
||||
|
||||
if (account == sleIssuance->at(sfIssuer))
|
||||
if (account == this->sle_->at(sfIssuer))
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const keylet = keylet::mptoken(mptIssuanceID, account);
|
||||
auto const sleToken = view.read(keylet); // NOTE: might be null
|
||||
auto const maybeDomainID = sleIssuance->at(~sfDomainID);
|
||||
auto const keylet = keylet::mptoken(mptID_, account);
|
||||
auto const sleToken = this->readView().read(keylet); // NOTE: might be null
|
||||
auto const maybeDomainID = this->sle_->at(~sfDomainID);
|
||||
bool expired = false;
|
||||
bool const authorizedByDomain = [&]() -> bool {
|
||||
// NOTE: defensive here, should be checked in preclaim
|
||||
if (!maybeDomainID.has_value())
|
||||
if (!static_cast<bool>(maybeDomainID))
|
||||
return false; // LCOV_EXCL_LINE
|
||||
|
||||
auto const ter = verifyValidDomain(view, account, *maybeDomainID, j);
|
||||
auto const ter = verifyValidDomain(this->applyView(), account, *maybeDomainID, j);
|
||||
if (isTesSuccess(ter))
|
||||
return true;
|
||||
if (ter == tecEXPIRED)
|
||||
@@ -436,7 +426,7 @@ enforceMPTokenAuthorization(
|
||||
// Either way, return tecNO_AUTH and there is nothing else to do
|
||||
return expired ? tecEXPIRED : tecNO_AUTH;
|
||||
}
|
||||
if (!authorizedByDomain && maybeDomainID.has_value())
|
||||
if (!authorizedByDomain && static_cast<bool>(maybeDomainID))
|
||||
{
|
||||
// Found an MPToken but the account is not authorized and we expect
|
||||
// it to have been authorized by the domain. This could be because the
|
||||
@@ -448,7 +438,7 @@ enforceMPTokenAuthorization(
|
||||
// We found an MPToken, but sfDomainID is not set, so this is a classic
|
||||
// MPToken which requires authorization by the token issuer.
|
||||
XRPL_ASSERT(
|
||||
sleToken != nullptr && !maybeDomainID.has_value(),
|
||||
sleToken != nullptr && !static_cast<bool>(maybeDomainID),
|
||||
"xrpl::enforceMPTokenAuthorization : found MPToken");
|
||||
if (sleToken->isFlag(lsfMPTAuthorized))
|
||||
return tesSUCCESS;
|
||||
@@ -460,7 +450,7 @@ enforceMPTokenAuthorization(
|
||||
// Found an MPToken, authorized by the domain. Ignore authorization flag
|
||||
// lsfMPTAuthorized because it is meaningless. Return tesSUCCESS
|
||||
XRPL_ASSERT(
|
||||
maybeDomainID.has_value(),
|
||||
static_cast<bool>(maybeDomainID),
|
||||
"xrpl::enforceMPTokenAuthorization : found MPToken for domain");
|
||||
return tesSUCCESS;
|
||||
}
|
||||
@@ -469,13 +459,11 @@ enforceMPTokenAuthorization(
|
||||
// Could not find MPToken but there should be one because we are
|
||||
// authorized by domain. Proceed to create it, then return tesSUCCESS
|
||||
XRPL_ASSERT(
|
||||
maybeDomainID.has_value() && sleToken == nullptr,
|
||||
static_cast<bool>(maybeDomainID) && sleToken == nullptr,
|
||||
"xrpl::enforceMPTokenAuthorization : new MPToken for domain");
|
||||
if (auto const err = authorizeMPToken(
|
||||
view,
|
||||
priorBalance, // priorBalance
|
||||
mptIssuanceID, // mptIssuanceID
|
||||
account, // account
|
||||
priorBalance, // priorBalance
|
||||
account, // account
|
||||
j);
|
||||
!isTesSuccess(err))
|
||||
return err;
|
||||
@@ -489,26 +477,39 @@ enforceMPTokenAuthorization(
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
template <typename ViewT>
|
||||
TER
|
||||
canTransfer(
|
||||
ReadView const& view,
|
||||
MPTIssue const& mptIssue,
|
||||
AccountID const& from,
|
||||
AccountID const& to)
|
||||
MPTokenIssuance<ViewT>::canTransfer(AccountID const& from, AccountID const& to) const
|
||||
{
|
||||
auto const mptID = keylet::mptIssuance(mptIssue.getMptID());
|
||||
auto const sleIssuance = view.read(mptID);
|
||||
if (!sleIssuance)
|
||||
if (!this->exists())
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
if (!sleIssuance->isFlag(lsfMPTCanTransfer))
|
||||
if (!this->sle_->isFlag(lsfMPTCanTransfer))
|
||||
{
|
||||
if (from != (*sleIssuance)[sfIssuer] && to != (*sleIssuance)[sfIssuer])
|
||||
if (from != (*this->sle_)[sfIssuer] && to != (*this->sle_)[sfIssuer])
|
||||
return TER{tecNO_AUTH};
|
||||
}
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
template <typename ViewT>
|
||||
bool
|
||||
MPTokenIssuance<ViewT>::requiresAuth() const
|
||||
{
|
||||
if (!this->exists())
|
||||
return false;
|
||||
return this->sle_->isFlag(lsfMPTRequireAuth);
|
||||
}
|
||||
|
||||
template <typename ViewT>
|
||||
bool
|
||||
MPTokenIssuance<ViewT>::canClawback() const
|
||||
{
|
||||
if (!this->exists())
|
||||
return false;
|
||||
return this->sle_->isFlag(lsfMPTCanClawback);
|
||||
}
|
||||
|
||||
TER
|
||||
canTrade(ReadView const& view, Asset const& asset)
|
||||
{
|
||||
@@ -524,13 +525,17 @@ canTrade(ReadView const& view, Asset const& asset)
|
||||
});
|
||||
}
|
||||
|
||||
template <typename ViewT>
|
||||
TER
|
||||
lockEscrowMPT(ApplyView& view, AccountID const& sender, STAmount const& amount, beast::Journal j)
|
||||
MPTokenIssuance<ViewT>::lockEscrow(
|
||||
AccountID const& sender,
|
||||
STAmount const& amount,
|
||||
beast::Journal j)
|
||||
requires kIsWritable
|
||||
{
|
||||
auto const mptIssue = amount.get<MPTIssue>();
|
||||
auto const mptID = keylet::mptIssuance(mptIssue.getMptID());
|
||||
auto sleIssuance = view.peek(mptID);
|
||||
if (!sleIssuance)
|
||||
ApplyView& view = this->applyView();
|
||||
auto const& mptIssue = mptIssue_;
|
||||
if (!this->exists())
|
||||
{ // LCOV_EXCL_START
|
||||
JLOG(j.error()) << "lockEscrowMPT: MPT issuance not found for " << mptIssue.getMptID();
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
@@ -545,7 +550,7 @@ lockEscrowMPT(ApplyView& view, AccountID const& sender, STAmount const& amount,
|
||||
// 1. Decrease the MPT Holder MPTAmount
|
||||
// 2. Increase the MPT Holder EscrowedAmount
|
||||
{
|
||||
auto const mptokenID = keylet::mptoken(mptID.key, sender);
|
||||
auto const mptokenID = keylet::mptoken(mptID_, sender);
|
||||
auto sle = view.peek(mptokenID);
|
||||
if (!sle)
|
||||
{ // LCOV_EXCL_START
|
||||
@@ -591,7 +596,7 @@ lockEscrowMPT(ApplyView& view, AccountID const& sender, STAmount const& amount,
|
||||
// 1. Increase the Issuance EscrowedAmount
|
||||
// 2. DO NOT change the Issuance OutstandingAmount
|
||||
{
|
||||
uint64_t const issuanceEscrowed = (*sleIssuance)[~sfLockedAmount].valueOr(0);
|
||||
uint64_t const issuanceEscrowed = (*this->sle_)[~sfLockedAmount].valueOr(0);
|
||||
auto const pay = amount.mpt().value();
|
||||
|
||||
// Overflow check for addition
|
||||
@@ -603,39 +608,39 @@ lockEscrowMPT(ApplyView& view, AccountID const& sender, STAmount const& amount,
|
||||
return tecINTERNAL;
|
||||
} // LCOV_EXCL_STOP
|
||||
|
||||
if (sleIssuance->isFieldPresent(sfLockedAmount))
|
||||
if (this->sle_->isFieldPresent(sfLockedAmount))
|
||||
{
|
||||
(*sleIssuance)[sfLockedAmount] += pay;
|
||||
(*this->sle_)[sfLockedAmount] += pay;
|
||||
}
|
||||
else
|
||||
{
|
||||
sleIssuance->setFieldU64(sfLockedAmount, pay);
|
||||
this->sle_->setFieldU64(sfLockedAmount, pay);
|
||||
}
|
||||
|
||||
view.update(sleIssuance);
|
||||
this->update();
|
||||
}
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
template <typename ViewT>
|
||||
TER
|
||||
unlockEscrowMPT(
|
||||
ApplyView& view,
|
||||
MPTokenIssuance<ViewT>::unlockEscrow(
|
||||
AccountID const& sender,
|
||||
AccountID const& receiver,
|
||||
STAmount const& netAmount,
|
||||
STAmount const& grossAmount,
|
||||
beast::Journal j)
|
||||
requires kIsWritable
|
||||
{
|
||||
ApplyView& view = this->applyView();
|
||||
if (!view.rules().enabled(fixTokenEscrowV1))
|
||||
{
|
||||
XRPL_ASSERT(netAmount == grossAmount, "xrpl::unlockEscrowMPT : netAmount == grossAmount");
|
||||
}
|
||||
|
||||
auto const& issuer = netAmount.getIssuer();
|
||||
auto const& mptIssue = netAmount.get<MPTIssue>();
|
||||
auto const mptID = keylet::mptIssuance(mptIssue.getMptID());
|
||||
auto sleIssuance = view.peek(mptID);
|
||||
if (!sleIssuance)
|
||||
auto const& mptIssue = mptIssue_;
|
||||
if (!this->exists())
|
||||
{ // LCOV_EXCL_START
|
||||
JLOG(j.error()) << "unlockEscrowMPT: MPT issuance not found for " << mptIssue.getMptID();
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
@@ -643,14 +648,14 @@ unlockEscrowMPT(
|
||||
|
||||
// Decrease the Issuance EscrowedAmount
|
||||
{
|
||||
if (!sleIssuance->isFieldPresent(sfLockedAmount))
|
||||
if (!this->sle_->isFieldPresent(sfLockedAmount))
|
||||
{ // LCOV_EXCL_START
|
||||
JLOG(j.error()) << "unlockEscrowMPT: no locked amount in issuance for "
|
||||
<< mptIssue.getMptID();
|
||||
return tecINTERNAL;
|
||||
} // LCOV_EXCL_STOP
|
||||
|
||||
auto const locked = sleIssuance->getFieldU64(sfLockedAmount);
|
||||
auto const locked = this->sle_->getFieldU64(sfLockedAmount);
|
||||
auto const redeem = grossAmount.mpt().value();
|
||||
|
||||
// Underflow check for subtraction
|
||||
@@ -664,19 +669,19 @@ unlockEscrowMPT(
|
||||
auto const newLocked = locked - redeem;
|
||||
if (newLocked == 0)
|
||||
{
|
||||
sleIssuance->makeFieldAbsent(sfLockedAmount);
|
||||
this->sle_->makeFieldAbsent(sfLockedAmount);
|
||||
}
|
||||
else
|
||||
{
|
||||
sleIssuance->setFieldU64(sfLockedAmount, newLocked);
|
||||
this->sle_->setFieldU64(sfLockedAmount, newLocked);
|
||||
}
|
||||
view.update(sleIssuance);
|
||||
this->update();
|
||||
}
|
||||
|
||||
if (issuer != receiver)
|
||||
{
|
||||
// Increase the MPT Holder MPTAmount
|
||||
auto const mptokenID = keylet::mptoken(mptID.key, receiver);
|
||||
auto const mptokenID = keylet::mptoken(mptID_, receiver);
|
||||
auto sle = view.peek(mptokenID);
|
||||
if (!sle)
|
||||
{ // LCOV_EXCL_START
|
||||
@@ -701,7 +706,7 @@ unlockEscrowMPT(
|
||||
else
|
||||
{
|
||||
// Decrease the Issuance OutstandingAmount
|
||||
auto const outstanding = sleIssuance->getFieldU64(sfOutstandingAmount);
|
||||
auto const outstanding = this->sle_->getFieldU64(sfOutstandingAmount);
|
||||
auto const redeem = netAmount.mpt().value();
|
||||
|
||||
// Underflow check for subtraction
|
||||
@@ -712,8 +717,8 @@ unlockEscrowMPT(
|
||||
return tecINTERNAL;
|
||||
} // LCOV_EXCL_STOP
|
||||
|
||||
sleIssuance->setFieldU64(sfOutstandingAmount, outstanding - redeem);
|
||||
view.update(sleIssuance);
|
||||
this->sle_->setFieldU64(sfOutstandingAmount, outstanding - redeem);
|
||||
this->update();
|
||||
}
|
||||
|
||||
if (issuer == sender)
|
||||
@@ -723,7 +728,7 @@ unlockEscrowMPT(
|
||||
return tecINTERNAL;
|
||||
} // LCOV_EXCL_STOP
|
||||
// Decrease the MPT Holder EscrowedAmount
|
||||
auto const mptokenID = keylet::mptoken(mptID.key, sender);
|
||||
auto const mptokenID = keylet::mptoken(mptID_, sender);
|
||||
auto sle = view.peek(mptokenID);
|
||||
if (!sle)
|
||||
{ // LCOV_EXCL_START
|
||||
@@ -766,7 +771,7 @@ unlockEscrowMPT(
|
||||
auto const diff = grossAmount.mpt().value() - netAmount.mpt().value();
|
||||
if (diff != 0)
|
||||
{
|
||||
auto const outstanding = sleIssuance->getFieldU64(sfOutstandingAmount);
|
||||
auto const outstanding = this->sle_->getFieldU64(sfOutstandingAmount);
|
||||
// Underflow check for subtraction
|
||||
if (!canSubtract(STAmount(mptIssue, outstanding), STAmount(mptIssue, diff)))
|
||||
{ // LCOV_EXCL_START
|
||||
@@ -775,20 +780,19 @@ unlockEscrowMPT(
|
||||
return tecINTERNAL;
|
||||
} // LCOV_EXCL_STOP
|
||||
|
||||
sleIssuance->setFieldU64(sfOutstandingAmount, outstanding - diff);
|
||||
view.update(sleIssuance);
|
||||
this->sle_->setFieldU64(sfOutstandingAmount, outstanding - diff);
|
||||
this->update();
|
||||
}
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
template <typename ViewT>
|
||||
TER
|
||||
createMPToken(
|
||||
ApplyView& view,
|
||||
MPTID const& mptIssuanceID,
|
||||
AccountID const& account,
|
||||
std::uint32_t const flags)
|
||||
MPTokenIssuance<ViewT>::createMPToken(AccountID const& account, std::uint32_t flags)
|
||||
requires kIsWritable
|
||||
{
|
||||
auto const mptokenKey = keylet::mptoken(mptIssuanceID, account);
|
||||
ApplyView& view = this->applyView();
|
||||
auto const mptokenKey = keylet::mptoken(mptID_, account);
|
||||
|
||||
auto const ownerNode =
|
||||
view.dirInsert(keylet::ownerDir(account), mptokenKey, describeOwnerDir(account));
|
||||
@@ -798,7 +802,7 @@ createMPToken(
|
||||
|
||||
auto mptoken = std::make_shared<SLE>(mptokenKey);
|
||||
(*mptoken)[sfAccount] = account;
|
||||
(*mptoken)[sfMPTokenIssuanceID] = mptIssuanceID;
|
||||
(*mptoken)[sfMPTokenIssuanceID] = mptID_;
|
||||
(*mptoken)[sfFlags] = flags;
|
||||
(*mptoken)[sfOwnerNode] = *ownerNode;
|
||||
|
||||
@@ -807,31 +811,28 @@ createMPToken(
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
template <typename ViewT>
|
||||
TER
|
||||
checkCreateMPT(
|
||||
xrpl::ApplyView& view,
|
||||
xrpl::MPTIssue const& mptIssue,
|
||||
xrpl::AccountID const& holder,
|
||||
beast::Journal j)
|
||||
MPTokenIssuance<ViewT>::checkCreateMPT(AccountID const& holder, beast::Journal j)
|
||||
requires kIsWritable
|
||||
{
|
||||
if (mptIssue.getIssuer() == holder)
|
||||
ApplyView& view = this->applyView();
|
||||
if (getIssuer() == holder)
|
||||
return tesSUCCESS;
|
||||
|
||||
auto const mptIssuanceID = keylet::mptIssuance(mptIssue.getMptID());
|
||||
auto const mptokenID = keylet::mptoken(mptIssuanceID.key, holder);
|
||||
auto const mptokenID = keylet::mptoken(mptID_, holder);
|
||||
if (!view.exists(mptokenID))
|
||||
{
|
||||
if (auto const err = createMPToken(view, mptIssue.getMptID(), holder, 0);
|
||||
!isTesSuccess(err))
|
||||
if (auto const err = createMPToken(holder, 0); !isTesSuccess(err))
|
||||
{
|
||||
return err;
|
||||
}
|
||||
auto const sleAcct = view.peek(keylet::account(holder));
|
||||
if (!sleAcct)
|
||||
auto acct = WAccountRoot(holder, view, j);
|
||||
if (!acct)
|
||||
{
|
||||
return tecINTERNAL;
|
||||
}
|
||||
adjustOwnerCount(view, sleAcct, 1, j);
|
||||
acct.adjustOwnerCount(1);
|
||||
}
|
||||
return tesSUCCESS;
|
||||
}
|
||||
@@ -850,13 +851,22 @@ availableMPTAmount(SLE const& sleIssuance)
|
||||
return max - outstanding;
|
||||
}
|
||||
|
||||
template <typename ViewT>
|
||||
std::int64_t
|
||||
availableMPTAmount(ReadView const& view, MPTID const& mptID)
|
||||
MPTokenIssuance<ViewT>::maxAmount() const
|
||||
{
|
||||
auto const sle = view.read(keylet::mptIssuance(mptID));
|
||||
if (!sle)
|
||||
if (!this->exists())
|
||||
Throw<std::runtime_error>(transHuman(tecINTERNAL));
|
||||
return availableMPTAmount(*sle);
|
||||
return maxMPTAmount(*this->sle_);
|
||||
}
|
||||
|
||||
template <typename ViewT>
|
||||
std::int64_t
|
||||
MPTokenIssuance<ViewT>::availableAmount() const
|
||||
{
|
||||
if (!this->exists())
|
||||
Throw<std::runtime_error>(transHuman(tecINTERNAL));
|
||||
return availableMPTAmount(*this->sle_);
|
||||
}
|
||||
|
||||
bool
|
||||
@@ -872,23 +882,41 @@ isMPTOverflow(
|
||||
return (sendAmount > maximumAmount || outstandingAmount > (limit - sendAmount));
|
||||
}
|
||||
|
||||
template <typename ViewT>
|
||||
STAmount
|
||||
issuerFundsToSelfIssue(ReadView const& view, MPTIssue const& issue)
|
||||
MPTokenIssuance<ViewT>::issuerFundsToSelfIssue() const
|
||||
{
|
||||
STAmount amount{issue};
|
||||
STAmount amount{mptIssue_};
|
||||
|
||||
auto const sle = view.read(keylet::mptIssuance(issue));
|
||||
if (!sle)
|
||||
if (!this->exists())
|
||||
return amount;
|
||||
auto const available = availableMPTAmount(*sle);
|
||||
return view.balanceHookSelfIssueMPT(issue, available);
|
||||
auto const available = availableMPTAmount(*this->sle_);
|
||||
return this->readView().balanceHookSelfIssueMPT(mptIssue_, available);
|
||||
}
|
||||
|
||||
template <typename ViewT>
|
||||
void
|
||||
issuerSelfDebitHookMPT(ApplyView& view, MPTIssue const& issue, std::uint64_t amount)
|
||||
MPTokenIssuance<ViewT>::issuerSelfDebitHook(std::uint64_t amount)
|
||||
requires kIsWritable
|
||||
{
|
||||
auto const available = availableMPTAmount(view, issue);
|
||||
view.issuerSelfDebitHookMPT(issue, amount, available);
|
||||
auto const available = availableAmount();
|
||||
this->applyView().issuerSelfDebitHookMPT(mptIssue_, amount, available);
|
||||
}
|
||||
|
||||
[[nodiscard]] TER
|
||||
deleteAMMMPToken(
|
||||
ApplyView& view,
|
||||
std::shared_ptr<SLE> sleMpt,
|
||||
AccountID const& ammAccountID,
|
||||
beast::Journal j)
|
||||
{
|
||||
if (!view.dirRemove(
|
||||
keylet::ownerDir(ammAccountID), (*sleMpt)[sfOwnerNode], sleMpt->key(), false))
|
||||
return tefBAD_LEDGER; // LCOV_EXCL_LINE
|
||||
|
||||
view.erase(sleMpt);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
static TER
|
||||
@@ -952,4 +980,8 @@ checkMPTTxAllowed(
|
||||
return checkMPTAllowed(view, txType, asset, accountID);
|
||||
}
|
||||
|
||||
// Explicit template instantiations
|
||||
template class MPTokenIssuance<ReadView>;
|
||||
template class MPTokenIssuance<ApplyView>;
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <xrpl/basics/Slice.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/basics/contract.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
@@ -269,11 +270,8 @@ insertToken(ApplyView& view, AccountID owner, STObject&& nft)
|
||||
// the NFT.
|
||||
std::shared_ptr<SLE> const page =
|
||||
getPageForToken(view, owner, nft[sfNFTokenID], [](ApplyView& view, AccountID const& owner) {
|
||||
adjustOwnerCount(
|
||||
view,
|
||||
view.peek(keylet::account(owner)),
|
||||
1,
|
||||
beast::Journal{beast::Journal::getNullSink()});
|
||||
WAccountRoot wrappedOwner(owner, view);
|
||||
wrappedOwner.adjustOwnerCount(1);
|
||||
});
|
||||
|
||||
if (!page)
|
||||
@@ -425,11 +423,8 @@ removeToken(
|
||||
|
||||
if (cnt != 0)
|
||||
{
|
||||
adjustOwnerCount(
|
||||
view,
|
||||
view.peek(keylet::account(owner)),
|
||||
cnt,
|
||||
beast::Journal{beast::Journal::getNullSink()});
|
||||
WAccountRoot wrappedOwner(owner, view);
|
||||
wrappedOwner.adjustOwnerCount(cnt);
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
@@ -464,11 +459,8 @@ removeToken(
|
||||
curr->makeFieldAbsent(sfPreviousPageMin);
|
||||
}
|
||||
|
||||
adjustOwnerCount(
|
||||
view,
|
||||
view.peek(keylet::account(owner)),
|
||||
-1,
|
||||
beast::Journal{beast::Journal::getNullSink()});
|
||||
WAccountRoot wrappedOwner(owner, view);
|
||||
wrappedOwner.adjustOwnerCount(-1);
|
||||
|
||||
view.update(curr);
|
||||
view.erase(prev);
|
||||
@@ -523,11 +515,8 @@ removeToken(
|
||||
view.peek(Keylet(ltNFTOKEN_PAGE, next->key()))))
|
||||
cnt++;
|
||||
|
||||
adjustOwnerCount(
|
||||
view,
|
||||
view.peek(keylet::account(owner)),
|
||||
-1 * cnt,
|
||||
beast::Journal{beast::Journal::getNullSink()});
|
||||
WAccountRoot wrappedOwner(owner, view);
|
||||
wrappedOwner.adjustOwnerCount(-1 * cnt);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
@@ -643,8 +632,8 @@ deleteTokenOffer(ApplyView& view, std::shared_ptr<SLE> const& offer)
|
||||
false))
|
||||
return false;
|
||||
|
||||
adjustOwnerCount(
|
||||
view, view.peek(keylet::account(owner)), -1, beast::Journal{beast::Journal::getNullSink()});
|
||||
WAccountRoot wrappedOwner(owner, view);
|
||||
wrappedOwner.adjustOwnerCount(-1);
|
||||
|
||||
view.erase(offer);
|
||||
return true;
|
||||
@@ -851,20 +840,20 @@ tokenOfferCreatePreclaim(
|
||||
return tecNO_LINE;
|
||||
}
|
||||
|
||||
if (isFrozen(view, nftIssuer, amount.get<Issue>().currency, amount.getIssuer()))
|
||||
if (IOUIssuance(view, amount.get<Issue>()).isFrozen(nftIssuer))
|
||||
return tecFROZEN;
|
||||
}
|
||||
|
||||
if (nftIssuer != acctID && ((nftFlags & nft::kFlagTransferable) == 0))
|
||||
{
|
||||
auto const root = view.read(keylet::account(nftIssuer));
|
||||
XRPL_ASSERT(root, "xrpl::nft::tokenOfferCreatePreclaim : non-null account");
|
||||
AccountRoot const acctRoot(nftIssuer, view);
|
||||
XRPL_ASSERT(acctRoot, "xrpl::nft::tokenOfferCreatePreclaim : non-null account");
|
||||
|
||||
if (auto minter = (*root)[~sfNFTokenMinter]; minter != acctID)
|
||||
if (auto minter = acctRoot->at(~sfNFTokenMinter); minter != acctID)
|
||||
return tefNFTOKEN_IS_NOT_TRANSFERABLE;
|
||||
}
|
||||
|
||||
if (isFrozen(view, acctID, amount.get<Issue>().currency, amount.getIssuer()))
|
||||
if (IOUIssuance(view, amount.get<Issue>()).isFrozen(acctID))
|
||||
return tecFROZEN;
|
||||
|
||||
// If this is an offer to buy the token, the account must have the
|
||||
@@ -882,26 +871,26 @@ tokenOfferCreatePreclaim(
|
||||
{
|
||||
// If a destination is specified, the destination must already be in
|
||||
// the ledger.
|
||||
auto const sleDst = view.read(keylet::account(*dest));
|
||||
AccountRoot const acctDst(*dest, view);
|
||||
|
||||
if (!sleDst)
|
||||
if (!acctDst)
|
||||
return tecNO_DST;
|
||||
|
||||
// check if the destination has disallowed incoming offers
|
||||
if (sleDst->isFlag(lsfDisallowIncomingNFTokenOffer))
|
||||
if ((acctDst->getFlags() & lsfDisallowIncomingNFTokenOffer) != 0u)
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
|
||||
if (owner)
|
||||
{
|
||||
auto const sleOwner = view.read(keylet::account(*owner));
|
||||
AccountRoot const acctOwner(*owner, view);
|
||||
|
||||
// defensively check
|
||||
// it should not be possible to specify owner that doesn't exist
|
||||
if (!sleOwner)
|
||||
if (!acctOwner)
|
||||
return tecNO_TARGET;
|
||||
|
||||
if (sleOwner->isFlag(lsfDisallowIncomingNFTokenOffer))
|
||||
if ((acctOwner->getFlags() & lsfDisallowIncomingNFTokenOffer) != 0u)
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
|
||||
@@ -933,9 +922,8 @@ tokenOfferCreateApply(
|
||||
beast::Journal j,
|
||||
std::uint32_t txFlags)
|
||||
{
|
||||
Keylet const acctKeylet = keylet::account(acctID);
|
||||
if (auto const acct = view.read(acctKeylet);
|
||||
priorBalance < view.fees().accountReserve((*acct)[sfOwnerCount] + 1))
|
||||
AccountRoot const acctRoot(acctID, view);
|
||||
if (priorBalance < view.fees().accountReserve(acctRoot->getFieldU32(sfOwnerCount) + 1))
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
|
||||
auto const offerID = keylet::nftoffer(acctID, seqProxy.value());
|
||||
@@ -987,7 +975,8 @@ tokenOfferCreateApply(
|
||||
}
|
||||
|
||||
// Update owner count.
|
||||
adjustOwnerCount(view, view.peek(acctKeylet), 1, j);
|
||||
WAccountRoot wrappedOwner(acctID, view);
|
||||
wrappedOwner.adjustOwnerCount(1);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
@@ -1004,8 +993,8 @@ checkTrustlineAuthorized(
|
||||
|
||||
if (view.rules().enabled(fixEnforceNFTokenTrustlineV2))
|
||||
{
|
||||
auto const issuerAccount = view.read(keylet::account(issue.account));
|
||||
if (!issuerAccount)
|
||||
AccountRoot const acctIssuer(issue.account, view);
|
||||
if (!acctIssuer)
|
||||
{
|
||||
JLOG(j.debug()) << "xrpl::nft::checkTrustlineAuthorized: can't "
|
||||
"receive IOUs from non-existent issuer: "
|
||||
@@ -1022,7 +1011,7 @@ checkTrustlineAuthorized(
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
if (issuerAccount->isFlag(lsfRequireAuth))
|
||||
if (acctIssuer->isFlag(lsfRequireAuth))
|
||||
{
|
||||
auto const trustLine = view.read(keylet::line(id, issue.account, issue.currency));
|
||||
|
||||
@@ -1056,8 +1045,8 @@ checkTrustlineDeepFrozen(
|
||||
|
||||
if (view.rules().enabled(featureDeepFreeze))
|
||||
{
|
||||
auto const issuerAccount = view.read(keylet::account(issue.account));
|
||||
if (!issuerAccount)
|
||||
AccountRoot const acctIssuer(issue.account, view);
|
||||
if (!acctIssuer)
|
||||
{
|
||||
JLOG(j.debug()) << "xrpl::nft::checkTrustlineDeepFrozen: can't "
|
||||
"receive IOUs from non-existent issuer: "
|
||||
|
||||
@@ -57,7 +57,8 @@ offerDelete(ApplyView& view, std::shared_ptr<SLE> const& sle, beast::Journal j)
|
||||
}
|
||||
}
|
||||
|
||||
adjustOwnerCount(view, view.peek(keylet::account(owner)), -1, j);
|
||||
WAccountRoot wrappedOwner(owner, view, j);
|
||||
wrappedOwner.adjustOwnerCount(-1);
|
||||
|
||||
view.erase(sle);
|
||||
|
||||
|
||||
@@ -50,15 +50,16 @@ closeChannel(
|
||||
}
|
||||
|
||||
// Transfer amount back to owner, decrement owner count
|
||||
auto const sle = view.peek(keylet::account(src));
|
||||
if (!sle)
|
||||
WAccountRoot wrappedOwner(src, view, j);
|
||||
if (!wrappedOwner)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
XRPL_ASSERT(
|
||||
(*slep)[sfAmount] >= (*slep)[sfBalance], "xrpl::closeChannel : minimum channel amount");
|
||||
(*sle)[sfBalance] = (*sle)[sfBalance] + (*slep)[sfAmount] - (*slep)[sfBalance];
|
||||
adjustOwnerCount(view, sle, -1, j);
|
||||
view.update(sle);
|
||||
(*wrappedOwner)[sfBalance] =
|
||||
(*wrappedOwner)[sfBalance] + (*slep)[sfAmount] - (*slep)[sfBalance];
|
||||
wrappedOwner.adjustOwnerCount(-1);
|
||||
wrappedOwner.update();
|
||||
|
||||
// Remove PayChan from ledger
|
||||
view.erase(slep);
|
||||
|
||||
@@ -38,16 +38,14 @@ namespace xrpl {
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
template <typename ViewT>
|
||||
STAmount
|
||||
creditLimit(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
AccountID const& issuer,
|
||||
Currency const& currency)
|
||||
IOUIssuance<ViewT>::creditLimit(AccountID const& account) const
|
||||
{
|
||||
STAmount result(Issue{currency, account});
|
||||
auto const& issuer = this->id();
|
||||
STAmount result(Issue{currency_, account});
|
||||
|
||||
auto sleRippleState = view.read(keylet::line(account, issuer, currency));
|
||||
auto sleRippleState = this->readView().read(keylet::line(account, issuer, currency_));
|
||||
|
||||
if (sleRippleState)
|
||||
{
|
||||
@@ -57,28 +55,27 @@ creditLimit(
|
||||
|
||||
XRPL_ASSERT(result.getIssuer() == account, "xrpl::creditLimit : result issuer match");
|
||||
XRPL_ASSERT(
|
||||
result.get<Issue>().currency == currency,
|
||||
result.get<Issue>().currency == currency_,
|
||||
"xrpl::creditLimit : result currency "
|
||||
"match");
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename ViewT>
|
||||
IOUAmount
|
||||
creditLimit2(ReadView const& v, AccountID const& acc, AccountID const& iss, Currency const& cur)
|
||||
IOUIssuance<ViewT>::creditLimit2(AccountID const& account) const
|
||||
{
|
||||
return toAmount<IOUAmount>(creditLimit(v, acc, iss, cur));
|
||||
return toAmount<IOUAmount>(creditLimit(account));
|
||||
}
|
||||
|
||||
template <typename ViewT>
|
||||
STAmount
|
||||
creditBalance(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
AccountID const& issuer,
|
||||
Currency const& currency)
|
||||
IOUIssuance<ViewT>::creditBalance(AccountID const& account) const
|
||||
{
|
||||
STAmount result(Issue{currency, account});
|
||||
auto const& issuer = this->id();
|
||||
STAmount result(Issue{currency_, account});
|
||||
|
||||
auto sleRippleState = view.read(keylet::line(account, issuer, currency));
|
||||
auto sleRippleState = this->readView().read(keylet::line(account, issuer, currency_));
|
||||
|
||||
if (sleRippleState)
|
||||
{
|
||||
@@ -90,7 +87,7 @@ creditBalance(
|
||||
|
||||
XRPL_ASSERT(result.getIssuer() == account, "xrpl::creditBalance : result issuer match");
|
||||
XRPL_ASSERT(
|
||||
result.get<Issue>().currency == currency,
|
||||
result.get<Issue>().currency == currency_,
|
||||
"xrpl::creditBalance : result currency "
|
||||
"match");
|
||||
return result;
|
||||
@@ -102,19 +99,35 @@ creditBalance(
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
template <typename ViewT>
|
||||
bool
|
||||
isIndividualFrozen(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
Currency const& currency,
|
||||
AccountID const& issuer)
|
||||
IOUIssuance<ViewT>::requiresAuth() const
|
||||
{
|
||||
if (isXRP(currency))
|
||||
if (!this->exists())
|
||||
return false;
|
||||
return this->sle_->isFlag(lsfRequireAuth);
|
||||
}
|
||||
|
||||
template <typename ViewT>
|
||||
bool
|
||||
IOUIssuance<ViewT>::canClawback() const
|
||||
{
|
||||
if (!this->exists())
|
||||
return false;
|
||||
return this->sle_->isFlag(lsfAllowTrustLineClawback);
|
||||
}
|
||||
|
||||
template <typename ViewT>
|
||||
bool
|
||||
IOUIssuance<ViewT>::isIndividualFrozen(AccountID const& account) const
|
||||
{
|
||||
if (isXRP(currency_))
|
||||
return false;
|
||||
auto const& issuer = this->id();
|
||||
if (issuer != account)
|
||||
{
|
||||
// Check if the issuer froze the line
|
||||
auto const sle = view.read(keylet::line(account, issuer, currency));
|
||||
auto const sle = this->readView().read(keylet::line(account, issuer, currency_));
|
||||
if (sle && sle->isFlag((issuer > account) ? lsfHighFreeze : lsfLowFreeze))
|
||||
return true;
|
||||
}
|
||||
@@ -123,46 +136,41 @@ isIndividualFrozen(
|
||||
|
||||
// Can the specified account spend the specified currency issued by
|
||||
// the specified issuer or does the freeze flag prohibit it?
|
||||
template <typename ViewT>
|
||||
bool
|
||||
isFrozen(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
Currency const& currency,
|
||||
AccountID const& issuer)
|
||||
IOUIssuance<ViewT>::isFrozen(AccountID const& account, int /*depth*/) const
|
||||
{
|
||||
if (isXRP(currency))
|
||||
if (isXRP(currency_))
|
||||
return false;
|
||||
auto sle = view.read(keylet::account(issuer));
|
||||
if (sle && sle->isFlag(lsfGlobalFreeze))
|
||||
if (this->isGlobalFrozen())
|
||||
return true;
|
||||
auto const& issuer = this->id();
|
||||
if (issuer != account)
|
||||
{
|
||||
// Check if the issuer froze the line
|
||||
sle = view.read(keylet::line(account, issuer, currency));
|
||||
if (sle && sle->isFlag((issuer > account) ? lsfHighFreeze : lsfLowFreeze))
|
||||
auto const sleLine = this->readView().read(keylet::line(account, issuer, currency_));
|
||||
if (sleLine && sleLine->isFlag((issuer > account) ? lsfHighFreeze : lsfLowFreeze))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename ViewT>
|
||||
bool
|
||||
isDeepFrozen(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
Currency const& currency,
|
||||
AccountID const& issuer)
|
||||
IOUIssuance<ViewT>::isDeepFrozen(AccountID const& account) const
|
||||
{
|
||||
if (isXRP(currency))
|
||||
if (isXRP(currency_))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
auto const& issuer = this->id();
|
||||
if (issuer == account)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
auto const sle = view.read(keylet::line(account, issuer, currency));
|
||||
auto const sle = this->readView().read(keylet::line(account, issuer, currency_));
|
||||
if (!sle)
|
||||
{
|
||||
return false;
|
||||
@@ -177,14 +185,14 @@ isDeepFrozen(
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
template <typename ViewT>
|
||||
TER
|
||||
trustCreate(
|
||||
ApplyView& view,
|
||||
IOUIssuance<ViewT>::trustCreate(
|
||||
bool const bSrcHigh,
|
||||
AccountID const& uSrcAccountID,
|
||||
AccountID const& uDstAccountID,
|
||||
uint256 const& uIndex, // ripple state entry
|
||||
SLE::ref sleAccount, // the account being set.
|
||||
WAccountRoot& wrappedAcct, // the account being set.
|
||||
bool const bAuth, // authorize account.
|
||||
bool const bNoRipple, // others cannot ripple through
|
||||
bool const bFreeze, // funds cannot leave
|
||||
@@ -196,7 +204,9 @@ trustCreate(
|
||||
std::uint32_t uQualityIn,
|
||||
std::uint32_t uQualityOut,
|
||||
beast::Journal j)
|
||||
requires kIsWritable
|
||||
{
|
||||
ApplyView& view = this->applyView();
|
||||
JLOG(j.trace()) << "trustCreate: " << to_string(uSrcAccountID) << ", "
|
||||
<< to_string(uDstAccountID) << ", " << saBalance.getFullText();
|
||||
|
||||
@@ -229,15 +239,15 @@ trustCreate(
|
||||
bool const bSetDst = saLimit.getIssuer() == uDstAccountID;
|
||||
bool const bSetHigh = bSrcHigh ^ bSetDst;
|
||||
|
||||
XRPL_ASSERT(sleAccount, "xrpl::trustCreate : non-null SLE");
|
||||
if (!sleAccount)
|
||||
XRPL_ASSERT(wrappedAcct, "xrpl::trustCreate : non-null SLE");
|
||||
if (!wrappedAcct)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
XRPL_ASSERT(
|
||||
sleAccount->getAccountID(sfAccount) == (bSetHigh ? uHighAccountID : uLowAccountID),
|
||||
wrappedAcct->getAccountID(sfAccount) == (bSetHigh ? uHighAccountID : uLowAccountID),
|
||||
"xrpl::trustCreate : matching account ID");
|
||||
auto const slePeer = view.peek(keylet::account(bSetHigh ? uLowAccountID : uHighAccountID));
|
||||
if (!slePeer)
|
||||
auto const peer = AccountRoot(bSetHigh ? uLowAccountID : uHighAccountID, view, j);
|
||||
if (!peer.exists())
|
||||
return tecNO_TARGET;
|
||||
|
||||
// Remember deletion hints.
|
||||
@@ -247,7 +257,7 @@ trustCreate(
|
||||
sleRippleState->setFieldAmount(bSetHigh ? sfHighLimit : sfLowLimit, saLimit);
|
||||
sleRippleState->setFieldAmount(
|
||||
bSetHigh ? sfLowLimit : sfHighLimit,
|
||||
STAmount(Issue{saBalance.get<Issue>().currency, bSetDst ? uSrcAccountID : uDstAccountID}));
|
||||
STAmount(Issue{currency_, bSetDst ? uSrcAccountID : uDstAccountID}));
|
||||
|
||||
if (uQualityIn != 0u)
|
||||
sleRippleState->setFieldU32(bSetHigh ? sfHighQualityIn : sfLowQualityIn, uQualityIn);
|
||||
@@ -274,14 +284,14 @@ trustCreate(
|
||||
uFlags |= (bSetHigh ? lsfHighDeepFreeze : lsfLowDeepFreeze);
|
||||
}
|
||||
|
||||
if (!slePeer->isFlag(lsfDefaultRipple))
|
||||
if ((peer->getFlags() & lsfDefaultRipple) == 0)
|
||||
{
|
||||
// The other side's default is no rippling
|
||||
uFlags |= (bSetHigh ? lsfLowNoRipple : lsfHighNoRipple);
|
||||
}
|
||||
|
||||
sleRippleState->setFieldU32(sfFlags, uFlags);
|
||||
adjustOwnerCount(view, sleAccount, 1, j);
|
||||
wrappedAcct.adjustOwnerCount(1);
|
||||
|
||||
// ONLY: Create ripple balance.
|
||||
sleRippleState->setFieldAmount(sfBalance, bSetHigh ? -saBalance : saBalance);
|
||||
@@ -342,8 +352,8 @@ updateTrustLine(
|
||||
if (!state)
|
||||
return false;
|
||||
|
||||
auto sle = view.peek(keylet::account(sender));
|
||||
if (!sle)
|
||||
WAccountRoot wrappedAcct(sender, view, j);
|
||||
if (!wrappedAcct)
|
||||
return false;
|
||||
|
||||
auto const senderReserveFlag = bSenderHigh ? lsfHighReserve : lsfLowReserve;
|
||||
@@ -358,7 +368,7 @@ updateTrustLine(
|
||||
// Sender is zero or negative.
|
||||
&& state->isFlag(senderReserveFlag)
|
||||
// Sender reserve is set.
|
||||
&& state->isFlag(senderNoRippleFlag) != sle->isFlag(lsfDefaultRipple) &&
|
||||
&& state->isFlag(senderNoRippleFlag) != wrappedAcct->isFlag(lsfDefaultRipple) &&
|
||||
!state->isFlag(senderFreezeFlag) &&
|
||||
!state->getFieldAmount(!bSenderHigh ? sfLowLimit : sfHighLimit)
|
||||
// Sender trust limit is 0.
|
||||
@@ -369,7 +379,7 @@ updateTrustLine(
|
||||
{
|
||||
// VFALCO Where is the line being deleted?
|
||||
// Clear the reserve of the sender, possibly delete the line!
|
||||
adjustOwnerCount(view, sle, -1, j);
|
||||
wrappedAcct.adjustOwnerCount(-1);
|
||||
|
||||
// Clear reserve flag.
|
||||
state->clearFlag(senderReserveFlag);
|
||||
@@ -381,29 +391,27 @@ updateTrustLine(
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename ViewT>
|
||||
TER
|
||||
issueIOU(
|
||||
ApplyView& view,
|
||||
AccountID const& account,
|
||||
STAmount const& amount,
|
||||
Issue const& issue,
|
||||
beast::Journal j)
|
||||
IOUIssuance<ViewT>::issue(AccountID const& account, STAmount const& amount, beast::Journal j)
|
||||
requires kIsWritable
|
||||
{
|
||||
ApplyView& view = this->applyView();
|
||||
auto const& issuerId = this->id();
|
||||
XRPL_ASSERT(
|
||||
!isXRP(account) && !isXRP(issue.account),
|
||||
"xrpl::issueIOU : neither account nor issuer is XRP");
|
||||
!isXRP(account) && !isXRP(issuerId), "xrpl::issueIOU : neither account nor issuer is XRP");
|
||||
|
||||
// Consistency check
|
||||
XRPL_ASSERT(issue == amount.get<Issue>(), "xrpl::issueIOU : matching issue");
|
||||
XRPL_ASSERT(this->getIssue() == amount.get<Issue>(), "xrpl::issueIOU : matching issue");
|
||||
|
||||
// Can't send to self!
|
||||
XRPL_ASSERT(issue.account != account, "xrpl::issueIOU : not issuer account");
|
||||
XRPL_ASSERT(issuerId != account, "xrpl::issueIOU : not issuer account");
|
||||
|
||||
JLOG(j.trace()) << "issueIOU: " << to_string(account) << ": " << amount.getFullText();
|
||||
|
||||
bool const bSenderHigh = issue.account > account;
|
||||
bool const bSenderHigh = issuerId > account;
|
||||
|
||||
auto const index = keylet::line(issue.account, account, issue.currency);
|
||||
auto const index = keylet::line(issuerId, account, currency_);
|
||||
|
||||
if (auto state = view.peek(index))
|
||||
{
|
||||
@@ -417,9 +425,9 @@ issueIOU(
|
||||
finalBalance -= amount;
|
||||
|
||||
auto const mustDelete =
|
||||
updateTrustLine(view, state, bSenderHigh, issue.account, startBalance, finalBalance, j);
|
||||
updateTrustLine(view, state, bSenderHigh, issuerId, startBalance, finalBalance, j);
|
||||
|
||||
view.creditHookIOU(issue.account, account, amount, startBalance);
|
||||
view.creditHookIOU(issuerId, account, amount, startBalance);
|
||||
|
||||
if (bSenderHigh)
|
||||
finalBalance.negate();
|
||||
@@ -431,11 +439,7 @@ issueIOU(
|
||||
if (mustDelete)
|
||||
{
|
||||
return trustDelete(
|
||||
view,
|
||||
state,
|
||||
bSenderHigh ? account : issue.account,
|
||||
bSenderHigh ? issue.account : account,
|
||||
j);
|
||||
view, state, bSenderHigh ? account : issuerId, bSenderHigh ? issuerId : account, j);
|
||||
}
|
||||
|
||||
view.update(state);
|
||||
@@ -446,21 +450,20 @@ issueIOU(
|
||||
// NIKB TODO: The limit uses the receiver's account as the issuer and
|
||||
// this is unnecessarily inefficient as copying which could be avoided
|
||||
// is now required. Consider available options.
|
||||
STAmount const limit(Issue{issue.currency, account});
|
||||
STAmount const limit(Issue{currency_, account});
|
||||
STAmount finalBalance = amount;
|
||||
|
||||
finalBalance.get<Issue>().account = noAccount();
|
||||
|
||||
auto const receiverAccount = view.peek(keylet::account(account));
|
||||
WAccountRoot receiverAccount(account, view, j);
|
||||
if (!receiverAccount)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
bool const noRipple = !receiverAccount->isFlag(lsfDefaultRipple);
|
||||
|
||||
return trustCreate(
|
||||
view,
|
||||
bSenderHigh,
|
||||
issue.account,
|
||||
issuerId,
|
||||
account,
|
||||
index.key,
|
||||
receiverAccount,
|
||||
@@ -475,29 +478,27 @@ issueIOU(
|
||||
j);
|
||||
}
|
||||
|
||||
template <typename ViewT>
|
||||
TER
|
||||
redeemIOU(
|
||||
ApplyView& view,
|
||||
AccountID const& account,
|
||||
STAmount const& amount,
|
||||
Issue const& issue,
|
||||
beast::Journal j)
|
||||
IOUIssuance<ViewT>::redeem(AccountID const& account, STAmount const& amount, beast::Journal j)
|
||||
requires kIsWritable
|
||||
{
|
||||
ApplyView& view = this->applyView();
|
||||
auto const& issuerId = this->id();
|
||||
XRPL_ASSERT(
|
||||
!isXRP(account) && !isXRP(issue.account),
|
||||
"xrpl::redeemIOU : neither account nor issuer is XRP");
|
||||
!isXRP(account) && !isXRP(issuerId), "xrpl::redeemIOU : neither account nor issuer is XRP");
|
||||
|
||||
// Consistency check
|
||||
XRPL_ASSERT(issue == amount.get<Issue>(), "xrpl::redeemIOU : matching issue");
|
||||
XRPL_ASSERT(this->getIssue() == amount.get<Issue>(), "xrpl::redeemIOU : matching issue");
|
||||
|
||||
// Can't send to self!
|
||||
XRPL_ASSERT(issue.account != account, "xrpl::redeemIOU : not issuer account");
|
||||
XRPL_ASSERT(issuerId != account, "xrpl::redeemIOU : not issuer account");
|
||||
|
||||
JLOG(j.trace()) << "redeemIOU: " << to_string(account) << ": " << amount.getFullText();
|
||||
|
||||
bool const bSenderHigh = account > issue.account;
|
||||
bool const bSenderHigh = account > issuerId;
|
||||
|
||||
if (auto state = view.peek(keylet::line(account, issue.account, issue.currency)))
|
||||
if (auto state = view.peek(keylet::line(account, issuerId, currency_)))
|
||||
{
|
||||
STAmount finalBalance = state->getFieldAmount(sfBalance);
|
||||
|
||||
@@ -511,7 +512,7 @@ redeemIOU(
|
||||
auto const mustDelete =
|
||||
updateTrustLine(view, state, bSenderHigh, account, startBalance, finalBalance, j);
|
||||
|
||||
view.creditHookIOU(account, issue.account, amount, startBalance);
|
||||
view.creditHookIOU(account, issuerId, amount, startBalance);
|
||||
|
||||
if (bSenderHigh)
|
||||
finalBalance.negate();
|
||||
@@ -524,11 +525,7 @@ redeemIOU(
|
||||
if (mustDelete)
|
||||
{
|
||||
return trustDelete(
|
||||
view,
|
||||
state,
|
||||
bSenderHigh ? issue.account : account,
|
||||
bSenderHigh ? account : issue.account,
|
||||
j);
|
||||
view, state, bSenderHigh ? issuerId : account, bSenderHigh ? account : issuerId, j);
|
||||
}
|
||||
|
||||
view.update(state);
|
||||
@@ -552,25 +549,26 @@ redeemIOU(
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
template <typename ViewT>
|
||||
TER
|
||||
requireAuth(ReadView const& view, Issue const& issue, AccountID const& account, AuthType authType)
|
||||
IOUIssuance<ViewT>::requireAuth(AccountID const& account, AuthType authType, int /*depth*/) const
|
||||
{
|
||||
if (isXRP(issue) || issue.account == account)
|
||||
auto const& issuer = this->id();
|
||||
if (isXRP(currency_) || issuer == account)
|
||||
return tesSUCCESS;
|
||||
|
||||
auto const trustLine = view.read(keylet::line(account, issue.account, issue.currency));
|
||||
auto const trustLine = this->readView().read(keylet::line(account, issuer, currency_));
|
||||
// If account has no line, and this is a strong check, fail
|
||||
if (!trustLine && authType == AuthType::StrongAuth)
|
||||
return tecNO_LINE;
|
||||
|
||||
// If this is a weak or legacy check, or if the account has a line, fail if
|
||||
// auth is required and not set on the line
|
||||
if (auto const issuerAccount = view.read(keylet::account(issue.account));
|
||||
issuerAccount && issuerAccount->isFlag(lsfRequireAuth))
|
||||
if (this->exists() && (static_cast<unsigned int>(this->sle_->isFlag(lsfRequireAuth)) != 0u))
|
||||
{
|
||||
if (trustLine)
|
||||
{
|
||||
return trustLine->isFlag((account > issue.account) ? lsfLowAuth : lsfHighAuth)
|
||||
return trustLine->isFlag((account > issuer) ? lsfLowAuth : lsfHighAuth)
|
||||
? tesSUCCESS
|
||||
: TER{tecNO_AUTH};
|
||||
}
|
||||
@@ -580,29 +578,29 @@ requireAuth(ReadView const& view, Issue const& issue, AccountID const& account,
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
template <typename ViewT>
|
||||
TER
|
||||
canTransfer(ReadView const& view, Issue const& issue, AccountID const& from, AccountID const& to)
|
||||
IOUIssuance<ViewT>::canTransfer(AccountID const& from, AccountID const& to) const
|
||||
{
|
||||
if (issue.native())
|
||||
if (isXRP(currency_))
|
||||
return tesSUCCESS;
|
||||
|
||||
auto const& issuerId = issue.getIssuer();
|
||||
auto const& issuerId = this->id();
|
||||
if (issuerId == from || issuerId == to)
|
||||
return tesSUCCESS;
|
||||
auto const sleIssuer = view.read(keylet::account(issuerId));
|
||||
if (sleIssuer == nullptr)
|
||||
if (!this->exists())
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const isRippleDisabled = [&](AccountID account) -> bool {
|
||||
// Line might not exist, but some transfers can create it. If this
|
||||
// is the case, just check the default ripple on the issuer account.
|
||||
auto const line = view.read(keylet::line(account, issue));
|
||||
auto const line = this->readView().read(keylet::line(account, issuerId, currency_));
|
||||
if (line)
|
||||
{
|
||||
bool const issuerHigh = issuerId > account;
|
||||
return line->isFlag(issuerHigh ? lsfHighNoRipple : lsfLowNoRipple);
|
||||
}
|
||||
return !sleIssuer->isFlag(lsfDefaultRipple);
|
||||
return !this->sle_->isFlag(lsfDefaultRipple);
|
||||
};
|
||||
|
||||
// Fail if rippling disabled on both trust lines
|
||||
@@ -618,85 +616,71 @@ canTransfer(ReadView const& view, Issue const& issue, AccountID const& from, Acc
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
template <typename ViewT>
|
||||
TER
|
||||
addEmptyHolding(
|
||||
ApplyView& view,
|
||||
IOUIssuance<ViewT>::addEmptyHolding(
|
||||
AccountID const& accountID,
|
||||
XRPAmount priorBalance,
|
||||
Issue const& issue,
|
||||
beast::Journal journal)
|
||||
requires kIsWritable
|
||||
{
|
||||
// Every account can hold XRP. An issuer can issue directly.
|
||||
if (issue.native() || accountID == issue.getIssuer())
|
||||
ApplyView& view = this->applyView();
|
||||
auto const& issuerId = this->id();
|
||||
// The issuer can issue directly.
|
||||
if (accountID == issuerId)
|
||||
return tesSUCCESS;
|
||||
|
||||
auto const& issuerId = issue.getIssuer();
|
||||
auto const& currency = issue.currency;
|
||||
if (isGlobalFrozen(view, issuerId))
|
||||
if (this->isGlobalFrozen())
|
||||
return tecFROZEN; // LCOV_EXCL_LINE
|
||||
|
||||
auto const& srcId = issuerId;
|
||||
auto const& dstId = accountID;
|
||||
auto const high = srcId > dstId;
|
||||
auto const index = keylet::line(srcId, dstId, currency);
|
||||
auto const sleSrc = view.peek(keylet::account(srcId));
|
||||
auto const sleDst = view.peek(keylet::account(dstId));
|
||||
if (!sleDst || !sleSrc)
|
||||
auto const index = keylet::line(srcId, dstId, currency_);
|
||||
WAccountRoot wrappedSrc(srcId, view, journal);
|
||||
WAccountRoot wrappedDst(dstId, view, journal);
|
||||
if (!wrappedDst || !wrappedSrc)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
if (!sleSrc->isFlag(lsfDefaultRipple))
|
||||
if (!wrappedSrc->isFlag(lsfDefaultRipple))
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
// If the line already exists, don't create it again.
|
||||
if (view.read(index))
|
||||
return tecDUPLICATE;
|
||||
|
||||
// Can the account cover the trust line reserve ?
|
||||
std::uint32_t const ownerCount = sleDst->at(sfOwnerCount);
|
||||
std::uint32_t const ownerCount = wrappedDst->at(sfOwnerCount);
|
||||
if (priorBalance < view.fees().accountReserve(ownerCount + 1))
|
||||
return tecNO_LINE_INSUF_RESERVE;
|
||||
|
||||
return trustCreate(
|
||||
view,
|
||||
high,
|
||||
srcId,
|
||||
dstId,
|
||||
index.key,
|
||||
sleDst,
|
||||
wrappedDst,
|
||||
/*bAuth=*/false,
|
||||
/*bNoRipple=*/true,
|
||||
/*bFreeze=*/false,
|
||||
/*deepFreeze*/ false,
|
||||
/*saBalance=*/STAmount{Issue{currency, noAccount()}},
|
||||
/*saLimit=*/STAmount{Issue{currency, dstId}},
|
||||
/*saBalance=*/STAmount{Issue{currency_, noAccount()}},
|
||||
/*saLimit=*/STAmount{Issue{currency_, dstId}},
|
||||
/*uQualityIn=*/0,
|
||||
/*uQualityOut=*/0,
|
||||
journal);
|
||||
}
|
||||
|
||||
template <typename ViewT>
|
||||
TER
|
||||
removeEmptyHolding(
|
||||
ApplyView& view,
|
||||
AccountID const& accountID,
|
||||
Issue const& issue,
|
||||
beast::Journal journal)
|
||||
IOUIssuance<ViewT>::removeEmptyHolding(AccountID const& accountID, beast::Journal journal)
|
||||
requires kIsWritable
|
||||
{
|
||||
if (issue.native())
|
||||
{
|
||||
auto const sle = view.read(keylet::account(accountID));
|
||||
if (!sle)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const balance = sle->getFieldAmount(sfBalance);
|
||||
if (balance.xrp() != 0)
|
||||
return tecHAS_OBLIGATIONS;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
ApplyView& view = this->applyView();
|
||||
auto const& issuerId = this->id();
|
||||
// `asset` is an IOU.
|
||||
// If the account is the issuer, then no line should exist. Check anyway.
|
||||
// If a line does exist, it will get deleted. If not, return success.
|
||||
bool const accountIsIssuer = accountID == issue.account;
|
||||
auto const line = view.peek(keylet::line(accountID, issue));
|
||||
bool const accountIsIssuer = accountID == issuerId;
|
||||
auto const line = view.peek(keylet::line(accountID, this->getIssue()));
|
||||
if (!line)
|
||||
return accountIsIssuer ? (TER)tesSUCCESS : (TER)tecOBJECT_NOT_FOUND;
|
||||
if (!accountIsIssuer && line->at(sfBalance)->iou() != beast::kZero)
|
||||
@@ -706,11 +690,11 @@ removeEmptyHolding(
|
||||
if (line->isFlag(lsfLowReserve))
|
||||
{
|
||||
// Clear reserve for low account.
|
||||
auto sleLowAccount = view.peek(keylet::account(line->at(sfLowLimit)->getIssuer()));
|
||||
if (!sleLowAccount)
|
||||
WAccountRoot wrappedLow(line->at(sfLowLimit)->getIssuer(), view, journal);
|
||||
if (!wrappedLow)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
adjustOwnerCount(view, sleLowAccount, -1, journal);
|
||||
wrappedLow.adjustOwnerCount(-1);
|
||||
// It's not really necessary to clear the reserve flag, since the line
|
||||
// is about to be deleted, but this will make the metadata reflect an
|
||||
// accurate state at the time of deletion.
|
||||
@@ -720,11 +704,11 @@ removeEmptyHolding(
|
||||
if (line->isFlag(lsfHighReserve))
|
||||
{
|
||||
// Clear reserve for high account.
|
||||
auto sleHighAccount = view.peek(keylet::account(line->at(sfHighLimit)->getIssuer()));
|
||||
if (!sleHighAccount)
|
||||
WAccountRoot wrappedHigh(line->at(sfHighLimit)->getIssuer(), view, journal);
|
||||
if (!wrappedHigh)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
adjustOwnerCount(view, sleHighAccount, -1, journal);
|
||||
wrappedHigh.adjustOwnerCount(-1);
|
||||
// It's not really necessary to clear the reserve flag, since the line
|
||||
// is about to be deleted, but this will make the metadata reflect an
|
||||
// accurate state at the time of deletion.
|
||||
@@ -748,13 +732,13 @@ deleteAMMTrustLine(
|
||||
auto const& [low, high] = std::minmax(
|
||||
sleState->getFieldAmount(sfLowLimit).getIssuer(),
|
||||
sleState->getFieldAmount(sfHighLimit).getIssuer());
|
||||
auto sleLow = view.peek(keylet::account(low));
|
||||
auto sleHigh = view.peek(keylet::account(high));
|
||||
if (!sleLow || !sleHigh)
|
||||
WAccountRoot wrappedLow(low, view, j);
|
||||
WAccountRoot wrappedHigh(high, view, j);
|
||||
if (!wrappedLow || !wrappedHigh)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
bool const ammLow = sleLow->isFieldPresent(sfAMMID);
|
||||
bool const ammHigh = sleHigh->isFieldPresent(sfAMMID);
|
||||
bool const ammLow = wrappedLow->isFieldPresent(sfAMMID);
|
||||
bool const ammHigh = wrappedHigh->isFieldPresent(sfAMMID);
|
||||
|
||||
// can't both be AMM
|
||||
if (ammLow && ammHigh)
|
||||
@@ -778,25 +762,14 @@ deleteAMMTrustLine(
|
||||
if (!sleState->isFlag(uFlags))
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
adjustOwnerCount(view, !ammLow ? sleLow : sleHigh, -1, j);
|
||||
WAccountRoot wrappedHolder = !ammLow ? wrappedLow : wrappedHigh;
|
||||
wrappedHolder.adjustOwnerCount(-1);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
deleteAMMMPToken(
|
||||
ApplyView& view,
|
||||
std::shared_ptr<SLE> sleMpt,
|
||||
AccountID const& ammAccountID,
|
||||
beast::Journal j)
|
||||
{
|
||||
if (!view.dirRemove(
|
||||
keylet::ownerDir(ammAccountID), (*sleMpt)[sfOwnerNode], sleMpt->key(), false))
|
||||
return tefBAD_LEDGER; // LCOV_EXCL_LINE
|
||||
|
||||
view.erase(sleMpt);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
// Explicit template instantiations
|
||||
template class IOUIssuance<ReadView>;
|
||||
template class IOUIssuance<ApplyView>;
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -51,22 +51,28 @@ bool
|
||||
isGlobalFrozen(ReadView const& view, Asset const& asset)
|
||||
{
|
||||
return asset.visit(
|
||||
[&](Issue const& issue) { return isGlobalFrozen(view, issue.getIssuer()); },
|
||||
[&](MPTIssue const& issue) { return isGlobalFrozen(view, issue); });
|
||||
[&](Issue const& issue) { return AccountRoot(issue.getIssuer(), view).isGlobalFrozen(); },
|
||||
[&](MPTIssue const& issue) { return MPTokenIssuance(view, issue).isGlobalFrozen(); });
|
||||
}
|
||||
|
||||
bool
|
||||
isIndividualFrozen(ReadView const& view, AccountID const& account, Asset const& asset)
|
||||
{
|
||||
return std::visit(
|
||||
[&](auto const& issue) { return isIndividualFrozen(view, account, issue); }, asset.value());
|
||||
return asset.visit(
|
||||
[&](Issue const& issue) { return IOUIssuance(view, issue).isIndividualFrozen(account); },
|
||||
[&](MPTIssue const& issue) {
|
||||
return MPTokenIssuance(view, issue).isIndividualFrozen(account);
|
||||
});
|
||||
}
|
||||
|
||||
bool
|
||||
isFrozen(ReadView const& view, AccountID const& account, Asset const& asset, int depth)
|
||||
{
|
||||
return std::visit(
|
||||
[&](auto const& issue) { return isFrozen(view, account, issue, depth); }, asset.value());
|
||||
return asset.visit(
|
||||
[&](Issue const& issue) { return IOUIssuance(view, issue).isFrozen(account); },
|
||||
[&](MPTIssue const& issue) {
|
||||
return MPTokenIssuance(view, issue).isFrozen(account, depth);
|
||||
});
|
||||
}
|
||||
|
||||
TER
|
||||
@@ -94,9 +100,10 @@ isAnyFrozen(
|
||||
std::initializer_list<AccountID> const& accounts,
|
||||
Issue const& issue)
|
||||
{
|
||||
auto const iou = IOUIssuance(view, issue);
|
||||
for (auto const& account : accounts)
|
||||
{
|
||||
if (isFrozen(view, account, issue.currency, issue.account))
|
||||
if (iou.isFrozen(account))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -111,7 +118,9 @@ isAnyFrozen(
|
||||
{
|
||||
return asset.visit(
|
||||
[&](Issue const& issue) { return isAnyFrozen(view, accounts, issue); },
|
||||
[&](MPTIssue const& issue) { return isAnyFrozen(view, accounts, issue, depth); });
|
||||
[&](MPTIssue const& issue) {
|
||||
return MPTokenIssuance(view, issue).isAnyFrozen(accounts, depth);
|
||||
});
|
||||
}
|
||||
|
||||
bool
|
||||
@@ -119,7 +128,7 @@ isDeepFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mpt
|
||||
{
|
||||
// Unlike IOUs, frozen / locked MPTs are not allowed to send or receive
|
||||
// funds, so checking "deep frozen" is the same as checking "frozen".
|
||||
return isFrozen(view, account, mptIssue, depth);
|
||||
return MPTokenIssuance(view, mptIssue).isFrozen(account, depth);
|
||||
}
|
||||
|
||||
bool
|
||||
@@ -167,8 +176,8 @@ getLineIfUsable(
|
||||
|
||||
if (zeroIfFrozen == FreezeHandling::ZeroIfFrozen)
|
||||
{
|
||||
if (isFrozen(view, account, currency, issuer) ||
|
||||
isDeepFrozen(view, account, currency, issuer))
|
||||
auto const iou = IOUIssuance(view, issuer, currency);
|
||||
if (iou.isFrozen(account) || iou.isDeepFrozen(account))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
@@ -177,14 +186,14 @@ getLineIfUsable(
|
||||
// we need to check if the associated assets have been frozen
|
||||
if (view.rules().enabled(fixFrozenLPTokenTransfer))
|
||||
{
|
||||
auto const sleIssuer = view.read(keylet::account(issuer));
|
||||
if (!sleIssuer)
|
||||
auto const issuerRoot = AccountRoot(issuer, view, j);
|
||||
if (!issuerRoot.exists())
|
||||
{
|
||||
return nullptr; // LCOV_EXCL_LINE
|
||||
}
|
||||
if (sleIssuer->isFieldPresent(sfAMMID))
|
||||
if (issuerRoot->isFieldPresent(sfAMMID))
|
||||
{
|
||||
auto const sleAmm = view.read(keylet::amm((*sleIssuer)[sfAMMID]));
|
||||
auto const sleAmm = view.read(keylet::amm((*issuerRoot)[sfAMMID]));
|
||||
|
||||
if (!sleAmm ||
|
||||
isLPTokenFrozen(view, account, (*sleAmm)[sfAsset], (*sleAmm)[sfAsset2]))
|
||||
@@ -249,7 +258,8 @@ accountHolds(
|
||||
STAmount const amount;
|
||||
if (isXRP(currency))
|
||||
{
|
||||
return {xrpLiquid(view, account, 0, j)};
|
||||
AccountRoot const accountRoot(account, view, j);
|
||||
return {accountRoot.xrpLiquid(0)};
|
||||
}
|
||||
|
||||
bool const returnSpendable = (includeFullBalance == SpendableHandling::FullBalance);
|
||||
@@ -317,7 +327,9 @@ accountHolds(
|
||||
{
|
||||
amount.clear(mptIssue);
|
||||
}
|
||||
else if (zeroIfFrozen == FreezeHandling::ZeroIfFrozen && isFrozen(view, account, mptIssue))
|
||||
else if (
|
||||
zeroIfFrozen == FreezeHandling::ZeroIfFrozen &&
|
||||
MPTokenIssuance(view, mptIssue).isFrozen(account))
|
||||
{
|
||||
amount.clear(mptIssue);
|
||||
}
|
||||
@@ -330,7 +342,8 @@ accountHolds(
|
||||
if (zeroIfUnauthorized == AuthHandling::ZeroIfUnauthorized &&
|
||||
view.rules().enabled(featureSingleAssetVault))
|
||||
{
|
||||
if (auto const err = requireAuth(view, mptIssue, account, AuthType::StrongAuth);
|
||||
if (auto const err =
|
||||
MPTokenIssuance(view, mptIssue).requireAuth(account, AuthType::StrongAuth);
|
||||
!isTesSuccess(err))
|
||||
amount.clear(mptIssue);
|
||||
}
|
||||
@@ -415,8 +428,8 @@ Rate
|
||||
transferRate(ReadView const& view, STAmount const& amount)
|
||||
{
|
||||
return amount.asset().visit(
|
||||
[&](Issue const& issue) { return transferRate(view, issue.getIssuer()); },
|
||||
[&](MPTIssue const& issue) { return transferRate(view, issue.getMptID()); });
|
||||
[&](Issue const& issue) { return IOUIssuance(view, issue).transferRate(); },
|
||||
[&](MPTIssue const& issue) { return MPTokenIssuance(view, issue).transferRate(); });
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -432,12 +445,13 @@ canAddHolding(ReadView const& view, Issue const& issue)
|
||||
{
|
||||
return tesSUCCESS; // No special checks for XRP
|
||||
}
|
||||
auto const issuer = AccountRoot(issue.getIssuer(), view);
|
||||
|
||||
auto const issuer = view.read(keylet::account(issue.getIssuer()));
|
||||
if (!issuer)
|
||||
if (!issuer.exists())
|
||||
{
|
||||
return terNO_ACCOUNT;
|
||||
}
|
||||
|
||||
if (!issuer->isFlag(lsfDefaultRipple))
|
||||
{
|
||||
return terNO_RIPPLE;
|
||||
@@ -462,11 +476,18 @@ addEmptyHolding(
|
||||
Asset const& asset,
|
||||
beast::Journal journal)
|
||||
{
|
||||
return std::visit(
|
||||
[&]<ValidIssueType TIss>(TIss const& issue) -> TER {
|
||||
return addEmptyHolding(view, accountID, priorBalance, issue, journal);
|
||||
// Every account can hold XRP.
|
||||
if (asset.native())
|
||||
return tesSUCCESS;
|
||||
return asset.visit(
|
||||
[&](Issue const& issue) {
|
||||
return WIOUIssuance(view, issue, journal)
|
||||
.addEmptyHolding(accountID, priorBalance, journal);
|
||||
},
|
||||
asset.value());
|
||||
[&](MPTIssue const& issue) {
|
||||
return WMPTokenIssuance(view, issue, journal)
|
||||
.addEmptyHolding(accountID, priorBalance, journal);
|
||||
});
|
||||
}
|
||||
|
||||
TER
|
||||
@@ -476,11 +497,25 @@ removeEmptyHolding(
|
||||
Asset const& asset,
|
||||
beast::Journal journal)
|
||||
{
|
||||
return std::visit(
|
||||
[&]<ValidIssueType TIss>(TIss const& issue) -> TER {
|
||||
return removeEmptyHolding(view, accountID, issue, journal);
|
||||
if (asset.native())
|
||||
{
|
||||
auto const account = AccountRoot(accountID, view, journal);
|
||||
if (!account.exists())
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const balance = account->getFieldAmount(sfBalance);
|
||||
if (balance.xrp() != 0)
|
||||
return tecHAS_OBLIGATIONS;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
return asset.visit(
|
||||
[&](Issue const& issue) {
|
||||
return WIOUIssuance(view, issue, journal).removeEmptyHolding(accountID, journal);
|
||||
},
|
||||
asset.value());
|
||||
[&](MPTIssue const& issue) {
|
||||
return WMPTokenIssuance(view, issue, journal).removeEmptyHolding(accountID, journal);
|
||||
});
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -492,21 +527,19 @@ removeEmptyHolding(
|
||||
TER
|
||||
requireAuth(ReadView const& view, Asset const& asset, AccountID const& account, AuthType authType)
|
||||
{
|
||||
return std::visit(
|
||||
[&]<ValidIssueType TIss>(TIss const& issue) {
|
||||
return requireAuth(view, issue, account, authType);
|
||||
},
|
||||
asset.value());
|
||||
return asset.visit(
|
||||
[&](Issue const& issue) { return IOUIssuance(view, issue).requireAuth(account, authType); },
|
||||
[&](MPTIssue const& issue) {
|
||||
return MPTokenIssuance(view, issue).requireAuth(account, authType);
|
||||
});
|
||||
}
|
||||
|
||||
TER
|
||||
canTransfer(ReadView const& view, Asset const& asset, AccountID const& from, AccountID const& to)
|
||||
{
|
||||
return std::visit(
|
||||
[&]<ValidIssueType TIss>(TIss const& issue) -> TER {
|
||||
return canTransfer(view, issue, from, to);
|
||||
},
|
||||
asset.value());
|
||||
return asset.visit(
|
||||
[&](Issue const& issue) { return IOUIssuance(view, issue).canTransfer(from, to); },
|
||||
[&](MPTIssue const& issue) { return MPTokenIssuance(view, issue).canTransfer(from, to); });
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -518,7 +551,7 @@ canTransfer(ReadView const& view, Asset const& asset, AccountID const& from, Acc
|
||||
// Direct send w/o fees:
|
||||
// - Redeeming IOUs and/or sending sender's own IOUs.
|
||||
// - Create trust line if needed.
|
||||
// --> bCheckIssuer : normally require issuer to be involved.
|
||||
// bCheckIssuer : normally require issuer to be involved.
|
||||
static TER
|
||||
directSendNoFeeIOU(
|
||||
ApplyView& view,
|
||||
@@ -585,7 +618,7 @@ directSendNoFeeIOU(
|
||||
&& sleRippleState->isFlag(senderReserveFlag)
|
||||
// Sender reserve is set.
|
||||
&& sleRippleState->isFlag(senderNoRippleFlag) !=
|
||||
view.read(keylet::account(uSenderID))->isFlag(lsfDefaultRipple) &&
|
||||
AccountRoot(uSenderID, view, j)->isFlag(lsfDefaultRipple) &&
|
||||
!sleRippleState->isFlag(senderFreezeFlag) &&
|
||||
!sleRippleState->getFieldAmount(bSenderHigh ? sfHighLimit : sfLowLimit)
|
||||
// Sender trust limit is 0.
|
||||
@@ -596,7 +629,8 @@ directSendNoFeeIOU(
|
||||
// Sender quality out is 0.
|
||||
{
|
||||
// Clear the reserve of the sender, possibly delete the line!
|
||||
adjustOwnerCount(view, view.peek(keylet::account(uSenderID)), -1, j);
|
||||
WAccountRoot wrappedSender(uSenderID, view, j);
|
||||
wrappedSender.adjustOwnerCount(-1);
|
||||
|
||||
// Clear reserve flag.
|
||||
sleRippleState->clearFlag(senderReserveFlag);
|
||||
@@ -638,28 +672,28 @@ directSendNoFeeIOU(
|
||||
<< to_string(uSenderID) << " -> " << to_string(uReceiverID) << " : "
|
||||
<< saAmount.getFullText();
|
||||
|
||||
auto const sleAccount = view.peek(keylet::account(uReceiverID));
|
||||
if (!sleAccount)
|
||||
WAccountRoot wrappedAccount(uReceiverID, view, j);
|
||||
if (!wrappedAccount)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
bool const noRipple = !sleAccount->isFlag(lsfDefaultRipple);
|
||||
bool const noRipple = !wrappedAccount->isFlag(lsfDefaultRipple);
|
||||
|
||||
return trustCreate(
|
||||
view,
|
||||
bSenderHigh,
|
||||
uSenderID,
|
||||
uReceiverID,
|
||||
index.key,
|
||||
sleAccount,
|
||||
false,
|
||||
noRipple,
|
||||
false,
|
||||
false,
|
||||
saBalance,
|
||||
saReceiverLimit,
|
||||
0,
|
||||
0,
|
||||
j);
|
||||
return WIOUIssuance(view, saAmount.get<Issue>(), j)
|
||||
.trustCreate(
|
||||
bSenderHigh,
|
||||
uSenderID,
|
||||
uReceiverID,
|
||||
index.key,
|
||||
wrappedAccount,
|
||||
false,
|
||||
noRipple,
|
||||
false,
|
||||
false,
|
||||
saBalance,
|
||||
saReceiverLimit,
|
||||
0,
|
||||
0,
|
||||
j);
|
||||
}
|
||||
|
||||
// Send regardless of limits.
|
||||
@@ -696,8 +730,10 @@ directSendNoLimitIOU(
|
||||
|
||||
// Calculate the amount to transfer accounting
|
||||
// for any transfer fees if the fee is not waived:
|
||||
saActual = (waiveFee == WaiveTransferFee::Yes) ? saAmount
|
||||
: multiply(saAmount, transferRate(view, issuer));
|
||||
WAccountRoot const wrappedIssuer(issuer, view, j);
|
||||
saActual = (waiveFee == WaiveTransferFee::Yes)
|
||||
? saAmount
|
||||
: multiply(saAmount, wrappedIssuer.transferRate());
|
||||
|
||||
JLOG(j.debug()) << "directSendNoLimitIOU> " << to_string(uSenderID) << " - > "
|
||||
<< to_string(uReceiverID) << " : deliver=" << saAmount.getFullText()
|
||||
@@ -762,9 +798,10 @@ directSendNoLimitMultiIOU(
|
||||
|
||||
// Calculate the amount to transfer accounting
|
||||
// for any transfer fees if the fee is not waived:
|
||||
WAccountRoot const wrappedIssuer(issuer, view, j);
|
||||
STAmount const actualSend = (waiveFee == WaiveTransferFee::Yes)
|
||||
? amount
|
||||
: multiply(amount, transferRate(view, issuer));
|
||||
: multiply(amount, wrappedIssuer.transferRate());
|
||||
actual += actualSend;
|
||||
takeFromSender += actualSend;
|
||||
|
||||
@@ -834,10 +871,8 @@ accountSendIOU(
|
||||
*/
|
||||
TER terResult(tesSUCCESS);
|
||||
|
||||
SLE::pointer const sender =
|
||||
uSenderID != beast::kZero ? view.peek(keylet::account(uSenderID)) : SLE::pointer();
|
||||
SLE::pointer const receiver =
|
||||
uReceiverID != beast::kZero ? view.peek(keylet::account(uReceiverID)) : SLE::pointer();
|
||||
WAccountRoot sender(uSenderID, view, j);
|
||||
WAccountRoot receiver(uReceiverID, view, j);
|
||||
|
||||
if (auto stream = j.trace())
|
||||
{
|
||||
@@ -871,7 +906,7 @@ accountSendIOU(
|
||||
|
||||
// Decrement XRP balance.
|
||||
sender->setFieldAmount(sfBalance, sndBal - saAmount);
|
||||
view.update(sender);
|
||||
sender.update();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -882,7 +917,7 @@ accountSendIOU(
|
||||
receiver->setFieldAmount(sfBalance, rcvBal + saAmount);
|
||||
view.creditHookIOU(xrpAccount(), uReceiverID, saAmount, -rcvBal);
|
||||
|
||||
view.update(receiver);
|
||||
receiver.update();
|
||||
}
|
||||
|
||||
if (auto stream = j.trace())
|
||||
@@ -930,8 +965,7 @@ accountSendMultiIOU(
|
||||
* ensure that transfers are balanced.
|
||||
*/
|
||||
|
||||
SLE::pointer const sender =
|
||||
senderID != beast::kZero ? view.peek(keylet::account(senderID)) : SLE::pointer();
|
||||
WAccountRoot sender(senderID, view, j);
|
||||
|
||||
if (auto stream = j.trace())
|
||||
{
|
||||
@@ -962,8 +996,7 @@ accountSendMultiIOU(
|
||||
if (!amount || (senderID == receiverID))
|
||||
continue;
|
||||
|
||||
SLE::pointer const receiver =
|
||||
receiverID != beast::kZero ? view.peek(keylet::account(receiverID)) : SLE::pointer();
|
||||
WAccountRoot receiver(receiverID, view, j);
|
||||
|
||||
if (auto stream = j.trace())
|
||||
{
|
||||
@@ -984,7 +1017,7 @@ accountSendMultiIOU(
|
||||
receiver->setFieldAmount(sfBalance, rcvBal + amount);
|
||||
view.creditHookIOU(xrpAccount(), receiverID, amount, -rcvBal);
|
||||
|
||||
view.update(receiver);
|
||||
receiver.update();
|
||||
|
||||
// Take what is actually sent
|
||||
takeFromSender += amount;
|
||||
@@ -1014,7 +1047,7 @@ accountSendMultiIOU(
|
||||
|
||||
// Decrement XRP balance.
|
||||
sender->setFieldAmount(sfBalance, sndBal - takeFromSender);
|
||||
view.update(sender);
|
||||
sender.update();
|
||||
}
|
||||
|
||||
if (auto stream = j.trace())
|
||||
@@ -1156,7 +1189,7 @@ directSendNoLimitMPT(
|
||||
// Sending 3rd party MPTs: transit.
|
||||
saActual = (waiveFee == WaiveTransferFee::Yes)
|
||||
? saAmount
|
||||
: multiply(saAmount, transferRate(view, saAmount.get<MPTIssue>().getMptID()));
|
||||
: multiply(saAmount, MPTokenIssuance(view, saAmount.get<MPTIssue>()).transferRate());
|
||||
|
||||
JLOG(j.debug()) << "directSendNoLimitMPT> " << to_string(uSenderID) << " - > "
|
||||
<< to_string(uReceiverID) << " : deliver=" << saAmount.getFullText()
|
||||
@@ -1267,7 +1300,7 @@ directSendNoLimitMultiMPT(
|
||||
// Sending 3rd party MPTs: transit.
|
||||
STAmount const actualSend = (waiveFee == WaiveTransferFee::Yes)
|
||||
? amount
|
||||
: multiply(amount, transferRate(view, amount.get<MPTIssue>().getMptID()));
|
||||
: multiply(amount, MPTokenIssuance(view, amount.get<MPTIssue>()).transferRate());
|
||||
actual += actualSend;
|
||||
takeFromSender += actualSend;
|
||||
|
||||
@@ -1407,15 +1440,15 @@ transferXRP(
|
||||
XRPL_ASSERT(from != to, "xrpl::transferXRP : sender is not receiver");
|
||||
XRPL_ASSERT(amount.native(), "xrpl::transferXRP : amount is XRP");
|
||||
|
||||
SLE::pointer const sender = view.peek(keylet::account(from));
|
||||
SLE::pointer const receiver = view.peek(keylet::account(to));
|
||||
if (!sender || !receiver)
|
||||
WAccountRoot acctSender(from, view, j);
|
||||
WAccountRoot acctReceiver(to, view, j);
|
||||
if (!acctSender || !acctReceiver)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
JLOG(j.trace()) << "transferXRP: " << to_string(from) << " -> " << to_string(to)
|
||||
<< ") : " << amount.getFullText();
|
||||
|
||||
if (sender->getFieldAmount(sfBalance) < amount)
|
||||
if (acctSender->getFieldAmount(sfBalance) < amount)
|
||||
{
|
||||
// VFALCO Its unfortunate we have to keep
|
||||
// mutating these TER everywhere
|
||||
@@ -1426,11 +1459,11 @@ transferXRP(
|
||||
}
|
||||
|
||||
// Decrement XRP balance.
|
||||
sender->setFieldAmount(sfBalance, sender->getFieldAmount(sfBalance) - amount);
|
||||
view.update(sender);
|
||||
acctSender->setFieldAmount(sfBalance, acctSender->getFieldAmount(sfBalance) - amount);
|
||||
acctSender.update();
|
||||
|
||||
receiver->setFieldAmount(sfBalance, receiver->getFieldAmount(sfBalance) + amount);
|
||||
view.update(receiver);
|
||||
acctReceiver->setFieldAmount(sfBalance, acctReceiver->getFieldAmount(sfBalance) + amount);
|
||||
acctReceiver.update();
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
@@ -518,7 +518,7 @@ oracle(AccountID const& account, std::uint32_t const& documentID) noexcept
|
||||
}
|
||||
|
||||
Keylet
|
||||
mptIssuance(std::uint32_t seq, AccountID const& issuer) noexcept
|
||||
mptIssuance(std::uint32_t const seq, AccountID const& issuer) noexcept
|
||||
{
|
||||
return mptIssuance(makeMptID(seq, issuer));
|
||||
}
|
||||
|
||||
@@ -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."),
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/CredentialHelpers.h>
|
||||
#include <xrpl/ledger/helpers/DelegateHelpers.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/NFTokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/OfferHelpers.h>
|
||||
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
|
||||
@@ -263,6 +264,7 @@ Transactor::Transactor(ApplyContext& ctx)
|
||||
, sink_(ctx.journal, toShortString(ctx.tx.getTransactionID()) + " ")
|
||||
, j_(sink_)
|
||||
, accountID_(ctx.tx.getAccountID(sfAccount))
|
||||
, account_(accountID_, ctx.view())
|
||||
{
|
||||
}
|
||||
|
||||
@@ -388,11 +390,11 @@ Transactor::checkFee(PreclaimContext const& ctx, XRPAmount baseFee)
|
||||
return tesSUCCESS;
|
||||
|
||||
auto const id = ctx.tx.getFeePayer();
|
||||
auto const sle = ctx.view.read(keylet::account(id));
|
||||
if (!sle)
|
||||
AccountRoot const acctRoot(id, ctx.view);
|
||||
if (!acctRoot)
|
||||
return terNO_ACCOUNT;
|
||||
|
||||
auto const balance = (*sle)[sfBalance].xrp();
|
||||
auto const balance = (*acctRoot)[sfBalance].xrp();
|
||||
|
||||
// NOTE: Because preclaim evaluates against a static readview, it
|
||||
// does not reflect fee deductions from other transactions paid by
|
||||
@@ -424,17 +426,15 @@ Transactor::payFee()
|
||||
auto const feePaid = ctx_.tx[sfFee].xrp();
|
||||
|
||||
auto const feePayer = ctx_.tx.getFeePayer();
|
||||
auto const sle = view().peek(keylet::account(feePayer));
|
||||
if (!sle)
|
||||
WAccountRoot payerAcct(feePayer, view(), j_);
|
||||
if (!payerAcct)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
// 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);
|
||||
payerAcct->setFieldAmount(sfBalance, payerAcct->getFieldAmount(sfBalance) - feePaid);
|
||||
if (feePayer != accountID_)
|
||||
view().update(sle); // done in `apply()` for the account
|
||||
|
||||
// VFALCO Should we call view().rawDestroyXRP() here as well?
|
||||
payerAcct.update(); // done in `apply()` for the account
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
@@ -443,9 +443,9 @@ Transactor::checkSeqProxy(ReadView const& view, STTx const& tx, beast::Journal j
|
||||
{
|
||||
auto const id = tx.getAccountID(sfAccount);
|
||||
|
||||
auto const sle = view.read(keylet::account(id));
|
||||
AccountRoot const acctRoot(id, view);
|
||||
|
||||
if (!sle)
|
||||
if (!acctRoot)
|
||||
{
|
||||
JLOG(j.trace()) << "applyTransaction: delay: source account does not exist "
|
||||
<< toBase58(id);
|
||||
@@ -453,7 +453,7 @@ Transactor::checkSeqProxy(ReadView const& view, STTx const& tx, beast::Journal j
|
||||
}
|
||||
|
||||
SeqProxy const tSeqProx = tx.getSeqProxy();
|
||||
SeqProxy const aSeq = SeqProxy::sequence((*sle)[sfSequence]);
|
||||
SeqProxy const aSeq = SeqProxy::sequence((*acctRoot)[sfSequence]);
|
||||
|
||||
if (tSeqProx.isSeq())
|
||||
{
|
||||
@@ -508,9 +508,9 @@ Transactor::checkPriorTxAndLastLedger(PreclaimContext const& ctx)
|
||||
{
|
||||
auto const id = ctx.tx.getAccountID(sfAccount);
|
||||
|
||||
auto const sle = ctx.view.read(keylet::account(id));
|
||||
AccountRoot const acctRoot(id, ctx.view);
|
||||
|
||||
if (!sle)
|
||||
if (!acctRoot)
|
||||
{
|
||||
JLOG(ctx.j.trace()) << "applyTransaction: delay: source account does not exist "
|
||||
<< toBase58(id);
|
||||
@@ -518,7 +518,7 @@ Transactor::checkPriorTxAndLastLedger(PreclaimContext const& ctx)
|
||||
}
|
||||
|
||||
if (ctx.tx.isFieldPresent(sfAccountTxnID) &&
|
||||
(sle->getFieldH256(sfAccountTxnID) != ctx.tx.getFieldH256(sfAccountTxnID)))
|
||||
(acctRoot->getFieldH256(sfAccountTxnID) != ctx.tx.getFieldH256(sfAccountTxnID)))
|
||||
return tefWRONG_PRIOR;
|
||||
|
||||
if (ctx.tx.isFieldPresent(sfLastLedgerSequence) &&
|
||||
@@ -577,8 +577,8 @@ Transactor::ticketDelete(
|
||||
|
||||
// Update the account root's TicketCount. If the ticket count drops to
|
||||
// zero remove the (optional) field.
|
||||
auto sleAccount = view.peek(keylet::account(account));
|
||||
if (!sleAccount)
|
||||
WAccountRoot wrappedAcct(account, view, j);
|
||||
if (!wrappedAcct)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.fatal()) << "Could not find Ticket owner account root.";
|
||||
@@ -586,11 +586,11 @@ Transactor::ticketDelete(
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
if (auto ticketCount = (*sleAccount)[~sfTicketCount])
|
||||
if (auto ticketCount = (*wrappedAcct)[~sfTicketCount])
|
||||
{
|
||||
if (*ticketCount == 1)
|
||||
{
|
||||
sleAccount->makeFieldAbsent(sfTicketCount);
|
||||
wrappedAcct->makeFieldAbsent(sfTicketCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -606,7 +606,7 @@ Transactor::ticketDelete(
|
||||
}
|
||||
|
||||
// Update the Ticket owner's reserve.
|
||||
adjustOwnerCount(view, sleAccount, -1, j);
|
||||
wrappedAcct.adjustOwnerCount(-1);
|
||||
|
||||
// Remove Ticket from ledger.
|
||||
view.erase(sleTicket);
|
||||
@@ -627,19 +627,19 @@ 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_));
|
||||
WAccountRoot acct(accountID_, view(), j_);
|
||||
|
||||
// sle must exist except for transactions
|
||||
// acct must exist except for transactions
|
||||
// that allow zero account.
|
||||
XRPL_ASSERT(
|
||||
sle != nullptr || accountID_ == beast::kZero,
|
||||
acct.exists() || accountID_ == beast::kZero,
|
||||
"xrpl::Transactor::apply : non-null SLE or zero account");
|
||||
|
||||
if (sle)
|
||||
if (acct)
|
||||
{
|
||||
preFeeBalance_ = STAmount{(*sle)[sfBalance]}.xrp();
|
||||
preFeeBalance_ = STAmount{(*acct)[sfBalance]}.xrp();
|
||||
|
||||
TER result = consumeSeqProxy(sle);
|
||||
TER result = consumeSeqProxy(acct.mutableSle());
|
||||
if (!isTesSuccess(result))
|
||||
return result;
|
||||
|
||||
@@ -647,10 +647,10 @@ Transactor::apply()
|
||||
if (!isTesSuccess(result))
|
||||
return result;
|
||||
|
||||
if (sle->isFieldPresent(sfAccountTxnID))
|
||||
sle->setFieldH256(sfAccountTxnID, ctx_.tx.getTransactionID());
|
||||
if (acct->isFieldPresent(sfAccountTxnID))
|
||||
acct->setFieldH256(sfAccountTxnID, ctx_.tx.getTransactionID());
|
||||
|
||||
view().update(sle);
|
||||
acct.update();
|
||||
}
|
||||
|
||||
return doApply();
|
||||
@@ -665,10 +665,9 @@ Transactor::checkSign(
|
||||
STObject const& sigObject,
|
||||
beast::Journal const j)
|
||||
{
|
||||
AccountRoot const acctSign(idAccount, view);
|
||||
{
|
||||
auto const sle = view.read(keylet::account(idAccount));
|
||||
|
||||
if (view.rules().enabled(featureLendingProtocol) && isPseudoAccount(sle))
|
||||
if (view.rules().enabled(featureLendingProtocol) && acctSign.isPseudoAccount())
|
||||
{
|
||||
// Pseudo-accounts can't sign transactions. This check is gated on
|
||||
// the Lending Protocol amendment because that's the project it was
|
||||
@@ -714,12 +713,11 @@ Transactor::checkSign(
|
||||
}
|
||||
|
||||
// Look up the account.
|
||||
auto const idSigner = calcAccountID(PublicKey(makeSlice(pkSigner)));
|
||||
auto const sleAccount = view.read(keylet::account(idAccount));
|
||||
if (!sleAccount)
|
||||
if (!acctSign)
|
||||
return terNO_ACCOUNT;
|
||||
|
||||
return checkSingleSign(view, idSigner, idAccount, sleAccount, j);
|
||||
auto const idSigner = calcAccountID(PublicKey(makeSlice(pkSigner)));
|
||||
return checkSingleSign(view, idSigner, idAccount, acctSign.sle(), j);
|
||||
}
|
||||
|
||||
NotTEC
|
||||
@@ -754,11 +752,11 @@ Transactor::checkBatchSign(PreclaimContext const& ctx)
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
auto const idSigner = calcAccountID(PublicKey(makeSlice(pkSigner)));
|
||||
auto const sleAccount = ctx.view.read(keylet::account(idAccount));
|
||||
AccountRoot const acctRoot(idAccount, ctx.view);
|
||||
|
||||
// A batch can include transactions from an un-created account ONLY
|
||||
// when the account master key is the signer
|
||||
if (!sleAccount)
|
||||
if (!acctRoot)
|
||||
{
|
||||
if (idAccount != idSigner)
|
||||
return tefBAD_AUTH;
|
||||
@@ -766,7 +764,7 @@ Transactor::checkBatchSign(PreclaimContext const& ctx)
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
if (ret = checkSingleSign(ctx.view, idSigner, idAccount, sleAccount, ctx.j);
|
||||
if (ret = checkSingleSign(ctx.view, idSigner, idAccount, acctRoot.sle(), ctx.j);
|
||||
!isTesSuccess(ret))
|
||||
return ret;
|
||||
}
|
||||
@@ -912,15 +910,15 @@ Transactor::checkMultiSign(
|
||||
|
||||
// In any of these cases we need to know whether the account is in
|
||||
// the ledger. Determine that now.
|
||||
auto const sleTxSignerRoot = view.read(keylet::account(txSignerAcctID));
|
||||
AccountRoot const acctSigner(txSignerAcctID, view);
|
||||
|
||||
if (signingAcctIDFromPubKey == txSignerAcctID)
|
||||
{
|
||||
// Either Phantom or Master. Phantoms automatically pass.
|
||||
if (sleTxSignerRoot)
|
||||
if (acctSigner)
|
||||
{
|
||||
// Master Key. Account may not have asfDisableMaster set.
|
||||
std::uint32_t const signerAccountFlags = sleTxSignerRoot->getFieldU32(sfFlags);
|
||||
std::uint32_t const signerAccountFlags = acctSigner->getFieldU32(sfFlags);
|
||||
|
||||
if ((signerAccountFlags & lsfDisableMaster) != 0u)
|
||||
{
|
||||
@@ -933,19 +931,19 @@ Transactor::checkMultiSign(
|
||||
{
|
||||
// May be a Regular Key. Let's find out.
|
||||
// Public key must hash to the account's regular key.
|
||||
if (!sleTxSignerRoot)
|
||||
if (!acctSigner)
|
||||
{
|
||||
JLOG(j.trace()) << "applyTransaction: Non-phantom signer "
|
||||
"lacks account root.";
|
||||
return tefBAD_SIGNATURE;
|
||||
}
|
||||
|
||||
if (!sleTxSignerRoot->isFieldPresent(sfRegularKey))
|
||||
if (!acctSigner->isFieldPresent(sfRegularKey))
|
||||
{
|
||||
JLOG(j.trace()) << "applyTransaction: Account lacks RegularKey.";
|
||||
return tefBAD_SIGNATURE;
|
||||
}
|
||||
if (signingAcctIDFromPubKey != sleTxSignerRoot->getAccountID(sfRegularKey))
|
||||
if (signingAcctIDFromPubKey != acctSigner->getAccountID(sfRegularKey))
|
||||
{
|
||||
JLOG(j.trace()) << "applyTransaction: Account doesn't match RegularKey.";
|
||||
return tefBAD_SIGNATURE;
|
||||
@@ -1074,18 +1072,25 @@ Transactor::reset(XRPAmount fee)
|
||||
{
|
||||
ctx_.discard();
|
||||
|
||||
auto const txnAcct = view().peek(keylet::account(ctx_.tx.getAccountID(sfAccount)));
|
||||
WAccountRoot txnAcct(ctx_.tx.getAccountID(sfAccount), view(), j_);
|
||||
|
||||
// The account should never be missing from the ledger. But if it
|
||||
// is missing then we can't very well charge it a fee, can we?
|
||||
if (!txnAcct)
|
||||
return {tefINTERNAL, beast::kZero};
|
||||
|
||||
auto const payerSle = view().peek(keylet::account(ctx_.tx.getFeePayer()));
|
||||
if (!payerSle)
|
||||
return {tefINTERNAL, beast::kZero}; // LCOV_EXCL_LINE
|
||||
auto const feePayer = ctx_.tx.getFeePayer();
|
||||
bool const hasDelegateAcct = (feePayer != accountID_);
|
||||
std::optional<WAccountRoot> delegateAcct;
|
||||
if (hasDelegateAcct)
|
||||
{
|
||||
delegateAcct.emplace(feePayer, view(), j_);
|
||||
if (!*delegateAcct)
|
||||
return {tefINTERNAL, beast::kZero}; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
auto const balance = payerSle->getFieldAmount(sfBalance).xrp();
|
||||
auto& payer = hasDelegateAcct ? *delegateAcct : txnAcct;
|
||||
auto const balance = payer->getFieldAmount(sfBalance).xrp();
|
||||
|
||||
// balance should have already been checked in checkFee / preFlight.
|
||||
XRPL_ASSERT(
|
||||
@@ -1104,15 +1109,15 @@ Transactor::reset(XRPAmount fee)
|
||||
// If for some reason we are unable to consume the ticket or sequence
|
||||
// then the ledger is corrupted. Rather than make things worse we
|
||||
// reject the transaction.
|
||||
payerSle->setFieldAmount(sfBalance, balance - fee);
|
||||
TER const ter{consumeSeqProxy(txnAcct)};
|
||||
payer->setFieldAmount(sfBalance, balance - fee);
|
||||
TER const ter{consumeSeqProxy(txnAcct.mutableSle())};
|
||||
XRPL_ASSERT(isTesSuccess(ter), "xrpl::Transactor::reset : result is tesSUCCESS");
|
||||
|
||||
if (isTesSuccess(ter))
|
||||
{
|
||||
view().update(txnAcct);
|
||||
if (payerSle != txnAcct)
|
||||
view().update(payerSle);
|
||||
txnAcct.update();
|
||||
if (hasDelegateAcct)
|
||||
delegateAcct->update();
|
||||
}
|
||||
|
||||
return {ter, fee};
|
||||
|
||||
@@ -711,6 +711,19 @@ NoDeepFreezeTrustLinesWithoutFreeze::finalize(
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
[[nodiscard]] static bool
|
||||
isPseudoAccount(std::shared_ptr<SLE const> sleAcct)
|
||||
{
|
||||
auto const& fields = getPseudoAccountFields();
|
||||
|
||||
// Intentionally use defensive coding here because it's cheap and makes the
|
||||
// semantics of true return value clean.
|
||||
return sleAcct && sleAcct->getType() == ltACCOUNT_ROOT &&
|
||||
std::count_if(fields.begin(), fields.end(), [&sleAcct](SField const* sf) -> bool {
|
||||
return sleAcct->isFieldPresent(*sf);
|
||||
}) > 0;
|
||||
}
|
||||
|
||||
void
|
||||
ValidNewAccountRoot::visitEntry(
|
||||
bool,
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
@@ -106,11 +107,11 @@ ValidLoanBroker::finalize(
|
||||
{
|
||||
for (auto const& field : {&sfLowLimit, &sfHighLimit})
|
||||
{
|
||||
auto const account = view.read(keylet::account(line->at(*field).getIssuer()));
|
||||
auto const account = AccountRoot(line->at(*field).getIssuer(), view, j);
|
||||
// This Invariant doesn't know about the rules for Trust Lines, so
|
||||
// if the account is missing, don't treat it as an error. This
|
||||
// loop is only concerned with finding Broker pseudo-accounts
|
||||
if (account && account->isFieldPresent(sfLoanBrokerID))
|
||||
if (account.exists() && account->isFieldPresent(sfLoanBrokerID))
|
||||
{
|
||||
auto const& loanBrokerID = account->at(sfLoanBrokerID);
|
||||
// create an entry if one doesn't already exist
|
||||
@@ -120,11 +121,11 @@ ValidLoanBroker::finalize(
|
||||
}
|
||||
for (auto const& mpt : mpts_)
|
||||
{
|
||||
auto const account = view.read(keylet::account(mpt->at(sfAccount)));
|
||||
auto const account = AccountRoot(mpt->at(sfAccount), view, j);
|
||||
// This Invariant doesn't know about the rules for MPTokens, so
|
||||
// if the account is missing, don't treat is as an error. This
|
||||
// loop is only concerned with finding Broker pseudo-accounts
|
||||
if (account && account->isFieldPresent(sfLoanBrokerID))
|
||||
if (account.exists() && account->isFieldPresent(sfLoanBrokerID))
|
||||
{
|
||||
auto const& loanBrokerID = account->at(sfLoanBrokerID);
|
||||
// create an entry if one doesn't already exist
|
||||
|
||||
@@ -535,16 +535,15 @@ ValidVault::finalize(
|
||||
result = false;
|
||||
}
|
||||
|
||||
auto const sleSharesIssuer =
|
||||
view.read(keylet::account(updatedShares->share.getIssuer()));
|
||||
if (!sleSharesIssuer)
|
||||
AccountRoot const acctSharesIssuer(updatedShares->share.getIssuer(), view);
|
||||
if (!acctSharesIssuer)
|
||||
{
|
||||
JLOG(j.fatal()) //
|
||||
<< "Invariant failed: shares issuer must exist";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isPseudoAccount(sleSharesIssuer))
|
||||
if (!acctSharesIssuer.isPseudoAccount())
|
||||
{
|
||||
JLOG(j.fatal()) //
|
||||
<< "Invariant failed: shares issuer must be a "
|
||||
@@ -552,7 +551,7 @@ ValidVault::finalize(
|
||||
result = false;
|
||||
}
|
||||
|
||||
if (auto const vaultId = (*sleSharesIssuer)[~sfVaultID];
|
||||
if (auto const vaultId = acctSharesIssuer->at(~sfVaultID);
|
||||
!vaultId || *vaultId != afterVault.key)
|
||||
{
|
||||
JLOG(j.fatal()) //
|
||||
|
||||
@@ -731,7 +731,8 @@ BookStep<TIn, TOut, TDerived>::forEachOffer(
|
||||
// Create MPToken for the offer's owner. No need to check
|
||||
// for the reserve since the offer is removed if it is consumed.
|
||||
// Therefore, the owner count remains the same.
|
||||
if (auto const err = checkCreateMPT(sb, assetIn.get<MPTIssue>(), owner, j_);
|
||||
if (auto const err =
|
||||
WMPTokenIssuance(sb, assetIn.get<MPTIssue>(), j_).checkCreateMPT(owner, j_);
|
||||
!isTesSuccess(err))
|
||||
{
|
||||
return true;
|
||||
@@ -892,7 +893,8 @@ BookStep<TIn, TOut, TDerived>::consumeOffer(
|
||||
if constexpr (std::is_same_v<TOut, MPTAmount>)
|
||||
{
|
||||
if (offer.owner() == issuer)
|
||||
issuerSelfDebitHookMPT(sb, book_.out.get<MPTIssue>(), ofrAmt.out.value());
|
||||
WMPTokenIssuance(sb, book_.out.get<MPTIssue>())
|
||||
.issuerSelfDebitHook(ofrAmt.out.value());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1336,7 +1338,7 @@ BookStep<TIn, TOut, TDerived>::check(StrandContext const& ctx) const
|
||||
}
|
||||
|
||||
auto issuerExists = [](ReadView const& view, Asset const& iss) -> bool {
|
||||
return isXRP(iss.getIssuer()) || view.exists(keylet::account(iss.getIssuer()));
|
||||
return isXRP(iss.getIssuer()) || AccountRoot(iss.getIssuer(), view).exists();
|
||||
};
|
||||
|
||||
if (!issuerExists(ctx.view, book_.in) || !issuerExists(ctx.view, book_.out))
|
||||
@@ -1388,8 +1390,8 @@ BookStep<TIn, TOut, TDerived>::rate(
|
||||
if (isXRP(issuer) || issuer == dstAccount)
|
||||
return kParityRate;
|
||||
return asset.visit(
|
||||
[&](Issue const&) { return transferRate(view, issuer); },
|
||||
[&](MPTIssue const& issue) { return transferRate(view, issue.getMptID()); });
|
||||
[&](Issue const&) { return AccountRoot(issuer, view).transferRate(); },
|
||||
[&](MPTIssue const& issue) { return MPTokenIssuance(view, issue).transferRate(); });
|
||||
};
|
||||
|
||||
template <class TIn, class TOut, class TDerived>
|
||||
@@ -1413,7 +1415,7 @@ BookStep<TIn, TOut, TDerived>::checkMPTDEX(ReadView const& view, AccountID const
|
||||
// BookStep. Fail both if in asset is locked. In the former case it is holder
|
||||
// to locked holder transfer. In the latter case it is not possible to tell if
|
||||
// it is issuer to holder or holder to holder transfer.
|
||||
if (isFrozen(view, owner, book_.in.get<MPTIssue>()))
|
||||
if (MPTokenIssuance(view, book_.in.get<MPTIssue>()).isFrozen(owner))
|
||||
return false;
|
||||
// Previous step is BookStep. BookStep only sends if CanTransfer is
|
||||
// set and not locked or the offer is owned by an issuer
|
||||
|
||||
@@ -448,10 +448,11 @@ DirectIPaymentStep::check(StrandContext const& ctx, std::shared_ptr<const SLE> c
|
||||
}
|
||||
|
||||
{
|
||||
auto const owed = creditBalance(ctx.view, dst_, src_, currency_);
|
||||
auto const iou = IOUIssuance(ctx.view, src_, currency_);
|
||||
auto const owed = iou.creditBalance(dst_);
|
||||
if (owed <= beast::kZero)
|
||||
{
|
||||
auto const limit = creditLimit(ctx.view, dst_, src_, currency_);
|
||||
auto const limit = iou.creditLimit(dst_);
|
||||
if (-owed >= limit)
|
||||
{
|
||||
JLOG(j_.debug()) << "DirectStepI: dry: owed: " << owed << " limit: " << limit;
|
||||
@@ -484,7 +485,7 @@ DirectStepI<TDerived>::maxPaymentFlow(ReadView const& sb) const
|
||||
return {srcOwed, DebtDirection::Redeems};
|
||||
|
||||
// srcOwed is negative or zero
|
||||
return {creditLimit2(sb, dst_, src_, currency_) + srcOwed, DebtDirection::Issues};
|
||||
return {IOUIssuance(sb, src_, currency_).creditLimit2(dst_) + srcOwed, DebtDirection::Issues};
|
||||
}
|
||||
|
||||
template <class TDerived>
|
||||
@@ -761,8 +762,10 @@ DirectStepI<TDerived>::qualitiesSrcIssues(ReadView const& sb, DebtDirection prev
|
||||
"xrpl::DirectStepI::qualitiesSrcIssues : will prevStepDebtDirection "
|
||||
"issue");
|
||||
|
||||
AccountRoot const issuer(src_, sb);
|
||||
|
||||
std::uint32_t const srcQOut =
|
||||
redeems(prevStepDebtDirection) ? transferRate(sb, src_).value : QUALITY_ONE;
|
||||
redeems(prevStepDebtDirection) ? issuer.transferRate().value : QUALITY_ONE;
|
||||
auto dstQIn = static_cast<TDerived const*>(this)->quality(sb, QualityDirection::In);
|
||||
|
||||
if (isLast_ && dstQIn > QUALITY_ONE)
|
||||
@@ -835,8 +838,8 @@ DirectStepI<TDerived>::check(StrandContext const& ctx) const
|
||||
return temBAD_PATH;
|
||||
}
|
||||
|
||||
auto const sleSrc = ctx.view.read(keylet::account(src_));
|
||||
if (!sleSrc)
|
||||
AccountRoot const acctSrc(src_, ctx.view);
|
||||
if (!acctSrc)
|
||||
{
|
||||
JLOG(j_.warn()) << "DirectStepI: can't receive IOUs from non-existent issuer: " << src_;
|
||||
return terNO_ACCOUNT;
|
||||
@@ -895,7 +898,7 @@ DirectStepI<TDerived>::check(StrandContext const& ctx) const
|
||||
}
|
||||
}
|
||||
|
||||
return static_cast<TDerived const*>(this)->check(ctx, sleSrc);
|
||||
return static_cast<TDerived const*>(this)->check(ctx, acctSrc.sle());
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/beast/utility/Zero.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/core/ServiceRegistry.h>
|
||||
#include <xrpl/ledger/PaymentSandbox.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
@@ -264,7 +264,7 @@ public:
|
||||
// Verify the consistency of the step. These checks are specific to
|
||||
// payments and assume that general checks were already performed.
|
||||
[[nodiscard]] TER
|
||||
check(StrandContext const& ctx, std::shared_ptr<const SLE> const& sleSrc) const;
|
||||
check(StrandContext const& ctx, AccountRoot<ReadView> const& srcRoot) const;
|
||||
|
||||
[[nodiscard]] std::string
|
||||
logString() const override
|
||||
@@ -312,7 +312,7 @@ public:
|
||||
// Verify the consistency of the step. These checks are specific to
|
||||
// offer crossing and assume that general checks were already performed.
|
||||
static TER
|
||||
check(StrandContext const& ctx, std::shared_ptr<const SLE> const& sleSrc);
|
||||
check(StrandContext const& ctx, AccountRoot<ReadView> const& srcRoot);
|
||||
|
||||
[[nodiscard]] std::string
|
||||
logString() const override
|
||||
@@ -328,8 +328,7 @@ public:
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
TER
|
||||
MPTEndpointPaymentStep::check(StrandContext const& ctx, std::shared_ptr<const SLE> const& sleSrc)
|
||||
const
|
||||
MPTEndpointPaymentStep::check(StrandContext const& ctx, AccountRoot<ReadView> const& srcRoot) const
|
||||
{
|
||||
// Since this is a payment, MPToken must be present. Perform all
|
||||
// MPToken related checks.
|
||||
@@ -337,15 +336,16 @@ MPTEndpointPaymentStep::check(StrandContext const& ctx, std::shared_ptr<const SL
|
||||
// requireAuth checks if MPTIssuance exist. Note that issuer to issuer
|
||||
// payment is invalid
|
||||
auto const& issuer = mptIssue_.getIssuer();
|
||||
auto const issuance = MPTokenIssuance(ctx.view, mptIssue_);
|
||||
if (src_ != issuer)
|
||||
{
|
||||
if (auto const ter = requireAuth(ctx.view, mptIssue_, src_); !isTesSuccess(ter))
|
||||
if (auto const ter = issuance.requireAuth(src_); !isTesSuccess(ter))
|
||||
return ter;
|
||||
}
|
||||
|
||||
if (dst_ != issuer)
|
||||
{
|
||||
if (auto const ter = requireAuth(ctx.view, mptIssue_, dst_); !isTesSuccess(ter))
|
||||
if (auto const ter = issuance.requireAuth(dst_); !isTesSuccess(ter))
|
||||
return ter;
|
||||
}
|
||||
|
||||
@@ -358,11 +358,10 @@ MPTEndpointPaymentStep::check(StrandContext const& ctx, std::shared_ptr<const SL
|
||||
{
|
||||
auto const& holder = ctx.isFirst ? src_ : dst_;
|
||||
// Payment between the holders
|
||||
if (isFrozen(ctx.view, holder, mptIssue_))
|
||||
if (issuance.isFrozen(holder))
|
||||
return tecLOCKED;
|
||||
|
||||
if (auto const ter = canTransfer(ctx.view, mptIssue_, holder, ctx.strandDst);
|
||||
!isTesSuccess(ter))
|
||||
if (auto const ter = issuance.canTransfer(holder, ctx.strandDst); !isTesSuccess(ter))
|
||||
return ter;
|
||||
}
|
||||
// Don't need to check if a payment is between issuer and holder
|
||||
@@ -393,7 +392,7 @@ MPTEndpointPaymentStep::check(StrandContext const& ctx, std::shared_ptr<const SL
|
||||
}
|
||||
|
||||
TER
|
||||
MPTEndpointOfferCrossingStep::check(StrandContext const& ctx, std::shared_ptr<const SLE> const&)
|
||||
MPTEndpointOfferCrossingStep::check(StrandContext const& ctx, AccountRoot<ReadView> const&)
|
||||
{
|
||||
return tesSUCCESS;
|
||||
}
|
||||
@@ -408,7 +407,8 @@ MPTEndpointOfferCrossingStep::checkCreateMPT(ApplyView& view, xrpl::DebtDirectio
|
||||
// for the reserve since the offer doesn't go on the books
|
||||
// if crossed. Insufficient reserve is allowed if the offer
|
||||
// crossed. See CreateOffer::applyGuts() for reserve check.
|
||||
if (auto const err = xrpl::checkCreateMPT(view, mptIssue_, dst_, j_); !isTesSuccess(err))
|
||||
if (auto const err = xrpl::WMPTokenIssuance(view, mptIssue_, j_).checkCreateMPT(dst_, j_);
|
||||
!isTesSuccess(err))
|
||||
{
|
||||
JLOG(j_.trace()) << "MPTEndpointStep::checkCreateMPT: failed create MPT";
|
||||
resetCache(srcDebtDir);
|
||||
@@ -757,8 +757,9 @@ MPTEndpointStep<TDerived>::qualitiesSrcIssues(
|
||||
"MPTEndpointStep<TDerived>::qualitiesSrcIssues : verify prev step debt "
|
||||
"direction");
|
||||
|
||||
std::uint32_t const srcQOut =
|
||||
redeems(prevStepDebtDirection) ? transferRate(sb, mptIssue_.getMptID()).value : QUALITY_ONE;
|
||||
std::uint32_t const srcQOut = redeems(prevStepDebtDirection)
|
||||
? MPTokenIssuance(sb, mptIssue_).transferRate().value
|
||||
: QUALITY_ONE;
|
||||
|
||||
// Unlike trustline, MPT doesn't have line quality field
|
||||
return {srcQOut, QUALITY_ONE};
|
||||
@@ -830,8 +831,8 @@ MPTEndpointStep<TDerived>::check(StrandContext const& ctx) const
|
||||
return temBAD_PATH;
|
||||
}
|
||||
|
||||
auto const sleSrc = ctx.view.read(keylet::account(src_));
|
||||
if (!sleSrc)
|
||||
AccountRoot const srcRoot(src_, ctx.view);
|
||||
if (!srcRoot.exists())
|
||||
{
|
||||
JLOG(j_.warn()) << "MPTEndpointStep: can't receive MPT from non-existent issuer: " << src_;
|
||||
return terNO_ACCOUNT;
|
||||
@@ -841,7 +842,7 @@ MPTEndpointStep<TDerived>::check(StrandContext const& ctx) const
|
||||
if (!(ctx.isLast && ctx.isFirst))
|
||||
{
|
||||
auto const& account = ctx.isFirst ? src_ : dst_;
|
||||
if (isFrozen(ctx.view, account, mptIssue_))
|
||||
if (MPTokenIssuance(ctx.view, mptIssue_).isFrozen(account))
|
||||
return terLOCKED;
|
||||
}
|
||||
|
||||
@@ -886,7 +887,7 @@ MPTEndpointStep<TDerived>::check(StrandContext const& ctx) const
|
||||
return temBAD_PATH;
|
||||
}
|
||||
|
||||
return static_cast<TDerived const*>(this)->check(ctx, sleSrc);
|
||||
return static_cast<TDerived const*>(this)->check(ctx, srcRoot);
|
||||
}
|
||||
|
||||
template <class TDerived>
|
||||
|
||||
@@ -122,7 +122,8 @@ accountFundsHelper(
|
||||
{
|
||||
if (id == asset.getIssuer())
|
||||
{
|
||||
return toAmount<T>(issuerFundsToSelfIssue(view, asset.get<MPTIssue>()));
|
||||
return toAmount<T>(
|
||||
MPTokenIssuance(view, asset.get<MPTIssue>()).issuerFundsToSelfIssue());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -119,7 +119,8 @@ protected:
|
||||
XRPAmount
|
||||
xrpLiquidImpl(ReadView& sb, std::int32_t reserveReduction) const
|
||||
{
|
||||
return xrpl::xrpLiquid(sb, acc_, reserveReduction, j_);
|
||||
AccountRoot const account(acc_, sb, j_);
|
||||
return account.xrpLiquid(reserveReduction);
|
||||
}
|
||||
|
||||
std::string
|
||||
@@ -343,8 +344,8 @@ XRPEndpointStep<TDerived>::check(StrandContext const& ctx) const
|
||||
return temBAD_PATH;
|
||||
}
|
||||
|
||||
auto sleAcc = ctx.view.read(keylet::account(acc_));
|
||||
if (!sleAcc)
|
||||
AccountRoot const acctRoot(acc_, ctx.view);
|
||||
if (!acctRoot)
|
||||
{
|
||||
JLOG(j_.warn()) << "XRPEndpointStep: can't send or receive XRP from "
|
||||
"non-existent account: "
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/CredentialHelpers.h>
|
||||
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
|
||||
#include <xrpl/ledger/helpers/NFTokenHelpers.h>
|
||||
@@ -226,12 +227,12 @@ AccountDelete::preclaim(PreclaimContext const& ctx)
|
||||
AccountID const account{ctx.tx[sfAccount]};
|
||||
AccountID const dst{ctx.tx[sfDestination]};
|
||||
|
||||
auto sleDst = ctx.view.read(keylet::account(dst));
|
||||
AccountRoot const acctDst(dst, ctx.view);
|
||||
|
||||
if (!sleDst)
|
||||
if (!acctDst)
|
||||
return tecNO_DST;
|
||||
|
||||
if (sleDst->isFlag(lsfRequireDestTag) && !ctx.tx[~sfDestinationTag])
|
||||
if (acctDst->isFlag(lsfRequireDestTag) && !ctx.tx[~sfDestinationTag])
|
||||
return tecDST_TAG_NEEDED;
|
||||
|
||||
// If credentials are provided - check them anyway
|
||||
@@ -243,21 +244,21 @@ AccountDelete::preclaim(PreclaimContext const& ctx)
|
||||
if (!ctx.tx.isFieldPresent(sfCredentialIDs))
|
||||
{
|
||||
// Check whether the destination account requires deposit authorization.
|
||||
if (sleDst->isFlag(lsfDepositAuth))
|
||||
if (acctDst->isFlag(lsfDepositAuth))
|
||||
{
|
||||
if (!ctx.view.exists(keylet::depositPreauth(dst, account)))
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
}
|
||||
|
||||
auto sleAccount = ctx.view.read(keylet::account(account));
|
||||
XRPL_ASSERT(sleAccount, "xrpl::AccountDelete::preclaim : non-null account");
|
||||
if (!sleAccount)
|
||||
AccountRoot const acctSrc(account, ctx.view);
|
||||
XRPL_ASSERT(acctSrc, "xrpl::AccountDelete::preclaim : non-null account");
|
||||
if (!acctSrc)
|
||||
return terNO_ACCOUNT;
|
||||
|
||||
// If an issuer has any issued NFTs resident in the ledger then it
|
||||
// cannot be deleted.
|
||||
if ((*sleAccount)[~sfMintedNFTokens] != (*sleAccount)[~sfBurnedNFTokens])
|
||||
if (acctSrc->at(~sfMintedNFTokens) != acctSrc->at(~sfBurnedNFTokens))
|
||||
return tecHAS_OBLIGATIONS;
|
||||
|
||||
// If the account owns any NFTs it cannot be deleted.
|
||||
@@ -276,7 +277,7 @@ AccountDelete::preclaim(PreclaimContext const& ctx)
|
||||
// We look at the account's Sequence rather than the transaction's
|
||||
// Sequence in preparation for Tickets.
|
||||
static constexpr std::uint32_t kSeqDelta{255};
|
||||
if ((*sleAccount)[sfSequence] + kSeqDelta > ctx.view.seq())
|
||||
if (acctSrc->getFieldU32(sfSequence) + kSeqDelta > ctx.view.seq())
|
||||
return tecTOO_SOON;
|
||||
|
||||
// We don't allow an account to be deleted if
|
||||
@@ -290,8 +291,8 @@ AccountDelete::preclaim(PreclaimContext const& ctx)
|
||||
// their account and mints a NFToken, it is possible that the
|
||||
// NFTokenSequence of this NFToken is the same as the one that the
|
||||
// authorized minter minted in a previous ledger.
|
||||
if ((*sleAccount)[~sfFirstNFTokenSequence].value_or(0) +
|
||||
(*sleAccount)[~sfMintedNFTokens].value_or(0) + kSeqDelta >
|
||||
if (acctSrc->at(~sfFirstNFTokenSequence).value_or(0) +
|
||||
acctSrc->at(~sfMintedNFTokens).value_or(0) + kSeqDelta >
|
||||
ctx.view.seq())
|
||||
return tecTOO_SOON;
|
||||
|
||||
@@ -344,20 +345,19 @@ AccountDelete::preclaim(PreclaimContext const& ctx)
|
||||
TER
|
||||
AccountDelete::doApply()
|
||||
{
|
||||
auto src = view().peek(keylet::account(accountID_));
|
||||
XRPL_ASSERT(src, "xrpl::AccountDelete::doApply : non-null source account");
|
||||
WAccountRoot src(accountID_, view(), j_);
|
||||
XRPL_ASSERT(src.exists(), "xrpl::AccountDelete::doApply : non-null source account");
|
||||
|
||||
auto const dstID = ctx_.tx[sfDestination];
|
||||
auto dst = view().peek(keylet::account(dstID));
|
||||
XRPL_ASSERT(dst, "xrpl::AccountDelete::doApply : non-null destination account");
|
||||
WAccountRoot dst(dstID, view(), j_);
|
||||
XRPL_ASSERT(dst.exists(), "xrpl::AccountDelete::doApply : non-null destination account");
|
||||
|
||||
if (!src || !dst)
|
||||
if (!src.exists() || !dst.exists())
|
||||
return tefBAD_LEDGER; // LCOV_EXCL_LINE
|
||||
|
||||
if (ctx_.tx.isFieldPresent(sfCredentialIDs))
|
||||
{
|
||||
if (auto err =
|
||||
verifyDepositPreauth(ctx_.tx, ctx_.view(), accountID_, dstID, dst, ctx_.journal);
|
||||
if (auto err = verifyDepositPreauth(ctx_.tx, ctx_.view(), accountID_, dst, ctx_.journal);
|
||||
!isTesSuccess(err))
|
||||
return err;
|
||||
}
|
||||
@@ -410,8 +410,8 @@ AccountDelete::doApply()
|
||||
if (remainingBalance > XRPAmount(0) && dst->isFlag(lsfPasswordSpent))
|
||||
dst->clearFlag(lsfPasswordSpent);
|
||||
|
||||
view().update(dst);
|
||||
view().erase(src);
|
||||
dst.update();
|
||||
src.erase();
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/DelegateHelpers.h>
|
||||
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
@@ -222,8 +223,8 @@ AccountSet::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
auto const id = ctx.tx[sfAccount];
|
||||
|
||||
auto const sle = ctx.view.read(keylet::account(id));
|
||||
if (!sle)
|
||||
AccountRoot const acctRoot(id, ctx.view);
|
||||
if (!acctRoot)
|
||||
return terNO_ACCOUNT;
|
||||
|
||||
std::uint32_t const uSetFlag = ctx.tx.getFieldU32(sfSetFlag);
|
||||
@@ -234,7 +235,7 @@ AccountSet::preclaim(PreclaimContext const& ctx)
|
||||
//
|
||||
// RequireAuth
|
||||
//
|
||||
if (bSetRequireAuth && !sle->isFlag(lsfRequireAuth))
|
||||
if (bSetRequireAuth && !acctRoot->isFlag(lsfRequireAuth))
|
||||
{
|
||||
if (!dirIsEmpty(ctx.view, keylet::ownerDir(id)))
|
||||
{
|
||||
@@ -250,7 +251,7 @@ AccountSet::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
if (uSetFlag == asfAllowTrustLineClawback)
|
||||
{
|
||||
if (sle->isFlag(lsfNoFreeze))
|
||||
if (acctRoot->isFlag(lsfNoFreeze))
|
||||
{
|
||||
JLOG(ctx.j.trace()) << "Can't set Clawback if NoFreeze is set";
|
||||
return tecNO_PERMISSION;
|
||||
@@ -265,7 +266,7 @@ AccountSet::preclaim(PreclaimContext const& ctx)
|
||||
else if (uSetFlag == asfNoFreeze)
|
||||
{
|
||||
// Cannot set NoFreeze if clawback is enabled
|
||||
if (sle->isFlag(lsfAllowTrustLineClawback))
|
||||
if (acctRoot->isFlag(lsfAllowTrustLineClawback))
|
||||
{
|
||||
JLOG(ctx.j.trace()) << "Can't set NoFreeze if clawback is enabled";
|
||||
return tecNO_PERMISSION;
|
||||
@@ -279,11 +280,10 @@ AccountSet::preclaim(PreclaimContext const& ctx)
|
||||
TER
|
||||
AccountSet::doApply()
|
||||
{
|
||||
auto const sle = view().peek(keylet::account(accountID_));
|
||||
if (!sle)
|
||||
if (!account_)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
std::uint32_t const uFlagsIn = sle->getFieldU32(sfFlags);
|
||||
std::uint32_t const uFlagsIn = account_->getFieldU32(sfFlags);
|
||||
std::uint32_t uFlagsOut = uFlagsIn;
|
||||
|
||||
STTx const& tx{ctx_.tx};
|
||||
@@ -314,13 +314,13 @@ AccountSet::doApply()
|
||||
//
|
||||
// RequireAuth
|
||||
//
|
||||
if (bSetRequireAuth && !sle->isFlag(lsfRequireAuth))
|
||||
if (bSetRequireAuth && !account_->isFlag(lsfRequireAuth))
|
||||
{
|
||||
JLOG(j_.trace()) << "Set RequireAuth.";
|
||||
uFlagsOut |= lsfRequireAuth;
|
||||
}
|
||||
|
||||
if (bClearRequireAuth && sle->isFlag(lsfRequireAuth))
|
||||
if (bClearRequireAuth && account_->isFlag(lsfRequireAuth))
|
||||
{
|
||||
JLOG(j_.trace()) << "Clear RequireAuth.";
|
||||
uFlagsOut &= ~lsfRequireAuth;
|
||||
@@ -329,13 +329,13 @@ AccountSet::doApply()
|
||||
//
|
||||
// RequireDestTag
|
||||
//
|
||||
if (bSetRequireDest && !sle->isFlag(lsfRequireDestTag))
|
||||
if (bSetRequireDest && !account_->isFlag(lsfRequireDestTag))
|
||||
{
|
||||
JLOG(j_.trace()) << "Set lsfRequireDestTag.";
|
||||
uFlagsOut |= lsfRequireDestTag;
|
||||
}
|
||||
|
||||
if (bClearRequireDest && sle->isFlag(lsfRequireDestTag))
|
||||
if (bClearRequireDest && account_->isFlag(lsfRequireDestTag))
|
||||
{
|
||||
JLOG(j_.trace()) << "Clear lsfRequireDestTag.";
|
||||
uFlagsOut &= ~lsfRequireDestTag;
|
||||
@@ -344,13 +344,13 @@ AccountSet::doApply()
|
||||
//
|
||||
// DisallowXRP
|
||||
//
|
||||
if (bSetDisallowXRP && !sle->isFlag(lsfDisallowXRP))
|
||||
if (bSetDisallowXRP && !account_->isFlag(lsfDisallowXRP))
|
||||
{
|
||||
JLOG(j_.trace()) << "Set lsfDisallowXRP.";
|
||||
uFlagsOut |= lsfDisallowXRP;
|
||||
}
|
||||
|
||||
if (bClearDisallowXRP && sle->isFlag(lsfDisallowXRP))
|
||||
if (bClearDisallowXRP && account_->isFlag(lsfDisallowXRP))
|
||||
{
|
||||
JLOG(j_.trace()) << "Clear lsfDisallowXRP.";
|
||||
uFlagsOut &= ~lsfDisallowXRP;
|
||||
@@ -359,7 +359,7 @@ AccountSet::doApply()
|
||||
//
|
||||
// DisableMaster
|
||||
//
|
||||
if ((uSetFlag == asfDisableMaster) && !sle->isFlag(lsfDisableMaster))
|
||||
if ((uSetFlag == asfDisableMaster) && !account_->isFlag(lsfDisableMaster))
|
||||
{
|
||||
if (!sigWithMaster)
|
||||
{
|
||||
@@ -367,7 +367,8 @@ AccountSet::doApply()
|
||||
return tecNEED_MASTER_KEY;
|
||||
}
|
||||
|
||||
if ((!sle->isFieldPresent(sfRegularKey)) && (!view().peek(keylet::signers(accountID_))))
|
||||
if ((!account_->isFieldPresent(sfRegularKey)) &&
|
||||
(!view().peek(keylet::signers(accountID_))))
|
||||
{
|
||||
// Account has no regular key or multi-signer signer list.
|
||||
return tecNO_ALTERNATIVE_KEY;
|
||||
@@ -377,7 +378,7 @@ AccountSet::doApply()
|
||||
uFlagsOut |= lsfDisableMaster;
|
||||
}
|
||||
|
||||
if ((uClearFlag == asfDisableMaster) && sle->isFlag(lsfDisableMaster))
|
||||
if ((uClearFlag == asfDisableMaster) && account_->isFlag(lsfDisableMaster))
|
||||
{
|
||||
JLOG(j_.trace()) << "Clear lsfDisableMaster.";
|
||||
uFlagsOut &= ~lsfDisableMaster;
|
||||
@@ -402,7 +403,7 @@ AccountSet::doApply()
|
||||
//
|
||||
if (uSetFlag == asfNoFreeze)
|
||||
{
|
||||
if (!sigWithMaster && !sle->isFlag(lsfDisableMaster))
|
||||
if (!sigWithMaster && !account_->isFlag(lsfDisableMaster))
|
||||
{
|
||||
JLOG(j_.trace()) << "Must use master key to set NoFreeze.";
|
||||
return tecNEED_MASTER_KEY;
|
||||
@@ -432,16 +433,16 @@ AccountSet::doApply()
|
||||
//
|
||||
// Track transaction IDs signed by this account in its root
|
||||
//
|
||||
if ((uSetFlag == asfAccountTxnID) && !sle->isFieldPresent(sfAccountTxnID))
|
||||
if ((uSetFlag == asfAccountTxnID) && !account_->isFieldPresent(sfAccountTxnID))
|
||||
{
|
||||
JLOG(j_.trace()) << "Set AccountTxnID.";
|
||||
sle->makeFieldPresent(sfAccountTxnID);
|
||||
account_->makeFieldPresent(sfAccountTxnID);
|
||||
}
|
||||
|
||||
if ((uClearFlag == asfAccountTxnID) && sle->isFieldPresent(sfAccountTxnID))
|
||||
if ((uClearFlag == asfAccountTxnID) && account_->isFieldPresent(sfAccountTxnID))
|
||||
{
|
||||
JLOG(j_.trace()) << "Clear AccountTxnID.";
|
||||
sle->makeFieldAbsent(sfAccountTxnID);
|
||||
account_->makeFieldAbsent(sfAccountTxnID);
|
||||
}
|
||||
|
||||
//
|
||||
@@ -468,12 +469,12 @@ AccountSet::doApply()
|
||||
if (!uHash)
|
||||
{
|
||||
JLOG(j_.trace()) << "unset email hash";
|
||||
sle->makeFieldAbsent(sfEmailHash);
|
||||
account_->makeFieldAbsent(sfEmailHash);
|
||||
}
|
||||
else
|
||||
{
|
||||
JLOG(j_.trace()) << "set email hash";
|
||||
sle->setFieldH128(sfEmailHash, uHash);
|
||||
account_->setFieldH128(sfEmailHash, uHash);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -487,12 +488,12 @@ AccountSet::doApply()
|
||||
if (!uHash)
|
||||
{
|
||||
JLOG(j_.trace()) << "unset wallet locator";
|
||||
sle->makeFieldAbsent(sfWalletLocator);
|
||||
account_->makeFieldAbsent(sfWalletLocator);
|
||||
}
|
||||
else
|
||||
{
|
||||
JLOG(j_.trace()) << "set wallet locator";
|
||||
sle->setFieldH256(sfWalletLocator, uHash);
|
||||
account_->setFieldH256(sfWalletLocator, uHash);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -506,12 +507,12 @@ AccountSet::doApply()
|
||||
if (messageKey.empty())
|
||||
{
|
||||
JLOG(j_.debug()) << "clear message key";
|
||||
sle->makeFieldAbsent(sfMessageKey);
|
||||
account_->makeFieldAbsent(sfMessageKey);
|
||||
}
|
||||
else
|
||||
{
|
||||
JLOG(j_.debug()) << "set message key";
|
||||
sle->setFieldVL(sfMessageKey, messageKey);
|
||||
account_->setFieldVL(sfMessageKey, messageKey);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -525,12 +526,12 @@ AccountSet::doApply()
|
||||
if (domain.empty())
|
||||
{
|
||||
JLOG(j_.trace()) << "unset domain";
|
||||
sle->makeFieldAbsent(sfDomain);
|
||||
account_->makeFieldAbsent(sfDomain);
|
||||
}
|
||||
else
|
||||
{
|
||||
JLOG(j_.trace()) << "set domain";
|
||||
sle->setFieldVL(sfDomain, domain);
|
||||
account_->setFieldVL(sfDomain, domain);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -544,12 +545,12 @@ AccountSet::doApply()
|
||||
if (uRate == 0 || uRate == QUALITY_ONE)
|
||||
{
|
||||
JLOG(j_.trace()) << "unset transfer rate";
|
||||
sle->makeFieldAbsent(sfTransferRate);
|
||||
account_->makeFieldAbsent(sfTransferRate);
|
||||
}
|
||||
else
|
||||
{
|
||||
JLOG(j_.trace()) << "set transfer rate";
|
||||
sle->setFieldU32(sfTransferRate, uRate);
|
||||
account_->setFieldU32(sfTransferRate, uRate);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -562,21 +563,21 @@ AccountSet::doApply()
|
||||
if ((uTickSize == 0) || (uTickSize == Quality::kMaxTickSize))
|
||||
{
|
||||
JLOG(j_.trace()) << "unset tick size";
|
||||
sle->makeFieldAbsent(sfTickSize);
|
||||
account_->makeFieldAbsent(sfTickSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
JLOG(j_.trace()) << "set tick size";
|
||||
sle->setFieldU8(sfTickSize, uTickSize);
|
||||
account_->setFieldU8(sfTickSize, uTickSize);
|
||||
}
|
||||
}
|
||||
|
||||
// Configure authorized minting account:
|
||||
if (uSetFlag == asfAuthorizedNFTokenMinter)
|
||||
sle->setAccountID(sfNFTokenMinter, ctx_.tx[sfNFTokenMinter]);
|
||||
account_->setAccountID(sfNFTokenMinter, ctx_.tx[sfNFTokenMinter]);
|
||||
|
||||
if (uClearFlag == asfAuthorizedNFTokenMinter && sle->isFieldPresent(sfNFTokenMinter))
|
||||
sle->makeFieldAbsent(sfNFTokenMinter);
|
||||
if (uClearFlag == asfAuthorizedNFTokenMinter && account_->isFieldPresent(sfNFTokenMinter))
|
||||
account_->makeFieldAbsent(sfNFTokenMinter);
|
||||
|
||||
if (uSetFlag == asfDisallowIncomingNFTokenOffer)
|
||||
{
|
||||
@@ -635,9 +636,9 @@ AccountSet::doApply()
|
||||
}
|
||||
|
||||
if (uFlagsIn != uFlagsOut)
|
||||
sle->setFieldU32(sfFlags, uFlagsOut);
|
||||
account_->setFieldU32(sfFlags, uFlagsOut);
|
||||
|
||||
ctx_.view().update(sle);
|
||||
account_.update();
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <xrpl/basics/Slice.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/PublicKey.h>
|
||||
@@ -27,9 +28,8 @@ SetRegularKey::calculateBaseFee(ReadView const& view, STTx const& tx)
|
||||
{
|
||||
if (calcAccountID(PublicKey(makeSlice(spk))) == id)
|
||||
{
|
||||
auto const sle = view.read(keylet::account(id));
|
||||
|
||||
if (sle && !sle->isFlag(lsfPasswordSpent))
|
||||
AccountRoot const acct(id, view);
|
||||
if (acct && !acct->isFlag(lsfPasswordSpent))
|
||||
{
|
||||
// flag is armed and they signed with the right account
|
||||
return XRPAmount{0};
|
||||
@@ -55,27 +55,26 @@ SetRegularKey::preflight(PreflightContext const& ctx)
|
||||
TER
|
||||
SetRegularKey::doApply()
|
||||
{
|
||||
auto const sle = view().peek(keylet::account(accountID_));
|
||||
if (!sle)
|
||||
if (!account_)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
if (!minimumFee(ctx_.registry, ctx_.baseFee, view().fees(), view().flags()))
|
||||
sle->setFlag(lsfPasswordSpent);
|
||||
account_->setFlag(lsfPasswordSpent);
|
||||
|
||||
if (ctx_.tx.isFieldPresent(sfRegularKey))
|
||||
{
|
||||
sle->setAccountID(sfRegularKey, ctx_.tx.getAccountID(sfRegularKey));
|
||||
account_->setAccountID(sfRegularKey, ctx_.tx.getAccountID(sfRegularKey));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Account has disabled master key and no multi-signer signer list.
|
||||
if (sle->isFlag(lsfDisableMaster) && !view().peek(keylet::signers(accountID_)))
|
||||
if (account_->isFlag(lsfDisableMaster) && !view().peek(keylet::signers(accountID_)))
|
||||
return tecNO_ALTERNATIVE_KEY;
|
||||
|
||||
sle->makeFieldAbsent(sfRegularKey);
|
||||
account_->makeFieldAbsent(sfRegularKey);
|
||||
}
|
||||
|
||||
ctx_.view().update(sle);
|
||||
account_.update();
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
@@ -180,11 +180,11 @@ static TER
|
||||
removeSignersFromLedger(
|
||||
ServiceRegistry& registry,
|
||||
ApplyView& view,
|
||||
Keylet const& accountKeylet,
|
||||
Keylet const& ownerDirKeylet,
|
||||
Keylet const& signerListKeylet,
|
||||
AccountID const& account,
|
||||
beast::Journal j)
|
||||
{
|
||||
auto const ownerDirKeylet = keylet::ownerDir(account);
|
||||
auto const signerListKeylet = keylet::signers(account);
|
||||
// We have to examine the current SignerList so we know how much to
|
||||
// reduce the OwnerCount.
|
||||
SLE::pointer const signers = view.peek(signerListKeylet);
|
||||
@@ -215,8 +215,8 @@ removeSignersFromLedger(
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
adjustOwnerCount(
|
||||
view, view.peek(accountKeylet), removeFromOwnerCount, registry.getJournal("View"));
|
||||
WAccountRoot wrappedAcct(account, view, j);
|
||||
wrappedAcct.adjustOwnerCount(removeFromOwnerCount);
|
||||
|
||||
view.erase(signers);
|
||||
|
||||
@@ -230,12 +230,7 @@ SignerListSet::removeFromLedger(
|
||||
AccountID const& account,
|
||||
beast::Journal j)
|
||||
{
|
||||
auto const accountKeylet = keylet::account(account);
|
||||
auto const ownerDirKeylet = keylet::ownerDir(account);
|
||||
auto const signerListKeylet = keylet::signers(account);
|
||||
|
||||
return removeSignersFromLedger(
|
||||
registry, view, accountKeylet, ownerDirKeylet, signerListKeylet, j);
|
||||
return removeSignersFromLedger(registry, view, account, j);
|
||||
}
|
||||
|
||||
NotTEC
|
||||
@@ -300,23 +295,18 @@ SignerListSet::validateQuorumAndSignerEntries(
|
||||
TER
|
||||
SignerListSet::replaceSignerList()
|
||||
{
|
||||
auto const accountKeylet = keylet::account(accountID_);
|
||||
auto const ownerDirKeylet = keylet::ownerDir(accountID_);
|
||||
auto const signerListKeylet = keylet::signers(accountID_);
|
||||
|
||||
// This may be either a create or a replace. Preemptively remove any
|
||||
// old signer list. May reduce the reserve, so this is done before
|
||||
// checking the reserve.
|
||||
if (TER const ter = removeSignersFromLedger(
|
||||
ctx_.registry, view(), accountKeylet, ownerDirKeylet, signerListKeylet, j_))
|
||||
if (TER const ter = removeSignersFromLedger(ctx_.registry, view(), accountID_, j_))
|
||||
return ter;
|
||||
|
||||
auto const sle = view().peek(accountKeylet);
|
||||
if (!sle)
|
||||
WAccountRoot wrappedAcct(accountID_, view(), j_);
|
||||
if (!wrappedAcct)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
// Compute new reserve. Verify the account has funds to meet the reserve.
|
||||
std::uint32_t const oldOwnerCount{(*sle)[sfOwnerCount]};
|
||||
std::uint32_t const oldOwnerCount{(*wrappedAcct)[sfOwnerCount]};
|
||||
|
||||
static constexpr int kAddedOwnerCount = 1;
|
||||
std::uint32_t const flags{lsfOneOwnerCount};
|
||||
@@ -330,11 +320,12 @@ SignerListSet::replaceSignerList()
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
|
||||
// Everything's ducky. Add the ltSIGNER_LIST to the ledger.
|
||||
Keylet const ownerDirKeylet = keylet::ownerDir(accountID_);
|
||||
Keylet const signerListKeylet = keylet::signers(accountID_);
|
||||
auto signerList = std::make_shared<SLE>(signerListKeylet);
|
||||
view().insert(signerList);
|
||||
writeSignersToSLE(signerList, flags);
|
||||
|
||||
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_));
|
||||
@@ -349,27 +340,22 @@ SignerListSet::replaceSignerList()
|
||||
|
||||
// If we succeeded, the new entry counts against the
|
||||
// creator's reserve.
|
||||
adjustOwnerCount(view(), sle, kAddedOwnerCount, viewJ);
|
||||
wrappedAcct.adjustOwnerCount(kAddedOwnerCount);
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
SignerListSet::destroySignerList()
|
||||
{
|
||||
auto const accountKeylet = keylet::account(accountID_);
|
||||
// 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);
|
||||
if (!ledgerEntry)
|
||||
if (!account_)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
if ((ledgerEntry->isFlag(lsfDisableMaster)) && (!ledgerEntry->isFieldPresent(sfRegularKey)))
|
||||
if ((account_->isFlag(lsfDisableMaster)) && (!account_->isFieldPresent(sfRegularKey)))
|
||||
return tecNO_ALTERNATIVE_KEY;
|
||||
|
||||
auto const ownerDirKeylet = keylet::ownerDir(accountID_);
|
||||
auto const signerListKeylet = keylet::signers(accountID_);
|
||||
return removeSignersFromLedger(
|
||||
ctx_.registry, view(), accountKeylet, ownerDirKeylet, signerListKeylet, j_);
|
||||
return removeSignersFromLedger(ctx_.registry, view(), accountID_, j_);
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -124,13 +124,12 @@ checkAttestationPublicKey(
|
||||
|
||||
AccountID const accountFromPK = calcAccountID(pk);
|
||||
|
||||
if (auto const sleAttestationSigningAccount =
|
||||
view.read(keylet::account(attestationSignerAccount)))
|
||||
if (AccountRoot const acctSigner(attestationSignerAccount, view); acctSigner)
|
||||
{
|
||||
if (accountFromPK == attestationSignerAccount)
|
||||
{
|
||||
// master key
|
||||
if (sleAttestationSigningAccount->isFlag(lsfDisableMaster))
|
||||
if (acctSigner->isFlag(lsfDisableMaster))
|
||||
{
|
||||
JLOG(j.trace()) << "Attempt to add an attestation with "
|
||||
"disabled master key.";
|
||||
@@ -140,8 +139,7 @@ checkAttestationPublicKey(
|
||||
else
|
||||
{
|
||||
// regular key
|
||||
if (std::optional<AccountID> const regularKey =
|
||||
(*sleAttestationSigningAccount)[~sfRegularKey];
|
||||
if (std::optional<AccountID> const regularKey = acctSigner->at(~sfRegularKey);
|
||||
regularKey != accountFromPK)
|
||||
{
|
||||
if (!regularKey)
|
||||
@@ -403,12 +401,12 @@ transferHelper(
|
||||
if (dst == src)
|
||||
return tesSUCCESS;
|
||||
|
||||
auto const dstK = keylet::account(dst);
|
||||
if (auto sleDst = psb.read(dstK))
|
||||
AccountRoot const acctDst(dst, psb);
|
||||
if (acctDst)
|
||||
{
|
||||
// Check dst tag and deposit auth
|
||||
|
||||
if (sleDst->isFlag(lsfRequireDestTag) && !dstTag)
|
||||
if (acctDst->isFlag(lsfRequireDestTag) && !dstTag)
|
||||
return tecDST_TAG_NEEDED;
|
||||
|
||||
// If the destination is the claim owner, and this is a claim
|
||||
@@ -417,7 +415,7 @@ transferHelper(
|
||||
bool const canBypassDepositAuth =
|
||||
dst == claimOwner && depositAuthPolicy == DepositAuthPolicy::DstCanBypass;
|
||||
|
||||
if (!canBypassDepositAuth && sleDst->isFlag(lsfDepositAuth) &&
|
||||
if (!canBypassDepositAuth && acctDst->isFlag(lsfDepositAuth) &&
|
||||
!psb.exists(keylet::depositPreauth(dst, src)))
|
||||
{
|
||||
return tecNO_PERMISSION;
|
||||
@@ -430,17 +428,17 @@ transferHelper(
|
||||
|
||||
if (amt.native())
|
||||
{
|
||||
auto const sleSrc = psb.peek(keylet::account(src));
|
||||
XRPL_ASSERT(sleSrc, "xrpl::transferHelper : non-null source account");
|
||||
if (!sleSrc)
|
||||
WAccountRoot acctSrc(src, psb, j);
|
||||
XRPL_ASSERT(acctSrc, "xrpl::transferHelper : non-null source account");
|
||||
if (!acctSrc)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
{
|
||||
auto const ownerCount = sleSrc->getFieldU32(sfOwnerCount);
|
||||
auto const ownerCount = acctSrc->getFieldU32(sfOwnerCount);
|
||||
auto const reserve = psb.fees().accountReserve(ownerCount);
|
||||
|
||||
auto const availableBalance = [&]() -> STAmount {
|
||||
STAmount curBal = (*sleSrc)[sfBalance];
|
||||
STAmount curBal = acctSrc->at(sfBalance);
|
||||
// Checking that account == src and postFeeBalance == curBal is
|
||||
// not strictly necessary, but helps protect against future
|
||||
// changes
|
||||
@@ -456,8 +454,8 @@ transferHelper(
|
||||
}
|
||||
}
|
||||
|
||||
auto sleDst = psb.peek(dstK);
|
||||
if (!sleDst)
|
||||
WAccountRoot acctDst(dst, psb, j);
|
||||
if (!acctDst)
|
||||
{
|
||||
if (canCreate == CanCreateDstPolicy::No)
|
||||
{
|
||||
@@ -471,17 +469,17 @@ transferHelper(
|
||||
}
|
||||
|
||||
// Create the account.
|
||||
sleDst = std::make_shared<SLE>(dstK);
|
||||
sleDst->setAccountID(sfAccount, dst);
|
||||
sleDst->setFieldU32(sfSequence, psb.seq());
|
||||
acctDst.newSLE();
|
||||
acctDst->setAccountID(sfAccount, dst);
|
||||
acctDst->setFieldU32(sfSequence, psb.seq());
|
||||
|
||||
psb.insert(sleDst);
|
||||
acctDst.insert();
|
||||
}
|
||||
|
||||
(*sleSrc)[sfBalance] = (*sleSrc)[sfBalance] - amt;
|
||||
(*sleDst)[sfBalance] = (*sleDst)[sfBalance] + amt;
|
||||
psb.update(sleSrc);
|
||||
psb.update(sleDst);
|
||||
acctSrc->at(sfBalance) = acctSrc->at(sfBalance) - amt;
|
||||
acctDst->at(sfBalance) = acctDst->at(sfBalance) + amt;
|
||||
acctSrc.update();
|
||||
acctDst.update();
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
@@ -717,7 +715,7 @@ finalizeClaimHelper(
|
||||
auto const cidOwner = (*sleClaimID)[sfAccount];
|
||||
{
|
||||
// Remove the claim id
|
||||
auto const sleOwner = outerSb.peek(keylet::account(cidOwner));
|
||||
WAccountRoot wrappedOwner(cidOwner, outerSb, j);
|
||||
auto const page = (*sleClaimID)[sfOwnerNode];
|
||||
if (!outerSb.dirRemove(keylet::ownerDir(cidOwner), page, sleClaimID->key(), true))
|
||||
{
|
||||
@@ -729,7 +727,7 @@ finalizeClaimHelper(
|
||||
// Remove the claim id from the ledger
|
||||
outerSb.erase(sleClaimID);
|
||||
|
||||
adjustOwnerCount(outerSb, sleOwner, -1, j);
|
||||
wrappedOwner.adjustOwnerCount(-1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -752,9 +750,9 @@ getSignersListAndQuorum(ReadView const& view, SLE const& sleBridge, beast::Journ
|
||||
std::uint32_t q = std::numeric_limits<std::uint32_t>::max();
|
||||
|
||||
AccountID const thisDoor = sleBridge[sfAccount];
|
||||
auto const sleDoor = [&] { return view.read(keylet::account(thisDoor)); }();
|
||||
AccountRoot const acctDoor(thisDoor, view);
|
||||
|
||||
if (!sleDoor)
|
||||
if (!acctDoor)
|
||||
{
|
||||
return {r, q, tecINTERNAL};
|
||||
}
|
||||
@@ -1133,14 +1131,13 @@ applyCreateAccountAttestations(
|
||||
return tecDIR_FULL; // LCOV_EXCL_LINE
|
||||
(*createdSleClaimID)[sfOwnerNode] = *page;
|
||||
|
||||
auto const sleDoor = psb.peek(doorK);
|
||||
if (!sleDoor)
|
||||
WAccountRoot wrappedDoor(doorAccount, psb, j);
|
||||
if (!wrappedDoor)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
// Reserve was already checked
|
||||
adjustOwnerCount(psb, sleDoor, 1, j);
|
||||
wrappedDoor.adjustOwnerCount(1);
|
||||
psb.insert(createdSleClaimID);
|
||||
psb.update(sleDoor);
|
||||
}
|
||||
|
||||
psb.apply(rawView);
|
||||
@@ -1418,25 +1415,25 @@ XChainCreateBridge::preclaim(PreclaimContext const& ctx)
|
||||
|
||||
if (!isXRP(bridgeSpec.issue(chainType)))
|
||||
{
|
||||
auto const sleIssuer = ctx.view.read(keylet::account(bridgeSpec.issue(chainType).account));
|
||||
AccountRoot const acctIssuer(bridgeSpec.issue(chainType).account, ctx.view);
|
||||
|
||||
if (!sleIssuer)
|
||||
if (!acctIssuer)
|
||||
return tecNO_ISSUER;
|
||||
|
||||
// Allowing clawing back funds would break the bridge's invariant that
|
||||
// wrapped funds are always backed by locked funds
|
||||
if (sleIssuer->isFlag(lsfAllowTrustLineClawback))
|
||||
if (acctIssuer->isFlag(lsfAllowTrustLineClawback))
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
|
||||
{
|
||||
// Check reserve
|
||||
auto const sleAcc = ctx.view.read(keylet::account(account));
|
||||
if (!sleAcc)
|
||||
AccountRoot const acctSrc(account, ctx.view);
|
||||
if (!acctSrc)
|
||||
return terNO_ACCOUNT;
|
||||
|
||||
auto const balance = (*sleAcc)[sfBalance];
|
||||
auto const reserve = ctx.view.fees().accountReserve((*sleAcc)[sfOwnerCount] + 1);
|
||||
auto const balance = acctSrc->at(sfBalance);
|
||||
auto const reserve = ctx.view.fees().accountReserve(acctSrc->at(sfOwnerCount) + 1);
|
||||
|
||||
if (balance < reserve)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
@@ -1453,8 +1450,8 @@ XChainCreateBridge::doApply()
|
||||
auto const reward = ctx_.tx[sfSignatureReward];
|
||||
auto const minAccountCreate = ctx_.tx[~sfMinAccountCreateAmount];
|
||||
|
||||
auto const sleAcct = ctx_.view().peek(keylet::account(account));
|
||||
if (!sleAcct)
|
||||
WAccountRoot wrappedAcct(account, ctx_.view(), j_);
|
||||
if (!wrappedAcct)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
STXChainBridge::ChainType const chainType =
|
||||
@@ -1481,10 +1478,10 @@ XChainCreateBridge::doApply()
|
||||
(*sleBridge)[sfOwnerNode] = *page;
|
||||
}
|
||||
|
||||
adjustOwnerCount(ctx_.view(), sleAcct, 1, ctx_.journal);
|
||||
wrappedAcct.adjustOwnerCount(1);
|
||||
|
||||
ctx_.view().insert(sleBridge);
|
||||
ctx_.view().update(sleAcct);
|
||||
wrappedAcct.update();
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
@@ -1564,8 +1561,7 @@ BridgeModify::doApply()
|
||||
auto const minAccountCreate = ctx_.tx[~sfMinAccountCreateAmount];
|
||||
bool const clearAccountCreate = ctx_.tx.isFlag(tfClearAccountCreateAmount);
|
||||
|
||||
auto const sleAcct = ctx_.view().peek(keylet::account(account));
|
||||
if (!sleAcct)
|
||||
if (!AccountRoot(account, ctx_.view(), j_))
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
STXChainBridge::ChainType const chainType =
|
||||
@@ -1622,7 +1618,7 @@ XChainClaim::preclaim(PreclaimContext const& ctx)
|
||||
return tecNO_ENTRY;
|
||||
}
|
||||
|
||||
if (!ctx.view.read(keylet::account(ctx.tx[sfDestination])))
|
||||
if (!AccountRoot(ctx.tx[sfDestination], ctx.view, ctx.j))
|
||||
{
|
||||
return tecNO_DST;
|
||||
}
|
||||
@@ -1705,7 +1701,6 @@ XChainClaim::doApply()
|
||||
{
|
||||
PaymentSandbox psb(&ctx_.view());
|
||||
|
||||
AccountID const account = ctx_.tx[sfAccount];
|
||||
auto const dst = ctx_.tx[sfDestination];
|
||||
STXChainBridge const bridgeSpec = ctx_.tx[sfXChainBridge];
|
||||
STAmount const& thisChainAmount = ctx_.tx[sfAmount];
|
||||
@@ -1727,11 +1722,10 @@ XChainClaim::doApply()
|
||||
// `finalizeClaimHelper`. Since `finalizeClaimHelper` can create child
|
||||
// views, it's important that the sle's lifetime doesn't overlap.
|
||||
|
||||
auto const sleAcct = psb.peek(keylet::account(account));
|
||||
auto const sleBridge = peekBridge(psb, bridgeSpec);
|
||||
auto const sleClaimID = psb.peek(claimIDKeylet);
|
||||
|
||||
if (!(sleBridge && sleClaimID && sleAcct))
|
||||
if (!(sleBridge && sleClaimID && account_))
|
||||
return Unexpected(tecINTERNAL);
|
||||
|
||||
AccountID const thisDoor = (*sleBridge)[sfAccount];
|
||||
@@ -1799,7 +1793,7 @@ XChainClaim::doApply()
|
||||
bridgeSpec,
|
||||
dst,
|
||||
dstTag,
|
||||
/*claimOwner*/ account,
|
||||
/*claimOwner*/ accountID_,
|
||||
sendingAmount,
|
||||
rewardPoolSrc,
|
||||
signatureReward,
|
||||
@@ -1904,12 +1898,10 @@ XChainCommit::doApply()
|
||||
{
|
||||
PaymentSandbox psb(&ctx_.view());
|
||||
|
||||
auto const account = ctx_.tx[sfAccount];
|
||||
auto const amount = ctx_.tx[sfAmount];
|
||||
auto const bridgeSpec = ctx_.tx[sfXChainBridge];
|
||||
|
||||
auto const sleAccount = psb.read(keylet::account(account));
|
||||
if (!sleAccount)
|
||||
if (!account_)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const sleBridge = readBridge(psb, bridgeSpec);
|
||||
@@ -1922,11 +1914,11 @@ XChainCommit::doApply()
|
||||
TransferHelperSubmittingAccountInfo submittingAccountInfo{
|
||||
.account = accountID_,
|
||||
.preFeeBalance = preFeeBalance_,
|
||||
.postFeeBalance = (*sleAccount)[sfBalance]};
|
||||
.postFeeBalance = (*account_)[sfBalance]};
|
||||
|
||||
auto const thTer = transferHelper(
|
||||
psb,
|
||||
account,
|
||||
accountID_,
|
||||
dst,
|
||||
/*dstTag*/ std::nullopt,
|
||||
/*claimOwner*/ std::nullopt,
|
||||
@@ -1979,12 +1971,12 @@ XChainCreateClaimID::preclaim(PreclaimContext const& ctx)
|
||||
|
||||
{
|
||||
// Check reserve
|
||||
auto const sleAcc = ctx.view.read(keylet::account(account));
|
||||
if (!sleAcc)
|
||||
AccountRoot const acctSrc(account, ctx.view);
|
||||
if (!acctSrc)
|
||||
return terNO_ACCOUNT;
|
||||
|
||||
auto const balance = (*sleAcc)[sfBalance];
|
||||
auto const reserve = ctx.view.fees().accountReserve((*sleAcc)[sfOwnerCount] + 1);
|
||||
auto const balance = acctSrc->at(sfBalance);
|
||||
auto const reserve = ctx.view.fees().accountReserve(acctSrc->at(sfOwnerCount) + 1);
|
||||
|
||||
if (balance < reserve)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
@@ -2001,8 +1993,8 @@ XChainCreateClaimID::doApply()
|
||||
auto const reward = ctx_.tx[sfSignatureReward];
|
||||
auto const otherChainSrc = ctx_.tx[sfOtherChainSource];
|
||||
|
||||
auto const sleAcct = ctx_.view().peek(keylet::account(account));
|
||||
if (!sleAcct)
|
||||
WAccountRoot wrappedAcct(account, ctx_.view(), j_);
|
||||
if (!wrappedAcct)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const sleBridge = peekBridge(ctx_.view(), bridgeSpec);
|
||||
@@ -2043,11 +2035,10 @@ XChainCreateClaimID::doApply()
|
||||
(*sleClaimID)[sfOwnerNode] = *page;
|
||||
}
|
||||
|
||||
adjustOwnerCount(ctx_.view(), sleAcct, 1, ctx_.journal);
|
||||
wrappedAcct.adjustOwnerCount(1);
|
||||
|
||||
ctx_.view().insert(sleClaimID);
|
||||
ctx_.view().update(sleBridge);
|
||||
ctx_.view().update(sleAcct);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
@@ -2180,13 +2171,11 @@ XChainCreateAccountCommit::doApply()
|
||||
{
|
||||
PaymentSandbox psb(&ctx_.view());
|
||||
|
||||
AccountID const account = ctx_.tx[sfAccount];
|
||||
STAmount const amount = ctx_.tx[sfAmount];
|
||||
STAmount const reward = ctx_.tx[sfSignatureReward];
|
||||
STXChainBridge const bridge = ctx_.tx[sfXChainBridge];
|
||||
|
||||
auto const sle = psb.peek(keylet::account(account));
|
||||
if (!sle)
|
||||
if (!account_)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const sleBridge = peekBridge(psb, bridge);
|
||||
@@ -2199,11 +2188,11 @@ XChainCreateAccountCommit::doApply()
|
||||
TransferHelperSubmittingAccountInfo submittingAccountInfo{
|
||||
.account = accountID_,
|
||||
.preFeeBalance = preFeeBalance_,
|
||||
.postFeeBalance = (*sle)[sfBalance]};
|
||||
.postFeeBalance = (*account_)[sfBalance]};
|
||||
STAmount const toTransfer = amount + reward;
|
||||
auto const thTer = transferHelper(
|
||||
psb,
|
||||
account,
|
||||
accountID_,
|
||||
dst,
|
||||
/*dstTag*/ std::nullopt,
|
||||
/*claimOwner*/ std::nullopt,
|
||||
|
||||
@@ -66,8 +66,6 @@ CheckCancel::doApply()
|
||||
|
||||
AccountID const srcId{sleCheck->getAccountID(sfAccount)};
|
||||
AccountID const dstId{sleCheck->getAccountID(sfDestination)};
|
||||
auto viewJ = ctx_.registry.get().getJournal("View");
|
||||
|
||||
// If the check is not written to self (and it shouldn't be), remove the
|
||||
// check from the destination account root.
|
||||
if (srcId != dstId)
|
||||
@@ -93,8 +91,8 @@ CheckCancel::doApply()
|
||||
}
|
||||
|
||||
// If we succeeded, update the check owner's reserve.
|
||||
auto const sleSrc = view().peek(keylet::account(srcId));
|
||||
adjustOwnerCount(view(), sleSrc, -1, viewJ);
|
||||
WAccountRoot wrappedSrc(srcId, view(), j_);
|
||||
wrappedSrc.adjustOwnerCount(-1);
|
||||
|
||||
// Remove check from ledger.
|
||||
view().erase(sleCheck);
|
||||
|
||||
@@ -106,16 +106,16 @@ CheckCash::preclaim(PreclaimContext const& ctx)
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
{
|
||||
auto const sleSrc = ctx.view.read(keylet::account(srcId));
|
||||
auto const sleDst = ctx.view.read(keylet::account(dstId));
|
||||
if (!sleSrc || !sleDst)
|
||||
AccountRoot const acctSrc(srcId, ctx.view);
|
||||
AccountRoot const acctDst(dstId, ctx.view);
|
||||
if (!acctSrc || !acctDst)
|
||||
{
|
||||
// If the check exists this should never occur.
|
||||
JLOG(ctx.j.warn()) << "Malformed transaction: source or destination not in ledger";
|
||||
return tecNO_ENTRY;
|
||||
}
|
||||
|
||||
if (sleDst->isFlag(lsfRequireDestTag) && !sleCheck->isFieldPresent(sfDestinationTag))
|
||||
if (acctDst->isFlag(lsfRequireDestTag) && !sleCheck->isFieldPresent(sfDestinationTag))
|
||||
{
|
||||
// The tag is basically account-specific information we don't
|
||||
// understand, but we can require someone to fill it in.
|
||||
@@ -190,8 +190,8 @@ CheckCash::preclaim(PreclaimContext const& ctx)
|
||||
auto const sleTrustLine =
|
||||
ctx.view.read(keylet::line(dstId, issuerId, currency));
|
||||
|
||||
auto const sleIssuer = ctx.view.read(keylet::account(issuerId));
|
||||
if (!sleIssuer)
|
||||
auto const issuer = AccountRoot{issuerId, ctx.view};
|
||||
if (!issuer)
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Can't receive IOUs from "
|
||||
"non-existent issuer: "
|
||||
@@ -199,7 +199,7 @@ CheckCash::preclaim(PreclaimContext const& ctx)
|
||||
return tecNO_ISSUER;
|
||||
}
|
||||
|
||||
if (sleIssuer->isFlag(lsfRequireAuth))
|
||||
if (issuer->isFlag(lsfRequireAuth))
|
||||
{
|
||||
if (!sleTrustLine)
|
||||
{
|
||||
@@ -233,7 +233,7 @@ CheckCash::preclaim(PreclaimContext const& ctx)
|
||||
|
||||
// However, the trustline from destination to issuer may
|
||||
// not be frozen.
|
||||
if (isFrozen(ctx.view, dstId, currency, issuerId))
|
||||
if (IOUIssuance(ctx.view, issuerId, currency).isFrozen(dstId))
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Cashing a check to a frozen trustline.";
|
||||
return tecFROZEN;
|
||||
@@ -242,8 +242,8 @@ CheckCash::preclaim(PreclaimContext const& ctx)
|
||||
return tesSUCCESS;
|
||||
},
|
||||
[&](MPTIssue const& issue) -> TER {
|
||||
auto const sleIssuer = ctx.view.read(keylet::account(issuerId));
|
||||
if (!sleIssuer)
|
||||
auto const issuer = AccountRoot{issuerId, ctx.view};
|
||||
if (!issuer)
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Can't receive MPTs from "
|
||||
"non-existent issuer: "
|
||||
@@ -251,14 +251,15 @@ CheckCash::preclaim(PreclaimContext const& ctx)
|
||||
return tecNO_ISSUER;
|
||||
}
|
||||
|
||||
if (auto const err = requireAuth(ctx.view, issue, dstId, AuthType::WeakAuth);
|
||||
auto const issuance = MPTokenIssuance(ctx.view, issue);
|
||||
if (auto const err = issuance.requireAuth(dstId, AuthType::WeakAuth);
|
||||
!isTesSuccess(err))
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Cashing a check to a MPT requiring auth.";
|
||||
return err;
|
||||
}
|
||||
|
||||
if (isFrozen(ctx.view, dstId, issue))
|
||||
if (issuance.isFrozen(dstId))
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Cashing a check to a frozen MPT.";
|
||||
return tecLOCKED;
|
||||
@@ -330,7 +331,8 @@ CheckCash::doApply()
|
||||
// from src's directory, we allow them to send that additional
|
||||
// incremental reserve amount in the transfer. Hence the -1
|
||||
// argument.
|
||||
STAmount const srcLiquid{xrpLiquid(psb, srcId, -1, viewJ)};
|
||||
WAccountRoot const wrappedSrc(srcId, psb, j_);
|
||||
STAmount const srcLiquid{wrappedSrc.xrpLiquid(-1)};
|
||||
|
||||
// Now, how much do they need in order to be successful?
|
||||
STAmount const xrpDeliver{
|
||||
@@ -382,19 +384,19 @@ 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 checkReserve = [&]() -> std::optional<WAccountRoot> {
|
||||
WAccountRoot dst(accountID_, psb, j_);
|
||||
|
||||
// Can the account cover the trust line's or MPT reserve?
|
||||
if (std::uint32_t const ownerCount = {sleDst->at(sfOwnerCount)};
|
||||
if (std::uint32_t const ownerCount = {dst->at(sfOwnerCount)};
|
||||
preFeeBalance_ < psb.fees().accountReserve(ownerCount + 1))
|
||||
{
|
||||
JLOG(j_.trace()) << "Trust line does not exist. "
|
||||
"Insufficient reserve to create line.";
|
||||
|
||||
return nullptr;
|
||||
return std::nullopt;
|
||||
}
|
||||
return sleDst;
|
||||
return dst;
|
||||
};
|
||||
|
||||
std::optional<Keylet> trustLineKey;
|
||||
@@ -421,36 +423,37 @@ CheckCash::doApply()
|
||||
// a. this (destination) account and
|
||||
// b. issuing account (not sending account).
|
||||
|
||||
auto const sleDst = checkReserve();
|
||||
if (sleDst == nullptr)
|
||||
auto dstOpt = checkReserve();
|
||||
if (!dstOpt)
|
||||
return tecNO_LINE_INSUF_RESERVE;
|
||||
auto& dst = *dstOpt;
|
||||
|
||||
Currency const& currency = issue.currency;
|
||||
STAmount initialBalance(flowDeliver.asset());
|
||||
initialBalance.get<Issue>().account = noAccount();
|
||||
|
||||
if (TER const ter = trustCreate(
|
||||
psb, // payment sandbox
|
||||
destLow, // is dest low?
|
||||
deliverIssuer, // source
|
||||
accountID_, // destination
|
||||
trustLineKey->key, // ledger index
|
||||
sleDst, // Account to add to
|
||||
false, // authorize account
|
||||
!sleDst->isFlag(lsfDefaultRipple), //
|
||||
false, // freeze trust line
|
||||
false, // deep freeze trust line
|
||||
initialBalance, // zero initial balance
|
||||
Issue(currency, accountID_), // limit of zero
|
||||
0, // quality in
|
||||
0, // quality out
|
||||
viewJ); // journal
|
||||
if (TER const ter = WIOUIssuance(psb, deliverIssuer, currency, viewJ)
|
||||
.trustCreate(
|
||||
destLow, // is dest low?
|
||||
deliverIssuer, // source
|
||||
accountID_, // destination
|
||||
trustLineKey->key, // ledger index
|
||||
dst, // Account to add to
|
||||
false, // authorize account
|
||||
!dst->isFlag(lsfDefaultRipple), //
|
||||
false, // freeze trust line
|
||||
false, // deep freeze trust line
|
||||
initialBalance, // zero initial balance
|
||||
Issue(currency, accountID_), // limit of zero
|
||||
0, // quality in
|
||||
0, // quality out
|
||||
viewJ); // journal
|
||||
!isTesSuccess(ter))
|
||||
{
|
||||
return ter;
|
||||
}
|
||||
|
||||
psb.update(sleDst);
|
||||
dst.update();
|
||||
|
||||
// Note that we _don't_ need to be careful about
|
||||
// destroying the trust line if the check cashing
|
||||
@@ -479,18 +482,19 @@ 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_);
|
||||
if (!psb.exists(mptokenKey))
|
||||
{
|
||||
auto sleDst = checkReserve();
|
||||
if (sleDst == nullptr)
|
||||
auto dst = checkReserve();
|
||||
if (!dst.has_value())
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
|
||||
if (auto const err = checkCreateMPT(psb, mptID, accountID_, j_);
|
||||
if (auto const err =
|
||||
WMPTokenIssuance(psb, mptID, j_).checkCreateMPT(accountID_, j_);
|
||||
!isTesSuccess(err))
|
||||
{
|
||||
return err;
|
||||
@@ -576,7 +580,8 @@ CheckCash::doApply()
|
||||
}
|
||||
|
||||
// If we succeeded, update the check owner's reserve.
|
||||
adjustOwnerCount(psb, psb.peek(keylet::account(srcId)), -1, viewJ);
|
||||
WAccountRoot wrappedSrc(srcId, psb, j_);
|
||||
wrappedSrc.adjustOwnerCount(-1);
|
||||
|
||||
// Remove check from ledger.
|
||||
psb.erase(sleCheck);
|
||||
|
||||
@@ -77,27 +77,27 @@ CheckCreate::preflight(PreflightContext const& ctx)
|
||||
TER
|
||||
CheckCreate::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
AccountID const dstId{ctx.tx[sfDestination]};
|
||||
AccountID const srcId{ctx.tx[sfAccount]};
|
||||
auto const sleDst = ctx.view.read(keylet::account(dstId));
|
||||
if (!sleDst)
|
||||
AccountID const dstId{ctx.tx[sfDestination]};
|
||||
AccountRoot const acctDst(dstId, ctx.view);
|
||||
if (!acctDst)
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Destination account does not exist.";
|
||||
return tecNO_DST;
|
||||
}
|
||||
|
||||
// Check if the destination has disallowed incoming checks
|
||||
if (sleDst->isFlag(lsfDisallowIncomingCheck))
|
||||
if (acctDst->isFlag(lsfDisallowIncomingCheck))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// Pseudo-accounts cannot cash checks. Note, this is not amendment-gated
|
||||
// because all writes to pseudo-account discriminator fields **are**
|
||||
// amendment gated, hence the behaviour of this check will always match the
|
||||
// currently active amendments.
|
||||
if (isPseudoAccount(sleDst))
|
||||
if (acctDst.isPseudoAccount())
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
if (sleDst->isFlag(lsfRequireDestTag) && !ctx.tx.isFieldPresent(sfDestinationTag))
|
||||
if (acctDst->isFlag(lsfRequireDestTag) && !ctx.tx.isFieldPresent(sfDestinationTag))
|
||||
{
|
||||
// The tag is basically account-specific information we don't
|
||||
// understand, but we can require someone to fill it in.
|
||||
@@ -152,9 +152,10 @@ CheckCreate::preclaim(PreclaimContext const& ctx)
|
||||
return std::nullopt;
|
||||
},
|
||||
[&](MPTIssue const& issue) -> std::optional<TER> {
|
||||
if (srcId != issuerId && isFrozen(ctx.view, srcId, issue))
|
||||
auto const issuance = MPTokenIssuance(ctx.view, issue);
|
||||
if (srcId != issuerId && issuance.isFrozen(srcId))
|
||||
return tecLOCKED;
|
||||
if (dstId != issuerId && isFrozen(ctx.view, dstId, issue))
|
||||
if (dstId != issuerId && issuance.isFrozen(dstId))
|
||||
return tecLOCKED;
|
||||
|
||||
return std::nullopt;
|
||||
@@ -175,15 +176,15 @@ CheckCreate::preclaim(PreclaimContext const& ctx)
|
||||
TER
|
||||
CheckCreate::doApply()
|
||||
{
|
||||
auto const sle = view().peek(keylet::account(accountID_));
|
||||
if (!sle)
|
||||
if (!account_)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
// A check counts against the reserve of the issuing account, but we
|
||||
// check the starting balance because we want to allow dipping into the
|
||||
// reserve to pay fees.
|
||||
{
|
||||
STAmount const reserve{view().fees().accountReserve(sle->getFieldU32(sfOwnerCount) + 1)};
|
||||
STAmount const reserve{
|
||||
view().fees().accountReserve(account_->getFieldU32(sfOwnerCount) + 1)};
|
||||
|
||||
if (preFeeBalance_ < reserve)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
@@ -211,7 +212,6 @@ CheckCreate::doApply()
|
||||
|
||||
view().insert(sleCheck);
|
||||
|
||||
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_)
|
||||
@@ -241,7 +241,7 @@ CheckCreate::doApply()
|
||||
sleCheck->setFieldU64(sfOwnerNode, *page);
|
||||
}
|
||||
// If we succeeded, the new entry counts against the creator's reserve.
|
||||
adjustOwnerCount(view(), sle, 1, viewJ);
|
||||
account_.adjustOwnerCount(1);
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
|
||||
@@ -89,15 +89,15 @@ 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 sleIssuer = view().peek(keylet::account(issuer));
|
||||
WAccountRoot wrappedSubject(accountID_, view(), j_);
|
||||
WAccountRoot wrappedIssuer(issuer, view(), j_);
|
||||
|
||||
if (!sleSubject || !sleIssuer)
|
||||
if (!wrappedSubject || !wrappedIssuer)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
{
|
||||
STAmount const reserve{
|
||||
view().fees().accountReserve(sleSubject->getFieldU32(sfOwnerCount) + 1)};
|
||||
view().fees().accountReserve(wrappedSubject->getFieldU32(sfOwnerCount) + 1)};
|
||||
if (preFeeBalance_ < reserve)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
}
|
||||
@@ -119,8 +119,8 @@ CredentialAccept::doApply()
|
||||
sleCred->setFieldU32(sfFlags, lsfAccepted);
|
||||
view().update(sleCred);
|
||||
|
||||
adjustOwnerCount(view(), sleIssuer, -1, j_);
|
||||
adjustOwnerCount(view(), sleSubject, 1, j_);
|
||||
wrappedIssuer.adjustOwnerCount(-1);
|
||||
wrappedSubject.adjustOwnerCount(1);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
@@ -126,13 +126,13 @@ CredentialCreate::doApply()
|
||||
sleCred->setFieldU32(sfExpiration, *optExp);
|
||||
}
|
||||
|
||||
auto const sleIssuer = view().peek(keylet::account(accountID_));
|
||||
if (!sleIssuer)
|
||||
WAccountRoot wrappedIssuer(accountID_, view(), j_);
|
||||
if (!wrappedIssuer)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
{
|
||||
STAmount const reserve{
|
||||
view().fees().accountReserve(sleIssuer->getFieldU32(sfOwnerCount) + 1)};
|
||||
view().fees().accountReserve(wrappedIssuer->getFieldU32(sfOwnerCount) + 1)};
|
||||
if (preFeeBalance_ < reserve)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
}
|
||||
@@ -153,7 +153,7 @@ CredentialCreate::doApply()
|
||||
return tecDIR_FULL;
|
||||
sleCred->setFieldU64(sfIssuerNode, *page);
|
||||
|
||||
adjustOwnerCount(view(), sleIssuer, 1, j_);
|
||||
wrappedIssuer.adjustOwnerCount(1);
|
||||
}
|
||||
|
||||
if (subject == accountID_)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include <xrpl/tx/transactors/delegate/DelegateSet.h>
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/core/ServiceRegistry.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
|
||||
@@ -68,8 +67,8 @@ DelegateSet::preclaim(PreclaimContext const& ctx)
|
||||
TER
|
||||
DelegateSet::doApply()
|
||||
{
|
||||
auto const sleOwner = ctx_.view().peek(keylet::account(accountID_));
|
||||
if (!sleOwner)
|
||||
WAccountRoot wrappedOwner(accountID_, ctx_.view(), j_);
|
||||
if (!wrappedOwner)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const& authAccount = ctx_.tx[sfAuthorize];
|
||||
@@ -95,7 +94,7 @@ DelegateSet::doApply()
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
STAmount const reserve{
|
||||
ctx_.view().fees().accountReserve(sleOwner->getFieldU32(sfOwnerCount) + 1)};
|
||||
ctx_.view().fees().accountReserve(wrappedOwner->getFieldU32(sfOwnerCount) + 1)};
|
||||
|
||||
if (preFeeBalance_ < reserve)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
@@ -106,7 +105,7 @@ DelegateSet::doApply()
|
||||
|
||||
sle->setFieldArray(sfPermissions, permissions);
|
||||
|
||||
// Add to delegating account's owner directory
|
||||
// Add to delegating account's owner directory.
|
||||
auto const page = ctx_.view().dirInsert(
|
||||
keylet::ownerDir(accountID_), delegateKey, describeOwnerDir(accountID_));
|
||||
|
||||
@@ -126,7 +125,7 @@ DelegateSet::doApply()
|
||||
(*sle)[sfDestinationNode] = *destPage;
|
||||
|
||||
ctx_.view().insert(sle);
|
||||
adjustOwnerCount(ctx_.view(), sleOwner, 1, ctx_.journal);
|
||||
wrappedOwner.adjustOwnerCount(1);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
@@ -162,11 +161,11 @@ DelegateSet::deleteDelegate(ApplyView& view, std::shared_ptr<SLE> const& sle, be
|
||||
}
|
||||
|
||||
// Only the delegating account's owner count was incremented on creation
|
||||
auto const sleOwner = view.peek(keylet::account(delegator));
|
||||
if (!sleOwner)
|
||||
WAccountRoot wrappedOwner(delegator, view, j);
|
||||
if (!wrappedOwner)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
adjustOwnerCount(view, sleOwner, -1, j);
|
||||
wrappedOwner.adjustOwnerCount(-1);
|
||||
|
||||
view.erase(sle);
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/Sandbox.h>
|
||||
#include <xrpl/ledger/helpers/AMMHelpers.h>
|
||||
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
||||
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/AMMCore.h>
|
||||
@@ -120,7 +121,7 @@ AMMBid::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
for (auto const& account : ctx.tx.getFieldArray(sfAuthAccounts))
|
||||
{
|
||||
if (!ctx.view.read(keylet::account(account[sfAccount])))
|
||||
if (AccountRoot const acct(account[sfAccount], ctx.view); !acct)
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "AMM Bid: Invalid Account.";
|
||||
return terNO_ACCOUNT;
|
||||
@@ -177,14 +178,14 @@ AMMBid::preclaim(PreclaimContext const& ctx)
|
||||
}
|
||||
|
||||
static std::pair<TER, bool>
|
||||
applyBid(ApplyContext& ctx, Sandbox& sb, AccountID const& account, beast::Journal j)
|
||||
applyBid(ApplyContext& ctx, Sandbox& sb, AccountID const& accountId, beast::Journal j)
|
||||
{
|
||||
using namespace std::chrono;
|
||||
auto const ammSle = sb.peek(keylet::amm(ctx.tx[sfAsset], ctx.tx[sfAsset2]));
|
||||
if (!ammSle)
|
||||
return {tecINTERNAL, false};
|
||||
STAmount const lptAMMBalance = (*ammSle)[sfLPTokenBalance];
|
||||
auto const lpTokens = ammLPHolds(sb, *ammSle, account, ctx.journal);
|
||||
auto const lpTokens = ammLPHolds(sb, *ammSle, accountId, ctx.journal);
|
||||
auto const& rules = ctx.view().rules();
|
||||
if (!rules.enabled(fixInnerObjTemplate))
|
||||
{
|
||||
@@ -216,11 +217,11 @@ applyBid(ApplyContext& ctx, Sandbox& sb, AccountID const& account, beast::Journa
|
||||
auto validOwner = [&](AccountID const& account) {
|
||||
// Valid range is 0-19 but the tailing slot pays MinSlotPrice
|
||||
// and doesn't refund so the check is < instead of <= to optimize.
|
||||
return timeSlot && *timeSlot < kTailingSlot && sb.read(keylet::account(account));
|
||||
return timeSlot && *timeSlot < kTailingSlot && AccountRoot(account, sb, j);
|
||||
};
|
||||
|
||||
auto updateSlot = [&](std::uint32_t fee, Number const& minPrice, Number const& burn) -> TER {
|
||||
auctionSlot.setAccountID(sfAccount, account);
|
||||
auctionSlot.setAccountID(sfAccount, accountId);
|
||||
auctionSlot.setFieldU32(sfExpiration, current + kTotalTimeSlotSecs);
|
||||
if (fee != 0)
|
||||
{
|
||||
@@ -251,7 +252,8 @@ applyBid(ApplyContext& ctx, Sandbox& sb, AccountID const& account, beast::Journa
|
||||
return tecINTERNAL;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
auto res = redeemIOU(sb, account, saBurn, lpTokens.get<Issue>(), ctx.journal);
|
||||
auto res = WIOUIssuance(sb, lpTokens.get<Issue>(), ctx.journal)
|
||||
.redeem(accountId, saBurn, ctx.journal);
|
||||
if (!isTesSuccess(res))
|
||||
{
|
||||
JLOG(ctx.journal.debug()) << "AMM Bid: failed to redeem.";
|
||||
@@ -350,7 +352,11 @@ applyBid(ApplyContext& ctx, Sandbox& sb, AccountID const& account, beast::Journa
|
||||
return {tecINTERNAL, false};
|
||||
}
|
||||
res = accountSend(
|
||||
sb, account, auctionSlot[sfAccount], toSTAmount(lpTokens.asset(), refund), ctx.journal);
|
||||
sb,
|
||||
accountId,
|
||||
auctionSlot[sfAccount],
|
||||
toSTAmount(lpTokens.asset(), refund),
|
||||
ctx.journal);
|
||||
if (!isTesSuccess(res))
|
||||
{
|
||||
JLOG(ctx.journal.debug()) << "AMM Bid: failed to refund.";
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user